feat(multiboot): Add multi-architecture boot system with shared data

Create a multi-boot storage system for Pi Zero Eye Remote that supports:
- ARM64: ESPRESSObin/MOCHAbin via U-Boot boot.scr
- AMD64: Any x86_64 UEFI system via GRUB
- Shared data: Cross-architecture application data on partition 4

Partition layout (16GB+ recommended):
- P1: EFI/FAT32 (512MB) - Boot files for both architectures
- P2: ext4 (3GB) - ARM64 rootfs
- P3: ext4 (3GB) - AMD64 rootfs
- P4: ext4 (remaining) - Shared SecuBox configs, state, logs

Features:
- Automatic bind mounts for /etc/secubox, /var/lib/secubox, /srv/secubox
- eMMC flasher image included in EFI partition
- Debootstrap-based AMD64 rootfs builder
- U-Boot script with USB/MMC detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-04-27 10:13:31 +02:00
parent 52c6b42665
commit 5cf69c0ce1
3 changed files with 924 additions and 0 deletions

100
image/multiboot/README.md Normal file
View File

@ -0,0 +1,100 @@
# SecuBox Multi-Boot Storage System
## Overview
Multi-architecture bootable storage for Pi Zero Eye Remote that supports:
- **ARM64**: ESPRESSObin/MOCHAbin via U-Boot
- **AMD64**: Any x86_64 UEFI system
- **Shared Data**: Cross-architecture application data
## Partition Layout (16GB+ recommended)
| Part | Type | Size | Mount | Purpose |
|------|------|------|-------|---------|
| 1 | EFI (FAT32) | 512MB | /boot/efi | UEFI + U-Boot boot files |
| 2 | ext4 | 3GB | / (ARM64) | SecuBox ARM64 live rootfs |
| 3 | ext4 | 3GB | / (AMD64) | SecuBox AMD64 live rootfs |
| 4 | ext4 | 8GB+ | /srv/data | Shared application data |
## Boot Files (Partition 1)
```
/boot/efi/
├── EFI/
│ └── BOOT/
│ ├── BOOTX64.EFI # GRUB for AMD64
│ └── grub.cfg # GRUB config
├── Image # ARM64 kernel
├── initrd.img # ARM64 initramfs
├── dtbs/ # ARM64 device trees
├── boot.scr # U-Boot script (ARM64)
├── grub/
│ └── grub.cfg # GRUB config (AMD64)
├── vmlinuz # AMD64 kernel
├── initrd-amd64.img # AMD64 initramfs
└── flash/
└── secubox-emmc.img.gz # eMMC flasher image
```
## Shared Data Structure (Partition 4)
```
/srv/data/
├── etc/
│ └── secubox/ # Shared configs
│ ├── api.toml
│ ├── users.json
│ ├── tls/
│ └── modules/
├── var/
│ └── lib/
│ └── secubox/ # Application state
│ ├── crowdsec/
│ ├── haproxy/
│ ├── wireguard/
│ └── dpi/
├── srv/
│ └── secubox/ # Service data
│ ├── mitmproxy/
│ ├── nginx/
│ └── certs/
└── log/
└── secubox/ # Shared logs
```
## Boot Flow
### ARM64 (ESPRESSObin)
1. U-Boot loads `boot.scr` from partition 1
2. Kernel + initrd from partition 1
3. Rootfs from partition 2
4. Mounts partition 4 as /srv/data
5. Bind-mounts shared paths
### AMD64 (UEFI)
1. UEFI loads GRUB from EFI/BOOT/BOOTX64.EFI
2. GRUB loads vmlinuz + initrd from partition 1
3. Rootfs from partition 3
4. Mounts partition 4 as /srv/data
5. Bind-mounts shared paths
### eMMC Flash
From either architecture:
```bash
secubox-flash-emmc # Interactive
# or
gunzip -c /boot/efi/flash/secubox-emmc.img.gz | dd of=/dev/mmcblk0 bs=4M status=progress
```
## Build
```bash
# Build complete multi-boot image
./build-multiboot.sh --size 16G --output secubox-multiboot.img
# Build AMD64 rootfs only
./build-amd64-rootfs.sh --output rootfs-amd64/
# Build ARM64 rootfs only
./build-arm64-rootfs.sh --output rootfs-arm64/
```

View File

@ -0,0 +1,295 @@
#!/usr/bin/env bash
# SecuBox AMD64 Live Rootfs Builder
# Creates Debian bookworm AMD64 rootfs with SecuBox packages
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT_DIR=""
MINIMAL=false
INCLUDE_DESKTOP=false
MIRROR="http://deb.debian.org/debian"
SECUBOX_REPO="https://apt.secubox.in"
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Build SecuBox AMD64 rootfs
Options:
-o, --output DIR Output directory for rootfs
--minimal Minimal installation (no SecuBox packages)
--desktop Include desktop environment
--mirror URL Debian mirror (default: $MIRROR)
-h, --help Show this help
EOF
exit 0
}
log() { echo "[$(date '+%H:%M:%S')] $*"; }
err() { echo "[ERROR] $*" >&2; exit 1; }
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output) OUTPUT_DIR="$2"; shift 2 ;;
--minimal) MINIMAL=true; shift ;;
--desktop) INCLUDE_DESKTOP=true; shift ;;
--mirror) MIRROR="$2"; shift 2 ;;
-h|--help) usage ;;
*) err "Unknown option: $1" ;;
esac
done
[[ -z "$OUTPUT_DIR" ]] && err "Output directory required (-o)"
}
check_deps() {
local deps=(debootstrap chroot)
for cmd in "${deps[@]}"; do
command -v "$cmd" &>/dev/null || err "Missing: $cmd"
done
[[ $EUID -eq 0 ]] || err "Must run as root"
}
bootstrap_rootfs() {
log "Bootstrapping Debian bookworm AMD64..."
mkdir -p "$OUTPUT_DIR"
debootstrap --arch=amd64 \
--include=systemd,systemd-sysv,dbus,locales,apt-transport-https,ca-certificates,curl,gnupg \
bookworm "$OUTPUT_DIR" "$MIRROR"
}
configure_rootfs() {
log "Configuring rootfs..."
# Set hostname
echo "secubox-amd64" > "$OUTPUT_DIR/etc/hostname"
# Configure hosts
cat > "$OUTPUT_DIR/etc/hosts" <<EOF
127.0.0.1 localhost
127.0.1.1 secubox-amd64
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
# Configure locales
echo "en_US.UTF-8 UTF-8" > "$OUTPUT_DIR/etc/locale.gen"
chroot "$OUTPUT_DIR" locale-gen
# Configure timezone
ln -sf /usr/share/zoneinfo/UTC "$OUTPUT_DIR/etc/localtime"
# Configure apt sources
cat > "$OUTPUT_DIR/etc/apt/sources.list" <<EOF
deb $MIRROR bookworm main contrib non-free non-free-firmware
deb $MIRROR bookworm-updates main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
EOF
# Configure network
cat > "$OUTPUT_DIR/etc/network/interfaces" <<EOF
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
# Create fstab
cat > "$OUTPUT_DIR/etc/fstab" <<EOF
# <file system> <mount point> <type> <options> <dump> <pass>
LABEL=secubox-amd64 / ext4 errors=remount-ro 0 1
LABEL=SECUBOX-EFI /boot/efi vfat umask=0077 0 1
EOF
}
install_kernel() {
log "Installing Linux kernel..."
chroot "$OUTPUT_DIR" apt-get update
chroot "$OUTPUT_DIR" apt-get install -y \
linux-image-amd64 \
linux-headers-amd64 \
firmware-linux \
firmware-linux-nonfree
}
install_base_packages() {
log "Installing base packages..."
chroot "$OUTPUT_DIR" apt-get install -y \
openssh-server \
sudo \
vim \
nano \
htop \
tmux \
git \
wget \
net-tools \
iproute2 \
iputils-ping \
dnsutils \
tcpdump \
iptables \
nftables \
nginx \
python3 \
python3-pip \
python3-venv \
python3-uvicorn \
python3-fastapi \
python3-jose \
python3-httpx \
python3-psutil \
python3-jinja2
}
add_secubox_repo() {
if [[ "$MINIMAL" == "true" ]]; then
log "Skipping SecuBox repo (minimal mode)"
return
fi
log "Adding SecuBox repository..."
# Add SecuBox GPG key
mkdir -p "$OUTPUT_DIR/etc/apt/keyrings"
curl -fsSL "${SECUBOX_REPO}/gpg.key" | gpg --dearmor -o "$OUTPUT_DIR/etc/apt/keyrings/secubox.gpg" 2>/dev/null || true
# Add SecuBox repo
cat > "$OUTPUT_DIR/etc/apt/sources.list.d/secubox.list" <<EOF
deb [signed-by=/etc/apt/keyrings/secubox.gpg] ${SECUBOX_REPO} bookworm main
EOF
chroot "$OUTPUT_DIR" apt-get update || true
}
install_secubox_packages() {
if [[ "$MINIMAL" == "true" ]]; then
log "Skipping SecuBox packages (minimal mode)"
return
fi
log "Installing SecuBox packages..."
# Install core SecuBox packages
chroot "$OUTPUT_DIR" apt-get install -y \
secubox-core \
secubox-hub \
secubox-haproxy \
secubox-crowdsec \
secubox-system \
secubox-hardening \
secubox-ipblock \
|| log "WARNING: Some SecuBox packages failed to install"
}
install_desktop() {
if [[ "$INCLUDE_DESKTOP" != "true" ]]; then
return
fi
log "Installing desktop environment..."
chroot "$OUTPUT_DIR" apt-get install -y \
xorg \
xfce4 \
xfce4-terminal \
lightdm \
firefox-esr \
|| log "WARNING: Desktop installation incomplete"
}
setup_users() {
log "Setting up users..."
# Set root password
echo "root:secubox" | chroot "$OUTPUT_DIR" chpasswd
# Create secubox user
chroot "$OUTPUT_DIR" useradd -m -s /bin/bash -G sudo secubox || true
echo "secubox:secubox" | chroot "$OUTPUT_DIR" chpasswd
# Allow sudo without password for secubox
echo "secubox ALL=(ALL) NOPASSWD:ALL" > "$OUTPUT_DIR/etc/sudoers.d/secubox"
chmod 440 "$OUTPUT_DIR/etc/sudoers.d/secubox"
}
setup_boot() {
log "Setting up boot configuration..."
# Install GRUB for UEFI
chroot "$OUTPUT_DIR" apt-get install -y grub-efi-amd64 || true
# Create boot directory structure
mkdir -p "$OUTPUT_DIR/boot/efi"
}
setup_secubox_branding() {
log "Adding SecuBox branding..."
# MOTD
cat > "$OUTPUT_DIR/etc/motd" <<'MOTD'
██████ ███████ ██████ ██ ██ ██████ ██████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ █████ ██ ██ ██ ██████ ██ ██ ███
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ ███████ ██████ ██████ ██████ ██████ ██ ██
SecuBox AMD64 Live System
https://secubox.in
MOTD
# Issue
cat > "$OUTPUT_DIR/etc/issue" <<'ISSUE'
SecuBox AMD64 \n \l
ISSUE
}
cleanup_rootfs() {
log "Cleaning up rootfs..."
chroot "$OUTPUT_DIR" apt-get clean
rm -rf "$OUTPUT_DIR/var/lib/apt/lists/"*
rm -rf "$OUTPUT_DIR/tmp/"*
rm -f "$OUTPUT_DIR/var/log/"*.log
}
main() {
parse_args "$@"
check_deps
log "Building SecuBox AMD64 Rootfs"
log "Output: $OUTPUT_DIR"
log "Minimal: $MINIMAL"
bootstrap_rootfs
configure_rootfs
install_kernel
install_base_packages
add_secubox_repo
install_secubox_packages
install_desktop
setup_users
setup_boot
setup_secubox_branding
cleanup_rootfs
log "============================================"
log "AMD64 Rootfs Complete: $OUTPUT_DIR"
log "============================================"
}
main "$@"

View File

@ -0,0 +1,529 @@
#!/usr/bin/env bash
# SecuBox Multi-Boot Storage Builder
# Creates bootable image for ARM64 + AMD64 with shared data
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT_DIR="${REPO_ROOT}/output"
CACHE_DIR="${REPO_ROOT}/cache"
# Defaults
IMAGE_SIZE="16G"
OUTPUT_FILE=""
ARM64_ROOTFS=""
AMD64_ROOTFS=""
EMMC_IMAGE=""
INCLUDE_EMMC=true
VERBOSE=false
# Partition sizes (in MB)
EFI_SIZE=512
ARM64_SIZE=3072
AMD64_SIZE=3072
# DATA_SIZE = remaining space
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Build SecuBox multi-boot storage image (ARM64 + AMD64 + Shared Data)
Options:
-s, --size SIZE Image size (default: 16G)
-o, --output FILE Output image file
--arm64-rootfs DIR ARM64 rootfs directory
--amd64-rootfs DIR AMD64 rootfs directory
--emmc-image FILE eMMC image to include for flashing
--no-emmc Don't include eMMC flasher image
-v, --verbose Verbose output
-h, --help Show this help
Examples:
$(basename "$0") -s 16G -o multiboot.img
$(basename "$0") --arm64-rootfs /path/to/arm64 --amd64-rootfs /path/to/amd64
EOF
exit 0
}
log() { echo "[$(date '+%H:%M:%S')] $*"; }
err() { echo "[ERROR] $*" >&2; exit 1; }
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-s|--size) IMAGE_SIZE="$2"; shift 2 ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
--arm64-rootfs) ARM64_ROOTFS="$2"; shift 2 ;;
--amd64-rootfs) AMD64_ROOTFS="$2"; shift 2 ;;
--emmc-image) EMMC_IMAGE="$2"; shift 2 ;;
--no-emmc) INCLUDE_EMMC=false; shift ;;
-v|--verbose) VERBOSE=true; shift ;;
-h|--help) usage ;;
*) err "Unknown option: $1" ;;
esac
done
[[ -z "$OUTPUT_FILE" ]] && OUTPUT_FILE="${OUTPUT_DIR}/secubox-multiboot-$(date +%Y%m%d).img"
}
check_deps() {
local deps=(parted mkfs.vfat mkfs.ext4 debootstrap grub-mkimage losetup)
for cmd in "${deps[@]}"; do
command -v "$cmd" &>/dev/null || err "Missing: $cmd"
done
[[ $EUID -eq 0 ]] || err "Must run as root"
}
create_image() {
log "Creating ${IMAGE_SIZE} image: ${OUTPUT_FILE}"
mkdir -p "$(dirname "$OUTPUT_FILE")"
truncate -s "$IMAGE_SIZE" "$OUTPUT_FILE"
}
partition_image() {
log "Partitioning image..."
parted -s "$OUTPUT_FILE" mklabel gpt
local start=1
local end=$((start + EFI_SIZE))
# Partition 1: EFI System Partition
parted -s "$OUTPUT_FILE" mkpart ESP fat32 ${start}MiB ${end}MiB
parted -s "$OUTPUT_FILE" set 1 esp on
parted -s "$OUTPUT_FILE" set 1 boot on
start=$end
end=$((start + ARM64_SIZE))
# Partition 2: ARM64 rootfs
parted -s "$OUTPUT_FILE" mkpart arm64-root ext4 ${start}MiB ${end}MiB
start=$end
end=$((start + AMD64_SIZE))
# Partition 3: AMD64 rootfs
parted -s "$OUTPUT_FILE" mkpart amd64-root ext4 ${start}MiB ${end}MiB
start=$end
# Partition 4: Shared data (rest of disk)
parted -s "$OUTPUT_FILE" mkpart shared-data ext4 ${start}MiB 100%
parted -s "$OUTPUT_FILE" print
}
setup_loop() {
log "Setting up loop device..."
LOOP_DEV=$(losetup -f --show -P "$OUTPUT_FILE")
log "Loop device: $LOOP_DEV"
sleep 1
}
format_partitions() {
log "Formatting partitions..."
mkfs.vfat -F 32 -n SECUBOX-EFI "${LOOP_DEV}p1"
mkfs.ext4 -L secubox-arm64 "${LOOP_DEV}p2"
mkfs.ext4 -L secubox-amd64 "${LOOP_DEV}p3"
mkfs.ext4 -L secubox-data "${LOOP_DEV}p4"
}
mount_partitions() {
log "Mounting partitions..."
MNT_EFI=$(mktemp -d)
MNT_ARM64=$(mktemp -d)
MNT_AMD64=$(mktemp -d)
MNT_DATA=$(mktemp -d)
mount "${LOOP_DEV}p1" "$MNT_EFI"
mount "${LOOP_DEV}p2" "$MNT_ARM64"
mount "${LOOP_DEV}p3" "$MNT_AMD64"
mount "${LOOP_DEV}p4" "$MNT_DATA"
}
install_efi_boot() {
log "Installing EFI boot files..."
mkdir -p "$MNT_EFI/EFI/BOOT"
mkdir -p "$MNT_EFI/grub"
mkdir -p "$MNT_EFI/dtbs/marvell"
mkdir -p "$MNT_EFI/flash"
# Install GRUB for AMD64 UEFI
if command -v grub-mkimage &>/dev/null; then
grub-mkimage -o "$MNT_EFI/EFI/BOOT/BOOTX64.EFI" \
-O x86_64-efi \
-p /grub \
part_gpt part_msdos fat ext2 normal linux boot \
configfile loopback chain efifwsetup efi_gop \
efi_uga ls search search_label search_fs_uuid \
search_fs_file gfxterm gfxterm_background gfxmenu test all_video
fi
# GRUB config for AMD64
cat > "$MNT_EFI/grub/grub.cfg" <<'GRUBCFG'
set timeout=5
set default=0
menuentry "SecuBox AMD64 Live" {
linux /vmlinuz root=LABEL=secubox-amd64 ro quiet
initrd /initrd-amd64.img
}
menuentry "SecuBox AMD64 Live (Recovery)" {
linux /vmlinuz root=LABEL=secubox-amd64 ro single
initrd /initrd-amd64.img
}
menuentry "Flash SecuBox to eMMC (ARM64 only)" {
echo "This option is for ARM64 systems only"
echo "Boot the ARM64 system and run: secubox-flash-emmc"
sleep 5
}
GRUBCFG
# Copy GRUB config to EFI/BOOT as well
cp "$MNT_EFI/grub/grub.cfg" "$MNT_EFI/EFI/BOOT/"
}
install_uboot_boot() {
log "Installing U-Boot boot files for ARM64..."
# Create boot.scr for ARM64 (ESPRESSObin)
cat > /tmp/boot.cmd <<'BOOTCMD'
# SecuBox Multi-Boot U-Boot Script
# For ESPRESSObin/MOCHAbin ARM64
echo "============================================"
echo "SecuBox Multi-Boot — ARM64"
echo "============================================"
# Detect boot device
if test "${devtype}" = "usb"; then
setenv bootpart "usb 0:1"
setenv rootpart "/dev/sda2"
echo "Booting from USB storage"
elif test "${devtype}" = "mmc"; then
setenv bootpart "mmc ${devnum}:1"
setenv rootpart "/dev/mmcblk${devnum}p2"
echo "Booting from MMC/SD"
else
# Default to USB
usb start
setenv bootpart "usb 0:1"
setenv rootpart "/dev/sda2"
fi
# Load kernel
echo "Loading ARM64 kernel..."
load ${bootpart} ${kernel_addr_r} Image
# Load device tree
echo "Loading device tree..."
if load ${bootpart} ${fdt_addr_r} dtbs/marvell/armada-3720-espressobin-v7-emmc.dtb; then
echo "Loaded ESPRESSObin v7 eMMC DTB"
else
load ${bootpart} ${fdt_addr_r} dtbs/marvell/armada-3720-espressobin-v7.dtb
fi
# Load initramfs
echo "Loading initramfs..."
if load ${bootpart} ${ramdisk_addr_r} initrd.img; then
setenv initrd_size ${filesize}
setenv use_initrd 1
else
setenv use_initrd 0
fi
# Boot arguments
# Data partition is partition 4
setenv bootargs "root=${rootpart} rootfstype=ext4 rootwait rootdelay=5 console=ttyMV0,115200 net.ifnames=0 secubox.data=/dev/sda4"
echo "Booting SecuBox ARM64..."
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
# Compile boot.scr if mkimage available
if command -v mkimage &>/dev/null; then
mkimage -A arm64 -T script -C none -d /tmp/boot.cmd "$MNT_EFI/boot.scr"
else
cp /tmp/boot.cmd "$MNT_EFI/boot.cmd"
fi
}
install_arm64_rootfs() {
log "Installing ARM64 rootfs..."
if [[ -n "$ARM64_ROOTFS" && -d "$ARM64_ROOTFS" ]]; then
log "Copying from $ARM64_ROOTFS"
rsync -aHAX "$ARM64_ROOTFS/" "$MNT_ARM64/"
else
# Check for existing built image
local arm64_img="${OUTPUT_DIR}/secubox-espressobin-v7-bookworm.img"
if [[ -f "$arm64_img" ]]; then
log "Extracting rootfs from $arm64_img"
local tmp_loop=$(losetup -f --show -P "$arm64_img")
local tmp_mnt=$(mktemp -d)
mount "${tmp_loop}p2" "$tmp_mnt"
rsync -aHAX "$tmp_mnt/" "$MNT_ARM64/"
umount "$tmp_mnt"
losetup -d "$tmp_loop"
rmdir "$tmp_mnt"
# Also copy kernel/initrd/dtbs to EFI partition
mount "${tmp_loop}p1" "$tmp_mnt" 2>/dev/null || true
if [[ -f "$tmp_mnt/Image" ]]; then
cp "$tmp_mnt/Image" "$MNT_EFI/"
cp "$tmp_mnt/initrd.img" "$MNT_EFI/" 2>/dev/null || true
cp -r "$tmp_mnt/dtbs" "$MNT_EFI/" 2>/dev/null || true
fi
umount "$tmp_mnt" 2>/dev/null || true
else
log "WARNING: No ARM64 rootfs source found, partition will be empty"
fi
fi
# Setup shared data mounts in fstab
setup_shared_mounts "$MNT_ARM64"
}
install_amd64_rootfs() {
log "Installing AMD64 rootfs..."
if [[ -n "$AMD64_ROOTFS" && -d "$AMD64_ROOTFS" ]]; then
log "Copying from $AMD64_ROOTFS"
rsync -aHAX "$AMD64_ROOTFS/" "$MNT_AMD64/"
else
# Build AMD64 rootfs with debootstrap
log "Building AMD64 rootfs with debootstrap..."
"${SCRIPT_DIR}/build-amd64-rootfs.sh" --output "$MNT_AMD64" --minimal
fi
# Copy AMD64 kernel to EFI partition
if [[ -f "$MNT_AMD64/boot/vmlinuz-"* ]]; then
cp "$MNT_AMD64/boot/vmlinuz-"* "$MNT_EFI/vmlinuz"
cp "$MNT_AMD64/boot/initrd.img-"* "$MNT_EFI/initrd-amd64.img" 2>/dev/null || true
fi
# Setup shared data mounts in fstab
setup_shared_mounts "$MNT_AMD64"
}
setup_shared_mounts() {
local rootfs="$1"
log "Setting up shared data mounts for $rootfs"
# Create mount points
mkdir -p "$rootfs/srv/data"
# Add to fstab
cat >> "$rootfs/etc/fstab" <<'FSTAB'
# SecuBox Shared Data Partition
LABEL=secubox-data /srv/data ext4 defaults,noatime 0 2
FSTAB
# Create systemd mount for bind mounts
mkdir -p "$rootfs/etc/systemd/system"
cat > "$rootfs/etc/systemd/system/secubox-shared-data.service" <<'SERVICE'
[Unit]
Description=SecuBox Shared Data Bind Mounts
After=srv-data.mount
Requires=srv-data.mount
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/secubox-mount-shared
[Install]
WantedBy=multi-user.target
SERVICE
# Create mount script
mkdir -p "$rootfs/usr/local/bin"
cat > "$rootfs/usr/local/bin/secubox-mount-shared" <<'SCRIPT'
#!/bin/bash
# Mount shared SecuBox data directories
DATA_ROOT="/srv/data"
# Create shared directories if they don't exist
mkdir -p "$DATA_ROOT"/{etc/secubox,var/lib/secubox,srv/secubox,log/secubox}
# Bind mount shared configs
mkdir -p /etc/secubox
mount --bind "$DATA_ROOT/etc/secubox" /etc/secubox
# Bind mount application state
mkdir -p /var/lib/secubox
mount --bind "$DATA_ROOT/var/lib/secubox" /var/lib/secubox
# Bind mount service data
mkdir -p /srv/secubox
mount --bind "$DATA_ROOT/srv/secubox" /srv/secubox
# Bind mount logs
mkdir -p /var/log/secubox
mount --bind "$DATA_ROOT/log/secubox" /var/log/secubox
echo "SecuBox shared data mounted"
SCRIPT
chmod +x "$rootfs/usr/local/bin/secubox-mount-shared"
# Enable the service
ln -sf ../secubox-shared-data.service "$rootfs/etc/systemd/system/multi-user.target.wants/" 2>/dev/null || true
}
setup_shared_data() {
log "Setting up shared data partition structure..."
mkdir -p "$MNT_DATA"/{etc/secubox,var/lib/secubox,srv/secubox,log/secubox}
mkdir -p "$MNT_DATA/etc/secubox"/{tls,modules}
mkdir -p "$MNT_DATA/var/lib/secubox"/{crowdsec,haproxy,wireguard,dpi,hub}
mkdir -p "$MNT_DATA/srv/secubox"/{mitmproxy,nginx,certs}
# Create default configs
cat > "$MNT_DATA/etc/secubox/api.toml" <<'TOML'
# SecuBox API Configuration (Shared)
[api]
jwt_secret = "" # Generated on first boot
[logging]
level = "INFO"
TOML
# Create empty users.json
echo '{}' > "$MNT_DATA/etc/secubox/users.json"
# Set permissions
chmod 750 "$MNT_DATA/etc/secubox"
chmod 750 "$MNT_DATA/var/lib/secubox"
}
install_emmc_flasher() {
if [[ "$INCLUDE_EMMC" != "true" ]]; then
log "Skipping eMMC flasher image"
return
fi
log "Installing eMMC flasher image..."
mkdir -p "$MNT_EFI/flash"
if [[ -n "$EMMC_IMAGE" && -f "$EMMC_IMAGE" ]]; then
if [[ "$EMMC_IMAGE" == *.gz ]]; then
cp "$EMMC_IMAGE" "$MNT_EFI/flash/secubox-emmc.img.gz"
else
gzip -c "$EMMC_IMAGE" > "$MNT_EFI/flash/secubox-emmc.img.gz"
fi
else
# Look for existing image
local emmc_img="${OUTPUT_DIR}/secubox-espressobin-v7-bookworm.img"
if [[ -f "$emmc_img" ]]; then
log "Compressing $emmc_img for eMMC flash..."
gzip -c "$emmc_img" > "$MNT_EFI/flash/secubox-emmc.img.gz"
else
log "WARNING: No eMMC image found, flash directory will be empty"
fi
fi
# Create flash script
cat > "$MNT_EFI/flash/flash-emmc.sh" <<'FLASH'
#!/bin/bash
# SecuBox eMMC Flasher
set -e
EMMC_DEV="/dev/mmcblk0"
IMG="/boot/efi/flash/secubox-emmc.img.gz"
echo "============================================"
echo "SecuBox eMMC Flasher"
echo "============================================"
if [[ ! -f "$IMG" ]]; then
echo "ERROR: Image not found: $IMG"
exit 1
fi
if [[ ! -b "$EMMC_DEV" ]]; then
echo "ERROR: eMMC device not found: $EMMC_DEV"
echo "Available block devices:"
lsblk
exit 1
fi
echo "WARNING: This will erase all data on $EMMC_DEV"
echo "Image: $IMG"
echo ""
read -p "Continue? [y/N] " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "Aborted."
exit 0
fi
echo "Flashing to $EMMC_DEV..."
gunzip -c "$IMG" | dd of="$EMMC_DEV" bs=4M status=progress conv=fsync
echo "Syncing..."
sync
echo "============================================"
echo "Flash complete! You can now boot from eMMC."
echo "============================================"
FLASH
chmod +x "$MNT_EFI/flash/flash-emmc.sh"
}
cleanup() {
log "Cleaning up..."
umount "$MNT_EFI" 2>/dev/null || true
umount "$MNT_ARM64" 2>/dev/null || true
umount "$MNT_AMD64" 2>/dev/null || true
umount "$MNT_DATA" 2>/dev/null || true
[[ -n "${LOOP_DEV:-}" ]] && losetup -d "$LOOP_DEV" 2>/dev/null || true
rmdir "$MNT_EFI" "$MNT_ARM64" "$MNT_AMD64" "$MNT_DATA" 2>/dev/null || true
}
main() {
parse_args "$@"
check_deps
trap cleanup EXIT
log "Building SecuBox Multi-Boot Image"
log "Size: $IMAGE_SIZE"
log "Output: $OUTPUT_FILE"
create_image
partition_image
setup_loop
format_partitions
mount_partitions
install_efi_boot
install_uboot_boot
install_arm64_rootfs
install_amd64_rootfs
setup_shared_data
install_emmc_flasher
log "============================================"
log "Multi-Boot Image Complete: $OUTPUT_FILE"
log "============================================"
log "Partitions:"
log " 1: EFI/Boot (FAT32) - UEFI + U-Boot"
log " 2: ARM64 rootfs (ext4)"
log " 3: AMD64 rootfs (ext4)"
log " 4: Shared data (ext4)"
}
main "$@"