diff --git a/build-iso-bootc-native.sh b/build-iso-bootc-native.sh new file mode 100644 index 0000000..edd685f --- /dev/null +++ b/build-iso-bootc-native.sh @@ -0,0 +1,607 @@ +#!/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" + +# 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; 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 + + 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..." + rm -rf "$BUILD_DIR" || print_error "Failed to remove previous build directory." + fi + + # Remove any existing container + if podman container exists "$CONTAINER_NAME" 2>/dev/null; then + print_status "Removing existing container: $CONTAINER_NAME" + podman container rm "$CONTAINER_NAME" || print_warning "Failed to remove container" + fi + + mkdir -p "$BUILD_DIR" "$OUTPUT_DIR" || print_error "Failed to create build directories." + 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..." + + # 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 + +# 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/* + +# Install ostree and apt-ostree (from your packages) +COPY pkgs/ /tmp/pkgs/ +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 +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" + + 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 + print_status "Copying ISOLINUX boot files..." + ISOLINUX_BIN_PATH="" + if [ -f "/usr/lib/syslinux/isolinux.bin" ]; then + ISOLINUX_BIN_PATH="/usr/lib/syslinux" + elif [ -f "/usr/lib/ISOLINUX/isolinux.bin" ]; then + ISOLINUX_BIN_PATH="/usr/lib/ISOLINUX" + fi + + if [ -z "$ISOLINUX_BIN_PATH" ]; then + print_error "isolinux.bin not found. Please install isolinux." + fi + + cp "$ISOLINUX_BIN_PATH/isolinux.bin" "$iso_dir/isolinux/" || print_error "Failed to copy isolinux.bin." + + # Copy ldlinux.c32 from the correct location + if [ -f "/usr/lib/syslinux/modules/bios/ldlinux.c32" ]; then + cp "/usr/lib/syslinux/modules/bios/ldlinux.c32" "$iso_dir/isolinux/" || print_error "Failed to copy ldlinux.c32." + else + print_error "ldlinux.c32 not found in /usr/lib/syslinux/modules/bios/." + 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_BIN_PATH/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 "$CONTAINER_NAME" 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 (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 "๐Ÿงช 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 "$@" \ No newline at end of file