diff --git a/docs/integrations/github.md b/docs/integrations/github.md index a6242c0f..88fccf27 100644 --- a/docs/integrations/github.md +++ b/docs/integrations/github.md @@ -5,9 +5,9 @@ HTTPS, both authenticated by an operator-supplied personal access token (PAT) — so it can run GitHub API calls and push commits without any manual `gh auth login`. -Provisioning is UI-driven: paste a PAT into the agent's credentials tab -and it works. No per-agent nix declaration, no rebuild — hive-c0re -injects the token into the agent's state dir out of band. +Provisioning is UI-driven: link a PAT to the agent in the swarm UI and it +works. No per-agent nix declaration, no rebuild — the agent fetches the token +from the swarm secret store itself. ## Enabling @@ -15,8 +15,7 @@ injects the token into the agent's state dir out of band. The integration is **on by default** for every agent (`services.hyperhive.agent.github.enable = true`), inert until the operator provisions a PAT. No per-agent declaration is -needed — an agent gains GitHub by having a PAT written to its token -file. +needed — an agent gains GitHub by having a PAT stored for it. @@ -27,29 +26,31 @@ services.hyperhive.github.enable = false; ``` hive-c0re's meta-flake renderer then injects `services.hyperhive.agent.github.enable = false` -into every agent, so no agent ships the `gh` wrapper or credential helper. +into every agent, so no agent ships the `gh` wrapper, the credential helper +or the token fetch. (`services.hyperhive.agent.github.enable` also exists per-agent for completeness, but the hive-wide host switch is the intended control.) -github.com only. The token **value** never touches nix — it's written to -`/github-token` separately (see [Provisioning](#provisioning)). +github.com only. The token **value** never touches nix — the agent writes it +to `/github-token` at runtime (see [Provisioning](#provisioning)). ## Provisioning -The PAT is operator-supplied. The primary path is the **dashboard -credentials tab** (github sub-tab): paste the PAT for an agent and submit -(`POST /api/github-account`). A CLI path also exists for -recovery/scripting: +The PAT is operator-supplied. In the [swarm UI](../swarm/ui.md#linking-external-accounts), +open the agent on `/agents`, choose **link github account** and paste the PAT +(`PUT /api/hives/{hive}/agents/{agent}/github-account`). swarm-controller +stores it at `swarm/agents//github-token` in the swarm secret store; +no hive writes it. One token per agent: linking again replaces it, +and no route hands it back. -```sh -hivectl github set-token --token-stdin # paste the PAT on stdin (preferred) -hivectl github set-token --token # inline (visible in shell history) -``` - -Either path has hive-c0re delegate the write to hive-priv, which stores the -file `0600` owned by the agent (so the container can read it) — the same -credential-injection path as forge/matrix tokens. See -[hivectl → GitHub](../tools/hivectl.md#github). +The agent's `hive-agent-github-token` unit reads that path under the +agent's own store certificate and writes `/github-token` (`0600`, +owned by the agent), on boot and every two minutes, replacing the file only +when the token changed. It needs a store identity +(`services.hyperhive.agent.bao.addr`); an agent without one gets no token. +The unit never deletes the file: a `github-token` already in place stays +when the store holds none or doesn't answer. Where the token lives and who +reads it: [credentials.md](../swarm/credentials.md). ## Security diff --git a/docs/networking/gateway.md b/docs/networking/gateway.md index 55adde6b..6a19b7c2 100644 --- a/docs/networking/gateway.md +++ b/docs/networking/gateway.md @@ -14,7 +14,7 @@ You rarely switch it on yourself. `gateway.enable` defaults to off, and every mo | --- | --- | --- | | `/` | swarm-ui dist (static), behind an authelia subrequest | `swarm-ui.nix`, `deploy.swarm-ui.enable` | | `auth./` | authelia (`9091`) | `swarm-authelia.nix`, `deploy.authelia.enable` | -| `forge./` | forgejo (`3000`) | `hive-forge/`, `deploy.forgejo.behindGateway` | +| `forge./` | forgejo (`3000`) | `hive-forge/`, `deploy.forgejo.enable` | | `chat./_matrix/*` | tuwunel (`8008`) | `hive-matrix.nix`, `swarm.matrix.gatewayHost != null` | | `chat./` | fluffychat-web static (404 with the GUI off) | `hive-matrix.nix`, `deploy.matrix.gui.enable` | | `chat./config.json` | inline JSON (FluffyChat boot config) | `hive-matrix.nix`, `deploy.matrix.gui.enable` | @@ -170,7 +170,7 @@ services.hyperhive.deploy.forgejo.openFirewall = false; # default `sshPort` serves `git clone/push/pull` over SSH (`git@:owner/repo.git` with `-p 2222`). SSH goes straight to Forgejo, not through nginx. -`openFirewall` (default `false`) opens `httpPort` and `sshPort` on the host. Agents don't need it — they come through the gateway. Set it for a browser reaching `http://:/` directly, or for external git clients pushing over SSH. The forge vhost behind the gateway (`deploy.forgejo.behindGateway`, default `true`) needs only the gateway's own `openFirewall`. +`openFirewall` (default `false`) opens `httpPort` and `sshPort` on the host. Agents don't need it — they come through the gateway. Set it for a browser reaching `http://:/` directly, or for external git clients pushing over SSH. Forge's gateway vhost doesn't need `openFirewall` — the gateway's own `openFirewall` option covers that path. ### `rootUrl` override @@ -178,14 +178,7 @@ services.hyperhive.deploy.forgejo.openFirewall = false; # default services.hyperhive.swarm.forge.rootUrl = "https://forge.example.com/"; ``` -`rootUrl` (default `null`) overrides the Forgejo `ROOT_URL` derived from `forge.domain` and the gateway: - -| Shape | Derived `ROOT_URL` | -|---|---| -| `deploy.forgejo.behindGateway = true` | `https:///` (no port suffix when `gateway.httpsPort == 443`) | -| `deploy.forgejo.behindGateway = false` | `http://:/` | - -Set it when `forge.domain` differs from the public URL, or for a bespoke shape such as an external reverse proxy on another host or path. It must end with `/` (an assertion enforces this). +`rootUrl` (default `null`) overrides the Forgejo `ROOT_URL` that's autoderived as `https:///`, with `:` appended when that port isn't 443. The gateway always terminates TLS, so the forge is always advertised over `https://`. Set `rootUrl` explicitly when `forge.domain` differs from the public URL, or for a bespoke shape such as an external reverse proxy on another host or path. It must end with `/` (an assertion enforces this). ## Security headers @@ -218,7 +211,7 @@ When enabled, every vhost adds `Strict-Transport-Security: max-age=…[; include `services.hyperhive.gateway.localHostsEntry = true` maps to `127.0.0.1` in the host's `/etc/hosts`: - the hive domain; -- every name a module on this host contributes to `gateway.localNames` — each swarm service this host runs adds its own (`forge.` when behind the gateway, `chat.`, `auth.`, the swarm UI's apex, …). +- every name a module on this host contributes to `gateway.localNames` — each swarm service this host runs adds its own (`forge.`, `chat.`, `auth.`, the swarm UI's apex, …). `services.hyperhive.deploy.singleHostSwarm` turns it on. Leave it off with real DNS. diff --git a/docs/scheduler/ci.md b/docs/scheduler/ci.md index 450c5d70..56dd7e1b 100644 --- a/docs/scheduler/ci.md +++ b/docs/scheduler/ci.md @@ -119,7 +119,7 @@ build can't hold the runner's single slot indefinitely). ## Container design -- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://` (resolved to the bridge IP via `networking.extraHosts`). It can't reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. Requires `deploy.forgejo.behindGateway = true`. +- **Private netns, bridge-attached**: the container runs in its own network namespace (`privateNetwork = true`, `hostBridge`) and reaches hive-forge through the gateway at `http://` (resolved to the bridge IP via `networking.extraHosts`). It can't reach host-loopback services — the core dashboard at `127.0.0.1:7000` and the raw forge port are unreachable from CI. - **Non-ephemeral**: runner credentials persist across restarts (written to container's stateDir on first registration, reused thereafter). - **Sandbox fallback**: nspawn containers can't create user-namespaces, so nix's sandboxing would always fail. Module sets `nix.settings.sandbox-fallback = true` in the container — nix builds run unsandboxed (safe because the container is already isolated). See `docs/process/gotchas.md`. - **Credential isolation**: the forge admin token (`forge-core-token`) never enters the container. hive-c0re holds it and performs all forge API calls (runner validation + registration-token mint, in `forge/ci_runner.rs`); via hive-priv it writes only the runner registration token to the host env-file `/run/hive-ci/runner-token`, which the container bind-mounts read-only. diff --git a/docs/swarm/credentials.md b/docs/swarm/credentials.md index 8a216831..89e63765 100644 --- a/docs/swarm/credentials.md +++ b/docs/swarm/credentials.md @@ -42,10 +42,6 @@ to the store under its own name, pulling what it needs when it needs it. No process reads a secret on another principal's behalf: the principal that needs a value is the principal that authenticates for it. -One per-agent credential file — the github token — sits outside this page: -it's operator-supplied and never passes through the store, so the table -below doesn't govern it. - **Per secret, the target specifies minter, reader, and renewal strategy.** Those three are the contract, and the reader is a process pulling a store path at runtime — not a path on disk, and not a unit whose job is to turn a @@ -69,6 +65,7 @@ of the cell says how. | `swarm/agents//queue` | `swarm-controller`, at agent creation | `hive-agent` in the agent container, under the agent's own certificate, held in memory — the identity it presents to the swarm queue, naming that one agent rather than its hive | ✅ `swarm-controller`'s five-minute pass re-mints a live agent's secret once it's 45 days old by `minted_at` on the stored object; a secret with no `minted_at` gets one stamped, value unchanged. The pass skips agents declared `Destroyed` — declaring an agent destroyed deletes every version of the path instead, the undo of the mint rather than another one | ✅ `hive-agent` reads the path before its first connect and again on every reconnect attempt, so a reconnect after a re-mint presents the new secret. An open connection keeps the secret it connected with; after a revocation the agent keeps retrying under the queue client's backoff | | `swarm/agents//forge-token` | `swarm-controller`, at agent creation and in a pass every 5 minutes over every agent with a store identity | the agent container itself, under its own certificate, fetched to `/run/hive-agent-forge-token/token` | ✅ the controller re-mints when the stored token is missing or no longer matches the forge (last eight characters and scopes) | ✅ the agent re-fetches on a 10-minute timer | | `swarm/agents//forge/