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…
⎇ 98f0b2d228ffebb06941dfc1f436167ab40d8f21
crates/hammerd/src/main.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(arje-link): transporte arje-bus → bus de agente, cierra B.2 end-to-end
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 arje_link;
16mod bus;
17mod control;
18mod crashes;
19mod events;
20mod watcher;
21
22#[derive(Parser)]
23#[command(name = "hammerd", version, about = "Daemon de hammer: bus de agente + diario.")]
24struct Args {
25 /// Socket del bus de agente.
26 #[arg(long, default_value = "/run/agent.sock")]
27 agent_sock: String,
28 /// FIFO de control humano del init.
29 #[arg(long, default_value = "/run/init.control")]
30 init_control: PathBuf,
31 /// Directorio del diario de mutaciones.
32 #[arg(long, default_value = "/var/lib/hammer/journal")]
33 journal: String,
34 /// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
35 /// activos). Default coincide con `hammer try`.
36 #[arg(long, default_value = hammer_overlay::DEFAULT_STATE_ROOT)]
37 overlay_state_root: String,
38 /// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
39 #[arg(long = "watch")]
40 extra_watch: Vec<PathBuf>,
41 /// Store CAS donde sellar artefactos de COMPILE.
42 #[arg(long, default_value = "/store")]
43 store: PathBuf,
44 /// Modo dev: desactiva el watcher fanotify (útil cuando hammerd corre sin CAP_SYS_ADMIN
45 /// y sólo queremos probar el bus). Igual que arrancar sin permisos, pero explícito.
46 #[arg(long)]
47 no_watcher: bool,
48 /// Fichero de política de capacidades del bus. Si existe, sustituye a la política por
49 /// defecto (misma-UID ⇒ casi todo; otros ⇒ query). Ver `docs/07-agent-bus.md` §4.
50 #[arg(long, default_value = "/etc/hammer/agent-caps.toml")]
51 agent_caps: PathBuf,
52}
53
54fn main() -> anyhow::Result<()> {
55 tracing_subscriber::fmt()
56 .with_writer(std::io::stderr)
57 .with_env_filter(
58 tracing_subscriber::EnvFilter::try_from_default_env()
59 .unwrap_or_else(|_| "info".into()),
60 )
61 .init();
62
63 let args = Args::parse();
64 tracing::info!(
65 agent_sock = %args.agent_sock,
66 init_control = %args.init_control.display(),
67 journal = %args.journal,
68 store = %args.store.display(),
69 "hammerd: arranque"
70 );
71
72 let event_bus = events::EventBus::new();
73
74 // Puente con el bus del init (arje): si ENTE_BUS_SOCK está definido, se suscribe a los
75 // eventos de ciclo de vida y publica los crashes en el bus de agente. No-op si no hay init.
76 arje_link::spawn_if_configured(event_bus.clone());
77
78 // FIFO de control humano: lo creamos siempre que se pueda (no es fatal).
79 let init_fifo_ok = match control::ensure_fifo(&args.init_control) {
80 Ok(path) => {
81 let reader_path = path.clone();
82 thread::Builder::new()
83 .name("init-ctl-reader".into())
84 .spawn(move || {
85 if let Err(e) = control::run_reader(&reader_path) {
86 tracing::warn!(error = %e, "init-ctl-reader: terminó");
87 }
88 })
89 .ok();
90 true
91 }
92 Err(e) => {
93 tracing::warn!(error = %e, "init-control: no pude crear FIFO; INIT del bus fallará");
94 false
95 }
96 };
97
98 // Watcher fanotify: opcional. Si falla (no caps), el daemon sigue con el bus solo.
99 if !args.no_watcher {
100 let journal = hammer_journal::Journal::open(&args.journal)?;
101 let mut dirs = watcher::default_watch_dirs();
102 dirs.extend(args.extra_watch.clone());
103 let overlay_root = PathBuf::from(&args.overlay_state_root);
104 let bus_for_watcher = event_bus.clone();
105 match watcher::Watcher::start_with_events(
106 &dirs,
107 journal,
108 Some(overlay_root),
109 Some(bus_for_watcher),
110 ) {
111 Ok(w) => {
112 tracing::info!("watcher fanotify activo");
113 thread::Builder::new()
114 .name("watcher".into())
115 .spawn(move || {
116 if let Err(e) = w.run_forever() {
117 tracing::warn!(error = %e, "watcher: terminó con error");
118 }
119 })
120 .ok();
121 }
122 Err(e) => {
123 tracing::warn!(
124 error = %e,
125 "watcher fanotify NO arrancó; daemon sigue (sin diario en este run)"
126 );
127 }
128 }
129 } else {
130 tracing::info!("--no-watcher: omitiendo fanotify");
131 }
132
133 // Bus de agente: bloqueante en este thread. Si falla, fin del daemon.
134 // Política de caps: del fichero si existe, si no la heurística por-UID por defecto.
135 let policy = match hammer_core::AgentCapsConfig::load(&args.agent_caps) {
136 Ok(Some(cfg)) => {
137 tracing::info!(path = %args.agent_caps.display(), rules = cfg.rule.len(),
138 "bus: política de caps cargada de fichero");
139 bus::policy_from_config(cfg)
140 }
141 Ok(None) => {
142 tracing::info!(path = %args.agent_caps.display(),
143 "bus: sin fichero de caps; uso la política por defecto (por-UID)");
144 bus::default_policy()
145 }
146 Err(e) => {
147 // Config presente pero inválida: fallar cerrado sería más seguro, pero romper el
148 // arranque del daemon por un typo es peor en la práctica. Avisamos fuerte y caemos
149 // a la política por defecto (restrictiva para no-dueños).
150 tracing::error!(path = %args.agent_caps.display(), error = %e,
151 "bus: fichero de caps inválido; uso la política por defecto");
152 bus::default_policy()
153 }
154 };
155 let ctx = bus::BusContext {
156 store_root: args.store,
157 init_control: if init_fifo_ok {
158 args.init_control
159 } else {
160 // Apuntamos a un path inexistente: cualquier INIT fallará con "init_send_failed"
161 // y un mensaje claro. Mejor que callar.
162 PathBuf::from("/dev/null/init-control-missing")
163 },
164 events: event_bus,
165 };
166 bus::serve_agent_bus(std::path::Path::new(&args.agent_sock), policy, ctx)?;
167 Ok(())
168}