hammer-install crece una cara humana sin tocar la mecánica EFI/BIOS validada: - sin device + TTY ⇒ interactive_setup: detecta discos (/sys/block, marca el montado como posible medio live), el usuario elige + confirma escribiendo el nombre del disco, y recoge hostname / credencial de root (contraseña vía cryptpw o authorized_keys) / zona horaria (best-effort si hay tzdata) / teclado / tamaños de / y /store. - apply_post_install_config escribe hostname+hosts, reemplaza el hash de root en /etc/shadow, instala authorized_keys, symlinkea localtime y deja install.conf; se llama tras la copia en AMBAS ramas (EFI y BIOS). - con device ⇒ autoinstalador de siempre (iso/efi-install-test intactos); la config sale de env (HAMMER_HOSTNAME/ROOT_PW/ROOT_AUTHKEYS/TZ/KEYMAP). - iso-image.sh: ISO instalador sin AUTO_INSTALL ahora recibe con la TUI en la consola (INSTALLER_INTERACTIVE, default on) y ofrece reiniciar/caer al live. Verificado: scripts/install-tui-test.sh (4/4, dry-run con sysfs+rootfs falsos) + iso-install-test.sh VERDE end-to-end en QEMU (autoinstalador con la config nueva bajo busybox real → disco bootea solo y sirve SSH). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
553 lines
25 KiB
Bash
Executable File
553 lines
25 KiB
Bash
Executable File
#!/bin/sh
|
|
# hammer-install — instalador a disco DESDE el medio live (Etapa E5 / refinamiento #1).
|
|
#
|
|
# Se inyecta en el rootfs del producto como /usr/bin/hammer-install (ver scripts/iso-image.sh
|
|
# INSTALLER=1). Corre DENTRO del live, como root real (arje-zero es PID1) ⇒ NO necesita unshare -r
|
|
# ni los rodeos rootless de install-image.sh: particiona, formatea, monta y copia con privilegios
|
|
# reales, usando sólo herramientas que el producto ya trae (busybox fdisk/mke2fs/mount/dd + uutils cp).
|
|
#
|
|
# El sistema que se instala es EL PROPIO LIVE: la raíz en RAM se copia al disco (autoinstalador). El
|
|
# payload de arranque (kernel + GRUB) viaja en /usr/lib/hammer/install/, armado en build-time (el host
|
|
# tiene grub-mkimage; el live no lo necesita).
|
|
#
|
|
# Layout en disco (MBR, no GPT — más simple y busybox-friendly; GRUB va en el hueco post-MBR):
|
|
# /dev/<dev>1 / ext2(montable ext4) + /boot/bzImage + /boot/grub + wrapper /sbin/init
|
|
# /dev/<dev>2 /store ext2
|
|
# /dev/<dev>3 /var/lib/hammer ext2
|
|
#
|
|
# Cadena de arranque del disco resultante: BIOS → MBR (boot.img) → core.img (hueco post-MBR) →
|
|
# (hd0,msdos1)/boot/grub/grub.cfg → linux /boot/bzImage root=/dev/<dev>1 → wrapper /sbin/init.
|
|
#
|
|
# Modo de uso:
|
|
# INTERACTIVO (sin argumentos, con TTY): hammer-install
|
|
# TUI en busybox sh: detecta discos, el usuario elige destino + confirma el borrado, recoge
|
|
# hostname / credencial de root / zona horaria / teclado / tamaños de partición, y recién ahí
|
|
# particiona+instala. Es la cara humana del instalador (el /init del ISO installer lo lanza).
|
|
# DESATENDIDO (con device): hammer-install /dev/vdb [ROOT_MB] [STORE_MB]
|
|
# Autoinstalador (lo usa iso-install-test.sh con AUTO_INSTALL). La config sale de env:
|
|
# HAMMER_HOSTNAME / HAMMER_ROOT_PW / HAMMER_ROOT_AUTHKEYS / HAMMER_TZ / HAMMER_KEYMAP.
|
|
set -eu
|
|
|
|
DEV="${1:-}"
|
|
ROOT_MB="${2:-}"
|
|
STORE_MB="${3:-}" # /var/lib/hammer toma el resto
|
|
PAYLOAD=/usr/lib/hammer/install
|
|
BB="${BB:-/bin/busybox}"
|
|
SYS_BLOCK="${SYS_BLOCK:-/sys/block}" # override para tests
|
|
MOUNTS="${MOUNTS:-/proc/mounts}" # override para tests
|
|
|
|
# bb: usa el busybox del live si existe; si no (p.ej. tests en el host), cae a las herramientas del PATH.
|
|
# Sólo lo usan las funciones NUEVAS (config/TUI). Las ramas EFI/BIOS validadas siguen con "$BB" literal
|
|
# y jamás corren en el host (el dry-run sale antes).
|
|
bb() { if [ -x "$BB" ]; then "$BB" "$@"; else "$@"; fi; }
|
|
|
|
_human() { # bytes → humano
|
|
_b="$1"
|
|
if [ "$_b" -ge 1073741824 ]; then printf '%s.%s GiB' "$((_b/1073741824))" "$(( (_b%1073741824)*10/1073741824 ))"
|
|
elif [ "$_b" -ge 1048576 ]; then printf '%s MiB' "$((_b/1048576))"
|
|
else printf '%s B' "$_b"; fi
|
|
}
|
|
|
|
_is_mounted() { # ¿alguna partición del disco $1 aparece en MOUNTS? (heurística "es el medio live")
|
|
_d="$1"
|
|
while read -r _src _rest; do
|
|
case "$_src" in "/dev/$_d"|"/dev/$_d"[0-9]*|"/dev/$_d"p[0-9]*) return 0 ;; esac
|
|
done < "$MOUNTS" 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
list_disks() { # emite por disco candidato: name<TAB>bytes<TAB>model<TAB>mounted(0|1)
|
|
for _p in "$SYS_BLOCK"/*; do
|
|
[ -d "$_p" ] || continue
|
|
_n=${_p##*/}
|
|
case "$_n" in loop*|ram*|sr*|fd*|dm-*|md*|zram*) continue ;; esac
|
|
[ -r "$_p/size" ] || continue
|
|
_sz=$(cat "$_p/size" 2>/dev/null || echo 0); [ "$_sz" -gt 0 ] || continue
|
|
_model=$(cat "$_p/device/model" 2>/dev/null | tr -d '\n'); _model=$(printf '%s' "$_model" | sed 's/[[:space:]]*$//')
|
|
_m=0; _is_mounted "$_n" && _m=1
|
|
printf '%s\t%s\t%s\t%s\n' "$_n" "$((_sz*512))" "${_model:-disco}" "$_m"
|
|
done
|
|
}
|
|
|
|
ask() { # ask VAR "prompt" "default"
|
|
if [ -n "$3" ]; then printf '%s [%s]: ' "$2" "$3"; else printf '%s: ' "$2"; fi
|
|
IFS= read -r _ans || _ans=""
|
|
[ -n "$_ans" ] || _ans="$3"
|
|
eval "$1=\$_ans"
|
|
}
|
|
|
|
read_secret() { # read_secret VAR "prompt" (sin eco si hay stty)
|
|
printf '%s: ' "$2"
|
|
bb stty -echo 2>/dev/null || true
|
|
IFS= read -r _sec || _sec=""
|
|
bb stty echo 2>/dev/null || true
|
|
echo
|
|
eval "$1=\$_sec"
|
|
}
|
|
|
|
have_cryptpw() { bb 2>&1 | grep -qw cryptpw; }
|
|
|
|
# Reescribe el campo de contraseña de un usuario en $mnt/etc/shadow (o crea el fichero).
|
|
_set_shadow() {
|
|
_u="$1"; _h="$2"; _f="$3/etc/shadow"
|
|
if [ ! -f "$_f" ]; then
|
|
bb mkdir -p "$3/etc"
|
|
printf '%s:%s:19000:0:99999:7:::\n' "$_u" "$_h" > "$_f"; bb chmod 600 "$_f"; return
|
|
fi
|
|
_t="$_f.hammer-tmp"
|
|
bb awk -F: -v u="$_u" -v h="$_h" 'BEGIN{OFS=":"}
|
|
{ if ($1==u){ $2=h; seen=1 } print }
|
|
END{ if(!seen) print u,h,"19000","0","99999","7","","","" }' "$_f" > "$_t" && bb mv "$_t" "$_f"
|
|
bb chmod 600 "$_f"
|
|
}
|
|
|
|
# Aplica la config recogida al rootfs YA copiado y montado en $1 (llamada por ambas ramas tras la copia).
|
|
apply_post_install_config() {
|
|
_mnt="$1"
|
|
bb mkdir -p "$_mnt/etc/hammer"
|
|
if [ -n "${CFG_HOSTNAME:-}" ]; then
|
|
printf '%s\n' "$CFG_HOSTNAME" > "$_mnt/etc/hostname"
|
|
if ! grep -q "[[:space:]]$CFG_HOSTNAME\$" "$_mnt/etc/hosts" 2>/dev/null; then
|
|
printf '127.0.0.1\tlocalhost %s\n::1\t\tlocalhost %s\n' "$CFG_HOSTNAME" "$CFG_HOSTNAME" >> "$_mnt/etc/hosts"
|
|
fi
|
|
fi
|
|
if [ -n "${CFG_ROOT_HASH:-}" ]; then _set_shadow root "$CFG_ROOT_HASH" "$_mnt"; fi
|
|
if [ -n "${CFG_ROOT_AUTHKEYS:-}" ]; then
|
|
bb mkdir -p "$_mnt/root/.ssh"; bb chmod 700 "$_mnt/root/.ssh"
|
|
printf '%s\n' "$CFG_ROOT_AUTHKEYS" >> "$_mnt/root/.ssh/authorized_keys"
|
|
bb chmod 600 "$_mnt/root/.ssh/authorized_keys"
|
|
fi
|
|
if [ -n "${CFG_TZ:-}" ] && [ -e "$_mnt/usr/share/zoneinfo/$CFG_TZ" ]; then
|
|
bb ln -sf "/usr/share/zoneinfo/$CFG_TZ" "$_mnt/etc/localtime"
|
|
printf '%s\n' "$CFG_TZ" > "$_mnt/etc/timezone"
|
|
fi
|
|
_ts=$(bb date -u +%FT%TZ 2>/dev/null || echo unknown)
|
|
{
|
|
echo "# escrito por hammer-install en $_ts"
|
|
echo "HOSTNAME=${CFG_HOSTNAME:-}"
|
|
echo "TIMEZONE=${CFG_TZ:-}"
|
|
echo "KEYMAP=${CFG_KEYMAP:-}" # best-effort: el producto aún no trae kbd/loadkeys
|
|
echo "TARGET=${DEV:-}"
|
|
} > "$_mnt/etc/hammer/install.conf"
|
|
}
|
|
|
|
# ---- recolección de config: interactiva (TUI) o desde env (desatendido) ----
|
|
CFG_HOSTNAME="${HAMMER_HOSTNAME:-hammer}"
|
|
CFG_ROOT_AUTHKEYS="${HAMMER_ROOT_AUTHKEYS:-}"
|
|
CFG_ROOT_HASH="${HAMMER_ROOT_HASH:-}"
|
|
CFG_TZ="${HAMMER_TZ:-}"
|
|
CFG_KEYMAP="${HAMMER_KEYMAP:-us}"
|
|
|
|
interactive_setup() {
|
|
echo
|
|
echo " # instalador de hammer"
|
|
echo " ─────────────────────────"
|
|
echo
|
|
_disks=$(list_disks)
|
|
[ -n "$_disks" ] || { echo "No detecté discos instalables (¿el medio live es el único dispositivo?)."; exit 1; }
|
|
echo "Discos detectados:"
|
|
OLDIFS=$IFS; IFS='
|
|
'; set --
|
|
for _l in $_disks; do set -- "$@" "$_l"; done
|
|
IFS=$OLDIFS
|
|
_i=1
|
|
for _l in "$@"; do
|
|
_n=$(printf '%s' "$_l" | cut -f1); _by=$(printf '%s' "$_l" | cut -f2)
|
|
_mo=$(printf '%s' "$_l" | cut -f3); _mm=$(printf '%s' "$_l" | cut -f4)
|
|
_w=""; [ "$_mm" = 1 ] && _w=" ⚠ montado (¿medio live?)"
|
|
printf ' %s) /dev/%-8s %10s %s%s\n' "$_i" "$_n" "$(_human "$_by")" "$_mo" "$_w"
|
|
_i=$((_i+1))
|
|
done
|
|
_total=$#
|
|
while :; do
|
|
ask _sel "Disco destino (1-$_total)" ""
|
|
case "$_sel" in ''|*[!0-9]*) echo " número inválido"; continue ;; esac
|
|
[ "$_sel" -ge 1 ] && [ "$_sel" -le "$_total" ] && break
|
|
echo " fuera de rango"
|
|
done
|
|
eval "_line=\${$_sel}"
|
|
_name=$(printf '%s' "$_line" | cut -f1); _bytes=$(printf '%s' "$_line" | cut -f2)
|
|
DEV="/dev/$_name"
|
|
_disk_mb=$((_bytes/1048576))
|
|
|
|
# --- tamaños de partición ---
|
|
_esp=64; _slack=16; _avail=$((_disk_mb - _esp - _slack))
|
|
[ "$_avail" -ge 600 ] || { echo "Disco demasiado chico ($(_human "$_bytes")); se necesitan ~700 MiB."; exit 1; }
|
|
_defr=4096; [ "$_defr" -gt $((_avail*6/10)) ] && _defr=$((_avail*6/10))
|
|
_defs=1024; [ "$_defs" -gt $((_avail/5)) ] && _defs=$((_avail/5))
|
|
while :; do
|
|
ask ROOT_MB "Tamaño de / en MB" "$_defr"
|
|
ask STORE_MB "Tamaño de /store en MB" "$_defs"
|
|
case "$ROOT_MB$STORE_MB" in *[!0-9]*) echo " usá sólo números"; continue ;; esac
|
|
if [ $((ROOT_MB + STORE_MB + _esp + _slack)) -le "$_disk_mb" ]; then break; fi
|
|
echo " no entra en el disco ($_disk_mb MiB) — / + /store + ESP deben sumar menos"
|
|
done
|
|
|
|
# --- identidad ---
|
|
ask CFG_HOSTNAME "Hostname" "${CFG_HOSTNAME:-hammer}"
|
|
echo "Credencial de root:"
|
|
echo " 1) contraseña"
|
|
echo " 2) clave SSH (authorized_keys)"
|
|
echo " 3) omitir"
|
|
ask _cred "Opción" "1"
|
|
case "$_cred" in
|
|
1) if have_cryptpw; then
|
|
while :; do
|
|
read_secret _pw1 "Contraseña de root"
|
|
read_secret _pw2 "Repetir"
|
|
[ "$_pw1" = "$_pw2" ] && [ -n "$_pw1" ] && break
|
|
echo " no coinciden (o vacía) — reintentá"
|
|
done
|
|
CFG_ROOT_HASH=$(printf '%s' "$_pw1" | bb cryptpw -m sha512 2>/dev/null || true)
|
|
[ -n "$CFG_ROOT_HASH" ] || echo " ⚠ no pude generar el hash; root queda como está"
|
|
else
|
|
echo " (cryptpw no disponible en este busybox — usá la opción de clave SSH)"
|
|
ask CFG_ROOT_AUTHKEYS "Pegá una línea authorized_keys" ""
|
|
fi ;;
|
|
2) ask CFG_ROOT_AUTHKEYS "Pegá una línea authorized_keys (o ruta a un fichero .pub)" ""
|
|
if [ -n "$CFG_ROOT_AUTHKEYS" ] && [ -f "$CFG_ROOT_AUTHKEYS" ]; then
|
|
CFG_ROOT_AUTHKEYS=$(cat "$CFG_ROOT_AUTHKEYS")
|
|
fi ;;
|
|
*) echo " (root sin credencial nueva)" ;;
|
|
esac
|
|
|
|
# --- locale (best-effort) ---
|
|
_zi="${ZONEINFO:-/usr/share/zoneinfo}"
|
|
if [ -d "$_zi" ]; then
|
|
ask CFG_TZ "Zona horaria (ej. America/La_Paz; vacío = UTC)" "${CFG_TZ:-}"
|
|
if [ -n "$CFG_TZ" ] && [ ! -e "$_zi/$CFG_TZ" ]; then
|
|
echo " ⚠ zona desconocida, se ignora"; CFG_TZ=""
|
|
fi
|
|
else
|
|
echo "(tzdata no instalado — la zona horaria se omite)"; CFG_TZ=""
|
|
fi
|
|
ask CFG_KEYMAP "Layout de teclado" "${CFG_KEYMAP:-us}"
|
|
|
|
# --- resumen + confirmación fuerte ---
|
|
_cd="sin cambios"; [ -n "$CFG_ROOT_HASH" ] && _cd="contraseña"; [ -n "$CFG_ROOT_AUTHKEYS" ] && _cd="clave SSH"
|
|
echo
|
|
echo "Resumen de instalación:"
|
|
echo " destino : $DEV ($(_human "$_bytes"))"
|
|
echo " / = ${ROOT_MB} MiB /store = ${STORE_MB} MiB /var/lib/hammer = resto"
|
|
echo " hostname: $CFG_HOSTNAME"
|
|
echo " root : $_cd"
|
|
echo " zona : ${CFG_TZ:-UTC} teclado: $CFG_KEYMAP"
|
|
echo
|
|
echo " ⚠ ESTO BORRA TODO EN $DEV."
|
|
ask _ok "Escribí el nombre del disco ($DEV) para confirmar, o Enter para cancelar" ""
|
|
[ "$_ok" = "$DEV" ] || { echo "Cancelado — no se tocó ningún disco."; exit 1; }
|
|
}
|
|
|
|
# --- resolución del modo: interactivo (sin device + TTY) vs desatendido (device dado) ---
|
|
if [ -z "$DEV" ]; then
|
|
if [ -t 0 ] || [ "${HAMMER_INSTALL_INTERACTIVE:-}" = 1 ]; then
|
|
interactive_setup
|
|
else
|
|
echo "uso: hammer-install <device> [ROOT_MB] [STORE_MB] (o sin args con un TTY para el modo interactivo)" >&2
|
|
exit 2
|
|
fi
|
|
fi
|
|
ROOT_MB="${ROOT_MB:-1500}"
|
|
STORE_MB="${STORE_MB:-400}"
|
|
# En modo desatendido con contraseña por env, derivar el hash acá (interactivo ya lo hizo).
|
|
if [ -z "$CFG_ROOT_HASH" ] && [ -n "${HAMMER_ROOT_PW:-}" ] && have_cryptpw; then
|
|
CFG_ROOT_HASH=$(printf '%s' "$HAMMER_ROOT_PW" | bb cryptpw -m sha512 2>/dev/null || true)
|
|
fi
|
|
|
|
# --- gancho de prueba: valida TUI + config sin tocar disco (HAMMER_INSTALL_DRYRUN=1) ---
|
|
if [ "${HAMMER_INSTALL_DRYRUN:-}" = 1 ]; then
|
|
echo "== DRYRUN: plan de instalación =="
|
|
echo " DEV=$DEV ROOT_MB=$ROOT_MB STORE_MB=$STORE_MB"
|
|
echo " HOSTNAME=$CFG_HOSTNAME TZ=${CFG_TZ:-UTC} KEYMAP=$CFG_KEYMAP"
|
|
echo " ROOT_HASH=$([ -n "$CFG_ROOT_HASH" ] && echo set || echo -) AUTHKEYS=$([ -n "$CFG_ROOT_AUTHKEYS" ] && echo set || echo -)"
|
|
if [ -n "${HAMMER_INSTALL_TESTROOT:-}" ]; then
|
|
apply_post_install_config "$HAMMER_INSTALL_TESTROOT"
|
|
echo " config aplicada a $HAMMER_INSTALL_TESTROOT"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
[ -b "$DEV" ] || { echo "hammer-install: $DEV no es un block device" >&2; exit 1; }
|
|
[ -d "$PAYLOAD" ] || { echo "hammer-install: falta el payload $PAYLOAD (¿ISO sin INSTALLER=1?)" >&2; exit 1; }
|
|
|
|
# --- Detección de firmware: UEFI ⇒ rama EFI-stub SOBERANA (ADR 0010 paso 5); BIOS ⇒ MBR+GRUB (abajo) ---
|
|
# La rama EFI usa una ESP tipo 0xEF en tabla MBR (UEFI arranca \EFI\BOOT\BOOTX64.EFI de una ESP MBR igual
|
|
# que de GPT) ⇒ alcanza con busybox fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools. El bzImage metal ES el
|
|
# binario EFI (EFI-stub, cmdline horneada carga el initrd); un initramfs mínimo pivota por LABEL. Mismos
|
|
# artefactos que scripts/install-image-efi.sh (validado en OVMF), armados desde dentro del live.
|
|
if [ -d /sys/firmware/efi ]; then
|
|
[ -r "$PAYLOAD/kernel/bzImage-efi" ] || { echo "hammer-install: live en UEFI pero falta $PAYLOAD/kernel/bzImage-efi (ISO sin kernel metal)" >&2; exit 1; }
|
|
ESP_MB="${ESP_MB:-64}"
|
|
echo "==> hammer-install (UEFI): EFI-stub soberano en $DEV (ESP ${ESP_MB}M, root ${ROOT_MB}M, store ${STORE_MB}M, estado=resto)"
|
|
echo " ¡esto BORRA $DEV!"
|
|
|
|
# MBR, 4 primarias: p1=ESP(0xEF) p2=root p3=store p4=estado. Sectores explícitos (como la rama BIOS).
|
|
SPM=2048
|
|
P1S=2048; P1E=$(( P1S + ESP_MB * SPM - 1 ))
|
|
P2S=$(( P1E + 1 )); P2E=$(( P2S + ROOT_MB * SPM - 1 ))
|
|
P3S=$(( P2E + 1 )); P3E=$(( P3S + STORE_MB* SPM - 1 ))
|
|
P4S=$(( P3E + 1 )) # estado = resto del disco
|
|
echo "==> particionando $DEV (MBR: ESP ${P1S}-${P1E} tipo ef, root ${P2S}-${P2E}, store ${P3S}-${P3E}, estado ${P4S}-fin)"
|
|
"$BB" fdisk "$DEV" >/dev/null 2>&1 <<FDISK || true
|
|
o
|
|
n
|
|
p
|
|
1
|
|
$P1S
|
|
$P1E
|
|
n
|
|
p
|
|
2
|
|
$P2S
|
|
$P2E
|
|
n
|
|
p
|
|
3
|
|
$P3S
|
|
$P3E
|
|
n
|
|
p
|
|
4
|
|
$P4S
|
|
|
|
t
|
|
1
|
|
ef
|
|
a
|
|
1
|
|
w
|
|
FDISK
|
|
"$BB" partprobe "$DEV" 2>/dev/null || true
|
|
"$BB" sync; sleep 1
|
|
P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"; P4="${DEV}4"
|
|
for p in "$P1" "$P2" "$P3" "$P4"; do
|
|
[ -b "$p" ] || { echo "hammer-install: no apareció la partición $p tras particionar" >&2; exit 1; }
|
|
done
|
|
|
|
echo "==> formateando (ESP FAT32 + 3 ext2 etiquetadas para el pivote por LABEL)"
|
|
"$BB" mkfs.vfat -F 32 -n HAMMER "$P1" >/dev/null 2>&1
|
|
"$BB" mke2fs -F -L hammer-root "$P2" >/dev/null 2>&1
|
|
"$BB" mke2fs -F -L hammer-store "$P3" >/dev/null 2>&1
|
|
"$BB" mke2fs -F -L hammer-state "$P4" >/dev/null 2>&1
|
|
|
|
# initramfs de pivote (busybox estático del live + /init: findfs LABEL=hammer-root → switch_root).
|
|
echo "==> armando el initramfs de pivote (busybox + /init switch_root a hammer-root)"
|
|
IRD_TREE=/run/hammer-efi-ird
|
|
"$BB" rm -rf "$IRD_TREE"
|
|
"$BB" mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" "$IRD_TREE/newroot"
|
|
cp "$BB" "$IRD_TREE/bin/busybox"; "$BB" chmod 0755 "$IRD_TREE/bin/busybox"
|
|
for a in sh mount umount findfs switch_root mkdir echo sleep; do "$BB" ln -sf busybox "$IRD_TREE/bin/$a"; done
|
|
cat > "$IRD_TREE/init" <<'INIT'
|
|
#!/bin/busybox sh
|
|
export PATH=/bin
|
|
mount -t proc proc /proc 2>/dev/null || true
|
|
mount -t sysfs sys /sys 2>/dev/null || true
|
|
mount -t devtmpfs dev /dev 2>/dev/null || true
|
|
log() { echo "$*"; echo "$*" > /dev/ttyS0 2>/dev/null || true; }
|
|
log "HAMMER-EFI: initramfs de pivote — resolviendo hammer-root por LABEL"
|
|
ROOTDEV=""
|
|
for _try in 1 2 3 4 5; do
|
|
ROOTDEV="$(findfs LABEL=hammer-root 2>/dev/null || true)"
|
|
[ -n "$ROOTDEV" ] && break
|
|
log "HAMMER-EFI: hammer-root aún no visible, reintento $_try…"; sleep 1
|
|
done
|
|
[ -n "$ROOTDEV" ] || { log "HAMMER-EFI-FALLO: no encuentro LABEL=hammer-root"; exec sh; }
|
|
log "HAMMER-EFI: hammer-root = $ROOTDEV → montando en /newroot"
|
|
mount -t ext4 "$ROOTDEV" /newroot || { log "HAMMER-EFI-FALLO: no pude montar $ROOTDEV"; exec sh; }
|
|
STOREDEV="$(findfs LABEL=hammer-store 2>/dev/null || true)"
|
|
STATEDEV="$(findfs LABEL=hammer-state 2>/dev/null || true)"
|
|
[ -n "$STOREDEV" ] && mount -t ext4 "$STOREDEV" /newroot/store 2>/dev/null || true
|
|
[ -n "$STATEDEV" ] && mount -t ext4 "$STATEDEV" /newroot/var/lib/hammer 2>/dev/null || true
|
|
log "HAMMER-EFI-DISK-PIVOT-OK: switch_root → /sbin/init"
|
|
exec switch_root /newroot /sbin/init
|
|
INIT
|
|
"$BB" chmod +x "$IRD_TREE/init"
|
|
( cd "$IRD_TREE" && "$BB" find . | "$BB" cpio -o -H newc 2>/dev/null | "$BB" gzip -1 ) > /run/hammer-efi-ird.cpio.gz
|
|
|
|
# Poblar la ESP (FAT32, montable por el live): BOOTX64.EFI = bzImage metal + el initramfs de pivote.
|
|
echo "==> poblando la ESP (\\EFI\\BOOT\\BOOTX64.EFI = kernel metal + \\initramfs.cpio.gz)"
|
|
ESPMNT=/run/hammer-esp
|
|
"$BB" mkdir -p "$ESPMNT"
|
|
"$BB" mount -t vfat "$P1" "$ESPMNT"
|
|
"$BB" mkdir -p "$ESPMNT/EFI/BOOT"
|
|
cp "$PAYLOAD/kernel/bzImage-efi" "$ESPMNT/EFI/BOOT/BOOTX64.EFI"
|
|
cp /run/hammer-efi-ird.cpio.gz "$ESPMNT/initramfs.cpio.gz"
|
|
"$BB" sync; "$BB" umount "$ESPMNT"
|
|
|
|
# Poblar la root (copia del live) — sin wrapper que monte particiones (el pivote ya montó store/estado).
|
|
echo "==> copiando la raíz del live → $P2 (excluye virtuales/efímeros, /store, /var/lib/hammer, payload)"
|
|
MNT=/run/hammer-install
|
|
"$BB" mkdir -p "$MNT"
|
|
"$BB" mount -t ext4 "$P2" "$MNT"
|
|
for entry in /* /.[!.]*; do
|
|
[ -e "$entry" ] || continue
|
|
name="${entry##*/}"
|
|
case "$name" in
|
|
proc|sys|dev|run|tmp|store|init) continue ;;
|
|
*) cp -a "$entry" "$MNT/" 2>/dev/null || true ;;
|
|
esac
|
|
done
|
|
"$BB" rm -rf "$MNT/var/lib/hammer" 2>/dev/null || true
|
|
"$BB" rm -rf "$MNT/usr/lib/hammer/install" 2>/dev/null || true
|
|
"$BB" mkdir -p "$MNT/store" "$MNT/var/lib/hammer" "$MNT/proc" "$MNT/sys" "$MNT/dev" "$MNT/run" "$MNT/tmp"
|
|
# /sbin/init: wrapper que auto-sana un upgrade interrumpido y arranca arje-zero (el pivote ya montó
|
|
# store/estado por LABEL, así que acá NO hace falta montar nada — sólo hammer-recover + exec).
|
|
if [ -x "$PAYLOAD/hammer-recover" ]; then
|
|
"$BB" mkdir -p "$MNT/usr/sbin"
|
|
cp "$PAYLOAD/hammer-recover" "$MNT/usr/sbin/hammer-recover"; "$BB" chmod 0755 "$MNT/usr/sbin/hammer-recover"
|
|
fi
|
|
# CLI hammer (static musl) para el menú de arranque por grafo (ADR 0010): lo copia al disco desde el
|
|
# payload; el wrapper lo invoca como `hammer boot menu`.
|
|
if [ -x "$PAYLOAD/hammer" ]; then
|
|
cp "$PAYLOAD/hammer" "$MNT/usr/bin/hammer"; "$BB" chmod 0755 "$MNT/usr/bin/hammer"
|
|
fi
|
|
"$BB" rm -f "$MNT/sbin/init"
|
|
cat > "$MNT/sbin/init" <<'INIT'
|
|
#!/bin/sh
|
|
# Wrapper PID1 (rama UEFI): el initramfs de pivote ya montó / /store /var/lib/hammer por LABEL antes del
|
|
# switch_root; sólo auto-sanamos un upgrade interrumpido y arrancamos el init real. El marcador a
|
|
# /dev/ttyS0 sobrevive `quiet` (que suprime el printk kernel-side) ⇒ prueba serie de que llegó a PID1.
|
|
# busybox switch_root NO mueve /dev ⇒ montamos devtmpfs antes (si no, /dev/ttyS0 aún no existe).
|
|
/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true
|
|
echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > /dev/ttyS0 2>/dev/null || true
|
|
[ -x /usr/sbin/hammer-recover ] && /usr/sbin/hammer-recover || true
|
|
# Menú de arranque por grafo (ADR 0010): emite /run/hammer/boot-graph.json y, si hay compositor (mirada),
|
|
# lo pinta sobre KMS. Graceful (sin compositor sigue) y al serial (no pinta tty0 ⇒ cero-parpadeo).
|
|
[ -x /usr/bin/hammer ] && /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true
|
|
exec /usr/bin/arje-zero
|
|
INIT
|
|
"$BB" chmod +x "$MNT/sbin/init"
|
|
# config del usuario (hostname / root / zona / teclado) recogida al inicio
|
|
apply_post_install_config "$MNT"
|
|
"$BB" sync
|
|
"$BB" umount "$MNT"
|
|
|
|
echo "✓ hammer-install (UEFI): $DEV instalado con EFI-stub SOBERANO (sin GRUB ni systemd-boot). Reiniciá sin el medio live."
|
|
exit 0
|
|
fi
|
|
|
|
# ===================== rama BIOS (MBR + GRUB) — el firmware NO es UEFI ==============================
|
|
[ -r "$PAYLOAD/grub/boot.img" ] && [ -r "$PAYLOAD/grub/core.img" ] || { echo "hammer-install: payload GRUB incompleto" >&2; exit 1; }
|
|
[ -r "$PAYLOAD/kernel/bzImage" ] || { echo "hammer-install: falta el kernel en el payload" >&2; exit 1; }
|
|
|
|
echo "==> hammer-install: instalando el sistema live en $DEV (root ${ROOT_MB}M, store ${STORE_MB}M, estado=resto)"
|
|
echo " ¡esto BORRA $DEV!"
|
|
|
|
# --- particionar MBR con busybox fdisk (3 primarias; p1 bootable) ---
|
|
# Pasamos sectores de inicio/fin EXPLÍCITOS para las tres: busybox fdisk, al pedir el inicio por
|
|
# defecto, ofrece el sector LIBRE MÁS BAJO (= el hueco 63..2047 que dejamos antes de p1) ⇒ con <enter>
|
|
# colocaría p2 en ese hueco. Por eso computamos los sectores y los tecleamos.
|
|
# p1 ARRANCA EN 2048 (1 MiB) A PROPÓSITO: deja un hueco post-MBR de 2047 sectores para el core.img de
|
|
# GRUB (~281 sectores). El default CHS de busybox (sector 63 ⇒ hueco de 62) lo DESBORDA, pisando el
|
|
# filesystem de p1 ⇒ GRUB cae a `grub>`.
|
|
SPM=2048
|
|
P1S=2048; P1E=$(( P1S + ROOT_MB * SPM - 1 ))
|
|
P2S=$(( P1E + 1 )); P2E=$(( P2S + STORE_MB * SPM - 1 ))
|
|
P3S=$(( P2E + 1 )) # p3 toma hasta el final del disco (último sector = default)
|
|
echo "==> particionando $DEV (MBR: p1 ${P1S}-${P1E}, p2 ${P2S}-${P2E}, p3 ${P3S}-fin)"
|
|
"$BB" fdisk "$DEV" >/dev/null 2>&1 <<FDISK || true
|
|
o
|
|
n
|
|
p
|
|
1
|
|
$P1S
|
|
$P1E
|
|
n
|
|
p
|
|
2
|
|
$P2S
|
|
$P2E
|
|
n
|
|
p
|
|
3
|
|
$P3S
|
|
|
|
a
|
|
1
|
|
w
|
|
FDISK
|
|
"$BB" partprobe "$DEV" 2>/dev/null || true
|
|
"$BB" sync; sleep 1
|
|
|
|
P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"
|
|
for p in "$P1" "$P2" "$P3"; do
|
|
[ -b "$p" ] || { echo "hammer-install: no apareció la partición $p tras particionar" >&2; exit 1; }
|
|
done
|
|
|
|
# --- formatear (busybox mke2fs = ext2; el driver ext4 del kernel lo monta) ---
|
|
echo "==> formateando particiones (ext2, montables como ext4)"
|
|
"$BB" mke2fs -F -L hammer-root "$P1" >/dev/null 2>&1
|
|
"$BB" mke2fs -F -L hammer-store "$P2" >/dev/null 2>&1
|
|
"$BB" mke2fs -F -L hammer-state "$P3" >/dev/null 2>&1
|
|
|
|
# --- copiar la raíz del live a la partición root, excluyendo virtuales + el payload ---
|
|
MNT=/run/hammer-install
|
|
"$BB" mkdir -p "$MNT"
|
|
"$BB" mount -t ext4 "$P1" "$MNT"
|
|
echo "==> copiando la raíz del live → $P1 (excluye virtuales/efímeros, /store, /var/lib/hammer y el payload)"
|
|
# Enumeramos TODO el top-level de / y copiamos salvo lo virtual/efímero — así no se nos escapa ningún
|
|
# dir del producto (p.ej. /ente con la seed.card, /linuxrc). uutils cp -a preserva modos/symlinks.
|
|
for entry in /* /.[!.]*; do
|
|
[ -e "$entry" ] || continue
|
|
name="${entry##*/}"
|
|
case "$name" in
|
|
proc|sys|dev|run|tmp|store|init) continue ;; # virtuales/efímeros + el /init marcador del live
|
|
*) cp -a "$entry" "$MNT/" 2>/dev/null || true ;;
|
|
esac
|
|
done
|
|
# /var sin /var/lib/hammer (estado) — se recrea vacío como mountpoint.
|
|
"$BB" rm -rf "$MNT/var/lib/hammer" 2>/dev/null || true
|
|
# Quitar el payload del disco instalado (sólo sirve en el live) y recrear mountpoints.
|
|
"$BB" rm -rf "$MNT/usr/lib/hammer/install" 2>/dev/null || true
|
|
"$BB" mkdir -p "$MNT/store" "$MNT/var/lib/hammer" "$MNT/proc" "$MNT/sys" "$MNT/dev" "$MNT/run" "$MNT/tmp" "$MNT/boot/grub/i386-pc"
|
|
|
|
# --- /boot: kernel + módulos GRUB + grub.cfg, y wrapper /sbin/init (monta las particiones dedicadas) ---
|
|
echo "==> poblando /boot (kernel + GRUB) y wrapper /sbin/init"
|
|
cp "$PAYLOAD/kernel/bzImage" "$MNT/boot/bzImage"; "$BB" chmod 0644 "$MNT/boot/bzImage"
|
|
cp "$PAYLOAD"/grub/i386-pc/*.mod "$MNT/boot/grub/i386-pc/" 2>/dev/null || true
|
|
cp "$PAYLOAD"/grub/i386-pc/*.lst "$MNT/boot/grub/i386-pc/" 2>/dev/null || true
|
|
ROOTDEV="${P1##*/}" # p.ej. vda1 cuando el disco instalado sea el único
|
|
cat > "$MNT/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_msdos
|
|
insmod ext2
|
|
menuentry "hammer" {
|
|
linux /boot/bzImage console=ttyS0 root=/dev/vda1 rw rdinit=/sbin/init
|
|
}
|
|
CFG
|
|
# hammer-recover: auto-sana un upgrade interrumpido al arranque (E4 #4b). Lo dejamos en /usr/sbin del
|
|
# disco; el wrapper lo corre tras montar /store y /var/lib/hammer (que necesita) y antes de incarnar.
|
|
if [ -x "$PAYLOAD/hammer-recover" ]; then
|
|
"$BB" mkdir -p "$MNT/usr/sbin"
|
|
cp "$PAYLOAD/hammer-recover" "$MNT/usr/sbin/hammer-recover"; "$BB" chmod 0755 "$MNT/usr/sbin/hammer-recover"
|
|
fi
|
|
"$BB" rm -f "$MNT/sbin/init"
|
|
cat > "$MNT/sbin/init" <<'INIT'
|
|
#!/bin/sh
|
|
# Wrapper PID1: el kernel monta la partición root; montamos las dedicadas, auto-sanamos un upgrade
|
|
# interrumpido (si lo hay) y arrancamos el init real.
|
|
/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)"
|
|
[ -x /usr/sbin/hammer-recover ] && /usr/sbin/hammer-recover || true
|
|
exec /usr/bin/arje-zero
|
|
INIT
|
|
"$BB" chmod +x "$MNT/sbin/init"
|
|
# config del usuario (hostname / root / zona / teclado) recogida al inicio
|
|
apply_post_install_config "$MNT"
|
|
"$BB" sync
|
|
"$BB" umount "$MNT"
|
|
|
|
# --- instalar GRUB: boot.img→MBR (440 B, sin pisar la tabla de particiones) + core.img→hueco post-MBR.
|
|
# Con install contiguo MBR los punteros por defecto de GRUB (kernel_sector=1, blocklist=2) ya valen
|
|
# ⇒ NO hace falta el parcheo binario que sí pide el caso GPT de install-image.sh.
|
|
echo "==> instalando GRUB (boot.img→MBR + core.img→hueco post-MBR)"
|
|
"$BB" dd if="$PAYLOAD/grub/boot.img" of="$DEV" bs=1 count=440 conv=notrunc 2>/dev/null
|
|
"$BB" dd if="$PAYLOAD/grub/core.img" of="$DEV" bs=512 seek=1 conv=notrunc 2>/dev/null
|
|
"$BB" sync
|
|
|
|
echo "✓ hammer-install: $DEV instalado. Reiniciá sin el medio live para arrancar del disco."
|