fuzzyworld

fuzzyworld-tools / files / runner / install-laptop.sh

install-laptop.sh

3.5 KB · history · blame

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.
12set -euo pipefail
13cd "$(dirname "$0")"
14DIR=${FW_RUNNER_DIR:-$HOME/.fw-runner}
15HOST=${FW_HOST:-dh}
16LABEL=net.fuzzyworld.fw-runner
17PLIST=$HOME/Library/LaunchAgents/$LABEL.plist
18
19if [ "${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
24fi
25
26mkdir -p "$DIR/.ssh" "$DIR/secrets" "$DIR/work"
27chmod 700 "$DIR/.ssh" "$DIR/secrets"
28cp fw-runner.py shoot.py "$DIR/"
29chmod +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
35FW_RUNNER_NAME=$(scutil --get LocalHostName)
36FW_RUNNER_TAGS=docker,chrome
37FW_RUNNER_POLITE=1
38FW_RUNNER_CPUS=2
39FW_RUNNER_MEMORY=4g
40ENV
41
42cat > "$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>
56XML
57launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
58launchctl bootstrap "gui/$(id -u)" "$PLIST"
59echo "runner installed in $DIR (launchd: every 2 minutes, polite; settings in $DIR/runner.env, log in $DIR/runner.log)"
60
61pub=$(cat "$DIR/.ssh/id_ed25519.pub")
62line="restrict,command=\"/home/fuzzygamer/fw/tools/server/runner-api\" $pub"
63if [ "${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)"
66else
67 echo "To let it take jobs, add this line to ~/.ssh/authorized_keys on DreamHost (or rerun with --authorize):"
68 echo " $line"
69fi
70echo "Repo secrets for jobs that deploy go in $DIR/secrets/<repo>.env"