"""Lector del CENSO (lo que `/proc` dice que corre) → modelo pivote «servicio» (SDD 29). El tercer lector de la familia, y el que no tiene equivalente en ningún init: `systemd` y `openrc` leen **lo declarado**, que es exactamente lo que miente — en gioser `rc-status` daba `stopped` para cinco servicios que estaban vivos, y los `no-declarado` (15 de 39) no aparecen en ninguna declaración porque, por definición, nadie los declaró. Éste lee **lo que corre**. Los tres se complementan y ninguno solo alcanza. Que los tres desemboquen en el mismo escritor es justamente el punto del pivote: la tarjeta de arje se emite en UN lugar, no uno por procedencia. ── LO QUE ESTE LECTOR NO PUEDE ───────────────────────────────────────────────────────────────── El censo **no lee `/proc//environ`** — trae tokens y contraseñas, y un censo se guarda en un fichero que se manda por ahí. Así que el `envp` de toda tarjeta sale VACÍO. Un servicio que dependa de variables de entorno va a arrancar y fallar, y eso se dice por su nombre, no en una nota general. """ import sys, os, tomllib sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from modelo import Servicio, Sistema # noqa: E402 from formatos import lector # noqa: E402 @lector("proc", familia="servicio") def leer(texto, nombre=""): sis = Sistema(formato_origen="proc") acta = sis.acta censo = tomllib.loads(texto) servicios = censo.get("servicio", []) if not servicios: acta.anota(0, "SIN-TRADUCIR", nombre or "censo", "el censo no tiene ningún `[[servicio]]`: ¿es el fichero correcto?") return sis decididos = [s for s in servicios if s.get("decision") == "muda"] if not decididos: # Salir con cero tarjetas y código 0 sería una semilla VACÍA que parece un éxito. Es el mismo # modo de fallo que un artefacto vacío en el store: un ausente falla ruidosamente, un vacío # llega hasta el final diciendo que todo fue bien. acta.anota(0, "SIN-TRADUCIR", nombre or "censo", f"ninguno de los {len(servicios)} servicios tiene `decision = \"muda\"`. La " "semilla saldría VACÍA y parecería un éxito: decidí primero (`planear.py --decide`)") return sis for s in decididos: srv = Servicio(nombre=s["name"], tipo="simple") srv.cmd = (s.get("cmdline") or "").strip() srv.cwd = (s.get("cwd") or "").strip() srv.usuario = (s.get("usuario") or "").strip() if srv.usuario: srv.lineas["usuario"] = 0 # Está corriendo y se decidió mudarlo ⇒ se quiere arriba. Es una decisión, y se anota como # tal: el censo no dice si estaba en el arranque, dice que está VIVO ahora. srv.arranque_auto = True srv.reinicio = "siempre" if not srv.cmd: acta.anota(0, "SIN-TRADUCIR", s["name"], "no se pudo leer su línea de comando en el origen: NO se emite tarjeta. Una " "tarjeta que no arranca es peor que una ausente — la ausente falla ruidosamente " "al primer arranque, la rota deja el servicio caído sin que nada avise") continue if s.get("ports"): acta.anota(0, "traducido", f"{s['name']} sirve {', '.join(str(p) for p in s['ports'])}") sis.servicios.append(srv) # ⚠ UNA entrada, no una por servicio. La limitación es del LECTOR y vale igual para todos, así # que repetirla 26 veces no informa 26 veces: tapa los hallazgos que sí son de un servicio # concreto (el que corre como `sergio`, el que no tiene cmdline). Un acta en la que casi todo es # la misma línea se deja de leer, y entonces no queda ningún acta. if sis.servicios: acta.anota(0, "decision", f"supervisión de las {len(sis.servicios)} tarjetas", "el censo dice que CORREN, no si el origen las levantaba al arrancar; se declaran " "con reinicio automático porque se decidió mudarlas. Sacá del `genesis` las que no " "quieras al boot") acta.anota(0, "SIN-TRADUCIR", f"envp VACÍO en las {len(sis.servicios)} tarjetas", "el censo NO lee `/proc//environ` (trae tokens), así que ninguna tarjeta " "lleva entorno. El que dependa de variables arranca y falla: " + " ".join(x.nombre for x in sis.servicios)) return sis