#!/bin/bash # ParticleOS ISO Builder - Pure bootc Approach # Uses bootc for the entire build process, no mmdebstrap needed # This is the true "bootc + xorriso" approach 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-native.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "$(date): Starting ParticleOS ISO build with pure bootc approach" 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" CONTAINER_NAME="particleos-builder" 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_CONTAINER_BUILD_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 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 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 for casper (required for live boot) if ! dpkg -s casper &>/dev/null; then print_status "Installing casper (required for live boot)..." sudo apt update sudo apt install -y casper 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 remove any existing container first if podman container exists "$CONTAINER_NAME" 2>/dev/null; then print_status "Removing existing container: $CONTAINER_NAME" podman container rm -f "$CONTAINER_NAME" 2>/dev/null || print_warning "Failed to remove container" fi # Force remove any existing images if podman image exists "$SYSTEM_IMAGE" 2>/dev/null; then print_status "Removing existing image: $SYSTEM_IMAGE" podman image rm -f "$SYSTEM_IMAGE" 2>/dev/null || print_warning "Failed to remove image" fi sudo rm -rf "$BUILD_DIR" || print_error "Failed to remove previous build directory." fi mkdir -p "$BUILD_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 bootc system definition create_bootc_definition() { print_header "Phase 3: Create bootc System Definition" print_status "Creating bootc system definition..." # Create a bootc-compatible system definition cat > "$BUILD_DIR/particleos-system.yaml" << 'EOF' # ParticleOS System Definition for bootc apiVersion: v1 kind: Image metadata: name: particleos-desktop version: "1.0.0" spec: os: name: ubuntu version: "24.04" packages: - name: ubuntu-minimal - name: plasma-desktop - name: plasma-workspace - name: sddm - name: kwin-x11 - name: flatpak - name: network-manager - name: plasma-nm - name: openssh-server - name: curl - name: wget - name: vim - name: nano - name: htop - name: neofetch - name: tree - name: pulseaudio - name: pulseaudio-utils - name: fonts-ubuntu - name: fonts-noto - name: build-essential - name: git - name: live-boot - name: live-config - name: casper - name: systemd-sysv - name: dbus - name: locales - name: resolvconf - name: linux-image-generic - name: linux-headers-generic - name: podman - name: skopeo - name: buildah - name: ostree - name: apt-ostree services: - name: sddm enabled: true - name: NetworkManager enabled: true - name: ssh enabled: true users: - name: particle groups: [sudo, wheel] shell: /bin/bash home: /home/particle files: - path: /etc/hostname content: particleos - path: /etc/hosts content: | 127.0.0.1 localhost 127.0.1.1 particleos.local particleos - path: /etc/apt-ostree/ref content: particleos/desktop/1.0.0 EOF print_success "bootc system definition created" } # Build system using bootc container workflow build_bootc_system() { print_header "Phase 4: Build System with bootc" print_status "Building system using bootc container workflow..." # Try to use apt-cacher-ng if available for container build local use_proxy=false if check_proxy_reachable; then print_status "Container build will use apt-cacher-ng: $APT_CACHER_NG_URL" CACHER_CONTAINER_BUILD_USED="Yes" use_proxy=true else print_warning "apt-cacher-ng not reachable. Container build will proceed without proxy." CACHER_CONTAINER_BUILD_USED="No (Fallback to direct)" use_proxy=false fi # Create a Dockerfile that bootc can build from cat > "$BUILD_DIR/Dockerfile" << 'EOF' # Start with Ubuntu 24.04 base FROM ubuntu:24.04 # Set environment ENV DEBIAN_FRONTEND=noninteractive ENV TZ=UTC # Configure apt proxy if available EOF # Add proxy configuration if available if [ "$use_proxy" = true ]; then cat >> "$BUILD_DIR/Dockerfile" << EOF RUN echo 'Acquire::http::Proxy "$APT_CACHER_NG_URL";' > /etc/apt/apt.conf.d/01proxy && \\ echo 'Acquire::https::Proxy "$APT_CACHER_NG_URL";' >> /etc/apt/apt.conf.d/01proxy EOF fi # Continue with the rest of the Dockerfile cat >> "$BUILD_DIR/Dockerfile" << 'EOF' # Update and install base packages RUN apt update && apt install -y \ systemd \ systemd-sysv \ dbus \ curl \ ca-certificates \ gnupg \ gpgv \ locales \ resolvconf \ && rm -rf /var/lib/apt/lists/* # Configure locales RUN locale-gen en_US.UTF-8 && update-locale LANG=en_US.UTF-8 # Block snapd installation with APT preferences RUN echo 'Package: snapd' > /etc/apt/preferences.d/no-snapd && \ echo 'Pin: release *' >> /etc/apt/preferences.d/no-snapd && \ echo 'Pin-Priority: -1' >> /etc/apt/preferences.d/no-snapd && \ echo '' >> /etc/apt/preferences.d/no-snapd && \ echo 'Package: snap' >> /etc/apt/preferences.d/no-snapd && \ echo 'Pin: release *' >> /etc/apt/preferences.d/no-snapd && \ echo 'Pin-Priority: -1' >> /etc/apt/preferences.d/no-snapd && \ echo '' >> /etc/apt/preferences.d/no-snapd && \ echo 'Package: firefox*' >> /etc/apt/preferences.d/no-snapd && \ echo 'Pin: release o=Ubuntu*' >> /etc/apt/preferences.d/no-snapd && \ echo 'Pin-Priority: -1' >> /etc/apt/preferences.d/no-snapd # Install desktop environment RUN apt update && 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 \ live-boot \ live-config \ casper \ linux-image-generic \ linux-headers-generic \ && rm -rf /var/lib/apt/lists/* # Install bootc and container tools RUN apt update && apt install -y \ 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 \ dracut \ dracut-core \ && rm -rf /var/lib/apt/lists/* # Copy and install local packages COPY pkgs/ /tmp/pkgs/ # Install ostree and bootc packages from local files RUN if [ -f /tmp/pkgs/libostree-1-1_2025.2-1~noble1_amd64.deb ]; then \ dpkg -i /tmp/pkgs/libostree-1-1_2025.2-1~noble1_amd64.deb; \ fi RUN if [ -f /tmp/pkgs/ostree_2025.2-1~noble1_amd64.deb ]; then \ dpkg -i /tmp/pkgs/ostree_2025.2-1~noble1_amd64.deb; \ fi RUN if [ -f /tmp/pkgs/bootc_1.5.1-1~noble1_amd64.deb ]; then \ dpkg -i /tmp/pkgs/bootc_1.5.1-1~noble1_amd64.deb; \ fi # Install apt-ostree from local packages if available RUN if [ -f /tmp/pkgs/apt-ostree_0.1.0-1_amd64.deb ]; then \ dpkg -i /tmp/pkgs/apt-ostree_0.1.0-1_amd64.deb; \ fi # Remove unwanted packages (snapd is already blocked by APT preferences) RUN apt purge -y ubuntu-advantage-tools || true && \ apt purge -y update-notifier || true && \ apt purge -y update-manager || true && \ apt purge -y unattended-upgrades || true && \ apt autoremove -y && \ apt clean # Configure system RUN 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 # Create user RUN useradd -m -s /bin/bash particle && \ usermod -aG sudo particle && \ echo 'particle ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/particle && \ chmod 0440 /etc/sudoers.d/particle # Configure apt-ostree RUN mkdir -p /etc/apt-ostree && \ echo "ref: particleos/desktop/1.0.0" > /etc/apt-ostree/ref # Enable services RUN systemctl enable sddm NetworkManager ssh && \ systemctl disable apt-daily.timer apt-daily-upgrade.timer # Create casper hook for live boot RUN mkdir -p /etc/casper && \ echo "particleos" > /etc/casper/hostname && \ echo "particle" > /etc/casper/username # Regenerate initrd to include live-boot modules RUN update-initramfs -u -k all # Clean up RUN rm -rf /tmp/pkgs /var/lib/apt/lists/* /tmp/* # Set labels for bootc 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" LABEL org.opencontainers.image.source="https://github.com/particleos/particleos-installer" # Expose ports EXPOSE 22 # Set default command CMD ["/lib/systemd/systemd"] EOF # Copy packages to build context if [ -d "$SCRIPT_DIR/pkgs" ]; then cp -r "$SCRIPT_DIR/pkgs" "$BUILD_DIR/" fi # Build the container image using podman print_status "Building container image..." cd "$BUILD_DIR" podman build -t "$SYSTEM_IMAGE" . || print_error "Failed to build container image." print_success "bootc system built: $SYSTEM_IMAGE" } # Extract filesystem from container for ISO creation extract_filesystem() { print_header "Phase 5: Extract Filesystem for ISO" print_status "Extracting filesystem from container..." # Create a temporary container from the image podman create --name "$CONTAINER_NAME" "$SYSTEM_IMAGE" || print_error "Failed to create container." # Extract the filesystem local extract_dir="$BUILD_DIR/extract" mkdir -p "$extract_dir" podman export "$CONTAINER_NAME" | tar -x -C "$extract_dir" || print_error "Failed to extract filesystem." # Remove the temporary container podman container rm "$CONTAINER_NAME" || print_warning "Failed to remove container" # Verify extraction was successful if [ ! -f "$extract_dir/etc/os-release" ]; then print_error "Filesystem extraction failed - /etc/os-release not found" fi print_success "Filesystem extracted to: $extract_dir" } # Create ISO using xorriso (reuse existing proven method) create_iso() { print_header "Phase 6: Create ISO with xorriso" local extract_dir="$BUILD_DIR/extract" local iso_dir="$BUILD_DIR/iso" 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 from extracted filesystem print_status "Copying kernel and initrd from extracted filesystem..." KERNEL_PATH=$(find "$extract_dir/boot" -maxdepth 1 -name "vmlinuz-*" | sort -V | tail -n 1) INITRD_PATH=$(find "$extract_dir/boot" -maxdepth 1 -name "initrd.img-*" | sort -V | tail -n 1) if [ -z "$KERNEL_PATH" ]; then print_error "Kernel (vmlinuz-*) not found in $extract_dir/boot." fi if [ -z "$INITRD_PATH" ]; then print_error "Initrd (initrd.img-*) not found in $extract_dir/boot." fi print_status "Using kernel: $KERNEL_PATH" print_status "Using initrd: $INITRD_PATH" cp "$KERNEL_PATH" "$iso_dir/casper/vmlinuz" || print_error "Failed to copy kernel." cp "$INITRD_PATH" "$iso_dir/casper/initrd" || print_error "Failed to copy initrd." # Create filesystem manifest print_status "Creating filesystem manifest..." # We need to chroot to get the package list, but we can't easily chroot into extracted fs # So we'll create a minimal manifest echo "particleos 1.0.0" > "$iso_dir/casper/filesystem.manifest" echo "ubuntu-minimal 24.04" >> "$iso_dir/casper/filesystem.manifest" echo "plasma-desktop 5.27" >> "$iso_dir/casper/filesystem.manifest" # Create filesystem size print_status "Calculating filesystem size..." du -sx --block-size=1 "$extract_dir" | cut -f1 > "$iso_dir/casper/filesystem.size" || print_error "Failed to create filesystem.size." # Create casper configuration print_status "Creating casper configuration..." cat > "$iso_dir/casper/casper.conf" << 'EOF' # Casper configuration for ParticleOS LIVE_HOSTNAME="particleos" LIVE_USERNAME="particle" LIVE_USER_FULLNAME="ParticleOS User" LIVE_USER_DEFAULT_GROUPS="adm cdrom dip lpadmin plugdev sambashare sudo" LIVE_IMAGETYPE="ParticleOS" LIVE_BOOT_CMDLINE="boot=casper quiet splash" EOF # Create squashfs print_status "Creating squashfs filesystem..." mksquashfs "$extract_dir" "$iso_dir/casper/filesystem.squashfs" -comp xz -e boot || 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 basic EFI structure (simplified for now) print_status "Creating basic EFI structure..." mkdir -p "$iso_dir/EFI/BOOT" || print_error "Failed to create EFI/BOOT directory." mkdir -p "$iso_dir/boot/grub" || print_error "Failed to create boot/grub directory." # 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 \ -isohybrid-mbr "$ISOLINUX_BASE_DIR/isohdpfx.bin" \ -partition_offset 16 \ -part_like_isohybrid \ -isohybrid-gpt-basdat \ "$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..." # Remove any remaining container if podman container exists "$CONTAINER_NAME" 2>/dev/null; then podman container rm -f "$CONTAINER_NAME" 2>/dev/null || print_warning "Failed to remove container" fi # Remove any remaining images if podman image exists "$SYSTEM_IMAGE" 2>/dev/null; then podman image rm -f "$SYSTEM_IMAGE" 2>/dev/null || print_warning "Failed to remove image" fi print_success "Cleanup completed" } # Signal trap for cleanup trap cleanup EXIT INT TERM # Main execution main() { echo "๐Ÿš€ ParticleOS ISO Builder (Pure bootc)" echo "======================================" echo "Project: $PROJECT_NAME" echo "Version: $VERSION" echo "Build Directory: $BUILD_DIR" echo "Tool: Pure bootc + xorriso" echo "" echo "๐Ÿ”„ This build uses:" echo " โ€ข bootc for container-based system building" echo " โ€ข Dockerfile for system definition" echo " โ€ข Container extraction for ISO creation" echo " โ€ข xorriso for proven ISO creation" echo "" check_prerequisites clean_build create_bootc_definition build_bootc_system extract_filesystem create_iso print_header "Build Complete!" echo "" echo "๐ŸŽ‰ ParticleOS ISO built successfully with pure bootc!" echo "๐Ÿ“ Location: $OUTPUT_DIR/${PROJECT_NAME}-${BUILD_TIMESTAMP}.iso" echo "๐Ÿณ Container Image: $SYSTEM_IMAGE" echo "" echo "Summary of Cacher Usage:" echo " Container Build: $CACHER_CONTAINER_BUILD_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 "โœ… Pure bootc workflow: Container-first approach!" } # Run main function main "$@"