docs: fix capitalization losses + one text corruption from the contractions pass
Review pass on top of the previous commit (85a94671) turned up three
real issues its own vale-only verification didn't catch:
- 'has not' -> 'hasn't' fired inside 'has nothing', corrupting
docs/swarm/README.md into 'hasn'thing to say upward'. Restored
'has nothing'.
- ~44 hits where the original text was genuinely sentence-initial
('It is', 'That is', 'They are', 'What is', 'Do not', ...) got
contracted in lowercase, silently de-capitalizing the start of a
sentence. Checked each one's real surrounding text and capitalized
the ones that needed it.
- Confirmed (by reading the actual text, not just trusting vale) that
the 3 remaining 'is not'/'has not'/'it is' hits after those fixes are
vale's own false positives: it matches these as a bare substring with
no trailing word boundary, so it also fires on 'is nothing', 'has
nothing', and the already-correct 'it isn't'. Left those alone --
there's no real issue in the text.
Re-verified with a fresh vale run: the remaining 17 hits are exactly
the union of the 14 legitimate ALL-CAPS/bold-emphasis skips from the
first commit and these 3 false positives. Full accounting in
/agents/iris/state/haiku-batches/contractions-report.md.
This commit is contained in:
parent
78021ce982
commit
ff377b0dee
15 changed files with 47 additions and 47 deletions
|
|
@ -58,7 +58,7 @@ declarations.
|
||||||
the PAT injected?** → [`integrations/github.md`](integrations/github.md)
|
the PAT injected?** → [`integrations/github.md`](integrations/github.md)
|
||||||
(operator content up top; the `gh`/git-push + notification-poller
|
(operator content up top; the `gh`/git-push + notification-poller
|
||||||
mechanics are in a collapsed "Implementation" section at the bottom).
|
mechanics are in a collapsed "Implementation" section at the bottom).
|
||||||
- **What is `/knowledge`? How does the hive-wide knowledge repo sync,
|
- **What's `/knowledge`? How does the hive-wide knowledge repo sync,
|
||||||
and how do I contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md).
|
and how do I contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md).
|
||||||
- **What does `hivectl` do? Provisioning, gateway users, container
|
- **What does `hivectl` do? Provisioning, gateway users, container
|
||||||
shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide);
|
shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide);
|
||||||
|
|
|
||||||
|
|
@ -476,9 +476,9 @@ Only present on the one host running
|
||||||
so it survives restarts and redeploys.
|
so it survives restarts and redeploys.
|
||||||
|
|
||||||
- `webhook-secret` — the HMAC key the swarm's forge webhooks are signed
|
- `webhook-secret` — the HMAC key the swarm's forge webhooks are signed
|
||||||
with. **Keep it.** it's handed to Forgejo when a hook is registered,
|
with. **Keep it.** It's handed to Forgejo when a hook is registered,
|
||||||
so replacing the file means every subsequent delivery fails
|
so replacing the file means every subsequent delivery fails
|
||||||
verification until the hook is re-registered with the new value. it's
|
verification until the hook is re-registered with the new value. It's
|
||||||
generated automatically on first start; there is nothing to configure.
|
generated automatically on first start; there is nothing to configure.
|
||||||
|
|
||||||
If the file is unreadable at startup the daemon still starts and logs
|
If the file is unreadable at startup the daemon still starts and logs
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ On by default, and listens on `httpsPort` (default 443) on every vhost beside th
|
||||||
|
|
||||||
The issuer is a **host-held hive CA**, not a bare self-signed leaf. A host service (`hive-tls-ca.service`, from the `hive-tls` module) generates a long-lived CA (`services.hyperhive.deploy.hive-controller.tls.caValidityDays`, default ~20y) under `services.hyperhive.deploy.hive-controller.tls.stateDir` (default `/var/lib/hive-tls`), then signs a gateway **leaf** (`leafValidityDays`, default 30d) with it. `hive-gateway-self-signed-cert` then imports the leaf into nginx's state dir (`/var/lib/hive-gateway/tls/{cert,key}.pem`).
|
The issuer is a **host-held hive CA**, not a bare self-signed leaf. A host service (`hive-tls-ca.service`, from the `hive-tls` module) generates a long-lived CA (`services.hyperhive.deploy.hive-controller.tls.caValidityDays`, default ~20y) under `services.hyperhive.deploy.hive-controller.tls.stateDir` (default `/var/lib/hive-tls`), then signs a gateway **leaf** (`leafValidityDays`, default 30d) with it. `hive-gateway-self-signed-cert` then imports the leaf into nginx's state dir (`/var/lib/hive-gateway/tls/{cert,key}.pem`).
|
||||||
|
|
||||||
⚠️ **don't collapse that import unit into pointing nginx at the CA dir.**
|
⚠️ **Don't collapse that import unit into pointing nginx at the CA dir.**
|
||||||
It does two jobs, and skipping it has taken the gateway down in production
|
It does two jobs, and skipping it has taken the gateway down in production
|
||||||
before. It re-modes the leaf (`hive-tls-ca` writes the key `0600
|
before. It re-modes the leaf (`hive-tls-ca` writes the key `0600
|
||||||
root:root`; nginx's pre-start `nginx -t` runs as the *nginx user*, so a
|
root:root`; nginx's pre-start `nginx -t` runs as the *nginx user*, so a
|
||||||
|
|
@ -455,7 +455,7 @@ legacy single-proxy block and all traffic is forwarded to the agent
|
||||||
daemon as before.
|
daemon as before.
|
||||||
|
|
||||||
**`extraFiles`**: per-agent `hyperhive.frontend.extraFiles` are in
|
**`extraFiles`**: per-agent `hyperhive.frontend.extraFiles` are in
|
||||||
`mergedDist`, not in the base `cfg.frontend` dist. they're not under
|
`mergedDist`, not in the base `cfg.frontend` dist. They're not under
|
||||||
the nix-store `alias` path, so requests for them fall through
|
the nix-store `alias` path, so requests for them fall through
|
||||||
`try_files` to `@<name>_dynamic` and are served by the agent daemon
|
`try_files` to `@<name>_dynamic` and are served by the agent daemon
|
||||||
as before.
|
as before.
|
||||||
|
|
@ -542,7 +542,7 @@ The daemon hashes passwords with BCrypt (cost 12) and writes
|
||||||
`$2y$`-prefixed hashes that nginx accepts natively. No external
|
`$2y$`-prefixed hashes that nginx accepts natively. No external
|
||||||
`htpasswd` binary is required.
|
`htpasswd` binary is required.
|
||||||
|
|
||||||
**what's not gated:** per-agent UI routes emitted into `agents.conf`
|
**What's not gated:** per-agent UI routes emitted into `agents.conf`
|
||||||
(served under `/agent/<name>/`) inherit no auth from `/` — nginx
|
(served under `/agent/<name>/`) inherit no auth from `/` — nginx
|
||||||
applies `auth_basic` per-location. Full per-agent coverage is a
|
applies `auth_basic` per-location. Full per-agent coverage is a
|
||||||
follow-up.
|
follow-up.
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ to all containers — agents and service containers alike.
|
||||||
`HOST_ADDRESS` is the bridge gateway IP (the address part of
|
`HOST_ADDRESS` is the bridge gateway IP (the address part of
|
||||||
`HIVE_NETWORK_SUBNET`, via `lifecycle::bridge_gateway_ip` — taken verbatim
|
`HIVE_NETWORK_SUBNET`, via `lifecycle::bridge_gateway_ip` — taken verbatim
|
||||||
so a non-`.1` operator override still resolves to wherever the bridge
|
so a non-`.1` operator override still resolves to wherever the bridge
|
||||||
actually lives). it's **load-bearing**: nixos-container's container-side
|
actually lives). It's **load-bearing**: nixos-container's container-side
|
||||||
network setup only installs a default route (`ip route add default via
|
network setup only installs a default route (`ip route add default via
|
||||||
$HOST_ADDRESS`) when `HOST_ADDRESS` is non-empty. In bridge mode the
|
$HOST_ADDRESS`) when `HOST_ADDRESS` is non-empty. In bridge mode the
|
||||||
host-side address/route setup is skipped, so writing it only affects the
|
host-side address/route setup is skipped, so writing it only affects the
|
||||||
|
|
@ -257,7 +257,7 @@ wiring is runtime:
|
||||||
gateway IP) into each container's `/etc`.
|
gateway IP) into each container's `/etc`.
|
||||||
- the `hyperhive-isolated-dns` oneshot (`nix/agent-modules/network.nix`), gated on that
|
- the `hyperhive-isolated-dns` oneshot (`nix/agent-modules/network.nix`), gated on that
|
||||||
marker, rewrites `/etc/resolv.conf` to `nameserver <gateway-ip>` at boot.
|
marker, rewrites `/etc/resolv.conf` to `nameserver <gateway-ip>` at boot.
|
||||||
it's ordered `before` the harness (`hive-ag3nt`), the matrix daemon, and
|
It's ordered `before` the harness (`hive-ag3nt`), the matrix daemon, and
|
||||||
`tea-login` so the resolver is correct before the first DNS lookup.
|
`tea-login` so the resolver is correct before the first DNS lookup.
|
||||||
|
|
||||||
**Why isolation is safe**: hive-c0re's control-plane sockets are unix
|
**Why isolation is safe**: hive-c0re's control-plane sockets are unix
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ complete a migration.
|
||||||
Two things it's not, both worth stating because both are common
|
Two things it's not, both worth stating because both are common
|
||||||
assumptions:
|
assumptions:
|
||||||
|
|
||||||
- **it's not the swarm controller**, and doesn't depend on one. it's
|
- **It's not the swarm controller**, and doesn't depend on one. It's
|
||||||
a NixOS host role: a btrfs subvolume tree, a socket-activated
|
a NixOS host role: a btrfs subvolume tree, a socket-activated
|
||||||
receiver, and the `wg-hive` interface the swarm module already brings
|
receiver, and the `wg-hive` interface the swarm module already brings
|
||||||
up. that's why it can be deployed before any controller exists.
|
up. That's why it can be deployed before any controller exists.
|
||||||
- **it's not a backup product.** It happens to hold the data a backup
|
- **It's not a backup product.** It happens to hold the data a backup
|
||||||
would hold, and it should be operated accordingly (see
|
would hold, and it should be operated accordingly (see
|
||||||
[Operating it](#operating-it)) --- but nothing in it does scheduling,
|
[Operating it](#operating-it)) --- but nothing in it does scheduling,
|
||||||
verification, or restore orchestration.
|
verification, or restore orchestration.
|
||||||
|
|
@ -56,7 +56,7 @@ services.hyperhive.swarm.snapshotStore = {
|
||||||
Two deliberate asymmetries in that pair, both liable to be misread as
|
Two deliberate asymmetries in that pair, both liable to be misread as
|
||||||
inconsistency:
|
inconsistency:
|
||||||
|
|
||||||
- **`address` has no default.** it's a deployment fact a pushing hive
|
- **`address` has no default.** It's a deployment fact a pushing hive
|
||||||
can't derive, and a wrong guess means streaming an agent's state at
|
can't derive, and a wrong guess means streaming an agent's state at
|
||||||
whatever happens to answer. Unset, a push fails naming this option.
|
whatever happens to answer. Unset, a push fails naming this option.
|
||||||
- **`port` does default** (`51821`), because it's a convention both
|
- **`port` does default** (`51821`), because it's a convention both
|
||||||
|
|
@ -64,7 +64,7 @@ inconsistency:
|
||||||
coordination, not a guess.
|
coordination, not a guess.
|
||||||
|
|
||||||
Note the option lives under `swarm.*` while the receiving host's lives
|
Note the option lives under `swarm.*` while the receiving host's lives
|
||||||
under `services.hyperhive.snapshotStore`. that's the distinction the
|
under `services.hyperhive.snapshotStore`. That's the distinction the
|
||||||
two namespaces carry throughout: `swarm.*` describes _the swarm_ as seen
|
two namespaces carry throughout: `swarm.*` describes _the swarm_ as seen
|
||||||
from this host, and a bare `services.hyperhive.<service>` describes _a
|
from this host, and a bare `services.hyperhive.<service>` describes _a
|
||||||
role this host performs_. A store host sets both --- one to run the
|
role this host performs_. A store host sets both --- one to run the
|
||||||
|
|
@ -78,7 +78,7 @@ and no credential argument, because the mesh is the authentication.
|
||||||
|
|
||||||
## The mesh is the authentication
|
## The mesh is the authentication
|
||||||
|
|
||||||
There are no certificates here, and no key material of its own. that's
|
There are no certificates here, and no key material of its own. That's
|
||||||
deliberate rather than an omission.
|
deliberate rather than an omission.
|
||||||
|
|
||||||
WireGuard's cryptokey routing already binds a peer's source address to
|
WireGuard's cryptokey routing already binds a peer's source address to
|
||||||
|
|
@ -116,7 +116,7 @@ The rule that matters:
|
||||||
|
|
||||||
Validation is an allowlist --- `[A-Za-z0-9_-]+` and nothing else. No
|
Validation is an allowlist --- `[A-Za-z0-9_-]+` and nothing else. No
|
||||||
slash and no dot means neither directory traversal nor an absolute path
|
slash and no dot means neither directory traversal nor an absolute path
|
||||||
can survive it. it's deliberately an allowlist and not a list of
|
can survive it. It's deliberately an allowlist and not a list of
|
||||||
forbidden characters: a blocklist only ever excludes the attacks
|
forbidden characters: a blocklist only ever excludes the attacks
|
||||||
somebody already thought of.
|
somebody already thought of.
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ nobody runs it day to day, so it rots and is discovered broken in
|
||||||
production.
|
production.
|
||||||
|
|
||||||
⚠️ **The assumption to keep true over time:** the store host runs
|
⚠️ **The assumption to keep true over time:** the store host runs
|
||||||
nothing else. that's true on day one and quietly false the day someone
|
nothing else. That's true on day one and quietly false the day someone
|
||||||
notices the box has spare disk. Nothing in the config objects when it
|
notices the box has spare disk. Nothing in the config objects when it
|
||||||
stops being true.
|
stops being true.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ container build:
|
||||||
`DeployWindow` — the deploy subtree's root, which holds it across every
|
`DeployWindow` — the deploy subtree's root, which holds it across every
|
||||||
phase below it (it declares `Resource::MetaWindow`). Two meta
|
phase below it (it declares `Resource::MetaWindow`). Two meta
|
||||||
mutations can therefore never interleave, so no commit lands inside another
|
mutations can therefore never interleave, so no commit lands inside another
|
||||||
node's staged window. it's a queue resource rather than a runtime mutex
|
node's staged window. It's a queue resource rather than a runtime mutex
|
||||||
because a resource is held by a subtree root across its whole subtree, which
|
because a resource is held by a subtree root across its whole subtree, which
|
||||||
a `MutexGuard` (bounded by one executor fn) can't — that's what lets a
|
a `MutexGuard` (bounded by one executor fn) can't — that's what lets a
|
||||||
multi-node deploy own one window. For the same reason the window must stay
|
multi-node deploy own one window. For the same reason the window must stay
|
||||||
|
|
@ -237,7 +237,7 @@ than to one DAG shape it happens to appear in. A **brace** is the one sanctioned
|
||||||
exception: a pure-resource-holder root that declares on behalf of a subtree
|
exception: a pure-resource-holder root that declares on behalf of a subtree
|
||||||
coordinated with itself, whose members then declare nothing.
|
coordinated with itself, whose members then declare nothing.
|
||||||
|
|
||||||
it's forced rather than stylistic. Declaring a resource means _"I need this
|
It's forced rather than stylistic. Declaring a resource means _"I need this
|
||||||
exclusively"_, and the agent lease is single-unit — so **two siblings that both
|
exclusively"_, and the agent lease is single-unit — so **two siblings that both
|
||||||
declared it could never run concurrently.** For a subtree whose whole point is
|
declared it could never run concurrently.** For a subtree whose whole point is
|
||||||
concurrency (`Prebuild` beside the `Signal` → `Drain` quiesce window), declaring
|
concurrency (`Prebuild` beside the `Signal` → `Drain` quiesce window), declaring
|
||||||
|
|
@ -349,7 +349,7 @@ the event's whole job.
|
||||||
## Container view
|
## Container view
|
||||||
|
|
||||||
`container_view.rs` maintains an in-memory snapshot of every nixos-container's
|
`container_view.rs` maintains an in-memory snapshot of every nixos-container's
|
||||||
systemd service state. it's polled on coordinator startup and re-scanned after
|
systemd service state. It's polled on coordinator startup and re-scanned after
|
||||||
every lifecycle operation (spawn, rebuild, kill) so the dashboard always reflects
|
every lifecycle operation (spawn, rebuild, kill) so the dashboard always reflects
|
||||||
the actual container status without a live `nixos-container list` call on each
|
the actual container status without a live `nixos-container list` call on each
|
||||||
render.
|
render.
|
||||||
|
|
@ -527,7 +527,7 @@ wants it.
|
||||||
|
|
||||||
Two things to know about the weights:
|
Two things to know about the weights:
|
||||||
|
|
||||||
- they're **hive-wide** — unlike the caps there is no per-agent
|
- They're **hive-wide** — unlike the caps there is no per-agent
|
||||||
override in `meta/resource-limits.json`, so every agent carries the
|
override in `meta/resource-limits.json`, so every agent carries the
|
||||||
same value and the weight does _not_ rank agents against each other.
|
same value and the weight does _not_ rank agents against each other.
|
||||||
What `80` buys is that agents yield to everything **not** on this
|
What `80` buys is that agents yield to everything **not** on this
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ credential.** Neither tier can tell a container's genuine Claude Code stats
|
||||||
from anything else shaped like OTLP arriving on that port — including data
|
from anything else shaped like OTLP arriving on that port — including data
|
||||||
smuggled out in resource attributes on an otherwise-legitimate export.
|
smuggled out in resource attributes on an otherwise-legitimate export.
|
||||||
|
|
||||||
that's a **different risk from the one the collector fixes**, and strictly
|
That's a **different risk from the one the collector fixes**, and strictly
|
||||||
smaller than what preceded it: before, every agent held the upstream credential
|
smaller than what preceded it: before, every agent held the upstream credential
|
||||||
itself, so it could do all of the above _and_ use the token anywhere else. The
|
itself, so it could do all of the above _and_ use the token anywhere else. The
|
||||||
collector removes the token and keeps the pipe. Agents are inside the trust
|
collector removes the token and keeps the pipe. Agents are inside the trust
|
||||||
|
|
@ -122,7 +122,7 @@ read. `0600` protects it from other containers, not from the agent itself. As
|
||||||
long as the direct path stays _selectable_, that hole stays selectable; an
|
long as the direct path stays _selectable_, that hole stays selectable; an
|
||||||
option that can reintroduce it's a hole with extra steps.
|
option that can reintroduce it's a hole with extra steps.
|
||||||
|
|
||||||
**Why the tiers stay separate on one box.** they're not collapsed when
|
**Why the tiers stay separate on one box.** They're not collapsed when
|
||||||
co-located: an all-local hive is a statement about _where_ processes run, not
|
co-located: an all-local hive is a statement about _where_ processes run, not
|
||||||
about the shape of the deployment. A boundary that disappears locally is one
|
about the shape of the deployment. A boundary that disappears locally is one
|
||||||
the local deployment stops testing.
|
the local deployment stops testing.
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ services.hyperhive.swarm.hives = {
|
||||||
```
|
```
|
||||||
|
|
||||||
One attrset describing **every** hive in the swarm, **including this
|
One attrset describing **every** hive in the swarm, **including this
|
||||||
one**, keyed by that hive's `hiveName`. it's meant to be _identical on
|
one**, keyed by that hive's `hiveName`. It's meant to be _identical on
|
||||||
every host_ — write it once, share it, and each host reads it correctly
|
every host_ — write it once, share it, and each host reads it correctly
|
||||||
because `services.hyperhive.hiveName` says which entry is itself.
|
because `services.hyperhive.hiveName` says which entry is itself.
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ evaluates cleanly points at a real machine that isn't the one you meant.
|
||||||
> inside a swarm comes from the swarm root ([`ca.md`](ca.md)): every
|
> inside a swarm comes from the swarm root ([`ca.md`](ca.md)): every
|
||||||
> hive chains to it, so one anchor replaces per-hive pinning entirely.
|
> hive chains to it, so one anchor replaces per-hive pinning entirely.
|
||||||
> What that genuinely drops is trusting a hive whose root this swarm
|
> What that genuinely drops is trusting a hive whose root this swarm
|
||||||
> does _not_ own — another swarm's, or one keeping its own CA. that's
|
> does _not_ own — another swarm's, or one keeping its own CA. That's
|
||||||
> a cross-swarm problem and wants a mechanism designed for it. (An
|
> a cross-swarm problem and wants a mechanism designed for it. (An
|
||||||
> earlier `certFingerprint` field existed for exactly that gap, pinning
|
> earlier `certFingerprint` field existed for exactly that gap, pinning
|
||||||
> a peer's TLS leaf for hive-c0re's own peer HTTPS checks — removed
|
> a peer's TLS leaf for hive-c0re's own peer HTTPS checks — removed
|
||||||
|
|
@ -183,7 +183,7 @@ hiveName = "pr1ma"; hiveName = "edge";
|
||||||
swarm.hives = { … }; swarm.hives = { … }; # byte-identical
|
swarm.hives = { … }; swarm.hives = { … }; # byte-identical
|
||||||
```
|
```
|
||||||
|
|
||||||
that's the point of the shape, and it removes a class of bug rather
|
That's the point of the shape, and it removes a class of bug rather
|
||||||
than saving typing: a per-host peer list let two hosts hold _different_
|
than saving typing: a per-host peer list let two hosts hold _different_
|
||||||
facts about the same third hive — a stale endpoint, a rotated
|
facts about the same third hive — a stale endpoint, a rotated
|
||||||
fingerprint — with nothing to detect the disagreement. One entry per
|
fingerprint — with nothing to detect the disagreement. One entry per
|
||||||
|
|
@ -276,7 +276,7 @@ service it points at: `services.hyperhive.swarm.snapshotStore.{address,
|
||||||
port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
|
port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
|
||||||
`hivectl agent <name> subvol snapshot push` has somewhere to stream to.
|
`hivectl agent <name> subvol snapshot push` has somewhere to stream to.
|
||||||
|
|
||||||
it's genuinely swarm-scoped rather than per-peer — a swarm has exactly
|
It's genuinely swarm-scoped rather than per-peer — a swarm has exactly
|
||||||
one store, because the receiver keys destinations by _agent_ so a
|
one store, because the receiver keys destinations by _agent_ so a
|
||||||
migrating agent keeps one unbroken incremental chain. See
|
migrating agent keeps one unbroken incremental chain. See
|
||||||
[snapshot-store.md](../networking/snapshot-store.md).
|
[snapshot-store.md](../networking/snapshot-store.md).
|
||||||
|
|
@ -303,7 +303,7 @@ still knows its own state — you just can't see it from here.
|
||||||
|
|
||||||
A hive publishes only once it has been given the three
|
A hive publishes only once it has been given the three
|
||||||
`swarm.statusPublish` coordinates below. A hive that has not reads
|
`swarm.statusPublish` coordinates below. A hive that has not reads
|
||||||
`never_reported` — it's not broken, it just hasn'thing to say upward.
|
`never_reported` — it's not broken, it just has nothing to say upward.
|
||||||
|
|
||||||
| freshness | what to do about it |
|
| freshness | what to do about it |
|
||||||
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ hive's own CA, and that hive CA signs the gateway leaf. A peer that
|
||||||
trusts the root once validates every hive in the swarm, present and
|
trusts the root once validates every hive in the swarm, present and
|
||||||
future, instead of being pinned to each one by hand.
|
future, instead of being pinned to each one by hand.
|
||||||
|
|
||||||
that's the whole point of the hierarchy — it turns per-peer trust from
|
That's the whole point of the hierarchy — it turns per-peer trust from
|
||||||
O(n²) hand-pinning into one anchor per swarm.
|
O(n²) hand-pinning into one anchor per swarm.
|
||||||
|
|
||||||
## Two provisioning modes, one structure
|
## Two provisioning modes, one structure
|
||||||
|
|
@ -49,7 +49,7 @@ swarm-services leaf can, and this host signs that one only under
|
||||||
and every client sees a name mismatch.
|
and every client sees a name mismatch.
|
||||||
|
|
||||||
So `hive-tls` emits an eval-time **warning** naming the uncovered names.
|
So `hive-tls` emits an eval-time **warning** naming the uncovered names.
|
||||||
it's deliberately not an assertion: this module can see what _it_ is
|
It's deliberately not an assertion: this module can see what _it_ is
|
||||||
able to issue, but not an operator-installed sub-CA in
|
able to issue, but not an operator-installed sub-CA in
|
||||||
`swarm.ca.stateDir`, an external ACME setup, or a certificate delivered
|
`swarm.ca.stateDir`, an external ACME setup, or a certificate delivered
|
||||||
by any other means. If you have arranged one, the warning is expected
|
by any other means. If you have arranged one, the warning is expected
|
||||||
|
|
@ -89,7 +89,7 @@ host:
|
||||||
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
|
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
that's the point of the hierarchy: the root is installed **once per
|
That's the point of the hierarchy: the root is installed **once per
|
||||||
swarm host**, not once per peer, so a hive joining later needs no edit on
|
swarm host**, not once per peer, so a hive joining later needs no edit on
|
||||||
the hives already running. A hive whose cert already chains to a public
|
the hives already running. A hive whose cert already chains to a public
|
||||||
CA has nothing to install — `peer-config` says so instead.
|
CA has nothing to install — `peer-config` says so instead.
|
||||||
|
|
@ -98,7 +98,7 @@ Handing a peer this hive's own `ca.pem` would not work even as a
|
||||||
one-off: once a hive CA is an intermediate under the swarm root, it's
|
one-off: once a hive CA is an intermediate under the swarm root, it's
|
||||||
no longer something a verifier can build a chain _to_ — OpenSSL won't
|
no longer something a verifier can build a chain _to_ — OpenSSL won't
|
||||||
terminate a chain at a trusted non-self-signed certificate without
|
terminate a chain at a trusted non-self-signed certificate without
|
||||||
`-partial_chain`. that's why the tool distributes the root, not a
|
`-partial_chain`. That's why the tool distributes the root, not a
|
||||||
per-hive file.
|
per-hive file.
|
||||||
|
|
||||||
## Adopting the hierarchy on an existing hive
|
## Adopting the hierarchy on an existing hive
|
||||||
|
|
@ -117,7 +117,7 @@ Adoption happens by itself, once. `hive-tls-ca.service` notices that
|
||||||
`ca-previous.pem`, and re-issues under the root; the next leaf is signed
|
`ca-previous.pem`, and re-issues under the root; the next leaf is signed
|
||||||
by the new CA.
|
by the new CA.
|
||||||
|
|
||||||
it's safe to automate here precisely because this is the
|
It's safe to automate here precisely because this is the
|
||||||
all-on-one-host shape: every consumer is on this box, so "when will they
|
all-on-one-host shape: every consumer is on this box, so "when will they
|
||||||
have refreshed" is knowable rather than guessed.
|
have refreshed" is knowable rather than guessed.
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ build log isn't something anyone reads twice.
|
||||||
|
|
||||||
To keep the current CA on purpose — a hive that deliberately stays
|
To keep the current CA on purpose — a hive that deliberately stays
|
||||||
outside the hierarchy, or one mid-migration — `touch` the marker file
|
outside the hierarchy, or one mid-migration — `touch` the marker file
|
||||||
named in the message. that's a decision, and it's recorded as one.
|
named in the message. That's a decision, and it's recorded as one.
|
||||||
|
|
||||||
A hive with **no** root configured at all isn't affected by any of
|
A hive with **no** root configured at all isn't affected by any of
|
||||||
this: it self-signs exactly as it always has.
|
this: it self-signs exactly as it always has.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ so the store is a perfectly good place for it.
|
||||||
|
|
||||||
**The generator and the reader are usually in different containers.** They share
|
**The generator and the reader are usually in different containers.** They share
|
||||||
the host's network namespace, which makes them feel co-located, but their
|
the host's network namespace, which makes them feel co-located, but their
|
||||||
filesystem roots are separate. that's why delivery is a **host-side copy rather
|
filesystem roots are separate. That's why delivery is a **host-side copy rather
|
||||||
than a bind mount**: `nixos-container` refuses to start when a bind source is
|
than a bind mount**: `nixos-container` refuses to start when a bind source is
|
||||||
missing, and a secret minted on another container's first boot doesn't exist
|
missing, and a secret minted on another container's first boot doesn't exist
|
||||||
yet. Binding it would make one container wait on a file that waits on a
|
yet. Binding it would make one container wait on a file that waits on a
|
||||||
|
|
@ -59,7 +59,7 @@ neither is a renaming of the other.
|
||||||
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it's stated rather than inferred | n/a — only the host running the store seals anything |
|
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it's stated rather than inferred | n/a — only the host running the store seals anything |
|
||||||
|
|
||||||
The three keys authelia mints for itself are generated in-container precisely
|
The three keys authelia mints for itself are generated in-container precisely
|
||||||
because nothing outside that container ever reads them. **that's the test worth
|
because nothing outside that container ever reads them. **That's the test worth
|
||||||
applying to any secret added here** — and the client secret's plaintext half is
|
applying to any secret added here** — and the client secret's plaintext half is
|
||||||
the one row that fails it, which is the entire reason a delivery step exists.
|
the one row that fails it, which is the entire reason a delivery step exists.
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ secret isn't the container that reads it.
|
||||||
The copy is only made when authelia is enabled **on this host** and something
|
The copy is only made when authelia is enabled **on this host** and something
|
||||||
published is being scraped; otherwise no secret is needed and none is placed.
|
published is being scraped; otherwise no secret is needed and none is placed.
|
||||||
|
|
||||||
⚠️ **don't read that delivery unit as the only way this collector is fed.**
|
⚠️ **Don't read that delivery unit as the only way this collector is fed.**
|
||||||
Whether it authenticates follows the _credential_, never another service's
|
Whether it authenticates follows the _credential_, never another service's
|
||||||
placement: a swarm collector may run on a host that holds neither store and no
|
placement: a swarm collector may run on a host that holds neither store and no
|
||||||
authelia, and then the secret is an ordinary operator-provided file named in
|
authelia, and then the secret is an ordinary operator-provided file named in
|
||||||
|
|
@ -108,12 +108,12 @@ admit anyone to the queue, so it belongs wherever the responder runs and nowhere
|
||||||
else.
|
else.
|
||||||
|
|
||||||
A hive that sets neither the public keys nor `autoGenerateCallout` fails at
|
A hive that sets neither the public keys nor `autoGenerateCallout` fails at
|
||||||
eval, naming the option it wants. that's deliberate: a queue that started
|
eval, naming the option it wants. That's deliberate: a queue that started
|
||||||
without them would accept `CONNECT {"user":"auth"}` from anyone sharing the
|
without them would accept `CONNECT {"user":"auth"}` from anyone sharing the
|
||||||
host's network namespace, and nothing would look wrong until somebody connected.
|
host's network namespace, and nothing would look wrong until somebody connected.
|
||||||
|
|
||||||
**All four or none** — the seed paths are required too, not just the public
|
**All four or none** — the seed paths are required too, not just the public
|
||||||
keys. they're two halves of the same pair: the server verifies with the public
|
keys. They're two halves of the same pair: the server verifies with the public
|
||||||
half, the responder signs with the private one. Supplying only the public keys
|
half, the responder signs with the private one. Supplying only the public keys
|
||||||
used to pass eval and leave the queue with an auth-callout nobody answers, which
|
used to pass eval and leave the queue with an auth-callout nobody answers, which
|
||||||
refuses every client rather than degrading — and a refusal reaches the client as
|
refuses every client rather than degrading — and a refusal reaches the client as
|
||||||
|
|
@ -163,7 +163,7 @@ internal CA deletes that file and names its own paths in
|
||||||
`deploy.bao.serverCertFile` / `clientCaFile`; the store itself has no opinion.
|
`deploy.bao.serverCertFile` / `clientCaFile`; the store itself has no opinion.
|
||||||
A hive that reads from a store on **another** machine names the reader's half —
|
A hive that reads from a store on **another** machine names the reader's half —
|
||||||
`clientCertFile`, `clientKeyFile`, `serverCaFile` — and places that leaf by hand.
|
`clientCertFile`, `clientKeyFile`, `serverCaFile` — and places that leaf by hand.
|
||||||
it's the one credential that can't come out of the store, being what opens it;
|
It's the one credential that can't come out of the store, being what opens it;
|
||||||
everything else a hive needs does.
|
everything else a hive needs does.
|
||||||
|
|
||||||
## The constraint that decides where the root lives
|
## The constraint that decides where the root lives
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ services.hyperhive.deploy.allSwarmServices = true;
|
||||||
|
|
||||||
**`deploy.allSwarmServices` is what "the swarm's shared services run
|
**`deploy.allSwarmServices` is what "the swarm's shared services run
|
||||||
here" means: every once-per-swarm service that's _optional_ takes its
|
here" means: every once-per-swarm service that's _optional_ takes its
|
||||||
`enable` from it.** that's the whole rule, stated once — the per-service
|
`enable` from it.** That's the whole rule, stated once — the per-service
|
||||||
sections below don't repeat it, so a service that stops deriving is a
|
sections below don't repeat it, so a service that stops deriving is a
|
||||||
visible difference rather than one more paragraph saying the same thing.
|
visible difference rather than one more paragraph saying the same thing.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ between "an operator's page" and "anyone with a session".
|
||||||
## What it costs to be reachable
|
## What it costs to be reachable
|
||||||
|
|
||||||
The apex is published to the hive's resolver like every other swarm
|
The apex is published to the hive's resolver like every other swarm
|
||||||
service, so agent containers can resolve it. that's deliberate and it's
|
service, so agent containers can resolve it. That's deliberate and it's
|
||||||
not a hole: **reachability isn't the access control here.** An agent
|
not a hole: **reachability isn't the access control here.** An agent
|
||||||
that resolves the name and connects still has no operator session, and
|
that resolves the name and connects still has no operator session, and
|
||||||
the subrequest denies it.
|
the subrequest denies it.
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ agent's container uid/gid** — `o=--x` is traverse without listing, and
|
||||||
both sockets are `0666`, which is all a dialer needs.
|
both sockets are `0666`, which is all a dialer needs.
|
||||||
|
|
||||||
**Ownership is declared, not repaired.** The tmpfiles.d entry written by
|
**Ownership is declared, not repaired.** The tmpfiles.d entry written by
|
||||||
`SyncAgentTmpfiles` names the uid/gid directly. don't add a chown
|
`SyncAgentTmpfiles` names the uid/gid directly. Don't add a chown
|
||||||
alongside it: `d` re-applies on every boot _and_ every agent
|
alongside it: `d` re-applies on every boot _and_ every agent
|
||||||
spawn/destroy, so ownership set afterwards is reverted the next time any
|
spawn/destroy, so ownership set afterwards is reverted the next time any
|
||||||
agent changes — which is exactly how this dir spent a long time at
|
agent changes — which is exactly how this dir spent a long time at
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
The sections below document specific mechanisms (the state-file endpoint,
|
The sections below document specific mechanisms (the state-file endpoint,
|
||||||
nixbld isolation, privilege separation). This section frames the model they
|
nixbld isolation, privilege separation). This section frames the model they
|
||||||
serve: **what hyperhive defends, what it deliberately doesn't, and where the
|
serve: **what hyperhive defends, what it deliberately doesn't, and where the
|
||||||
operator is accepting risk.** it's the reference for "is it safe to give an
|
operator is accepting risk.** It's the reference for "is it safe to give an
|
||||||
agent capability X?".
|
agent capability X?".
|
||||||
|
|
||||||
### The trust boundary is the container, not credential storage
|
### The trust boundary is the container, not credential storage
|
||||||
|
|
@ -66,7 +66,7 @@ checkpoints**, not about sandboxing the agent from its own tools:
|
||||||
merges restricted to the operators team + a required operators-team approval
|
merges restricted to the operators team + a required operators-team approval
|
||||||
(`apply_operator_branch_protection` / the config-repo equivalent) — and an
|
(`apply_operator_branch_protection` / the config-repo equivalent) — and an
|
||||||
agent (a write collaborator, not a repo admin) can neither change those
|
agent (a write collaborator, not a repo admin) can neither change those
|
||||||
settings nor merge its own PR. it's **not** set up for external VCS (GitHub
|
settings nor merge its own PR. It's **not** set up for external VCS (GitHub
|
||||||
etc.), though — there, operator-merge is process + accepted risk, not a
|
etc.), though — there, operator-merge is process + accepted risk, not a
|
||||||
technical control.
|
technical control.
|
||||||
- **Approvals** — config changes, schedule additions, and other
|
- **Approvals** — config changes, schedule additions, and other
|
||||||
|
|
@ -161,7 +161,7 @@ services — including the core dashboard at `127.0.0.1:<dashboard_port>`, which
|
||||||
has no application-layer auth of its own (see [`docs/scheduler/ci.md`](../scheduler/ci.md)). The `0600`
|
has no application-layer auth of its own (see [`docs/scheduler/ci.md`](../scheduler/ci.md)). The `0600`
|
||||||
token policy bounds file reads; network isolation bounds network reach.
|
token policy bounds file reads; network isolation bounds network reach.
|
||||||
|
|
||||||
**what's NOT exposed**:
|
**What's NOT exposed**:
|
||||||
|
|
||||||
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent
|
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent
|
||||||
user `<name>`. nixbld users can't read it.
|
user `<name>`. nixbld users can't read it.
|
||||||
|
|
@ -171,7 +171,7 @@ token policy bounds file reads; network isolation bounds network reach.
|
||||||
can't read it.
|
can't read it.
|
||||||
|
|
||||||
**Policy**: all credential files written to agent state directories MUST be mode
|
**Policy**: all credential files written to agent state directories MUST be mode
|
||||||
`0600` or stricter. don't create world-readable secret files in agent state dirs.
|
`0600` or stricter. Don't create world-readable secret files in agent state dirs.
|
||||||
|
|
||||||
### Long-term fix
|
### Long-term fix
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -852,7 +852,7 @@ life). Two consequences for anything rendering it:
|
||||||
string; don't switch on specific values. `restarting` in
|
string; don't switch on specific values. `restarting` in
|
||||||
particular no longer exists, because no node kind is unique to a
|
particular no longer exists, because no node kind is unique to a
|
||||||
restart.
|
restart.
|
||||||
- it's **not** exclusively operator-initiated, and **not** limited
|
- It's **not** exclusively operator-initiated, and **not** limited
|
||||||
to rebuild-shaped work — `running_transients()` on the backend is
|
to rebuild-shaped work — `running_transients()` on the backend is
|
||||||
a status-only test (any `Running` node whose payload names a
|
a status-only test (any `Running` node whose payload names a
|
||||||
non-empty agent lights a pill), so work the operator never
|
non-empty agent lights a pill), so work the operator never
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue