secubox-deb/image/build-image.sh
CyberMind-FR feda7182ed fix(rpi400): ship auth runtime deps + wire --kiosk through CI
secubox-auth/users crash at import on the rpi400 image because it is built
with `dpkg -i --force-depends` + debootstrap --include, so their declared
Depends (python3-argon2, python3-pyotp, python3-qrcode) are never pulled —
unlike normal apt installs on mochabin. The engine does `from argon2 import
...` / `import pyotp` / `import qrcode`, so the daemon dies at startup →
nginx 502 → no login. Add pyotp+qrcode (pure-Python) to INCLUDE_PKGS and
argon2 via the QEMU-proven pip step (mirroring the cryptography exclusion).

Also expose the kiosk GUI through CI: a `kiosk` workflow_dispatch boolean
→ build-image.sh --kiosk → forwarded to build-rpi-usb.sh, so a rebuilt
rpi400 image can ship the chromium fullscreen dashboard.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-22 11:53:42 +02:00

1686 lines
65 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="8G" # Taille de l'image totale (increased for full install)
ROOT_SIZE="5.5G" # Taille partition rootfs (needs ~4GB for full install)
DATA_SIZE="2G" # 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=1 # Intégrer les .deb locaux dans l'image (default: ON)
# SecuBox versioning
SECUBOX_VERSION="1.10.0"
BUILD_TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
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 ;;
--kiosk) INCLUDE_KIOSK=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
# Redirect to build-rpi-usb.sh for Raspberry Pi boards
if [[ "${USE_RPI_SCRIPT:-0}" == "1" ]] || [[ "$BOARD" == "rpi400" ]] || [[ "$BOARD" == "rpi4" ]]; then
log "Raspberry Pi board detected - using build-rpi-usb.sh"
RPI_ARGS="--out ${OUT_DIR}"
[[ ${SLIPSTREAM_DEBS:-0} -eq 1 ]] && RPI_ARGS="$RPI_ARGS --slipstream"
[[ ${INCLUDE_KIOSK:-0} -eq 1 ]] && RPI_ARGS="$RPI_ARGS --kiosk"
exec bash "${SCRIPT_DIR}/build-rpi-usb.sh" $RPI_ARGS
fi
# 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}"
# Core system packages
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,cloud-guest-utils,parted,u-boot-tools,libubootenv-tool"
# Python dependencies for SecuBox modules (apt packages)
# Only pure-Python packages here: debootstrap second-stage cannot reliably run
# postinst scripts for C-extension wheels (python3-argon2, python3-zmq, etc. all
# fail with "Failure while configuring base packages"). Everything else is pulled
# post-debootstrap by `apt-get install -f -y`, when /proc and /sys are real.
# See issue #218 for the original investigation.
INCLUDE_PKGS+=",python3-fastapi,python3-uvicorn,python3-httpx,python3-psutil"
INCLUDE_PKGS+=",python3-aiosqlite,python3-jinja2,python3-jwt"
INCLUDE_PKGS+=",python3-aiofiles,python3-pil,python3-tomli,python3-pydantic"
INCLUDE_PKGS+=",python3-toml,python3-netifaces"
# Network and security tools
INCLUDE_PKGS+=",bridge-utils,traceroute,dnsutils,whois,mtr-tiny,nmap,iputils-arping"
INCLUDE_PKGS+=",avahi-daemon,avahi-utils,ieee-data,procps,openssl"
INCLUDE_PKGS+=",fonts-noto-color-emoji,locales,console-setup"
# Optional heavy services (installed but may be disabled)
# Note: crowdsec, netdata, glances are large - moved to post-debootstrap for --no-install-recommends
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 secubox.local
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/"
# Network fallback script - DHCP with smart auto-IP collision avoidance
cat > "${ROOTFS}/usr/sbin/secubox-net-fallback" <<'FALLBACK'
#!/bin/bash
# SecuBox Network Fallback - DHCP first, then smart auto-IP with ARP collision detection
# Avoids IP conflicts when multiple SecuBox devices are on same network
logger -t secubox-net "Network fallback starting..."
# Fallback network (SecuBox default when no DHCP)
FALLBACK_NETWORK="10.55.255"
FALLBACK_GW="10.55.255.1"
# IP range for auto-assignment (avoid .1 gateway and .255 broadcast)
IP_START=100
IP_END=250
# Check if IP is in use via ARP probe (RFC 5227)
# Returns 0 if IP is FREE, 1 if IP is TAKEN
check_ip_free() {
local ip="$1"
local iface="$2"
# Send ARP probe (src=0.0.0.0, dst=target IP)
# If we get a reply, the IP is taken
if command -v arping &>/dev/null; then
# arping returns 0 if reply received (IP taken), 1 if no reply (IP free)
if arping -D -q -c 2 -w 2 -I "$iface" "$ip" 2>/dev/null; then
return 0 # No reply = IP is free
else
return 1 # Got reply = IP is taken
fi
else
# Fallback: ping check (less reliable but works)
if ping -c 1 -W 1 "$ip" &>/dev/null; then
return 1 # Got reply = IP is taken
else
return 0 # No reply = probably free
fi
fi
}
# Find a free IP in the fallback range using ARP probing
find_free_ip() {
local iface="$1"
local mac=$(cat /sys/class/net/$iface/address 2>/dev/null | tr -d ':')
# Generate a pseudo-random starting point based on MAC address
# This spreads devices across the range to reduce collision probability
local mac_seed=$(printf "%d" "0x${mac:8:4}" 2>/dev/null || echo "12345")
local range=$((IP_END - IP_START))
local start_offset=$((mac_seed % range))
logger -t secubox-net "Searching for free IP in ${FALLBACK_NETWORK}.${IP_START}-${IP_END} (seed offset: $start_offset)"
# Try IPs starting from our MAC-based offset, wrapping around
for ((i=0; i<range; i++)); do
local offset=$(( (start_offset + i) % range ))
local test_suffix=$((IP_START + offset))
local test_ip="${FALLBACK_NETWORK}.${test_suffix}"
if check_ip_free "$test_ip" "$iface"; then
logger -t secubox-net "Found free IP: $test_ip"
echo "$test_ip"
return 0
else
logger -t secubox-net "IP $test_ip is in use, trying next..."
fi
done
logger -t secubox-net "ERROR: No free IP found in range!"
return 1
}
# Announce our IP via gratuitous ARP (alert other devices)
announce_ip() {
local ip="$1"
local iface="$2"
if command -v arping &>/dev/null; then
# Send gratuitous ARP to announce our presence
arping -A -c 3 -I "$iface" "$ip" 2>/dev/null &
logger -t secubox-net "Announced IP $ip via gratuitous ARP"
fi
}
# Common gateway IPs to probe for existing networks
GATEWAYS="192.168.1.1 192.168.0.1 192.168.2.1 10.55.255.1 10.0.0.1 10.0.1.1 172.16.0.1"
discover_lan() {
local iface="$1"
for gw in $GATEWAYS; do
local subnet_base="${gw%.*}"
# Find a free IP in this subnet
local test_ip
for suffix in 250 249 248 251 252; do
test_ip="${subnet_base}.${suffix}"
if check_ip_free "$test_ip" "$iface"; then
break
fi
done
# Temporarily add IP to test gateway reachability
ip addr add "${test_ip}/24" dev "$iface" 2>/dev/null
if ping -c 1 -W 1 -I "$iface" "$gw" &>/dev/null; then
logger -t secubox-net "Discovered gateway $gw on $iface"
ip route add default via "$gw" dev "$iface" 2>/dev/null || true
echo "nameserver $gw" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
announce_ip "$test_ip" "$iface"
logger -t secubox-net "Auto-configured $iface: ${test_ip}/24 via $gw"
return 0
fi
ip addr del "${test_ip}/24" dev "$iface" 2>/dev/null
done
return 1
}
# Configure fallback network with collision-free IP
configure_fallback() {
local iface="$1"
logger -t secubox-net "Configuring fallback network on $iface"
# Find a free IP in the fallback range
local free_ip
free_ip=$(find_free_ip "$iface")
if [[ -z "$free_ip" ]]; then
# Last resort: use MAC-based IP
local mac=$(cat /sys/class/net/$iface/address 2>/dev/null | tr -d ':')
local mac_suffix=$(printf "%d" "0x${mac:8:4}" 2>/dev/null || echo "200")
mac_suffix=$((100 + (mac_suffix % 150)))
free_ip="${FALLBACK_NETWORK}.${mac_suffix}"
logger -t secubox-net "Using MAC-based fallback IP: $free_ip"
fi
# Configure the IP
ip addr add "${free_ip}/24" dev "$iface" 2>/dev/null
# Check if gateway exists
if ping -c 1 -W 1 "$FALLBACK_GW" &>/dev/null; then
ip route add default via "$FALLBACK_GW" dev "$iface" 2>/dev/null || true
echo "nameserver $FALLBACK_GW" > /etc/resolv.conf
fi
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
# Announce our IP
announce_ip "$free_ip" "$iface"
logger -t secubox-net "Fallback configured: $iface = $free_ip"
return 0
}
# Wait for system to settle
sleep 10
GOT_IP=0
# Process each network interface
for iface in /sys/class/net/*; do
[ -e "$iface" ] || continue
IFACE=$(basename "$iface")
# Skip virtual interfaces
case "$IFACE" in
lo|dummy*|docker*|veth*|br-*|virbr*) continue ;;
esac
# Skip LAN interfaces - SecuBox IS the router on these (provides DHCP, not receives it)
# lan0, lan1 on EspressoBin are downstream ports to clients
case "$IFACE" in
lan*)
logger -t secubox-net "Skipping LAN interface $IFACE (downstream port)"
continue
;;
esac
# Skip interfaces that are part of a bridge (already managed)
if [[ -d "/sys/class/net/$IFACE/brport" ]]; then
logger -t secubox-net "Skipping $IFACE (bridged interface)"
continue
fi
# Only process WAN/uplink interfaces (need DHCP from upstream router)
case "$IFACE" in
eth*|wan*|en*) ;;
*) continue ;;
esac
ip link set "$IFACE" up 2>/dev/null
sleep 2 # Wait for link
# Check if already has valid IP
CURRENT_IP=$(ip -4 addr show "$IFACE" 2>/dev/null | grep -oP 'inet \K[0-9.]+' | grep -v '^169\.254\.' | head -1)
if [[ -n "$CURRENT_IP" ]]; then
logger -t secubox-net "Interface $IFACE already has IP $CURRENT_IP"
GOT_IP=1
continue
fi
# Try DHCP first
logger -t secubox-net "Requesting DHCP on $IFACE..."
if command -v dhclient &>/dev/null; then
timeout 30 dhclient -1 -v "$IFACE" 2>&1 | logger -t secubox-net || true
else
networkctl reconfigure "$IFACE" 2>/dev/null || true
sleep 15
fi
# Check if DHCP succeeded
CURRENT_IP=$(ip -4 addr show "$IFACE" 2>/dev/null | grep -oP 'inet \K[0-9.]+' | grep -v '^169\.254\.' | head -1)
if [[ -n "$CURRENT_IP" ]]; then
logger -t secubox-net "DHCP succeeded on $IFACE: $CURRENT_IP"
GOT_IP=1
continue
fi
# DHCP failed on WAN interface - try auto-discovery then fallback
if [[ "$IFACE" == e* ]] || [[ "$IFACE" == eth* ]] || [[ "$IFACE" == wan* ]] || [[ "$IFACE" == en* ]]; then
logger -t secubox-net "DHCP failed on $IFACE, trying LAN auto-discovery..."
if discover_lan "$IFACE"; then
logger -t secubox-net "LAN auto-discovery succeeded on $IFACE"
GOT_IP=1
else
logger -t secubox-net "Auto-discovery failed, using fallback network..."
if configure_fallback "$IFACE"; then
GOT_IP=1
fi
fi
fi
done
# Final status
if [[ "$GOT_IP" -eq 1 ]]; then
logger -t secubox-net "Network configuration complete"
else
logger -t secubox-net "WARNING: No network configured!"
fi
FALLBACK
chmod +x "${ROOTFS}/usr/sbin/secubox-net-fallback"
# Systemd service for network fallback
cat > "${ROOTFS}/etc/systemd/system/secubox-net-fallback.service" <<'NETSVC'
[Unit]
Description=SecuBox Network Fallback
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/secubox-net-fallback
RemainAfterExit=yes
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
NETSVC
chroot "${ROOTFS}" systemctl enable secubox-net-fallback.service 2>/dev/null || true
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"
# Install heavy services that aren't in debootstrap (crowdsec, netdata, glances, X11)
log "Installing security services and optional components..."
# Add CrowdSec repository
log " Adding CrowdSec repository..."
chroot "${ROOTFS}" bash -c '
curl -s https://install.crowdsec.net | bash 2>/dev/null || true
' 2>/dev/null || warn "CrowdSec repo setup failed"
# Install security services
# For lite profiles, install only essential services
chroot "${ROOTFS}" apt-get update -q 2>/dev/null
if [[ "${SECUBOX_LITE:-0}" != "1" ]]; then
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
crowdsec glances netdata haproxy qrencode mosquitto coturn 2>/dev/null" || warn "Some services not installed"
else
log " Installing lite security services (no netdata/glances)..."
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
crowdsec haproxy qrencode 2>/dev/null" || warn "Some services not installed"
fi
# Install X11 packages for kiosk/UI mode (lighter xorg install)
# Skip for lite profiles (ESPRESSObin with limited storage/RAM)
# Driver coverage: include all common video drivers so bare-metal x86_64
# (Intel/AMD/Nvidia/VESA) and VM targets (QXL/VMware) all get fast X11
# init via modesetting + the matching userspace driver. ~15 MB extra over
# the previous fbdev-only install. See issue #226.
if [[ "${SECUBOX_LITE:-0}" != "1" ]]; then
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
kbd xinit xserver-xorg-core chromium unclutter x11-xserver-utils \
xserver-xorg-input-libinput \
xserver-xorg-video-fbdev xserver-xorg-video-vesa \
xserver-xorg-video-intel xserver-xorg-video-amdgpu xserver-xorg-video-radeon \
xserver-xorg-video-nouveau xserver-xorg-video-qxl xserver-xorg-video-vmware \
2>/dev/null" || warn "X11 packages partial"
else
log " Skipping X11/kiosk packages (lite profile)"
fi
# Install LXC/container tools (for secubox-mail, secubox-streamlit)
# Skip for lite profiles - too heavy for limited storage
if [[ "${SECUBOX_LITE:-0}" != "1" ]]; then
chroot "${ROOTFS}" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
lxc debootstrap 2>/dev/null" || warn "LXC packages not installed"
else
log " Skipping LXC packages (lite profile)"
fi
# Clean apt cache to save space
chroot "${ROOTFS}" apt-get clean
chroot "${ROOTFS}" rm -rf /var/lib/apt/lists/*
ok "Services and optional components 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" 2>/dev/null \
| gpg --dearmor > "${ROOTFS}/usr/share/keyrings/secubox.gpg" 2>/dev/null \
&& [ -s "${ROOTFS}/usr/share/keyrings/secubox.gpg" ]; then
# apt's signed-by= requires a DEARMORED (binary) keyring; the published
# secubox-keyring.gpg is ASCII-armored, so dearmor it on the way in.
# Feeding the armored file directly yields "NO_PUBKEY 44E50F0178E8BC7E
# / repository not signed" even though the key is correct.
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
# Non-interactive conffile handling: secubox-mesh's mesh.toml triggers a dpkg
# conffile prompt that fails the headless chroot install (#USB-build). Keep
# the packaged conffile, never prompt.
chroot "${ROOTFS}" bash -c 'DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
-o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef secubox-full' \
|| warn "secubox-full non disponible"
chroot "${ROOTFS}" dpkg --configure -a --force-confold 2>/dev/null || true
else
warn "APT repo SecuBox non disponible — skip (Phase 4)"
fi
# ── Pre-install Python dependencies (BEFORE slipstream) ───────────────────
log "Pre-installing Python dependencies for SecuBox modules..."
install -d -m 755 "${ROOTFS}/usr/lib/systemd/system"
install -d -m 755 "${ROOTFS}/etc/systemd/system"
# Install comprehensive Python dependencies via pip (covers all SecuBox modules)
chroot "${ROOTFS}" pip3 install --break-system-packages -q \
fastapi uvicorn[standard] python-jose[cryptography] httpx \
jinja2 tomli pyroute2 psutil authlib aiosqlite aiofiles \
pydantic toml netifaces pillow zmq pyjwt cryptography \
python-multipart email-validator \
textual 2>/dev/null || warn "pip install partiel"
ok "Python dependencies installed"
# Slipstream: intégrer les .deb locaux directement
if [[ $SLIPSTREAM_DEBS -eq 1 ]]; then
# Check output/debs/ first (fresh builds), then output/
if [[ -d "${REPO_DIR}/output/debs" ]] && ls "${REPO_DIR}/output/debs"/secubox-*.deb >/dev/null 2>&1; then
DEBS_DIR="${REPO_DIR}/output/debs"
elif [[ -d "${REPO_DIR}/output" ]] && ls "${REPO_DIR}/output"/secubox-*.deb >/dev/null 2>&1; then
DEBS_DIR="${REPO_DIR}/output"
else
DEBS_DIR="${REPO_DIR}/output/debs"
fi
if [[ -d "${DEBS_DIR}" ]] && ls "${DEBS_DIR}"/secubox-*.deb >/dev/null 2>&1; then
log "Slipstream: installation des paquets locaux..."
install -d "${ROOTFS}/tmp/secubox-debs"
# Filter packages by architecture: only copy _all.deb and _${DEBIAN_ARCH}.deb
SLIP_COUNT=0
SKIP_COUNT=0
for deb in "${DEBS_DIR}"/secubox-*.deb; do
[[ -f "$deb" ]] || continue
deb_name=$(basename "$deb")
case "$deb_name" in
*_all.deb|*_${DEBIAN_ARCH}.deb)
cp "$deb" "${ROOTFS}/tmp/secubox-debs/"
((SLIP_COUNT++)) || true
;;
*)
((SKIP_COUNT++)) || true
;;
esac
done
[[ $SKIP_COUNT -gt 0 ]] && log "Slipstream: skipped ${SKIP_COUNT} packages (wrong architecture)"
log "Slipstream: ${SLIP_COUNT} packages to install"
# Installer secubox-core en premier (dépendance)
if ls "${ROOTFS}/tmp/secubox-debs/secubox-core_"*.deb >/dev/null 2>&1; then
log "Installing secubox-core (dependency)..."
chroot "${ROOTFS}" bash -c 'dpkg -i --force-depends /tmp/secubox-debs/secubox-core_*.deb' 2>/dev/null || true
fi
# Installer tous les autres paquets
log "Installing all packages..."
chroot "${ROOTFS}" bash -c 'dpkg -i --force-depends --force-overwrite /tmp/secubox-debs/*.deb' 2>&1 | \
grep -v "^dpkg: warning" | grep -v "^Selecting\|^Preparing\|^Unpacking\|^Setting up" | head -30 || true
# Resolve any remaining Debian deps declared in packages/secubox-*/debian/control
# that weren't pre-installed via INCLUDE_PKGS (issue #218). `apt-get install -f`
# completes half-configured packages by pulling their declared Depends from apt.
log "Resolving slipstreamed package dependencies (apt-get install -f)..."
chroot "${ROOTFS}" apt-get install -f -y --no-install-recommends 2>&1 | tail -5 || true
# Configure (idempotent — most packages were already set up by the -f run)
chroot "${ROOTFS}" dpkg --configure -a --force-confold 2>/dev/null || true
# Count installed
INSTALLED_COUNT=$(chroot "${ROOTFS}" dpkg -l 'secubox-*' 2>/dev/null | grep "^ii" | wc -l)
# Nettoyer
rm -rf "${ROOTFS}/tmp/secubox-debs"
ok "Slipstream: ${INSTALLED_COUNT}/${SLIP_COUNT} paquets installés"
# ── Nginx cleanup after package install ──────────────────────────────────
log "Cleaning bad nginx configs from conf.d..."
for conf in "${ROOTFS}/etc/nginx/conf.d/"*secubox*.conf "${ROOTFS}/etc/nginx/conf.d/secubox-"*; do
[[ -f "$conf" ]] || [[ -L "$conf" ]] || continue
real_file="$conf"
if [[ -L "$conf" ]]; then
target=$(readlink "$conf")
if [[ "$target" == /* ]]; then
real_file="${ROOTFS}${target}"
else
real_file="${ROOTFS}/etc/nginx/conf.d/${target}"
fi
fi
if [[ -f "$real_file" ]] && grep -q "^location" "$real_file" 2>/dev/null; then
base=$(basename "$conf" .conf | sed 's/^secubox-//')
mkdir -p "${ROOTFS}/etc/nginx/secubox.d"
if [[ ! -f "${ROOTFS}/etc/nginx/secubox.d/${base}.conf" ]]; then
cp "$real_file" "${ROOTFS}/etc/nginx/secubox.d/${base}.conf" 2>/dev/null || true
fi
rm -f "$conf"
fi
done
log "Cleaning bad nginx configs from sites-enabled..."
for site in "${ROOTFS}/etc/nginx/sites-enabled/"*; do
[[ -f "$site" ]] || [[ -L "$site" ]] || continue
[[ "$(basename "$site")" == "secubox" ]] && continue
real_file="$site"
[[ -L "$site" ]] && real_file=$(readlink -f "$site") && real_file="${ROOTFS}${real_file#${ROOTFS}}"
[[ -f "$real_file" ]] || { rm -f "$site"; continue; }
if grep -q "^location" "$real_file" 2>/dev/null && ! grep -q "^server" "$real_file" 2>/dev/null; then
base=$(basename "$site" | sed 's/^secubox-//')
mkdir -p "${ROOTFS}/etc/nginx/secubox.d"
cp "$real_file" "${ROOTFS}/etc/nginx/secubox.d/${base}.conf" 2>/dev/null || true
rm -f "$site"
fi
done
# Activate .dpkg-new configs (dpkg leaves these when conffiles exist)
log "Activating .dpkg-new nginx configs..."
DPKG_NEW_COUNT=0
for newconf in "${ROOTFS}/etc/nginx/secubox.d/"*.dpkg-new; do
[[ -f "$newconf" ]] || continue
target="${newconf%.dpkg-new}"
mv "$newconf" "$target"
((DPKG_NEW_COUNT++)) || true
done
[[ $DPKG_NEW_COUNT -gt 0 ]] && ok "Activated ${DPKG_NEW_COUNT} .dpkg-new configs"
# Test nginx configuration
if chroot "${ROOTFS}" nginx -t 2>&1; then
ok "Nginx configuration valid"
else
warn "Nginx configuration has errors (will be fixed at firstboot)"
fi
else
warn "Slipstream: pas de secubox-*.deb dans output/ ou output/debs/"
fi
fi
# 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é"
# Enable kiosk service for x64/VM builds (GUI mode by default)
if [[ $IS_X64 -eq 1 ]] || [[ "${BOARD}" == "vm-arm64" ]]; then
# Copy kiosk service and launcher
if [[ -f "${SCRIPT_DIR}/systemd/secubox-kiosk.service" ]]; then
cp "${SCRIPT_DIR}/systemd/secubox-kiosk.service" "${ROOTFS}/etc/systemd/system/"
fi
if [[ -f "${SCRIPT_DIR}/sbin/secubox-kiosk-launcher" ]]; then
cp "${SCRIPT_DIR}/sbin/secubox-kiosk-launcher" "${ROOTFS}/usr/sbin/"
chmod +x "${ROOTFS}/usr/sbin/secubox-kiosk-launcher"
fi
if [[ -f "${SCRIPT_DIR}/sbin/secubox-kiosk-setup" ]]; then
cp "${SCRIPT_DIR}/sbin/secubox-kiosk-setup" "${ROOTFS}/usr/sbin/"
chmod +x "${ROOTFS}/usr/sbin/secubox-kiosk-setup"
fi
if [[ -f "${SCRIPT_DIR}/sbin/secubox-cmdline-handler" ]]; then
cp "${SCRIPT_DIR}/sbin/secubox-cmdline-handler" "${ROOTFS}/usr/sbin/"
chmod +x "${ROOTFS}/usr/sbin/secubox-cmdline-handler"
fi
if [[ -f "${SCRIPT_DIR}/sbin/secubox-mode" ]]; then
cp "${SCRIPT_DIR}/sbin/secubox-mode" "${ROOTFS}/usr/sbin/"
chmod +x "${ROOTFS}/usr/sbin/secubox-mode"
fi
# Enable kiosk service and set graphical target
chroot "${ROOTFS}" systemctl enable secubox-kiosk.service 2>/dev/null || true
chroot "${ROOTFS}" systemctl set-default graphical.target 2>/dev/null || true
# Disable getty on tty1 (kiosk uses VT7, console access via tty2)
chroot "${ROOTFS}" systemctl disable getty@tty1.service 2>/dev/null || true
# Enable autologin on tty2 for emergency access
mkdir -p "${ROOTFS}/etc/systemd/system/getty@tty2.service.d"
cat > "${ROOTFS}/etc/systemd/system/getty@tty2.service.d/autologin.conf" <<'AUTOLOGIN'
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
Type=idle
AUTOLOGIN
chroot "${ROOTFS}" systemctl enable getty@tty2.service 2>/dev/null || true
ok "Kiosk service installed and enabled (graphical.target, TTY2 autologin)"
fi
# ── Operator tools: flash/install/TUI (all boards) ──────────────────
# These are useful regardless of kiosk/X11 availability — flash internal
# eMMC/disk from a running system, drop into the console TUI, perform a
# factory reset. See issue #228.
for tool in secubox-flash-disk secubox-flash-emmc secubox-console-tui secubox-factory-reset; do
if [[ -f "${SCRIPT_DIR}/sbin/${tool}" ]]; then
cp "${SCRIPT_DIR}/sbin/${tool}" "${ROOTFS}/usr/sbin/"
chmod +x "${ROOTFS}/usr/sbin/${tool}"
fi
done
# secubox-kiosk-tui: discoverable alias for the kiosk-mode fallback TUI
# (the TTY-side dashboard operators reach when the X11 kiosk is down or
# off). Matches the `kiosk` mode in secubox-mode.
if [[ -f "${ROOTFS}/usr/sbin/secubox-console-tui" ]]; then
ln -sf secubox-console-tui "${ROOTFS}/usr/sbin/secubox-kiosk-tui"
fi
# ── 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.10.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
# ── SecuBox Profile and Status Scripts ─────────────────────────────
log "Installing SecuBox profile scripts..."
# Profile.d script for login status display
mkdir -p "${ROOTFS}/etc/profile.d"
cat > "${ROOTFS}/etc/profile.d/secubox-login.sh" <<'PROFILE'
#!/bin/bash
# SecuBox Login Status Display
# Shows quick system status on interactive shell login
# Only run on interactive terminals
[[ $- != *i* ]] && return
[[ -z "$PS1" ]] && return
# Avoid double display with splash
[[ -n "$SECUBOX_STATUS_SHOWN" ]] && return
export SECUBOX_STATUS_SHOWN=1
# Colors
GOLD='\033[38;5;214m'
CYAN='\033[38;5;45m'
GREEN='\033[38;5;82m'
RED='\033[38;5;196m'
GRAY='\033[38;5;242m'
WHITE='\033[38;5;250m'
RESET='\033[0m'
# Quick status line after MOTD
show_quick_status() {
local ip=$(hostname -I 2>/dev/null | awk '{print $1}')
local services_ok=0
local services_fail=0
for svc in nginx secubox-api nftables; do
if systemctl is-active --quiet "$svc" 2>/dev/null; then
((services_ok++))
else
((services_fail++))
fi
done
# Mode indicator
local mode_icon="🖥️"
local mode_text="Console"
if [[ -f /var/lib/secubox/.kiosk-enabled ]]; then
mode_icon="🖼️"
mode_text="Kiosk"
elif [[ -f /var/lib/secubox/.tui-enabled ]]; then
mode_icon="📟"
mode_text="TUI"
fi
echo ""
echo -e "${GRAY}────────────────────────────────────────────────────────────${RESET}"
echo -e " ${mode_icon} ${CYAN}${mode_text}${RESET} │ ${GREEN}●${RESET} ${services_ok} services │ 🌐 ${CYAN}${ip:-no-ip}${RESET}"
echo -e "${GRAY}────────────────────────────────────────────────────────────${RESET}"
echo -e " ${GRAY}Type ${WHITE}secubox-status${GRAY} for details • ${WHITE}secubox-help${GRAY} for commands${RESET}"
echo ""
}
# Only show on tty login, not in SSH or within screen/tmux
if [[ -z "$SSH_TTY" ]] && [[ -z "$TMUX" ]] && [[ -z "$STY" ]]; then
show_quick_status
fi
PROFILE
chmod +x "${ROOTFS}/etc/profile.d/secubox-login.sh"
# SecuBox help command
cat > "${ROOTFS}/usr/bin/secubox-help" <<'HELP_CMD'
#!/bin/bash
# SecuBox Quick Help
GOLD='\033[38;5;214m'
CYAN='\033[38;5;45m'
WHITE='\033[38;5;250m'
GRAY='\033[38;5;242m'
RESET='\033[0m'
echo -e "${GOLD}"
echo ' ╭─────────────────────────────────────────────────────────╮'
echo ' │ ⚡ SecuBox Quick Commands │'
echo ' ╰─────────────────────────────────────────────────────────╯'
echo -e "${RESET}"
echo -e " ${WHITE}System${RESET}"
echo -e " ${CYAN}secubox-status${GRAY} System overview with services${RESET}"
echo -e " ${CYAN}secubox-logs${GRAY} Live security logs${RESET}"
echo -e " ${CYAN}secubox-services${GRAY} Manage services${RESET}"
echo ""
echo -e " ${WHITE}Network${RESET}"
echo -e " ${CYAN}secubox-network${GRAY} Network configuration${RESET}"
echo -e " ${CYAN}secubox-firewall${GRAY} Firewall rules status${RESET}"
echo ""
echo -e " ${WHITE}Security${RESET}"
echo -e " ${CYAN}secubox-threats${GRAY} View blocked threats${RESET}"
echo -e " ${CYAN}secubox-waf-status${GRAY} WAF inspection status${RESET}"
echo ""
echo -e " ${WHITE}Modes${RESET}"
echo -e " ${CYAN}secubox-mode kiosk${GRAY} Switch to Kiosk GUI${RESET}"
echo -e " ${CYAN}secubox-mode tui${GRAY} Switch to TUI dashboard${RESET}"
echo -e " ${CYAN}secubox-mode console${GRAY} Switch to shell console${RESET}"
echo ""
echo -e " ${GRAY}Web UI: https://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):9443${RESET}"
echo ""
HELP_CMD
chmod +x "${ROOTFS}/usr/bin/secubox-help"
# SecuBox logs command
cat > "${ROOTFS}/usr/bin/secubox-logs" <<'LOGS_CMD'
#!/bin/bash
# SecuBox Live Security Logs
echo "📋 SecuBox Security Logs (Ctrl+C to exit)"
echo "─────────────────────────────────────────"
journalctl -f -u 'secubox-*' -u crowdsec -u suricata -u nginx --no-pager 2>/dev/null || \
journalctl -f --no-pager
LOGS_CMD
chmod +x "${ROOTFS}/usr/bin/secubox-logs"
# SecuBox status command
cat > "${ROOTFS}/usr/bin/secubox-status" <<'STATUS_SCRIPT'
#!/bin/bash
# SecuBox Status - CRT-style system overview
# CyberMind — https://cybermind.fr
# Colors
GOLD='\033[38;5;214m'
CYAN='\033[38;5;45m'
GREEN='\033[38;5;82m'
RED='\033[38;5;196m'
GRAY='\033[38;5;242m'
WHITE='\033[38;5;250m'
RESET='\033[0m'
# Status indicators
ok="${GREEN}●${RESET}"
fail="${RED}●${RESET}"
warn="${GOLD}●${RESET}"
# Header
echo -e "${CYAN}"
echo ' ╭──────────────────────────────────────────────────────────╮'
echo ' │ ⚡ SecuBox System Status ⚡ │'
echo ' ╰──────────────────────────────────────────────────────────╯'
echo -e "${RESET}"
# System info
echo -e "${WHITE} 📊 System Info${RESET}"
echo -e " ${GRAY}Hostname:${RESET} $(hostname)"
echo -e " ${GRAY}Uptime:${RESET} $(uptime -p 2>/dev/null || echo 'N/A')"
echo -e " ${GRAY}Memory:${RESET} $(free -h | awk '/^Mem:/{printf "%s / %s (%.1f%%)", $3, $2, $3/$2*100}')"
echo -e " ${GRAY}Disk:${RESET} $(df -h / | awk 'NR==2{printf "%s / %s (%s)", $3, $2, $5}')"
echo ""
# Network info
echo -e "${WHITE} 🌐 Network${RESET}"
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v '^lo$'); do
ip_addr=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1)
if [[ -n "$ip_addr" ]]; then
echo -e " ${GREEN}●${RESET} ${GRAY}${iface}:${RESET} ${CYAN}${ip_addr}${RESET}"
fi
done
echo ""
# Core services
echo -e "${WHITE} 🔧 Core Services${RESET}"
services=(nginx haproxy secubox-api secubox-hub crowdsec suricata)
for svc in "${services[@]}"; do
if systemctl is-active --quiet "$svc" 2>/dev/null; then
echo -e " ${ok} ${GRAY}${svc}${RESET}"
elif systemctl list-unit-files "${svc}.service" 2>/dev/null | grep -q "$svc"; then
echo -e " ${fail} ${GRAY}${svc}${RESET} (stopped)"
fi
done
echo ""
# Mode detection
echo -e "${WHITE} 🎮 Display Mode${RESET}"
if [[ -f /var/lib/secubox/.kiosk-enabled ]]; then
echo -e " ${ok} ${CYAN}Kiosk GUI${RESET} (Web browser on tty7)"
elif [[ -f /var/lib/secubox/.tui-enabled ]]; then
echo -e " ${ok} ${CYAN}Console TUI${RESET} (Text dashboard on tty1)"
else
echo -e " ${warn} ${CYAN}Console Shell${RESET} (Standard login)"
fi
echo ""
# Quick links
echo -e "${GOLD} ────────────────────────────────────────────────────────────${RESET}"
echo -e "${WHITE} 🔗 Quick Access${RESET}"
IP=$(hostname -I | awk '{print $1}')
echo -e " ${GRAY}Dashboard:${RESET} ${CYAN}https://${IP:-localhost}:9443${RESET}"
echo -e " ${GRAY}Admin API:${RESET} ${CYAN}https://${IP:-localhost}:9443/api/v1/${RESET}"
echo ""
STATUS_SCRIPT
chmod +x "${ROOTFS}/usr/bin/secubox-status"
# SecuBox password reset command
cat > "${ROOTFS}/usr/bin/secubox-passwd" <<'PASSWD_SCRIPT'
#!/bin/bash
# SecuBox Password Reset
set -e
USERS_FILE="/etc/secubox/users.json"
CONF_FILE="/etc/secubox/secubox.conf"
echo "SecuBox Password Reset"
[[ $EUID -ne 0 ]] && echo "Error: Run as root" && exit 1
read -sp "New password for admin: " NEW_PASS && echo
read -sp "Confirm password: " CONFIRM && echo
[[ "$NEW_PASS" != "$CONFIRM" ]] && echo "Passwords don't match" && exit 1
[[ ${#NEW_PASS} -lt 4 ]] && echo "Password too short" && exit 1
NEW_HASH=$(echo -n "$NEW_PASS" | sha256sum | cut -d' ' -f1)
if [[ -f "$USERS_FILE" ]]; then
sed -i "s/\"password_hash\": \"[^\"]*\"/\"password_hash\": \"${NEW_HASH}\"/g" "$USERS_FILE"
echo "Updated users.json"
fi
if [[ -f "$CONF_FILE" ]]; then
sed -i "s/^password = .*/password = \"${NEW_PASS}\"/" "$CONF_FILE"
echo "Updated secubox.conf"
fi
systemctl restart secubox-portal secubox-hub 2>/dev/null || true
echo "Password reset complete!"
PASSWD_SCRIPT
chmod +x "${ROOTFS}/usr/bin/secubox-passwd"
ok "SecuBox profile scripts installed"
# ── CRT-Style Boot Banners ─────────────────────────────────────────
log "Creating boot banners..."
# Pre-login banner (/etc/issue) - ROOT green per Charte §05.
printf '%b' "\e[38;5;29m
██████ ███████ ██████ ██ ██ ██████ ██████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ █████ ██ ██ ██ ██████ ██ ██ ███
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ ███████ ██████ ██████ ██████ ██████ ██ ██
\e[0m
\e[38;5;45m >> CyberMind Security Platform\e[0m \e[38;5;82mv${SECUBOX_VERSION}\e[0m \e[38;5;242m\\\\l @ \\\\n\e[0m
\e[38;5;242m Build: ${BUILD_TIMESTAMP}\e[0m
\e[38;5;250m [user] Default: \e[38;5;214mroot\e[38;5;250m / \e[38;5;214msecubox\e[0m
\e[38;5;250m [web] Web UI: \e[38;5;45mhttps://<IP>:9443\e[0m
\e[38;5;250m [ssh] SSH: \e[38;5;45mport 22\e[0m
\e[38;5;242m─────────────────────────────────────────────────────────────\e[0m
" > "${ROOTFS}/etc/issue"
# Post-login MOTD
printf '%b' "\e[38;5;214m
╔═══════════════════════════════════════════════════════════════╗
║\e[38;5;45m ███████╗███████╗ ██████╗██╗ ██╗██████╗ ██████╗ ██╗ ██╗ \e[38;5;214m║
║\e[38;5;45m ██╔════╝██╔════╝██╔════╝██║ ██║██╔══██╗██╔═══██╗╚██╗██╔╝ \e[38;5;214m║
║\e[38;5;45m ███████╗█████╗ ██║ ██║ ██║██████╔╝██║ ██║ ╚███╔╝ \e[38;5;214m║
║\e[38;5;45m ╚════██║██╔══╝ ██║ ██║ ██║██╔══██╗██║ ██║ ██╔██╗ \e[38;5;214m║
║\e[38;5;45m ███████║███████╗╚██████╗╚██████╔╝██████╔╝╚██████╔╝██╔╝ ██╗ \e[38;5;214m║
║\e[38;5;45m ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ \e[38;5;214m║
║\e[38;5;82m ⚡ eMMC / SD IMAGE ⚡ v${SECUBOX_VERSION} \e[38;5;214m║
╚═══════════════════════════════════════════════════════════════╝\e[0m
\e[38;5;242m Build: ${BUILD_TIMESTAMP}\e[0m
\e[38;5;250m 🌐 Web UI: \e[38;5;45mhttps://<IP>:9443\e[0m
\e[38;5;250m 🔐 Credentials: \e[38;5;214mroot\e[38;5;250m / \e[38;5;214msecubox\e[0m
\e[38;5;250m 📖 Docs: \e[38;5;45mhttps://secubox.in/docs\e[0m
\e[38;5;242m Type \e[38;5;82msecubox-status\e[38;5;242m for system overview\e[0m
" > "${ROOTFS}/etc/motd"
ok "Boot banners installed"
# ── É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="secubox-kiosk"
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="SecuBox"
GRUB_CMDLINE_LINUX_DEFAULT="quiet net.ifnames=1 secubox.mode=kiosk secubox.kiosk=1"
GRUB_CMDLINE_LINUX=""
GRUB_TERMINAL=console
GRUB_DISABLE_OS_PROBER=true
GRUB_DISABLE_SUBMENU=true
EOF
# Create custom GRUB menu with boot modes (40_custom)
# This script generates menu entries dynamically based on available kernels
cat > "${ROOTFS}/etc/grub.d/40_custom" <<'GRUBCUSTOM'
#!/bin/sh
exec tail -n +3 $0
# Custom SecuBox boot menu entries will be generated by 41_secubox
GRUBCUSTOM
chmod +x "${ROOTFS}/etc/grub.d/40_custom"
# Create 41_secubox which generates proper menu entries
cat > "${ROOTFS}/etc/grub.d/41_secubox" <<'GRUB41'
#!/bin/sh
set -e
# Find the latest kernel
KVER=$(ls /boot/vmlinuz-* 2>/dev/null | sort -V | tail -1 | sed 's|/boot/vmlinuz-||')
if [ -z "$KVER" ]; then
exit 0
fi
# Get root UUID
ROOT_UUID=$(blkid -s UUID -o value /dev/disk/by-label/rootfs 2>/dev/null || echo "")
if [ -z "$ROOT_UUID" ]; then
ROOT_DEV="root=LABEL=rootfs"
else
ROOT_DEV="root=UUID=$ROOT_UUID"
fi
cat << EOF
# ══════════════════════════════════════════════════════════════════
# SecuBox Boot Menu
# ══════════════════════════════════════════════════════════════════
menuentry "SecuBox (Kiosk Mode)" --class secubox --class debian --class os \$menuentry_id_option 'secubox-kiosk' {
load_video
insmod gzio
insmod part_gpt
insmod ext2
search --no-floppy --label rootfs --set=root
linux /boot/vmlinuz-${KVER} ${ROOT_DEV} ro quiet splash secubox.mode=kiosk secubox.kiosk=1 net.ifnames=1
initrd /boot/initrd.img-${KVER}
}
menuentry "SecuBox (Console Mode)" --class secubox --class debian --class os \$menuentry_id_option 'secubox-console' {
load_video
insmod gzio
insmod part_gpt
insmod ext2
search --no-floppy --label rootfs --set=root
linux /boot/vmlinuz-${KVER} ${ROOT_DEV} ro secubox.mode=console net.ifnames=1
initrd /boot/initrd.img-${KVER}
}
menuentry "SecuBox (Recovery - Single User)" --class secubox --class debian --class os \$menuentry_id_option 'secubox-recovery' {
load_video
insmod gzio
insmod part_gpt
insmod ext2
search --no-floppy --label rootfs --set=root
linux /boot/vmlinuz-${KVER} ${ROOT_DEV} ro single net.ifnames=1
initrd /boot/initrd.img-${KVER}
}
EOF
GRUB41
chmod +x "${ROOTFS}/etc/grub.d/41_secubox"
# Enable kiosk mode by default (create marker files)
mkdir -p "${ROOTFS}/var/lib/secubox"
touch "${ROOTFS}/var/lib/secubox/.kiosk-enabled"
echo "x11" > "${ROOTFS}/var/lib/secubox/.kiosk-mode"
log " Kiosk mode enabled by default"
ok "GRUB configuré with SecuBox boot menu"
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)
# ESP: 512MB, ROOT: 5.5GB, DATA: remaining (~2GB)
parted -s "${IMG_FILE}" \
mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \
mkpart ROOT ext4 513MiB 6145MiB \
mkpart DATA ext4 6145MiB 100% \
set 1 esp on \
set 1 boot on
else
# GPT pour ARM hardware (boot + rootfs + data)
# boot: 256MB, ROOT: 5.5GB, DATA: remaining
parted -s "${IMG_FILE}" \
mklabel gpt \
mkpart boot fat32 2MiB 258MiB \
mkpart ROOT ext4 258MiB 5890MiB \
mkpart DATA ext4 5890MiB 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"
# Create EFI grub.cfg that chains to main config (fix for VirtualBox/OVMF)
ROOT_UUID=$(chroot "${MNT}" blkid -s UUID -o value /dev/disk/by-label/rootfs 2>/dev/null || \
grep -oP 'root=UUID=\K[^ ]+' "${MNT}/boot/grub/grub.cfg" | head -1)
if [[ -n "$ROOT_UUID" ]]; then
cat > "${MNT}/boot/efi/EFI/BOOT/grub.cfg" << EOFGRUB
# SecuBox GRUB EFI config - search and chain to main config
search --no-floppy --fs-uuid --set=root ${ROOT_UUID}
set prefix=(\$root)/boot/grub
configfile \$prefix/grub.cfg
EOFGRUB
ok "EFI grub.cfg créé (UUID: ${ROOT_UUID})"
else
warn "Impossible de créer EFI grub.cfg - UUID rootfs non trouvé"
fi
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}"
# Use LABEL for reliable root identification (device names change based on connected storage)
ROOT_DEV="LABEL=rootfs"
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}"