Files
omarchy-iso/plans/dual-boot.md
T

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:

  1. Detect existing partitions and free space ourselves (with sgdisk/parted)
  2. Create the LUKS partition and Btrfs subvolumes ourselves (with cryptsetup/mkfs.btrfs)
  3. Mount everything at /mnt/archinstall
  4. Tell archinstall to use "config_type": "pre_mounted_config" — it installs packages into our pre-mounted tree and does zero disk operations
  5. 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 via arch-chroot "$INSTALL_ROOT" ...
  • Edit target files under $INSTALL_ROOT only — write mkinitcpio hook config, limine.conf, crypttab, and related files in $INSTALL_ROOT/etc and $INSTALL_ROOT/boot (never in the live environment root)
  • Settle kernel partition state — after creating the new partition, run partprobe "$DISK" and udevadm settle before cryptsetup to avoid device-node races
  • Deterministic fstab — always generate /etc/fstab for 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 encrypt for busybox initramfs or sd-encrypt for 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 lsblk PARTTYPE matching EFI GUID)
    • Free unallocated space (via sgdisk -F/-E or parted 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. Sets DUAL_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:

  1. Select the largest contiguous free region and run sgdisk to create new partition there (type 8309 = Linux LUKS)
  2. partprobe "$DISK" + udevadm settle so new partition node is present
  3. cryptsetup luksFormat + luksOpen with user's password
  4. mkfs.btrfs on /dev/mapper/omarchy_root
  5. Create Btrfs subvolumes: @, @home, @log, @pkg
  6. Mount subvolumes at /mnt/archinstall with compress=zstd
  7. Mount existing ESP (untouched) at /mnt/archinstall/boot
  8. Generate target /etc/fstab from mounted filesystems before post-install steps

Add configure_dual_boot_post_install() — runs after archinstall:

  1. Write /etc/crypttab entry using LUKS UUID/PARTUUID (stable identifier), e.g. omarchy_root UUID=<luks-uuid> none luks,discard
  2. Update mkinitcpio hooks before filesystems: encrypt (busybox) or sd-encrypt (systemd)
  3. Regenerate initramfs with arch-chroot "$INSTALL_ROOT" mkinitcpio -P
  4. 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 equivalent rd.luks.uuid= form)
  5. Run arch-chroot "$INSTALL_ROOT" limine-scan to detect other OS boot entries (Windows, etc.)
  6. Preserve current firmware default boot order unless user explicitly chooses to make Limine first (capture and restore BootOrder if efibootmgr side 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 <100MB free at install time: show a strong warning (do not block install)
  • If ESP has <64MB free: 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

  1. Regression: Full-disk install in VM with empty disk — must work identically to today
  2. Dual-boot with Windows: VM with Windows installed + 40GB free space — detect Windows, install alongside, both OSes boot
  3. Dual-boot with Linux: VM with another Linux + free space — similar test
  4. No free space: VM with fully partitioned disk — dual-boot option should not appear
  5. Encryption: After dual-boot install, verify LUKS unlock prompt at boot and encrypted root
  6. Initramfs mode: Validate both busybox and systemd hook paths boot correctly (encrypt vs sd-encrypt)
  7. Boot order: Verify existing default UEFI entry remains unchanged unless user opted to promote Limine