diff --git a/scripts/_install_sudoers.sh b/scripts/_install_sudoers.sh new file mode 100755 index 0000000..3ae43da --- /dev/null +++ b/scripts/_install_sudoers.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Installs a TEMPORARY /etc/sudoers.d/mac-setup (NOPASSWD for seth) so an unattended setup run +# has sudo for Homebrew's long, child-spawning install. Reads the sudo password from stdin. +# Called by run.sh, which removes the drop-in afterward; a 40-min self-destruct is the backstop. +set -euo pipefail +read -r SUDO_PW +tmp=$(mktemp) +printf 'seth ALL=(ALL) NOPASSWD: ALL\n' > "$tmp" +sudo -S -p '' -v <<<"$SUDO_PW" # prime once from the piped password +sudo visudo -cf "$tmp" # validate OFF to the side (bad sudoers breaks sudo) +sudo install -m 440 -o root -g wheel "$tmp" /etc/sudoers.d/mac-setup +rm -f "$tmp" +nohup sudo bash -c 'sleep 2400; rm -f /etc/sudoers.d/mac-setup' >/dev/null 2>&1 & # survives ssh drop +echo "[set] temp NOPASSWD drop-in installed (+40m self-destruct)" diff --git a/scripts/run.sh b/scripts/run.sh index a846cd1..030c7c2 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,16 +1,31 @@ #!/bin/bash -# From steel141: sync repo to the Mac, run setup there, pull backups back, then do the tank side. +# From steel141: sync repo to the Mac, run setup there UNATTENDED, pull backups back, then tank side. # Usage: scripts/run.sh [--no-tank] -# sudo on the Mac is primed from $HOMELAB_PASSWORD over a forced pty (ssh -tt) so this works from a -# non-interactive session; with the var unset it falls back to an interactive prompt. +# +# Homebrew's install needs sudo for 10+ min across child processes. We grant a TEMPORARY +# /etc/sudoers.d/mac-setup (NOPASSWD) for the run and guarantee its removal three ways: +# 1. explicit removal after setup, 2. an EXIT trap (covers failures / a dropped ssh from here), +# 3. a 40-min self-destruct on the Mac itself (covers steel141 losing the network). +# $HOMELAB_PASSWORD is used once (piped, never in argv) to install the drop-in. set -euo pipefail cd "$(dirname "$0")/.." +[[ -n ${HOMELAB_PASSWORD:-} ]] || { echo "HOMELAB_PASSWORD must be set"; exit 1; } + rsync -a --delete --exclude .git --exclude .backup --exclude Brewfile.lock.json ./ mac:mac/ -if [[ -n ${HOMELAB_PASSWORD:-} ]]; then - printf '%s\n' "$HOMELAB_PASSWORD" | ssh -tt mac 'sudo -S -v && HOSTNAME_WANT=mac ~/mac/scripts/setup.sh; exit' | tr -d '\r' -else - ssh -t mac 'HOSTNAME_WANT=mac ~/mac/scripts/setup.sh' -fi + +cleanup(){ printf '%s\n' "$HOMELAB_PASSWORD" | ssh -o BatchMode=yes mac "sudo -S -p '' rm -f /etc/sudoers.d/mac-setup 2>/dev/null; sudo -K 2>/dev/null" || true; } +trap cleanup EXIT + +# Install temp NOPASSWD drop-in (installer is a repo file, rsynced above; password piped to its stdin). +printf '%s\n' "$HOMELAB_PASSWORD" | ssh -o BatchMode=yes mac 'bash ~/mac/scripts/_install_sudoers.sh' + +# sudo is now passwordless on the Mac; run setup unattended (Homebrew + everything). +ssh -o BatchMode=yes mac "HOSTNAME_WANT=mac ~/mac/scripts/setup.sh" + +# Explicit removal + confirm; then disarm the trap so it doesn't double-run. +cleanup; trap - EXIT +ssh -o BatchMode=yes mac 'test ! -e /etc/sudoers.d/mac-setup && echo "[ok] temp sudoers removed" || echo "[WARN] temp sudoers STILL PRESENT"' + mkdir -p .backup/mac && rsync -a mac:.mac-setup-backup/ .backup/mac/ [[ ${1:-} == --no-tank ]] && exit 0 PUB=$(ssh mac cat .ssh/id_ed25519.pub) diff --git a/scripts/setup.sh b/scripts/setup.sh index f81f1fe..3e8da5b 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,9 +12,7 @@ log(){ printf '\033[1;33m[%s]\033[0m %s\n' "$1" "$2"; } # ---------- preflight ---------- [[ $(uname -m) == arm64 && $(sw_vers -productVersion) == 26.* ]] || { echo "not the Mac this was written for"; exit 1; } [[ ${HOSTNAME_WANT:-} ]] || { echo "HOSTNAME_WANT= required"; exit 1; } -sudo -v # ticket already primed by run.sh over the pty; prompts if run by hand -( while true; do sudo -n true; sleep 50; done ) & -KEEPALIVE=$!; trap 'kill $KEEPALIVE 2>/dev/null' EXIT +sudo -n true 2>/dev/null || { echo "passwordless sudo unavailable — run via scripts/run.sh (it installs a temp drop-in)"; exit 1; } # ---------- helpers ---------- backup_domain(){ # once per domain per run