8.8 KiB
Dual-Boot Installation Support for Omarchy ISO
Context
Currently, the Omarchy installer wipes the entire selected disk ("wipe": true) and creates two partitions: a 2GB FAT32 ESP and a LUKS-encrypted Btrfs root. This means users must dedicate an entire disk to Omarchy. Adding dual-boot support lets users install alongside an existing OS (typically Windows) while keeping the Omarchy partition encrypted.
Core Approach: pre_mounted_config
Do our own partitioning, then hand off to archinstall for package installation only.
archinstall's manual_partitioning mode has a long history of bugs with existing partitions and LUKS (archinstall issues #998, #2072, #2444). Instead, we:
- Detect existing partitions and free space ourselves (with
sgdisk/parted) - Create the LUKS partition and Btrfs subvolumes ourselves (with
cryptsetup/mkfs.btrfs) - Mount everything at
/mnt/archinstall - Tell archinstall to use
"config_type": "pre_mounted_config"— it installs packages into our pre-mounted tree and does zero disk operations - Handle encryption config (crypttab, mkinitcpio hooks, bootloader cmdline) in post-install
The existing full-disk path stays completely unchanged. Dual-boot is a parallel code path.
Implementation Guardrails
- Run target changes in chroot — all target-system commands (
mkinitcpio -P,limine-scan, bootloader config updates) must run viaarch-chroot "$INSTALL_ROOT" ... - Edit target files under
$INSTALL_ROOTonly — writemkinitcpiohook config,limine.conf,crypttab, and related files in$INSTALL_ROOT/etcand$INSTALL_ROOT/boot(never in the live environment root) - Settle kernel partition state — after creating the new partition, run
partprobe "$DISK"andudevadm settlebeforecryptsetupto avoid device-node races - Deterministic fstab — always generate
/etc/fstabfor dual-boot with overwrite semantics (e.g.,genfstab -U "$INSTALL_ROOT" > "$INSTALL_ROOT/etc/fstab") to avoid duplicate entries on retries - Hook compatibility — when updating mkinitcpio hooks, add
encryptfor busybox initramfs orsd-encryptfor systemd initramfs; never assume one universally - Safe rollback on failure — on any dual-boot setup failure: unmount mounts, close LUKS mapping, and surface recovery guidance; optionally delete the just-created partition only if it was created in this run
Scope Constraints
- UEFI/GPT only — dual-boot on MBR/BIOS is too fragile; those users get full-disk only
- No auto-shrinking — users must free up space from their existing OS first (e.g., Windows Disk Management). Too risky to shrink partitions automatically
- Minimum 20GB free space required for Omarchy
User Flow
Select disk → Detect layout →
If disk has existing OS + ESP + ≥20GB free space:
"How would you like to install?"
→ "Install alongside Windows (use 85GB free space)"
→ "Erase entire disk"
If disk is empty or no free space:
Existing "Confirm overwriting" flow (unchanged)
File Changes
1. configs/airootfs/root/configurator
Add after disk selection (line 233), before config generation:
-
detect_disk_layout()— examines selected disk for:- GPT partition table (required for dual-boot)
- Existing ESP partition (via
lsblkPARTTYPE matching EFI GUID) - Free unallocated space (via
sgdisk -F/-Eorparted print free) - OS detection for installer UX warnings/prompts (not boot correctness): mount ESP read-only, check common vendor paths like
EFI/Microsoft,EFI/systemd,EFI/ubuntu,EFI/fedora,EFI/GRUB
-
install_mode_form()— shows gum choose with dual-boot vs erase options when conditions are met. SetsDUAL_BOOT=true/false.
Generate different JSON when DUAL_BOOT=true:
{
"disk_config": {
"config_type": "pre_mounted_config",
"mountpoint": "/mnt/archinstall"
}
}
No device_modifications, no disk_encryption, no btrfs_options — we handle all of that ourselves. Everything else (hostname, kernels, packages, etc.) stays the same.
Write install_mode.sh alongside the other config files:
DUAL_BOOT=true
DISK=/dev/nvme0n1
ESP_PARTITION=/dev/nvme0n1p1
2. configs/airootfs/root/.automated_script.sh
Source install_mode.sh in run_configurator() if it exists.
Add setup_dual_boot_partitions() — runs before archinstall when DUAL_BOOT=true:
- Select the largest contiguous free region and run
sgdiskto create new partition there (type 8309 = Linux LUKS) partprobe "$DISK"+udevadm settleso new partition node is presentcryptsetup luksFormat+luksOpenwith user's passwordmkfs.btrfson/dev/mapper/omarchy_root- Create Btrfs subvolumes:
@,@home,@log,@pkg - Mount subvolumes at
/mnt/archinstallwithcompress=zstd - Mount existing ESP (untouched) at
/mnt/archinstall/boot - Generate target
/etc/fstabfrom mounted filesystems before post-install steps
Add configure_dual_boot_post_install() — runs after archinstall:
- Write
/etc/crypttabentry using LUKS UUID/PARTUUID (stable identifier), e.g.omarchy_root UUID=<luks-uuid> none luks,discard - Update mkinitcpio hooks before
filesystems:encrypt(busybox) orsd-encrypt(systemd) - Regenerate initramfs with
arch-chroot "$INSTALL_ROOT" mkinitcpio -P - Configure Limine kernel cmdline to match initramfs mode:
- busybox
encrypt:cryptdevice=UUID=<uuid>:omarchy_root root=/dev/mapper/omarchy_root - systemd
sd-encrypt:rd.luks.name=<uuid>=omarchy_root root=/dev/mapper/omarchy_root(or equivalentrd.luks.uuid=form)
- busybox
- Run
arch-chroot "$INSTALL_ROOT" limine-scanto detect other OS boot entries (Windows, etc.) - Preserve current firmware default boot order unless user explicitly chooses to make Limine first (capture and restore
BootOrderifefibootmgrside effects reorder entries)
Use $INSTALL_ROOT variable — defaults to /mnt for full-disk, /mnt/archinstall for dual-boot. Post-install steps (offline mirror bind-mount, sudoers, omarchy copy) reference this instead of hardcoded /mnt.
3. builder/build-iso.sh + builder/archinstall.packages
Add parted to packages available in the live ISO (needed for free space detection).
Bootloader Coexistence
Limine installs to ESP/EFI/arch-limine/ and registers via efibootmgr. This sits alongside:
- Windows Boot Manager at
ESP/EFI/Microsoft/Boot/bootmgfw.efi - Any other bootloader in its own ESP subdirectory
limine-scan auto-detects other UEFI boot entries and adds them to limine.conf. Users can choose OS at boot.
Boot order policy: creating a new EFI entry must not silently change the current default boot target. Keep existing firmware boot order by default and only promote Limine when user opts in.
Implementation note: when registering Limine with efibootmgr, do not pass reorder flags by default; if firmware still changes order, explicitly restore prior BootOrder unless the user opted to promote Limine.
ESP size note: Windows typically creates a 100-260MB ESP. This is enough for Limine + kernel + initramfs, but Snapper boot snapshots (which copy kernels to ESP) will be limited or disabled.
ESP free-space threshold:
- If ESP has
<100MBfree at install time: show a strong warning (do not block install) - If ESP has
<64MBfree: hide dual-boot option and require full-disk install (or user frees space first)
BitLocker warning: If Windows is detected, show a warning about having the BitLocker recovery key ready, since modifying ESP/UEFI entries can trigger recovery.
Edge Cases
| Scenario | Behavior |
|---|---|
| Disk is empty | Full-disk install only (current behavior) |
| Existing OS but no free space | Full-disk install only, with note to free space first |
| Free space < 20GB | Dual-boot option not shown |
| MBR/BIOS disk | Dual-boot option not shown |
| ESP < 100MB free after mount | Warn user; continue allowed |
| ESP < 64MB free after mount | Dual-boot option not shown |
| Multiple free regions | Use largest contiguous region |
| Partitioning/setup failure mid-run | Cleanup mounts/LUKS and abort safely |
Verification
- Regression: Full-disk install in VM with empty disk — must work identically to today
- Dual-boot with Windows: VM with Windows installed + 40GB free space — detect Windows, install alongside, both OSes boot
- Dual-boot with Linux: VM with another Linux + free space — similar test
- No free space: VM with fully partitioned disk — dual-boot option should not appear
- Encryption: After dual-boot install, verify LUKS unlock prompt at boot and encrypted root
- Initramfs mode: Validate both busybox and systemd hook paths boot correctly (
encryptvssd-encrypt) - Boot order: Verify existing default UEFI entry remains unchanged unless user opted to promote Limine