#!/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 "$@"