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…
takana etapa 5d: comentarios de crates, CLAUDE.md y el skill de granja
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}