commit 9223981d8a73a01fc1050a299bb6ffe6863fbec0 Author: 28allday Date: Sat Mar 28 12:46:34 2026 +0000 Initial commit: Omarchy dual-boot ISO patcher for Windows coexistence Co-Authored-By: Claude Opus 4.6 (1M context) diff --git a/patch-omarchy-dualboot.sh b/patch-omarchy-dualboot.sh new file mode 100755 index 0000000..b5d823b --- /dev/null +++ b/patch-omarchy-dualboot.sh @@ -0,0 +1,1035 @@ +#!/bin/bash +#=============================================================================== +# PATCH OMARCHY ISO FOR DUAL-BOOT +# +# This version hooks into the original Omarchy installer (archinstall) +# We just handle partitioning, then let archinstall do everything else +# +# Usage: sudo ./patch-omarchy-dualboot.sh omarchy-3.x.x.iso +#=============================================================================== + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[✓]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[✗]${NC} $1"; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +[[ $EUID -ne 0 ]] && error "Run as root: sudo $0" + +# Auto-detect ISO if not provided +if [[ -z "$1" ]]; then + SOURCE_ISO=$(find "$SCRIPT_DIR" -maxdepth 1 -name "omarchy-*.iso" ! -name "omarchy-dualboot-*" ! -name "omarchy-installer-*" 2>/dev/null | head -1) + if [[ -z "$SOURCE_ISO" ]]; then + error "No Omarchy ISO found in $SCRIPT_DIR + +Please either: + 1. Place omarchy-*.iso in the same directory as this script + 2. Or specify the path: sudo $0 /path/to/omarchy.iso" + fi + log "Auto-detected ISO: $(basename "$SOURCE_ISO")" +else + SOURCE_ISO="$1" +fi + +[[ ! -f "$SOURCE_ISO" ]] && error "ISO not found: $SOURCE_ISO" +WORK_DIR="$SCRIPT_DIR/.omarchy-patch-$$" +OUTPUT_ISO="$SCRIPT_DIR/omarchy-dualboot-$(date +%Y.%m.%d).iso" + +# Check and install dependencies +MISSING_PKGS="" +command -v xorriso &>/dev/null || MISSING_PKGS="$MISSING_PKGS xorriso" +command -v unsquashfs &>/dev/null || MISSING_PKGS="$MISSING_PKGS squashfs-tools" +command -v isoinfo &>/dev/null || MISSING_PKGS="$MISSING_PKGS cdrtools" + +if [[ -n "$MISSING_PKGS" ]]; then + log "Installing missing dependencies:$MISSING_PKGS" + pacman -S --noconfirm --needed $MISSING_PKGS || error "Failed to install dependencies" + log "Dependencies installed" +fi + +log "Patching: $SOURCE_ISO" + +# Cleanup on exit +cleanup() { + umount "$WORK_DIR/iso" 2>/dev/null || true + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +mkdir -p "$WORK_DIR"/{iso,extracted,newiso} + +#=============================================================================== +# EXTRACT ISO +#=============================================================================== + +log "Extracting ISO..." +mount -o loop,ro "$SOURCE_ISO" "$WORK_DIR/iso" +cp -a "$WORK_DIR/iso/"* "$WORK_DIR/newiso/" +umount "$WORK_DIR/iso" + +# Find squashfs +SQUASHFS="$WORK_DIR/newiso/arch/x86_64/airootfs.sfs" +[[ ! -f "$SQUASHFS" ]] && error "Squashfs not found at expected path" + +log "Extracting squashfs (this takes a few minutes)..." +unsquashfs -d "$WORK_DIR/extracted" "$SQUASHFS" + +# Verify squashfs structure +if [[ ! -d "$WORK_DIR/extracted/root" ]]; then + error "Unexpected squashfs structure: /root directory not found!" +fi + +log "Original /root contents:" +ls -la "$WORK_DIR/extracted/root/" | grep -E '\.(zlogin|zprofile|automated|bash)' || echo " (no matching files)" + +#=============================================================================== +# CREATE DUAL-BOOT WRAPPER SCRIPT +#=============================================================================== + +log "Creating dual-boot wrapper..." + +# This script runs BEFORE the original installer +# It handles partitioning, then lets archinstall do the rest +cat > "$WORK_DIR/extracted/root/dualboot-setup.sh" << 'DUALBOOT_SCRIPT' +#!/bin/bash +#=============================================================================== +# DUAL-BOOT PARTITION SETUP +# Creates partitions in free space, then hands off to archinstall +#=============================================================================== + +set -e + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' + +log() { echo -e "${GREEN}[✓]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[✗]${NC} $1"; exit 1; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +header() { + echo "" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BOLD} $1${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" +} + +cleanup_and_exit() { + warn "Setup cancelled or failed. Cleaning up..." + umount -R /mnt 2>/dev/null || true + cryptsetup close cryptroot 2>/dev/null || true + exit 1 +} + +#=============================================================================== +# CLEANUP FUNCTION FOR FAILED INSTALLATIONS +#=============================================================================== + +cleanup_failed_install() { + header "CLEANUP FAILED INSTALLATION" + + echo "This will remove Linux partitions from a failed installation attempt." + echo "Windows partitions will NOT be touched." + echo "" + + echo "Available drives:" + lsblk -d -o NAME,SIZE,MODEL | grep -E "^(nvme|sd|vd)" + echo "" + + read -p "Enter drive to clean (e.g., nvme0n1): " drive_input + DRIVE="/dev/$drive_input" + + [[ ! -b "$DRIVE" ]] && error "Drive not found: $DRIVE" + [[ "$DRIVE" == *"nvme"* ]] && PART_PREFIX="${DRIVE}p" || PART_PREFIX="$DRIVE" + + echo "" + echo "Current partitions:" + lsblk -o NAME,SIZE,FSTYPE,LABEL "$DRIVE" + echo "" + + cryptsetup close cryptroot 2>/dev/null || true + umount -R /mnt 2>/dev/null || true + + LINUX_EFI="" + LINUX_ROOT="" + + for part in ${PART_PREFIX}*; do + [[ "$part" == "$DRIVE" || "$part" == "${PART_PREFIX}" ]] && continue + [[ ! -b "$part" ]] && continue + + LABEL=$(lsblk -n -o LABEL "$part" 2>/dev/null | tr -d ' ') + FSTYPE=$(lsblk -n -o FSTYPE "$part" 2>/dev/null | tr -d ' ') + + if [[ "$LABEL" == "LINUXEFI" ]]; then + LINUX_EFI="$part" + echo -e "${YELLOW}Found Linux EFI partition: $part${NC}" + elif [[ "$FSTYPE" == "crypto_LUKS" ]]; then + LINUX_ROOT="$part" + echo -e "${YELLOW}Found LUKS partition: $part${NC}" + fi + done + + if [[ -z "$LINUX_EFI" && -z "$LINUX_ROOT" ]]; then + echo "" + info "No Linux partitions found from failed installation." + read -p "Press Enter to continue..." _ + return 1 + fi + + echo "" + echo -e "${RED}WARNING: The following partitions will be DELETED:${NC}" + [[ -n "$LINUX_EFI" ]] && echo " - $LINUX_EFI (Linux EFI)" + [[ -n "$LINUX_ROOT" ]] && echo " - $LINUX_ROOT (Linux Root/LUKS)" + echo "" + + read -p "Type 'DELETE' to confirm removal: " confirm + if [[ "$confirm" != "DELETE" ]]; then + echo "Cancelled." + return 1 + fi + + if [[ -n "$LINUX_ROOT" ]]; then + PART_NUM=$(echo "$LINUX_ROOT" | grep -oE '[0-9]+$') + log "Deleting partition $PART_NUM..." + sgdisk -d "$PART_NUM" "$DRIVE" + fi + + if [[ -n "$LINUX_EFI" ]]; then + PART_NUM=$(echo "$LINUX_EFI" | grep -oE '[0-9]+$') + log "Deleting partition $PART_NUM..." + sgdisk -d "$PART_NUM" "$DRIVE" + fi + + partprobe "$DRIVE" + + for entry in $(efibootmgr 2>/dev/null | grep -i "Omarchy" | sed -n 's/^Boot\([0-9A-Fa-f]*\).*/\1/p'); do + efibootmgr -b "$entry" -B 2>/dev/null || true + done + + log "Cleanup complete!" + echo "" + lsblk -o NAME,SIZE,FSTYPE,LABEL "$DRIVE" + echo "" + read -p "Press Enter to continue..." _ + return 0 +} + +#=============================================================================== +# MAIN MENU +#=============================================================================== + +header "OMARCHY DUAL-BOOT INSTALLER" + +echo "This installer will set up Omarchy alongside Windows." +echo "Windows partitions will NOT be touched." +echo "" +echo "Options:" +echo " 1) Install Omarchy (dual-boot with Windows)" +echo " 2) Clean up failed installation" +echo " 3) Standard install (wipe entire disk)" +echo " 4) Exit to shell" +echo "" +read -p "Select option [1]: " MENU_CHOICE +MENU_CHOICE=${MENU_CHOICE:-1} + +case $MENU_CHOICE in + 2) + cleanup_failed_install || true + exec /root/dualboot-setup.sh + ;; + 3) + # Run original installer + info "Starting standard Omarchy installer..." + exec /root/.automated_script.sh.orig + ;; + 4) + echo "Type '/root/dualboot-setup.sh' to restart installer." + exit 0 + ;; + 1) + # Continue with dual-boot setup + ;; + *) + exec /root/dualboot-setup.sh + ;; +esac + +#=============================================================================== +# PRE-FLIGHT CHECKS +#=============================================================================== + +[[ ! -d /sys/firmware/efi ]] && error "UEFI mode required for dual-boot" + +header "SELECT DRIVE" + +echo "Available drives:" +echo "" +lsblk -d -o NAME,SIZE,MODEL | grep -E "^(nvme|sd|vd)" +echo "" + +mapfile -t DRIVES < <(lsblk -d -n -o NAME,TRAN | awk '$2!="usb" {print $1}' | grep -E "^(nvme|sd|vd)") + +if [[ ${#DRIVES[@]} -eq 1 ]]; then + DRIVE="/dev/${DRIVES[0]}" + info "Found: $DRIVE" + read -p "Use this drive? (Y/n): " confirm + [[ "$confirm" =~ ^[Nn]$ ]] && error "Cancelled" +else + read -p "Enter drive (e.g., nvme0n1): " drive_input + DRIVE="/dev/$drive_input" +fi + +[[ ! -b "$DRIVE" ]] && error "Drive not found: $DRIVE" +[[ "$DRIVE" == *"nvme"* ]] && PART_PREFIX="${DRIVE}p" || PART_PREFIX="$DRIVE" + +echo "" +echo "Current partitions on $DRIVE:" +lsblk -o NAME,SIZE,FSTYPE,LABEL "$DRIVE" +echo "" + +# Check for Windows +if lsblk -o FSTYPE "$DRIVE" | grep -q ntfs; then + log "Windows detected - it will be preserved" +else + warn "Windows not detected on this drive" + read -p "Continue anyway? (y/N): " cont + [[ ! "$cont" =~ ^[Yy]$ ]] && error "Cancelled" +fi + +# Check free space +FREE_SECTORS=$(sgdisk -p "$DRIVE" 2>/dev/null | grep "Total free space" | awk '{print $5}') +if [[ "$FREE_SECTORS" =~ ^[0-9]+$ ]]; then + FREE_GB=$((FREE_SECTORS * 512 / 1024 / 1024 / 1024)) + [[ $FREE_GB -lt 20 ]] && error "Need 20GB+ free space, found ${FREE_GB}GB" + log "Free space: ${FREE_GB}GB" +else + warn "Could not determine free space" +fi + +#=============================================================================== +# ENCRYPTION PASSWORD +#=============================================================================== + +header "DISK ENCRYPTION" + +echo "Your Linux partition will be encrypted with LUKS2." +echo "You'll enter your user password in the next step (configurator)." +echo "" + +while true; do + read -s -p "Enter LUKS encryption password: " LUKS_PASS; echo "" + read -s -p "Confirm password: " LUKS_PASS2; echo "" + [[ "$LUKS_PASS" == "$LUKS_PASS2" ]] && break + warn "Passwords don't match, try again" +done + +#=============================================================================== +# CONFIRMATION +#=============================================================================== + +header "CONFIRM PARTITIONING" + +echo "The following will be created in FREE SPACE on $DRIVE:" +echo "" +echo " • 1GB EFI partition (for Linux bootloader)" +echo " • Rest: LUKS2 encrypted partition (for Linux root)" +echo "" +echo -e "${GREEN}Windows partitions will NOT be touched.${NC}" +echo "" +read -p "Type 'yes' to continue: " confirm +[[ "$confirm" != "yes" ]] && error "Cancelled" + +#=============================================================================== +# CREATE PARTITIONS +#=============================================================================== + +header "CREATING PARTITIONS" + +trap cleanup_and_exit ERR + +DRIVE_NAME=$(basename "$DRIVE") +LAST_PART=$(lsblk -n -o NAME "$DRIVE" | grep -v "^${DRIVE_NAME}$" | grep -oE '[0-9]+$' | sort -n | tail -1) +LAST_PART=${LAST_PART:-0} + +# Create EFI partition +log "Creating Linux EFI partition (1GB)..." +NEXT=$((LAST_PART + 1)) +sgdisk -n "${NEXT}:0:+1G" -t "${NEXT}:ef00" "$DRIVE" +EFI_PART="${PART_PREFIX}${NEXT}" +partprobe "$DRIVE"; udevadm settle; sleep 2 +mkfs.fat -F32 -n "LINUXEFI" "$EFI_PART" +log "Created: $EFI_PART" + +# Create root partition +log "Creating Linux root partition..." +NEXT=$((NEXT + 1)) +sgdisk -n "${NEXT}:0:0" -t "${NEXT}:8300" "$DRIVE" +ROOT_PART="${PART_PREFIX}${NEXT}" +partprobe "$DRIVE"; udevadm settle; sleep 2 +log "Created: $ROOT_PART" + +#=============================================================================== +# SETUP ENCRYPTION & FILESYSTEM +#=============================================================================== + +header "SETTING UP ENCRYPTION" + +log "Formatting LUKS2..." +echo -n "$LUKS_PASS" | cryptsetup luksFormat --type luks2 --key-file=- "$ROOT_PART" + +log "Opening encrypted volume..." +echo -n "$LUKS_PASS" | cryptsetup open --key-file=- "$ROOT_PART" cryptroot +LUKS_UUID=$(blkid -s UUID -o value "$ROOT_PART") + +log "Creating btrfs filesystem..." +mkfs.btrfs -f /dev/mapper/cryptroot + +log "Creating subvolumes..." +mount /dev/mapper/cryptroot /mnt +btrfs subvolume create /mnt/@ +btrfs subvolume create /mnt/@home +btrfs subvolume create /mnt/@log +btrfs subvolume create /mnt/@pkg +# NOTE: Do NOT create @snapshots - snapper will create .snapshots as nested subvolume +umount /mnt + +log "Mounting filesystems..." +mount -o compress=zstd,subvol=@ /dev/mapper/cryptroot /mnt +mkdir -p /mnt/{home,var/log,var/cache/pacman/pkg,boot} +mount -o compress=zstd,subvol=@home /dev/mapper/cryptroot /mnt/home +mount -o compress=zstd,subvol=@log /dev/mapper/cryptroot /mnt/var/log +mount -o compress=zstd,subvol=@pkg /dev/mapper/cryptroot /mnt/var/cache/pacman/pkg +# NOTE: Do NOT mount .snapshots - snapper creates it as nested subvolume in @ +mount "$EFI_PART" /mnt/boot + +log "Partitions ready!" + +# Save info for later +echo "$LUKS_UUID" > /tmp/luks_uuid +echo "$EFI_PART" > /tmp/efi_part +echo "$ROOT_PART" > /tmp/root_part +echo "$DRIVE" > /tmp/install_drive + +trap - ERR + +#=============================================================================== +# HAND OFF TO ARCHINSTALL +#=============================================================================== + +header "STARTING OMARCHY INSTALLER" + +echo "Partitions are ready. Now the Omarchy installer will collect your" +echo "user information and install the system." +echo "" +info "The disk is already partitioned - just enter your user details." +echo "" +read -p "Press Enter to continue to Omarchy setup..." _ + +# Run the configurator to get user info +cd /root +source /root/omarchy/install/helpers/all.sh 2>/dev/null || true + +# Set colors +if [[ $(tty) == "/dev/tty"* ]]; then + echo -en "\e]P01a1b26" + echo -en "\e]P7a9b1d6" + echo -en "\e]PFc0caf5" + echo -en "\033[0m" + clear +fi + +# Run configurator +./configurator + +# Get username from generated config +OMARCHY_USER=$(jq -r '.users[0].username' user_credentials.json 2>/dev/null) + +if [[ -z "$OMARCHY_USER" || "$OMARCHY_USER" == "null" ]]; then + error "Failed to get username from configurator" +fi + +log "User: $OMARCHY_USER" + +#=============================================================================== +# MODIFY CONFIG FOR PRE-MOUNTED PARTITIONS +#=============================================================================== + +header "CONFIGURING INSTALLATION" + +log "Updating configuration for dual-boot..." + +# Only modify disk_config and remove bootloader, preserve everything else from generated config +# This keeps the correct kernel, locale, packages, profile, etc. +# We MUST remove bootloader because archinstall crashes when trying to +# detect root device with pre_mounted_config - it fails BEFORE installing packages +# We install limine manually after archinstall completes +jq '.disk_config = {"config_type": "pre_mounted_config", "mountpoint": "/mnt"} | del(.bootloader)' \ + user_configuration.json > user_configuration.json.tmp +mv user_configuration.json.tmp user_configuration.json + +# Extract kernel name for later use in bootloader config +KERNEL_NAME=$(jq -r '.kernels[0] // "linux"' user_configuration.json) +echo "$KERNEL_NAME" > /tmp/kernel_name +log "Using kernel: $KERNEL_NAME" + +log "Configuration updated for pre-mounted partitions" + +#=============================================================================== +# RUN ARCHINSTALL +#=============================================================================== + +header "INSTALLING SYSTEM" + +log "Running archinstall..." +info "This will take several minutes..." + +# Initialize keyring +pacman-key --init +pacman-key --populate archlinux +pacman-key --populate omarchy 2>/dev/null || true + +# For offline install, ensure we use the offline-only pacman config +# and skip database sync since the database is already on the ISO +cat > /tmp/pacman-offline.conf << 'PACCONF' +[options] +HoldPkg = pacman glibc +Architecture = auto +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional + +[offline] +SigLevel = Optional TrustAll +Server = file:///var/cache/omarchy/mirror/offline/ +PACCONF + +# Overwrite system pacman.conf with offline-only version +cp /tmp/pacman-offline.conf /etc/pacman.conf + +# Debug: verify config +log "Pacman config set to offline-only:" +grep -E "^\[|^Server" /etc/pacman.conf + +# Skip pacman -Sy for offline install - database already exists on ISO +# Check for either offline.db or offline.db.tar.gz +if [[ -f /var/cache/omarchy/mirror/offline/offline.db ]] || [[ -f /var/cache/omarchy/mirror/offline/offline.db.tar.gz ]]; then + log "Offline package database found - skipping sync" +else + warn "Offline database not found - attempting sync..." + pacman --config /tmp/pacman-offline.conf -Sy --noconfirm +fi + +# Run archinstall with our config (same flags as original Omarchy installer) +# bootloader=none prevents archinstall from crashing on pre_mounted_config +archinstall \ + --config user_configuration.json \ + --creds user_credentials.json \ + --silent \ + --skip-ntp \ + --skip-wkd \ + --skip-wifi-check + +# Verify archinstall created the user +if [[ ! -d /mnt/home/$OMARCHY_USER ]]; then + error "archinstall failed - user $OMARCHY_USER not created" +fi + +log "Base system installed!" + +# Install limine bootloader package (archinstall didn't because we removed bootloader config) +log "Installing limine bootloader..." +# Copy offline pacman config to chroot and mount offline mirror first +cp /tmp/pacman-offline.conf /mnt/etc/pacman.conf +mkdir -p /mnt/var/cache/omarchy/mirror/offline +mount --bind /var/cache/omarchy/mirror/offline /mnt/var/cache/omarchy/mirror/offline +# Install limine using offline repo (no -Sy needed, packages are local) +arch-chroot /mnt pacman --noconfirm --needed -S limine + +#=============================================================================== +# POST-INSTALL CONFIGURATION +#=============================================================================== + +header "CONFIGURING BOOTLOADER" + +LUKS_UUID=$(cat /tmp/luks_uuid) +KERNEL_NAME=$(cat /tmp/kernel_name) + +# Configure mkinitcpio for encryption using Omarchy's drop-in approach +log "Configuring initramfs for encryption..." + +# Install required packages +log "Installing plymouth and limine tools..." +arch-chroot /mnt pacman --noconfirm --needed -S plymouth limine-snapper-sync limine-mkinitcpio-hook || warn "Some packages may already be installed" + +# Create Omarchy-style mkinitcpio drop-in config with encryption support +# This uses the same hooks as Omarchy but includes encrypt for LUKS +mkdir -p /mnt/etc/mkinitcpio.conf.d +cat > /mnt/etc/mkinitcpio.conf.d/omarchy_hooks.conf << 'MKINIT' +HOOKS=(base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs) +MKINIT + +cat > /mnt/etc/mkinitcpio.conf.d/thunderbolt_module.conf << 'MKINIT' +MODULES+=(thunderbolt) +MKINIT + +# Rebuild initramfs (warnings about missing firmware are OK) +log "Building initramfs (warnings about missing firmware are normal)..." +if ! arch-chroot /mnt mkinitcpio -P; then + warn "mkinitcpio reported warnings - checking if initramfs was created..." +fi + +# Verify the initramfs was actually created for our kernel +if [[ -f /mnt/boot/initramfs-${KERNEL_NAME}.img ]]; then + log "initramfs-${KERNEL_NAME}.img created successfully" +elif [[ -f /mnt/boot/initramfs-linux.img ]]; then + log "initramfs-linux.img created successfully" +else + error "initramfs was not created! Check mkinitcpio output above." +fi + +# Verify kernel was installed +if [[ ! -f /mnt/boot/vmlinuz-$KERNEL_NAME ]]; then + error "Kernel vmlinuz-$KERNEL_NAME not found in /mnt/boot/" +fi +log "Found kernel: vmlinuz-$KERNEL_NAME" + +# Configure Limine bootloader using Omarchy's approach +log "Configuring Limine bootloader..." +mkdir -p /mnt/boot/EFI/BOOT /mnt/boot/EFI/limine + +# Copy Limine EFI files +cp /mnt/usr/share/limine/BOOTX64.EFI /mnt/boot/EFI/BOOT/ +cp /mnt/usr/share/limine/BOOTX64.EFI /mnt/boot/EFI/limine/ + +# Create /etc/default/limine for limine-update (Omarchy style) +CRYPT_CMDLINE="cryptdevice=UUID=$LUKS_UUID:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@" +cat > /mnt/etc/default/limine << LIMINEDEF +TARGET_OS_NAME="Omarchy" + +ESP_PATH="/boot" + +KERNEL_CMDLINE[default]="$CRYPT_CMDLINE rw quiet splash" + +ENABLE_UKI=yes +CUSTOM_UKI_NAME="omarchy" + +ENABLE_LIMINE_FALLBACK=yes + +# Find and add other bootloaders (Windows) +FIND_BOOTLOADERS=yes + +BOOT_ORDER="*, *fallback, Snapshots" + +MAX_SNAPSHOT_ENTRIES=5 + +SNAPSHOT_FORMAT_CHOICE=5 +LIMINEDEF + +# Create base limine.conf (limine-update will add entries) +cat > /mnt/boot/limine.conf << 'LIMINE' +default_entry: 2 +interface_branding: Omarchy Bootloader +interface_branding_color: 2 +hash_mismatch_panic: no + +term_background: 1a1b26 +backdrop: 1a1b26 + +term_palette: 15161e;f7768e;9ece6a;e0af68;7aa2f7;bb9af7;7dcfff;a9b1d6 +term_palette_bright: 414868;f7768e;9ece6a;e0af68;7aa2f7;bb9af7;7dcfff;c0caf5 + +term_foreground: c0caf5 +term_foreground_bright: c0caf5 +term_background_bright: 24283b +LIMINE + +# Run limine-update to generate boot entries (including snapshots) +log "Running limine-update to generate boot entries..." +arch-chroot /mnt limine-update || warn "limine-update had issues" + +# Enable btrfs quota for space-aware snapshot cleanup +log "Enabling btrfs quota..." +btrfs quota enable /mnt || warn "Could not enable btrfs quota" + +#=============================================================================== +# INSTALL OMARCHY +#=============================================================================== + +header "INSTALLING OMARCHY PACKAGES" + +# Mount offline mirror if available +if [[ -d /var/cache/omarchy/mirror/offline ]]; then + mkdir -p /mnt/var/cache/omarchy/mirror/offline + mount --bind /var/cache/omarchy/mirror/offline /mnt/var/cache/omarchy/mirror/offline +fi + +if [[ -d /opt/packages ]]; then + mkdir -p /mnt/opt/packages + mount --bind /opt/packages /mnt/opt/packages +fi + +# Backup system pacman.conf and use ISO's version temporarily for offline install +cp /mnt/etc/pacman.conf /mnt/etc/pacman.conf.bak +cp /etc/pacman.conf /mnt/etc/pacman.conf + +# Copy omarchy to user's home +mkdir -p /mnt/home/$OMARCHY_USER/.local/share/ +cp -a /root/omarchy /mnt/home/$OMARCHY_USER/.local/share/ +# Ensure all scripts are executable +chmod -R +x /mnt/home/$OMARCHY_USER/.local/share/omarchy/bin/ +chmod -R +x /mnt/home/$OMARCHY_USER/.local/share/omarchy/install/ +find /mnt/home/$OMARCHY_USER/.local/share/omarchy -name "*.sh" -exec chmod +x {} \; +arch-chroot /mnt chown -R $OMARCHY_USER:$OMARCHY_USER /home/$OMARCHY_USER/.local/ + +# Get user info from configurator-generated files +USER_FULL_NAME="" +USER_EMAIL="" +[[ -f /root/user_full_name.txt ]] && USER_FULL_NAME=$( /mnt/etc/pacman.conf << 'PACMANCONF' +[options] +Color +ILoveCandy +VerbosePkgLists +HoldPkg = pacman glibc +Architecture = auto +CheckSpace +ParallelDownloads = 5 +DownloadUser = alpm + +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional + +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +[multilib] +Include = /etc/pacman.d/mirrorlist + +[omarchy] +SigLevel = Optional TrustAll +Server = https://pkgs.omarchy.org/stable/$arch +PACMANCONF + +# Set up mirrorlist +cat > /mnt/etc/pacman.d/mirrorlist << 'MIRRORLIST' +Server = https://stable-mirror.omarchy.org/$repo/os/$arch +MIRRORLIST + +log "Pacman configured with Omarchy stable repos" + +# Now that Omarchy is installed, set Plymouth theme and rebuild initramfs +log "Setting Omarchy Plymouth theme..." +if [[ -d /mnt/usr/share/plymouth/themes/omarchy ]]; then + arch-chroot /mnt plymouth-set-default-theme omarchy + log "Rebuilding initramfs with Omarchy Plymouth theme..." + arch-chroot /mnt mkinitcpio -P +else + warn "Omarchy Plymouth theme not found" +fi + +#=============================================================================== +# FINAL SETUP +#=============================================================================== + +header "FINAL CONFIGURATION" + +# Enable services +log "Enabling services..." +arch-chroot /mnt systemctl enable sddm 2>/dev/null || true +arch-chroot /mnt systemctl enable NetworkManager 2>/dev/null || true +arch-chroot /mnt systemctl enable iwd 2>/dev/null || true +arch-chroot /mnt systemctl enable bluetooth 2>/dev/null || true + +# Snapper configuration +log "Installing and configuring snapper..." +arch-chroot /mnt pacman --noconfirm --needed -S snapper 2>/dev/null || true + +# Create snapper config for root - this creates .snapshots as nested subvolume in @ +log "Creating snapper root config..." +arch-chroot /mnt snapper -c root create-config / || warn "snapper create-config had issues" + +# Enable snapper services +arch-chroot /mnt systemctl enable snapper-cleanup.timer 2>/dev/null || true +arch-chroot /mnt systemctl enable snapper-timeline.timer 2>/dev/null || true +arch-chroot /mnt systemctl enable limine-snapper-sync.service 2>/dev/null || true + +# Create initial snapshot +log "Creating initial snapshot..." +arch-chroot /mnt snapper -c root create -d "Fresh install" || warn "Could not create initial snapshot" + +# Sync snapshots to limine boot menu +log "Syncing snapshots to limine..." +arch-chroot /mnt limine-snapper-sync 2>/dev/null || warn "limine-snapper-sync had issues (may need to run after first boot)" + +log "Snapper configured with bootable snapshots" + +# Configure SDDM auto-login (Omarchy uses hyprland-uwsm session) +log "Configuring auto-login for $OMARCHY_USER..." +mkdir -p /mnt/etc/sddm.conf.d +cat > /mnt/etc/sddm.conf.d/autologin.conf << SDDMCONF +[Autologin] +User=$OMARCHY_USER +Session=hyprland-uwsm +[Theme] +Current=breeze +SDDMCONF + +# Create UEFI boot entry +log "Creating UEFI boot entry..." +DRIVE=$(cat /tmp/install_drive) +EFI_PART=$(cat /tmp/efi_part) +EFI_PART_NUM=$(echo "$EFI_PART" | grep -oE '[0-9]+$') + +# Remove any existing Omarchy or Limine entries to avoid duplicates +for entry in $(efibootmgr 2>/dev/null | grep -iE "Omarchy|Limine" | sed -n 's/^Boot\([0-9A-Fa-f]*\).*/\1/p'); do + log "Removing old boot entry: $entry" + efibootmgr -b "$entry" -B 2>/dev/null || true +done + +# Create single Omarchy entry +efibootmgr --create --disk "$DRIVE" --part "$EFI_PART_NUM" \ + --loader '\\EFI\\BOOT\\BOOTX64.EFI' --label 'Omarchy' 2>/dev/null || \ + warn "Could not create UEFI entry - use F12 boot menu" + +#=============================================================================== +# CLEANUP +#=============================================================================== + +header "CLEANING UP" + +umount /mnt/var/cache/omarchy/mirror/offline 2>/dev/null || true +umount /mnt/opt/packages 2>/dev/null || true +umount -R /mnt +cryptsetup close cryptroot + +rm -f /tmp/luks_uuid /tmp/efi_part /tmp/root_part /tmp/install_drive /tmp/kernel_name + +#=============================================================================== +# DONE +#=============================================================================== + +header "INSTALLATION COMPLETE!" + +echo "" +echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ Omarchy has been installed alongside Windows! ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo "To boot:" +echo " 1. Reboot your computer" +echo " 2. Press F12/F8/DEL at startup for boot menu" +echo " 3. Select 'Omarchy' for Linux" +echo " 4. Select 'Windows Boot Manager' for Windows" +echo "" +echo " Username: $OMARCHY_USER" +echo "" +read -p "Press Enter to reboot..." _ +reboot +DUALBOOT_SCRIPT + +chmod +x "$WORK_DIR/extracted/root/dualboot-setup.sh" + +#=============================================================================== +# MODIFY BOOT SEQUENCE +#=============================================================================== + +log "Configuring boot sequence..." + +# Backup original automated script +if [[ -f "$WORK_DIR/extracted/root/.automated_script.sh" ]]; then + mv "$WORK_DIR/extracted/root/.automated_script.sh" "$WORK_DIR/extracted/root/.automated_script.sh.orig" +fi + +# Replace .zlogin to run our dual-boot setup +cat > "$WORK_DIR/extracted/root/.zlogin" << 'EOF' +# Omarchy Dual-Boot Installer +if [[ $(tty) == "/dev/tty1" ]]; then + /root/dualboot-setup.sh +fi +EOF + +log "Boot sequence configured" + +#=============================================================================== +# REPACK SQUASHFS +#=============================================================================== + +log "Repacking squashfs (this takes several minutes)..." +rm "$SQUASHFS" +mksquashfs "$WORK_DIR/extracted" "$SQUASHFS" -comp zstd -Xcompression-level 3 + +if [[ ! -f "$SQUASHFS" ]]; then + error "Failed to create squashfs!" +fi +log "New squashfs created: $(du -h "$SQUASHFS" | cut -f1)" + +#=============================================================================== +# UPDATE BOOT CONFIGS +#=============================================================================== + +log "Creating new ISO..." + +ISO_LABEL=$(isoinfo -d -i "$SOURCE_ISO" 2>/dev/null | grep "Volume id:" | cut -d: -f2 | tr -d ' ' || echo "OMARCHY") +log "ISO label: $ISO_LABEL" + +# Update boot configs to use label instead of UUID +log "Updating boot configurations..." +for cfg in "$WORK_DIR/newiso/boot/syslinux/"*.cfg \ + "$WORK_DIR/newiso/loader/entries/"*.conf \ + "$WORK_DIR/newiso/boot/grub/grub.cfg" \ + "$WORK_DIR/newiso/EFI/BOOT/grub.cfg"; do + if [[ -f "$cfg" ]]; then + sed -i "s/archisosearchuuid=[^ ]*/archisolabel=$ISO_LABEL/g" "$cfg" + fi +done + +#=============================================================================== +# EXTRACT EFI BOOT IMAGE FROM ORIGINAL ISO +#=============================================================================== + +log "Extracting EFI boot image from original ISO..." + +# Get the xorriso parameters from the original ISO to replicate its boot setup +XORRISO_PARAMS=$(xorriso -indev "$SOURCE_ISO" -report_el_torito as_mkisofs 2>/dev/null) + +# Extract the appended EFI partition from the original ISO +# Format: -append_partition 2 0xef --interval:local_fs:STARTd-ENDd:: +EFI_LINE=$(echo "$XORRISO_PARAMS" | grep "append_partition 2 0xef") +if [[ -n "$EFI_LINE" ]]; then + log "Found EFI partition in original ISO" + # Extract start and end from the interval (format: NUMBERd-NUMBERd) + EFI_RANGE=$(echo "$EFI_LINE" | grep -oE '[0-9]+d-[0-9]+d') + EFI_START=$(echo "$EFI_RANGE" | cut -d'-' -f1 | tr -d 'd') + EFI_END=$(echo "$EFI_RANGE" | cut -d'-' -f2 | tr -d 'd') + + if [[ -n "$EFI_START" && -n "$EFI_END" ]]; then + EFI_SIZE=$((EFI_END - EFI_START + 1)) + log "Extracting EFI partition: start=$EFI_START end=$EFI_END size=$EFI_SIZE sectors" + # The 'd' values from xorriso are in 512-byte sectors + dd if="$SOURCE_ISO" of="$WORK_DIR/efi.img" bs=512 skip="$EFI_START" count="$EFI_SIZE" status=progress + + if [[ -f "$WORK_DIR/efi.img" ]]; then + EFI_IMG_SIZE=$(stat -c%s "$WORK_DIR/efi.img") + log "EFI image created: $((EFI_IMG_SIZE / 1024 / 1024))MB" + else + warn "Failed to extract EFI image" + fi + else + warn "Could not parse EFI partition range" + fi +else + warn "No EFI partition found in original ISO" +fi + +#=============================================================================== +# CREATE ISO +#=============================================================================== + +if [[ -f "$WORK_DIR/newiso/boot/syslinux/isolinux.bin" ]]; then + log "Creating hybrid BIOS/UEFI ISO..." + + if [[ -f "$WORK_DIR/efi.img" ]]; then + # Use extracted EFI image for proper UEFI boot + xorriso -as mkisofs \ + -iso-level 3 \ + -full-iso9660-filenames \ + -rational-rock \ + -volid "$ISO_LABEL" \ + -eltorito-boot boot/syslinux/isolinux.bin \ + -eltorito-catalog boot/syslinux/boot.cat \ + -no-emul-boot \ + -boot-load-size 4 \ + -boot-info-table \ + -isohybrid-mbr "$WORK_DIR/newiso/boot/syslinux/isohdpfx.bin" \ + -eltorito-alt-boot \ + -e --interval:appended_partition_2:all:: \ + -no-emul-boot \ + -append_partition 2 0xef "$WORK_DIR/efi.img" \ + -isohybrid-gpt-basdat \ + -o "$OUTPUT_ISO" \ + "$WORK_DIR/newiso" + else + warn "EFI image not found, using fallback method..." + xorriso -as mkisofs \ + -iso-level 3 \ + -full-iso9660-filenames \ + -rational-rock \ + -volid "$ISO_LABEL" \ + -eltorito-boot boot/syslinux/isolinux.bin \ + -eltorito-catalog boot/syslinux/boot.cat \ + -no-emul-boot \ + -boot-load-size 4 \ + -boot-info-table \ + -isohybrid-mbr "$WORK_DIR/newiso/boot/syslinux/isohdpfx.bin" \ + -eltorito-alt-boot \ + -e EFI/BOOT/BOOTX64.EFI \ + -no-emul-boot \ + -isohybrid-gpt-basdat \ + -o "$OUTPUT_ISO" \ + "$WORK_DIR/newiso" + fi +else + log "Creating UEFI-only ISO..." + xorriso -as mkisofs \ + -iso-level 3 \ + -full-iso9660-filenames \ + -volid "$ISO_LABEL" \ + -e EFI/BOOT/BOOTX64.EFI \ + -no-emul-boot \ + -isohybrid-gpt-basdat \ + -o "$OUTPUT_ISO" \ + "$WORK_DIR/newiso" +fi + +#=============================================================================== +# DONE +#=============================================================================== + +echo "" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN} SUCCESS!${NC}" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +echo " Created: $OUTPUT_ISO" +echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" +echo "" +echo " This ISO provides:" +echo " • Option 1: Dual-boot install (preserves Windows)" +echo " • Option 3: Standard install (original Omarchy installer)" +echo "" +echo " Next steps:" +echo " 1. Copy ISO to Ventoy USB drive" +echo " 2. Boot from Ventoy" +echo " 3. Select option 1 for dual-boot" +echo ""