gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ dba9ec47c5b4fa5918abdb35ac9466f1560ccd4f scripts/install-image.sh Shell · 186 líneas · 10.2 KB Historia crudo

scripts/hammer-install: instalador a disco in-place (Etapa E2) 2d18ed6dc · sergio ·

1#!/bin/sh
2# install-image.sh — Etapa E1 (SDD 13): produce una imagen de disco AUTO-BOOTEABLE. A diferencia de
3# scripts/disk-image.sh (B3, que se arranca con `qemu -kernel` para verificar el auto-alojamiento),
4# ésta lleva GRUB en el propio disco ⇒ bootea con `qemu-system-x86_64 -drive file=img` SIN `-kernel` ni
5# firmware extra (SeaBIOS de QEMU → MBR → core.img → kernel), como en hardware real.
6#
7# Layout (GPT, BIOS):
8#   /dev/vda1  BIOS boot (ef02, ~2 MiB, sin fs)   core.img de GRUB
9#   /dev/vda2  /                ext4  [hammer-root]   + /boot/bzImage + /boot/grub
10#   /dev/vda3  /store           ext4  [hammer-store]
11#   /dev/vda4  /var/lib/hammer  ext4  [hammer-state]
12#
13# Instalación de GRUB en USERSPACE (sin root ni loop): grub-mkimage arma core.img; grub-bios-setup
14# escribe boot.img→MBR + core.img→partición BIOS sobre el fichero imagen (I/O de bloques). Los módulos
15# de GRUB + el kernel van en /boot de la root, que mke2fs -d puebla desde un directorio.
16#
17# Uso:
18#   ./scripts/install-image.sh                                  # crea work/hammer-install.img
19#   BOOT=1 KVM=1 ./scripts/install-image.sh                     # además lo arranca (auto-boot, sin -kernel)
20#
21# Target IN-PLACE (Etapa E2): si IMG es un block device (o PREALLOC=1 sobre un fichero pre-dimensionado),
22# se instala EN SITIO — no se trunca ni se borra el nodo, sólo se valida capacidad. Esa es la base de
23# `hammer install /dev/sdX` (ver scripts/hammer-install.sh): misma lógica, apuntando a un disco real.
24#
25# Variables:
26#   ROOTFS      dir rootfs a empaquetar       (default work/builder-rootfs)
27#   PREALLOC    1 ⇒ trata IMG como target pre-dimensionado in-place (no trunca; prueba el camino device)
28#   KERNEL      bzImage a embeber en /boot     (default el del store: store/*-linux/boot/bzImage)
29#   IMG         imagen de salida               (default work/hammer-install.img)
30#   ROOT_SIZE   MiB de /         (default 4096)
31#   STORE_SIZE  MiB de /store    (default 2048)
32#   STATE_SIZE  MiB de /var/lib/hammer (default 2048)
33#   CMDLINE     línea de comando del kernel    (default "console=ttyS0 root=/dev/vda2 rw rdinit=/sbin/init")
34#   BOOT        1 ⇒ arranca la imagen con QEMU tras crearla (auto-boot, sin -kernel)
35#   KVM/MEM     passthrough al arranque de prueba
36set -eu
37ROOT="$(cd "$(dirname "$0")/.." && pwd)"
38cd "$ROOT"
39
40ROOTFS="${ROOTFS:-work/builder-rootfs}"
41IMG="${IMG:-work/hammer-install.img}"
42ROOT_SIZE="${ROOT_SIZE:-4096}"
43STORE_SIZE="${STORE_SIZE:-2048}"
44STATE_SIZE="${STATE_SIZE:-2048}"
45CMDLINE="${CMDLINE:-console=ttyS0 root=/dev/vda2 rw rdinit=/sbin/init}"
46KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}"
47GRUB_LIB="${GRUB_LIB:-/usr/lib/grub/i386-pc}"
48
49[ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS (corré selfhost-verify para armar el builder)" >&2; exit 1; }
50[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; }
51[ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel a embeber (set KERNEL=<bzImage>)" >&2; exit 1; }
52[ -d "$GRUB_LIB" ] && [ -r "$GRUB_LIB/boot.img" ] || { echo "faltan los módulos GRUB i386-pc en $GRUB_LIB" >&2; exit 1; }
53for t in sfdisk mke2fs grub-mkimage grub-bios-setup; do
54    command -v "$t" >/dev/null 2>&1 || { echo "falta $t" >&2; exit 1; }
55done
56unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
57
58STAGE="work/.install-stage"
59rm -rf "$STAGE"
60mkdir -p "$STAGE"
61ROOT_TREE="$STAGE/root"
62ROOT_IMG="$STAGE/root.img"
63STORE_IMG="$STAGE/store.img"
64STATE_IMG="$STAGE/state.img"
65GRUB_STAGE="$STAGE/grub"
66
67# --- árbol root: hardlinks del rootfs SIN store/estado + /boot (kernel + GRUB) + wrapper /sbin/init ---
68echo "==> staging root: rootfs (hardlinks) + /boot/bzImage + /boot/grub + wrapper /sbin/init"
69cp -al "$ROOTFS" "$ROOT_TREE"
70rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
71mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer" "$ROOT_TREE/boot/grub/i386-pc"
72cp "$KERNEL" "$ROOT_TREE/boot/bzImage"; chmod 0644 "$ROOT_TREE/boot/bzImage"
73# Módulos GRUB que core.img cargará en tiempo de boot desde /boot/grub/i386-pc.
74cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$ROOT_TREE/boot/grub/i386-pc/" 2>/dev/null || true
75# grub.cfg con consola serie (para -nographic) y el menuentry del kernel embebido.
76cat > "$ROOT_TREE/boot/grub/grub.cfg" <<CFG
77set timeout=2
78set default=0
79serial --unit=0 --speed=115200
80terminal_input serial console
81terminal_output serial console
82insmod part_gpt
83insmod ext2
84menuentry "hammer" {
85    linux /boot/bzImage $CMDLINE
86}
87CFG
88# Wrapper /sbin/init: monta las particiones dedicadas y hace exec del init real (igual que B3).
89rm -f "$ROOT_TREE/sbin/init"
90cat > "$ROOT_TREE/sbin/init" <<'INIT'
91#!/bin/sh
92# Wrapper PID1 (Etapa E/B3): el kernel monta vda2 como /; montamos las particiones dedicadas.
93/bin/busybox mount -t ext4 /dev/vda3 /store          || echo "init: no pude montar /store (/dev/vda3)"
94/bin/busybox mount -t ext4 /dev/vda4 /var/lib/hammer || echo "init: no pude montar /var/lib/hammer (/dev/vda4)"
95exec /usr/bin/arje-zero
96INIT
97chmod +x "$ROOT_TREE/sbin/init"
98
99# --- GPT: vda1=BIOS boot (ef02), vda2=/, vda3=/store, vda4=/var/lib/hammer ---
100SECT_MIB=2048
101BIOS_SECT=4096                                   # 2 MiB para core.img
102ROOT_SECT=$(( ROOT_SIZE  * SECT_MIB ))
103STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
104STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
105START1=2048
106START2=$(( START1 + BIOS_SECT ))
107START3=$(( START2 + ROOT_SECT ))
108START4=$(( START3 + STORE_SECT ))
109TOTAL_SECT=$(( START4 + STATE_SECT + 2048 ))
110BIOS_GUID="21686148-6449-6E6F-744E-656564454649" # GPT "BIOS boot partition"
111
112# Target = block device (instalación in-place, Etapa E2 `hammer install /dev/sdX`) o fichero imagen.
113# Un device tiene tamaño fijo: NO se trunca ni se borra el nodo; se valida capacidad. PREALLOC=1 fuerza
114# este modo sobre un fichero pre-dimensionado (para probar el camino de device sin hardware real).
115TOTAL_BYTES=$(( TOTAL_SECT * 512 ))
116if [ -b "$IMG" ] || [ "${PREALLOC:-0}" = 1 ]; then
117    cur=$(blockdev --getsize64 "$IMG" 2>/dev/null || stat -c%s "$IMG" 2>/dev/null || echo 0)
118    [ "$cur" -ge "$TOTAL_BYTES" ] || { echo "target $IMG es ${cur}B < ${TOTAL_BYTES}B requeridos" >&2; exit 1; }
119    echo "==> target IN-PLACE: $IMG ($(( cur/1024/1024 ))M disponibles; root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
120else
121    echo "==> imagen : $IMG (GPT BIOS-boot, root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
122    rm -f "$IMG"
123    truncate -s "$TOTAL_BYTES" "$IMG"
124fi
125sfdisk --quiet "$IMG" >/dev/null <<EOF
126label: gpt
127start=$START1, size=$BIOS_SECT,  type=$BIOS_GUID, name="bios-boot"
128start=$START2, size=$ROOT_SECT,  type=linux,      name="hammer-root"
129start=$START3, size=$STORE_SECT, type=linux,      name="hammer-store"
130start=$START4, size=$STATE_SECT, type=linux,      name="hammer-state"
131EOF
132
133# --- poblar las 3 particiones ext4 (root-owned vía unshare -r) y empalmar en su offset ---
134echo "==> mke2fs + empalme de las particiones ext4 (unshare -r, sin sudo)"
135unshare -r sh -eu <<EOF
136mke2fs -q -t ext4 -L hammer-root  -E root_owner=0:0 -d "$ROOT_TREE"               "$ROOT_IMG"  ${ROOT_SIZE}M
137mke2fs -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store"            "$STORE_IMG" ${STORE_SIZE}M
138mke2fs -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer"   "$STATE_IMG" ${STATE_SIZE}M
139dd if="$ROOT_IMG"  of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
140dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
141dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
142EOF
143
144# --- instalar GRUB en userspace SIN root/loop: grub-mkimage arma core.img, y un patch binario
145#     determinista coloca boot.img→MBR + core.img→BIOS boot partition (vda1) ajustando los punteros.
146#     (`grub-bios-setup` se descarta: sondea el disco FÍSICO del host —/dev/nvme…, 660 root:disk— para
147#      "adivinar el root device" del dir -d, y falla sin root; el patch manual evita ese sondeo.)
148echo "==> instalar GRUB (grub-mkimage + patch binario boot/core, sin root/loop)"
149mkdir -p "$GRUB_STAGE"
150# core.img: prefix a (hd0,gpt2)/boot/grub (root = 2ª partición GPT); embebemos lo justo para leer
151# GPT+ext2, cargar grub.cfg (normal/configfile) y el kernel (linux).
152grub-mkimage -O i386-pc -p '(hd0,gpt2)/boot/grub' -o "$GRUB_STAGE/core.img" \
153    biosdisk part_gpt ext2 normal configfile linux echo ls boot
154# Punteros (ABI estable de GRUB i386-pc):
155#   boot.img  off 0x5c  kernel_sector (u64 LE): LBA del 1er sector de core.img      → START1
156#   core.img  off 0x1F4 blocklist.start (u64 LE): LBA del RESTO de core.img         → START1+1
157# Verificamos los valores por defecto (1 y 2: core.img asumido contiguo tras el MBR) antes de tocar.
158START1="$START1" GRUB_LIB="$GRUB_LIB" GRUB_STAGE="$GRUB_STAGE" IMG="$IMG" python3 - <<'PY'
159import os, struct
160s1   = int(os.environ["START1"])
161img  = os.environ["IMG"]
162boot = open(os.path.join(os.environ["GRUB_LIB"], "boot.img"), "rb").read()
163core = bytearray(open(os.path.join(os.environ["GRUB_STAGE"], "core.img"), "rb").read())
164boot = bytearray(boot)
165assert struct.unpack_from("<Q", boot, 0x5c)[0] == 1,  "boot.img kernel_sector != 1 (ABI GRUB cambió)"
166assert struct.unpack_from("<Q", core, 0x1f4)[0] == 2, "core.img blocklist.start != 2 (ABI GRUB cambió)"
167struct.pack_into("<Q", boot, 0x5c,  s1)       # core.img empieza en START1
168struct.pack_into("<Q", core, 0x1f4, s1 + 1)   # el resto de core.img, justo después
169with open(img, "r+b") as f:
170    f.seek(s1 * 512); f.write(core)           # core.img → BIOS boot partition (vda1)
171    f.seek(0);        f.write(boot[:440])      # boot.img → MBR, sin pisar la GPT protective (446+)
172print(f"   boot.img→MBR (kernel_sector={s1}), core.img→sector {s1} ({len(core)} B), blocklist.start={s1+1}")
173PY
174
175rm -rf "$STAGE"
176echo "==> imagen instalable creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
177echo "    auto-boot: qemu-system-x86_64 -drive file=$IMG,format=raw -nographic   (sin -kernel)"
178
179if [ "${BOOT:-0}" = 1 ]; then
180    echo "==> arranque de prueba (auto-boot, sin -kernel)"
181    accel=""
182    [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host"
183    # shellcheck disable=SC2086
184    exec qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \
185        -drive file="$IMG",format=raw,if=virtio
186fi