Watch
0
0
Fork
You've already forked hyperhive
0

nix: comments state current behaviour, not history

Rewrite comments and option descriptions under nix/ that narrate how the
code got here ("used to", "new name", "sat at the TOP", "no longer",
renamed/moved narration, attributions) to say what the code does now and
why. Comments and description strings only; no logic changes.

Files in open PR #4970's diff are left alone. Refs #3902
This commit is contained in:
atlas 2026-10-05 02:34:39 +02:00
commit 1c90619fbb
45 changed files with 331 additions and 411 deletions

View file

@ -79,10 +79,10 @@ in
# #
# Both spellings, for the same reason ../host-modules/deploy.nix lists both # Both spellings, for the same reason ../host-modules/deploy.nix lists both
# of `swarm|deploy.matrix.registrationTokenFile`: an existing agent.nix may # of `swarm|deploy.matrix.registrationTokenFile`: an existing agent.nix may
# carry either the pre-rename `hyperhive.*` path or the current one, and only # carry either the `hyperhive.*` path or the `services.hyperhive.agent.*`
# the one it actually carries will fire. The rename shim that used to bridge # one, and only the one it actually carries will fire. Neither is an entry
# them is gone from ./renamed-options.nix — a rename pointing at an # in ./renamed-options.nix — a rename pointing at an undeclared option
# undeclared option breaks evaluation for everyone, removed or not. # breaks evaluation for everyone, removed or not.
(lib.mkRemovedOptionModule [ "hyperhive" "matrix" "enable" ] matrixEnableRemoved) (lib.mkRemovedOptionModule [ "hyperhive" "matrix" "enable" ] matrixEnableRemoved)
(lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "matrix" "enable" ] matrixEnableRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "matrix" "enable" ] matrixEnableRemoved)
]; ];

View file

@ -10,8 +10,8 @@
let let
userName = config.services.hyperhive.agent.user.name; userName = config.services.hyperhive.agent.user.name;
# The token the agent fetched from the swarm secret store # The token the agent fetched from the swarm secret store
# (./forge-token.nix), then the state-dir file the hive used to write, # (./forge-token.nix), then the state-dir file the hive writes, which is
# which is still the only copy for an agent without a store identity. # the only copy for an agent without a store identity.
# Both are PATHS; each reader below takes the first that holds a token. # Both are PATHS; each reader below takes the first that holds a token.
fetchedTokenFile = config.services.hyperhive.agent.forge.tokenFile; fetchedTokenFile = config.services.hyperhive.agent.forge.tokenFile;
stateTokenFile = "/agents/${userName}/state/forge-token"; stateTokenFile = "/agents/${userName}/state/forge-token";

View file

@ -37,7 +37,7 @@ let
# per-agent "does this agent have a homeserver to reach" fact. So an agent the # per-agent "does this agent have a homeserver to reach" fact. So an agent the
# hive handed no homeserver URL, whose operator declared no external account # hive handed no homeserver URL, whose operator declared no external account
# either, has an empty set here and gets none of the units — the case the # either, has an empty set here and gets none of the units — the case the
# deleted `matrix.enable = false` used to express. # "matrix off" case.
# #
# No cycle: `matrixAccounts`'s own definition reads `matrix.url`, never this # No cycle: `matrixAccounts`'s own definition reads `matrix.url`, never this
# binding, so the `mkIf`s below may read the merged option value freely. # binding, so the `mkIf`s below may read the merged option value freely.
@ -75,9 +75,9 @@ in
then talks to the wrong machine. An absent integration, never a then talks to the wrong machine. An absent integration, never a
misdirected one. misdirected one.
`null` is also this agent's **matrix off switch**, and the `null` is also this agent's **matrix off switch** — there is no
replacement for the `services.hyperhive.agent.matrix.enable` separate `services.hyperhive.agent.matrix.enable` boolean: the
boolean that used to exist: the hive-internal `main` account in hive-internal `main` account in
`services.hyperhive.agent.matrixAccounts` is declared from this `services.hyperhive.agent.matrixAccounts` is declared from this
URL, so `null` leaves that set empty and the whole integration — URL, so `null` leaves that set empty and the whole integration —
daemon unit, path watcher, injected MCP entry — is not generated daemon unit, path watcher, injected MCP entry — is not generated
@ -214,12 +214,11 @@ in
config = { config = {
assertions = [ assertions = [
# The "extras require `matrix.enable`" assertion that used to head this # There is no "extras require `matrix.enable`" assertion: a non-empty
# list is gone with the option: a non-empty account set is now what # account set is what enables matrix, so that condition is the
# enables matrix, so the condition it checked has become the definition # definition of the thing it would check. An operator declaring only an
# of the thing it was checking. An operator declaring only an external # external account, with no hive homeserver, gets a rendered config —
# account, with no hive homeserver, is a config this module now renders # matrix on, no `main`.
# rather than rejects — matrix on, no `main`.
# #
# `main` is not a forbidden key --- this module declares it # `main` is not a forbidden key --- this module declares it
# itself (see the `matrixAccounts.main` definition below), so the # itself (see the `matrixAccounts.main` definition below), so the

View file

@ -1,10 +1,9 @@
# Deprecation shims for the agent tier's old option namespace. # Deprecation shims for the agent tier's top-level `hyperhive.*` spelling.
# #
# Every per-agent option used to live at the top level, `hyperhive.<name>`, # Per-agent options live at `services.hyperhive.agent.<name>`, beside the host
# while the host tier has always been under `services.hyperhive.*`. The agent # tier's `services.hyperhive.*`. These entries keep an `agent.nix` that still
# tier moved to `services.hyperhive.agent.<name>`; these entries keep an # uses the top-level `hyperhive.<name>` spelling evaluating, with a
# existing `agent.nix` evaluating (with a deprecation warning) against the old # deprecation warning.
# spelling.
# #
# ⚠️ One entry per LEAF, not per namespace. A single entry for a parent path # ⚠️ One entry per LEAF, not per namespace. A single entry for a parent path
# only covers its children when the parent is a submodule-typed option; every # only covers its children when the parent is a submodule-typed option; every
@ -119,9 +118,9 @@
[ "hyperhive" "logs" "queryUrl" ] [ "hyperhive" "logs" "queryUrl" ]
[ "services" "hyperhive" "agent" "logs" "queryUrl" ] [ "services" "hyperhive" "agent" "logs" "queryUrl" ]
) )
# `matrix.enable` is deliberately absent from this list: the option it # `matrix.enable` is deliberately absent from this list: it has no
# renamed to no longer exists, and a rename pointing at an undeclared # replacement option, and a rename pointing at an undeclared option
# option breaks evaluation for everyone. Both of its spellings are # breaks evaluation for everyone. Both of its spellings are
# `mkRemovedOptionModule` in ./default.nix instead. # `mkRemovedOptionModule` in ./default.nix instead.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "hyperhive" "matrix" "url" ] [ "hyperhive" "matrix" "url" ]

View file

@ -239,8 +239,8 @@ in
# every hive — the name it answers to, the ports and URLs it is reached # every hive — the name it answers to, the ports and URLs it is reached
# on, the client id it is registered under. These six are what the host # on, the client id it is registered under. These six are what the host
# running it decides; its two packages moved as well, further down with # running it decides; its two packages moved as well, further down with
# the other `*.package` moves. ⚠️ `enable` is NOT here: it was renamed # the other `*.package` moves. ⚠️ `enable` is NOT here: its shim lives in
# earlier and its shim lives in ./hive-matrix.nix, so that file is the one # ./hive-matrix.nix, so that file is the one
# to check before assuming this table is the whole matrix story. # to check before assuming this table is the whole matrix story.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "openFirewall" ] [ "services" "hyperhive" "swarm" "matrix" "openFirewall" ]
@ -255,7 +255,7 @@ in
[ "services" "hyperhive" "deploy" "matrix" "maxRequestSize" ] [ "services" "hyperhive" "deploy" "matrix" "maxRequestSize" ]
) )
# Both spellings of the registration token, removed rather than renamed: # Both spellings of the registration token, removed rather than renamed:
# the homeserver no longer accepts a shared registration secret at all, # the homeserver does not accept a shared registration secret at all,
# so there is no path to point a definition at. Accounts are created by # so there is no path to point a definition at. Accounts are created by
# the swarm's appservice. # the swarm's appservice.
(lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "matrix" "registrationTokenFile" ] '' (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "matrix" "registrationTokenFile" ] ''
@ -301,28 +301,27 @@ in
[ "services" "hyperhive" "deploy" "victorialogs" "retentionPeriod" ] [ "services" "hyperhive" "deploy" "victorialogs" "retentionPeriod" ]
) )
# The switch over all of the above, and the name changes with the path # The switch over all of the above. `allSwarmServices` says whether THIS
# because the old one described the wrong subject: those services are # host runs every service the swarm requires; those services are
# required of the SWARM, while the option says whether THIS host runs # required of the SWARM, so the name is about the swarm rather than this
# them. `allSwarmServices` is mara's own phrasing of what it means. # host's wiring.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "enableRequiredServices" ] [ "services" "hyperhive" "swarm" "enableRequiredServices" ]
[ "services" "hyperhive" "deploy" "allSwarmServices" ] [ "services" "hyperhive" "deploy" "allSwarmServices" ]
) )
# The mode above that one. It sat at the TOP of `services.hyperhive`, # The mode above that one lives under `deploy`, not at the TOP of
# which is the same defect one tier up: that namespace is everything # `services.hyperhive`: that namespace is everything about hyperhive, not
# about hyperhive, not the settings of a single hive. The new name says # the settings of a single hive. `singleHostSwarm` names what the mode
# what the mode asserts — the whole swarm runs on this host — instead of # asserts — the whole swarm runs on this host — instead of its mechanism.
# naming its mechanism.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "enableAllLocalDefaults" ] [ "services" "hyperhive" "enableAllLocalDefaults" ]
[ "services" "hyperhive" "deploy" "singleHostSwarm" ] [ "services" "hyperhive" "deploy" "singleHostSwarm" ]
) )
# The hive TLS state dir sat at the TOP of `services.hyperhive`, which is # The hive TLS state dir lives under `deploy.hive-controller` (the
# meant to be everything about hyperhive rather than the settings of one # hive-c0re daemon's namespace), not at the TOP of `services.hyperhive`,
# hive. `hive-controller` is hive-c0re's new name (mara, on the issue). # which is everything about hyperhive rather than the settings of one hive.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "tls" "stateDir" ] [ "services" "hyperhive" "tls" "stateDir" ]
[ "services" "hyperhive" "deploy" "hive-controller" "tls" "stateDir" ] [ "services" "hyperhive" "deploy" "hive-controller" "tls" "stateDir" ]

View file

@ -86,8 +86,8 @@ in
clientKeyFile = lib.mkDefault "${pkiDir}/client-key.pem"; clientKeyFile = lib.mkDefault "${pkiDir}/client-key.pem";
serverCaFile = lib.mkDefault "${pkiDir}/ca.pem"; serverCaFile = lib.mkDefault "${pkiDir}/ca.pem";
# 🩸 Readers that used to present `client.pem` above, each now pointed at # 🩸 The readers below each present a leaf of their own rather than
# a leaf of its own. The leaf is what bao sees, so this pairing is the # `client.pem` above. The leaf is what bao sees, so this pairing is the
# whole of what turns "one principal with the union of their grants" into # whole of what turns "one principal with the union of their grants" into
# one principal per grant — see ./swarm-bao.nix's `perHiveReaders` block # one principal per grant — see ./swarm-bao.nix's `perHiveReaders` block
# for the grants themselves. # for the grants themselves.
@ -193,11 +193,11 @@ in
[ -s ${pkiDir}/matrix-ctl.pem ] || ${signLeaf} ${pkiDir} matrix-ctl \ [ -s ${pkiDir}/matrix-ctl.pem ] || ${signLeaf} ${pkiDir} matrix-ctl \
${lib.escapeShellArg deployCfg.bao.matrixCtlCommonName} "" clientAuth ${lib.escapeShellArg deployCfg.bao.matrixCtlCommonName} "" clientAuth
# 🩸 The readers that used to present `client.pem` above. Each carries # 🩸 The readers' leaves, separate from `client.pem` above. Each carries
# its own subject, which is the entire mechanism: bao matches a # its own subject, which is the entire mechanism: bao matches a
# cert-auth role on the CN, so units sharing one leaf were one principal # cert-auth role on the CN, so units sharing one leaf would be one
# holding the union of their grants, and the union included every agent # principal holding the union of their grants, and the union would
# credential in the swarm. # include every agent credential in the swarm.
# #
# ⚠️ The first carries THIS hive's name in the subject, unlike the three # ⚠️ The first carries THIS hive's name in the subject, unlike the three
# service leaves above. Its grant names one hive's path, because a queue # service leaves above. Its grant names one hive's path, because a queue

View file

@ -42,9 +42,9 @@ let
# a certificate the store accepts, and that is true on the store's own host # a certificate the store accepts, and that is true on the store's own host
# and on a hive three networks away for exactly the same reason. # and on a hive three networks away for exactly the same reason.
# #
# 🩸 This principal's OWN leaf, not `clientCertFile` — the hive's, which four # 🩸 This principal's OWN leaf, not `clientCertFile` — the hive's. Bao
# units used to share. Bao matches a cert-auth role on the CN, so one leaf for # matches a cert-auth role on the CN, so one leaf shared by four readers
# four readers was ONE principal holding the union of four grants: read on # would be ONE principal holding the union of four grants: read on
# `swarm/agents/*` AND `swarm/hives/<hive>/*` AND `swarm/services/*`, when # `swarm/agents/*` AND `swarm/hives/<hive>/*` AND `swarm/services/*`, when
# this unit reads one queue credential and nothing else. Its own leaf carries # this unit reads one queue credential and nothing else. Its own leaf carries
# `<queueAgentCommonNamePrefix>-<hive>` and its role grants the single path # `<queueAgentCommonNamePrefix>-<hive>` and its role grants the single path
@ -53,11 +53,11 @@ let
baoDeploy.queueAgentClientCertFile != null && baoDeploy.queueAgentClientKeyFile != null; baoDeploy.queueAgentClientCertFile != null && baoDeploy.queueAgentClientKeyFile != null;
# Does this host read the store at all — the hive's own leaf, which is the # Does this host read the store at all — the hive's own leaf, which is the
# one thing a remote-store deployment has always had to place by hand. Only # one thing a remote-store deployment has to place by hand. Only used to
# used to decide whether a missing per-principal leaf is a mistake or a # decide whether a missing per-principal leaf is a mistake or a deployment
# deployment that has no store: a host holding neither is the supported # that has no store: a host holding neither is the supported no-store
# no-store shape, and one holding this pair but not the pair above stopped # shape, and one holding this pair but not the pair above reads the store
# short of naming this reader's own. # without a reader identity of its own.
hiveReaderIdentity = baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null; hiveReaderIdentity = baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null;
credentialDir = toString deployCfg.hive-controller.queue.agentCredentialDir; credentialDir = toString deployCfg.hive-controller.queue.agentCredentialDir;

View file

@ -115,17 +115,14 @@ in
HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES = otel.extraResourceAttributes; HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES = otel.extraResourceAttributes;
} }
# HYPERHIVE_OTEL_HEADERS_CREDENTIAL is deliberately NOT emitted, and # HYPERHIVE_OTEL_HEADERS_CREDENTIAL is deliberately NOT emitted, and
# its absence is the security half of this design: it was the variable # its absence is the security half of this design: that variable would
# that put the upstream token into an agent's own settings.json. The # put the upstream token into an agent's own settings.json. Nothing
# delivery path it drove — an nspawn credential forwarded by # delivers the token into the container — no nspawn credential, no
# host_config.rs, then written to an agent-readable file by # agent-readable header file.
# claude-settings.nix's `hive-otel-header` oneshot — no longer exists
# anywhere; it was removed along with this variable's last consumer.
# #
# Kept as a comment rather than deleted because the useful part is the # The rule: the collector holding the credential achieves nothing while
# RULE, not the history: the collector holding the credential achieves # anything else hands out a copy, so there is exactly one holder and it
# nothing while anything else hands out a copy, so there is exactly one # is on the host.
# holder and it is on the host.
// lib.optionalAttrs (otel.metricIntervalMs != null) { // lib.optionalAttrs (otel.metricIntervalMs != null) {
HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs; HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs;
} }

View file

@ -207,11 +207,8 @@ in
# `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service` # `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service`
# in self-signed mode (see the hive-ca-trust helper). Runner registration # in self-signed mode (see the hive-ca-trust helper). Runner registration
# is no longer on the boot-critical path — hive-c0re owns it out of band — # is not on the boot-critical path — hive-c0re owns it out of band — so
# so the container boots immediately and needs no start-timeout bump. The # the container boots immediately and needs no start-timeout bump.
# former `TimeoutStartSec = mkForce "180s"` band-aid (which papered over a
# boot-path forge + core-token round-trip that could trip the nspawn start
# timeout into a ~60s restart loop) is gone with that move.
systemd.services."container@hive-ci" = caTrust.containerOrdering; systemd.services."container@hive-ci" = caTrust.containerOrdering;
containers.hive-ci = { containers.hive-ci = {
@ -415,7 +412,7 @@ in
# runner stays down (Restart=on-failure retries) until c0re # runner stays down (Restart=on-failure retries) until c0re
# registers it, rather than blocking the container's start. # registers it, rather than blocking the container's start.
# #
# No nix-daemon wait anymore: builds route through the HOST daemon # No nix-daemon wait: builds route through the HOST daemon
# (bound socket above), which is up before the container — nix's # (bound socket above), which is up before the container — nix's
# own connect retries cover any brief window. `mkBefore` runs this # own connect retries cover any brief window. `mkBefore` runs this
# ahead of any upstream pre-steps. # ahead of any upstream pre-steps.

View file

@ -142,11 +142,6 @@ in
matter for access from outside the host. Flip to `true` when matter for access from outside the host. Flip to `true` when
you want the operator's browser or external git clients to you want the operator's browser or external git clients to
hit the forge directly. hit the forge directly.
**Breaking change**: this used to default to `true`. If you
relied on the old default for external reach, add
`services.hyperhive.deploy.forgejo.openFirewall = true;` to your
host config before rebuilding.
''; '';
}; };
@ -783,7 +778,7 @@ in
Group = "forgejo"; Group = "forgejo";
SyslogIdentifier = "forgejo-sso-source"; SyslogIdentifier = "forgejo-sso-source";
}; };
# `FORGEJO_CUSTOM` (not `GITEA_CUSTOM` — forgejo renamed it) # `FORGEJO_CUSTOM` (the forgejo-prefixed name, not `GITEA_CUSTOM`)
# is how the CLI finds the app.ini upstream's module wrote. # is how the CLI finds the app.ini upstream's module wrote.
# #
# This unit is also a trust-bundle consumer (declared in the # This unit is also a trust-bundle consumer (declared in the
@ -930,29 +925,25 @@ in
# re-mint instead of being silently masked by a stamp that # re-mint instead of being silently masked by a stamp that
# outlived what it claims exists. # outlived what it claims exists.
if [ ! -s "$token_path" ]; then if [ ! -s "$token_path" ]; then
# `write:admin` added for `swarm-controller::forge::Client:: # `write:admin` is required by `swarm-controller::forge::Client::
# ensure_agent_user` (the `CreateForgeUser` jobq node) — # ensure_agent_user` (the `CreateForgeUser` jobq node):
# Forgejo's `admin_create_user` HTTP endpoint refused every # Forgejo's `admin_create_user` HTTP endpoint refuses a
# call with "token does not have at least one of required # token without it ("token does not have at least one of
# scope(s): [write:admin]" without it, an already-deployed # required scope(s): [write:admin]"). A host whose token was
# swarm hitting this the moment that node shipped. A host # minted without this scope keeps it — the
# whose token was minted before this scope was added won't # `[ ! -s "$token_path" ]` guard above only mints when the
# pick it up automatically — the `[ ! -s "$token_path" ]` # file is absent, by design (see its own comment) — so such a
# guard above only mints when the file is absent, by design # deployment needs its token file deleted to force a re-mint
# (see its own comment) — so an existing deployment needs # with the full scope.
# its token file deleted to force a re-mint with the new
# scope.
# #
# Token name carries a timestamp suffix rather than the # Token name carries a timestamp suffix rather than a fixed
# fixed `swarm-controller-boot` this used to be — a real # name: deleting the token *file* (the remediation above)
# incident, not a hypothetical: deleting the token *file* # doesn't delete the token Forgejo already has under that
# (the remediation above) doesn't delete the token Forgejo # name, so re-running this unit would fail with "Command
# already has under that name, so re-running this unit hit # error: access token name has been used already". Same
# "Command error: access token name has been used already" # approach `hive-c0re::forge::users::mint_token` uses for its
# and failed outright. Same fix `hive-c0re::forge::users:: # own agent tokens — a monotonic suffix means a re-mint can
# mint_token` already uses for its own agent tokens, for # never collide with whatever this account already has.
# the identical reason — a monotonic suffix means a re-mint
# can never collide with whatever this account already has.
out=$(forgejo admin user generate-access-token \ out=$(forgejo admin user generate-access-token \
--username ${lib.escapeShellArg swarmControllerForgeUser} \ --username ${lib.escapeShellArg swarmControllerForgeUser} \
--token-name "swarm-controller-boot-$(date +%s)" \ --token-name "swarm-controller-boot-$(date +%s)" \

View file

@ -30,9 +30,10 @@ let
effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl;
in in
{ {
# Forge moved under `swarm` when the swarm-global services were # Forge lives under `swarm`, beside the other swarm-global services. One
# consolidated. One rename for the namespace: the subtree comes with it, # rename for the top-level `services.hyperhive.forge` namespace: the
# so existing hives keep evaluating and get one warning naming both paths. # subtree comes with it, so existing hives keep evaluating and get one
# warning naming both paths.
imports = [ imports = [
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "forge" ] [ "services" "hyperhive" "forge" ]

View file

@ -55,10 +55,9 @@ let
# `cfg.useSelfSigned` (options.nix) is the derived single source of truth. # `cfg.useSelfSigned` (options.nix) is the derived single source of truth.
useSelfSigned = cfg.useSelfSigned; useSelfSigned = cfg.useSelfSigned;
# nginx's own state dir. Kept at the historical `/var/lib/hive-gateway` # nginx's own state dir, fixed at `/var/lib/hive-gateway` independent of
# path rather than renamed with the move: it holds the imported leaf # this module's own path: it holds the imported leaf across reboots, and
# across reboots, and renaming it would strand every existing hive's # a different path would strand every existing hive's certs.
# certs for no gain.
tlsDir = "/var/lib/hive-gateway/tls"; tlsDir = "/var/lib/hive-gateway/tls";
# TLS cert + key paths. # TLS cert + key paths.
# - self-signed (default): the hive leaf the store issues (the # - self-signed (default): the hive leaf the store issues (the
@ -207,14 +206,12 @@ in
# before c0re has run, and pin owner + mode rather than leaving it # before c0re has run, and pin owner + mode rather than leaving it
# to whoever creates the path first. # to whoever creates the path first.
# #
# ⚠️ There is deliberately NO rule for /var/lib/hyperhive here. One # ⚠️ There is deliberately NO rule for /var/lib/hyperhive here.
# used to declare it `0755 root root` and could never win:
# `hive-c0re.service` sets `StateDirectory = "hyperhive"` with # `hive-c0re.service` sets `StateDirectory = "hyperhive"` with
# `StateDirectoryMode = "0750"`, which systemd re-applies on every # `StateDirectoryMode = "0750"`, which systemd re-applies on every
# start. Two mechanisms owning one path, and the loser still read as # start, so a tmpfiles rule for the same path could never win: two
# a guarantee — it is why the resulting outage was first misdiagnosed # mechanisms owning one path, the loser still reading as a guarantee.
# as someone having changed the mode. c0re's own unit owns that dir; # c0re's own unit owns that dir; this module has no opinion about it.
# this module no longer has an opinion about it.
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
# The gateway's own config dir — NOT under /var/lib/hyperhive. c0re # The gateway's own config dir — NOT under /var/lib/hyperhive. c0re
# writes here, nginx reads here, and neither needs any access to the # writes here, nginx reads here, and neither needs any access to the
@ -393,23 +390,15 @@ in
# ⚠️ NO `SupplementaryGroups = [ "hive-core" ]` on nginx, and its # ⚠️ NO `SupplementaryGroups = [ "hive-core" ]` on nginx, and its
# absence is load-bearing rather than an omission. # absence is load-bearing rather than an omission.
# #
# It used to be here, to let nginx traverse `/var/lib/hyperhive` and # `hive-c0re.service` declares `StateDirectory = "hyperhive"` with
# reach the config fragments that lived inside: `hive-c0re.service` # `StateDirectoryMode = "0750"` owned by `hive-core`, and systemd
# declares `StateDirectory = "hyperhive"` with `StateDirectoryMode = # re-applies that on every c0re start. The group would let nginx
# "0750"` owned by `hive-core`, and systemd re-applies that on every # traverse that dir, but it also gives nginx read access to everything
# c0re start — beating this module's own tmpfiles rule for the same # ELSE group-readable under it, including the broker sqlite — i.e.
# path. Without the group, nginx failed its config test and never # every message between every agent, reachable by the process whose
# started (`nginx: [emerg] open() ".../agents.conf" failed (13: # entire job is parsing untrusted network input. The config fragments
# Permission denied)`), taking the gateway and every hive domain # nginx reads live in the gateway's own dir instead, so nginx needs no
# behind it down. # access to c0re's tree. Adding this line would reopen that exposure.
#
# The group fixed the symptom and paid for it: it also gave nginx
# read access to everything ELSE group-readable under that dir,
# including the broker sqlite — i.e. every message between every
# agent, reachable by the process whose entire job is parsing
# untrusted network input. Moving the fragments to the gateway's own
# dir removes the need and the exposure together. Re-adding this line
# would restore both.
networking.firewall = lib.mkIf cfg.openFirewall { networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ allowedTCPPorts = [
@ -424,12 +413,11 @@ in
# every name a service module contributed. See `docs/networking/gateway.md` # every name a service module contributed. See `docs/networking/gateway.md`
# ("Local dev"). # ("Local dev").
# #
# This used to restate the per-service list a THIRD time (after the # This does not restate the per-service list (the vhosts and the
# vhosts and the dnsmasq records), with its own copy of each # dnsmasq records already carry it). It is the same question —
# service's guard. It is the same question — "which names does this # "which names does this host answer for" — so it reads the same
# host answer for" — so it reads the same answer; a service added # answer; a service added later lands here with no edit, and cannot
# later lands here with no edit, and cannot land here with a # land here with a different condition than it used for DNS.
# different condition than it used for DNS.
networking.hosts = lib.mkIf cfg.localHostsEntry { networking.hosts = lib.mkIf cfg.localHostsEntry {
"127.0.0.1" = lib.unique ([ hyperhiveDomain ] ++ cfg.localNames); "127.0.0.1" = lib.unique ([ hyperhiveDomain ] ++ cfg.localNames);
}; };

View file

@ -82,10 +82,6 @@ in
nginx vhost. Leave off when running behind another reverse nginx vhost. Leave off when running behind another reverse
proxy (e.g. caddy / traefik on the host) that handles TLS proxy (e.g. caddy / traefik on the host) that handles TLS
termination + forwards to `port`. termination + forwards to `port`.
**Note**: this used to default to `true`. Add
`services.hyperhive.gateway.openFirewall = true;` to your host
config if external reach stopped working after a recent upgrade.
''; '';
}; };

View file

@ -187,22 +187,19 @@ let
# Swagger UI: nginx hosts the FULL themed dist (`swaggerUiTheme` — # Swagger UI: nginx hosts the FULL themed dist (`swaggerUiTheme` —
# vendored Swagger UI + our overlay, see nix/packages/swagger-ui- # vendored Swagger UI + our overlay, see nix/packages/swagger-ui-
# dist.nix + swagger-ui-theme.nix) straight from the store, with NO # dist.nix + swagger-ui-theme.nix) straight from the store, with NO
# fallback to hive-c0re at all — per the operator's shape: "core # fallback to hive-c0re at all: core hosts only the JSON. Only
# should not need the swagger ui at all if it is hosted in gateway" # `/api/openapi.json` (the live-generated spec `index.html` fetches;
# / "core only hosts the json". Only `/api/openapi.json` # not under this prefix) proxies to c0re via "/api/" below — that's
# (the live-generated spec `index.html` fetches; not under this # the one thing that has to stay dynamic.
# prefix) still proxies to c0re via "/api/" below — that's the one
# thing that has to stay dynamic.
# #
# Prefix location, not exact-match: wins over "/api/" on plain # Prefix location, not exact-match: wins over "/api/" on plain
# prefix length (no ordering/`=` needed), and now needs to cover # prefix length (no ordering/`=` needed), and covers every file in
# every file in the tree (bundle.js, maps, favicons, …), not just # the tree (bundle.js, maps, favicons, …), not just our 2 override
# our 2 override files — hive-c0re no longer serves any of this as # files — hive-c0re serves none of this as a fallback.
# a fallback once its own `utoipa-swagger-ui` mount is removed. # `= /api/docs` (no trailing slash) redirects to `/api/docs/`: nginx
# `= /api/docs` (no trailing slash) issues the same redirect # owns that redirect because hive-c0re has no route for it, and the
# `utoipa-swagger-ui`'s router used to: that mount is going away # H0M3 hub's own `/api/docs` link (no trailing slash) would 404
# too, so nginx has to own it now, or the H0M3 hub's own `/api/docs` # without it.
# link (no trailing slash) would 404 once hive-c0re drops the route.
swaggerUiLocations = { swaggerUiLocations = {
"= /api/docs" = { "= /api/docs" = {
extraConfig = '' extraConfig = ''

View file

@ -153,12 +153,11 @@ let
# to host it — otherwise moving the container between hives would look # to host it — otherwise moving the container between hives would look
# like a different homeserver. # like a different homeserver.
# #
# ⚠️ Changing this default is a BREAKING change in a way that moving # ⚠️ Changing this default is breaking: `serverName` is baked
# `gatewayHost` was not: `serverName` is baked irrevocably into every # irrevocably into every user and room id, so a deployment that
# user and room id, so a deployment that rebuilds onto a new one is a # rebuilds onto a new one is a *different homeserver*. Existing hives
# *different homeserver*, not a renamed one. Existing hives pin the old # pin their value explicitly (see the option's description); the
# value explicitly (see the option's description); the default is what # default is what a fresh swarm gets.
# a fresh swarm gets.
# #
# Total on a null swarm domain, deliberately: the required-domain # Total on a null swarm domain, deliberately: the required-domain
# assertion in hive-network.nix is what should fire, not a coercion # assertion in hive-network.nix is what should fire, not a coercion
@ -251,9 +250,10 @@ in
# settings: docs/integrations/matrix.md. Vhost map + discovery flow + tuning # settings: docs/integrations/matrix.md. Vhost map + discovery flow + tuning
# knobs: docs/networking/gateway.md. # knobs: docs/networking/gateway.md.
# Matrix moved under `swarm` when the swarm-global services were # Matrix lives under `swarm`, beside the other swarm-global services. One
# consolidated. One rename for the namespace: the subtree comes with it, # rename for the top-level `services.hyperhive.matrix` namespace: the
# so existing hives keep evaluating and get one warning naming both paths. # subtree comes with it, so existing hives keep evaluating and get one
# warning naming both paths.
imports = [ imports = [
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "matrix" ] [ "services" "hyperhive" "matrix" ]
@ -311,11 +311,6 @@ in
the homeserver to other hives or when an external matrix the homeserver to other hives or when an external matrix
client needs to reach the client-server API directly. client needs to reach the client-server API directly.
**Breaking change**: this used to default to `true`. If you
relied on the old default for external reach, add
`services.hyperhive.deploy.matrix.openFirewall = true;` to your host
config before rebuilding.
Note: federation (the matrix-spec well-known port 8448) is Note: federation (the matrix-spec well-known port 8448) is
intentionally not opened here. tuwunel serves the federation intentionally not opened here. tuwunel serves the federation
API on the same `httpPort` as the client-server API by API on the same `httpPort` as the client-server API by

View file

@ -130,9 +130,9 @@ let
# leaf to hold, and its absence there is the correct state rather # leaf to hold, and its absence there is the correct state rather
# than a stale one — its own vhosts are covered by the hive leaf # than a stale one — its own vhosts are covered by the hive leaf
# above. Anywhere else it is expected: the leaf comes from the # above. Anywhere else it is expected: the leaf comes from the
# secret store's `pki` mount now, not from a sub-CA that exists on # secret store's `pki` mount, reachable from every host rather than
# one host in the swarm, so "this host cannot issue it" is no longer # from a sub-CA on one host in the swarm, so "this host cannot issue
# one of the answers. # it" is not one of the answers.
want_svc=${if localServiceDomains == [ ] then "0" else "1"} want_svc=${if localServiceDomains == [ ] then "0" else "1"}
# Expiry is not the only way a leaf goes wrong. One signed when the # Expiry is not the only way a leaf goes wrong. One signed when the
@ -346,17 +346,14 @@ in
}; };
config = lib.mkIf active { config = lib.mkIf active {
# 🚫 The "these service names are outside this hive's domain and this # 🚫 There is deliberately NO warning that these service names are
# host cannot sign for them" warning is GONE, and its absence is the # outside this hive's domain and this host cannot sign for them. The
# deliverable rather than a tidy-up. It fired on # services leaf comes from the secret store's `pki` mount, which every
# `!swarm.ca.autoConfigure` — i.e. "this host does not hold the swarm # hive reaches over the network with its own identity, so holding the
# root key" — which was the whole reason a hive could end up serving # swarm root key does not decide whether a hive can serve a
# its own leaf on a swarm-service name. The services leaf now comes # swarm-service name. Such a warning would fire on every rebuild of
# from the secret store's `pki` mount, which every hive reaches over # every hive that is not the CA host, about a fallback that does not
# the network with its own identity, so holding the root key stopped # happen.
# being the thing that decides. Left in place it would warn, on every
# rebuild of every hive that is not the CA host, about a fallback
# that no longer happens.
# The state dir and the trust bundle. Never waits on the store: the # The state dir and the trust bundle. Never waits on the store: the
# store's container is ordered after this unit, which writes the # store's container is ordered after this unit, which writes the

View file

@ -103,8 +103,8 @@ in
# Where the swarm lives. Declared beside the hive's own identity # Where the swarm lives. Declared beside the hive's own identity
# because it is what that identity is derived FROM — every hive in a # because it is what that identity is derived FROM — every hive in a
# swarm is a sub-domain of it. Unlike the renamed options nearby, this # swarm is a sub-domain of it. No alias: this option has no earlier
# is genuinely new: nothing moved here, so there is no alias. # spelling.
options.services.hyperhive.swarm.domain = lib.mkOption { options.services.hyperhive.swarm.domain = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
@ -145,7 +145,7 @@ in
(`services.hyperhive.deploy.hive-controller.enable`, (`services.hyperhive.deploy.hive-controller.enable`,
`deploy.bao.enable`, `deploy.matrix.enable`). Distinct from `deploy.bao.enable`, `deploy.matrix.enable`). Distinct from
`services.hyperhive.domain` (the machine-addressable DNS name) `services.hyperhive.domain` (the machine-addressable DNS name)
but no longer merely cosmetic: a hive occupies and not merely cosmetic: a hive occupies
`<hiveName>.<swarm.domain>`, so this is the label the hive is `<hiveName>.<swarm.domain>`, so this is the label the hive is
*addressed* by as well as the one it is called. Exposed to *addressed* by as well as the one it is called. Exposed to
agents as `HYPERHIVE_HIVE_NAME`; surfaced in the dashboard agents as `HYPERHIVE_HIVE_NAME`; surfaced in the dashboard

View file

@ -60,9 +60,8 @@ in
# rather than the file. # rather than the file.
# #
# ⛔ **Do not hand this path to a consumer that reads only ONE # ⛔ **Do not hand this path to a consumer that reads only ONE
# certificate from it.** This comment used to offer an OIDC verifier's # certificate from it** — an OIDC verifier's `issuer_ca_path` is
# `issuer_ca_path` as the motivating example; that is exactly what took # exactly such a reader, and takes the swarm collector down. The bundle is
# the swarm collector down for forty minutes once. The bundle is
# `system CAs ++ hive anchors`, so the anchor is ~123rd, and a # `system CAs ++ hive anchors`, so the anchor is ~123rd, and a
# first-certificate-only reader gets whichever public CA sorts first and # first-certificate-only reader gets whichever public CA sorts first and
# can verify nothing of ours — with a full, valid, 125-certificate file # can verify nothing of ours — with a full, valid, 125-certificate file

View file

@ -68,7 +68,7 @@ in
⚠️ The collector is therefore in the path of all telemetry. It runs ⚠️ The collector is therefore in the path of all telemetry. It runs
on the same host as the agents and restarts on failure, and on the same host as the agents and restarts on failure, and
telemetry is not the control plane, so degraded telemetry is not telemetry is not the control plane, so degraded telemetry is not
degraded operation — but the export no longer survives independently degraded operation — but the export does not survive independently
of anything host-side of anything host-side
''; '';
@ -239,7 +239,7 @@ in
onward, which is one hop for the whole hive rather than one per onward, which is one hop for the whole hive rather than one per
target. target.
No longer empty on a default hive: this collector's own endpoint Not empty on a default hive: this collector's own endpoint
is always in here, so the scrape receiver is emitted everywhere. is always in here, so the scrape receiver is emitted everywhere.
The `!= { }` guards around it stay meaningful — the set can still The `!= { }` guards around it stay meaningful — the set can still
be forced empty, and a `prometheus` receiver with nothing to be forced empty, and a `prometheus` receiver with nothing to

View file

@ -132,10 +132,10 @@ in
tracked as a follow-up rather than papered over. tracked as a follow-up rather than papered over.
On by default: a swarm's hives have identities, and that is a On by default: a swarm's hives have identities, and that is a
fact about the swarm rather than about any one host. It used to fact about the swarm rather than about any one host. It does not
default to whether the queue ran on *this* machine, which made depend on whether the queue runs on *this* machine, so the answer
the answer differ between two hosts of one swarm — set it false is the same on every host of one swarm — set it false for a swarm
for a swarm whose hives authenticate to nothing. whose hives authenticate to nothing.
The clients are inert until something authenticates with them: The clients are inert until something authenticates with them:
each is a client id and a secret sitting on this host. What each is a client id and a secret sitting on this host. What

Some files were not shown because too many files have changed in this diff Show more