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 uvinstall_node.sh: Node.js via nvminstall_rust.sh: Rust via rustupinstall_go.sh,install_aws.sh,install_kubectl.sh, etc.guide.sh: Interactive upgrade guidetest_smoke.sh: Smoke test for audit output
Shared utilities: lib/ directory (colors, logging, common functions)
Setup
Requirements:
- Bash 4.0+
curlorwgetfor 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 bashor#!/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.mdif 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.shfix(install-python): handle uv bootstrap failuredocs(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:
- Add debug:
bash -x ./scripts/install_<tool>.sh - Check logs:
./scripts/install_<tool>.sh 2>&1 | tee install.log - Verify permissions:
ls -la scripts/
Download fails:
- Check network:
curl -I https://github.com - Check URL:
echo "$URL"(verify it's correct) - Try manual download:
curl -fsSL "$URL"
Installation fails:
- Check prerequisites (e.g., Python for uv, curl for rustup)
- Check disk space:
df -h - Check permissions:
ls -ld "$INSTALL_PREFIX"
PATH not updated:
- Source shell config:
source ~/.bashrcorsource ~/.zshrc - Check PATH:
echo $PATH | tr ':' '\n' | grep local - Verify binary location:
ls -la ~/.local/bin/<tool>
Reconcile fails:
- Check current installation:
which <tool> - Check installation method:
cli_audit.py --only <tool> - Manually remove old version first:
apt remove <tool>orcargo uninstall <tool>
Documentation:
- Script-specific docs: README.md (this directory)
- Troubleshooting: ../docs/TROUBLESHOOTING.md
- Architecture: ../docs/DEPLOYMENT.md
House Rules
Installation preferences (Phase 2 planning):
- User-level preferred:
~/.local/bin(workstations) - System-level for servers:
/usr/local/bin - Vendor tools first: rustup, nvm, uv over system packages
- See ../docs/adr/ADR-002-package-manager-hierarchy.md
Reconciliation strategy:
- Parallel approach: Keep both installations, prefer user via PATH
- No automatic removal (user chooses)
- See ../docs/adr/ADR-003-parallel-installation-approach.md
Version policy:
- Always latest by default
- Warn on major version upgrades
- See ../docs/adr/ADR-004-always-latest-version-policy.md
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.