#@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 < 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