refactor(#2431): extract hive-priv-sock crate from hive-sh4re
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.
This commit is contained in:
parent
ffde085c35
commit
e0461e1af6
18 changed files with 52 additions and 26 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -1520,6 +1520,7 @@ dependencies = [
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"forgejo-api",
|
"forgejo-api",
|
||||||
"hive-host-sock",
|
"hive-host-sock",
|
||||||
|
"hive-priv-sock",
|
||||||
"hive-sh4re",
|
"hive-sh4re",
|
||||||
"hmac",
|
"hmac",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
|
|
@ -1609,7 +1610,7 @@ name = "hive-priv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hive-sh4re",
|
"hive-priv-sock",
|
||||||
"libc",
|
"libc",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -1617,6 +1618,13 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hive-priv-sock"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hive-sh4re"
|
name = "hive-sh4re"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ members = [
|
||||||
"hive-matrix-mcp",
|
"hive-matrix-mcp",
|
||||||
"hive-metric",
|
"hive-metric",
|
||||||
"hive-priv",
|
"hive-priv",
|
||||||
|
"hive-priv-sock",
|
||||||
"hive-sh4re",
|
"hive-sh4re",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -41,6 +42,7 @@ indicatif = "0.17"
|
||||||
hive-sh4re = { path = "hive-sh4re" }
|
hive-sh4re = { path = "hive-sh4re" }
|
||||||
hive-claude = { path = "hive-claude" }
|
hive-claude = { path = "hive-claude" }
|
||||||
hive-host-sock = { path = "hive-host-sock" }
|
hive-host-sock = { path = "hive-host-sock" }
|
||||||
|
hive-priv-sock = { path = "hive-priv-sock" }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
tower-http = { version = "0.6", features = ["fs"] }
|
tower-http = { version = "0.6", features = ["fs"] }
|
||||||
rmcp = { version = "1.7", default-features = false, features = [
|
rmcp = { version = "1.7", default-features = false, features = [
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ clap-markdown = "0.1"
|
||||||
indicatif.workspace = true
|
indicatif.workspace = true
|
||||||
hive-sh4re.workspace = true
|
hive-sh4re.workspace = true
|
||||||
hive-host-sock.workspace = true
|
hive-host-sock.workspace = true
|
||||||
|
hive-priv-sock.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
listenfd = "1"
|
listenfd = "1"
|
||||||
petgraph.workspace = true
|
petgraph.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use axum::{
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use hive_sh4re::priv_proto::{InfraAction, InfraContainer};
|
use hive_priv_sock::{InfraAction, InfraContainer};
|
||||||
|
|
||||||
use super::{AppState, error_response};
|
use super::{AppState, error_response};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,10 @@ pub(super) async fn get_journal(
|
||||||
};
|
};
|
||||||
match crate::priv_client::read_container_journal(
|
match crate::priv_client::read_container_journal(
|
||||||
&prefixed,
|
&prefixed,
|
||||||
hive_sh4re::priv_proto::JournalQuery {
|
hive_priv_sock::JournalQuery {
|
||||||
lines,
|
lines,
|
||||||
boot: true,
|
boot: true,
|
||||||
output: hive_sh4re::priv_proto::JournalOutput::ShortIso,
|
output: hive_priv_sock::JournalOutput::ShortIso,
|
||||||
unit,
|
unit,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,8 @@ struct InfraContainerView {
|
||||||
/// Extracted out of [`api_state`] to keep it under clippy's
|
/// Extracted out of [`api_state`] to keep it under clippy's
|
||||||
/// `too_many_lines` limit.
|
/// `too_many_lines` limit.
|
||||||
async fn infra_container_views() -> Vec<InfraContainerView> {
|
async fn infra_container_views() -> Vec<InfraContainerView> {
|
||||||
let mut infra_containers =
|
let mut infra_containers = Vec::with_capacity(hive_priv_sock::InfraContainer::ALL.len());
|
||||||
Vec::with_capacity(hive_sh4re::priv_proto::InfraContainer::ALL.len());
|
for container in hive_priv_sock::InfraContainer::ALL {
|
||||||
for container in hive_sh4re::priv_proto::InfraContainer::ALL {
|
|
||||||
infra_containers.push(InfraContainerView {
|
infra_containers.push(InfraContainerView {
|
||||||
name: container.unit_name(),
|
name: container.unit_name(),
|
||||||
running: crate::lifecycle::infra_is_running(container).await,
|
running: crate::lifecycle::infra_is_running(container).await,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use hive_sh4re::priv_proto::{BindMount, CredentialMount};
|
use hive_priv_sock::{BindMount, CredentialMount};
|
||||||
|
|
||||||
use crate::coordinator::{AgentPaths, HiveEnv};
|
use crate::coordinator::{AgentPaths, HiveEnv};
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ async fn set_nspawn_flags(
|
||||||
%agent_name, %gateway_ip, %bridge,
|
%agent_name, %gateway_ip, %bridge,
|
||||||
"network isolation: PRIVATE_NETWORK=1 (DHCP)"
|
"network isolation: PRIVATE_NETWORK=1 (DHCP)"
|
||||||
);
|
);
|
||||||
Some(hive_sh4re::priv_proto::NetworkIsolation { bridge, gateway_ip })
|
Some(hive_priv_sock::NetworkIsolation { bridge, gateway_ip })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ pub async fn is_running(name: &str) -> bool {
|
||||||
/// `container@<unit_name>.service` directly rather than going through
|
/// `container@<unit_name>.service` directly rather than going through
|
||||||
/// [`container_name`]. Used by the dashboard C0R3 page's 1NFR4 sub-tab to
|
/// [`container_name`]. Used by the dashboard C0R3 page's 1NFR4 sub-tab to
|
||||||
/// show each infra container's live status dot.
|
/// 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());
|
let unit = format!("container@{}.service", container.unit_name());
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.args(["is-active", "--quiet", &unit])
|
.args(["is-active", "--quiet", &unit])
|
||||||
|
|
@ -853,8 +853,8 @@ fn make_log_callback(
|
||||||
logs: Option<std::sync::Arc<crate::build_logs::BuildLogs>>,
|
logs: Option<std::sync::Arc<crate::build_logs::BuildLogs>>,
|
||||||
log_id: Option<i64>,
|
log_id: Option<i64>,
|
||||||
cmdline: String,
|
cmdline: String,
|
||||||
) -> impl FnMut(hive_sh4re::priv_proto::PrivStream, &str) {
|
) -> impl FnMut(hive_priv_sock::PrivStream, &str) {
|
||||||
use hive_sh4re::priv_proto::PrivStream;
|
use hive_priv_sock::PrivStream;
|
||||||
move |stream, line| match stream {
|
move |stream, line| match stream {
|
||||||
PrivStream::Stdout => {
|
PrivStream::Stdout => {
|
||||||
tracing::info!(target: "nixos-container", cmdline = %cmdline, "{line}");
|
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).
|
// is delegated to hive-priv (hive-c0re itself runs unprivileged).
|
||||||
let res = crate::priv_client::read_container_journal(
|
let res = crate::priv_client::read_container_journal(
|
||||||
container,
|
container,
|
||||||
hive_sh4re::priv_proto::JournalQuery {
|
hive_priv_sock::JournalQuery {
|
||||||
lines: 40,
|
lines: 40,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub const STATE_ROOT: &str = "/var/lib/hyperhive";
|
||||||
/// `/run/hyperhive` — hive-c0re's runtime root (host admin socket, the
|
/// `/run/hyperhive` — hive-c0re's runtime root (host admin socket, the
|
||||||
/// per-agent runtime dirs). Regenerated each boot; not persistent state.
|
/// per-agent runtime dirs). Regenerated each boot; not persistent state.
|
||||||
// nix: `RuntimeDirectory=hyperhive` on the hive-c0re service (hive-c0re.nix) — must match.
|
// 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.
|
// stay in sync; the privsep boundary prevents importing across the crate.
|
||||||
pub const RUNTIME_ROOT: &str = "/run/hyperhive";
|
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` /
|
/// dashboard state-file allow-list uses it for `strip_prefix` /
|
||||||
/// `starts_with` checks), so it stays a const; [`agents_root`] wraps it.
|
/// `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.
|
// 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.
|
// the privsep boundary prevents importing across the crate.
|
||||||
pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
|
pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//! a persistent connection.
|
//! a persistent connection.
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use hive_sh4re::priv_proto::{
|
use hive_priv_sock::{
|
||||||
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, NetworkIsolation,
|
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, NetworkIsolation,
|
||||||
PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream,
|
PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use hive_host_sock::{HostRequest, HostResponse, LifecycleScope};
|
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::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::net::{UnixListener, UnixStream};
|
use tokio::net::{UnixListener, UnixStream};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ pub(super) async fn handle_restart(
|
||||||
// same restart tool. The `InfraContainer` enum parse both recognises
|
// same restart tool. The `InfraContainer` enum parse both recognises
|
||||||
// these (never agent children, so disjoint from the child path below)
|
// these (never agent children, so disjoint from the child path below)
|
||||||
// and yields the typed value the restart path needs.
|
// and yields the typed value the restart path needs.
|
||||||
if let Ok(container) = name.parse::<hive_sh4re::priv_proto::InfraContainer>() {
|
if let Ok(container) = name.parse::<hive_priv_sock::InfraContainer>() {
|
||||||
return handle_restart_infra(coord, agent, container).await;
|
return handle_restart_infra(coord, agent, container).await;
|
||||||
}
|
}
|
||||||
if let Some(err) = require_descendant(agent, name, "restart") {
|
if let Some(err) = require_descendant(agent, name, "restart") {
|
||||||
|
|
@ -68,7 +68,7 @@ pub(super) async fn handle_restart(
|
||||||
async fn handle_restart_infra(
|
async fn handle_restart_infra(
|
||||||
coord: &Arc<Coordinator>,
|
coord: &Arc<Coordinator>,
|
||||||
agent: &str,
|
agent: &str,
|
||||||
container: hive_sh4re::priv_proto::InfraContainer,
|
container: hive_priv_sock::InfraContainer,
|
||||||
) -> AgentResponse {
|
) -> AgentResponse {
|
||||||
let name = container.unit_name();
|
let name = container.unit_name();
|
||||||
// Record the attempt in the operator-visible privileged-action audit
|
// Record the attempt in the operator-visible privileged-action audit
|
||||||
|
|
|
||||||
|
|
@ -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)");
|
tracing::info!(%agent, machine = %c, %n, "get_host_journal (container)");
|
||||||
return match crate::priv_client::read_container_journal(
|
return match crate::priv_client::read_container_journal(
|
||||||
c,
|
c,
|
||||||
hive_sh4re::priv_proto::JournalQuery {
|
hive_priv_sock::JournalQuery {
|
||||||
lines: n,
|
lines: n,
|
||||||
unit: unit.clone(),
|
unit: unit.clone(),
|
||||||
priority: priority.as_ref().map(|p| p.as_str().to_owned()),
|
priority: priority.as_ref().map(|p| p.as_str().to_owned()),
|
||||||
|
|
@ -1059,7 +1059,7 @@ async fn handle_get_logs(agent: &str, lines: Option<u32>) -> AgentResponse {
|
||||||
tracing::info!(%agent, %machine, %n, "manager: get_logs");
|
tracing::info!(%agent, %machine, %n, "manager: get_logs");
|
||||||
match crate::priv_client::read_container_journal(
|
match crate::priv_client::read_container_journal(
|
||||||
&machine,
|
&machine,
|
||||||
hive_sh4re::priv_proto::JournalQuery {
|
hive_priv_sock::JournalQuery {
|
||||||
lines: n,
|
lines: n,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
10
hive-priv-sock/Cargo.toml
Normal file
10
hive-priv-sock/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "hive-priv-sock"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde.workspace = true
|
||||||
|
|
@ -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`)
|
//! 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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
hive-sh4re.workspace = true
|
hive-priv-sock.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
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,
|
AGENT_PREFIX, AGENT_RUNTIME_ROOT, AGENT_STATE_ROOT, BindMount, CredentialMount, InfraAction,
|
||||||
InfraContainer, JournalQuery, META_DIR, MIGRATE_STAGING_ROOT, NetworkIsolation, PRIV_SOCK,
|
InfraContainer, JournalQuery, META_DIR, MIGRATE_STAGING_ROOT, NetworkIsolation, PRIV_SOCK,
|
||||||
PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine, SIBLING_CONTAINERS,
|
PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine, SIBLING_CONTAINERS,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize};
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod jobs;
|
pub mod jobs;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod priv_proto;
|
|
||||||
pub mod wire_time;
|
pub mod wire_time;
|
||||||
|
|
||||||
/// Server-side hard cap on `Recv.max` (see `AgentRequest::Recv`). Bounds
|
/// Server-side hard cap on `Recv.max` (see `AgentRequest::Recv`). Bounds
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue