972eb6619dcf119cbb7916cd20e56ee42f00bc29
75
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
972eb6619d |
espejo provisional del repo en takana.gioser.net/repo, sin firmar y diciéndolo
Mientras repo.gioser.net espera su vhost (root), el catálogo queda servido bajo el sitio que Caddy ya atiende: /work/sergio/gioser-web/takana/repo, escribible sin root y con certificado válido. 173 paquetes; `install zsh` desde la URL pública tarda 0,22 s (1331 ficheros) y el binario corre. El índice va SIN FIRMA a propósito. La clave de release está en /root/.config/takana/keys y desde la jaula no se alcanza; firmarlo con otra habría sido peor que no firmarlo, porque una autoría inventada se lee igual que una real — es la «firma sin gestión de claves» que repo-perfil.sh se niega a hacer. Se le quitó la firma de la clave de prueba con la que se ensayó. Control: --require-signed lo rechaza. Dos cuidados, porque vive dentro del clon de git de otra web: repo/.gitignore con `*` (el directorio se ignora a sí mismo, así un `git add -A` en gioser-web no se lleva 173 .tkn) y hermanas.py no lo borra (sólo escribe index.html). Comprobado que el clon de la web sigue limpio. Se quita con un rm -rf el día que repo.gioser.net sirva el firmado. No cuenta como el segundo origen del ADR 0014: un origen sin firma no es un origen. |
||
|
|
2e3b8101c0 |
publicar-repo: como root, el scratch de build va aparte — si no, envenena el árbol del usuario
`work_root` sale de `dirname(store)/work`, o sea `/work` con el store en `/store`, y ahí `sources` y `out` son ENLACES a `/work/sergio/work/…`, que es del usuario. Publicar como root dejaría directorios de root en su árbol y su siguiente build moriría con un permiso denegado que no menciona la causa. No es hipotético: root ya congeló el clon de tawasuyu del dueño dejándole 24 entradas suyas dentro del `.git`, y del lado de root no falló nada (ver publicar-webs.sh). Si corre como root y nadie fijó TAKANA_WORK, lo manda a /var/tmp/takana-publicar-work y lo dice. Comprobado que el override no mueve el hash: zsh sella en b3:0be3630d… con y sin él. Y el mensaje final deja de decir «falta el vhost» cuando lo acaba de poner: si se instaló y aun así no responde, lo que hay que mirar es el certificado, el DNS y el repo, en ese orden. |
||
|
|
b7d30f2f15 |
publicar-repo: el vhost, con validación y vuelta atrás — y publicar deja de ser compilar
Dos cosas que el ensayo del guion destapó, las dos medidas: 1. `repo-perfil.sh` NO acotaba el build. `pack --build` es instantáneo con cache-hit y una compilación entera si el artefacto no está sellado, así que «publicá el repo» se convertía en silencio en «ponete a compilar el perfil»: la corrida se puso a moler `libnftnl` en una caja de 4 cores que ya estaba a load 46, y después venía `rust`. `build-repo.sh` llevaba `timeout` desde siempre; el que se corre en producción, no. Ahora BUILD_TIMEOUT (120 s) y SKIP_UNSEALED=1, con el vencimiento tratado como «no estaba sellado» y no como error. Resultado del perfil entero: 173 anclados, 0 sin ancla, 2 saltados (os-release, rust), sin compilar nada. 2. `--install-vhost` pone el bloque de Caddy sin que haya que editar a mano, pero el orden es lo único que lo vuelve seguro: copia → añade → `caddy validate` → SÓLO ENTONCES `reload`. Si validate o reload fallan, restaura la copia y recarga con ella. Un reload con la config rota no tira el sitio nuevo: tira los 19 de la caja. Probado en los dos caminos contra un Caddy de juguete (el de la caja no tiene el admin abierto): con reload bueno, el admin muestra el server nuevo sirviendo el repo y el sitio previo intacto; con reload fallido, el Caddyfile vuelve byte a byte y no queda rastro del bloque. Y `repo-perfil.sh` pasa a respetar TAKANA del entorno: la guarda de publicar-repo.sh comprobaba un binario y se publicaba con otro, que es peor que no tener guarda. |
||
|
|
0d6599ac14 |
servidor: repo.gioser.net existe (A → 2.29.29.217) y el guion que lo publica sin tocar el Caddyfile
El nombre está dado de alta en la zona de Hetzner con la convención de la zona (A, ttl 60, la misma IP que takana/git/gitea/www). Resuelve — comprobado contra Cloudflare; el resolutor de Google aún servía el NXDOMAIN cacheado, que es negativo de 1 h por el `minimum` del SOA y no un fallo del alta. ⚠ La API vieja de DNS de Hetzner (dns.hetzner.com/api/v1) ya no es la buena y devuelve HTML de la consola web, que es lo peor que puede devolver: no falla, contesta. Las zonas están hoy en la API de cloud y las lee el MISMO token de ~/.config/hcloud/cli.toml (gioser.net = zona 986350). Hoy el nombre resuelve y no sirve nada: falta el vhost, que es root. `publicar-repo.sh` hace la parte publicable —repo firmado en /srv/repo + verificación contra trust/— y para imprimiendo el bloque de Caddy en vez de editarlo: esta caja sirve 19 dominios y su Caddyfile ya lleva 20 `.bak`. Y aborta antes de publicar si el takana de la caja es anterior al arreglo del SDD 28 §5.5 (se detecta porque no conoce `outdated`): con uno viejo el repo sale con las 16 recetas multi-parche irreproducibles y 8 paquetes prometiendo un binario que no existe. |
||
|
|
7be17b5a2a |
pack: el control del target_bin AVISA, no aborta — abortar dejaba 65 paquetes fuera del catálogo
El control que agregué en el commit anterior era el natural y estaba mal: publicar el perfil `servidor` entero con él dejó 65 de 175 paquetes fuera, porque son LIBRERÍAS (zlib, ncurses, openssl, musl-*, gmp…) que no traen ningún binario y existen para que otros las resuelvan como dep — un campo que su consumidor ni mira habría tirado un tercio del repo. Lo destapó el barrido, no el razonamiento: la versión que abortaba pasaba los tests igual. Ahora: corrige el path si el binario quedó en otro bindir (que es el caso que importa), y si no hay ninguno lo dice y publica igual. Medido con el perfil `servidor`: 162 publicados y firmados, 8 con target_bin corregido (bash, sed, tar, parted, dhcpcd, squid, wpa_supplicant, zsh — todos publicados hasta hoy prometiendo un path que no existe), 64 sin binario, 12 sin sellar en este store, 1 fallo (os-release, ya conocido). Contra el repo firmado por HTTP: parted (6 parches + binario en /usr/sbin) instala en 0,17 s y responde 3.7; bash responde 5.3.0(1). Queda vivo: `install <librería>` directo sigue fallando tras hidratar (el .swm exige que el target_bin exista). Hacer el campo opcional toca el formato — su propia unidad de trabajo. |
||
|
|
9fe1e010e2 |
paquetería: zsh se puede instalar — los parches múltiples viajaban unidos y el target_bin era una adivinanza
Cerrar el lazo servir→instalar sobre `zsh` (pedido del usuario) destapó dos fallos de la familia
del `strip_debug` del SDD 28 §5.2: cosas que el `.swm` no transportaba fiel y que sólo se ven
cerrando el lazo, no leyendo el código.
1. `pack` CONCATENABA los N parches en el `patch` inline único, y `hash_inputs` mete una entrada
por parche (con `of_inputs` length-prefijado) ⇒ el receptor sellaba en otra dirección y el
`expected_hash` no coincidía NUNCA. Medido: zsh anclado b3:0be3630d…, reproducido b3:d6329a62…
(idéntico al de una copia de la receta con sus 6 parches concatenados a mano). Afectaba a las
16 recetas del corpus con ≥2 parches. Ahora viajan como lista `patches`, en orden, un fichero
por parche del lado del receptor; el `patch` único se sigue leyendo para no invalidar lo
publicado. Regresión con control negativo en swm_bridge.
2. `target_bin` se adivinaba `/usr/bin/{name}`; zsh instala en `/bin` (--bindir=/bin), así que el
paquete prometía un path que el artefacto no tiene y el error salía en el cliente DESPUÉS de
hidratar 1331 ficheros. Con `--build` el artefacto está delante: se comprueba, se corrige
diciéndolo, y si no hay ningún binario con ese nombre se ABORTA al publicar.
Y el verbo que faltaba para «avisame cuándo actualizar»: `takana outdated` compara la DB de
instalados con el índice firmado (por hash, no por versión: una re-publicación con la misma
etiqueta es otro artefacto). No construye, no baja .swm y no actualiza — instalar es verificar.
Medido tras el arreglo: install zsh ⇒ cache-hit del hash anclado, 1331 ficheros en 0,14 s, zsh 5.9
corriendo.
|
||
|
|
2bbfd80d93 |
«una sola sesión» es del kernel; «un solo Claude» era empaquetado nuestro
El usuario chocó con Resource busy al abrir un segundo agente y lo llamó por su nombre: «no quiero restricciones arbitrarias». Conviene separar las tres cosas, porque desde afuera se sienten igual. Del kernel: dos overlays sobre el mismo upper los niega overlayfs, no nosotros. Eso no se toca. Nuestro: una instancia por persona, de ahí «un solo Claude» — y instancias distintas conviven; crear la segunda cuesta 0 s y 16 K porque la imagen se comparte de sólo lectura. Accidentes, ya corregidos: el directorio de instancias era root-only (una persona no podía crear la suya), ~/.ssh iba ro, /store iba ro (el agente no podía sellar), claude abría /opt/takana siempre y /usr/bin/claude era una copia congelada. Y queda un defecto real que hoy bloquea la segunda instancia: aprovisionarla muere con «Can't mkdir parents for /run/user/0». Medido: esta caja no tiene /run/user —no hay logind— y qorpa arma esa ruta sin alternativa; XDG_RUNTIME_DIR no alcanza porque lo honra otra rama. Es arreglo en el crate. La distinción que importa: la jaula restringe al agente, no a la persona —sergio tiene sudo y manda en la caja—, y dentro de la jaula el alcance es declarativo. Lo indefendible no es que haya límites: es que un límite accidental parezca de diseño. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c7c2337559 |
el informe del agente de adentro: los cinco defectos y las tres trampas
Primer informe hecho DESDE la jaula, y vale más que cualquier inspección desde el anfitrión porque son las cosas con las que se choca al usarla. Queda registrado qué se arregló y —lo importante— DÓNDE vive cada arreglo: 1 y 2 en el upper, que es caché descartable, y por eso hay un guion que los repone tras cada recreate; 3 y 5 en el manifiesto; 4 en el worker. Del 4: se autorizó la clave de la caja, no la de root — credencial distinta y revocable. Del 5: takana NO se compila adentro; el binario es el musl del store, y lo que fallaba es que el target/release del anfitrión enlaza a /usr/bin/takana, que adentro es el de Arch. Y las tres trampas de orientación que el agente reportó: el store real es /store y los runbooks dicen ./store porque están escritos para el hub; hay dos checkouts y no son intercambiables; y el .fleet ausente no es defecto, lo repone la cosecha. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5999aeda45 |
borré /usr/bin/id probando el setuid — qué pasó y por qué no se prueba así
Para saber si el bit setuid elevaba copié busybox sobre /usr/bin/id (busybox elige el applet por argv[0] y necesitaba un nombre válido) y después borré «mis» copias de prueba. Una era el `id` del sistema, y no quedaba otro: /bin/id tampoco existía. El usuario se lo encontró de frente: «claude: line 23: id: not found» y «-bash: /usr/bin/id: No such file or directory». Restaurado como sus hermanos de identidad —whoami y groups apuntan a ../../bin/busybox— y comprobado con id de root, de sergio y con el `id -un` que usa el lanzador. Barrido: 0 enlaces colgados en /bin, /usr/bin, /sbin y /usr/sbin. La lección es de método: para probar una propiedad del sistema no se usa un nombre que el sistema ya ocupa. La sonda correcta fue la que terminé escribiendo —cinco líneas de C a un nombre inventado—. Y es la misma familia que el /etc/shadow del §6.51: la caja no avisa cuando le falta algo esencial, se entera el que lo usa. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f126be8691 |
§6.52: el driver de C queda entregado, y su control destapó un profile.d mudo
El envoltorio existe y está puesto: /usr/bin/cc y c++ sobre el seed-zig que ya estaba en el store, más las tres banderas en profile.d. No mete nada nuevo en el corpus; volver a zig el compilador oficial de la distro sigue siendo decisión de ADR. Y el cuarto control —preguntar por la VARIABLE en una shell de login, no por el fichero— encontró que en esta caja /etc/profile.d no lo leía nadie porque /etc/profile no existía: las banderas habrían quedado presentes, con contenido y sin efecto, y el bash_completion.sh que ya estaba ahí llevaba quién sabe cuánto igual de mudo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a170885357 |
las proc-macros SÍ se pueden compilar en la caja: falta un cc, no un parche
Con rust sellado, la caja compila estáticos sin tocar nada, pero ninguna dylib —y eso incluye todas las proc-macros—. El `-L /usr/lib` obvio EMPEORA las cosas: rompe los estáticos, porque rustc enlaza musl static-PIE con su copia self-contained y ese camino le hace preferir la libc.a del sistema, que no es PIC. Cuatro controles lo aíslan y el error nombra al culpable (`__malloc_size_classes`). O sea que ningún RUSTFLAGS global sirve: apaga un caso y enciende el otro. Quien distingue los dos casos es un driver de C. La caja no tiene gcc ni clang —clang18 en el store son sólo cabeceras—, pero sí tiene zig: el artefacto seed-zig del bootstrap trae zig 0.16.0 y corre. Con `cc → zig cc` y `-C linker=cc -C linker-flavor=gcc -C link-self-contained=no` la cadena completa funciona: la proc-macro enlaza, el programa que la usa compila e imprime, y el estático sigue bien. Las tres banderas son necesarias; la variante fina `=-linker` es inestable en este triple. Lo que queda es una decisión, no trabajo: seed-zig no tiene receta —es producto del bootstrap—, y volverlo el `cc` del sistema es elegir el compilador de C de la distro. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d4896c2999 |
pagada la deuda: el git del corpus vuelve a clonar por HTTPS
El §6.50 quinquies dejó la caja trayendo fuentes por SSH porque su git no tenía remote-https. Eso era el síntoma; la causa estaba un paso antes de donde mirábamos —incluida la nota que yo mismo había escrito, que culpaba al Makefile—: el `configure` de git prueba libcurl con AC_CHECK_LIB, que enlaza `conftest.c -lcurl` y nada más, y con libcurl estática faltan sus privadas (-lssl -lcrypto -lz). El test falla, config.mak.autogen se lleva NO_CURL=YesPlease, el Makefile excluye los tres helpers y conserva git-http-backend —esa asimetría en el artefacto era la firma, y estuvo a la vista desde el 14 de septiembre—. El build termina en verde. Medido sobre el artefacto: b3:215742cb… trae git-remote-http, -https, git-http-fetch y los ftp, y CLONA por HTTPS (clone, no ls-remote). En la caja, hidratado y con la reescritura apagada, funcionan tanto `git clone` como `git clone --mirror`, que es el comando exacto del fetch de takana. Y el radio, medido en vez de temido: la nota vieja decía «raíz de perfil.base ⇒ radio grande»; `yupana radio git` dice cero dependientes directos y cero transitivos. El miedo escrito a mano había sobrestimado el costo de arreglarlo, y eso lo mantuvo roto tres días. Las reescrituras url.insteadOf quedan retiradas de la caja; el guion se conserva como salida de emergencia y así lo dice su cabecera. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
32fea6c25a |
el control que cierra el ssh: la caja construyó una receta de tawasuyu de punta a punta
puriy-costura era la única de las 36 que NO estaba en el store de la caja —o sea, un fallo de caché de verdad, no un cache-hit disfrazado de éxito—. La caja la hizo entera y sola: clonó por SSH (111,7 M de mirror), vendoreó, compiló y selló. El hash que predice el hub y el que selló la caja son IDÉNTICOS (b3:7d63655aa5fea3cbc08724887617d7f58b325fcb2e00ac050b1937966c2459e6), que es lo único que prueba que el transporte no contamina el artefacto; 2,2 M con contenido y el binario corre. Con esto la caja cumple la condición que puso el usuario para poder borrar gioser —«con capacidad de compilar tawasuyu y las recetas de takana»— desde FUENTE y por su cuenta, no por artefactos que le llegan del worker. Los dos negativos del guion también están probados: un repo inexistente lo hace fallar, y una huella cambiada la rechaza. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8d1e765bf7 |
las fuentes git se traen por ssh — y la raíz de 5,9 G tiró la caja entera
La caja no tiene git-remote-http, así que las 36 recetas que clonan de git.tawasuyu.net y las ~90 de GitHub no se podían construir ahí. La salida es usar SSH, y sale gratis en hashes porque la URL es un LOCALIZADOR y no entra en hash_inputs (ADR 0013): mismo commit por otro transporte, mismo artefacto. No se toca ninguna receta —el worker sigue por HTTPS— sino la caja, con dos `url.insteadOf`. Tres cosas medidas: (1) cargo vendor clona aparte y con su propio cliente ssh —se plantó por la clave del host, que la tenía con puerto y cargo la busca sin él, y después por autenticación— y se resuelve con `net.git-fetch-with-cli = true`; (2) la huella de github.com se PINEA contra la publicada, no se acepta a ciegas; (3) CARGO_HOME estaba en /root/.cargo, o sea en la raíz de 5,9 G: el vendor bajó 2,7 G, la raíz llegó al 100 % y la caja NO se degradó, se cayó entera —sin HTTP, sin SSH y sin responder al ping, con Hetzner informando «running»—. Rescate otra vez. Rastro que deja un ENOSPC y que conviene saber leer: caddy escupiendo «no space left on device», y mis propios `>>` dejando 105 bytes NULOS al final de /root/.ssh/config —un append que falla por ENOSPC no deja el fichero como estaba: lo deja extendido y relleno de ceros—. Reparado en el rescate y permanente: /root/.cargo → /work/cargo-home, poda horaria de logs (ente-gitea.log había llegado SOLO a 114 M y nadie lo rotaba) y los dos configs reescritos. Tras el arranque: raíz al 54 %, 18 entes corriendo, los 7 dominios de la caja en 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f13f4f542d |
cómo se instala software de tawasuyu en la caja — y por qué no con actualizar-servidor.sh
`install-tawasuyu.sh` y `actualizar-servidor.sh` existen y siguen ahí (el monorepo está clonado en /work/sergio/tawasuyu), pero lo que hacen es `cargo build --release` + `install -m755` a /usr/local/bin: producen exactamente la clase de binario que la mudanza encontró irreproducible —2,7 G de sueltos que nadie provee, tres de ellos corriendo desde un inodo BORRADO— y además escriben servicios en /etc/init.d/, que en esta caja no existe. La forma de acá, hecha de punta a punta con shuma-tui: receta pinneada ⇒ `takana build` ⇒ sellado b3:f795ee0f… ⇒ el artefacto viaja por rsync ⇒ symlink desde el store ⇒ `shuma-tui --help` contesta. El camino completo es el del §6.46 (repo firmado + `takana install --require-signed`), que es lo que convierte «copiar un binario» en «instalar un paquete». ⚠ Y el límite de hoy: el build NO se puede hacer en la caja para recetas que clonan por HTTPS, porque su git no tiene remote-https. Las que bajan tarballs con curl sí construyen ahí (hoy sellaron intel-ucode y sof-firmware); las de tawasuyu se construyen en el worker. Arreglarlo es re-sellar `git` con libcurl, raíz de perfil.base: unidad propia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c8ac57d46e |
sudo repuesto y el taller de trabajo: dónde se programa en la caja
sudo no andaba por DOS cosas a la vez, y una era el corte de energía: /usr/bin/sudo no tenía el bit setuid, y /etc/sudoers.d/sergio HABÍA DESAPARECIDO — lo escribí minutos antes del reset del §6.51 y se fue con el mismo corte que dejó el cortafuegos en NULs. Repuestos los dos, con sync, `sudo id` devuelve uid=0. `visudo` no está instalado y no hace falta: el drop-in en /etc/sudoers.d/ es la forma correcta. (doas sigue dando «Operation not permitted» aun setuid; queda como rareza sin explicar.) LOS REPOSITORIOS ESTÁN EN LA CAJA: los 44 en el gitea mudado el 14-sep (sergio 939 M, tawasuyu 547 M). Lo que faltaba era un CLON DE TRABAJO: sólo estaba /opt/takana, el del hub. Ahora `~/src → /work/sergio` con tawasuyu clonado (478 M) y su origin al gitea de la caja. ⚠ El taller va en /work y no en el home: la raíz tiene 2,4 G libres y /work 43 G. ⚠ Y clonar acá tiene dos trampas medidas: el git del corpus NO tiene remote-https (no se puede clonar por HTTPS), y clonar por SSH exige la llave registrada en la CUENTA de gitea, no en el authorized_keys del sistema. Mientras tanto se clona por ruta del sistema de ficheros, como root (los dirs de organización son 0700 de gitea) y con `safe.directory`. `PROY=/work/sergio/tawasuyu claude` abre el agente sobre ese repo — comprobado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8db55db465 |
la cuenta necesitaba sus LLAVES, no su contraseña
El primer `ssh -i ~/.ssh/tawasuyu sergio@gioser.net -p 2345` dio «Permission denied (publickey)», y era exacto: el sshd de la caja tiene `PasswordAuthentication no`, así que la clave sirve para `su` y la consola, no para SSH. Faltaba `~/.ssh/authorized_keys`, que se creó vacío con la cuenta. Copiadas las dos llaves de gioser (`tawasuyu` y `sergio@tatata`), entra — y `claude` abre en la misma sesión. Dos detalles que hacen que se sienta como la máquina de siempre: `gioser.net` ya resuelve a la caja, y la llave de host es LA MISMA que la de gioser (se copiaron en el cutover del gitea, §6.25), comprobado: SHA256:ltSl+rgr2Uc… en las dos. Por eso no salta el aviso de «REMOTE HOST IDENTIFICATION HAS CHANGED» al entrar al nombre de siempre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a3e891a601 |
me dejé afuera de la caja: 20 minutos caída, y las tres causas encadenadas
Crear una cuenta tiró el servidor. Ninguna de las tres causas era la que parecía: 1. La caja NUNCA tuvo /etc/shadow. Crearlo con todas las cuentas bloqueadas (!) hizo que sshd RECHAZARA LA LLAVE de root: este sshd no acepta login por llave de una cuenta bloqueada. La forma correcta acá es la vieja — el hash en el campo 2 de /etc/passwd, sin shadow. 2. La caja NO reinicia por ACPI: `hcloud server reboot` no hizo nada (arje-zero no atiende la señal). La única forma de reiniciarla desde fuera es `reset`, que es un corte de energía. 3. Y ese corte dejó el reglaset del cortafuegos a MEDIO ESCRIBIR — 5 KB de NULs: ext4 journaló el tamaño y los datos nunca llegaron al disco. Un fichero medio escrito con `policy drop` y sin los `accept` es exactamente una caja que arranca, corre todo y no deja entrar a nadie. ⇒ después de escribir algo que el arranque necesita, `sync` ANTES de cualquier reset. Lo que el rodeo destapó y vale más que el susto: · EL CORTAFUEGOS NO SE APLICABA EN NINGÚN ARRANQUE (su card sale 78 si nft -c falla, y con el fichero corrupto fallaba siempre): la caja llevaba días arrancando SIN FILTRAR, en silencio. Reparado, el arranque ya deja 21 reglas dport, comprobado tras reiniciar. · netup es DHCP-only y falló dos veces; se le añadió al init una IP estática de respaldo con la forma de Hetzner (/32 + puerta scope link)… que no corría, porque su guarda miraba /sys y el init NO MONTA /sys. El diagnóstico que lo probó hubo que escribirlo a FICHERO: sin red, el log tiene que estar en el disco. · el mapa real de particiones: sda2 root · sda3 state · sda4 work · sdb store. El costo, sin adornos: ~20 minutos de caída de los ocho dominios y tres resets duros, uno de ellos disparado por mí a los 400 s mientras la caja estaba haciendo su trabajo. En esta caja cada diagnóstico remoto cuesta un ciclo de 6-8 minutos: el primero conviene gastarlo en dejar evidencia en disco, no en probar una corazonada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eb96c9219e |
el agente corre en la caja y puede construir — la condición que el usuario puso para borrar gioser
$ claude -p "Decí exactamente: CLAUDE CORRIENDO EN LA CAJA" CLAUDE CORRIENDO EN LA CAJA Turno real —API, credenciales, respuesta— desde 2.29.29.217. El CLI es un ELF glibc de 360 M y la caja es musl, así que va enjaulado (ADR 0015, el caso de sergioh-api). La instancia declara red, nesting y SEIS directorios: /opt/takana, /work/dev-fs, /store, /root/.claude, /root/.ssh (ro) y el CLI (ro). Es ANCHA y está dicho: un agente que construye, commitea y empuja necesita lo mismo que un humano; lo que la jaula compra es que el alcance esté escrito y acotado. ⚠ El `takana` que construye NO se instala adentro: sale del store, que ya está concedido — es estático musl y corre igual dentro de una imagen glibc. LA CAJA CONSTRUYE: sellados hoy `intel-ucode` (156 signatures) y `sof-firmware` (2 firmware + 8 topologías). Faltaba una pieza que el lab no traía: `.dev-fs/tools` (zig 0.13/0.16 y go, 1 G) — la imagen del lab empaqueta `alpine/` y nada más, y el primer intento murió con «zig no encontrado». 🧱 Y lo que NO anda, con su rodeo: `takana build` dentro de la jaula muere con «bwrap: Can't mount proc on /proc» aunque `nesting = true`. No es el anidamiento —un `bwrap --proc /proc` a mano SÍ funciona adentro— sino el userns anidado del sandbox del build; probado también con `root = false`, que es la sospecha que el propio código documenta, y falla igual. El rodeo que sí anda, escrito en el lanzador: pedirle el build al ANFITRIÓN por ssh a 127.0.0.1:22022. Así se selló sof-firmware desde dentro de la jaula. Manifiesto y lanzador versionados en scripts/servidor/: una instancia que sólo existe en la caja se pierde con la caja. ⚠ Pendiente menor: la memoria del agente está en `-mnt-vvv-takana` y allá el repo vive en /opt/takana ⇒ el índice POR RUTA no coincide y arranca sin memoria del proyecto aunque los 733 ficheros estén en disco. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7295106943 |
lo único que queda de la mudanza son 150 decisiones sobre datos — y el último rescate
Con las ocho puertas en verde no queda trabajo técnico: queda decidir. La hoja (`docs/state/mudanza-decisiones-pendientes.txt`) trae 150 entradas y 31,9 G, ordenadas por tamaño, con la sugerencia entre corchetes cuando hay un HECHO que la respalde y en blanco cuando sólo lo sabe el dueño. Se aplica en una pasada con `planear.py --decidir`, sin teclas unitarias. Y el último rescate antes de cerrar: `/mnt/vvv/gioserv` tenía 26 entradas sin commitear y su remoto era una RUTA LOCAL de la máquina que se borra. Lo commiteado está a salvo (su HEAD es el mismo que el del gitea nuevo); lo que no estaba en ningún lado —el diff completo, con `api/main.py` +92/−23, y los 4 no rastreados— quedó en takana:/work/mudanza-secretos/gioserv-pendiente/, verificado con 0 ficheros faltantes. No reapunté el remoto ni commiteé nada: eso es del dueño del repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
35fed3aa65 |
PUERTA 7 EN VERDE: el respaldo corre desde la caja — LAS OCHO PUERTAS ESTÁN EN VERDE
respaldados (manifiesto del Storage Box): 3686 en el store de la caja: 1320 de la caja, NO respaldados: 0 ← la cuenta que decide errores y reintentos: 0 y 0 Pero la primera corrida no terminó, y el motivo vale por sí solo: se cayó 40 veces con el MISMO fichero porque una transferencia interrumpida el 11-sep había dejado un parcial en el destino con el modo del origen (-r--r--r--, el store es de sólo lectura por diseño). Ningún intento posterior puede reescribirlo: esa ruta quedaba envenenada PARA SIEMPRE, y el bucle la golpeaba cada 60 s llamándola «corte de red». Arreglado con --chmod=Fu+w (un corte a mitad se retoma) y dando al rsync 23 tres intentos en vez de cuarenta, nombrando la causa probable al tercero. 🪤 Y dos veces el mismo error mío en la misma tarde: `pgrep -f <patrón>` SE ENCUENTRA A SÍ MISMO —el patrón está en la línea de comando del propio pgrep y del ssh que lo lanza—, así que «sigue corriendo» era verdad para siempre y el vigía que armé con esa condición nunca podía dispararse. La forma correcta es el truco del corchete: `grep "[r]espaldo-storagebox"`. Hermano del `pkill -f` que mata tu propia shell. Lo que separa esto del borrado de gioser ya no es técnico: es la decisión del usuario, a mano y nunca por automatización. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c79c7aa9be |
las ocho puertas al cierre del día: siete en verde y una corriendo
2 store entero ✅ 1320 artefactos, 0 vacíos, /store al 62 % tras el gc 4 la granja late allá ✅ §6.43 — con el lab pineado, y la caja gana por uno en los dos grafos 5 el host se instala de su repo ✅ §6.46 — 171 publicados, install --require-signed en verde 7 el respaldo desde la caja ⏳ primera corrida completa en marcha 1,3,6,8 ✅ Lo que separa esto del borrado de gioser ya no es técnico: es la corrida de respaldo terminando y la decisión del usuario — a mano, con las ocho en verde, nunca por automatización. Y lo que queda vivo en gioser es trabajo de la mudanza, no servicios: los ~10 G de árboles personales sin decidir. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ef2f9391d9 |
puerta 5 verde: la caja publica su repo firmado y se instala de él
Estaba a medias desde el §5.4 por el lab. Con el lab instalado (§6.43), cerrada en dos movimientos,
los dos en la caja:
publicar: 171 paquetes con expected_hash anclado, 3 sin ancla, índice verificado contra ./trust,
firmado con la clave de release que se mudó el 16-sep. Falló uno: os-release.
instalar: `takana install duf --require-signed --trust ./trust` ⇒ «release: trusted (by release)»,
deps resueltas, apply OK, registrado. Bajo --prefix y --db propios, sin tocar el sistema.
⚠ El matiz honesto vale más que el verde: el artefacto YA ESTABA en el store, así que se ejercitó
resolver + verificar firma + hidratar, NO reproducir desde fuente. Lo que el lab destrabó y sí quedó
probado aparte es el HASH (§6.43, cuatro de cuatro iguales a gioser), que es donde moría antes. Queda
sin ejercitar «paquete que no está en el store» — y en una caja de producción es discutible que se
quiera: un servidor no tiene por qué compilar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
449028f6cb |
la poda de fuentes ya corre en la caja: 5,4 G — y van 32,2 G recuperados hoy
El paso de poda del latido moría con `/dev/fd/63: No such file or directory` y un `viejos: unbound variable` de consecuencia. La causa no era el script: la sustitución de procesos de bash necesita /dev/fd y UNA CAJA TAKANA NO LO TIENE (gioser sí). O sea que ningún `<(...)` de ningún script del hub funcionaba allá; éste fue el primero en toparse. Arreglado en los dos lados: /dev/fd -> /proc/self/fd en la caja con Card OneShot en el genesis (comprobado: `cat <(echo funciona)` responde), y en el script fuera `<(...)` y fuera `df -B1 --output=avail`, que es GNU — sin eso corría pero decía «libres 0.0 G», y un 0 se lee como disco lleno. Resultado: 2 árboles podados, 5,4 G. Con el store-gc del §6.44 son 32,2 G recuperados hoy en la caja: /store al 62 %, /work al 29 %. ⇒ Van dos parches con forma de Card OneShot para cosas que debería hacer el init (hostname y ahora /dev/fd). Es una lista que conviene no alargar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6901beee93 |
el primer store-gc de la caja: 26,8 G liberados, y el patrón GNU-vs-busybox ya va por cinco
/store pasó de 84,7 G usados (91 %) a 57,9 G (62 %): 337 superados borrados y VERIFICADOS, 1657 → 1320 artefactos. Los 150 huérfanos (11,4 G) quedan intactos por diseño: son el único ejemplar de su nombre. EL CONTROL QUE NO ES OBVIO: 11 binarios de /usr/bin de esa caja son symlinks que apuntan DENTRO del store — los daemons instalados estos dos días. Un gc que borre el artefacto equivocado no rompe el store, rompe el /usr/bin de una máquina en producción. Comprobado antes (los 11 enlazan al hash vigente) y después (los 11 resuelven, 21 entes corriendo). Y el gc no sabía decir sus dos números: `du --files0-from=-` es GNU y busybox no lo tiene; `df -h /home` estaba cableado cuando el store vive en /store (sin /home, `tail -1` devolvía la CABECERA: de ahí «libres: Available → Available»). El primer arreglo también estaba mal —`xargs -d` es otra extensión GNU— y eso imprimió «huérfanos ?»: ese `?` es deliberado, un cero inventado habría dicho «no hay nada que ganar» con 11,4 G en huérfanos. Con `xargs -0` la caja ya contesta 11.4G. ⇒ Cinco tropiezos del mismo día con la misma forma (/dev/tcp, llaves, find -newermt, sustitución de procesos, y estos dos): un script del hub escrito en una máquina GNU no corre en la caja hasta que se prueba ahí. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ba3ed16169 |
la caja ya es un HUB: el lab viajó pineado, hashea igual, y el latido late desde allá
El §6.42 dijo qué faltaba y el usuario eligió darle el lab a la caja. Hecho. EL LAB NO SE COPIA, SE PINEA Y SE TRAE. `lab-image.sh` lo empaqueta determinista (--sort=name --mtime=@1 --owner=0, excluyendo var/log/apk.log), lo publica al Storage Box con el sha en el nombre, y `--traer` VERIFICA EL SHA ANTES DE EXTRAER. El empaquetado de hoy devolvió exactamente el sha que el repo ya pineaba (d1e341d5…): o sea que el lab de gioser nunca derivó Y que el empaquetado es reproducible de verdad, no una promesa del comentario. LA PRUEBA QUE DECIDE no es que arranque: la caja calcula los MISMOS ArtifactHash que gioser en las cuatro recetas de control (zlib, busybox, caddy, shuma-daemon). Con otro lab serían otros números y el store no lo notaría. ⚠ Y UNA TRAMPA QUE ME TENDÍ SOLO: al copiar los 24 artefactos que le faltaban al store de la caja, `rsync -a --files-from=<lista de DIRECTORIOS>` mandó 2.281 bytes y «total size is 0» — pero creó los 24 DIRECTORIOS VACÍOS. `--files-from` no recursa sin `-r`, y un directorio vacío en el store ES UN CACHE-HIT (§3 de CLAUDE.md): `build` lo habría dado por sellado sin construir. Detectado contando, borrados los 24, repetido con `-r`: 3,18 GB y 0 vacíos de 1655. Stores convergidos (copiados también obs-studio y spectacle, justo los dos que el worker no logra construir). La caja no empata, gana por uno: corpus 930 selladas / 1 deuda contra 929 / 2; KDE 1106 / 1 contra 1105 / 2. Latido mudado: cron apagado en gioser, encendido en la caja, y un ciclo corrido a mano mirando lo que publica — `repo al día (ff)` → `estado commiteado+pusheado` con los números buenos. ⚠ Lo que queda apretado es el disco: /store de la caja al 91%, 8,2 G libres. Un hub sella y cosecha: ése es el próximo muro, y `store-gc.sh` no se corrió nunca allá. ⇒ PUERTA 4 EN VERDE. De las ocho quedan dos a medias (5 y 7) y ninguna en rojo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
644be2645b |
el latido NO se puede mudar todavía: un hub no es el que tiene el repo, es el que tiene el LAB
Se intentó el último intercambio —cron de cosecha de gioser a la caja— y salió mal en el primer
ciclo. Vuelta atrás hecha en minutos; queda escrito porque el motivo no era ninguno de los previstos.
Funcionó todo lo mecánico: la caja sembró al worker, leyó su manifiesto, regeneró los ocho ficheros
de estado, pasó los vigías, se puso al día por fast-forward y commiteó+empujó. Lo que publicó es el
problema:
caja : {recipes: 1112, nodes: 1115, unhashable: 1112, ajeno: 3}
gioser: {recipes: 1112, nodes: 1115, sealed: 1105, never: 5, debt: 2}
`unhashable: 1112` no es «sin sellar»: es que NO SE PUDO CALCULAR el hash de ninguna receta, porque
EL LAB ENTRA EN EL ArtifactHash y una caja de producción no lo tiene — a propósito. El commit
|
||
|
|
897b4eaf28 |
INTERCAMBIO de tejido hecho: la identidad cambió de máquina, no de dueño
Apagados en gioser (relay, serve y willay-crosscheck), encendidos en la caja: `tejido` en :4102 y `willay-crosscheck` en :4103, los dos alcanzables desde fuera. La prueba de que la identidad VIAJÓ no es que el servicio arranque —eso pasaría igual con semillas nuevas, que es el fallo silencioso que esto existe para evitar— sino que la semilla sea la misma: `device.seed` y `roster.postcard` con sha256 IDÉNTICO a los dos lados. El PeerId sale de ahí, así que sigue siendo 12D3KooWBw2u…. ⚠ LO QUE SÍ CAMBIA: el multiaddr que los clientes tienen configurado lleva la IP. antes /ip4/204.168.193.248/tcp/4102/p2p/12D3KooWBw2u… ahora /ip4/2.29.29.217/tcp/4102/p2p/12D3KooWBw2u… (mismo PeerId, otra IP) 🧱 SIN EL CORTAFUEGOS HABRÍA SIDO UN VERDE FALSO: el reglaset abría 80/443/1137/2345/22022 y nada más. Con el relay encendido y el 4102 filtrado, `status` diría «corriendo · 0 reinicios» y la flota no llegaría. Abiertos 4102 y 4103 con su ban por tasa; NO el 33097, que era el puerto efímero de un `tejido serve` cliente. Y el fichero quedó IDEMPOTENTE (crear+borrar la tabla antes de definirla), que era deuda del §6.33: 15 reglas dport antes, 21 después — no 36. ⚠ La política que genera ese reglaset NO EXISTE en ningún disco: ni la ruta que su cabecera nombra ni el binario `cortafuegos`. El generado sobrevivió a su fuente, así que hoy se edita a mano. 🆔 Y takana acepta ULIDs que arje-zero RECHAZA: la card no encarnaba por `invalid character`, y era la `L` de «RELAY» — carácter excluido del alfabeto Crockford. `service-cards` lo dio por bueno («26 alfanuméricos») y el consumidor lo tiró. El validador del productor más laxo que el del consumidor siempre termina en un fallo lejos de donde se escribió. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d0a65ad1ca |
las ocho puertas re-medidas: siete en verde o a medias, UNA en rojo — y es un corte, no trabajo
La tabla del §6.3 era del 10 de septiembre. Hoy, entera: 2 store entero ✅ 1632 artefactos, 0 vacíos (eran 1362) · ⚠ /store al 88% 4 la granja late allá ❌ el latido SIGUE en gioser 6 dominios vivos 200 ✅ los 8 contra 2.29.29.217, `sergio` incluido 7 respaldo desde allá ⬖ el script corre y LISTA el Storage Box desde la caja 1,3,8 ✅ · 5 ⬖ (sirve, no consume: reproducir exige el lab entero) La puerta 4 es ahora UNA LÍNEA DE CRONTAB. La caja ya tiene git, python3, rsync, ssh, takana, flock, la llave del worker y el `.fleet`; le faltaban dos costuras y están hechas: `store -> /store` (el script espera ./store, acá es partición) y `target/release/takana -> /usr/bin/takana` (no hay árbol de build en una caja instalada). Prueba de sólo lectura: `estado-granja.sh` desde la caja ve al worker, lee el grafo KDE y dice «CRON DE COSECHA: NO instalado». NO lo instalé, por el mismo motivo que tejido: con el cron en las dos máquinas, las dos harían `rsync --delete` al worker y las dos cosecharían y commitearían. El latido se INTERCAMBIA, no se duplica, y es el último movimiento antes del borrado. De paso, el repo de /opt/takana estaba 5 días atrasado y quedó en origin/main exacto (0 diferencias), con tar de respaldo de los 92 ficheros previos. ⚠ Esos 92 no eran trabajo local: era contenido que YA estaba río arriba metido en el árbol sin commitear — un árbol que parece sucio y está atrasado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ec469aef6b |
los nueve daemons propios declarados — y la línea entre «se muda» y «se INTERCAMBIA»
Nueve de las diez sellaron y están promovidas. Cinco corren en la caja (matilda, tupu, pacha, pacha-secretos, willay-daemon, más el par de shuma de ayer); cuatro quedan instaladas y FRENADAS a propósito, cada una con su guarda diciendo qué falta. ⚠ LA PAUTA DE LOS VHOSTS NO SIRVE PARA UNA IDENTIDAD. Con los sitios la regla fue «mudá el DNS y dejá el origen encendido, así volver cuesta un minuto» (§6.26). Con `tejido` es justo lo contrario: su README dice que la clave que el roster atesta ES la identidad de transporte libp2p (`~/.tejido/device.seed`), así que dos máquinas con la misma semilla no son dos réplicas — son el MISMO PeerId en dos sitios, dos impostores mutuos para la flota. `tejido` se INTERCAMBIA: se apaga allá, se enciende acá, en ese orden. Todo listo para el intercambio (binario, cuenta 964, identidad 700 instalada) y su Card en `cards.d` pero NO en el `genesis`, para que un reinicio no lo encienda. Y arrastra a `willay-crosscheck`, que no es independiente: corriendo su línea a mano dice «no hay roster en /root/.tejido/roster.postcard — emparejá primero». Vive dentro de la red de tejido. `thasnuna` tampoco puede: su INSTALAR.md —escrito hoy por el frente tawasuyu para esta mudanza— pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es glibc de ~300 M (jaula qorpa). De ahí sale un hallazgo que vale para el respaldo entero: la Card `openrc-openclaw` de gioser lleva la API key de su proveedor EN CLARO dentro del JSON de /etc/arje/cards.d/, que se respalda y se copia. Un secreto dentro de una Card viaja a todas partes. `--label` de willay-crosscheck NOMBRA A LA MÁQUINA: en gioser decía `momento`. Ahora sale de `hostname` y la guarda frena si no hay. Y tres veredictos FALSOS en una tarde, los tres por probar con lo que la caja no tiene: `/dev/tcp` (busybox ash no lo tiene) dio los 5 puertos de gioser «cerrados»; la expansión de llaves dijo que los artefactos no habían llegado; y `find -newermt "-2 minutes"` dijo que tupu no escribía. Tupu SÍ escribe: su fichero tiene tamaño CONSTANTE —es una serie fija— así que ni los bytes ni ese find prueban nada; lo que decide es el mtime, medido dos veces con 40 s de por medio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
98cd672a10 |
cuatro daemons propios corriendo en la caja — y la máquina se llamaba «(none)»
Sellaron `matilda`, `pacha`, `pacha-secretos` y `sandokan-watch`; las cuatro promovidas a canónicas (hash idéntico antes y después de moverlas) y las tres primeras ya supervisadas por arje. `matilda` archivó 78 ficheros leyendo el access log de caddy; `pacha` corre con SUS reglas y no con las de fábrica; `pacha-secretos` vuelve a existir como binario — en gioser era un inodo borrado. TRES CORREN COMO ROOT, declarado en vez de heredado. No es descuido: `matilda` lee `/var/log/caddy/requests.json`, que caddy crea `-rw------- root root` y recrea igual en cada rotación; y los tres del trío escriben en el MISMO `/var/lib/tupu`, cuyos 322 ficheros en gioser son root:root — bajar a uno solo deja mezcla de dueños en un árbol compartido. O los tres, o ninguno. La salida limpia (caddy con `mode 640` + cuenta propia del árbol) queda anotada en la receta. `pacha` y `pacha-secretos` sí bajan a cuenta propia: en gioser tampoco corrían como root. 🕳️ LA CAJA NO TENÍA HOSTNAME. `hostname` decía `(none)` y `/etc/hostname` no existía — NINGUNA caja takana lo fija: ni netup, ni el product-rootfs. Nunca importó porque nada archivaba por nombre, hasta que llegó matilda: sus primeros 72 ficheros fueron a `/var/lib/tupu/none/`, y dos máquinas distintas archivarían en la misma carpeta sin que nada falle. Puesto `/etc/hostname` + una Card OneShot en el genesis para que sobreviva al reinicio. Lo correcto es que lo haga `netup`. 🛡️ Y `sandokan-watch` quedaba VERDE SIN VIGILAR: en takana no hay `auth.log` ni journalctl, así que arrancaba, decía «sin fuente de accesos: no se guarda nada» y se quedaba «corriendo · 0 reinicios». Su Card ahora sale 78 nombrando el problema y NO entra al genesis: un vigía que no vigila es peor que uno caído, porque el caído se ve. De paso, dos veredictos FALSOS en una tarde por probar con algo que la caja no tiene: `/dev/tcp` no existe en busybox ash (dio «cerrado» para los cinco puertos de gioser, incluidos :80 y :443, que sirven) y la expansión de llaves tampoco (dijo que los artefactos no habían llegado; habían llegado). Misma familia que el §6.23. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f32b28c63e |
CUTOVER de sergio.gioser.net: el último vhost sale de gioser — ya no queda ninguno
`https://sergio.gioser.net/` y `/shuma/` contestan 200 contra 2.29.29.217, con TLS público. **Ningún dominio resuelve ya a gioser.** ⚠ Y el 200 de `/shuma/` había que mirarlo dos veces: el bloque termina en `try_files … /index.html`, así que CUALQUIER ruta inexistente devuelve 200 con la SPA — un `curl -o /dev/null` habría dado el mismo verde con el proxy mal puesto. Lo que decide es el cuerpo (17 bytes: «shuma-gateway ok») y el control es compararlo con lo que el origen viejo sigue sirviendo. Igual con `/shuma/rpc`: las dos máquinas contestan el mismo 400 con el mismo JSON. Lo construido, en orden: las dos recetas sellaron en el worker con los hashes anticipados y salen `statically linked` (nada de cargador, que era lo que le faltaba al binario glibc de gioser) · la cuenta `shuma` declarada con `[[user]]`, uid 967, y el descenso con `setuidgid` en el argv de la Card porque el payload Native de arje NO tiene campo de usuario · la config que no se regenera (`gateway-token`, `identity.x25519`) instalada, y la guarda de la Card la EXIGE en vez de crearla: un token nuevo deja fuera a los clientes ya emparejados · las dos Cards en `cards.d` Y en el genesis de la semilla · las dos recetas y los dos labels declarados en `perfil.servidor`. ⚠ LA SHELL DE LA CUENTA NO ES UN DETALLE: el default de `[[user]]` es `/bin/false` —correcto para gitea o squid, exactamente lo contrario para un demonio que abre PTYs—. Con la shell inerte el servicio arranca, se supervisa, contesta 200 y cada pestaña muere al instante: un fallo que no se ve al desplegar, se ve al usarlo. Va `shell = "/bin/sh"`. `[[user]]` y `[[service]]` están fuera de `hash_inputs`: declarar todo eso no movió ningún ArtifactHash, comprobado antes y después en los dos. Y una corrección al §6.25: para cambiar el VALOR de un rrset no hace falta DELETE+POST (que deja una ventana sin registro). La API tiene `POST /v1/zones/<id>/rrsets/<n>/<t>/actions/set_records`, que es atómica. Con TTL 60 la resolución pública cambió en menos de 8 segundos. El origen de gioser sigue corriendo a propósito, como en §6.26: volver atrás es una llamada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
628abdbb51 |
mudanza: movidos los guiones, la memoria, los secretos y el estado — verificando en DESTINO
Con las decisiones puestas, se movió lo que no dependía de nada más. Cada copia se verificó del lado del destino, que es lo que el rsync con exit 0 y 1367 artefactos vacíos dejó escrito: 27 guiones (75 KB) → takana:/work/rescate-guiones sha256sum -c ⇒ 27 OK memoria de Claude (6,3 M) → takana:/root/.claude 733 ficheros = 733 transcripts (1,3 G) → StorageBox claude-gioser rsync -an ⇒ 1 pendiente (el vivo) clave PRIVADA de release → takana:/root/.config/takana/keys sha256 igual + pública == trust/ credenciales y config → takana:/work/mudanza-secretos 27 ficheros = 27 estado de sigma/tupu/willay → takana:/work/mudanza-estado 351 ficheros, 68 M Las credenciales NO se instalaron en su sitio a propósito: la caja ya tiene su /root/.ssh, su crontab y su Caddyfile, y pisarlos rompe lo que funciona. El .gitconfig es el caso más claro — trae los `insteadOf` que reescriben remotos. Se instalan cuando se mude el hub. La excepción es la clave de release, que sí fue a su ruta canónica: sin ella nadie puede volver a firmar el repo, y eso no falla el día que se borra gioser sino la próxima vez que alguien publica. Y un «576 M contra 1,3 G» que parecía copia truncada: el Storage Box COMPRIME, y su shell restringida no tiene find ni acepta tuberías, así que el conteo de ficheros ahí no se puede hacer. Lo que sí: preguntarle a rsync en seco, que compara contra el destino real. Un número que no cuadra merece una segunda medición antes de un diagnóstico. No se movieron ~10 G de árboles personales de /home ni /opt: qué datos valen no se deduce de la máquina, y ésos son proyectos del usuario, no servicios de un censo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
aa2752fa7b |
los sueltos, desglosados: 2,7 G de los que lo irreemplazable son 27 guiones de 1 M
El usuario pidió el detalle de los binarios que ningún paquete provee, que es lo que la mudanza no puede reconstruir. Medido en los tres directorios (`/usr/local/bin`, `~/.local/bin`, `/root/.local/bin`): tawasuyu 57 ficheros 1003 M salida del monorepo ⇒ se reconstruye terceros 10 ficheros 841 M kiro-cli 571 M, kcl, qdrant, listmonk… ⇒ se re-bajan respaldo/duplicado 21 ficheros 876 M /root/.local/bin es una COPIA de kiro-cli + el CLI claude guiones 27 ficheros <1 M git-deploy, webhook-deploy.py, mirada-session… ⇒ NADA los provee Lo caro no es lo valioso: 1,8 G de los 2,7 son reconstruibles o duplicados, y lo único que no está en ningún repo ni paquete son 27 guiones que suman menos de un megabyte. Y los 572 M que el censo no veía (§6.34) eran justamente la copia duplicada de kiro-cli. De `.claude` decide mi criterio, por pedido: de 6,8 G viajan 15 M — `projects/*/memory` (6,2 M), `settings.json`, `plugins` e `history.jsonl`. `jobs` (5,3 G), `downloads`, `archivo-sesiones` y los caches no viajan; los transcripts crudos (973 M) van al Storage Box, no a la caja: no hacen falta para funcionar, la memoria es su destilado. ⚠ Y la trampa que haría inútil todo eso: la memoria se indexa POR RUTA (`-mnt-vvv-takana`), y en la caja el repo está en `/opt/takana` — copiarla tal cual deja 163 ficheros que NO se encuentran, sin que nada falle. ⚠ Segundo hallazgo: la caja no tiene usuario `sergio` (sólo root, sshd, gitea, proxy) y shuma corre sin privilegio en gioser. La tarjeta que lo declare tiene que crear la cuenta, y el payload Native de arje no tiene campo de usuario. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e2bb4f9b89 |
censar: «no existe» y «no puedo mirar» se confundían — 572 M invisibles, y el censo de hoy
El censo ya tenía tres estados a propósito (`ausente` / `sin_permiso` / `ok`), pero el tercero sólo se detectaba sobre la PROPIA ruta: cuando el que no deja pasar es un ANCESTRO, `[ -e "$p" ]` contesta que no existe y la ruta caía en `ausente`, que el censo descarta sin decir nada. Medido en gioser: `/root/.local` es 0700 ⇒ un censo corrido como `sergio` daba `/root/.local/bin` por AUSENTE. Como root son 572 M de binarios puestos a mano que ningún paquete provee — justo la clase que la mudanza no puede reconstruir. Arreglado: si un ancestro existe y no es atravesable, el estado es `sin_permiso`. Probado en los dos sentidos, que es lo que separa un guardián de un adorno: ancestro 0700 de otro dueño → sin_permiso (sale en el ⚠ del resumen) ruta que NO existe de verdad → ausente (no se reporta) ← el control que tiene que pasar Con el arreglo, el censo sin privilegio pasa de 2 rutas ciegas a 13, entre ellas el home entero de `artix`, que antes no figuraba de ninguna manera. Y el §6.34 con el estado MEDIDO de la mudanza hoy: de los 29 dominios queda UNO sirviéndose desde gioser (`sergio.gioser.net`); los otros seis vivos ya contestan 200 contra 2.29.29.217. El rescate del §6.20 sigue coincidiendo bit a bit con cuatro de los seis procesos que corren desde un inodo borrado, y los otros dos tienen en disco una versión más nueva. Lo que queda abierto son los pasos 6 y 7: el montón de daemons propios (shuma, willay, pacha, tejido, tupu, matilda…) y los datos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eaa7d02725 |
el corte, con hora: 93 minutos — y en un servicio que ya autoriza no va límite por tasa
Del `access.log`, sin ambigüedad. La IP del usuario (`45.234.61.160`, autenticada como `sergio`, 2709 peticiones) tuvo tres silencios: **93,2 min (11:59:37 → 13:32:49 UTC)**, 50,1 min (10:44:58 → 11:35:06) y 10,0 min (11:45:06 → 11:55:05). Los tres son el `timeout` de 10 minutos del ban disparándose una y otra vez: cada vez que volvía, su primera ráfaga lo baneaba de nuevo. El corte grande terminó exactamente cuando vacié los sets. 🧨 Y LA EXENCIÓN QUE TENÍA NO SERVÍA: la ACL histórica de squid exime `45.234.60.0/24` y él entra desde `45.234.61.160` — **una red de al lado**. Parecía cubierto y no lo estaba. La pertenencia se MIDE (`ip in red`), no se deduce del parecido del prefijo. DECISIÓN DE FONDO: en un servicio que YA AUTORIZA no va límite por tasa. El uso de esta caja es INTENSIVO —varias sesiones de agente en paralelo, cada una abriendo túneles a la vez— y un límite calibrado para «una persona normal navegando» no protege de nada: el atacante ajusta su ritmo, el usuario no puede trabajar más despacio. Squid ya pide usuario y contraseña y tiene su ACL. El ban del puerto queda apagado (60000/min, ráfaga 1000) y contra un flood queda el tope GLOBAL `syn_nuevas_por_seg`, que no distingue usuarios ni banea a nadie por ser rápido. Control tras aplicar: su túnel pasando —`45.234.61.160 … CONNECT api.anthropic.com:443 sergio`— y `45.234.61.160 ∈ 45.234.60.0/23` comprobado con aritmética de redes, no a ojo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
618dcfd851 |
🧨 el cortafuegos baneaba a los usuarios del proxy — y squid nunca se cayó
Preguntaron si el proxy estaba caído. NO lo estaba: el ente llevaba horas corriendo con ↻ 0 y contestando 407. Lo caído era el ACCESO — el cortafuegos de ayer baneaba a los autorizados. La prueba en una línea: **154.197.1.2, una IP que está en la ACL de squid (gente con contraseña), apareció en `@ban4`**, con el contador del ban en 38.246 paquetes tirados. LA CAUSA NO ES LA TASA, ES LA RÁFAGA: `limit rate over 300/minute burst 5 packets` tolera cinco conexiones por encima del promedio, y un navegador o un agente detrás de un proxy abre DECENAS de CONNECT en el mismo instante aunque su promedio sea bajísimo. Y como @ban4 es UN SOLO set consultado antes que todo, caer por el puerto del proxy te tira también el web y el git. ⚠ El tipo ya traía el campo `rafaga` (default 5) con un comentario que dice «para un servicio web va alto (100-200)». Otra vez leí el .ron de EJEMPLO y no el TIPO. Hecho, en orden de urgencia: flush de los sets (servicio restaurado en el acto) · los 21 IPs y 5 redes de la ACL de squid a `confiables` · `rafaga` por servicio según su clientela (ssh 5, git 20, web 100, proxy 200) · proxy a 1200/min con ban de 60 s, que es red de contención contra un flood y no control de uso. Control después: CERO autorizados en el set, 24 baneados (escáneres) y tráfico real pasando —`154.194.14.37 … CONNECT api.deepseek.com:443 sigma`, 298 K tunelados—. ⚠⚠ Y un fallo de método que casi lo tapa: **`nft -f` SUMA a la tabla existente**. Cargué el reglaset corregido encima del viejo y quedaron 30 reglas con las VIEJAS ADELANTE, así que los `confiables` nuevos no se evaluaban nunca. Por eso `cortafuegos apply-input` borra la tabla antes de cargar. La lección: un cortafuegos correcto y uno usable no son lo mismo, y la diferencia NO se ve al aplicarlo. El control que lo habría cazado es el que no corrí: una ráfaga de conexiones desde una IP que no esté en `confiables`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
24a6dd3b2f |
el cortafuegos puesto en la caja — y el primer baneado fui yo
`table inet tawasuyu_input` con policy drop, 15 reglas de puerto, y el set `ban4` llenandose solo: a los pocos minutos, 27 IPs baneadas y paquetes tirados por el contador. El reemplazo de fail2ban funcionando sobre trafico real, dentro del kernel y sin daemon. 🧨 EL PRIMER BANEADO FUI YO, EN SEGUNDOS. Aplicada la primera version: ssh y https bien, y `http`, proxy y git-ssh muertos; al minuto la caja entera dejo de contestarme. La causa no es una regla mal escrita: **el set @ban4 es UNO SOLO para todos los servicios** y `ip saddr @ban4 drop` esta antes que todo, asi que pasarse de tasa en UN puerto te tira en TODOS — y el `nuevas_por_min: 5` del puerto de administracion es exactamente lo que me pasa a mi, porque cada comando remoto es una conexion nueva. Lo devolvio el INTERRUPTOR DE HOMBRE MUERTO (un `nft flush ruleset` programado a 180 s que sólo se desarma si la verificacion externa sale bien). Sin consola serie, esa es la diferencia entre un susto y un rescue. ⚠ Y el tipo ya lo avisaba: `PoliticaEntrada` tiene un campo `confiables` cuyo comentario describe palabra por palabra lo que me paso. Copie el .ron de EJEMPLO en vez de leer el TIPO. La cura fue una linea con el hub y el worker, que se aceptan ANTES de la logica de tasa. ⚠ `nft -c` rechazo 5 reglas antes de aplicar nada: `ct count over N` es CONFIG_NFT_CONNLIMIT y este kernel no lo trae. Quedan COMENTADAS y no borradas en el fichero que se aplica — lo que la politica declara y el kernel no puede tiene que verse. El simbolo ya entro a la receta del kernel. Y para que sobreviva al reinicio, `nftables` declara su [[service]] con `lifecycle = "oneshot"`: cargar un reglaset no es un daemon, es un acto. Va PRIMERO en el genesis — entre que la red esta y que las reglas cargan, la caja esta abierta. Probado con `nft flush ruleset` + `arjectl start`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
70e4c540d3 |
la caja arrancó con nf_tables — y la Semilla la levantó ENTERA sola
`nft list ruleset` contesta vacío y sin error: nf_tables funciona por primera vez en una máquina
takana. Comprobado con el mecanismo que reemplaza a fail2ban y no con una regla de adorno: un set
`flags dynamic` con `add @ban4 { ip saddr timeout 10s limit rate over 5/minute } drop`, aceptado por
el kernel y releído tal cual.
LO QUE EL REINICIO PROBÓ DE PASO, y vale más que el kernel: la Semilla levantó los OCHO entes sola,
todos con ↻ 0 — incluidos los DOS enjaulados en qorpa, que arrancan desde el genesis con su
newuidmap, su overlay y su harkaq igual que un binario nativo. Todo lo que se editó en
/ente/seed.card.json a lo largo del día era hasta ahora una declaración SIN PROBAR: un reinicio es
el único examen que toma. Desde fuera, al minuto: siete dominios en 200, el proxy en 407 y
`git ls-remote` por SSH:2345 devolviendo el commit. Cero intervención.
⚠ `reboot` NO reinicia una caja con arje-zero: le pide a PID 1 que lo haga y arje-zero no implementa
ese protocolo — el comando vuelve, no dice nada, y la máquina sigue arriba (`up 4 days` después de
«reiniciar»). Funciona `reboot -f`, que llama a reboot(2) saltándose al init. Falta la pieza: un
init que gobierna producción tiene que atender un apagado ordenado.
🧨 Y CASI REINICIO EL HUB POR UN BACKTICK: el aviso decía «no atiende el `reboot` normal» dentro de
comillas DOBLES, y la shell ejecutó lo de entre backticks — `reboot`, en gioser. No pasó nada porque
la sesión no es root y OpenRC contestó «you must be root». Es la TERCERA vez en el día que un
backtick dentro de un texto se ejecuta; las otras dos sólo se comieron un comentario. La regla deja
de ser de estilo: nunca un backtick dentro de comillas dobles en una shell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
4753a991f1 |
qdrant, act_runner y fail2ban MUEREN — y el sustituto del último no puede correr todavía
Decisión del usuario sobre lo que quedaba del censo: · `qdrant` (:6333/:6334) — es la base vectorial de `gioser_api`, y ese backend es un FÓSIL (api.gioser.net da 502 permanente desde el §6.9). Una base sin consumidor no se muda. La receta queda en el catálogo, que no es lo mismo que la imagen, y eso NO es deuda. · `act_runner` (:41027) — runner de CI, binario suelto que nadie provee. Si mañana hace falta CI, entra por receta y no por un binario copiado. · `fail2ban` — **tawasuyu ya tiene el sustituto y es mejor**: `shared/cortafuegos` (SDD-ENTRADA §1) no mira logs con un daemon, usa DOS SETS DINÁMICOS DEL KERNEL (`ban4`/`ban6`, `flags dynamic`, `timeout`) con `limit rate over N/minute` POR IP de origen. El ban lo aplica nftables sin proceso y sin latencia. 🧨 PERO EL SUSTITUTO NO ARRANCA: `nft list ruleset` da «cache initialization failed: Invalid argument». `nft 1.1.6` está instalado y sellado; lo que falta está un piso más abajo, en el `.config` que publica el artefacto del kernel: CONFIG_NETFILTER=y · CONFIG_NF_CONNTRACK=y · **# CONFIG_NF_TABLES is not set** y la causa: **# CONFIG_NETFILTER_ADVANCED is not set** (NF_TABLES cuelga de ella) Es la lección del §6.4 muro 1 otra vez: la receta dice lo que se CAMBIÓ; sólo el `.config` sellado dice lo que QUEDÓ. `linux-generic` sale de `make defconfig` y defconfig apaga NETFILTER_ADVANCED. ⇒ HOY LA CAJA NO TIENE CORTAFUEGOS DE NINGUNA CLASE, con :22022, :2345, :1137, :80 y :443 públicos. Hay que decirlo entero. La cura es un kernel con NETFILTER_ADVANCED + NF_TABLES (trabajo del SDD 22) y termina en un reinicio de producción. MITIGACIÓN HECHA HOY, que no depende del kernel: el `sshd` ofrecía `password` y `keyboard-interactive` —su config eran CUATRO líneas y ninguna hablaba de autenticación, así que regía el default de OpenSSH— que es justo la puerta que fail2ban cerraba. Ahora es sólo claves, con cuatro controles: la clave entra, :22022 y :2345 contestan `Permission denied (publickey)` sin ofrecer contraseña, y el `git ls-remote` por SSH sigue andando. La lección: una defensa se muda con su CAPACIDAD, no con su nombre. Decidir «fail2ban muere porque tenemos algo mejor» es correcto Y deja un hueco abierto hasta que ese algo mejor pueda ejecutarse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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 |