Files
gh-netresearch-claude-code-…/references/examples/coding-agent-cli/scripts-AGENTS.md
2025-11-30 08:43:17 +08:00

10 KiB

Installation Scripts - Agent Guide

Scope: Shell scripts for tool installation, update, uninstall, reconcile

Overview

13+ Bash scripts for installing developer tools with multiple actions:

  • install: Fresh installation (default action)
  • update: Upgrade to latest version
  • uninstall: Remove installation
  • reconcile: Switch to preferred installation method (e.g., system → user)

Key scripts:

  • install_core.sh: Core tools (fd, fzf, ripgrep, jq, yq, bat, delta, just)
  • install_python.sh: Python toolchain via uv
  • install_node.sh: Node.js via nvm
  • install_rust.sh: Rust via rustup
  • install_go.sh, install_aws.sh, install_kubectl.sh, etc.
  • guide.sh: Interactive upgrade guide
  • test_smoke.sh: Smoke test for audit output

Shared utilities: lib/ directory (colors, logging, common functions)

Setup

Requirements:

  • Bash 4.0+
  • curl or wget for downloads
  • Internet access for fresh installs
  • Appropriate permissions (user for ~/.local/bin, sudo for system)

Environment variables:

INSTALL_PREFIX=${INSTALL_PREFIX:-~/.local}  # Default: user-level
FORCE_INSTALL=1                              # Skip confirmation prompts
DEBUG=1                                      # Verbose output

Permissions:

make scripts-perms  # Ensure all scripts are executable

Build & Tests

Run individual script:

# Install action (default)
./scripts/install_python.sh

# Update action
./scripts/install_python.sh update

# Uninstall action
./scripts/install_python.sh uninstall

# Reconcile action (switch installation method)
./scripts/install_node.sh reconcile

Via Make:

make install-python              # Install Python toolchain
make update-python               # Update Python toolchain
make uninstall-python            # Uninstall Python toolchain
make reconcile-node              # Switch Node.js to nvm-managed

Smoke test:

./scripts/test_smoke.sh          # Verify audit output format

Debug mode:

DEBUG=1 ./scripts/install_python.sh
bash -x ./scripts/install_python.sh  # Trace execution

Code Style

Shell standards:

  • Bash 4.0+ features allowed
  • Shebang: #!/usr/bin/env bash or #!/bin/bash
  • Set strict mode: set -euo pipefail
    • -e: Exit on error
    • -u: Error on undefined variables
    • -o pipefail: Fail on pipe errors

Formatting:

  • 4-space indentation (matches EditorConfig)
  • Function names: lowercase_with_underscores
  • Constants: UPPER_CASE
  • Local variables: lowercase

Structure:

#!/usr/bin/env bash
set -euo pipefail

# Source shared utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/colors.sh" || true
source "${SCRIPT_DIR}/lib/common.sh" || true

# Main function per action
install_tool() {
    echo_info "Installing <tool>..."
    # Implementation
}

update_tool() {
    echo_info "Updating <tool>..."
    # Implementation
}

uninstall_tool() {
    echo_info "Uninstalling <tool>..."
    # Implementation
}

reconcile_tool() {
    echo_info "Reconciling <tool>..."
    # Implementation
}

# Action dispatcher
ACTION="${1:-install}"
case "$ACTION" in
    install) install_tool ;;
    update) update_tool ;;
    uninstall) uninstall_tool ;;
    reconcile) reconcile_tool ;;
    *) echo "Usage: $0 {install|update|uninstall|reconcile}"; exit 1 ;;
esac

Error handling:

# Good: Check command exists before using
if ! command -v curl >/dev/null 2>&1; then
    echo_error "curl not found. Install it first."
    exit 1
fi

# Good: Check return codes
if ! download_file "$URL" "$DEST"; then
    echo_error "Download failed"
    exit 1
fi

# Good: Cleanup on error
trap 'rm -rf "$TMPDIR"' EXIT ERR

Confirmation prompts:

# Good: Skip prompt if FORCE_INSTALL=1
if [[ "${FORCE_INSTALL:-0}" != "1" ]]; then
    read -p "Install <tool>? [y/N] " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
fi

Security

Download verification:

# Always use HTTPS
URL="https://github.com/owner/repo/releases/download/..."

# Verify checksums when available
EXPECTED_SHA256="abc123..."
ACTUAL_SHA256=$(sha256sum "$FILE" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
    echo_error "Checksum mismatch!"
    exit 1
fi

Path safety:

# Good: Quote variables, use absolute paths
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"
mv "$TMPFILE" "$INSTALL_DIR/tool"

# Bad: Unquoted, relative paths
mkdir -p $INSTALL_DIR
mv tool bin/

Sudo usage:

# Good: Prompt for sudo only when needed
if [[ "$INSTALL_PREFIX" == "/usr/local" ]]; then
    if ! sudo -v; then
        echo_error "Sudo required for system installation"
        exit 1
    fi
    sudo mv "$FILE" "$INSTALL_PREFIX/bin/"
else
    # User-level, no sudo
    mv "$FILE" "$INSTALL_PREFIX/bin/"
fi

No secrets in scripts:

  • No API keys, tokens, passwords in scripts
  • Use environment variables: ${GITHUB_TOKEN:-}
  • Document required env vars in script comments

PR/Commit Checklist

Before commit:

  • Run shellcheck <script> (if available)
  • Test install action: ./scripts/install_<tool>.sh
  • Test update action: ./scripts/install_<tool>.sh update
  • Test uninstall action: ./scripts/install_<tool>.sh uninstall
  • Update scripts/README.md if new script or behavior change
  • Verify script permissions: make scripts-perms

Script checklist:

  • Shebang: #!/usr/bin/env bash
  • Strict mode: set -euo pipefail
  • Source shared lib: source "${SCRIPT_DIR}/lib/colors.sh"
  • Action dispatcher (install/update/uninstall/reconcile)
  • Error handling (check return codes, trap on exit)
  • Confirmation prompts (respect FORCE_INSTALL)
  • PATH updates (add to ~/.bashrc or ~/.zshrc if needed)

Commit messages:

  • feat(scripts): add install_terraform.sh
  • fix(install-python): handle uv bootstrap failure
  • docs(scripts): update README with reconcile action

Good vs Bad Examples

Good: Robust download with fallback

download_file() {
    local url="$1"
    local dest="$2"

    if command -v curl >/dev/null 2>&1; then
        curl -fsSL "$url" -o "$dest"
    elif command -v wget >/dev/null 2>&1; then
        wget -q "$url" -O "$dest"
    else
        echo_error "Neither curl nor wget found"
        return 1
    fi
}

Bad: Assumes curl exists

download_file() {
    curl -fsSL "$1" -o "$2"  # Fails if curl not installed
}

Good: Version comparison

version_gt() {
    test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}

CURRENT_VERSION="1.2.3"
LATEST_VERSION="1.3.0"

if version_gt "$LATEST_VERSION" "$CURRENT_VERSION"; then
    echo "Upgrade available"
fi

Bad: String comparison for versions

if [[ "$LATEST_VERSION" > "$CURRENT_VERSION" ]]; then
    # Wrong: "1.10.0" < "1.9.0" (string comparison)
    echo "Upgrade available"
fi

Good: Cleanup on exit

TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT ERR

# Download to temp
download_file "$URL" "$TMPDIR/file"
# ... process ...
# Cleanup happens automatically via trap

Bad: Manual cleanup (error-prone)

TMPDIR=$(mktemp -d)
download_file "$URL" "$TMPDIR/file"
# ... process ...
rm -rf "$TMPDIR"  # Skipped if earlier command fails

Good: Action-specific logic

install_rust() {
    if command -v rustup >/dev/null 2>&1; then
        echo_warn "rustup already installed"
        return 0
    fi

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
    source "$HOME/.cargo/env"
}

update_rust() {
    if ! command -v rustup >/dev/null 2>&1; then
        echo_error "rustup not installed. Run install first."
        return 1
    fi

    rustup update
}

When Stuck

Script fails silently:

  1. Add debug: bash -x ./scripts/install_<tool>.sh
  2. Check logs: ./scripts/install_<tool>.sh 2>&1 | tee install.log
  3. Verify permissions: ls -la scripts/

Download fails:

  1. Check network: curl -I https://github.com
  2. Check URL: echo "$URL" (verify it's correct)
  3. Try manual download: curl -fsSL "$URL"

Installation fails:

  1. Check prerequisites (e.g., Python for uv, curl for rustup)
  2. Check disk space: df -h
  3. Check permissions: ls -ld "$INSTALL_PREFIX"

PATH not updated:

  1. Source shell config: source ~/.bashrc or source ~/.zshrc
  2. Check PATH: echo $PATH | tr ':' '\n' | grep local
  3. Verify binary location: ls -la ~/.local/bin/<tool>

Reconcile fails:

  1. Check current installation: which <tool>
  2. Check installation method: cli_audit.py --only <tool>
  3. Manually remove old version first: apt remove <tool> or cargo uninstall <tool>

Documentation:

House Rules

Installation preferences (Phase 2 planning):

Reconciliation strategy:

Version policy:

Script structure:

  • Multi-action support: install, update, uninstall, reconcile
  • Shared utilities in lib/
  • Consistent error handling and logging
  • Make integration via make install-<tool>

Quick Start: Run make install-core to install essential tools, then make audit to verify.

Troubleshooting: See README.md for per-script troubleshooting and ../docs/TROUBLESHOOTING.md for general issues.