diff options
| -rw-r--r-- | installer/lib/config.sh | 17 | ||||
| -rw-r--r-- | installer/lib/disk.sh | 18 | ||||
| -rw-r--r-- | tests/unit/test_config.bats | 43 | ||||
| -rw-r--r-- | tests/unit/test_disk.bats | 46 |
4 files changed, 123 insertions, 1 deletions
diff --git a/installer/lib/config.sh b/installer/lib/config.sh index ed54e36..dea65a0 100644 --- a/installer/lib/config.sh +++ b/installer/lib/config.sh @@ -17,6 +17,10 @@ LOCALE="en_US.UTF-8" KEYMAP="us" ENABLE_SSH="yes" # SSH with root login (default yes for headless) NO_ENCRYPT="no" # Skip filesystem encryption (testing only) +SWAP_SIZE="" # Optional swap partition (e.g. "100G") — carved + # between EFI and root so hibernate has a resume + # target outside the pool. Single-disk only; empty + # keeps the original two-partition layout. # Required fields — installer errors out if any are still empty at install time. HOSTNAME="" @@ -142,6 +146,19 @@ validate_config() { ((++errors)) fi + # SWAP_SIZE: sgdisk size syntax (digits + K/M/G/T), single disk only — + # a per-disk swap on a RAID layout has no defined resume device. + if [[ -n "$SWAP_SIZE" ]]; then + if [[ ! "$SWAP_SIZE" =~ ^[0-9]+[KMGT]$ ]]; then + warn "Invalid SWAP_SIZE '$SWAP_SIZE' (expected e.g. 100G)" + ((++errors)) + fi + if [[ ${#SELECTED_DISKS[@]} -gt 1 ]]; then + warn "SWAP_SIZE is only supported on single-disk installs" + ((++errors)) + fi + fi + if [[ $errors -gt 0 ]]; then error "Config validation failed with $errors error(s)" fi diff --git a/installer/lib/disk.sh b/installer/lib/disk.sh index ae7801b..ba2d3a2 100644 --- a/installer/lib/disk.sh +++ b/installer/lib/disk.sh @@ -25,12 +25,18 @@ partition_disk() { wipefs -af "$disk" || error "Failed to wipe signatures on $disk" sgdisk --zap-all "$disk" || error "Failed to zap GPT on $disk" sgdisk -n 1:0:+${efi_size} -t 1:EF00 -c 1:"EFI" "$disk" || error "Failed to create EFI partition on $disk" + # Optional swap partition for hibernate: physically between EFI and + # root (carved first so root's 0:0 takes the remainder), numbered 3 so + # the EFI=1/ROOT=2 contract every downstream caller relies on holds. + if [[ -n "${SWAP_SIZE:-}" ]]; then + sgdisk -n 3:0:+${SWAP_SIZE} -t 3:8200 -c 3:"SWAP" "$disk" || error "Failed to create swap partition on $disk" + fi sgdisk -n 2:0:0 -t 2:$root_type -c 2:"ROOT" "$disk" || error "Failed to create root partition on $disk" partprobe "$disk" 2>/dev/null || true sleep 1 - info "Partitioned $disk: EFI=${efi_size}, ROOT=remainder" + info "Partitioned $disk: EFI=${efi_size}${SWAP_SIZE:+, SWAP=${SWAP_SIZE}}, ROOT=remainder" } # Partition every disk in SELECTED_DISKS, format each EFI partition, @@ -92,6 +98,16 @@ get_root_partition() { fi } +# Partition 3 exists only when SWAP_SIZE was set at partition time. +get_swap_partition() { + local disk="$1" + if [[ "$disk" =~ nvme ]]; then + echo "${disk}p3" + else + echo "${disk}3" + fi +} + ############################# # Disk Selection (Interactive) ############################# diff --git a/tests/unit/test_config.bats b/tests/unit/test_config.bats index 4169c5e..554c0c7 100644 --- a/tests/unit/test_config.bats +++ b/tests/unit/test_config.bats @@ -302,3 +302,46 @@ EOF [[ "$output" == *"LUKS_PASSPHRASE"* ]] ! [[ "$output" == *"ZFS_PASSPHRASE"* ]] } + +############################# +# SWAP_SIZE validation +############################# + +@test "validate_config accepts empty SWAP_SIZE (default layout)" { + HOSTNAME=h; TIMEZONE=UTC; ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda); RAID_LEVEL=""; SWAP_SIZE="" + run validate_config + [[ "$output" != *"SWAP_SIZE"* ]] +} + +@test "validate_config accepts a well-formed SWAP_SIZE on a single disk" { + HOSTNAME=h; TIMEZONE=UTC; ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda); RAID_LEVEL=""; SWAP_SIZE=100G + run validate_config + [[ "$output" != *"Invalid SWAP_SIZE"* ]] + [[ "$output" != *"single-disk"* ]] +} + +@test "validate_config rejects SWAP_SIZE without a unit suffix" { + HOSTNAME=h; TIMEZONE=UTC; ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda); RAID_LEVEL=""; SWAP_SIZE=100 + run validate_config + [ "$status" -eq 1 ] + [[ "$output" == *"Invalid SWAP_SIZE"* ]] +} + +@test "validate_config rejects SWAP_SIZE with a bogus unit" { + HOSTNAME=h; TIMEZONE=UTC; ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda); RAID_LEVEL=""; SWAP_SIZE=100Q + run validate_config + [ "$status" -eq 1 ] + [[ "$output" == *"Invalid SWAP_SIZE"* ]] +} + +@test "validate_config rejects SWAP_SIZE on a multi-disk layout" { + HOSTNAME=h; TIMEZONE=UTC; ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda /dev/sdb); RAID_LEVEL=mirror; SWAP_SIZE=100G + run validate_config + [ "$status" -eq 1 ] + [[ "$output" == *"single-disk"* ]] +} diff --git a/tests/unit/test_disk.bats b/tests/unit/test_disk.bats index b962445..462f5d7 100644 --- a/tests/unit/test_disk.bats +++ b/tests/unit/test_disk.bats @@ -320,3 +320,49 @@ partition_disks_setup() { run disk_in_use /dev/zzz999 [ "$status" -eq 1 ] } + +############################# +# get_swap_partition +############################# + +@test "get_swap_partition: SATA disk gets numeric suffix 3" { + run get_swap_partition /dev/sda + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda3" ] +} + +@test "get_swap_partition: NVMe disk gets p-prefixed suffix 3" { + run get_swap_partition /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme0n1p3" ] +} + +############################# +# partition_disk SWAP_SIZE layout +############################# + +@test "partition_disk without SWAP_SIZE keeps the two-partition layout" { + partition_disks_setup + FILESYSTEM=zfs + SWAP_SIZE="" + partition_disk /dev/sda + for c in "${CALLS[@]}"; do + [[ "$c" != *"8200"* ]] + done +} + +@test "partition_disk with SWAP_SIZE carves swap before root so root takes remainder" { + partition_disks_setup + FILESYSTEM=zfs + SWAP_SIZE=100G + partition_disk /dev/nvme0n1 + local swap_idx=-1 root_idx=-1 i=0 + for c in "${CALLS[@]}"; do + [[ "$c" == *"-n 3:0:+100G -t 3:8200"* ]] && swap_idx=$i + [[ "$c" == *"-n 2:0:0"* ]] && root_idx=$i + i=$((i+1)) + done + [ "$swap_idx" -ge 0 ] + [ "$root_idx" -ge 0 ] + [ "$swap_idx" -lt "$root_idx" ] +} |
