417 lines
16 KiB
Bash
417 lines
16 KiB
Bash
#!/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 |