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.
187 lines
10 KiB
Bash
187 lines
10 KiB
Bash
#!/bin/sh
|
|
# install-image.sh — Etapa E1 (SDD 13): produce una imagen de disco AUTO-BOOTEABLE. A diferencia de
|
|
# scripts/disk-image.sh (B3, que se arranca con `qemu -kernel` para verificar el auto-alojamiento),
|
|
# ésta lleva GRUB en el propio disco ⇒ bootea con `qemu-system-x86_64 -drive file=img` SIN `-kernel` ni
|
|
# firmware extra (SeaBIOS de QEMU → MBR → core.img → kernel), como en hardware real.
|
|
#
|
|
# Layout (GPT, BIOS):
|
|
# /dev/vda1 BIOS boot (ef02, ~2 MiB, sin fs) core.img de GRUB
|
|
# /dev/vda2 / ext4 [takana-root] + /boot/bzImage + /boot/grub
|
|
# /dev/vda3 /store ext4 [takana-store]
|
|
# /dev/vda4 /var/lib/hammer ext4 [takana-state]
|
|
#
|
|
# Instalación de GRUB en USERSPACE (sin root ni loop): grub-mkimage arma core.img; grub-bios-setup
|
|
# escribe boot.img→MBR + core.img→partición BIOS sobre el fichero imagen (I/O de bloques). Los módulos
|
|
# de GRUB + el kernel van en /boot de la root, que mke2fs -d puebla desde un directorio.
|
|
#
|
|
# Uso:
|
|
# ./scripts/install-image.sh # crea work/hammer-install.img
|
|
# BOOT=1 KVM=1 ./scripts/install-image.sh # además lo arranca (auto-boot, sin -kernel)
|
|
#
|
|
# Target IN-PLACE (Etapa E2): si IMG es un block device (o PREALLOC=1 sobre un fichero pre-dimensionado),
|
|
# se instala EN SITIO — no se trunca ni se borra el nodo, sólo se valida capacidad. Esa es la base de
|
|
# `takana install /dev/sdX` (ver scripts/hammer-install.sh): misma lógica, apuntando a un disco real.
|
|
#
|
|
# Variables:
|
|
# ROOTFS dir rootfs a empaquetar (default work/builder-rootfs)
|
|
# PREALLOC 1 ⇒ trata IMG como target pre-dimensionado in-place (no trunca; prueba el camino device)
|
|
# KERNEL bzImage a embeber en /boot (default el del store: store/*-linux/boot/bzImage)
|
|
# IMG imagen de salida (default work/hammer-install.img)
|
|
# ROOT_SIZE MiB de / (default 4096)
|
|
# STORE_SIZE MiB de /store (default 2048)
|
|
# STATE_SIZE MiB de /var/lib/hammer (default 2048)
|
|
# CMDLINE línea de comando del kernel (default "console=ttyS0 root=/dev/vda2 rw rdinit=/sbin/init")
|
|
# BOOT 1 ⇒ arranca la imagen con QEMU tras crearla (auto-boot, sin -kernel)
|
|
# KVM/MEM passthrough al arranque de prueba
|
|
set -eu
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
ROOTFS="${ROOTFS:-work/builder-rootfs}"
|
|
IMG="${IMG:-work/hammer-install.img}"
|
|
ROOT_SIZE="${ROOT_SIZE:-4096}"
|
|
STORE_SIZE="${STORE_SIZE:-2048}"
|
|
STATE_SIZE="${STATE_SIZE:-2048}"
|
|
CMDLINE="${CMDLINE:-console=ttyS0 root=/dev/vda2 rw rdinit=/sbin/init}"
|
|
KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}"
|
|
GRUB_LIB="${GRUB_LIB:-/usr/lib/grub/i386-pc}"
|
|
|
|
[ -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; }
|
|
[ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel a embeber (set KERNEL=<bzImage>)" >&2; exit 1; }
|
|
[ -d "$GRUB_LIB" ] && [ -r "$GRUB_LIB/boot.img" ] || { echo "faltan los módulos GRUB i386-pc en $GRUB_LIB" >&2; exit 1; }
|
|
for t in sfdisk mke2fs grub-mkimage grub-bios-setup; do
|
|
command -v "$t" >/dev/null 2>&1 || { echo "falta $t" >&2; exit 1; }
|
|
done
|
|
unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
|
|
|
|
STAGE="work/.install-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"
|
|
GRUB_STAGE="$STAGE/grub"
|
|
|
|
# --- árbol root: hardlinks del rootfs SIN store/estado + /boot (kernel + GRUB) + wrapper /sbin/init ---
|
|
echo "==> staging root: rootfs (hardlinks) + /boot/bzImage + /boot/grub + 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" "$ROOT_TREE/boot/grub/i386-pc"
|
|
cp "$KERNEL" "$ROOT_TREE/boot/bzImage"; chmod 0644 "$ROOT_TREE/boot/bzImage"
|
|
# Módulos GRUB que core.img cargará en tiempo de boot desde /boot/grub/i386-pc.
|
|
cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$ROOT_TREE/boot/grub/i386-pc/" 2>/dev/null || true
|
|
# grub.cfg con consola serie (para -nographic) y el menuentry del kernel embebido.
|
|
cat > "$ROOT_TREE/boot/grub/grub.cfg" <<CFG
|
|
set timeout=2
|
|
set default=0
|
|
serial --unit=0 --speed=115200
|
|
terminal_input serial console
|
|
terminal_output serial console
|
|
insmod part_gpt
|
|
insmod ext2
|
|
menuentry "hammer" {
|
|
linux /boot/bzImage $CMDLINE
|
|
}
|
|
CFG
|
|
# Wrapper /sbin/init: monta las particiones dedicadas y hace exec del init real (igual que B3).
|
|
rm -f "$ROOT_TREE/sbin/init"
|
|
cat > "$ROOT_TREE/sbin/init" <<'INIT'
|
|
#!/bin/sh
|
|
# Wrapper PID1 (Etapa E/B3): el kernel monta vda2 como /; montamos las particiones dedicadas.
|
|
/bin/busybox mount -t ext4 /dev/vda3 /store || echo "init: no pude montar /store (/dev/vda3)"
|
|
/bin/busybox mount -t ext4 /dev/vda4 /var/lib/hammer || echo "init: no pude montar /var/lib/hammer (/dev/vda4)"
|
|
exec /usr/bin/arje-zero
|
|
INIT
|
|
chmod +x "$ROOT_TREE/sbin/init"
|
|
|
|
# --- GPT: vda1=BIOS boot (ef02), vda2=/, vda3=/store, vda4=/var/lib/hammer ---
|
|
SECT_MIB=2048
|
|
BIOS_SECT=4096 # 2 MiB para core.img
|
|
ROOT_SECT=$(( ROOT_SIZE * SECT_MIB ))
|
|
STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
|
|
STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
|
|
START1=2048
|
|
START2=$(( START1 + BIOS_SECT ))
|
|
START3=$(( START2 + ROOT_SECT ))
|
|
START4=$(( START3 + STORE_SECT ))
|
|
TOTAL_SECT=$(( START4 + STATE_SECT + 2048 ))
|
|
BIOS_GUID="21686148-6449-6E6F-744E-656564454649" # GPT "BIOS boot partition"
|
|
|
|
# Target = block device (instalación in-place, Etapa E2 `takana install /dev/sdX`) o fichero imagen.
|
|
# Un device tiene tamaño fijo: NO se trunca ni se borra el nodo; se valida capacidad. PREALLOC=1 fuerza
|
|
# este modo sobre un fichero pre-dimensionado (para probar el camino de device sin hardware real).
|
|
TOTAL_BYTES=$(( TOTAL_SECT * 512 ))
|
|
if [ -b "$IMG" ] || [ "${PREALLOC:-0}" = 1 ]; then
|
|
cur=$(blockdev --getsize64 "$IMG" 2>/dev/null || stat -c%s "$IMG" 2>/dev/null || echo 0)
|
|
[ "$cur" -ge "$TOTAL_BYTES" ] || { echo "target $IMG es ${cur}B < ${TOTAL_BYTES}B requeridos" >&2; exit 1; }
|
|
echo "==> target IN-PLACE: $IMG ($(( cur/1024/1024 ))M disponibles; root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
|
|
else
|
|
echo "==> imagen : $IMG (GPT BIOS-boot, root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
|
|
rm -f "$IMG"
|
|
truncate -s "$TOTAL_BYTES" "$IMG"
|
|
fi
|
|
sfdisk --quiet "$IMG" >/dev/null <<EOF
|
|
label: gpt
|
|
start=$START1, size=$BIOS_SECT, type=$BIOS_GUID, name="bios-boot"
|
|
start=$START2, size=$ROOT_SECT, type=linux, name="hammer-root"
|
|
start=$START3, size=$STORE_SECT, type=linux, name="hammer-store"
|
|
start=$START4, size=$STATE_SECT, type=linux, name="hammer-state"
|
|
EOF
|
|
|
|
# --- poblar las 3 particiones ext4 (root-owned vía unshare -r) y empalmar en su offset ---
|
|
echo "==> mke2fs + empalme de las particiones ext4 (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
|
|
dd if="$ROOT_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
|
|
dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
|
|
dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
|
|
EOF
|
|
|
|
# --- instalar GRUB en userspace SIN root/loop: grub-mkimage arma core.img, y un patch binario
|
|
# determinista coloca boot.img→MBR + core.img→BIOS boot partition (vda1) ajustando los punteros.
|
|
# (`grub-bios-setup` se descarta: sondea el disco FÍSICO del host —/dev/nvme…, 660 root:disk— para
|
|
# "adivinar el root device" del dir -d, y falla sin root; el patch manual evita ese sondeo.)
|
|
echo "==> instalar GRUB (grub-mkimage + patch binario boot/core, sin root/loop)"
|
|
mkdir -p "$GRUB_STAGE"
|
|
# core.img: prefix a (hd0,gpt2)/boot/grub (root = 2ª partición GPT); embebemos lo justo para leer
|
|
# GPT+ext2, cargar grub.cfg (normal/configfile) y el kernel (linux).
|
|
grub-mkimage -O i386-pc -p '(hd0,gpt2)/boot/grub' -o "$GRUB_STAGE/core.img" \
|
|
biosdisk part_gpt ext2 normal configfile linux echo ls boot
|
|
# Punteros (ABI estable de GRUB i386-pc):
|
|
# boot.img off 0x5c kernel_sector (u64 LE): LBA del 1er sector de core.img → START1
|
|
# core.img off 0x1F4 blocklist.start (u64 LE): LBA del RESTO de core.img → START1+1
|
|
# Verificamos los valores por defecto (1 y 2: core.img asumido contiguo tras el MBR) antes de tocar.
|
|
START1="$START1" GRUB_LIB="$GRUB_LIB" GRUB_STAGE="$GRUB_STAGE" IMG="$IMG" python3 - <<'PY'
|
|
import os, struct
|
|
s1 = int(os.environ["START1"])
|
|
img = os.environ["IMG"]
|
|
boot = open(os.path.join(os.environ["GRUB_LIB"], "boot.img"), "rb").read()
|
|
core = bytearray(open(os.path.join(os.environ["GRUB_STAGE"], "core.img"), "rb").read())
|
|
boot = bytearray(boot)
|
|
assert struct.unpack_from("<Q", boot, 0x5c)[0] == 1, "boot.img kernel_sector != 1 (ABI GRUB cambió)"
|
|
assert struct.unpack_from("<Q", core, 0x1f4)[0] == 2, "core.img blocklist.start != 2 (ABI GRUB cambió)"
|
|
struct.pack_into("<Q", boot, 0x5c, s1) # core.img empieza en START1
|
|
struct.pack_into("<Q", core, 0x1f4, s1 + 1) # el resto de core.img, justo después
|
|
with open(img, "r+b") as f:
|
|
f.seek(s1 * 512); f.write(core) # core.img → BIOS boot partition (vda1)
|
|
f.seek(0); f.write(boot[:440]) # boot.img → MBR, sin pisar la GPT protective (446+)
|
|
print(f" boot.img→MBR (kernel_sector={s1}), core.img→sector {s1} ({len(core)} B), blocklist.start={s1+1}")
|
|
PY
|
|
|
|
rm -rf "$STAGE"
|
|
echo "==> imagen instalable creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
|
|
echo " auto-boot: qemu-system-x86_64 -drive file=$IMG,format=raw -nographic (sin -kernel)"
|
|
|
|
if [ "${BOOT:-0}" = 1 ]; then
|
|
echo "==> arranque de prueba (auto-boot, sin -kernel)"
|
|
accel=""
|
|
[ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host"
|
|
# shellcheck disable=SC2086
|
|
exec qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \
|
|
-drive file="$IMG",format=raw,if=virtio
|
|
fi
|