diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md index e0f7e7c9..93f14b73 100644 --- a/docs/28-servidor-de-produccion.md +++ b/docs/28-servidor-de-produccion.md @@ -994,6 +994,54 @@ el mismo día. Es la diferencia entre una receta «sellada» y una receta *usada --- +### 6.11 🗄️ La imagen del perfil, con gitea, ARRANCA y SIRVE *(2026-09-14)* + +`servidor-image.sh` + QEMU: **PID 1 = `arje-zero`, gitea encarnado por él (`ppid=1`), corriendo como +`uid=916`, escuchando en :3000 y devolviendo `200` con `takana git`**, con su +`gitea.db` creada por él mismo. Es el primer servicio de PAQUETE que arranca en una imagen de takana: +los que había venían horneados en el bootstrap. + +La cadena entera, eslabón por eslabón: `[[user]]` → `/etc/passwd` de la imagen · `[[service]]` → +`takana service-cards` → `genesis` de la seed → arje lo encarna → `setuidgid` → sirve. + +#### Los tres fallos que sólo aparecieron AL PROBAR LA IMAGEN + +Los tres pasan el sellado, el `hash`, el resolutor de servicios y el armado. Ninguno se ve sin +arrancar la imagen. + +1. **⚠⚠ Escribir en el rootfs hidratado es escribir DENTRO del store.** `takana users --merge` hacía + `fs::write` sobre `/etc/passwd`, y ese fichero y el del artefacto `product-rootfs` son + **el mismo inode** (medido: 1225824, `2 links`, modo `444`) — el rootfs se arma con hardlinks. + Habría mutado un artefacto sellado, y toda imagen futura habría salido con la cuenta metida dentro + del producto. Se salvó porque el store es de sólo lectura y salió `Permission denied`: confiar en + eso es confiar en un permiso. Ahora escribe por temporal + `rename`, con un control que comprueba + que el fichero del store conserva su contenido y baja a 1 link. + +2. **La imagen traía el binario, la cuenta… y nadie lo arrancaba.** El `genesis` sólo tenía lo que + hornea `takana-bootstrap` (`sshd`, `console-getty`, `hammerd`): `servidor-image.sh` **no inyectaba + las Cards** — eso sólo existía en el camino de las imágenes de escritorio. Y ninguna métrica lo + dice: `targets.py --services` responde que el perfil lo habilita, y lo habilita; lo que faltaba era + el paso que lleva esa declaración a la imagen. + +3. **⚡ `trap invalid opcode`: el binario sólo corría en la CPU que lo compiló.** Con la card ya en el + genesis, gitea moría al instante dentro de QEMU-TCG. El sandbox exporta `CC` apuntando a un wrapper + con `-mcpu=baseline` (y su comentario ya avisaba: «de paso cierra el SIGILL de AVX en qemu64»), y + **la fase Go del propio builder lo pisaba** con `CC="zig cc"` a secas, que es `-mcpu=native`. No + falla al compilar ni al sellar: falla al ejecutar en otra CPU. Y el `ArtifactHash` no puede + cazarlo, porque la CPU del builder no entra en `hash_inputs` — **dos workers distintos sellan bytes + distintos bajo la misma dirección**. Arreglado en el builder y en la receta; re-hashea las cinco + recetas `cgo = true` (`gitea`, `usql`, `sq`, `gocryptfs`, `naabu`). + +#### Lo que NO está probado, y falta para la mudanza + +El `app.ini`, el usuario del sitio y los datos se pusieron **a mano** dentro de la VM para llegar al +200. Eso es exactamente lo que el plan de mudanza tiene que traer de gioser — `/etc/gitea` ya está en +`scripts/mudanza/rutas-fuera-de-git.txt`. + +Y **la imagen no trae `arjectl`**: tras poner la config hubo que REINICIAR para que arje volviera a +intentarlo. El backoff de `restart` se agota y no hay forma de pedirle a PID 1 que relance un ente en +caliente. Para una caja de producción eso es una pieza que falta, no una comodidad. + ## 7. Reusar los scripts que ya existen, y no escribir de nuevo Pedido explícito del usuario. El inventario de lo que ya hace el trabajo: