e7b77ff657c6ac09ac700c003290b47f081f1e53
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f2bffd9e4b |
planear: cruzar la config contra las decisiones — y los puertos se adjudicaban por NOMBRE
La config del servidor declara de qué servicios depende: cada `reverse_proxy` y cada `php_fastcgi` apuntan a algo. Cruzarlo contra la decisión tomada sobre cada servicio caza un fallo que ninguna otra parte ve: **el dominio se MUDA y el servicio que lo sirve está marcado MUERE**. Las dos decisiones son razonables por separado y juntas dejan el sitio nuevo devolviendo 502 — no lo ve el DNS, no lo ven los procesos, y no lo ve quien decide de a una entrada por vez, que es como se decide. En gioser apareció el revés: dos sitios proxean a un puerto que NINGÚN servicio censado sirve, o sea que ya devuelven 502 hoy, en el origen — `mail.sigma.gioser.net` → :9000 y `api.gioser.net` → :8000. Confirmado aparte con `ss -lntp`: no hay nada escuchando en ninguno de los dos. **Y un falso positivo que hubo que matar primero.** La primera corrida acusaba a `sergio.gioser.net` de mudarse dejando atrás a `shuma`. Falso, y la causa estaba en el CENSO: los puertos se adjudicaban por prefijo de nombre (`proc.startswith(name[:15])`), así que un `shuma` DECLARADO-MUERTO se quedaba con el 7378 — que lo escucha `shuma-gateway` (pid 294, verificado con `ss -lntp`). Un declarado-muerto por definición no puede estar escuchando. Ahora se atan por PID, que es lo único sin ambigüedad, con caída al nombre sólo para servicios VIVOS cuando `ss` no da el pid. El puerto es la señal más fuerte de que algo sirve, así que colgárselo al servicio equivocado envenena todo lo que se derive de él — acá se derivó un guardián acusando al inocente, que es la forma más rápida de que un guardián se deje de leer. De paso, el lector de Caddy entiende `php_fastcgi` (antes caía en «directiva no reconocida») y registra a dónde proxea cada sitio, incluidos los bloques anidados. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
0f3b137578 |
censar: leer la config del servidor web — cinco sitios sirven un root que NO EXISTE
La sonda DNS dice si un dominio resuelve; no si el servidor tiene algo que servirle. Eso lo dice el
FICHERO DE CONFIGURACIÓN, y leerlo no toca al origen: ni una petición, ni riesgo de fail2ban.
Para leerlo se agregó un LECTOR de Caddy al centro (`formatos/caddy.py` ya tenía el escritor), y el
censo lo usa en vez de tener su propio parser a medias — los de nginx y apache ya existían, y dos
parsers del mismo formato es cómo se separan sin que nadie lo note. Cuarto par de la familia web.
Sobre el Caddyfile real de gioser, cinco sitios apuntan a un `root` que no existe — y son justo los
que devolvían los 502 que en su día hicieron que el censo SE BANEARA A SÍ MISMO al sondearlos:
aura.gioser.net → /var/www/aura_frontend · sigma → /var/www/sigma/frontend
summa → /var/www/summa/frontend · kosmofono → … · dev.summa → …
Es evidencia MÁS FUERTE que el DNS: no hay nada que servir, devuelve 502 resuelva donde resuelva. Va
como recomendación `muere` con la ruta y el fichero donde está el bloque.
**Y sirve para lo contrario, que es donde el aviso hacía daño.** Un directorio que la config SÍ
referencia no es huérfano: el plan marcaba `/var/www/git-tawasuyu` como «nadie lo recuerda» estando
servido, y ese aviso aplicado tira `git.tawasuyu.net`.
Dos bugs propios, los dos encontrados contra el fichero real y no sobre un ejemplo mío:
· El `root` de ese sitio vive DENTRO de un `handle`, y yo saltaba los bloques anidados enteros por no
saber modelarlos. Que el pivote no sepa MODELAR algo no es razón para no VERLO: el bloque sigue
marcado SIN-TRADUCIR pero sus `root`/`reverse_proxy` se leen. Con eso aparecieron dos raíces
ausentes más, también anidadas.
· Detectar la cabecera de sitio con una lista negra de directivas dejaba pasar `log { output file … {`
y el snippet `(acceso) {`: DOS dominios inventados que el censo habría puesto a decidir. La regla
que aguanta es positiva — todos los tokens de la cabecera tienen que PARECER una dirección.
Sin regresión: `nginx → caddy` y `apache → caddy` siguen dando `Valid configuration` con el caddy del
corpus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
d0e3aadb12 |
declarar: /proc pasa a ser un lector del centro — y había DOS emisores de tarjetas, mal los dos
`declarar.py` tenía su propio molde de card de arje y `formatos/arje.py` el suyo: el N×M que el
pivote existe para evitar, adentro de mi propio código. Y no quedó en teoría — CADA COPIA TENÍA UN
CAMPO MAL DE LA RAÍZ, Y NINGUNO DE LOS DOS EL MISMO:
campo declarar.py arje.py semilla REAL del producto
provides ["Spawn","Journal"] ✓ [] ✗ ["Spawn","Journal"]
supervision Restart{…} ✗ "OneShot" ✓ "OneShot"
Las consecuencias son concretas: sin `Spawn`/`Journal` las hijas no tienen quién las lance ni dónde
escribir, y una card `Virtual` con `Restart` le pide a arje que respawnee algo que nunca corrió.
Ahora hay un lector `proc` (el censo es un formato de origen, igual que systemd u OpenRC) y el
escritor `arje` es el ÚNICO que emite tarjetas; `declarar.py` queda con lo suyo, el perfil. Tres
lectores en la familia: systemd y openrc leen LO DECLARADO —que es lo que miente, `rc-status` daba
`stopped` para cinco servicios vivos— y `proc` lee LO QUE CORRE, que es donde aparecen los 15
`no-declarado`.
Dos cosas más, las dos sobre no mentir:
· **Una semilla vacía parecería un éxito.** Si ningún servicio tiene `decision = "muda"`, el lector
lo DICE y sale ≠0 en vez de emitir cero tarjetas en silencio. Mismo modo de fallo que un artefacto
vacío en el store.
· **El acta se ahogaba en su propio ruido.** Anotaba el `envp` vacío una vez POR SERVICIO: 26 líneas
idénticas que tapaban los dos hallazgos reales (`shuma-daemon` corre como `sergio`; los que no
tienen cmdline). La limitación es del lector y vale para todos ⇒ una entrada nombrando a los 26.
Un acta donde casi todo es la misma línea se deja de leer, y entonces no queda ningún acta. Y al
revés: las entradas que SÍ son por servicio ahora lo nombran (`gitea: cwd=/var/lib/gitea`).
Controles: la raíz generada coincide campo por campo con la del producto; dos corridas dan el fichero
byte a byte idéntico; 26 tarjetas con 26 ids únicos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
61c33800c5 |
traducir: lector de OpenRC — el par que cierra el camino de gioser, y casi la mitad NO se puede leer
`openrc → arje` sin escribir ningún traductor de ese par: cuarto plugin, cuarto par. OpenRC es el init de gioser, la máquina que esta mudanza termina borrando. **El hecho que manda acá: un servicio de OpenRC es un PROGRAMA, no una declaración.** Un unit de systemd se lee; un script de OpenRC puede hacer cualquier cosa antes de arrancar nada. Medido sobre el `/etc/init.d` real: **60 de 121 definen su propia `start()`/`stop()`**. En ésos no hay `command=` que valga — leer las variables y emitir tarjeta daría un servicio que arranca OTRA COSA. Regla al revés que en systemd: `start()` propia ⇒ SIN-TRADUCIR y NO se emite tarjeta. Los números del corpus real cierran solos: 121 − 1 (no es openrc-run) − 60 (start propia) − 1 (sin `command=`) = 59, y el lector emitió exactamente 59, con 59 ids únicos. Dos cosas que OpenRC obliga a ir a buscar fuera del script: `/etc/conf.d/<x>`, donde viven los argumentos de verdad (a diferencia del `EnvironmentFile=` de systemd, éste SÍ está en la máquina: se lee y se aplica), y `/etc/runlevels/`, que es lo único que dice si el servicio arranca solo. **Tres defectos que sólo aparecieron corriendo contra las 121 de verdad**, no sobre un ejemplo mío: - `name=` no es un identificador sino un rótulo humano: en gioser vale «Aura Backend», con espacio, y se iba al id de la tarjeta y al path del cgroup. El identificador es el nombre del fichero. - Ids repetidos: `/etc/init.d` guarda copias `*.bak-FECHA` junto a los servicios vivos y son scripts válidos; dos con el mismo nombre dan el MISMO id determinista ⇒ semilla con dos cards homónimas. El escritor lo detecta y no emite la segunda. - La tarjeta decía `"desde": "systemd"` viniendo de OpenRC: el campo que existe para saber de dónde salió algo era justo el que mentía. Estaba cableado. Y el centro deja de filtrar la entrada por extensión: los servicios de OpenRC no tienen ninguna, y filtrar en el centro es que lo que no entra se pierda EN SILENCIO. Filtra el lector, que sabe, y lo anota — así un `.bak` en `/etc/init.d` se REPORTA en vez de desaparecer. Controles: dos corridas dan el fichero byte a byte idéntico; los tres pares previos sin regresión (`nginx → caddy` sigue dando `Valid configuration`, `systemd → arje` sigue emitiendo sus 3 tarjetas). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
fefa92ec5a |
traducir: familia «servicio» — systemd → tarjeta de arje, y el pivote pasa a ser uno POR FAMILIA
Un unit de systemd no es un sitio web. Meterlo en `Sitio`/`Ruta` sería justo el «parecerse» que el acta existe para evitar, así que cada familia tiene su modelo y el centro empareja SÓLO dentro de la familia. Un par cruzado se rechaza con un error, no con un intento: `systemd → caddy` sale por `sys.exit`, porque traducir entre familias daría un fichero que PARECE correcto. Dos familias hoy: `web` (nginx, apache → caddy) y `servicio` (systemd → arje). 3 pares con 5 plugins. **Dónde una elisión silenciosa no pierde una opción sino que CAMBIA el sistema.** El payload `Native` de arje acepta `exec`, `argv` y `envp` y nada más (comprobado en la semilla real del producto): NO hay campo de usuario. Un `User=git` traducido en silencio correría el servicio COMO ROOT — escalación de privilegios en un fichero generado que nadie vuelve a leer. Sale SIN-TRADUCIR con su línea. Igual `EnvironmentFile=` (el fichero no está en la máquina donde se traduce ⇒ envp incompleto, falla tarde), `Type=forking` (arje supervisa al proceso que lanza ⇒ BUCLE DE REINICIO) y todo el endurecimiento, que callado entrega un servicio MENOS confinado que el original. Lo que sí tiene destino exacto: `Type=oneshot` → `"supervision": "OneShot"`, que ya existe en la semilla del producto — buscarlo antes de declararlo intraducible evitó una elisión inventada. **Lector y escritor no opinan del mismo campo.** `User=` lo CAPTURA el lector y lo JUZGA el escritor; cuando los dos anotaban, el acta decía dos cosas distintas de la misma línea, y un acta que se contradice se deja de leer. El lector registra en qué línea vio cada campo (`Servicio.lineas`) para que el escritor señale la línea real en vez de un 0. Controles: la tarjeta generada tiene el MISMO juego de claves que la tarjeta real de `sshd` del producto (+`_mudanza` de procedencia); dos corridas dan el fichero byte a byte idéntico; y la familia `web` sigue validando con el caddy del corpus (`Valid configuration` en los dos pares). De paso, `ulid_determinista` sale a `ids.py`: lo necesitaban `declarar.py` y el escritor de arje, y copiarlo habría dejado dos generadores de id que se pueden separar sin que nada falle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
3137d47f16 |
traducir: CENTRO de traducción con modelo pivote y plugins — N+M en vez de N×M
Traducir de a pares no escala: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y cada formato nuevo agrega 2N. Con un modelo intermedio son N lectores + M escritores: un formato nuevo cuesta uno o dos plugins y estrena todos los pares de golpe. **Probado, no argumentado**: el lector de apache se agregó SIN TOCAR el escritor de Caddy, y con eso `apache → caddy` funciona sin que exista ningún traductor de ese par. Las dos salidas las valida el caddy del propio corpus: `Valid configuration` en las dos. Los plugins se DESCUBREN (`formatos/` + `@lector` / `@escritor`): una lista mantenida a mano se desincroniza y el formato nuevo «no existe» sin que nada falle. `--list` dice qué pares habilita. **El acta viaja DENTRO del modelo**, y es la decisión que hace que el pivote no mienta: si fuera un efecto de cada traductor, lo que el LECTOR no entendió se perdería antes de llegar al escritor. El escritor además puede agregarle — Caddy no tiene equivalente de `location ~`, así que en vez de emitir un `handle` de prefijo que SE PARECE, lo manda al acta. Parecerse es peor que faltar. Y un bug del lector de apache, encontrado probando: `ProxyPass` tiene dos formas y dentro de `<Location>` lleva sólo la URL. Mirar sólo la de tres tokens dejaba sin traducir la forma más común y el acta la reportaba como «no reconocida» — un falso negativo que manda a escribir a mano algo que el lector sí sabe hacer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |