diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16684c4f..3d5be49a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,3 +130,37 @@ 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" + 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" \ + --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 = {