736 lines
No EOL
28 KiB
Bash
Executable file
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 "$@" |