hyperhive/hive-sh4re/src/priv_proto.rs
atlas 4bff450343 feat(gateway): hivectl gateway user management + fix htpasswdFile assertion
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.

Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.

Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
2026-06-01 23:25:28 +02:00

122 lines
4.2 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 container name. Used by `hive-priv` to skip the `h-` prefix
/// and by `hive-c0re` for identity checks.
pub const MANAGER_NAME: &str = "root";
/// 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";
/// 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,
// --- 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>,
}