From 965f5f5ba0cdb5a2ed49c61e2535f4a7719a4310 Mon Sep 17 00:00:00 2001 From: thethreemagi Date: Mon, 27 Jul 2026 23:00:22 +0100 Subject: [PATCH] Add first-boot orchestrator Runs the stages in order and fails soft. A stage failure must never wedge boot: there is no keyboard attached to this box, so a machine that comes up with sshd and a broken GPU driver is recoverable and one that hangs is not. --- bootstrap.sh | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 bootstrap.sh diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..3ed4aa4 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# ============================================================================= +# mediabox-bootstrap — first-boot orchestrator +# +# Runs ONCE from mediabox-firstboot.service, but every stage is idempotent so +# you can re-run this by hand at any time: +# sudo /srv/mediabox-bootstrap/bootstrap.sh +# +# Or run a single stage: +# sudo /srv/mediabox-bootstrap/scripts/20-cifs.sh +# +# DESIGN RULE: no stage may leave the box unbootable or unreachable. Every +# stage that can fail, fails soft and logs. sshd is already up by the time +# this runs; keeping it that way is the whole safety net on a headless box. +# ============================================================================= +set -uo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STATE_DIR="/var/lib/mediabox" +LOG="/var/log/mediabox-bootstrap.log" + +mkdir -p "$STATE_DIR" +exec > >(tee -a "$LOG") 2>&1 + +say() { printf '\n\033[1;36m=== %s\033[0m\n' "$*"; } +ok() { printf '\033[1;32m [ok]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m [warn]\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31m [FAIL]\033[0m %s\n' "$*"; } + +[ "$(id -u)" -eq 0 ] || { fail "must run as root"; exit 1; } + +FAILED=() + +run_stage() { + local script="$1" name + name="$(basename "$script")" + say "$name" + if [ ! -x "$script" ]; then chmod +x "$script" 2>/dev/null || true; fi + if "$script"; then + ok "$name complete" + else + fail "$name exited $? — continuing (see $LOG)" + FAILED+=("$name") + fi +} + +say "mediabox bootstrap starting $(date -Is)" +echo "host: $(hostname) kernel: $(uname -r) ip: $(hostname -I)" + +run_stage "$REPO_DIR/scripts/00-preflight.sh" +run_stage "$REPO_DIR/scripts/10-secrets.sh" +run_stage "$REPO_DIR/scripts/20-cifs.sh" +run_stage "$REPO_DIR/scripts/30-nvidia.sh" +run_stage "$REPO_DIR/scripts/40-shell-mcp.sh" + +say "bootstrap summary" +if [ ${#FAILED[@]} -eq 0 ]; then + ok "all stages completed" + touch "$STATE_DIR/firstboot.done" +else + warn "stages needing attention: ${FAILED[*]}" + warn "NOT marking first-boot done — fix and re-run $0" +fi + +cat <<'NEXT' + +-------------------------------------------------------------------------- + REMAINING STEPS — deliberately NOT automated +-------------------------------------------------------------------------- + 1. Write the NAS password into the credentials file (over the MCP, never + onto the USB): + printf 'username=<nas-user>\npassword=<nas-pass>\ndomain=WORKGROUP\n' \ + > /etc/cifs/korval.cred + chmod 600 /etc/cifs/korval.cred + systemctl daemon-reload + /srv/mediabox-bootstrap/scripts/20-cifs.sh --verify + + 2. Verify Quick Sync before deploying Plex: + vainfo --display drm --device /dev/dri/renderD128 + + 3. Deploy Plex. PLEX_CLAIM expires in 4 minutes, so this is human-in-the-loop: + # get a fresh token from https://plex.tv/claim THEN immediately: + PLEX_CLAIM=claim-xxxxx /srv/mediabox-bootstrap/scripts/50-plex.sh + + 4. Repoint NPM proxy host 42 from mediabox-mcp:8103 to 10.0.1.20:8103, + then delete the mediabox-mcp container and its key on arrsstack. + + 5. Leave D: alone until the soak is done. +-------------------------------------------------------------------------- +NEXT + +exit 0