refactor(#2285): repoint all hive-c0re host-path consumers to paths.rs
This commit is contained in:
parent
4162f557c9
commit
187c364feb
16 changed files with 95 additions and 125 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue