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.

This commit is contained in:
Raphael Numbus
2025-11-23 19:36:37 +01:00
parent 6827785db7
commit 8601bdad7f
9 changed files with 320 additions and 587 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
agents/ agents/
ai-production/ ai-production/
extra-files/ extra-files/
test.sh
-110
View File
@@ -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";
};
};
};
};
};
}
-134
View File
@@ -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";
};
};
};
};
};
}
-158
View File
@@ -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";
};
};
};
};
};
}
@@ -1,9 +1,27 @@
{ lib, ... }: { lib, ... }:
{ {
disko.devices = { 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 = { disk = {
# Boot disk # Boot disk
system = { system-1 = {
type = "disk"; type = "disk";
device = "${BOOT_DISK_1}"; device = "${BOOT_DISK_1}";
content = { 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";
};
};
};
};
};
}
@@ -2,8 +2,7 @@
{ {
disko.devices = { disko.devices = {
disk = { disk = {
# Boot disk system-1 = {
system = {
type = "disk"; type = "disk";
device = "${BOOT_DISK_1}"; device = "${BOOT_DISK_1}";
content = { content = {
@@ -19,68 +18,56 @@
mountOptions = [ "umask=0077" ]; mountOptions = [ "umask=0077" ];
}; };
}; };
luks = { crypt_p1 = {
size = "100%"; size = "100%";
content = { content = {
type = "luks"; 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 = { settings = {
allowDiscards = true; allowDiscards = true;
}; };
content = { content = {
type = "lvm_pv"; type = "btrfs";
vg = "pool"; 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";
};
};
};
};
};
}
+22
View File
@@ -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}";
};
};
};
};
};
};
+22
View File
@@ -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}";
};
};
};
};
};
};
+213 -104
View File
@@ -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() { prerun_action() {
echo -e "$1" 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 if [[ "$SETUP_ANSWER" == "done" ]]; then
: :
else else
echo " Aborting - you did not type 'done'." echo ' ❌ Aborting - you did not type "done".'
exit 1 exit 1
fi fi
} }
@@ -65,14 +67,27 @@ necessary_credentials_with_config() {
fi 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() { 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..." echo -e "\n\n 🔎 Detecting graphics card on target host..."
VGA_INFO=$(ssh_to_host "lspci -nn | grep -i 'vga'") VGA_INFO=$(ssh_to_host "lspci -nn | grep -i 'vga'")
if echo "$VGA_INFO" | grep -iq "intel"; then if echo "$VGA_INFO" | grep -iq "intel"; then
@@ -127,11 +142,11 @@ files_generation() {
mkdir -p extra-files/mnt/data-storage/immich mkdir -p extra-files/mnt/data-storage/immich
echo -e "\n\n ✅ Generating new SSH for numbus-admin..." 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..." 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 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) SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt)
echo -e "\n\n ✅ Generating sops-nix configuration files..." echo -e "\n\n ✅ Generating sops-nix configuration files..."
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml
@@ -230,103 +245,205 @@ files_generation() {
} }
disk_config_generation() { disk_config_generation() {
ssh_to_host() { gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
ssh -i extra-files/home/numbus-admin/.ssh/id_ed25519 nixos@$TARGET_HOST "$1" ⚠️ $(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 "\n\n 🔎 Fetching and analyzing disks from target host... (This may take a moment)"
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."
SETUP_ANSWER="$(gum input --placeholder "Type 'understood' once you have read the warning.")" declare -A DISK_INFO_MAP
if [[ "$SETUP_ANSWER" == "understood" ]]; then declare -A DISK_SIZE_MAP
: declare -A DISK_BY_ID_MAP
else declare -A DISK_LABEL_MAP
echo " Aborting - you did not type 'understood'." 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 exit 1
fi 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") if [ ${#SELECTED_BOOT_LABELS[@]} -eq 0 ]; then echo " ❌ No boot disk selected. Aborting."; exit 1; fi
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
declare -A BY_ID_MAP BOOT_DISK_1_NAME=${DISK_INFO_MAP["${SELECTED_BOOT_LABELS[0]}"]}
BOOT_DISK_1=${DISK_BY_ID_MAP[$BOOT_DISK_1_NAME]}
while read -r line; do BOOT_DISK_2=""
if [[ "$line" =~ "-> ../../"(.*)$ ]]; then if [ ${#SELECTED_BOOT_LABELS[@]} -eq 2 ]; then
dev_name="${BASH_REMATCH[1]}" BOOT_DISK_2_NAME=${DISK_INFO_MAP["${SELECTED_BOOT_LABELS[1]}"]}
by_id_path="/dev/disk/by-id/$(echo "$line" | awk '{print $9}')" BOOT_DISK_2=${DISK_BY_ID_MAP[$BOOT_DISK_2_NAME]}
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]}"]}
fi fi
REMAINING_DISKS=() REMAINING_DISKS=()
for option in "${DISK_OPTIONS[@]}"; do for label in "${DISK_OPTIONS[@]}"; do
is_boot_disk=false is_boot_disk=false
for selected in "${SELECTED_BOOT_OPTIONS[@]}"; do for selected in "${SELECTED_BOOT_LABELS[@]}"; do
if [[ "$option" == "$selected" ]]; then if [[ "$label" == "$selected" ]]; then is_boot_disk=true; break; fi
is_boot_disk=true
break
fi
done done
if ! $is_boot_disk; then if ! $is_boot_disk; then REMAINING_DISKS+=("$label"); fi
REMAINING_DISKS+=("$option")
fi
done done
if [ ${#REMAINING_DISKS[@]} -gt 0 ]; then if [ ${#REMAINING_DISKS[@]} -gt 0 ]; then
echo -e "\n\n ➡️ Please choose your data disks (up to 4):" echo -e ""
mapfile -t SELECTED_DATA_OPTIONS < <(gum choose --limit 4 "${REMAINING_DISKS[@]}") gum style --foreground 212 " ➡️ Please choose your data and parity disks (up to 9 total)."
NUMBER_OF_DATA_DISKS=${#SELECTED_DATA_OPTIONS[@]} mapfile -t SELECTED_DATA_LABELS < <(gum choose --limit 9 --header "$HEADER" "${REMAINING_DISKS[@]}")
for i in $(seq 0 $(($NUMBER_OF_DATA_DISKS - 1))); do
declare "DATA_DISK_$(($i + 1))"="${DISK_MAP["${SELECTED_DATA_OPTIONS[$i]}"]}" if [ ${#SELECTED_DATA_LABELS[@]} -gt 0 ]; then
done 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 else
echo -e "\n\n ️ No remaining disks available for data storage." echo -e "\n️ No remaining disks to select for data."
NUMBER_OF_DATA_DISKS=0
fi 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 NUMBER_OF_DATA_DISKS=0
echo -e "\n\n ✅ Generating disk configuration from template: $DISK_CONFIG_TEMPLATE" for i in {1..9}; do
envsubst < "$DISK_CONFIG_TEMPLATE" > disk-config.nix disk_var="DATA_DISK_$i"
else [[ -n "${!disk_var}" ]] && NUMBER_OF_DATA_DISKS=$((NUMBER_OF_DATA_DISKS + 1))
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)." done
echo " Looked for: $DISK_CONFIG_TEMPLATE"
exit 1 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 <<EOF
### Disk Configuration Summary
Please review the selected disk layout before proceeding.
**Boot Disks ($NUMBER_OF_BOOT_DISKS):**
* **Boot 1:** \`$BOOT_DISK_1\`
$( [[ -n "$BOOT_DISK_2" ]] && echo "* **Boot 2:** \`$BOOT_DISK_2\`" || echo "* **Boot 2:** *Not configured*")
**Data Disks ($NUMBER_OF_DATA_DISKS):**
$(for i in {1..9}; do disk_var="DATA_DISK_$i"; [[ -n "${!disk_var}" ]] && echo "* **Data $i:** \`${!disk_var}\`"; done)
$( [[ $NUMBER_OF_DATA_DISKS -eq 0 ]] && echo "* *Not configured*")
**Parity Disks ($NUMBER_OF_PARITY_DISKS):**
$(for i in {1..3}; do disk_var="PARITY_DISK_$i"; [[ -n "${!disk_var}" ]] && echo "* **Parity $i:** \`${!disk_var}\`"; done)
$( [[ $NUMBER_OF_PARITY_DISKS -eq 0 ]] && echo "* *Not configured*")
EOF
)
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")"
gum confirm "Proceed with this disk configuration?" || { echo " ❌ Aborting as requested."; exit 1; }
echo -e "\n\n ⚙️ Generating disko configuration from templates..."
template_file="config-files/disks/boot-${NUMBER_OF_BOOT_DISKS}.nix"
(envsubst < "$template_file") > 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() { deploy() {
@@ -338,7 +455,7 @@ deploy() {
--chown "/home/numbus-admin/" 1000:1000 \ --chown "/home/numbus-admin/" 1000:1000 \
--target-host nixos@$TARGET_HOST --target-host nixos@$TARGET_HOST
echo -e "\n\n ✅ Installation successfull !!" echo -e "\n\n ✅ Installation successfull !"
sleep 1 sleep 1
} }
@@ -375,18 +492,6 @@ EOF
sleep 1 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 # 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") 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 echo $ACTION_ANSWER
@@ -395,16 +500,20 @@ if [[ "$ACTION_ANSWER" == "[1] 🌐 Deploy NixOS on a remote machine" ]]; then
echo -e "\n ➡️ Proceeding with deployment…" 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." 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 necessary_credentials
setup_ssh
hardware_detection hardware_detection
files_generation files_generation
disk_config_generation disk_config_generation
generate_disko_config
elif [[ "$ACTION_ANSWER" == "[2] 💽 Deploy NixOS on a remote machine with a file configuration" ]]; then 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…" 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." 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 necessary_credentials_with_config
setup_ssh
hardware_detection hardware_detection
files_generation files_generation
disk_config_generation disk_config_generation
generate_disko_config
elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then
echo -e "\n ➡️ Proceeding with update…" 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." prerun_action "\n\n ➡️ On the target host : make sure the NixOS installation you want to update is up-and-running, accessible with SSH."