tawasuyu

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

⎇ 4190c7b43e5bd323a9b166340cfa16cd324ca2e4 scripts/disk-image.sh Shell · 120 líneas · 6.9 KB Historia crudo

takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4 476168bb0 · Sergio ·

1#!/bin/sh
2# disk-image.sh — Etapa B3 (mounts dedicados): empaqueta el rootfs como una imagen de disco virtio
3# PARTICIONADA (GPT) con tres filesystems ext4 dedicados, en vez de una única partición que mete todo
4# junto (B2). Es el paso hacia la imagen instalable real: el store y el estado mutable dejan de ser meros
5# directorios del rootfs y pasan a ser particiones propias, como en un sistema instalado de verdad.
6#
7#   /dev/vda1  →  /                  rootfs (runtime + toolchain + /work del rebuild)   [takana-root]
8#   /dev/vda2  →  /store             artefactos CAS BLAKE3 (inmutables)                 [takana-store]
9#   /dev/vda3  →  /var/lib/hammer    estado mutable: journal + overlays (upper/work)    [takana-state]
10#
11# El kernel (recipes/linux.toml) trae VIRTIO_BLK+EXT4+EFI_PARTITION(=y por defconfig) ⇒ lee la GPT y
12# monta /dev/vda1 como root sin initramfs. Las otras dos particiones las monta un wrapper `/sbin/init`
13# (montar /store + /var/lib/hammer, luego `exec /usr/bin/arje-zero`) — el init real (arje-zero) sigue
14# en su sitio; el wrapper es el puente mientras arje no lea fstab/cards de montaje por sí mismo.
15#
16# Construcción sin root ni loopback:
17#   - `cp -al` arma un árbol del rootfs por HARDLINKS (sin copiar datos), le vacía store/ y
18#     var/lib/hammer/ (deja mountpoints vacíos) y le instala el wrapper /sbin/init. NUNCA muta $ROOTFS.
19#   - `mke2fs -d <dir>` puebla cada ext4 desde un directorio sin montar nada (e2fsprogs >= 1.43).
20#   - `unshare -r` mapea nuestro uid→0 ⇒ los ficheros uid-host se sellan como uid 0 en la imagen (igual
21#     que el `--owner=root:root` del cpio; el copy-up de overlay del rebuild falla EACCES si van uid 1000).
22#   - `sfdisk` escribe la GPT sobre el fichero-imagen (no necesita root) y `dd conv=notrunc` empalma cada
23#     ext4 en el offset de su partición. Tamaños exactos: la partición mide lo mismo que el ext4 (M==MiB).
24#
25# Uso:
26#   ./scripts/disk-image.sh                                   # crea work/hammer-disk.img
27#   BOOT=1 KERNEL=<bzImage> KVM=1 ./scripts/disk-image.sh     # además bootea y corre rebuild-stage1
28#
29# Variables:
30#   ROOTFS      dir rootfs a empaquetar     (default work/builder-rootfs, lo arma selfhost-verify)
31#   IMG         imagen de salida            (default work/hammer-disk.img)
32#   ROOT_SIZE   MiB de la partición /        (default 6144; incluye /work del rebuild)
33#   STORE_SIZE  MiB de la partición /store   (default 2048; artefactos CAS ~0.4G + reseal del rebuild)
34#   STATE_SIZE  MiB de /var/lib/hammer       (default 2048; journal + overlays)
35#   BOOT        1 ⇒ bootea con drive-rebuild.py tras crear la imagen
36#   KERNEL      bzImage con VIRTIO_BLK+EXT4  (requerido si BOOT=1)
37#   KVM/MEM/NET passthrough a drive-rebuild.py
38set -eu
39ROOT="$(cd "$(dirname "$0")/.." && pwd)"
40cd "$ROOT"
41
42ROOTFS="${ROOTFS:-work/builder-rootfs}"
43IMG="${IMG:-work/hammer-disk.img}"
44ROOT_SIZE="${ROOT_SIZE:-6144}"
45STORE_SIZE="${STORE_SIZE:-2048}"
46STATE_SIZE="${STATE_SIZE:-2048}"
47
48[ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS (corré selfhost-verify para armar el builder)" >&2; exit 1; }
49[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; }
50command -v sfdisk >/dev/null 2>&1 || { echo "falta sfdisk (util-linux) para la tabla GPT" >&2; exit 1; }
51command -v mke2fs >/dev/null 2>&1 || { echo "falta mke2fs (e2fsprogs)" >&2; exit 1; }
52unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
53
54STAGE="work/.disk-stage"
55rm -rf "$STAGE"
56mkdir -p "$STAGE"
57ROOT_TREE="$STAGE/root"
58ROOT_IMG="$STAGE/root.img"
59STORE_IMG="$STAGE/store.img"
60STATE_IMG="$STAGE/state.img"
61
62# --- árbol del rootfs SIN store/estado, con el wrapper /sbin/init (hardlinks, no copia datos) ---
63echo "==> staging root (hardlinks) sin /store ni /var/lib/hammer + wrapper /sbin/init"
64cp -al "$ROOTFS" "$ROOT_TREE"
65rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
66mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"   # mountpoints vacíos
67rm -f "$ROOT_TREE/sbin/init"                               # era symlink → /usr/bin/arje-zero
68cat > "$ROOT_TREE/sbin/init" <<'INIT'
69#!/bin/sh
70# Wrapper PID1 (Etapa B3): monta los filesystems dedicados antes del init real. El kernel arranca con
71# root=/dev/vda1; /store (CAS inmutable) y /var/lib/hammer (estado mutable) son particiones propias que
72# el kernel NO monta solo. DEVTMPFS_MOUNT=y deja /dev poblado, así que /dev/vda{2,3} ya existen aquí.
73/bin/busybox mount -t ext4 /dev/vda2 /store          || echo "init: no pude montar /store (/dev/vda2)"
74/bin/busybox mount -t ext4 /dev/vda3 /var/lib/hammer || echo "init: no pude montar /var/lib/hammer (/dev/vda3)"
75exec /usr/bin/arje-zero
76INIT
77chmod +x "$ROOT_TREE/sbin/init"
78
79# --- tabla de particiones GPT (sectores de 512 B; 1 MiB = 2048 sectores, alineación por defecto) ---
80SECT_MIB=2048
81ROOT_SECT=$(( ROOT_SIZE  * SECT_MIB ))
82STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
83STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
84START1=2048
85START2=$(( START1 + ROOT_SECT ))
86START3=$(( START2 + STORE_SECT ))
87TOTAL_SECT=$(( START3 + STATE_SECT + 2048 ))   # + 1 MiB de slack para la GPT de respaldo
88
89echo "==> imagen : $IMG (GPT, root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
90rm -f "$IMG"
91truncate -s $(( TOTAL_SECT * 512 )) "$IMG"
92sfdisk --quiet "$IMG" >/dev/null <<EOF
93label: gpt
94start=$START1, size=$ROOT_SECT,  type=linux, name="hammer-root"
95start=$START2, size=$STORE_SECT, type=linux, name="hammer-store"
96start=$START3, size=$STATE_SECT, type=linux, name="hammer-state"
97EOF
98
99# --- poblar cada ext4 (root-owned vía unshare -r) y empalmar en su offset ---
100echo "==> mke2fs + empalme de las 3 particiones (unshare -r, sin sudo)"
101unshare -r sh -eu <<EOF
102mke2fs -q -t ext4 -L hammer-root  -E root_owner=0:0 -d "$ROOT_TREE"          "$ROOT_IMG"  ${ROOT_SIZE}M
103mke2fs -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store"       "$STORE_IMG" ${STORE_SIZE}M
104mke2fs -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer" "$STATE_IMG" ${STATE_SIZE}M
105# conv=sparse,notrunc: empalma sin destruir la GPT y deja los bloques cero del ext4 como huecos (si no,
106# dd rellena la imagen entera y la sparse del truncate se pierde → 11G reales en vez de ~2G).
107dd if="$ROOT_IMG"  of="$IMG" bs=512 seek=$START1 conv=sparse,notrunc status=none
108dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
109dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
110EOF
111
112rm -rf "$STAGE"
113echo "==> imagen creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
114
115if [ "${BOOT:-0}" = 1 ]; then
116    [ -n "${KERNEL:-}" ] || { echo "BOOT=1 requiere KERNEL=<bzImage con VIRTIO_BLK+EXT4>" >&2; exit 1; }
117    echo "==> booteando de disco particionado (root=/dev/vda1, /store+/var/lib/hammer montados por /sbin/init)"
118    DISK="$IMG" KERNEL="$KERNEL" KVM="${KVM:-0}" MEM="${MEM:-6144}" NET="${NET:-1}" \
119        python3 scripts/drive-rebuild.py
120fi