Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
ff4fe56b2This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env bash
# copperhead bootstrap installer.
#
# Checks the prerequisites (Node >= 20, git, KiCad's kicad-cli), installs
# copperhead itself, and verifies the result with `copperhead doctor`.
#
# Conservative by design: it never runs sudo, never edits shell config, and
# prints the exact command for you to run whenever it cannot act safely on its
# own. Idempotent: rerunning on a ready machine installs nothing and exits 0.
#
# Usage:
# ./install.sh interactive: asks before installing anything
# ./install.sh --yes assume yes (still never sudo)
# ./install.sh --check-only report what is missing, install nothing
#
# Supported platforms: macOS and Linux. Exit code 0 means node, git,
# kicad-cli, and copperhead are all usable; 1 means at least one is still
# missing (the report says which, and how to fix it).
set -euo pipefail
MIN_NODE_MAJOR=20
ASSUME_YES=0
CHECK_ONLY=0
for arg in "$@"; do
case "$arg" in
-y|--yes) ASSUME_YES=1 ;;
--check-only) CHECK_ONLY=1 ;;
-h|--help)
sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "unknown option: $arg (try --help)" >&2
exit 2
;;
esac
done
if [ -t 1 ]; then
BOLD=$(printf '\033[1m'); RED=$(printf '\033[31m'); GREEN=$(printf '\033[32m')
YELLOW=$(printf '\033[33m'); RESET=$(printf '\033[0m')
else
BOLD=''; RED=''; GREEN=''; YELLOW=''; RESET=''
fi
# One line per check: `step` sets a pending section label and the next status
# line prints it in a fixed-width column; continuation lines align under the
# status column.
STEP=''
step() { STEP="$1"; }
_label() {
if [ -n "$STEP" ]; then
printf '%s' "$BOLD"; printf '%-23s' "$STEP"; printf '%s' "$RESET"
STEP=''
else
printf '%-23s' ''
fi
}
ok() { _label; printf '%sok%s %s\n' "$GREEN" "$RESET" "$1"; }
fail() { _label; printf '%sfail%s %s\n' "$RED" "$RESET" "$1"; }
info() { _label; printf '%sinfo%s %s\n' "$YELLOW" "$RESET" "$1"; }
# Ask for consent before any install. Non-interactive shells default to no,
# so `curl | bash` without --yes can only ever report, never install.
confirm() {
if [ "$CHECK_ONLY" = 1 ]; then return 1; fi
if [ "$ASSUME_YES" = 1 ]; then return 0; fi
if [ ! -t 0 ]; then
info "non-interactive shell: skipping (\"$1\"); rerun with --yes to allow it"
return 1
fi
_label; printf '%s [y/N] ' "$1"
read -r reply
case "$reply" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
}
# ---------- platform ----------
OS=$(uname -s)
case "$OS" in
Darwin) PLATFORM=macos ;;
Linux) PLATFORM=linux ;;
*)
echo "unsupported platform: $OS (this script supports macOS and Linux)" >&2
exit 1
;;
esac
PKG_MANAGER=""
for pm in brew apt-get dnf pacman zypper; do
if command -v "$pm" >/dev/null 2>&1; then PKG_MANAGER=$pm; break; fi
done
kicad_install_hint() {
case "$PKG_MANAGER" in
brew) echo "brew install --cask kicad" ;;
apt-get) echo "sudo apt-get install kicad" ;;
dnf) echo "sudo dnf install kicad" ;;
pacman) echo "sudo pacman -S kicad" ;;
zypper) echo "sudo zypper install kicad" ;;
*) echo "download KiCad from https://www.kicad.org/download/" ;;
esac
}
node_install_hint() {
case "$PKG_MANAGER" in
brew) echo "brew install node" ;;
*) echo "install Node >= ${MIN_NODE_MAJOR} from https://nodejs.org/ (or via nvm: https://github.com/nvm-sh/nvm)" ;;
esac
}
git_install_hint() {
case "$PKG_MANAGER" in
brew) echo "brew install git" ;;
apt-get) echo "sudo apt-get install git" ;;
dnf) echo "sudo dnf install git" ;;
pacman) echo "sudo pacman -S git" ;;
zypper) echo "sudo zypper install git" ;;
*) echo "install git from https://git-scm.com/downloads" ;;
esac
}
echo "${BOLD}copperhead bootstrap (${PLATFORM}${PKG_MANAGER:+, package manager: $PKG_MANAGER})${RESET}"
MISSING=0
# ---------- node ----------
step "1/4 Node.js"
if command -v node >/dev/null 2>&1; then
NODE_VERSION=$(node --version)
NODE_MAJOR=${NODE_VERSION#v}; NODE_MAJOR=${NODE_MAJOR%%.*}
if [ "$NODE_MAJOR" -ge "$MIN_NODE_MAJOR" ] 2>/dev/null; then
ok "node $NODE_VERSION (>= $MIN_NODE_MAJOR)"
else
fail "node $NODE_VERSION is too old (copperhead needs >= $MIN_NODE_MAJOR)"
info "upgrade: $(node_install_hint)"
MISSING=1
fi
else
fail "node not found on PATH"
info "install: $(node_install_hint)"
MISSING=1
fi
# ---------- git ----------
step "2/4 git"
if command -v git >/dev/null 2>&1; then
ok "$(git --version)"
else
fail "git not found on PATH (copperhead snapshots and commits its work)"
info "install: $(git_install_hint)"
MISSING=1
fi
# ---------- kicad-cli ----------
step "3/4 KiCad (kicad-cli)"
if command -v kicad-cli >/dev/null 2>&1; then
ok "kicad-cli $(kicad-cli version 2>/dev/null || echo '(version probe failed)')"
else
# On macOS the binary ships inside the app bundle and is not on PATH by
# default; detect that case and print the PATH line instead of a reinstall.
MAC_KICAD_BIN="/Applications/KiCad/KiCad.app/Contents/MacOS"
if [ "$PLATFORM" = macos ] && [ -x "$MAC_KICAD_BIN/kicad-cli" ]; then
fail "KiCad is installed but kicad-cli is not on PATH"
info "add it yourself (this script never edits shell config):"
info " echo 'export PATH=\"\$PATH:$MAC_KICAD_BIN\"' >> ~/.zprofile && exec zsh"
else
fail "kicad-cli not found on PATH (copperhead verifies every edit with ERC/DRC)"
info "install KiCad >= 8: $(kicad_install_hint)"
if [ "$PLATFORM" = macos ]; then
info "then put kicad-cli on PATH (it ships inside KiCad.app/Contents/MacOS)"
fi
fi
MISSING=1
fi
# ---------- copperhead ----------
step "4/4 copperhead"
in_repo_checkout() {
[ -f package.json ] && grep -q '"name": "copperhead"' package.json
}
if command -v copperhead >/dev/null 2>&1; then
ok "copperhead $(copperhead --version 2>/dev/null || echo '(already on PATH)')"
elif ! command -v npm >/dev/null 2>&1; then
fail "npm not found (comes with Node); fix the Node.js step first"
MISSING=1
elif in_repo_checkout; then
info "running from a copperhead source checkout"
if ! confirm "build from source and link it globally (npm install && npm run build && npm link)?"; then
info "skipped; install later with: npm install && npm run build && npm link"
MISSING=1
# Refuse to build a dirty checkout: npm install may rewrite package-lock.json,
# and a linked binary should correspond to a committed state. Print the
# commands instead of acting (conservative-by-design contract).
elif git rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -n "$(git status --porcelain)" ]; then
fail "this checkout has uncommitted changes; refusing to build and link from a dirty tree"
info "commit or stash them, then rerun; or build manually: npm install && npm run build && npm link"
MISSING=1
else
npm install
npm run build
if npm link; then
hash -r 2>/dev/null || true
if command -v copperhead >/dev/null 2>&1; then
ok "linked: copperhead $(copperhead --version 2>/dev/null || true) at $(command -v copperhead)"
else
info "linked into $(npm prefix -g)/bin, which is not on PATH; add it or run in place: node dist/cli.js"
MISSING=1
fi
else
fail "npm link failed (usually a permissions issue with the global npm prefix)"
info "either fix the prefix (https://docs.npmjs.com/resolving-eacces-permissions-errors) or run it in place: node dist/cli.js"
MISSING=1
fi
fi
else
if confirm "install copperhead globally (npm install -g copperhead)?"; then
if npm install -g copperhead; then
hash -r 2>/dev/null || true
if command -v copperhead >/dev/null 2>&1; then
ok "installed: copperhead $(copperhead --version 2>/dev/null || true) -> $(command -v copperhead)"
else
info "installed into $(npm prefix -g)/bin, which is not on PATH; add it or use npx: npx copperhead check"
MISSING=1
fi
else
fail "npm install -g failed (usually a permissions issue with the global npm prefix)"
info "either fix the prefix (https://docs.npmjs.com/resolving-eacces-permissions-errors) or use npx: npx copperhead check"
MISSING=1
fi
else
info "skipped; install later with: npm install -g copperhead (or use npx copperhead)"
MISSING=1
fi
fi
# ---------- verify ----------
step "Verification"
if [ "$MISSING" = 0 ]; then
# doctor exits 1 when any critical check fails, including a missing model
# API key. A fresh machine has no key yet, so that alone is a next step,
# not a bootstrap failure; the tool checks above are what this script gates on.
# Older copperhead releases predate `doctor`; probe before relying on it.
# (probe the help listing: commander answers `doctor --help` with the global
# help and exit 0 even when the subcommand does not exist)
if copperhead --help 2>/dev/null | grep -qE '^[[:space:]]+doctor'; then
if DOCTOR_OUT=$(copperhead doctor 2>&1); then
ok "environment ready (copperhead doctor passed)"
else
info "copperhead doctor reports remaining setup (typically the model backend):"
# shellcheck disable=SC2001 # multi-line indent: sed beats ${var//}
echo "$DOCTOR_OUT" | sed 's/^/ /'
fi
else
info "this copperhead ($(copperhead --version 2>/dev/null || true)) predates \`copperhead doctor\`; tools were verified individually above"
info "upgrade later with: npm install -g copperhead@latest"
fi
echo "${GREEN}${BOLD}done.${RESET} next: run ${BOLD}copperhead init${RESET} in a KiCad repo, then ${BOLD}copperhead check${RESET}"
exit 0
else
fail "some prerequisites are missing; follow the guidance above and rerun this script"
info "rerunning is safe: the script is idempotent and skips whatever is already set up"
exit 1
fi