diff --git a/package.json b/package.json index cc98348a..e2e11a57 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "format": "{ git diff --name-only HEAD -- 'src/**/*.ts' 'src/**/*.tsx'; git ls-files --others --exclude-standard -- 'src/**/*.ts' 'src/**/*.tsx'; } | xargs -r prettier --write", "format:all": "prettier --write \"src/**/*.{ts,tsx}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx}\"", - "setup": "bash scripts/setup/officer-setup.sh" + "setup": "bash scripts/install.sh" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.41", diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 00000000..558b8543 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# ============================================================================= +# Officer — install +# ============================================================================= +# +# One command, blank machine to running platform. It runs the two halves in +# order and does nothing else itself: +# +# setup/machine-setup/machine-setup.sh a usable machine — packages, tailnet, +# runtimes, docker, shell +# setup/officer-setup.sh the platform on top of it — repo, +# dependencies, postgres, .env, secret +# store, schema, build, pm2 +# +# They stay two scripts because they answer two different questions and are worth +# running separately: a machine you already trust needs only the second, and a +# machine you are rebuilding needs only the first. This is the wrapper for the +# case where you want both, which is most first runs. +# +# Both are re-runnable. Each remembers the steps it finished and skips them, so +# stopping halfway and coming back costs nothing. +# +# ── Privileges ── +# +# Linux needs root: apt, systemd units, useradd, netplan, ufw, and creating +# directories owned by the service account. macOS must NOT be root: Homebrew +# refuses to run as one, and there is nothing to chown because the account +# running this IS the owner. Both scripts enforce that themselves; this checks +# first so the failure arrives before anything has been done. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MACHINE="$SCRIPT_DIR/setup/machine-setup/machine-setup.sh" +OFFICER="$SCRIPT_DIR/setup/officer-setup.sh" + +BOLD='\033[1m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +say() { echo -e "$*"; } +die() { + echo -e "${YELLOW}error:${NC} $*" >&2 + exit 1 +} + +[[ -r "$MACHINE" ]] || die "missing $MACHINE" +[[ -r "$OFFICER" ]] || die "missing $OFFICER" + +# Which halves to run. Both by default. +RUN_MACHINE=true +RUN_OFFICER=true +for arg in "$@"; do + case "$arg" in + --machine-only) RUN_OFFICER=false ;; + --officer-only) RUN_MACHINE=false ;; + -h | --help) + say "usage: install.sh [--machine-only | --officer-only]" + say "" + say " no flags both halves, machine first" + say " --machine-only stop after the machine is provisioned" + say " --officer-only the platform only, on a machine you already trust" + exit 0 + ;; + *) die "unknown option: $arg" ;; + esac +done + +KERNEL="$(uname -s)" +case "$KERNEL" in + Darwin) + [[ "$EUID" -eq 0 ]] && die "do not run this with sudo on macOS — Homebrew refuses to run as root. Run it as yourself." + ;; + Linux) + [[ "$EUID" -ne 0 ]] && die "please run as root: sudo ./scripts/install.sh" + ;; + *) die "unsupported system: $KERNEL. Officer installs on Linux and macOS." ;; +esac + +say "" +say "${BOLD}Officer install${NC}" +say " system: $KERNEL" +$RUN_MACHINE && say " 1/2 machine setup" +$RUN_OFFICER && say " $($RUN_MACHINE && echo 2/2 || echo 1/1) officer setup" +say "" +say " Either half can be run on its own later:" +say " scripts/setup/machine-setup/machine-setup.sh" +say " scripts/setup/officer-setup.sh" +say "" + +# Not `set -e`'s job: a half that exits non-zero should say which half, and stop +# before the next one starts on a machine that is not ready for it. +if $RUN_MACHINE; then + bash "$MACHINE" || die "machine setup did not finish — fix what it reported, then run this again" +fi + +if $RUN_OFFICER; then + bash "$OFFICER" || die "officer setup did not finish — fix what it reported, then run this again" +fi + +say "" +say "${GREEN}Done.${NC}" diff --git a/scripts/setup/officer-setup.sh b/scripts/setup/officer-setup.sh index 946f4303..ef6c5501 100755 --- a/scripts/setup/officer-setup.sh +++ b/scripts/setup/officer-setup.sh @@ -66,7 +66,17 @@ echo -e "${BOLD}╔════════════════════ echo -e "${BOLD}║ Officer Setup ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}" -if [[ "$EUID" -ne 0 ]]; then +# root on Linux, NOT root on macOS — the same split machine-setup makes, for the +# same reason. On Linux this creates directories owned by another account and +# drops privileges with `sudo -u`. On macOS the account running the script IS the +# owner, so there is nothing to chown and nothing to drop to — and Homebrew, which +# machine-setup ran just before this, refuses to run as root at all. +OFFICER_OS="$(uname -s)" +if [[ "$OFFICER_OS" == "Darwin" ]]; then + if [[ "$EUID" -eq 0 ]]; then + fail "Do not run this with sudo on macOS — run it as yourself." + fi +elif [[ "$EUID" -ne 0 ]]; then fail "Please run as root: sudo ./officer-setup.sh" fi