#!/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)   [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
#   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
