From c42ad1330c9251cad8394e766177116e35b840ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Sat, 16 May 2026 00:25:43 +0200 Subject: [PATCH] lifecycle: pre-wire applied remote in proposed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup_proposed now lands a git remote named 'applied' on every proposed//config pointing at /applied//.git — the path as seen from inside the manager container, where the RO bind in set_nspawn_flags makes the URL resolve. From the manager: git fetch applied git log applied/main git show applied/refs/tags/deployed/ git diff applied/main HEAD git rebase applied/main all work without manually constructing the path each time. The RO bind blocks push at the kernel level so the remote can only fetch. Idempotent — also applied to pre-existing proposed repos (no-op if the remote is already correct, set-url if drifted) so the startup migration picks up the wiring on existing agents. --- hive-c0re/src/lifecycle.rs | 59 +++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 3debb67..06afc27 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -285,25 +285,50 @@ pub async fn list() -> Result> { /// manager can't be surprised by hive-c0re commits or working-tree /// resets. pub async fn setup_proposed(proposed_dir: &Path, name: &str) -> Result<()> { - if proposed_dir.join(".git").exists() { - return Ok(()); + let fresh = !proposed_dir.join(".git").exists(); + if fresh { + std::fs::create_dir_all(proposed_dir) + .with_context(|| format!("create {}", proposed_dir.display()))?; + let agent_path = proposed_dir.join("agent.nix"); + if !agent_path.exists() { + std::fs::write(&agent_path, initial_agent_nix(name)) + .with_context(|| format!("write {}", agent_path.display()))?; + } + let flake_path = proposed_dir.join("flake.nix"); + if !flake_path.exists() { + std::fs::write(&flake_path, initial_flake_nix()) + .with_context(|| format!("write {}", flake_path.display()))?; + } + git(proposed_dir, &["init", "--initial-branch=main"]).await?; + git(proposed_dir, &["add", "agent.nix", "flake.nix"]).await?; + git_commit(proposed_dir, "hive-c0re init").await?; } - std::fs::create_dir_all(proposed_dir) - .with_context(|| format!("create {}", proposed_dir.display()))?; - let agent_path = proposed_dir.join("agent.nix"); - if !agent_path.exists() { - std::fs::write(&agent_path, initial_agent_nix(name)) - .with_context(|| format!("write {}", agent_path.display()))?; + // Idempotently wire the `applied` remote — purely for the + // manager's ergonomics. The URL is the path inside the manager + // container (`/applied//.git`), where the RO bind in + // `set_nspawn_flags` makes it real. hive-c0re itself never + // dereferences this remote; the host-side fetch in + // `request_apply_commit` uses absolute host paths. + ensure_applied_remote(proposed_dir, name).await +} + +async fn ensure_applied_remote(proposed_dir: &Path, name: &str) -> Result<()> { + let want = format!("/applied/{name}/.git"); + let existing = git_command() + .current_dir(proposed_dir) + .args(["remote", "get-url", "applied"]) + .output() + .await + .with_context(|| format!("git remote get-url applied in {}", proposed_dir.display()))?; + if existing.status.success() { + let current = String::from_utf8_lossy(&existing.stdout).trim().to_owned(); + if current == want { + return Ok(()); + } + // URL drifted (path scheme changed, etc.) — re-point it. + return git(proposed_dir, &["remote", "set-url", "applied", &want]).await; } - let flake_path = proposed_dir.join("flake.nix"); - if !flake_path.exists() { - std::fs::write(&flake_path, initial_flake_nix()) - .with_context(|| format!("write {}", flake_path.display()))?; - } - git(proposed_dir, &["init", "--initial-branch=main"]).await?; - git(proposed_dir, &["add", "agent.nix", "flake.nix"]).await?; - git_commit(proposed_dir, "hive-c0re init").await?; - Ok(()) + git(proposed_dir, &["remote", "add", "applied", &want]).await } /// Set up the applied repo. First-spawn only: init the repo, pull