Files
numbus-server/deploy.sh
T
2025-11-14 19:36:36 +01:00

417 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.
#!/bin/bash
set -euo pipefail
cat <<EOF
██████ █████ ███ ███████ █████████
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
█████████ █████
███░░░░░███ ░░███
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
███ ░███
░░██████
░░░░░░
EOF
sleep 1
cleanup() {
echo -e "\n 🏗️ Cleaning up before exit..."
rm -rf /home/numbus-admin/.ssh/id_ed25519 /home/numbus-admin/.ssh/id_ed25519.pub
rm -rf /etc/nixos/*
rm -rf /var/lib/sops-nix/
echo -e "\n ✅ Cleanup done."
}
files_generation() {
echo -e "\n\n ✅ Generating new SSH for numbus-admin..."
mkdir -p /home/numbus-admin/.ssh/
ssh-keygen -t ed25519 -C numbus-admin@numbus-server -f /home/numbus-admin/.ssh/id_ed25519 -N "" -q
echo -e "\n\n ✅ Generating sops-nix keys..."
mkdir -p /var/lib/sops-nix/
age-keygen -o /var/lib/sops-nix/key.txt
SOPS_PUBLIC_KEY=$(age-keygen -y /var/lib/sops-nix/key.txt)
echo -e "\n\n ✅ Generating sops-nix configuration files..."
echo """# .sops.yaml
keys:
- &primary $SOPS_PUBLIC_KEY
creation_rules:
- path_regex: secrets/secrets.yaml$
key_groups:
- age:
- *primary""" > .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 <<EOF > 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 <<EOF > 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 <<EOF > 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