#!/bin/bash set -euo pipefail cat < .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_api_password=$(openssl rand -base64 29 | tr -d "=+/" | cut -c1-64) echo -e "\n\n ✅ Encrypting secrets in the correct file..." mkdir -p secrets/ cd secrets/ echo """ssh-public-keys: $SSH_PUBLIC_KEY docker: nextcloud: | DOMAIN_NAME=$DOMAIN_NAME NEXTCLOUD_ENABLE_DRI_DEVICE=$TARGET_GRAPHICS frigate: | DOMAIN_NAME=$DOMAIN_NAME FRIGATE_MQTT_USER=$HOME_ASSISTANT_MQTT_USER FRIGATE_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD traefik: | DOMAIN_NAME=$DOMAIN_NAME CF_DNS_API_TOKEN: $CF_DNS_API_TOKEN hass: | DOMAIN_NAME=$DOMAIN_NAME HOME_ASSISTANT_MQTT_USER: $HOME_ASSISTANT_MQTT_USER HOME_ASSISTANT_MQTT_PASSWORD: $HOME_ASSISTANT_MQTT_PASSWORD passbolt: | DOMAIN_NAME=$DOMAIN_NAME TZ="Europe/Paris" PASSBOLT_MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE PASSBOLT_MYSQL_USER: $PASSBOLT_MYSQL_USER PASSBOLT_MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD SENDER_EMAIL_ADDRESS: $SENDER_EMAIL_ADDRESS SENDER_EMAIL_ADDRESS_PASSWORD: $SENDER_EMAIL_ADDRESS_PASSWORD SENDER_EMAIL_DOMAIN: $SENDER_EMAIL_DOMAIN SENDER_EMAIL_PORT: $SENDER_EMAIL_PORT EMAIL_ADDRESS: $EMAIL_ADDRESS pihole: | DOMAIN_NAME=$DOMAIN_NAME TZ="Europe/Paris" HOME_ROUTER_SUBNET=$HOME_ROUTER_SUBNET HOME_ROUTER_IP=$HOME_ROUTER_IP HOME_SERVER_IP=$HOME_SERVER_IP FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password""" | sops encrypt --filename-override secrets.yaml \ --input-type yaml --output-type yaml \ --age $SOPS_PUBLIC_KEY \ --output secrets.yaml cd ../ 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 ✅ Copying files to the new installation..." mkdir -p extra-files/etc/nixos/ mkdir -p extra-files/home/numbus-admin/.ssh/ mkdir -p extra-files/var/lib/sops-nix/ mkdir -p extra-files/mnt/config-storage/docker-data/traefik/config/conf mkdir -p extra-files/mnt/data-storage/docker-data/nextcloud mkdir -p extra-files/mnt/data-storage/docker-data/immich mkdir -p extra-files/mnt/config-storage/docker-data/hass/mqtt/config mkdir -p extra-files/mnt/config-storage/docker-data/hass/mqtt/data cp -ravu secrets/ docker/ .sops.yaml configuration.nix disk-config.nix flake.nix hardware-configuration.nix extra-files/etc/nixos/ cp -ravu /home/numbus-admin/.ssh/ extra-files/home/numbus-admin/ cp -ravu /var/lib/sops-nix/key.txt extra-files/var/lib/sops-nix/ echo -e "\n\n ✅ Writing docker configuration files..." cat < extra-files/mnt/config-storage/docker-data/traefik/config/traefik.yaml global: checkNewVersion: false sendAnonymousUsage: false # - level: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL] log: level: ERROR accesslog: {} api: dashboard: true insecure: true entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443 forwardedHeaders: trustedIPs: # Local IPs - "127.0.0.1/32" - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" certificatesResolvers: cloudflare: acme: email: ${EMAIL_ADDRESS} storage: /var/traefik/certs/cloudflare-acme.json caServer: "https://acme-v02.api.letsencrypt.org/directory" dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "9.9.9.9:53" serversTransport: insecureSkipVerify: true providers: docker: exposedByDefault: false network: traefik_frigate, traefik_hass, traefik_nextcloud, traefik_passbolt, traefik_pihole file: directory: "/etc/traefik/conf/" watch: true EOF cat < extra-files/mnt/config-storage/docker-data/traefik/config/conf/nextcloud.yaml http: routers: nextcloud: rule: "Host(\`nextcloud.${DOMAIN_NAME}\`)" entrypoints: - "websecure" service: nextcloud middlewares: - nextcloud-chain tls: certresolver: "cloudflare" services: nextcloud: loadBalancer: servers: - url: "http://nextcloud-aio-apache:11000" middlewares: nextcloud-secure-headers: headers: hostsProxyHeaders: - "X-Forwarded-Host" referrerPolicy: "same-origin" BrowserXssFilter: true ContentTypeNosniff: true ForceSTSHeader: true STSIncludeSubdomains: true STSPreload: true STSSeconds: 315360000 https-redirect: redirectscheme: scheme: https nextcloud-chain: chain: middlewares: - https-redirect - nextcloud-secure-headers EOF cat <<'EOF' > extra-files/mnt/config-storage/docker-data/traefik/config/conf/headers.yaml http: middlewares: passbolt: headers: FrameDeny: true AccessControlAllowMethods: 'GET,OPTIONS,PUT' AccessControlAllowOriginList: - origin-list-or-null AccessControlMaxAge: 100 AddVaryHeader: true BrowserXssFilter: true ContentTypeNosniff: true ForceSTSHeader: true STSIncludeSubdomains: true STSPreload: true ContentSecurityPolicy: default-src 'self' 'unsafe-inline' CustomFrameOptionsValue: SAMEORIGIN ReferrerPolicy: same-origin PermissionsPolicy: vibrate 'self' STSSeconds: 315360000 EOF cat <<'EOF' > extra-files/mnt/config-storage/docker-data/traefik/config/conf/tls.yaml tls: options: default: minVersion: VersionTLS12 sniStrict: true curvePreferences: - CurveP521 - CurveP384 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 EOF cat < extra-files/mnt/config-storage/docker-data/hass/mqtt/config/mosquitto.conf persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log listener 1883 ## Authentication ## allow_anonymous false password_file /mosquitto/config/password.txt EOF touch extra-files/mnt/config-storage/docker-data/hass/mqtt/config/password.txt chmod 0700 extra-files/mnt/config-storage/docker-data/hass/mqtt/config/password.txt nix shell nixpkgs#mosquitto -c mosquitto_passwd -b extra-files/mnt/config-storage/docker-data/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 $TARGET_USER@$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 ➡️ Does the target server has graphics ? (integrated or discrete) :" read -r TARGET_GRAPHICS 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 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 -rp "Enter the full path to the config file: " 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\n ✅ $VAR imported successfully from the config file" sleep 0.1 else echo "\n\n ❌ $VAR is missing or empty" sleep 0.1 MISSING=1 fi done if [[ "$MISSING" == "1" ]]; then exit 1 fi files_generation deploy } trap cleanup EXIT 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…" TARGET_USER="nixos" nixos_deployment elif [[ "$ACTION_ANSWER" == "2" ]]; then echo -e "\n ➡️ Proceeding with deployment using a config file…" TARGET_USER="nixos" nixos_deployment_with_config elif [[ "$ACTION_ANSWER" == "3" ]]; then echo -e "\n ➡️ Proceeding with update…" TARGET_USER="numbus-admin" nixos_deployment_with_config else echo "Aborting – you did not type '1, 2 or 3'." exit 1 fi