diff --git a/hive-c0re/src/agent_config/capabilities.rs b/hive-c0re/src/agent_config/capabilities.rs index 396e9305..dd8e4b33 100644 --- a/hive-c0re/src/agent_config/capabilities.rs +++ b/hive-c0re/src/agent_config/capabilities.rs @@ -28,7 +28,7 @@ const CAPABILITIES_FILE: &str = "capabilities.json"; #[must_use] pub fn capabilities_path() -> PathBuf { - crate::meta::meta_dir().join(CAPABILITIES_FILE) + crate::paths::meta_root().join(CAPABILITIES_FILE) } /// Read the per-agent capability map. Returns an empty map when the diff --git a/hive-c0re/src/agent_config/tool_groups.rs b/hive-c0re/src/agent_config/tool_groups.rs index 528f94a5..4bb1fe08 100644 --- a/hive-c0re/src/agent_config/tool_groups.rs +++ b/hive-c0re/src/agent_config/tool_groups.rs @@ -30,7 +30,7 @@ const TOOL_GROUPS_FILE: &str = "tool-groups.json"; #[must_use] pub fn tool_groups_path() -> PathBuf { - crate::meta::meta_dir().join(TOOL_GROUPS_FILE) + crate::paths::meta_root().join(TOOL_GROUPS_FILE) } /// Read the per-agent tool-group map. Returns an empty map when the diff --git a/hive-c0re/src/agent_config/topology.rs b/hive-c0re/src/agent_config/topology.rs index 08acd088..cdbdb510 100644 --- a/hive-c0re/src/agent_config/topology.rs +++ b/hive-c0re/src/agent_config/topology.rs @@ -34,7 +34,7 @@ const TOPOLOGY_FILE: &str = "topology.json"; #[must_use] pub fn topology_path() -> PathBuf { - crate::meta::meta_dir().join(TOPOLOGY_FILE) + crate::paths::meta_root().join(TOPOLOGY_FILE) } /// Snapshot of the topology map. Read on every `container_view::build_all` @@ -451,7 +451,7 @@ const ROLES_FILE: &str = "roles.json"; #[must_use] pub fn roles_path() -> std::path::PathBuf { - crate::meta::meta_dir().join(ROLES_FILE) + crate::paths::meta_root().join(ROLES_FILE) } /// Read the roles map from disk. Returns an empty map when absent or diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 462cb460..53c96fcc 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -7,7 +7,6 @@ use anyhow::{Context, Result}; use std::fmt::Write as _; -use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -31,17 +30,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; -/// 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 -/// can read it at `/run/hive-state/agents.conf`. Subdirectory scoping -/// avoids exposing the rest of `/var/lib/hyperhive/` (which may contain -/// forge tokens or other credentials) to the gateway container. -#[must_use] -pub fn host_conf_path() -> PathBuf { - crate::paths::gateway_agents_conf() -} - /// Nginx proxy headers present in every per-agent location block. /// `$connection_upgrade` is defined in the http context by the NixOS /// nginx module when `recommendedProxySettings = true` (which the @@ -162,7 +150,7 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String { } /// Atomically write the nginx include file for `names` to -/// [`host_conf_path()`]. Skips the write + reload when the rendered +/// [`crate::paths::gateway_agents_conf()`]. Skips the write + reload when the rendered /// body matches what's already on disk (idempotent; avoids spurious /// gateway reloads on a quiet tick). /// @@ -184,7 +172,7 @@ pub async fn write(names: &[String]) -> Result<()> { .ok() .filter(|s| !s.is_empty()); let body = render(names, frontend_dir.as_deref()); - let path = host_conf_path(); + let path = crate::paths::gateway_agents_conf(); if std::fs::read_to_string(&path).ok().as_deref() == Some(&body) { return Ok(()); } diff --git a/hive-c0re/src/job_queue/exec.rs b/hive-c0re/src/job_queue/exec.rs index a37b7202..66adbe41 100644 --- a/hive-c0re/src/job_queue/exec.rs +++ b/hive-c0re/src/job_queue/exec.rs @@ -120,7 +120,7 @@ async fn run_prebuild( } } ctx.step("nix build"); - let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display()); + let flake_ref = format!("{}#{name}", crate::paths::meta_root().display()); crate::lifecycle::prebuild_toplevel(name, &flake_ref, &|log_id| ctx.build_log(log_id)).await?; Ok(NodeOutput::default()) } @@ -145,7 +145,7 @@ async fn run_swap(coord: &Arc, claim: &Claim, ctx: &Ctx<'_>) -> Res match &result { Ok(()) => { if let Some(rev) = crate::auto_update::current_flake_rev(&coord.hyperhive_flake) - && let Err(e) = std::fs::write(crate::auto_update::rev_marker_path(name), rev) + && let Err(e) = std::fs::write(crate::paths::applied_rev_marker(name), rev) { tracing::warn!(%name, error = ?e, "write rev marker failed"); } diff --git a/hive-c0re/src/job_queue/submit.rs b/hive-c0re/src/job_queue/submit.rs index fa889fe7..073c3671 100644 --- a/hive-c0re/src/job_queue/submit.rs +++ b/hive-c0re/src/job_queue/submit.rs @@ -49,7 +49,7 @@ pub fn restart(coord: &Arc, agent: &str, source: Source, reason: St /// — the old fast-lane `run_start` upgrade, moved to submit time. pub fn start(coord: &Arc, agent: &str, source: Source, reason: String) -> u64 { set_wanted(coord, agent, Wanted::Up); - let stored = std::fs::read_to_string(crate::auto_update::rev_marker_path(agent)).ok(); + let stored = std::fs::read_to_string(crate::paths::applied_rev_marker(agent)).ok(); let stale = crate::auto_update::current_flake_rev(&coord.hyperhive_flake) .is_some_and(|rev| stored.as_deref() != Some(rev.as_str())); if stale { diff --git a/hive-c0re/src/lifecycle/mod.rs b/hive-c0re/src/lifecycle/mod.rs index 5472b4b1..9e2d8936 100644 --- a/hive-c0re/src/lifecycle/mod.rs +++ b/hive-c0re/src/lifecycle/mod.rs @@ -635,7 +635,7 @@ pub async fn rebuild_no_meta( on_build_log_id: &(dyn Fn(i64) + Send + Sync), ) -> Result { prepare_rebuild_dirs(name, paths).await?; - let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display()); + let flake_ref = format!("{}#{name}", crate::paths::meta_root().display()); if container_exists(name).await { // Rebuild strategy: stop-before-update + pre-build. // See `docs/coordinator.md::Container lifecycle`. diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index fb8b0425..a7d833f3 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -4,7 +4,7 @@ //! `finalize_deploy` / `abort_deploy`, `lock_update_hyperhive`): //! `docs/approvals.md::Meta flake`. -use std::path::{Path, PathBuf}; +use std::path::Path; use anyhow::{Context, Result, bail}; use tokio::process::Command; @@ -56,11 +56,6 @@ pub struct AgentSpec { pub port: u16, } -#[must_use] -pub fn meta_dir() -> PathBuf { - crate::paths::meta_root() -} - /// Idempotently reconcile the meta repo with the current agent set. /// First call inits the git repo, runs `nix flake lock`, and lands a /// seed commit. Subsequent calls only touch `flake.nix` when the @@ -68,7 +63,7 @@ pub fn meta_dir() -> PathBuf { /// no-op. pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?; let new_flake = render_flake( @@ -241,7 +236,7 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> { /// meta history only carries successful deploys. pub async fn prepare_deploy(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); nix(&dir, &["flake", "update", &input]).await?; // Stage the new lock — git+file://'s dirty-tree fetcher reads @@ -255,7 +250,7 @@ pub async fn prepare_deploy(name: &str) -> Result<()> { /// place (nothing staged → nothing to commit). pub async fn finalize_deploy(name: &str, sha: &str, tag: &str) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); if !paths_dirty(&dir, &["flake.lock"]).await? { return Ok(()); } @@ -273,7 +268,7 @@ pub async fn finalize_deploy(name: &str, sha: &str, tag: &str) -> Result<()> { /// captured in `applied/`'s annotated `failed/` tag. pub async fn abort_deploy() -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); git(&dir, &["restore", "--staged", "flake.lock"]).await?; git(&dir, &["restore", "flake.lock"]).await } @@ -284,7 +279,7 @@ pub async fn abort_deploy() -> Result<()> { /// semantics — it always wants the latest main. pub async fn lock_update_for_rebuild(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); nix(&dir, &["flake", "update", &input]).await?; if !paths_dirty(&dir, &["flake.lock"]).await? { @@ -325,7 +320,7 @@ fn agent_input_override(applied_dir: &Path, sha: &str) -> String { /// actual apply, so this is the right "would this apply" gate. pub async fn verify_commit(name: &str, applied_dir: &Path, sha: &str) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); let over = agent_input_override(applied_dir, sha); let attr = format!(".#nixosConfigurations.{name}.config.system.build.toplevel.drvPath"); @@ -355,7 +350,7 @@ pub async fn verify_commit(name: &str, applied_dir: &Path, sha: &str) -> Result< /// file) when targeting specific inputs. pub async fn lock_update(inputs: &[String]) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let mut args: Vec<&str> = vec!["flake", "update"]; for i in inputs { args.push(i.as_str()); @@ -380,7 +375,7 @@ pub async fn lock_update(inputs: &[String]) -> Result<()> { /// because the per-agent inputs aren't touched. pub async fn lock_update_hyperhive() -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); nix(&dir, &["flake", "update", "hyperhive"]).await?; if !paths_dirty(&dir, &["flake.lock"]).await? { return Ok(()); @@ -396,7 +391,7 @@ pub async fn lock_update_hyperhive() -> Result<()> { pub async fn commit_tool_groups(agent: &str, groups: &[String]) -> Result<()> { let _guard = META_LOCK.lock().await; crate::tool_groups::set_groups(agent, groups)?; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); if crate::tool_groups::tool_groups_path().exists() { git(&dir, &["add", "tool-groups.json"]).await?; } @@ -417,7 +412,7 @@ pub async fn commit_capabilities(agent: &str, caps: &[String]) -> Result<()> { let _guard = META_LOCK.lock().await; crate::capabilities::set_caps(agent, caps) .map_err(|e| anyhow::anyhow!("set capabilities for {agent}: {e}"))?; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); if crate::capabilities::capabilities_path().exists() { git(&dir, &["add", "capabilities.json"]).await?; } @@ -450,7 +445,7 @@ pub async fn commit_perms( caps: Option<&[String]>, ) -> Result<()> { let _guard = META_LOCK.lock().await; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let mut parts: Vec<&str> = Vec::new(); if let Some(groups) = groups { crate::tool_groups::set_groups(agent, groups)?; @@ -492,7 +487,7 @@ pub async fn commit_topology( ) -> std::result::Result<(), String> { let _guard = META_LOCK.lock().await; crate::topology::set_parent(child, new_parent)?; - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let stage = async { git(&dir, &["add", "topology.json"]).await?; if paths_dirty(&dir, &["topology.json"]).await? { @@ -554,7 +549,7 @@ pub async fn bulk_commit_topology( crate::topology::write(&next).map_err(|e| format!("{e:#}"))?; } // Commit the whole batch as one git operation. - let dir = meta_dir(); + let dir = crate::paths::meta_root(); let commit_msg = if moves.len() == 1 { let (child, new_parent) = moves[0]; format!("topology: {} → {}", child, new_parent.unwrap_or("")) diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index 13f99ccf..5ed34f51 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -4,7 +4,7 @@ //! Kill-switch: `HIVE_SKIP_META_MIGRATION=1`. Full migration sequence //! and phase details: `docs/approvals.md::Migration from the pre-tag`. -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use anyhow::{Context, Result}; @@ -17,18 +17,6 @@ use crate::tool_groups; 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 { - 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 { - crate::paths::hroot_rename_marker() -} - /// Substring that identifies the *current* agent flake boilerplate. /// Bumped whenever the template changes so the startup migration /// re-renders existing agents onto the new shape. Today the marker @@ -83,7 +71,7 @@ pub async fn run(coord: &Arc) -> Result<()> { } // Phase 4: container repoint, guarded by marker. - if repoint_marker().exists() { + if crate::paths::meta_migration_marker().exists() { tracing::debug!("migration: phase 4 marker present, skipping repoint"); return Ok(()); } @@ -104,7 +92,7 @@ pub async fn run(coord: &Arc) -> Result<()> { } if all_ok && !names.is_empty() - && let Err(e) = std::fs::write(repoint_marker(), b"done\n") + && let Err(e) = std::fs::write(crate::paths::meta_migration_marker(), b"done\n") { tracing::warn!(error = ?e, "migration: write repoint marker failed"); } @@ -169,20 +157,20 @@ fn migrate_harness_files(name: &str) { /// conf files present; on the next hive-c0re start the marker is /// absent so the phase retries. async fn rename_manager_container(coord: &Arc) { - if hroot_rename_marker().exists() { + if crate::paths::hroot_rename_marker().exists() { return; } let old_conf = std::path::PathBuf::from("/etc/nixos-containers/root.conf"); let new_conf = std::path::PathBuf::from("/etc/nixos-containers/h-root.conf"); if !old_conf.exists() { // Fresh install — root container was never created under the old name. - let _ = std::fs::write(hroot_rename_marker(), b"done\n"); + let _ = std::fs::write(crate::paths::hroot_rename_marker(), b"done\n"); return; } if new_conf.exists() { // Already renamed (but marker was lost — write it and return). tracing::info!("migration phase 5: h-root.conf already present, marking done"); - let _ = std::fs::write(hroot_rename_marker(), b"done\n"); + let _ = std::fs::write(crate::paths::hroot_rename_marker(), b"done\n"); return; } tracing::info!("migration phase 5: renaming root container to h-root"); @@ -243,7 +231,7 @@ async fn rename_manager_container(coord: &Arc) { } tracing::info!("migration phase 5: root container renamed to h-root"); - let _ = std::fs::write(hroot_rename_marker(), b"done\n"); + let _ = std::fs::write(crate::paths::hroot_rename_marker(), b"done\n"); // Clean up the old conf file so `nixos-container list` doesn't show // a stale stopped `root` entry. Best-effort; a failure here is // harmless — h-root is already running and the marker is written. @@ -312,7 +300,7 @@ async fn migrate_applied_repo(name: &str) -> Result<()> { async fn repoint_container(name: &str) -> Result<()> { let container = lifecycle::container_name(name); - let flake_ref = format!("{}#{name}", meta::meta_dir().display()); + let flake_ref = format!("{}#{name}", crate::paths::meta_root().display()); let out = Command::new("nixos-container") .args(["update", &container, "--flake", &flake_ref]) .output() diff --git a/hive-c0re/src/paths.rs b/hive-c0re/src/paths.rs index cacbb7d2..ee3bb0b0 100644 --- a/hive-c0re/src/paths.rs +++ b/hive-c0re/src/paths.rs @@ -224,6 +224,10 @@ pub fn shared_root() -> PathBuf { pub const KNOWLEDGE_DIR: &str = "/var/lib/hyperhive/knowledge"; /// `gateway/` — generated nginx include fragments for the gateway vhost. +/// The gateway container bind-mounts *this subdir only* (not the whole +/// state root) at `/run/hive-state/`, so nginx can read `agents.conf` +/// without the rest of `/var/lib/hyperhive/` (forge/matrix tokens, etc.) +/// being exposed to the gateway container. // nix: bind-mounted into the gateway container (hive-gateway.nix) — must match. #[must_use] pub fn gateway_dir() -> PathBuf { diff --git a/hive-c0re/src/workers/auto_update.rs b/hive-c0re/src/workers/auto_update.rs index 3b2c280d..3c6109ce 100644 --- a/hive-c0re/src/workers/auto_update.rs +++ b/hive-c0re/src/workers/auto_update.rs @@ -16,7 +16,7 @@ //! Booting with no config change performs no meta commit — only //! reconciles. See `docs/coordinator.md::Boot reconcile`. -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use anyhow::Result; @@ -24,14 +24,6 @@ use anyhow::Result; use crate::coordinator::Coordinator; use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME}; -/// Marker file recording the hyperhive rev a sub-agent's container was last -/// built against. Sibling of `applied//` (rather than inside it) to -/// 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 { - crate::paths::applied_rev_marker(name) -} - /// Resolve the current rev of `hyperhive_flake`. For a path on disk we /// canonicalize (following symlinks) so a /etc/hyperhive → /nix/store/... /// update yields a different string. For anything else we return None. @@ -159,7 +151,7 @@ pub async fn ensure_root_agent(coord: &Arc) -> Result<()> { tracing::warn!(error = ?e, "agent_power: set manager wanted=up failed"); } if let Some(rev) = current_rev { - let _ = std::fs::write(rev_marker_path(MANAGER_NAME), &rev); + let _ = std::fs::write(crate::paths::applied_rev_marker(MANAGER_NAME), &rev); } Ok(()) } @@ -248,7 +240,7 @@ pub async fn run(coord: Arc) -> Result<()> { } }; let fresh = current_rev.as_ref().is_some_and(|rev| { - std::fs::read_to_string(rev_marker_path(name)) + std::fs::read_to_string(crate::paths::applied_rev_marker(name)) .is_ok_and(|stored| stored == rev.as_str()) }); if fresh {