Script reformatting with addition of a TUI. Now working on the disk configuration step.

This commit is contained in:
Raphaël Billet
2025-11-18 22:34:04 +01:00
parent 60e5dd2615
commit c666160b4e
2 changed files with 200 additions and 198 deletions
+195 -198
View File
@@ -1,84 +1,78 @@
#@GEMINI.md @agents Take the NixOS expert role. I would like to make this installer universal, this means that the disko config has to adapt
#to the available disks in the system. Since covering every possible disk configuration would be impossible, I would like to cover a few of them
#that are relevant in the context of a home server. First I want every disk to be encrypted. Second, there always has to be a boot drive on which
#nixos, docker and config data (small data) is installed. This drive can be standalone (even though that is kind of pointless in production but this
#is more for testing purposes). Third, if present, other disks (2 or 3 never more) than the boot drive must be used in a redundant way for the
#big data (nextcloud user data, immich photos, ...). Fourth, if the data disks are SSDs or NVMes, they must use ZFS (mirror or raid1).
#!/bin/bash
set -euo pipefail
cat <<EOF
██████ █████ ███ ███████ █████████
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
█████████ █████
███░░░░░███ ░░███
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
███ ░███
░░██████
░░░░░░
EOF
sleep 1
hardware_detection() {
echo -e "\n\n 🔎 Detecting graphics card on target host..."
VGA_INFO=$(ssh nixos@$TARGET_HOST 'lspci -nn | grep -i "vga"')
if echo "$VGA_INFO" | grep -iq "intel"; then
echo -e " ✅ Intel graphics card detected."
TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "amd"; then
echo -e " ✅ AMD graphics card detected."
TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "nvidia"; then
echo -e " ✅ NVIDIA graphics card detected."
TARGET_GRAPHICS="true"
install_prerun_action() {
echo -e "\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."
SETUP_ANSWER="$(gum input --placeholder "Type 'done' when you have finished.")"
if [[ "$SETUP_ANSWER" == "done" ]]; then
:
else
echo -e " ️ No dedicated graphics card detected."
TARGET_GRAPHICS="false"
echo " Aborting - you did not type 'done'."
exit 1
fi
}
echo -e "\n\n 🔎 Detecting transconding acceleration on target host..."
if ls /dev/dri/renderD128; then
echo -e " ✅ Transcoding capable card detected."
TARGET_GRAPHICS_RENDERER="true"
update_prerun_action() {
echo -e "\n\n ➡️ On the target host : make sure the NixOS installation you want to update is up-and-running, accessible with SSH."
SETUP_ANSWER="$(gum input --placeholder "Type 'done' when you have finished.")"
if [[ "$SETUP_ANSWER" == "done" ]]; then
:
else
echo -e " ️ No transcoding capable card detected."
TARGET_GRAPHICS_RENDERER="false"
echo " Aborting - you did not type 'done'."
exit 1
fi
}
echo -e "\n\n 🔎 Detecting USB Google Coral TPU on target host..."
if ssh nixos@$TARGET_HOST 'lsusb | grep -iq "google"'; 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
necessary_credentials() {
#TARGET SETTINGS
echo -e "\n\n ➡️ Please provide the IP address of the target host :"
TARGET_HOST="$(gum input --placeholder "192.168.1.100")"
echo -e "\n\n ➡️ Please provide the public SSH key of an authorized device :"
SSH_PUBLIC_KEY="$(gum input --placeholder "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc")"
echo -e "\n\n 🔎 Detecting Zigbee coordinator on target host..."
if ssh nixos@$TARGET_HOST 'ls /dev/serial/by-id/ | grep -i "zigbee"'; then
echo -e " ✅ Zigbee device found in /dev/serial/by-id/."
TARGET_ZIGBEE_DEVICE=$(ssh nixos@$TARGET_HOST 'ls /dev/serial/by-id/ | grep -i "zigbee"')
TARGET_ZIGBEE_DEVICE_PATH="/dev/serial/by-id/$TARGET_ZIGBEE_DEVICE"
TARGET_ZIGBEE="true"
else
echo -e " ️ No Zigbee device found."
TARGET_ZIGBEE="false"
# TRAEFIK SETTINGS
echo -e "\n\n ➡️ Please provide the domain name (FQDN) your home server will use :"
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) :"
EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
echo -e "\n\n ➡️ Please provide a cloudflare API token with DNS zone permission :"
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 :"
SENDER_EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
echo -e "\n\n ➡️ Please provide the password of this email address :"
SENDER_EMAIL_ADDRESS_PASSWORD="$(gum input --placeholder "abcd efgh ijkl mnop")"
echo -e "\n\n ➡️ Please provide the SMTP server endpoint :"
SENDER_EMAIL_DOMAIN="$(gum input --placeholder "smtp.gmail.com")"
echo -e "\n\n ➡️ Please provide the smtp TLS port (for gmail : 587) :"
SENDER_EMAIL_PORT="$(gum input --placeholder "587")"
# NETWORK SETTINGS
echo -e "\n\n ➡️ Please provide your home network subnet :"
HOME_ROUTER_SUBNET="$(gum input --placeholder "192.168.1.1/24")"
echo -e "\n\n ➡️ Please provide the ip address of your router :"
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.) :"
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 TARGET_DISK 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"
else
echo "\n ❌ $VAR is missing or empty"
MISSING=1
fi
done
if [[ "$MISSING" == "1" ]]; then
exit 1
fi
}
@@ -117,15 +111,12 @@ files_generation() {
--age $SOPS_PUBLIC_KEY \
--output extra-files/etc/nixos/secrets/secrets.yaml
echo -e "\n\n ✅ Writing correct disk to disk-config.nix..."
sed -i s+TARGET_DISK+$TARGET_DISK+g disk-config.nix
echo -e "\n\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\n ✅ Adapting the docker configuration to your hardware..."
if [[ "$TARGET_GRAPHICS" == "true" && "$TARGET_USB_CORAL" == "true" ]]; then
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" && "$TARGET_USB_CORAL" == "true" ]]; then
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
@@ -135,12 +126,12 @@ files_generation() {
N;
/----------------------------------------- #/c\
devices:\
- /dev/dri:/dev/dri\
- /dev/dri/renderD128:/dev/dri/renderD128\
- /dev/bus/usb:/dev/bus/usb
}
}
}' docker/frigate.nix
elif [[ "$TARGET_GRAPHICS" == "true" && "$TARGET_USB_CORAL" == "false" ]]; then
elif [[ "$TARGET_GRAPHICS_RENDERER" == "true" && "$TARGET_USB_CORAL" == "false" ]]; then
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
@@ -150,11 +141,11 @@ files_generation() {
N;
/----------------------------------------- #/c\
devices:\
- /dev/dri:/dev/dri\
- /dev/dri/renderD128:/dev/dri/renderD128\
}
}
}' docker/frigate.nix
elif [[ "$TARGET_GRAPHICS" == "false" && "$TARGET_USB_CORAL" == "true" ]]; then
elif [[ "$TARGET_GRAPHICS_RENDERER" == "false" && "$TARGET_USB_CORAL" == "true" ]]; then
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
@@ -179,7 +170,7 @@ files_generation() {
N;
/----------------------------------- #/c\
devices:\
- ${TARGET_ZIGBEE_DEVICE_PATH}:/dev/ttyUSB0
- /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0
}
}
}" docker/hass.nix
@@ -199,6 +190,74 @@ files_generation() {
nix shell nixpkgs#mosquitto -c mosquitto_passwd -b extra-files/mnt/config-storage/hass/mqtt/config/password.txt $HOME_ASSISTANT_MQTT_USER $HOME_ASSISTANT_MQTT_PASSWORD
}
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
echo -e " ✅ Intel graphics card detected."
TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "amd"; then
echo -e " ✅ AMD graphics card detected."
TARGET_GRAPHICS="true"
elif echo "$VGA_INFO" | grep -iq "nvidia"; then
echo -e " ✅ NVIDIA graphics card detected."
TARGET_GRAPHICS="true"
else
echo -e " ️ No dedicated graphics card detected."
TARGET_GRAPHICS="false"
fi
echo -e "\n\n 🔎 Detecting transconding acceleration on target host..."
if ssh_to_host "ls /dev/dri/renderD128"; 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'"; 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'"; 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'")
TARGET_ZIGBEE="true"
else
echo -e " ️ No Zigbee device found."
TARGET_ZIGBEE="false"
fi
}
disk_config_generation() {
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."
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'."
exit 1
fi
sleep 10
}
deploy() {
echo -e "\n\n 🔄 Deploying to the remote server..."
nix run github:nix-community/nixos-anywhere -- \
@@ -212,130 +271,68 @@ deploy() {
sleep 1
}
nixos_deployment() {
echo -e "\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."
echo -e " Type 'done' when you have finished."
read -r SETUP_ANSWER
if [[ "$SETUP_ANSWER" == "done" ]]; then
:
else
echo "Aborting - you did not type 'done'."
exit 1
fi
#TARGET SETTINGS
echo -e "\n\n ➡️ Please provide the IP address of the target host :"
read -r TARGET_HOST
echo -e "\n\n ➡️ Please provide the disk you want to install NixOS on (i.e. /dev/vda, /dev/sda, /dev/nvme0n1...) :"
read -r TARGET_DISK
echo -e "\n\n ➡️ Please provide the public SSH key of an authorized device :"
read -r SSH_PUBLIC_KEY
# TRAEFIK SETTINGS
echo -e "\n\n ➡️ Please provide the domain name (FQDN) your home server will use (i.e. yourdomain.com):"
read -r DOMAIN_NAME
echo -e "\n\n ➡️ Please provide a valid email address (will be used for ACME, and your services) :"
read -r EMAIL_ADDRESS
echo -e "\n\n ➡️ Please provide a cloudflare API token with DNS zone permission :"
read -r CF_DNS_API_TOKEN
#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 (Gmail for example)."
echo -e " Please provide a valid sender email address :"
read -r SENDER_EMAIL_ADDRESS
echo -e "\n\n ➡️ Please provide the password of this email address :"
read -r SENDER_EMAIL_ADDRESS_PASSWORD
echo -e "\n\n ➡️ Please provide the smtp endpoint (for gmail : smtp.gmail.com) :"
read -r SENDER_EMAIL_DOMAIN
echo -e "\n\n ➡️ Please provide the smtp TLS port (for gmail : 587) :"
read -r SENDER_EMAIL_PORT
#NETWORK SETTINGS
echo -e "\n\n ➡️ Please provide your home network subnet (i.e. 192.168.1.1/24) :"
read -r HOME_ROUTER_SUBNET
echo -e "\n\n ➡️ Please provide the ip address of your router (i.e. 192.168.1.1) :"
read -r HOME_ROUTER_IP
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\n range that is not in use. 192.168.1.5 for example.) :"
read -r HOME_SERVER_IP
echo -e "\n\n ➡️ Please provide enter the password of the remote target."
ssh-copy-id nixos@$TARGET_HOST
hardware_detection
files_generation
deploy
}
nixos_deployment_with_config() {
echo -e "\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."
echo -e " Type 'done' when you have finished."
read -r SETUP_ANSWER
if [[ "$SETUP_ANSWER" == "done" ]]; then
:
else
echo "Aborting - you did not type 'done'."
exit 1
fi
echo -e "\n\n ➡️ Please provide the path to a config file :"
read -erp CONFIG_PATH
CONFIG_PATH=$(realpath -m "$CONFIG_PATH")
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "Error: '$CONFIG_PATH' does not exist or is not a regular file."
exit 1
elif [[ ! -r "$CONFIG_PATH" ]]; then
echo "Error: '$CONFIG_PATH' is not readable."
exit 1
fi
if grep -qE '^[[:space:]]*[^[:space:]#][^=]*=' "$CONFIG_PATH"; then
source "$CONFIG_PATH"
else
echo "Config contains unsupported syntax."
exit 1
fi
REQUIRED_VARS=(TARGET_HOST TARGET_DISK 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"
sleep 0.1
else
echo "\n ❌ $VAR is missing or empty"
sleep 0.1
MISSING=1
fi
done
if [[ "$MISSING" == "1" ]]; then
exit 1
fi
files_generation
deploy
}
nixos_update() {
}
echo -e "\n\n Please choose an action (i.e. 1, 2 or 3) :\n"
echo -e " - [1] 🌐 Deploy NixOS on a remote machine"
echo -e " - [2] 💽 Deploy NixOS on a remote machine with a file configuration"
echo -e " - [3] 🛠️ Update a NixOS remote machine"
read -r ACTION_ANSWER
set -euo pipefail
if [[ "$ACTION_ANSWER" == "1" ]]; then
cat <<EOF
██████ █████ ███ ███████ █████████
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
█████████ █████
███░░░░░███ ░░███
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
███ ░███
░░██████
░░░░░░
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
if [[ "$ACTION_ANSWER" == "[1] 🌐 Deploy NixOS on a remote machine" ]]; then
echo -e "\n ➡️ Proceeding with deployment…"
nixos_deployment
elif [[ "$ACTION_ANSWER" == "2" ]]; then
install_prerun_action
necessary_credentials
files_generation
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…"
nixos_deployment_with_config
elif [[ "$ACTION_ANSWER" == "3" ]]; then
install_prerun_action
necessary_credentials_with_config
files_generation
elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then
echo -e "\n ➡️ Proceeding with update…"
update_prerun_action
nixos_update
else
echo "Aborting - you did not type '1, 2 or 3'."
+5
View File
@@ -0,0 +1,5 @@
ssh_to_host() {
ssh raphael@192.168.11.1 "$1"
}
VGA_INFO=$(ssh_to_host "lspci -nn | grep -i 'vga'")
echo $VGA_INFO