docs: fix 329 Microsoft.Contractions hits with proper safety checks
Apply contraction fixes across ~40 doc files (setup, integrations, lifecycle, networking, scheduler, swarm, tools, trust-boundary, UI, etc.). Skipped 14 hits: - 10 where words appear in ALL CAPS for deliberate emphasis (is NOT, do NOT, etc.) - 4 where text could not be safely located due to markdown formatting or column position Applied via systematic scan with checks for fenced code blocks, inline code spans, and intentional caps. Preserves sentence-initial capitalization throughout.
This commit is contained in:
parent
77296aff35
commit
78021ce982
38 changed files with 320 additions and 320 deletions
|
|
@ -107,7 +107,7 @@ now set unconditionally for every agent. The mechanism:
|
|||
The dir is `0751`, owned by the agent's container uid/gid, so
|
||||
nginx reaches `web.sock` through `o=--x` (traverse) and the socket's
|
||||
own `0666`. The gateway is one of three principals sharing that dir
|
||||
and does not own its ownership rules — see
|
||||
and doesn't own its ownership rules — see
|
||||
[`docs/trust-boundary/boundary.md`](../trust-boundary/boundary.md#the-per-agent-socket-dir).
|
||||
3. **Marker gate**. After successful `bind_unix`, the harness drops
|
||||
`<dir>/hyperhive-socket-bound` next to the socket. c0re's
|
||||
|
|
@ -118,13 +118,13 @@ now set unconditionally for every agent. The mechanism:
|
|||
`/var/lib/hive-gateway/conf/agents.conf` — a plain nginx include
|
||||
file with one `location /agent/<name>/` block per agent. Always
|
||||
a UDS upstream (`http://unix:/run/hive-agent/<name>/web.sock:/`);
|
||||
if the socket is not yet bound, nginx returns 502 caught by the
|
||||
if the socket isn't yet bound, nginx returns 502 caught by the
|
||||
`error_page 502 503 504 = /__hive_agent_unreachable` directive.
|
||||
nginx includes `/var/lib/hive-gateway/conf/agents.conf` — the same
|
||||
path c0re writes, since both run on the host.
|
||||
After each write, c0re triggers the appropriate nginx action via
|
||||
`hive-priv` (which is root; hive-c0re runs as the unprivileged
|
||||
`hive-core` user and cannot act on a system unit).
|
||||
`hive-core` user and can't act on a system unit).
|
||||
`hive-priv` queries `ActiveState` and dispatches:
|
||||
- active → `systemctl reload nginx` (SIGHUP, zero-downtime)
|
||||
- failed → `systemctl reset-failed nginx` + `systemctl start nginx`
|
||||
|
|
@ -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`).
|
||||
|
||||
⚠️ **Do not 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
|
||||
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
|
||||
|
|
@ -216,13 +216,13 @@ swarm-services fallback below is for.
|
|||
|
||||
**Why a CA, not a bare leaf**: a bare self-signed leaf is its own trust anchor, so every regeneration is a new anchor every consumer must re-trust — and a runtime-generated leaf can't be wired into an agent's build-time trust store at all. With a stable CA, agents and federation peers trust it *once*; leaf rotation never re-breaks them.
|
||||
|
||||
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](../swarm/ca.md) has the hierarchy), and an intermediate is not a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it is rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
|
||||
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](../swarm/ca.md) has the hierarchy), and an intermediate isn't a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it's rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
|
||||
|
||||
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client cannot bootstrap.
|
||||
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client can't bootstrap.
|
||||
|
||||
**Cert shape**: leaf subject CN = bare hive domain; subjectAltName is `<hive>` plus wildcard `*.<hive>`, so all current and future sub-domain vhosts validate under the same leaf + the hive CA. A swarm service whose name is *not* under this hive's domain cannot be added here — the hive CA is name-constrained to `<hive>`, and a violating SAN invalidates the whole leaf, not just that name. Those names get the swarm-services leaf instead ([`swarm/ca.md`](../swarm/ca.md)).
|
||||
**Cert shape**: leaf subject CN = bare hive domain; subjectAltName is `<hive>` plus wildcard `*.<hive>`, so all current and future sub-domain vhosts validate under the same leaf + the hive CA. A swarm service whose name is *not* under this hive's domain can't be added here — the hive CA is name-constrained to `<hive>`, and a violating SAN invalidates the whole leaf, not just that name. Those names get the swarm-services leaf instead ([`swarm/ca.md`](../swarm/ca.md)).
|
||||
|
||||
**Rotation**: `hive-tls-ca.service` is idempotent — it re-signs the leaf when it is missing or within 30 days of expiry, always under the same CA (so consumer trust is undisturbed). The CA itself is regenerated only if missing or already expired. To force a leaf rotation, delete `gateway.pem` under the state dir and restart the unit, then reload `nginx`.
|
||||
**Rotation**: `hive-tls-ca.service` is idempotent — it re-signs the leaf when it's missing or within 30 days of expiry, always under the same CA (so consumer trust is undisturbed). The CA itself is regenerated only if missing or already expired. To force a leaf rotation, delete `gateway.pem` under the state dir and restart the unit, then reload `nginx`.
|
||||
|
||||
**Cert prompts**: browsers still warn once per host until the hive's `trust-bundle.pem` is added to the browser/OS trust store (an anchor, not the leaf, is the thing to trust). Agent trust is wired separately (see the agent-trust work for `/run/hive-ca`).
|
||||
|
||||
|
|
@ -242,7 +242,7 @@ nginx reads the directory directly and uses `cert.pem` + `key.pem` (override `tl
|
|||
|
||||
`tls.certDir` and `tls.acme.enable` set together is an assertion error.
|
||||
|
||||
**Key file permissions**: nixpkgs's `security.acme` outputs private keys as `0640 root:acme` by default. nginx runs as the `nginx` user and cannot read a key with that ownership. Fix with:
|
||||
**Key file permissions**: nixpkgs's `security.acme` outputs private keys as `0640 root:acme` by default. nginx runs as the `nginx` user and can't read a key with that ownership. Fix with:
|
||||
|
||||
```nix
|
||||
security.acme.certs."example.com".group = "nginx";
|
||||
|
|
@ -258,7 +258,7 @@ for an operator who wants their own TLS terminator:
|
|||
- give the gateway the real cert via `tls.certDir` (or `tls.acme`) so it
|
||||
serves proper TLS directly — no separate proxy needed; or
|
||||
- front it over a **unix socket** rather than a plain-http TCP port (the
|
||||
intended direction for "bring your own proxy" — the gateway is not meant
|
||||
intended direction for "bring your own proxy" — the gateway isn't meant
|
||||
to expose an unencrypted TCP upstream).
|
||||
|
||||
Because of this, `.well-known/matrix/{client,server}` discovery responses
|
||||
|
|
@ -369,7 +369,7 @@ that path.
|
|||
services.hyperhive.swarm.forge.rootUrl = "https://forge.example.com/";
|
||||
```
|
||||
|
||||
`rootUrl` (default **null**) overrides the Forgejo `ROOT_URL` that is
|
||||
`rootUrl` (default **null**) overrides the Forgejo `ROOT_URL` that's
|
||||
auto-derived from `forge.domain` + gateway state. The auto-derivation
|
||||
covers most cases:
|
||||
|
||||
|
|
@ -455,7 +455,7 @@ legacy single-proxy block and all traffic is forwarded to the agent
|
|||
daemon as before.
|
||||
|
||||
**`extraFiles`**: per-agent `hyperhive.frontend.extraFiles` are in
|
||||
`mergedDist`, not in the base `cfg.frontend` dist. They are 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
|
||||
`try_files` to `@<name>_dynamic` and are served by the agent daemon
|
||||
as before.
|
||||
|
|
@ -493,7 +493,7 @@ default status code would point at the wrong component. The per-agent
|
|||
routes qualify (a 502 there means the harness is restarting, not that
|
||||
the gateway is broken), and so does `auth.<swarm>` — a dead authelia
|
||||
upstream almost always means the user store was never bootstrapped, and
|
||||
a bare 502 blames the proxy, which is the one part that is working.
|
||||
a bare 502 blames the proxy, which is the one part that's working.
|
||||
|
||||
Forge / matrix / fluffychat still get nginx defaults: their upstreams
|
||||
being down means what the status code says, so a themed page would add
|
||||
|
|
@ -542,7 +542,7 @@ The daemon hashes passwords with BCrypt (cost 12) and writes
|
|||
`$2y$`-prefixed hashes that nginx accepts natively. No external
|
||||
`htpasswd` binary is required.
|
||||
|
||||
**What is 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
|
||||
applies `auth_basic` per-location. Full per-agent coverage is a
|
||||
follow-up.
|
||||
|
|
@ -618,18 +618,18 @@ the opposite arm run as a control:
|
|||
|
||||
- `verify` + `depth` — the chain is leaf -> intermediate -> root.
|
||||
- `trusted_cert` — the bundle; nginx reads ALL certs in the file, which
|
||||
the bundle's own doc warns is not true of every consumer.
|
||||
the bundle's own doc warns isn't true of every consumer.
|
||||
- `ssl_name` — checks the HOSTNAME too. Without it a chain-only check
|
||||
accepts any certificate this CA ever signed, and for an internal CA
|
||||
that is every service on the hive.
|
||||
- `server_name on` — sends SNI, or the far end cannot pick a cert.
|
||||
that's every service on the hive.
|
||||
- `server_name on` — sends SNI, or the far end can't pick a cert.
|
||||
|
||||
**⚠️ Session-cache footgun**: `proxy_ssl_session_reuse` is left at its
|
||||
default (on), deliberately — this is used on per-request auth
|
||||
subrequests, so the handshake it avoids is paid on every request. Worth
|
||||
knowing when testing though: the session cache is keyed by upstream
|
||||
address and NOT by trust config, so two locations pointing at one
|
||||
upstream with different trust do not verify independently.
|
||||
upstream with different trust don't verify independently.
|
||||
|
||||
**⚠️ Host-header clobber footgun**: `verifiedProxyTo` also pins `Host`
|
||||
(and reinstates the rest of nginx's `recommendedProxySettings` header
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ The flows, end to end:
|
|||
`/run/hive/mcp.sock`, privileged helper `/run/hive/priv.sock`,
|
||||
operator admin `/run/hyperhive/host.sock`, and the per-agent UI
|
||||
sockets under `/run/hive-agent/` are unix domain sockets
|
||||
bind-mounted through the VFS; private netns does not affect them.
|
||||
bind-mounted through the VFS; private netns doesn't affect them.
|
||||
|
||||
## Container shape (where dnsmasq lives)
|
||||
|
||||
|
|
@ -100,8 +100,8 @@ listener on `bridgeIp` is on the host's bridge interface.
|
|||
```
|
||||
|
||||
Requires `services.hyperhive.domain` to be set — the dnsmasq resolver
|
||||
is authoritative for `<hive-domain>` and its sub-domains. You do not
|
||||
write it: it is read from this hive's entry in the swarm directory
|
||||
is authoritative for `<hive-domain>` and its sub-domains. You don't
|
||||
write it: it's read from this hive's entry in the swarm directory
|
||||
(`docs/swarm/README.md` § Hive identity config).
|
||||
|
||||
## Bridge addressing
|
||||
|
|
@ -159,7 +159,7 @@ agent containers.
|
|||
interfaces stay closed. The hive resolver isn't an external-facing
|
||||
service.
|
||||
- Port 67 (UDP) admits DHCP requests to the dnsmasq pool. dnsmasq
|
||||
receives DHCP via a regular UDP socket (it does not use a
|
||||
receives DHCP via a regular UDP socket (it doesn't use a
|
||||
netfilter-bypassing raw socket), so the hole is mandatory — without
|
||||
it containers never get a lease and fall back to 169.254.x.x.
|
||||
- Ports 80 and 443 let isolated agents reach nginx (gateway
|
||||
|
|
@ -174,7 +174,7 @@ boot its `firewall-start` flushes the `nixos-fw` chains, rebuilds them
|
|||
from the container's (empty) port list, and deletes the host's
|
||||
`nixos-nat-*` chains without recreating them, silently wiping the
|
||||
bridge holes above plus the agents' NAT. Private-netns containers
|
||||
(agents, hive-ci) may keep their own firewall — it is scoped to their
|
||||
(agents, hive-ci) may keep their own firewall — it's scoped to their
|
||||
namespace.
|
||||
|
||||
### Reaching host services (`exposeHostPorts`)
|
||||
|
|
@ -225,7 +225,7 @@ address arithmetic.
|
|||
`PRIVATE_NETWORK=1`, `LOCAL_ADDRESS=` (empty), `HOST_ADDRESS=<bridge-ip>`,
|
||||
and `HOST_BRIDGE=<bridgeName>` via `lifecycle::set_nspawn_flags` when
|
||||
creating or updating containers. Both variables are validated **once at
|
||||
daemon startup**, not per container: they are process-global, so a
|
||||
daemon startup**, not per container: they're process-global, so a
|
||||
missing or malformed value is a misconfigured daemon rather than one bad
|
||||
container, and failing at boot gives a single diagnostic instead of one
|
||||
per agent. There is no non-isolated mode to fall back to. `LOCAL_ADDRESS` is left empty so the
|
||||
|
|
@ -236,7 +236,7 @@ to all containers — agents and service containers alike.
|
|||
`HOST_ADDRESS` is the bridge gateway IP (the address part of
|
||||
`HIVE_NETWORK_SUBNET`, via `lifecycle::bridge_gateway_ip` — taken verbatim
|
||||
so a non-`.1` operator override still resolves to wherever the bridge
|
||||
actually lives). It is **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
|
||||
$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
|
||||
|
|
@ -248,7 +248,7 @@ but no path off the bridge subnet (no internet, no `api.anthropic.com`).
|
|||
nixos-container copies the **host's** `/etc/resolv.conf` into the container
|
||||
at every start. The host resolver (e.g. `127.0.0.53` from systemd-resolved,
|
||||
or a LAN router) is unreachable from a private netns and isn't
|
||||
authoritative for the hive's own zones, so it is replaced with the
|
||||
authoritative for the hive's own zones, so it's replaced with the
|
||||
bridge dnsmasq at boot. Because the copy happens on every start, a
|
||||
declarative `environment.etc."resolv.conf"` would be clobbered — so the
|
||||
wiring is runtime:
|
||||
|
|
@ -257,7 +257,7 @@ wiring is runtime:
|
|||
gateway IP) into each container's `/etc`.
|
||||
- the `hyperhive-isolated-dns` oneshot (`nix/agent-modules/network.nix`), gated on that
|
||||
marker, rewrites `/etc/resolv.conf` to `nameserver <gateway-ip>` at boot.
|
||||
It is 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.
|
||||
|
||||
**Why isolation is safe**: hive-c0re's control-plane sockets are unix
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ The swarm's `btrfs receive` endpoint. Hives push agent snapshots to it
|
|||
over the WireGuard mesh; a destination hive later pulls one back to
|
||||
complete a migration.
|
||||
|
||||
Two things it is not, both worth stating because both are common
|
||||
Two things it's not, both worth stating because both are common
|
||||
assumptions:
|
||||
|
||||
- **It is not the swarm controller**, and does not depend on one. It is
|
||||
- **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
|
||||
receiver, and the `wg-hive` interface the swarm module already brings
|
||||
up. That is why it can be deployed before any controller exists.
|
||||
- **It is not a backup product.** It happens to hold the data a backup
|
||||
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
|
||||
would hold, and it should be operated accordingly (see
|
||||
[Operating it](#operating-it)) --- but nothing in it does scheduling,
|
||||
verification, or restore orchestration.
|
||||
|
|
@ -56,15 +56,15 @@ services.hyperhive.swarm.snapshotStore = {
|
|||
Two deliberate asymmetries in that pair, both liable to be misread as
|
||||
inconsistency:
|
||||
|
||||
- **`address` has no default.** It is a deployment fact a pushing hive
|
||||
cannot derive, and a wrong guess means streaming an agent's state at
|
||||
- **`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
|
||||
whatever happens to answer. Unset, a push fails naming this option.
|
||||
- **`port` does default** (`51821`), because it is a convention both
|
||||
- **`port` does default** (`51821`), because it's a convention both
|
||||
ends read from the same option docs --- a default there is
|
||||
coordination, not a guess.
|
||||
|
||||
Note the option lives under `swarm.*` while the receiving host's lives
|
||||
under `services.hyperhive.snapshotStore`. That is the distinction the
|
||||
under `services.hyperhive.snapshotStore`. that's the distinction the
|
||||
two namespaces carry throughout: `swarm.*` describes _the swarm_ as seen
|
||||
from this host, and a bare `services.hyperhive.<service>` describes _a
|
||||
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
|
||||
|
||||
There are no certificates here, and no key material of its own. That is
|
||||
There are no certificates here, and no key material of its own. that's
|
||||
deliberate rather than an omission.
|
||||
|
||||
WireGuard's cryptokey routing already binds a peer's source address to
|
||||
|
|
@ -97,16 +97,16 @@ you need to move an agent.
|
|||
|
||||
The destination is keyed by **agent**.
|
||||
|
||||
This is not cosmetic. After a migration, an agent's next incremental
|
||||
This isn't cosmetic. After a migration, an agent's next incremental
|
||||
send arrives from a _different_ hive than the previous one. Keying by
|
||||
hive would split that agent's snapshot chain across two directories,
|
||||
and `btrfs send -p` would fail to find its parent --- breaking exactly
|
||||
the case the store exists to serve.
|
||||
|
||||
## What the sender can and cannot choose
|
||||
## What the sender can and can't choose
|
||||
|
||||
A `btrfs send` stream carries no notion of _which agent_ it belongs to,
|
||||
and the subvolume name inside it is chosen by the sender. So the
|
||||
and the subvolume name inside it's chosen by the sender. So the
|
||||
protocol is one `agent <name>` header line, then the raw stream.
|
||||
|
||||
The rule that matters:
|
||||
|
|
@ -116,7 +116,7 @@ The rule that matters:
|
|||
|
||||
Validation is an allowlist --- `[A-Za-z0-9_-]+` and nothing else. No
|
||||
slash and no dot means neither directory traversal nor an absolute path
|
||||
can survive it. It is 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
|
||||
somebody already thought of.
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ address being set are assertions, not documentation --- bound to
|
|||
`0.0.0.0` this socket is an unauthenticated remote write into agent
|
||||
state.
|
||||
|
||||
Binding is not sufficient on its own. NixOS's firewall is default-deny
|
||||
Binding isn't sufficient on its own. NixOS's firewall is default-deny
|
||||
and filters in netfilter, _before_ a packet reaches a bound socket, so
|
||||
the port is opened explicitly --- and scoped to the mesh interface:
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ nobody runs it day to day, so it rots and is discovered broken in
|
|||
production.
|
||||
|
||||
⚠️ **The assumption to keep true over time:** the store host runs
|
||||
nothing else. That is 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
|
||||
stops being true.
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ periodically), not here. Count rather than age is deliberate: a count
|
|||
is bounded by construction, whereas an age policy silently scales disk
|
||||
usage with how hot a hive runs.
|
||||
|
||||
Per-agent or per-hive `btrfs qgroup` quotas are not configured yet.
|
||||
Per-agent or per-hive `btrfs qgroup` quotas aren't configured yet.
|
||||
Without them one runaway hive can fill the store and take out every
|
||||
other hive's snapshots.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue