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>
This commit is contained in:
Sergio
2026-09-16 19:07:20 +00:00
co-authored by Claude Opus 5
parent 15157f8cfe
commit f32b28c63e
4 changed files with 135 additions and 1 deletions
+67
View File
@@ -2153,6 +2153,73 @@ por criterio ajeno.
De `/var/lib` (8,1 G) se movió sólo el estado de los daemons que viven: **6,1 G son swap** y 1,9 G
el gitea, que ya está mudado desde el §6.14.
### 6.37 ✅ CUTOVER DE `sergio.gioser.net` — el último vhost, y gioser deja de servir *(2026-09-16)*
**Ya no queda ningún dominio sirviéndose desde gioser.** El último era éste, y lo que lo ataba no
era el sitio sino su consola: `/shuma/*`. Verificado desde fuera, contra la IP nueva:
```
https://sergio.gioser.net/ → 200 408 bytes, la SPA
https://sergio.gioser.net/shuma/ → 200 «shuma-gateway ok» ← el gateway, no la SPA
https://sergio.gioser.net/ruta-inventada → 200 la SPA (el fallback, que es lo correcto)
```
⚠ **El `200` de `/shuma/` había que mirarlo dos veces.** El bloque termina en
`try_files {path} {path}/ /index.html`, así que **cualquier ruta inexistente devuelve 200 con la
SPA**: un `curl -o /dev/null` que sólo mira el código habría dado el mismo verde con el proxy mal
puesto. Lo que decide es el CUERPO — 17 bytes que dicen `shuma-gateway ok` — y el control es
compararlo con lo que el origen viejo sigue sirviendo, byte por byte. Igual con `/shuma/rpc`: las
dos máquinas contestan el MISMO `400 {"error":"bad json: …"}`.
#### Lo que hubo que construir, en orden
1. **Las dos recetas** (§6.34) sellaron en el worker: `b3:107dfb54…-shuma-gateway` (6,3 M) y
`b3:f9b92acc…-shuma-daemon` (5,6 M) — los hashes que `takana hash` había anticipado antes de
encolarlas. `file` dice **`statically linked`**: nada de cargador, que era exactamente lo que le
faltaba al binario glibc de gioser (y cuyo inodo, además, ya estaba borrado del disco).
2. **La cuenta, declarada y sin privilegio.** `[[user]] shuma` (uid 967) en la receta del daemon, y
el descenso con `setuidgid` dentro del `argv` de la Card, igual que gitea — porque el payload
`Native` de arje **no tiene campo de usuario**. Declararlo «como estaba» lo habría puesto de
root, y esto es un demonio que abre PTYs y lanza shells.
3. **⚠ Y la shell de la cuenta NO es un detalle.** El default de `[[user]]` es `/bin/false`, que es
lo correcto para gitea o squid y **exactamente lo contrario** para éste: con la shell inerte el
servicio arranca, se supervisa, contesta 200… y cada pestaña que alguien abra muere al instante.
Un fallo que no se ve al desplegar: se ve al usarlo. Va `shell = "/bin/sh"`.
4. **La config que no se regenera.** El `gateway-token` (32 B) y `keys/identity.x25519` viajaron a
`/var/lib/shuma/.config/shuma/`: generar unos nuevos deja fuera a los clientes ya emparejados.
Por eso la guarda de la Card **exige** el token y no lo crea, y dice de dónde sale.
5. **Las dos Cards** entran en `/etc/arje/cards.d/` **y** en el `genesis` de `/ente/seed.card.json`
(son dos preguntas distintas: qué se puede encarnar a pedido y qué arranca solo). La semilla
quedó con 13 entradas y vuelve a parsear.
6. **Declarado en `perfil.servidor`**: los dos paquetes y los dos labels en `servicios`. Entran como
PAR — el gateway sin el daemon es un puente a ninguna parte.
`[[user]]` y `[[service]]` están **fuera de `hash_inputs`**, así que declarar todo eso no movió
ningún ArtifactHash: comprobado en los dos, antes y después.
#### 🔁 El DNS: la API tiene una acción para esto y el §6.25 no la había encontrado
Aquel cutover dejó escrito «un `PUT` sobre el rrset no puede cambiar el tipo: hay que `DELETE` del
CNAME y `POST` del A». Es cierto para un cambio de TIPO. Para cambiar el VALOR, el `PUT` tampoco
sirve —`can't update records with this endpoint`— pero existe una acción que lo hace **atómica**:
```
POST /v1/zones/<id>/rrsets/<nombre>/<tipo>/actions/set_records {"records":[{"value":"…"}]}
```
Sin ventana sin registro, que es lo que sí tiene el `DELETE`+`POST`. Con TTL 60 la resolución
pública cambió **en menos de 8 segundos**, y volver atrás es la misma llamada con la IP vieja.
#### El origen sigue vivo, a propósito
Como en §6.26: en gioser `shuma-daemon`, `shuma-gateway` y su bloque de caddy **siguen corriendo**.
Mudar el nombre y dejar el origen encendido permite volver en un minuto, y no cuesta nada mientras
la máquina exista. Lo que ya no existe es la dependencia: **ningún dominio resuelve a gioser**.
⚠ Pendiente menor, anotado donde se va a leer: el daemon avisa al arrancar que no encuentra
`shuma-askpass` («sudo/ssh pedirán la clave por el TTY»). Es otro crate del mismo workspace y es
otra receta — no bloquea la consola.
## 7. Reusar los scripts que ya existen, y no escribir de nuevo
Pedido explícito del usuario. El inventario de lo que ya hace el trabajo:
+11 -1
View File
@@ -1325,6 +1325,10 @@ paquetes = [
# copiar un directorio — lo que NO es trivial es el binario, ver el encabezado de `recipes/gitea.toml`.
# Su `[[service]]` está en la receta y el perfil lo arranca (abajo, en `servicios`).
"gitea",
# `shuma-daemon` / `shuma-gateway` (2026-09-16): la consola remota que sirve `/shuma/*` en
# `sergio.gioser.net`. Construidos desde el monorepo de tawasuyu por decisión del usuario, en vez
# de mudar el binario glibc de gioser —cuyo inodo, además, ya estaba BORRADO del disco.
"shuma-daemon", "shuma-gateway",
# `curl`/`wget`: bajar del mirror y del repo. `takana install --repo https://…` los necesita del
# lado del cliente, y son también la única forma de diagnosticar un origen caído desde la caja.
"curl", "wget",
@@ -1428,4 +1432,10 @@ paquetes = [
# métrica reclama: el latido y la hora. Sus paquetes ya estaban declarados arriba y sellados —lo que
# faltaba era el eslabón que los ARRANCA, que es este. Una caja con `cronie` instalado y sin `crond`
# corriendo se ve idéntica a una con latido, y no rota un log ni respalda nada.
servicios = ["sshd", "gitea", "caddy", "minga", "squid", "crond", "chronyd"]
# ⚠ `shuma-daemon` y `shuma-gateway` (2026-09-16): la consola de `sergio.gioser.net`, que es el
# ÚLTIMO vhost que quedaba atado a gioser (SDD 28 §6.34). Entran como PAR: el gateway sin el daemon
# es un puente a ninguna parte, y por eso los dos labels van juntos o no va ninguno.
# La cuenta `shuma` la declara `recipes/shuma-daemon.toml` con `[[user]]`, sin privilegio y con
# `shell = /bin/sh` —no `/bin/false`—, porque este demonio existe para abrir PTYs: con la shell
# inerte el servicio arranca, se supervisa, y cada pestaña muere al instante.
servicios = ["sshd", "gitea", "caddy", "minga", "squid", "crond", "chronyd", "shuma-daemon", "shuma-gateway"]
+36
View File
@@ -24,3 +24,39 @@ compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "static"
flags = ["-p", "shuma-daemon", "--bin", "shuma-daemon"]
# ── LA CUENTA (SDD 30) ──────────────────────────────────────────────────────────────────────────
# Fuera de `hash_inputs`: declarar esto NO re-hashea shuma-daemon (control corrido, el hash no se
# movió).
#
# ⚠ EN GIOSER CORRE COMO `sergio`, Y ESO NO SE PUEDE TRADUCIR SOLO. El payload `Native` de arje no
# tiene campo de usuario (SDD 29 §4.0 quinquies), así que una tarjeta escrita «como estaba» lo
# pondría de ROOT — y esto es un demonio que abre PTYs y ejecuta shells: correrlo de root no es
# perder una opción, es entregar la máquina. La cuenta es propia y sin privilegio, y el descenso se
# hace en el comando con `setuidgid`, igual que gitea.
[[user]]
name = "shuma"
uid = 967
home = "/var/lib/shuma"
# ⚠ Y LA SHELL NO ES UN DETALLE ACÁ. El default de `[[user]]` es `/bin/false`, que es lo correcto
# para un demonio (gitea, squid) y **lo exactamente contrario** para éste: shuma-daemon existe para
# abrir PTYs y lanzar shells. Con `/bin/false` el servicio arranca, se supervisa, contesta… y cada
# pestaña que alguien abra muere al instante. Un fallo que no se ve al desplegar: se ve al usarlo.
shell = "/bin/sh"
# ── EL SERVICIO ─────────────────────────────────────────────────────────────────────────────────
# Las guardas salen 78 NOMBRANDO EL ARREGLO. Es lo que hace la diferencia entre un servicio que
# reintenta para siempre con «unknown user» y uno que dice qué falta: medido con gitea, cuya imagen
# arrancaba perfecta y sin el servicio por el que existe.
#
# `XDG_RUNTIME_DIR` no es decoración: es donde el daemon pone su socket, y es por ese socket que el
# gateway le habla. En gioser vale `/run/shuma`, medido en el entorno del proceso vivo.
[[service]]
label = "shuma-daemon"
id = "01M2EKDA00SH0MADAEM0N7X4K2"
exec = "/bin/busybox"
argv = ["sh", "-c", "/bin/grep -q '^shuma:' /etc/passwd || { echo 'shuma-daemon: no existe el usuario `shuma` en /etc/passwd — la receta lo declara con [[user]]; la imagen lo compone con `takana users`' >&2; exit 78; }; mkdir -p /run/shuma /var/lib/shuma || exit 78; chown shuma:shuma /run/shuma /var/lib/shuma || exit 78; cd /var/lib/shuma || exit 78; exec /usr/bin/setuidgid shuma /usr/bin/shuma-daemon"]
envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/var/lib/shuma"], ["USER", "shuma"], ["XDG_RUNTIME_DIR", "/run/shuma"]]
networking = "full"
cgroup = "arje.slice/shuma-daemon"
restart = { initial_ms = 1000, max_ms = 30000 }
+21
View File
@@ -32,3 +32,24 @@ target = "x86_64-linux-musl"
# exactamente lo que le falta hoy al binario de gioser, que es ELF dinámico contra glibc.
link = "static"
flags = ["-p", "shuma-gateway", "--bin", "shuma-gateway"]
# ── EL SERVICIO (SDD 30) ────────────────────────────────────────────────────────────────────────
# Fuera de `hash_inputs`: declarar esto NO re-hashea el gateway.
#
# La cuenta la declara `shuma-daemon` —es la misma— y por eso acá no va otro `[[user]]`: dos recetas
# declarando la misma cuenta con distinto uid abortan la imagen a propósito (`takana users --merge`).
#
# ⚠ DOS GUARDAS, Y LAS DOS SALEN 78 DICIENDO EL ARREGLO:
# 1. sin el usuario, esto correría de root — y es el puente HTTP hacia un demonio que abre shells;
# 2. sin `gateway-token` no autentica a nadie. El fichero son 32 bytes que en gioser viven en
# `~/.config/shuma/gateway-token`; si se genera uno nuevo, los clientes ya emparejados dejan de
# entrar. Por eso la guarda no lo crea: lo EXIGE y nombra de dónde sale.
[[service]]
label = "shuma-gateway"
id = "01M2EKDA00SH0MAGATEWAY4K21"
exec = "/bin/busybox"
argv = ["sh", "-c", "/bin/grep -q '^shuma:' /etc/passwd || { echo 'shuma-gateway: falta el usuario `shuma` — lo declara recipes/shuma-daemon.toml' >&2; exit 78; }; test -s /var/lib/shuma/.config/shuma/gateway-token || { echo 'shuma-gateway: falta /var/lib/shuma/.config/shuma/gateway-token (32 B) — viene de ~/.config/shuma/gateway-token del origen; si generás uno nuevo, los clientes emparejados dejan de entrar' >&2; exit 78; }; cd /var/lib/shuma || exit 78; exec /usr/bin/setuidgid shuma /usr/bin/shuma-gateway"]
envp = [["PATH", "/usr/bin:/bin"], ["HOME", "/var/lib/shuma"], ["USER", "shuma"], ["XDG_RUNTIME_DIR", "/run/shuma"]]
networking = "full"
cgroup = "arje.slice/shuma-gateway"
restart = { initial_ms = 1000, max_ms = 30000 }