Files
Numbus/deploy.sh
T
2025-11-27 22:01:30 +01:00

550 lines
26 KiB
Bash
Executable File

#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum openssl sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
prerun_action() {
echo -e "$1"
SETUP_ANSWER="$(gum input --placeholder 'Type "done" when you have finished.')"
if [[ "$SETUP_ANSWER" == "done" ]]; then
:
else
echo ' ❌ Aborting - you did not type "done".'
exit 1
fi
}
necessary_credentials() {
#TARGET SETTINGS
echo -e "\n\n ➡️ Please provide the IP address of the target host :"
export TARGET_HOST="$(gum input --placeholder "192.168.1.100")"
echo -e "\n\n ➡️ Please provide the public SSH key of an authorized device :"
export SSH_PUBLIC_KEY="$(gum input --placeholder "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc")"
# TRAEFIK SETTINGS
echo -e "\n\n ➡️ Please provide the domain name (FQDN) your home server will use :"
export DOMAIN_NAME="$(gum input --placeholder "yourdomain.com")"
echo -e "\n\n ➡️ Please provide a valid email address (will be used for ACME, and your services) :"
export EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
echo -e "\n\n ➡️ Please provide a cloudflare API token with DNS zone permission :"
export CF_DNS_API_TOKEN="$(gum input --placeholder "bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE")"
# SMTP SETTINGS
echo -e "\n\n ➡️ Some services will be able to send you emails. For that you need an email that supports sending emails.\n Please provide a valid sender email address :"
export SENDER_EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
echo -e "\n\n ➡️ Please provide the password of this email address :"
export SENDER_EMAIL_ADDRESS_PASSWORD="$(gum input --placeholder "abcd efgh ijkl mnop")"
echo -e "\n\n ➡️ Please provide the SMTP server endpoint :"
export SENDER_EMAIL_DOMAIN="$(gum input --placeholder "smtp.gmail.com")"
echo -e "\n\n ➡️ Please provide the smtp TLS port (for gmail : 587) :"
export SENDER_EMAIL_PORT="$(gum input --placeholder "587")"
# NETWORK SETTINGS
echo -e "\n\n ➡️ Please provide your home network subnet :"
export HOME_ROUTER_SUBNET="$(gum input --placeholder "192.168.1.1/24")"
echo -e "\n\n ➡️ Please provide the ip address of your router :"
export HOME_ROUTER_IP="$(gum input --placeholder "192.168.1.1")"
echo -e "\n\n ➡️ Please choose the ip address that your server will use (i.e. any address in the 192.168.1.1/24 range that is not in use.) :"
export HOME_SERVER_IP="$(gum input --placeholder "192.168.1.5")"
}
necessary_credentials_with_config() {
echo -e "\n\n ➡️ Please choose your configuration file :"
CONFIG_PATH="$(gum file)"
source "$CONFIG_PATH"
REQUIRED_VARS=(TARGET_HOST SSH_PUBLIC_KEY DOMAIN_NAME EMAIL_ADDRESS CF_DNS_API_TOKEN SENDER_EMAIL_ADDRESS SENDER_EMAIL_ADDRESS_PASSWORD SENDER_EMAIL_DOMAIN SENDER_EMAIL_PORT HOME_ROUTER_SUBNET HOME_ROUTER_IP HOME_SERVER_IP)
MISSING=0
for VAR in "${REQUIRED_VARS[@]}"; do
if [[ -v $VAR && -n ${!VAR} ]]; then
echo -e "\n ✅ $VAR imported successfully from the config file"
export $VAR
else
echo "\n ❌ $VAR is missing or empty"
MISSING=1
fi
done
if [[ "$MISSING" == "1" ]]; then
exit 1
fi
}
setup_ssh() {
echo -e "\n\n ✅ Generating new SSH for numbus-admin..."
mkdir -p extra-files/home/numbus-admin/.ssh/
chmod 700 extra-files/home/numbus-admin/.ssh/
ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "extra-files/home/numbus-admin/.ssh/id_ed25519" -N "" -q
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" "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" "nixos@$TARGET_HOST" "$1"
}
hardware_detection() {
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" 2>/dev/null; then
echo -e " ✅ Intel graphics card detected."
export TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "amd" 2>/dev/null; then
echo -e " ✅ AMD graphics card detected."
export TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "nvidia" 2>/dev/null; then
echo -e " ✅ NVIDIA graphics card detected."
export TARGET_GRAPHICS="true"
else
echo -e " ⚠️ No dedicated graphics card detected."
export TARGET_GRAPHICS="false"
fi
echo -e "\n\n 🔎 Detecting transconding acceleration on target host..."
if ssh_to_host "ls /dev/dri/renderD300" 2>/dev/null; then
echo -e " ✅ Transcoding capable card detected."
TARGET_GRAPHICS_RENDERER="true"
else
echo -e " ⚠️ No transcoding capable card detected."
TARGET_GRAPHICS_RENDERER="false"
fi
echo -e "\n\n 🔎 Detecting USB Google Coral TPU on target host..."
if ssh_to_host "lsusb | grep -iq 'google'" 2>/dev/null; then
echo -e " ✅ USB Google Coral TPU detected."
TARGET_USB_CORAL="true"
else
echo -e " ⚠️ No USB Google Coral TPU detected."
TARGET_USB_CORAL="false"
fi
echo -e "\n\n 🔎 Detecting Zigbee coordinator on target host..."
if ssh_to_host "ls /dev/serial/by-id/ | grep -i 'zigbee'" 2>/dev/null; then
echo -e " ✅ Zigbee device found in /dev/serial/by-id/."
TARGET_ZIGBEE_DEVICE=$(ssh_to_host "ls /dev/serial/by-id/ | grep -i 'zigbee'")
else
echo -e " ⚠️ No Zigbee device found."
TARGET_ZIGBEE_DEVICE=""
fi
}
services_selection() {
echo -e "\n\n ➡️ You will now select the services you want installed on your server:"
declare -A SERVICE_MAP
SERVICE_MAP["Pi-Hole: Block ads on all your devices"]="pihole"
SERVICE_MAP["Home Assistant: Manage your smart home or security cameras"]="hass"
SERVICE_MAP["Passbolt: Secure password manager with collaboration features"]="passbolt"
SERVICE_MAP["Frigate [Home Assistant required]: Secure your house with security cameras"]="frigate"
SERVICE_MAP["Nextcloud: No fuss Office 365 replacement"]="nextcloud"
SERVICE_MAP["Immich: Pictures and videos backup with local machine-learning"]="immich"
mapfile -t SERVICE_DESCRIPTIONS < <(for key in "${!SERVICE_MAP[@]}"; do echo "$key"; done | sort)
SELECTED_DESCRIPTIONS_STRING=$(gum choose --no-limit --header "Homelab services:" "${SERVICE_DESCRIPTIONS[@]}")
SERVICES=()
if [[ -n "$SELECTED_DESCRIPTIONS_STRING" ]]; then
while IFS= read -r line; do
SERVICES+=("${SERVICE_MAP[$line]}")
done <<< "$SELECTED_DESCRIPTIONS_STRING"
fi
}
files_generation() {
echo -e "\n\n ✅ Generating necessary folder tree..."
mkdir -p extra-files/var/lib/sops-nix/
mkdir -p extra-files/etc/nixos/secrets/
mkdir -p extra-files/mnt/config-storage/traefik/config/conf/
mkdir -p extra-files/mnt/config-storage/hass/mqtt/config/
mkdir -p extra-files/mnt/config-storage/hass/mqtt/data/
mkdir -p extra-files/mnt/data-storage/nextcloud/
mkdir -p extra-files/mnt/data-storage/immich/
echo -e "\n ✅ Generating sops-nix keys..."
ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt
export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt)
echo -e "\n ✅ Generating sops-nix configuration files..."
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml
echo -e "\n ✅ Generating secure random database passwords..."
export HOME_ASSISTANT_MQTT_USER="$(openssl rand -hex 10)"
export HOME_ASSISTANT_MQTT_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export PASSBOLT_MYSQL_DATABASE="$(openssl rand -hex 10)"
export PASSBOLT_MYSQL_USER="$(openssl rand -hex 10)"
export PASSBOLT_MYSQL_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export FTLCONF_WEBSERVER_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_1="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_2="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_3="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_4="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_5="$(openssl rand -base64 32 | tr -d '\=+/')"
export DATA_DISK_6="$(openssl rand -base64 32 | tr -d '\=+/')"
export PARITY_DISK_1="$(openssl rand -base64 32 | tr -d '\=+/ ')"
export PARITY_DISK_2="$(openssl rand -base64 32 | tr -d '\=+/ ')"
export PARITY_DISK_3="$(openssl rand -base64 32 | tr -d '\=+/ ')"
echo "$REMOTE_PASS" | ssh_to_host """ >/dev/null 2>&1
sudo -S mkdir -p /run/secrets/disks/
echo -n $DATA_DISK_1 | sudo -S tee /run/secrets/disks/data-disk-1
echo -n $DATA_DISK_2 | sudo -S tee /run/secrets/disks/data-disk-2
echo -n $DATA_DISK_3 | sudo -S tee /run/secrets/disks/data-disk-3
echo -n $DATA_DISK_4 | sudo -S tee /run/secrets/disks/data-disk-4
echo -n $DATA_DISK_5 | sudo -S tee /run/secrets/disks/data-disk-5
echo -n $DATA_DISK_6 | sudo -S tee /run/secrets/disks/data-disk-6
echo -n $PARITY_DISK_1 | sudo -S tee /run/secrets/disks/parity-disk-1
echo -n $PARITY_DISK_2 | sudo -S tee /run/secrets/disks/parity-disk-2
echo -n $PARITY_DISK_3 | sudo -S tee /run/secrets/disks/parity-disk-3
"""
mkdir -p extra-files/run/secrets/disks/
echo -n $DATA_DISK_1 > extra-files/run/secrets/disks/data-disk-1
echo -n $DATA_DISK_2 > extra-files/run/secrets/disks/data-disk-2
echo -n $DATA_DISK_3 > extra-files/run/secrets/disks/data-disk-3
echo -n $DATA_DISK_4 > extra-files/run/secrets/disks/data-disk-4
echo -n $DATA_DISK_5 > extra-files/run/secrets/disks/data-disk-5
echo -n $DATA_DISK_6 > extra-files/run/secrets/disks/data-disk-6
echo -n $PARITY_DISK_1 > extra-files/run/secrets/disks/parity-disk-1
echo -n $PARITY_DISK_2 > extra-files/run/secrets/disks/parity-disk-2
echo -n $PARITY_DISK_3 > extra-files/run/secrets/disks/parity-disk-3
echo -e "\n ✅ Encrypting secrets in the correct file..."
envsubst < "config-files/sops-nix/secrets.yaml" | sops encrypt --filename-override secrets.yaml \
--input-type yaml --output-type yaml \
--age $SOPS_PUBLIC_KEY \
--output extra-files/etc/nixos/secrets/secrets.yaml
cp -avu extra-files/etc/nixos/secrets/secrets.yaml ./secrets/secrets.yaml
echo -e "\n ✅ Writing correct ips to configuration.nix..."
sed -i s+HOME_SERVER_IP+$HOME_SERVER_IP+g configuration.nix
sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_IP+g configuration.nix
echo -e "\n ✅ Adapting the docker configuration to your hardware..."
DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
DEVICES_BLOCK+=" - /dev/dri/renderD300:/dev/dri/renderD300\n"
fi
if [[ "$TARGET_USB_CORAL" == "true" ]]; then
DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
fi
if [[ -n "$DEVICES_BLOCK" ]]; then
REPLACEMENT="devices:\n${DEVICES_BLOCK%\\n}"
sed -i.bak "s|# --- frigate devices --- #|$REPLACEMENT|" docker/frigate.original
else
sed -i.bak "/# --- frigate devices --- #/d" docker/frigate.original
fi
if [[ -n "$TARGET_ZIGBEE_DEVICE" ]]; then
REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
sed -i.bak "s|# --- hass devices --- #|$REPLACEMENT|" docker/hass.original
else
sed -i.bak "/# --- hass devices --- #/d" docker/hass.original
fi
echo -e "\n ✅ Copying configuration files for the selected homelab services..."
cp docker/traefik.original docker/traefik.nix
for service in "${SERVICES[@]}"; do
cp docker/${service}.original docker/${service}.nix
done
echo -e "\n ✅ Writing docker configuration files..."
envsubst < config-files/docker/traefik/headers.yaml > extra-files/mnt/config-storage/traefik/config/conf/headers.yaml
envsubst < config-files/docker/traefik/nextcloud.yaml > extra-files/mnt/config-storage/traefik/config/conf/nextcloud.yaml
envsubst < config-files/docker/traefik/tls.yaml > extra-files/mnt/config-storage/traefik/config/conf/tls.yaml
envsubst < config-files/docker/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml
envsubst < config-files/docker/hass/mosquitto.conf > extra-files/mnt/config-storage/hass/mqtt/config/mosquitto.conf
touch extra-files/mnt/config-storage/hass/mqtt/config/password.txt
chmod 0700 extra-files/mnt/config-storage/hass/mqtt/config/password.txt
mosquitto_passwd -b extra-files/mnt/config-storage/hass/mqtt/config/password.txt $HOME_ASSISTANT_MQTT_USER $HOME_ASSISTANT_MQTT_PASSWORD
}
disk_config_generation() {
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 🔎 Fetching and analyzing disks from target host... (This may take a moment)"
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
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[@]}")
if [ ${#SELECTED_BOOT_LABELS[@]} -eq 0 ]; then echo " ❌ No boot disk selected. Aborting."; exit 1; fi
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 label in "${DISK_OPTIONS[@]}"; do
is_boot_disk=false
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+=("$label"); fi
done
if [ ${#REMAINING_DISKS[@]} -gt 0 ]; then
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 to select for data."
fi
# --- Final Recap ---
NUMBER_OF_BOOT_DISKS=0
[[ -n "$BOOT_DISK_1" ]] && NUMBER_OF_BOOT_DISKS=$((NUMBER_OF_BOOT_DISKS + 1)) && export BOOT_DISK_1
[[ -n "$BOOT_DISK_2" ]] && NUMBER_OF_BOOT_DISKS=$((NUMBER_OF_BOOT_DISKS + 1)) && export BOOT_DISK_2
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 <<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 -e "\n ✅ 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 -e "\n ✅ 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 -e "\n ✅ Generated $NUMBER_OF_PARITY_DISKS parity disk configuration(s)."
# Close the imports block
cat <<'EOF' >> disk-config.nix
};
};
}
EOF
echo -e "\n ✅ Final disko configuration created at 'disk-config.nix'."
}
deploy() {
echo -e "\n\n 🔄 Deploying to the remote server..."
nix run github:nix-community/nixos-anywhere -- \
--generate-hardware-config nixos-generate-config ./hardware-configuration.nix \
--flake .#numbus-server \
--extra-files extra-files \
--chown "/home/numbus-admin/" 1000:1000 \
--target-host nixos@$TARGET_HOST
echo -e "\n\n ✅ Installation successfull !"
sleep 1
}
nixos_update() {
echo -e "\n\n 🔄 Updating NixOS on the remote server..."
echo "coming soon !"
}
set -euo pipefail
cat <<EOF
██████ █████ ███ ███████ █████████
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
█████████ █████
███░░░░░███ ░░███
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
███ ░███
░░██████
░░░░░░
EOF
sleep 1
# 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
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
services_selection
files_generation
disk_config_generation
deploy
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
services_selection
files_generation
disk_config_generation
deploy
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."
nixos_update
else
echo "Aborting - you did not type '1, 2 or 3'."
exit 1
fi