Neither script ever becomes the user it sets the machine up for — a process cannot change its own uid, so both run as root and drop privileges per command instead. Everything Officer owns ends up belonging to that user and every pm2 process runs as them, but the session you are left holding is root's. Two things that fixes are invisible until they bite: group membership is fixed at LOGIN, so the `docker` group just granted is not in the current session, and the shell configuration was written into their home and is not loaded in root's. Both present as "the machine is broken" rather than "log in again". Printed by whichever half runs LAST. The first attempt put it at the end of both, which says it twice on a full install — and the first time it is wrong, because officer-setup is about to run and still needs the root session it tells you to leave. install.sh is the only thing that knows whether anything follows, so it sets OFFICER_SETUP_FOLLOWS and machine-setup stays quiet. Also drops "Pre-flight complete. The remaining sections are not built yet." from the end of officer-setup. All 11 sections exist; that line last made sense when 6 did. Verified: bash -n on all three, the set -e behaviour of `$RUN_OFFICER && export` under --machine-only, and the suppression across all five ways in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
172 lines
6.8 KiB
Bash
Executable File
172 lines
6.8 KiB
Bash
Executable File
#!/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.
|
|
#
|
|
# Run it as yourself — it asks for administrator rights when it needs them.
|
|
#
|
|
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
|
|
|
|
# Kept before the loop below eats them: this script re-executes itself through sudo
|
|
# further down, and `shift` would otherwise leave it re-running with no arguments —
|
|
# silently dropping --officer-only and turning a platform-only run into a full one.
|
|
#
|
|
# The `${x[@]+"${x[@]}"}` form is for `set -u`: expanding an empty array unquoted-safe
|
|
# is an error on bash before 4.4, and this runs on whatever the machine came with.
|
|
ORIGINAL_ARGS=(${@+"$@"})
|
|
|
|
# A `while`/`shift` loop rather than `for arg in "$@"`, because --repo takes a value
|
|
# and a for-loop cannot consume the argument after it.
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--machine-only) RUN_OFFICER=false ;;
|
|
--officer-only) RUN_MACHINE=false ;;
|
|
--repo)
|
|
[[ -n "${2:-}" ]] || die "--repo needs a URL"
|
|
OFFICER_REPO="$2"
|
|
shift
|
|
;;
|
|
--repo=*) OFFICER_REPO="${1#--repo=}" ;;
|
|
-h | --help)
|
|
say "usage: install.sh [--machine-only | --officer-only] [--repo <url>]"
|
|
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"
|
|
say " --repo <url> clone the platform from here instead of the default"
|
|
say ""
|
|
say " The default is a private Gitea over SSH, which only authenticates on a"
|
|
say " machine whose key it already knows. Pass an https URL on a fresh box."
|
|
exit 0
|
|
;;
|
|
*) die "unknown option: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Exported so `officer-setup.sh` reads it from the environment and this script does
|
|
# not have to forward arguments it does not own. `lib/repo.sh` takes it as
|
|
# `${OFFICER_REPO:-<default>}`, so unset here still means the default there.
|
|
[[ -n "${OFFICER_REPO:-}" ]] && export OFFICER_REPO
|
|
|
|
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) ;;
|
|
*) die "unsupported system: $KERNEL. Officer installs on Linux and macOS." ;;
|
|
esac
|
|
|
|
SELF="$SCRIPT_DIR/install.sh"
|
|
|
|
# One report for the whole run, not one per half. Both scripts append to this
|
|
# file, so the person reviewing it sees a single account of what happened rather
|
|
# than two they have to stitch together and hope are complete.
|
|
#
|
|
# Exported before either half starts, and timestamped once here — if each script
|
|
# made its own name they would differ by however long the first one took.
|
|
export REPORT_FILE="${REPORT_FILE:-${HOME}/officer-install-report-$(date '+%Y%m%d-%H%M%S').md}"
|
|
|
|
# ── Privileges: asked for, not demanded ──
|
|
#
|
|
# Run this as YOURSELF. On Linux it needs root for apt, systemd units, useradd,
|
|
# netplan, ufw and for creating directories owned by the service account — so it
|
|
# asks, once, through sudo, and re-executes itself. Typing `sudo` yourself works
|
|
# too and changes nothing, but it should not be the price of starting.
|
|
#
|
|
# Variables are passed to sudo explicitly rather than with -E. `env_reset` is the
|
|
# sudoers default and strips the environment, which is how DATA_PATH was lost
|
|
# once already; naming them on the command line survives it.
|
|
#
|
|
# macOS never escalates. Homebrew refuses to run as root, and nothing in the
|
|
# macOS path needs it — the account running this IS the owner, so there is
|
|
# nothing to chown and nothing to drop privileges to.
|
|
if [[ "$KERNEL" != "Darwin" && "$EUID" -ne 0 ]]; then
|
|
command -v sudo >/dev/null 2>&1 || die "this needs root and sudo is not installed — run it as root"
|
|
say ""
|
|
say " This needs administrator rights. You will be asked for your password."
|
|
say ""
|
|
exec sudo \
|
|
OFFICER_ROOT="${OFFICER_ROOT:-}" \
|
|
SETUP_USERNAME="${SETUP_USERNAME:-}" \
|
|
MACHINE_ROLE="${MACHINE_ROLE:-}" \
|
|
REPORT_FILE="${REPORT_FILE:-}" \
|
|
OFFICER_REPO="${OFFICER_REPO:-}" \
|
|
bash "$SELF" ${ORIGINAL_ARGS[@]+"${ORIGINAL_ARGS[@]}"}
|
|
fi
|
|
|
|
|
|
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.
|
|
# ── Who says "you are still root" ──
|
|
#
|
|
# Both halves end as root and both need to say so, but only the LAST one to run
|
|
# should — otherwise a full install says it twice, once in the middle where it is
|
|
# wrong, because officer-setup is about to run and still needs the privilege.
|
|
#
|
|
# So the rule is "say it if nothing follows you", and this is the only place that
|
|
# knows whether anything does.
|
|
if $RUN_MACHINE; then
|
|
$RUN_OFFICER && export OFFICER_SETUP_FOLLOWS=1
|
|
bash "$MACHINE" || die "machine setup did not finish — fix what it reported, then run this again"
|
|
unset OFFICER_SETUP_FOLLOWS
|
|
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}"
|