diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 1f59dcc9..e3051517 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -430,20 +430,6 @@ pub async fn rebuild_no_meta( set_resource_limits(&container)?; systemd_daemon_reload().await?; if was_running { - // Pre-build the system toplevel **before** stopping the - // running container so the agent keeps serving its - // previous generation while the eval + fetch + build - // happens out-of-band (#706). `nixos-container update` - // then finds the toplevel cached and skips straight to - // the profile-swap + restart — downtime collapses to that - // window only. Build failures surface here, before we - // touch the container. - // - // When the container is already stopped there's no - // downtime to shave (mara on #721#9007) — let `update` - // do the build inline rather than evaluating the flake - // twice for nothing. - prebuild_toplevel(name, &flake_ref).await?; run(&["stop", &container]).await?; } run(&["update", &container, "--flake", &flake_ref]).await?; @@ -453,11 +439,8 @@ pub async fn rebuild_no_meta( Ok(()) } } else { - // First spawn: no running container, no downtime to shave. - // `nixos-container create` builds + creates atomically — if - // the build fails, no container record is left around to - // clean up — so a pre-build adds nothing but a duplicate - // eval (mara on #721#9007). + // First spawn: create the container first (which writes the nspawn + // conf file), then overwrite with our flags and start. run(&["create", &container, "--flake", &flake_ref]).await?; set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?; @@ -466,89 +449,6 @@ pub async fn rebuild_no_meta( } } -/// Pre-build the agent's `system.build.toplevel` derivation against -/// `meta#` so the subsequent `nixos-container update` / -/// `create` finds the result already in the store. The container -/// itself is untouched — this is purely a store-warming pass. -/// -/// Streams nix's stdout to INFO and stderr to WARN like the -/// `nixos-container` shellouts so progress shows up in journald as -/// it happens. `--no-link` keeps us from littering the working -/// directory with `result` symlinks. Per-derivation cost: pure -/// cache hit when nothing changed (handful of seconds for the -/// eval), expensive only on the rebuild that actually has work. -/// -/// Returns the same error shape as the other nixos-container -/// helpers so callers can use `?` without translation. -async fn prebuild_toplevel(name: &str, flake_ref: &str) -> Result<()> { - use tokio::io::{AsyncBufReadExt, BufReader}; - let attr = format!("{flake_ref}.config.system.build.toplevel"); - let args = vec![ - "--extra-experimental-features", - "nix-command flakes", - "build", - "--no-link", - "--print-out-paths", - &attr, - ]; - let cmdline = format!("nix {}", args.join(" ")); - tracing::info!(%name, %cmdline, "prebuild: warming system toplevel"); - let mut child = Command::new("nix") - .args(&args) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .with_context(|| format!("spawn {cmdline}"))?; - - let stdout = child.stdout.take().expect("piped stdout"); - let stderr = child.stderr.take().expect("piped stderr"); - - let stdout_cmdline = cmdline.clone(); - let pump_stdout = tokio::spawn(async move { - let mut lines = BufReader::new(stdout).lines(); - while let Ok(Some(line)) = lines.next_line().await { - tracing::info!(target: "nix-prebuild", cmdline = %stdout_cmdline, "{line}"); - } - }); - - let stderr_cmdline = cmdline.clone(); - let stderr_tail: std::sync::Arc>> = - std::sync::Arc::new(std::sync::Mutex::new( - std::collections::VecDeque::with_capacity(32), - )); - let stderr_tail_pump = stderr_tail.clone(); - let pump_stderr = tokio::spawn(async move { - let mut lines = BufReader::new(stderr).lines(); - while let Ok(Some(line)) = lines.next_line().await { - tracing::warn!(target: "nix-prebuild", cmdline = %stderr_cmdline, "{line}"); - let mut tail = stderr_tail_pump.lock().unwrap(); - if tail.len() == 32 { - tail.pop_front(); - } - tail.push_back(line); - } - }); - - let status = child - .wait() - .await - .with_context(|| format!("wait {cmdline}"))?; - let _ = pump_stdout.await; - let _ = pump_stderr.await; - - if !status.success() { - let tail = stderr_tail - .lock() - .unwrap() - .iter() - .cloned() - .collect::>() - .join("\n"); - bail!("prebuild {cmdline} failed ({status}): {tail}"); - } - Ok(()) -} - pub async fn list() -> Result> { let out = Command::new("nixos-container") .arg("list")