#!/usr/bin/env bash set -euf -o pipefail # Ask if initialized for production or test while true; do read -p "Do you want to init a [P]roduction or [T]est environment? (P/T): " DEPLOYMENT_ENVIRONMENT case "$DEPLOYMENT_ENVIRONMENT" in [Pp]* ) DEPLOYMENT_ENVIRONMENT="PRODUCTION" ENV_TEMPLATE="env.prod.template" break ;; [Tt]* ) DEPLOYMENT_ENVIRONMENT="TEST" ENV_TEMPLATE="env.test.template" break ;; * ) echo "Please answer with P or T." ;; esac done source $(dirname "$(readlink -f "$0")")/../${ENV_TEMPLATE} SERVICE_DOMAIN="${RESTIC_TAG:?Restic backup tag is missing -- RESTIC_TAG}" BACKUP_TARGET_DOMAIN="${RESTIC_REPO_ADDRESS:?Restic backup target domain is missing -- RESTIC_REPO_ADDRESS}" BACKUP_TARGET_USER="${RESTIC_REPO_USER:?Restic backup target user is missing -- RESTIC_REPO_USER}" HOSTNAME=$(hostname -f) # Function to securely query user for a password, verify it, and return it for further use prompt_password() { local purpose="$1" local password password_confirm while true; do printf "Enter password for %s: " "$purpose" read -rs password printf "\nConfirm password for %s: " "$purpose" read -rs password_confirm printf "\n" # Check if passwords match if [[ "$password" == "$password_confirm" ]]; then RETURNED_PASSWORD="$password" printf "Password verified for %s.\n" "$purpose" return 0 else printf "Error: Passwords do not match. Please try again.\n" >&2 fi done } # Trap SIGINT to exit gracefully if the user aborts with CTRL+C trap 'printf "\nOperation aborted by user.\n" >&2; exit 1' SIGINT cd "$(dirname "$(realpath "$0")")/../" AUTHENTIK_DOCKER_COMPOSE_PATH="$(realpath "$(pwd)")" if [[ ! -f ./docker-compose.yml ]]; then [[ "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]] && ln -s ./docker-compose.prod.yml ./docker-compose.yml [[ "${DEPLOYMENT_ENVIRONMENT}" == "TEST" ]] && ln -s ./docker-compose.test.yml ./docker-compose.yml fi # Check if .env exists and exit if it is if [[ ! -f ./.env ]]; then cat ./${ENV_TEMPLATE} >> ./.env echo "# SECRETS" >> ./.env prompt_password "PG_PASS (leave empty to generate a password)"; echo "PG_PASS=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 36 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD prompt_password "AUTHENTIK_SECRET_KEY (leave empty to generate a password)"; echo "AUTHENTIK_SECRET_KEY=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 60 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD prompt_password "AUTHENTIK_EMAIL__PASSWORD"; echo "AUTHENTIK_EMAIL__PASSWORD=${RETURNED_PASSWORD}" >> ./.env; unset RETURNED_PASSWORD prompt_password "GEOIPUPDATE_LICENSE_KEY"; echo "GEOIPUPDATE_LICENSE_KEY=${RETURNED_PASSWORD}" >> ./.env; unset RETURNED_PASSWORD prompt_password "RESTIC_REPO_PASSWORD (leave empty to generate a password)"; echo "RESTIC_REPO_PASSWORD=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 60 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD echo "" >> ./.env fi # Check if lego.env exists and exit if it is if [[ ! -f ./lego.env && "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]]; then echo "# Lego - Let's Encrypt certificate tool" >> ./lego.env prompt_password HETZNER_API_KEY; echo "HETZNER_API_KEY=${RETURNED_PASSWORD}" >> ./lego.env; unset RETURNED_PASSWORD echo "" >> ./.env fi BACKUP_TARGET_KEY_TYPES="ed25519,rsa" BACKUP_TARGET_IPV4=$(dig +short "${BACKUP_TARGET_DOMAIN}" A | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') BACKUP_TARGET_IPV6=$(dig +short "${BACKUP_TARGET_DOMAIN}" AAAA | grep -E '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$') # Check if ssh key already exists, otherwise generate one [[ ! -d ./data/restic/ssh/ ]] && mkdir -p ./data/restic/ssh/ && chmod 700 ./data/restic/ssh/ if [[ ! -f ./data/restic/ssh/id_ed25519 ]]; then ssh-keygen -t ed25519 -C "${SERVICE_DOMAIN}" -f ./data/restic/ssh/id_ed25519 && chmod 600 ./data/restic/ssh/id_ed25519 # Copy SSH key to backup target cat ./data/restic/ssh/id_ed25519.pub | ssh -p23 ${BACKUP_TARGET_USER}@${BACKUP_TARGET_DOMAIN} install-ssh-key fi # Setup known_hosts for backup container if [[ ! -f ./data/restic/ssh/known_hosts ]]; then ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_DOMAIN} > ./data/restic/ssh/known_hosts ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV4} >> ./data/restic/ssh/known_hosts ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV6} >> ./data/restic/ssh/known_hosts ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_DOMAIN} >> ./data/restic/ssh/known_hosts ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV4} >> ./data/restic/ssh/known_hosts ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV6} >> ./data/restic/ssh/known_hosts chmod 600 ./data/restic/ssh/known_hosts fi if [[ "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]]; then # Generate dhparam, if not existing [[ ! -d ./data/nginx/certs ]] && mkdir -p ./data/nginx/certs && chmod 700 ./data/nginx/certs && chown 101:101 ./data/nginx/certs || true [[ ! -f ./data/nginx/dhparams.pem ]] && echo "" && openssl dhparam -out ./data/nginx/dhparams.pem 4096 && chown 101:101 ./data/nginx/dhparams.pem \ && echo "" && echo "Checking generated dhparams" && openssl dhparam -check -in ./data/nginx/dhparams.pem || true # Create certificate if [[ ! -d ./data/.lego ]]; then echo "" echo "Create certificate" lego \ --path ./data/.lego \ --accept-tos \ --email="acme@base23.de" \ --domains="*.base23.de" \ --dns hetzner \ run \ && install -m 400 -o 101 -g 101 "./data/.lego/certificates"/{_.base23.de.crt,_.base23.de.issuer.crt,_.base23.de.key} "./data/nginx/certs" fi # Setup directory for acme cheallenges [[ ! -d ./data/nginx/acme ]] && mkdir -p ./data/nginx/acme # Setup cronjob to automatically renew certificates [[ ! -f /etc/systemd/system/lego-renew-wildcard-base23-de.service ]] && cat < /etc/systemd/system/lego-renew-wildcard-base23-de.service && systemctl daemon-reload [Unit] Description=SSL Certificate renewal for *.base23.de with LEGO Documentation=https://go-acme.github.io/lego/ Wants=network-online.target After=network-online.target [Service] Type=oneshot EnvironmentFile=${AUTHENTIK_DOCKER_COMPOSE_PATH}/lego.env ExecStart=${AUTHENTIK_DOCKER_COMPOSE_PATH}/scripts/cert_renew.sh WorkingDirectory=${AUTHENTIK_DOCKER_COMPOSE_PATH}/ User=root Group=root RemainAfterExit=no [Install] WantedBy=multi-user.target EOF [[ ! -f /etc/systemd/system/lego-renew-wildcard-base23-de.timer ]] && cat < /etc/systemd/system/lego-renew-wildcard-base23-de.timer && systemctl daemon-reload && systemctl enable --now lego-renew-wildcard-base23-de.timer [Unit] Description=SSL Certificate renewal for *.base23.de with LEGO Timer [Timer] OnCalendar=*-*-* 01:32:00 # add extra delay, here up to 1 hour: RandomizedDelaySec=1h Persistent=true [Install] WantedBy=timers.target EOF fi