Commit graph hyperhive/nix/host-modules/local-defaults.nix
Author SHA1 Message Date
atlas
13b1b41172 local-defaults: assert the controller through deploy, not the old alias
Round-3 review catch: the all-local mode still set
`services.hyperhive.swarm.controller.enable` -- the exact path this
branch's own `mkRenamedOptionModule` deprecates. It forwards correctly,
so nothing broke, but every eval with `enableAllLocalDefaults = true`
printed a deprecation warning and the tree depended on the shim it is
retiring.

It survived the rewrite because the path is SPLIT ACROSS NESTING:
`swarm = { controller.enable = ...; }`. No grep for a dotted path can
match text that is not contiguous -- not the prefix-anchored pattern the
rewrite used, and not the suffix-anchored one added after that missed
hive-tls.nix. What finds it is the bare tail with no prefix at all
(`^\s*controller\.enable\s*=`), which reports exactly one hit outside
deploy.nix's declarations.

The assignment moves out of the `swarm` attrset rather than staying in
it: `deploy.*` is a different top-level path, so the collision the
neighbouring warning describes -- two definitions of `swarm`, the nested
one silently lost -- does not apply.
2026-08-30 04:23:22 +02:00
atlas
d3b40da1c8 deploy: give every option an enable, and name the controller one
Two corrections from review, applied forward on this branch rather than
by rewriting it.

`deploy.<service>` was a bare bool, which makes
`deploy.forgejo = { enable; ci; }` unrepresentable -- the nested
CI-runner sub-option this namespace was designed around. Every entry is
now an attrset with an `enable`, so a second per-host deployment
decision becomes an ordinary addition rather than a migration.

`deploy.controller` is now `deploy.swarm-controller`, consistent with
`deploy.swarm-ui`, which was introduced in the same commit.

89 references rewritten across 24 files -- nix, Rust, docs, and the
repo's own CLAUDE.md.

The prefix-anchored sweep missed exactly one, and it was live code:
hive-tls.nix spells it `hyperhiveCfg.deploy.controller` -- the only
`hyperhiveCfg` prefix among 45 references. A suffix grep
(`\.deploy\.<name>`) finds it; a path-anchored one cannot, because the
head of a reference is whatever alias the reading file happens to bind.
2026-08-30 04:23:22 +02:00
atlas
64e8c62f18 deploy: move the queue toggle
Same shape as the others. Three of the eight references were prose
rather than code: two assertion messages naming the option to an
operator, and a `defaultText` in the authelia module, whose
`oidc.hiveIdentities` derives from whether the queue runs here.

Also corrects a comment in swarm.nix that this commit's own hunk sits
directly beneath. It said `enableRequiredServices` turns on matrix and
authelia "but NOT nats (nats has no mode that enables it)", which stopped
being true when the queue was added to that switch — before this change,
not by it. The reasoning it introduces is still right and still worth
keeping; only the premise had expired.
2026-08-30 04:23:22 +02:00
atlas
37ca7676d6 deploy: move the SSO provider toggle
The largest of these moves: sixteen references spelled through `let`
aliases across eight modules, plus eight more spelled as a path, plus
five documentation pages.

authelia is also the clearest case for why the two namespaces exist.
`swarm.authelia.url` is needed by *every* hive in the swarm — it says
where to send a browser to authenticate — while running the container is
the business of exactly one host. The client half and the server half
were sharing a namespace whose whole contract is "identical everywhere",
and only one of them could honour it.

`swarm.authelia.oidc.clients` stays where it is for the same reason:
several modules register a client there, gated on authelia running here,
and the registry itself is what the service *is* rather than a decision
about this machine.

One sweep note worth recording: a grep for `swarm.authelia.enable` misses
`swarmCfg.authelia.enable`, because the prefix is whatever the reading
file bound. Grepping the suffix `.authelia.enable` finds both, and found
a reference in swarm.nix that the path-shaped pattern did not.
2026-08-30 04:23:22 +02:00
atlas
0b7357d4b8 deploy: move the controller and swarm-ui toggles
One commit rather than two because they are not independent: the UI's
`enable` had the controller's as its literal default, so moving the
controller alone would leave the UI's default naming an option that no
longer exists.

The UI keeps that derivation in its new home — it is a view onto the
controller's state and reaches it over that daemon's unix socket, so the
host running the controller is the host that can serve it.

Three spellings had to move together for the UI, not one: the `default`,
the `defaultText` shown in the options doc, and the description prose
that names the old path in words. A grep for the option path finds the
first two.

The sweep also reached outside nix: `swarm-controller`'s crate README and
its `//!` module doc both named the option, as did this repo's own
CLAUDE.md and four pages under docs/. An option's name is API, and its
documentation lives wherever someone thought to write it down.
2026-08-30 04:23:22 +02:00
atlas
072dbd80a7 fix(#3343): move the all-local queue derivations into the deployment mode
Review point: the co-located defaults are an auto-deploy special case and
belong with the other ones, not inside each option's own default. An
option should describe itself; the mode describes what a deployment shape
implies. local-defaults.nix already says exactly this in its header.

Options now default to empty (= unset, which the assertions refuse), and
enableAllLocalDefaults fills in loopback + the minted secret path.
2026-08-16 19:37:49 +02:00
atlas
4365520ada feat(swarm-nats): mint the auth-callout nkeys on all-local hives
A hive whose whole swarm is one box had to be handed two nkeys by hand
before its queue could authenticate anyone, which is the one deployment
shape where nobody else can supply them.

`autoGenerateCallout` mints both keypairs on the host on first boot,
keeps the seeds at 0600 host-side, and writes only the public halves
into a fragment the server reads at start. The all-local mode turns it
on; everywhere else the options stay operator-supplied and the
fail-closed eval assertions keep their full force.

The server config is not rewritten to do this. A wrapper includes
upstream's rendered `settings` verbatim plus the runtime fragment, and
the fragment wins — measured, along with the property that makes the
whole shape safe: the empty strings the options render in auto mode are
values `nats-server` refuses to start on, so any field the merge fails
to reach fails closed loudly rather than leaving a walk-in-able server.

The wrapper, the settings symlink and the fragment are siblings in one
runtime directory, and that is forced rather than tidy: NATS resolves an
include with filepath.Join against the config file's own directory,
which strips a leading slash, so an absolute include silently becomes a
relative one and the server never finds it. The includes are therefore
bare filenames. That also means nothing in the closure would otherwise
name the rendered settings, so the generator's symlink to it is what
keeps it from being garbage-collected under a running server.

`accounts` and `authorization` are defined once and rendered twice, into
`settings` and into the fragment template. Written out separately they
would diverge silently and backwards: the fragment is the later
definition, so a future edit to `settings` alone would be ignored on
exactly the hives that use auto mode.

`validateConfig` goes off in auto mode because `nats-server -t` rejects
the empty keys at build time; the parse check moves to server start,
where the fragment exists. Upstream's own option description names this
case.
2026-08-16 15:59:06 +02:00
atlas
f80facbbe0 refactor(3202): all-local asserts the host's own /etc/hosts entries
Clause 2 of #3202, reading 1 (mara: "the all local stuff and swarm
services auto conf belong in those mods, not spread all over").

`gateway.localHostsEntry` is the gateway's only local-deployment knob —
`openFirewall` is about EXTERNAL exposure, `tls.acme` needs a public DNS
name, `hsts` is a hardening choice. It is now asserted by the mode in
local-defaults.nix, beside the three swarm toggles, instead of being the
one all-local implication an operator still had to know about.

`mkDefault`, so "all local except this" still needs no new option.

⚠️ The non-obvious half: this does NOT change what CONTAINERS resolve.
dnsmasq sets `no-hosts = true` unconditionally, so agents keep getting
the bridge IP from the authoritative `address=` rules rather than the
host's 127.0.0.1 — which would point every agent at its own netns. That
guard already existing is what makes this safe to default on; without it
this one line would break every agent's access to the forge.
2026-08-13 17:26:08 +02:00
atlas
21ceb75850 feat(3089): all-local asserts the swarm controller
`enableAllLocalDefaults` already asserts the swarm's shared services and
its CA; the controller was the one swarm-level thing it left off, so the
default deployment ran authelia, matrix and the forge with nothing
controlling them — and, until the previous commit in this area, without
`swarmctl` either.

The controller's own option stays `default = false`. Running it is a
statement about swarm topology rather than about hyperhive being
installed, and no single host can infer that on its own. But "this box
is the whole deployment" IS that statement, which is why the mode may
assert what `services.hyperhive.enable` never could.

Derived from the mode, not from `enableRequiredServices`: a hive in a
larger swarm can legitimately want the shared services without being the
host that controls them.

`mkDefault`, so `enableAllLocalDefaults = true` with an explicit
`controller.enable = false` still yields a controller-less box — the
mode fills in for an operator who hasn't spoken and never argues with
one who has.
2026-08-11 23:50:19 +02:00
atlas
048bdd29a8 refactor(nix): make all-local a deployment mode, not a default
mara: enableAllLocalDefaults is more of a deployment mode via settings
set, less a default setting.

That reframing is the change. A mode asserts values; an option declares
what it is when nobody asks. Written as default = <flag>, every service
option had to name a switch it has no relationship to, and the answer to
what does all-local turn on was spread across five files.

Two modules now hold the two tiers - local-defaults.nix for the mode and
what it asserts directly, swarm-required-services.nix for the
services-live-here switch and the per-service enables under it. Each
service module keeps its own declaration and loses only the line about
when a deployment wants it.

mkDefault is the right precedence rather than a compromise: it beats an
option default and yields to any explicit definition, so the mode fills
in for an operator who has not spoken and never argues with one who has.
Evaluated config is unchanged in both directions.
2026-08-05 19:41:11 +02:00