From c599daa346b4d294fb6c62bdc6dffb4294cf982c Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 9 Sep 2026 22:53:55 +0000 Subject: [PATCH] =?UTF-8?q?SDD=2028:=20el=20servidor=20de=20producci=C3=B3?= =?UTF-8?q?n=20=E2=80=94=20y=20`perfil.servidor`=20declarando=20la=20deuda?= =?UTF-8?q?=20que=20no=20se=20ve?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El frente arranca con el inventario MEDIDO de gioser, no con un plan: PID 1 es `arje-zero` (nuestro init ya gobierna un servidor real, aunque sobre userland Artix), Hetzner Cloud arranca por **BIOS** —que es justo el layout que `takana-install.sh` ya escribe— y el kernel propio trae VIRTIO/EXT4 `=y` con `netup` haciendo DHCPv4, que es exactamente el caso de una caja hcloud. Dos hallazgos que cambian el trabajo: - **La secuencia de arranque de gioser no está escrita en ningún lado.** `rc-status` dice `stopped` para caddy, gitea, sshd, cronie y act-runner y los cinco están VIVOS colgando de PID 1. Un `arje-absorb --from openrc` produciría una Semilla perfecta que no levanta el servidor. - **La mitad del Caddyfile describe un servidor que ya no existe**: 4 de 19 dominios sin DNS y sin ficheros en `/var/www`, uno en 502. Una mudanza fiel copia también la basura ⇒ la regla queda escrita: no se muda lo que no responde. `[perfil.servidor]` hereda `cli` (SDD 27 §7.1) y declara 5 raíces que existen (openssh, caddy, curl, wget, tmux) y **5 que NO** (takana, chrony, nftables, cronie, logrotate). Quedan `wanted`, y `wanted` no es `debt` ⇒ `drenaje.json` seguirá diciendo `deuda=0`. Se declaran igual: sin ellas el perfil saldría N/N —completo y verde— describiendo un servidor sin hora, sin cortafuegos y sin latido. Es la lección de `foot` en escritorio-sway, pagada por adelantado. Cuenta esperada: 5, verificada resolviendo cada nombre contra el campo `name` de las 869 recetas. La peor de esas cinco: **takana no tiene receta**. El corpus construye 869 y no la suya, así que el host no puede instalar takana desde el repo de takana — el bloqueante de que el servidor se sirva a sí mismo. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x --- docs/28-servidor-de-produccion.md | 351 ++++++++++++++++++++++++++++++ docs/state/targets.toml | 59 +++++ 2 files changed, 410 insertions(+) create mode 100644 docs/28-servidor-de-produccion.md diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md new file mode 100644 index 00000000..5b627043 --- /dev/null +++ b/docs/28-servidor-de-produccion.md @@ -0,0 +1,351 @@ +# SDD 28 — El servidor de producción: instalar takana puro en una caja remota y mudarse a ella + +Escrito 2026-09-09 a pedido del usuario («qué tan lejos estamos de montar un server takana en +Hetzner y que sea producción, y mover allá todo lo que tengo acá, y de una vez mantenerlo por repo +takana»). Todo número marcado *(medido)* salió de un comando corrido ese día sobre `gioser`. + +Continúa [SDD 19](19-lanzamiento-publico.md) (lanzamiento público) y +[SDD 27](27-perfiles-instalables-y-el-instalador.md) (metapaquetes e instalador). **No repite el +instalador**: SDD 27 decide QUÉ se instala y con qué verbo; esto decide DÓNDE, y cómo se llega. + +--- + +## 0. La corrección de encuadre: ya estamos en Hetzner + +`gioser` **es** un servidor Hetzner (hcloud id `126324694`, hel1, 4 cores / 7,6 GiB) *(medido)*. La +pregunta no era «cómo llegar a Hetzner» sino **cómo tener una caja de takana** en vez de ser un +inquilino más de una máquina compartida que: + +- sirve **19 dominios** por Caddy, +- tiene `/mnt/vvv` al **89 %** (27 G libres) *(medido)*, +- es **fija y sin backup** por pedido explícito del usuario (blindaje anti-borrado en `deadman.sh` y + `farm-down.sh`, ver [ADR 0014](adr/0014-distribucion-multiorigen.md) y la memoria del proyecto), +- y **no publica nada de takana**: cero entradas de takana en el Caddyfile *(medido)*. + +### 0.1 El hallazgo que cambia el tamaño del problema + +``` +$ ps -p 1 -o comm=,args= +arje-zero /usr/local/sbin/arje-zero +$ cat /proc/cmdline +BOOT_IMAGE=/vmlinuz-linux root=UUID=54de62df-… rw net.ifnames=0 splash \ + init=/usr/local/sbin/arje-zero loglevel=4 +``` + +**El init de takana ya gobierna un servidor de producción real.** Caddy, gitea, sshd, qdrant y +act-runner cuelgan directo de PID 1 *(medido: `ps -eo pid,ppid,comm | awk '$2==1'`)*. + +Pero **eso no es takana puro**: es Artix con nuestro init inyectado por `init=` en el cmdline. El +kernel, la libc y los 19 servicios son ajenos. Se probó la mitad difícil sobre userland prestado. + +### 0.2 ⚠ La declaración del arranque no existe en ningún sitio legible + +`rc-status` de OpenRC dice `stopped` para **caddy, gitea, sshd, cronie y act-runner**, y los cinco +están **vivos** *(medido, los dos comandos)*. `/etc/arje/cards.d` tiene **4 tarjetas** (`acpid`, +`openrc-openclaw`, `squid`, `zram-swap`) y ninguna de ellas los levanta. + +⇒ **La secuencia de arranque de esta caja no está escrita.** Es lo menos reproducible de toda la +mudanza y lo primero que hay que capturar — ver §4. + +--- + +## 1. Inventario de lo que hay que mudar *(medido 2026-09-09)* + +### Datos en `/mnt/vvv` (217 G de 255) + +| | | +|---|---| +| `tawasuyu` | **124 G** | +| `takana` | **91 G** — incluye el store de 55 G bind-mounteado desde el otro volumen ⇒ ~36 G propios | +| `rustup` | 29 G | +| `act-runner` · `gioserv` · `_respaldo` | 6,5 · 5,4 · 4,1 G | +| ~20 dirs `*-standalone` | agora, card, chasqui, cosmos, dominium, khipu, minga, mirada, nakui, nahual, pata, pineal, pluma, shuma, takiy, llimphi… | + +### El volumen `harkaq-cosecha` (250 G, 148 G usados) + +| | | +|---|---| +| `store` | **55 G**, **1327 artefactos** — lo irreemplazable | +| `work-sources` + `cargo-home` + `gopath` | ~85 G — **caché regenerable**, no se muda | +| `work-tarballs` · `devfs-cache` · `work-out` | 3,2 G · 4,5 G · 397 M | + +**El store no se copia por red.** Los dos volúmenes están en `hel1`; un volumen hcloud se desengancha +de una caja y se engancha a otra en la misma ubicación. La mudanza del store son dos comandos. + +### Servicios vivos, todos hijos de arje-zero + +`caddy` :80/:443 · `gitea` :3002 · `sshd` :2345 · `act_runner` (el CI de tawasuyu) · `qdrant` +:6333/:6334 · `openclaw` · `zeroclaw` · `shuma-gateway` :7378 · `tejido` :4102 y :33097 · +`puerta-f6e393ff` · `uvicorn` :8771 · `python3` :8770 · `squid` :1137 · `fail2ban` · `glances` · +`php-fpm` · `metalog` · **`adb` :5037**. + +### Lo que no está en ningún repo + +`/var/lib/gitea` (**2,2 G**: SQLite + los repos de git de todo, **incluido el `origin` de takana**) · +`/var/www` (287 M) · `/home/sergio/gioser-web/` · `/etc/caddy/Caddyfile` **+ 20 ficheros `.bak`** · +los certificados de letsencrypt · los crontabs · `/etc/arje/cards.d` · las 3 credenciales de +`~/.config/hammer/*.env` · las llaves `~/.ssh/{github5,sergio}` · el token hcloud · y **la memoria +del agente en `~/.claude/`**. + +### 1.1 La basura, contada — porque una mudanza fiel copia también la basura + +De los 19 dominios del Caddyfile *(probados uno por uno desde la propia caja)*: + +| responden 200 | rotos | +|---|---| +| gioser.net · tawasuyu.net · git.gioser.net · summa.gioser.net · sergio.gioser.net | **api.gioser.net → 502** (backend caído) | +| | **aura · sigma · kosmofono · terapeuta.ec → sin DNS** | + +Y sus raíces **no existen en disco**: `/var/www` sólo tiene `terapeuta`, `goaccess`, `ogisoer`, +`git-tawasuyu`, `letsencrypt` y un `.bak` — no hay `aura_frontend`, ni `sigma`, ni `summa/frontend`, +ni `kosmofono`. **La mitad del Caddyfile describe un servidor que ya no existe.** + +⇒ **Regla de la mudanza: no se muda lo que no responde.** Lo que no tiene DNS ni ficheros se anota y +se deja morir con la caja vieja. La utilidad del §4 tiene que saber decir *declarado / vivo / fósil*. + +--- + +## 2. La caja: qué, cuánto y por qué + +Precios consultados a la API de hcloud el 2026-09-09 (hel1, x86, tipos no deprecados) *(medido)*: + +| tipo | recursos | €/mes | +|---|---|---:| +| **`cx53`** | **16 c / 32 G / 320 G** | **29,49** | +| `cpx41` | 8 c / 16 G / 240 G | 32,49 | +| `ccx23` (dedicado) | 4 c / 16 G / 160 G | 85,99 | + +**Elegido `cx53`**: es el mejor punto de la tabla y entra holgado el conjunto tawasuyu + takana + +rustup (~244 G) si algún día se muda todo. + +**Decisiones del usuario (2026-09-09):** + +1. **La caja PUBLICA; el LXC prestado sigue moliendo.** `dev.gioser.net` cuesta €0 y es la máquina + con más RAM (6 c / 16 G + 8 G swap). Separar *servir* de *compilar* mantiene la caja chica y + barata. La caja nueva puede además compilar, con el LXC enchufado como segundo worker. +2. **«Producción» = operativo pero privado.** Sin descarga pública todavía ⇒ **no** se activan los + bloqueantes legales del [SDD 19 §2](19-lanzamiento-publico.md) (espejo de fuentes público por + HTTP, marca de terceros). Lo que sí hace falta igual es la **clave de release estable** (§5). +3. **gioser se BORRA** cuando la mudanza esté comprobada. Eso convierte esto en un experimento con + fecha de caducidad, y cambia el diseño entero: ver §6. + +⚠ **El blindaje anti-borrado de gioser es deliberado y hay que levantarlo a mano y a propósito.** +`deadman.sh` y `farm-down.sh` lo protegen por lista negra de nombre **y** por ausencia del label +`role=hammer-worker`. Ninguna automatización debe poder borrarlo: cuando llegue el día, se borra a +mano, con la comprobación del §6 en verde y no antes. + +--- + +## 3. Instalar takana puro en una caja remota + +### 3.1 Lo que ya está, y por qué está más cerca de lo que parece + +**El dato que lo destraba: Hetzner Cloud arranca por BIOS, no por EFI.** *(medido en gioser: no +existe `/sys/firmware/efi`; GPT + GRUB; `/boot` es vfat de 1 G.)* Y `scripts/takana-install.sh` +escribe **exactamente ese layout**: GPT + GRUB BIOS, `SeaBIOS → GRUB → kernel → arje-zero PID1`. +El camino EFI —que fue el que más trabajo costó ([ADR 0010](adr/0010-arranque-grafo-mirada.md))— es +el que Hetzner **no** necesita. + +| pieza | estado *(medido)* | +|---|---| +| kernel con virtio | `recipes/linux.toml` compila `VIRTIO`, `VIRTIO_PCI`, `VIRTIO_BLK`, `VIRTIO_NET`, `EXT4_FS` **`=y`** ⇒ sin initramfs | +| red | `crates/netup` = cliente **DHCPv4 propio** en Rust sobre netlink, autodetecta interfaz y espera carrier. gioser toma su IP por **DHCP en `eth0`** (`/32`, `net.ifnames=0`, DNS `185.12.64.2`) ⇒ es justo su caso | +| acceso | `recipes/openssh.toml` sellado; el `product-rootfs` vigente **ya trae `sshd`**; `takana-install.sh` acepta `AUTHKEYS` | +| servir | `recipes/caddy.toml` existe | +| escritura a disco | `scripts/takana-install.sh` (no interactivo) y `scripts/takana-live-install.sh` (TUI), probado por `scripts/install-tui-test.sh` | + +### 3.2 El camino remoto: rescue → `dd` → reboot + +Hetzner Cloud tiene un **sistema de rescate** (Debian por SSH) que se activa desde la API sin tocar +la caja. Desde ahí se escribe la imagen al disco y se reinicia. **No hace falta ISO, ni consola, ni +medio físico** — que es justo lo que hace innecesario el 90 % del trabajo de ISO/USB para este caso. + +### 3.3 Los cuatro huecos, nombrados + +1. **`perfil.servidor`** — **HECHO hoy** en `docs/state/targets.toml`. Ver §3.4. +2. **`console=ttyS0` en el cmdline** — el único salvavidas si el arranque falla y no hay teclado. + Hetzner da consola web; sin serial en el kernel, esa consola no muestra nada útil. +3. **IPv6** — `netup` hace DHCPv4 y Hetzner entrega un `/64` **estático**. La caja arrancaría con + IPv4 sola. Aceptable para empezar, pero hay que saberlo **antes**, no descubrirlo. +4. **Un `.img` del perfil servidor** que el rescue pueda escribir. Es `hydrate-profile.py` + + `install-image.sh`, los dos ya existentes — ver §7. + +### 3.4 El perfil, y la deuda que declara *(hecho 2026-09-09)* + +`[perfil.servidor]` hereda `cli` (que hereda `base`, [SDD 27 §7.1](27-perfiles-instalables-y-el-instalador.md)) +y añade **5 raíces que existen** (`openssh`, `caddy`, `curl`, `wget`, `tmux`) y **5 que NO**: + +| raíz que no resuelve | por qué está declarada igual | +|---|---| +| **`takana`** | **el propio takana no tiene receta.** El corpus construye 869 recetas y no la suya ⇒ el binario sólo existe como `cargo build` sobre un clon. Es EL bloqueante del §5 | +| `chrony` | sin NTP, un reloj que deriva rompe TLS y las firmas — y falla diciendo «firma inválida», no «la hora» | +| `nftables` | `dev.gioser.net` está hoy expuesto con fuerza bruta a root en el journal; una caja nueva no debería nacer así | +| `cronie` | el latido son tres líneas de crontab, y sin systemd no hay timers: o hay cron, o el latido es una tarjeta de arje-zero. **Decisión pendiente** | +| `logrotate` | en una caja que sirve, el log de acceso crece hasta llenar el disco | + +Quedan **`wanted`** en el grafo, y **`wanted` no es `debt`** ⇒ `drenaje.json` seguirá diciendo +`deuda=0`. Se declaran igual porque **una deuda escrita se ve y una omitida no**: sin ellas el perfil +saldría N/N —completo y verde— describiendo un servidor sin hora, sin cortafuegos y sin latido. Es +la lección de `foot` en `escritorio-sway` (121/121 sellado y sin emulador de terminal), pagada por +adelantado. **Cuenta esperada: 5.** Un sexto `wanted` es un error de tipeo, no una receta nueva. + +--- + +## 4. La utilidad de migración: absorber lo VIVO, no lo declarado + +`arje-absorb` ya existe, está sellado (`recipes/arje-absorb.toml`) y su `--help` dice: lee +`sysvinit | runit | dinit | openrc | auto` y **emite una Tarjeta Semilla** con cada servicio del init +ajeno como hija de arje-zero. Tiene parsers para los cinco (`systemd.rs`, `openrc.rs`, `runit.rs`, +`dinit.rs`, `sysvinit.rs`). + +**Pero lee la declaración, y en gioser la declaración miente** (§0.2). Un `arje-absorb --from openrc` +sobre esta caja produciría una Semilla que **no levanta el servidor**, y lo haría en silencio: la +Semilla sería sintácticamente perfecta. + +⇒ Lo que falta es un **segundo lector**: absorber del **proceso vivo** (`/proc`: `ppid==1`, `cmdline`, +`cwd`, entorno, fds en LISTEN) y **reconciliar** ambas lecturas marcando dónde discrepan. Eso es lo +que el usuario nombró como «lo aprendido de matilda» — absorber lo que hay, no lo que está escrito. + +La utilidad debe clasificar en tres, no en dos: + +| clase | criterio | qué se hace | +|---|---|---| +| **vivo** | proceso corriendo **y** declarado | se muda | +| **declarado, muerto** | en la config, sin proceso ni ficheros (los 4 dominios sin DNS) | **fósil: se anota y se deja morir** | +| **vivo, no declarado** | proceso con `ppid==1` que ningún init declara (caddy, gitea, sshd, act-runner) | **se escribe la tarjeta que faltaba** — es el trabajo real | + +**Se construye al final**, estrenándose con la migración gioser → caja nueva, con el caso del +`rc-status` que miente como test de aceptación. Su especificación es el §1 de este documento. + +--- + +## 5. Que el servidor sirva sus propios paquetes a su propio host + +Existe el camino entero: `scripts/build-repo.sh` (corpus → `.swm` firmados + índice), +`takana install --repo` que **ya acepta lista HTTP multi-origen** y verifica cada paquete contra el +`digest` del índice firmado ([ADR 0014](adr/0014-distribucion-multiorigen.md)), `takana upgrade` con +rollback, y caddy para servir `dist/repo` con un `file_server`. + +Lo que falta **no es servir**, son dos cosas: + +1. **La clave.** `build-repo.sh` firma hoy con una **clave efímera** por defecto (`keygen release` si + no se le pasa `KEY=`) *(medido, línea 33)*. Sin clave estable, «firmado» no significa nada. Es el + [SDD 19 §3.2](19-lanzamiento-publico.md) y es requisito aunque el repo sea privado. +2. **La receta de `takana`** (§3.4). El host necesita `takana` instalado para consumir los paquetes, + y hoy no puede instalarlo desde el repo porque takana no está en el repo. + +⚠ **Y una caja que se sirve a sí misma es un solo origen** — justo lo que el ADR 0014 existe para no +tener. El camino normal puede ser el local, pero la lista de orígenes del host debe incluir también +el Storage Box o gioser. Si no, el primer origen roto es el último. + +**Lo que esto vale**: cerrar el bucle es el ensayo de «actualización en sitio probada de verdad» +([SDD 19 §5.2](19-lanzamiento-publico.md)), hecho sobre una máquina que importa pero que **no es el +laptop de nadie**. + +--- + +## 6. La mudanza como experimento: el criterio de borrado + +El usuario decidió que **gioser se borra** al final. Eso no es un detalle de calendario: es lo que +convierte la mudanza en un experimento con criterio de aceptación, y hay que escribirlo **antes**. + +**Regla base**: un ausente falla ruidosamente; un vacío llega hasta el final diciendo que todo fue +bien. Aplicado acá: *«lo copié»* no es prueba de nada. **La prueba es que el servicio responde en la +caja nueva y que el dato se verifica por contenido.** + +### 6.1 Las puertas, en orden. Ninguna se salta. + +| # | puerta | cómo se comprueba | +|---|---|---| +| 1 | la caja nueva arranca **takana puro** y se entra por SSH | `ssh` a la IP nueva tras el reboot del rescue | +| 2 | el **store** está entero | `1327` artefactos **y ninguno vacío** — `respaldo-storagebox.sh --listar` separa los vacíos; un directorio vacío **no** es un artefacto | +| 3 | los **grafos** salen idénticos | `build-state.py` en las dos máquinas ⇒ los cuatro grafos **byte a byte**. Es la prueba de que un hub está bien montado, no que «funcione» | +| 4 | la **granja** late en la caja nueva | una cosecha completa con el LXC enchufado por `.fleet` y artefactos que vuelven | +| 5 | el **repo** se sirve y el host se actualiza de sí mismo | §5 en verde, con la clave estable | +| 6 | cada dominio **vivo** responde 200 desde fuera | la tabla del §1.1, repetida contra la IP nueva | +| 7 | el **respaldo** al Storage Box corre desde la caja nueva | una corrida completa, no un `--seco` | +| 8 | lo **fósil** está anotado y decidido | lista del §1.1 con «muere» o «revive» al lado de cada uno | + +⚠ **Puerta 3 tiene una trampa conocida**: `sealed_remoto` dependía de la máquina (laptop 700, gioser +751 sobre el MISMO corpus) y por eso se sacó del JSON. Si los grafos difieren, mirar primero si la +diferencia es «cuánto de esto tengo en disco» y no «qué existe». + +### 6.2 Lo que la mudanza tiene que producir, además de la mudanza + +El usuario lo pidió explícito: **que este experimento saque recetas y las pruebe**. La caja vieja es +un catálogo de software real en producción, y cada servicio que hoy corre sobre binarios de Artix es +una receta candidata con un caso de uso comprobado detrás: + +| lo que corre hoy en Artix | receta | +|---|---| +| `caddy` | ✅ existe (`recipes/caddy.toml`) | +| `openssh` | ✅ existe | +| `gitea` | ✗ — y es la que más peso tiene: aloja el `origin` de takana | +| `qdrant`, `squid`, `fail2ban`, `php-fpm`, `metalog`, `glances` | ✗ | +| `chrony`, `nftables`, `cronie`, `logrotate` | ✗ — ya declaradas `wanted` en `perfil.servidor` | + +**Cada una que se cierre se prueba sola**: el servicio o levanta en la caja nueva o no, y eso se ve +el mismo día. Es la diferencia entre una receta «sellada» y una receta *usada*. + +--- + +## 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: + +| para | script existente | qué le falta | +|---|---|---| +| armar el rootfs de un perfil | `scripts/hydrate-profile.py` | nada — es el camino bueno (los `hydrate-*.sh` por escritorio **divergen** de `targets.toml`) | +| producir el `.img` | `scripts/install-image.sh` / `scripts/product-image.sh` | apuntar al `perfil.servidor` | +| escribir a disco | `scripts/takana-install.sh` | el envoltorio de rescue del §3.2 | +| instalador con TUI | `scripts/takana-live-install.sh` | ser el **mismo motor** que el remoto, con las respuestas por fichero | +| provisionar un worker | `scripts/farm/vps-setup.sh` | ya soporta apt **y** dnf; le falta la rama «takana puro» (sin gestor de paquetes ajeno) | +| enchufar a la granja | `scripts/farm/.fleet` | **está en `.gitignore`** ⇒ un hub recién clonado nace con la flota vacía **en silencio** | +| latido | `scripts/farm/cosecha-cron.sh` | decidir cron vs tarjeta de arje (§3.4) | +| respaldo | `scripts/respaldo-storagebox.sh` | nada — es interrumpible y continuable | +| absorber el init ajeno | `arje-absorb` | el lector de procesos vivos (§4) | + +⚠ **Un motor, tres frentes.** La tentación es escribir el instalador remoto aparte del TUI. Ahí es +donde divergen: ya pasó con los `hydrate-*.sh` contra `targets.toml`, y con la unidad systemd del +worker que quedó vieja mientras el repo estaba al día. La instalación remota tiene que ser **la misma +que hace la persona**, grabada en un fichero de respuestas. + +--- + +## 8. `churay` como instalador centralizado — la dirección, no la decisión + +El usuario lo planteó el 2026-09-09: **un instalador único, con CLI y TUI**, que sirva para + +- las **apps de tawasuyu**, +- **paquetes sueltos en cualquier distro**, al estilo nix (instalar sin ser root, sin tocar el + sistema anfitrión), +- **takana** mismo, +- y **`llimphi`**. + +Encaja con lo que ya hay y por eso vale escribirlo: `takana install` ya resuelve por nombre contra un +índice firmado y **ya acepta múltiples orígenes**; `qorpa` ([ADR 0015](adr/0015-imagenes-ajenas.md)) +ya corre imágenes ajenas enjauladas sobre el mismo kernel; y el vigía de espejos de churay ya corre +en cron desde gioser. + +**No se decide acá.** Es un ADR propio, y tiene al menos tres preguntas abiertas antes de escribir +una línea: (1) si el instalador «en cualquier distro» hidrata binarios o reproduce desde fuente —la +misma pregunta del [SDD 27 §4](27-perfiles-instalables-y-el-instalador.md), y la respuesta puede no +ser la misma fuera de takana; (2) quién es el dueño del verbo, si `takana install` crece o si +`churay` lo envuelve; (3) qué pasa con un host glibc, que es exactamente lo que el ADR 0015 aparta +del store a propósito. Queda anotado para que se **decida**, no para que se descubra a mitad. + +--- + +## 9. Estado y orden + +| paso | estado | +|---|---| +| **0.** `perfil.servidor` + este documento + el renombre `hammer-*` de los instaladores | ✅ **2026-09-09** | +| **1.** caja `cx53` de sacrificio: rescue → `dd` → arranca takana puro (§3) | pendiente | +| **2.** el servidor se sirve sus paquetes y se actualiza a sí mismo (§5) | pendiente — requiere clave estable + receta de `takana` | +| **3.** mudanza con las 8 puertas del §6, y recetas nuevas del §6.2 | pendiente | +| **4.** la utilidad de absorción (§4), estrenándose con esta migración | pendiente | +| **5.** `churay` como instalador centralizado (§8) | ADR propio, sin decidir | + +**El volumen del store NO se engancha a la caja nueva hasta que pase la puerta 1.** Una caja de +sacrificio con el store dentro deja de ser de sacrificio. diff --git a/docs/state/targets.toml b/docs/state/targets.toml index 81085566..1f75a566 100644 --- a/docs/state/targets.toml +++ b/docs/state/targets.toml @@ -864,3 +864,62 @@ paquetes = [ # pedirlo es promover uno al corpus. Queda escrito para que se elija, no para que se descubra. "hicolor-icon-theme", ] + +[perfil.servidor] +descripcion = "la caja que SIRVE: sshd, servidor web y las herramientas de operar una máquina remota sin pantalla" +hereda = ["cli"] +# Nace el 2026-09-09 del frente «servidor de producción» (SDD 27). Existe porque las cuatro cosas que +# ese frente quiere —instalar takana puro en una caja remota, servir el repo, que la caja se actualice +# a sí misma, y migrar gioser— empiezan todas por la misma pregunta: QUÉ se instala. Hasta hoy eso no +# estaba declarado en ningún lado. El `sshd` del producto, por ejemplo, entra por +# `product-userland-from-repo.sh` y NO por este manifiesto: dos listas para lo mismo y una sola +# versionada como destino, que es exactamente lo que este fichero existe para no tener. +# +# ⚠ LA LECCIÓN DE `foot`, APLICADA ANTES DE PAGARLA: `escritorio-sway` daba 121/121 sellado y era +# inusable porque a la declaración le faltaba el emulador de terminal. La métrica mide la clausura de +# lo DECLARADO y no puede ver lo que falta en la declaración. Un servidor tiene su propia versión de +# ese agujero, y no es el software de servir: es la HORA (sin reloj, TLS y las firmas fallan), el +# CORTAFUEGOS y el DISPARADOR PERIÓDICO. Por eso van declarados abajo aunque todavía no existan. +# +# ⚠ LAS CUATRO ÚLTIMAS RAÍCES NO RESUELVEN A NINGUNA RECETA — a propósito, y hay que saberlo. +# Quedan `wanted` en el grafo, y `wanted` NO es `debt`: `drenaje.json` seguirá diciendo `deuda=0` +# ([[gnome-sin-fuentes]] trampa 1). Se declaran igual porque una deuda escrita y contada se ve, y una +# omitida no: un `perfil.servidor` sin ellas saldría N/N —completo y verde— describiendo un servidor +# sin hora, sin cortafuegos y sin latido. Cuenta esperada al escribirlas: **5 `wanted`**. Si al mirar +# `build-state.json` aparece un sexto, es un error de tipeo en un nombre, no una receta nueva. +paquetes = [ + # ── lo que existe y está sellado ────────────────────────────────────────────────────────────── + # `openssh`: el acceso. Es la raíz que convierte «la caja arrancó» en «puedo entrar»; sin ella una + # instalación remota deja una máquina viva e inalcanzable, que es peor que una que no arrancó. + "openssh", + # `caddy`: sirve `dist/repo` (índice firmado + los `.tkn`/`.swm`). TLS automático, un binario, cero + # dependencias — y es el mismo que ya sirve los 19 dominios de gioser, así que la migración no + # cambia de servidor web además de cambiar de sistema. + "caddy", + # `curl`/`wget`: bajar del mirror y del repo. `takana install --repo https://…` los necesita del + # lado del cliente, y son también la única forma de diagnosticar un origen caído desde la caja. + "curl", "wget", + # `tmux`: un build largo por SSH que muere con la sesión es un build perdido. En una caja sin + # pantalla es infraestructura, no comodidad. + "tmux", + + # ── declarado y NO existe todavía: la deuda del perfil, contada ─────────────────────────────── + # `takana`: el propio takana NO tiene receta. El corpus construye 869 recetas y no la suya, así que + # hoy el binario sólo existe como `cargo build --release` sobre un clon del repo. Es EL bloqueante + # de «que el servidor sirva sus paquetes a su propio host»: el host necesita `takana` instalado para + # consumirlos, y no puede instalarlo desde el repo porque no está en el repo. + "takana", + # `chrony`: la hora. Sin NTP, un reloj que deriva rompe la validación de certificados TLS y la de + # firmas con expiración — y falla con errores que no dicen «la hora», dicen «firma inválida». + "chrony", + # `nftables`: el cortafuegos. `dev.gioser.net` está hoy expuesto a internet con fuerza bruta a root + # en el journal; una caja nueva no debería nacer así. + "nftables", + # `cronie`: el disparador periódico. El latido de la granja son tres líneas de crontab, y en una + # distro sin systemd no hay timers: o hay cron, o el latido se vuelve una tarjeta de arje-zero. Es + # una decisión pendiente, y se declara para que se decida en vez de descubrirse. + "cronie", + # `logrotate`: en una caja que sirve, el log de acceso crece hasta llenar el disco. Caddy rota el + # suyo por configuración, pero nada más lo hace. + "logrotate", +]