From 3b8ab17240e41db73b3e3dc9e8b3173dd7ca7d37 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 2 Nov 2026 01:52:17 +0100 Subject: [PATCH] bound the forge's SSO source setup and retry it with a growing delay --- nix/host-modules/hive-forge/default.nix | 20 +++++++++++++++++--- nix/module-eval/swarm-services-switch.nix | 18 ++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index 2cbd39d1..dfeaa30e 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -113,6 +113,9 @@ let oneWord = path: builtins.match ".*[[:space:]].*" path == null; forwarderCredential = id: "/run/credentials/${forwarderUnit}.service/${id}"; mintUnit = "forgejo-swarm-controller-account"; + # `timeout` arguments for the forgejo calls that fetch the issuer's discovery + # document; the CLI has no client timeout of its own. + ssoCallTimeout = "--kill-after=3 20"; mintCredential = id: "/run/credentials/${mintUnit}.service/${id}"; # ROOT_URL forgejo advertises in clone links + outbound URLs. @@ -897,7 +900,10 @@ in after = [ "forgejo.service" ]; requires = [ "forgejo.service" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig = { + # ../lib/store-retry.nix: a secret that is not there yet, or an + # authelia that is not answering, is retried. + inherit (storeRetry) startLimitBurst startLimitIntervalSec; + serviceConfig = storeRetry.serviceConfig // { Type = "oneshot"; RemainAfterExit = true; User = "forgejo"; @@ -907,6 +913,14 @@ in # it, and this unit fails to start until then; that unit starts # it again once the file is written. LoadCredential = [ "oidc-client-secret:${oidcSecretPath}" ]; + # This unit is in the boot wait of a container with a one-minute + # start limit. Each registration call is capped at 20 s, so an + # authelia that never answers fails the attempt, and the growing + # delay below retries it. + TimeoutStartSec = 30; + RestartSec = 5; + RestartSteps = 6; + RestartMaxDelaySec = 300; }; # `FORGEJO_CUSTOM` (the forgejo-prefixed name, not `GITEA_CUSTOM`) # is how the CLI finds the app.ini upstream's module wrote. @@ -966,12 +980,12 @@ in id=$(forgejo admin auth list \ | grep "[[:space:]]${ssoSourceName}[[:space:]]" \ | cut -f1) - forgejo admin auth update-oauth --id "$id" \ + timeout ${ssoCallTimeout} forgejo admin auth update-oauth --id "$id" \ --name ${lib.escapeShellArg ssoSourceName} \ --secret "$secret" "''${args[@]}" echo "updated OIDC login source ${ssoSourceName} (id $id)" else - forgejo admin auth add-oauth \ + timeout ${ssoCallTimeout} forgejo admin auth add-oauth \ --name ${lib.escapeShellArg ssoSourceName} \ --secret "$secret" "''${args[@]}" echo "added OIDC login source ${ssoSourceName}" diff --git a/nix/module-eval/swarm-services-switch.nix b/nix/module-eval/swarm-services-switch.nix index 2092f3a0..eed1cfbe 100644 --- a/nix/module-eval/swarm-services-switch.nix +++ b/nix/module-eval/swarm-services-switch.nix @@ -267,10 +267,6 @@ let "victoriametrics.service" ]; } - { - why = "known gap: registering the login source makes an outbound HTTPS call to authelia, and the unit has neither a timeout nor a Restart="; - units = [ "forgejo-sso-source.service" ]; - } ]; allowedUnstated = key: @@ -1441,7 +1437,7 @@ let } { # The presence arms for the check above: it reads real units and real - # limits. Each named unit states its budget (30 s, 30 s, 30 s, 20 s, 20 s), + # limits. Each named unit states its budget (30 s, 30 s, 30 s, 30 s, 20 s, 20 s), # so it is found at a limit equal to it and not one second above (the # boundary), and the container limits are the ones the check reads. name = "the boot-wait check finds the units that state a budget, at the boundary, and reads each container's own limit"; @@ -1453,8 +1449,11 @@ let secrets = "authelia-swarm-secrets.service"; sync = "swarm-matrix-appservice-sync.service"; render = "swarm-matrix-appservice-render.service"; + sso = "forgejo-sso-source.service"; in lib.elem mint (keys "hive-forge" 30) + && lib.elem sso (keys "hive-forge" 30) + && !(lib.elem sso (keys "hive-forge" 31)) && lib.elem fetch (keys "hive-forge" 30) && !(lib.elem mint (keys "hive-forge" 31)) && !(lib.elem fetch (keys "hive-forge" 31)) @@ -1480,9 +1479,16 @@ let sc = u.serviceConfig; in sc.Restart == "on-failure" && sc.RestartSteps > 0 && sc.RestartMaxDelaySec > sc.RestartSec; + forge = serviceContainers.hive-forge.config.systemd.services; authelia = serviceContainers.swarm-authelia.config.systemd.services; in - grows serviceContainers.hive-forge.config.systemd.services.forgejo-swarm-controller-account + grows forge.forgejo-swarm-controller-account + && grows forge.forgejo-sso-source + && forge.forgejo-sso-source.serviceConfig.TimeoutStartSec == 30 + # Both registration calls fetch the issuer's discovery document and + # run under a client timeout. + && lib.hasInfix "timeout --kill-after=3 20 forgejo admin auth add-oauth" forge.forgejo-sso-source.script + && lib.hasInfix "timeout --kill-after=3 20 forgejo admin auth update-oauth" forge.forgejo-sso-source.script && grows authelia.authelia-swarm-secrets && authelia.swarm-authelia-bridge.serviceConfig.TimeoutStartSec == 10; }