Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/hive-forge/service.nix
atlas 93f1a76ea7 swarm CA: retire the host-held root CA, the store holds the only anchor
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
2026-10-05 21:17:10 +02:00

195 lines
8.2 KiB
Nix
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
};
};
}