qorpa D8: el pin de sniper verificado, y el pin deja de ser una cita
Cierra el ⚠ que quedaba en la tabla de D8. Tres cosas, y la del medio es la que más duele. 1. **Sniper pineado, y por la versión correcta.** No se pinea «la última» sino la que `latest-container-runtime-depot.txt` dice que despliega el cliente de Steam — 3.0.20260805.254768. Pinear otra sería pinear algo que nadie corre. Se pinean DOS artefactos: la imagen (rootfs, 302 MB, 11196 ficheros) y el depot `SteamLinuxRuntime_sniper.tar.xz` con pressure-vessel adentro. El segundo es el que destraba el paso 6: ese runtime sólo se baja al instalar un juego (credenciales), y pineado se coloca a mano ⇒ pressure-vessel se puede ejercitar sin cuenta, sin juego y sin pantalla. 2. **Los digests estaban en prosa y ABREVIADOS, y eso no es un pin.** Cuando la poda se llevó las imágenes, `895661bd…` no alcanzó para volver a traerlas: hubo que ir a buscar los sha256 otra vez a upstream. Ahora enteros y verificados en `docs/state/qorpa-imagenes.toml`, con de qué lista salieron y **si esa lista está firmada** — que no todas: Ubuntu firma su SHA256SUMS, Arch firma el tarball pero no la lista, y Valve no firma nada. 3. **`--retry` de curl no cubría el fallo que de verdad pasa.** Los 302 MB de sniper murieron al 73% con `HTTP/2 INTERNAL_ERROR` y curl NO reintentó: sin `--retry-all-errors` sólo considera transitorios los timeouts y los 5xx. Y aun reintentando, sin `-C -` cada intento vuelve a empezar de cero. Con las dos banderas el mismo pull sobrevivió dos cortes más y llegó. El parcial ya no se borra al fallar (es lo que permite reanudar); es seguro porque quien decide es el sha256 de después, y `prune` ya lo barre. De paso, dos comprobaciones en vez de suposiciones: el rootfs de sniper viene en `files/` con un hermano `metadata` y el anclaje por estructura lo elevó solo (tercera forma real de empaquetado), y traer el depot como rootfs FALLA en vez de adivinar («no encuentro un rootfs en el archivo»). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q
This commit is contained in:
@@ -309,19 +309,40 @@ fn pull(
|
||||
/// Descarga a fichero. NO se reusa `download::fetch_url_to_file` a propósito: ése trae el cuerpo
|
||||
/// entero a memoria (le sirve para un patch de KB) y acá hablamos de cientos de MB, con `--max-time`
|
||||
/// de 300 s que un rootfs no respeta.
|
||||
///
|
||||
/// **`--retry` a secas no cubre el fallo que de verdad pasa.** Medido trayendo el runtime *sniper*
|
||||
/// (302 MB): la CDN de Valve cortó el stream al 73% con `HTTP/2 INTERNAL_ERROR` (exit 92) y curl
|
||||
/// **no reintentó**, porque sin `--retry-all-errors` sólo considera transitorios los timeouts y los
|
||||
/// 5xx — un stream roto a media transferencia no entra en esa lista. Y aun reintentando, sin
|
||||
/// `-C -` cada intento vuelve a empezar de cero: en un fichero de cientos de MB sobre una conexión
|
||||
/// que se corta, eso no converge nunca.
|
||||
///
|
||||
/// Por eso el parcial **NO se borra al fallar**: es lo que hace que el siguiente intento reanude en
|
||||
/// vez de rebajar todo. Es seguro porque quien decide no es el fichero sino el `sha256` que se
|
||||
/// verifica después —un parcial corrupto no puede pasar por bueno— y porque el nombre lleva punto
|
||||
/// delante, así que `list` no lo ve como imagen y `prune` ya lo barre como «resto de un pull a
|
||||
/// medias».
|
||||
fn download_to_file(url: &str, dst: &Path) -> Result<()> {
|
||||
if let Some(p) = dst.parent() {
|
||||
std::fs::create_dir_all(p)?;
|
||||
}
|
||||
let st = Command::new("curl")
|
||||
.args(["-fL", "--retry", "3", "--retry-delay", "2", "--connect-timeout", "30",
|
||||
"--progress-bar", "-o"])
|
||||
.args(["-fL", "--retry", "5", "--retry-delay", "2", "--retry-all-errors",
|
||||
"--connect-timeout", "30", "-C", "-", "--progress-bar", "-o"])
|
||||
.arg(dst)
|
||||
.arg(url)
|
||||
.status()
|
||||
.context("no pude ejecutar curl")?;
|
||||
if !st.success() {
|
||||
std::fs::remove_file(dst).ok();
|
||||
let reanudable = std::fs::metadata(dst).map(|m| m.len()).unwrap_or(0);
|
||||
if reanudable > 0 {
|
||||
eprintln!(
|
||||
" (quedaron {} bajados en {}: el siguiente intento reanuda desde ahí; \
|
||||
`qorpa prune` los barre)",
|
||||
human(reanudable),
|
||||
dst.display()
|
||||
);
|
||||
}
|
||||
bail!("curl falló bajando {url} (exit {:?})", st.code());
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -275,11 +275,27 @@ no es lo que *se puede* traer, sino lo que **nosotros probamos, espejamos y publ
|
||||
imagen curada es una segunda cadena de suministro que hay que sostener (§NO-resuelve 3). Se curan
|
||||
**tres**, y cada una entra por un trabajo distinto — no por sabor:
|
||||
|
||||
| imagen | tipo | qué trabajo hace | pin (verificado 2026-09-03) |
|
||||
| imagen | tipo | qué trabajo hace | pin |
|
||||
|---|---|---|---|
|
||||
| **Arch bootstrap** | 1 | juegos: `multilib` de 32 bits de primera clase, y SteamOS *es* Arch ⇒ extiende el argumento de D6 («la configuración exacta que Valve prueba») | `archlinux-bootstrap-2026.09.01-x86_64.tar.zst`, sha256 `895661bd…`, en `archive.archlinux.org` (archivado para siempre) |
|
||||
| **Ubuntu base LTS** | 1 | binarios comerciales: Zoom, Slack, Discord, Spotify, Chrome, JetBrains se compilan y prueban contra esto | `ubuntu-base-24.04.3-base-amd64.tar.gz`, sha256 `6bc2cde3…`, `cdimage.ubuntu.com` + `old-releases` |
|
||||
| **Steam Runtime «sniper»** | 2 | la única **sellable**: al no mutar entra al store por `file_drop` y su digest va al índice firmado — cadena de custodia nuestra | ⚠ pin sin verificar todavía |
|
||||
| **Arch bootstrap** | 1 | juegos: `multilib` de 32 bits de primera clase, y SteamOS *es* Arch ⇒ extiende el argumento de D6 («la configuración exacta que Valve prueba») | `archlinux-bootstrap-2026.09.01-x86_64.tar.zst`, en `archive.archlinux.org` (archivado para siempre) |
|
||||
| **Ubuntu base LTS** | 1 | binarios comerciales: Zoom, Slack, Discord, Spotify, Chrome, JetBrains se compilan y prueban contra esto | `ubuntu-base-24.04.3-base-amd64.tar.gz`, `cdimage.ubuntu.com` + `old-releases` |
|
||||
| **Steam Runtime «sniper»** | 2 | la única **sellable**: al no mutar entra al store por `file_drop` y su digest va al índice firmado — cadena de custodia nuestra | ✅ **verificado 2026-09-03**: snapshot `3.0.20260805.254768` (el que despliega el cliente, no «el último»), imagen y depot |
|
||||
|
||||
**Los digests enteros están en [`docs/state/qorpa-imagenes.toml`](../state/qorpa-imagenes.toml), no
|
||||
acá.** Estaban acá, abreviados —`895661bd…`— y cuando la poda se llevó las imágenes del disco no
|
||||
alcanzaron para volver a traerlas: hubo que ir a buscarlos otra vez a upstream. **Un pin que no se
|
||||
puede pegar en un comando no es un pin, es una cita.** El fichero además dice de qué lista de sumas
|
||||
salió cada uno y **si esa lista viene firmada**, que es la única forma honesta de hablar de una
|
||||
cadena de suministro ajena: Ubuntu firma su `SHA256SUMS`, Arch no firma la lista pero sí el tarball,
|
||||
y **Valve no firma nada** — su garantía entera es TLS más el pin.
|
||||
|
||||
**Del sniper se pinean dos cosas, y la segunda es la que destraba el paso 6.** La imagen
|
||||
(`Platform-…-runtime.tar.gz`, un rootfs) y el **depot** `SteamLinuxRuntime_sniper.tar.xz`, que trae
|
||||
pressure-vessel más la imagen y es lo que Steam despliega en `steamapps/common/`. Ese runtime
|
||||
**sólo se baja al instalar un juego**, y eso pedía credenciales: pineado se coloca a mano, y
|
||||
pressure-vessel se puede ejercitar **sin cuenta, sin juego y sin pantalla**. El depot **no es un
|
||||
rootfs**: se trae con `--verify-only`, y traerlo a secas falla el anclaje en vez de adivinar
|
||||
(comprobado: «no encuentro un rootfs en el archivo»).
|
||||
|
||||
**Fedora queda BYO, no curada.** No por calidad —publica `Fedora-Container-Base-Generic-43-1.6`
|
||||
con CHECKSUM firmado y se pinea igual de bien— sino porque hace el **mismo trabajo que Arch** en el
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# Las imágenes ajenas CURADAS (qorpa, ADR 0015 D8). Un pin por imagen, entero y verificado.
|
||||
#
|
||||
# ── POR QUÉ ESTE FICHERO EXISTE ─────────────────────────────────────────────────────────────────
|
||||
# Los pines vivían en la tabla de D8, en prosa y ABREVIADOS (`895661bd…`). Cuando la poda se llevó
|
||||
# las imágenes del disco, esos pines no alcanzaban para volver a traerlas: un sha256 truncado no es
|
||||
# un pin, es una cita. Hubo que ir a buscar los digests otra vez a upstream — que esta vez estaban,
|
||||
# pero es exactamente el trance del que el pin debía protegernos.
|
||||
#
|
||||
# Regla que sale de ahí: **un pin que no se puede pegar en un comando no es un pin.**
|
||||
#
|
||||
# ── QUÉ ES CADA CAMPO ───────────────────────────────────────────────────────────────────────────
|
||||
# kind 1 = rootfs mutable (se le instala adentro con su gestor) · 2 = runtime inmutable
|
||||
# sha256 LA IDENTIDAD. Es el sha del ARCHIVO, no del árbol (ver ADR 0015 §Orden 2), así que
|
||||
# dos máquinas que lo traigan de orígenes distintos coinciden ⇒ ADR 0013 y 0014 se
|
||||
# aplican sin cambiar nada: la URL no entra en la identidad y espejar es gratis.
|
||||
# checksums de dónde salió el digest, para poder re-verificarlo contra upstream sin adivinar.
|
||||
# signed si esa lista viene firmada. Se escribe porque §NO-resuelve 3 dice que ésta es una
|
||||
# SEGUNDA cadena de suministro sin garantías nuestras: lo honesto es decir cuánta
|
||||
# garantía trae cada una, y no traen la misma.
|
||||
# verified fecha en que ALGUIEN lo bajó y el sha dio. No es la fecha de publicación.
|
||||
#
|
||||
# Los campos van en inglés como el resto de los manifiestos de qorpa (`image.toml`,
|
||||
# `instance.toml`); los comentarios, en castellano — `CLAUDE.md` regla 4.
|
||||
#
|
||||
# ── LO QUE ESTE FICHERO NO ES ───────────────────────────────────────────────────────────────────
|
||||
# No es una lista cerrada: `hammer qorpa pull <url> --sha256 <hex>` acepta cualquier rootfs (D8 b).
|
||||
# Curada es la lista de lo que NOSOTROS probamos, espejamos y sostenemos, porque cada imagen es una
|
||||
# cadena de suministro más que mantener. Fedora queda BYO a propósito: hace el mismo trabajo que
|
||||
# Arch y una tercera cadena mutable es justo la normalización que el ADR quiere evitar.
|
||||
|
||||
[arch-bootstrap]
|
||||
kind = 1
|
||||
label = "archlinux-bootstrap-2026.09.01"
|
||||
work = """
|
||||
Juegos. Trae `multilib` de 32 bits de primera clase —las libs que la F2 del plan de juegos daba por
|
||||
«una campaña entera»— y SteamOS *es* Arch, así que extiende el argumento de D6: adentro corre la
|
||||
configuración que Valve prueba.
|
||||
"""
|
||||
url = "https://archive.archlinux.org/iso/2026.09.01/archlinux-bootstrap-2026.09.01-x86_64.tar.zst"
|
||||
sha256 = "895661bdf6c64e91b7725874165fd05dd30c438d3ffec661671ab5cfb261ca58"
|
||||
checksums = "https://archive.archlinux.org/iso/2026.09.01/sha256sums.txt"
|
||||
signed = "el tarball tiene .sig PGP propio; la lista de sumas NO está firmada"
|
||||
verified = "2026-09-03"
|
||||
# El bootstrap trae TRES entradas arriba (`root.x86_64`, `version`, `pkglist.x86_64.txt`): el
|
||||
# anclaje por estructura lo eleva solo. La regla vieja —«si hay un solo directorio arriba, ése es
|
||||
# el rootfs»— no disparaba y dejaba el rootfs un nivel abajo con todo en verde.
|
||||
|
||||
[ubuntu-base]
|
||||
kind = 1
|
||||
label = "ubuntu-base-24.04.3"
|
||||
work = """
|
||||
Binarios comerciales. Zoom, Slack, Discord, Spotify, Chrome y JetBrains se compilan y se prueban
|
||||
contra Ubuntu LTS: adentro corren sin que nadie tenga que adivinar contra qué glibc los ligaron.
|
||||
"""
|
||||
url = "https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/ubuntu-base-24.04.3-base-amd64.tar.gz"
|
||||
sha256 = "6bc2cde3930ad088b3bb46fa45279e96d25bc3810f209850ecbe4722711874f9"
|
||||
checksums = "https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/SHA256SUMS"
|
||||
signed = "sí — SHA256SUMS.gpg junto a la lista"
|
||||
verified = "2026-09-03"
|
||||
# Cuando salga 24.04.5 el punto-release viejo se muda a old-releases.ubuntu.com. No importa: la URL
|
||||
# no es la identidad (ADR 0013), el sha256 sí.
|
||||
|
||||
[steam-runtime-sniper]
|
||||
kind = 2
|
||||
label = "sniper-platform-3.0.20260805.254768"
|
||||
work = """
|
||||
El runtime en el que Valve corre los juegos. Es la única imagen INMUTABLE de la terna: no se le
|
||||
instala nada adentro, así que es la única sellable —entra al store por `file_drop` y su digest
|
||||
puede ir al índice firmado, cadena de custodia nuestra— y la única que no abre una segunda cadena
|
||||
mutable.
|
||||
"""
|
||||
url = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/com.valvesoftware.SteamRuntime.Platform-amd64%2Ci386-sniper-runtime.tar.gz"
|
||||
sha256 = "1a293c312215c59e4e5348ce210594cd7967d1a1fcaec6248a024e0878776f2b"
|
||||
checksums = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/SHA256SUMS"
|
||||
signed = "NO — ni .asc ni .gpg ni .sig; toda la garantía es TLS + el pin"
|
||||
verified = "2026-09-03"
|
||||
# El rootfs viene en `files/` con un hermano `metadata` al lado: tercera forma real de empaquetado,
|
||||
# después de la plana (Ubuntu) y la de Arch. El anclaje por estructura la resolvió sin ayuda.
|
||||
# La versión no se eligió por «la última»: `latest-container-runtime-depot.txt` dice cuál despliega
|
||||
# de verdad el cliente de Steam, y ES ésta. Pinear otra sería pinear algo que nadie corre.
|
||||
|
||||
[steam-linux-runtime-sniper-depot]
|
||||
kind = 2
|
||||
label = "steamlinuxruntime-sniper-3.0.20260805.254768"
|
||||
work = """
|
||||
El bundle que el cliente de Steam despliega en `steamapps/common/SteamLinuxRuntime_sniper`: trae
|
||||
pressure-vessel MÁS la imagen de arriba. Se pinea porque es lo que destraba el paso 6 del ADR — el
|
||||
runtime sólo se baja al instalar un juego, y eso pedía credenciales; pineado se coloca a mano y
|
||||
pressure-vessel se puede ejercitar sin cuenta, sin juego y sin pantalla.
|
||||
"""
|
||||
url = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/SteamLinuxRuntime_sniper.tar.xz"
|
||||
sha256 = "e264f0639ab775338311036f207b35cebe99bc417b016b53931ebca8b30b3d94"
|
||||
checksums = "https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20260805.254768/SHA256SUMS"
|
||||
signed = "NO — igual que arriba"
|
||||
verified = "2026-09-03"
|
||||
# NO es un rootfs: es un depot con `run`, `pressure-vessel/` y la imagen adentro. Traerlo con
|
||||
# `pull` a secas tiene que FALLAR el anclaje en vez de adivinar; se trae con `--verify-only`, que
|
||||
# guarda el archivo verificado sin desempacar.
|
||||
Reference in New Issue
Block a user