250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.
El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.
Y el hallazgo caro: casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.
Además 14 rutas de módulo en docs, que el barrido anterior no tocó
porque no es frontera de palabra.
Cadena completa medida hoy: el worker depositó 63 artefactos (1,34 GB) en
8 s a 153 MB/s, y el hub los promovió con mv. Respaldo 1542 → 1605, buzón
en 0, guardián cuadrando. Contra los 440 kB/s del laptop, 348×.
Dos fallos de la prueba, que importan más que el resultado:
1. `ssh` cortaba con «Host key verification failed» porque la granja REUSA
IPs y la host key cambia con razón. Como el stderr iba a /dev/null, el
error salía como «no obtuve la pública del worker»: culpaba al worker
cuando el problema estaba en el known_hosts del laptop. Ahora va por
ssh_worker(), que purga la entrada vieja — correcto sólo acá, porque la
identidad del worker es su label hcloud, no su llave.
2. verificar-buzon daba «aislado ✓» sin haber probado que escribe. El
aviso de host key se comía el head -1, y el testigo se llamaba .btest,
que `ls` no muestra. Un testigo invisible no prueba nada. Ahora exige
las dos mitades y las dos están verdes: escribe en su buzón, y
../hammer/store no existe para él.
Y el progreso de rsync sólo con TTY: sin terminal reescribe con \r y deja
miles de copias de la misma línea. 8 segundos bastaron para un log
ilegible; en el latido sería cada media hora.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Los permisos de subcuenta del Storage Box son BINARIOS: --readonly sí o no.
No hay append-only ni write-sin-delete (verificado en la API, no recordado).
Así que la contención se hace por alcance: el worker escribe en un BUZÓN
cuyo home es incoming/, y hammer/store sencillamente no existe para él. Lo
máximo que puede destruir es lo que él mismo depositó y aún no se promovió,
que sigue estando en el volumen. Un permiso puede estar mal puesto; un
directorio fuera de tu home, no.
La promoción es un mv DENTRO del mismo filesystem: un rename, instantáneo,
cero bytes por la red. Eso es lo que la hace viable con un uplink de
440 kB/s — el laptop manda órdenes, los datos van worker→box por dentro de
Hetzner (122 MB/s, 280×).
Medido de la shell del box, que NO es un bash:
· no hay `for` ⇒ los lotes se arman en el hub
· `a; b` no encadena de fiar ⇒ una orden por conexión
· `mv a b c dest/` sí acepta varios orígenes ⇒ cientos por conexión
· `find` no existe y devuelve 0 EN SILENCIO ⇒ parece «no hay respaldo»
Y la trampa que motivó partir el trabajo en dos conjuntos: `mv A dest/` con
dest/A ya existente NO falla, mete A DENTRO y deja dest/A/A. Como el store
es CAS, un artefacto ya respaldado es idéntico ⇒ no se mueve, se borra del
buzón. Los dos caminos probados de punta a punta con un artefacto falso,
recuento verificado y sin anidar.
Defensa en profundidad aparte: plan de snapshots diario (03:17 UTC, retiene
7 de 10) y la carpeta ZFS visible para recuperar ficheros sueltos sin
rollback. La subcuenta no las alcanza: viven en la cuenta, con el token que
el worker no tiene.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dos cosas medidas al traer los 167 GB.
1. `rsync -a` SIN `-H` no preserva los enlaces duros, y el store comparte ficheros entre artefactos
justamente así. Llegó inflado: 76 G en el box → 159 G en el volumen, con **0 ficheros de nlink>1**
al llegar — ésa es la prueba de que se perdieron, no una sospecha. (El 76 G del box es además ZFS
comprimido, así que las dos cosas se sumaban y parecía peor.) El contenido es correcto —es CAS y
los hashes casan—, pero ocupa de más y el volumen no sobra.
2. El respaldo conserva artefactos que el hub YA podó. Son SUPERADOS: existe otro con el hash
vigente, y no pueden dar cache-hit nunca porque la receta que los nombraba cambió. Eran 544 de
1536 · 31 G. Podados con work/store-gc-superados.txt como lista.
El guardián es RECONTAR tras el rm: «borré 544» y «hay 544 menos» son afirmaciones distintas, y
sólo la segunda es la que importa. Cuadró.
Volumen: 992 artefactos vigentes, 95 G libres de 246.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MEDIDO, no supuesto: el uplink de la oficina da 440 kB/s ⇒ los 127 G del store son 82 horas. Subir
el store desde el laptop no es lento, es imposible. Pero el Storage Box está en hel1 y el worker
está en hel1: por la red interna la misma copia va a 122 MB/s. Son 280×.
⇒ El laptop deja de ser el camino de los datos. Sólo manda las RECETAS (26 M).
POR QUÉ SUBCUENTA Y NO LA CUENTA PRINCIPAL. El worker es efímero y se borra solo; darle la
credencial principal sería darle permiso de borrado sobre el ÚNICO respaldo que existe. Un respaldo
al que puede escribir la máquina de la que hay que protegerse no es un respaldo. La subcuenta acota
el daño a cero por construcción: --readonly, --reachable-externally=false, home acotado a hammer/.
LA VERIFICACIÓN ES NEGATIVA. «Sólo lectura» es una afirmación sobre lo que el sistema IMPIDE, así
que leer no la prueba. Hay que intentar escribir y borrar y exigir que fallen:
rm → «Read-only file system» · scp → «dest open: Failure» · respaldo intacto.
El script trae ese paso como subcomando `verificar` y sale ≠0 si la subcuenta resulta escribir.
Dos gotchas que costaron:
· la API exige contraseña con mayúscula+minúscula+número+símbolo aunque después se use llave;
· la shell del Storage Box es RESTRINGIDA: acepta ls/mkdir/rm pero NO redirección, así que
`echo k > authorized_keys` falla EN SILENCIO (crea el directorio, no el fichero). Va con scp.
La llave se genera EN EL WORKER (nunca viaja una privada desde el laptop) y muere con él.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>