Etapa B3: /store y /var/lib/hammer en particiones dedicadas (GPT)
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>
This commit is contained in:
@@ -102,12 +102,14 @@ impl Store {
|
||||
/// Sella el árbol de salida de un build en el store bajo su hash.
|
||||
///
|
||||
/// - Si el destino ya existe (caché), devuelve la ruta sin tocar `out_dir`.
|
||||
/// - Si no, mueve `out_dir` por rename atómico al destino y marca el árbol read-only
|
||||
/// (todos los archivos pierden `w`; directorios mantienen `x` para poder atravesarlos).
|
||||
/// - Si no, mueve `out_dir` al destino y marca el árbol read-only (todos los archivos
|
||||
/// pierden `w`; directorios mantienen `x` para poder atravesarlos).
|
||||
///
|
||||
/// Requisito: `out_dir` y el store deben estar en el mismo filesystem para que el rename
|
||||
/// sea atómico. El lab garantiza esto colocando el tmpfs/DESTDIR del sandbox bajo el
|
||||
/// mismo punto de montaje que el store, o haciendo una copia previa si no.
|
||||
/// Camino rápido: `rename` atómico cuando `out_dir` y el store comparten filesystem.
|
||||
/// Camino EXDEV (Etapa B3, `/store` es una partición dedicada ≠ la de `/work`): `rename(2)`
|
||||
/// no cruza filesystems ⇒ caemos a copia recursiva a un staging DENTRO del store + rename
|
||||
/// store-interno (mismo FS, atómico) + borrado del original. La publicación sigue siendo
|
||||
/// atómica (nadie ve el `dst` a medio escribir).
|
||||
pub fn seal(
|
||||
&self,
|
||||
out_dir: &Path,
|
||||
@@ -121,18 +123,68 @@ impl Store {
|
||||
if let Some(parent) = dst.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::rename(out_dir, &dst).map_err(|e| {
|
||||
crate::Error::Store(format!(
|
||||
"rename {} → {}: {e}",
|
||||
out_dir.display(),
|
||||
dst.display()
|
||||
))
|
||||
})?;
|
||||
match std::fs::rename(out_dir, &dst) {
|
||||
Ok(()) => {}
|
||||
// EXDEV (18) = Cross-device link: out_dir y el store viven en filesystems distintos.
|
||||
Err(e) if e.raw_os_error() == Some(EXDEV) => {
|
||||
let parent = dst.parent().unwrap_or_else(|| Path::new("."));
|
||||
let fname = dst.file_name().expect("dst con nombre");
|
||||
let staging = parent.join(format!(".seal-tmp-{}", fname.to_string_lossy()));
|
||||
let _ = std::fs::remove_dir_all(&staging); // restos de un intento previo
|
||||
copy_tree(out_dir, &staging).map_err(|e| {
|
||||
crate::Error::Store(format!(
|
||||
"copia cross-device {} → {}: {e}",
|
||||
out_dir.display(),
|
||||
staging.display()
|
||||
))
|
||||
})?;
|
||||
std::fs::rename(&staging, &dst).map_err(|e| {
|
||||
let _ = std::fs::remove_dir_all(&staging);
|
||||
crate::Error::Store(format!("rename store-interno → {}: {e}", dst.display()))
|
||||
})?;
|
||||
std::fs::remove_dir_all(out_dir).map_err(|e| {
|
||||
crate::Error::Store(format!("limpiar origen {}: {e}", out_dir.display()))
|
||||
})?;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(crate::Error::Store(format!(
|
||||
"rename {} → {}: {e}",
|
||||
out_dir.display(),
|
||||
dst.display()
|
||||
)))
|
||||
}
|
||||
}
|
||||
make_tree_read_only(&dst)?;
|
||||
Ok(dst)
|
||||
}
|
||||
}
|
||||
|
||||
/// EXDEV en Linux. Evita una dependencia de `libc` por una sola constante.
|
||||
const EXDEV: i32 = 18;
|
||||
|
||||
/// Copia recursiva de `src` a `dst` preservando symlinks (target literal, sin resolver) y
|
||||
/// los modos de archivos y directorios. Usada por el fallback cross-device de `seal`.
|
||||
fn copy_tree(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||
let meta = std::fs::symlink_metadata(src)?;
|
||||
let ft = meta.file_type();
|
||||
if ft.is_symlink() {
|
||||
let target = std::fs::read_link(src)?;
|
||||
std::os::unix::fs::symlink(target, dst)?;
|
||||
} else if ft.is_dir() {
|
||||
std::fs::create_dir_all(dst)?;
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
copy_tree(&entry.path(), &dst.join(entry.file_name()))?;
|
||||
}
|
||||
// Modos del directorio DESPUÉS de poblarlo (un dir 0o555 no admitiría escrituras).
|
||||
std::fs::set_permissions(dst, meta.permissions())?;
|
||||
} else {
|
||||
// Archivo regular: `std::fs::copy` lleva contenido + bits de permiso.
|
||||
std::fs::copy(src, dst)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Quita los bits de escritura de cada archivo regular del árbol. Los directorios mantienen
|
||||
/// sus permisos originales (típicamente 0o755): eso es suficiente para garantizar
|
||||
/// "contenido inmutable" sin estorbar al GC o a operaciones administrativas como `rm -rf`.
|
||||
@@ -196,6 +248,30 @@ mod tests {
|
||||
assert!(store.has(&hash(), "demo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_tree_preserva_symlinks_y_modos() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let src = tmp.path().join("src");
|
||||
std::fs::create_dir_all(src.join("sub")).unwrap();
|
||||
std::fs::write(src.join("sub/exe"), b"#!/bin/sh\n").unwrap();
|
||||
std::fs::set_permissions(src.join("sub/exe"), std::fs::Permissions::from_mode(0o755))
|
||||
.unwrap();
|
||||
std::os::unix::fs::symlink("sub/exe", src.join("enlace")).unwrap();
|
||||
|
||||
let dst = tmp.path().join("dst");
|
||||
copy_tree(&src, &dst).unwrap();
|
||||
|
||||
// El symlink se replica con su target literal (no se resuelve).
|
||||
let link = std::fs::symlink_metadata(dst.join("enlace")).unwrap();
|
||||
assert!(link.file_type().is_symlink());
|
||||
assert_eq!(std::fs::read_link(dst.join("enlace")).unwrap().to_str(), Some("sub/exe"));
|
||||
// El modo 0o755 del archivo sobrevive.
|
||||
let exe = std::fs::metadata(dst.join("sub/exe")).unwrap();
|
||||
assert_eq!(exe.permissions().mode() & 0o777, 0o755);
|
||||
assert_eq!(std::fs::read(dst.join("sub/exe")).unwrap(), b"#!/bin/sh\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_by_hash_resolves_by_prefix() {
|
||||
let store_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -182,8 +182,13 @@ hammer bootstrap manifest # imprime el bootstrap.json (log de transp
|
||||
como receta git pinned en un lote posterior. ✅
|
||||
- **Layout de la semilla:** ingesta pura en Stage 0 + resolución del toolchain al usarlo
|
||||
(`SeedSpec::toolchain_dir`, localiza `zig` en raíz o hijo versionado). ✅
|
||||
- **Particionado/montaje** de `/store` y `/var/lib/hammer` en la imagen destino: track posterior
|
||||
del roadmap. ☐
|
||||
- **Particionado/montaje** de `/store` y `/var/lib/hammer` en la imagen destino: ✅ (Etapa B3).
|
||||
`scripts/disk-image.sh` arma una imagen GPT con 3 particiones ext4 dedicadas —
|
||||
`/dev/vda1`→`/`, `/dev/vda2`→`/store` (CAS inmutable), `/dev/vda3`→`/var/lib/hammer` (estado
|
||||
mutable). El kernel monta vda1; un wrapper `/sbin/init` monta vda2/vda3 y hace `exec` del init
|
||||
real (arje-zero). Consecuencia que hubo que resolver: con `/store` en su propia partición, el
|
||||
sellado del store cruza filesystems y `rename(2)` da EXDEV ⇒ `Store::seal` cae a copia-a-staging
|
||||
dentro del store + rename store-interno (atómico). Verificado in-VM: rebuild ✓ REPRODUCIBLE.
|
||||
- **Kernel:** importado pinned ahora; from-source después. ☐
|
||||
|
||||
## 7. Auto-alojamiento: el builder rootfs (camino a Stage 2 pleno)
|
||||
|
||||
+98
-31
@@ -1,53 +1,120 @@
|
||||
#!/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).
|
||||
# disk-image.sh — Etapa B3 (mounts dedicados): empaqueta el rootfs como una imagen de disco virtio
|
||||
# PARTICIONADA (GPT) con tres filesystems ext4 dedicados, en vez de una única partición que mete todo
|
||||
# junto (B2). Es el paso hacia la imagen instalable real: el store y el estado mutable dejan de ser meros
|
||||
# directorios del rootfs y pasan a ser particiones propias, como en un sistema instalado de verdad.
|
||||
#
|
||||
# `mke2fs -d <dir>` puebla la imagen DESDE un directorio sin montar nada y sin root (e2fsprogs >= 1.43).
|
||||
# /dev/vda1 → / rootfs (runtime + toolchain + /work del rebuild) [hammer-root]
|
||||
# /dev/vda2 → /store artefactos CAS BLAKE3 (inmutables) [hammer-store]
|
||||
# /dev/vda3 → /var/lib/hammer estado mutable: journal + overlays (upper/work) [hammer-state]
|
||||
#
|
||||
# El kernel (recipes/linux.toml) trae VIRTIO_BLK+EXT4+EFI_PARTITION(=y por defconfig) ⇒ lee la GPT y
|
||||
# monta /dev/vda1 como root sin initramfs. Las otras dos particiones las monta un wrapper `/sbin/init`
|
||||
# (montar /store + /var/lib/hammer, luego `exec /usr/bin/arje-zero`) — el init real (arje-zero) sigue
|
||||
# en su sitio; el wrapper es el puente mientras arje no lea fstab/cards de montaje por sí mismo.
|
||||
#
|
||||
# Construcción sin root ni loopback:
|
||||
# - `cp -al` arma un árbol del rootfs por HARDLINKS (sin copiar datos), le vacía store/ y
|
||||
# var/lib/hammer/ (deja mountpoints vacíos) y le instala el wrapper /sbin/init. NUNCA muta $ROOTFS.
|
||||
# - `mke2fs -d <dir>` puebla cada ext4 desde un directorio sin montar nada (e2fsprogs >= 1.43).
|
||||
# - `unshare -r` mapea nuestro uid→0 ⇒ los ficheros uid-host se sellan como uid 0 en la imagen (igual
|
||||
# que el `--owner=root:root` del cpio; el copy-up de overlay del rebuild falla EACCES si van uid 1000).
|
||||
# - `sfdisk` escribe la GPT sobre el fichero-imagen (no necesita root) y `dd conv=notrunc` empalma cada
|
||||
# ext4 en el offset de su partición. Tamaños exactos: la partición mide lo mismo que el ext4 (M==MiB).
|
||||
#
|
||||
# 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
|
||||
# ./scripts/disk-image.sh # crea work/hammer-disk.img
|
||||
# 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
|
||||
# ROOTFS dir rootfs a empaquetar (default work/builder-rootfs, lo arma selfhost-verify)
|
||||
# IMG imagen de salida (default work/hammer-disk.img)
|
||||
# ROOT_SIZE MiB de la partición / (default 6144; incluye /work del rebuild)
|
||||
# STORE_SIZE MiB de la partición /store (default 2048; artefactos CAS ~0.4G + reseal del rebuild)
|
||||
# STATE_SIZE MiB de /var/lib/hammer (default 2048; journal + overlays)
|
||||
# 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}"
|
||||
ROOT_SIZE="${ROOT_SIZE:-6144}"
|
||||
STORE_SIZE="${STORE_SIZE:-2048}"
|
||||
STATE_SIZE="${STATE_SIZE:-2048}"
|
||||
|
||||
[ -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; }
|
||||
[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; }
|
||||
command -v sfdisk >/dev/null 2>&1 || { echo "falta sfdisk (util-linux) para la tabla GPT" >&2; exit 1; }
|
||||
command -v mke2fs >/dev/null 2>&1 || { echo "falta mke2fs (e2fsprogs)" >&2; exit 1; }
|
||||
unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
|
||||
|
||||
echo "==> rootfs : $ROOTFS ($(du -sh "$ROOTFS" | cut -f1))"
|
||||
echo "==> imagen : $IMG ($SIZE, ext4, label hammer-root)"
|
||||
STAGE="work/.disk-stage"
|
||||
rm -rf "$STAGE"
|
||||
mkdir -p "$STAGE"
|
||||
ROOT_TREE="$STAGE/root"
|
||||
ROOT_IMG="$STAGE/root.img"
|
||||
STORE_IMG="$STAGE/store.img"
|
||||
STATE_IMG="$STAGE/state.img"
|
||||
|
||||
# --- árbol del rootfs SIN store/estado, con el wrapper /sbin/init (hardlinks, no copia datos) ---
|
||||
echo "==> staging root (hardlinks) sin /store ni /var/lib/hammer + wrapper /sbin/init"
|
||||
cp -al "$ROOTFS" "$ROOT_TREE"
|
||||
rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
|
||||
mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer" # mountpoints vacíos
|
||||
rm -f "$ROOT_TREE/sbin/init" # era symlink → /usr/bin/arje-zero
|
||||
cat > "$ROOT_TREE/sbin/init" <<'INIT'
|
||||
#!/bin/sh
|
||||
# Wrapper PID1 (Etapa B3): monta los filesystems dedicados antes del init real. El kernel arranca con
|
||||
# root=/dev/vda1; /store (CAS inmutable) y /var/lib/hammer (estado mutable) son particiones propias que
|
||||
# el kernel NO monta solo. DEVTMPFS_MOUNT=y deja /dev poblado, así que /dev/vda{2,3} ya existen aquí.
|
||||
/bin/busybox mount -t ext4 /dev/vda2 /store || echo "init: no pude montar /store (/dev/vda2)"
|
||||
/bin/busybox mount -t ext4 /dev/vda3 /var/lib/hammer || echo "init: no pude montar /var/lib/hammer (/dev/vda3)"
|
||||
exec /usr/bin/arje-zero
|
||||
INIT
|
||||
chmod +x "$ROOT_TREE/sbin/init"
|
||||
|
||||
# --- tabla de particiones GPT (sectores de 512 B; 1 MiB = 2048 sectores, alineación por defecto) ---
|
||||
SECT_MIB=2048
|
||||
ROOT_SECT=$(( ROOT_SIZE * SECT_MIB ))
|
||||
STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
|
||||
STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
|
||||
START1=2048
|
||||
START2=$(( START1 + ROOT_SECT ))
|
||||
START3=$(( START2 + STORE_SECT ))
|
||||
TOTAL_SECT=$(( START3 + STATE_SECT + 2048 )) # + 1 MiB de slack para la GPT de respaldo
|
||||
|
||||
echo "==> imagen : $IMG (GPT, root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
|
||||
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)"
|
||||
truncate -s $(( TOTAL_SECT * 512 )) "$IMG"
|
||||
sfdisk --quiet "$IMG" >/dev/null <<EOF
|
||||
label: gpt
|
||||
start=$START1, size=$ROOT_SECT, type=linux, name="hammer-root"
|
||||
start=$START2, size=$STORE_SECT, type=linux, name="hammer-store"
|
||||
start=$START3, size=$STATE_SECT, type=linux, name="hammer-state"
|
||||
EOF
|
||||
|
||||
# --- poblar cada ext4 (root-owned vía unshare -r) y empalmar en su offset ---
|
||||
echo "==> mke2fs + empalme de las 3 particiones (unshare -r, sin sudo)"
|
||||
unshare -r sh -eu <<EOF
|
||||
mke2fs -q -t ext4 -L hammer-root -E root_owner=0:0 -d "$ROOT_TREE" "$ROOT_IMG" ${ROOT_SIZE}M
|
||||
mke2fs -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store" "$STORE_IMG" ${STORE_SIZE}M
|
||||
mke2fs -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer" "$STATE_IMG" ${STATE_SIZE}M
|
||||
# conv=sparse,notrunc: empalma sin destruir la GPT y deja los bloques cero del ext4 como huecos (si no,
|
||||
# dd rellena la imagen entera y la sparse del truncate se pierde → 11G reales en vez de ~2G).
|
||||
dd if="$ROOT_IMG" of="$IMG" bs=512 seek=$START1 conv=sparse,notrunc status=none
|
||||
dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
|
||||
dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
|
||||
EOF
|
||||
|
||||
rm -rf "$STAGE"
|
||||
echo "==> imagen creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
|
||||
|
||||
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)"
|
||||
echo "==> booteando de disco particionado (root=/dev/vda1, /store+/var/lib/hammer montados por /sbin/init)"
|
||||
DISK="$IMG" KERNEL="$KERNEL" KVM="${KVM:-0}" MEM="${MEM:-6144}" NET="${NET:-1}" \
|
||||
python3 scripts/drive-rebuild.py
|
||||
fi
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
#
|
||||
# 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).
|
||||
# DISK imagen de disco PARTICIONADA (GPT) a bootear como ROOT real (Etapa B3). Si está
|
||||
# seteada, se ignora el initramfs: QEMU monta la imagen como disco virtio y el kernel
|
||||
# arranca con `root=/dev/vda1 rw rdinit=/sbin/init` — sin initramfs ni switch_root (/ ya
|
||||
# es un mount ext4 real, pivotable). La partición 1 es /, y el wrapper /sbin/init monta
|
||||
# las particiones dedicadas /store (vda2) y /var/lib/hammer (vda3) antes del init real.
|
||||
# La arma scripts/disk-image.sh. Requiere 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)
|
||||
@@ -62,10 +64,11 @@ 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="…".
|
||||
# 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.
|
||||
# Boot de disco (Etapa B3) vs initramfs. En disco / ya es un mount ext4 real ⇒ rdinit=/sbin/init directo,
|
||||
# sin el wrapper switch_root del path initramfs. La GPT tiene 3 particiones: vda1=/ , vda2=/store ,
|
||||
# vda3=/var/lib/hammer; el kernel monta vda1 y el wrapper /sbin/init monta las otras dos.
|
||||
if DISK:
|
||||
APPEND = os.environ.get("APPEND", "console=ttyS0 root=/dev/vda rw rdinit=/sbin/init")
|
||||
APPEND = os.environ.get("APPEND", "console=ttyS0 root=/dev/vda1 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")
|
||||
|
||||
Reference in New Issue
Block a user