refactor(#2285): repoint all hive-c0re host-path consumers to paths.rs

This commit is contained in:
damocles 2026-07-10 19:52:38 +02:00 committed by mara
commit 187c364feb
16 changed files with 95 additions and 125 deletions

View file

@ -421,10 +421,11 @@ enum MatrixCmd {
},
}
/// Default htpasswd file path — the host-side location of the gateway's
/// credential store, pre-created by a tmpfiles rule when
/// `services.hyperhive.gateway.auth.enable = true`.
const DEFAULT_HTPASSWD_FILE: &str = "/var/lib/hyperhive/gateway/gateway.htpasswd";
// Default htpasswd file path — the host-side location of the gateway's
// credential store, pre-created by a tmpfiles rule when
// `services.hyperhive.gateway.auth.enable = true`. Literal lives in
// `hive_c0re::paths`.
use hive_c0re::paths::GATEWAY_HTPASSWD as DEFAULT_HTPASSWD_FILE;
#[derive(Subcommand)]
enum GatewayCmd {
@ -530,10 +531,10 @@ enum QuotaCmd {
},
}
/// Default host admin socket path. Must match `hive-c0re`'s default in
/// `main.rs` (`/run/hyperhive/host.sock`) — the daemon binds there and
/// `hivectl agents` connects to it.
const DEFAULT_HOST_SOCKET: &str = "/run/hyperhive/host.sock";
// Default host admin socket path. Shared with `hive-c0re`'s `main.rs`
// default via `hive_c0re::paths::HOST_SOCKET` — the daemon binds there
// and `hivectl agents` connects to it.
use hive_c0re::paths::HOST_SOCKET as DEFAULT_HOST_SOCKET;
#[derive(Subcommand)]
enum AgentsCmd {
@ -689,10 +690,12 @@ async fn main() -> Result<()> {
/// core (headless / SSH hosts where no browser opener exists); the open
/// is convenience on top, so a missing/failed `xdg-open` is not an error.
async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> {
let urls = query_hive_urls(socket).await.context(
"could not reach the hive-c0re daemon for URLs — is hive-c0re running? \
(the socket is at /run/hyperhive/host.sock)",
)?;
let urls = query_hive_urls(socket).await.with_context(|| {
format!(
"could not reach the hive-c0re daemon for URLs — is hive-c0re running? \
(the socket is at {DEFAULT_HOST_SOCKET})"
)
})?;
let (url, hint) = match target {
OpenTarget::Home => (
urls.home,
@ -1067,7 +1070,10 @@ fn agent_exists(name: &str) -> Result<bool> {
/// container.
fn choom(name: &str, resume_session: Option<&str>) -> Result<()> {
if !agent_exists(name)? {
bail!("no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)");
bail!(
"no such agent: '{name}' (no state dir under {}/)",
hive_c0re::paths::AGENTS_ROOT
);
}
let container = hive_c0re::lifecycle::container_name(name);
// Enter as the agent's unix user (== agent name) so claude reads the

View file

@ -244,7 +244,7 @@ pub fn hive_swarm_names() -> (Option<String>, Option<String>) {
/// render the `deployed:<sha12>` chip per container row.
fn read_meta_locked_revs() -> HashMap<String, String> {
let mut out = HashMap::new();
let Ok(raw) = std::fs::read_to_string("/var/lib/hyperhive/meta/flake.lock") else {
let Ok(raw) = std::fs::read_to_string(crate::paths::meta_flake_lock()) else {
return out;
};
let Ok(json) = serde_json::from_str::<serde_json::Value>(&raw) else {

View file

@ -28,16 +28,6 @@ const DASHBOARD_CHANNEL: usize = 256;
/// `Coordinator::set_last_stopped_running`.
const LAST_STOPPED_RUNNING_KEY: &str = "last_stopped_running";
const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents";
/// Manager-editable per-agent config repos. Bind-mounted RW into the manager
/// container as `/agents/<name>/`. Hive-c0re only writes to these on first
/// spawn (initial commit); after that it's manager-only.
const AGENT_STATE_ROOT: &str = "/var/lib/hyperhive/agents";
/// Hive-c0re-only authoritative per-agent config repos. Containers build from
/// these. Manager has no filesystem access; the only way to update is via
/// `request_apply_commit` + user approval.
const APPLIED_STATE_ROOT: &str = "/var/lib/hyperhive/applied";
pub struct Coordinator {
pub broker: Arc<Broker>,
pub approvals: Arc<Approvals>,
@ -1443,7 +1433,7 @@ impl Coordinator {
}
pub fn agent_dir(name: &str) -> PathBuf {
PathBuf::from(format!("{AGENT_RUNTIME_ROOT}/{name}"))
crate::paths::agent_runtime_dir(name)
}
pub fn socket_path(name: &str) -> PathBuf {
@ -1454,7 +1444,7 @@ impl Coordinator {
///
/// Per-agent state root (parent of `config/`, future `prompts/`, etc.).
pub fn agent_state_root(name: &str) -> PathBuf {
PathBuf::from(format!("{AGENT_STATE_ROOT}/{name}"))
crate::paths::agent_state_dir(name)
}
/// Manager-editable proposed config repo. Bind-mounted into the manager
@ -1490,7 +1480,7 @@ impl Coordinator {
/// Authoritative applied config repo. Hive-c0re-only.
pub fn agent_applied_dir(name: &str) -> PathBuf {
PathBuf::from(format!("{APPLIED_STATE_ROOT}/{name}"))
crate::paths::applied_dir(name)
}
/// Enumerate names that have a persistent state dir under
@ -1500,7 +1490,7 @@ impl Coordinator {
/// subtracting `lifecycle::list()`.
#[must_use]
pub fn kept_state_names() -> Vec<String> {
let Ok(rd) = std::fs::read_dir(AGENT_STATE_ROOT) else {
let Ok(rd) = std::fs::read_dir(crate::paths::agents_root()) else {
return Vec::new();
};
let mut out: Vec<String> = rd

View file

@ -50,7 +50,7 @@ pub struct MetaInputView {
/// tree — every input shown once, at its shallowest path.
pub(super) fn read_meta_inputs() -> Vec<MetaInputView> {
let mut out = Vec::new();
let Ok(raw) = std::fs::read_to_string("/var/lib/hyperhive/meta/flake.lock") else {
let Ok(raw) = std::fs::read_to_string(crate::paths::meta_flake_lock()) else {
return out;
};
let Ok(json) = serde_json::from_str::<serde_json::Value>(&raw) else {

View file

@ -13,6 +13,7 @@ use axum::response::{IntoResponse, Response};
use serde::Deserialize;
use super::error_response;
use crate::paths::{AGENTS_ROOT, SHARED_ROOT};
#[derive(Deserialize)]
pub(super) struct StateFileQuery {
@ -27,8 +28,6 @@ fn resolve_state_path(
raw: &str,
) -> std::result::Result<(std::path::PathBuf, std::fs::Metadata), String> {
use std::os::unix::fs::PermissionsExt as _;
const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
const SHARED_ROOT: &str = "/var/lib/hyperhive/shared";
let raw = raw.trim();
let (mapped, root): (std::path::PathBuf, &str) =
if let Some(rest) = raw.strip_prefix("/agents/") {
@ -136,12 +135,9 @@ fn reject_symlinks_below(
/// broker-message ingest so the dashboard event already carries the
/// verified set; security rules stay in sync with the read endpoint.
pub fn scan_validated_paths(body: &str) -> Vec<String> {
const PREFIXES: [&str; 4] = [
"/agents/",
"/shared/",
"/var/lib/hyperhive/agents/",
"/var/lib/hyperhive/shared/",
];
let agents_slash = format!("{AGENTS_ROOT}/");
let shared_slash = format!("{SHARED_ROOT}/");
let prefixes: [&str; 4] = ["/agents/", "/shared/", &agents_slash, &shared_slash];
let mut out = Vec::<String>::new();
for raw in body.split(|c: char| c.is_whitespace()) {
// Trim trailing natural-language punctuation that wouldn't
@ -151,7 +147,7 @@ pub fn scan_validated_paths(body: &str) -> Vec<String> {
if token.is_empty() {
continue;
}
if !PREFIXES.iter().any(|p| token.starts_with(p)) {
if !prefixes.iter().any(|p| token.starts_with(p)) {
continue;
}
// Cheap dedupe — typical message has 0-3 refs.

View file

@ -31,8 +31,6 @@ static LAST_FAILED_RELOAD: AtomicU64 = AtomicU64::new(0);
/// Minimum gap between retry attempts after a reload failure (30 s).
const RELOAD_RETRY_SECS: u64 = 30;
const HOST_CONF_PATH: &str = "/var/lib/hyperhive/gateway/agents.conf";
/// Host-side path where c0re writes the generated nginx include file.
/// The gateway container bind-mounts `/var/lib/hyperhive/gateway/`
/// (not the whole parent dir) at `/run/hive-state/` so nginx inside
@ -41,7 +39,7 @@ const HOST_CONF_PATH: &str = "/var/lib/hyperhive/gateway/agents.conf";
/// forge tokens or other credentials) to the gateway container.
#[must_use]
pub fn host_conf_path() -> PathBuf {
PathBuf::from(HOST_CONF_PATH)
crate::paths::gateway_agents_conf()
}
/// Nginx proxy headers present in every per-agent location block.

View file

@ -61,49 +61,22 @@ pub const CONTAINER_MANAGER_AGENTS_MOUNT: &str = "/agents";
/// inside the container.
pub const CONTAINER_MANAGER_APPLIED_MOUNT: &str = "/applied";
/// The on-host root that gets bind-mounted to `/agents` inside the manager.
/// Hard-coded to match `AGENT_STATE_ROOT` in coordinator.rs (kept duplicated
/// here so lifecycle stays usable as a leaf module).
pub(super) const HOST_AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
/// On-host applied repo root, mirrored RO into the manager. Matches
/// `APPLIED_STATE_ROOT` in coordinator.rs.
const HOST_APPLIED_ROOT: &str = "/var/lib/hyperhive/applied";
/// On-host meta repo root, mirrored RO into the manager. Matches
/// `meta::meta_dir()` but duplicated here so lifecycle stays a leaf.
const HOST_META_ROOT: &str = "/var/lib/hyperhive/meta";
/// Shared directory accessible to all agents. All agents bind-mount this RW.
const HOST_SHARED_ROOT: &str = "/var/lib/hyperhive/shared";
/// Append bind flags for `child`'s state, harness, and config dirs into
/// `binds`, all read-write. The RW on `state` is deliberate (recovery),
/// not an oversight; see docs/persistence.md ("Parent access to child
/// state") for the rationale. Creates missing host-side directories so
/// nspawn doesn't refuse to start; missing dirs are non-fatal.
fn bind_child_agent_dirs(child: &str, binds: &mut Vec<BindMount>) {
let state_dir = format!("{HOST_AGENTS_ROOT}/{child}/state");
let harness_dir = format!("{HOST_AGENTS_ROOT}/{child}/harness");
let config_dir = format!("{HOST_AGENTS_ROOT}/{child}/config");
for dir in [&state_dir, &harness_dir, &config_dir] {
let _ = std::fs::create_dir_all(dir);
let child_root = crate::paths::agent_state_dir(child);
for sub in ["state", "harness", "config"] {
let host = child_root.join(sub);
let _ = std::fs::create_dir_all(&host);
binds.push(BindMount {
host_path: host.to_string_lossy().into_owned(),
container_path: format!("/agents/{child}/{sub}"),
read_only: false,
});
}
binds.push(BindMount {
host_path: state_dir,
container_path: format!("/agents/{child}/state"),
read_only: false,
});
binds.push(BindMount {
host_path: harness_dir,
container_path: format!("/agents/{child}/harness"),
read_only: false,
});
binds.push(BindMount {
host_path: config_dir,
container_path: format!("/agents/{child}/config"),
read_only: false,
});
}
/// Hive-wide secrets forwarded into every agent container via nspawn
@ -154,8 +127,9 @@ async fn set_nspawn_flags(
notes_dir: &Path,
) -> Result<()> {
// Ensure /shared directory exists before binding. systemd-nspawn requires the bind source to exist.
std::fs::create_dir_all(HOST_SHARED_ROOT)
.with_context(|| format!("create {HOST_SHARED_ROOT}"))?;
let shared_root = crate::paths::shared_root();
std::fs::create_dir_all(&shared_root)
.with_context(|| format!("create {}", shared_root.display()))?;
// Make /shared writable by every agent. Containers share host uids (no
// PrivateUsers), but each agent is a distinct unix user, so a root-owned
// 0755 dir leaves them unable to write — the documented "read/write for
@ -169,8 +143,8 @@ async fn set_nspawn_flags(
{
use std::os::unix::fs::PermissionsExt as _;
let perms = std::fs::Permissions::from_mode(0o1777);
std::fs::set_permissions(HOST_SHARED_ROOT, perms)
.with_context(|| format!("chmod 1777 {HOST_SHARED_ROOT}"))?;
std::fs::set_permissions(&shared_root, perms)
.with_context(|| format!("chmod 1777 {}", shared_root.display()))?;
}
// Ensure /knowledge dir exists. It may be empty until forge seeds it;
// nspawn refuses to start if the bind source is missing entirely.
@ -204,7 +178,7 @@ async fn set_nspawn_flags(
read_only: false,
},
BindMount {
host_path: HOST_SHARED_ROOT.to_owned(),
host_path: shared_root.to_string_lossy().into_owned(),
container_path: CONTAINER_SHARED_MOUNT.to_owned(),
read_only: false,
},
@ -234,10 +208,11 @@ async fn set_nspawn_flags(
read_only: false,
});
}
let own_config = format!("{HOST_AGENTS_ROOT}/{agent_name}/config");
std::fs::create_dir_all(&own_config).with_context(|| format!("create {own_config}"))?;
let own_config = crate::paths::agent_state_dir(agent_name).join("config");
std::fs::create_dir_all(&own_config)
.with_context(|| format!("create {}", own_config.display()))?;
binds.push(BindMount {
host_path: own_config,
host_path: own_config.to_string_lossy().into_owned(),
container_path: format!("/agents/{agent_name}/config"),
read_only: true,
});
@ -270,15 +245,16 @@ async fn set_nspawn_flags(
// startup migration, but make sure the directory is there
// before the role holder comes up in case set_nspawn_flags
// fires first (e.g. cold start with no agents).
std::fs::create_dir_all(HOST_META_ROOT)
.with_context(|| format!("create {HOST_META_ROOT}"))?;
let meta_root = crate::paths::meta_root();
std::fs::create_dir_all(&meta_root)
.with_context(|| format!("create {}", meta_root.display()))?;
binds.push(BindMount {
host_path: HOST_APPLIED_ROOT.to_owned(),
host_path: crate::paths::applied_root().to_string_lossy().into_owned(),
container_path: CONTAINER_MANAGER_APPLIED_MOUNT.to_owned(),
read_only: true,
});
binds.push(BindMount {
host_path: HOST_META_ROOT.to_owned(),
host_path: meta_root.to_string_lossy().into_owned(),
container_path: crate::meta::CONTAINER_MANAGER_META_MOUNT.to_owned(),
read_only: true,
});

View file

@ -864,7 +864,7 @@ pub async fn sync_tmpfiles() {
/// # Errors
/// Returns an error if `create_dir_all` fails.
pub fn ensure_agent_runtime_dir(name: &str) -> Result<()> {
let dir = std::path::PathBuf::from(format!("/run/hyperhive/agents/{name}"));
let dir = crate::paths::agent_runtime_dir(name);
std::fs::create_dir_all(&dir)
.with_context(|| format!("create agent runtime dir {}", dir.display()))
}

View file

@ -9,7 +9,6 @@ use anyhow::{Context, Result, bail};
use super::git::{
git, git_command, git_commit, git_read_tree_reset, git_rev_parse, git_root_commit, git_tag,
};
use super::host_config::HOST_AGENTS_ROOT;
/// Initialize the manager-editable proposed repo. Seeds two tracked
/// files: `agent.nix` (the module the manager edits) and `flake.nix`
@ -217,7 +216,7 @@ pub fn ensure_state_dir(notes_dir: &Path) -> Result<()> {
/// brand-new agent on a btrfs host gets a real subvolume. Subvolume creation
/// is privileged, so it's delegated to hive-priv.
pub async fn ensure_agent_state_subvolume(name: &str) -> Result<()> {
let root = Path::new(HOST_AGENTS_ROOT).join(name);
let root = crate::paths::agent_state_dir(name);
if root.exists() {
return Ok(());
}

View file

@ -21,7 +21,7 @@ use hive_c0re::{
#[command(name = "hive-c0re", about = "hyperhive coordinator daemon and CLI")]
struct Cli {
/// Path to the host admin socket.
#[arg(long, global = true, default_value = "/run/hyperhive/host.sock")]
#[arg(long, global = true, default_value = hive_c0re::paths::HOST_SOCKET)]
socket: PathBuf,
#[command(subcommand)]

View file

@ -7,7 +7,7 @@
//! the full UIAA round-trip, token-file shape, and host/container
//! bind-mount layout.
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::{Context, Result};
use reqwest::StatusCode;
@ -22,11 +22,6 @@ const MATRIX_CONTAINER: &str = "hive-matrix";
/// netns so `localhost:<port>` resolves both from the daemon and from
/// inside any sub-agent container.
const MATRIX_HTTP: &str = "http://localhost:8008";
/// Host path of the matrix registration token. Must match
/// `hyperhive.matrix.registrationTokenFile` in `nix/modules/hive-matrix.nix`
/// (same path is bind-mounted read-only into the tuwunel container so
/// the homeserver can read it via `registration_token_file`).
const REGISTER_TOKEN_PATH: &str = "/var/lib/hyperhive/matrix-register-token";
/// Length (bytes) of the random registration token. 32 raw bytes ⇒
/// 64-char hex string; comfortable for a long-lived shared secret.
const REGISTER_TOKEN_BYTES: usize = 32;
@ -148,8 +143,8 @@ fn random_hex(n: usize) -> Result<String> {
/// against the same secret hive-c0re holds.
pub fn ensure_register_token() -> Result<String> {
use std::os::unix::fs::PermissionsExt;
let path = Path::new(REGISTER_TOKEN_PATH);
if let Ok(existing) = std::fs::read_to_string(path) {
let path = crate::paths::matrix_register_token();
if let Ok(existing) = std::fs::read_to_string(&path) {
let trimmed = existing.trim().to_owned();
if !trimmed.is_empty() {
return Ok(trimmed);
@ -159,9 +154,9 @@ pub fn ensure_register_token() -> Result<String> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).ok();
}
std::fs::write(path, format!("{token}\n"))
std::fs::write(&path, format!("{token}\n"))
.with_context(|| format!("write registration token to {}", path.display()))?;
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
tracing::info!(path = %path.display(), "matrix: generated registration token");
Ok(token)
}

View file

@ -13,8 +13,6 @@ use tokio::sync::Mutex;
use crate::coordinator::HiveEnv;
use crate::lifecycle;
const META_ROOT: &str = "/var/lib/hyperhive/meta";
const APPLIED_ROOT: &str = "/var/lib/hyperhive/applied";
const GIT_NAME: &str = "c0re";
const GIT_EMAIL: &str = "c0re@hyperhive.local";
@ -60,7 +58,7 @@ pub struct AgentSpec {
#[must_use]
pub fn meta_dir() -> PathBuf {
PathBuf::from(META_ROOT)
crate::paths::meta_root()
}
/// Idempotently reconcile the meta repo with the current agent set.
@ -840,7 +838,7 @@ fn ca_embed_state(dir: &std::path::Path) -> (Vec<(String, String)>, bool) {
/// Returns an empty vec when the lock is missing or unparsable —
/// safe degradation, the worst case is no dedup for that agent.
fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
let path = std::path::PathBuf::from(format!("{APPLIED_ROOT}/{name}/flake.lock"));
let path = crate::paths::applied_dir(name).join("flake.lock");
let Ok(raw) = std::fs::read_to_string(&path) else {
return Vec::new();
};
@ -931,8 +929,9 @@ where
for spec in agents {
let _ = writeln!(
out,
" agent-{}.url = \"git+file://{APPLIED_ROOT}/{}\";",
spec.name, spec.name,
" agent-{}.url = \"git+file://{}\";",
spec.name,
crate::paths::applied_dir(&spec.name).display(),
);
// For each canonical input the agent declares in its own
// `flake.nix` (detected by reading its applied `flake.lock`),

View file

@ -20,13 +20,13 @@ const KILL_SWITCH: &str = "HIVE_SKIP_META_MIGRATION";
/// Marker for phase 4. Once present, container repoint is skipped on
/// future restarts.
fn repoint_marker() -> PathBuf {
PathBuf::from("/var/lib/hyperhive/.meta-migration-done")
crate::paths::meta_migration_marker()
}
/// Marker for phase 5. Once present, root→h-root container rename is
/// skipped on future restarts.
fn hroot_rename_marker() -> PathBuf {
PathBuf::from("/var/lib/hyperhive/.hroot-rename-done")
crate::paths::hroot_rename_marker()
}
/// Substring that identifies the *current* agent flake boilerplate.
@ -45,7 +45,7 @@ pub async fn run(coord: &Arc<Coordinator>) -> Result<()> {
// can leave `.git/index.lock` behind, which blocks every
// subsequent meta op until somebody `rm`s it manually. We just
// booted so nothing of ours is holding it; safe to clear.
let meta_lock = std::path::PathBuf::from("/var/lib/hyperhive/meta/.git/index.lock");
let meta_lock = crate::paths::meta_git_index_lock();
if meta_lock.exists() {
match std::fs::remove_file(&meta_lock) {
Ok(()) => tracing::warn!("cleared stale meta/.git/index.lock"),

View file

@ -148,11 +148,15 @@ pub fn agent_sockets_file() -> PathBuf {
// ---------------------------------------------------------------------------
/// `agents/` — per-agent persistent state root (one subdir per agent,
/// bind-mounted into each container as `/agents/<name>`).
/// bind-mounted into each container as `/agents/<name>`). A `&str` (the
/// 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-base.nix / agent-base.nix) — must match.
pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
#[must_use]
pub fn agents_root() -> PathBuf {
state_root().join("agents")
PathBuf::from(AGENTS_ROOT)
}
/// `agents/<name>` — one agent's persistent state root.
@ -202,11 +206,15 @@ pub fn meta_git_index_lock() -> PathBuf {
meta_root().join(".git/index.lock")
}
/// `shared/` — the cross-agent `/shared` scratch space.
/// `shared/` — the cross-agent `/shared` scratch space. A `&str` (the
/// dashboard state-file allow-list uses it for prefix checks), so it
/// stays a const; [`shared_root`] wraps it.
// nix: bind-mounted into every agent container as `/shared` (harness-base.nix) — must match.
pub const SHARED_ROOT: &str = "/var/lib/hyperhive/shared";
#[must_use]
pub fn shared_root() -> PathBuf {
state_root().join("shared")
PathBuf::from(SHARED_ROOT)
}
/// `knowledge/` — local checkout of the `internal/knowledge` repo. A
@ -228,6 +236,12 @@ pub fn gateway_agents_conf() -> PathBuf {
gateway_dir().join("agents.conf")
}
/// `gateway/gateway.htpasswd` — nginx basic-auth credential store for the
/// operator dashboard vhost. A `&str` (used as a `hivectl` clap
/// `default_value`), so it stays a const rather than a `PathBuf` fn.
// nix: read by the gateway container's nginx (hive-gateway.nix) — must match.
pub const GATEWAY_HTPASSWD: &str = "/var/lib/hyperhive/gateway/gateway.htpasswd";
/// `forge-core-token` — the hive-c0re forge account API token. A `&str`
/// (used in `Path::new` + user-facing `format!` messages), so it stays a
/// const rather than a `PathBuf` fn.

View file

@ -17,8 +17,9 @@ use anyhow::{Context, Result};
/// gateway container bind-mounts this whole tree (read-only) so it
/// can `proxy_pass` to any agent. Each agent's container bind-mounts
/// only its own `<name>/` subdir — agents can only access their own
/// sockets.
pub const AGENT_SOCKET_DIR: &str = "/run/hive-agent";
/// sockets. The literal lives in [`crate::paths`]; re-exported here
/// under the name this module's consumers have always used.
pub use crate::paths::AGENT_SOCKET_DIR;
/// Socket filename inside each per-agent subdir. Fixed so the path
/// derives entirely from `(AGENT_SOCKET_DIR, name)` — no second

View file

@ -29,7 +29,7 @@ use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME};
/// keep it out of the applied repo's git history. Uses a leading dot so a
/// glob over `applied/*` doesn't include it.
pub fn rev_marker_path(name: &str) -> PathBuf {
PathBuf::from(format!("/var/lib/hyperhive/applied/.{name}.hyperhive-rev"))
crate::paths::applied_rev_marker(name)
}
/// Resolve the current rev of `hyperhive_flake`. For a path on disk we
@ -58,13 +58,9 @@ pub fn current_flake_rev(hyperhive_flake: &str) -> Option<String> {
/// nix-build disk saturation that's long enough that concurrent sweeps
/// starved the runtime and stalled the per-agent sockets.
pub async fn agent_config_pending(name: &str, deployed_sha: Option<&str>) -> bool {
let applied = crate::paths::applied_dir(name);
let applied_head = tokio::process::Command::new("git")
.args([
"-C",
&format!("/var/lib/hyperhive/applied/{name}"),
"rev-parse",
"HEAD",
])
.args(["-C", &applied.to_string_lossy(), "rev-parse", "HEAD"])
.output()
.await
.ok()