The secret store's pki root issues every hive's gateway leaf and the
swarm's service names, so the root swarm-ca.service generated on the
host anchors nothing and its key is a secret outside the store.
- swarm-ca.nix is gone: swarm-ca.service, root.pem and root-key.pem,
and the swarm.ca.{autoConfigure,stateDir,validityDays} options. A
definition of any of them fails evaluation with a removal message
(swarm-ca-removed.nix), and a tmpfiles rule deletes /var/lib/swarm-ca
on every host. singleHostSwarm no longer sets a swarm.* option.
- hive-tls-ca drops the $root slot: trust-bundle.pem holds the store's
pki root and a hive CA's public ca.pem/ca-previous.pem only.
- hivectl peer-config no longer prints the scp line for the swarm root;
the store's pki root is the one step.
- The gateway's cert import no longer stands a hive leaf or a throwaway
in for a missing swarm-services leaf where it Requires= the issuing
unit: it fails, naming swarm-services-cert.service. The stand-in stays
where the gateway does not wait (no swarm name, or the store's UI
only, whose passthrough must start with the store sealed).
Comments and docs that named swarm-ca.nix, swarm.ca.* or the host root
now name the store's pki root.
Refs #4588
195 lines
8.2 KiB
Nix
195 lines
8.2 KiB
Nix
# The swarm's forge as every hive sees it: the names and ports it answers on,
|
||
# the URLs it advertises, and the OIDC client it is registered as. What the
|
||
# host running it decides, and the container itself, are in ./default.nix.
|
||
{
|
||
lib,
|
||
config,
|
||
...
|
||
}:
|
||
let
|
||
cfg = config.services.hyperhive.swarm.forge;
|
||
gatewayCfg = config.services.hyperhive.gateway;
|
||
swarmDomain = config.services.hyperhive.swarm.domain;
|
||
|
||
# Forgejo's name for the login source. Duplicated in ./default.nix, which
|
||
# registers the source under it.
|
||
ssoSourceName = "authelia";
|
||
|
||
# Forgejo's OAuth2 callback shape. `ssoSourceName` and `effectiveRootUrl`
|
||
# must equal their copies in ./default.nix: a mismatch is a rejected login
|
||
# with no error text worth reading.
|
||
ssoRedirectUri = "${effectiveRootUrl}user/oauth2/${ssoSourceName}/callback";
|
||
|
||
# Forgejo's `ROOT_URL`, duplicated from ./default.nix, which documents its
|
||
# shape.
|
||
defaultRootUrl =
|
||
let
|
||
portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}";
|
||
in
|
||
"https://${cfg.domain}${portSuffix}/";
|
||
effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl;
|
||
in
|
||
{
|
||
# Forge lives under `swarm`, beside the other swarm-global services. One
|
||
# rename for the top-level `services.hyperhive.forge` namespace: the
|
||
# subtree comes with it, so existing hives keep evaluating and get one
|
||
# warning naming both paths.
|
||
imports = [
|
||
(lib.mkRenamedOptionModule
|
||
[ "services" "hyperhive" "forge" ]
|
||
[ "services" "hyperhive" "swarm" "forge" ]
|
||
)
|
||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "forge" "sso" "enable" ] ''
|
||
SSO is no longer optional: the forge always registers the swarm's
|
||
authelia as a login source.
|
||
|
||
Removed rather than defaulted to true so a config that turned it
|
||
OFF fails here, where the line is, instead of silently gaining a
|
||
login provider on the next rebuild. Drop the line; if it was
|
||
false, set services.hyperhive.deploy.forgejo.sso.clientSecretFile and
|
||
services.hyperhive.swarm.authelia.url as the assertions describe.
|
||
'')
|
||
];
|
||
|
||
# Every swarm needs this forge — it's the canonical store for the meta
|
||
# flake + every agent's config repo (and the `internal/*` repos) — but
|
||
# only one host runs it: `deploy.forgejo.enable`, in ../deploy.nix. What
|
||
# follows is what every hive needs to reach it, wherever it runs.
|
||
options.services.hyperhive.swarm.forge = {
|
||
httpPort = lib.mkOption {
|
||
type = lib.types.port;
|
||
default = 3000;
|
||
description = ''
|
||
TCP port the forge serves HTTP on. Default 3000 sits outside
|
||
hyperhive's claimed ranges (dashboard 7000, every agent in
|
||
8100..8999 via FNV-1a hash). Change this if you already have
|
||
another forgejo bound to 3000.
|
||
'';
|
||
};
|
||
|
||
sshPort = lib.mkOption {
|
||
type = lib.types.port;
|
||
default = 2222;
|
||
description = ''
|
||
TCP port the forge's built-in SSH server listens on. Kept off
|
||
22 so it doesn't clash with the host's openssh. Agents push
|
||
with `ssh -p <sshPort> git@<domain>:<owner>/<repo>.git`.
|
||
'';
|
||
};
|
||
|
||
domain = lib.mkOption {
|
||
type = lib.types.str;
|
||
# Under the SWARM domain, not this hive's: a swarm runs one forge
|
||
# and every hive in it reaches the same host, so the name belongs
|
||
# to the swarm rather than to whichever hive happens to run it.
|
||
#
|
||
# Total on a null swarm domain so the required-domain assertion in
|
||
# hive-network.nix is the thing that fires; see the comment there.
|
||
default = if swarmDomain == null then "forge.invalid" else "forge.${swarmDomain}";
|
||
defaultText = lib.literalExpression ''"forge.''${services.hyperhive.swarm.domain}"'';
|
||
example = "git.example.com";
|
||
description = ''
|
||
Public hostname for the forge. Doubles as both the forgejo
|
||
`DOMAIN` setting (clone URLs forgejo advertises) AND the
|
||
gateway vhost server-name (sub-domain routing — see
|
||
`docs/networking/gateway.md`).
|
||
|
||
Defaults to `forge.''${services.hyperhive.swarm.domain}` — the
|
||
swarm's domain, not this hive's, because a swarm runs **one**
|
||
forge that every hive in it talks to.
|
||
|
||
⚠️ To use the per-hive name `forge.''${services.hyperhive.domain}`
|
||
instead, pin it here. Certificates follow either way: the
|
||
store's `swarm-services` role issues for the configured names,
|
||
not for a fixed tree.
|
||
|
||
Set to a full hostname (`git.example.com`,
|
||
`forge.internal.lan`, etc.) for a bespoke vhost shape — the
|
||
full domain goes here, no separate sub-domain-label option.
|
||
'';
|
||
};
|
||
|
||
publicUrl = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = "https://${cfg.domain}";
|
||
defaultText = lib.literalExpression ''"https://''${domain}"'';
|
||
example = "https://forge.example.com";
|
||
description = ''
|
||
Browser-facing forge URL the dashboard uses to build clickable
|
||
forge links (the H0M3 Forge tile, per-agent-row forge links) —
|
||
sourced into every agent container + hive-c0re as
|
||
`HIVE_FORGE_PUBLIC_URL`.
|
||
|
||
Defaults to `https://''${cfg.domain}`, the gateway vhost. When
|
||
`null`, the dashboard **hides** forge links rather than
|
||
guessing one — see `docs/web-ui/dashboard.md::H0M3 page` for
|
||
the rationale (a link built from the operator's own browser
|
||
hostname + a container port is only an accident away from
|
||
wrong on any deployment that isn't plain localhost).
|
||
'';
|
||
};
|
||
|
||
rootUrl = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = null;
|
||
example = "https://forge.example.com/";
|
||
description = ''
|
||
Override the auto-derived forgejo `ROOT_URL`. When `null`
|
||
(default), `ROOT_URL` is `https://''${cfg.domain}/`. The gateway
|
||
always terminates TLS (self-signed is the implicit floor when no
|
||
`gateway.tls.certDir` / ACME is set), so the forge is always
|
||
advertised over https. A non-canonical `gateway.httpsPort` is
|
||
appended as `:<port>`.
|
||
|
||
Set this only for a genuinely bespoke shape (e.g. an external
|
||
reverse proxy on a different host/path). Must end with `/` per
|
||
forgejo's `ROOT_URL` contract.
|
||
'';
|
||
};
|
||
|
||
# The swarm's authelia is always registered as an OpenID Connect
|
||
# login source here — there is no toggle: a forge without it has no
|
||
# way to log a person in through the swarm's SSO.
|
||
#
|
||
# **Additive, never exclusive.** Forgejo keeps its local password
|
||
# database and gains an extra "sign in with" button; this does not
|
||
# disable local login. Deliberate: an identity provider that can take
|
||
# the forge offline when it hiccups is a worse forge than one with
|
||
# two ways in — which is also what makes always-on safe.
|
||
sso = {
|
||
clientId = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "forgejo";
|
||
description = ''
|
||
OAuth2 client id this forge identifies itself with. Must match
|
||
the `id` of the corresponding entry in
|
||
`services.hyperhive.swarm.authelia.oidc.clients`.
|
||
'';
|
||
};
|
||
redirectUri = lib.mkOption {
|
||
type = lib.types.str;
|
||
readOnly = true;
|
||
default = ssoRedirectUri;
|
||
defaultText = lib.literalExpression ''"''${ROOT_URL}user/oauth2/authelia/callback"'';
|
||
description = ''
|
||
OAuth2 callback authelia sends the browser back to, and the URI
|
||
it matches **exactly**.
|
||
|
||
Read-only: forgejo derives it from its own `ROOT_URL` and the
|
||
login source's name, so it is a fact other modules read rather
|
||
than a knob. The glue that registers this client wherever
|
||
authelia runs reads it from here instead of restating the
|
||
format.
|
||
|
||
⚠️ With {option}`services.hyperhive.swarm.forge.rootUrl` unset,
|
||
`ROOT_URL` follows the gateway's `httpsPort`, which is
|
||
per-host. An authelia host that is not the forge's host renders
|
||
the forge's callback only if the two agree on it; set `rootUrl`
|
||
if they do not.
|
||
'';
|
||
};
|
||
# The secret half is a path on the host that runs the forge, so it
|
||
# lives under `deploy.forgejo.sso`, in ./default.nix.
|
||
};
|
||
};
|
||
}
|