From 9a65643769162734045c990df17272d9bf592b18 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 21 Oct 2026 01:25:19 +0200 Subject: [PATCH] swarm-bao granter stops retrying a rejected bootstrap token swarm-bao-granter-role exits 4 when the store is unsealed but refuses the bootstrap token, and Restart=on-failure retried that every 30s for as long as the token file existed. RestartPreventExitStatus=4 makes that permanent failure leave the unit failed once. Exit 1 (store sealed or unreachable) keeps retrying. On deploy: the unit goes red once instead of looping every 30s. The stale token on muede-lpt2 still needs the operator step (remove it or mint a fresh one); this does not clear it. --- docs/swarm/bao.md | 5 +++++ nix/host-modules/swarm-bao.nix | 3 +++ nix/module-eval/bao-grants.nix | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/docs/swarm/bao.md b/docs/swarm/bao.md index 268d307f..ec5dcc9c 100644 --- a/docs/swarm/bao.md +++ b/docs/swarm/bao.md @@ -40,6 +40,11 @@ the bootstrap commands. It never skips. `swarm-bao-granter-role` skips while the token file is absent, which is the steady state afterwards; the token's TTL means a forgotten file expires rather than lingering. +A sealed or unreachable store makes the unit retry every 30 s. A store that +refuses the token (expired, revoked, or missing the `bao-bootstrap` policy) +makes it exit 4 and stay `failed` with the one-time step in its log; mint a +fresh token or remove the file, then restart it. + After that, a new or changed `swarm-*` grant needs no operator step: the unit that writes it changes, and the deploy restarts it. A root step comes back only when the granter itself needs a path it lacks, such as a new mount. diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index a6ac67b7..a38576bf 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -3135,6 +3135,9 @@ in RemainAfterExit = true; Restart = "on-failure"; RestartSec = 30; + # Exit 4 is a refused bootstrap token, which retrying cannot fix: + # the unit stays failed until the operator replaces the token. + RestartPreventExitStatus = "4"; TimeoutStartSec = 60; # Runs only once the script above exits 0, which is when the # granter may first configure `auth/oidc`; the viewer unit exits 0 diff --git a/nix/module-eval/bao-grants.nix b/nix/module-eval/bao-grants.nix index 506c4fe5..976ba45a 100644 --- a/nix/module-eval/bao-grants.nix +++ b/nix/module-eval/bao-grants.nix @@ -1154,6 +1154,16 @@ let && u.serviceConfig.Restart == "on-failure" ) grantingUnitNames; } + { + # Exit 4 is the refused-token path, which no retry can fix; exit 1 (store + # sealed or unreachable) stays retried. + name = "the granter's unit stays failed on exit 4 and retries other failures"; + ok = + let + sc = baoGrantHere.systemd.services.swarm-bao-granter-role.serviceConfig; + in + sc.RestartPreventExitStatus == "4" && sc.Restart == "on-failure"; + } { # Every reader and granting unit's start pulls the PKI unit in, so a mass # container start outruns systemd's default limit of 5 starts in 10s.