secubox-deb/image/build-image.sh
CyberMind-FR 39667e41bc fix: ESPRESSObin v7 eMMC boot - DTB, root device, mv88e6xxx blacklist
- Use armada-3720-espressobin-v7-emmc.dtb to enable both SDHCI controllers
- Fix root device: /dev/mmcblk1p2 (eMMC is mmc1 with dual controller DTB)
- Add modprobe.blacklist=mv88e6xxx,dsa_core to prevent DSA probe loop
- Add sdhci.debug_quirks2=0x40 to fix xenon-sdhci DDR timing issues
- Add initramfs hooks for sdhci-xenon and mv88e6xxx blacklist
- Add systemd service to load mv88e6xxx after rootfs mount
- Fix fstab tmpfs mount (remove non-existent user reference)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-06 18:07:30 +02:00

800 lines
29 KiB
Bash
Executable File

#!/usr/bin/env bash
# ══════════════════════════════════════════════════════════════════
# SecuBox-DEB — build-image.sh
# debootstrap → image .img flashable sur eMMC/SD (arm64) ou .vdi (x64)
# Usage : sudo bash image/build-image.sh --board mochabin [OPTIONS]
# ══════════════════════════════════════════════════════════════════
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
# ── Defaults ──────────────────────────────────────────────────────
BOARD="mochabin"
SUITE="bookworm"
IMG_SIZE="4G" # Taille de l'image totale
ROOT_SIZE="2.5G" # Taille partition rootfs
DATA_SIZE="1G" # Taille partition data
OUT_DIR="${REPO_DIR}/output"
KEEP_ROOTFS=0
APT_MIRROR="http://deb.debian.org/debian"
APT_SECUBOX="https://apt.secubox.in"
CONVERT_VDI=0 # Convertir en VDI pour VirtualBox
USE_LOCAL_CACHE=0 # Utiliser cache APT local
LOCAL_CACHE_PORT="3142"
LOCAL_REPO_PORT="8080"
SLIPSTREAM_DEBS=0 # Intégrer les .deb locaux dans l'image
RED='\033[0;31m'; CYAN='\033[0;36m'; GOLD='\033[0;33m'
GREEN='\033[0;32m'; NC='\033[0m'; BOLD='\033[1m'
log() { echo -e "${CYAN}[build]${NC} $*"; }
ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
err() { echo -e "${RED}[FAIL ]${NC} $*" >&2; exit 1; }
warn() { echo -e "${GOLD}[ WARN]${NC} $*"; }
# ── Parse args ────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: sudo bash build-image.sh [OPTIONS]
--board BOARD mochabin|espressobin-v7|espressobin-ultra|vm-x64 (défaut: mochabin)
--suite SUITE Debian suite (défaut: bookworm)
--out DIR Répertoire de sortie (défaut: ./output)
--size SIZE Taille totale image (défaut: 4G)
--vdi Convertir en VDI (VirtualBox) en plus du raw
--local-cache Utiliser cache APT local (apt-cacher-ng + repo local)
--slipstream Intégrer les .deb de output/debs/ dans l'image
--keep-rootfs Garder le rootfs décompressé après build
--help Cette aide
Boards supportés :
mochabin Marvell Armada 7040 (arm64) — SecuBox Pro
espressobin-v7 Marvell Armada 3720 (arm64) — SecuBox Lite
espressobin-ultra Marvell Armada 3720 (arm64) — SecuBox Lite+
vm-x64 VirtualBox/QEMU (amd64) — Test/Dev
vm-arm64 QEMU virt (arm64) — Test/Dev ARM64
Cache local :
Exécuter d'abord : sudo bash scripts/setup-local-cache.sh
Puis builder avec : sudo bash image/build-image.sh --board vm-x64 --local-cache
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
--board) BOARD="$2"; shift 2 ;;
--suite) SUITE="$2"; shift 2 ;;
--out) OUT_DIR="$2"; shift 2 ;;
--size) IMG_SIZE="$2"; shift 2 ;;
--vdi) CONVERT_VDI=1; shift ;;
--local-cache) USE_LOCAL_CACHE=1; shift ;;
--slipstream) SLIPSTREAM_DEBS=1; shift ;;
--keep-rootfs) KEEP_ROOTFS=1; shift ;;
--help|-h) usage ;;
*) err "Argument inconnu: $1" ;;
esac
done
# ── Vérifications ─────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && err "Ce script doit être exécuté en root (sudo)"
BOARD_DIR="${REPO_DIR}/board/${BOARD}"
[[ -d "$BOARD_DIR" ]] || err "Board inconnu: ${BOARD} (pas de dossier board/${BOARD}/)"
# Charger config du board
source "${BOARD_DIR}/config.mk" 2>/dev/null || true
# Déterminer l'architecture
DEBIAN_ARCH="${DEBIAN_ARCH:-arm64}"
IS_X64=0
IS_ARM64=0
NEED_QEMU=0
case "$DEBIAN_ARCH" in
amd64)
IS_X64=1
HOST_ARCH=$(uname -m)
[[ "$HOST_ARCH" != "x86_64" ]] && NEED_QEMU=1
;;
arm64)
IS_ARM64=1
HOST_ARCH=$(uname -m)
[[ "$HOST_ARCH" != "aarch64" ]] && NEED_QEMU=1
;;
*)
err "Architecture non supportée: $DEBIAN_ARCH"
;;
esac
# Vérifier outils requis
REQUIRED_TOOLS="debootstrap parted mkfs.fat mkfs.ext4 rsync"
# mkimage (u-boot-tools) est optionnel mais recommandé pour ARM
if [[ $IS_ARM64 -eq 1 ]] && [[ $NEED_QEMU -eq 1 ]]; then
REQUIRED_TOOLS="$REQUIRED_TOOLS qemu-aarch64-static"
fi
if [[ $IS_X64 -eq 1 ]] && [[ $CONVERT_VDI -eq 1 ]]; then
REQUIRED_TOOLS="$REQUIRED_TOOLS qemu-img"
fi
for cmd in $REQUIRED_TOOLS; do
command -v "$cmd" >/dev/null || err "Manquant: $cmd"
done
# ── Local cache detection ─────────────────────────────────────
if [[ $USE_LOCAL_CACHE -eq 1 ]]; then
LOCAL_CACHE_HOST="127.0.0.1"
# Vérifier apt-cacher-ng
if curl -sf "http://${LOCAL_CACHE_HOST}:${LOCAL_CACHE_PORT}" >/dev/null 2>&1; then
APT_MIRROR="http://${LOCAL_CACHE_HOST}:${LOCAL_CACHE_PORT}/deb.debian.org/debian"
log "Cache APT local détecté : ${APT_MIRROR}"
else
warn "apt-cacher-ng non accessible — utilisation du miroir distant"
fi
# Vérifier repo local SecuBox
if curl -sf "http://${LOCAL_CACHE_HOST}:${LOCAL_REPO_PORT}/dists/${SUITE}/Release" >/dev/null 2>&1; then
APT_SECUBOX="http://${LOCAL_CACHE_HOST}:${LOCAL_REPO_PORT}"
log "Repo SecuBox local détecté : ${APT_SECUBOX}"
else
warn "Repo local SecuBox non accessible — fallback apt.secubox.in"
fi
fi
# ── Setup ─────────────────────────────────────────────────────────
mkdir -p "$OUT_DIR"
WORK_DIR=$(mktemp -d /tmp/secubox-build-XXXXXX)
ROOTFS="${WORK_DIR}/rootfs"
IMG_FILE="${OUT_DIR}/secubox-${BOARD}-${SUITE}.img"
log "══════════════════════════════════════════════════════════"
log "Board : ${BOLD}${BOARD}${NC}"
log "Architecture: ${BOLD}${DEBIAN_ARCH}${NC}"
log "Suite : ${SUITE}"
log "Image : ${IMG_FILE}"
log "Work dir : ${WORK_DIR}"
log "APT Mirror : ${APT_MIRROR}"
log "SecuBox Repo: ${APT_SECUBOX}"
[[ $USE_LOCAL_CACHE -eq 1 ]] && log "Local Cache : ${GREEN}enabled${NC}"
[[ $SLIPSTREAM_DEBS -eq 1 ]] && log "Slipstream : ${GREEN}enabled${NC}"
log "══════════════════════════════════════════════════════════"
cleanup() {
log "Nettoyage..."
umount -lf "${ROOTFS}/proc" 2>/dev/null || true
umount -lf "${ROOTFS}/sys" 2>/dev/null || true
umount -lf "${ROOTFS}/dev" 2>/dev/null || true
umount -lf "${ROOTFS}" 2>/dev/null || true
[[ -n "${LOOP:-}" ]] && losetup -d "${LOOP}" 2>/dev/null || true
[[ $KEEP_ROOTFS -eq 0 ]] && rm -rf "${WORK_DIR}" 2>/dev/null || true
}
trap cleanup EXIT
# ── Étape 1 : Debootstrap ─────────────────────────────────────────
log "1/7 Debootstrap ${SUITE} ${DEBIAN_ARCH}..."
mkdir -p "${ROOTFS}"
DEBOOTSTRAP_OPTS="--arch=${DEBIAN_ARCH}"
INCLUDE_PKGS="systemd,systemd-sysv,dbus,netplan.io,nftables,openssh-server"
INCLUDE_PKGS+=",python3,python3-pip,nginx,curl,wget,ca-certificates,gnupg,apt-transport-https"
INCLUDE_PKGS+=",iproute2,iputils-ping,ethtool,net-tools,wireguard-tools"
INCLUDE_PKGS+=",sudo,less,vim-tiny,logrotate,cron,rsync,jq,dnsmasq"
if [[ $IS_X64 -eq 1 ]]; then
# x64 : ajouter GRUB EFI + linux-image
INCLUDE_PKGS+=",grub-efi-amd64,linux-image-amd64,efibootmgr"
elif [[ $IS_ARM64 -eq 1 ]]; then
# arm64 : ajouter linux-image pour tous les boards ARM
INCLUDE_PKGS+=",linux-image-arm64"
if [[ "${BOARD}" == "vm-arm64" ]]; then
# arm64 VM : ajouter aussi GRUB EFI arm64
INCLUDE_PKGS+=",grub-efi-arm64,efibootmgr"
fi
fi
if [[ $NEED_QEMU -eq 1 ]]; then
DEBOOTSTRAP_OPTS+=" --foreign"
fi
debootstrap ${DEBOOTSTRAP_OPTS} \
--include="${INCLUDE_PKGS}" \
"${SUITE}" "${ROOTFS}" "${APT_MIRROR}"
# QEMU pour cross-arch
if [[ $NEED_QEMU -eq 1 ]]; then
if [[ $IS_ARM64 -eq 1 ]]; then
cp /usr/bin/qemu-aarch64-static "${ROOTFS}/usr/bin/"
elif [[ $IS_X64 -eq 1 ]]; then
cp /usr/bin/qemu-x86_64-static "${ROOTFS}/usr/bin/" 2>/dev/null || true
fi
chroot "${ROOTFS}" /debootstrap/debootstrap --second-stage
fi
ok "Debootstrap terminé"
# ── Étape 2 : Configuration base ──────────────────────────────────
log "2/7 Configuration système de base..."
# Monter les filesystems spéciaux
mount -t proc proc "${ROOTFS}/proc"
mount -t sysfs sysfs "${ROOTFS}/sys"
mount --bind /dev "${ROOTFS}/dev"
# Hostname
cat > "${ROOTFS}/etc/hostname" <<< "secubox-${BOARD}"
# /etc/hosts
cat > "${ROOTFS}/etc/hosts" <<EOF
127.0.0.1 localhost
127.0.1.1 secubox-${BOARD} secubox
::1 localhost ip6-localhost ip6-loopback
EOF
# fstab
if [[ $IS_X64 -eq 1 ]]; then
cat > "${ROOTFS}/etc/fstab" <<EOF
# SecuBox fstab — VM x64
LABEL=rootfs / ext4 defaults,noatime,commit=60 0 1
LABEL=data /data ext4 defaults,noatime 0 2
LABEL=ESP /boot/efi vfat umask=0077 0 0
tmpfs /run/secubox tmpfs mode=0755 0 0
EOF
else
cat > "${ROOTFS}/etc/fstab" <<EOF
# SecuBox fstab — ARM
LABEL=rootfs / ext4 defaults,noatime,commit=60 0 1
LABEL=data /data ext4 defaults,noatime 0 2
LABEL=boot /boot vfat defaults,noatime 0 0
tmpfs /run/secubox tmpfs mode=0755 0 0
EOF
fi
# locale
chroot "${ROOTFS}" bash -c "locale-gen en_US.UTF-8 || true"
chroot "${ROOTFS}" bash -c "update-locale LANG=en_US.UTF-8 || true"
# Mot de passe root par défaut (à changer au premier boot)
chroot "${ROOTFS}" bash -c 'echo "root:secubox" | chpasswd'
log "Mot de passe root par défaut: secubox"
# Autoriser SSH root login
sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' "${ROOTFS}/etc/ssh/sshd_config"
sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' "${ROOTFS}/etc/ssh/sshd_config"
log "SSH root login activé"
# timezone
echo "Europe/Paris" > "${ROOTFS}/etc/timezone"
chroot "${ROOTFS}" dpkg-reconfigure -f noninteractive tzdata 2>/dev/null || true
# netplan de base
install -d "${ROOTFS}/etc/netplan"
cp "${BOARD_DIR}/netplan/00-secubox.yaml" "${ROOTFS}/etc/netplan/"
ok "Configuration base terminée"
# ── Étape 3 : APT SecuBox + paquets ──────────────────────────────
log "3/7 Installation paquets SecuBox..."
# Install Python dependencies FIRST (required by SecuBox packages)
log "Installing Python dependencies..."
chroot "${ROOTFS}" apt-get install -y -q python3-pip python3-venv 2>/dev/null || true
chroot "${ROOTFS}" pip3 install --break-system-packages -q \
fastapi uvicorn python-jose httpx jinja2 tomli pyroute2 psutil pydantic 2>&1 | tail -5 || true
ok "Python dependencies installed"
# Ajouter repo SecuBox (si disponible)
SECUBOX_REPO_OK=0
if [[ "$APT_SECUBOX" == "http://127.0.0.1:"* ]]; then
# Repo local sans signature GPG (pour tests)
if curl -sf "${APT_SECUBOX}/dists/${SUITE}/Release" >/dev/null 2>&1; then
cat > "${ROOTFS}/etc/apt/sources.list.d/secubox.list" <<EOF
deb [trusted=yes] ${APT_SECUBOX} ${SUITE} main
EOF
SECUBOX_REPO_OK=1
log "Repo local SecuBox configuré (trusted=yes)"
fi
elif curl -sf "${APT_SECUBOX}/secubox-keyring.gpg" -o "${ROOTFS}/usr/share/keyrings/secubox.gpg" 2>/dev/null; then
cat > "${ROOTFS}/etc/apt/sources.list.d/secubox.list" <<EOF
deb [signed-by=/usr/share/keyrings/secubox.gpg] ${APT_SECUBOX} ${SUITE} main
EOF
SECUBOX_REPO_OK=1
fi
if [[ $SECUBOX_REPO_OK -eq 1 ]]; then
chroot "${ROOTFS}" apt-get update -q
chroot "${ROOTFS}" apt-get install -y -q secubox-full || warn "secubox-full non disponible"
else
warn "APT repo SecuBox non disponible — skip (Phase 4)"
fi
# Slipstream: intégrer les .deb locaux directement
if [[ $SLIPSTREAM_DEBS -eq 1 ]]; then
DEBS_DIR="${REPO_DIR}/output/debs"
if [[ -d "${DEBS_DIR}" ]] && ls "${DEBS_DIR}"/*.deb >/dev/null 2>&1; then
log "Slipstream: installation des paquets locaux..."
install -d "${ROOTFS}/tmp/secubox-debs"
cp "${DEBS_DIR}"/*.deb "${ROOTFS}/tmp/secubox-debs/"
# Installer secubox-core en premier (dépendance)
if [[ -f "${ROOTFS}/tmp/secubox-debs/secubox-core_"*.deb ]]; then
chroot "${ROOTFS}" dpkg -i /tmp/secubox-debs/secubox-core_*.deb 2>/dev/null || true
fi
# Installer tous les autres paquets
chroot "${ROOTFS}" dpkg -i /tmp/secubox-debs/*.deb 2>/dev/null || true
chroot "${ROOTFS}" apt-get install -f -y -q 2>/dev/null || true
# Nettoyer
rm -rf "${ROOTFS}/tmp/secubox-debs"
ok "Slipstream: $(ls "${DEBS_DIR}"/*.deb | wc -l) paquets installés"
else
warn "Slipstream: pas de .deb dans ${DEBS_DIR}"
fi
fi
# Python deps communs
chroot "${ROOTFS}" pip3 install --break-system-packages -q \
fastapi uvicorn[standard] python-jose[cryptography] httpx \
jinja2 tomli pyroute2 psutil authlib aiosqlite 2>/dev/null || warn "pip install partiel"
# VirtualBox/QEMU Guest Tools pour VM
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
log "Installation des outils VM..."
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
qemu-guest-agent 2>/dev/null" || true
# VirtualBox Guest Additions (non-interactive)
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
virtualbox-guest-utils 2>/dev/null" || warn "VBox Guest Additions non installées"
# Enable guest agent
chroot "${ROOTFS}" systemctl enable qemu-guest-agent 2>/dev/null || true
ok "Outils VM installés"
fi
ok "Paquets installés"
# ── Initramfs hooks pour ARM (sdhci-xenon pour eMMC + mv88e6xxx blacklist) ──
if [[ $IS_ARM64 -eq 1 ]] && [[ "${BOARD}" != "vm-arm64" ]]; then
log "Installation hooks initramfs (sdhci-xenon + mv88e6xxx blacklist)..."
# Hook 1: sdhci-xenon pour support eMMC Armada 3720
cat > "${ROOTFS}/etc/initramfs-tools/hooks/sdhci-xenon" <<'HOOK'
#!/bin/sh
PREREQ=""
prereqs() { echo "$PREREQ"; }
case "$1" in prereqs) prereqs; exit 0;; esac
. /usr/share/initramfs-tools/hook-functions
# Armada 3720 eMMC support
manual_add_modules sdhci_xenon
manual_add_modules sdhci
manual_add_modules mmc_block
manual_add_modules mmc_core
manual_add_modules phy_mvebu_a3700_comphy
HOOK
chmod +x "${ROOTFS}/etc/initramfs-tools/hooks/sdhci-xenon"
# Hook 2: Blacklist mv88e6xxx DSA switch driver during initramfs
# Prevents probe loop that causes boot failures on ESPRESSObin/MOCHAbin
cat > "${ROOTFS}/etc/initramfs-tools/hooks/mv88e6xxx-blacklist" <<'HOOK'
#!/bin/sh
PREREQ=""
prereqs() { echo "$PREREQ"; }
case "$1" in prereqs) prereqs; exit 0;; esac
. /usr/share/initramfs-tools/hook-functions
# Blacklist DSA switch driver during initramfs to prevent probe loop
mkdir -p "${DESTDIR}/etc/modprobe.d"
cat > "${DESTDIR}/etc/modprobe.d/mv88e6xxx-initramfs.conf" <<'MODPROBE'
# Blacklist DSA switch driver during initramfs
# Driver loads after rootfs mount via systemd-modules-load
blacklist mv88e6xxx
blacklist mv88e6085
blacklist dsa_core
MODPROBE
HOOK
chmod +x "${ROOTFS}/etc/initramfs-tools/hooks/mv88e6xxx-blacklist"
# Also create the modprobe.d config in rootfs for good measure
mkdir -p "${ROOTFS}/etc/modprobe.d"
cat > "${ROOTFS}/etc/modprobe.d/mv88e6xxx-delay.conf" <<'MODPROBE'
# Delay mv88e6xxx load to avoid probe loop during boot
# The driver is blacklisted in initramfs and loads after rootfs mount
softdep mv88e6xxx pre: mmc_block
softdep dsa_core pre: mmc_block
MODPROBE
# SDHCI quirks for xenon-sdhci DDR timing issues on ESPRESSObin eMMC
cat > "${ROOTFS}/etc/modprobe.d/sdhci-xenon-fix.conf" <<'MODPROBE'
# Fix xenon-sdhci DDR timing issues on Armada 3720 eMMC
# SDHCI_QUIRK2_BROKEN_DDR50 = 0x40
options sdhci debug_quirks2=0x40
MODPROBE
# Install systemd service to load mv88e6xxx after boot
cat > "${ROOTFS}/etc/systemd/system/mv88e6xxx-load.service" <<'SERVICE'
[Unit]
Description=Load Marvell DSA switch driver after boot
DefaultDependencies=no
After=local-fs.target systemd-modules-load.service
Before=network-pre.target
Wants=network-pre.target
[Service]
Type=oneshot
ExecStart=/sbin/modprobe dsa_core
ExecStart=/sbin/modprobe mv88e6xxx
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SERVICE
chroot "${ROOTFS}" systemctl enable mv88e6xxx-load.service 2>/dev/null || true
# Regénérer initramfs avec les modules eMMC et blacklist
log "Regénération initramfs..."
chroot "${ROOTFS}" update-initramfs -u 2>/dev/null || warn "update-initramfs partiel"
ok "Hooks initramfs installés (sdhci-xenon + mv88e6xxx blacklist + delayed load service)"
fi
# ── Étape 4 : firstboot.sh ────────────────────────────────────────
log "4/7 Installation firstboot..."
install -m 755 "${SCRIPT_DIR}/firstboot.sh" "${ROOTFS}/usr/bin/secubox-firstboot"
# Unit systemd firstboot (one-shot)
cat > "${ROOTFS}/etc/systemd/system/secubox-firstboot.service" <<'EOF'
[Unit]
Description=SecuBox First Boot Initialization
After=network.target
ConditionPathExists=!/var/lib/secubox/.firstboot_done
[Service]
Type=oneshot
ExecStart=/usr/bin/secubox-firstboot
ExecStartPost=/usr/bin/touch /var/lib/secubox/.firstboot_done
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
chroot "${ROOTFS}" systemctl enable secubox-firstboot.service
ok "firstboot.sh installé"
# ── Pre-generate SSL certificates for nginx ─────────────────────────
# (firstboot will regenerate on first boot, but nginx needs certs to start)
# Generate certs on HOST (chroot may lack /dev/urandom)
log "Pre-generating SSL certificates..."
mkdir -p "${ROOTFS}/etc/secubox/tls"
mkdir -p "${ROOTFS}/run/secubox"
mkdir -p "${ROOTFS}/var/lib/secubox"
openssl req -x509 -newkey rsa:2048 -days 365 \
-keyout "${ROOTFS}/etc/secubox/tls/key.pem" \
-out "${ROOTFS}/etc/secubox/tls/cert.pem" \
-nodes -subj "/CN=secubox/O=CyberMind SecuBox/C=FR" \
-addext "subjectAltName=DNS:localhost,DNS:secubox.local,IP:127.0.0.1,IP:192.168.1.1" \
2>/dev/null
if [[ -f "${ROOTFS}/etc/secubox/tls/cert.pem" ]]; then
chmod 640 "${ROOTFS}/etc/secubox/tls/key.pem"
chmod 644 "${ROOTFS}/etc/secubox/tls/cert.pem"
ok "SSL certificates pre-generated"
else
warn "SSL cert generation failed - nginx may not start"
fi
# ── Étape 5 : Configuration bootloader ────────────────────────────
log "5/7 Configuration bootloader..."
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
# GRUB EFI pour x64 ou arm64 VM
cat > "${ROOTFS}/etc/default/grub" <<'EOF'
GRUB_DEFAULT=0
GRUB_TIMEOUT=3
GRUB_DISTRIBUTOR="SecuBox"
GRUB_CMDLINE_LINUX_DEFAULT="quiet net.ifnames=1"
GRUB_CMDLINE_LINUX=""
GRUB_TERMINAL=console
EOF
ok "GRUB configuré"
else
# U-Boot pour ARM hardware
warn "U-Boot : kernel/DTB à placer dans board/${BOARD}/kernel/"
fi
# ── Étape 6 : Construction de l'image ─────────────────────────────
log "6/7 Construction image GPT ${IMG_SIZE}..."
# Démonter les filesystems avant création image
umount -lf "${ROOTFS}/proc" 2>/dev/null || true
umount -lf "${ROOTFS}/sys" 2>/dev/null || true
umount -lf "${ROOTFS}/dev" 2>/dev/null || true
# Créer fichier image
fallocate -l "${IMG_SIZE}" "${IMG_FILE}"
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
# GPT + ESP pour UEFI (x64 ou arm64 VM)
parted -s "${IMG_FILE}" \
mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \
mkpart ROOT ext4 513MiB 3073MiB \
mkpart DATA ext4 3073MiB 100% \
set 1 esp on \
set 1 boot on
else
# GPT pour ARM hardware (boot + rootfs + data)
parted -s "${IMG_FILE}" \
mklabel gpt \
mkpart boot fat32 2MiB 258MiB \
mkpart ROOT ext4 258MiB 2818MiB \
mkpart DATA ext4 2818MiB 100% \
set 1 boot on
fi
# Associer loop device
LOOP=$(losetup -f --show -P "${IMG_FILE}")
log " loop device: ${LOOP}"
# Formater partitions
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
mkfs.fat -F32 -n "ESP" "${LOOP}p1"
else
mkfs.fat -F32 -n "boot" "${LOOP}p1"
fi
mkfs.ext4 -L "rootfs" -q "${LOOP}p2"
mkfs.ext4 -L "data" -q "${LOOP}p3"
# Copier rootfs
MNT="${WORK_DIR}/mnt"
mkdir -p "${MNT}"
mount "${LOOP}p2" "${MNT}"
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
mkdir -p "${MNT}/boot/efi" "${MNT}/data"
mount "${LOOP}p1" "${MNT}/boot/efi"
else
mkdir -p "${MNT}/boot" "${MNT}/data"
mount "${LOOP}p1" "${MNT}/boot"
fi
rsync -ax --exclude={"/proc/*","/sys/*","/dev/*","/run/*","/tmp/*"} \
"${ROOTFS}/" "${MNT}/"
# Installation GRUB pour x64 ou arm64 VM
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
# Monter les fs pour GRUB install
mount --bind /dev "${MNT}/dev"
mount --bind /proc "${MNT}/proc"
mount --bind /sys "${MNT}/sys"
if [[ $IS_X64 -eq 1 ]]; then
GRUB_TARGET="x86_64-efi"
else
GRUB_TARGET="arm64-efi"
fi
chroot "${MNT}" grub-install --target=${GRUB_TARGET} --efi-directory=/boot/efi \
--bootloader-id=secubox --no-nvram --removable 2>/dev/null || warn "grub-install partiel"
chroot "${MNT}" update-grub 2>/dev/null || warn "update-grub partiel"
umount -lf "${MNT}/sys" 2>/dev/null || true
umount -lf "${MNT}/proc" 2>/dev/null || true
umount -lf "${MNT}/dev" 2>/dev/null || true
else
# ARM physique : copier kernel + DTB depuis linux-image-arm64
log "Configuration boot U-Boot pour ${BOARD}..."
# Copier le kernel (vmlinuz → Image pour U-Boot)
VMLINUZ=$(ls "${MNT}/boot/vmlinuz-"* 2>/dev/null | head -1)
if [[ -n "$VMLINUZ" ]]; then
cp "$VMLINUZ" "${MNT}/boot/Image"
ok "Kernel copié : $(basename $VMLINUZ) → Image"
else
warn "Kernel vmlinuz non trouvé dans /boot"
fi
# Copier l'initramfs (requis pour sdhci-xenon sur eMMC)
INITRD=$(ls "${MNT}/boot/initrd.img-"* 2>/dev/null | head -1)
if [[ -n "$INITRD" ]]; then
cp "$INITRD" "${MNT}/boot/initrd.img"
ok "Initramfs copié : $(basename $INITRD) → initrd.img"
else
warn "Initramfs non trouvé dans /boot"
fi
# Copier le DTB depuis /usr/lib/linux-image-*/
DTB_DIR=$(ls -d "${MNT}/usr/lib/linux-image-"*/ 2>/dev/null | head -1)
if [[ -d "$DTB_DIR" ]]; then
mkdir -p "${MNT}/boot/dtbs"
# Copier les DTBs Marvell (Armada)
if [[ -d "${DTB_DIR}/marvell" ]]; then
cp -r "${DTB_DIR}/marvell" "${MNT}/boot/dtbs/"
ok "DTBs Marvell copiés vers /boot/dtbs/marvell/"
fi
else
warn "Répertoire DTB non trouvé dans /usr/lib/linux-image-*/"
fi
# Créer extlinux.conf pour U-Boot distroboot
mkdir -p "${MNT}/boot/extlinux"
# Default to eMMC variant DTB for boards with eMMC
KERNEL_DTS="${KERNEL_DTS:-armada-3720-espressobin-v7-emmc}"
# Determine root device based on DTB variant
# With -emmc DTB: SD=mmc0/mmcblk0, eMMC=mmc1/mmcblk1
# Without -emmc DTB: only SD or eMMC as mmcblk0
if [[ "$KERNEL_DTS" == *"-emmc"* ]]; then
ROOT_DEV="/dev/mmcblk1p2"
else
ROOT_DEV="/dev/mmcblk0p2"
fi
cat > "${MNT}/boot/extlinux/extlinux.conf" <<EXTLINUX
DEFAULT secubox
TIMEOUT 30
PROMPT 1
LABEL secubox
KERNEL /Image
INITRD /initrd.img
FDT /dtbs/marvell/${KERNEL_DTS}.dtb
APPEND root=${ROOT_DEV} rootfstype=ext4 rootwait rootdelay=5 console=ttyMV0,115200 net.ifnames=0 modprobe.blacklist=mv88e6xxx,dsa_core sdhci.debug_quirks2=0x40
EXTLINUX
ok "extlinux.conf créé pour ${KERNEL_DTS} (root=${ROOT_DEV})"
# Créer boot.scr pour U-Boot (alternative à extlinux)
# Utiliser le boot.cmd spécifique au board s'il existe
BOARD_BOOTCMD="${SCRIPT_DIR}/../board/${BOARD}/boot.cmd"
if [[ -f "$BOARD_BOOTCMD" ]]; then
log "Utilisation du boot.cmd spécifique au board ${BOARD}"
cp "$BOARD_BOOTCMD" "${MNT}/boot/boot.cmd"
else
# Fallback: générer un boot.cmd générique
cat > "${MNT}/boot/boot.cmd" <<'BOOTCMD'
# SecuBox U-Boot boot script
# Auto-generated fallback — board-specific boot.cmd recommended
echo "============================================"
echo "SecuBox Boot Script"
echo "============================================"
# Detect boot device from U-Boot environment
if test -n "${devtype}" -a -n "${devnum}"; then
echo "Boot device from env: ${devtype} ${devnum}"
setenv bootdev "${devnum}"
else
setenv bootdev 0
echo "Using mmc 0 as boot device"
fi
setenv bootpart "mmc ${bootdev}:1"
# Linux eMMC is typically mmcblk0 regardless of U-Boot device number
setenv rootpart "/dev/mmcblk0p2"
echo "Boot partition: ${bootpart}"
echo "Root partition: ${rootpart}"
# Load kernel
echo "Loading kernel from ${bootpart}..."
load ${bootpart} ${kernel_addr_r} Image
# Load initramfs (required for eMMC support)
echo "Loading initramfs..."
if load ${bootpart} ${ramdisk_addr_r} initrd.img; then
echo "Initramfs loaded OK"
setenv initrd_size ${filesize}
setenv use_initrd 1
else
echo "WARNING: No initramfs found"
setenv use_initrd 0
fi
# Load DTB
echo "Loading device tree..."
BOOTCMD
# Ajouter le DTB spécifique au board
cat >> "${MNT}/boot/boot.cmd" <<BOOTCMD_DTB
if load \${bootpart} \${fdt_addr_r} dtbs/marvell/${KERNEL_DTS}.dtb; then
echo "DTB loaded: ${KERNEL_DTS}.dtb"
else
echo "ERROR: Failed to load DTB ${KERNEL_DTS}.dtb"
fi
BOOTCMD_DTB
cat >> "${MNT}/boot/boot.cmd" <<'BOOTCMD_END'
# Set boot args
# modprobe.blacklist: prevent mv88e6xxx DSA driver from loading during initramfs
# sdhci.debug_quirks2=0x40: fix xenon-sdhci DDR timing issues
setenv bootargs "root=${rootpart} rootfstype=ext4 rootwait rootdelay=5 console=ttyMV0,115200 net.ifnames=0 modprobe.blacklist=mv88e6xxx,dsa_core sdhci.debug_quirks2=0x40"
echo "Boot args: ${bootargs}"
echo "============================================"
echo "Booting SecuBox..."
echo "============================================"
if test "${use_initrd}" = "1"; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${initrd_size} ${fdt_addr_r}
else
booti ${kernel_addr_r} - ${fdt_addr_r}
fi
BOOTCMD_END
fi
# Compiler le bootscript si mkimage est disponible
if command -v mkimage >/dev/null 2>&1; then
mkimage -C none -A arm64 -T script -d "${MNT}/boot/boot.cmd" "${MNT}/boot/boot.scr" >/dev/null
ok "boot.scr créé (U-Boot bootscript)"
else
warn "mkimage non disponible — boot.scr non généré (installer u-boot-tools)"
fi
fi
# Démonter
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
umount "${MNT}/boot/efi"
else
umount "${MNT}/boot"
fi
umount "${MNT}"
losetup -d "${LOOP}"
LOOP=""
ok "Image construite : ${IMG_FILE}"
# ── Étape 7 : Compression + conversion VDI ────────────────────────
log "7/7 Post-traitement..."
# Conversion VDI pour VirtualBox (x64 seulement)
if [[ $IS_X64 -eq 1 ]] && [[ $CONVERT_VDI -eq 1 ]]; then
VDI_FILE="${OUT_DIR}/secubox-${BOARD}-${SUITE}.vdi"
log " Conversion en VDI..."
if qemu-img convert -f raw -O vdi "${IMG_FILE}" "${VDI_FILE}"; then
ok "VDI créé : ${VDI_FILE}"
else
err "Échec conversion VDI"
fi
fi
# Conversion QCOW2 pour QEMU (arm64 VM)
if [[ "${BOARD}" == "vm-arm64" ]]; then
QCOW2_FILE="${OUT_DIR}/secubox-${BOARD}-${SUITE}.qcow2"
log " Conversion en QCOW2..."
qemu-img convert -f raw -O qcow2 "${IMG_FILE}" "${QCOW2_FILE}"
ok "QCOW2 créé : ${QCOW2_FILE}"
fi
# Compression
log " Compression gzip..."
gzip -9 -f "${IMG_FILE}"
IMG_GZ="${IMG_FILE}.gz"
sha256sum "${IMG_GZ}" > "${IMG_GZ}.sha256"
ok "Livrable : ${IMG_GZ} ($(du -sh "${IMG_GZ}" | cut -f1))"
echo ""
echo -e "${GOLD}${BOLD}════════════════════════════════════════════${NC}"
echo -e "${GREEN}${BOLD} Image SecuBox-${BOARD} prête !${NC}"
echo ""
if [[ $IS_X64 -eq 1 ]]; then
echo -e " VirtualBox : Importer le .vdi ou décompresser le .img"
echo -e " QEMU : qemu-system-x86_64 -drive file=${IMG_FILE},format=raw -enable-kvm"
if [[ $CONVERT_VDI -eq 1 ]]; then
echo -e " VDI : ${VDI_FILE}"
fi
elif [[ "${BOARD}" == "vm-arm64" ]]; then
echo -e " QEMU arm64 :"
echo -e " qemu-system-aarch64 -M virt -cpu cortex-a72 -m 2048 -smp 2 \\"
echo -e " -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \\"
echo -e " -drive file=${QCOW2_FILE},format=qcow2 \\"
echo -e " -device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22 \\"
echo -e " -nographic"
echo -e " QCOW2 : ${QCOW2_FILE}"
else
echo -e " Flasher : zcat ${IMG_GZ} | dd of=/dev/mmcblk0 bs=4M status=progress"
fi
echo -e "${GOLD}${BOLD}════════════════════════════════════════════${NC}"