SDD 29 + censar.py: la mudanza como PRODUCTO — censo, plan, aplicación. Sin IA.

Corrección de rumbo pedida por el usuario: el SDD 28 derivó hacia «poner ESTA caja a punto» —
instalar caddy, copiar claves, montar discos—, y eso es configurar un servidor, no construir algo.
**caddy no es un hueco de la imagen: es una instalación particular del usuario, y el programa tiene
que DESCUBRIRLA, no traerla.**

Y la consecuencia de método: todo lo que hice a mano en el SDD 28 ES la especificación de este
programa. Cada paso fue determinista; el único juicio fue «¿esto se muda o muere?», que es justo lo
que se le pregunta al usuario. No hace falta IA: hace falta que esté escrito.

**Etapa 1 implementada: `scripts/mudanza/censar.py`.** Sólo lee. Cruza lo DECLARADO contra lo VIVO y
reporta tres clases, cada una justificada por un error medido:

- `rc-status` decía `stopped` de cinco servicios que estaban VIVOS ⇒ la verdad es `ppid==1`, no el init.
- De 29 dominios, 10 vivos: 13 sin DNS, 2 en 502 y **4 que ya resuelven a otra máquina** — por eso se
  compara la IP del DNS contra las de la máquina, o `ya-mudado` se lee como `vivo`.
- `du -sh` no dice cuánto ocupa COPIAR con hardlinks (60 G vs 85 G): se reportan los dos.
- Los nombres no coinciden (`act-runner`/`act_runner`, `crond`/`cronie`, `dbus-daemon`/`dbus`): sin
  normalizar, el mismo servicio sale a la vez como `no-declarado` y `declarado-muerto`.
- Lo descartado se CUENTA: un `ppid==1` que no es servicio suele ser un huérfano, y eso es hallazgo.

Probado contra gioser, donde las respuestas ya se sabían a mano: encuentra MÁS (29 dominios contra
los 19 que probé; apareció `hifas.gioser.net`). Y afinarlo importó: la primera versión daba 28
`no-declarado` con ruido, la segunda da 18 y son reales (`matilda`, `pacha`, `puerta-…`, `adb`).
Un guardián con hallazgos falsos se ignora entero.

El SDD deja escritas las etapas 2 (plan exportable: el fichero ES la interfaz) y 3 (aplicación
idempotente que verifica EN DESTINO y deja el server corriendo), y mide qué ata la imagen a Hetzner:
menos de lo que parece — BIOS vs UEFI y los metadatos de red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
This commit is contained in:
Sergio
2026-09-11 14:51:08 +00:00
co-authored by Claude Opus 5
parent 62f1c2b198
commit 95c16097e4
2 changed files with 550 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
# SDD 29 — La mudanza: imagen portable, pasos exportables, y una app que censa, confirma y ejecuta
Escrito 2026-09-11, a pedido del usuario, corrigiendo el rumbo del SDD 28:
> «quiero que estas máquinas sean un ejemplo. Tú dices "falta caddy", pero caddy es una instalación
> mía particular. Quiero que quede una imagen instalable siempre desde Hetzner o cualquier otro
> servicio, poder ejecutar una instalación paso a paso y que los pasos sean exportables para
> reproducirlos en cualquier lado; y que si estoy mudando, la app mudadora detecte qué servicios hay,
> qué contenido, confirme con el usuario qué se muda y qué se borra, y deje al servidor nuevo
> corriendo. Todo automático, sin IA.»
## 0. La corrección que ordena todo
El SDD 28 derivó hacia «poner ESTA caja a punto»: instalar caddy, copiar claves, montar discos. Eso
es configurar un servidor, no construir un producto. **caddy no es un hueco de la imagen: es una
instalación particular del usuario, y el programa tiene que DESCUBRIRLA, no traerla.**
La imagen lleva lo que hace a un sistema takana. Todo lo demás —qué servicios corren, qué dominios
sirven, qué datos pesan— **se censa en la máquina vieja**, se confirma con el usuario, y se ejecuta.
Y hay una consecuencia de método que conviene decir fuerte: **todo lo que hice a mano en el SDD 28 es
la especificación de este programa.** Censar, cruzar declaración contra realidad, separar vivo de
fósil, confirmar, copiar, verificar. Cada paso fue determinista; ninguno necesitó juicio salvo
«¿esto se muda o muere?», que es justamente lo que se le pregunta al usuario. **No hace falta IA: hace
falta que esté escrito.**
## 1. Las tres etapas
```
CENSO PLAN APLICACIÓN
censar.py → <censo>.toml (decisiones) → ejecutar, idempotente
sólo lee revisable · exportable deja el server corriendo
```
El fichero del medio es el producto real: **es «los pasos exportables»**. Se revisa, se versiona, se
lleva a otro proveedor, se vuelve a correr. Un plan que se ejecutó es también el registro de lo que
se hizo.
## 2. Etapa 1 — el censo *(implementado: `scripts/mudanza/censar.py`)*
**La regla que lo ordena: cruzar lo DECLARADO contra lo VIVO**, y reportar tres clases:
| clase | qué es | por qué importa |
|---|---|---|
| `vivo` | declarado **y** corriendo | candidato a mudarse |
| `declarado-muerto` | declarado, sin proceso | candidato a morir |
| **`no-declarado`** | **corriendo, sin declaración** | **el trabajo real: nadie sabe cómo levantarlo** |
No es teoría — cada regla salió de un error medido:
- **`rc-status` decía `stopped` de caddy, gitea, sshd, cronie y act-runner, y los cinco estaban
vivos.** Un censo que lea el init y no `/proc` produce un plan que no arranca.
- **De 29 dominios del Caddyfile, sólo 10 están vivos**: 13 no tienen DNS, 2 dan 502, y **4 ya
resuelven a otra máquina** — su bloque de config es fósil aunque el sitio funcione. Por eso el
censo compara la IP del DNS contra las IPs de la máquina: sin eso, `ya-mudado` se lee como `vivo`.
- **El caddy de la caja nueva murió en un reinicio y nadie lo notó**, porque estaba arrancado a mano
y no declarado. Ésa es la clase `no-declarado`, y es la que duele.
- **`du -sh` no dice cuánto ocupa COPIAR** un árbol con hardlinks (medido: 60 G vs 85 G). El censo
reporta los dos números.
- **Los nombres no coinciden entre init y proceso**: `act-runner`/`act_runner`, `crond`/`cronie`,
`dbus-daemon`/`dbus`, `fail2ban-server`/`fail2ban`. Sin normalizar, el mismo servicio sale a la vez
como `no-declarado` y `declarado-muerto`: las dos clases que más importan, las dos mal.
- **Lo descartado se cuenta, no se esconde.** Un proceso con `ppid==1` que no es un servicio suele
ser un huérfano, y eso es un hallazgo (los `firefox` fugados que dejaron la granja sin compilar
hora y media fueron exactamente eso).
**Y el criterio que decide si el censo sirve**: un guardián con hallazgos falsos se ignora entero.
La primera versión daba 28 `no-declarado` con ruido; tras normalizar nombres da 18, y son reales
(`matilda`, `pacha`, `puerta-…`, `adb`, `ModemManager`). Afinarlo no es cosmética: es la diferencia
entre que alguien lo lea o no.
## 3. Etapa 2 — el plan *(pendiente)*
El censo emite cada entrada con `decision = ""`. El plan es ese fichero con las decisiones puestas
(`muda` / `muere`), más el orden y las dependencias. Requisitos:
1. **Nada sin decidir se ejecuta.** Una entrada vacía aborta: el silencio no es consentimiento.
2. **Lo que muere se dice por su nombre**, con su tamaño, antes de borrar nada.
3. **El plan es exportable y re-ejecutable** en otro proveedor. Es el artefacto que el usuario pidió.
4. **Un TUI para llenarlo**, y también editable a mano — el fichero es la interfaz, el TUI es comodidad.
## 4. Etapa 3 — la aplicación *(pendiente)*
Ejecuta el plan contra la máquina nueva. Requisitos, todos pagados en el SDD 28:
1. **Idempotente y reanudable.** Cada copia grande se cortó al menos una vez.
2. **Verifica en DESTINO, no en origen.** «Lo copié» no prueba nada: el `rsync` que llenó el disco
dejó 1367 artefactos vacíos y sólo se vio contando en el destino.
3. **Los servicios `no-declarado` se declaran**, no se replican a mano. Es la salida natural hacia
`arje-absorb`, que ya traduce sysvinit/runit/dinit/openrc a una Semilla — pero **leyendo la
declaración, que es justo la que miente**: hay que alimentarlo con el censo.
4. **Deja el servidor corriendo**, y lo prueba desde afuera: cada dominio `vivo` del plan tiene que
contestar 200 contra la IP NUEVA antes de dar la mudanza por hecha.
## 5. La imagen, independiente del proveedor
Lo que hoy la ata a Hetzner es poco y está medido:
- **Arranque**: la imagen es GPT+BIOS. Hetzner Cloud arranca BIOS; otros proveedores piden UEFI. Los
dos caminos existen (`install-image.sh` / `install-image-efi.sh`); falta **una imagen que haga las
dos** (ESP + BIOS boot en el mismo disco) para no elegir por proveedor.
- **Red**: `netup` hace DHCPv4 y ya cubre el caso difícil (IP `/32` con la puerta fuera del prefijo,
que es lo de Hetzner). Falta IPv6 estático y los metadatos de otros proveedores.
- **Instalación**: `rescue → dd → reboot` no es de Hetzner: sirve en cualquier entorno de rescate con
SSH. Lo único propio es cómo se ENTRA a ese rescate, que es una línea por proveedor.
- **Disco**: ya se resuelve solo — el store es la última partición y crece al disco entero en el
primer arranque.
**la imagen está más cerca de ser portable que la herramienta de mudarse.** Por eso el orden es
censo → plan → aplicación, y la portabilidad de la imagen va en paralelo.