Pure `nix fmt` output from the commit before this one — no hand edits. 203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs. Reproduce with `nix develop -c nix fmt` on the parent commit; the result should be byte-identical to this tree. None of the 13 `.prettierignore` entries appears here — verified by intersecting the changed-file list against the ignore file, with a control proving the intersection finds a match when one exists.
186 lines
10 KiB
Markdown
186 lines
10 KiB
Markdown
# Swarm-wide services
|
|
|
|
Some things exist once per **swarm** rather than once per hive. Two
|
|
options say where the optional ones live, and everything else derives:
|
|
|
|
```nix
|
|
services.hyperhive.deploy.singleHostSwarm = true; # everything on this box
|
|
# or, for a dedicated services host with hives elsewhere:
|
|
services.hyperhive.deploy.allSwarmServices = true;
|
|
```
|
|
|
|
**`deploy.allSwarmServices` is what "the swarm's shared services run
|
|
here" means: every once-per-swarm service that is _optional_ takes its
|
|
`enable` from it.** That is the whole rule, stated once — the per-service
|
|
sections below do not repeat it, so a service that stops deriving is a
|
|
visible difference rather than one more paragraph saying the same thing.
|
|
|
|
`singleHostSwarm` is the all-on-one-box switch above it: it defaults
|
|
both `deploy.allSwarmServices` and `swarm.ca.autoConfigure` (the swarm CA
|
|
is generated here). Each derived toggle can still be set on its own,
|
|
which wins, so "all local except X" needs no further option.
|
|
|
|
**Both default to off**, and that is deliberate: a host cannot tell
|
|
whether it is meant to be the swarm's service host, so this is an
|
|
operator saying so rather than something inferred. With them off, a hive
|
|
is a _client_ of those services — it configures how to reach them and
|
|
runs none of them.
|
|
|
|
The forge is the exception, and not because it is per-hive: it is
|
|
swarm-wide but **not optional**, being the canonical store for the meta
|
|
flake and every agent's config repo, so it deploys with hyperhive itself
|
|
and has no `enable` to derive from anything.
|
|
|
|
## Deployment shapes
|
|
|
|
Those two options are what makes the difference between deployments, so
|
|
the shapes worth naming are the ones they produce:
|
|
|
|
- **All-local.** Everything on one machine:
|
|
`singleHostSwarm = true`. Setup is automatic apart from
|
|
choosing a domain and creating the first user.
|
|
- **Services on the swarm controller host.**
|
|
`deploy.allSwarmServices = true` there; the required services
|
|
deploy together on that host, with hives elsewhere.
|
|
- **Fully spread out.** One container / VM / machine per service,
|
|
somewhere.
|
|
|
|
**These are a set, not a ladder with a correct top, and in-between
|
|
shapes are supported.** Each derived toggle can be set on its own (see
|
|
above), which is what makes "all local except X" a configuration rather
|
|
than an unsupported edge case. Nothing in hyperhive prescribes a
|
|
deployment model, so a doc that treats one shape as the real one and
|
|
the others as compromises is wrong about the system rather than
|
|
opinionated.
|
|
|
|
The practical consequence is that **co-location is normal**: in the
|
|
first two shapes services share a host by design. Where a specific
|
|
service has something to say about sharing a host --- extra
|
|
requirements, or a cost worth weighing --- that belongs in the doc for
|
|
that service, not here.
|
|
|
|
## Services
|
|
|
|
### SSO (authelia)
|
|
|
|
One authelia per swarm, in a `swarm-authelia` container, at
|
|
`auth.<swarm-domain>`. Operator and agents are both subjects of the same
|
|
provider, differentiated by roles and claims rather than by mechanism —
|
|
there is one IdP and one auth path.
|
|
|
|
- **`deploy.authelia`** — run the container here.
|
|
- **`swarm.authelia.url`** — where clients are sent to authenticate.
|
|
Present on **every** hive, defaulting to this host's own instance only
|
|
when this module is the thing running it; otherwise `null`, and a hive
|
|
joining someone else's swarm sets it explicitly. Null means "no SSO
|
|
configured", and consumers say so rather than guessing an address.
|
|
|
|
The users database is written by swarm-controller, not by hand: agents
|
|
are created and destroyed continuously, so the subject set is dynamic.
|
|
This module only guarantees the file exists and parses, so authelia
|
|
starts with nobody in it rather than failing to start — a provider with
|
|
no subjects yet is the correct state before anything has provisioned
|
|
them. Session and storage keys are generated in the container on first
|
|
boot and never rotated automatically; replacing one invalidates data
|
|
already written (sessions, the encrypted store), so that is an operator
|
|
action.
|
|
|
|
Storage is local sqlite and the notifier writes to a file. Both are
|
|
small-deployment choices, and the scope is the justification: redis
|
|
buys shared session state across replicas and there is one instance;
|
|
SMTP exists to mail humans, and provisioning here is programmatic.
|
|
|
|
See [`sso.md`](sso.md) for bootstrapping the first user and the OIDC
|
|
relying-party flow, and [`secrets.md`](secrets.md) for where each of
|
|
authelia's keys is generated and read.
|
|
|
|
### Metrics (VictoriaMetrics + Grafana)
|
|
|
|
The swarm's telemetry lands in one VictoriaMetrics and is read through
|
|
one Grafana, in two containers at `metrics.<swarm-domain>` and
|
|
`grafana.<swarm-domain>`. Two containers rather than one so Grafana can
|
|
be restarted or broken without taking the time-series database with it.
|
|
|
|
They derive together: a store with no UI is unreadable and a UI with no
|
|
store is empty. To run one without the other, set it directly:
|
|
|
|
```nix
|
|
services.hyperhive.deploy.victoriametrics.enable = true;
|
|
services.hyperhive.deploy.grafana.enable = false;
|
|
```
|
|
|
|
⚠️ **This starts a database that grows for as long as the swarm runs.**
|
|
See `retentionPeriod` below before leaving it at its default.
|
|
|
|
| Option | When you'd touch it |
|
|
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `deploy.victoriametrics.retentionPeriod` | Default `5y`. Lower it once you have measured how fast this swarm actually fills a disk — the default is deliberately generous because too-short silently discards history you cannot get back. |
|
|
| `swarm.grafana.oidc.role` | Default `Admin` for everyone who logs in. Lower to `Viewer`/`Editor` if the swarm grows operators who should not be able to reconfigure Grafana. |
|
|
| `swarm.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
|
|
|
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
|
authelia ones — there is no separate Grafana password, and the local
|
|
login form is switched off whenever SSO is configured. If you enable
|
|
Grafana on a host with no authelia, the form stays on and Grafana's
|
|
default `admin`/`admin` applies; change it before exposing that host.
|
|
|
|
**Where the data comes from.** The swarm's OTEL collector, below.
|
|
|
|
Neither container is reachable except through the gateway: both bind
|
|
loopback, and VictoriaMetrics' write endpoint takes no credential, so
|
|
the collector is the only intended writer.
|
|
|
|
### Logs (VictoriaLogs)
|
|
|
|
Each hive ships its journals to one VictoriaLogs at `logs.<swarm-domain>`,
|
|
behind the same SSO as everything else: the swarm's own service containers,
|
|
the hive's daemons and infra containers, and the harness units inside every
|
|
agent container. The collector below is what writes to it.
|
|
|
|
**Reading them.** Open Grafana, pick **Explore**, and choose the
|
|
`VictoriaLogs` datasource — it is provisioned for you. Grafana's _Logs
|
|
Drilldown_ app is deliberately not installed: it only supports Loki, and
|
|
no setting here changes that, so Explore is the log browser for this
|
|
swarm.
|
|
|
|
| Option | When you'd touch it |
|
|
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `deploy.victorialogs.retentionPeriod` | Default `30d`, far shorter than the metrics store's — logs are bulkier per unit of value and are usually read within days of being written. Raise it if you need to answer questions about last quarter. |
|
|
| `swarm.victorialogs.domain` | Only to rename it. |
|
|
| `swarm.victorialogs.port` | Only if something else on the services host already claims `9428`. |
|
|
|
|
Like the metrics store, it binds loopback and takes no credential of its
|
|
own: the gateway vhost is the only way in, and the collector is the only
|
|
intended writer.
|
|
|
|
### Telemetry collector (OTEL)
|
|
|
|
The swarm's collector receives from every hive's own collector and is the
|
|
only process that decides where telemetry goes: it writes the store above
|
|
and exports to `otel.endpoint`, doing both when both are configured. It
|
|
also holds the upstream credential, which is why no hive and no agent
|
|
needs one.
|
|
|
|
It runs in a `swarm-otel` container. Its `swarm.otel.port` defaults to
|
|
`4319` rather than OTLP's usual `4318`, which the hive tier uses — swarm
|
|
containers share the host's network namespace, so two collectors on one
|
|
port is a coin toss at runtime rather than an error at build time.
|
|
|
|
Every hive's own collector reaches this one by its gateway name,
|
|
`swarm.otel.domain` (default `otel.<swarm domain>`) — the same
|
|
by-domain-through-the-gateway shape every other swarm service uses, not a
|
|
loopback URL an operator has to redirect. There is nothing to set on a hive
|
|
that does not run the swarm's services; the name resolves through the
|
|
gateway either way.
|
|
|
|
| Option | When you'd touch it |
|
|
| ------------------- | --------------------------------------------------------------------------------------- |
|
|
| `swarm.otel.domain` | Only to rename it — the default already resolves correctly for every hive in the swarm. |
|
|
| `swarm.otel.port` | Only if something else on the services host already claims `4319`. |
|
|
|
|
With neither `otel.endpoint` nor the store enabled, this collector is
|
|
refused at eval — a tier that receives samples and drops them looks
|
|
healthy while losing data.
|
|
|
|
Agent-side configuration, and what a hive's own collector does, are in
|
|
[`../scheduler/observability.md`](../scheduler/observability.md).
|