feat(installer): add multi-disk BTRFS support

- Allow selecting multiple drives in the TTY installer using gum choose --no-limit.
- Add installer/disko-btrfs-multi.nix template for BTRFS RAID/Single setups.
- Dynamically generate multi-disk disko configurations with LUKS-on-every-disk.
- Default to BTRFS 'single' data and 'raid1' metadata for maximum capacity across mismatched drives (e.g., 20GB + 120GB SSDs).
- Update roadmap and structure documentation to reflect the new capabilities.
This commit is contained in:
Bernardo Magri
2026-04-26 19:44:34 +01:00
parent 6de8ecd093
commit c66f0b19cd
4 changed files with 174 additions and 6 deletions

View File

@@ -78,7 +78,7 @@ Each PR description should reference the row(s) in `docs/SCRIPTS.md` it closes,
- Software-profile multi-select (Now). - Software-profile multi-select (Now).
- Richer disk metadata (Shipped). - Richer disk metadata (Shipped).
- Form-factor → laptop preset (Now, depends on Pillar 5). - Form-factor → laptop preset (Now, depends on Pillar 5).
- `disko-golden.nix` variants for software-RAID and BTRFS-pool-as-root. - `disko-golden.nix` variants for software-RAID and BTRFS-pool-as-root (Shipped).
- Pre-flight resume polish (Next). - Pre-flight resume polish (Next).
- "What's installed?" summary screen on boot of a freshly-installed system, sourced from `state.json` + `nomarchy-system-scripts` introspection. - "What's installed?" summary screen on boot of a freshly-installed system, sourced from `state.json` + `nomarchy-system-scripts` introspection.
- Optional non-LUKS branch in the installer for users who explicitly opt out of FDE. - Optional non-LUKS branch in the installer for users who explicitly opt out of FDE.
@@ -138,6 +138,7 @@ Nomarchy is moving away from being a "flavor" of Omarchy to its own distinct ide
(Move items here when they land — keep them brief, link the commit/PR.) (Move items here when they land — keep them brief, link the commit/PR.)
- _2026-04-26_ — Multi-disk BTRFS support in the installer. Added `installer/disko-btrfs-multi.nix` template and updated `installer/install.sh` to allow selecting multiple drives via `gum choose --no-limit`. Implements BTRFS "single" data + RAID1 metadata across multiple LUKS-encrypted drives.
- _2026-04-26_ — Distro Branding Phase 2. Updated bootloader entries to use "Nomarchy" as the label. Set ISO volume IDs to `NOMARCHY_INSTALLER` and `NOMARCHY_LIVE`. Fixed branding in Plymouth theme metadata and SDDM metadata. - _2026-04-26_ — Distro Branding Phase 2. Updated bootloader entries to use "Nomarchy" as the label. Set ISO volume IDs to `NOMARCHY_INSTALLER` and `NOMARCHY_LIVE`. Fixed branding in Plymouth theme metadata and SDDM metadata.
- _2026-04-26_ — Distro Branding Phase 1. Renamed `installerIso` to `nomarchy-installer` and `installerIsoGraphical` to `nomarchy-live`. Updated metadata and host configurations. Scrubbed "Omarchy" from Plymouth and installer messages. - _2026-04-26_ — Distro Branding Phase 1. Renamed `installerIso` to `nomarchy-installer` and `installerIsoGraphical` to `nomarchy-live`. Updated metadata and host configurations. Scrubbed "Omarchy" from Plymouth and installer messages.
- _2026-04-26_ — Fix `hardware-db.sh` missing in `nomarchy-installer.nix`. Resolved boot error where `install.sh` failed to source the hardware database on the TTY installer ISO. - _2026-04-26_ — Fix `hardware-db.sh` missing in `nomarchy-installer.nix`. Resolved boot error where `install.sh` failed to source the hardware database on the TTY installer ISO.

View File

@@ -126,6 +126,7 @@ The `lib/` directory provides centralized logic and data structures to maintain
### `installer/` (Bootstrap) ### `installer/` (Bootstrap)
- **`install.sh`**: The interactive TTY-based installer. It handles disk partitioning, NixOS installation, and generating a clean "Downstream" flake for the user. - **`install.sh`**: The interactive TTY-based installer. It handles disk partitioning, NixOS installation, and generating a clean "Downstream" flake for the user.
- **`disko-golden.nix`**: The standard partition layout (BTRFS on top of LUKS2). - **`disko-golden.nix`**: The standard partition layout (BTRFS on top of LUKS2).
- **`disko-btrfs-multi.nix`**: Multi-disk BTRFS RAID/Single layout template.
- **`disko-btrfs-luks.nix`**: A simpler reference layout for disk management. - **`disko-btrfs-luks.nix`**: A simpler reference layout for disk management.
### `hosts/` (Targets) ### `hosts/` (Targets)

View File

@@ -0,0 +1,76 @@
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "@MAIN_DRIVE@";
content = {
type = "gpt";
partitions = {
ESP = {
priority = 1;
name = "ESP";
start = "1M";
end = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted_main";
settings = {
allowDiscards = true;
passwordFile = "/dev/shm/nomarchy-luks.key";
};
content = {
type = "btrfs";
extraArgs = [ "-f" "-d single" "-m raid1" @BTRFS_DEVICES@ ];
subvolumes = {
"@" = {
mountpoint = "/";
mountOptions = [ "compress=zstd" "noatime" ];
};
"@persist" = {
mountpoint = "/persist";
mountOptions = [ "compress=zstd" "noatime" ];
};
"@home" = {
mountpoint = "/home";
mountOptions = [ "compress=zstd" "noatime" ];
};
"@nix" = {
mountpoint = "/nix";
mountOptions = [ "compress=zstd" "noatime" ];
};
"@log" = {
mountpoint = "/var/log";
mountOptions = [ "compress=zstd" "noatime" ];
};
"@snapshots" = {
mountpoint = "/.snapshots";
mountOptions = [ "compress=zstd" "noatime" ];
};
};
postCreateHook = ''
MNTPOINT=$(mktemp -d)
mount -t btrfs /dev/mapper/crypted_main $MNTPOINT
btrfs subvolume snapshot -r $MNTPOINT/@ $MNTPOINT/root-blank
umount $MNTPOINT
'';
};
};
};
};
};
};
@ADDITIONAL_DISKS@
};
};
}

View File

@@ -282,8 +282,8 @@ select_disk() {
local picker local picker
picker=$(printf '%s' "$rows" | column -t -s $'\t') picker=$(printf '%s' "$rows" | column -t -s $'\t')
local choice local choice
choice=$(printf '%s\n' "$picker" | gum choose --header "Select target drive") choice=$(printf '%s\n' "$picker" | gum choose --no-limit --header "Select target drive(s) - Use Space to select multiple for BTRFS RAID/Single")
TARGET_DRIVE=$(awk '{print $1}' <<<"$choice") TARGET_DRIVE=$(awk '{print $1}' <<<"$choice" | xargs)
if [[ -z "$TARGET_DRIVE" ]]; then if [[ -z "$TARGET_DRIVE" ]]; then
error "No drive selected" error "No drive selected"
@@ -806,6 +806,49 @@ edit_fields() {
# STEP 9: EXECUTION # STEP 9: EXECUTION
# ============================================================================ # ============================================================================
# Pre-wipe the target drive before invoking disko.
#
# disko (at our pinned revision) gates two destructive steps on blkid:
# - lib/types/gpt.nix runs `sgdisk --clear` only when blkid sees no PT
# - lib/types/filesystem.nix skips mkfs entirely when blkid reports the
# target FS type already exists on the partition device
#
# On a previously-installed disk those branches mis-fire: blkid sees the old
# GPT and the old vfat ESP, so disko overlays its new partition entries on
# the existing table without zapping and skips mkfs.vfat, leaving the kernel
# to read a stale FAT BPB on the new (slightly different) ESP extent. mount
# then errors with "wrong fs type, bad option, bad superblock".
prewipe_target_drive() {
local drive="$1"
info "Pre-wiping $drive (clearing stale signatures)..."
# Tear down anything a prior aborted run left active.
umount -R /mnt 2>/dev/null || true
cryptsetup close crypted 2>/dev/null || true
swapoff -a 2>/dev/null || true
local part
if compgen -G "${drive}*" >/dev/null; then
for part in "${drive}"?*; do
[[ -b "$part" ]] || continue
wipefs -af "$part" >/dev/null 2>&1 || true
done
fi
wipefs -af "$drive" >/dev/null 2>&1 || true
sgdisk --zap-all "$drive" >/dev/null 2>&1 || true
# 16 MiB covers LUKS2 binary headers (04 MiB) and the BTRFS first
# superblock (64 KiB) — wipefs alone misses damaged variants of these.
dd if=/dev/zero of="$drive" bs=1M count=16 conv=fsync status=none 2>/dev/null || true
partprobe "$drive" 2>/dev/null || true
udevadm settle
success "Pre-wipe complete"
}
execute_installation() { execute_installation() {
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
execute_dry_run execute_dry_run
@@ -815,12 +858,59 @@ execute_installation() {
section "Installing Nomarchy" section "Installing Nomarchy"
# 9.1 Partition with disko # 9.1 Partition with disko
info "Partitioning disk..." info "Partitioning disk(s)..."
for d in $TARGET_DRIVE; do
prewipe_target_drive "$d"
done
local disko_file tmp_disko local disko_file tmp_disko
disko_file="$NOMARCHY_REPO/installer/disko-golden.nix"
tmp_disko=$(mktemp --suffix=.nix) tmp_disko=$(mktemp --suffix=.nix)
sed "s|@TARGET_DRIVE@|${TARGET_DRIVE}|g" "$disko_file" > "$tmp_disko" local drives=($TARGET_DRIVE)
if [[ ${#drives[@]} -gt 1 ]]; then
disko_file="$NOMARCHY_REPO/installer/disko-btrfs-multi.nix"
local main_drive="${drives[0]}"
local btrfs_devs=""
local additional_disks=""
for (( i=1; i<${#drives[@]}; i++ )); do
local d="${drives[$i]}"
local name="extra_$i"
local luks_name="crypted_$name"
btrfs_devs+=", \"/dev/mapper/$luks_name\""
additional_disks+=" $name = {
type = \"disk\";
device = \"$d\";
content = {
type = \"gpt\";
partitions = {
luks = {
size = \"100%\";
content = {
type = \"luks\";
name = \"$luks_name\";
settings = {
allowDiscards = true;
passwordFile = \"/dev/shm/nomarchy-luks.key\";
};
content = {
type = \"btrfs\";
};
};
};
};
};
};
"
done
sed "s|@MAIN_DRIVE@|${main_drive}|g; s|@BTRFS_DEVICES@|${btrfs_devs}|g; s|@ADDITIONAL_DISKS@|${additional_disks}|g" "$disko_file" > "$tmp_disko"
else
disko_file="$NOMARCHY_REPO/installer/disko-golden.nix"
sed "s|@TARGET_DRIVE@|${TARGET_DRIVE}|g" "$disko_file" > "$tmp_disko"
fi
# Provide the LUKS passphrase via tmpfs so the secret never touches a # Provide the LUKS passphrase via tmpfs so the secret never touches a
# spinning disk. /dev/shm is tmpfs on the live ISO. We restrict perms # spinning disk. /dev/shm is tmpfs on the live ISO. We restrict perms