From e0461e1af6ac106e4096c872faa44648790a24a0 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 14 Jul 2026 19:52:12 +0200 Subject: [PATCH] refactor(#2431): extract hive-priv-sock crate from hive-sh4re MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the priv-socket wire types (PrivRequest/PrivResponse/PrivEvent and friends) out of hive-sh4re into their own hive-priv-sock crate, mirroring the existing hive-host-sock split. hive-priv — the root-privileged helper — now depends on just this narrow protocol crate instead of the much larger daemon-shared crate, shrinking its dependency surface and making the privsep boundary easier to audit. No server/client implementation lives here, only the wire contract; hive-c0re still depends on hive-sh4re directly for everything else. --- Cargo.lock | 10 +++++++++- Cargo.toml | 2 ++ hive-c0re/Cargo.toml | 1 + hive-c0re/src/dashboard/infra_containers.rs | 2 +- hive-c0re/src/dashboard/journal.rs | 4 ++-- hive-c0re/src/dashboard/state_snapshot.rs | 5 ++--- hive-c0re/src/lifecycle/host_config.rs | 4 ++-- hive-c0re/src/lifecycle/mod.rs | 8 ++++---- hive-c0re/src/paths.rs | 4 ++-- hive-c0re/src/priv_client.rs | 2 +- hive-c0re/src/server.rs | 2 +- hive-c0re/src/socket_server/lifecycle_handlers.rs | 4 ++-- hive-c0re/src/socket_server/mod.rs | 4 ++-- hive-priv-sock/Cargo.toml | 10 ++++++++++ .../src/priv_proto.rs => hive-priv-sock/src/lib.rs | 11 +++++++++-- hive-priv/Cargo.toml | 2 +- hive-priv/src/main.rs | 2 +- hive-sh4re/src/lib.rs | 1 - 18 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 hive-priv-sock/Cargo.toml rename hive-sh4re/src/priv_proto.rs => hive-priv-sock/src/lib.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index ed1bf70e..c530ea47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1520,6 +1520,7 @@ dependencies = [ "clap_complete", "forgejo-api", "hive-host-sock", + "hive-priv-sock", "hive-sh4re", "hmac", "indicatif", @@ -1609,7 +1610,7 @@ name = "hive-priv" version = "0.1.0" dependencies = [ "anyhow", - "hive-sh4re", + "hive-priv-sock", "libc", "serde_json", "tokio", @@ -1617,6 +1618,13 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "hive-priv-sock" +version = "0.1.0" +dependencies = [ + "serde", +] + [[package]] name = "hive-sh4re" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ba8c79c2..f4e3e7c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "hive-matrix-mcp", "hive-metric", "hive-priv", + "hive-priv-sock", "hive-sh4re", ] @@ -41,6 +42,7 @@ indicatif = "0.17" hive-sh4re = { path = "hive-sh4re" } hive-claude = { path = "hive-claude" } hive-host-sock = { path = "hive-host-sock" } +hive-priv-sock = { path = "hive-priv-sock" } thiserror = "2" tower-http = { version = "0.6", features = ["fs"] } rmcp = { version = "1.7", default-features = false, features = [ diff --git a/hive-c0re/Cargo.toml b/hive-c0re/Cargo.toml index d5e203c5..91b2c76f 100644 --- a/hive-c0re/Cargo.toml +++ b/hive-c0re/Cargo.toml @@ -20,6 +20,7 @@ clap-markdown = "0.1" indicatif.workspace = true hive-sh4re.workspace = true hive-host-sock.workspace = true +hive-priv-sock.workspace = true libc.workspace = true listenfd = "1" petgraph.workspace = true diff --git a/hive-c0re/src/dashboard/infra_containers.rs b/hive-c0re/src/dashboard/infra_containers.rs index db67326b..80c60e57 100644 --- a/hive-c0re/src/dashboard/infra_containers.rs +++ b/hive-c0re/src/dashboard/infra_containers.rs @@ -10,7 +10,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use hive_sh4re::priv_proto::{InfraAction, InfraContainer}; +use hive_priv_sock::{InfraAction, InfraContainer}; use super::{AppState, error_response}; diff --git a/hive-c0re/src/dashboard/journal.rs b/hive-c0re/src/dashboard/journal.rs index 708ddbbd..9398e490 100644 --- a/hive-c0re/src/dashboard/journal.rs +++ b/hive-c0re/src/dashboard/journal.rs @@ -74,10 +74,10 @@ pub(super) async fn get_journal( }; match crate::priv_client::read_container_journal( &prefixed, - hive_sh4re::priv_proto::JournalQuery { + hive_priv_sock::JournalQuery { lines, boot: true, - output: hive_sh4re::priv_proto::JournalOutput::ShortIso, + output: hive_priv_sock::JournalOutput::ShortIso, unit, ..Default::default() }, diff --git a/hive-c0re/src/dashboard/state_snapshot.rs b/hive-c0re/src/dashboard/state_snapshot.rs index d4f2593d..18470db2 100644 --- a/hive-c0re/src/dashboard/state_snapshot.rs +++ b/hive-c0re/src/dashboard/state_snapshot.rs @@ -145,9 +145,8 @@ struct InfraContainerView { /// Extracted out of [`api_state`] to keep it under clippy's /// `too_many_lines` limit. async fn infra_container_views() -> Vec { - let mut infra_containers = - Vec::with_capacity(hive_sh4re::priv_proto::InfraContainer::ALL.len()); - for container in hive_sh4re::priv_proto::InfraContainer::ALL { + let mut infra_containers = Vec::with_capacity(hive_priv_sock::InfraContainer::ALL.len()); + for container in hive_priv_sock::InfraContainer::ALL { infra_containers.push(InfraContainerView { name: container.unit_name(), running: crate::lifecycle::infra_is_running(container).await, diff --git a/hive-c0re/src/lifecycle/host_config.rs b/hive-c0re/src/lifecycle/host_config.rs index 74f8ad0f..1b1a93ec 100644 --- a/hive-c0re/src/lifecycle/host_config.rs +++ b/hive-c0re/src/lifecycle/host_config.rs @@ -5,7 +5,7 @@ use std::path::Path; use anyhow::{Context, Result}; -use hive_sh4re::priv_proto::{BindMount, CredentialMount}; +use hive_priv_sock::{BindMount, CredentialMount}; use crate::coordinator::{AgentPaths, HiveEnv}; @@ -313,7 +313,7 @@ async fn set_nspawn_flags( %agent_name, %gateway_ip, %bridge, "network isolation: PRIVATE_NETWORK=1 (DHCP)" ); - Some(hive_sh4re::priv_proto::NetworkIsolation { bridge, gateway_ip }) + Some(hive_priv_sock::NetworkIsolation { bridge, gateway_ip }) } else { None } diff --git a/hive-c0re/src/lifecycle/mod.rs b/hive-c0re/src/lifecycle/mod.rs index 55f0acab..8c81f0bf 100644 --- a/hive-c0re/src/lifecycle/mod.rs +++ b/hive-c0re/src/lifecycle/mod.rs @@ -549,7 +549,7 @@ pub async fn is_running(name: &str) -> bool { /// `container@.service` directly rather than going through /// [`container_name`]. Used by the dashboard C0R3 page's 1NFR4 sub-tab to /// show each infra container's live status dot. -pub async fn infra_is_running(container: hive_sh4re::priv_proto::InfraContainer) -> bool { +pub async fn infra_is_running(container: hive_priv_sock::InfraContainer) -> bool { let unit = format!("container@{}.service", container.unit_name()); Command::new("systemctl") .args(["is-active", "--quiet", &unit]) @@ -853,8 +853,8 @@ fn make_log_callback( logs: Option>, log_id: Option, cmdline: String, -) -> impl FnMut(hive_sh4re::priv_proto::PrivStream, &str) { - use hive_sh4re::priv_proto::PrivStream; +) -> impl FnMut(hive_priv_sock::PrivStream, &str) { + use hive_priv_sock::PrivStream; move |stream, line| match stream { PrivStream::Stdout => { tracing::info!(target: "nixos-container", cmdline = %cmdline, "{line}"); @@ -981,7 +981,7 @@ async fn container_journal_tail(container: &str) -> String { // is delegated to hive-priv (hive-c0re itself runs unprivileged). let res = crate::priv_client::read_container_journal( container, - hive_sh4re::priv_proto::JournalQuery { + hive_priv_sock::JournalQuery { lines: 40, ..Default::default() }, diff --git a/hive-c0re/src/paths.rs b/hive-c0re/src/paths.rs index 63be260a..045d4761 100644 --- a/hive-c0re/src/paths.rs +++ b/hive-c0re/src/paths.rs @@ -31,7 +31,7 @@ pub const STATE_ROOT: &str = "/var/lib/hyperhive"; /// `/run/hyperhive` — hive-c0re's runtime root (host admin socket, the /// per-agent runtime dirs). Regenerated each boot; not persistent state. // nix: `RuntimeDirectory=hyperhive` on the hive-c0re service (hive-c0re.nix) — must match. -// sh4re: `hive_sh4re::priv_proto::AGENT_RUNTIME_ROOT` is `RUNTIME_ROOT + "/agents"` and must +// priv-sock: `hive_priv_sock::AGENT_RUNTIME_ROOT` is `RUNTIME_ROOT + "/agents"` and must // stay in sync; the privsep boundary prevents importing across the crate. pub const RUNTIME_ROOT: &str = "/run/hyperhive"; @@ -163,7 +163,7 @@ pub fn agent_sockets_file() -> PathBuf { /// dashboard state-file allow-list uses it for `strip_prefix` / /// `starts_with` checks), so it stays a const; [`agents_root`] wraps it. // nix: agent container bind-mount source (harness modules / agent.nix template) — must match. -// sh4re: `hive_sh4re::priv_proto::AGENT_STATE_ROOT` is the same value and must stay in sync; +// priv-sock: `hive_priv_sock::AGENT_STATE_ROOT` is the same value and must stay in sync; // the privsep boundary prevents importing across the crate. pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents"; diff --git a/hive-c0re/src/priv_client.rs b/hive-c0re/src/priv_client.rs index 5cd5f29d..3d24bdbc 100644 --- a/hive-c0re/src/priv_client.rs +++ b/hive-c0re/src/priv_client.rs @@ -7,7 +7,7 @@ //! a persistent connection. use anyhow::{Context as _, Result, bail}; -use hive_sh4re::priv_proto::{ +use hive_priv_sock::{ BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, }; diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 7a9c3a9e..008359da 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::{Context, Result}; use hive_host_sock::{HostRequest, HostResponse, LifecycleScope}; -use hive_sh4re::priv_proto::{InfraAction, InfraContainer}; +use hive_priv_sock::{InfraAction, InfraContainer}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{UnixListener, UnixStream}; diff --git a/hive-c0re/src/socket_server/lifecycle_handlers.rs b/hive-c0re/src/socket_server/lifecycle_handlers.rs index f6640c72..86f24ca3 100644 --- a/hive-c0re/src/socket_server/lifecycle_handlers.rs +++ b/hive-c0re/src/socket_server/lifecycle_handlers.rs @@ -44,7 +44,7 @@ pub(super) async fn handle_restart( // same restart tool. The `InfraContainer` enum parse both recognises // these (never agent children, so disjoint from the child path below) // and yields the typed value the restart path needs. - if let Ok(container) = name.parse::() { + if let Ok(container) = name.parse::() { return handle_restart_infra(coord, agent, container).await; } if let Some(err) = require_descendant(agent, name, "restart") { @@ -68,7 +68,7 @@ pub(super) async fn handle_restart( async fn handle_restart_infra( coord: &Arc, agent: &str, - container: hive_sh4re::priv_proto::InfraContainer, + container: hive_priv_sock::InfraContainer, ) -> AgentResponse { let name = container.unit_name(); // Record the attempt in the operator-visible privileged-action audit diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index c71ae5e0..1f00baa7 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -875,7 +875,7 @@ pub async fn dispatch_host_journal(agent: &str, args: HostJournalArgs<'_>) -> Ag tracing::info!(%agent, machine = %c, %n, "get_host_journal (container)"); return match crate::priv_client::read_container_journal( c, - hive_sh4re::priv_proto::JournalQuery { + hive_priv_sock::JournalQuery { lines: n, unit: unit.clone(), priority: priority.as_ref().map(|p| p.as_str().to_owned()), @@ -1059,7 +1059,7 @@ async fn handle_get_logs(agent: &str, lines: Option) -> AgentResponse { tracing::info!(%agent, %machine, %n, "manager: get_logs"); match crate::priv_client::read_container_journal( &machine, - hive_sh4re::priv_proto::JournalQuery { + hive_priv_sock::JournalQuery { lines: n, ..Default::default() }, diff --git a/hive-priv-sock/Cargo.toml b/hive-priv-sock/Cargo.toml new file mode 100644 index 00000000..e2902d84 --- /dev/null +++ b/hive-priv-sock/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hive-priv-sock" +edition.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +serde.workspace = true diff --git a/hive-sh4re/src/priv_proto.rs b/hive-priv-sock/src/lib.rs similarity index 98% rename from hive-sh4re/src/priv_proto.rs rename to hive-priv-sock/src/lib.rs index 21de9467..4bd16e5d 100644 --- a/hive-sh4re/src/priv_proto.rs +++ b/hive-priv-sock/src/lib.rs @@ -1,7 +1,14 @@ -//! Wire types for the `hive-priv` privileged-helper socket. +//! Wire types for the `hive-priv` privileged-helper socket +//! (`/run/hive/priv.sock`). //! //! Both `hive-priv` (server) and `hive-c0re` (client via `priv_client`) -//! import these so the shapes stay in sync. +//! import these so the shapes stay in sync. Split out of `hive-sh4re` so +//! `hive-priv` — the privileged root helper — can depend on just this +//! protocol crate instead of the much larger daemon-shared crate: fewer +//! dependencies in the root-privileged binary's supply chain, and a +//! narrower interface makes the boundary this crate encodes easier to +//! audit. No server/client implementation lives here, only the wire +//! contract (mirrors `hive-host-sock`'s split for the host admin socket). use serde::{Deserialize, Serialize}; diff --git a/hive-priv/Cargo.toml b/hive-priv/Cargo.toml index 39a369fc..5f427dc8 100644 --- a/hive-priv/Cargo.toml +++ b/hive-priv/Cargo.toml @@ -8,7 +8,7 @@ workspace = true [dependencies] anyhow.workspace = true -hive-sh4re.workspace = true +hive-priv-sock.workspace = true libc.workspace = true serde_json.workspace = true tokio.workspace = true diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index 3b117275..3ea8f0d8 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -20,7 +20,7 @@ use std::path::{Path, PathBuf}; use anyhow::{Context as _, Result, bail}; -use hive_sh4re::priv_proto::{ +use hive_priv_sock::{ AGENT_PREFIX, AGENT_RUNTIME_ROOT, AGENT_STATE_ROOT, BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, META_DIR, MIGRATE_STAGING_ROOT, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine, SIBLING_CONTAINERS, diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 71312e65..8963dc61 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; pub mod assets; pub mod jobs; pub mod paths; -pub mod priv_proto; pub mod wire_time; /// Server-side hard cap on `Recv.max` (see `AgentRequest::Recv`). Bounds