The privsep drop to the hive-core user left four journalctl -M <container> call sites shelling out directly. -M enters the container namespace via the machine bus, which needs root, so all container-journal reads failed with Permission denied. Add a ReadContainerJournal verb to hive-priv and route dashboard get_journal, manager get_logs, the rebuild-failure journal tail, and the agent host-journal -M path through it. Host-journal reads (no -M) stay direct via systemd-journal group membership.
184 lines
6.3 KiB
Rust
184 lines
6.3 KiB
Rust
//! Wire types for the `hive-priv` privileged-helper socket.
|
|
//!
|
|
//! Both `hive-priv` (server) and `hive-c0re` (client via `priv_client`)
|
|
//! import these so the shapes stay in sync.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Default socket path for the privileged helper.
|
|
pub const PRIV_SOCK: &str = "/run/hive/priv.sock";
|
|
|
|
/// Manager logical agent name. The manager's system container name is
|
|
/// `h-ruth` (same `h-` prefix convention as every other agent).
|
|
pub const MANAGER_NAME: &str = "ruth";
|
|
|
|
/// Sub-agent container prefix. System container name = `h-<agent_name>`.
|
|
pub const AGENT_PREFIX: &str = "h-";
|
|
|
|
/// Sibling service containers managed by hive-c0re.
|
|
pub const SIBLING_CONTAINERS: &[&str] = &["hive-forge", "hive-matrix", "hive-gateway"];
|
|
|
|
/// Host path of the meta flake. The flake ref for agent `<name>` is
|
|
/// `{META_DIR}#{name}`, derived by `hive-priv` — never passed over the wire.
|
|
pub const META_DIR: &str = "/var/lib/hyperhive/meta";
|
|
|
|
/// Output format for `ReadContainerJournal`. Maps to journalctl
|
|
/// `--output=<...>`. Restricted to the two formats hive callers use so
|
|
/// the wire type can't smuggle an arbitrary `--output` value.
|
|
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum JournalOutput {
|
|
/// `short` — the journalctl default (syslog-style timestamps).
|
|
#[default]
|
|
Short,
|
|
/// `short-iso` — ISO 8601 timestamps.
|
|
ShortIso,
|
|
}
|
|
|
|
impl JournalOutput {
|
|
/// The string journalctl expects after `--output=`.
|
|
pub fn as_journalctl(self) -> &'static str {
|
|
match self {
|
|
JournalOutput::Short => "short",
|
|
JournalOutput::ShortIso => "short-iso",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One bind-mount entry for `WriteNspawnFlags`.
|
|
/// hive-priv constructs `--bind=<host_path>:<container_path>` (or `--bind-ro=`)
|
|
/// and validates both paths before writing the conf file.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BindMount {
|
|
pub host_path: String,
|
|
pub container_path: String,
|
|
pub read_only: bool,
|
|
}
|
|
|
|
/// A request to the privileged helper.
|
|
///
|
|
/// Wire format: one JSON object per line over `/run/hive/priv.sock`.
|
|
/// Every variant is a specific known operation — no pass-through
|
|
/// shell commands or arbitrary paths. New privileged ops get new
|
|
/// variants.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "op", rename_all = "snake_case")]
|
|
pub enum PrivRequest {
|
|
// --- Container lifecycle ---
|
|
/// `nixos-container start <name>`
|
|
StartContainer { name: String },
|
|
|
|
/// `nixos-container stop <name>`
|
|
StopContainer { name: String },
|
|
|
|
/// `nixos-container kill <name>`
|
|
KillContainer { name: String },
|
|
|
|
/// `nixos-container update <name> --flake <flake_ref>`
|
|
/// The flake ref is derived from `name` by hive-priv.
|
|
UpdateContainer { name: String },
|
|
|
|
/// `nixos-container create <name> --flake <flake_ref>`
|
|
/// The flake ref is derived from `name` by hive-priv.
|
|
CreateContainer { name: String },
|
|
|
|
/// `nixos-container destroy <name>`
|
|
DestroyContainer { name: String },
|
|
|
|
/// `nixos-container list`
|
|
ListContainers,
|
|
|
|
// --- Container journal reads ---
|
|
/// Read a container's journal via `journalctl -M <container>`.
|
|
/// Requires root: the machine-bus transport enters the container's
|
|
/// namespace, so this can't run from the unprivileged hive-c0re
|
|
/// process. hive-priv validates `container` against the managed-
|
|
/// container allowlist, then runs journalctl and returns its output.
|
|
///
|
|
/// The filters (`unit` / `priority` / `grep` / `since` / `until`)
|
|
/// are applied within the already-authorized machine and passed to
|
|
/// journalctl as plain argument values; they can't widen access
|
|
/// beyond the validated `container`.
|
|
ReadContainerJournal {
|
|
/// System container name (`h-<agent>` or a sibling service).
|
|
container: String,
|
|
/// `-n <lines>`.
|
|
lines: u32,
|
|
/// `-b` — restrict to the current boot.
|
|
#[serde(default)]
|
|
boot: bool,
|
|
/// `--output=<...>`.
|
|
#[serde(default)]
|
|
output: JournalOutput,
|
|
/// `-u <unit>`.
|
|
#[serde(default)]
|
|
unit: Option<String>,
|
|
/// `-p <priority>`.
|
|
#[serde(default)]
|
|
priority: Option<String>,
|
|
/// `--grep=<regex>`.
|
|
#[serde(default)]
|
|
grep: Option<String>,
|
|
/// `--since=<ts>`.
|
|
#[serde(default)]
|
|
since: Option<String>,
|
|
/// `--until=<ts>`.
|
|
#[serde(default)]
|
|
until: Option<String>,
|
|
},
|
|
|
|
// --- Config file writes ---
|
|
/// Update `/etc/nixos-containers/<container>.conf`: strip network-isolation
|
|
/// vars, force `PRIVATE_NETWORK=0`, and set `EXTRA_NSPAWN_FLAGS` from the
|
|
/// provided bind-mount list. Written by `lifecycle::set_nspawn_flags`.
|
|
WriteNspawnFlags {
|
|
container: String,
|
|
binds: Vec<BindMount>,
|
|
},
|
|
|
|
/// Write `/run/systemd/system/container@<container>.service.d/hyperhive-limits.conf`
|
|
/// with `[Service]\nMemoryMax=<memory_max>\nCPUQuota=<cpu_quota>\n`.
|
|
/// Written by `lifecycle::set_resource_limits`.
|
|
WriteResourceLimits {
|
|
container: String,
|
|
memory_max: String,
|
|
cpu_quota: String,
|
|
},
|
|
|
|
/// Remove `/run/systemd/system/container@<container>.service.d/` if present.
|
|
/// Called by `lifecycle::destroy` to clean up the resource-limits drop-in.
|
|
RemoveServiceDropin { container: String },
|
|
|
|
// --- System ---
|
|
/// Run `systemctl daemon-reload`.
|
|
DaemonReload,
|
|
|
|
/// Reload nginx inside the `hive-gateway` container via
|
|
/// `systemd-run --machine=hive-gateway nginx -s reload`.
|
|
ReloadGatewayNginx,
|
|
|
|
// --- Socket dir ownership ---
|
|
/// Set ownership of `/run/hive-agent/<agent_name>/` to `uid:gid`.
|
|
/// Called by `lifecycle::set_nspawn_flags` after `create_dir_all`.
|
|
ChownSocketDir {
|
|
agent_name: String,
|
|
uid: u32,
|
|
gid: u32,
|
|
},
|
|
|
|
/// Set mode of `/run/hive-agent/<agent_name>/`.
|
|
/// Fallback when uid lookup returns `None` on first spawn.
|
|
ChmodSocketDir { agent_name: String, mode: u32 },
|
|
}
|
|
|
|
/// Response from the privileged helper.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PrivResponse {
|
|
pub ok: bool,
|
|
#[serde(default)]
|
|
pub stdout: String,
|
|
#[serde(default)]
|
|
pub stderr: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<String>,
|
|
}
|