particleos-installer/build-iso-mmdebstrap-hardened.sh
2025-07-22 08:20:05 +00:00

553 lines
No EOL
22 KiB
Bash
Executable file

#!/bin/bash
# ParticleOS ISO Builder with mmdebstrap - HARDENED VERSION
# Builds a bootable ISO using mmdebstrap + apt-ostree integration
# Includes comprehensive error handling, safety checks, and hardening measures
set -euo pipefail
# 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"
}
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_DIR="$SCRIPT_DIR/build"
CHROOT_DIR="$BUILD_DIR/chroot"
ISO_DIR="$BUILD_DIR/iso"
OUTPUT_DIR="$SCRIPT_DIR/output"
# Enhanced 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."
exit 1
fi
# Track mounted filesystems for cleanup
MOUNTED_FILESYSTEMS=()
# Enhanced cleanup function to unmount filesystems in reverse order
cleanup_mounts() {
print_status "Cleaning up mounted filesystems..."
# Iterate in reverse for safer unmounting
for (( i=${#MOUNTED_FILESYSTEMS[@]}-1; i>=0; i-- )); do
mount_point="${MOUNTED_FILESYSTEMS[i]}"
if mountpoint -q "$mount_point" 2>/dev/null; then
print_status "Unmounting $mount_point"
sudo umount "$mount_point" 2>/dev/null || print_warning "Failed to unmount $mount_point"
fi
done
MOUNTED_FILESYSTEMS=()
}
# Signal trap to ensure cleanup on script exit (handles Ctrl+C, errors, etc.)
trap cleanup_mounts EXIT INT TERM
# Enhanced safe mount function with error checking
safe_mount() {
local source="$1"
local target="$2"
local type="${3:-}"
# Validate mount points
if [[ ! -d "$target" ]]; then
print_error "Mount target directory does not exist: $target"
exit 1
fi
if [ -n "$type" ]; then
sudo mount -t "$type" "$source" "$target"
else
sudo mount --bind "$source" "$target"
fi
if [ $? -eq 0 ]; then
MOUNTED_FILESYSTEMS+=("$target")
print_status "Mounted $source to $target"
else
print_error "Failed to mount $source to $target"
exit 1
fi
}
# Enhanced safe chroot function with error checking
safe_chroot() {
local command="$1"
local description="${2:-chroot command}"
print_status "Running: $description"
if sudo chroot "$CHROOT_DIR" bash -c "$command"; then
print_success "$description completed successfully"
else
print_error "$description failed"
exit 1
fi
}
# Enhanced prerequisites check with better error handling
check_prerequisites() {
print_header "Phase 1: Check Prerequisites"
local missing_packages=()
# Check for build tools
for package in mmdebstrap squashfs-tools xorriso grub-pc-bin grub-efi-amd64-bin; do
if ! dpkg -s "$package" &>/dev/null; then
missing_packages+=("$package")
fi
done
# Enhanced ISOLINUX checks
if [ ! -f "/usr/lib/ISOLINUX/isohdpfx.bin" ]; then
print_error "ISOLINUX isohdpfx.bin not found. Please ensure 'isolinux' package is installed."
exit 1
fi
# Also check for isolinux.bin in standard locations
if [ ! -f "/usr/lib/syslinux/isolinux.bin" ] && [ ! -f "/usr/lib/ISOLINUX/isolinux.bin" ]; then
print_error "isolinux.bin not found. Please ensure 'isolinux' package is installed."
exit 1
fi
if [ ${#missing_packages[@]} -gt 0 ]; then
print_status "Installing missing packages: ${missing_packages[*]}"
sudo apt update || { print_error "Failed to update package lists"; exit 1; }
sudo apt install -y "${missing_packages[@]}" || { print_error "Failed to install missing packages"; exit 1; }
fi
# Enhanced disk space check (increased to 15GB for safety)
local available_space=$(df "$SCRIPT_DIR" | awk 'NR==2 {print $4}')
local required_space=$((15 * 1024 * 1024)) # 15GB in KB
if [ "$available_space" -lt "$required_space" ]; then
print_error "Insufficient disk space. Need at least 15GB free, have $(($available_space / 1024 / 1024))GB"
exit 1
fi
# Check network connectivity
if ! ping -c 1 archive.ubuntu.com &>/dev/null; then
print_error "Cannot reach Ubuntu archives. Check your internet connection."
exit 1
fi
if ! curl -s -I "https://git.raines.xyz/robojerk/apt-ostree/raw/branch/main/apt-ostree_0.1.0-1_amd64.deb" | head -n 1 | grep "HTTP/[12] [23].." > /dev/null; then
print_error "Cannot reach apt-ostree repository. Check your internet connection."
exit 1
fi
print_success "All prerequisites satisfied"
}
# Enhanced clean build environment with expanded safety checks
clean_build() {
print_header "Phase 2: Clean Build Environment"
if [ -d "$BUILD_DIR" ]; then
# Enhanced safety check: Expanded blacklist of critical directories
if [[ "$BUILD_DIR" == "/" ]] || [[ "$BUILD_DIR" == "/home" ]] || [[ "$BUILD_DIR" == "/opt" ]] || \
[[ "$BUILD_DIR" == "/usr" ]] || [[ "$BUILD_DIR" == "/var" ]] || [[ "$BUILD_DIR" == "/etc" ]] || \
[[ "$BUILD_DIR" == "/root" ]] || [[ "$BUILD_DIR" == "/tmp" ]] || [[ "$BUILD_DIR" == "/sbin" ]] || \
[[ "$BUILD_DIR" == "/bin" ]] || [[ "$BUILD_DIR" == "/lib" ]] || [[ "$BUILD_DIR" == "/lib64" ]]; then
print_error "BUILD_DIR ($BUILD_DIR) is a critical system directory. Aborting for safety."
exit 1
fi
print_status "Removing previous build directory: $BUILD_DIR..."
sudo rm -rf "$BUILD_DIR" || { print_error "Failed to remove previous build directory"; exit 1; }
fi
mkdir -p "$BUILD_DIR" "$CHROOT_DIR" "$ISO_DIR" "$OUTPUT_DIR" || { print_error "Failed to create build directories"; exit 1; }
print_success "Build environment cleaned"
}
# Enhanced base system creation using mmdebstrap
create_base_system() {
print_header "Phase 3: Create Base System with mmdebstrap"
print_status "Creating base Ubuntu system using mmdebstrap..."
# Enhanced base system with mmdebstrap
# Using --variant=minbase as it provides a minimal system more suitable for custom builds.
# Added 'locales' for proper locale generation and 'resolvconf' for DNS if needed.
if sudo mmdebstrap \
--architectures=amd64 \
--variant=minbase \
--include=systemd,systemd-sysv,dbus,curl,ca-certificates,gnupg,locales,resolvconf \
noble \
"$CHROOT_DIR" \
http://archive.ubuntu.com/ubuntu/; then
print_success "Base system created with mmdebstrap"
else
print_error "Failed to create base system with mmdebstrap"
exit 1
fi
}
# Enhanced base system configuration
configure_base_system() {
print_header "Phase 4: Configure Base System"
print_status "Configuring base system..."
# Mount necessary filesystems
safe_mount "/dev" "$CHROOT_DIR/dev"
safe_mount "/run" "$CHROOT_DIR/run"
safe_mount "none" "$CHROOT_DIR/proc" "proc"
safe_mount "none" "$CHROOT_DIR/sys" "sysfs"
# Configure package sources
safe_chroot "echo 'deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse' > /etc/apt/sources.list" "Configure main sources"
safe_chroot "echo 'deb http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse' >> /etc/apt/sources.list" "Configure updates sources"
safe_chroot "echo 'deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse' >> /etc/apt/sources.list" "Configure security sources"
# Add Forgejo repository for apt-ostree and potentially other DEB packages (like Firefox if available)
print_status "Setting up Forgejo repository for apt-ostree and other packages..."
safe_chroot "mkdir -p /etc/apt/keyrings" "Create keyrings directory"
# Use -o /dev/null for curl output to stdout, pipe to gpg
safe_chroot "curl -fsSL https://git.raines.xyz/api/packages/robojerk/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/forgejo-robojerk.gpg" "Download and import Forgejo GPG key"
safe_chroot "echo 'deb [signed-by=/etc/apt/keyrings/forgejo-robojerk.gpg] https://git.raines.xyz/api/packages/robojerk/debian noble main' > /etc/apt/sources.list.d/forgejo.list" "Add Forgejo repository"
# Update package lists
safe_chroot "apt update" "Update package lists"
# Enhanced package installation with ostree and bootc
# Including 'ostree' and 'bootc' as requested
# If a DEB for Firefox exists in any enabled repo, it will be preferred over Snap due to preferences below.
safe_chroot "DEBIAN_FRONTEND=noninteractive apt install -y \
kubuntu-desktop plasma-desktop plasma-workspace kde-plasma-desktop sddm \
ostree bootc flatpak \
network-manager plasma-nm \
openssh-server \
curl wget vim nano htop neofetch tree \
firefox \
pulseaudio pulseaudio-utils \
fonts-ubuntu fonts-noto \
build-essential git \
live-boot live-config casper" "Install desktop and core live packages"
# Download and install apt-ostree from custom repository (assuming it's not in the Forgejo apt repo directly)
print_status "Installing apt-ostree from custom repository directly..."
safe_chroot "timeout 60 wget -O /tmp/apt-ostree.deb 'https://git.raines.xyz/robojerk/apt-ostree/raw/branch/main/apt-ostree_0.1.0-1_amd64.deb'" "Download apt-ostree"
safe_chroot "dpkg -i /tmp/apt-ostree.deb || apt install -f -y" "Install apt-ostree and fix dependencies"
safe_chroot "rm -f /tmp/apt-ostree.deb" "Clean up apt-ostree download"
# Enhanced snap removal and blocking
print_status "Removing snapd and setting APT preferences to block snaps..."
safe_chroot "DEBIAN_FRONTEND=noninteractive apt purge -y snapd ubuntu-advantage-tools update-notifier update-manager unattended-upgrades" "Purge unwanted packages"
safe_chroot "apt autoremove -y" "Autoremove orphaned packages"
safe_chroot "apt-mark hold snapd" "Hold snapd"
# Create an APT preference file to strongly discourage snapd and enforce non-snap Firefox
safe_chroot "echo 'Package: snapd' > /etc/apt/preferences.d/nosnap.pref" "Create nosnap.pref"
safe_chroot "echo 'Pin: release *' >> /etc/apt/preferences.d/nosnap.pref" "Set nosnap.pref pin release"
safe_chroot "echo 'Pin-Priority: -1' >> /etc/apt/preferences.d/nosnap.pref" "Set nosnap.pref pin priority"
safe_chroot "echo 'Package: firefox' >> /etc/apt/preferences.d/nosnap.pref" "Add firefox to nosnap.pref"
safe_chroot "echo 'Pin: release o=Ubuntu' >> /etc/apt/preferences.d/nosnap.pref" "Pin Firefox to Ubuntu archive"
safe_chroot "echo 'Pin-Priority: 1000' >> /etc/apt/preferences.d/nosnap.pref" "Give high priority to APT Firefox"
# Enhanced system configuration
safe_chroot "echo 'particleos' > /etc/hostname" "Set hostname"
safe_chroot "echo '127.0.0.1 localhost' >> /etc/hosts" "Add localhost to hosts" # More standard
safe_chroot "echo '127.0.1.1 particleos.local particleos' >> /etc/hosts" "Add hostname to hosts"
safe_chroot "ln -sf /usr/share/zoneinfo/UTC /etc/localtime" "Set timezone to UTC"
safe_chroot "locale-gen en_US.UTF-8" "Generate locale" # Important for desktop
safe_chroot "update-locale LANG=en_US.UTF-8" "Set default locale"
# Enhanced user creation
print_status "Creating user 'particle'..."
safe_chroot "useradd -m -s /bin/bash particle" "Create particle user"
safe_chroot "echo 'particle:particle' | chpasswd" "Set particle password"
safe_chroot "usermod -aG sudo particle" "Add particle to sudo group"
safe_chroot "echo 'particle ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/particle" "Allow particle sudo without password (for live system)"
safe_chroot "chmod 0440 /etc/sudoers.d/particle" "Set correct permissions for sudoers file"
# Enhanced service configuration
print_status "Enabling essential services..."
safe_chroot "systemctl enable sddm NetworkManager ssh" "Enable key services" # Removed systemd-networkd, systemd-resolved if NetworkManager is used for DNS.
safe_chroot "systemctl disable apt-daily.timer apt-daily-upgrade.timer" "Disable daily apt timers for live system"
# Enhanced apt-ostree configuration
print_status "Configuring apt-ostree..."
safe_chroot "mkdir -p /etc/apt-ostree" "Create apt-ostree config directory"
safe_chroot "echo 'ref: particleos/desktop/1.0.0' > /etc/apt-ostree/ref" "Configure apt-ostree ref"
# Additional OSTree setup (initial commit, etc.) would typically happen here if building a committed image
# For a live ISO, apt-ostree mostly provides its tools.
# Enhanced cleanup
print_status "Cleaning APT caches in chroot..."
safe_chroot "apt clean" "Clean apt cache"
safe_chroot "rm -rf /var/lib/apt/lists/*" "Remove apt lists"
safe_chroot "rm -rf /tmp/*" "Clean /tmp"
print_success "Base system configured"
}
# Enhanced live filesystem creation
create_live_fs() {
print_header "Phase 5: Create Live Filesystem"
print_status "Creating live filesystem structure..."
# Create ISO directory structure
# Adding EFI/BOOT and boot/grub for UEFI/BIOS
mkdir -p "$ISO_DIR"/{casper,boot/grub,EFI/BOOT,isolinux} || { print_error "Failed to create ISO directories"; exit 1; }
# Enhanced kernel and initrd copying
print_status "Copying kernel and initramfs..."
# Find the latest kernel and initrd
local kernel_version=$(basename "$CHROOT_DIR"/boot/vmlinuz-* | sort -V | tail -n 1 | sed 's/vmlinuz-//')
local initrd_version=$(basename "$CHROOT_DIR"/boot/initrd.img-* | sort -V | tail -n 1 | sed 's/initrd.img-//')
if [ -z "$kernel_version" ] || [ -z "$initrd_version" ]; then
print_error "Could not find kernel or initrd in chroot/boot."
exit 1
fi
cp "$CHROOT_DIR/boot/vmlinuz-$kernel_version" "$ISO_DIR/casper/vmlinuz" || { print_error "Failed to copy kernel"; exit 1; }
cp "$CHROOT_DIR/boot/initrd.img-$initrd_version" "$ISO_DIR/casper/initrd" || { print_error "Failed to copy initrd"; exit 1; }
print_success "Kernel and initramfs copied."
# Enhanced filesystem manifest creation
print_status "Creating filesystem manifest..."
safe_chroot "dpkg-query -W --showformat='\${Package} \${Version}\n' > /filesystem.manifest" "Create filesystem manifest"
cp "$CHROOT_DIR/filesystem.manifest" "$ISO_DIR/casper/filesystem.manifest" || { print_error "Failed to copy filesystem.manifest"; exit 1; }
safe_chroot "rm /filesystem.manifest" "Clean up manifest in chroot" # Remove temp file in chroot
# Enhanced filesystem size calculation
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"; exit 1; }
print_success "Filesystem size calculated."
# Enhanced squashfs creation
print_status "Creating squashfs filesystem from chroot..."
# IMPORTANT: Unmount internal chroot mounts before mksquashfs for cleanest image
cleanup_mounts # Call the cleanup function here to unmount /dev, /run, /proc, /sys from chroot
if sudo mksquashfs "$CHROOT_DIR" "$ISO_DIR/casper/filesystem.squashfs" -comp xz -e boot; then
print_success "Squashfs created."
else
print_error "Failed to create squashfs."
exit 1
fi
print_status "Re-mounting filesystems in chroot for any subsequent operations (though none currently follow in this script)."
# Re-mount for consistency, though currently nothing follows. The EXIT trap will handle final unmount.
safe_mount "/dev" "$CHROOT_DIR/dev"
safe_mount "/run" "$CHROOT_DIR/run"
safe_mount "none" "$CHROOT_DIR/proc" "proc"
safe_mount "none" "$CHROOT_DIR/sys" "sysfs"
}
# Enhanced boot configuration setup
setup_boot() {
print_header "Phase 6: Setup Boot Configuration"
print_status "Setting up boot configuration..."
# Enhanced GRUB configuration (for UEFI)
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
print_success "GRUB configuration created."
# Enhanced ISOLINUX configuration (for BIOS)
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
print_success "ISOLINUX configuration created."
# Enhanced ISOLINUX boot files copying
print_status "Copying ISOLINUX boot files..."
# Determine correct isolinux path (can vary by distro/version)
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 path not found. ISO creation might fail."
exit 1
fi
cp "$ISOLINUX_BIN_PATH/isolinux.bin" "$ISO_DIR/isolinux/" || { print_error "Failed to copy isolinux.bin"; exit 1; }
print_success "ISOLINUX boot files copied."
# Enhanced EFI boot image creation (for UEFI)
# This assumes grub-efi-amd64-bin provides the necessary files to create efi.img.
# A robust solution might involve grub-mkimage. For a simple live ISO, this is often sufficient.
print_status "Creating EFI boot image..."
GRUB_EFI_DIR="/usr/lib/grub/efi-amd64" # Standard path for grub-efi-amd64-bin
if [ ! -d "$GRUB_EFI_DIR" ]; then
print_error "GRUB EFI directory not found at $GRUB_EFI_DIR. Cannot create EFI boot image."
exit 1
fi
mkdir -p "$ISO_DIR/EFI/BOOT" || { print_error "Failed to create EFI/BOOT directory"; exit 1; }
# Copy essential EFI files for direct boot (e.g., used by some VMs/UEFI firmwares)
cp "$GRUB_EFI_DIR/grubx64.efi" "$ISO_DIR/EFI/BOOT/bootx64.efi" || { print_error "Failed to copy grubx64.efi"; exit 1; }
# Create the EFI grub.cfg for the live system
cat > "$ISO_DIR/EFI/BOOT/grub.cfg" << 'EOF'
search --set=root --file /casper/vmlinuz
set timeout=10
set default=0
menuentry "Try ParticleOS without installing" {
linux ($root)/casper/vmlinuz boot=casper quiet splash ---
initrd ($root)/casper/initrd
}
menuentry "Install ParticleOS" {
linux ($root)/casper/vmlinuz boot=casper quiet splash ---
initrd ($root)/casper/initrd
}
menuentry "Check disc for defects" {
linux ($root)/casper/vmlinuz boot=casper integrity-check quiet splash ---
initrd ($root)/casper/initrd
}
EOF
print_success "EFI boot configuration created."
}
# Enhanced ISO creation
create_iso() {
print_header "Phase 7: Create ISO" # Updated phase number
print_status "Creating bootable ISO using xorriso..."
# Enhanced check for isolinux.bin location for xorriso
local isolinux_bin=""
if [ -f "/usr/lib/syslinux/isolinux.bin" ]; then
isolinux_bin="/usr/lib/syslinux/isolinux.bin"
elif [ -f "/usr/lib/ISOLINUX/isolinux.bin" ]; then
isolinux_bin="/usr/lib/ISOLINUX/isolinux.bin"
fi
if [ -z "$isolinux_bin" ]; then
print_error "Cannot find isolinux.bin for xorriso. Aborting ISO creation."
exit 1
fi
# Enhanced ISO creation using xorriso
# -part_like_isohybrid: Better for modern systems
if sudo xorriso -as mkisofs \
-o "$OUTPUT_DIR/${PROJECT_NAME}-${VERSION}.iso" \
-J -joliet-long \
-r -V "ParticleOS ${VERSION}" \
-b isolinux/isolinux.bin \
-boot-load-size 4 -boot-info-table \
-no-emul-boot -eltorito-alt-boot \
-e EFI/BOOT/bootx64.efi -no-emul-boot \
-isohybrid-mbr "$isolinux_bin" \
-partition_offset 16 \
-part_like_isohybrid \
"$ISO_DIR"; then
print_success "ISO created successfully: $OUTPUT_DIR/${PROJECT_NAME}-${VERSION}.iso"
else
print_error "Failed to create ISO"
exit 1
fi
}
# Enhanced main build process
main() {
echo "🚀 ParticleOS ISO Builder (mmdebstrap) - HARDENED VERSION"
echo "=================================================="
echo "Project: $PROJECT_NAME"
echo "Version: $VERSION"
echo "Build Directory: $BUILD_DIR"
echo "Tool: mmdebstrap"
echo ""
echo "🛡️ This build includes enhanced safety measures:"
echo " - Expanded directory blacklist"
echo " - Enhanced error handling"
echo " - Better mount/unmount management"
echo " - Comprehensive ISOLINUX support"
echo " - Robust cleanup procedures"
echo ""
# Run enhanced build phases
check_prerequisites
clean_build
create_base_system
configure_base_system
create_live_fs # New phase for clarity
setup_boot # New phase for clarity
create_iso
print_header "Build Complete!"
echo ""
echo "🎉 ParticleOS ISO built successfully!"
echo "📁 Location: $OUTPUT_DIR/${PROJECT_NAME}-${VERSION}.iso"
echo ""
echo "🧪 Test the ISO:"
echo " qemu-system-x86_64 -m 4G -enable-kvm \\"
echo " -cdrom $OUTPUT_DIR/${PROJECT_NAME}-${VERSION}.iso \\"
echo " -boot d"
echo ""
echo "🛡️ Enhanced safety measures applied!"
}
# Run main function
main "$@"