250 líneas de comentario en 151 scripts. Control verificado: el diff no toca NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa. El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces: las tres eran el MOTD que el script escribe DENTRO de la imagen construida — texto del producto, no comentario del script. Se cambiaron aparte y a propósito, que es rebranding, no limpieza. Y el hallazgo caro: casaba contra , que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate. La etapa 4 lo movió a y el script quedó casando NADA. No fallaba: imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja. Además 14 rutas de módulo en docs, que el barrido anterior no tocó porque no es frontera de palabra.
121 lines
6.9 KiB
Bash
Executable File
121 lines
6.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# 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.
|
|
#
|
|
# /dev/vda1 → / rootfs (runtime + toolchain + /work del rebuild) [takana-root]
|
|
# /dev/vda2 → /store artefactos CAS BLAKE3 (inmutables) [takana-store]
|
|
# /dev/vda3 → /var/lib/hammer estado mutable: journal + overlays (upper/work) [takana-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
|
|
# 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)
|
|
# 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}"
|
|
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; }
|
|
[ -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; }
|
|
|
|
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"
|
|
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 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
|