0a7afebe9c9f56a31e617dff2e158cfa5daa27dc
35
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0a7afebe9c |
la caja tiene HORA, LATIDO y ROTACIÓN — y el respaldo del gitea dejó de ser a mano
`planear.py --revisar` sobre el censo ordena lo que queda, y lo primero no eran los servicios del usuario sino TRES CAPACIDADES que ninguna metrica reclama: el disparador periodico, la hora y la rotacion de logs. Los tres paquetes ya estaban sellados y declarados en `perfil.servidor` desde que ese perfil nacio — lo que faltaba era el eslabon que los ARRANCA, igual que con squid. · `chronyd`: la caja iba **15 s atrasada** y nadie la corregia. Ahora stratum 3 contra los NTP de Hetzner, 0,000005 s de NTP. Sin hora no hay TLS ni firmas, y el sintoma no se parece a la causa. · `crond`: no existia el latido. Verificado con una entrada `* * * * *` en el crontab REAL. · `logrotate`: `/var/log/squid` sin rotar. Diario, con `squid -k rotate` (squid mantiene los ficheros abiertos: un rename a secas lo deja escribiendo en un inode que nadie puede leer). · `scripts/respaldo-gitea.sh` + `17 3 * * *`: cierra el hueco del §6.15 — los 44 repos vivian en UNA copia y el respaldo se hacia a mano. Sube 23+21 repos (1,5 G) y un `gitea.db` de 332 M tomado con `.backup`, consistente con el servidor vivo. El control no es que el script salga 0: es CONTAR los repos de los dos lados. `chrony` y `cronie` NO declaraban `[[service]]` — el paquete llegaba a la imagen y no lo arrancaba nadie. Ahora lo declaran (fuera de `hash_inputs`: los hashes no se movieron) y el perfil los habilita. TRES MEDICIONES que valen mas que el resultado: 1. **La deriva era de gioser, no de la caja**: despues de sincronizar, el HUB quedo 3 s adelantado. Medir «contra el otro» sin un tercero no dice quien esta mal. 2. **El spool de cronie es `/var/spool/cron/<usuario>`**, no `…/crontabs/<usuario>`: `crontab -l` mostraba las entradas y `crontabs/` estaba vacio. Un crontab restaurado en el sitio equivocado es un fichero perfecto que nadie lee. 3. 🧨 **`sqlite3` seguia INERTE en la caja** (`compressBound: symbol not found`): el ultimo de los 23 del §6.4. Le faltaba `zlib-shared`, sellado y declarado en `perfil.base` desde el 11-09 — pero la caja se instalo antes. Sin el no habia snapshot consistente. Un paquete declarado no es un paquete instalado: en una caja viva vale lo que el `upgrade` proyecto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b927eeffcb |
squid corre desde el corpus: la jaula duró unas horas y se tiró
`b3:932ba096…` proyectado en la caja y arrancado por arje SIN bwrap: `pid 11548 · uid=968 · exe=/usr/sbin/squid`, con la cuenta `proxy` que declara la receta. La instancia `qorpa/squid` se borró — el manifiesto es descartable por diseño (ADR 0015 D3) y esto es para lo que existía. El arreglo de esta tanda: **`--disable-log-daemon-helpers` compilaba perfecto y no arrancaba**. El default de `access_log` es `daemon:`, asi que sin `log_file_daemon` squid muere con `FATAL: logfile_daemon /usr/lib/squid/log_file_daemon: (2) No such file or directory`. Sellaba, hasheaba y pasaba `-k parse` — se ve SOLO levantando el servicio con la config real. Familia [[subcomando-sin-driver]]. El control que lo caza, y que corre en el hub antes de tocar produccion: con la config REAL del origen y en un puerto aparte, arranque completo + CONNECT a claude.ai y api.anthropic.com con TCP_TUNNEL/200 + el access.log escrito por el log daemon + `basic_ncsa_auth` leyendo el `passwd` de verdad (control negativo: clave mala ⇒ `ERR Wrong password`). Los DOS primeros intentos de esta receta habrian llegado a produccion sin ese control: el binario estaba sellado y el hash era estable las dos veces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a1ccce2b65 |
squid mudado — y la jaula arrancaba DEGRADADA cuando la arranca un init
El proxy de salida sirve desde la caja: `squid 7.7` en 2.29.29.217:1137 con la config, el `passwd` de los tres usuarios y las dos ACL de gioser tal cual, dentro de una instancia qorpa y supervisado por arje. El de gioser sigue vivo e intacto (7.6). Es un servicio con gente encima: el access.log del origen mostraba CONNECT a api.anthropic.com y a claude.ai en el minuto anterior a mudarlo. Control en los dos sentidos: desde una IP no autorizada, 407 igual que el original; desde localhost, que su propia config permite, TCP_TUNNEL/200 a claude.ai y api.anthropic.com. EL MURO QUE VALE, y es un bug de qorpa: **el rango de subuid se buscaba por `$USER`**. Una shell interactiva lo trae; un init NO. Bajo arje la tarjeta arranca con PATH y HOME, el nombre salia vacio, no habia rango, y la jaula caia al userns de UN SOLO ID — con lo que squid moria en `setgid(15): (22) Invalid argument` y el supervisor lo reintentaba 80 veces. El aviso de qorpa estaba impreso y era correcto («no hay rango para "" en /etc/subuid»), pero lo que se ve en el bucle es el error de squid: **la degradacion silenciosa la paga el de mas abajo**. Y el MISMO comando a mano funcionaba, porque la shell si pone `$USER`: el sintoma dependia de quien lo arrancaba. ⇒ `nombre_de_usuario()` deriva el nombre del UID REAL leyendo /etc/passwd y el entorno queda como ultimo recurso, con la mitad pura (`nombre_en_passwd`) probada, control negativo incluido: un uid que no esta NO inventa un nombre — devolver algo ahi haria buscar un rango ajeno. Los otros tres muros quedan documentados en §6.27: provisionar ANTES de montar la config (pacman aborta si el paquete no puede escribir sus defaults), `/dev/shm` que bwrap crea 0755 y squid no puede usar al bajar a `proxy`, y el pidfile que en una jaula con `--unshare-pid` SIEMPRE parece fresco porque cada corrida tiene su propio PID 2. Y la pregunta que corresponde —¿no va en una receta?— con su respuesta: si, y sigue pendiente. La jaula fue la via urgente. La diferencia con php-fpm importa: PHP no queremos que entre al corpus, squid es C y su sitio natural es una receta como la de gitea. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d52824efeb |
mudados tawasuyu.net y gioser.net — y los padres que inventa bwrap eran 0700
Los dos sitios que quedaban con trafico real sirven desde la caja `takana`, con TLS publico y
verificados desde fuera. Los servicios viejos SIGUEN corriendo en gioser a proposito (decision del
usuario: mover el DNS y dejar el origen vivo, para poder volver en un minuto).
· `tawasuyu.net` + `www`: la raiz en gioser era EL MONOREPO ENTERO (145 G) servido por HTTP. El log
de accesos dice que las unicas rutas con trafico son `/`, `/descargas` y `/web`, asi que se
replicaron solo los dos subarboles que el sitio usa (1,2 M + 3,8 G) con la MISMA estructura, para
que cada `rewrite` siga siendo el mismo.
· `gioser.net` + `www`: 428 M de estaticos + `/reencuentro`, que necesita PHP. `/hooks/*` NO se
muda: lo atiende `webhook-deploy.py`, que redespliega aura/sigma/summa/brahman — los cuatro
FOSILES del §6.19 — y tiene cero peticiones. Muere con la caja vieja.
· `sergio` dejo de ser `CNAME -> www` y tiene su A propio (apuntando a gioser, sin cambio visible).
Sin eso, mover `www` se lo llevaba puesto: en esa zona casi todo cuelga de `www`.
PHP no entra al corpus y no hace falta: `php-fpm 8.5.10` corre en la instancia `gioser-php` (ADR
0015), supervisado por arje. Control contra el original en los dos sentidos: POST con la trampa
anti-robots da `{"ok":true}` 200 y GET da 405, igual que en gioser.
EL MURO, que es general y no de PHP: para montar en `/work/www/gioser-web`, bwrap crea `/work` y
`/work/www` —que la imagen no trae— y los crea **0700 root**. Adentro somos root y a mano todo
funciona; pero un servicio que BAJA de privilegio (php-fpm a `http`, o cualquier instancia con
`run_as`) no puede ni atravesarlos, y el sintoma es un «File not found» sobre un fichero que ESTA.
Medido con el control que lo separa de un problema de permisos del anfitrion: como `http`, leer un
fichero de la IMAGEN funciona y leer el directorio CONCEDIDO da Permission denied.
`grants_to_args` crea ahora los ancestros que faltan con `--perms 0755 --dir`, y SOLO los que la
imagen no trae: hacerlo sobre `/etc` o `/home` le cambiaria los modos a la imagen. Con test, y
probado rompiendolo a proposito (sale `["/etc","/etc/php"]` en vez de `["/etc/php"]`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
486bf7bb22 |
SDD 28 §6.25: api.sergio.gioser.net sirve desde la caja, dentro de una jaula qorpa
El primer servicio AJENO que corre sobre takana. 200 con TLS publico desde 2.29.29.217 y `/api/chat/` contestando con Gemini de verdad, con el backend en una instancia `qorpa` sobre el rootfs de Arch pineado por sha256, supervisado por arje-zero como el ente `sergioh-api`. Es el que §6.21 llamo «lo que decide la fecha de borrado de gioser»: su venv trae extensiones `cpython-314-...-linux-gnu.so`, o sea glibc, y no tiene camino a musl. La coincidencia que lo hizo barato: el Arch pineado trae python 3.14.7 y el venv de gioser es 3.14.6 — misma serie, asi que el venv se REHACE adentro con pip en vez de copiarse. La seccion trae ademas el muro (la jaula no viajaba en ninguna imagen — commit anterior), los tres tropiezos del camino (el uid 1001 del rsync que cae fuera del userns; una instancia admite UN solo `run`; `ps` que no ve procesos y `dig` que no existe, dos ausencias que se leen como diagnostico) y lo que quedo declarado: `instance.toml` con un unico dir concedido, las 84 deps pineadas en `requirements.lock`, y la tarjeta en cards.d Y en el genesis. Y una correccion de rumbo en §9: la lista «lo que falta para el cutover» era del 11-09 y sus pasos 1-5 ya estan hechos. Lo que sigue vivo en gioser son TRES vhosts, no quince. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
93362fc21b |
el TLS arreglado en la caja, la distro con nombre, y el ENOSPC que no era de la raíz
**curl ya valida**: `ca-certificates` re-sellado con **119 hash-links en el capath**, generados en el build, y la caja baja por HTTPS sin apaño (200). Tardó tres intentos y los dos primeros los cazó la guarda de la propia receta: `c_rehash` no estaba en el PATH (lo instala esa misma receta en `/out/usr/bin`) y los certificados no están sueltos en `usr/share/ca-certificates/` sino en `mozilla/`, así que sólo veía el bundle y lo saltaba —correctamente— con «does not contain exactly one certificate». Sin la guarda habría sellado un capath vacío las tres veces. **fastfetch 2.68.1 corre en la caja** (pedido del usuario) y al correrlo destapó que **`/etc/os-release` no existía**: la distro era anónima para cualquier programa que no fuera suyo. Ahora `OS: takana x86_64`. Va como receta propia (`source.dir`) y no como constante de `takana-bootstrap`: meterlo ahí re-sellaría el product-rootfs —el baseline del selfhost— por cinco líneas. Sin VERSION_ID ni fecha: un sello con la fecha del build cambiaría el hash cada día sin motivo, y con él la clausura de toda imagen que lo lleve. 🧨 Y el hallazgo estructural: `upgrade apply` falló dos veces con **No space left on device teniendo 3 G libres en la raíz**. No era `/` ni los inodos (9%): el estado de generaciones vive en **`/var/lib/hammer`, que es sda3 y mide 487 MB** — la partición «estado» del layout. Cada generación guarda una copia del árbol aplicado (272 MB el nuestro), así que dos no caben. Movido a `/work` por symlink, igual que qorpa, y la generación 6 entró. El layout de la imagen necesita revisión: 512 MB no alcanzan para un mecanismo que guarda un árbol por generación, y el síntoma apunta al sitio equivocado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
88decb0498 |
🧨 el curl de la distro no puede validar TLS: el capath no tiene hash-links
Al traer la primera imagen ajena, la caja falló con «curl failed to verify the legitimacy of the
server». El certificado del servidor estaba bien; lo que falta es de este lado:
* CApath: /etc/ssl/certs
* SSL certificate ... unable to get local issuer certificate (20)
$ curl --cacert /etc/ssl/certs/ca-certificates.crt ... → 200
El curl del corpus se compila con `--with-ca-path=/etc/ssl/certs` y SIN CAfile, y ese directorio
tiene un solo fichero: el bundle. Un capath necesita hash-links (`c_rehash`) — el ÍNDICE — y no los
hay. Los certificados están; el índice no.
Por qué faltan: la receta deja el hook `/etc/ca-certificates/update.d/certhash`, que en Alpine
ejecuta `apk` al instalar. **En takana no lo ejecuta nadie**: la distro proyecta artefactos, no corre
post-install. Todo presente, y el paso que lo conecta no existe.
Alcance: no es sólo `qorpa pull`. Es `install --repo https://…`, el mirror y cualquier curl/wget de
una caja instalada. No falla al construir ni al instalar: falla la primera vez que la máquina intenta
bajar algo. No se había visto porque el hub usa el curl de Artix.
Arreglado en la receta: los hash-links se generan EN EL BUILD (donde c_rehash y perl existen) y
viajan en el artefacto, con una guarda que falla ruidosamente si el rehash no produce ninguno.
Re-sella `ca-certificates`, raíz de `perfil.base`. Apaño mientras tanto: `CURL_CA_BUNDLE=…` (200).
Y el estado de qorpa en la caja: preflight de «BLOQUEA 1 · LIMITA 4» a «LIMITA 1» — qorpa movido a
/work (la raíz tenía 3 G), subuid provisionado y `setcap` aplicado a newuidmap/newgidmap SIN tocar el
artefacto del store (inodes distintos: en una caja instalada esos binarios son copias). El pull baja
y verifica el sha256, y muere al desempacar: el rootfs de Arch es `.tar.zst` y el `tar` de la imagen
es el de busybox. `qorpa pull` necesita `zstd -dc | tar -x` para `.zst`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
c7efb5cc56 |
rescatados 5 binarios que ya no existían en disco — y el muro de sergio: glibc, no el sitio
Antes de tocar `sergio.gioser.net` apareció lo urgente: CINCO procesos vivos cuyo binario ya no
existe en disco (`/proc/<pid>/exe` → «(deleted)»). Sólo vivían como inode huérfano: si el proceso
muere o la máquina se reinicia, se pierden. Es el «paso que caduca» del SDD 29, caducando de verdad.
tejido · sandokan-mcp · pacha-secretos · puerta-f6e393ffbef3999e · shuma-gateway (240 M)
Recuperados leyendo `/proc/<pid>/exe` con el proceso vivo, y ya están FUERA de gioser: en
`/work/rescate-binarios/` de la caja y en el Storage Box. Cinco de los trece binarios que nadie
provee dejaron de depender de que nadie reinicie nada.
Y `sergio.gioser.net` son tres piezas, de las que sólo una se muda hoy:
· frontend: 117 M de estático ⇒ copiado a `/work/www/sergioh` ✅
· `/shuma/*`: `shuma-gateway` en :7378, ELF dinámico y con el binario borrado ⇒ hay que construirlo
desde tawasuyu (es Rust del monorepo, factible)
· `api.sergio`: uvicorn con un venv de **405 M** — fastapi + pydantic + google-generativeai +
**langchain**, con extensiones `cpython-314-x86_64-linux-GNU.so` ⇒ **glibc**. Reempaquetar ese
árbol de PyPI como recetas no es realista y en musl no corre. Es el caso canónico del ADR 0015
(qorpa), que sigue PROPUESTO.
Por eso el DNS de `sergio` NO se movió: mover el nombre sin backend deja el sitio servido y la
consola rota. El frontend queda copiado y esperando.
Esto es lo que decide la fecha de borrado de gioser: no es «mudar dominios», es que un servicio vivo
no tiene hoy camino a takana. Tres salidas, y ninguna es técnica: qorpa · que viva en otra máquina
(summa ya lo hace) · o que muera.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
52fea61cbd |
fósiles barridos: gioser pasa de 19 vhosts a 2
Medido dominio por dominio (DNS por DoH **y** HTTP contra la IP del origen) antes de tocar nada:
5 fósiles sin DNS (aura, api.aura, sigma, kosmofono, api.kosmofono) · 3 que ya viven en OTRA máquina
(summa, dev.summa, api.dev.summa → 154.197.1.2, así que sus bloques en gioser eran código muerto) ·
2 en 502 permanente (mail.sigma con :9000 caído, api.gioser.net con :8000 caído) · 5 mudados hoy ·
y **2 vivos de verdad**: `sergio.gioser.net` y `api.sergio.gioser.net`.
Dato que ordena: **ninguna de las raíces estáticas de los fósiles existe en disco**
(`/var/www/{aura_frontend,sigma,kosmofono,summa,summa-dev}`). El Caddyfile servía directorios
ausentes — la configuración sobrevivió a sus datos. De los backends sólo `:8770` escucha, y su
dominio ya apunta a otra parte.
Los 10 bloques muertos fuera (respaldo previo, `validate`, `reload`), con control: `sergio` y
`api.sergio` siguen en 200, y los mudados responden desde la caja.
⚠ El único fósil CON datos es `terapeuta.ec`: 279 M en `/var/www/terapeuta`, sin DNS y sin vhost. No
se borra acá; se anota para la decisión de borrado de la máquina.
Lo que queda por mudar es un frente acotado: un backend Python en :7378 y una API en :8771.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
b2d72cafcf |
gioser tiene arje Y OpenRC a la vez — y mudados los dos sitios estáticos
El usuario avisó de que esa máquina está con arje desde hace meses. Tiene razón, y las dos cosas son ciertas: PID 1 **es** `arje-zero`, la card `openrc-gitea` **no** llama a `rc-service` (ejecuta el binario directo — el prefijo es herencia del nombre que le puso `arje-absorb` al traducir), y sin embargo **`/run/openrc/started/` existe con servicios dentro** (NetworkManager, dbus, dhcpcd, localmount…) y `rc-update show default` listaba gitea. OpenRC quedó como residuo ACTIVO, capaz de arrancar por su cuenta: eso fue lo que revivió al gitea con ppid ≠ 1 después de que arje lo parara. Regla para el resto de la mudanza: antes de dar un servicio por apagado, preguntar a los DOS — `arjectl list-units` y `rc-update show default`. Un nombre que dice `openrc-` y no es OpenRC, al lado de un OpenRC real que nadie esperaba que siguiera operando. Y mudados `takana.gioser.net` y `hifas.gioser.net`: estáticos puros, 52 K, vhost en la caja, DNS de CNAME a A, fuera del origen. 200 con TLS válido desde 2.29.29.217; `sergio.gioser.net` sigue en 200 y gioser baja de 16 a 14 vhosts. Se repitió lo del ACME: el primer certificado se pide antes de que propague el DNS y falla contra el origen. Conviene mover el DNS y RECIÉN ENTONCES añadir el vhost, o asumir un `restart` de más. Los 14 que quedan tienen backend propio o son fósiles: no se mudan copiando un directorio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
70a76aedcb |
⚠ la comprobación que faltaba: DOS commits se habían quedado en el gitea viejo
Comparados los 845 refs de los dos lados, repo por repo. Dos diferencias: una esperada (`sergio/takana` main, el nuevo por delante con los commits del cutover — fast-forward verificado) y una que NO: `tawasuyu/tawasuyu` tenía en el viejo `b69008eb` (freebsd) y `02a9535f` (shuma, **19:48**), quince minutos DESPUÉS del snapshot final. Por qué: el gitea viejo había resucitado por OpenRC y un cliente con el DNS cacheado (el CNAME anterior tenía TTL 600) lo empujó a gioser aunque el autoritativo ya dijera la caja. Hacían falta las dos condiciones a la vez. Recuperados con un push del ref exacto desde el clon local; la UI del nuevo ya muestra `02a9535f`. Segunda pasada: 845/845 y una sola diferencia, la esperada. En metadatos, `action` tenía tres filas posteriores al snapshot (los mismos dos repos) y CERO issues/comentarios/releases. El orden para el resto de la mudanza: bajar el TTL ANTES del corte · apagar el origen de verdad (los dos supervisores) ANTES de tocar el DNS · comparar refs después, siempre · y dejar el origen apagado un rato antes de borrarlo, precisamente para poder comparar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
dc0927a028 |
cerrar el intermedio: el gitea viejo apagado DE VERDAD, el :22 cerrado y los datos respaldados
⚠ EL GITEA VIEJO HABÍA VUELTO A ARRANCAR. Media hora después del corte, gioser servía otra vez en :3002. `arjectl list-units` ya no lo mostraba —el stop de arje seguía en pie— y el proceso tenía ppid ≠ 1: lo levantó **OpenRC**, que lo tenía en el runlevel `default`. Dos supervisores para el mismo servicio, y parar uno no para el otro. Antes del corte pasaba lo contrario: `rc-service stop` decía «already stopped» con el proceso vivo, porque quien lo tenía era arje. La pregunta no es «¿está parado?» sino «¿QUIÉN lo tiene?», y hay que responderla dos veces. Arreglado con `rc-update del gitea default` + `rc-service gitea stop`. El origen deja de servir git: los tres bloques salen del Caddyfile de gioser (con respaldo, `validate` y `reload`) y quedan 16; control inmediato de que no rompí lo demás —`sergio.gioser.net` y `hifas.gioser.net` siguen en 200—. `gitea.gioser.net` pasa a A → la caja. El `:22` cerrado sin perder el acceso: administración en **22022**, git por SSH en **2345**, y el 22 en `Connection refused`. La secuencia es la única segura: añadir el puerto nuevo → reiniciar → ENTRAR por él → sólo entonces quitar el 22. Verificado en ese orden, y el clone por 2345 sigue. 🧨 Y lo que apareció al mirar: **los datos del gitea nunca estuvieron respaldados**. `respaldo-storagebox.sh` cubre el STORE de artefactos, no `/var/lib/gitea` — los 44 repos vivían en una sola copia. La mudanza no lo empeoró, pero lo vuelve urgente porque gioser se borra. Hecho hoy desde la caja: snapshot de la DB con el servicio vivo + rsync a `u647150:gitea/` (1,7 G). ⚠ Al Storage Box se entra por el puerto **23**: el 22 da SFTP restringido y contesta «Permission denied (publickey,password)», que se lee como «no tengo la clave» teniendo la clave perfecta. Pendiente: que ese respaldo sea periódico — un renglón en el cron de la caja. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
69526768b7 |
CUTOVER: el gitea sirve desde la caja nueva — TLS válido, HTTPS y SSH, supervisado por arje
`git.gioser.net` y `git.tawasuyu.net` responden **200 con TLS válido desde 2.29.29.217**, `git clone` funciona por HTTPS **y** por SSH:2345, y `gitea` + `caddy` corren supervisados por `arje-zero`. El gitea de gioser está parado. Es el primer servicio real que deja la máquina que se va a borrar. Mudanza INCREMENTAL: los DNS de gioser son casi todos `CNAME → www`, así que mover `www` habría mudado quince dominios cuyos backends siguen allá. Se convirtió sólo `git` (las dos zonas) de CNAME a A propio con TTL 60 — reversible en un minuto. Cinco cosas que sólo se aprenden haciéndolo: · **Parar el origen no es `rc-service gitea stop`**: dice «already stopped» con el proceso vivo, y matarlo no alcanza — lo revive arje-zero, que lo tiene como card `openrc-gitea` con Restart y **9001 reinicios** en el contador. Se paró con `arjectl stop openrc-gitea` (el arjectl que construimos hoy, hablando con el arje de gioser), y para eso hubo que extraer su card del genesis y escribirla en `cards.d` — que es justo el hueco del §6.12. · **El token de `hcloud` también gestiona el DNS** (Cloud API unificada, `/v1/zones`); la API vieja `dns.hetzner.com/api/v1` redirige a la consola web. Y un `PUT` sobre el rrset no puede cambiar el TIPO: hay que DELETE del CNAME y POST del A. · **ACME falló primero contra gioser** (502) porque el challenge salió antes de que propagara el DNS. Con el DNS al día, `arjectl restart caddy` → certificate obtained successfully. · **La identidad SSH se muda con el servicio**: `REMOTE HOST IDENTIFICATION HAS CHANGED` hasta que se copiaron las claves de host de gioser a la caja. Así los clones existentes no notan nada; el precio es limpiar el known_hosts propio del `:22` de administración. · El `sshd` del producto escucha sólo en `:22` ⇒ el git por SSH necesitó `Port 2345` y que el usuario `gitea` tenga shell real (su authorized_keys fuerza `command="gitea serv …"`). `caddy` gana su `[[service]]` (con guarda del Caddyfile, HOME propio para los certificados de ACME —o cada reinicio pediría certificados nuevos y se comería el límite de emisión— y la nota de por qué corre como root) y el perfil lo arranca. Consecuencia para la granja: las 23 recetas que clonan por `https://git.tawasuyu.net/…` **ya apuntan a la caja**. Comprobado: el worker resuelve 2.29.29.217 y `git ls-remote` responde. Revertir, si hiciera falta: `arjectl start openrc-gitea` en gioser y los dos `git` de vuelta a CNAME. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
4d77d232d3 |
la mudanza sin terceros: las fuentes de tawasuyu por HTTPS público, y el orden del cutover
Tres cosas, y las tres quitan vueltas. 1. NO HACE FALTA NINGUNA CREDENCIAL EN EL WORKER, Y TAMPOCO UN USUARIO NUEVO `tawasuyu/tawasuyu` y `sergio/takana` son repos PÚBLICOS en el gitea. Las 23 recetas que apuntaban a `gitea@git.tawasuyu.net` (SSH, que exige la clave que es el SSH de todo) pasan a `https://git.tawasuyu.net/…`. La URL es locator y NO entra en `hash_inputs` (ADR 0013): hecho con control antes/después, **ningún ArtifactHash se movió**. Medido en el worker: `git clone --mirror --filter=blob:none` + `git archive` extrae el árbol (155 M) **sin una sola credencial**. Un usuario propio en gitea sólo haría falta para un repo PRIVADO; hoy ninguna receta usa uno. Si mañana hace falta, es una cuenta de máquina con acceso al repo que toque — nunca la clave personal. De paso, tres recetas decían «HUB-ONLY: el worker secretless recibe Connection refused». Ya no es cierto y el comentario decía lo contrario de lo que pasa: corregido en las tres. 2. LA COPIA FUERA LA DA LA MUDANZA, NO GITHUB (decisión del usuario) El «paso 1: espejar los 26 repos» deja de ser el paso 1. En cuanto el gitea vive en la caja nueva, esos repos dejan de existir sólo en la máquina que se borra — que es lo que el paso pedía. El objetivo es dejar de pagar dos máquinas, no sumar una dependencia. `espejar-repos.sh` queda como herramienta disponible. 3. EL §9 REESCRITO: qué está hecho, qué falta y en qué orden Hecho y cerrado: la caja arranca takana puro · store/grafos/granja/respaldo · repo firmado · la imagen del perfil servidor con gitea sirviendo 200 supervisado por arje · `arjectl` · y el ensayo con los datos REALES de gioser corriendo sobre takana. Falta, en orden: actualizar la caja a la imagen nueva (`upgrade`, no `dd`) → mudar los datos del gitea (`.backup` + rsync, con el origen parado en el corte) → caddy con los 6 dominios vivos → DNS → verificar desde fuera con un clone real → el resto de servicios del censo → borrar gioser con las 8 puertas en verde. Bloqueantes con su tamaño: `git` sellado sin `remote-http` (afecta a clonar DESDE una caja takana, no al gitea que sirve; re-sella una raíz de `base`) y la puerta 5, que no bloquea la mudanza. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
390fdf2dcb |
ensayo con los DATOS REALES de gioser: su gitea corriendo en takana — y el git de la distro no clona por HTTP
Sin tocar gioser (todo lectura) y en la VM desechable. Dentro de la VM: `<title>GioSer Gitea: Git with a cup of tea</title>`, 200, `/explore/repos` listando los repos de verdad (sergio/takana, tawasuyu/agora, card, chasqui, cosmos, khipu, llimphi…) y el clon de uno de ellos con 478 commits y HEAD correcto. El snapshot de la DB se saca EN CALIENTE y sale consistente: `sqlite3 gitea.db ".backup …"` con el servidor vivo, 1,5 s para 314 M, `integrity_check` ok, 44 filas en `repository`. Copiar el fichero a pelo mientras el servidor escribe es justo lo que no hay que hacer. Tres detalles que sólo aparecen con los datos puestos: · **El uid del origen no es el del destino.** Los ficheros llegan con su uid NUMÉRICO y el gitea de gioser es 969, no el 916 que declaraba la receta. O se chownean 2 G —lento, y hay que acordarse— o gitea no puede leer sus datos, y eso no falla al copiar: falla al arrancar. La receta pasa a 969, alineada con el origen. El ArtifactHash no se mueve (`[[user]]` está fuera de hash_inputs). · El `app.ini` de gioser escucha en `127.0.0.1:3002` porque allá caddy hace de proxy: la caja sirve, pero sólo desde dentro. `HTTP_ADDR`/`ROOT_URL` son adaptación, no copiado. · 🧨 **El `git` de la distro no puede clonar por HTTP**: `git: 'remote-http' is not a git command`. Medido sobre el artefacto sellado: `git-core/` trae `git-remote-ext`, `git-remote-fd` y `git-http-backend` (el lado SERVIDOR) y no `git-remote-http`; `strings` da 0 referencias a libcurl pese a que `curl` está en `[deps] build`. No se había notado porque en el hub se usa el git de Artix, no el sellado. Un hub nuevo no podría clonar el repo por HTTPS. Re-sellar `git` es raíz de `perfil.base` ⇒ unidad propia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
fd09ca77b0 |
arjectl en la imagen: relanzar en caliente — y el genesis NO alcanzaba para eso
El servicio que falla y agota su backoff sólo se podía recuperar reiniciando la máquina entera.
Ya no. Y no hubo que escribir nada: **el cliente existía en tawasuyu y el crate se llama `arje-ctl`**
(el binario, `arjectl`). Buscarlo por `arjectl` no lo encontraba, y de ahí salió mi conclusión falsa
de que había que implementarlo — el protocolo ya traía ListEntes, SpawnCardFromDisk,
StopCardFromDisk, KillEnte y EnteStatus.
`recipes/arjectl.toml` lo construye del MISMO commit que `arje-zero` (98db584f), y no por comodidad:
el bus es un protocolo entre dos binarios y un cliente de otro árbol puede conectar sin entenderse
con el init. Publica sólo `arjectl`; el crate también produce un `systemctl` de camuflaje que acá no
se instala — en una distro sin systemd, ese nombre en el PATH invita a escribir runbooks con el
verbo ajeno.
⚠ EL HUECO QUE SÓLO SE VE USÁNDOLO: el genesis de la seed dice qué arranca AL BOOT, pero
`start`/`restart` usan `SpawnCardFromDisk`, que lee `/etc/arje/cards.d/<label>.json` — y el armado
no lo escribía:
$ arjectl start gitea
Error: arje-zero rechazó: card gitea: No such file or directory
(buscada en /etc/arje/cards.d/gitea.json)
Son dos preguntas distintas —qué arranca solo, y qué se puede encarnar a pedido— y arje las responde
desde sitios distintos. `inyectar-cards.py` escribe ahora los dos árboles, y en `cards.d` escribe
TODAS las cards, no sólo las nuevas: `sshd` viene del product-rootfs y tampoco era relanzable.
Medido con la VM arrancada UNA sola vez: la imagen trae `cards.d/{gitea,sshd}.json` · `list-units`
da PID/CPU/MEM/HILOS/reinicios · poner el `app.ini` + `arjectl start gitea` ⇒ **GET / 200 sin
reiniciar** (uptime 6 min) · `arjectl restart gitea` cambia el PID (118 → 192) y sigue en 200.
⚠ Y un aviso que costó un HTTP intermitente: **`arjectl start` sobre un Ente YA VIVO lo DUPLICA** —
`SpawnCardFromDisk` no deduplica por label y arje le da un ULID nuevo. En gitea el síntoma fue
`unable to lock level db … resource temporarily unavailable` y un `[F]`: dos servidores peleando por
el mismo estado. Para relanzar se usa `restart`, o se mira `list-units` antes. Un `start` idempotente
es trabajo de arje, no de esta imagen.
⚠ Deuda anotada, no barrida: `arjectl` va en `perfil.servidor` porque este frente es el que lo pagó.
TODA imagen de takana corre arje-zero como PID 1 y ninguna se puede operar sin él ⇒ el argumento
para subirlo a `base` es fuerte, y es una línea. Se deja como decisión.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
9cb80fcfd6 |
SDD 28 §6.11: la imagen del perfil servidor con gitea ARRANCA y sirve — 200 desde fuera de la VM
PID 1 = arje-zero · gitea encarnado por él (ppid=1) · corriendo como uid=916, la cuenta que declara `[[user]]` · escuchando en :3000 · `GET /` devuelve 200 con <title>takana git</title> · y su gitea.db creada por él mismo. 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]] → service-cards → genesis de la seed → arje encarna → setuidgid → sirve. Queda escrito lo que NO está probado: el app.ini, el usuario del sitio y los datos se pusieron A MANO en la VM para llegar al 200 — es justo lo que la mudanza tiene que traer de gioser. Y la imagen no trae `arjectl`: tras poner la config hubo que reiniciar, porque el backoff de restart se agota y no hay forma de relanzar un ente en caliente. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
6f5cc2629a |
mudanza: de dónde sale cada binario — y la herramienta corrigió un error mío
«Instalá el paquete» es lo que uno ya sabía. La respuesta útil sale de cruzar dos hechos: quién posee el fichero en el ORIGEN (el censo se lo pregunta al gestor de paquetes de esa máquina, en un lote) y si el corpus de takana tiene una receta con ese nombre. receta-takana → instalarlo del repo firmado, NO copiar el binario paquete-ajeno → escribir receta, o qorpa (ADR 0015) suelto → nadie lo provee: llevarlo con su entorno o escribirle receta Medido sobre gioser (37 servicios vivos): 4 receta-takana · 4 paquete-ajeno · **11 SUELTOS** · 7 sin binario legible. Los sueltos viven en `/usr/local/bin` o en un home (`qdrant`, `matilda`, `pacha-secretos`, `act_runner`, `shuma-gateway`) y son los que se pierden al apagar el origen. Sorpresa medida: `/usr/bin/caddy` tampoco tiene dueño — está puesto a mano. **Y corrigió un error mío**: en el SDD 28 escribí que `gitea` no tenía receta y que «es la que más peso tiene». Las dos cosas falsas — `recipes/gitea.toml` existe y está sellada. Lo afirmé de memoria; el programa fue a mirar. Corregido allá con la nota. **Y un fallo del clasificador, que vale como regla**: calculaba la raíz del repo con un `dirname` de menos, no encontraba ninguna receta y contestaba `suelto` A TODO, incluido `caddy`. Un clasificador que contesta siempre lo mismo no clasifica. Ahora falla ruidosamente si no encuentra el catálogo, en vez de dar una respuesta falsa con forma de respuesta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
e5858d23e9 |
SDD 28 §6.13: upgrade apply no es un gestor de paquetes — cada generación ES un árbol
Lo destapó usarlo dos veces seguidas: tras aplicar `zstd-cli` y después `rsync`, el informe dice `- 1 retirados` y `zstd` desaparece. Aplicar un árbol nuevo RETIRA los ficheros del anterior, porque una generación es el estado completo y no un incremento. Es coherente con la ayuda («aplica un árbol Stage1/PRODUCTO») y con que exista `rollback`: lo que se revierte es un sistema, no un paquete. ⇒ Usarlo para paquetes sueltos funciona una vez y se deshace a la siguiente. La forma correcta de poner una caja al día es componer el árbol del perfil —lo que ya hace `servidor-image.sh`—, sellarlo y aplicar ESO. Que además sería la «actualización en sitio probada» del SDD 19 §5.2 de verdad: una imagen entera con rollback sobre una caja viva. La caja quedó con `zstd` y `rsync` puestos a mano: funciona y no es el camino, igual que el `git`. De paso, verificado que el arreglo de `recipes/rsync.toml` sirve: el respaldo ya no imprime el aviso de compresión degradada — `compress list` pasó de `zlibx zlib none` a `zstd zlibx zlib none`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
426093cd20 |
SDD 28 §6.13: takana upgrade probado de verdad — apply, rollback y un corte duro
Probado con un paquete que la caja necesitaba (`zstd-cli`): apply deja generación, la caja pasa a desempacar su propio lab, `rollback` devuelve el estado anterior fichero a fichero (9 restaurados, 1 borrado), y tras el arreglo de durabilidad **sobrevive a un `hcloud server reset`, que es un corte DURO y no un apagado limpio**. Y cruza la frontera que `hydrate` no puede: store en el volumen, `/` en el disco local. Es el único camino de actualización que funciona en una caja instalada. El bug que destapó está en el commit anterior: `write_atomic` era atómico frente a otros procesos y no frente a un corte, así que el primer upgrade se perdió al reiniciar y `recover` abortaba con la huella misma del corte. Verificado en la máquina en los dos sentidos: binario viejo ⇒ estado en 0 bytes; binario nuevo ⇒ estado íntegro. `recipes/takana.toml` re-pineado al commit del arreglo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
19576e0039 |
SDD 28 §6.12: la caja dejó de ser desechable, y hydrate no puede cruzar el store
Dos consecuencias de que la caja ya sea un hub: 1. **`dd` de la imagen ya no es actualizar: es perder.** Sobrescribe el disco local, donde ahora viven el clon, las dos claves, las credenciales del respaldo, `/srv/repo` y caddy. El store se salva sólo por estar en el volumen. Y peor: la imagen etiqueta su partición local como `hammer-store`, así que tras un `dd` habría DOS filesystems con esa etiqueta y el `findfs` del arranque elegiría cualquiera. El `dd` es para PROVISIONAR; actualizar es `takana upgrade`, que existe con generaciones y rollback y está sin probar acá. 2. **`takana hydrate` no puede proyectar al root vivo**: hidrata con hardlinks y en una caja instalada el store es SIEMPRE otra partición que `/` (`Cross-device link`). No es culpa del volumen — en el layout original `/store` es sda4 y `/` es sda2. Hidratar al FHS vivo nunca fue posible en una caja instalada; funciona en el hub porque ahí store y destino comparten filesystem. El `git` arreglado se instaló copiando el artefacto a mano: funciona, y no es el camino. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
9afe5afecc |
SDD 28: PUERTA 4 CERRADA — la granja late en la caja nueva
Con el volumen `takana-store` montado, la cosecha completa del worker corrió: **1369 → 1575 artefactos, 0 vacíos**, exactamente los 206 que faltaban. 12,4 G por la red para 39 G lógicos (`speedup 3.14`, el ahorro de `-H`); `/store` queda en 73,7 G de 97,9. Anotado: el enlace con el worker va a 11 MB/s y no a los 90 de gioser↔caja, porque el LXC vive en el Proxmox de gioser, fuera de la red de Hetzner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
40002523b2 |
SDD 28 §6.11: la distro traía un git que no podía clonar — y lo destapó querer ser un hub
`git ls-remote` andaba y `git clone` moría con `invalid index-pack output`, así que parecía un fallo de red. El informe COMPLETO —no la última línea— decía qué y dónde: sha1dc leyendo un `uint32_t` desalineado, abortado por el runtime ubsan que `-fsanitize=undefined` en CFLAGS había enlazado. Arreglado por los dos lados: el sanitizador vuelve a ser sólo de enlace, y `-DSHA1DC_FORCE_ALIGNED_ACCESS` quita el UB en la fuente. `git` es raíz de `perfil.base`, así que esto arregla la distro entera y no sólo al hub. De paso queda anotado que la clave del gitea es `~/.ssh/tawasuyu`, no la de la granja. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
543676e1e9 |
SDD 28 §6.9: PUERTA 8 CERRADA — los fósiles, decididos uno por uno
Decisión del usuario (2026-09-11): **`terapeuta.ec` y `andino.ec` MUEREN**. Los 279 M de `/var/www/terapeuta` se van con la caja y no se archivan. Los otros fósiles (aura, sigma, kosmofono, gitea-redir, api.gioser, mail.sigma) también mueren: ni DNS ni ficheros ni dueño. `summa`/`api.summa`/`dev.summa` no se mudan porque ya viven en otra máquina — sólo hay que sacar sus bloques del Caddyfile al apagar gioser. De las 19 entradas, **13 no se mudan**; la superficie real son 6 sitios y el que pesa es el gitea. Que quede escrito ES la puerta: el día que se borre gioser, `terapeuta.ec` no se va a poder recuperar, y la diferencia entre "lo decidimos" y "se nos pasó" es ese párrafo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
fb8bbba0b9 |
SDD 28 §6.10: el respaldo desde otro hub destapó CINCO bloqueos, uno de ellos destructivo
El más grave: el paso del repo va con `--delete`, y el `/opt/takana` de la caja llegó por `rsync --exclude=.git`. Una corrida real desde ahí habría BORRADO `.git` del Storage Box — el historial entero — en silencio, porque rsync haría exactamente lo que se le pidió. La regla que sale y que vale más allá de este script: **un `--delete` convierte «respaldar» en «sincronizar», y sincronizar desde un origen incompleto no sube menos: BORRA.** Cualquier respaldo con `--delete` necesita una guarda de completitud del ORIGEN, no sólo del destino. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
52144e040f |
SDD 28 §6.9-6.10: los 19 dominios sondeados uno por uno, y el respaldo corriendo desde la caja
**Puerta 8 (fósiles), sondeada por DNS Y por HTTP**: de las 19 entradas del Caddyfile, gioser sólo sirve **6** de verdad (las dos landings, el gitea x2, sergio x2). Las otras 13 NO hay que mudarlas: 8 no tienen DNS, 2 dan 502, y **3 ya viven en otra máquina** (`summa`, `api.summa`, `dev.summa` → 154.197.1.2) aunque el bloque de Caddy siga en gioser. O sea que la superficie real a mudar son 6 sitios, y el que pesa es el gitea porque aloja el `origin` de takana. ⚠ `terapeuta.ec` es el único fósil CON DATOS (279 M en /var/www) y sin DNS: hay que decidir explícitamente si se archiva o muere con la caja. **Puerta 7**: la caja alcanza el Storage Box y refresca el manifiesto — 3140 artefactos, con 2 VACÍOS detectados y excluidos (`gnome-desktop` ×2). Para llegar ahí hubo que arreglar tres bloqueos de la misma familia —*el instrumental asume que el hub es gioser*—: la raíz cableada, el binario de desarrollo, y el rsync sin zstd que hacía que el respaldo no se ejecutara en absoluto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
6d90323fb7 |
SDD 28 §6.8: puerta 4 — el camino de la granja probado de punta a punta; falta disco, no ingeniería
Desde la caja, sin tocar el latido de gioser: llevada la clave de la granja y el `.fleet` (los dos fuera de git a propósito), la caja alcanza al worker `PruebasIA`, `estado-granja.sh` corre allá y lee bien el avance KDE, y —lo que cierra el lazo— se cosechó un artefacto REAL del worker (`speedtest-go`, 12 M) con el transporte de la granja: llegó con su `.hammer/recipe.toml` y **su binario CORRE en la caja**. Worker construye → hub cosecha → binario ejecuta. Lo que falta es CAPACIDAD: 206 artefactos del worker que la caja no tiene, ~23 G a la media del corpus, contra 3,7 G libres (94 % de ocupación). Forzarlo repetiría el llenado que dejó 1367 artefactos vacíos. Las tres salidas quedan escritas: enganchar `harkaq-cosecha` (= el cutover, gioser deja de construir en ese instante y hoy está moliendo KDE), un volumen nuevo para la caja (~€5/mes, no interrumpe nada), o podar (deshace la mudanza). Es decisión del usuario, no de ingeniería. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
9a62f452d3 |
SDD 28 §6.7: PUERTA 3 EN VERDE — los dos grafos idénticos byte a byte
gioser: 7435d33d62577e8ce154e8c5e354e0465139cfbc632afe710076ea2223cea605
caja: 7435d33d62577e8ce154e8c5e354e0465139cfbc632afe710076ea2223cea605
`base 61/61 · cli 84/84 · mirada 41/41 · servidor 96/96 · falta 0`, `wanted 3` (chrony, cronie,
logrotate: la deuda declarada), grafo CIERRA, topo-sort OK, en las dos máquinas. Es la prueba de que
un segundo hub está bien montado — no que "funcione".
Costó tres intentos y los dos fallos son la parte que vale:
1. **`unhashable 875`**, todas, con el lab bien y `takana hash` andando a mano. Los scripts asumen un
árbol de DESARROLLO (`HAMMER = ROOT/target/release/takana`); en una caja instalada el binario es
`/usr/bin/takana` y `target/` ni existe. No falla ruidosamente: sale el corpus entero como
`unhashable`, que se lee como "este corpus no se puede hashear".
2. **Dos nodos divergentes** (`aichat`, `zola`): `sealed` en gioser, `never` en la caja. No era deriva
ni pérdida — **tampoco están en disco en gioser**: son los 2 sellados avalados por los MANIFIESTOS
(`work/farm-sellados.txt`, `work/respaldo-sellados.txt`). Y `work/` está gitignored, así que un hub
recién sembrado no los tiene y degrada esos nodos en silencio.
Misma familia que `.fleet`: un fichero fuera de git del que depende una medición compartida.
**Sembrar un hub no es clonar el repo: es repo + store + lab + manifiestos.**
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
0fd87745c6 |
SDD 28 §6.6: el lab es parte de la IDENTIDAD, y copiar un store con rsync -a lo revienta
**Un hub no es repo+store: es repo+store+LAB.** Con python3 ya vivo, `takana hash` en la caja seguía
fallando por el apk db del lab: el toolchain entra en el ArtifactHash, así que una máquina sin lab no
puede ni preguntar cuál es el hash vigente de una receta — no puede computar el grafo. Sembrado el
lab, el hash testigo coincide byte a byte en las dos máquinas (`zlib` → `b3:dc363f26…`).
**Y la primera copia del store murió con el disco lleno, dejando 1367 de 1368 artefactos VACÍOS.**
La puerta 2 corrida EN EL DESTINO lo cazó en el acto — para eso está.
La causa, medida sobre el store de gioser:
du -sh 60 G (hardlinks contados UNA vez)
du -sh --count-links 85 G (hardlinks EXPANDIDOS)
.dmerge 11 G (la caché que hardlinkea al store)
`rsync` sin `-H` NO preserva hardlinks: los expande en copias enteras. O sea que «el store son 60 G»
—lo que dice `du` y lo que uno planifica— son 85 G al copiarlo, más lo que `.dmerge` expanda. La
partición de 68,7 G no tenía ninguna chance.
La copia correcta es `rsync -aH --exclude='.dmerge'`. Y la lección general: **`du -sh` sobre un árbol
con hardlinks no dice cuánto ocupa COPIARLO**; para dimensionar hay que medir con `--count-links`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
7e41ffd0e6 |
SDD 28 §6.5: el muro derribado — de 23 binarios inertes a 1, y sin mover un ArtifactHash
Elegidas las salidas 1 y 2 del §6.4; hecha la 1, que resultó más barata de lo que parecía y desbloquea la 2 en vez de competir con ella. `musl-shared` publica `/usr/lib/libc.so` + `/lib/ld-musl-x86_64.so.1`. Como variante y no tocando la canónica: `musl` es Stage 1 y su `of_tree` es el baseline del selfhost. Control: `takana hash recipes/musl.toml` sigue en `b3:ce952f72…`. Y `musl` no es dep de nadie ⇒ cero re-hasheo del corpus. En la caja: `sh: python3: not found` → `Python 3.12.10`. De 23 inertes quedó **1**, `sqlite3`, que pedía `libz.so.1`; `zlib-shared` ya estaba sellado y sólo faltaba declararla en `base`. La salida 2 (python/perl estáticos) sigue viva pero deja de ser urgente: con el cargador publicado el sistema ya no cuelga del rootfs del lab. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
be368df070 |
SDD 28 §6.3-6.4: puerta 2 en verde, el disco arreglado, y la puerta 3 choca con un muro estructural
**Puerta 2 ✅**: 1362 artefactos reales, 0 vacíos. (El primer conteo dio «3 vacíos» y eran `.mirror-tmp`, `.bootstrap-tmp` y `.divergen` — directorios de trabajo, no artefactos: el chequeo tiene que acotar por el patrón `<hash>-<nombre>`, no listar el directorio.) **El disco**: la imagen ocupaba 7 G de 76,3 y los otros 69 eran inalcanzables. Arreglado; `/store` en la caja pasó de 487 M a **68,7 G**. Con eso el store de 55 G entra en disco local y **la mudanza ya no depende de desenganchar el volumen de gioser** — que era la parte que paraba la granja. **Puerta 3 🚧 BLOQUEADA, y no por configuración.** Al ir a computar los grafos: $ python3 -c "print(1)" sh: python3: not found <- y `command -v python3` decía /usr/bin/python3 Está y no arranca: es un ELF DINÁMICO que pide `/lib/ld-musl-x86_64.so.1`, y **en la imagen no hay ningún cargador dinámico**. Tampoco en el store: el artefacto `musl` publica sólo lo estático (`libc.a`, `crt*.o`, headers) — no hay `libc.so` ni `ld-musl` en NINGÚN artefacto del corpus. Lo que hace que esto funcione en gioser es el rootfs Alpine del LAB. Contado sobre la imagen: **23 binarios inertes de 726**. La suite binutils entera, más perl, python3, sqlite3, flex y nft. Frena la mudanza porque TODO el instrumental del proyecto es Python (build-state, targets, yupana, hydrate-profile, drenar, triaje): un servidor takana no puede correr ni una de sus herramientas. Las tres salidas —publicar el cargador desde musl (toca Stage 1 y mueve el baseline del selfhost), python/perl estáticos (frente propio), o aceptar que el instrumental vive en el hub (contradice apagar gioser)— son decisión de ADR. Queda escrito, medido, y sin tocar nada de Stage 1 de rebote. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
f4efd8b3e3 |
SDD 28 §5: la caja sirve su propio repo firmado — y consumirlo choca con la decisión del SDD 27 §4
Cerrado: clave de release estable, 88/88 del perfil publicados con hash anclado, servidos por la caja, y el control de firma en los dos sentidos (confianza buena ⇒ instala; confianza vacía ⇒ aborta). El lazo destapó dos cosas que sólo se ven cerrándolo: - **`strip_debug` no viajaba en el `.swm`** y ES entrada de hash ⇒ 40 recetas del corpus (18 de las 88 de este perfil) no se podían instalar. Nadie lo sabía porque nunca se había hecho el viaje completo receta → paquete → `install --require-signed`. - **El loopback nunca se levantaba.** `lo` DOWN, y el síntoma era un timeout de 30 s con caddy escuchando: un cuelgue que parece del servidor, no un "connection refused". Y deja el límite escrito, que es lo que más vale: **la caja sirve, verifica y descarga, pero NO puede reproducir**. `install` construye desde fuente y eso exige el LAB ENTERO en el cliente — no es que falte un compilador: el lab entra en el ArtifactHash, así que sin él no se puede ni calcular el hash a comparar. Es el SDD 27 §4 visto desde el otro lado: para cualquiera que no sea un hub de build, reproducir no es una opción, y el default tiene que ser hidratar artefactos firmados. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
3870ca3566 |
SDD 28 §3.6-3.8: la caja corre takana puro — y el muro era la puerta de enlace, no el arranque
Puerta 1 pasada: PID 1 `arje-zero`, kernel 7.1.2 propio, `/dev/sda3 -> /store` y `/dev/sda4 -> /var/lib/hammer` montados por etiqueta, IP y salida por `netup`, y se entra por SSH. Una caja Hetzner sin una línea de Debian debajo. Escribir los 7,1 G comprimidos: ~17 s. Queda escrito lo que costó, que es lo reusable: - **El kernel del corpus no sirve para hcloud** (`linux.toml` apaga SCSI; el disco es virtio-SCSI). `linux-generic` sí, y su `CONFIG_SCSI_VIRTIO=y` no está en la receta: lo pone `defconfig` y sólo se ve en el `.config` que el artefacto publica. La receta dice lo que se cambió; el `.config` sellado dice lo que quedó. - **La hidratación pisa `/sbin/init`** con el de busybox: la imagen habría arrancado perfecta con el init equivocado. - **La puerta de enlace de Hetzner está FUERA del prefijo** (IP `/32`, gw `172.31.1.1`). Sin ruta on-link el default se rechaza y la caja queda con IP y sin salida — arranca, levanta sshd y no se puede entrar. Nunca había aparecido porque netup sólo se probaba contra el slirp de QEMU, que da un /24 con la puerta dentro: el único entorno de prueba no contenía el caso. - **Un binario dentro del `product-rootfs` está congelado**: arreglar netup no llegaba a la imagen hasta declararlo raíz del perfil. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
f287b3045c |
SDD 28 §3.5: la caja está creada — y el precio de la tabla no dice que la máquina exista
`takana` (id 165447050, cx33, hel1, 2.29.29.217, €8,49/mes). Va sin el label `role=hammer-worker` a propósito: `farm-down.sh` y `deadman.sh` borran SÓLO lo que lo lleva, así que ninguna automatización de la granja puede tocarla. Sin protección de borrado, también a propósito: es la caja de sacrificio. **`cx53` no se pudo comprar — `Available: no` en las TRES ubicaciones.** Y la disponibilidad cambia por día: `cx33` daba `no` en hel1 el 09 y `yes` el 10 con la misma consulta. `hcloud server-type list` devuelve precios de tipos que no se pueden crear ⇒ la recomendación de ayer (cx53 a €29,49) estaba hecha sobre la tabla de precios, no sobre el stock. Queda escrito para no repetirlo. **Tres cosas del §3 quedan confirmadas en la caja DESTINO, no inferidas de gioser:** - Arranca por BIOS (sin `/sys/firmware/efi`) **y aun así la imagen trae un ESP** (`sda15`, 244 M vfat). ⇒ **una partición EFI presente NO significa arranque EFI**; un instalador que decidiera la rama mirando particiones elegiría mal justo acá. `takana-live-install.sh` mira lo correcto. - `console=tty1 console=ttyS0` viene en el cmdline de Debian ⇒ el hueco del serial es real: sin él la consola web de Hetzner no muestra nada. - `eth0` con `/32` dinámico por DHCP, idéntico a gioser ⇒ es el caso exacto de `netup`. Anotado lo que la caja arrastra: 7,6 GiB y CERO swap (misma clase que el `global_oom` de gioser ⇒ no construir pesado ahí) y `sshd` con `PasswordAuthentication yes`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
c599daa346 |
SDD 28: el servidor de producción — y perfil.servidor declarando la deuda que no se ve
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |