diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 0a7d22f..433ba77 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -65,10 +65,15 @@ jobs: - name: Deploy guard preflight run: | + # The gitea runner runs on muffin itself, so the /nix/store path + # produced by "Build muffin" is already resolvable on the target. + # SSH to root is still needed so the check can read agenix secrets + # (e.g. /run/agenix/jellyfin-api-key is mode 0400 root). + guard=$(readlink -f result)/sw/bin/deploy-guard-check ssh -i /run/agenix/ci-deploy-key \ -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile=/etc/ci-known-hosts \ - root@server-public deploy-guard-check + root@server-public "$guard" - name: Deploy via deploy-rs run: | diff --git a/deploy.sh b/deploy.sh index 707bcda..b35b661 100755 --- a/deploy.sh +++ b/deploy.sh @@ -13,6 +13,13 @@ # the remote deploy via deploy-rs when explicitly named. # # DEPLOY_GUARD_FORCE=1 is equivalent to passing --force. +# +# The preflight builds the guard derivation locally, copies it to muffin's +# nix store, then invokes it by /nix/store path over SSH as root (so the +# jellyfin check can read /run/agenix/jellyfin-api-key). Building the exact +# binary we're about to deploy avoids the bootstrap gap where +# /run/current-system/sw/bin/deploy-guard-check may not yet exist on the target +# (first deploy of the feature, post-rollback wiping it, etc). set -eu @@ -30,11 +37,15 @@ case "$arg" in if [ "$force" = "1" ]; then echo "deploy-guard: preflight skipped (--force)" else - # Single SSH probe. Exit 255 is a connectivity failure; treat as a hard - # abort — without the preflight there is no other gate that prevents - # deploy-rs from partially activating while users are online. + echo "deploy-guard: building preflight binary..." + guard=$(nix build --no-link --print-out-paths \ + '.#nixosConfigurations.muffin.config.system.build.deployGuardCheck') + + echo "deploy-guard: copying to muffin..." + nix copy --to ssh-ng://root@server-public "$guard" + output=$(ssh -o BatchMode=yes -o ConnectTimeout=5 \ - root@server-public deploy-guard-check 2>&1) && rc=0 || rc=$? + root@server-public "$guard/bin/deploy-guard-check" 2>&1) && rc=0 || rc=$? if [ "$rc" -eq 0 ]; then [ -n "$output" ] && printf '%s\n' "$output" diff --git a/modules/server-deploy-guard.nix b/modules/server-deploy-guard.nix index 9a0c290..d3302d1 100644 --- a/modules/server-deploy-guard.nix +++ b/modules/server-deploy-guard.nix @@ -159,6 +159,13 @@ in config = lib.mkIf cfg.enable { environment.systemPackages = [ aggregator ]; + # Expose the aggregator as a named system build so preflight drivers + # (deploy.sh, CI) can build just this derivation and invoke it by its + # /nix/store path — avoiding the bootstrap gap where + # /run/current-system/sw/bin/deploy-guard-check may not yet exist on the + # target (first deploy of the feature, post-rollback, etc). + system.build.deployGuardCheck = aggregator; + assertions = [ { assertion = cfg.checks != { };