- mesa/wayland/wayland-protocols/libdrm/seatd/samurai/meson + patches: batch C de gráficos previo
que jamás se mandó al worker, arrastrado en incoming/. Parqueado en tandas/hold-nongo/.
- syncthing: build especial (genera la GUI embebida 'auto.Assets' vía go generate/build.go) ⇒
diferida a staged-fallidos con el motivo. Quedan 18 go-12 limpias en la cola.
go-12 (terraform/k8s) destapó la race: con árboles de deps de varios GB en paralelo, el disco
cruza DISK_HIGH y el watchdog corría 'go clean -modcache' a mitad de los 'go mod vendor' en vuelo
(.partial: no such file) ⇒ 5 recetas no convergían. Ahora difiere la purga del modcache si hay un
vendor de host activo (la purga de work/sources ya libera lo grueso y es segura). syncthing: main
real en ./cmd/syncthing (la raíz tiene build-constraints que excluyen todo).
El 'go mod vendor' acumula GOMODCACHE (~/go/pkg/mod) sin tope: una tanda Go
grande lo llevo a 31G y lleno el disco de 80G -> I/O-wait disparo el load a 23
(cuello real, no CPU/RAM). El watchdog ahora purga 'go clean -modcache/-cache'
cuando el disco supera DISK_HIGH% (def 82). El vendor/ local de cada receta ya
tiene lo necesario; si pisa un vendor en curso, esa receta reintenta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra la fragilidad manual del patron Go (path del main por receta). Ahora
importar Go es tan automatico como Rust: import -> pin -> build, sin tocar nada.
- lib.rs: BuildSys::Go (go.mod, prioritario sobre configure/make auxiliares que
traen muchos proyectos Go). resolve_phases deriva el compile generico:
'go install -trimpath -ldflags=-buildid=' del paquete main. install='true'
(go install ya deja en GOBIN=/out/usr/bin).
- detect_go_main(): detecta el dir del main por FILESYSTEM (no compila, evita
contaminarse con mains de ejemplo rotos en docs/scripts que rompen go list).
Heuristica raiz > cmd/<x> > menor profundidad; excluye vendor/docs/test/etc.
go install nombra el binario solo (cmd/mlr->mlr, raiz->modulo).
- nix_import.rs + nix-import.sh: detecta is_go (vendorHash de buildGoModule),
emite deps.build=['go'] sin phases (BuildSys::Go las deriva).
- tests: go_mod_wins_over_configure, detect_go_main_picks_cmd_over_docs.
Validado end-to-end: miller (cmd/mlr->mlr, esquiva docs rotos), amfora (raiz),
duf (import->pin->build 100% automatico, binario estatico que corre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el muro del frente Go (vendoring) replicando el patron de Rust:
vendorear in-situ en el fetch (host, con red), no tarball-al-mirror.
- fetch.rs: vendor_go_deps() analoga a vendor_cargo_deps — corre go mod vendor
en el host si hay go.mod; el sandbox compila offline con -mod=vendor.
- lib.rs: engancha vendor_go_deps tras los patches cuando el arbol trae go.mod
(sin BuildSys::Go; el compile lo fija la receta en build.phases).
- bootstrap-devfs.sh: instala el toolchain go del host (1.26.4, estatico) en
.dev-fs/tools/go + symlink en ~/.cargo/bin (PATH del worker via .cargo/env).
- amfora: PRIMERA receta Go del corpus. Build end-to-end validado (repo+commit
-> fetch vendorea -> sandbox offline -> binario estatico que corre, Amfora
v1.11.0). REPRODUCIBLE bit-a-bit: dos stores independientes dan of_tree
b3:9ec1d5fe (trimpath + buildid= en el compile). Promovida al repo (282).
Quedan 12 recetas Go en tandas/staged-2026-06-26/ (cheat/dnsx/croc/dyff/csvtk/
git-town/miller/jump/gron/qrcp/walk/gtrash): mecanico, copiar el patron de
amfora ajustando el nombre del binario.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/product-image-from-repo.sh cierra el lazo Etapa F+G entero:
base product-rootfs (arje-zero PID1 + musl/busybox) → hidrata userland rico vía
`hammer install --require-signed` del repo firmado → imagen GRUB auto-booteable →
boot en QEMU + handshake SSH que CORRE los tools del repo.
VALIDADO e2e: booteó, arje-zero PID1, hammerd+netup+sshd, y por SSH corrieron
bat 0.26.1 / fd 10.4.2 / jq 1.8.1 / eza — userland farmeado, servido desde el
repo firmado, no del bootstrap hardcodeado. 240 bins en /usr/bin (base+repo).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el lazo Etapa F+G: el userland del producto se arma vía `hammer install
--repo --prefix --require-signed` desde el repo firmado, no del bootstrap
hardcodeado. scripts/product-userland-from-repo.sh hidrata un set curado (13+
tools validados estáticos del repo).
Dos bugs reales del path install/pack encontrados+arreglados:
1. pack derivaba target_bin=/usr/bin/{name}; para paquetes con binario≠nombre
(ripgrep→rg, repgrep→rgr) quedaba mal y el sanity-check de install fallaba.
Ahora pack lo deriva del flag `--bin <X>` de la receta Cargo.
2. La reproducción del source_patch derivaba el NOMBRE de la receta del
target_bin ⇒ "rg" ≠ "ripgrep" del corpus ⇒ no cache-hit ⇒ rebuild + dup en
el store (find_by_hash ambiguo). build_source_patch/run_apply ahora reciben
el nombre del paquete (install/bootstrap lo pasan; apply=None) ⇒ cache-hit
del artefacto del corpus, sin duplicar.
(El "EACCES" inicial era sólo --store ausente: DEFAULT_STORE=/store root-only.)
hammer-build 49/49 tests ok; ripgrep install validado e2e (rg hidratado).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tres frentes que aparecieron al provisionar un CCX13 (Ubuntu 24.04) de verdad:
- bootstrap-devfs §3b: el loader musl-256-keys ahora DETECTA la versión del
rootfs (apk db) en vez de hardcodear 1.2.5. Alpine edge avanzó a musl 1.2.6 ⇒
un loader 1.2.5 vs coreutils 1.2.6 da `renameat2: symbol not found` → chmod
roto en el sandbox → el wrapper zig-cc no queda +x → EACCES. case con sha de
1.2.5 y 1.2.6, die si aparece una nueva.
- vps-setup §2b: compila bubblewrap 0.11.2 si el de la distro no soporta
--overlay-src (Ubuntu 24.04 trae 0.9.0 sin overlayfs; el sandbox lo necesita).
- farm-sync: excluye /.scratch (44G de fuentes mrustc/rustc) + *.png/content*
del rsync al worker (casi copia 44G de más).
Smoke test en el VPS: builds compilan código real (pasan cc/chmod), rustc 2
cores al 93%, RAM holgada. Worker funcional.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paraleliza el build a un/varios VPS sin exponer gitea ni la clave de firma.
Modelo: el laptop es el HUB (firma + gitea); el worker sólo construye la cola
incoming/ y sella al store local (PROMOTE=0). Reproducibilidad bit-a-bit +
content-addressing ⇒ el store del worker es byte-idéntico, se rsync-ea de vuelta
y el hash valida solo (no hay que confiar en el VPS).
- vps-setup.sh: provisiona Debian/Ubuntu (deps, userns p/bwrap, swap 16G,
rustup, build hammer, bootstrap-devfs rootfs Alpine edge, systemd unit).
- farm-worker-loop.sh: loop autónomo 24/7, PROMOTE=0, watchdog de disco
integrado; muele lo que haya sin esperar al hub.
- hammer-farm.service: systemd, JOBS=2 (CCX13 = 2 vCPU dedicado), Nice/idle-io.
- farm-sync.sh (hub): rsync código+cola arriba, store sellado abajo,
promote+firma+commit+push. Acceso único laptop->VPS por SSH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnóstico cerrado: el reboot-probe probó que el kernel está VIVO en el metal del
usuario (se reinicia solo); la pantalla negra es porque el panel TigerLake se apaga al
pasar el kernel y solo i915 (driver Intel real) lo reenciende — efifb/simpledrm/earlyprintk
escriben a un fb muerto (validado: nomodeset tampoco). Era un recorte de más (i915 venía
-d del kernel QEMU). Fix = lo que toda distro normal trae:
- linux-metal.toml: -e DRM_I915 -e DRM_FBDEV_EMULATION (saca -d DRM_I915).
- metal-firmware.sh: inyecta i915/tgl_* (DMC/GuC/HuC, 1.3M) en /lib/firmware/i915.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Diagnóstico metal: con systemd-boot, earlyprintk=efi NO imprime NADA del kernel
(solo 'Loaded initrd MEDIA_GUID' + 'Measured initrd PCR 9' y cuelga) ⇒ el kernel
no llega ni a su consola temprana: hang en ExitBootServices o el decompresor.
- metal-usb-sdboot.sh: piso de 48MiB a la ESP (FAT32 -F exige ~33MiB; con initramfs
chico caía debajo y la firmware no leía el BOOTX64.EFI → 'Not Found').
- USB de bisección (work/hammer-tiny-usb.img, initramfs busybox 1.3MB): ✓ OVMF llega a
/init. Si en el metal del usuario ARRANCA a shell ⇒ el cuelgue era el initrd 100MB;
si TAMBIÉN cuelga ⇒ es el kernel/EBS (probar nokaslr/no5lvl).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El boot por EFI-stub DIRECTO se cuelga en el firmware del usuario tras 'Measured initrd
PCR 9' (quirk EFI_LOAD_OPTION del firmware + initrd 100MB por la firmware). Pivote:
- scripts/metal-usb-sdboot.sh: arma work/hammer-metal-usb.img (disco GPT+ESP FAT32) con
systemd-boot (binario del host, crutch de bringup) como BOOTX64.EFI + loader entry.
systemd-boot provee el initrd por LoadFile2 (esquiva el parseo de cmdline del firmware)
y permite EDITAR la cmdline en vivo (tecla 'e') ⇒ iterar sin recompilar 45min.
✓ VALIDADO OVMF: menú → 'Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID' → arje-zero PID1.
- linux-metal.toml: kernel diagnóstico — cmdline +earlyprintk=efi,keep +ignore_loglevel
+efi=novamap (ve el boot en pantalla post-ExitBootServices) y -DEBUG_WX (saca el trace).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
work/hammer-metal.iso (257M) bootea en OVMF por las dos vías como DISCO:
- virtio-disk y USB-storage(xhci): EFI-stub carga kernel+initrd de la ESP (GPT
isohybrid), cmdline horneado aplica, usbhid/cfg80211 init, arje-zero PID1, SIN panic.
- El '-cdrom' falla (El Torito EFI se trunca >32MB) pero es IRRELEVANTE: un USB es disco
⇒ la firmware lee la ESP por GPT. Confirmado el escenario real del pendrive.
Falta solo el hardware: quemar y bootear (Secure Boot OFF).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/hammer-banner.txt: icono ASCII cabeza-de-martillo rellena de '#' (el hash
content-addressed) + handle, wordmark 'hammer' (toilet pagga, estilo pixel-block que
rima con la cabeza de hashes) + acrónimo HAMMER + uso WiFi. metal-iso.sh lo copia a
/etc/motd ⇒ es lo primero que ves al loguear en consola. Reutilizable como base de
logo/wallpaper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La rama GRUB-EFI cuelga (bug GRUB 2.14 del host rolling, afecta a cualquier kernel).
Solución: bootear el kernel por su EFI-STUB directo, sin GRUB-EFI.
- linux-metal.toml: CONFIG_CMDLINE_BOOL=y + CMDLINE horneado
('console=tty0 ... initrd=/initramfs.cpio.gz rdinit=/init'). Sin FORCE ⇒ en BIOS
GRUB sigue mandando su cmdline. Confirmado en fuente (libstub/file.c:50): el
EFI-stub convierte '/'→'\' y efi_load_initrd cae a la carga por cmdline sin loader.
- iso-image.sh: modo EFI_STUB=1 ⇒ la ESP lleva el bzImage como \EFI\BOOT\BOOTX64.EFI
+ initramfs.cpio.gz en su raíz (FAT32). La firmware lanza el kernel directo.
- metal-iso.sh: pasa EFI=1 EFI_STUB=1 (híbrido: BIOS GRUB + UEFI EFI-stub).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/metal-iso.sh: arma work/hammer-metal.iso (kernel metal + firmware AX201
+ wpa_supplicant + wifi-connect), consola en pantalla (console=tty0).
- VALIDADO BIOS (QEMU): el kernel metal bootea, inicializan usbhid/i8042 (teclado),
e1000/e1000e/igb, cfg80211+regulatory (stack WiFi), simpledrm/VGA console, y
arje-zero arranca PID1. iwlwifi/nvme presentes (no bindean en QEMU sin hw real).
- HALLAZGO: la rama UEFI (OVMF) NO bootea el kernel — regresión pre-existente de
GRUB 2.14 (host rolling): GRUB-EFI dice 'Booting' y el kernel queda mudo, con
CUALQUIER kernel (el QEMU tmb falla; efi-boot-test FALLA UEFI / PASA BIOS).
Fix propuesto: boot por EFI-stub directo (sin GRUB-EFI) — requiere kernel con
CONFIG_CMDLINE embebido + ESP con bzImage como BOOTX64.EFI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pieza 1+2 de 'hammer en el metal' (laptop TigerLake, WiFi AX201):
- recipes/linux-metal.toml: variante del kernel con hardware real built-in
(efifb/simpledrm, USB+HID, AHCI/NVMe, cfg80211+mac80211+iwlwifi/iwlmvm,
e1000e/r8169, USB-CDC). Conserva los bits hammer (overlay/userns/fanotify/
virtio) ⇒ sigue booteando en QEMU. MODULES=off (monolítico, sin modprobe).
Deja linux.toml INTOCADO (su of_tree es load-bearing del selfhost).
- scripts/metal-firmware.sh: inyecta iwlwifi-QuZ-a0-hr-b0/cc-a0 + regulatory.db
en /lib/firmware de un rootfs (descomprime .zst → .ucode plano). Blobs fijos
(TODO pin a linux-firmware).
- ESTADO.md: resumen humano del proyecto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El edge rust depende de scudo-malloc y los shims rustc/cargo lo linkean como primer NEEDED ⇒
scudo interpone malloc en todo el proceso y CRASHEA a rustc en ciertos proc-macros (corrupted
chunk header / SIGSEGV). No se puede apk-del (rust depende). Mitigación: aliasar
/usr/lib/libscudo.so → la libc musl ⇒ el NEEDED resuelve pero malloc cae a musl, sin scudo.
Los shims no usan símbolos scudo-específicos (sólo querían el override), seguro y reversible
(.orig-scudo). bootstrap-devfs.sh lo reproduce (paso 3a-bis).
Destrabados+promovidos: trippy (trip 0.13.0), jnv (0.7.1), presenterm (0.16.1) — antes
crasheaban rustc, ahora construyen+corren static-musl.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El usuario aprobó subir el rustc del sandbox. El rootfs de build (.dev-fs/alpine) migró a
repos Alpine edge: rust/cargo 1.96.0 (+ gcc 15.2/musl 1.2.6/llvm22) + clang-dev/clang-libs
(libclang p/bindgen de los *-sys). bootstrap-devfs.sh actualizado para reproducirlo. El 4/4
NO se afecta (usa SWAP_RUST con el rust hammer-built 1.91.1); esto es el toolchain del CATÁLOGO.
Destrabados+promovidos: oxlint (1.94) y ruff (1.94) construyen+sellan static-musl. ouch lleva
gueto gcc pero queda staged: sus deps C++ (unrar-ng-sys/libbzip3-sys) chocan en el link final
(-lstdc++ vs la libc++ de zig). Frontera C++ = ortogonal al MSRV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La pieza que escala a miles: scripts/build-farm.sh construye N recetas a la vez
(worker pool xargs -P) sobre recipes/incoming/, y lo que CONSTRUYE+sella lo promueve
incoming/->recipes/ + publica al repo firmado (en serie: el index.json es mutable
compartido); lo que falla queda staged con su motivo. Idempotente (cache-hit del store).
Corrida sobre las 22 del lote Rust #2, JOBS=4 en la laptop libre (8 cores/19GB):
BUILD-YIELD 11/22 promovidos, los 11 corren static-musl (bandwhich/difftastic/fclones/
gping/grex/mcfly/miniserve/navi/onefetch/pastel/viu). Corpus 71->82, repo 82.
Los 11 fallos, FLAGEADOS por la granja (esto es el valor: triage automatico, no
adivinanza) y staged en incoming/ para revision:
- crate+dep-C bajo zig-cc (frente conocido, mismo family que el gueto gcc): delta
(libgit2-sys), gitui (openssl-sys), jless (libc-stdhandle).
- MSRV > 1.91.1 (techo del sandbox): ouch (1.93). watchexec: feature nightly E0658.
- LINK colgado con zig-cc en binarios Rust grandes (deadlock, hubo que matarlos):
broot, pueue, starship. Patron a investigar (zig cc + lld en binarios pesados).
- import roto (no build): xsv/zellij 'No such file' (la fuente no clasifico),
dog (importo un dog.c ajeno, no el cliente DNS Rust).
GOTCHA infra: builds Rust grandes pueden COLGARSE en el link final con zig-cc
(broot 7min frozen, pueue/starship idem); la granja deja matar el slot
(pkill -f 'build recipes/incoming/<n>.toml') y sigue con los demas.
Arranca el tier-2 de la escalera (C clasico via Alpine, que trae los parches musl):
- import-batch.sh: env PREFER=nix|alpine. Para tandas C, PREFER=alpine antepone Alpine —
el import de nix de un C tiene exito (tarball) pero SIN parches musl => romperia al
construir. Refactor a 'tiers' ordenados (misma escalera, dos sentidos).
- tandas/cli-c.txt: primera tanda C (tree/which/sed/gawk/gzip/tar/jq/file).
- importador Alpine: build-deps = SOLO makedepends. El depends de abuild es RUNTIME
(gzip depends=less para zless) — no hace falta para compilar y rompia hammer build
(buscaba recipes/less.toml). Ahora va como comentario de provenance. Mismo patron que
el fix de buildInputs-de-nix en recetas Rust.
VALIDADO: import C 8/8 desde Alpine/main con parches musl bajados; gzip pierde el less
espurio. test extracts_source_patches_deps_phases actualizado (depends != build-dep).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Faltaba el orquestador de "importar tandas a escala": las piezas per-paquete ya
existían (nix-import.sh, alpine-import.sh) pero sin un driver que aplicara la ESCALERA
DE FALLBACK en lote.
- import-batch.sh <pkg...> | -f <listfile>: por paquete intenta nix (Rust/Go/CLI,
~100% musl) y cae a Alpine main→community si el import falla (Alpine trae los parches
de musl). Mapeo opcional nixattr=alpinepkg. Escribe OUTDIR/<pkg>.toml.
- Mide el YIELD POR TIER (nix / alpine / fallaron) — el dato real al escalar, sin
especular. stdout limpio = nombres importados (encadenable con xargs/pin-recipes).
- Encadena con las piezas existentes: pin-recipes.sh (Fase 2 tag→SHA) + build-repo.sh
(Etapa F pack --build + repo firmado).
- tandas/cli-rust.txt: primera tanda curada (tier 1 Rust) para el modo -f.
VALIDADO REAL en vivo (nix 2.34.7): tanda {fd, hyperfine, jq} → yield 3/3, nix=2
(fd/hyperfine), alpine=1 (jq cae a main + baja test-portable.patch). pin en lote sobre
la tanda: fd v10.4.2 → SHA 7027d453, jq (tarball) salteado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los imports github salen con commit=tag flotante (v1.1.0); el pin lo resuelve al SHA inmutable
⇒ el laboratorio se vuelve determinista al estilo Nix (origen anclado a un punto fijo).
- hammer-cli: `hammer pin <recipe>` (in-place o --out). Usa `git ls-remote` (host-agnóstico, sin
API ni tokens ni rate-limits), prefiere el commit dereferenciado `^{}` para tags anotados.
Reescritura DIRIGIDA de la línea `commit = "<tag>"` (preserva comentarios/formato; no toca
version u otras que casen). No-op si ya es SHA (idempotente) o tarball (ya anclado por sha256).
is_git_sha (40 hex sha1 / 64 hex sha256). +1 test.
- scripts/pin-recipes.sh: ancla en lote (recipes/*.toml).
- Validado real: sd v1.1.0 → 4a7b216552d6… (git ls-remote), idempotente. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El tier de mayor yield (Rust) salía como github sin --bin ni install ⇒ no buildeable. Ahora un
paquete buildRustPackage sale build-ready, con el patrón de la receta ripgrep.
- nix_import.rs: NixPkg gana is_rust + main_program. Si is_rust ⇒ flags=["--bin", <bin>] (bin =
meta.mainProgram, ripgrep→rg) + install "cp target/release/<bin> /out/usr/bin/<bin>". +1 test.
- nix-import.sh: detecta Rust por `hasAttr "cargoDeps" p`; main_program = meta.mainProgram or pname.
- Validado real: import fd → repo+commit, flags=["--bin","fd"], install template. Build-ready
(sólo el commit es tag, no SHA — refinamiento aparte). 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mueve las recetas de Alpine de "importan" a "casi buildean":
- alpine_import.rs: translate_abuild() en las fases — substituye $pkgdir→/out (el DESTDIR del lab),
$pkgname→nombre, $pkgver→versión. NO toca $CBUILD/$CHOST/--shared (juicio por-paquete, marcado).
+1 test.
- scripts/alpine-import.sh: baja el tarball UNA vez y calcula el sha256 (Alpine publica sha512,
hammer pide sha256), reemplazando el FIXME ⇒ receta lista sin tocar el hash a mano.
- VALIDADO real: import bzip2 → sha256 ab5a0317… resuelto, 5 parches musl bajados, install
traducido a /out. Recipe build-ready (sin $pkgdir ni FIXME en código). 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda fuente del catálogo, y la RESPUESTA a "¿qué si el build falla en musl?": Alpine ya
porta miles de paquetes a musl CON los parches; su APKBUILD los trae. Un import de nix los pierde.
- crates/hammer-cli/alpine_import.rs: PARSEA el APKBUILD (no lo ejecuta) → receta hammer.
Extrae pkgname/pkgver (expande $var), la URL del tarball, LOS .patch (→ source.patches, lo
central), makedepends+depends → deps (filtra -dev, !negados, pins versionados, auto-refs),
build()/package() → fases. sha256 queda FIXME (Alpine publica sha512; el wrapper lo calcula). 3 tests.
- `hammer import-alpine [FILE|-]`; scripts/alpine-import.sh <pkg> [main|community] baja el
APKBUILD + sus .patch de aports.
- VALIDADO contra aports REAL: import coreutils 9.11 → patches renameat2-fakeroot.patch +
coreutils-9.10-dash-tests.patch BAJADOS a disco; deps limpias (acl/attr/bash/openssl/perl/utmps);
fases build/package capturadas. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Poblar el catálogo no es opcional: 34 recetas a mano = userland desierto. nixpkgs es el mayor set
de recetas DESDE FUENTE ⇒ semilla natural. Importamos la RECETA (source+hash+deps), nunca el
binario del cache de nix — hammer reconstruye desde fuente ("verificar, no confiar").
- crates/hammer-cli/nix_import.rs: consume el JSON normalizado de nix y emite una receta hammer.
Clasifica el origen: fetchurl flat → tarball+sha256 (convierte el hash nix SRI/base32/hex → hex);
fetchFromGitHub → repo+commit (hammer pinea por commit, no necesita el hash NAR). Filtra el ruido
de stdenv (setup-hooks, wrappers). nix_base32 decode portado. 11 tests.
- `hammer import-nix [FILE|-]` (stdin) → receta .toml; valida que parsee como Recipe.
- scripts/nix-import.sh <attr>: `nix eval --apply` produce el JSON normalizado y lo pipea al
importador. NIX_STORE= para store local si /nix/store no es escribible.
- VALIDADO contra nixpkgs REAL (nix 2.34): import hello (tarball, sha256→hex) + ripgrep (github→
repo+commit); pipeline completo nix→import→pack→.swm probado con hello. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convierte la maquinaria de paquetería en la cadena de suministro real: cada receta del corpus
se vuelve un .swm, el repo es el catálogo firmado, install lo reproduce localmente.
- scripts/build-repo.sh: packea recipes/*.toml al repo, anclando expected_hash con `pack --build`
(cache-hit si el artefacto ya está sellado; fallback a pack sin ancla por timeout/fallo), y
firma el release (clave efímera o KEY=). Env: REPO/STORE/KEY/DISTRO/BUILD_TIMEOUT.
- /dist/ gitignoreado (el repo es artefacto reproducible desde el script).
- Validado: poblado de 34 paquetes (31 con expected_hash anclado), release firmado. install
ripgrep desde el repo FIRMADO en modo --require-signed → release trusted → reproduce
b3:9e6079fd (casa el ancla del catálogo) → hidrata → rg 14.1.1 corre. Cadena completa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El modelo de generaciones es in-place (sin menu NixOS que ofrecer en GRUB); lo
que encaja es auto-sanar un upgrade interrumpido al boot. crates/hammer-recover
= mini-binario static-musl (lo unico que el producto necesita, sin el CLI hammer
completo): sin pending.json es no-op; con uno completa (roll-forward) o deshace
(roll-back) -> FHS siempre consistente; nunca aborta el boot. iso-image.sh
INSTALLER=1 lo compila (target musl) y bundlea al payload; hammer-live-install.sh
lo copia a /usr/sbin/hammer-recover y el wrapper /sbin/init instalado lo corre
tras montar /store y /var/lib/hammer, antes de incarnar arje-zero. iso-install-
test.sh valida el hook al boot (marker 'hammer-recover: sin upgrade interrumpido').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El apply escribe el plan completo a pending.json ANTES de proyectar y lo limpia
al commitear; un corte a media proyección deja pending.json + FHS a medias. La
proyección se hizo re-entrante (project_plan, backups idempotentes via
backup_existing_once que nunca pisa el original) ⇒ recover COMPLETA (roll-forward,
re-verifica of_tree) o DESHACE (roll-back: restaura backups, borra la gen a
medias, current->padre). apply se niega con PendingExists si hay intento; status
lo avisa. CLI: hammer upgrade recover [--rollback]. 5 tests (corte a media
proyeccion -> ambos modos) + ejercicio en upgrade-e2e-test.sh. 14 tests verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iso-image.sh EFI=1 añade un 2º El Torito EFI: ESP FAT (poblada con el mtools de
hammer, recipes/mtools.toml) con EFI/BOOT/BOOTX64.EFI = grubx64.efi
(grub-mkimage -O x86_64-efi). GRUB EFI lee el mismo grub.cfg y carga el kernel
EFI_STUB (ya en el kernel, sin rebuild) + initrd vía protocolo EFI. El mismo ISO
sigue booteando por BIOS (El Torito i386-pc) ⇒ híbrido. efi-boot-test.sh valida
ambas firmwares E2E (OVMF→grubx64.efi→sshd, y SeaBIOS→sshd).
GOTCHA: mformat -F fuerza FAT32 (min ~33MiB); sobre ESP chica deja FAT inválido
que la firmware no lee -> sin -F, auto FAT12/16.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el lazo ISO live -> disco instalado -> bootea solo. iso-image.sh
INSTALLER=1 bundlea kernel + GRUB MBR (boot/core.img + modulos) en el initramfs
bajo /usr/lib/hammer/install/ + inyecta /usr/bin/hammer-install. hammer-install
corre dentro del live como root real (busybox fdisk/mke2fs/mount/dd + uutils cp):
particiona MBR (/,/store,/var/lib/hammer), formatea, copia la propia raiz del
live (autoinstalador), escribe /boot+wrapper, instala GRUB con 2 dd (boot->MBR,
core->hueco post-MBR; punteros default 1/2 ya valen en MBR contiguo, sin parcheo).
AUTO_INSTALL=<dev> = /init desatendido (install+poweroff). iso-install-test.sh
valida E2E: ISO+disco blanco -> HAMMER-INSTALL-OK -> boot del disco solo ->
GRUB->kernel->arje-zero->sshd, particiones dedicadas montadas.
GOTCHAS: busybox fdisk CHS-alinea a 63 (hueco 62 < core 281 sectores) -> pisa el
FS de p1 -> grub>; fix sectores explicitos (p1@2048). Copiar top-level de /
enumerado, no lista fija, o se escapa /ente/seed.card.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/iso-image.sh arma un ISO 9660/El Torito isohybrid usando el xorriso
construido por hammer (recipes/xorriso.toml, dogfooding): GRUB core El Torito
(grub-mkimage -O i386-pc-eltorito + iso9660) carga kernel + initramfs del ISO;
el rootfs del producto va como cpio.gz (live de RAM). scripts/iso-boot-test.sh
valida E2E in-VM: SeaBIOS->GRUB 2.14->Linux 6.16.12->/init (marcador
HAMMER-ISO-LIVE-OK)->arje-zero PID1->hammerd->netup(DHCP)->sshd escuchando.
El producto entero corre desde el medio live, sin tocar disco. ISO 113M.
SDD 13 E5 marcado primer corte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tras un rollback las generaciones más nuevas quedan inalcanzables (no hay redo).
prune(state, keep) borra esas huérfanas (la cadena viva = current+ancestros vía
parent siempre se conserva); --keep N recorta además la profundidad de rollback.
live_chain() expuesto. 2 tests nuevos + paso prune en upgrade-e2e-test.sh (3->1
gens, árbol vivo intacto).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Crate hammer-upgrade + CLI 'hammer upgrade apply|rollback|status'. Aplica un
árbol Stage1/producto del store sobre el FHS vivo dejando una generación
(manifest + backup de bytes previos), atómico fichero-a-fichero con 'current'
como commit-point. Rollback restaura EXACTO al árbol anterior (o pre-upgrades).
Cada cambio al journal (HammerHydrate, replay-able). Verificación opcional del
of_tree esperado (índice de mirror E3 / release firmada). Maneja ficheros +
symlinks. 7 tests unitarios + scripts/upgrade-e2e-test.sh (apply v1->v2->
rollback->rollback contra el binario real). SDD 13 actualizado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El producto atestado escribe la pubkey de la rootkey en /etc/arje/rootkey.pub (32 bytes
raw), leyéndola del attest_rootkey del seed ya firmado (lo que arje-packager computó de
nuestra rootkey privada) ⇒ cero criptografía nueva en hammer. El gate de arje
(attest_gate.rs::ancla_externa) la PREFIERE sobre la rootkey auto-declarada del seed
(trust = ancla.or(seed.attest_rootkey)): cierra el ataque "seed reescrito por completo"
que el WARN "sin ancla soberana externa" señalaba (hueco A2). product_attested_hash v2.
- assemble_attested_product_rootfs: tras firmar, extrae attest_rootkey (array 32B) y lo
escribe en etc/arje/rootkey.pub; test hermético verifica el ancla.
- scripts/attest-boot-test.sh: nuevo modo SEED_REWRITE=1 (re-firma el seed con rootkey
ATACANTE, deja la rootkey.pub legítima intacta ⇒ debe HALT).
Validado E2E en QEMU sobre el artefacto REAL v2 (hammer bootstrap product --attest):
· ÍNTEGRO → WARN desaparece, "anclada a rootkey soberana externa", 3 ✓, SSH OK.
· SEED_REWRITE → gate: ancla ≠ attest_rootkey, 3× "autor no confiable" → Halt → sin SSH.
Nota: el ancla en fichero cierra el seed-rewrite; el ancla compilada ARJE_ATTEST_ROOTKEY
(dentro del binario atestado) sería más fuerte pero exige rebuild por-rootkey (documentada
en attest_gate.rs, no hecha). Queda sólo el pendiente #2 (Cargo.lock en tawasuyu).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`product_attested()` + CLI `hammer bootstrap product --attest [--policy] [--rootkey]`:
estructura el spike attest-boot-test.sh dentro de hammer-bootstrap. Hidrata el init
CON gate (arje-zero-attest) sobre /usr/bin/arje-zero, fija attest_policy, deriva los
--bin label=path de la PROPIA seed (PID1 + execs Native, DFS) y FIRMA con arje-packager
(host, static musl) → seed firmada en /ente/seed.card.json; regenera /ente/attest.json
coherente con el init nuevo y sella product-attested-rootfs APARTE (núcleo+product base
intactos, Separación Mecanismo/Política).
- AttestConfig{policy, rootkey:[u8;32]}; DEV_ATTEST_ROOTKEY determinista ⇒ firmas
Ed25519 reproducibles ⇒ árbol sellado reproducible. product_attested_hash v1.
- GOTCHA hardlinks read-only del store: romper /ente/seed.card.json antes de que el
packager escriba (EACCES) y /ente/attest.json antes de reescribir; tmp de firma en
staging/.attest-build se limpia antes de sellar.
- critical_bins_from_seed: getty+sshd comparten /bin/busybox ⇒ 3 concesiones por hash.
- Validado en host contra los sellos reales (3 concesiones, init 13MB, hammer attest ✓).
- Test hermético assemble_attested_swaps_gate_signs_seed_and_regenerates_attest (packager
sintético). 40 tests verde.
- scripts/attest-boot-test.sh: MODO A REAL (PRODUCT_ATTESTED=<hash> bootea el artefacto
real) + MODO B SPIKE fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra la integración canónica firmada de I4 (decisión del usuario): la mitad de
arje (gate) + la mitad de hammer (firma) acopladas con la cripto real de
arje-attest/agora — NO el manifiesto plano de /ente/attest.json.
tawasuyu (pusheado, commit c78a0ada en main): `arje-packager --seed-out` emite el
seed FIRMADO standalone (el gate attest_gate.rs ya estaba en main).
hammer:
- recipes/arje-packager.toml: el firmador (build-time tool, static musl, corre en
el host). GOTCHA: lib+bin + monorepo virtual ⇒ flags `-p ... --bin ...`.
- recipes/arje-zero-attest.toml: arje-zero CON gate, variante SÓLO de producto
(commit c78a0ada). El arje-zero del núcleo (9967b02c) NO se toca ⇒ of_tree
baseline (9adefb82/7fa6cb4e) BLINDADO. Separación Mecanismo/Política.
- scripts/attest-boot-test.sh: valida E2E sobre una copia del product-rootfs
(override del PID1 por el gated + seed firmado por arje-packager + boot QEMU).
Validado in-VM (rootkey fija ⇒ firmas Ed25519 deterministas):
- ÍNTEGRO: gate atesta arje-zero+hammerd+busybox ✓ (politica=Halt) → servicios
arriba → SSH OK.
- TAMPER (1 byte en hammerd tras firmar): "atestación ✗ binario no atestado" →
"ARRANQUE FALLIDO ... Halt — abortando antes de incarnar" → shell de rescate,
SIN SSH. La integridad comprometida NO levanta el entorno.
Pendiente (estructurar bien): cablear esto en hammer-bootstrap product() (firmar
+ hidratar el gated automáticamente) y reproducibilidad (Cargo.lock committeado
en tawasuyu para el gated). El mecanismo ya está probado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`hammer install <device>`: vuelca el product-rootfs lean sobre un disco
destino (block device real /dev/sdX, o un fichero pre-dimensionado para
pruebas) con el mismo layout GPT + GRUB BIOS de E1 pero IN-PLACE. Tras
instalar, el disco arranca solo (SeaBIOS → GRUB → arje-zero → hammerd+
getty+sshd). Contraparte "a disco real" de product-image.sh (imagen portátil).
- install-image.sh generalizado: si IMG es block device (o PREALLOC=1 sobre
fichero pre-dimensionado) NO trunca ni borra el nodo — valida capacidad e
instala in-place. sfdisk/mke2fs -d/dd-splice/GRUB son idénticos (dd a un
offset de device = a un offset de fichero). El camino rootless de E1
(unshare -r + dd conv=sparse,notrunc + patch GRUB) sirve igual al device.
- hammer-install.sh: resuelve el product-rootfs, provisiona authorized_keys
(AUTHKEYS=<file> o TESTKEY=1 efímera), guardas de seguridad (FORCE=1 para
un block device, rechazo si está montado), y delega a install-image.sh.
Con BOOT=1+TESTKEY=1 arranca el target y valida por SSH.
Validado: instalación IN-PLACE sobre un fichero pre-dimensionado de 2200M
(ejercita el camino exacto del device, sin hardware real) → el disco
instalado auto-bootea (sin -kernel) y sirve SSH: ls = "uutils coreutils
0.9.0", /dev/vda3→/store y /dev/vda4→/var/lib/hammer montadas. El device
real sólo añade el guard FORCE + autodetección [-b].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el pipeline de release engineering para el product-rootfs: del
artefacto sellado por `hammer bootstrap product` a un disco GRUB-booteable
que arranca solo en QEMU (`-drive file=img`, SIN -kernel) y sirve SSH.
- product-image.sh: hidrata el product-rootfs sellado a un dir escribible,
provisiona authorized_keys de prueba y delega el armado del disco a
install-image.sh (GRUB BIOS, particiones dedicadas vda2=/ vda3=/store
vda4=/var/lib/hammer). A diferencia de install-image por defecto (que
empaqueta el BUILDER con todo el toolchain), la root es el product-rootfs
LEAN ⇒ imagen de PRODUCTO. Con BOOT=1 auto-bootea + handshake SSH (slirp
hostfwd) como validación.
GOTCHA: BOOT=1 (para el boot propio) se hereda por entorno a
install-image.sh, que haría su PROPIO `exec qemu` foreground y bloquearía
⇒ se pasa BOOT=0 explícito al delegar.
- hammer-bootstrap: el producto crea los mountpoints /store y /var/lib/hammer
(disk-ready) para el wrapper /sbin/init de la imagen. Test actualizado.
Validado in-VM: la imagen auto-bootea por GRUB (sin -kernel) → arje PID1 →
hammerd+getty+sshd; SSH OK, ls = "uutils coreutils 0.9.0" (userland Rust del
disco), /dev/vda3→/store y /dev/vda4→/var/lib/hammer montadas. 36 tests verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extiende la capa de producto con el userland Rust-nativo ADOPTADO, fuera del
núcleo blindado (sigue sin tocar STAGE1_COMPONENTS ni el of_tree).
- USERLAND_COMPONENTS = [uutils, findutils, findutils-xargs, diffutils,
ripgrep]: se hidratan sobre el 4/4 DESPUÉS de busybox ⇒ sus symlinks en
/usr/bin ensombrecen los applets busybox.
- ADELGAZAR BUSYBOX (determinista, sin depender del PATH del shell): por cada
nombre que el userland Rust provee en /usr/bin, se RETIRA el symlink
homónimo de busybox en /bin, /sbin, /usr/sbin (busybox suele dejar `ls` en
/bin, fuera del /usr/bin ya ensombrecido). La tool Rust queda ÚNICA en PATH.
El binario busybox y lo no reemplazado (sh/ash, tar, mount) se preservan.
- product_rootfs_hash v2 (incluye userland); build_components() helper;
assemble_product_rootfs hidrata base→userland→servicios.
- 36 tests verde (nuevo: ensombrecido + retiro de applet + sh/busybox quedan).
Validado in-VM (product-boot-test.sh sobre el product-rootfs de la ruta real):
ls = "uutils coreutils 0.9.0" (/usr/bin/ls), find = "find (Rust) 0.9.1",
rg = "ripgrep 14.1.1"; /bin/ls y /bin/cat retirados, /bin/sh + busybox
preservados. SSH sigue verde. "Adelgazar busybox" cerrado en el producto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidación arquitectónica de sshd-como-servicio (Separación Mecanismo/
Política, decisión del usuario). El núcleo NO se toca: STAGE1_COMPONENTS +
STAGE1_SEED_CARD siguen siendo el mecanismo base atómico que el
selfhost-verify reconstruye bit a bit (of_tree 9adefb82/7fa6cb4e blindado).
Nuevo en hammer-bootstrap:
- SERVICE_COMPONENTS = [netup, openssh] (política de producto).
- SSHD_SERVICE_CARD: card genesis Native/Restart (netup + ssh-keygen -A +
exec sshd -D), validado E2E en QEMU.
- product_seed_card(): compone la seed de producto = seed base + cards de
servicio apendados al genesis vía serde_json (hammer sigue autocontenido,
sin dep de card-core). El núcleo (hammerd+getty) se preserva.
- assemble_product_rootfs() + product(): HIDRATACIÓN TARDÍA — hidrata el
stage1-rootfs ya sellado (4/4 verificado) + inyecta openssh/netup encima
+ escribe configs (passwd con sshd, sshd_config con PidFile /run, /var/empty
0711, /root/.ssh) y sella un `product-rootfs` APARTE. of_tree del núcleo
intacto. Idempotente, reproducible.
GOTCHA: los ficheros hidratados son hardlinks read-only al store ⇒ romper
el hardlink (remove+write) en vez de chmod (mutaría el inodo del store).
- CLI: `hammer bootstrap product --rootfs <base> --recipes recipes`.
- 4 tests nuevos (seed compone, hash determinista, assemble inyecta, recetas
de servicio parsean). 36/36 verde.
scripts/product-boot-test.sh: valida que el product-rootfs de la RUTA REAL
bootea en QEMU y sirve SSH (sólo provisiona authorized_keys de prueba, no
ensambla nada). VERDE: arje levanta hammerd+getty+sshd, handshake real
"Accepted publickey for root", guest responde (seed=hammer-product con 3
cards, Linux 6.16.12). Cierra el agujero de verificación con la arquitectura
final, no con el spike sucio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spike de validación (sucio, temporal) que cierra el agujero que el bwrap
anidado dejó: bootea un work/rootfs-ssh-test desechable (4/4 limpio del
store + openssh + netup PRECOMPILADOS) como initramfs, con sshd como
genesis card supervisado por arje-zero, red slirp con hostfwd :2222->:22,
y hace el handshake SSH real desde el host.
VERDE: arje valida la seed e instancia el card sshd (Native/Restart);
netup toma lease 10.0.2.15 (DHCP slirp); ssh-keygen -A genera host keys;
sshd escucha :22; "Accepted publickey for root" y el guest ejecuta el
comando (HANDSHAKE_OK uid=0, Linux 6.16.12). Quema las 3 incógnitas:
(1) handshake post-chroot/setuid de privsep como root REAL del guest
(lo que el bwrap no pudo), (2) arje gobierna el lifecycle del daemon,
(3) los fds de red no segfaultean contra musl.
Gotchas: cpio -R 0:0 (sin root-owned /var/empty sshd lo rechaza);
chmod u+w sobre el staging (el store está sellado read-only).
NO es plomería de producto: la consolidación (SERVICE_COMPONENTS +
hidratacion tardia sobre el 4/4 verificado, of_tree blindado) es el Paso 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
netup = binario hammer-propio (sync, sólo libc, sin tokio) que configura la red:
levanta el link (netlink RTM_NEWLINK), negocia un lease DHCPv4 a mano sobre UDP
(DISCOVER/OFFER/REQUEST/ACK con flag broadcast), y aplica IP + ruta default +
/etc/resolv.conf (RTM_NEWADDR/RTM_NEWROUTE). Hand-roll de netlink y DHCP porque
no hay cliente DHCP Rust "maduro" para adoptar al estilo ripgrep, y el workspace
es 100% sync (rtnetlink arrastraría tokio). Reemplaza el `ip` estático de busybox.
Validado in-VM contra el DHCP de QEMU slirp: ✓ lease 10.0.2.15 + ruta + DNS +
egress TCP. Autodetecta la NIC (primera no-loopback) y espera carrier por sysfs.
Infra de prueba:
- scripts/drive-netup.py: bootea la VM, corre netup y verifica lease/NAT/DNS.
SLIRP=1 ⇒ red user-mode (cero setup de host); si no, tap del lab.
- scripts/labnet.sh: lab de LAN real opcional (tap directo + dnsmasq + NAT) para
fidelidad de hardware; no requerido para validar netup.
- scripts/netdiag.sh: diagnóstico del camino DHCP del lab (tcpdump+nft+dnsmasq).
Lección: para validar el CÓDIGO conviene slirp primero (cero plomería de host);
la LAN real (firewall INPUT, sutilezas del bridge, perms) es fidelidad posterior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primer entregable de release engineering (SDD 13 nuevo): scripts/install-image.sh
produce una imagen GPT que se BOOTEA SOLA en QEMU (`-drive file=img`, sin
-kernel ni firmware extra) — SeaBIOS → MBR → GRUB → kernel del propio disco →
arje-zero PID 1. Capaz de arrancar en hardware real.
Layout: vda1=BIOS boot (ef02), vda2=/ (con /boot/bzImage + /boot/grub),
vda3=/store, vda4=/var/lib/hammer. Reusa el particionado sin-root de B3
(sfdisk + mke2fs -d bajo unshare -r + dd conv=sparse) y el wrapper /sbin/init.
GRUB instalado SIN root ni loop: grub-bios-setup sondea el disco físico del
host (/dev/nvme…, 660 root:disk) para adivinar el root device del dir -d y falla
sin privilegios. Lo reemplaza un patch binario determinista sobre la ABI estable
de GRUB i386-pc: grub-mkimage arma core.img; un script Python escribe core.img
en la BIOS boot partition y parchea los punteros (boot.img off 0x5c kernel_sector
→ LBA de core.img; core.img off 0x1F4 blocklist.start → resto de core.img),
con asserts de los valores por defecto (1 y 2) para fallar ruidoso si la ABI
cambia. boot.img va al MBR sin pisar la GPT protective (sólo 440 B).
Verificado in-VM (KVM, sin -kernel): SeaBIOS → GRUB 2.14 → Linux 6.16.12 →
vda1..4 detectadas, vda2 root + vda3/vda4 montadas por el wrapper (0 errores) →
arje-zero PID 1 + hammerd (store=/store journal=/var/lib/hammer/journal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/disk-image.sh ahora arma una imagen GPT de 3 particiones ext4 en vez
de una sola: vda1=/ , vda2=/store (CAS inmutable), vda3=/var/lib/hammer (estado
mutable). Construcción sin root ni loopback: cp -al stagea el rootfs por
hardlinks (vacía store/ y var/lib/hammer/, instala el wrapper /sbin/init),
mke2fs -d puebla cada ext4 bajo unshare -r (root-owned), sfdisk escribe la GPT
y dd conv=sparse,notrunc empalma cada fs en su offset (imagen sparse, ~2G
reales). El wrapper /sbin/init monta vda2/vda3 y hace exec de arje-zero (el
kernel sólo monta vda1). drive-rebuild.py: root=/dev/vda1 en modo DISK.
Consecuencia resuelta: con /store en su propia partición el sellado cruza
filesystems y rename(2) da EXDEV. Store::seal cae a copia recursiva a un
staging dentro del store (preserva symlinks+modos) + rename store-interno
(atómico, mismo FS) + borrado del origen. Test copy_tree añadido.
Verificado in-VM (kernel hammer, KVM): vda{1,2,3} montados dedicados, 0
errores Cross-device, stage1' == stage1 ✓ REPRODUCIBLE. Cierra el ☐ de
SDD 11 §6 (particionado/montaje en la imagen destino).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B1 — kernel con disco+FS: linux.toml añade VIRTIO_BLK/VIRTIO_PCI/VIRTIO_NET/EXT4_FS
built-in (=y, sin módulos), aditivo al path initramfs. B2 — infra de disco:
- scripts/disk-image.sh: empaqueta un rootfs como imagen ext4 booteable con
`mke2fs -d` bajo `unshare -r` (archivos root-owned sin sudo, como el cpio
--owner=root:root; evita EACCES en el copy-up de overlay del rebuild).
- scripts/drive-rebuild.py: modo DISK= ⇒ QEMU monta la imagen como virtio /dev/vda
y el kernel arranca con `root=/dev/vda rw rdinit=/sbin/init` — sin initramfs.
Verificado en QEMU/KVM: el kernel monta /dev/vda ext4 como root REAL y arje-zero
arranca como PID 1 DIRECTO, sin el hack /init+switch_root (el muro pivot_root del
path initramfs desaparece por construcción: / ya es un mount pivotable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flex/bison estaban en el devfs base (apk) Y como recipes/{flex,bison}.toml en
deps.build del kernel — pero las capas de deps van BAJO el rootfs, así que el
Alpine flex/bison eclipsaba a los hammer y las recetas eran decorativas. Quito
flex/bison del devfs/bootstrap-devfs.sh; ahora el kernel los toma de deps.build
(hammer). Validado: rebuild en store fresco → kconfig compila con flex/bison
hammer (lexer.lex.o/parser.tab.o), HAMMER_KERNEL=1 verify → ✓ REPRODUCIBLE.
(El bzImage difiere en 1 byte del Alpine-flex/bison build: el timestamp embebido
UTS_VERSION, no flex/bison; el kernel no es input del of_tree del 4/4.)
m4 (que bison invoca) se deja en el base como tool autotools general. Con esto
los build-deps ESPECIALIZADOS del kernel (flex/bison/openssl/elfutils) son todos
hammer-from-source; sólo quedan las libs base/runtime (m4, libz/libzstd de libelf,
gcc) como capa de arranque, igual status que libc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tools/objtool del kernel enlaza -lelf (libelf+gelf.h); venía de `apk add
elfutils-dev`. Ahora recipes/elfutils.toml construye SÓLO libelf (0.194, lo que
objtool necesita — no libdw/libdwfl/src) desde fuente, wired como deps.build del
kernel. Con esto NINGÚN build-dep del kernel viene de Alpine: el camino del
kernel es 100% hammer-from-source.
elfutils es glibc-céntrico; musl no trae <error.h>/<argp.h>/<libintl.h>/fts/
obstack/rawmemchr. En vez del parche completo de Alpine, shims mínimos en compat/
(vía CPPFLAGS/-include): error.h y argp.h (sólo los usan color.c/printversion.c,
cuyos .o van en libeu.a pero objtool NO referencia ⇒ basta que compilen); libintl
no-op (+ --disable-nls); rawmemchr inline; y libargp/libfts/libobstack.a stubs
para pasar los AC_SEARCH_LIBS del configure (símbolos inertes para libelf). CC=gcc.
bootstrap-devfs.sh: elfutils-dev fuera del NEEDED. Validado: purgué elfutils-dev
del devfs, rebuild → objtool linkea el libelf hammer (sin gelf.h Alpine), y
HAMMER_KERNEL=1 verify → ✓ REPRODUCIBLE bit a bit, DRIVER_RC=0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>