From db8e83447a7f3a1432f20283cab16487ceca4954 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 17:43:35 +0000 Subject: [PATCH] Add working Authelia SSO install based on authelia-setup repo Ports the full working configuration from outis1one/authelia-setup: - Authelia 4.39.20 on caddy_net with secrets via env var files - Generates jwt/session/storage secrets with openssl at install time - Prompts for domain, admin user, SMTP settings, timezone - Generates argon2id password hash via Docker during install - Writes configuration.yml, users.yml, docker-compose.yml, .env - Auto-injects (authelia) snippet + auth portal block into Caddyfile - Creates caddy_net Docker network if missing - Adds Authelia to whiptail service menu, uninstall list, and detection - Adds Authelia snippet as commented example in new Caddyfile template https://claude.ai/code/session_01FvqXSZyk3g7rUombwLcprZ --- ubuntu-post-install.sh | 323 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) diff --git a/ubuntu-post-install.sh b/ubuntu-post-install.sh index f669de2..5fe75cb 100644 --- a/ubuntu-post-install.sh +++ b/ubuntu-post-install.sh @@ -2565,6 +2565,7 @@ else [ -d "$DOCKER_DIR/magicmirror" ] && EXISTING_SERVICES[MAGICMIRROR]="ON" [ -d "$DOCKER_DIR/actualbudget" ] && EXISTING_SERVICES[ACTUALBUDGET]="ON" [ -d "$DOCKER_DIR/keycloak" ] && EXISTING_SERVICES[KEYCLOAK]="ON" + [ -d "$DOCKER_DIR/authelia" ] && EXISTING_SERVICES[AUTHELIA]="ON" [ -d "$DOCKER_DIR/caddy" ] && EXISTING_SERVICES[CADDY]="ON" [ -d "$DOCKER_DIR/lms" ] && EXISTING_SERVICES[LYRION]="ON" [ -d "$DOCKER_DIR/mealie" ] && EXISTING_SERVICES[MEALIE]="ON" @@ -2605,6 +2606,7 @@ else "MAGICMIRROR" "Smart mirror / dashboard display" ${EXISTING_SERVICES[MAGICMIRROR]:-OFF} \ "ACTUALBUDGET" "Personal finance management with bank sync" ${EXISTING_SERVICES[ACTUALBUDGET]:-OFF} \ "KEYCLOAK" "Identity & Access Management (SSO)" ${EXISTING_SERVICES[KEYCLOAK]:-OFF} \ + "AUTHELIA" "SSO + 2FA auth portal for Caddy" ${EXISTING_SERVICES[AUTHELIA]:-OFF} \ "CADDY" "Reverse proxy with automatic HTTPS" ${EXISTING_SERVICES[CADDY]:-OFF} \ "FAIL2BAN" "Intrusion prevention system" ${EXISTING_SERVICES[FAIL2BAN]:-OFF} \ "LYRION" "Music streaming server (LMS)" ${EXISTING_SERVICES[LYRION]:-OFF} \ @@ -2636,6 +2638,7 @@ else [ -n "${EXISTING_SERVICES[MAGICMIRROR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MAGICMIRROR \"Smart mirror\" ON" [ -n "${EXISTING_SERVICES[ACTUALBUDGET]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ACTUALBUDGET \"Personal finance\" ON" [ -n "${EXISTING_SERVICES[KEYCLOAK]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS KEYCLOAK \"Identity management\" ON" + [ -n "${EXISTING_SERVICES[AUTHELIA]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS AUTHELIA \"SSO + 2FA auth portal\" ON" [ -n "${EXISTING_SERVICES[CADDY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS CADDY \"Reverse proxy\" ON" [ -n "${EXISTING_SERVICES[FAIL2BAN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FAIL2BAN \"Intrusion prevention\" ON" [ -n "${EXISTING_SERVICES[LYRION]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS LYRION \"Music server\" ON" @@ -2686,6 +2689,7 @@ else : ${INSTALL_MAGICMIRROR:="n"} : ${INSTALL_ACTUALBUDGET:="n"} : ${INSTALL_KEYCLOAK:="n"} + : ${INSTALL_AUTHELIA:="n"} : ${INSTALL_CADDY:="n"} : ${INSTALL_FAIL2BAN:="n"} : ${INSTALL_LMS:="n"} @@ -2712,6 +2716,7 @@ else if echo "$SELECTED_SERVICES" | grep -q "MAGICMIRROR"; then INSTALL_MAGICMIRROR="y"; fi if echo "$SELECTED_SERVICES" | grep -q "ACTUALBUDGET"; then INSTALL_ACTUALBUDGET="y"; fi if echo "$SELECTED_SERVICES" | grep -q "KEYCLOAK"; then INSTALL_KEYCLOAK="y"; fi + if echo "$SELECTED_SERVICES" | grep -q "AUTHELIA"; then INSTALL_AUTHELIA="y"; fi if echo "$SELECTED_SERVICES" | grep -q "CADDY"; then INSTALL_CADDY="y"; fi if echo "$SELECTED_SERVICES" | grep -q "FAIL2BAN"; then INSTALL_FAIL2BAN="y"; fi if echo "$SELECTED_SERVICES" | grep -q "LYRION"; then INSTALL_LMS="y"; fi @@ -2785,6 +2790,7 @@ else if echo "$SELECTED_SERVICES" | grep -q "MAGICMIRROR"; then uninstall_service "MagicMirror" "$DOCKER_DIR/magicmirror" "magicmirror"; fi if echo "$SELECTED_SERVICES" | grep -q "ACTUALBUDGET"; then uninstall_service "ActualBudget" "$DOCKER_DIR/actualbudget" "actualbudget"; fi if echo "$SELECTED_SERVICES" | grep -q "KEYCLOAK"; then uninstall_service "Keycloak" "$DOCKER_DIR/keycloak" "keycloak"; fi + if echo "$SELECTED_SERVICES" | grep -q "AUTHELIA"; then uninstall_service "Authelia" "$DOCKER_DIR/authelia" "authelia"; fi if echo "$SELECTED_SERVICES" | grep -q "CADDY"; then uninstall_service "Caddy" "$DOCKER_DIR/caddy" "caddy"; fi if echo "$SELECTED_SERVICES" | grep -q "LYRION"; then uninstall_service "Lyrion" "$DOCKER_DIR/lms" "lms"; fi if echo "$SELECTED_SERVICES" | grep -q "MEALIE"; then uninstall_service "Mealie" "$DOCKER_DIR/mealie" "mealie"; fi @@ -4822,6 +4828,303 @@ EOF fi fi + # ---- AUTHELIA ---- + if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_AUTHELIA" ]; then + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ AUTHELIA - SSO & Two-Factor Authentication Portal │" + echo "│ Protects any Caddy subdomain with a single login + TOTP │" + echo "│ Port: 9091 (internal only, accessed via Caddy) │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Authelia? (y/n):" "n" INSTALL_AUTHELIA + fi + + if [ "$INSTALL_AUTHELIA" = "y" ] || [ "$INSTALL_AUTHELIA" = "Y" ]; then + AUTHELIA_DIR="$DOCKER_DIR/authelia" + check_service_exists "Authelia" "$AUTHELIA_DIR" AUTHELIA_RECONFIGURE + + if [ "$AUTHELIA_RECONFIGURE" = "true" ]; then + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $AUTHELIA_DIR" + else + echo "Installing Authelia..." + mkdir -p "$AUTHELIA_DIR/config/secrets" "$AUTHELIA_DIR/data" + + # Collect configuration from user + echo "" + echo " Authelia needs a few details to configure." + echo "" + prompt_text " Your domain (e.g., example.com):" "example.com" AUTHELIA_DOMAIN + prompt_text " Admin username:" "admin" AUTHELIA_ADMIN_USER + prompt_text " Admin display name:" "Administrator" AUTHELIA_ADMIN_DISPLAY + prompt_text " Admin email:" "admin@${AUTHELIA_DOMAIN}" AUTHELIA_ADMIN_EMAIL + prompt_text " SMTP server (e.g., smtp.migadu.com):" "smtp.migadu.com" AUTHELIA_SMTP_HOST + prompt_text " SMTP port:" "587" AUTHELIA_SMTP_PORT + prompt_text " SMTP username (full email):" "authelia@${AUTHELIA_DOMAIN}" AUTHELIA_SMTP_USER + prompt_text " SMTP password:" "" AUTHELIA_SMTP_PASS + prompt_text " Timezone (e.g., America/New_York):" "America/New_York" AUTHELIA_TZ + + # Generate secrets + echo "" + echo " Generating secrets..." + JWT_SECRET=$(openssl rand -hex 32) + SESSION_SECRET=$(openssl rand -hex 32) + STORAGE_SECRET=$(openssl rand -hex 32) + + echo "$JWT_SECRET" > "$AUTHELIA_DIR/config/secrets/jwt_secret" + echo "$SESSION_SECRET" > "$AUTHELIA_DIR/config/secrets/session_secret" + echo "$STORAGE_SECRET" > "$AUTHELIA_DIR/config/secrets/storage_secret" + echo "$AUTHELIA_SMTP_PASS" > "$AUTHELIA_DIR/config/secrets/smtp_password" + chmod 600 "$AUTHELIA_DIR/config/secrets/"* + echo " ✓ Secrets generated" + + # Generate password hash for admin user + echo "" + echo " Generating password hash for admin user..." + prompt_text " Temporary password for admin (users reset via email):" "TempPass2026!" AUTHELIA_TEMP_PASS + AUTHELIA_HASH=$(docker run --rm authelia/authelia:4.39.20 \ + authelia crypto hash generate argon2 --password "$AUTHELIA_TEMP_PASS" 2>/dev/null \ + | grep -oP '(?<=Digest: ).*' || echo "REPLACE_WITH_HASH") + if [ "$AUTHELIA_HASH" = "REPLACE_WITH_HASH" ]; then + echo " ⚠ Could not generate hash automatically. Run this after install:" + echo " docker run --rm authelia/authelia:4.39.20 authelia crypto hash generate argon2 --password 'yourpassword'" + echo " Then update $AUTHELIA_DIR/config/users.yml" + else + echo " ✓ Password hash generated" + fi + + ensure_docker_dir_ownership "$AUTHELIA_DIR" + # Authelia requires its config/data owned by uid 1000 + chown -R 1000:1000 "$AUTHELIA_DIR/config" "$AUTHELIA_DIR/data" + + cd "$AUTHELIA_DIR" + + # .env file + cat > .env << AUTHELIA_ENV +MY_DOMAIN=${AUTHELIA_DOMAIN} +SMTP_USER=${AUTHELIA_SMTP_USER} +DOCKER_MY_NETWORK=caddy_net +TZ=${AUTHELIA_TZ} +AUTHELIA_ENV + + # docker-compose.yml + cat > docker-compose.yml << 'AUTHELIA_COMPOSE' +name: authelia + +services: + authelia: + image: authelia/authelia:4.39.20 + pull_policy: missing + container_name: authelia + user: "1000:1000" + volumes: + - ./config:/config + - ./data:/data + environment: + - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/config/secrets/jwt_secret + - AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session_secret + - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage_secret + - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/config/secrets/smtp_password + - AUTHELIA_NOTIFIER_SMTP_USERNAME=${SMTP_USER} + - AUTHELIA_NOTIFIER_SMTP_SENDER=Authelia <${SMTP_USER}> + expose: + - 9091 + restart: unless-stopped + networks: + - caddy_net + +networks: + caddy_net: + external: true +AUTHELIA_COMPOSE + + # configuration.yml + cat > config/configuration.yml << AUTHELIA_CONFIG +--- +# Authelia configuration +# Secrets are injected via AUTHELIA_* environment variables in docker-compose.yml + +theme: dark + +server: + address: tcp://0.0.0.0:9091 + +log: + level: info + file_path: /data/authelia.log + +totp: + period: 30 + skew: 1 + +authentication_backend: + file: + path: /config/users.yml + password: + algorithm: argon2id + iterations: 3 + memory: 65536 + parallelism: 4 + key_length: 32 + salt_length: 16 + +access_control: + default_policy: deny + rules: + - domain: "*.${AUTHELIA_DOMAIN}" + policy: two_factor + +session: + name: authelia_session + expiration: 12h + inactivity: 2h + remember_me: 7d + cookies: + - domain: ${AUTHELIA_DOMAIN} + authelia_url: https://auth.${AUTHELIA_DOMAIN} + default_redirection_url: https://${AUTHELIA_DOMAIN} + +storage: + local: + path: /data/db.sqlite3 + +notifier: + disable_startup_check: false + smtp: + address: smtp://${AUTHELIA_SMTP_HOST}:${AUTHELIA_SMTP_PORT} + timeout: 10s + identifier: localhost + subject: "[Authelia] {title}" + startup_check_address: ${AUTHELIA_SMTP_USER} + disable_require_tls: false + disable_starttls: false +AUTHELIA_CONFIG + + # users.yml + cat > config/users.yml << AUTHELIA_USERS +--- +# Authelia users database +# To add users: copy a block, update username/email/displayname, restart authelia +# To generate a hash: docker run --rm authelia/authelia:4.39.20 authelia crypto hash generate argon2 --password 'thepassword' +# Login with username (not email). Tell users to use "Forgot Password" to set their own password. + +users: + ${AUTHELIA_ADMIN_USER}: + displayname: "${AUTHELIA_ADMIN_DISPLAY}" + email: ${AUTHELIA_ADMIN_EMAIL} + password: "${AUTHELIA_HASH}" + groups: + - admins + - users +AUTHELIA_USERS + + chown -R 1000:1000 "$AUTHELIA_DIR/config" "$AUTHELIA_DIR/data" + + echo " ✓ Authelia configured at $AUTHELIA_DIR" + echo "" + + # Ensure caddy_net Docker network exists + if ! docker network ls --format '{{.Name}}' | grep -q "^caddy_net$"; then + echo " Creating Docker network caddy_net..." + docker network create caddy_net && echo " ✓ caddy_net created" || echo " ⚠ Failed to create caddy_net" + else + echo " ✓ Docker network caddy_net already exists" + fi + + # Inject Authelia snippet into Caddyfile if Caddy is installed + CADDY_FILE="$DOCKER_DIR/caddy/Caddyfile" + if [ -f "$CADDY_FILE" ]; then + echo "" + echo " Configuring Caddy for Authelia..." + + # Add (authelia) snippet at top if not already present + if ! grep -q "(authelia)" "$CADDY_FILE"; then + cp "$CADDY_FILE" "$CADDY_FILE.backup.$(date +%Y%m%d-%H%M%S)" + AUTHELIA_SNIPPET=$(cat << 'SNIPPET_EOF' +# ── Authelia forward auth snippet ───────────────────────────────────────────── +(authelia) { + forward_auth authelia:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Name Remote-Email + } +} + +SNIPPET_EOF +) + # Prepend snippet before existing content + echo "$AUTHELIA_SNIPPET" | cat - "$CADDY_FILE" > "$CADDY_FILE.tmp" && mv "$CADDY_FILE.tmp" "$CADDY_FILE" + echo " ✓ Authelia snippet added to Caddyfile" + else + echo " ✓ Authelia snippet already in Caddyfile" + fi + + # Add auth portal site block if not already present + AUTH_DOMAIN="auth.${AUTHELIA_DOMAIN}" + if ! grep -q "$AUTH_DOMAIN" "$CADDY_FILE"; then + cat >> "$CADDY_FILE" << CADDY_AUTH_BLOCK + +# ── Authelia login portal ────────────────────────────────────────────────────── +${AUTH_DOMAIN} { + reverse_proxy authelia:9091 + log { + output file /var/log/caddy/auth.log + } +} +CADDY_AUTH_BLOCK + echo " ✓ Authelia portal block added for ${AUTH_DOMAIN}" + fi + + # Reload Caddy if it's running + if docker ps --format '{{.Names}}' | grep -q "^caddy$"; then + docker exec -w /etc/caddy caddy caddy reload 2>/dev/null \ + && echo " ✓ Caddy reloaded" \ + || echo " ⚠ Caddy reload failed — reload manually after fixing Caddyfile" + fi + else + echo " ℹ Caddy not yet installed. Install Caddy and add this to your Caddyfile:" + echo "" + echo ' (authelia) {' + echo ' forward_auth authelia:9091 {' + echo ' uri /api/authz/forward-auth' + echo ' copy_headers Remote-User Remote-Groups Remote-Name Remote-Email' + echo ' }' + echo ' }' + echo "" + echo " auth.${AUTHELIA_DOMAIN} {" + echo " reverse_proxy authelia:9091" + echo " }" + echo "" + fi + + prompt_yn "Start Authelia now? (y/n):" "y" START_AUTHELIA + if [ "$START_AUTHELIA" = "y" ] || [ "$START_AUTHELIA" = "Y" ]; then + docker compose up -d 2>/dev/null && echo " ✓ Authelia started" || echo " ⚠ Failed to start Authelia" + sleep 2 + docker compose logs --tail=20 authelia 2>/dev/null || true + fi + + echo "" + echo " Auth portal: https://auth.${AUTHELIA_DOMAIN}" + echo " Config dir: $AUTHELIA_DIR/config" + echo " Users file: $AUTHELIA_DIR/config/users.yml" + echo " Data dir: $AUTHELIA_DIR/data" + echo "" + echo " Admin login: ${AUTHELIA_ADMIN_USER} (use Forgot Password to set real password)" + echo "" + echo " To protect a Caddy site, add 'import authelia' to its block:" + echo " myservice.${AUTHELIA_DOMAIN} {" + echo " import authelia" + echo " reverse_proxy localhost:PORT" + echo " }" + echo "" + echo " To add users:" + echo " Edit $AUTHELIA_DIR/config/users.yml then:" + echo " cd $AUTHELIA_DIR && docker compose restart authelia" + echo "" + fi + fi # End AUTHELIA_RECONFIGURE check + fi # End INSTALL_AUTHELIA check + # ---- CADDY WEB SERVER ---- if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_CADDY" ]; then echo "" @@ -4901,6 +5204,26 @@ CADDY_COMPOSE # Example configuration - edit this for your services # Uncomment and modify these examples: +# ── Authelia SSO snippet (auto-added by installer if Authelia is installed) ─── +# (authelia) { +# forward_auth authelia:9091 { +# uri /api/authz/forward-auth +# copy_headers Remote-User Remote-Groups Remote-Name Remote-Email +# } +# } +# +# Authelia login portal +# auth.yourdomain.com { +# reverse_proxy authelia:9091 +# } +# +# To protect any service with Authelia, add: import authelia +# Example: +# myservice.yourdomain.com { +# import authelia +# reverse_proxy localhost:PORT +# } + # ActualBudget # budget.yourdomain.com { # log {