From 187c364febaa36987af694d29b6efd14d6b8837f Mon Sep 17 00:00:00 2001 From: damocles Date: Fri, 10 Jul 2026 19:52:38 +0200 Subject: [PATCH] refactor(#2285): repoint all hive-c0re host-path consumers to paths.rs --- hive-c0re/src/bin/hivectl.rs | 32 +++++++----- hive-c0re/src/container_view.rs | 2 +- hive-c0re/src/coordinator.rs | 18 ++----- hive-c0re/src/dashboard/meta_inputs.rs | 2 +- hive-c0re/src/dashboard/state_files.rs | 14 ++--- hive-c0re/src/gateway_nginx.rs | 4 +- hive-c0re/src/lifecycle/host_config.rs | 72 +++++++++----------------- hive-c0re/src/lifecycle/mod.rs | 2 +- hive-c0re/src/lifecycle/setup.rs | 3 +- hive-c0re/src/main.rs | 2 +- hive-c0re/src/matrix.rs | 15 ++---- hive-c0re/src/meta.rs | 11 ++-- hive-c0re/src/migrate.rs | 6 +-- hive-c0re/src/paths.rs | 22 ++++++-- hive-c0re/src/workers/agent_sockets.rs | 5 +- hive-c0re/src/workers/auto_update.rs | 10 ++-- 16 files changed, 95 insertions(+), 125 deletions(-) diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index bde9f405..b393d92e 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -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 { /// 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 diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs index 9cdda653..28b10f19 100644 --- a/hive-c0re/src/container_view.rs +++ b/hive-c0re/src/container_view.rs @@ -244,7 +244,7 @@ pub fn hive_swarm_names() -> (Option, Option) { /// render the `deployed:` chip per container row. fn read_meta_locked_revs() -> HashMap { 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::(&raw) else { diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 6312c71e..931c725d 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -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//`. 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, pub approvals: Arc, @@ -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 { - 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 = rd diff --git a/hive-c0re/src/dashboard/meta_inputs.rs b/hive-c0re/src/dashboard/meta_inputs.rs index b02fc82a..aa8e0fae 100644 --- a/hive-c0re/src/dashboard/meta_inputs.rs +++ b/hive-c0re/src/dashboard/meta_inputs.rs @@ -50,7 +50,7 @@ pub struct MetaInputView { /// tree — every input shown once, at its shallowest path. pub(super) fn read_meta_inputs() -> Vec { 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::(&raw) else { diff --git a/hive-c0re/src/dashboard/state_files.rs b/hive-c0re/src/dashboard/state_files.rs index 002abde7..08afc1c8 100644 --- a/hive-c0re/src/dashboard/state_files.rs +++ b/hive-c0re/src/dashboard/state_files.rs @@ -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 { - 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::::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 { 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. diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 673d70a2..462cb460 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -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. diff --git a/hive-c0re/src/lifecycle/host_config.rs b/hive-c0re/src/lifecycle/host_config.rs index f7ae69b9..73210e53 100644 --- a/hive-c0re/src/lifecycle/host_config.rs +++ b/hive-c0re/src/lifecycle/host_config.rs @@ -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) { - 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, }); diff --git a/hive-c0re/src/lifecycle/mod.rs b/hive-c0re/src/lifecycle/mod.rs index 44f109f1..5472b4b1 100644 --- a/hive-c0re/src/lifecycle/mod.rs +++ b/hive-c0re/src/lifecycle/mod.rs @@ -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())) } diff --git a/hive-c0re/src/lifecycle/setup.rs b/hive-c0re/src/lifecycle/setup.rs index a97898ba..d1f0aaf0 100644 --- a/hive-c0re/src/lifecycle/setup.rs +++ b/hive-c0re/src/lifecycle/setup.rs @@ -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(()); } diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 12cbac7a..8efa4fcc 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -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)] diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index bc611661..cbbc7123 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -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:` 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 { /// against the same secret hive-c0re holds. pub fn ensure_register_token() -> Result { 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 { 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) } diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index ad9034bf..fb8b0425 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -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`), diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index e9b92861..61ea11a2 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -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) -> 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"), diff --git a/hive-c0re/src/paths.rs b/hive-c0re/src/paths.rs index a3b60844..cacbb7d2 100644 --- a/hive-c0re/src/paths.rs +++ b/hive-c0re/src/paths.rs @@ -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/`). +/// bind-mounted into each container as `/agents/`). 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/` — 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. diff --git a/hive-c0re/src/workers/agent_sockets.rs b/hive-c0re/src/workers/agent_sockets.rs index 6e0dbfda..21910aeb 100644 --- a/hive-c0re/src/workers/agent_sockets.rs +++ b/hive-c0re/src/workers/agent_sockets.rs @@ -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 `/` 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 diff --git a/hive-c0re/src/workers/auto_update.rs b/hive-c0re/src/workers/auto_update.rs index 1c7a909c..cc4e1a91 100644 --- a/hive-c0re/src/workers/auto_update.rs +++ b/hive-c0re/src/workers/auto_update.rs @@ -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 { /// 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()