From 426093cd20624e3bf9e5faf60e44011751c6f97e Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Sep 2026 13:31:56 +0000 Subject: [PATCH] =?UTF-8?q?SDD=2028=20=C2=A76.13:=20`takana=20upgrade`=20p?= =?UTF-8?q?robado=20de=20verdad=20=E2=80=94=20apply,=20rollback=20y=20un?= =?UTF-8?q?=20corte=20duro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probado con un paquete que la caja necesitaba (`zstd-cli`): apply deja generación, la caja pasa a desempacar su propio lab, `rollback` devuelve el estado anterior fichero a fichero (9 restaurados, 1 borrado), y tras el arreglo de durabilidad **sobrevive a un `hcloud server reset`, que es un corte DURO y no un apagado limpio**. Y cruza la frontera que `hydrate` no puede: store en el volumen, `/` en el disco local. Es el único camino de actualización que funciona en una caja instalada. El bug que destapó está en el commit anterior: `write_atomic` era atómico frente a otros procesos y no frente a un corte, así que el primer upgrade se perdió al reiniciar y `recover` abortaba con la huella misma del corte. Verificado en la máquina en los dos sentidos: binario viejo ⇒ estado en 0 bytes; binario nuevo ⇒ estado íntegro. `recipes/takana.toml` re-pineado al commit del arreglo. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x --- docs/28-servidor-de-produccion.md | 44 +++++++++++++++++++++++++++++++ recipes/takana.toml | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md index 97f282d3..89a1954c 100644 --- a/docs/28-servidor-de-produccion.md +++ b/docs/28-servidor-de-produccion.md @@ -897,6 +897,50 @@ store y destino están en el mismo filesystem. Es la misma familia de EXDEV que Para instalar el `git` arreglado se copió el artefacto a mano. Eso **funciona y no es el camino**: el camino es `takana upgrade`, y probarlo es su propia unidad de trabajo. +### 6.13 ✅ `takana upgrade`, probado de verdad — y el bug de durabilidad que destapó + +§6.12 dejó dicho que el camino de actualización de una caja instalada es `takana upgrade`, no `dd` ni +`hydrate`. Probado con un paquete que la caja **realmente necesitaba**: `zstd`. + +| paso | resultado | +|---|---| +| `upgrade apply …-zstd-cli` | `✓ generación 2 aplicada · + 1 añadidos, ~ 1 pisados, - 8 retirados` | +| la prueba de uso | `zstd -dc lab-image.tar.zst \| tar -xf -` ⇒ **la caja desempaca su propio lab** | +| `upgrade rollback` | `✓ generación 2 revertida · 9 restaurados, 1 borrados` — `zstd` se va, `libzstd.a` vuelve | +| re-`apply` + **corte duro sin `sync`** | sobrevive: generación viva 1, manifiesto íntegro | + +✔ **Y cruza la frontera que `hydrate` no puede**: el store está en el volumen y `/` en el disco local, +y `upgrade` copia en vez de enlazar. Es, de hecho, el único camino que funciona en una caja instalada. + +#### ⚠ El bug: `write_atomic` era atómico pero NO DURABLE + +El primer intento pareció funcionar y **se perdió al reiniciar**. La caja volvió con: + +``` +pending.json 0 bytes +generations/2/manifest.json 0 bytes +upgrade status → Error: json: EOF while parsing a value +upgrade recover → Error: json: EOF while parsing a value +``` + +`write_atomic` hacía temporal + `rename`: atómico frente a otros PROCESOS, no frente a un CORTE. +`rename` sobre un fichero cuyos datos siguen en caché deja, tras el corte, la entrada apuntando a +bloques nunca escritos — cero bytes. Y `hcloud server reset` **es un corte duro**, no un apagado +limpio: ahí está la mitad operativa de la lección. + +Lo grave no es perder un upgrade: es que **`recover` —que existe exactamente para «un apply +interrumpido por un corte»— abortaba con la huella más probable de ese corte**. + +Arreglado: `fsync` del temporal antes del `rename` y `fsync` del directorio después (hacen falta los +dos), y un `pending.json` vacío se reporta como `PendingCorrupt`, nombrando el corte y apuntando al +árbol de respaldos. Con tests y su control. + +**Verificado en la máquina, en los dos sentidos**: con el binario viejo, apply + reset duro ⇒ estado +en 0 bytes; con el nuevo, la misma secuencia ⇒ estado íntegro y el paquete en su sitio. + +⚠ Lo que NO se arregló: si el manifiesto se pierde, `recover` no puede deshacer solo — no sabe qué se +tocó. El árbol de respaldos tiene la información; reconstruir desde ahí es su propia unidad. + ### 6.2 Lo que la mudanza tiene que producir, además de la mudanza El usuario lo pidió explícito: **que este experimento saque recetas y las pruebe**. La caja vieja es diff --git a/recipes/takana.toml b/recipes/takana.toml index 8bddb8b0..3fdb905a 100644 --- a/recipes/takana.toml +++ b/recipes/takana.toml @@ -35,7 +35,7 @@ license = "MIT" # El repo del propio takana: el `commit` es el identificador inmutable; la URL es sólo locator y no # entra en `hash_inputs` (ADR 0013 §1). Subirlo cuando el CLI avance. repo = "ssh://gitea@git.gioser.net:2345/sergio/takana.git" -commit = "c0545ea40cbf530c9b80fc9a76a20551163574cd" +commit = "68d052136c06b74f858a8a33d1963f45cf244fa0" [build] compiler = "zig-cc"