From a2fca3ace6f746b6c5d7fa4e83ccfbf79fe988ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Jan 2026 15:36:14 +0000 Subject: [PATCH 1/8] Add installation script for one-shot CLI deployment Add install.sh that allows users to install the Honeybadger CLI with a single curl command. The script: - Detects OS (Linux/macOS) and architecture (x86_64/arm64) - Downloads the appropriate release from GitHub (latest by default) - Supports specifying a specific version with --version flag - Installs binary to /usr/local/bin - Configures systemd service for the metrics agent - Accepts API key via --api-key flag or prompts interactively - Supports --interval flag for custom reporting intervals - Includes --no-service flag for binary-only installation - Applies security hardening to the systemd service unit --- install.sh | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..ac1e089 --- /dev/null +++ b/install.sh @@ -0,0 +1,443 @@ +#!/bin/bash +# +# Honeybadger CLI Installation Script +# +# This script installs the Honeybadger CLI (hb) and optionally configures it +# to run as a systemd service for continuous metrics reporting. +# +# Usage: +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --api-key YOUR_API_KEY +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 +# +# Options: +# --api-key KEY Honeybadger API key for the agent +# --version VERSION Specific version to install (default: latest) +# --interval SECONDS Metrics reporting interval (default: 60) +# --no-service Install binary only, skip systemd service setup +# --help Show this help message +# + +set -e + +# Configuration +GITHUB_REPO="honeybadger-io/cli" +BINARY_NAME="hb" +INSTALL_DIR="/usr/local/bin" +SERVICE_NAME="honeybadger-agent" +DEFAULT_INTERVAL=60 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script options +API_KEY="" +VERSION="latest" +INTERVAL=$DEFAULT_INTERVAL +INSTALL_SERVICE=true + +####################################### +# Print colored output +####################################### +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +success() { + echo -e "${GREEN}[OK]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +####################################### +# Show usage information +####################################### +usage() { + cat << EOF +Honeybadger CLI Installation Script + +Usage: + $0 [options] + +Options: + --api-key KEY Honeybadger API key for the agent + --version VERSION Specific version to install (default: latest) + --interval SECONDS Metrics reporting interval in seconds (default: 60) + --no-service Install binary only, skip systemd service setup + --help Show this help message + +Examples: + # Install latest version and configure as systemd service + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash + + # Install with API key provided + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --api-key YOUR_API_KEY + + # Install specific version + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 + + # Install binary only (no systemd service) + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --no-service + +EOF + exit 0 +} + +####################################### +# Parse command line arguments +####################################### +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + --api-key) + API_KEY="$2" + shift 2 + ;; + --version) + VERSION="$2" + shift 2 + ;; + --interval) + INTERVAL="$2" + shift 2 + ;; + --no-service) + INSTALL_SERVICE=false + shift + ;; + --help|-h) + usage + ;; + *) + error "Unknown option: $1" + usage + ;; + esac + done +} + +####################################### +# Check if running as root +####################################### +check_root() { + if [[ $EUID -ne 0 ]]; then + error "This script must be run as root (use sudo)" + exit 1 + fi +} + +####################################### +# Check for required dependencies +####################################### +check_dependencies() { + local missing=() + + if ! command -v curl &> /dev/null; then + missing+=("curl") + fi + + if ! command -v tar &> /dev/null; then + missing+=("tar") + fi + + if [[ ${#missing[@]} -gt 0 ]]; then + error "Missing required dependencies: ${missing[*]}" + error "Please install them and try again." + exit 1 + fi +} + +####################################### +# Detect operating system +####################################### +detect_os() { + local os + os=$(uname -s) + + case "$os" in + Linux) + echo "Linux" + ;; + Darwin) + echo "Darwin" + ;; + *) + error "Unsupported operating system: $os" + exit 1 + ;; + esac +} + +####################################### +# Detect CPU architecture +####################################### +detect_arch() { + local arch + arch=$(uname -m) + + case "$arch" in + x86_64|amd64) + echo "x86_64" + ;; + aarch64|arm64) + echo "arm64" + ;; + *) + error "Unsupported architecture: $arch" + exit 1 + ;; + esac +} + +####################################### +# Get the latest release version from GitHub +####################################### +get_latest_version() { + local version + version=$(curl -sS "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') + + if [[ -z "$version" ]]; then + error "Failed to fetch latest version from GitHub" + exit 1 + fi + + echo "$version" +} + +####################################### +# Download and install the binary +####################################### +install_binary() { + local os=$1 + local arch=$2 + local version=$3 + + # Remove 'v' prefix if present for the download URL + local version_number="${version#v}" + + # Construct download URL + local archive_name="cli_${os}_${arch}.tar.gz" + local download_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${archive_name}" + + info "Downloading Honeybadger CLI ${version} for ${os}/${arch}..." + + # Create temporary directory + local tmp_dir + tmp_dir=$(mktemp -d) + trap "rm -rf $tmp_dir" EXIT + + # Download archive + if ! curl -sSL -o "${tmp_dir}/${archive_name}" "$download_url"; then + error "Failed to download from: $download_url" + exit 1 + fi + + # Extract archive + info "Extracting archive..." + if ! tar -xzf "${tmp_dir}/${archive_name}" -C "$tmp_dir"; then + error "Failed to extract archive" + exit 1 + fi + + # Install binary + info "Installing binary to ${INSTALL_DIR}/${BINARY_NAME}..." + if ! install -m 755 "${tmp_dir}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}"; then + error "Failed to install binary" + exit 1 + fi + + success "Binary installed successfully" +} + +####################################### +# Prompt for API key if not provided +####################################### +prompt_api_key() { + if [[ -z "$API_KEY" ]]; then + echo "" + echo -e "${YELLOW}Honeybadger API Key Required${NC}" + echo "You can find your API key in your Honeybadger project settings." + echo "" + read -rp "Enter your Honeybadger API key: " API_KEY + + if [[ -z "$API_KEY" ]]; then + error "API key is required for the agent service" + exit 1 + fi + fi +} + +####################################### +# Check if systemd is available +####################################### +check_systemd() { + if ! command -v systemctl &> /dev/null; then + warn "systemd is not available on this system" + warn "Skipping service installation. You can run the agent manually with:" + echo " ${INSTALL_DIR}/${BINARY_NAME} agent --api-key YOUR_API_KEY" + return 1 + fi + return 0 +} + +####################################### +# Create systemd service file +####################################### +create_systemd_service() { + local service_file="/etc/systemd/system/${SERVICE_NAME}.service" + + info "Creating systemd service..." + + cat > "$service_file" << EOF +[Unit] +Description=Honeybadger Agent - System Metrics Reporter +Documentation=https://github.com/honeybadger-io/cli +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=${INSTALL_DIR}/${BINARY_NAME} agent --interval ${INTERVAL} +Restart=always +RestartSec=10 +Environment="HONEYBADGER_API_KEY=${API_KEY}" + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=read-only +PrivateTmp=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true + +[Install] +WantedBy=multi-user.target +EOF + + # Secure the service file (contains API key) + chmod 600 "$service_file" + + success "Systemd service created at ${service_file}" +} + +####################################### +# Enable and start the service +####################################### +start_service() { + info "Reloading systemd daemon..." + systemctl daemon-reload + + info "Enabling ${SERVICE_NAME} service..." + systemctl enable "$SERVICE_NAME" + + info "Starting ${SERVICE_NAME} service..." + systemctl start "$SERVICE_NAME" + + # Wait a moment and check status + sleep 2 + if systemctl is-active --quiet "$SERVICE_NAME"; then + success "Service is running" + else + warn "Service may not have started correctly. Check with: systemctl status ${SERVICE_NAME}" + fi +} + +####################################### +# Print installation summary +####################################### +print_summary() { + echo "" + echo "================================================" + echo -e "${GREEN}Honeybadger CLI Installation Complete${NC}" + echo "================================================" + echo "" + echo "Binary installed: ${INSTALL_DIR}/${BINARY_NAME}" + echo "Version: ${VERSION}" + + if [[ "$INSTALL_SERVICE" == true ]] && check_systemd; then + echo "" + echo "Systemd Service: ${SERVICE_NAME}" + echo "" + echo "Useful commands:" + echo " systemctl status ${SERVICE_NAME} # Check service status" + echo " systemctl restart ${SERVICE_NAME} # Restart the service" + echo " systemctl stop ${SERVICE_NAME} # Stop the service" + echo " journalctl -u ${SERVICE_NAME} -f # View logs" + else + echo "" + echo "Run the agent manually:" + echo " ${BINARY_NAME} agent --api-key YOUR_API_KEY" + fi + + echo "" + echo "CLI Usage:" + echo " ${BINARY_NAME} --help # Show all commands" + echo " ${BINARY_NAME} agent --help # Agent help" + echo " ${BINARY_NAME} deploy --help # Deploy reporting help" + echo "" +} + +####################################### +# Main installation flow +####################################### +main() { + echo "" + echo "================================================" + echo " Honeybadger CLI Installer" + echo "================================================" + echo "" + + # Parse command line arguments + parse_args "$@" + + # Check prerequisites + check_root + check_dependencies + + # Detect system + local os + local arch + os=$(detect_os) + arch=$(detect_arch) + info "Detected system: ${os}/${arch}" + + # Determine version to install + if [[ "$VERSION" == "latest" ]]; then + VERSION=$(get_latest_version) + fi + info "Version to install: ${VERSION}" + + # Install binary + install_binary "$os" "$arch" "$VERSION" + + # Verify installation + if ! "${INSTALL_DIR}/${BINARY_NAME}" --version &> /dev/null; then + warn "Binary installed but version check failed" + fi + + # Setup systemd service if requested + if [[ "$INSTALL_SERVICE" == true ]]; then + if check_systemd; then + prompt_api_key + create_systemd_service + start_service + fi + fi + + # Print summary + print_summary +} + +# Run main function +main "$@" From c42d80ef0ecae9ef08ffe4be8fd1afa7bb56491d Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Tue, 3 Mar 2026 13:38:19 -0800 Subject: [PATCH 2/8] Address PR feedback on installation script - Validate --interval is a positive integer - Validate flag arguments aren't empty or another flag - Hide API key input during interactive prompt (read -s) - Move temp directory cleanup trap to main for proper scoping - Stop existing service before overwriting during reinstall - Fix usage() to exit with error code when called from error path - Remove unused version_number variable - Track service installation state to avoid redundant check_systemd call in print_summary Co-Authored-By: Claude Opus 4.6 --- install.sh | 52 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/install.sh b/install.sh index ac1e089..974e83e 100755 --- a/install.sh +++ b/install.sh @@ -39,6 +39,8 @@ API_KEY="" VERSION="latest" INTERVAL=$DEFAULT_INTERVAL INSTALL_SERVICE=true +SERVICE_INSTALLED=false +TMP_DIR="" ####################################### # Print colored output @@ -90,7 +92,7 @@ Examples: curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --no-service EOF - exit 0 + exit "${1:-0}" } ####################################### @@ -100,14 +102,30 @@ parse_args() { while [[ $# -gt 0 ]]; do case $1 in --api-key) + if [[ -z "${2:-}" || "$2" == --* ]]; then + error "--api-key requires a value" + exit 1 + fi API_KEY="$2" shift 2 ;; --version) + if [[ -z "${2:-}" || "$2" == --* ]]; then + error "--version requires a value" + exit 1 + fi VERSION="$2" shift 2 ;; --interval) + if [[ -z "${2:-}" || "$2" == --* ]]; then + error "--interval requires a value" + exit 1 + fi + if ! [[ "$2" =~ ^[0-9]+$ ]] || [[ "$2" -le 0 ]]; then + error "--interval must be a positive integer (got: $2)" + exit 1 + fi INTERVAL="$2" shift 2 ;; @@ -120,7 +138,7 @@ parse_args() { ;; *) error "Unknown option: $1" - usage + usage 1 ;; esac done @@ -222,36 +240,28 @@ install_binary() { local arch=$2 local version=$3 - # Remove 'v' prefix if present for the download URL - local version_number="${version#v}" - # Construct download URL local archive_name="cli_${os}_${arch}.tar.gz" local download_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${archive_name}" info "Downloading Honeybadger CLI ${version} for ${os}/${arch}..." - # Create temporary directory - local tmp_dir - tmp_dir=$(mktemp -d) - trap "rm -rf $tmp_dir" EXIT - # Download archive - if ! curl -sSL -o "${tmp_dir}/${archive_name}" "$download_url"; then + if ! curl -sSL -o "${TMP_DIR}/${archive_name}" "$download_url"; then error "Failed to download from: $download_url" exit 1 fi # Extract archive info "Extracting archive..." - if ! tar -xzf "${tmp_dir}/${archive_name}" -C "$tmp_dir"; then + if ! tar -xzf "${TMP_DIR}/${archive_name}" -C "$TMP_DIR"; then error "Failed to extract archive" exit 1 fi # Install binary info "Installing binary to ${INSTALL_DIR}/${BINARY_NAME}..." - if ! install -m 755 "${tmp_dir}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}"; then + if ! install -m 755 "${TMP_DIR}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}"; then error "Failed to install binary" exit 1 fi @@ -268,7 +278,8 @@ prompt_api_key() { echo -e "${YELLOW}Honeybadger API Key Required${NC}" echo "You can find your API key in your Honeybadger project settings." echo "" - read -rp "Enter your Honeybadger API key: " API_KEY + read -rsp "Enter your Honeybadger API key: " API_KEY + echo "" if [[ -z "$API_KEY" ]]; then error "API key is required for the agent service" @@ -296,6 +307,12 @@ check_systemd() { create_systemd_service() { local service_file="/etc/systemd/system/${SERVICE_NAME}.service" + # Stop existing service if present + if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then + info "Stopping existing ${SERVICE_NAME} service..." + systemctl stop "$SERVICE_NAME" + fi + info "Creating systemd service..." cat > "$service_file" << EOF @@ -365,7 +382,7 @@ print_summary() { echo "Binary installed: ${INSTALL_DIR}/${BINARY_NAME}" echo "Version: ${VERSION}" - if [[ "$INSTALL_SERVICE" == true ]] && check_systemd; then + if [[ "$SERVICE_INSTALLED" == true ]]; then echo "" echo "Systemd Service: ${SERVICE_NAME}" echo "" @@ -401,6 +418,10 @@ main() { # Parse command line arguments parse_args "$@" + # Create temporary directory for downloads + TMP_DIR=$(mktemp -d) + trap 'rm -rf "$TMP_DIR"' EXIT + # Check prerequisites check_root check_dependencies @@ -432,6 +453,7 @@ main() { prompt_api_key create_systemd_service start_service + SERVICE_INSTALLED=true fi fi From 1311cafeeacb070c8d8f4393856c8d950ea33f4e Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Tue, 3 Mar 2026 13:58:07 -0800 Subject: [PATCH 3/8] Fix install script issues: TTY prompt, --version flag, sudo examples - Read API key from /dev/tty so prompt works in curl|bash piped execution - Set rootCmd.Version so cobra's --version flag works for post-install check - Add sudo to all curl|bash usage examples since script requires root Co-Authored-By: Claude Opus 4.6 --- cmd/root.go | 18 +++++++++++++++--- install.sh | 21 +++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 59a042e..34b5038 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -40,6 +40,12 @@ func Execute() error { func init() { cobra.OnInitialize(initConfig) + if Version != "" { + rootCmd.Version = Version + } else { + rootCmd.Version = "dev" + } + rootCmd.PersistentFlags(). StringVar(&cfgFile, "config", "", "config file (default is config/honeybadger.yml)") rootCmd.PersistentFlags(). @@ -49,13 +55,19 @@ func init() { rootCmd.PersistentFlags(). StringVar(&endpoint, "endpoint", defaultEndpoint, "Honeybadger endpoint") - if err := viper.BindPFlag("api_key", rootCmd.PersistentFlags().Lookup("api-key")); err != nil { + if err := viper.BindPFlag( + "api_key", rootCmd.PersistentFlags().Lookup("api-key"), + ); err != nil { fmt.Printf("error binding api-key flag: %v\n", err) } - if err := viper.BindPFlag("auth_token", rootCmd.PersistentFlags().Lookup("auth-token")); err != nil { + if err := viper.BindPFlag( + "auth_token", rootCmd.PersistentFlags().Lookup("auth-token"), + ); err != nil { fmt.Printf("error binding auth-token flag: %v\n", err) } - if err := viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint")); err != nil { + if err := viper.BindPFlag( + "endpoint", rootCmd.PersistentFlags().Lookup("endpoint"), + ); err != nil { fmt.Printf("error binding endpoint flag: %v\n", err) } } diff --git a/install.sh b/install.sh index 974e83e..d3b6235 100755 --- a/install.sh +++ b/install.sh @@ -6,9 +6,9 @@ # to run as a systemd service for continuous metrics reporting. # # Usage: -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --api-key YOUR_API_KEY -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --version v1.0.0 # # Options: # --api-key KEY Honeybadger API key for the agent @@ -80,16 +80,16 @@ Options: Examples: # Install latest version and configure as systemd service - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash # Install with API key provided - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --api-key YOUR_API_KEY + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY # Install specific version - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --version v1.0.0 # Install binary only (no systemd service) - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --no-service + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --no-service EOF exit "${1:-0}" @@ -274,11 +274,16 @@ install_binary() { ####################################### prompt_api_key() { if [[ -z "$API_KEY" ]]; then + if [[ ! -t 0 ]] && [[ ! -e /dev/tty ]]; then + error "No API key provided and no terminal available for interactive prompt" + error "Re-run with: --api-key YOUR_API_KEY" + exit 1 + fi echo "" echo -e "${YELLOW}Honeybadger API Key Required${NC}" echo "You can find your API key in your Honeybadger project settings." echo "" - read -rsp "Enter your Honeybadger API key: " API_KEY + read -rsp "Enter your Honeybadger API key: " API_KEY < /dev/tty echo "" if [[ -z "$API_KEY" ]]; then From 24c5506d3b9fca6b3c5ee641a191b2493bfd1e5d Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Tue, 3 Mar 2026 13:59:50 -0800 Subject: [PATCH 4/8] Suppress gosec false positives for CI lint - G704: endpoint URL is user-configured via flag/env, not tainted - G101: test struct fields are not real credentials Co-Authored-By: Claude Opus 4.6 --- cmd/agent.go | 2 +- cmd/root_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/agent.go b/cmd/agent.go index 5c31556..3251904 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -122,7 +122,7 @@ func sendMetric(payload interface{}) error { req.Header.Set("X-API-Key", viper.GetString("api_key")) client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) + resp, err := client.Do(req) // #nosec G704 - endpoint is user-configured, not tainted input if err != nil { return fmt.Errorf("error sending metrics: %w", err) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 80d1875..958a141 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -73,7 +73,7 @@ endpoint: https://config.honeybadger.io t.Fatalf("Failed to write config file: %v", err) } - tests := []struct { + tests := []struct { // #nosec G101 - test data, not real credentials name string envAPIKey string envEndpoint string From 5401be1eb3e5773568329e176a83d64093287cfb Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Tue, 3 Mar 2026 14:21:01 -0800 Subject: [PATCH 5/8] Fix G101 lint failures by using v2 exclusion config The inline #nosec comment on the struct definition wasn't suppressing gosec G101 warnings on individual test case literals. Use the proper golangci-lint v2 linters.exclusions.rules config to exclude G101 from test files instead. Co-Authored-By: Claude Opus 4.6 --- .golangci.yml | 6 ++++++ cmd/root_test.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 37e4915..2b7524e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,6 +10,12 @@ linters: - gosec - revive - misspell + exclusions: + rules: + - path: _test\.go + linters: + - gosec + text: "G101" formatters: enable: diff --git a/cmd/root_test.go b/cmd/root_test.go index d9f8fc1..2b0dd80 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -148,7 +148,7 @@ endpoint: https://config.honeybadger.io t.Fatalf("Failed to write config file: %v", err) } - tests := []struct { // #nosec G101 - test data, not real credentials + tests := []struct { name string envAPIKey string envEndpoint string From 2d3ad5d2289a057a0da1a1afec475d38cceaba00 Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Tue, 3 Mar 2026 14:24:19 -0800 Subject: [PATCH 6/8] Address Copilot PR feedback on install script - Add --fail flag to curl download so HTTP 404/500 errors fail fast instead of writing error pages that cause confusing tar extraction failures - Create install directory with mkdir -p for minimal distros where /usr/local/bin may not exist - Pre-create systemd service file with 600 permissions via install(1) so the API key is never exposed with default umask permissions Co-Authored-By: Claude Opus 4.6 --- install.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index d3b6235..f5807a5 100755 --- a/install.sh +++ b/install.sh @@ -247,7 +247,7 @@ install_binary() { info "Downloading Honeybadger CLI ${version} for ${os}/${arch}..." # Download archive - if ! curl -sSL -o "${TMP_DIR}/${archive_name}" "$download_url"; then + if ! curl -fsSL -o "${TMP_DIR}/${archive_name}" "$download_url"; then error "Failed to download from: $download_url" exit 1 fi @@ -260,6 +260,7 @@ install_binary() { fi # Install binary + mkdir -p "$INSTALL_DIR" info "Installing binary to ${INSTALL_DIR}/${BINARY_NAME}..." if ! install -m 755 "${TMP_DIR}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}"; then error "Failed to install binary" @@ -320,6 +321,8 @@ create_systemd_service() { info "Creating systemd service..." + # Pre-create with restrictive permissions since the file will contain the API key + install -m 600 /dev/null "$service_file" cat > "$service_file" << EOF [Unit] Description=Honeybadger Agent - System Metrics Reporter @@ -347,9 +350,6 @@ ProtectControlGroups=true WantedBy=multi-user.target EOF - # Secure the service file (contains API key) - chmod 600 "$service_file" - success "Systemd service created at ${service_file}" } From d8bc612410422642196c8ee016d1c3391efc519b Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Wed, 4 Mar 2026 09:14:14 -0800 Subject: [PATCH 7/8] Add install script documentation to README Co-Authored-By: Claude Opus 4.6 --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 27e4d21..e4ee9ee 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,22 @@ Note: The install path includes `/cmd/hb` so Go installs the `hb` binary name. Pre-built binaries for Linux, macOS, and Windows are available on the [GitHub releases page](https://github.com/honeybadger-io/cli/releases). +### Install Script + +On Linux, you can use the install script to download the binary and optionally set up a systemd service for the [metrics agent](#reporting-api-commands): + +```bash +curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY +``` + +To install the binary without setting up a systemd service: + +```bash +curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --no-service +``` + +Run with `--help` for all options including `--version` and `--interval`. + ## Configuration The CLI can be configured using either command-line flags, environment variables, or a configuration file. From 4fe7da82bd21595990005cba4a1baa77c9338f31 Mon Sep 17 00:00:00 2001 From: Benjamin Curtis Date: Wed, 11 Mar 2026 14:23:42 -0700 Subject: [PATCH 8/8] Make install script user-friendly: opt-in systemd, no-sudo install Flip systemd service setup from opt-out (--no-service) to opt-in (--service), and support installing without sudo by defaulting to ~/.local/bin for non-root users. Adds --install-dir override, PATH detection hint, and warnings when service-only flags are used without --service. Co-Authored-By: Claude Opus 4.6 --- README.md | 12 ++++--- install.sh | 103 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e4ee9ee..5c21b8f 100644 --- a/README.md +++ b/README.md @@ -26,19 +26,21 @@ Pre-built binaries for Linux, macOS, and Windows are available on the [GitHub re ### Install Script -On Linux, you can use the install script to download the binary and optionally set up a systemd service for the [metrics agent](#reporting-api-commands): +On Linux and macOS, you can use the install script to download the binary: ```bash -curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY +curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash ``` -To install the binary without setting up a systemd service: +No `sudo` required — the binary installs to `~/.local/bin` by default. When run as root, it installs to `/usr/local/bin`. + +To also set up a systemd service for the [metrics agent](#reporting-api-commands): ```bash -curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --no-service +curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --service --api-key YOUR_API_KEY ``` -Run with `--help` for all options including `--version` and `--interval`. +Run with `--help` for all options including `--version`, `--interval`, and `--install-dir`. ## Configuration diff --git a/install.sh b/install.sh index f5807a5..2e8b538 100755 --- a/install.sh +++ b/install.sh @@ -6,15 +6,16 @@ # to run as a systemd service for continuous metrics reporting. # # Usage: -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY -# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --version v1.0.0 +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --service --api-key YOUR_API_KEY +# curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 # # Options: -# --api-key KEY Honeybadger API key for the agent +# --service Also set up a systemd service (requires root) +# --api-key KEY Honeybadger API key for the agent (requires --service) # --version VERSION Specific version to install (default: latest) -# --interval SECONDS Metrics reporting interval (default: 60) -# --no-service Install binary only, skip systemd service setup +# --interval SECONDS Metrics reporting interval (default: 60, requires --service) +# --install-dir DIR Override install directory # --help Show this help message # @@ -23,7 +24,7 @@ set -e # Configuration GITHUB_REPO="honeybadger-io/cli" BINARY_NAME="hb" -INSTALL_DIR="/usr/local/bin" +INSTALL_DIR="" SERVICE_NAME="honeybadger-agent" DEFAULT_INTERVAL=60 @@ -38,7 +39,7 @@ NC='\033[0m' # No Color API_KEY="" VERSION="latest" INTERVAL=$DEFAULT_INTERVAL -INSTALL_SERVICE=true +INSTALL_SERVICE=false SERVICE_INSTALLED=false TMP_DIR="" @@ -72,24 +73,22 @@ Usage: $0 [options] Options: - --api-key KEY Honeybadger API key for the agent + --service Also set up a systemd service for the metrics agent (requires root) + --api-key KEY Honeybadger API key for the agent (requires --service) --version VERSION Specific version to install (default: latest) - --interval SECONDS Metrics reporting interval in seconds (default: 60) - --no-service Install binary only, skip systemd service setup + --interval SECONDS Metrics reporting interval in seconds (default: 60, requires --service) + --install-dir DIR Override install directory (default: ~/.local/bin or /usr/local/bin as root) --help Show this help message Examples: - # Install latest version and configure as systemd service - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash + # Install the binary (no sudo required) + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash - # Install with API key provided - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --api-key YOUR_API_KEY + # Install a specific version + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | bash -s -- --version v1.0.0 - # Install specific version - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --version v1.0.0 - - # Install binary only (no systemd service) - curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --no-service + # Install and set up as a systemd service (requires sudo) + curl -sSL https://raw.githubusercontent.com/honeybadger-io/cli/main/install.sh | sudo bash -s -- --service --api-key YOUR_API_KEY EOF exit "${1:-0}" @@ -129,10 +128,18 @@ parse_args() { INTERVAL="$2" shift 2 ;; - --no-service) - INSTALL_SERVICE=false + --service) + INSTALL_SERVICE=true shift ;; + --install-dir) + if [[ -z "${2:-}" || "$2" == --* ]]; then + error "--install-dir requires a value" + exit 1 + fi + INSTALL_DIR="$2" + shift 2 + ;; --help|-h) usage ;; @@ -145,15 +152,34 @@ parse_args() { } ####################################### -# Check if running as root +# Determine install directory based on privileges ####################################### -check_root() { - if [[ $EUID -ne 0 ]]; then - error "This script must be run as root (use sudo)" - exit 1 +resolve_install_dir() { + if [[ -z "$INSTALL_DIR" ]]; then + if [[ $EUID -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi fi } +####################################### +# Check if install dir is in PATH +####################################### +check_path() { + case ":${PATH}:" in + *":${INSTALL_DIR}:"*) + ;; + *) + warn "${INSTALL_DIR} is not in your PATH" + echo " Add it by running:" + echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" + echo " To make it permanent, add the line above to your shell profile (~/.bashrc, ~/.zshrc, etc.)" + ;; + esac +} + ####################################### # Check for required dependencies ####################################### @@ -423,12 +449,30 @@ main() { # Parse command line arguments parse_args "$@" + # Warn if --api-key or --interval used without --service + if [[ "$INSTALL_SERVICE" == false ]]; then + if [[ -n "$API_KEY" ]]; then + warn "--api-key has no effect without --service" + fi + if [[ "$INTERVAL" -ne "$DEFAULT_INTERVAL" ]]; then + warn "--interval has no effect without --service" + fi + fi + + # If --service is requested, require root + if [[ "$INSTALL_SERVICE" == true ]] && [[ $EUID -ne 0 ]]; then + error "--service requires root (use sudo)" + exit 1 + fi + # Create temporary directory for downloads TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT + # Determine install directory + resolve_install_dir + # Check prerequisites - check_root check_dependencies # Detect system @@ -452,6 +496,9 @@ main() { warn "Binary installed but version check failed" fi + # Check if install dir is in PATH + check_path + # Setup systemd service if requested if [[ "$INSTALL_SERVICE" == true ]]; then if check_systemd; then