From 4cb529351e7b96558948efb6770430c4c5908c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Sat, 16 May 2026 00:28:26 +0200 Subject: [PATCH] lifecycle::rebuild through meta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rebuild now does sync_agents (idempotent — no-op when the rendered flake matches disk; recovers from a divergent meta repo on the side) followed by lock_update_for_rebuild which relocks just this agent's input and commits the lock change if any. flake ref for nixos-container update flips from applied/#default to meta#. new helper meta::lock_update_for_rebuild is single-phase (no separate finalize): rebuild has no failure-revert semantics — it always wants the latest applied//main. spawn already syncs meta before container create; rebuild now picks up the meta side on every manual ↻ R3BU1LD. --- hive-c0re/src/lifecycle.rs | 19 ++++++++++++++----- hive-c0re/src/meta.rs | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 594bc88..93e6bdf 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -265,14 +265,12 @@ pub async fn destroy(name: &str) -> Result<()> { pub async fn rebuild( name: &str, - // hyperhive_flake + dashboard_port unused after the meta-flake - // overhaul; kept on the signature until callers are reworked. - _hyperhive_flake: &str, + hyperhive_flake: &str, agent_dir: &Path, applied_dir: &Path, claude_dir: &Path, notes_dir: &Path, - _dashboard_port: u16, + dashboard_port: u16, ) -> Result<()> { validate(name)?; if let Some(other) = port_collision(name).await { @@ -284,8 +282,19 @@ pub async fn rebuild( setup_applied(applied_dir, None, name).await?; ensure_claude_dir(claude_dir)?; ensure_state_dir(notes_dir)?; + // Sync the meta flake (idempotent — no-op when the rendered + // flake matches disk) so a manual rebuild from the dashboard + // can also recover from a divergent meta repo (e.g. an agent + // got added directly via `nixos-container create` outside + // hive-c0re). + let agents = agents_for_meta(None).await?; + crate::meta::sync_agents(hyperhive_flake, dashboard_port, &agents).await?; + // Then bump just this agent's input — picks up whatever + // `applied//main` currently points at (deployed/). + // Commits the lock if it changed. + crate::meta::lock_update_for_rebuild(name).await?; let container = container_name(name); - let flake_ref = format!("{}#default", applied_dir.display()); + let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display()); set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?; systemd_daemon_reload().await?; diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 1b7ed9b..e434d24 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -119,6 +119,22 @@ pub async fn abort_deploy() -> Result<()> { git(&dir, &["restore", "flake.lock"]).await } +/// One-shot used by the manual-rebuild path: relock just one +/// agent's input and commit the lock change if any. Single-phase +/// (no separate finalize) because rebuild has no failure-revert +/// semantics — it always wants the latest main. +#[allow(dead_code)] // wired up by lifecycle::rebuild in this commit +pub async fn lock_update_for_rebuild(name: &str) -> Result<()> { + let dir = meta_dir(); + let input = format!("agent-{name}"); + nix(&dir, &["flake", "lock", "--update-input", &input]).await?; + if !git_is_clean(&dir).await? { + git(&dir, &["add", "flake.lock"]).await?; + git_commit(&dir, &format!("rebuild {name}: lock update")).await?; + } + Ok(()) +} + /// One-shot used by the auto-update path: pin the latest hyperhive /// rev, commit if the lock changed. Cheaper than `sync_agents` /// because the per-agent inputs aren't touched.