Watch
0
0
Fork
You've already forked hyperhive
0

build agents from internal/meta when it pins their config

A rebuild's MetaSync resolves the head of the swarm's internal/meta once and
records it; the prebuild and the swap build that flake instead of the hive's
own, and a first deploy does the same in Create. hive-priv takes the commit as
a validated meta_rev on UpdateContainer and CreateContainer and builds from its
own forge URL (HIVE_PRIV_FORGE_URL).

The head is used only when its flake lists the agent at the config commit the
forge's main is at; otherwise, and when the repo has no commit or this hive has
no core token file for nix's git, the hive renders locally as before.

hive-c0re's and hive-priv's gitconfig now share the forge credential helper and
rewrite the forge's https URLs to http, which hive-priv lacked.
This commit is contained in:
atlas 2026-10-24 14:59:43 +02:00
commit 7c0a622d41
19 changed files with 831 additions and 64 deletions

View file

@ -101,12 +101,36 @@ impl std::str::FromStr for InfraContainer {
}
}
/// Host path of the meta flake. The flake ref for agent `<name>` is
/// `{META_DIR}#{name}`, derived by `hive-priv` — never passed over the wire.
/// Must stay in sync with `hive-c0re::paths::meta_root()` (`STATE_ROOT +
/// "/meta"`); the privsep boundary prevents importing across the crate.
/// Host path of the meta flake a hive renders itself. The flake ref for agent
/// `<name>` is `{META_DIR}#{name}`, derived by `hive-priv` — never passed over
/// the wire. Must stay in sync with `hive-c0re::paths::meta_root()`
/// (`STATE_ROOT + "/meta"`); the privsep boundary prevents importing across
/// the crate.
pub const META_DIR: &str = "/var/lib/hyperhive/meta";
/// Whether `rev` is a git commit id: 40 lowercase hex digits. The only part of
/// the `internal/meta` flake ref that crosses the wire (`meta_rev` on
/// `PrivRequest::UpdateContainer` and `CreateContainer`), so `hive-priv` checks
/// it before it builds a ref.
#[must_use]
pub fn valid_meta_rev(rev: &str) -> bool {
rev.len() == 40 && rev.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
}
/// The flake the swarm publishes at `internal/meta`, at `rev`:
/// `git+<forge_base>/internal/meta.git?rev=<rev>`. `forge_base` is the forge's
/// `http` origin, which the hive's git credential helper answers for.
///
/// One definition for `hive-c0re`'s prebuild and `hive-priv`'s build, so both
/// name the same flake.
#[must_use]
pub fn meta_flake_url(forge_base: &str, rev: &str) -> String {
format!(
"git+{}/internal/meta.git?rev={rev}",
forge_base.trim_end_matches('/')
)
}
/// Root of per-agent state directories on the host.
/// Subdirectory layout: `<AGENT_STATE_ROOT>/<name>/state/<file>`.
/// Used by `WriteAgentStateFile` to derive the write path so the
@ -227,23 +251,33 @@ pub enum PrivRequest {
KillContainer { name: String },
/// `nixos-container update <name> --flake <flake_ref>`
/// The flake ref is derived from `name` by hive-priv.
/// The flake ref is derived from `name` by hive-priv: the hive's own meta
/// flake, or `internal/meta` at `meta_rev` when that is set (the forge URL
/// is hive-priv's own and never on the wire).
/// When `stream` is `true`, hive-priv sends `PrivEvent::Line` messages
/// as the process runs, then a terminal `PrivEvent::Done`.
UpdateContainer {
name: String,
#[serde(default)]
stream: bool,
/// A commit of `internal/meta` to build from, checked by
/// `valid_meta_rev`. `serde(default)` so a request from a hive-c0re
/// that predates the field builds from the hive's own flake.
#[serde(default)]
meta_rev: Option<String>,
},
/// `nixos-container create <name> --flake <flake_ref>`
/// The flake ref is derived from `name` by hive-priv.
/// The flake ref is derived from `name` (and `meta_rev`) as for
/// `UpdateContainer`.
/// When `stream` is `true`, hive-priv sends `PrivEvent::Line` messages
/// as the process runs, then a terminal `PrivEvent::Done`.
CreateContainer {
name: String,
#[serde(default)]
stream: bool,
#[serde(default)]
meta_rev: Option<String>,
},
/// `nixos-container destroy <name>`
@ -656,7 +690,7 @@ pub enum PrivEvent {
#[cfg(test)]
mod tests {
use super::InfraContainer;
use super::{InfraContainer, PrivRequest, meta_flake_url, valid_meta_rev};
#[test]
fn infra_control_names_no_c0re_or_swarm_service() {
@ -685,4 +719,56 @@ mod tests {
fn gateway_resolves_to_host_nginx() {
assert_eq!(InfraContainer::Gateway.service_unit(), "nginx.service");
}
#[test]
fn only_a_full_lowercase_commit_id_is_a_meta_rev() {
assert!(valid_meta_rev(&"a1".repeat(20)));
for bad in [
"",
"main",
&"a".repeat(39),
&"a".repeat(41),
&"A".repeat(40),
&format!("{}?x", "a".repeat(38)),
&format!("{}#x", "a".repeat(39)),
&format!("{} ", "a".repeat(39)),
] {
assert!(!valid_meta_rev(bad), "{bad:?}");
}
}
#[test]
fn the_meta_flake_url_names_internal_meta_at_the_rev() {
let rev = "a".repeat(40);
assert_eq!(
meta_flake_url("http://forge.t.local/", &rev),
format!("git+http://forge.t.local/internal/meta.git?rev={rev}")
);
}
/// A hive-c0re that predates `meta_rev` sends neither field.
#[test]
fn a_request_without_meta_rev_builds_from_the_hives_own_flake() {
for op in ["update_container", "create_container"] {
let req: PrivRequest =
serde_json::from_str(&format!(r#"{{"op":"{op}","name":"atlas"}}"#)).unwrap();
match req {
PrivRequest::UpdateContainer { meta_rev, .. }
| PrivRequest::CreateContainer { meta_rev, .. } => assert_eq!(meta_rev, None),
other => panic!("{other:?}"),
}
}
let with: PrivRequest = serde_json::from_str(&format!(
r#"{{"op":"update_container","name":"atlas","meta_rev":"{}"}}"#,
"b".repeat(40)
))
.unwrap();
assert!(matches!(
with,
PrivRequest::UpdateContainer {
meta_rev: Some(_),
..
}
));
}
}