Files
Numbus/deploy.sh
T
2025-11-14 19:48:16 +01:00

343 lines
16 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#@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"
else
echo -e " ️ No dedicated graphics card detected."
TARGET_GRAPHICS="false"
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"
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 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
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"
fi
}
files_generation() {
echo -e "\n\n ✅ Generating necessary folder tree..."
mkdir -p extra-files/home/numbus-admin/.ssh/
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\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
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)
echo -e "\n\n ✅ Generating sops-nix configuration files..."
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml
echo -e "\n\n ✅ Generating secure random database passwords..."
HOME_ASSISTANT_MQTT_USER=$(openssl rand -base64 29 | tr -d "123456789=+/" | cut -c1-10)
HOME_ASSISTANT_MQTT_PASSWORD=$(openssl rand -base64 29 | tr -d "=+/" | cut -c1-64)
PASSBOLT_MYSQL_DATABASE=$(openssl rand -base64 29 | tr -d "123456789=+/" | cut -c1-10)
PASSBOLT_MYSQL_USER=$(openssl rand -base64 29 | tr -d "123456789=+/" | cut -c1-10)
PASSBOLT_MYSQL_PASSWORD=$(openssl rand -base64 29 | tr -d "=+/" | cut -c1-64)
FTLCONF_WEBSERVER_PASSWORD=$(openssl rand -base64 29 | tr -d "=+/" | cut -c1-64)
echo -e "\n\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
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
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
/DEVICES SECTION WILL APPEAR HERE IF CORAL/{
N;
/TPU OR INTEGRATED GRAPHICS ARE PRESENT/{
N;
/----------------------------------------- #/c\
devices:\
- /dev/dri:/dev/dri\
- /dev/bus/usb:/dev/bus/usb
}
}
}' docker/frigate.nix
elif [[ "$TARGET_GRAPHICS" == "true" && "$TARGET_USB_CORAL" == "false" ]]; then
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
/DEVICES SECTION WILL APPEAR HERE IF CORAL/{
N;
/TPU OR INTEGRATED GRAPHICS ARE PRESENT/{
N;
/----------------------------------------- #/c\
devices:\
- /dev/dri:/dev/dri\
}
}
}' docker/frigate.nix
elif [[ "$TARGET_GRAPHICS" == "false" && "$TARGET_USB_CORAL" == "true" ]]; then
sed -i.bak '
/^[[:space:]]*# ----------------------------------------- #/{
N;
/DEVICES SECTION WILL APPEAR HERE IF CORAL/{
N;
/TPU OR INTEGRATED GRAPHICS ARE PRESENT/{
N;
/----------------------------------------- #/c\
devices:\
- /dev/bus/usb:/dev/bus/usb
}
}
}' docker/frigate.nix
fi
if [[ "$TARGET_ZIGBEE" == "true" ]]; then
sed -i.bak "
/^[[:space:]]*# ----------------------------------- #/{
N;
/DEVICES SECTION WILL APPEAR HERE IF/{
N;
/ZIGBEE USB DEVICE IS PRESENT/{
N;
/----------------------------------- #/c\
devices:\
- ${TARGET_ZIGBEE_DEVICE_PATH}:/dev/ttyUSB0
}
}
}" docker/hass.nix
fi
echo -e "\n\n ✅ Copying files to the new installation..."
cp -ravu secrets/ .sops.yaml hardware-configuration.nix extra-files/etc/nixos/
echo -e "\n\n ✅ Writing docker configuration files..."
envsubst < config-files/traefik/headers.yaml > extra-files/mnt/config-storage/traefik/config/conf/headers.yaml
envsubst < config-files/traefik/nextcloud.yaml > extra-files/mnt/config-storage/traefik/config/conf/nextcloud.yaml
envsubst < config-files/traefik/tls.yaml > extra-files/mnt/config-storage/traefik/config/conf/tls.yaml
envsubst < config-files/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml
envsubst < config-files/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
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
}
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_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
if [[ "$ACTION_ANSWER" == "1" ]]; then
echo -e "\n ➡️ Proceeding with deployment…"
nixos_deployment
elif [[ "$ACTION_ANSWER" == "2" ]]; then
echo -e "\n ➡️ Proceeding with deployment using a config file…"
nixos_deployment_with_config
elif [[ "$ACTION_ANSWER" == "3" ]]; then
echo -e "\n ➡️ Proceeding with update…"
nixos_update
else
echo "Aborting - you did not type '1, 2 or 3'."
exit 1
fi