docs(gateway): describe what is, not what changed

Per review: docs represent current state. Every "used to" / "no longer"
clause this branch introduced is gone — including the History section in
network.md, which was a whole subsection about a sync mechanism that
doesn't exist.

Where the removed clause was carrying a real constraint, the constraint
stays and is stated in the present tense instead of as a delta: nothing
narrows what the gateway's nginx can reach except the directory
permissions in front of a socket, and nothing bounds `ReloadGatewayNginx`
except the hard-coded unit name. Those read as rules now rather than as
the story of how they came to be rules.
This commit is contained in:
atlas 2026-08-11 18:09:51 +02:00
commit 98d895cf9e
15 changed files with 77 additions and 146 deletions

View file

@ -5,9 +5,9 @@
//! is reached from the dashboard — already fully operator-authenticated —
//! so no capability check is needed here, just the same audit trail.
//!
//! The two surfaces no longer cover the same set: the gateway is the
//! operator's to restart and not an agent's, since nginx on the host fronts
//! every hive service. This endpoint keeps all four.
//! The two surfaces cover different sets: this endpoint takes all four,
//! while the agent path refuses the gateway — nginx on the host fronts
//! every hive service, so bouncing it is the operator's call.
use axum::{
extract::{Path as AxumPath, State},

View file

@ -193,8 +193,7 @@ pub(super) async fn get_journal_host(
axum::extract::Query(q): axum::extract::Query<JournalHostQuery>,
) -> Result<Response, ProblemDetails> {
let lines = q.lines.unwrap_or(500).min(5000);
// `nginx.service` is the gateway: its logs used to live in the
// hive-gateway container's journal and are host-side now.
// `nginx.service` is the gateway — its logs are host-side.
let allowed = ["hive-c0re.service", "hive-priv.service", "nginx.service"];
let unit = match q.unit.as_deref().filter(|s| !s.is_empty()) {
Some(u) => {

View file

@ -167,16 +167,12 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
/// After a successful write, triggers the appropriate nginx action via
/// `hive-priv` (hive-c0re runs unprivileged and cannot act on a system
/// unit): reload when nginx is active, reset-failed+start when in a
/// failed state, plain start otherwise. Writer and nginx are now on the
/// same machine, so this is a plain unit action rather than the old
/// `systemd-run --machine=hive-gateway` hop across the container
/// boundary. It stays an explicit trigger rather than a systemd path
/// unit watching the file. A path unit would now *work* — `IN_MOVED_TO`
/// (fired by the atomic rename) failed to propagate across the nspawn
/// mount-namespace boundary, and that boundary is gone — but it is still
/// not wanted: the write already knows it changed something, and a
/// watcher turns one causal edge into a race with the writer's own
/// rename (see `docs/gateway.md`).
/// failed state, plain start otherwise. Writer and nginx are on the same
/// machine, so it is a plain unit action with no machine-bus hop.
/// It stays an explicit trigger rather than a systemd path
/// unit watching the file: the write already knows it changed something,
/// and a watcher would turn one causal edge into a race with the writer's
/// own rename (see `docs/gateway.md`).
///
/// The priv call is best-effort — a failed sync is logged but not fatal.
/// `reload_if_pending` retries on the next `spawn_poll` tick so a

View file

@ -219,11 +219,11 @@ pub fn shared_root() -> PathBuf {
pub const KNOWLEDGE_DIR: &str = "/var/lib/hyperhive/knowledge";
/// `gateway/` — generated nginx include fragments for the gateway vhost.
/// nginx runs on the host and reads this path directly; it used to be
/// bind-mounted into a gateway container at `/run/hive-state/`, exposing
/// this subdir *only* so the rest of `/var/lib/hyperhive/` (forge/matrix
/// tokens, etc.) stayed out of reach. On the host that narrowing is the
/// unit's sandbox, not a mount — nginx is not confined by this path.
/// nginx runs on the host and reads this path directly. ⚠️ Nothing about
/// this path confines it: what keeps nginx away from the rest of
/// `/var/lib/hyperhive/` (forge/matrix tokens, etc.) is the unit's own
/// sandbox, so widening that sandbox widens what a gateway compromise
/// reaches.
// nix: named by the gateway's nginx config (hive-gateway/vhosts.nix) — must match.
#[must_use]
pub fn gateway_dir() -> PathBuf {

View file

@ -14,11 +14,10 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
/// Host-side parent directory holding per-agent socket subdirs. The
/// gateway's nginx runs on the host and reads this whole tree, so it
/// can `proxy_pass` to any agent — it used to get there through a
/// read-only bind-mount of the same tree. Each agent's container
/// bind-mounts only its own `<name>/` subdir, which is still what stops
/// one agent reaching another's socket. The literal lives in `hive-host-sock` (shared with
/// gateway's nginx runs on the host and reads this whole tree, so it can
/// `proxy_pass` to any agent. Each agent's container bind-mounts only its
/// own `<name>/` subdir — that mount is what stops one agent reaching
/// another's socket. The literal lives in `hive-host-sock` (shared with
/// `hivectl`); re-exported here under the name this module's consumers
/// have always used.
pub use hive_host_sock::AGENT_SOCKET_DIR;