fuzzyworld-tools / files / runner / install-laptop.sh
install-laptop.sh
| 1 | #!/bin/bash |
| 2 | # Install or update the CI runner on this Mac, as a polite second runner. Run on the laptop: |
| 3 | # |
| 4 | # runner/install-laptop.sh copy the runner, make its ssh key, add the launchd job |
| 5 | # runner/install-laptop.sh --authorize ...and allow that key on DreamHost (restricted to server/runner-api) |
| 6 | # runner/install-laptop.sh --remove stop it and remove the launchd job (files stay) |
| 7 | # |
| 8 | # The runner wakes every 2 minutes and claims a job only when the Mac is on AC power, the CPU is mostly idle |
| 9 | # and Docker is running (FW_RUNNER_POLITE=1). Containers get a low CPU share and FW_RUNNER_CPUS cores. |
| 10 | # Settings (environment): FW_RUNNER_DIR (default ~/.fw-runner), FW_HOST (DreamHost ssh alias, default: dh). |
| 11 | # Needs Docker (Docker Desktop, OrbStack, ...) and Python 3 with `venv`; PyYAML is installed into the runner's own venv. |
| 12 | set -euo pipefail |
| 13 | cd "$(dirname "$0")" |
| 14 | DIR=${FW_RUNNER_DIR:-$HOME/.fw-runner} |
| 15 | HOST=${FW_HOST:-dh} |
| 16 | LABEL=net.fuzzyworld.fw-runner |
| 17 | PLIST=$HOME/Library/LaunchAgents/$LABEL.plist |
| 18 | |
| 19 | if [ "${1:-}" = --remove ]; then |
| 20 | launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true |
| 21 | rm -f "$PLIST" |
| 22 | echo "launchd job removed; $DIR is untouched (delete it to forget the key, secrets and logs)" |
| 23 | exit 0 |
| 24 | fi |
| 25 | |
| 26 | mkdir -p "$DIR/.ssh" "$DIR/secrets" "$DIR/work" |
| 27 | chmod 700 "$DIR/.ssh" "$DIR/secrets" |
| 28 | cp fw-runner.py shoot.py "$DIR/" |
| 29 | chmod +x "$DIR/fw-runner.py" |
| 30 | [ -x "$DIR/venv/bin/python" ] || python3 -m venv "$DIR/venv" |
| 31 | "$DIR/venv/bin/python" -c 'import yaml' 2>/dev/null || "$DIR/venv/bin/pip" -q install pyyaml |
| 32 | [ -f "$DIR/.ssh/id_ed25519" ] || ssh-keygen -q -t ed25519 -N '' -C "fw-runner $(scutil --get LocalHostName) (CI only on DreamHost)" -f "$DIR/.ssh/id_ed25519" |
| 33 | # The first install writes the settings; later installs leave your edits alone. |
| 34 | [ -f "$DIR/runner.env" ] || cat > "$DIR/runner.env" <<ENV |
| 35 | FW_RUNNER_NAME=$(scutil --get LocalHostName) |
| 36 | FW_RUNNER_TAGS=docker,chrome |
| 37 | FW_RUNNER_POLITE=1 |
| 38 | FW_RUNNER_CPUS=2 |
| 39 | FW_RUNNER_MEMORY=4g |
| 40 | ENV |
| 41 | |
| 42 | cat > "$PLIST" <<XML |
| 43 | <?xml version="1.0" encoding="UTF-8"?> |
| 44 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 45 | <plist version="1.0"><dict> |
| 46 | <key>Label</key><string>$LABEL</string> |
| 47 | <key>ProgramArguments</key><array><string>$DIR/venv/bin/python</string><string>$DIR/fw-runner.py</string></array> |
| 48 | <key>StartInterval</key><integer>120</integer> |
| 49 | <key>EnvironmentVariables</key><dict><key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.orbstack/bin:/Applications/Docker.app/Contents/Resources/bin</string></dict> |
| 50 | <key>StandardOutPath</key><string>$DIR/runner.log</string> |
| 51 | <key>StandardErrorPath</key><string>$DIR/runner.log</string> |
| 52 | <key>ProcessType</key><string>Background</string> |
| 53 | <key>LowPriorityIO</key><true/> |
| 54 | <key>Nice</key><integer>10</integer> |
| 55 | </dict></plist> |
| 56 | XML |
| 57 | launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true |
| 58 | launchctl bootstrap "gui/$(id -u)" "$PLIST" |
| 59 | echo "runner installed in $DIR (launchd: every 2 minutes, polite; settings in $DIR/runner.env, log in $DIR/runner.log)" |
| 60 | |
| 61 | pub=$(cat "$DIR/.ssh/id_ed25519.pub") |
| 62 | line="restrict,command=\"/home/fuzzygamer/fw/tools/server/runner-api\" $pub" |
| 63 | if [ "${1:-}" = --authorize ]; then |
| 64 | ssh "$HOST" "grep -qF '$pub' ~/.ssh/authorized_keys || echo '$line' >> ~/.ssh/authorized_keys" |
| 65 | echo "key allowed on $HOST (runner-api only)" |
| 66 | else |
| 67 | echo "To let it take jobs, add this line to ~/.ssh/authorized_keys on DreamHost (or rerun with --authorize):" |
| 68 | echo " $line" |
| 69 | fi |
| 70 | echo "Repo secrets for jobs that deploy go in $DIR/secrets/<repo>.env" |