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:
@@ -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.
|
||||
Reference in New Issue
Block a user