particleos-installer/build-iso-bootc-native.sh
2025-07-24 06:35:20 +00:00

607 lines
No EOL
18 KiB
Bash

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