nixfleet_agent/activation/mod.rs
1//! Activation pipeline (RFC-0005 §4). The runtime worker
2//! (`runtime/workers/activation.rs`) is the wire-layer entry point;
3//! this module owns the seven LOADBEARING operational steps the
4//! activation must preserve:
5//!
6//! 1. `realise.rs` — `nix-store --realise` forces fetch + signature
7//! verification of the target closure with trust-failure detection
8//! distinct from generic failure (SignatureMismatch outcome).
9//! 2. `profile.rs::set` — `nix-env --set` flips
10//! `/nix/var/nix/profiles/system` to point at the target. Required
11//! before fire because `switch-to-configuration` reads that profile
12//! to derive the generation number it writes into bootloader entries.
13//! 3. Pre-switch basename read — captured for `verify_poll`'s
14//! flip-to-unexpected detection (third basename = symlink got
15//! switched to something OTHER than expected OR previous; caller
16//! rolls back).
17//! 4. `linux::fire_switch` / `darwin::fire_switch` — per-OS detached
18//! subprocess invocation. Linux uses `systemd-run --unit` (cgroup
19//! isolation so agent SIGTERM cannot kill mid-switch); Darwin uses
20//! `setsid()` to detach from agent's process group (launchd reload
21//! sends SIGTERM to the whole group).
22//! 5. `verify_poll.rs` — polls `/run/current-system` until expected,
23//! with three outcomes: Settled, Timeout, FlippedToUnexpected.
24//! POLL_BUDGET (300s) locked to CP's DEFAULT_CONFIRM_DEADLINE_SECS
25//! (360s) minus slack — exceeding splits state.
26//! 6. `profile::self_correct_profile` — recovers when the activation
27//! script re-pointed the profile (some closures do this). Non-fatal
28//! if self-correction itself fails (the live switch still happened).
29//! 7. `linux::detect_switch_inhibitors` — detects critical-component
30//! swaps (dbus, systemd, kernel) that nixos-rebuild refuses to
31//! live-switch. DeferredPendingReboot outcome triggers
32//! `switch-to-configuration boot` (writes bootloader entries) so
33//! the next reboot lands on the new generation.
34//!
35//! The legacy v0.1-shaped `activate(target: &EvaluatedTarget)` entry
36//! point + `comms::confirm()` post-activation report are NOT restored —
37//! they belonged to the pre-v0.2 architecture. The v0.2 entry is
38//! `runtime::workers::activation::handle_intent(ActivationIntent)`,
39//! which constructs an `ActivationTarget` from the intent and calls
40//! `activate_with(&DEFAULT_BACKEND, &target)`. Completion / failure
41//! flows back through the existing `Event::LocalActivation*` reducer
42//! events; CP reads from the outbound queue (no separate confirm HTTP
43//! call needed in v0.2).
44
45pub mod darwin;
46pub mod linux;
47pub mod pipeline;
48pub mod profile;
49pub mod realise;
50pub mod rollback;
51pub mod types;
52pub mod verify_poll;
53
54pub use pipeline::activate_with;
55pub use rollback::rollback_with;
56pub use types::{
57 ActivationBackend, ActivationOutcome, ActivationTarget, DEFAULT_BACKEND, DefaultBackend,
58 POLL_BUDGET, POLL_INTERVAL, RollbackOutcome,
59};
60
61/// High-level activate facade — production callers use `DEFAULT_BACKEND`.
62/// Test paths that need a stub backend call `activate_with` directly.
63pub async fn activate(target: &ActivationTarget) -> anyhow::Result<ActivationOutcome> {
64 activate_with(&DEFAULT_BACKEND, target).await
65}
66
67/// High-level rollback facade.
68pub async fn rollback() -> anyhow::Result<RollbackOutcome> {
69 rollback_with(&DEFAULT_BACKEND).await
70}