#!/bin/bash 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 " Aborting - you did not type 'done'." exit 1 fi } 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 " 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 :" 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")" # 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 } 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 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_RENDERER" == "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/renderD128:/dev/dri/renderD128\ - /dev/bus/usb:/dev/bus/usb } } }' docker/frigate.nix elif [[ "$TARGET_GRAPHICS_RENDERER" == "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/renderD128:/dev/dri/renderD128\ } } }' docker/frigate.nix elif [[ "$TARGET_GRAPHICS_RENDERER" == "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:\ - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/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 } 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 -- \ --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() { } set -euo pipefail cat < /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…" 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…" 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'." exit 1 fi