particleos-installer/build-iso-bootc-xorriso.sh

736 lines
No EOL
28 KiB
Bash
Executable file

#!/bin/bash
# ParticleOS ISO Builder with bootc + xorriso
# Combines modern container-based system management with proven ISO creation
# This approach uses bootc for system definition and xorriso for ISO creation
set -euo pipefail
# Enable logging with timestamped directory
BUILD_TIMESTAMP=$(date +"%y%m%d%H%M")
LOG_DIR="logs/${BUILD_TIMESTAMP}"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/build-iso-bootc-xorriso.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "$(date): Starting ParticleOS ISO build with bootc + xorriso"
echo "$(date): Log directory: $LOG_DIR"
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
print_header() {
echo ""
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}================================${NC}"
}
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_NAME="particleos"
VERSION="1.0.0"
BUILD_TIMESTAMP=$(date +"%y%m%d-%H%M")
BUILD_DIR="$SCRIPT_DIR/build"
OUTPUT_DIR="$SCRIPT_DIR/output"
CHROOT_DIR="$BUILD_DIR/chroot"
ISO_DIR="$BUILD_DIR/iso"
SYSTEM_IMAGE="particleos-system.oci"
# apt-cacher-ng configuration
APT_CACHER_NG_HOST="192.168.1.79"
APT_CACHER_NG_PORT="3142"
APT_CACHER_NG_URL="http://${APT_CACHER_NG_HOST}:${APT_CACHER_NG_PORT}"
# Variables to track cacher usage
CACHER_MMDEBSTRAP_USED="No (Fallback to direct)"
CACHER_CHROOT_APT_USED="No (Fallback to direct)"
# Safety check: Ensure BUILD_DIR is within SCRIPT_DIR
if [[ ! "$BUILD_DIR" =~ ^"$SCRIPT_DIR" ]]; then
print_error "BUILD_DIR ($BUILD_DIR) is not within SCRIPT_DIR ($SCRIPT_DIR). Aborting for safety."
fi
# Check if apt-cacher-ng is reachable
check_proxy_reachable() {
print_status "Checking if apt-cacher-ng at $APT_CACHER_NG_HOST:$APT_CACHER_NG_PORT is reachable..."
# Use netcat to check if the port is open
if nc -zv "$APT_CACHER_NG_HOST" "$APT_CACHER_NG_PORT" &>/dev/null; then
print_success "apt-cacher-ng at $APT_CACHER_NG_HOST:$APT_CACHER_NG_PORT is reachable."
return 0 # Reachable
else
print_warning "apt-cacher-ng at $APT_CACHER_NG_HOST:$APT_CACHER_NG_PORT is NOT reachable."
return 1 # Not reachable
fi
}
# Check prerequisites
check_prerequisites() {
print_header "Phase 1: Check Prerequisites"
# Check for bootc
if ! command -v bootc &> /dev/null; then
print_error "bootc is not installed. Please install bootc first."
fi
# Check for ostree
if ! command -v ostree &> /dev/null; then
print_error "ostree is not installed. Please install ostree first."
fi
# Check for podman
if ! command -v podman &> /dev/null; then
print_error "podman is not installed. Please install podman first."
fi
# Check for build tools
local missing_packages=()
for package in mmdebstrap squashfs-tools xorriso grub-pc-bin grub-efi-amd64-bin isolinux syslinux-common netcat-openbsd; do
if ! dpkg -s "$package" &>/dev/null; then
missing_packages+=("$package")
fi
done
if [ ${#missing_packages[@]} -gt 0 ]; then
print_status "Installing missing packages: ${missing_packages[*]}"
sudo apt update
sudo apt install -y "${missing_packages[@]}"
fi
# Check disk space (need at least 10GB free)
local available_space=$(df "$SCRIPT_DIR" | awk 'NR==2 {print $4}')
local required_space=$((10 * 1024 * 1024)) # 10GB in KB
if [ "$available_space" -lt "$required_space" ]; then
print_error "Insufficient disk space. Need at least 10GB free, have $(($available_space / 1024 / 1024))GB"
fi
# Check network connectivity
if ! ping -c 1 archive.ubuntu.com &>/dev/null; then
print_error "Cannot reach Ubuntu archives. Check your internet connection."
fi
print_success "All prerequisites satisfied"
}
# Clean build environment
clean_build() {
print_header "Phase 2: Clean Build Environment"
# Clean build directories
if [ -d "$BUILD_DIR" ]; then
print_status "Removing previous build directory: $BUILD_DIR..."
if [[ "$BUILD_DIR" == "/" ]] || [[ "$BUILD_DIR" == "/home" ]] || [[ "$BUILD_DIR" == "/opt" ]] || \
[[ "$BUILD_DIR" == "/usr" ]] || [[ "$BUILD_DIR" == "/var" ]] || [[ "$BUILD_DIR" == "/etc" ]]; then
print_error "BUILD_DIR ($BUILD_DIR) is a critical system directory. Aborting for safety."
fi
# Force unmount any remaining mounts first
sudo umount -f "$CHROOT_DIR/dev" 2>/dev/null || true
sudo umount -f "$CHROOT_DIR/run" 2>/dev/null || true
sudo umount -f "$CHROOT_DIR/proc" 2>/dev/null || true
sudo umount -f "$CHROOT_DIR/sys" 2>/dev/null || true
sudo rm -rf "$BUILD_DIR" || print_error "Failed to remove previous build directory."
fi
mkdir -p "$BUILD_DIR" "$CHROOT_DIR" "$ISO_DIR" "$OUTPUT_DIR" || print_error "Failed to create build directories."
chmod 755 "$OUTPUT_DIR" || print_error "Failed to set permissions on output directory."
print_success "Build environment cleaned"
}
# Create base system using mmdebstrap (similar to current approach)
create_base_system() {
print_header "Phase 3: Create Base System"
# Try to use apt-cacher-ng if available for mmdebstrap
if check_proxy_reachable; then
print_status "Attempting mmdebstrap with apt-cacher-ng: $APT_CACHER_NG_URL"
export http_proxy="$APT_CACHER_NG_URL/"
export https_proxy="$APT_CACHER_NG_URL/"
CACHER_MMDEBSTRAP_USED="Yes"
else
print_warning "apt-cacher-ng not reachable. mmdebstrap will proceed without proxy."
# Ensure proxy variables are unset if they were set previously
unset http_proxy
unset https_proxy
CACHER_MMDEBSTRAP_USED="No (Fallback to direct)"
fi
print_status "Creating base Ubuntu system using mmdebstrap..."
# Create base system with mmdebstrap
mmdebstrap \
--architectures=amd64 \
--variant=apt \
--include=systemd,systemd-sysv,dbus,curl,ca-certificates,gnupg,gpgv,locales,resolvconf \
--mode=unshare \
noble \
"$CHROOT_DIR" \
http://archive.ubuntu.com/ubuntu/ || print_error "mmdebstrap failed."
# Unset the proxy variables after mmdebstrap completes to avoid affecting subsequent commands outside the chroot
unset http_proxy
unset https_proxy
print_success "Base system created"
}
# Configure base system with bootc integration
configure_base_system() {
print_header "Phase 4: Configure Base System"
print_status "Configuring base system with bootc integration..."
# Mount necessary filesystems
sudo mount --bind /dev "$CHROOT_DIR/dev"
sudo mount --bind /run "$CHROOT_DIR/run"
sudo mount -t proc none "$CHROOT_DIR/proc"
sudo mount -t sysfs none "$CHROOT_DIR/sys"
# Configure package sources
sudo chroot "$CHROOT_DIR" bash -c '
echo "deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse" > /etc/apt/sources.list && \
echo "deb http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse" >> /etc/apt/sources.list && \
echo "deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse" >> /etc/apt/sources.list
' || print_error "Failed to configure APT sources."
# Configure APT proxy for chroot, only if cacher is reachable
if check_proxy_reachable; then
print_status "Configuring chroot APT to use apt-cacher-ng proxy: $APT_CACHER_NG_URL"
sudo chroot "$CHROOT_DIR" bash -c "cat > \"/etc/apt/apt.conf.d/01proxy\" << EOF
Acquire::http::Proxy \"$APT_CACHER_NG_URL\";
Acquire::https::Proxy \"$APT_CACHER_NG_URL\";
EOF" || print_error "Failed to configure APT proxy in chroot."
CACHER_CHROOT_APT_USED="Yes"
else
print_warning "apt-cacher-ng not reachable. Chroot APT will connect directly."
CACHER_CHROOT_APT_USED="No (Fallback to direct)"
# Ensure no proxy config file exists in case of previous failed runs
sudo chroot "$CHROOT_DIR" rm -f /etc/apt/apt.conf.d/01proxy || true
fi
# Block snapd installation with APT preferences
print_status "Configuring APT preferences to block snapd..."
sudo chroot "$CHROOT_DIR" bash -c 'cat > "/etc/apt/preferences.d/no-snapd" << "EOF"
Package: snapd
Pin: release *
Pin-Priority: -1
Package: snap
Pin: release *
Pin-Priority: -1
Package: firefox*
Pin: release o=Ubuntu*
Pin-Priority: -1
EOF' || print_error "Failed to configure APT preferences."
# Install gpgv first for secure package verification
print_status "Installing gpgv for secure package verification..."
sudo chroot "$CHROOT_DIR" bash -c 'DEBIAN_FRONTEND=noninteractive apt update --allow-unauthenticated' || print_error "Failed to update APT lists."
sudo chroot "$CHROOT_DIR" bash -c 'DEBIAN_FRONTEND=noninteractive apt install -y --allow-unauthenticated gpgv' || print_error "Failed to install gpgv."
# Update package lists securely
sudo chroot "$CHROOT_DIR" apt update || print_error "Failed to update package lists."
# Install bootc and ostree packages
print_status "Installing bootc and ostree packages..."
sudo chroot "$CHROOT_DIR" bash -c 'DEBIAN_FRONTEND=noninteractive apt install -y \
initramfs-tools \
podman \
skopeo \
buildah \
conmon \
crun \
netavark \
aardvark-dns \
slirp4netns \
fuse-overlayfs \
uidmap \
libsubid4 \
libslirp0 \
passt \
thin-provisioning-tools \
lvm2 \
mdadm \
dmeventd \
libaio1t64 \
libdevmapper-event1.02.1 \
liblvm2cmd2.03 \
kpartx \
pigz \
live-boot \
live-config \
casper \
grub-efi-amd64-signed' || print_error "Failed to install bootc dependencies and live components."
# Install desktop environment
print_status "Installing desktop environment..."
sudo chroot "$CHROOT_DIR" bash -c 'DEBIAN_FRONTEND=noninteractive apt install -y \
plasma-desktop \
plasma-workspace \
sddm \
kwin-x11 \
flatpak \
network-manager \
plasma-nm \
openssh-server \
curl \
wget \
vim \
nano \
htop \
neofetch \
tree \
pulseaudio \
pulseaudio-utils \
fonts-ubuntu \
fonts-noto \
build-essential \
git \
systemd-sysv \
dbus \
locales \
resolvconf \
linux-image-generic \
linux-headers-generic' || print_error "Failed to install desktop packages."
# Install downloaded packages if available (install in correct order)
if [ -d "$SCRIPT_DIR/pkgs" ] && [ "$(ls -A "$SCRIPT_DIR/pkgs" 2>/dev/null)" ]; then
print_status "Installing downloaded packages in correct order..."
cp "$SCRIPT_DIR/pkgs"/*.deb "$CHROOT_DIR/tmp/" || print_error "Failed to copy packages."
# Install packages in dependency order
# 1. First install libostree-1-1 (dependency)
if [ -f "$CHROOT_DIR/tmp/libostree-1-1_2025.2-1~noble1_amd64.deb" ]; then
print_status "Installing libostree-1-1..."
sudo chroot "$CHROOT_DIR" apt install -y /tmp/libostree-1-1_2025.2-1~noble1_amd64.deb || print_error "Failed to install libostree-1-1"
fi
# 2. Then install ostree
if [ -f "$CHROOT_DIR/tmp/ostree_2025.2-1~noble1_amd64.deb" ]; then
print_status "Installing ostree..."
sudo chroot "$CHROOT_DIR" apt install -y /tmp/ostree_2025.2-1~noble1_amd64.deb || print_error "Failed to install ostree"
fi
# 3. Then install bootc
if [ -f "$CHROOT_DIR/tmp/bootc_1.5.1-1~noble1_amd64.deb" ]; then
print_status "Installing bootc..."
sudo chroot "$CHROOT_DIR" apt install -y /tmp/bootc_1.5.1-1~noble1_amd64.deb || print_error "Failed to install bootc"
fi
# 4. Finally install apt-ostree
if [ -f "$CHROOT_DIR/tmp/apt-ostree_0.1.0-1_amd64.deb" ]; then
print_status "Installing apt-ostree..."
sudo chroot "$CHROOT_DIR" apt install -y /tmp/apt-ostree_0.1.0-1_amd64.deb || print_error "Failed to install apt-ostree"
fi
# 5. Install other packages
for pkg in "$CHROOT_DIR/tmp"/*.deb; do
if [ -f "$pkg" ]; then
pkg_name=$(basename "$pkg")
if [[ "$pkg_name" != *"libostree-1-1"* ]] && [[ "$pkg_name" != *"ostree"* ]] && [[ "$pkg_name" != *"bootc"* ]] && [[ "$pkg_name" != *"apt-ostree"* ]]; then
print_status "Installing $pkg_name..."
sudo chroot "$CHROOT_DIR" apt install -y "/tmp/$pkg_name" || print_warning "Failed to install $pkg_name, continuing anyway"
fi
fi
done
sudo chroot "$CHROOT_DIR" rm -f /tmp/*.deb || print_warning "Failed to clean up package files"
else
# Fallback: Install apt-ostree from system repositories
print_status "No downloaded packages found, installing apt-ostree from system repositories..."
sudo chroot "$CHROOT_DIR" apt install -y apt-ostree || print_warning "apt-ostree installation failed, continuing anyway"
fi
# Crucial: Regenerate initramfs after installing live-boot and kernel
print_status "Updating initramfs for all installed kernels..."
sudo chroot "$CHROOT_DIR" update-initramfs -u -k all || print_error "Failed to update initramfs."
# Remove unwanted packages (snapd is already blocked by APT preferences)
print_status "Removing unwanted packages..."
sudo chroot "$CHROOT_DIR" bash -c 'DEBIAN_FRONTEND=noninteractive apt purge -y ubuntu-advantage-tools update-notifier update-manager unattended-upgrades' || print_error "Failed to purge unwanted packages."
sudo chroot "$CHROOT_DIR" apt autoremove -y || print_error "Failed to autoremove packages."
# Configure system settings
sudo chroot "$CHROOT_DIR" bash -c '
echo "particleos" > /etc/hostname && \
echo "127.0.0.1 localhost" >> /etc/hosts && \
echo "127.0.1.1 particleos.local particleos" >> /etc/hosts && \
ln -sf /usr/share/zoneinfo/UTC /etc/localtime && \
locale-gen en_US.UTF-8 && \
update-locale LANG=en_US.UTF-8
' || print_error "Failed to configure system settings."
# Create user
print_status "Creating particle user..."
sudo chroot "$CHROOT_DIR" useradd -m -s /bin/bash particle || print_error "Failed to create particle user."
sudo chroot "$CHROOT_DIR" usermod -aG sudo particle || print_error "Failed to add particle to sudo group."
sudo chroot "$CHROOT_DIR" bash -c "echo 'particle ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/particle" || print_error "Failed to create sudoers file."
sudo chroot "$CHROOT_DIR" chmod 0440 /etc/sudoers.d/particle || print_error "Failed to set sudoers permissions."
# Enable services
print_status "Enabling essential services..."
sudo chroot "$CHROOT_DIR" systemctl enable sddm NetworkManager ssh || print_error "Failed to enable services."
sudo chroot "$CHROOT_DIR" systemctl disable apt-daily.timer apt-daily-upgrade.timer || print_error "Failed to disable apt timers."
# Configure apt-ostree
print_status "Configuring apt-ostree..."
sudo chroot "$CHROOT_DIR" mkdir -p /etc/apt-ostree || print_error "Failed to create apt-ostree config directory."
sudo chroot "$CHROOT_DIR" bash -c 'echo "ref: particleos/desktop/1.0.0" > /etc/apt-ostree/ref' || print_error "Failed to configure apt-ostree ref."
# Clean up
print_status "Cleaning up..."
sudo chroot "$CHROOT_DIR" apt clean || print_error "Failed to clean apt cache."
sudo chroot "$CHROOT_DIR" rm -rf /var/lib/apt/lists/* /tmp/* || print_error "Failed to remove apt lists."
# Unmount filesystems
sudo umount "$CHROOT_DIR/dev"
sudo umount "$CHROOT_DIR/run"
sudo umount "$CHROOT_DIR/proc"
sudo umount "$CHROOT_DIR/sys"
print_success "Base system configured with bootc integration"
}
# Create bootc-compatible system image
create_bootc_image() {
print_header "Phase 5: Create bootc System Image"
print_status "Creating bootc-compatible system image..."
# Create a temporary directory for the image
local image_dir="$BUILD_DIR/image"
mkdir -p "$image_dir"
# Copy the chroot filesystem to the image directory
print_status "Copying filesystem to image directory..."
sudo cp -a "$CHROOT_DIR"/* "$image_dir/" || print_error "Failed to copy filesystem."
# Create ostree repository structure
print_status "Creating ostree repository structure..."
mkdir -p "$image_dir/ostree/repo"
# Initialize ostree repository
ostree init --repo="$image_dir/ostree/repo" --mode=archive || print_error "Failed to initialize ostree repository."
# Create ostree commit from the filesystem
print_status "Creating ostree commit..."
ostree commit --repo="$image_dir/ostree/repo" \
--branch=particleos/desktop/1.0.0 \
--subject="ParticleOS Desktop 1.0.0" \
--body="ParticleOS Desktop system with apt-ostree" \
--tree=dir="$image_dir" || print_error "Failed to create ostree commit."
# Create bootc-compatible container image
print_status "Creating bootc-compatible container image..."
# Create Dockerfile for the container image
cat > "$BUILD_DIR/Dockerfile" << 'EOF'
FROM scratch
COPY . /
LABEL org.opencontainers.image.title="ParticleOS Desktop"
LABEL org.opencontainers.image.description="Atomic Ubuntu Desktop with apt-ostree"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.vendor="ParticleOS"
EOF
# Build container image
cd "$BUILD_DIR"
podman build -t "$SYSTEM_IMAGE" . || print_error "Failed to build container image."
print_success "bootc system image created: $SYSTEM_IMAGE"
}
# Create ISO using xorriso (reuse existing proven method)
create_iso() {
print_header "Phase 6: Create ISO with xorriso"
print_status "Creating ISO structure..."
# Create ISO directory structure
mkdir -p "$ISO_DIR"/{casper,boot/grub,EFI/BOOT,isolinux} || print_error "Failed to create ISO directory structure."
# Copy kernel and initrd
print_status "Copying kernel and initramfs..."
# Get the latest kernel and initrd
KERNEL_VERSION=$(basename $(ls -1 "$CHROOT_DIR/boot/vmlinuz-"* | sort -V | tail -n 1) | sed 's/vmlinuz-//')
INITRD_VERSION=$(basename $(ls -1 "$CHROOT_DIR/boot/initrd.img-"* | sort -V | tail -n 1) | sed 's/initrd.img-//')
if [ -z "$KERNEL_VERSION" ]; then
print_error "Kernel (vmlinuz-*) not found in $CHROOT_DIR/boot."
fi
if [ -z "$INITRD_VERSION" ]; then
print_error "Initrd (initrd.img-*) not found in $CHROOT_DIR/boot."
fi
cp "$CHROOT_DIR/boot/vmlinuz-$KERNEL_VERSION" "$ISO_DIR/casper/vmlinuz" || print_error "Failed to copy kernel."
cp "$CHROOT_DIR/boot/initrd.img-$INITRD_VERSION" "$ISO_DIR/casper/initrd" || print_error "Failed to copy initrd."
# Create filesystem manifest
print_status "Creating filesystem manifest..."
sudo chroot "$CHROOT_DIR" dpkg-query -W --showformat='${Package} ${Version}\n' > "$ISO_DIR/casper/filesystem.manifest" || print_error "Failed to create filesystem manifest."
# Create filesystem size
print_status "Calculating filesystem size..."
sudo du -sx --block-size=1 "$CHROOT_DIR" | cut -f1 > "$ISO_DIR/casper/filesystem.size" || print_error "Failed to create filesystem.size."
# Create squashfs
print_status "Creating squashfs filesystem..."
# Exclude /boot/grub from squashfs to prevent conflicts with the ISO's bootloader
sudo mksquashfs "$CHROOT_DIR" "$ISO_DIR/casper/filesystem.squashfs" -comp xz -e boot/grub || print_error "Failed to create squashfs."
# Create GRUB configuration
cat > "$ISO_DIR/boot/grub/grub.cfg" << 'EOF'
set timeout=10
set default=0
menuentry "Try ParticleOS without installing" {
linux /casper/vmlinuz boot=casper quiet splash ---
initrd /casper/initrd
}
menuentry "Install ParticleOS" {
linux /casper/vmlinuz boot=casper quiet splash ---
initrd /casper/initrd
}
menuentry "Check disc for defects" {
linux /casper/vmlinuz boot=casper integrity-check quiet splash ---
initrd /casper/initrd
}
EOF
# Create ISOLINUX configuration
cat > "$ISO_DIR/isolinux/isolinux.cfg" << 'EOF'
DEFAULT live
TIMEOUT 300
PROMPT 1
LABEL live
MENU LABEL Try ParticleOS without installing
KERNEL /casper/vmlinuz
APPEND boot=casper initrd=/casper/initrd quiet splash ---
LABEL live-install
MENU LABEL Install ParticleOS
KERNEL /casper/vmlinuz
APPEND boot=casper initrd=/casper/initrd quiet splash ---
LABEL check
MENU LABEL Check disc for defects
KERNEL /casper/vmlinuz
APPEND boot=casper integrity-check initrd=/casper/initrd quiet splash ---
EOF
# Copy ISOLINUX boot files - REVISED PATH DISCOVERY
print_status "Copying ISOLINUX boot files..."
ISOLINUX_BASE_DIR=""
if [ -d "/usr/lib/syslinux" ]; then
ISOLINUX_BASE_DIR="/usr/lib/syslinux"
elif [ -d "/usr/lib/ISOLINUX" ]; then
ISOLINUX_BASE_DIR="/usr/lib/ISOLINUX"
elif [ -d "/usr/share/syslinux" ]; then
ISOLINUX_BASE_DIR="/usr/share/syslinux"
fi
if [ -z "$ISOLINUX_BASE_DIR" ]; then
print_error "Syslinux base directory not found. Please ensure 'syslinux-utils' or 'isolinux' is installed."
fi
ISOLINUX_BIN_SRC=""
LDLINUX_C32_SRC=""
MEMDISK_C32_SRC=""
MBOOT_C32_SRC=""
LIBUTIL_C32_SRC=""
# Search for isolinux.bin
if [ -f "$ISOLINUX_BASE_DIR/isolinux.bin" ]; then
ISOLINUX_BIN_SRC="$ISOLINUX_BASE_DIR/isolinux.bin"
elif [ -f "$ISOLINUX_BASE_DIR/bios/isolinux.bin" ]; then
ISOLINUX_BIN_SRC="$ISOLINUX_BASE_DIR/bios/isolinux.bin"
fi
# Search for ldlinux.c32 and other common .c32 modules
if [ -f "$ISOLINUX_BASE_DIR/ldlinux.c32" ]; then
LDLINUX_C32_SRC="$ISOLINUX_BASE_DIR/ldlinux.c32"
elif [ -f "$ISOLINUX_BASE_DIR/modules/bios/ldlinux.c32" ]; then
LDLINUX_C32_SRC="$ISOLINUX_BASE_DIR/modules/bios/ldlinux.c32"
elif [ -f "$ISOLINUX_BASE_DIR/isolinux/ldlinux.c32" ]; then
LDLINUX_C32_SRC="$ISOLINUX_BASE_DIR/isolinux/ldlinux.c32"
fi
# Search for other critical .c32 files
if [ -f "$ISOLINUX_BASE_DIR/memdisk/memdisk.c32" ]; then
MEMDISK_C32_SRC="$ISOLINUX_BASE_DIR/memdisk/memdisk.c32"
elif [ -f "$ISOLINUX_BASE_DIR/modules/bios/memdisk.c32" ]; then
MEMDISK_C32_SRC="$ISOLINUX_BASE_DIR/modules/bios/memdisk.c32"
fi
if [ -f "$ISOLINUX_BASE_DIR/mboot.c32" ]; then
MBOOT_C32_SRC="$ISOLINUX_BASE_DIR/mboot.c32"
elif [ -f "$ISOLINUX_BASE_DIR/modules/bios/mboot.c32" ]; then
MBOOT_C32_SRC="$ISOLINUX_BASE_DIR/modules/bios/mboot.c32"
fi
if [ -f "$ISOLINUX_BASE_DIR/libutil.c32" ]; then
LIBUTIL_C32_SRC="$ISOLINUX_BASE_DIR/libutil.c32"
elif [ -f "$ISOLINUX_BASE_DIR/modules/bios/libutil.c32" ]; then
LIBUTIL_C32_SRC="$ISOLINUX_BASE_DIR/modules/bios/libutil.c32"
fi
SYSLINUX_MODULES_PATH=""
if [ -d "/usr/lib/syslinux/modules/bios" ]; then
SYSLINUX_MODULES_PATH="/usr/lib/syslinux/modules/bios"
elif [ -d "/usr/share/syslinux/modules/bios" ]; then
SYSLINUX_MODULES_PATH="/usr/share/syslinux/modules/bios"
fi
if [ -z "$ISOLINUX_BIN_SRC" ]; then
print_error "isolinux.bin not found within detected syslinux paths. Please check your syslinux installation."
fi
if [ -z "$LDLINUX_C32_SRC" ]; then
print_error "ldlinux.c32 not found within detected syslinux paths. Ensure 'syslinux-common' is installed and up-to-date."
fi
cp "$ISOLINUX_BIN_SRC" "$ISO_DIR/isolinux/" || print_error "Failed to copy isolinux.bin."
cp "$LDLINUX_C32_SRC" "$ISO_DIR/isolinux/" || print_error "Failed to copy ldlinux.c32."
# Copy other essential .c32 modules that ldlinux.c32 might depend on
if [ -n "$MEMDISK_C32_SRC" ]; then
cp "$MEMDISK_C32_SRC" "$ISO_DIR/isolinux/" || print_warning "Failed to copy memdisk.c32, continuing anyway."
fi
if [ -n "$MBOOT_C32_SRC" ]; then
cp "$MBOOT_C32_SRC" "$ISO_DIR/isolinux/" || print_warning "Failed to copy mboot.c32, continuing anyway."
fi
if [ -n "$LIBUTIL_C32_SRC" ]; then
cp "$LIBUTIL_C32_SRC" "$ISO_DIR/isolinux/" || print_warning "Failed to copy libutil.c32, continuing anyway."
fi
if [ -f "$ISOLINUX_BASE_DIR/menu.c32" ]; then
cp "$ISOLINUX_BASE_DIR/menu.c32" "$ISO_DIR/isolinux/" || print_warning "Failed to copy menu.c32, continuing anyway."
elif [ -f "$SYSLINUX_MODULES_PATH/menu.c32" ]; then
cp "$SYSLINUX_MODULES_PATH/menu.c32" "$ISO_DIR/isolinux/" || print_warning "Failed to copy menu.c32, continuing anyway."
fi
# Create EFI boot image
print_status "Creating EFI boot image..."
mkdir -p "$ISO_DIR/EFI/BOOT" || print_error "Failed to create EFI/BOOT directory."
grub-mkimage \
-o "$ISO_DIR/EFI/BOOT/bootx64.efi" \
-p "/boot/grub" \
-O x86_64-efi \
boot linux normal configfile part_gpt part_msdos fat \
squash4 test configfile search loadenv \
efi_gop efi_uga all_video gfxterm_menu \
chain iso9660 || print_error "Failed to create EFI boot image."
# Create ISO using xorriso
print_status "Creating bootable ISO using xorriso..."
xorriso -as mkisofs \
-o "$OUTPUT_DIR/${PROJECT_NAME}-${BUILD_TIMESTAMP}.iso" \
-J -joliet-long \
-r -V "ParticleOS ${VERSION}" \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-boot-load-size 4 -boot-info-table \
-no-emul-boot -eltorito-alt-boot \
-e EFI/BOOT/bootx64.efi -no-emul-boot \
-isohybrid-mbr "$ISOLINUX_BASE_DIR/isohdpfx.bin" \
-partition_offset 16 \
-part_like_isohybrid \
"$ISO_DIR" || print_error "Failed to create ISO with xorriso."
print_success "ISO created successfully: $OUTPUT_DIR/${PROJECT_NAME}-${BUILD_TIMESTAMP}.iso"
}
# Cleanup function
cleanup() {
print_status "Cleaning up build environment..."
# Unmount any remaining mounts
if mountpoint -q "$CHROOT_DIR/dev" 2>/dev/null; then
sudo umount "$CHROOT_DIR/dev" 2>/dev/null || true
fi
if mountpoint -q "$CHROOT_DIR/run" 2>/dev/null; then
sudo umount "$CHROOT_DIR/run" 2>/dev/null || true
fi
if mountpoint -q "$CHROOT_DIR/proc" 2>/dev/null; then
sudo umount "$CHROOT_DIR/proc" 2>/dev/null || true
fi
if mountpoint -q "$CHROOT_DIR/sys" 2>/dev/null; then
sudo umount "$CHROOT_DIR/sys" 2>/dev/null || true
fi
print_success "Cleanup completed"
}
# Signal trap for cleanup
trap cleanup EXIT INT TERM
# Main execution
main() {
echo "🚀 ParticleOS ISO Builder (bootc + xorriso)"
echo "==========================================="
echo "Project: $PROJECT_NAME"
echo "Version: $VERSION"
echo "Build Directory: $BUILD_DIR"
echo "Tool: bootc + xorriso (Hybrid Approach)"
echo ""
echo "🔄 This build combines:"
echo " • bootc for modern container-based system management"
echo " • xorriso for proven ISO creation"
echo " • ostree for atomic updates"
echo " • apt-ostree for Debian/Ubuntu package management"
echo ""
check_prerequisites
clean_build
create_base_system
configure_base_system
create_bootc_image
create_iso
print_header "Build Complete!"
echo ""
echo "🎉 ParticleOS ISO built successfully with bootc + xorriso!"
echo "📁 Location: $OUTPUT_DIR/${PROJECT_NAME}-${BUILD_TIMESTAMP}.iso"
echo "🐳 Container Image: $SYSTEM_IMAGE"
echo ""
echo "Summary of Cacher Usage:"
echo " mmdebstrap: $CACHER_MMDEBSTRAP_USED"
echo " Chroot APT: $CACHER_CHROOT_APT_USED"
echo ""
echo "🧪 Test the ISO:"
echo " qemu-system-x86_64 -m 4G -enable-kvm \\"
echo " -cdrom $OUTPUT_DIR/${PROJECT_NAME}-${BUILD_TIMESTAMP}.iso \\"
echo " -boot d"
echo ""
echo "🚀 Deploy with bootc:"
echo " sudo bootc install to-disk /dev/sda --source-imgref oci:$SYSTEM_IMAGE"
echo ""
echo "✅ Best of both worlds: Modern container workflow + proven ISO creation!"
}
# Run main function
main "$@"