Etapa B (B1+B2): boot desde disco real ext4, sin switch_root
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>
Semantic delta
No definition changed: this change is text, documentation or data.
3 files · +78 −5
- recipes/linux.toml +6 −1
- scripts/disk-image.sh +53 −0
- scripts/drive-rebuild.py +19 −4
diff --git a/recipes/linux.toml b/recipes/linux.toml
index 1f158442..48118e10 100644
--- a/recipes/linux.toml
+++ b/recipes/linux.toml
@@ -25,6 +25,10 @@
# Config: defconfig x86_64 (base PC booteable en QEMU: 8250 serial, initrd, PCI) + ajustes monolíticos
# - E1000/OVERLAY_FS/USER_NS =y (built-in): el builder usa e1000 (vendoring) + overlay (sandbox bwrap)
# + user namespaces (bwrap). Built-in ⇒ sin inyección de .ko ni version-magic.
+# - VIRTIO_BLK/VIRTIO_PCI/VIRTIO_NET/EXT4_FS =y (Etapa B, imagen en disco real): permiten montar un
+# disco virtio (`/dev/vda`, ext4) como root sin initramfs ⇒ `root=/dev/vda rw` directo, sin el hack
+# /init+switch_root. Built-in (no módulos) porque no hay initramfs que cargue .ko antes del root.
+# Aditivos: no tocan el camino initramfs del selfhost-verify (que sigue booteando igual).
# - MODULE_SIG/DEBUG_INFO_BTF off: evitan firma de módulos y pahole (BTF), ausente del toolchain.
# - UNWINDER_FRAME_POINTER en vez de ORC: reduce objtool pero NO lo elimina — en x86_64 las
# mitigaciones (RETHUNK/retpoline-validation) seleccionan CONFIG_OBJTOOL igual, y objtool necesita
@@ -71,13 +75,14 @@ scripts/config -d MODULE_SIG -d MODULE_SIG_ALL -d DEBUG_INFO_BTF -d DEBUG_INFO \
-e E1000 -e OVERLAY_FS -e USER_NS -e NAMESPACES \
-e FANOTIFY -e FANOTIFY_ACCESS_PERMISSIONS \
-e OVERLAY_FS_REDIRECT_DIR -e OVERLAY_FS_INDEX -e OVERLAY_FS_XINO_AUTO -e OVERLAY_FS_METACOPY \
+ -e VIRTIO -e VIRTIO_PCI -e VIRTIO_BLK -e VIRTIO_NET -e EXT4_FS \
-d DRM -d AGP -d FB -d BACKLIGHT_CLASS_DEVICE -d SOUND -d SND \
-d USB_SUPPORT -d MEDIA_SUPPORT -d INFINIBAND -d BT -d NFC -d CAN \
-d WLAN -d WIRELESS -d CFG80211 -d MAC80211 -d RFKILL \
-d HID_SUPPORT -d HID -d INPUT_MOUSE -d INPUT_JOYSTICK -d INPUT_TABLET -d INPUT_TOUCHSCREEN \
-d WATCHDOG -d THUNDERBOLT -d FIREWIRE -d HWMON -d POWER_SUPPLY -d MEMSTICK -d MMC \
-d ATA -d SCSI -d NVME_CORE -d MD -d STAGING -d IIO -d PPS -d PTP_1588_CLOCK \
- -d EXT4_FS -d XFS_FS -d BTRFS_FS -d F2FS_FS -d JFS_FS -d REISERFS_FS -d GFS2_FS -d NTFS3_FS \
+ -d XFS_FS -d BTRFS_FS -d F2FS_FS -d JFS_FS -d REISERFS_FS -d GFS2_FS -d NTFS3_FS \
-d DRM_I915 -d DRM_AMDGPU -d DRM_NOUVEAU -d DRM_RADEON && \
make ARCH=x86_64 olddefconfig
"""
diff --git a/scripts/disk-image.sh b/scripts/disk-image.sh
new file mode 100755
index 00000000..57e167b1
--- /dev/null
+++ b/scripts/disk-image.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+# disk-image.sh — Etapa B (imagen en disco real): empaqueta un rootfs como imagen ext4 booteable de
+# disco virtio (/dev/vda) en vez de initramfs. Con un kernel que trae VIRTIO_BLK+EXT4 built-in
+# (recipes/linux.toml), QEMU monta la imagen como root REAL y el kernel arranca con
+# `root=/dev/vda rw rdinit=/sbin/init` — sin initramfs, sin el wrapper /init+switch_root: `/` ya es un
+# mount ext4 pivotable (el muro pivot_root del path initramfs desaparece por construcción).
+#
+# `mke2fs -d <dir>` puebla la imagen DESDE un directorio sin montar nada y sin root (e2fsprogs >= 1.43).
+#
+# Uso:
+# ./scripts/disk-image.sh # crea work/hammer-disk.img desde work/builder-rootfs
+# BOOT=1 KERNEL=<bzImage> KVM=1 ./scripts/disk-image.sh # además bootea y corre rebuild-stage1
+#
+# Variables:
+# ROOTFS dir rootfs a empaquetar (default work/builder-rootfs, lo arma selfhost-verify)
+# IMG imagen de salida (default work/hammer-disk.img)
+# SIZE tamaño de la imagen (default 6G; el rootfs ronda ~2G + holgura para el rebuild)
+# BOOT 1 ⇒ bootea con drive-rebuild.py tras crear la imagen
+# KERNEL bzImage con VIRTIO_BLK+EXT4 (requerido si BOOT=1)
+# KVM/MEM/NET passthrough a drive-rebuild.py
+set -eu
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT"
+
+ROOTFS="${ROOTFS:-work/builder-rootfs}"
+IMG="${IMG:-work/hammer-disk.img}"
+SIZE="${SIZE:-6G}"
+
+[ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS (corré selfhost-verify para armar el builder)" >&2; exit 1; }
+[ -x "$ROOTFS/sbin/init" ] || [ -L "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; }
+
+echo "==> rootfs : $ROOTFS ($(du -sh "$ROOTFS" | cut -f1))"
+echo "==> imagen : $IMG ($SIZE, ext4, label hammer-root)"
+rm -f "$IMG"
+# Ownership root en la imagen (igual que el `--owner=root:root` del cpio): el rebuild usa bwrap+overlay
+# con un userns que mapea 0→0; si los ficheros van con el uid del host (1000) el copy-up falla EACCES.
+# `unshare -r` mapea nuestro uid→0, así los ficheros uid-1000 se ven como root y mke2fs escribe uid 0
+# en la imagen — sin sudo. mke2fs -d puebla desde el dir sin montar. Fallback a mke2fs directo si no hay
+# unshare (la imagen quedaría uid-host: bootea y da shell, pero el rebuild fallaría en el copy-up).
+if command -v unshare >/dev/null 2>&1 && unshare -r true 2>/dev/null; then
+ unshare -r mke2fs -q -t ext4 -L hammer-root -d "$ROOTFS" "$IMG" "$SIZE"
+else
+ echo " (sin unshare -r: imagen con uid del host; el rebuild in-VM podría fallar el copy-up)" >&2
+ mke2fs -q -t ext4 -L hammer-root -d "$ROOTFS" "$IMG" "$SIZE"
+fi
+echo "==> imagen creada: $(du -h "$IMG" | cut -f1)"
+
+if [ "${BOOT:-0}" = 1 ]; then
+ [ -n "${KERNEL:-}" ] || { echo "BOOT=1 requiere KERNEL=<bzImage con VIRTIO_BLK+EXT4>" >&2; exit 1; }
+ echo "==> booteando de disco (root=/dev/vda, sin initramfs ni switch_root)"
+ DISK="$IMG" KERNEL="$KERNEL" KVM="${KVM:-0}" MEM="${MEM:-6144}" NET="${NET:-1}" \
+ python3 scripts/drive-rebuild.py
+fi
diff --git a/scripts/drive-rebuild.py b/scripts/drive-rebuild.py
index 5eed8532..228975cf 100755
--- a/scripts/drive-rebuild.py
+++ b/scripts/drive-rebuild.py
@@ -8,6 +8,10 @@
#
# Variables de entorno (todas opcionales):
# BUILDER_CPIO initramfs del builder (default work/builder.cpio.gz)
+# DISK imagen ext4 a bootear como ROOT real (Etapa B). Si está seteada, se ignora el
+# initramfs: QEMU monta la imagen como disco virtio (/dev/vda) y el kernel arranca con
+# `root=/dev/vda rw rdinit=/sbin/init` — sin initramfs ni switch_root (/ ya es un mount
+# ext4 real, pivotable). Requiere un kernel con VIRTIO_BLK+EXT4 (recipes/linux.toml).
# KERNEL bzImage/vmlinuz a bootear (default /boot/vmlinuz-linux)
# MEM RAM de la VM en MiB (default 6144)
# CPU modelo de CPU TCG (default Broadwell; AVX2 sin KVM)
@@ -29,6 +33,9 @@ def envpath(name, default):
CPIO = envpath("BUILDER_CPIO", "work/builder.cpio.gz")
+DISK = os.environ.get("DISK", "") # Etapa B: si está, booteamos de disco real en vez de initramfs.
+if DISK and not os.path.isabs(DISK):
+ DISK = os.path.join(ROOT, DISK)
KERNEL = os.environ.get("KERNEL", "/boot/vmlinuz-linux")
LOG = envpath("LOG", "work/rebuild-run.log")
MEM = os.environ.get("MEM", "6144")
@@ -37,7 +44,8 @@ DEADLINE = int(os.environ.get("DEADLINE", "14400"))
WANT_KVM = os.environ.get("KVM", "0") == "1"
WANT_NET = os.environ.get("NET", "1") == "1"
-for p, what in [(CPIO, "initramfs del builder"), (KERNEL, "kernel")]:
+_needed = [(DISK, "imagen de disco")] if DISK else [(CPIO, "initramfs del builder")]
+for p, what in _needed + [(KERNEL, "kernel")]:
if not os.path.exists(p):
sys.exit(f"no existe {what}: {p}")
@@ -54,12 +62,19 @@ net = ["-netdev", "user,id=n0", "-device", "e1000,netdev=n0"] if WANT_NET else [
# (p.ej. el frente kernel-from-source: el bzImage hammer bootea pero bwrap falla en pivot_root porque /
# es el rootfs absoluto del namespace; `rootfstype=tmpfs` NO lo arregla — la causa es que / no tiene
# mount padre, no el tipo de fs). Override con APPEND="…".
-APPEND = os.environ.get("APPEND", "console=ttyS0 rdinit=/sbin/init")
+# Boot de disco (Etapa B) vs initramfs. En disco / ya es un mount ext4 real ⇒ rdinit=/sbin/init directo
+# (arje-zero PID1), sin el wrapper switch_root del path initramfs.
+if DISK:
+ APPEND = os.environ.get("APPEND", "console=ttyS0 root=/dev/vda rw rdinit=/sbin/init")
+ media = ["-drive", f"file={DISK},if=virtio,format=raw", "-append", APPEND]
+else:
+ APPEND = os.environ.get("APPEND", "console=ttyS0 rdinit=/sbin/init")
+ media = ["-initrd", CPIO, "-append", APPEND]
QEMU = (["qemu-system-x86_64", "-m", MEM, "-no-reboot", "-nographic"] + accel + net +
- ["-kernel", KERNEL, "-initrd", CPIO, "-append", APPEND])
+ ["-kernel", KERNEL] + media)
print(f"==> kernel : {KERNEL}")
-print(f"==> initramfs : {CPIO}")
+print(f"==> {'disco ' if DISK else 'initramfs '}: {DISK or CPIO}")
print(f"==> mem : {MEM} MiB deadline: {DEADLINE}s net: {'sí' if WANT_NET else 'no'}")
print(f"==> log : {LOG}")