Skip to content

Deploy pi from main within minutes: gated pull vs CI push #481

Description

@etrobert-bot

Goal

Pi converges on merged main within minutes instead of nightly. Promptness is the
requirement ("starts ≤1 min after CI green"); strict liveness (live ≤1 min) is
explicitly not required. France box deferred — this is pi-only.

Invariant: never deploy raw main

Every deploy gates on all-builds green. That guarantees both that the commit
builds and that the aarch64 closure is already in Cachix (tower's post-build
hook pushes synchronously during the CI build). This is an improvement over the
status quo, where the nightly autoUpgrade pulls raw main with no CI gating
at all.

CI cannot catch builds-fine-but-misbehaves-at-runtime changes; those deploy
minutes after merge. Accepted: rollback is manual (ssh pi +
nixos-rebuild switch --rollback), physical access as backstop.

Two candidate implementations

Build both as separate PRs and compare working code; the loser doesn't merge.

A. Gated pull

  • CI job (after all-builds, main only, contents: write) fast-forwards a
    deploy ref: git push origin HEAD:deploy.
  • Pi's system.autoUpgrade retargets from main to deploy, dates = "*:0/1"
    (or *:0/2), drop randomizedDelaySec.
  • ExecCondition on the nixos-upgrade service compares git ls-remote of
    deploy against a last-deployed-rev state file (configurationRevision is
    unset in this flake, so nixos-version --configuration-revision can't be the
    comparison source). Idle ticks cost one git ls-remote.
  • Latency: ~CI + 3–5 min (flake eval runs on the Pi 4 every real deploy).
  • No new credentials anywhere.

B. CI push

  • Deploy job (after all-builds, push to main only) on a tower runner:
    nixos-rebuild switch --flake .#pi --target-host <pi>. Eval happens on tower;
    pi only receives store paths and activates — no on-pi eval.
  • SSH key stored as a GitHub environment secret with the environment
    restricted to main — branch/PR workflow runs cannot read it, so "can push a
    branch" does not become "root on pi".
  • Key is from=-pinned to tower in pi's authorized_keys, so an exfiltrated
    key is useless off tower.
  • Existing nightly autoUpgrade kept unchanged as offline-catch-up backstop.
  • Latency: ~CI + ≤1 min.

Comparison criteria

  • End-to-end latency (merge → live on pi)
  • Moving parts / custom code each design actually required
  • Credential surface (A: none; B: main-scoped, from=-pinned key — verify the
    scoping holds in practice)
  • Failure behavior when pi is offline or the deploy fails mid-way

Explicitly rejected

  • Polling raw main faster: races CI/Cachix and wastes on-pi eval; frequency
    was never the bottleneck.
  • comin: no use for its testing-branch feature over ssh pi +
    nixos-rebuild test; dependency not earning its place.
  • Auto-rollback / health checks on pi: LAN physical access suffices (reconsider
    for the France box).
  • Deploy windows and allowReboot: complexity with no observed problem behind
    it. Kernel updates still apply only on manual reboot.

Doc follow-through (whichever lands)

  • Update "Pi auto-upgrades from main nightly — test before merging" in
    CLAUDE.md — merges now go live in minutes, so the discipline tightens.
  • Same note in modules/lan-dns.nix if repeated there.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions