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:
parent
ea63b0d150
commit
1c90619fbb
45 changed files with 331 additions and 411 deletions
|
|
@ -79,10 +79,10 @@ in
|
|||
#
|
||||
# Both spellings, for the same reason ../host-modules/deploy.nix lists both
|
||||
# of `swarm|deploy.matrix.registrationTokenFile`: an existing agent.nix may
|
||||
# carry either the pre-rename `hyperhive.*` path or the current one, and only
|
||||
# the one it actually carries will fire. The rename shim that used to bridge
|
||||
# them is gone from ./renamed-options.nix — a rename pointing at an
|
||||
# undeclared option breaks evaluation for everyone, removed or not.
|
||||
# carry either the `hyperhive.*` path or the `services.hyperhive.agent.*`
|
||||
# one, and only the one it actually carries will fire. Neither is an entry
|
||||
# in ./renamed-options.nix — a rename pointing at an undeclared option
|
||||
# breaks evaluation for everyone, removed or not.
|
||||
(lib.mkRemovedOptionModule [ "hyperhive" "matrix" "enable" ] matrixEnableRemoved)
|
||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "matrix" "enable" ] matrixEnableRemoved)
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
let
|
||||
userName = config.services.hyperhive.agent.user.name;
|
||||
# The token the agent fetched from the swarm secret store
|
||||
# (./forge-token.nix), then the state-dir file the hive used to write,
|
||||
# which is still the only copy for an agent without a store identity.
|
||||
# (./forge-token.nix), then the state-dir file the hive writes, which is
|
||||
# the only copy for an agent without a store identity.
|
||||
# Both are PATHS; each reader below takes the first that holds a token.
|
||||
fetchedTokenFile = config.services.hyperhive.agent.forge.tokenFile;
|
||||
stateTokenFile = "/agents/${userName}/state/forge-token";
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ let
|
|||
# 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
|
||||
# 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
|
||||
# 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
|
||||
misdirected one.
|
||||
|
||||
`null` is also this agent's **matrix off switch**, and the
|
||||
replacement for the `services.hyperhive.agent.matrix.enable`
|
||||
boolean that used to exist: the hive-internal `main` account in
|
||||
`null` is also this agent's **matrix off switch** — there is no
|
||||
separate `services.hyperhive.agent.matrix.enable` boolean: the
|
||||
hive-internal `main` account in
|
||||
`services.hyperhive.agent.matrixAccounts` is declared from this
|
||||
URL, so `null` leaves that set empty and the whole integration —
|
||||
daemon unit, path watcher, injected MCP entry — is not generated
|
||||
|
|
@ -214,12 +214,11 @@ in
|
|||
|
||||
config = {
|
||||
assertions = [
|
||||
# The "extras require `matrix.enable`" assertion that used to head this
|
||||
# list is gone with the option: a non-empty account set is now what
|
||||
# enables matrix, so the condition it checked has become the definition
|
||||
# of the thing it was checking. An operator declaring only an external
|
||||
# account, with no hive homeserver, is a config this module now renders
|
||||
# rather than rejects — matrix on, no `main`.
|
||||
# There is no "extras require `matrix.enable`" assertion: a non-empty
|
||||
# account set is what enables matrix, so that condition is the
|
||||
# definition of the thing it would check. An operator declaring only an
|
||||
# external account, with no hive homeserver, gets a rendered config —
|
||||
# matrix on, no `main`.
|
||||
#
|
||||
# `main` is not a forbidden key --- this module declares it
|
||||
# itself (see the `matrixAccounts.main` definition below), so the
|
||||
|
|
|
|||
|
|
@ -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>`,
|
||||
# while the host tier has always been under `services.hyperhive.*`. The agent
|
||||
# tier moved to `services.hyperhive.agent.<name>`; these entries keep an
|
||||
# existing `agent.nix` evaluating (with a deprecation warning) against the old
|
||||
# spelling.
|
||||
# Per-agent options live at `services.hyperhive.agent.<name>`, beside the host
|
||||
# tier's `services.hyperhive.*`. These entries keep an `agent.nix` that still
|
||||
# uses the top-level `hyperhive.<name>` spelling evaluating, with a
|
||||
# deprecation warning.
|
||||
#
|
||||
# ⚠️ 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
|
||||
|
|
@ -119,9 +118,9 @@
|
|||
[ "hyperhive" "logs" "queryUrl" ]
|
||||
[ "services" "hyperhive" "agent" "logs" "queryUrl" ]
|
||||
)
|
||||
# `matrix.enable` is deliberately absent from this list: the option it
|
||||
# renamed to no longer exists, and a rename pointing at an undeclared
|
||||
# option breaks evaluation for everyone. Both of its spellings are
|
||||
# `matrix.enable` is deliberately absent from this list: it has no
|
||||
# replacement option, and a rename pointing at an undeclared option
|
||||
# breaks evaluation for everyone. Both of its spellings are
|
||||
# `mkRemovedOptionModule` in ./default.nix instead.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "hyperhive" "matrix" "url" ]
|
||||
|
|
|
|||
|
|
@ -239,8 +239,8 @@ in
|
|||
# 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
|
||||
# running it decides; its two packages moved as well, further down with
|
||||
# the other `*.package` moves. ⚠️ `enable` is NOT here: it was renamed
|
||||
# earlier and its shim lives in ./hive-matrix.nix, so that file is the one
|
||||
# the other `*.package` moves. ⚠️ `enable` is NOT here: its shim lives in
|
||||
# ./hive-matrix.nix, so that file is the one
|
||||
# to check before assuming this table is the whole matrix story.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "matrix" "openFirewall" ]
|
||||
|
|
@ -255,7 +255,7 @@ in
|
|||
[ "services" "hyperhive" "deploy" "matrix" "maxRequestSize" ]
|
||||
)
|
||||
# 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
|
||||
# the swarm's appservice.
|
||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "matrix" "registrationTokenFile" ] ''
|
||||
|
|
@ -301,28 +301,27 @@ in
|
|||
[ "services" "hyperhive" "deploy" "victorialogs" "retentionPeriod" ]
|
||||
)
|
||||
|
||||
# The switch over all of the above, and the name changes with the path
|
||||
# because the old one described the wrong subject: those services are
|
||||
# required of the SWARM, while the option says whether THIS host runs
|
||||
# them. `allSwarmServices` is mara's own phrasing of what it means.
|
||||
# The switch over all of the above. `allSwarmServices` says whether THIS
|
||||
# host runs every service the swarm requires; those services are
|
||||
# required of the SWARM, so the name is about the swarm rather than this
|
||||
# host's wiring.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "enableRequiredServices" ]
|
||||
[ "services" "hyperhive" "deploy" "allSwarmServices" ]
|
||||
)
|
||||
|
||||
# The mode above that one. It sat at the TOP of `services.hyperhive`,
|
||||
# which is the same defect one tier up: that namespace is everything
|
||||
# about hyperhive, not the settings of a single hive. The new name says
|
||||
# what the mode asserts — the whole swarm runs on this host — instead of
|
||||
# naming its mechanism.
|
||||
# The mode above that one lives under `deploy`, not at the TOP of
|
||||
# `services.hyperhive`: that namespace is everything about hyperhive, not
|
||||
# the settings of a single hive. `singleHostSwarm` names what the mode
|
||||
# asserts — the whole swarm runs on this host — instead of its mechanism.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "enableAllLocalDefaults" ]
|
||||
[ "services" "hyperhive" "deploy" "singleHostSwarm" ]
|
||||
)
|
||||
|
||||
# The hive TLS state dir sat at the TOP of `services.hyperhive`, which is
|
||||
# meant to be everything about hyperhive rather than the settings of one
|
||||
# hive. `hive-controller` is hive-c0re's new name (mara, on the issue).
|
||||
# The hive TLS state dir lives under `deploy.hive-controller` (the
|
||||
# hive-c0re daemon's namespace), not at the TOP of `services.hyperhive`,
|
||||
# which is everything about hyperhive rather than the settings of one hive.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "tls" "stateDir" ]
|
||||
[ "services" "hyperhive" "deploy" "hive-controller" "tls" "stateDir" ]
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ in
|
|||
clientKeyFile = lib.mkDefault "${pkiDir}/client-key.pem";
|
||||
serverCaFile = lib.mkDefault "${pkiDir}/ca.pem";
|
||||
|
||||
# 🩸 Readers that used to present `client.pem` above, each now pointed at
|
||||
# a leaf of its own. The leaf is what bao sees, so this pairing is the
|
||||
# 🩸 The readers below each present a leaf of their own rather than
|
||||
# `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
|
||||
# one principal per grant — see ./swarm-bao.nix's `perHiveReaders` block
|
||||
# for the grants themselves.
|
||||
|
|
@ -193,11 +193,11 @@ in
|
|||
[ -s ${pkiDir}/matrix-ctl.pem ] || ${signLeaf} ${pkiDir} matrix-ctl \
|
||||
${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
|
||||
# cert-auth role on the CN, so units sharing one leaf were one principal
|
||||
# holding the union of their grants, and the union included every agent
|
||||
# credential in the swarm.
|
||||
# cert-auth role on the CN, so units sharing one leaf would be one
|
||||
# principal holding the union of their grants, and the union would
|
||||
# include every agent credential in the swarm.
|
||||
#
|
||||
# ⚠️ 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
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ let
|
|||
# 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.
|
||||
#
|
||||
# 🩸 This principal's OWN leaf, not `clientCertFile` — the hive's, which four
|
||||
# units used to share. Bao matches a cert-auth role on the CN, so one leaf for
|
||||
# four readers was ONE principal holding the union of four grants: read on
|
||||
# 🩸 This principal's OWN leaf, not `clientCertFile` — the hive's. Bao
|
||||
# matches a cert-auth role on the CN, so one leaf shared by four readers
|
||||
# would be ONE principal holding the union of four grants: read on
|
||||
# `swarm/agents/*` AND `swarm/hives/<hive>/*` AND `swarm/services/*`, when
|
||||
# this unit reads one queue credential and nothing else. Its own leaf carries
|
||||
# `<queueAgentCommonNamePrefix>-<hive>` and its role grants the single path
|
||||
|
|
@ -53,11 +53,11 @@ let
|
|||
baoDeploy.queueAgentClientCertFile != null && baoDeploy.queueAgentClientKeyFile != null;
|
||||
|
||||
# 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
|
||||
# used to decide whether a missing per-principal leaf is a mistake or a
|
||||
# deployment that has no store: a host holding neither is the supported
|
||||
# no-store shape, and one holding this pair but not the pair above stopped
|
||||
# short of naming this reader's own.
|
||||
# one thing a remote-store deployment has to place by hand. Only used to
|
||||
# decide whether a missing per-principal leaf is a mistake or a deployment
|
||||
# that has no store: a host holding neither is the supported no-store
|
||||
# shape, and one holding this pair but not the pair above reads the store
|
||||
# without a reader identity of its own.
|
||||
hiveReaderIdentity = baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null;
|
||||
|
||||
credentialDir = toString deployCfg.hive-controller.queue.agentCredentialDir;
|
||||
|
|
|
|||
|
|
@ -115,17 +115,14 @@ in
|
|||
HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES = otel.extraResourceAttributes;
|
||||
}
|
||||
# HYPERHIVE_OTEL_HEADERS_CREDENTIAL is deliberately NOT emitted, and
|
||||
# its absence is the security half of this design: it was the variable
|
||||
# that put the upstream token into an agent's own settings.json. The
|
||||
# delivery path it drove — an nspawn credential forwarded by
|
||||
# host_config.rs, then written to an agent-readable file by
|
||||
# claude-settings.nix's `hive-otel-header` oneshot — no longer exists
|
||||
# anywhere; it was removed along with this variable's last consumer.
|
||||
# its absence is the security half of this design: that variable would
|
||||
# put the upstream token into an agent's own settings.json. Nothing
|
||||
# delivers the token into the container — no nspawn credential, no
|
||||
# agent-readable header file.
|
||||
#
|
||||
# Kept as a comment rather than deleted because the useful part is the
|
||||
# RULE, not the history: the collector holding the credential achieves
|
||||
# nothing while anything else hands out a copy, so there is exactly one
|
||||
# holder and it is on the host.
|
||||
# The rule: the collector holding the credential achieves nothing while
|
||||
# anything else hands out a copy, so there is exactly one holder and it
|
||||
# is on the host.
|
||||
// lib.optionalAttrs (otel.metricIntervalMs != null) {
|
||||
HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,11 +207,8 @@ in
|
|||
|
||||
# `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service`
|
||||
# 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 —
|
||||
# so the container boots immediately and needs no start-timeout bump. The
|
||||
# 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.
|
||||
# is not on the boot-critical path — hive-c0re owns it out of band — so
|
||||
# the container boots immediately and needs no start-timeout bump.
|
||||
systemd.services."container@hive-ci" = caTrust.containerOrdering;
|
||||
|
||||
containers.hive-ci = {
|
||||
|
|
@ -415,7 +412,7 @@ in
|
|||
# runner stays down (Restart=on-failure retries) until c0re
|
||||
# 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
|
||||
# own connect retries cover any brief window. `mkBefore` runs this
|
||||
# ahead of any upstream pre-steps.
|
||||
|
|
|
|||
|
|
@ -142,11 +142,6 @@ in
|
|||
matter for access from outside the host. Flip to `true` when
|
||||
you want the operator's browser or external git clients to
|
||||
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";
|
||||
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.
|
||||
#
|
||||
# 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
|
||||
# outlived what it claims exists.
|
||||
if [ ! -s "$token_path" ]; then
|
||||
# `write:admin` added for `swarm-controller::forge::Client::
|
||||
# ensure_agent_user` (the `CreateForgeUser` jobq node) —
|
||||
# Forgejo's `admin_create_user` HTTP endpoint refused every
|
||||
# call with "token does not have at least one of required
|
||||
# scope(s): [write:admin]" without it, an already-deployed
|
||||
# swarm hitting this the moment that node shipped. A host
|
||||
# whose token was minted before this scope was added won't
|
||||
# pick it up automatically — the `[ ! -s "$token_path" ]`
|
||||
# guard above only mints when the file is absent, by design
|
||||
# (see its own comment) — so an existing deployment needs
|
||||
# its token file deleted to force a re-mint with the new
|
||||
# scope.
|
||||
# `write:admin` is required by `swarm-controller::forge::Client::
|
||||
# ensure_agent_user` (the `CreateForgeUser` jobq node):
|
||||
# Forgejo's `admin_create_user` HTTP endpoint refuses a
|
||||
# token without it ("token does not have at least one of
|
||||
# required scope(s): [write:admin]"). A host whose token was
|
||||
# minted without this scope keeps it — the
|
||||
# `[ ! -s "$token_path" ]` guard above only mints when the
|
||||
# file is absent, by design (see its own comment) — so such a
|
||||
# deployment needs its token file deleted to force a re-mint
|
||||
# with the full scope.
|
||||
#
|
||||
# Token name carries a timestamp suffix rather than the
|
||||
# fixed `swarm-controller-boot` this used to be — a real
|
||||
# incident, not a hypothetical: deleting the token *file*
|
||||
# (the remediation above) doesn't delete the token Forgejo
|
||||
# already has under that name, so re-running this unit hit
|
||||
# "Command error: access token name has been used already"
|
||||
# and failed outright. Same fix `hive-c0re::forge::users::
|
||||
# mint_token` already uses for its own agent tokens, for
|
||||
# the identical reason — a monotonic suffix means a re-mint
|
||||
# can never collide with whatever this account already has.
|
||||
# Token name carries a timestamp suffix rather than a fixed
|
||||
# name: deleting the token *file* (the remediation above)
|
||||
# doesn't delete the token Forgejo already has under that
|
||||
# name, so re-running this unit would fail with "Command
|
||||
# error: access token name has been used already". Same
|
||||
# approach `hive-c0re::forge::users::mint_token` uses for its
|
||||
# own agent tokens — a monotonic suffix means a re-mint can
|
||||
# never collide with whatever this account already has.
|
||||
out=$(forgejo admin user generate-access-token \
|
||||
--username ${lib.escapeShellArg swarmControllerForgeUser} \
|
||||
--token-name "swarm-controller-boot-$(date +%s)" \
|
||||
|
|
|
|||
|
|
@ -30,9 +30,10 @@ let
|
|||
effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl;
|
||||
in
|
||||
{
|
||||
# Forge moved under `swarm` when the swarm-global services were
|
||||
# consolidated. One rename for the namespace: the subtree comes with it,
|
||||
# so existing hives keep evaluating and get one warning naming both paths.
|
||||
# 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" ]
|
||||
|
|
|
|||
|
|
@ -55,10 +55,9 @@ let
|
|||
# `cfg.useSelfSigned` (options.nix) is the derived single source of truth.
|
||||
useSelfSigned = cfg.useSelfSigned;
|
||||
|
||||
# nginx's own state dir. Kept at the historical `/var/lib/hive-gateway`
|
||||
# path rather than renamed with the move: it holds the imported leaf
|
||||
# across reboots, and renaming it would strand every existing hive's
|
||||
# certs for no gain.
|
||||
# nginx's own state dir, fixed at `/var/lib/hive-gateway` independent of
|
||||
# this module's own path: it holds the imported leaf across reboots, and
|
||||
# a different path would strand every existing hive's certs.
|
||||
tlsDir = "/var/lib/hive-gateway/tls";
|
||||
# TLS cert + key paths.
|
||||
# - 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
|
||||
# to whoever creates the path first.
|
||||
#
|
||||
# ⚠️ There is deliberately NO rule for /var/lib/hyperhive here. One
|
||||
# used to declare it `0755 root root` and could never win:
|
||||
# ⚠️ There is deliberately NO rule for /var/lib/hyperhive here.
|
||||
# `hive-c0re.service` sets `StateDirectory = "hyperhive"` with
|
||||
# `StateDirectoryMode = "0750"`, which systemd re-applies on every
|
||||
# start. Two mechanisms owning one path, and the loser still read as
|
||||
# a guarantee — it is why the resulting outage was first misdiagnosed
|
||||
# as someone having changed the mode. c0re's own unit owns that dir;
|
||||
# this module no longer has an opinion about it.
|
||||
# start, so a tmpfiles rule for the same path could never win: two
|
||||
# mechanisms owning one path, the loser still reading as a guarantee.
|
||||
# c0re's own unit owns that dir; this module has no opinion about it.
|
||||
systemd.tmpfiles.rules = [
|
||||
# The gateway's own config dir — NOT under /var/lib/hyperhive. c0re
|
||||
# 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
|
||||
# absence is load-bearing rather than an omission.
|
||||
#
|
||||
# It used to be here, to let nginx traverse `/var/lib/hyperhive` and
|
||||
# reach the config fragments that lived inside: `hive-c0re.service`
|
||||
# declares `StateDirectory = "hyperhive"` with `StateDirectoryMode =
|
||||
# "0750"` owned by `hive-core`, and systemd re-applies that on every
|
||||
# c0re start — beating this module's own tmpfiles rule for the same
|
||||
# path. Without the group, nginx failed its config test and never
|
||||
# started (`nginx: [emerg] open() ".../agents.conf" failed (13:
|
||||
# Permission denied)`), taking the gateway and every hive domain
|
||||
# behind it down.
|
||||
#
|
||||
# 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.
|
||||
# `hive-c0re.service` declares `StateDirectory = "hyperhive"` with
|
||||
# `StateDirectoryMode = "0750"` owned by `hive-core`, and systemd
|
||||
# re-applies that on every c0re start. The group would let nginx
|
||||
# traverse that dir, but it also gives nginx read access to everything
|
||||
# ELSE group-readable under it, including the broker sqlite — i.e.
|
||||
# every message between every agent, reachable by the process whose
|
||||
# entire job is parsing untrusted network input. The config fragments
|
||||
# nginx reads live in the gateway's own dir instead, so nginx needs no
|
||||
# access to c0re's tree. Adding this line would reopen that exposure.
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [
|
||||
|
|
@ -424,12 +413,11 @@ in
|
|||
# every name a service module contributed. See `docs/networking/gateway.md`
|
||||
# ("Local dev").
|
||||
#
|
||||
# This used to restate the per-service list a THIRD time (after the
|
||||
# vhosts and the dnsmasq records), with its own copy of each
|
||||
# service's guard. It is the same question — "which names does this
|
||||
# host answer for" — so it reads the same answer; a service added
|
||||
# later lands here with no edit, and cannot land here with a
|
||||
# different condition than it used for DNS.
|
||||
# This does not restate the per-service list (the vhosts and the
|
||||
# dnsmasq records already carry it). It is the same question —
|
||||
# "which names does this host answer for" — so it reads the same
|
||||
# answer; a service added later lands here with no edit, and cannot
|
||||
# land here with a different condition than it used for DNS.
|
||||
networking.hosts = lib.mkIf cfg.localHostsEntry {
|
||||
"127.0.0.1" = lib.unique ([ hyperhiveDomain ] ++ cfg.localNames);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -82,10 +82,6 @@ in
|
|||
nginx vhost. Leave off when running behind another reverse
|
||||
proxy (e.g. caddy / traefik on the host) that handles TLS
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -187,22 +187,19 @@ let
|
|||
# Swagger UI: nginx hosts the FULL themed dist (`swaggerUiTheme` —
|
||||
# vendored Swagger UI + our overlay, see nix/packages/swagger-ui-
|
||||
# dist.nix + swagger-ui-theme.nix) straight from the store, with NO
|
||||
# fallback to hive-c0re at all — per the operator's shape: "core
|
||||
# should not need the swagger ui at all if it is hosted in gateway"
|
||||
# / "core only hosts the json". Only `/api/openapi.json`
|
||||
# (the live-generated spec `index.html` fetches; not under this
|
||||
# prefix) still proxies to c0re via "/api/" below — that's the one
|
||||
# thing that has to stay dynamic.
|
||||
# fallback to hive-c0re at all: core hosts only the JSON. Only
|
||||
# `/api/openapi.json` (the live-generated spec `index.html` fetches;
|
||||
# not under this prefix) 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 length (no ordering/`=` needed), and now needs to cover
|
||||
# every file in the tree (bundle.js, maps, favicons, …), not just
|
||||
# our 2 override files — hive-c0re no longer serves any of this as
|
||||
# a fallback once its own `utoipa-swagger-ui` mount is removed.
|
||||
# `= /api/docs` (no trailing slash) issues the same redirect
|
||||
# `utoipa-swagger-ui`'s router used to: that mount is going away
|
||||
# too, so nginx has to own it now, or the H0M3 hub's own `/api/docs`
|
||||
# link (no trailing slash) would 404 once hive-c0re drops the route.
|
||||
# prefix length (no ordering/`=` needed), and covers every file in
|
||||
# the tree (bundle.js, maps, favicons, …), not just our 2 override
|
||||
# files — hive-c0re serves none of this as a fallback.
|
||||
# `= /api/docs` (no trailing slash) redirects to `/api/docs/`: nginx
|
||||
# owns that redirect because hive-c0re has no route for it, and the
|
||||
# H0M3 hub's own `/api/docs` link (no trailing slash) would 404
|
||||
# without it.
|
||||
swaggerUiLocations = {
|
||||
"= /api/docs" = {
|
||||
extraConfig = ''
|
||||
|
|
|
|||
|
|
@ -153,12 +153,11 @@ let
|
|||
# to host it — otherwise moving the container between hives would look
|
||||
# like a different homeserver.
|
||||
#
|
||||
# ⚠️ Changing this default is a BREAKING change in a way that moving
|
||||
# `gatewayHost` was not: `serverName` is baked irrevocably into every
|
||||
# user and room id, so a deployment that rebuilds onto a new one is a
|
||||
# *different homeserver*, not a renamed one. Existing hives pin the old
|
||||
# value explicitly (see the option's description); the default is what
|
||||
# a fresh swarm gets.
|
||||
# ⚠️ Changing this default is breaking: `serverName` is baked
|
||||
# irrevocably into every user and room id, so a deployment that
|
||||
# rebuilds onto a new one is a *different homeserver*. Existing hives
|
||||
# pin their value explicitly (see the option's description); the
|
||||
# default is what a fresh swarm gets.
|
||||
#
|
||||
# Total on a null swarm domain, deliberately: the required-domain
|
||||
# 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
|
||||
# knobs: docs/networking/gateway.md.
|
||||
|
||||
# Matrix moved under `swarm` when the swarm-global services were
|
||||
# consolidated. One rename for the namespace: the subtree comes with it,
|
||||
# so existing hives keep evaluating and get one warning naming both paths.
|
||||
# Matrix lives under `swarm`, beside the other swarm-global services. One
|
||||
# rename for the top-level `services.hyperhive.matrix` namespace: the
|
||||
# subtree comes with it, so existing hives keep evaluating and get one
|
||||
# warning naming both paths.
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "matrix" ]
|
||||
|
|
@ -311,11 +311,6 @@ in
|
|||
the homeserver to other hives or when an external matrix
|
||||
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
|
||||
intentionally not opened here. tuwunel serves the federation
|
||||
API on the same `httpPort` as the client-server API by
|
||||
|
|
|
|||
|
|
@ -130,9 +130,9 @@ let
|
|||
# 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
|
||||
# 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
|
||||
# one host in the swarm, so "this host cannot issue it" is no longer
|
||||
# one of the answers.
|
||||
# secret store's `pki` mount, reachable from every host rather than
|
||||
# from a sub-CA on one host in the swarm, so "this host cannot issue
|
||||
# it" is not one of the answers.
|
||||
want_svc=${if localServiceDomains == [ ] then "0" else "1"}
|
||||
|
||||
# Expiry is not the only way a leaf goes wrong. One signed when the
|
||||
|
|
@ -346,17 +346,14 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf active {
|
||||
# 🚫 The "these service names are outside this hive's domain and this
|
||||
# host cannot sign for them" warning is GONE, and its absence is the
|
||||
# deliverable rather than a tidy-up. It fired on
|
||||
# `!swarm.ca.autoConfigure` — i.e. "this host does not hold the swarm
|
||||
# root key" — which was the whole reason a hive could end up serving
|
||||
# its own leaf on a swarm-service name. The services leaf now comes
|
||||
# from the secret store's `pki` mount, which every hive reaches over
|
||||
# the network with its own identity, so holding the root key stopped
|
||||
# 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.
|
||||
# 🚫 There is deliberately NO warning that these service names are
|
||||
# outside this hive's domain and this host cannot sign for them. The
|
||||
# services leaf comes from the secret store's `pki` mount, which every
|
||||
# hive reaches over the network with its own identity, so holding the
|
||||
# swarm root key does not decide whether a hive can serve a
|
||||
# swarm-service name. Such a warning would fire on every rebuild of
|
||||
# every hive that is not the CA host, about a fallback that does not
|
||||
# happen.
|
||||
|
||||
# The state dir and the trust bundle. Never waits on the store: the
|
||||
# store's container is ordered after this unit, which writes the
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ in
|
|||
|
||||
# Where the swarm lives. Declared beside the hive's own identity
|
||||
# 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
|
||||
# is genuinely new: nothing moved here, so there is no alias.
|
||||
# swarm is a sub-domain of it. No alias: this option has no earlier
|
||||
# spelling.
|
||||
options.services.hyperhive.swarm.domain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
|
|
@ -145,7 +145,7 @@ in
|
|||
(`services.hyperhive.deploy.hive-controller.enable`,
|
||||
`deploy.bao.enable`, `deploy.matrix.enable`). Distinct from
|
||||
`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
|
||||
*addressed* by as well as the one it is called. Exposed to
|
||||
agents as `HYPERHIVE_HIVE_NAME`; surfaced in the dashboard
|
||||
|
|
|
|||
|
|
@ -60,9 +60,8 @@ in
|
|||
# rather than the file.
|
||||
#
|
||||
# ⛔ **Do not hand this path to a consumer that reads only ONE
|
||||
# certificate from it.** This comment used to offer an OIDC verifier's
|
||||
# `issuer_ca_path` as the motivating example; that is exactly what took
|
||||
# the swarm collector down for forty minutes once. The bundle is
|
||||
# certificate from it** — an OIDC verifier's `issuer_ca_path` is
|
||||
# exactly such a reader, and takes the swarm collector down. The bundle is
|
||||
# `system CAs ++ hive anchors`, so the anchor is ~123rd, and a
|
||||
# first-certificate-only reader gets whichever public CA sorts first and
|
||||
# can verify nothing of ours — with a full, valid, 125-certificate file
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ in
|
|||
⚠️ The collector is therefore in the path of all telemetry. It runs
|
||||
on the same host as the agents and restarts on failure, and
|
||||
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
|
||||
'';
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ in
|
|||
onward, which is one hop for the whole hive rather than one per
|
||||
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.
|
||||
The `!= { }` guards around it stay meaningful — the set can still
|
||||
be forced empty, and a `prometheus` receiver with nothing to
|
||||
|
|
|
|||
|
|
@ -132,10 +132,10 @@ in
|
|||
tracked as a follow-up rather than papered over.
|
||||
|
||||
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
|
||||
default to whether the queue ran on *this* machine, which made
|
||||
the answer differ between two hosts of one swarm — set it false
|
||||
for a swarm whose hives authenticate to nothing.
|
||||
fact about the swarm rather than about any one host. It does not
|
||||
depend on whether the queue runs on *this* machine, so the answer
|
||||
is the same on every host of one swarm — set it false for a swarm
|
||||
whose hives authenticate to nothing.
|
||||
|
||||
The clients are inert until something authenticates with them:
|
||||
each is a client id and a secret sitting on this host. What
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ let
|
|||
# 🏷️ The id EXTENDS the hive's own (`hive-<name>-agent`) rather than taking a
|
||||
# prefix of its own (`agent-<name>`), because that reads as *the agent called
|
||||
# `<name>`* — which is the one thing this identity does not carry. The cost is
|
||||
# that the two ids are no longer distinguishable by prefix, so the responder's
|
||||
# that the two ids are not distinguishable by prefix, so the responder's
|
||||
# agent rule must be tried BEFORE its hive rule; `policy.rs` says so at the
|
||||
# match site, and the assertion below covers the case that ordering cannot.
|
||||
#
|
||||
|
|
@ -905,11 +905,11 @@ in
|
|||
};
|
||||
environment = {
|
||||
SWARM_AUTHELIA_BRIDGE_BIND = "127.0.0.1:${toString cfg.bridgePort}";
|
||||
# ONE file, and this is it. The bridge used to carry a
|
||||
# second, private `…-users.json` it treated as canonical
|
||||
# while writing `users.yml` as a rendering of it — two
|
||||
# canonical stores for one physical file, which is what
|
||||
# made `swarm agent create` refuse to start on a hive whose
|
||||
# ONE file, and this is it. The bridge keeps no second,
|
||||
# private users store: a store of its own treated as canonical,
|
||||
# with `users.yml` written as a rendering of it, would be two
|
||||
# canonical stores for one physical file, and would make
|
||||
# `swarm agent create` refuse to start on a hive whose
|
||||
# `users.yml` already held users.
|
||||
SWARM_AUTHELIA_BRIDGE_USERS_FILE = deployCfg.authelia.usersFile;
|
||||
# The CONFIGURED authelia, not whatever is on `PATH`: the
|
||||
|
|
|
|||
|
|
@ -760,22 +760,22 @@ let
|
|||
}
|
||||
'';
|
||||
|
||||
# ── the principals that used to share the hive's own leaf ──────────────────
|
||||
# ── the principals that each hold a leaf of their own ──────────────────────
|
||||
#
|
||||
# 🩸 Each of these reads exactly ONE path in the store, and until this split
|
||||
# each did it holding `deploy.bao.clientCertFile` — the hive's own leaf,
|
||||
# whose policy (`swarm-secret-client`'s `policy::render`) granted read on
|
||||
# `swarm/agents/*`, `swarm/hives/<hive>/*` AND `swarm/services/*`. Principals
|
||||
# presenting one certificate are ONE principal to bao, so the union of what
|
||||
# they need was the only grant expressible: the unit that fetches Grafana's
|
||||
# OIDC client secret could fetch every agent credential in the swarm, and the
|
||||
# unit that fetches this hive's queue credential could fetch Grafana's. Least
|
||||
# privilege was not misconfigured, it was unrepresentable.
|
||||
# 🩸 Each of these reads exactly ONE path in the store, and does it holding
|
||||
# its own leaf rather than `deploy.bao.clientCertFile` — the hive's own leaf,
|
||||
# whose policy (`swarm-secret-client`'s `policy::render`) grants read on
|
||||
# `swarm/agents/*`, `swarm/hives/<hive>/*` AND `swarm/services/*`.
|
||||
# Principals presenting one certificate are ONE principal to bao, so sharing
|
||||
# the hive's leaf would leave only the union of what they need expressible:
|
||||
# the unit that fetches Grafana's OIDC client secret could fetch every agent
|
||||
# credential in the swarm, and the unit that fetches this hive's queue
|
||||
# credential could fetch Grafana's.
|
||||
#
|
||||
# Each now gets its own leaf, its own cert-auth role and a policy holding the
|
||||
# one `secret/data/…` path that principal's script actually names — spelled to
|
||||
# the LEAF rather than to a prefix, for the reason `matrixCtlPolicyText` above
|
||||
# gives for doing the same.
|
||||
# Each gets its own leaf, its own cert-auth role and a policy holding the
|
||||
# one `secret/data/…` path that principal's script actually names — spelled
|
||||
# to the LEAF rather than to a prefix, for the reason `matrixCtlPolicyText`
|
||||
# above gives for doing the same.
|
||||
#
|
||||
# ⚠️ The queue-credential reader is PER HIVE and the OIDC readers are not,
|
||||
# and that asymmetry is the shape of the paths rather than a preference. A
|
||||
|
|
@ -784,7 +784,7 @@ let
|
|||
# `swarm/services/<client-id>/` and a swarm registers each of those exactly
|
||||
# once. A single role for a per-hive reader would have to name `hives/*` to
|
||||
# serve every hive — which would let one hive read another's queue
|
||||
# credential.
|
||||
# credential, a reach no hive needs.
|
||||
readStanza = path: ''
|
||||
path "${path}" {
|
||||
capabilities = ["read"]
|
||||
|
|
@ -1024,10 +1024,9 @@ let
|
|||
# `tls_require_and_verify_client_cert` on the API listener to scrape THAT
|
||||
# instead, would each trade that property away for nothing.
|
||||
#
|
||||
# Unconditional, and that is the change this carries: the reader is now the
|
||||
# collector in this very container (below), which renders wherever the store
|
||||
# does. There is no longer a host where this endpoint has no reader, so there
|
||||
# is nothing left for it to be a function of.
|
||||
# Unconditional: the reader is the collector in this very container (below),
|
||||
# which renders wherever the store does. There is no host where this
|
||||
# endpoint has no reader, so it is not a function of anything.
|
||||
metricsListener = {
|
||||
metrics = {
|
||||
type = "tcp";
|
||||
|
|
@ -1104,22 +1103,18 @@ let
|
|||
'';
|
||||
|
||||
# ⚠️ Neither the listener above nor the forwarder below has a condition of
|
||||
# its own, and they lost theirs for the same reason at different times.
|
||||
# its own.
|
||||
#
|
||||
# The listener used to be gated on `scrapeHere = deployCfg.swarm-otel.enable`
|
||||
# — "does the swarm's collector happen to run on THIS host" — because the
|
||||
# thing reading it was that collector, over loopback, which only works when
|
||||
# the two land together. mara's ruling (*"move the existing scraper
|
||||
# to the local collector"*) removes the premise rather than the symptom: the
|
||||
# scraper is now in this container, so the reader is wherever the store is,
|
||||
# and the metrics path stops being silently dead on every host that does not
|
||||
# also run swarm-otel.
|
||||
# The listener's reader is the collector in this container, which sits
|
||||
# wherever the store does. "Does the swarm's collector run on THIS host" is
|
||||
# not a question it depends on: gating it on that would leave the metrics
|
||||
# path silently dead on every host that does not also run swarm-otel.
|
||||
#
|
||||
# The forwarder never had a defensible one. "Does the swarm have a collector"
|
||||
# is not a question that can be false (mara: "the swarm always has at least
|
||||
# one instance of all the swarm level services"), so both earlier gates —
|
||||
# `otel.enable`, then `deploy.swarm-otel.enable` — asked about the wrong host
|
||||
# and rendered no forwarder wherever the answer happened to be no.
|
||||
# "Does the swarm have a collector" is not a question that can be false —
|
||||
# the swarm always has at least one instance of all the swarm level
|
||||
# services — so gating the forwarder on `otel.enable` or
|
||||
# `deploy.swarm-otel.enable` would ask about the wrong host and render no
|
||||
# forwarder wherever the answer happened to be no.
|
||||
#
|
||||
# The only condition that belongs on either is the one already enclosing this
|
||||
# block: the store is deployed on this host.
|
||||
|
|
@ -2133,14 +2128,13 @@ in
|
|||
# granter step, on the host where that step runs.
|
||||
environment.etc."hyperhive/bao-bootstrap-policy.hcl".source = ./bao-bootstrap-policy.hcl;
|
||||
|
||||
# 🚫 No `swarm.otel.scrapeTargets.bao` entry any more, and its absence is
|
||||
# the deliverable rather than a tidy-up. That option is read only by the
|
||||
# SWARM collector, over loopback, so declaring the store there meant its
|
||||
# metrics existed exactly on the hosts that also ran swarm-otel and
|
||||
# nowhere else — silently, since a store with no entry looks identical to
|
||||
# one nothing scrapes. The scrape moved into this container's own
|
||||
# collector (see `receivers.prometheus` below), which travels with the
|
||||
# container's own collector is confirmed.
|
||||
# 🚫 No `swarm.otel.scrapeTargets.bao` entry, and its absence is
|
||||
# deliberate. That option is read only by the SWARM collector, over
|
||||
# loopback, so a store declared there would have metrics exactly on the
|
||||
# hosts that also run swarm-otel and nowhere else — silently, since a
|
||||
# store with no entry looks identical to one nothing scrapes. The scrape
|
||||
# runs in this container's own collector (see `receivers.prometheus`
|
||||
# below), which travels with the store.
|
||||
|
||||
# ⚠️ The one nginx exception to this file's header, and it is one because
|
||||
# it never terminates. `ssl_preread` reads the SNI off the ClientHello
|
||||
|
|
@ -2898,11 +2892,11 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
# 🩸 The principals that used to log in as the hive. See the block around
|
||||
# `perHiveReaders` for what each grant is and why the first is per hive;
|
||||
# the units are identical apart from the objects they
|
||||
# write, so the shape lives in `readerPolicyUnit` and the difference is
|
||||
# visible here in one line each.
|
||||
# 🩸 The principals that each log in with a leaf of their own, not as the
|
||||
# hive. See the block around `perHiveReaders` for what each grant is and
|
||||
# why the first is per hive; the units are identical apart from the
|
||||
# objects they write, so the shape lives in `readerPolicyUnit` and the
|
||||
# difference is visible here in one line each.
|
||||
systemd.services.swarm-bao-queue-agent-policy = readerPolicyUnit "write the per-hive queue-credential bao policies and cert-auth roles" queueAgentReaders;
|
||||
|
||||
systemd.services.swarm-bao-grafana-oidc-policy = readerPolicyUnit "write Grafana's OIDC-secret-reader bao policy and cert-auth role" grafanaOidcReaders;
|
||||
|
|
@ -3701,31 +3695,26 @@ in
|
|||
# this line leaves a collector that validates, starts,
|
||||
# reports healthy and forwards nothing.
|
||||
directory = "/var/log/journal";
|
||||
# ⚠️ `--merge` stays from when this journal was host-linked
|
||||
# and `journalctl --follow` (unqualified) found no entry for
|
||||
# the current boot under that tree, crash-looping the
|
||||
# receiver every ~2s with "No journal boot entry found for
|
||||
# the specified boot (+0)". The journal is local now, so the
|
||||
# current boot has an entry here too — kept rather than
|
||||
# dropped so the first deploy changes exactly one thing;
|
||||
# revisit once delivery is confirmed.
|
||||
# ⚠️ `--merge` is a guard: an unqualified
|
||||
# `journalctl --follow` over a journal with no entry for the
|
||||
# current boot (a host-linked one) crash-loops the receiver
|
||||
# every ~2s with "No journal boot entry found for the
|
||||
# specified boot (+0)". This journal is container-local, so
|
||||
# the current boot has an entry and the flag changes nothing.
|
||||
merge = true;
|
||||
storage = "file_storage";
|
||||
};
|
||||
|
||||
# The scrape that used to belong to the swarm collector, now
|
||||
# one hop shorter and one host less fussy — mara:
|
||||
# *"move the existing scraper to the local collector"*.
|
||||
# The store's scrape, run by this container's own collector.
|
||||
#
|
||||
# ⚠️ `metrics_path` and `params` are STATED, and both have to
|
||||
# be: openbao serves no `/metrics` at all, and
|
||||
# `/v1/sys/metrics` answers JSON unless the format is asked
|
||||
# for. A scrape of the default path 404s and a scrape without
|
||||
# the query parses as nothing — each reads as a dead exporter
|
||||
# rather than as a wrong request, which is how the previous
|
||||
# owner of this scrape learned to spell it
|
||||
# (./swarm-otel.nix's `loopbackScrapeConfig`, still the
|
||||
# renderer for every other service's target).
|
||||
# rather than as a wrong request. The spelling matches
|
||||
# ./swarm-otel.nix's `loopbackScrapeConfig`, the renderer for
|
||||
# every other service's target.
|
||||
#
|
||||
# No credential, unlike the swarm collector's
|
||||
# `publishedScrapeTargets` jobs: the target is the loopback
|
||||
|
|
|
|||
|
|
@ -15,12 +15,11 @@
|
|||
# something real — nothing whose trust store is built at build time can
|
||||
# name it: `docs/swarm/ca.md::Distributing the root`.
|
||||
#
|
||||
# ⚠️ This root no longer issues the swarm's SERVICE certificates, and the
|
||||
# `swarm-services-ca` sub-CA that used to sit under it is gone rather than
|
||||
# moved. Those come out of the secret store's own `pki` mount now, anchored
|
||||
# on a root generated inside it (./swarm-bao.nix), and ./hive-tls.nix asks
|
||||
# for them over the network instead of signing them here. What is left here
|
||||
# is one job: the anchor each hive's own CA is issued under.
|
||||
# ⚠️ This root does not issue the swarm's SERVICE certificates; there is no
|
||||
# `swarm-services-ca` sub-CA under it. Those come out of the secret store's
|
||||
# own `pki` mount, anchored on a root generated inside it (./swarm-bao.nix),
|
||||
# and ./hive-tls.nix asks for them over the network instead of signing them
|
||||
# here. This root has one job: the anchor each hive's own CA is issued under.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
|
|
|
|||
|
|
@ -336,8 +336,8 @@ in
|
|||
hostUnit = true;
|
||||
enable = deployCfg.swarm-controller.enable;
|
||||
})
|
||||
# The avatar moved with the forge-objects pass that uploads it: from
|
||||
# hive-c0re's boot sweep to this daemon.
|
||||
# The avatar belongs to the forge-objects pass that uploads it, which
|
||||
# runs in this daemon.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "c0re" "orgAvatarPng" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "configOrgAvatarPng" ]
|
||||
|
|
|
|||
|
|
@ -215,11 +215,11 @@ let
|
|||
# its identity yet, which is a thing to say out loud rather than to route
|
||||
# around by reaching into authelia's tree whenever it happens to be local.
|
||||
#
|
||||
# 🩸 Grafana's OWN leaf, not `clientCertFile` — the hive's, which four units
|
||||
# used to share. Bao matches a cert-auth role on the CN, so one leaf for four
|
||||
# readers was ONE principal holding the union of four grants: this unit could
|
||||
# read every agent credential in the swarm and every other service's OIDC
|
||||
# client secret, when what it needs is the one path `storeSecretPath` names.
|
||||
# 🩸 Grafana's OWN leaf, not `clientCertFile` — the hive's. Bao matches a
|
||||
# cert-auth role on the CN, so one leaf shared by four readers would be ONE
|
||||
# principal holding the union of four grants: this unit could read every
|
||||
# agent credential in the swarm and every other service's OIDC client
|
||||
# secret, when what it needs is the one path `storeSecretPath` names.
|
||||
haveClientIdentity =
|
||||
baoDeploy.grafanaOidcClientCertFile != null && baoDeploy.grafanaOidcClientKeyFile != null;
|
||||
|
||||
|
|
@ -377,7 +377,7 @@ in
|
|||
and for two different reasons. Traces and Profiles front Tempo
|
||||
and Pyroscope, which this swarm does not run — a UI for a backend
|
||||
that does not answer. Logs Drilldown is the subtler one: this
|
||||
swarm *does* run a log store, so that argument no longer applies
|
||||
swarm *does* run a log store, so that argument does not apply
|
||||
to it, but the app is Loki-only and no configuration here changes
|
||||
that. Its volume views call `/loki/api/v1/index/volume`, which
|
||||
VictoriaLogs does not implement and answers "unsupported path
|
||||
|
|
@ -398,12 +398,12 @@ in
|
|||
services.hyperhive.gateway.enable = lib.mkDefault true;
|
||||
services.hyperhive.gateway.dns.enable = lib.mkDefault true;
|
||||
|
||||
# Registering the client is NOT here any more: it has to happen on the
|
||||
# host that runs authelia, and this whole block is gated on the host that
|
||||
# runs Grafana. ./glue-grafana-oidc-client.nix is where it moved to.
|
||||
# Registering the client is NOT here: it has to happen on the host that
|
||||
# runs authelia, and this whole block is gated on the host that runs
|
||||
# Grafana. ./glue-grafana-oidc-client.nix does it.
|
||||
|
||||
# This arm fires only where Grafana runs — this whole block is under
|
||||
# `deploy.grafana.enable`. SSO itself needs no assertion any more:
|
||||
# `deploy.grafana.enable`. SSO itself needs no assertion:
|
||||
# `swarm.authelia.url` is non-nullable, so every swarm has one and the
|
||||
# OIDC block below always names a real endpoint.
|
||||
assertions = [
|
||||
|
|
@ -511,14 +511,13 @@ in
|
|||
# authelia's host wrote it into, and that is true whether authelia is a
|
||||
# network away or in the container next door.
|
||||
#
|
||||
# 🩸 A second unit here used to copy the plaintext out of authelia's host
|
||||
# tree wherever the two were co-located, with the OIDC block gated on
|
||||
# either route being available. The ruling that deleted it: the store
|
||||
# exists so a host holds ONE out-of-band secret — its client certificate —
|
||||
# and reads everything else with it, so skipping the store when the
|
||||
# producer is local buys a round trip and costs a second delivery path, a
|
||||
# second way for the file to be wrong, and a gate whose false arm was a
|
||||
# Grafana with no login. Recorded in docs/swarm/secrets.md.
|
||||
# 🩸 No second unit copies the plaintext out of authelia's host tree
|
||||
# where the two are co-located. The store exists so a host holds ONE
|
||||
# out-of-band secret — its client certificate — and reads everything else
|
||||
# with it, so skipping the store when the producer is local would buy a
|
||||
# round trip and cost a second delivery path, a second way for the file
|
||||
# to be wrong, and a gate whose false arm is a Grafana with no login.
|
||||
# Recorded in docs/swarm/secrets.md.
|
||||
#
|
||||
# Shaped after ./glue-queue-agent-credential.nix, because it is a store
|
||||
# reader and this is what the store's readers do: a cert login that fails
|
||||
|
|
@ -771,18 +770,17 @@ in
|
|||
# property this option exists for — it should not arrive only
|
||||
# once a plugin happens to be listed.
|
||||
#
|
||||
# - 🩸 and the one this comment used to miss: it also sets
|
||||
# upstream's `plugins.preinstall_disabled` (nixpkgs'
|
||||
# - 🩸 it also sets upstream's `plugins.preinstall_disabled`
|
||||
# (nixpkgs'
|
||||
# grafana.nix defaults it to `declarativePlugins != null`,
|
||||
# saying preinstall "causes issues with declarativePlugins").
|
||||
# Preinstall is what fetches the Drilldown apps on first
|
||||
# start, so claiming this directory silently removes them.
|
||||
# That is why the option's default is not empty.
|
||||
#
|
||||
# The claim that used to sit here — "nothing is taken over on a
|
||||
# hive with no plugins, manual installation is impossible anyway"
|
||||
# — reasoned only about plugins a PERSON installs. Upstream
|
||||
# installs some itself, and those are the ones that went missing.
|
||||
# Taking this directory over is not harmless on a hive with no
|
||||
# plugins: that reasoning covers only plugins a PERSON installs,
|
||||
# and upstream installs some itself.
|
||||
declarativePlugins = deployCfg.grafana.plugins;
|
||||
|
||||
settings = {
|
||||
|
|
@ -839,9 +837,7 @@ in
|
|||
# Not conditional on anything, and the block below is not
|
||||
# either, which is what keeps the two from ever disagreeing.
|
||||
# Whether a password box sits on a public login page cannot
|
||||
# depend on which host happens to run authelia — and it used to,
|
||||
# in a module where the OIDC block was gated and this line was
|
||||
# not.
|
||||
# depend on which host happens to run authelia.
|
||||
auth.disable_login_form = true;
|
||||
|
||||
# Emitted in every deployment, because SSO is a requirement of
|
||||
|
|
|
|||
|
|
@ -115,10 +115,9 @@ in
|
|||
today) listens on, at `127.0.0.1`. Reached through the gateway at
|
||||
`https://''${domain}/''${producerName}/`, same shape as a hive's
|
||||
own receiver — a swarm-level producer may not share a host with
|
||||
this collector (mara, on the swarm-controller topology: "the
|
||||
swarm services dont have to run on the same host as the swarm
|
||||
controller"), so loopback-only reachability is not a supported
|
||||
shape here any more than it is for a hive.
|
||||
this collector (the swarm services need not run on the same host
|
||||
as the swarm controller), so loopback-only reachability is not a
|
||||
supported shape here, as it is not for a hive.
|
||||
|
||||
Authenticated by `oidc/''${producerName}`, checking for the
|
||||
audience `services.hyperhive.swarm.controller.queueClientId`
|
||||
|
|
|
|||
|
|
@ -250,10 +250,10 @@ let
|
|||
# IS asserted is narrower and lives in `assertions` below — see
|
||||
# `hiveReaderIdentity`.
|
||||
#
|
||||
# 🩸 The collector's OWN leaf, not `clientCertFile` — the hive's, which four
|
||||
# units used to share. Bao matches a cert-auth role on the CN, so one leaf for
|
||||
# four readers was ONE principal holding the union of four grants: this unit
|
||||
# could read every agent credential in the swarm and Grafana's OIDC client
|
||||
# 🩸 The collector's OWN leaf, not `clientCertFile` — the hive's. Bao
|
||||
# matches a cert-auth role on the CN, so one leaf shared by four readers
|
||||
# would be ONE principal holding the union of four grants: this unit could
|
||||
# read every agent credential in the swarm and Grafana's OIDC client
|
||||
# secret, when what it needs is the one path `storeSecretPath` names.
|
||||
haveClientIdentity =
|
||||
baoDeploy.otelOidcClientCertFile != null && baoDeploy.otelOidcClientKeyFile != null;
|
||||
|
|
@ -396,23 +396,21 @@ in
|
|||
# this is how it authenticates *itself* to a service published behind
|
||||
# the gateway.
|
||||
#
|
||||
# Registering the client is NOT here any more: it has to happen on the
|
||||
# host that runs authelia, and this whole block is gated on the host that
|
||||
# runs the collector. ./glue-swarm-otel-oidc-client.nix is where it moved
|
||||
# to, the same split ./swarm-grafana.nix made for its own client.
|
||||
# Registering the client is NOT here: it has to happen on the host that
|
||||
# runs authelia, and this whole block is gated on the host that runs the
|
||||
# collector. ./glue-swarm-otel-oidc-client.nix does it, the same split
|
||||
# ./swarm-grafana.nix has for its own client.
|
||||
|
||||
# THE delivery unit — one route, in every deployment. The secret authelia
|
||||
# minted arrives out of the swarm secret store, which the publisher on
|
||||
# authelia's host wrote it into, whether authelia is a network away or in
|
||||
# the container next door.
|
||||
#
|
||||
# 🩸 A unit here used to copy the plaintext directly out of authelia's
|
||||
# host tree, reachable only because they share this host's network
|
||||
# namespace — which stopped working the moment authelia moved to another
|
||||
# host, which is the defect this whole change exists to fix. The ruling
|
||||
# that deleted it rather than gave it a remote sibling: the store exists
|
||||
# so a host holds ONE out-of-band secret — its client certificate — and
|
||||
# reads everything else with it. Recorded in docs/swarm/secrets.md.
|
||||
# 🩸 No unit here copies the plaintext directly out of authelia's host
|
||||
# tree: that is reachable only where authelia shares this host's network
|
||||
# namespace, and breaks the moment authelia is on another host. The store
|
||||
# exists so a host holds ONE out-of-band secret — its client certificate —
|
||||
# and reads everything else with it. Recorded in docs/swarm/secrets.md.
|
||||
#
|
||||
# Shaped after ./glue-queue-agent-credential.nix: a cert login that fails
|
||||
# LOUDLY, since every state it fails on is one a retry fixes, then a read
|
||||
|
|
@ -638,8 +636,8 @@ in
|
|||
'';
|
||||
}
|
||||
{
|
||||
# The blacklist now lives in a shared file, so this module no longer
|
||||
# controls its contents — and a guard whose subject can be edited
|
||||
# The blacklist lives in a shared file, so this module does not
|
||||
# control its contents — and a guard whose subject can be edited
|
||||
# elsewhere has to assert that its own case is still in there. Without
|
||||
# this, deleting one line from `reserved-names.nix` would silently
|
||||
# retire the check below rather than fail anything.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Migration shim for `services.hyperhive.swarm.peers`, replaced by the
|
||||
# `swarm.hives` directory in ./swarm.nix.
|
||||
#
|
||||
# ⚠️ This could not be a `mkRenamedOptionModule`. `hives` is not `peers`
|
||||
# under a new name, it is **`peers` ∪ {self}**: the set gains a member no
|
||||
# ⚠️ This is not a `mkRenamedOptionModule`. `hives` is not `peers`
|
||||
# under another name, it is **`peers` ∪ {self}**: the set gains a member no
|
||||
# existing config has ever written down, because a host's own identity
|
||||
# lived in *other* options entirely. A rename migrates a name and a
|
||||
# lives in *other* options entirely. A rename migrates a name and a
|
||||
# default can re-root a meaning; neither can conjure a new member.
|
||||
#
|
||||
# The whole module is self-contained and deletable — one file to remove
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ in
|
|||
'')
|
||||
];
|
||||
|
||||
# ⚠️ `services.hyperhive.swarm.ui` no longer exists as a namespace. `enable`
|
||||
# moved in an earlier slice and `package` in this one, and those were its
|
||||
# only two options — so both paths survive purely as renames in ./deploy.nix.
|
||||
# ⚠️ `services.hyperhive.swarm.ui` is not a namespace. Its two options,
|
||||
# `enable` and `package`, live under `deploy.swarm-ui`, and the `swarm.ui`
|
||||
# paths survive purely as renames in ./deploy.nix.
|
||||
options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
|
||||
|
|
|
|||
|
|
@ -262,6 +262,5 @@ in
|
|||
# ⚠️ Like the metrics store's, that endpoint is unauthenticated — which is
|
||||
# why `listenAddress` above is loopback and why nothing reaches it except
|
||||
# through the gateway, where the ingest route is authenticated. (This module
|
||||
# does declare a vhost; the line that used to say otherwise was already
|
||||
# wrong before the collector started pushing through it.)
|
||||
# declares a vhost.)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ let
|
|||
# explain their own admission rules; the second says why they are not one
|
||||
# list. They are applied HERE rather than in the module that happens to
|
||||
# consume them, because a hive name composes identifiers whatever else is
|
||||
# enabled — ./swarm-otel.nix used to hold these guards and gated them on its
|
||||
# own `enable`, so a swarm without the collector had no check at all.
|
||||
# enabled — gating them on another module's `enable` would leave a swarm
|
||||
# without that module with no check at all.
|
||||
reservedNames = import ../reserved-names.nix;
|
||||
reservedFragments = import ../reserved-hive-fragments.nix;
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ in
|
|||
# ⚠️ `lib.mkRemovedOptionModule` cannot do this job here, and
|
||||
# neither of its two halves survives the move into a submodule:
|
||||
# its `apply = throw` fires only when the value is READ, and
|
||||
# nothing reads this any more (that being the point of removing
|
||||
# nothing reads this (that being the point of removing
|
||||
# it); its `config.assertions` half would land on a submodule
|
||||
# that declares no `assertions` option at all. It is a
|
||||
# top-level tool. The assertion below is the working shape, and
|
||||
|
|
@ -316,14 +316,14 @@ in
|
|||
assertions = [
|
||||
{
|
||||
# An EMPTY `hives` fires this too, deliberately: since
|
||||
# `swarm.domain` became required, every hive is in a swarm — a
|
||||
# `swarm.domain` is required, every hive is in a swarm — a
|
||||
# swarm of one is still a swarm — so a directory with no entry
|
||||
# for this host is missing one either way. It also has to fire
|
||||
# here, because this host's own domain is now read out of the
|
||||
# directory: without the entry `services.hyperhive.domain` is
|
||||
# null and the generic required-domain assertion in
|
||||
# ./hive-network.nix would fire instead, naming an option the
|
||||
# operator should no longer be setting.
|
||||
# operator should not be setting.
|
||||
#
|
||||
# Guarded on `hiveName != null` so the required-hiveName
|
||||
# assertion in ./hyperhive.nix is what fires for that case —
|
||||
|
|
@ -349,11 +349,10 @@ in
|
|||
{
|
||||
# An error rather than a warning, deliberately. Re-declaring the
|
||||
# removed option above is what stops "option does not exist"; on
|
||||
# its own it would also turn a config that used to FAIL into one
|
||||
# that quietly evaluates with the setting ignored, which is a
|
||||
# worse answer than the unhelpful error it replaces. The operator
|
||||
# asked for a removal error, so this stays fatal until the field
|
||||
# is gone from their config.
|
||||
# its own it would turn a config that sets the removed field into
|
||||
# one that quietly evaluates with the setting ignored, which is a
|
||||
# worse answer than an error. So this stays fatal until the field
|
||||
# is gone from the config.
|
||||
#
|
||||
# Wording follows `lib.mkRemovedOptionModule`'s, so it reads like
|
||||
# every other removal the module system reports.
|
||||
|
|
@ -536,8 +535,8 @@ in
|
|||
# swarm's token endpoint. The other two coordinates are this machine's —
|
||||
# where the queue is *as seen from here*, and where its secret sits on this
|
||||
# disk — so they hang off the daemon that uses them. `hive-controller` is
|
||||
# hive-c0re's new name, which is why the namespace is the daemon's rather
|
||||
# than a `deploy.statusPublish` of its own.
|
||||
# the hive-c0re daemon's namespace, hence these live there rather than
|
||||
# under a `deploy.statusPublish` of their own.
|
||||
#
|
||||
# ⚠️ The all-or-nothing assertion above now spans both namespaces. That is
|
||||
# one service's own options either side of the split, not a service reaching
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ let
|
|||
{
|
||||
# A homeserver URL is the whole input: from it the module derives the
|
||||
# hive-internal `main` account, and from a non-empty account set the three
|
||||
# things that used to hang off `matrix.enable`.
|
||||
# matrix pieces: daemon unit, path watcher and injected MCP entry.
|
||||
name = "an agent with a homeserver gets a main account and the matrix units";
|
||||
ok =
|
||||
let
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ let
|
|||
ok = refusedWithoutIdentity controllerNoStore && !(refusedWithoutIdentity baoControllerHere);
|
||||
}
|
||||
{
|
||||
# No hive mints an agent's matrix account any more, so a controller that
|
||||
# No hive mints an agent's matrix account, so a controller that
|
||||
# did not know the homeserver would create every agent without one. The
|
||||
# default is the swarm's own `chat.` vhost, with no operator setting.
|
||||
name = "the controller is told the swarm's homeserver by default";
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ let
|
|||
deploy.bao.enable = true;
|
||||
deploy.bao.extraListenAddresses = [ "10.0.0.1" ];
|
||||
# Pinned, not incidental: the case counting these listeners is about the
|
||||
# declared API addresses. ⚠️ Since the store got its own collector, this no longer keeps the METRICS
|
||||
# listener out of the count — that one is a function of the store, not of
|
||||
# a collector's placement — so the case below subtracts it by name rather
|
||||
# than by leaving the swarm tier off.
|
||||
# declared API addresses. ⚠️ Leaving the swarm tier off does not keep the
|
||||
# METRICS listener out of the count — that one is a function of the
|
||||
# store, not of a collector's placement — so the case below subtracts it
|
||||
# by name.
|
||||
deploy.swarm-otel.enable = false;
|
||||
};
|
||||
|
||||
|
|
@ -156,11 +156,10 @@ let
|
|||
&& (j.params.format or [ ]) == [ "prometheus" ];
|
||||
}
|
||||
{
|
||||
# 🎯 This move's deliverable, and it needs BOTH halves or it is not a move:
|
||||
# the job is in the store's own container, and it is no longer declared
|
||||
# to the swarm collector. Only the second half can catch a "move" that
|
||||
# left the original behind — two scrapers of one endpoint, double-counted
|
||||
# and neither obviously wrong.
|
||||
# 🎯 Needs BOTH halves: the job is in the store's own container, and it
|
||||
# is not declared to the swarm collector. Only the second half can catch
|
||||
# a scrape declared in both places — two scrapers of one endpoint,
|
||||
# double-counted and neither obviously wrong.
|
||||
name = "the store's metrics are scraped by its own collector, not the swarm's";
|
||||
ok = baoScrapeJob baoWithCollector "bao" != null && scrapeJob baoWithCollector "bao" == null;
|
||||
}
|
||||
|
|
@ -206,20 +205,13 @@ let
|
|||
j != null && !(j ? metrics_path) && !(j ? params);
|
||||
}
|
||||
{
|
||||
# 🩸 This case USED to be the absence arm — "a store with no collector
|
||||
# beside it serves no metrics" — and this inverts it deliberately
|
||||
# rather than deleting it, because the condition it asserted is the bug.
|
||||
#
|
||||
# The old reasoning was sound for the old reader: the endpoint is
|
||||
# unauthenticated, so it should not exist where nothing reads it, and
|
||||
# the only reader was a swarm collector that had to be on this host.
|
||||
# What that rendered on every OTHER host was a store whose metrics went
|
||||
# nowhere, indistinguishably from a store nobody had asked to scrape.
|
||||
#
|
||||
# The reader now ships inside the container, so "no collector beside it"
|
||||
# describes no host that exists. The endpoint is still not exposed to
|
||||
# anything new: it is loopback in a netns whose only other occupants are
|
||||
# the store and its collector.
|
||||
# 🩸 The store's metrics endpoint does not depend on where the swarm
|
||||
# collector runs. The endpoint is unauthenticated, so it should not exist
|
||||
# where nothing reads it; the reader ships inside the container, so every
|
||||
# store has one. Gating the endpoint on a collector beside the store
|
||||
# would render a store whose metrics go nowhere, indistinguishable from
|
||||
# a store nobody had asked to scrape. The endpoint is loopback in a
|
||||
# netns whose only other occupants are the store and its collector.
|
||||
name = "a store whose swarm collector is elsewhere still serves and scrapes its metrics";
|
||||
ok =
|
||||
let
|
||||
|
|
@ -272,12 +264,11 @@ let
|
|||
(j.merge or false) == true && j.directory == "/var/log/journal";
|
||||
}
|
||||
{
|
||||
# The empty-directory trap this receiver used to fall into: `host` mode
|
||||
# bind-mounts a host directory journald never actually writes into, so
|
||||
# `merge`/`directory` above would keep validating and starting while
|
||||
# the receiver reads nothing — silently, same as every other failure
|
||||
# mode here. Guards against re-adding the exact flag this was fixed by
|
||||
# dropping.
|
||||
# The empty-directory trap: `host` mode bind-mounts a host directory
|
||||
# journald never actually writes into, so `merge`/`directory` above
|
||||
# would keep validating and starting while the receiver reads nothing —
|
||||
# silently, same as every other failure mode here. Guards against the
|
||||
# flag that links it.
|
||||
name = "the store's container does not link its journal onto the host";
|
||||
ok = !(lib.elem "--link-journal=host" (baoWithCollector.containers.swarm-bao.extraFlags or [ ]));
|
||||
}
|
||||
|
|
@ -291,7 +282,7 @@ let
|
|||
{
|
||||
# Both ends of the hop, because a mismatch between them is silent in both
|
||||
# directions: the exporter retries into a 404 and the receiver never
|
||||
# hears from it. Not "on one host" any more — the far end is the gateway
|
||||
# hears from it. Not "on one host" — the far end is the gateway
|
||||
# vhost the swarm collector serves its name on, which is the whole point
|
||||
# of addressing it by name rather than by a bridge address.
|
||||
name = "the store's forwarder exports to the swarm collector's own route";
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
#
|
||||
# Where the forge runs. A swarm has one, on the host with
|
||||
# `deploy.forgejo.enable`; every other hive is a client of it, and the parts
|
||||
# of a split deployment that used to lean on every host running a forge
|
||||
# (the OIDC client, the controller's token, the CI runner) still have to
|
||||
# hold.
|
||||
# of a split deployment that must not assume every host runs a forge
|
||||
# (the OIDC client, the controller's token, the CI runner) have to hold.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
|
|||
|
|
@ -70,13 +70,11 @@ let
|
|||
deploy.bao.grafanaOidcClientKeyFile = "/etc/pki/bao-grafana-oidc-key.pem";
|
||||
};
|
||||
|
||||
# A Grafana host holding no store identity. This used to be the shape the
|
||||
# module went QUIET on — no OIDC block, a warning, and a container whose
|
||||
# login form is off regardless, so no way in and nothing failed. It is kept
|
||||
# rather than deleted because the shape is still reachable by an operator;
|
||||
# what changed is the deliverable, from a warning nothing reads back to a
|
||||
# refusal naming the two options to set. Only the identity is missing, so an
|
||||
# arm below can name which refusal fired.
|
||||
# A Grafana host holding no store identity. The module refuses it with an
|
||||
# assertion naming the two options to set, rather than going QUIET: no OIDC
|
||||
# block can render and the container's login form is off regardless, so
|
||||
# there would be no way in and nothing would fail. Only the identity is
|
||||
# missing, so an arm below can name which refusal fired.
|
||||
grafanaNoIdentity = hive {
|
||||
deploy.grafana.enable = true;
|
||||
deploy.grafana.plugins = [ ];
|
||||
|
|
@ -104,9 +102,9 @@ let
|
|||
) m.assertions;
|
||||
cases = [
|
||||
{
|
||||
# This fixture enables grafana and NOT authelia, which is the shape the
|
||||
# login form used to stay enabled in: the toggle read "both services are
|
||||
# on this host" rather than "grafana requires SSO". Grafana ships an
|
||||
# This fixture enables grafana and NOT authelia, so the login-form toggle
|
||||
# has to follow "grafana requires SSO" rather than "both services are on
|
||||
# this host". Grafana ships an
|
||||
# `admin`/`admin` account and its vhost is on the public gateway, so a
|
||||
# password box there is a way in whatever the topology.
|
||||
name = "grafana disables its local login form even where authelia is not on this host";
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ let
|
|||
|
||||
# The hive-name guards, with the collector explicitly OFF. That is the whole
|
||||
# property: the guards live where `swarm.hives` is declared, so they run in a
|
||||
# deployment that has a secret store and no collector — which used to skip
|
||||
# them entirely, because they were assertions inside swarm-otel's own `mkIf`.
|
||||
# deployment that has a secret store and no collector, rather than being
|
||||
# assertions inside swarm-otel's own `mkIf`.
|
||||
#
|
||||
# ⚠️ `controllerCommonName` is overridden to a name containing NO reserved
|
||||
# fragment. Its default (`swarm-controller`) contains `swarm` and is caught
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ let
|
|||
# refuses; see ./host-modules/deploy.nix. `package` and `bridgePackage` are
|
||||
# ordinary options, so they do carry one. The two arms
|
||||
# below have different jobs. `usersFile` tests the rename; the nats one tests
|
||||
# that a reader repointed to the new namespace still renders the derived
|
||||
# path, which is the failure this slice could actually have shipped — seven
|
||||
# of those reads went through an alias a path-shaped grep cannot see.
|
||||
# that a reader pointed at the `deploy` namespace still renders the derived
|
||||
# path — seven of those reads go through an alias a path-shaped grep
|
||||
# cannot see.
|
||||
autheliaOldPath = hive {
|
||||
deploy.authelia.enable = true;
|
||||
deploy.nats.enable = true;
|
||||
|
|
@ -84,8 +84,8 @@ let
|
|||
|
||||
# A hive running NOTHING of the swarm's own services — no IdP here, no
|
||||
# `swarm.authelia.url` set by hand. The whole point of the fixture is what it
|
||||
# does *not* say: it is the shape whose IdP address used to be null, and
|
||||
# before that a co-location-derived guess.
|
||||
# does *not* say: no IdP address is given and none is derived from
|
||||
# co-location.
|
||||
autheliaNotColocated = hive { };
|
||||
# The same swarm's IdP host, for the arm that the two agree.
|
||||
autheliaColocated = hive { deploy.authelia.enable = true; };
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ let
|
|||
# deployment this unit exists for: authelia is the one host the store is
|
||||
# guaranteed not to share once either has a machine of its own.
|
||||
#
|
||||
# ⚠️ `enable` is deliberately NOT set here. It used to be, with a comment
|
||||
# saying the default asked whether both ran on this host — which documented
|
||||
# the co-location bug instead of catching it. Leaving it unset is what makes
|
||||
# ⚠️ `enable` is deliberately NOT set here: leaving it unset is what makes
|
||||
# this fixture exercise the default rather than mask it.
|
||||
secretPublisherRemote = hive {
|
||||
deploy.authelia.enable = true;
|
||||
|
|
@ -125,9 +123,9 @@ let
|
|||
&& !(lib.elem "swarm-grafana" (clients grafanaRemoteAuthelia));
|
||||
}
|
||||
{
|
||||
# The collector's half of the same defect and the same fix: this used to
|
||||
# be gated on `deploy.swarm-otel.enable`, so a split deployment
|
||||
# registered the client nowhere and authelia minted nothing to publish.
|
||||
# The collector's half of the same property: the client is not gated on
|
||||
# `deploy.swarm-otel.enable`, which would let a split deployment register
|
||||
# it nowhere, so authelia would mint nothing to publish.
|
||||
name = "the swarm's collector client is registered wherever authelia runs";
|
||||
ok =
|
||||
let
|
||||
|
|
|
|||
|
|
@ -112,10 +112,10 @@ let
|
|||
&& !(lib.hasInfix "secret/swarm/hives/" s);
|
||||
}
|
||||
{
|
||||
# The defect itself. These exporters used to be gated on the stores'
|
||||
# PER-HOST enables, so a collector that did not share a host with them
|
||||
# rendered none at all and dropped everything it received, from every
|
||||
# hive — silently, because an absent exporter is not an error.
|
||||
# These exporters must not be gated on the stores' PER-HOST enables: a
|
||||
# collector that did not share a host with them would render none at all
|
||||
# and drop everything it received, from every hive — silently, because
|
||||
# an absent exporter is not an error.
|
||||
name = "a collector that hosts neither store still exports to both";
|
||||
ok =
|
||||
let
|
||||
|
|
|
|||
|
|
@ -61,10 +61,10 @@ let
|
|||
# derivation means a prompt edit doesn't bust the cargo cache.
|
||||
#
|
||||
# …and the dep cache underneath is the one WITHOUT test targets. This
|
||||
# derivation has never run a test — `doCheck = false` below says so —
|
||||
# but it used to sit on a cache that compiled every dev-dependency and
|
||||
# test harness anyway, work whose only consumers are the checks. A
|
||||
# deploy paid for it on every toolchain or dependency change.
|
||||
# derivation never runs a test — `doCheck = false` below says so — so it
|
||||
# sits on a cache that skips every dev-dependency and test harness, work
|
||||
# whose only consumers are the checks. Compiling them here would make a
|
||||
# deploy pay for it on every toolchain or dependency change.
|
||||
workspaceBuild = craneLib.buildPackage {
|
||||
src = cleanSrc;
|
||||
cargoArtifacts = cargoArtifactsBinOnly;
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ buildNpmPackage {
|
|||
# Read from `../../frontend/npm-deps-hash`, the single source of
|
||||
# truth `./swarm-ui.nix` and `../checks.nix`'s `swarm-ui-typecheck`
|
||||
# also read — all three build from the one `frontend/package-lock.json`,
|
||||
# so one file keeps them from drifting independently (this used to be
|
||||
# a hardcoded copy per derivation, and only some of the three got
|
||||
# updated the one time the lockfile changed). Regenerate with
|
||||
# so one file keeps them from drifting independently (a hardcoded copy
|
||||
# per derivation would let some of the three miss a lockfile change).
|
||||
# Regenerate with
|
||||
# `prefetch-npm-deps frontend/package-lock.json` and overwrite the
|
||||
# file whenever the lockfile changes.
|
||||
npmDepsHash = builtins.readFile ../../frontend/npm-deps-hash;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
# Pure vendored Swagger UI 5.17.14 static dist — no hyperhive theming
|
||||
# (see ./swagger-ui-theme.nix for that layer). Deliberately NOT sourced
|
||||
# via the Rust `utoipa-swagger-ui-vendored` crate / Cargo.lock (per the
|
||||
# operator's ask: a nix package that just contains the plain static
|
||||
# dist, not fetched via cargo) — hive-c0re no longer needs to know this
|
||||
# exists at all once the gateway hosts it directly.
|
||||
# via the Rust `utoipa-swagger-ui-vendored` crate / Cargo.lock: this is a
|
||||
# nix package that just contains the plain static dist, not fetched via
|
||||
# cargo — hive-c0re does not need to know this exists, because the
|
||||
# gateway hosts it directly.
|
||||
#
|
||||
# Sourced straight from the `swagger-ui-dist` npm package, which ships
|
||||
# exactly the built `dist/` files (no source, no build step needed) at
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
# `{{config}}` placeholder its OWN build.rs
|
||||
# injects (`replace_default_url_with_config`
|
||||
# + `format_config`); hive-c0re doesn't serve
|
||||
# this file at all anymore, so that rewrite
|
||||
# this file at all, so that rewrite
|
||||
# is baked in statically here instead —
|
||||
# hand-verified byte-for-byte against what
|
||||
# `format_config` would produce, by applying
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@
|
|||
# The swarm collector names its components `<kind>/<owner>` and uses the hive
|
||||
# name as the owner, so a hive called `swarm` would silently replace the
|
||||
# swarm tier's own pipelines and lose its own — it would keep accepting
|
||||
# pushes into a pipeline that routes nowhere. Previously enforced only
|
||||
# against hive names, in `swarm-otel.nix`'s own `reservedOwners`.
|
||||
# pushes into a pipeline that routes nowhere. `swarm-otel.nix` asserts
|
||||
# this name stays in the list, since its own `reservedOwners` reads it.
|
||||
"swarm"
|
||||
|
||||
# ---- usernames the forge refuses -------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue