From 8601bdad7f5572906cf408dc1a913f48928508fe Mon Sep 17 00:00:00 2001 From: Raphael Numbus Date: Sun, 23 Nov 2025 19:36:37 +0100 Subject: [PATCH] Huge update to the disk selection method. Now support striped or mirrored boot disk(s), and up to 9 disks for data with automatic inclusion of up to 3 parity disks. --- .gitignore | 3 +- config-files/disks/boot-1-data-2.nix | 110 ------ config-files/disks/boot-1-data-3.nix | 134 -------- config-files/disks/boot-1-data-4.nix | 158 --------- .../disks/{boot-1-data-0.nix => boot-1.nix} | 46 ++- .../disks/{boot-1-data-1.nix => boot-2.nix} | 95 +++--- config-files/disks/data.nix | 22 ++ config-files/disks/parity.nix | 22 ++ deploy.sh | 317 ++++++++++++------ 9 files changed, 320 insertions(+), 587 deletions(-) delete mode 100644 config-files/disks/boot-1-data-2.nix delete mode 100644 config-files/disks/boot-1-data-3.nix delete mode 100644 config-files/disks/boot-1-data-4.nix rename config-files/disks/{boot-1-data-0.nix => boot-1.nix} (85%) rename config-files/disks/{boot-1-data-1.nix => boot-2.nix} (51%) create mode 100644 config-files/disks/data.nix create mode 100644 config-files/disks/parity.nix diff --git a/.gitignore b/.gitignore index d304be4..5cd9696 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ agents/ ai-production/ -extra-files/ \ No newline at end of file +extra-files/ +test.sh \ No newline at end of file diff --git a/config-files/disks/boot-1-data-2.nix b/config-files/disks/boot-1-data-2.nix deleted file mode 100644 index 766ee78..0000000 --- a/config-files/disks/boot-1-data-2.nix +++ /dev/null @@ -1,110 +0,0 @@ -{ lib, ... }: -{ - disko.devices = { - disk = { - # Boot disk - system = { - type = "disk"; - device = "${BOOT_DISK_1}"; - content = { - type = "gpt"; - partitions = { - ESP = { - size = "1G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; - }; - }; - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted"; - settings = { - allowDiscards = true; - }; - content = { - type = "lvm_pv"; - vg = "pool"; - }; - }; - }; - }; - }; - }; - - # First data disk - data1 = { - type = "disk"; - device = "${DATA_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-1"; - keyFile = "/run/secrets/disks/data-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-1"; - }; - }; - }; - }; - }; - }; - - # Parity disk - parity1 = { - type = "disk"; - device = "${PARITY_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-parity-1"; - keyFile = "/run/secrets/disks/parity-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/parity-1"; - }; - }; - }; - }; - }; - }; - }; - - # Boot disk LVM configuration - lvm_vg = { - pool = { - type = "lvm_vg"; - lvs = { - root = { - size = "100%FREE"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - }; - }; - swap = { - size = "8G"; - content.type = "swap"; - }; - }; - }; - }; - }; -} diff --git a/config-files/disks/boot-1-data-3.nix b/config-files/disks/boot-1-data-3.nix deleted file mode 100644 index d364027..0000000 --- a/config-files/disks/boot-1-data-3.nix +++ /dev/null @@ -1,134 +0,0 @@ -{ lib, ... }: -{ - disko.devices = { - disk = { - # Boot disk - system = { - type = "disk"; - device = "${BOOT_DISK_1}"; - content = { - type = "gpt"; - partitions = { - ESP = { - size = "1G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; - }; - }; - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted"; - settings = { - allowDiscards = true; - }; - content = { - type = "lvm_pv"; - vg = "pool"; - }; - }; - }; - }; - }; - }; - - # First data disk - data1 = { - type = "disk"; - device = "${DATA_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-1"; - keyFile = "/run/secrets/disks/data-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-1"; - }; - }; - }; - }; - }; - }; - - # Second data disk - data2 = { - type = "disk"; - device = "${DATA_DISK_2}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-2"; - keyFile = "/run/secrets/disks/data-disk-2"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-2"; - }; - }; - }; - }; - }; - }; - - # Parity disk - parity1 = { - type = "disk"; - device = "${PARITY_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-parity-1"; - keyFile = "/run/secrets/disks/parity-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/parity-1"; - }; - }; - }; - }; - }; - }; - }; - - # Boot disk LVM configuration - lvm_vg = { - pool = { - type = "lvm_vg"; - lvs = { - root = { - size = "100%FREE"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - }; - }; - swap = { - size = "8G"; - content.type = "swap"; - }; - }; - }; - }; - }; -} \ No newline at end of file diff --git a/config-files/disks/boot-1-data-4.nix b/config-files/disks/boot-1-data-4.nix deleted file mode 100644 index 1dcea29..0000000 --- a/config-files/disks/boot-1-data-4.nix +++ /dev/null @@ -1,158 +0,0 @@ -{ lib, ... }: -{ - disko.devices = { - disk = { - # Boot disk - system = { - type = "disk"; - device = "${BOOT_DISK_1}"; - content = { - type = "gpt"; - partitions = { - ESP = { - size = "1G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; - }; - }; - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted"; - settings = { - allowDiscards = true; - }; - content = { - type = "lvm_pv"; - vg = "pool"; - }; - }; - }; - }; - }; - }; - - # First data disk - data1 = { - type = "disk"; - device = "${DATA_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-1"; - keyFile = "/run/secrets/disks/data-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-1"; - }; - }; - }; - }; - }; - }; - - # Second data disk - data2 = { - type = "disk"; - device = "${DATA_DISK_2}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-2"; - keyFile = "/run/secrets/disks/data-disk-2"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-2"; - }; - }; - }; - }; - }; - }; - - # Third data disk - data3 = { - type = "disk"; - device = "${DATA_DISK_3}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-3"; - keyFile = "/run/secrets/disks/data-disk-3"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-3"; - }; - }; - }; - }; - }; - }; - - # Parity disk - parity1 = { - type = "disk"; - device = "${PARITY_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-parity-1"; - keyFile = "/run/secrets/disks/parity-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/parity-1"; - }; - }; - }; - }; - }; - }; - }; - - # Boot disk LVM configuration - lvm_vg = { - pool = { - type = "lvm_vg"; - lvs = { - root = { - size = "100%FREE"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - }; - }; - swap = { - size = "8G"; - content.type = "swap"; - }; - }; - }; - }; - }; -} \ No newline at end of file diff --git a/config-files/disks/boot-1-data-0.nix b/config-files/disks/boot-1.nix similarity index 85% rename from config-files/disks/boot-1-data-0.nix rename to config-files/disks/boot-1.nix index f1c44d0..b26ede5 100644 --- a/config-files/disks/boot-1-data-0.nix +++ b/config-files/disks/boot-1.nix @@ -1,9 +1,27 @@ { lib, ... }: { disko.devices = { + # Boot disk LVM configuration + lvm_vg = { + pool = { + type = "lvm_vg"; + lvs = { + root = { + size = "100%FREE"; + content.type = "filesystem"; + content.format = "btrfs"; + content.mountpoint = "/"; + }; + swap = { + size = "16G"; + content.type = "swap"; + }; + }; + }; + }; disk = { # Boot disk - system = { + system-1 = { type = "disk"; device = "${BOOT_DISK_1}"; content = { @@ -35,28 +53,4 @@ }; }; }; - }; - }; - - # Boot disk LVM configuration - lvm_vg = { - pool = { - type = "lvm_vg"; - lvs = { - root = { - size = "100%FREE"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - }; - }; - swap = { - size = "8G"; - content.type = "swap"; - }; - }; - }; - }; - }; -} \ No newline at end of file + }; \ No newline at end of file diff --git a/config-files/disks/boot-1-data-1.nix b/config-files/disks/boot-2.nix similarity index 51% rename from config-files/disks/boot-1-data-1.nix rename to config-files/disks/boot-2.nix index d58ef13..9d5a4a0 100644 --- a/config-files/disks/boot-1-data-1.nix +++ b/config-files/disks/boot-2.nix @@ -2,8 +2,7 @@ { disko.devices = { disk = { - # Boot disk - system = { + system-1 = { type = "disk"; device = "${BOOT_DISK_1}"; content = { @@ -19,68 +18,56 @@ mountOptions = [ "umask=0077" ]; }; }; - luks = { + crypt_p1 = { size = "100%"; content = { type = "luks"; - name = "crypted"; + name = "nixos-p1"; + settings = { + allowDiscards = true; + }; + }; + }; + }; + }; + }; + system-2 = { + type = "disk"; + device = "${BOOT_DISK_2}"; + content = { + type = "gpt"; + partitions = { + crypt_p2 = { + size = "100%"; + content = { + type = "luks"; + name = "nixos-p2"; settings = { allowDiscards = true; }; content = { - type = "lvm_pv"; - vg = "pool"; + type = "btrfs"; + extraArgs = [ + "-d raid1" + "/dev/mapper/nixos-p1" + ]; + subvolumes = { + "/root" = { + mountpoint = "/"; + mountOptions = [ + "rw" + "relatime" + "ssd" + ]; + }; + "/swap" = { + mountpoint = "none"; + swap.size = "16G"; + }; + }; }; }; }; }; }; }; - - # First data disk - data1 = { - type = "disk"; - device = "${DATA_DISK_1}"; - content = { - type = "gpt"; - partitions = { - luks = { - size = "100%"; - content = { - type = "luks"; - name = "crypted-data-1"; - keyFile = "/run/secrets/disks/data-disk-1"; - content = { - type = "filesystem"; - format = "xfs"; - mountpoint = "/mnt/data-1"; - }; - }; - }; - }; - }; - }; - }; - - # Boot disk LVM configuration - lvm_vg = { - pool = { - type = "lvm_vg"; - lvs = { - root = { - size = "100%FREE"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; - }; - }; - swap = { - size = "8G"; - content.type = "swap"; - }; - }; - }; - }; - }; -} diff --git a/config-files/disks/data.nix b/config-files/disks/data.nix new file mode 100644 index 0000000..678cef1 --- /dev/null +++ b/config-files/disks/data.nix @@ -0,0 +1,22 @@ + data-${DISK_NUMBER} = { + type = "disk"; + device = "${DISK_PATH}"; + content = { + type = "gpt"; + partitions = { + luks = { + size = "100%"; + content = { + type = "luks"; + name = "crypted-data-${DISK_NUMBER}"; + keyFile = "/run/secrets/disks/data-disk-${DISK_NUMBER}"; + content = { + type = "filesystem"; + format = "xfs"; + mountpoint = "/mnt/data-${DISK_NUMBER}"; + }; + }; + }; + }; + }; + }; diff --git a/config-files/disks/parity.nix b/config-files/disks/parity.nix new file mode 100644 index 0000000..1588078 --- /dev/null +++ b/config-files/disks/parity.nix @@ -0,0 +1,22 @@ + parity-${DISK_NUMBER} = { + type = "disk"; + device = "${DISK_PATH}"; + content = { + type = "gpt"; + partitions = { + luks = { + size = "100%"; + content = { + type = "luks"; + name = "crypted-parity-${DISK_NUMBER}"; + keyFile = "/run/secrets/disks/parity-disk-${DISK_NUMBER}"; + content = { + type = "filesystem"; + format = "xfs"; + mountpoint = "/mnt/parity-${DISK_NUMBER}"; + }; + }; + }; + }; + }; + }; diff --git a/deploy.sh b/deploy.sh index 9902618..8072b9a 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,11 +1,13 @@ -#!/bin/bash +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p gum openssl sops ssh-to-age age sshpass envsubst + prerun_action() { echo -e "$1" - SETUP_ANSWER="$(gum input --placeholder "Type 'done' when you have finished.")" + SETUP_ANSWER="$(gum input --placeholder 'Type "done" when you have finished.')" if [[ "$SETUP_ANSWER" == "done" ]]; then : else - echo " Aborting - you did not type 'done'." + echo ' ❌ Aborting - you did not type "done".' exit 1 fi } @@ -65,14 +67,27 @@ necessary_credentials_with_config() { fi } +setup_ssh() { + REMOTE_PASS=$(gum input --password --placeholder "Enter password for 'nixos' on '$TARGET_HOST'") + if [ -z "$REMOTE_PASS" ]; then + echo " ❌ Password is required to proceed. Aborting." + exit 1 + fi + echo -e "\n\n ➡️ Copying SSH key to target host 'nixos@$TARGET_HOST'..." + if sshpass -p "$REMOTE_PASS" ssh-copy-id -o StrictHostKeyChecking=no -i "extra-files/home/numbus-admin/.ssh/id_ed25519.pub" "nixos@$TARGET_HOST"; then + echo " ✅ SSH key copied successfully." + else + echo " ❌ Failed to copy SSH key. Please check the host IP and password." + exit 1 + fi + export REMOTE_PASS +} + +ssh_to_host() { + ssh -i "extra-files/home/numbus-admin/.ssh/id_ed25519.pub" "nixos@$TARGET_HOST" "$1" +} + hardware_detection() { - echo -e "\n\n ➡️ Please provide the password of the target host :" - ssh-copy-id -i extra-files/home/numbus-admin/.ssh/id_ed25519.pub nixos@$TARGET_HOST - - ssh_to_host() { - ssh -i extra-files/home/numbus-admin/.ssh/id_ed25519 nixos@$TARGET_HOST "$1" - } - echo -e "\n\n 🔎 Detecting graphics card on target host..." VGA_INFO=$(ssh_to_host "lspci -nn | grep -i 'vga'") if echo "$VGA_INFO" | grep -iq "intel"; then @@ -127,11 +142,11 @@ files_generation() { mkdir -p extra-files/mnt/data-storage/immich echo -e "\n\n ✅ Generating new SSH for numbus-admin..." - ssh-keygen -t ed25519 -C numbus-admin@numbus-server -f extra-files/home/numbus-admin/.ssh/id_ed25519 -N "" -q + ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "extra-files/home/numbus-admin/.ssh/id_ed25519" -N "" -q echo -e "\n\n ✅ Generating sops-nix keys..." - nix run nixpkgs#ssh-to-age -- -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt - SOPS_PUBLIC_KEY=$(nix shell nixpkgs#age -c age-keygen -y extra-files/var/lib/sops-nix/key.txt) + ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt + SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt) echo -e "\n\n ✅ Generating sops-nix configuration files..." envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml @@ -230,103 +245,205 @@ files_generation() { } disk_config_generation() { - ssh_to_host() { - ssh -i extra-files/home/numbus-admin/.ssh/id_ed25519 nixos@$TARGET_HOST "$1" - } + gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 " + ⚠️ $(gum style --foreground 212 'WARNING:') You will choose the disks to install NixOS on. + !! PLEASE MAKE SURE YOU BACKED UP ANY IMPORTANT DATA !! + !! ALL DATA WILL BE WIPED ON THE DISKS YOU CHOOSE !! + Please press CTRL+C to abort. + " + gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting."; exit 1; } - echo -e "\n\n ⚠️ WARNING: you will choose the disks you want to install NixOS on." - echo -e " !! PLEASE MAKE SURE YOU BACKED UP ANY IMPORTANT DATA !!" - echo -e " !! ALL DATA WILL BE WIPED ON THE DISKS YOU CHOOSE !!" - echo -e " Press CTRL+C to abort." + echo -e "\n\n 🔎 Fetching and analyzing disks from target host... (This may take a moment)" - SETUP_ANSWER="$(gum input --placeholder "Type 'understood' once you have read the warning.")" - if [[ "$SETUP_ANSWER" == "understood" ]]; then - : - else - echo " Aborting - you did not type 'understood'." + declare -A DISK_INFO_MAP + declare -A DISK_SIZE_MAP + declare -A DISK_BY_ID_MAP + declare -A DISK_LABEL_MAP + DISK_OPTIONS=() + + DISK_NAMES=$(ssh_to_host "lsblk -d -n -o NAME,TYPE | awk '\$2==\"disk\" {print \$1}'") + + for name in $DISK_NAMES; do + details=$(echo "$REMOTE_PASS" | ssh_to_host " + set -e + devpath=/dev/$name + rota=1 # Default to rotational (HDD) + [ -f /sys/block/$name/queue/rotational ] && rota=\$(cat /sys/block/$name/queue/rotational) + tran=\$(lsblk -d -n -o TRAN \"\$devpath\" || echo 'unknown') + health=\$(sudo -S smartctl -H \"\$devpath\" 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') + by_id=\$(ls -l /dev/disk/by-id | grep -m 1 \"../../$name\$\" | awk '{print \"/dev/disk/by-id/\"\$9}') + + # Determine type + if [[ \"\$name\" == nvme* ]]; then type=\"NVMe\"; # Check for NVMe first + elif [[ \"\$rota\" == \"0\" ]]; then type=\"SSD\"; + elif [[ \"\$tran\" == \"usb\" ]]; then type=\"USB\"; + else type=\"HDD\"; fi + + # Fallback for health and by-id + [[ -z \"\$health\" || \"\$health\" == \"\" ]] && health=\"N/A\" + [ -z \"\$by_id\" ] && by_id=\"\$devpath\" + + # Get size last, after other commands that might fail + size=\$(lsblk -b -d -n -o SIZE \"\$devpath\") + echo \"\$size:::\$type:::\$health:::\$by_id\" + ") + + mapfile -t parts < <(echo "$details" | tr ':' '\n') + size="${parts[0]}" + disk_type="${parts[3]}" + health="${parts[6]}" + by_id="${parts[9]}" + + human_size=$(numfmt --to=iec-i --suffix=B "$size") + label=$(printf "%-12s %-12s %-12s %-12s %s" "$name" "$disk_type" "$human_size" "$health" "$by_id") + + DISK_OPTIONS+=("$label") + DISK_INFO_MAP["$label"]="$name" + DISK_SIZE_MAP["$name"]="$size" + DISK_BY_ID_MAP["$name"]="$by_id" + DISK_LABEL_MAP["$name"]="$label" + done + + if [ ${#DISK_OPTIONS[@]} -eq 0 ]; then + echo " ❌ No disks found on the target host. Aborting." exit 1 fi - echo -e "\n\n 🔎 Fetching disks from target host..." + HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "ID") + gum style --foreground 212 " ➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation:" + echo -e "" + mapfile -t SELECTED_BOOT_LABELS < <(gum choose --limit 2 --header "$HEADER" "${DISK_OPTIONS[@]}") - DISK_JSON=$(ssh_to_host "lsblk -d --json -o NAME,ROTA,SIZE,PATH") - BY_ID_RAW=$(ssh_to_host "ls -l /dev/disk/by-id/") - if [ -z "$DISK_JSON" ]; then - echo " ❌ Could not find any disks on the target host. Aborting." - exit 1 - fi + if [ ${#SELECTED_BOOT_LABELS[@]} -eq 0 ]; then echo " ❌ No boot disk selected. Aborting."; exit 1; fi - declare -A BY_ID_MAP - - while read -r line; do - if [[ "$line" =~ "-> ../../"(.*)$ ]]; then - dev_name="${BASH_REMATCH[1]}" - by_id_path="/dev/disk/by-id/$(echo "$line" | awk '{print $9}')" - if [[ ! -v "BY_ID_MAP[$dev_name]" && ! "$by_id_path" =~ -part ]]; then - BY_ID_MAP["$dev_name"]="$by_id_path" - fi - fi - done <<< "$BY_ID_RAW" - - declare -A DISK_MAP - declare -a DISK_OPTIONS - - while read -r name type size; do - by_id=${BY_ID_MAP[$name]} - if [ -z "$by_id" ]; then continue; fi - option=$(printf "%-8s %-5s %-8s (%s)" "$name" "$type" "$size" "$by_id") - DISK_OPTIONS+=("$option") - DISK_MAP["$option"]="$by_id" - done < <(echo "$DISK_JSON" | jq -r '.blockdevices[] | "\(.name) \(if .name | test("^nvme") then "NVMe" else (if .rota == "0" then "SSD" else "HDD" end) end) \(.size)"') - echo -e "\n\n ➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation:" - mapfile -t SELECTED_BOOT_OPTIONS < <(gum choose --limit 2 "${DISK_OPTIONS[@]}") - if [ ${#SELECTED_BOOT_OPTIONS[@]} -eq 0 ]; then - echo " ❌ No boot disk selected. Aborting." - exit 1 - fi - - NUMBER_OF_BOOT_DISKS=${#SELECTED_BOOT_OPTIONS[@]} - BOOT_DISK_1=${DISK_MAP["${SELECTED_BOOT_OPTIONS[0]}"]} - - if [ "$NUMBER_OF_BOOT_DISKS" -eq 2 ]; then - BOOT_DISK_2=${DISK_MAP["${SELECTED_BOOT_OPTIONS[1]}"]} + BOOT_DISK_1_NAME=${DISK_INFO_MAP["${SELECTED_BOOT_LABELS[0]}"]} + BOOT_DISK_1=${DISK_BY_ID_MAP[$BOOT_DISK_1_NAME]} + BOOT_DISK_2="" + if [ ${#SELECTED_BOOT_LABELS[@]} -eq 2 ]; then + BOOT_DISK_2_NAME=${DISK_INFO_MAP["${SELECTED_BOOT_LABELS[1]}"]} + BOOT_DISK_2=${DISK_BY_ID_MAP[$BOOT_DISK_2_NAME]} fi REMAINING_DISKS=() - for option in "${DISK_OPTIONS[@]}"; do + for label in "${DISK_OPTIONS[@]}"; do is_boot_disk=false - for selected in "${SELECTED_BOOT_OPTIONS[@]}"; do - if [[ "$option" == "$selected" ]]; then - is_boot_disk=true - break - fi + for selected in "${SELECTED_BOOT_LABELS[@]}"; do + if [[ "$label" == "$selected" ]]; then is_boot_disk=true; break; fi done - if ! $is_boot_disk; then - REMAINING_DISKS+=("$option") - fi + if ! $is_boot_disk; then REMAINING_DISKS+=("$label"); fi done if [ ${#REMAINING_DISKS[@]} -gt 0 ]; then - echo -e "\n\n ➡️ Please choose your data disks (up to 4):" - mapfile -t SELECTED_DATA_OPTIONS < <(gum choose --limit 4 "${REMAINING_DISKS[@]}") - NUMBER_OF_DATA_DISKS=${#SELECTED_DATA_OPTIONS[@]} - for i in $(seq 0 $(($NUMBER_OF_DATA_DISKS - 1))); do - declare "DATA_DISK_$(($i + 1))"="${DISK_MAP["${SELECTED_DATA_OPTIONS[$i]}"]}" - done + echo -e "" + gum style --foreground 212 " ➡️ Please choose your data and parity disks (up to 9 total)." + mapfile -t SELECTED_DATA_LABELS < <(gum choose --limit 9 --header "$HEADER" "${REMAINING_DISKS[@]}") + + if [ ${#SELECTED_DATA_LABELS[@]} -gt 0 ]; then + selected_data_names=() + for label in "${SELECTED_DATA_LABELS[@]}"; do + selected_data_names+=("${DISK_INFO_MAP[$label]}") + done + + num_selected=${#selected_data_names[@]} + num_parity=0 + if (( num_selected > 0 )); then + num_parity=$(( (num_selected - 1) / 3 + 1 )) + fi + + # Sort selected disks by size (largest first) + sorted_disks=($( + for name in "${selected_data_names[@]}"; do + echo "${DISK_SIZE_MAP[$name]} $name" + done | sort -rn | awk '{print $2}' + )) + + # Assign parity disks (the largest ones) + parity_disks_final=() + for i in $(seq 0 $((num_parity - 1))); do + [[ -n "${sorted_disks[$i]}" ]] && parity_disks_final+=("${DISK_BY_ID_MAP[${sorted_disks[$i]}]}") + done + + # Assign data disks (the remaining ones) + data_disks_final=() + for i in $(seq $num_parity $((num_selected - 1))); do + [[ -n "${sorted_disks[$i]}" ]] && data_disks_final+=("${DISK_BY_ID_MAP[${sorted_disks[$i]}]}") + done + + # Set exported variables (up to 9 data disks and 2 parity disks) + for i in {0..8}; do export "DATA_DISK_$((i+1))"="${data_disks_final[$i]:-}"; done + for i in {0..2}; do export "PARITY_DISK_$((i+1))"="${parity_disks_final[$i]:-}"; done + fi else - echo -e "\n\n ℹ️ No remaining disks available for data storage." - NUMBER_OF_DATA_DISKS=0 + echo -e "\nℹ️ No remaining disks to select for data." fi - DISK_CONFIG_TEMPLATE="config-files/disks/boot-${NUMBER_OF_BOOT_DISKS}-data-${NUMBER_OF_DATA_DISKS}.nix" + # --- Final Recap --- + NUMBER_OF_BOOT_DISKS=0 + [[ -n "$BOOT_DISK_1" ]] && NUMBER_OF_BOOT_DISKS=$((NUMBER_OF_BOOT_DISKS + 1)) + [[ -n "$BOOT_DISK_2" ]] && NUMBER_OF_BOOT_DISKS=$((NUMBER_OF_BOOT_DISKS + 1)) - if [[ -f "$DISK_CONFIG_TEMPLATE" ]]; then - echo -e "\n\n ✅ Generating disk configuration from template: $DISK_CONFIG_TEMPLATE" - envsubst < "$DISK_CONFIG_TEMPLATE" > disk-config.nix - else - echo -e "\n\n ❌ Error: No disk configuration template found for $NUMBER_OF_BOOT_DISKS boot disk(s) and $NUMBER_OF_DATA_DISKS data disk(s)." - echo " Looked for: $DISK_CONFIG_TEMPLATE" - exit 1 + NUMBER_OF_DATA_DISKS=0 + for i in {1..9}; do + disk_var="DATA_DISK_$i" + [[ -n "${!disk_var}" ]] && NUMBER_OF_DATA_DISKS=$((NUMBER_OF_DATA_DISKS + 1)) + done + + NUMBER_OF_PARITY_DISKS=0 + for i in {1..3}; do + disk_var="PARITY_DISK_$i" + [[ -n "${!disk_var}" ]] && NUMBER_OF_PARITY_DISKS=$((NUMBER_OF_PARITY_DISKS + 1)) + done + + RECAP_CONTENT=$(cat < disk-config.nix + echo " ✅ Generated boot disk configuration." + + for i in $(seq 1 $NUMBER_OF_DATA_DISKS); do + disk_var="DATA_DISK_$i" + export DISK_NUMBER=$i + export DISK_PATH=${!disk_var} + (envsubst < "config-files/disks/data.nix") >> disk-config.nix + done + [[ "$NUMBER_OF_DATA_DISKS" -gt 0 ]] && echo " ✅ Generated $NUMBER_OF_DATA_DISKS data disk configuration(s)." + + for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do + disk_var="PARITY_DISK_$i" + export DISK_NUMBER=$i + export DISK_PATH=${!disk_var} + (envsubst < "config-files/disks/parity.nix") >> disk-config.nix + done + [[ "$NUMBER_OF_PARITY_DISKS" -gt 0 ]] && echo " ✅ Generated $NUMBER_OF_PARITY_DISKS parity disk configuration(s)." + + # Close the imports block + cat <<'EOF' >> disk-config.nix + }; + }; +} +EOF + echo -e " ✅ Final disko configuration created at 'disk-config.nix'." } deploy() { @@ -338,7 +455,7 @@ deploy() { --chown "/home/numbus-admin/" 1000:1000 \ --target-host nixos@$TARGET_HOST - echo -e "\n\n ✅ Installation successfull !!" + echo -e "\n\n ✅ Installation successfull !" sleep 1 } @@ -375,18 +492,6 @@ EOF sleep 1 -# Pre-run checks -if ! command -v gum &> /dev/null; then - echo " ❌ 'gum' is not installed. Please install it to use the interactive TUI." - echo " Add 'gum' to your configuration.nix in the system packages section." - exit 1 -fi -if ! command -v openssl &> /dev/null; then - echo " ❌ 'openssl' is not installed." - echo " Add 'openssl' to your configuration.nix in the system packages section." - exit 1 -fi - # Choose the action ACTION_ANSWER=$(gum choose "[1] 🌐 Deploy NixOS on a remote machine" "[2] 💽 Deploy NixOS on a remote machine with a file configuration" "[3] 🛠️ Update a NixOS remote machine") echo $ACTION_ANSWER @@ -395,16 +500,20 @@ if [[ "$ACTION_ANSWER" == "[1] 🌐 Deploy NixOS on a remote machine" ]]; then echo -e "\n ➡️ Proceeding with deployment…" prerun_action "\n\n ➡️ On the target host : start the computer and boot into the NixOS iso.\n Launch a console and set up a new user password." necessary_credentials + setup_ssh hardware_detection files_generation disk_config_generation + generate_disko_config elif [[ "$ACTION_ANSWER" == "[2] 💽 Deploy NixOS on a remote machine with a file configuration" ]]; then echo -e "\n ➡️ Proceeding with deployment using a config file…" prerun_action "\n\n ➡️ On the target host : start the computer and boot into the NixOS iso.\n Launch a console and set up a new user password." necessary_credentials_with_config + setup_ssh hardware_detection files_generation disk_config_generation + generate_disko_config elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then echo -e "\n ➡️ Proceeding with update…" prerun_action "\n\n ➡️ On the target host : make sure the NixOS installation you want to update is up-and-running, accessible with SSH."