scripts/product-image: imagen de disco auto-booteable del producto (Etapa E)

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>
This commit is contained in:
2026-06-20 18:09:09 -04:00
co-authored by Claude Opus 4.8
parent 20936e1f96
commit 8ba571aebc
2 changed files with 76 additions and 0 deletions
+7
View File
@@ -565,6 +565,10 @@ fn assemble_product_rootfs(
let dotssh = staging.join("root/.ssh"); // authorized_keys provisionadas en deploy.
std::fs::create_dir_all(&dotssh)?;
std::fs::set_permissions(&dotssh, std::fs::Permissions::from_mode(0o700))?;
// Mountpoints de las particiones dedicadas del disco de producto (vacíos; el wrapper /sbin/init de
// la imagen monta vda3→/store y vda4→/var/lib/hammer). `/var/lib/hammer` ya viene de la base.
std::fs::create_dir_all(staging.join("store"))?;
std::fs::create_dir_all(staging.join("var/lib/hammer"))?;
Ok(())
}
@@ -1666,6 +1670,9 @@ mod tests {
let mode = std::fs::metadata(staging.join("var/empty")).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o711, "/var/empty debe ser 0711");
assert!(staging.join("root/.ssh").is_dir(), "/root/.ssh para authorized_keys");
// Mountpoints de disco (el producto es disk-ready): /store + /var/lib/hammer existen vacíos.
assert!(staging.join("store").is_dir() && staging.join("var/lib/hammer").is_dir(),
"/store y /var/lib/hammer como mountpoints del disco de producto");
}
#[test]