gioser

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…

TOML 1572Shell 203Rust 109Markdown 993728 Definitions

⎇ 89ecd54135a72e486f94df045d191d231b253638 crates/hammerd/src/main.rs Rust · 138 lines · 5.0 KB History Who touched what raw

Fase 6 — bucle agéntico: hammer-agent (cliente + translator + orchestrator) y hammer ai 89ecd5413 · Sergio ·

1//! `hammerd` — daemon de hammer. Tres responsabilidades:
2//!   1. Diario de mutaciones: fanotify sobre /bin,/sbin,/lib,/etc → `hammer-journal`.
3//!   2. FIFO de control humano: `/run/init.control` (texto crudo, una línea por comando).
4//!   3. Bus de agente: `/run/agent.sock` (JSON-líneas, SO_PEERCRED). Ver `docs/07-agent-bus.md`.
5//!
6//! Cada subsistema vive en su thread. Si uno falla en init (típicamente el watcher sin
7//! CAP_SYS_ADMIN), los demás siguen arrancando: el daemon en dev puede correr sin sudo y aún
8//! así probar el bus.
9
10use std::path::PathBuf;
11use std::thread;
12
13use clap::Parser;
14
15mod bus;
16mod control;
17mod events;
18mod watcher;
19
20#[derive(Parser)]
21#[command(name = "hammerd", version, about = "Daemon de hammer: bus de agente + diario.")]
22struct Args {
23    /// Socket del bus de agente.
24    #[arg(long, default_value = "/run/agent.sock")]
25    agent_sock: String,
26    /// FIFO de control humano del init.
27    #[arg(long, default_value = "/run/init.control")]
28    init_control: PathBuf,
29    /// Directorio del diario de mutaciones.
30    #[arg(long, default_value = "/var/lib/hammer/journal")]
31    journal: String,
32    /// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
33    /// activos). Default coincide con `hammer try`.
34    #[arg(long, default_value = hammer_overlay::DEFAULT_STATE_ROOT)]
35    overlay_state_root: String,
36    /// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
37    #[arg(long = "watch")]
38    extra_watch: Vec<PathBuf>,
39    /// Store CAS donde sellar artefactos de COMPILE.
40    #[arg(long, default_value = "/store")]
41    store: PathBuf,
42    /// Modo dev: desactiva el watcher fanotify (útil cuando hammerd corre sin CAP_SYS_ADMIN
43    /// y sólo queremos probar el bus). Igual que arrancar sin permisos, pero explícito.
44    #[arg(long)]
45    no_watcher: bool,
46}
47
48fn main() -> anyhow::Result<()> {
49    tracing_subscriber::fmt()
50        .with_writer(std::io::stderr)
51        .with_env_filter(
52            tracing_subscriber::EnvFilter::try_from_default_env()
53                .unwrap_or_else(|_| "info".into()),
54        )
55        .init();
56
57    let args = Args::parse();
58    tracing::info!(
59        agent_sock = %args.agent_sock,
60        init_control = %args.init_control.display(),
61        journal = %args.journal,
62        store = %args.store.display(),
63        "hammerd: arranque"
64    );
65
66    let event_bus = events::EventBus::new();
67
68    // FIFO de control humano: lo creamos siempre que se pueda (no es fatal).
69    let init_fifo_ok = match control::ensure_fifo(&args.init_control) {
70        Ok(path) => {
71            let reader_path = path.clone();
72            thread::Builder::new()
73                .name("init-ctl-reader".into())
74                .spawn(move || {
75                    if let Err(e) = control::run_reader(&reader_path) {
76                        tracing::warn!(error = %e, "init-ctl-reader: terminó");
77                    }
78                })
79                .ok();
80            true
81        }
82        Err(e) => {
83            tracing::warn!(error = %e, "init-control: no pude crear FIFO; INIT del bus fallará");
84            false
85        }
86    };
87
88    // Watcher fanotify: opcional. Si falla (no caps), el daemon sigue con el bus solo.
89    if !args.no_watcher {
90        let journal = hammer_journal::Journal::open(&args.journal)?;
91        let mut dirs = watcher::default_watch_dirs();
92        dirs.extend(args.extra_watch.clone());
93        let overlay_root = PathBuf::from(&args.overlay_state_root);
94        let bus_for_watcher = event_bus.clone();
95        match watcher::Watcher::start_with_events(
96            &dirs,
97            journal,
98            Some(overlay_root),
99            Some(bus_for_watcher),
100        ) {
101            Ok(w) => {
102                tracing::info!("watcher fanotify activo");
103                thread::Builder::new()
104                    .name("watcher".into())
105                    .spawn(move || {
106                        if let Err(e) = w.run_forever() {
107                            tracing::warn!(error = %e, "watcher: terminó con error");
108                        }
109                    })
110                    .ok();
111            }
112            Err(e) => {
113                tracing::warn!(
114                    error = %e,
115                    "watcher fanotify NO arrancó; daemon sigue (sin diario en este run)"
116                );
117            }
118        }
119    } else {
120        tracing::info!("--no-watcher: omitiendo fanotify");
121    }
122
123    // Bus de agente: bloqueante en este thread. Si falla, fin del daemon.
124    let policy = bus::default_policy();
125    let ctx = bus::BusContext {
126        store_root: args.store,
127        init_control: if init_fifo_ok {
128            args.init_control
129        } else {
130            // Apuntamos a un path inexistente: cualquier INIT fallará con "init_send_failed"
131            // y un mensaje claro. Mejor que callar.
132            PathBuf::from("/dev/null/init-control-missing")
133        },
134        events: event_bus,
135    };
136    bus::serve_agent_bus(std::path::Path::new(&args.agent_sock), policy, ctx)?;
137    Ok(())
138}