diff --git a/crates/takana-cli/src/qorpa.rs b/crates/takana-cli/src/qorpa.rs index 08da0059..ddbdb9ac 100644 --- a/crates/takana-cli/src/qorpa.rs +++ b/crates/takana-cli/src/qorpa.rs @@ -834,15 +834,33 @@ struct Grants { seal_image: bool, } -/// Dónde vive `harkaq-exec`. Misma convención que el harkaq del build (`HARKAQ_BIN`). +/// Dónde vive `harkaq-exec`. Misma convención que el harkaq del build (`HARKAQ_BIN`), **más el +/// binario instalado**, que es el caso de cualquier máquina que no sea el hub de desarrollo. +/// +/// ⚠ MEDIDO el 2026-09-15 en la caja de producción: sólo se buscaba en `~/.cache/harkaq`, o sea en +/// la caché de quien lo hubiera compilado a mano. En una caja instalada no hay `gcc`, ni `scripts/`, +/// ni caché — así que `qorpa` abortaba con una instrucción imposible de seguir y la jaula del ADR +/// 0015 funcionaba únicamente en la máquina donde alguien la había construido. Ahora +/// `recipes/harkaq-exec.toml` lo publica en `/usr/bin` y va declarado en `perfil.base`. +/// +/// El orden es deliberado: lo que el operador declara (`HARKAQ_BIN`) gana, después el árbol de +/// desarrollo —para que un cambio en la jaula se pruebe sin instalar nada—, y el paquete al final. +/// Si no está ninguno se devuelve el primer candidato, que es el que nombra el error. fn harkaq_exec() -> PathBuf { - std::env::var("HARKAQ_BIN") - .map(PathBuf::from) - .unwrap_or_else(|_| { - PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/root".into())) - .join(".cache/harkaq") - }) - .join("harkaq-exec") + if let Ok(dir) = std::env::var("HARKAQ_BIN") { + return PathBuf::from(dir).join("harkaq-exec"); + } + let cache = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/root".into())) + .join(".cache/harkaq") + .join("harkaq-exec"); + if cache.exists() { + return cache; + } + let instalado = PathBuf::from("/usr/bin/harkaq-exec"); + if instalado.exists() { + return instalado; + } + cache } /// Las capabilities que hacen que el uid 0 de adentro sea root DE VERDAD. @@ -1616,8 +1634,11 @@ fn run_instance( let exec = harkaq_exec(); if !exec.exists() { bail!( - "no encuentro harkaq-exec en {} — la instancia NO va a correr enjaulada y eso no se \ - hace en silencio.\n construilo: gcc -O1 -Wall -static -o {} scripts/harkaq/harkaq-exec.c\n o corré con --no-jail si sabés lo que estás haciendo.", + "no encuentro harkaq-exec en {} ni en /usr/bin — la instancia NO va a correr \ + enjaulada y eso no se hace en silencio.\n instalalo: está en `perfil.base` como \ + receta `harkaq-exec`; en una caja instalada, `takana upgrade`.\n o construilo: \ + gcc -O1 -Wall -static -o {} scripts/harkaq/harkaq-exec.c (sólo en el árbol de \ + desarrollo)\n o corré con --no-jail si sabés lo que estás haciendo.", exec.display(), exec.display() ); } diff --git a/docs/state/targets.toml b/docs/state/targets.toml index 8c720b97..e6087034 100644 --- a/docs/state/targets.toml +++ b/docs/state/targets.toml @@ -42,6 +42,15 @@ paquetes = [ # Estaba sellado desde hace meses y en UN solo perfil (`escritorio-kde`) — la lección de `foot` # otra vez: el paquete existe, y no viaja en la imagen que lo necesita. "tar", + # `bwrap` + `harkaq-exec` (2026-09-15): LA JAULA. Los invoca `takana qorpa` —para correr una imagen + # ajena— y también el sandbox de `takana build` (`takana-build/src/sandbox.rs` llama a `bwrap` por + # PATH). Estaban en **CERO perfiles**: `bwrap` sellado desde hace meses con `"perfiles": []`, y + # `harkaq-exec` sin receta siquiera, sólo como un `gcc` a mano en `~/.cache/harkaq` del hub. O sea + # que ninguna imagen de takana podía enjaular nada, ni construir. Se descubrió mudando + # `api.sergio.gioser.net` a la caja de producción: `qorpa provision` abortó pidiendo un binario que + # sólo existía en la máquina donde alguien lo había compilado. Van en `base` porque el CLI que los + # llama viaja en TODAS las imágenes — [[subcomando-sin-driver]] visto un piso más abajo. + "bwrap", "harkaq-exec", # `bash-completion` (2026-09-09): el tabulador de las herramientas del sistema. Entra como raíz # porque NADIE lo alcanza por deps de build — los paquetes que instalan completados sólo preguntan # por su `.pc` y, si no está, se saltan la instalación en silencio. Es un hueco de RUNTIME puro, diff --git a/recipes/harkaq-exec.toml b/recipes/harkaq-exec.toml new file mode 100644 index 00000000..f02b0bf9 --- /dev/null +++ b/recipes/harkaq-exec.toml @@ -0,0 +1,57 @@ +# harkaq-exec — el último eslabón de la jaula, empaquetado como receta. +# +# POR QUÉ EXISTE (descubierto el 2026-09-15, mudando `api.sergio.gioser.net` a la caja nueva). +# `takana qorpa provision` murió en la caja de producción con: +# +# Error: no encuentro harkaq-exec en /root/.cache/harkaq/harkaq-exec — la instancia NO va a +# correr enjaulada y eso no se hace en silencio. +# construilo: gcc -O1 -Wall -static -o … scripts/harkaq/harkaq-exec.c +# +# El mensaje es bueno y la receta que propone es imposible: **en una caja instalada no hay gcc, ni +# árbol de desarrollo, ni `scripts/`**. El binario sólo existía como un `gcc` a mano en el hub, en +# una caché de `$HOME` — o sea que la jaula del ADR 0015 funcionaba únicamente en la máquina donde +# alguien la había compilado. Es [[subcomando-sin-driver]] otra vez, un piso más abajo: `takana +# qorpa` viaja en TODAS las imágenes y su herramienta en NINGUNA. +# +# ⚠ Y su compañero `bwrap` estaba igual: sellado desde hace meses, declarado en CERO perfiles +# (`"perfiles": []` en `build-state.json`) — y lo invocan por PATH tanto `qorpa` como el sandbox de +# `takana build` (`takana-build/src/sandbox.rs`). Los dos entran a `perfil.base` en la misma unidad +# de trabajo: una imagen de takana que no puede enjaular no puede ni construir. +# +# ── EL PIN VA AL COMMIT QUE TOCÓ LA FUENTE, no a HEAD ───────────────────────────────────────────── +# `scripts/harkaq/harkaq-exec.c` no cambia desde el 2026-09-03; el repo, cada media hora (el cron de +# la cosecha commitea el estado del grafo). Pinear HEAD re-hashearía esta receta cada media hora sin +# que su fuente se moviera. El commit de abajo es el último que tocó el `.c` y su `.h`. +# +# La fuente vive en `scripts/` y no en un árbol propio de la receta a propósito: `harkaq-run.sh`, +# `harkaq-farm-setup.sh` y `q2-runtime-base.sh` la compilan desde ahí, y dos copias del mismo `.c` +# divergen en silencio. El precio es subir el `commit` cuando la jaula cambie — el mismo que paga +# `recipes/takana.toml`. + +name = "harkaq-exec" +version = "0.0.1" +license = "MIT" + +[source] +# El repo del propio takana; la URL es sólo locator y no entra en `hash_inputs` (ADR 0013 §1). +repo = "https://git.gioser.net/sergio/takana.git" +commit = "1d9ddcee373fa58f3a311cf5444b2d30040046ab" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +# Estático **obligatorio**, no por costumbre: harkaq-exec corre DENTRO del rootfs del sandbox, donde +# no hay cargador dinámico que valga (lo dice el encabezado del propio `.c`). +link = "static" + +[build.phases] +# El árbol es el repo entero, sin sistema de build que detectar: las tres fases van a mano. +configure = "true" +# `-mcpu=baseline` por reproducibilidad independiente de la CPU del builder, como el resto del lab +# (y ver [[cgo-sin-baseline-sigill]]: hornear la ISA del que compila es un SIGILL en otra caja). +# Los headers de seccomp/landlock salen del rootfs del lab; `harkaq-uapi.h` está al lado del `.c`. +compile = "zig cc -mcpu=baseline -static -O1 -Wall -o harkaq-exec scripts/harkaq/harkaq-exec.c" +install = "mkdir -p /out/usr/bin && cp harkaq-exec /out/usr/bin/harkaq-exec" + +[deps] +build = ["binutils"]