tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

⎇ main crates/takana-recover/src/main.rs Rust · 83 líneas · 4.0 KB Historia Quién tocó qué crudo

takana etapa 5d: comentarios de crates, CLAUDE.md y el skill de granja b818f5249 · Sergio ·

1//! `hammer-recover` — auto-recuperación de upgrades al **arranque** (E4 / refinamiento #4b).
2//!
3//! El producto NO lleva el CLI `takana` completo; este mini-binario (estático musl) es lo único que el
4//! sistema instalado necesita para auto-sanar un upgrade interrumpido. El wrapper `/sbin/init` lo corre
5//! **tras montar `/store` y `/var/lib/hammer`** y **antes** de incarnar arje-zero:
6//!
7//! - Sin intento pendiente ⇒ no-op (el caso normal).
8//! - Con un `pending.json` (un `takana upgrade apply` cortado por un apagón/reinicio): intenta
9//!   **completar** (roll-forward, re-ejecuta el plan idempotente + commitea); si no puede (p.ej. el árbol
10//!   ya no está en `/store`), **deshace** (roll-back) para dejar el FHS consistente. En cualquier caso el
11//!   sistema arranca en un estado coherente, no a medias.
12//!
13//! Encaja con el modelo de generaciones **in-place** (el FHS es la generación viva): no hay menú de
14//! generaciones tipo NixOS que seleccionar, sino una reparación determinista del estado a medias.
15//!
16//! Paths del producto (overridables por entorno para tests):
17//!   `TAKANA_RECOVER_ROOT=/`  `TAKANA_RECOVER_STORE=/store`
18//!   `TAKANA_RECOVER_STATE=/var/lib/hammer/upgrades`  `TAKANA_RECOVER_JOURNAL=/var/lib/hammer/journal`
19//! Flag: `--rollback` fuerza deshacer en vez de completar.
20//!
21//! NUNCA sale con código ≠0 por un fallo de recuperación: es un hook de arranque, no debe abortar el
22//! boot. Reporta por consola y deja que arje-zero siga (con el estado más consistente que logró).
23
24use std::path::PathBuf;
25
26/// Lee la variable aceptando el nombre nuevo y, si falta, el viejo (ADR 0016).
27///
28/// La caída va inline y no por `takana_core::env`: este es un mini-binario que se copia a
29/// `/usr/sbin` de los sistemas instalados, y no vale la pena arrastrarle una dep entera por
30/// dos líneas. Se retira junto con el resto de las caídas en la etapa 6.
31fn env_path(key: &str, default: &str) -> PathBuf {
32    let viejo = key.strip_prefix("TAKANA_").map(|s| format!("HAMMER_{s}"));
33    std::env::var(key)
34        .ok()
35        .or_else(|| viejo.and_then(|v| std::env::var(v).ok()))
36        .map(PathBuf::from)
37        .unwrap_or_else(|| PathBuf::from(default))
38}
39
40fn main() {
41    let force_rollback = std::env::args().skip(1).any(|a| a == "--rollback");
42    let root = env_path("TAKANA_RECOVER_ROOT", "/");
43    let store = env_path("TAKANA_RECOVER_STORE", "/store");
44    let state = env_path("TAKANA_RECOVER_STATE", "/var/lib/hammer/upgrades");
45    let journal_dir = env_path("TAKANA_RECOVER_JOURNAL", "/var/lib/hammer/journal");
46
47    match takana_upgrade::pending(&state) {
48        Ok(None) => {
49            println!("hammer-recover: sin upgrade interrumpido");
50            return;
51        }
52        Ok(Some(p)) => {
53            println!("hammer-recover: upgrade interrumpido (generación {}) — recuperando", p.id);
54        }
55        Err(e) => {
56            eprintln!("hammer-recover: no pude leer el estado de upgrades: {e}");
57            return;
58        }
59    }
60
61    let journal = takana_journal::Journal::open(&journal_dir).ok();
62    let j = journal.as_ref();
63
64    if force_rollback {
65        match takana_upgrade::recover(&store, &root, &state, j, true) {
66            Ok(r) => println!("hammer-recover: DESHECHO (generación {} descartada)", r.generation),
67            Err(e) => eprintln!("hammer-recover: rollback falló: {e}"),
68        }
69        return;
70    }
71
72    // Optimista: completar el upgrade. Si el roll-forward falla, deshacer ⇒ FHS consistente igual.
73    match takana_upgrade::recover(&store, &root, &state, j, false) {
74        Ok(r) => println!("hammer-recover: COMPLETADO (generación {})", r.generation),
75        Err(e) => {
76            eprintln!("hammer-recover: roll-forward falló ({e}); deshaciendo para dejar el FHS consistente");
77            match takana_upgrade::recover(&store, &root, &state, j, true) {
78                Ok(r) => println!("hammer-recover: DESHECHO tras fallo (generación {})", r.generation),
79                Err(e2) => eprintln!("hammer-recover: rollback de respaldo también falló: {e2}"),
80            }
81        }
82    }
83}