From 7ef5dc6e971755b52871d0ba7d0e44529c57ee78 Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 10 Jul 2026 04:13:13 +0200 Subject: [PATCH 1/2] feat(pi): deploy on CI green instead of waiting for nightly Add a deploy-pi job to the build workflow that runs `nixos-rebuild switch --flake .#pi --target-host root@pi` from tower's self-hosted runner once all-builds passes. Gating on all-builds keeps the invariant that we never deploy raw main: the aarch64 closure is already in Cachix, pushed by tower's post-build hook during CI. The deploy key is scoped to main via the pi-deploy environment (deployment-branch policy) and pinned to tower's addresses via `from=` in pi's authorized_keys, so an exfiltrated key is useless off tower. The nightly system.autoUpgrade stays as the offline-catch-up backstop. Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 32 ++++++++++++++++++++++++++++++ CLAUDE.md | 7 +++++-- modules/hosts/pi/configuration.nix | 7 +++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16684c4f..154136ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,3 +130,35 @@ jobs: || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} run: exit 1 + + # Deploy the freshly-built, cache-populated pi closure. Gated on all-builds + # succeeding (default `needs` semantics), so we never deploy raw main — the + # aarch64 closure is already in Cachix, pushed by tower's post-build hook. + # The pi-deploy environment restricts PI_DEPLOY_SSH_KEY to the main branch. + deploy-pi: + name: deploy-pi + needs: all-builds + if: ${{ github.event_name == 'push' }} + runs-on: [self-hosted, Linux] + environment: pi-deploy + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Deploy to pi + env: + PI_DEPLOY_SSH_KEY: ${{ secrets.PI_DEPLOY_SSH_KEY }} + run: | + workdir=$(mktemp --directory) + trap 'rm --recursive --force "$workdir"' EXIT + key="$workdir/key" + umask 077 + printf '%s\n' "$PI_DEPLOY_SSH_KEY" > "$key" + export NIX_SSHOPTS="-i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=$workdir/known_hosts" + nix shell nixpkgs#nixos-rebuild nixpkgs#openssh --command \ + nixos-rebuild switch \ + --flake ".#pi" \ + --target-host root@100.68.23.44 \ + --accept-flake-config \ + --print-build-logs diff --git a/CLAUDE.md b/CLAUDE.md index 020a74b6..93e9d24b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -116,8 +116,11 @@ the insecure package, the assertion fails and prompts removal. are port-forwarded to tower (`.10`). **LAN DHCP + DNS:** served by `pi` via `dnsmasq` (`modules/lan-dns.nix`, -listening on `end0`, static `.18`). Pi auto-upgrades from main nightly — test -before merging. +listening on `end0`, static `.18`). Merges to main deploy to pi within ~1 min of +CI going green: the `deploy-pi` job in `.github/workflows/build.yml` runs +`nixos-rebuild switch --flake .#pi --target-host root@pi` from tower's +self-hosted runner after `all-builds` passes. A nightly `system.autoUpgrade` +stays as the offline-catch-up backstop. **Static LAN addresses:** `pi end0` `.18` (MAC `DC:A6:32:13:51:14`), `tower` `.10` (motherboard NIC `enp11s0`, static via NetworkManager in diff --git a/modules/hosts/pi/configuration.nix b/modules/hosts/pi/configuration.nix index 03c53511..a584e11a 100644 --- a/modules/hosts/pi/configuration.nix +++ b/modules/hosts/pi/configuration.nix @@ -38,6 +38,13 @@ randomizedDelaySec = "5min"; }; + # CI deploys to pi via nixos-rebuild --target-host root@pi. The key is pinned + # to tower's addresses (Tailscale + LAN) so an exfiltrated key is useless + # off tower. Private half lives only at tower:/home/soft/.ssh/pi-deploy. + users.users.root.openssh.authorizedKeys.keys = [ + ''from="100.103.91.42,192.168.0.10" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINhMQOKCOSv2wUSKkWerWUQsj3e+8Ko1zNdm553hkIpM pi-deploy CI'' + ]; + networking.hostName = "pi"; networking.networkmanager = { From 1695b34ea9e992bf21c5f6f8bc18ef921394dacb Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 10 Jul 2026 04:15:46 +0200 Subject: [PATCH 2/2] fix(ci): wrap NIX_SSHOPTS to satisfy yamllint line-length Co-Authored-By: Claude Fable 5 --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 154136ea..3d5be49a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -155,7 +155,9 @@ jobs: key="$workdir/key" umask 077 printf '%s\n' "$PI_DEPLOY_SSH_KEY" > "$key" - export NIX_SSHOPTS="-i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=$workdir/known_hosts" + export NIX_SSHOPTS="-i $key -o IdentitiesOnly=yes" + NIX_SSHOPTS="$NIX_SSHOPTS -o StrictHostKeyChecking=accept-new" + NIX_SSHOPTS="$NIX_SSHOPTS -o UserKnownHostsFile=$workdir/known_hosts" nix shell nixpkgs#nixos-rebuild nixpkgs#openssh --command \ nixos-rebuild switch \ --flake ".#pi" \