From 012fecef5ed25fdf5c83a82930c503e418e96a1a Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 22 Sep 2026 20:14:46 +0200 Subject: [PATCH 1/2] fix(deploy): trust the Forgejo host key so auto-upgrade can fetch system.autoUpgrade fetches the flake over ssh as root. A machine whose root has never connected by hand has no known_hosts entry, so the run dies at 'Host key verification failed' before it even reaches authentication. batm3 did exactly that, silently, from its 2026-08-06 install until 09-22: six weeks on its install generation while a unit nobody was watching reported failure every night. sintra only ever worked because a human had ssh'd as root once and accepted the key. Declaring the key means a freshly flashed ATM updates from first boot with no manual step. Verified against the key sintra's root already trusts. Refs #98 Co-Authored-By: Claude Fable 5.1 --- deploy/nixos/configuration.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deploy/nixos/configuration.nix b/deploy/nixos/configuration.nix index 2306f5a..330421c 100644 --- a/deploy/nixos/configuration.nix +++ b/deploy/nixos/configuration.nix @@ -159,6 +159,18 @@ # Auto-updates (optional - disabled by default for stability) # system.autoUpgrade.enable = false; + # Trust the Forgejo host key up front. system.autoUpgrade fetches the flake + # over ssh AS ROOT, and a machine whose root has never connected by hand has + # no known_hosts entry, so every nightly run dies at + # "Host key verification failed" before it reaches authentication. batm3 did + # exactly that, silently, from its 2026-08-06 install until 09-22 (#98): it + # sat on its install generation for six weeks while reporting a failed unit + # nobody was watching. sintra only ever worked because a human had ssh'd as + # root once and accepted the key. Declaring it means a freshly flashed ATM + # can update from first boot with no manual step. + programs.ssh.knownHosts."git.atitlan.io".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMlo3f05o4+bk0+8x2VG91o9GubshOb46HmBPvND9pJx"; + # pragma: allowlist secret # Ensure WireGuard private key directory exists with correct permissions system.activationScripts.wireguard-key = '' From 71b2691f8c1b3a39f356b23860ac6481414301ca Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 22 Sep 2026 20:14:46 +0200 Subject: [PATCH 2/2] fix(deploy): don't fail activation over an unprovisioned WireGuard tunnel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wg0.key is written per machine after flashing. Until it is, the unit's `wg set … private-key` exits 1 with 'fopen: No such file or directory', and one failed unit makes switch-to-configuration exit 4 — which marks the whole nightly system.autoUpgrade run as failed even though the new generation applied. sintra has reported a broken updater on that basis alone; its tunnel was never provisioned and wg0 has never existed. Skip the unit when there is no key rather than failing activation over an interface that was never set up. A provisioned machine is unaffected. Guarded on wg0 still being declared so the live image, which mkForce's the interfaces away, doesn't inherit a unit with no ExecStart. Refs #98 Co-Authored-By: Claude Fable 5.1 --- deploy/nixos/configuration.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/deploy/nixos/configuration.nix b/deploy/nixos/configuration.nix index 330421c..652de98 100644 --- a/deploy/nixos/configuration.nix +++ b/deploy/nixos/configuration.nix @@ -181,6 +181,20 @@ fi ''; + # The tunnel is operator-provisioned: wg0.key is written per machine after + # flashing, and until it is, `wg set … private-key` exits 1 with + # "fopen: No such file or directory". One failed unit makes + # switch-to-configuration exit 4, which marks the entire nightly + # system.autoUpgrade run as failed — so an ATM that simply never had its + # tunnel provisioned reports a broken updater for the life of the machine + # (sintra, #98). Skip the unit when there is no key instead of failing + # activation over an interface that was never set up; a provisioned machine + # is unaffected. Guarded on wg0 still being declared so the live image, + # which mkForce's the interfaces away, doesn't get a unit with no ExecStart. + systemd.services = lib.mkIf (config.networking.wireguard.interfaces ? wg0) { + wireguard-wg0.unitConfig.ConditionPathExists = "/var/lib/wireguard/wg0.key"; + }; + # In-place rename migration: lamassu user → bitspire user. # Runs after `users` activation so the bitspire user exists with its UID. # Idempotent: re-running on an already-migrated system is a chown no-op.