From f564b4b6d875a0df39ab2d386327bb67e47bbfcc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 3 Jun 2026 03:44:26 +0000 Subject: [PATCH] Remove Keycloak; standardize on Authelia for SSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keycloak never reliably ran (fiddly reverse-proxy/hostname config) and the repo has standardized on Authelia for SSO + 2FA. This rips Keycloak out entirely: - Delete the install block, whiptail menu entry, uninstall plumbing, and EXISTING_SERVICES detection from both 24.04 and 26.04 scripts - Delete docker-compose-keycloak.yml, fix-keycloak-proxy.sh, and KEYCLOAK-SETUP-GUIDE.md - Remove the Keycloak block from caddy-setup-helper.sh - Update docs (CADDY-FAIL2BAN-SETUP.md, SECURITY-IMPROVEMENTS.md, NEW-SCRIPT-STRUCTURE.md, SCRIPT-FLOW-INTRO.txt) to reference Authelia Also documents the fail2ban/Authelia overlap: Authelia handles failed-login regulation (per-account lockout); the Caddy fail2ban jail is complementary firewall-level IP banning. Neither does geo-blocking — noted CrowdSec / Caddy GeoIP as the path for that. https://claude.ai/code/session_017eA2qqq9jfF2tNtpUYL8vK --- CADDY-FAIL2BAN-SETUP.md | 22 +- KEYCLOAK-SETUP-GUIDE.md | 678 ----------------------------------- NEW-SCRIPT-STRUCTURE.md | 4 +- SCRIPT-FLOW-INTRO.txt | 2 +- SECURITY-IMPROVEMENTS.md | 163 ++------- caddy-setup-helper.sh | 36 -- docker-compose-keycloak.yml | 137 ------- fix-keycloak-proxy.sh | 184 ---------- ubuntu-post-install-24.04.sh | 646 +-------------------------------- ubuntu-post-install-26.04.sh | 646 +-------------------------------- 10 files changed, 45 insertions(+), 2473 deletions(-) delete mode 100644 KEYCLOAK-SETUP-GUIDE.md delete mode 100644 docker-compose-keycloak.yml delete mode 100755 fix-keycloak-proxy.sh diff --git a/CADDY-FAIL2BAN-SETUP.md b/CADDY-FAIL2BAN-SETUP.md index b179e0f..12b0cb8 100644 --- a/CADDY-FAIL2BAN-SETUP.md +++ b/CADDY-FAIL2BAN-SETUP.md @@ -61,17 +61,17 @@ budget.yourdomain.com { } ``` -#### Keycloak (Identity & Access Management) +#### Authelia (SSO + 2FA auth portal) ```caddy auth.yourdomain.com { log { - output file /var/log/caddy/keycloak-access.log + output file /var/log/caddy/authelia-access.log format json level INFO } - reverse_proxy localhost:8180 + reverse_proxy localhost:9091 # Security headers header { @@ -315,15 +315,23 @@ logpath = /var/log/caddy/actualbudget-access.log maxretry = 3 bantime = 7200 -[caddy-keycloak] +[caddy-authelia] enabled = true port = http,https filter = caddy-auth -logpath = /var/log/caddy/keycloak-access.log +logpath = /var/log/caddy/authelia-access.log maxretry = 5 bantime = 3600 ``` +> **Note:** Authelia already performs its own failed-login *regulation* +> (per-account lockout after repeated failures). This jail is complementary +> defense-in-depth that bans the offending IP at the firewall level, and also +> covers services that don't sit behind Authelia. Neither Authelia nor +> fail2ban provides **geo-blocking** — for country-level blocking or IP +> reputation feeds, consider [CrowdSec](https://www.crowdsec.net/) (a modern +> fail2ban alternative with a Caddy bouncer) or a Caddy GeoIP module. + ## Best Practices 1. **Always backup before changes** @@ -359,7 +367,7 @@ cp ~/docker/caddy/Caddyfile ~/docker/caddy/Caddyfile.backup ### Service Ports - **ActualBudget**: 5006 -- **Keycloak**: 8180 +- **Authelia**: 9091 - **Caddy**: 80 (HTTP), 443 (HTTPS) ## Support @@ -368,4 +376,4 @@ For issues: - Caddy documentation: https://caddyserver.com/docs/ - Fail2ban manual: https://www.fail2ban.org/wiki/index.php/MANUAL_0_8 - ActualBudget docs: https://actualbudget.org/docs/ -- Keycloak docs: https://www.keycloak.org/documentation +- Authelia docs: https://www.authelia.com/ diff --git a/KEYCLOAK-SETUP-GUIDE.md b/KEYCLOAK-SETUP-GUIDE.md deleted file mode 100644 index 88bef08..0000000 --- a/KEYCLOAK-SETUP-GUIDE.md +++ /dev/null @@ -1,678 +0,0 @@ -# Keycloak Setup Guide -## Complete Manual and Automated Configuration Guide - -This guide explains Keycloak concepts and how to configure it both automatically (via the script) and manually (via the web UI). - ---- - -## Table of Contents -1. [What is Keycloak?](#what-is-keycloak) -2. [Key Concepts](#key-concepts) -3. [Automated Setup (via Script)](#automated-setup) -4. [Manual Setup (via Web UI)](#manual-setup) -5. [Configuring External Services](#configuring-external-services) -6. [Reconfiguration & Adding Realms](#reconfiguration) -7. [Common Use Cases](#common-use-cases) -8. [Troubleshooting](#troubleshooting) - ---- - -## What is Keycloak? - -Keycloak is an **Identity and Access Management (IAM)** system that provides: -- **Single Sign-On (SSO)**: Log in once, access all your services -- **User Management**: Create, manage, and authenticate users in one place -- **OAuth2/OIDC**: Industry-standard authentication for web apps -- **Social Login**: Allow login via Google, GitHub, etc. -- **Multi-Factor Authentication (MFA)**: Add extra security with 2FA/TOTP -- **LDAP/Active Directory Integration**: Connect to existing user directories - -**Think of Keycloak as:** A centralized login system for all your self-hosted services. - ---- - -## Key Concepts - -### 1. **Realm** -A **realm** is an isolated container for users, clients, and configuration. - -**Analogy:** Think of a realm like a "company" or "organization" in Keycloak. - -**Why you need it:** -- The default `master` realm is for Keycloak admin only -- You create a separate realm (e.g., `homelab`) for your actual users and applications -- Realms are completely isolated - users in one realm can't access another - -**Example:** -- `master` realm: Only for Keycloak administrators -- `homelab` realm: For your personal services (ActualBudget, Jellyfin, etc.) -- `family` realm: Separate realm for family members (optional) - -### 2. **OAuth2/OpenID Connect (OIDC) Client** -A **client** is an application that uses Keycloak for authentication. - -**Analogy:** Each service (ActualBudget, Jellyfin, etc.) is a "client" that asks Keycloak "Is this user allowed to log in?" - -**Required information:** -- **Client ID**: Name of the application (e.g., `actualbudget`) -- **Client Secret**: Password for the application (auto-generated, 64-char hex) -- **Redirect URIs**: Where Keycloak sends users after login - - Example: `https://budget.yourdomain.com/*` - - Must match EXACTLY or login will fail - -**Flow:** -1. User clicks "Login" in ActualBudget -2. ActualBudget redirects to Keycloak: `https://auth.yourdomain.com/login` -3. User logs in with username/password -4. Keycloak redirects back to ActualBudget: `https://budget.yourdomain.com/callback` -5. ActualBudget gets user info and logs them in - -### 3. **Users** -A **user** is a person who can log in to your services. - -**User attributes:** -- Username (required, unique) -- Email (optional but recommended) -- First name / Last name (optional) -- Password (set via Credentials tab) -- Email verified (set to true to skip verification) -- Enabled (must be true for user to log in) - -### 4. **Redirect URIs** -**Critical concept:** The redirect URI is where Keycloak sends the user after successful login. - -**Common mistakes:** -- ❌ `http://localhost:5006` (won't work for external services) -- ❌ `https://budget.example.com` (missing wildcard or path) -- ✅ `https://budget.example.com/*` (correct - allows all paths) - -**For external services (like Pikapod):** -- Pikapod gives you a URL like: `https://actualbudget-abc123.pikapod.net` -- Your redirect URI: `https://actualbudget-abc123.pikapod.net/*` -- Your Keycloak URL: `https://auth.yourdomain.com` (must be publicly accessible) - ---- - -## Automated Setup (via Script) - -The script automates everything for you. Here's what it does: - -### Step 1: Install Keycloak -```bash -./ubuntu-post-install.sh -# Select KEYCLOAK in whiptail menu -``` - -Prompts: -- Admin password (for Keycloak admin console) -- Database password (for PostgreSQL) - -### Step 2: Automated Configuration -``` -Configure Keycloak with initial realm and clients? (y/n): y -``` - -This automatically: -1. ✅ Waits for Keycloak to start (health check) -2. ✅ Logs in using admin CLI (`kcadm.sh`) -3. ✅ Creates a realm (e.g., `homelab`) -4. ✅ Creates OAuth client for ActualBudget (if selected) -5. ✅ Creates generic OAuth client template -6. ✅ Saves all credentials to `~/docker/keycloak/*.txt` -7. ✅ Optionally creates initial user - -### Step 3: What Gets Created - -**Realm:** `homelab` (or your custom name) - -**ActualBudget OAuth Client:** -- Client ID: `actualbudget` -- Client Secret: (saved to `actualbudget-oauth.txt`) -- Redirect URIs: - - `http://localhost:5006/*` (local development) - - `http://yourdomain.com:5006/*` (local with domain) - - `https://yourdomain.com/*` (production - any subdomain) - - `https://budget.yourdomain.com/*` (specific subdomain) - -**Generic OAuth Client:** -- Client ID: `generic-app` -- Client Secret: (saved to `generic-oauth.txt`) -- Can be cloned for other services - -**Initial User:** -- Username, email, password you provide -- Immediately active -- Can log in to all services - -### Step 4: Configuration Files - -All credentials saved to: -``` -~/docker/keycloak/actualbudget-oauth.txt -~/docker/keycloak/generic-oauth.txt -``` - -These files contain: -- Client ID -- Client Secret -- Authorization URL -- Token URL -- User Info URL -- Instructions for configuring each service - ---- - -## Manual Setup (via Web UI) - -If you prefer to configure Keycloak manually, or want to add services later: - -### Access Admin Console -``` -URL: http://localhost:8180/admin -Username: admin -Password: [your admin password] -``` - -### Step 1: Create a Realm - -1. **Click dropdown** in top-left corner (shows "Master") -2. **Click "Create Realm"** -3. **Realm name:** `homelab` (or your choice) -4. **Click "Create"** - -**Settings to configure:** -- **Login tab:** - - ✅ User registration: OFF (you create users manually) - - ✅ Forgot password: ON (allows password resets) - - ✅ Remember me: ON (convenience) - - ✅ Login with email: ON (users can use email instead of username) - -- **Email tab:** (optional, for password resets) - - Configure SMTP settings if you want email features - -### Step 2: Create an OAuth2 Client (for ActualBudget) - -1. **Switch to your realm** (`homelab`) via dropdown -2. **Go to Clients** (left menu) -3. **Click "Create client"** - -**General Settings:** -- **Client type:** OpenID Connect -- **Client ID:** `actualbudget` -- **Name:** `ActualBudget` -- **Description:** `Personal Finance Management` -- **Click "Next"** - -**Capability config:** -- ✅ Client authentication: ON (creates a secret) -- ✅ Authorization: OFF (not needed) -- ✅ Standard flow: ON (authorization code flow) -- ✅ Direct access grants: ON (allows username/password) -- ❌ Implicit flow: OFF (deprecated) -- ❌ Service accounts: OFF (not needed for web apps) -- **Click "Next"** - -**Login settings:** - -**Important: Adjust these for your setup!** - -**For local ActualBudget:** -``` -Root URL: http://localhost:5006 -Home URL: http://localhost:5006 -Valid redirect URIs: - http://localhost:5006/* - http://localhost:5006/callback - -Valid post logout redirect URIs: + - -Web origins: - http://localhost:5006 -``` - -**For external ActualBudget (Pikapod, etc.):** -``` -Root URL: https://actualbudget-abc123.pikapod.net -Home URL: https://actualbudget-abc123.pikapod.net -Valid redirect URIs: - https://actualbudget-abc123.pikapod.net/* - https://actualbudget-abc123.pikapod.net/callback - -Valid post logout redirect URIs: + - -Web origins: - https://actualbudget-abc123.pikapod.net -``` - -**For self-hosted with domain:** -``` -Root URL: https://budget.yourdomain.com -Home URL: https://budget.yourdomain.com -Valid redirect URIs: - https://budget.yourdomain.com/* - https://budget.yourdomain.com/callback - -Valid post logout redirect URIs: + - -Web origins: - https://budget.yourdomain.com -``` - -4. **Click "Save"** - -### Step 3: Get Client Secret - -1. **Go to "Credentials" tab** -2. **Copy "Client secret"** (you'll need this for ActualBudget) -3. **Save it somewhere safe!** - -### Step 4: Create a User - -1. **Go to Users** (left menu) -2. **Click "Create user"** - -**User details:** -- **Username:** `john` (required) -- **Email:** `john@example.com` (optional but recommended) -- **Email verified:** ✅ ON (skip email verification) -- **First name:** `John` -- **Last name:** `Doe` -- **Enabled:** ✅ ON (user can log in) -- **Click "Create"** - -**Set password:** -1. **Go to "Credentials" tab** -2. **Click "Set password"** -3. **Enter password** (twice) -4. **Temporary:** ❌ OFF (user won't be forced to change it) -5. **Click "Save"** -6. **Confirm** in popup - -### Step 5: Test Login - -1. **Go to Realm Settings** → **Endpoints** -2. **Click "OpenID Endpoint Configuration"** (opens JSON) -3. **Find:** `authorization_endpoint` -4. **Copy URL** and open in browser -5. **Add:** `?client_id=actualbudget&response_type=code&redirect_uri=http://localhost:5006/callback` -6. **Log in** with your user -7. **You should see:** Redirect to callback URL (may error if ActualBudget not configured, but login works) - ---- - -## Configuring External Services - -### Keycloak MUST be Publicly Accessible - -**Critical:** For external services like Pikapod, your Keycloak must be accessible from the internet. - -### Requirements: -1. ✅ **Domain name** (e.g., `yourdomain.com`) -2. ✅ **DNS A record** pointing to your server -3. ✅ **Caddy reverse proxy** with HTTPS -4. ✅ **Port 80/443 open** in firewall -5. ✅ **Keycloak accessible** at `https://auth.yourdomain.com` - -### Setup Caddy for Keycloak - -**Add to Caddyfile:** -```caddy -auth.yourdomain.com { - log { - output file /var/log/caddy/keycloak-access.log - format json - level INFO - } - - reverse_proxy localhost:8180 - - # Security headers - header { - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - X-Frame-Options "SAMEORIGIN" - X-Content-Type-Options "nosniff" - X-XSS-Protection "1; mode=block" - Referrer-Policy "strict-origin-when-cross-origin" - } -} -``` - -**Reload Caddy:** -```bash -cd ~/docker/caddy -docker exec -w /etc/caddy caddy caddy reload -``` - -**Test:** -``` -https://auth.yourdomain.com/admin -``` - -### Configure DNS - -**Add A record:** -``` -auth.yourdomain.com → [Your Server IP] -``` - -**Or use CNAME:** -``` -auth → yourdomain.com -``` - -### Example: ActualBudget on Pikapod - -**Scenario:** -- Keycloak: `https://auth.yourdomain.com` (your server) -- ActualBudget: `https://actualbudget-abc123.pikapod.net` (Pikapod) - -**In Keycloak:** - -1. **Create client:** `actualbudget-pikapod` -2. **Redirect URIs:** - ``` - https://actualbudget-abc123.pikapod.net/* - https://actualbudget-abc123.pikapod.net/callback - ``` -3. **Web origins:** - ``` - https://actualbudget-abc123.pikapod.net - ``` - -**In ActualBudget (Pikapod):** - -Settings → Authentication: -``` -Client ID: actualbudget-pikapod -Client Secret: [from Keycloak credentials tab] - -Authorization URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/auth -Token URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/token -User Info URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/userinfo -``` - -**Flow:** -1. User visits `https://actualbudget-abc123.pikapod.net` -2. Clicks "Login" -3. Redirects to `https://auth.yourdomain.com/realms/homelab/...` -4. User logs in -5. Redirects back to `https://actualbudget-abc123.pikapod.net/callback` -6. User is logged in! - ---- - -## Reconfiguration & Adding Realms - -You can re-run the script to add more realms or clients! - -### Option 1: Re-run the Script - -```bash -cd ~/docker/keycloak -docker compose down -cd ~ -./ubuntu-post-install.sh -# Select KEYCLOAK again -# Choose "Configure Keycloak..." → Yes -# Enter new realm name: "family" -# Create new users -``` - -**This creates:** -- New realm with new users -- New OAuth clients for that realm -- Separate from your existing realm - -### Option 2: Add Realm Manually - -**Via Web UI:** -1. Go to admin console -2. Click realm dropdown -3. "Create Realm" -4. Name: `family` -5. Repeat client/user creation steps - -### Option 3: Use Script Helper - -The script can be extended to add a helper: - -```bash -cd ~/docker/keycloak - -# Login to admin CLI -docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ - --server http://localhost:8080 \ - --realm master \ - --user admin \ - --password [YOUR_ADMIN_PASSWORD] - -# Create new realm -docker exec keycloak /opt/keycloak/bin/kcadm.sh create realms \ - -s realm=family \ - -s enabled=true - -# Create new client -docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r family \ - -s clientId=my-new-service \ - -s enabled=true \ - -s clientAuthenticatorType=client-secret \ - -s secret=$(openssl rand -hex 32) \ - -s 'redirectUris=["https://service.yourdomain.com/*"]' - -# Create new user -docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r family \ - -s username=alice \ - -s email=alice@example.com \ - -s enabled=true - -# Set password -docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r family \ - --username alice \ - --new-password 'AlicePassword123!' -``` - ---- - -## Common Use Cases - -### Use Case 1: All Local Services -**Setup:** -- Keycloak: `http://localhost:8180` -- ActualBudget: `http://localhost:5006` -- Jellyfin: `http://localhost:8096` - -**Configuration:** -- No domain needed -- Use `localhost` URLs everywhere -- Redirect URIs: `http://localhost:PORT/*` - -### Use Case 2: Self-Hosted with Domain -**Setup:** -- Keycloak: `https://auth.yourdomain.com` -- ActualBudget: `https://budget.yourdomain.com` -- Jellyfin: `https://jellyfin.yourdomain.com` - -**Configuration:** -- Requires domain + Caddy -- Use HTTPS URLs -- Redirect URIs: `https://service.yourdomain.com/*` - -### Use Case 3: Mixed (Local + External) -**Setup:** -- Keycloak: `https://auth.yourdomain.com` (self-hosted) -- ActualBudget: `https://actualbudget-abc.pikapod.net` (Pikapod) -- Jellyfin: `https://jellyfin.yourdomain.com` (self-hosted) - -**Configuration:** -- Keycloak MUST be publicly accessible -- Each service gets its own client -- ActualBudget redirect: `https://actualbudget-abc.pikapod.net/*` -- Jellyfin redirect: `https://jellyfin.yourdomain.com/*` - ---- - -## Troubleshooting - -### Issue: "Invalid redirect URI" -**Cause:** Redirect URI in Keycloak doesn't match what the app is using. - -**Fix:** -1. Check error message for actual redirect URI -2. Add EXACT URI to Keycloak client settings -3. Include wildcard: `https://domain.com/*` - -### Issue: "Client not found" -**Cause:** Client ID doesn't match. - -**Fix:** -1. Check client ID in Keycloak -2. Ensure it matches exactly in application -3. Case-sensitive! - -### Issue: "Invalid client secret" -**Cause:** Wrong secret or expired. - -**Fix:** -1. Go to Keycloak → Clients → Credentials -2. Copy secret again (or regenerate) -3. Update in application - -### Issue: External service can't reach Keycloak -**Cause:** Keycloak not publicly accessible. - -**Fix:** -1. Ensure Caddy is running: `docker ps | grep caddy` -2. Check DNS: `dig auth.yourdomain.com` -3. Test URL: `curl https://auth.yourdomain.com` -4. Check firewall: `sudo ufw status` (80/443 open?) - -### Issue: Login succeeds but redirect fails -**Cause:** CORS or redirect URI mismatch. - -**Fix:** -1. Add domain to "Web Origins" in client settings -2. Check redirect URI includes protocol (https://) -3. Check for typos in domain name - -### Issue: Can't login to Keycloak admin console -**Cause:** Container not started or wrong password. - -**Fix:** -```bash -# Check if running -docker ps | grep keycloak - -# Check logs -docker logs keycloak --tail 50 - -# Restart -cd ~/docker/keycloak -docker compose restart - -# Reset admin password (if needed) -docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ - --server http://localhost:8080 \ - --realm master \ - --user admin \ - --password NEW_PASSWORD_HERE -``` - ---- - -## Quick Reference - -### Important URLs - -**Local:** -``` -Admin Console: http://localhost:8180/admin -Realm Endpoints: http://localhost:8180/realms/{realm-name}/.well-known/openid-configuration -``` - -**Production:** -``` -Admin Console: https://auth.yourdomain.com/admin -Realm Endpoints: https://auth.yourdomain.com/realms/{realm-name}/.well-known/openid-configuration -``` - -### OAuth URLs (for realm "homelab") - -**Local:** -``` -Authorization: http://localhost:8180/realms/homelab/protocol/openid-connect/auth -Token: http://localhost:8180/realms/homelab/protocol/openid-connect/token -User Info: http://localhost:8180/realms/homelab/protocol/openid-connect/userinfo -Logout: http://localhost:8180/realms/homelab/protocol/openid-connect/logout -``` - -**Production:** -``` -Authorization: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/auth -Token: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/token -User Info: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/userinfo -Logout: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/logout -``` - -### Common Commands - -```bash -# Start Keycloak -cd ~/docker/keycloak -docker compose up -d - -# Stop Keycloak -docker compose down - -# View logs -docker logs keycloak -f - -# Access shell -docker exec -it keycloak bash - -# Login to admin CLI -docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ - --server http://localhost:8080 \ - --realm master \ - --user admin \ - --password YOUR_PASSWORD - -# Export realm configuration (backup) -docker exec keycloak /opt/keycloak/bin/kc.sh export \ - --dir /opt/keycloak/data/export \ - --realm homelab - -# Copy export to host -docker cp keycloak:/opt/keycloak/data/export ./backup/ -``` - ---- - -## Summary - -**Keycloak provides:** -- ✅ Single Sign-On for all your services -- ✅ Centralized user management -- ✅ OAuth2/OIDC authentication -- ✅ Works with local and external services -- ✅ Professional-grade security - -**Automated setup does:** -- ✅ Creates realm -- ✅ Creates OAuth clients -- ✅ Creates initial user -- ✅ Saves all credentials -- ✅ Ready to use immediately - -**Manual setup allows:** -- ✅ Full control over configuration -- ✅ Multiple realms (family, work, etc.) -- ✅ Custom client settings -- ✅ Advanced features (LDAP, MFA, etc.) - -**For external services:** -- ✅ Keycloak must be publicly accessible -- ✅ Use Caddy with HTTPS -- ✅ Configure proper redirect URIs -- ✅ Test OAuth flow before production - -For questions or issues, check the Keycloak documentation: https://www.keycloak.org/documentation diff --git a/NEW-SCRIPT-STRUCTURE.md b/NEW-SCRIPT-STRUCTURE.md index c126012..5d12f98 100644 --- a/NEW-SCRIPT-STRUCTURE.md +++ b/NEW-SCRIPT-STRUCTURE.md @@ -51,7 +51,7 @@ │ │ │ === DOCKER SERVICES === │ │ [ ] ACTUALBUDGET Personal finance │ -│ [ ] KEYCLOAK Identity management │ +│ [ ] AUTHELIA SSO + 2FA auth portal │ │ [ ] CADDY Reverse proxy │ │ [ ] FAIL2BAN_CADDY Protect Caddy services │ │ [ ] JELLYFIN Media server │ @@ -75,7 +75,7 @@ **Dependency-aware installation order:** 1. Install Caddy first (if selected) -2. Install services that depend on Caddy (Keycloak, etc.) +2. Install services that depend on Caddy (Authelia, etc.) 3. Install fail2ban for Caddy (if selected + Caddy installed) 4. Install independent services in parallel where possible diff --git a/SCRIPT-FLOW-INTRO.txt b/SCRIPT-FLOW-INTRO.txt index e4aef69..e230e5a 100644 --- a/SCRIPT-FLOW-INTRO.txt +++ b/SCRIPT-FLOW-INTRO.txt @@ -31,7 +31,7 @@ This script is divided into TWO main phases: │ • Skip services you don't want │ │ │ │ Services include: │ -│ • Self-hosted apps (ActualBudget, Keycloak, Jellyfin, etc.) │ +│ • Self-hosted apps (ActualBudget, Authelia, Jellyfin, etc.) │ │ • Network services (Samba, VPNs, fail2ban) │ │ • Monitoring tools (Uptime Kuma, Portainer, Watchtower) │ │ • And many more... │ diff --git a/SECURITY-IMPROVEMENTS.md b/SECURITY-IMPROVEMENTS.md index 7340b81..5d4a840 100644 --- a/SECURITY-IMPROVEMENTS.md +++ b/SECURITY-IMPROVEMENTS.md @@ -11,7 +11,7 @@ This document describes the comprehensive security and infrastructure improvemen **Solution:** - Added `ensure_docker_dir_ownership()` helper function -- Applied to ALL 25+ services (Immich, Keycloak, ActualBudget, Jellyfin, etc.) +- Applied to ALL 25+ services (Immich, ActualBudget, Jellyfin, etc.) - Fixed disaster recovery path (line 309) - All Docker directories now properly owned by sudo user @@ -19,8 +19,8 @@ This document describes the comprehensive security and infrastructure improvemen --- -### 2. Keycloak Security Overhaul -**Problem:** Weak default passwords, special characters causing issues, development mode in production. +### 2. Password & Credential Management +**Problem:** Weak default passwords and credentials hardcoded in compose files. **Solutions Implemented:** @@ -30,35 +30,20 @@ This document describes the comprehensive security and infrastructure improvemen - **Auto-generation:** Press ENTER to generate secure passwords automatically - **Validation:** Real-time password validation with retry loop -#### Production vs Development Mode -- **Production mode:** Uses `start` command, requires hostname configuration -- **Development mode:** Uses `start-dev` command, relaxed security for testing -- **Hostname support:** Proper `KC_HOSTNAME` configuration for public deployment +#### Generated Secrets +- Services that need cryptographic secrets generate them automatically (e.g. Authelia's JWT, session, and storage secrets via `openssl rand`). #### Environment Variables - All credentials moved to `.env` file -- Admin password and database password securely stored +- Passwords and secrets securely stored - No more hardcoded passwords in docker-compose.yml -**Example Keycloak .env file structure:** -```env -# Keycloak Environment Variables -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD= -POSTGRES_DB=keycloak -POSTGRES_USER=keycloak -POSTGRES_PASSWORD= -KC_PROXY=edge -KC_HTTP_ENABLED=true -KC_HOSTNAME=auth.yourdomain.com # (if production mode) -``` - --- ### 3. Environment Variable Management (.env Files) **Services Now Using .env Files:** -- ✅ Keycloak (admin + database passwords) +- ✅ Authelia (JWT/session/storage secrets + SMTP password) - ✅ ActualBudget (timezone and config) - ✅ Immich (already had .env) - ✅ FindMyDevice (already had .env) @@ -120,7 +105,7 @@ labels: **Services with fail2ban monitoring:** - ActualBudget -- Keycloak +- Authelia - All other internet-facing services **fail2ban Configuration:** @@ -130,7 +115,10 @@ labels: - Ban duration: 3600 seconds (1 hour) - Detection window: 600 seconds -**Detailed Setup:** See `CADDY-FAIL2BAN-SETUP.md` for complete configuration. +**Note:** When Authelia is in use, it provides its own failed-login regulation +(account lockout after repeated failures). The Caddy fail2ban jail is +complementary defense-in-depth at the HTTP layer. See +`CADDY-FAIL2BAN-SETUP.md` for complete configuration. --- @@ -154,7 +142,7 @@ PASSWORD=$(generate_password 20) # 20-character password ``` ### `validate_password(password [min_length])` -Validates passwords for Keycloak compatibility. +Validates passwords for compatibility (alphanumeric only). **Validation Rules:** - Minimum length (default: 12 characters) @@ -170,67 +158,9 @@ fi --- -## Keycloak Setup Guide - -### For ActualBudget on Pikapods - -1. **Install Keycloak with production mode:** - ```bash - sudo bash ubuntu-post-install.sh - # Select Keycloak from menu - # Choose production mode (y) - # Enter hostname: auth.yourdomain.com - # Press ENTER to auto-generate secure passwords - ``` - -2. **Configure Caddy2:** - - Script automatically prompts for Caddy configuration - - Enter your domain (e.g., auth.yourdomain.com) - - Ensure DNS A record points to your server - -3. **Configure DNS:** - ``` - auth.yourdomain.com → Your Server IP - ``` - -4. **Access Keycloak:** - ``` - https://auth.yourdomain.com - ``` - -5. **Set up ActualBudget OAuth:** - - The script automatically creates an OAuth client for ActualBudget - - Client details saved to: `~/docker/keycloak/actualbudget-oauth.txt` - - Use these credentials in your Pikapod ActualBudget instance - -6. **Configure ActualBudget on Pikapods:** - - Go to your ActualBudget settings - - Enable OpenID Connect - - Enter your Keycloak details: - - Issuer: `https://auth.yourdomain.com/realms/homelab` - - Client ID: (from actualbudget-oauth.txt) - - Client Secret: (from actualbudget-oauth.txt) - -### For Other Self-Hosted Services - -The script can create generic OAuth clients for other services. After Keycloak installation, you can: - -1. Access Keycloak admin console -2. Create new OAuth2/OIDC clients -3. Configure redirect URIs for your services -4. Use the client credentials in your service configuration - -**Generic Client Template:** -- Client ID: your-service-name -- Client Type: Confidential -- Standard Flow Enabled: Yes -- Valid Redirect URIs: https://your-service.com/* - ---- - ## Password Requirements Reference -### Keycloak Passwords +### Password Rules - **Minimum:** 12 characters - **Recommended:** 16+ characters - **Format:** Alphanumeric only (a-zA-Z0-9) @@ -238,7 +168,9 @@ The script can create generic OAuth clients for other services. After Keycloak i - **Generation:** Press ENTER for auto-generated secure passwords ### Why No Special Characters? -Keycloak has issues with special characters in certain authentication flows and database connection strings. Restricting to alphanumeric ensures compatibility. +Some services and database connection strings mishandle special characters in +certain authentication flows. Restricting to alphanumeric ensures broad +compatibility while remaining cryptographically strong. ### Password Strength with Alphanumeric Only - 12 characters: ~62^12 = 3.2 × 10^21 combinations @@ -264,21 +196,6 @@ docker ps # Should work without permission errors ``` -### Keycloak -```bash -# Check .env file exists -cat ~/docker/keycloak/.env -# Should contain KEYCLOAK_ADMIN_PASSWORD and POSTGRES_PASSWORD - -# Check production mode -cat ~/docker/keycloak/docker-compose.yml | grep command -# Should show "start" for production or "start-dev" for development - -# Test access -curl http://localhost:8180/health -# Should return health status -``` - ### Caddy2 ```bash # Check Caddy is running @@ -316,20 +233,6 @@ If you have existing services: 4. Verify ownership: `ls -la ~/docker/actualbudget` 5. Restart container: `cd ~/docker/actualbudget && docker compose restart` -### Existing Keycloak -1. **IMPORTANT:** Backup your data first! - ```bash - cp -r ~/docker/keycloak ~/docker/keycloak.backup - ``` -2. Stop existing container: - ```bash - cd ~/docker/keycloak && docker compose down - ``` -3. Run updated script and select Keycloak -4. Choose whether to keep existing data or start fresh -5. If keeping data, manually update .env with your existing passwords -6. Restart: `docker compose up -d` - --- ## Troubleshooting @@ -340,19 +243,6 @@ If you have existing services: sudo chown -R $USER:$USER ~/docker ``` -### Keycloak Won't Start -```bash -# Check logs -docker logs keycloak - -# Common issues: -# 1. Missing KC_HOSTNAME in production mode -# 2. Database connection failed (check postgres container) -# 3. Port 8180 already in use - -# Fix: Edit .env and docker-compose.yml as needed -``` - ### Caddy Certificate Errors ```bash # Check Caddy logs @@ -375,19 +265,17 @@ sudo ufw status ## Security Best Practices 1. **Change default passwords:** Even with auto-generation, review and update if needed -2. **Use production mode for Keycloak:** Never use development mode for internet-facing deployments -3. **Enable fail2ban:** Monitor and ban malicious IPs -4. **Regular updates:** Keep containers updated (use Watchtower in notify mode) -5. **Backup .env files:** Store securely, separate from compose files -6. **Use HTTPS everywhere:** Configure Caddy2 for all public services -7. **Limit exposed ports:** Only expose necessary ports to the internet -8. **Monitor logs:** Regular review of Caddy and fail2ban logs +2. **Enable fail2ban:** Monitor and ban malicious IPs +3. **Regular updates:** Keep containers updated (use Watchtower in notify mode) +4. **Backup .env files:** Store securely, separate from compose files +5. **Use HTTPS everywhere:** Configure Caddy2 for all public services +6. **Limit exposed ports:** Only expose necessary ports to the internet +7. **Monitor logs:** Regular review of Caddy and fail2ban logs --- ## Additional Resources -- **Keycloak Setup Guide:** `KEYCLOAK-SETUP-GUIDE.md` - **Caddy + fail2ban Setup:** `CADDY-FAIL2BAN-SETUP.md` - **Main Script:** `ubuntu-post-install.sh` - **Caddy Helper:** `caddy-setup-helper.sh` @@ -402,8 +290,3 @@ If you encounter issues: 2. Verify ownership: `ls -la ~/docker` 3. Review this document for troubleshooting steps 4. Check existing documentation in repository - ---- - -**Last Updated:** 2026-01-13 -**Script Version:** Latest (with security improvements) diff --git a/caddy-setup-helper.sh b/caddy-setup-helper.sh index efc9dfc..e58a29a 100755 --- a/caddy-setup-helper.sh +++ b/caddy-setup-helper.sh @@ -394,7 +394,6 @@ print_info "You can now add your services to the Caddyfile" echo "" echo "Available services to add:" echo " - ActualBudget (Personal Finance) - Port 5006" -echo " - Keycloak (Identity & Access Management) - Port 8180" echo "" if ask_yn "Would you like to add ActualBudget to Caddyfile?" "n"; then @@ -432,41 +431,6 @@ $AB_DOMAIN { fi fi -if ask_yn "Would you like to add Keycloak to Caddyfile?" "n"; then - read -p "Enter domain for Keycloak (e.g., auth.yourdomain.com): " KC_DOMAIN - - if [ -n "$KC_DOMAIN" ]; then - KC_CONFIG=" -# Keycloak - Identity & Access Management -$KC_DOMAIN { - log { - output file /var/log/caddy/keycloak-access.log - format json - level INFO - } - - reverse_proxy localhost:8180 - - # Security headers - header { - Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\" - X-Frame-Options \"SAMEORIGIN\" - X-Content-Type-Options \"nosniff\" - X-XSS-Protection \"1; mode=block\" - Referrer-Policy \"strict-origin-when-cross-origin\" - } -} -" - - if echo "$KC_CONFIG" >> "$CADDYFILE_PATH"; then - print_success "Added Keycloak configuration to Caddyfile" - else - print_error "Failed to add Keycloak configuration" - ERROR_MESSAGES+=("Add Keycloak manually - see CADDY-FAIL2BAN-SETUP.md") - fi - fi -fi - # ============================== # 12. VALIDATE AND RELOAD CADDY # ============================== diff --git a/docker-compose-keycloak.yml b/docker-compose-keycloak.yml deleted file mode 100644 index dced564..0000000 --- a/docker-compose-keycloak.yml +++ /dev/null @@ -1,137 +0,0 @@ -# Keycloak - Open-source Identity and Access Management -# https://www.keycloak.org/ -# -# DEPLOYMENT INSTRUCTIONS: -# 1. Create directory: mkdir -p ~/docker/keycloak -# 2. Copy this file: cp docker-compose-keycloak.yml ~/docker/keycloak/docker-compose.yml -# 3. Create .env file with credentials (see .env template below) -# 4. Start the service: cd ~/docker/keycloak && docker compose up -d -# 5. Access at: http://localhost:8180/admin (admin console) -# -# .ENV FILE TEMPLATE: -# Create a file named .env in ~/docker/keycloak/ with: -# KEYCLOAK_ADMIN=admin -# KEYCLOAK_ADMIN_PASSWORD= -# POSTGRES_DB=keycloak -# POSTGRES_USER=keycloak -# POSTGRES_PASSWORD= -# KC_DB=postgres -# KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak -# KC_DB_USERNAME=keycloak -# KC_DB_PASSWORD= -# KC_PROXY_HEADERS=xforwarded -# KC_HTTP_ENABLED=true -# KC_HOSTNAME_STRICT=false -# KC_LOG_LEVEL=INFO -# KC_HEALTH_ENABLED=true -# KC_METRICS_ENABLED=true -# # KC_HOSTNAME=auth.yourdomain.com # Uncomment for production -# -# REVERSE PROXY SETUP (with Caddy): -# Add to your Caddyfile: -# auth.yourdomain.com { -# reverse_proxy localhost:8180 -# } -# -# PRODUCTION DEPLOYMENT: -# For production, you should: -# 1. Use a PostgreSQL database (see postgres service below) -# 2. Enable HTTPS via reverse proxy -# 3. Set KC_HOSTNAME to your domain -# 4. Use strong admin password -# 5. Configure proper realm and clients - -name: keycloak - -services: - # PostgreSQL database for Keycloak (recommended for production) - postgres: - image: postgres:16-alpine - container_name: keycloak-db - restart: unless-stopped - env_file: - - .env - volumes: - - ./postgres-data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U keycloak"] - interval: 10s - timeout: 5s - retries: 5 - - keycloak: - image: quay.io/keycloak/keycloak:latest - container_name: keycloak - restart: unless-stopped - command: - - start-dev # Use 'start' for production mode - env_file: - - .env - ports: - - "8180:8080" # HTTP port (use reverse proxy for HTTPS) - # - "8787:8787" # Debug port (uncomment if needed) - volumes: - # Optional: Custom themes - # - ./themes:/opt/keycloak/themes - # Optional: Custom providers/extensions - # - ./providers:/opt/keycloak/providers - - ./data:/opt/keycloak/data - depends_on: - postgres: - condition: service_healthy - labels: - # Fail2ban support - - "io.podman.annotations.label/fail2ban.enable=true" - - "io.podman.annotations.label/fail2ban.filter=caddy-auth" - -# NOTES: -# - Admin console: http://localhost:8180/admin -# - Credentials: Stored in .env file -# - Database: PostgreSQL (persistent data in ./postgres-data) -# - Proxy: Uses KC_PROXY_HEADERS=xforwarded (v2 config, no deprecated warnings) -# -# FIRST-TIME SETUP: -# 1. Create .env file with secure passwords (see template above) -# 2. Start containers: docker compose up -d -# 3. Login to admin console at http://localhost:8180/admin -# 4. Create a realm (e.g., "homelab" or "myrealm") -# 5. Create clients for your applications (OAuth2/OIDC) -# 6. Add users or configure identity providers (LDAP, SAML, Social) -# -# COMMON USE CASES: -# - Single Sign-On (SSO) for multiple applications -# - OAuth2/OIDC provider for custom apps (ActualBudget, etc.) -# - SAML 2.0 identity provider -# - User federation with LDAP/Active Directory -# - Multi-factor authentication (MFA/2FA) -# - Social login (Google, GitHub, Facebook, etc.) -# -# PRODUCTION CHECKLIST: -# [ ] Create .env file with secure passwords (12+ chars, alphanumeric only) -# [ ] Set KC_HOSTNAME in .env to your domain (e.g., auth.yourdomain.com) -# [ ] Use 'start' instead of 'start-dev' command in docker-compose.yml -# [ ] Configure HTTPS via reverse proxy (Caddy/nginx) -# [ ] Set KC_HOSTNAME_STRICT=true in .env for production -# [ ] Configure DNS A record for your hostname -# [ ] Set proper file permissions: chmod 600 .env -# [ ] Configure backup strategy for PostgreSQL -# [ ] Set up monitoring (metrics enabled via KC_METRICS_ENABLED=true) -# -# BACKUP: -# docker compose down -# tar -czf keycloak-backup-$(date +%Y%m%d).tar.gz postgres-data data -# docker compose up -d -# -# RESTORE: -# docker compose down -# tar -xzf keycloak-backup-YYYYMMDD.tar.gz -# docker compose up -d -# -# UPDATES: -# docker compose pull -# docker compose up -d -# -# DOCUMENTATION: -# - Official docs: https://www.keycloak.org/documentation -# - Getting started: https://www.keycloak.org/getting-started/getting-started-docker -# - Server admin: https://www.keycloak.org/docs/latest/server_admin/ diff --git a/fix-keycloak-proxy.sh b/fix-keycloak-proxy.sh deleted file mode 100755 index 025e201..0000000 --- a/fix-keycloak-proxy.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/bash -# -# Fix Keycloak proxy configuration -# This updates Keycloak to use v2 proxy headers (KC_PROXY_HEADERS) -# instead of deprecated v1 (KC_PROXY) -# - -set -e - -KC_DIR="$HOME/docker/keycloak" - -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "Keycloak Proxy Configuration Fix" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" -echo "This script will:" -echo " 1. Backup your current .env file" -echo " 2. Replace deprecated KC_PROXY with KC_PROXY_HEADERS" -echo " 3. Ensure docker-compose.yml uses env_file" -echo " 4. Restart Keycloak with new configuration" -echo "" - -if [ ! -d "$KC_DIR" ]; then - echo "❌ Error: Keycloak directory not found at $KC_DIR" - exit 1 -fi - -cd "$KC_DIR" - -# Backup existing configuration -BACKUP_DIR="$KC_DIR/backups" -mkdir -p "$BACKUP_DIR" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -# Check if .env exists -if [ -f ".env" ]; then - echo "✓ Found existing .env file" - cp .env "$BACKUP_DIR/.env.backup.$TIMESTAMP" - echo "✓ Backed up .env to $BACKUP_DIR/.env.backup.$TIMESTAMP" - - # Check if it has the old KC_PROXY setting - if grep -q "KC_PROXY=" .env 2>/dev/null; then - echo "" - echo "Updating .env file..." - - # Replace KC_PROXY with KC_PROXY_HEADERS - sed -i 's/^KC_PROXY=.*/KC_PROXY_HEADERS=xforwarded/' .env - - # Add KC_PROXY_HEADERS if it doesn't exist and KC_PROXY didn't either - if ! grep -q "KC_PROXY_HEADERS=" .env 2>/dev/null; then - echo "" >> .env - echo "# Proxy settings (v2) - Trust X-Forwarded-* headers from Caddy2" >> .env - echo "KC_PROXY_HEADERS=xforwarded" >> .env - fi - - echo "✓ Updated KC_PROXY to KC_PROXY_HEADERS=xforwarded" - elif grep -q "KC_PROXY_HEADERS=" .env 2>/dev/null; then - echo "✓ Already using KC_PROXY_HEADERS - no changes needed" - else - echo "" - echo "Adding KC_PROXY_HEADERS to .env..." - echo "" >> .env - echo "# Proxy settings (v2) - Trust X-Forwarded-* headers from Caddy2" >> .env - echo "KC_PROXY_HEADERS=xforwarded" >> .env - echo "✓ Added KC_PROXY_HEADERS=xforwarded" - fi -else - echo "⚠ No .env file found" - echo "" - echo "Please create a .env file with your Keycloak credentials." - echo "See SECURITY-IMPROVEMENTS.md for the template." - exit 1 -fi - -# Check docker-compose.yml -if [ -f "docker-compose.yml" ]; then - cp docker-compose.yml "$BACKUP_DIR/docker-compose.yml.backup.$TIMESTAMP" - echo "✓ Backed up docker-compose.yml to $BACKUP_DIR/docker-compose.yml.backup.$TIMESTAMP" - - # Check if docker-compose.yml has hardcoded KC_PROXY - if grep -q "KC_PROXY=" docker-compose.yml 2>/dev/null; then - echo "" - echo "⚠ Found KC_PROXY in docker-compose.yml" - echo " Removing it (should be in .env file instead)..." - - # Remove the KC_PROXY line from docker-compose.yml - sed -i '/KC_PROXY=/d' docker-compose.yml - echo "✓ Removed KC_PROXY from docker-compose.yml" - fi - - # Ensure it uses env_file - if ! grep -q "env_file:" docker-compose.yml 2>/dev/null; then - echo "⚠ docker-compose.yml doesn't use env_file" - echo " You may need to update it manually to use 'env_file: - .env'" - else - echo "✓ docker-compose.yml uses env_file" - fi -fi - -# Show current configuration -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "Current Configuration:" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" - -if [ -f ".env" ]; then - echo "Proxy Settings:" - grep "KC_PROXY" .env | grep -v "^#" || echo " (none found)" - echo "" - - if grep -q "KC_HOSTNAME=" .env | grep -v "^#" 2>/dev/null; then - echo "Hostname:" - grep "KC_HOSTNAME=" .env | grep -v "^#" - echo "" - fi -fi - -# Ask to restart -echo "" -read -p "Restart Keycloak with new configuration? (y/n): " RESTART - -if [ "$RESTART" = "y" ] || [ "$RESTART" = "Y" ]; then - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "Restarting Keycloak..." - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - - docker compose down - echo "✓ Stopped Keycloak" - - echo "" - echo "Starting Keycloak (this may take a minute)..." - docker compose up -d - - # Wait for Keycloak to be ready - echo "" - echo "Waiting for Keycloak to be ready..." - KC_READY=false - for i in {1..60}; do - if docker exec keycloak curl -sf http://localhost:8080/health/ready > /dev/null 2>&1; then - KC_READY=true - echo "" - echo "✓ Keycloak is ready" - break - fi - echo -n "." - sleep 2 - done - echo "" - - if [ "$KC_READY" = true ]; then - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "✅ Keycloak successfully updated!" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - echo "Changes applied:" - echo " • Deprecated KC_PROXY removed" - echo " • New KC_PROXY_HEADERS=xforwarded configured" - echo " • Configuration stored in .env file" - echo "" - echo "The 'Hostname v1 options [proxy]' warnings should be gone." - echo "" - echo "Check the logs:" - echo " docker compose logs -f keycloak" - echo "" - else - echo "" - echo "⚠ Keycloak may still be starting. Check logs:" - echo " docker compose logs -f keycloak" - fi -else - echo "" - echo "Skipping restart. To apply changes later, run:" - echo " cd $KC_DIR && docker compose restart" -fi - -echo "" -echo "Backup location: $BACKUP_DIR/" -echo " - .env.backup.$TIMESTAMP" -echo " - docker-compose.yml.backup.$TIMESTAMP" -echo "" diff --git a/ubuntu-post-install-24.04.sh b/ubuntu-post-install-24.04.sh index c79410f..acfd309 100644 --- a/ubuntu-post-install-24.04.sh +++ b/ubuntu-post-install-24.04.sh @@ -1331,7 +1331,7 @@ generate_password() { openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length" } -# Validate password for Keycloak (alphanumeric only, minimum length) +# Validate password (alphanumeric only, minimum length) # Usage: validate_password "password" [min_length] # Returns 0 if valid, 1 if invalid validate_password() { @@ -1344,7 +1344,7 @@ validate_password() { return 1 fi - # Check for special characters (not allowed for Keycloak) + # Check for special characters (not allowed) if echo "$password" | grep -q '[^a-zA-Z0-9]'; then echo " ⚠ Password must contain only letters and numbers (no special characters)" return 1 @@ -2586,7 +2586,6 @@ else [ -d "$DOCKER_DIR/filebrowser" ] && EXISTING_SERVICES[FILEBROWSER]="ON" [ -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" @@ -2627,7 +2626,6 @@ else "FILEBROWSER" "Web-based file manager" ${EXISTING_SERVICES[FILEBROWSER]:-OFF} \ "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} \ @@ -2659,7 +2657,6 @@ else [ -n "${EXISTING_SERVICES[FILEBROWSER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FILEBROWSER \"Web file manager\" ON" [ -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" @@ -2710,7 +2707,6 @@ else : ${INSTALL_FILEBROWSER:="n"} : ${INSTALL_MAGICMIRROR:="n"} : ${INSTALL_ACTUALBUDGET:="n"} - : ${INSTALL_KEYCLOAK:="n"} : ${INSTALL_AUTHELIA:="n"} : ${INSTALL_CADDY:="n"} : ${INSTALL_FAIL2BAN:="n"} @@ -2737,7 +2733,6 @@ else if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then INSTALL_FILEBROWSER="y"; fi 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 @@ -2811,7 +2806,6 @@ else if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then uninstall_service "FileBrowser" "$DOCKER_DIR/filebrowser" "filebrowser"; fi 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 @@ -4231,625 +4225,6 @@ AB_COMPOSE fi # End AB_RECONFIGURE check fi # End INSTALL_ACTUALBUDGET check - # ---- KEYCLOAK ---- - if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_KEYCLOAK" ]; then - echo "" - echo "┌─────────────────────────────────────────────────────────────────┐" - echo "│ KEYCLOAK - Identity and Access Management (IAM) │" - echo "│ SSO, OAuth2, SAML, User Management, MFA │" - echo "│ Port: 8180 (HTTP) - Use reverse proxy for HTTPS │" - echo "└─────────────────────────────────────────────────────────────────┘" - prompt_yn "Install Keycloak? (y/n):" "n" INSTALL_KEYCLOAK - fi - - if [ "$INSTALL_KEYCLOAK" = "y" ] || [ "$INSTALL_KEYCLOAK" = "Y" ]; then - KC_DIR="$DOCKER_DIR/keycloak" - - if [ "$DRY_RUN" = true ]; then - echo "[DRY-RUN] Would create $KC_DIR" - else - echo "Installing Keycloak..." - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "PASSWORD REQUIREMENTS:" - echo " • Minimum 12 characters (16+ recommended)" - echo " • Letters and numbers ONLY (no special characters)" - echo " • Press ENTER for secure auto-generated password" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - - # Prompt for admin password with validation - KC_ADMIN_PASS="" - while true; do - read -s -p "Enter Keycloak admin password [auto-generate]: " KC_ADMIN_PASS - echo "" - - # Generate secure password if user pressed Enter - if [ -z "$KC_ADMIN_PASS" ]; then - KC_ADMIN_PASS=$(generate_password 20) - echo " ✓ Generated secure admin password (saved in .env)" - break - fi - - # Validate password - if validate_password "$KC_ADMIN_PASS" 12; then - echo " ✓ Admin password accepted" - break - fi - echo " Please try again." - done - - # Prompt for database password with validation - KC_DB_PASS="" - while true; do - read -s -p "Enter database password [auto-generate]: " KC_DB_PASS - echo "" - - # Generate secure password if user pressed Enter - if [ -z "$KC_DB_PASS" ]; then - KC_DB_PASS=$(generate_password 32) - echo " ✓ Generated secure database password (saved in .env)" - break - fi - - # Validate password - if validate_password "$KC_DB_PASS" 12; then - echo " ✓ Database password accepted" - break - fi - echo " Please try again." - done - echo "" - - # Ask about production vs development mode - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "DEPLOYMENT MODE:" - echo " • Production: Requires HTTPS via Caddy2 (recommended)" - echo " • Development: HTTP only, relaxed security (testing only)" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - prompt_yn "Use production mode? (requires Caddy2 reverse proxy) (y/n):" "y" KC_PRODUCTION - - KC_HOSTNAME="" - KC_START_CMD="start-dev" - KC_HOSTNAME_STRICT="false" - - if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then - KC_START_CMD="start" - KC_HOSTNAME_STRICT="false" - - echo "" - echo "Enter your Keycloak hostname (e.g., auth.yourdomain.com)" - echo "This should match your Caddy2 configuration." - read -p "Hostname: " KC_HOSTNAME - - if [ -n "$KC_HOSTNAME" ]; then - echo " ✓ Production mode enabled with hostname: $KC_HOSTNAME" - echo " ⚠ Make sure Caddy2 is configured for this domain!" - else - echo " ⚠ No hostname provided - using relaxed mode" - KC_HOSTNAME="" - fi - fi - - mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data" - ensure_docker_dir_ownership "$KC_DIR" - cd "$KC_DIR" - - # Create .env file for sensitive credentials - cat > .env << KC_ENV -# Keycloak Environment Variables -# ⚠ KEEP THIS FILE SECURE - Contains sensitive passwords - -# Admin Credentials -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS - -# Database Credentials -POSTGRES_DB=keycloak -POSTGRES_USER=keycloak -POSTGRES_PASSWORD=$KC_DB_PASS -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak -KC_DB_USERNAME=keycloak -KC_DB_PASSWORD=$KC_DB_PASS - -# Keycloak Configuration -# Proxy settings (v2) - Trust X-Forwarded-* headers from Caddy2 -KC_PROXY_HEADERS=xforwarded -KC_HTTP_ENABLED=true -KC_HOSTNAME_STRICT=$KC_HOSTNAME_STRICT -KC_LOG_LEVEL=INFO -KC_HEALTH_ENABLED=true -KC_METRICS_ENABLED=true -KC_ENV - - # Add hostname to .env if provided - if [ -n "$KC_HOSTNAME" ]; then - echo "KC_HOSTNAME=$KC_HOSTNAME" >> .env - fi - - # Create docker-compose.yml - cat > docker-compose.yml << KC_COMPOSE -name: keycloak - -services: - postgres: - image: postgres:16-alpine - container_name: keycloak-db - restart: unless-stopped - env_file: - - .env - volumes: - - ./postgres-data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U keycloak"] - interval: 10s - timeout: 5s - retries: 5 - - keycloak: - image: quay.io/keycloak/keycloak:latest - container_name: keycloak - restart: unless-stopped - command: - - $KC_START_CMD - env_file: - - .env - ports: - - "8180:8080" - volumes: - - ./data:/opt/keycloak/data - depends_on: - postgres: - condition: service_healthy - labels: - - "io.podman.annotations.label/fail2ban.enable=true" - - "io.podman.annotations.label/fail2ban.filter=caddy-auth" -KC_COMPOSE - - echo "" - echo " ✓ Keycloak configured at $KC_DIR" - echo " ✓ Credentials saved in .env file" - if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then - echo " ✓ Production mode enabled" - else - echo " ℹ Development mode (use production mode for internet-facing deployments)" - fi - echo "" - - # If Caddy is installed/being installed, offer to configure it for Keycloak - if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ] || [ -d "$DOCKER_DIR/caddy" ]; then - echo "" - prompt_yn "Configure Caddy reverse proxy for Keycloak? (y/n):" "y" CONFIGURE_CADDY_KC - - if [ "$CONFIGURE_CADDY_KC" = "y" ] || [ "$CONFIGURE_CADDY_KC" = "Y" ]; then - CADDY_DIR="$DOCKER_DIR/caddy" - - # Ask for domain - prompt_text " Domain for Keycloak (e.g., auth.yourdomain.com):" "auth.localhost" KC_CADDY_DOMAIN - - if [ -f "$CADDY_DIR/Caddyfile" ]; then - # Backup existing Caddyfile - mkdir -p "$CADDY_DIR/backups" - cp "$CADDY_DIR/Caddyfile" "$CADDY_DIR/backups/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)" - echo " ✓ Backed up existing Caddyfile" - - # Check if Keycloak config already exists - if ! grep -q "$KC_CADDY_DOMAIN" "$CADDY_DIR/Caddyfile"; then - # Add Keycloak configuration - cat >> "$CADDY_DIR/Caddyfile" << EOF - -# Keycloak - Identity and Access Management -$KC_CADDY_DOMAIN { - log { - output file /var/log/caddy/keycloak-access.log - format json - level INFO - } - - reverse_proxy localhost:8180 - - # Security headers - header { - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - X-Frame-Options "SAMEORIGIN" - X-Content-Type-Options "nosniff" - X-XSS-Protection "1; mode=block" - Referrer-Policy "strict-origin-when-cross-origin" - } -} -EOF - echo " ✓ Added Keycloak configuration to Caddyfile" - - # Reload Caddy if it's running - if docker ps --format '{{.Names}}' | grep -q "caddy"; then - CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep "caddy" | head -1) - echo " Reloading Caddy configuration..." - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then - echo " ✓ Formatted Caddyfile" - fi - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then - echo " ✓ Caddy reloaded successfully" - echo "" - echo " Keycloak will be available at: https://$KC_CADDY_DOMAIN" - else - echo " ⚠ Failed to reload Caddy - check logs" - echo " Manual reload: cd $CADDY_DIR && docker exec -w /etc/caddy caddy caddy reload" - fi - else - echo " ⚠ Caddy container not running - start it to use this configuration" - fi - else - echo " ℹ Keycloak configuration already exists in Caddyfile" - fi - else - echo " ⚠ Caddyfile not found at $CADDY_DIR/Caddyfile" - echo " You can configure Caddy manually later" - fi - fi - fi - - prompt_yn "Start Keycloak now? (y/n):" "y" START_KC - if [ "$START_KC" = "y" ] || [ "$START_KC" = "Y" ]; then - echo " Starting Keycloak (this may take a minute)..." - docker compose up -d 2>/dev/null && echo " ✓ Keycloak started" || echo " ⚠ Failed to start Keycloak" - - # Automated initial configuration - echo "" - prompt_yn "Configure Keycloak with initial realm and clients? (y/n):" "y" CONFIGURE_KC - - if [ "$CONFIGURE_KC" = "y" ] || [ "$CONFIGURE_KC" = "Y" ]; then - echo "" - echo " Configuring Keycloak..." - echo " This will create a realm and OAuth2 clients for your services." - echo "" - - # Get realm name - prompt_text " Realm name (e.g., homelab, services):" "homelab" KC_REALM - - # Get domain configuration for redirect URIs - echo "" - echo " ──────────────────────────────────────────────────────────────" - echo " DOMAIN CONFIGURATION" - echo " ──────────────────────────────────────────────────────────────" - echo "" - echo " Keycloak needs to know where your services are hosted." - echo "" - echo " Options:" - echo " 1. Local only (http://localhost:PORT)" - echo " 2. Public domain (https://yourdomain.com)" - echo " 3. Both local and public" - echo "" - prompt_text " Enter your setup (1/2/3):" "1" KC_SETUP_TYPE - - KC_DOMAIN="localhost" - KC_PUBLIC_DOMAIN="" - KC_EXTERNAL_SERVICE="" - - if [ "$KC_SETUP_TYPE" = "2" ] || [ "$KC_SETUP_TYPE" = "3" ]; then - echo "" - prompt_text " Your public domain (e.g., example.com):" "" KC_PUBLIC_DOMAIN - - echo "" - echo " ⚠ IMPORTANT: For Keycloak to work with external services," - echo " it MUST be accessible at https://auth.$KC_PUBLIC_DOMAIN" - echo "" - echo " This requires:" - echo " ✓ DNS A record: auth.$KC_PUBLIC_DOMAIN → Your Server IP" - echo " ✓ Caddy reverse proxy configured" - echo " ✓ Ports 80/443 open in firewall" - echo "" - prompt_yn " Is Keycloak accessible at https://auth.$KC_PUBLIC_DOMAIN? (y/n):" "n" KC_DOMAIN_READY - - if [ "$KC_DOMAIN_READY" != "y" ] && [ "$KC_DOMAIN_READY" != "Y" ]; then - echo "" - echo " ⚠ WARNING: Keycloak won't work with external services until" - echo " you configure Caddy and DNS. See KEYCLOAK-SETUP-GUIDE.md" - echo "" - echo " You can still proceed and configure Caddy later." - echo "" - fi - - # Ask about external services (like Pikapod) - echo "" - prompt_yn " Are you using external hosted services (e.g., Pikapod)? (y/n):" "n" KC_HAS_EXTERNAL - - if [ "$KC_HAS_EXTERNAL" = "y" ] || [ "$KC_HAS_EXTERNAL" = "Y" ]; then - echo "" - echo " Enter the URL of your external service (e.g., https://actualbudget-abc.pikapod.net)" - prompt_text " External service URL:" "" KC_EXTERNAL_SERVICE - fi - fi - - if [ "$KC_SETUP_TYPE" = "1" ] || [ "$KC_SETUP_TYPE" = "3" ]; then - KC_DOMAIN="localhost" - fi - - # Wait for Keycloak to be fully ready (can take 30-60 seconds) - echo "" - echo " Waiting for Keycloak to be ready..." - KC_READY=false - for i in {1..60}; do - if docker exec keycloak curl -sf http://localhost:8080/health/ready > /dev/null 2>&1; then - KC_READY=true - echo " ✓ Keycloak is ready" - break - fi - echo -n "." - sleep 2 - done - echo "" - - if [ "$KC_READY" = true ]; then - # Login to Keycloak admin CLI - echo " Logging in to Keycloak admin CLI..." - docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ - --server http://localhost:8080 \ - --realm master \ - --user admin \ - --password "$KC_ADMIN_PASS" > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Logged in to Keycloak" - - # Create realm - echo " Creating realm '$KC_REALM'..." - docker exec keycloak /opt/keycloak/bin/kcadm.sh create realms \ - -s realm="$KC_REALM" \ - -s enabled=true \ - -s displayName="$KC_REALM" \ - -s registrationAllowed=false \ - -s resetPasswordAllowed=true \ - -s rememberMe=true \ - -s loginWithEmailAllowed=true \ - -s duplicateEmailsAllowed=false \ - -s sslRequired=EXTERNAL > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created realm '$KC_REALM'" - fi - - # Create OAuth2 client for ActualBudget - if [ "$INSTALL_ACTUALBUDGET" = "y" ] || [ "$INSTALL_ACTUALBUDGET" = "Y" ]; then - echo " Creating OAuth2 client for ActualBudget..." - AB_CLIENT_SECRET=$(openssl rand -hex 32) - - # Build redirect URIs based on configuration - AB_REDIRECT_URIS='["http://localhost:5006/*","http://localhost:5006/callback"' - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://budget.'$KC_PUBLIC_DOMAIN'/*","https://budget.'$KC_PUBLIC_DOMAIN'/callback"' - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://'$KC_PUBLIC_DOMAIN':5006/*","https://'$KC_PUBLIC_DOMAIN':5006/callback"' - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"'$KC_EXTERNAL_SERVICE'/*","'$KC_EXTERNAL_SERVICE'/callback"' - fi - - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"']' - - # Build web origins - AB_WEB_ORIGINS='["http://localhost:5006"' - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"https://budget.'$KC_PUBLIC_DOMAIN'","https://'$KC_PUBLIC_DOMAIN':5006"' - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"'$KC_EXTERNAL_SERVICE'"' - fi - - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"']' - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \ - -s clientId=actualbudget \ - -s name="ActualBudget" \ - -s description="Personal Finance Management" \ - -s enabled=true \ - -s clientAuthenticatorType=client-secret \ - -s secret="$AB_CLIENT_SECRET" \ - -s publicClient=false \ - -s standardFlowEnabled=true \ - -s directAccessGrantsEnabled=true \ - -s serviceAccountsEnabled=false \ - -s "redirectUris=$AB_REDIRECT_URIS" \ - -s "webOrigins=$AB_WEB_ORIGINS" \ - -s protocol=openid-connect > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created ActualBudget client" - echo " Client ID: actualbudget" - echo " Client Secret: $AB_CLIENT_SECRET" - echo "" - - # Save to file with appropriate URLs - KC_AUTH_URL="http://localhost:8180" - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - KC_AUTH_URL="https://auth.$KC_PUBLIC_DOMAIN" - fi - - cat > "$KC_DIR/actualbudget-oauth.txt" << EOF -ActualBudget OAuth2 Configuration -================================== - -Client ID: actualbudget -Client Secret: $AB_CLIENT_SECRET - -LOCAL DEVELOPMENT: -Authorization URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth -Token URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token -User Info URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo -EOF - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -PRODUCTION (with Caddy at https://auth.$KC_PUBLIC_DOMAIN): -Authorization URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/auth -Token URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/token -User Info URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/userinfo -EOF - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -EXTERNAL SERVICE ($KC_EXTERNAL_SERVICE): -- Use PRODUCTION URLs above -- Keycloak MUST be accessible at: https://auth.$KC_PUBLIC_DOMAIN -- Redirect URI configured: $KC_EXTERNAL_SERVICE/* -EOF - fi - - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -Redirect URIs configured: -- http://localhost:5006/* (local) -EOF - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF -- https://budget.$KC_PUBLIC_DOMAIN/* (self-hosted) -EOF - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF -- $KC_EXTERNAL_SERVICE/* (external) -EOF - fi - - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -To configure ActualBudget: -1. Go to ActualBudget settings -2. Enable OpenID/OAuth authentication -3. Enter the Client ID and Secret above -4. Use the URLs above based on your setup -EOF - echo " ✓ Saved OAuth config to $KC_DIR/actualbudget-oauth.txt" - fi - fi - - # Create a generic OAuth2 client template for other services - echo " Creating generic OAuth2 client for other services..." - GENERIC_CLIENT_SECRET=$(openssl rand -hex 32) - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \ - -s clientId=generic-app \ - -s name="Generic Application" \ - -s description="Template client for other services" \ - -s enabled=true \ - -s clientAuthenticatorType=client-secret \ - -s secret="$GENERIC_CLIENT_SECRET" \ - -s publicClient=false \ - -s standardFlowEnabled=true \ - -s directAccessGrantsEnabled=true \ - -s 'redirectUris=["http://localhost:*/*","https://'$KC_DOMAIN'/*","https://*.'$KC_DOMAIN'/*"]' \ - -s 'webOrigins=["*"]' \ - -s protocol=openid-connect > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created generic OAuth2 client template" - cat > "$KC_DIR/generic-oauth.txt" << EOF -Generic OAuth2 Client Configuration -==================================== - -Client ID: generic-app -Client Secret: $GENERIC_CLIENT_SECRET - -Use this as a template for other services. You can clone this client -in the Keycloak admin console and modify the redirect URIs. - -Base URLs: -- Authorization: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth -- Token: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token -- User Info: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo - -For production: Replace localhost:8180 with https://auth.$KC_DOMAIN -EOF - echo " ✓ Saved config to $KC_DIR/generic-oauth.txt" - fi - - # Optionally create initial user - echo "" - prompt_yn "Create an initial user in realm '$KC_REALM'? (y/n):" "y" CREATE_USER - - if [ "$CREATE_USER" = "y" ] || [ "$CREATE_USER" = "Y" ]; then - prompt_text " Username:" "$ACTUAL_USER" KC_USERNAME - prompt_text " Email:" "${KC_USERNAME}@${KC_DOMAIN}" KC_EMAIL - prompt_text " First name:" "" KC_FIRSTNAME - prompt_text " Last name:" "" KC_LASTNAME - - echo " Password for $KC_USERNAME:" - read -s KC_USER_PASS - echo "" - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r "$KC_REALM" \ - -s username="$KC_USERNAME" \ - -s email="$KC_EMAIL" \ - -s firstName="$KC_FIRSTNAME" \ - -s lastName="$KC_LASTNAME" \ - -s enabled=true \ - -s emailVerified=true > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - # Set password - KC_USER_ID=$(docker exec keycloak /opt/keycloak/bin/kcadm.sh get users -r "$KC_REALM" -q username="$KC_USERNAME" 2>/dev/null | grep -o '"id" : "[^"]*"' | cut -d'"' -f4) - - docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r "$KC_REALM" \ - --username "$KC_USERNAME" \ - --new-password "$KC_USER_PASS" > /dev/null 2>&1 - - echo " ✓ Created user: $KC_USERNAME" - echo " ✓ Password set" - echo "" - echo " This user can now log in to ActualBudget and other services!" - fi - fi - - echo "" - echo " ✓ Keycloak configuration complete!" - echo "" - echo " Next steps:" - echo " 1. Go to http://localhost:8180/admin" - echo " 2. Login with admin / $KC_ADMIN_PASS" - echo " 3. Switch to realm '$KC_REALM' (top-left dropdown)" - echo " 4. Manage users in Users menu" - echo " 5. OAuth configs saved to $KC_DIR/*.txt" - echo "" - - else - echo " ⚠ Failed to login to Keycloak admin CLI" - echo " You can configure Keycloak manually via the web UI" - fi - else - echo " ⚠ Keycloak did not become ready in time" - echo " You can configure it manually after it starts" - fi - fi - fi - - echo "" - echo " Admin console: http://localhost:8180/admin" - echo " Username: admin" - echo " Password: $KC_ADMIN_PASS" - echo " Database: PostgreSQL (./postgres-data)" - if [ -n "$KC_REALM" ]; then - echo " Realm: $KC_REALM" - echo " Config files: $KC_DIR/*.txt" - fi - echo "" - echo " ⚠ For production:" - echo " - Use HTTPS via reverse proxy (Caddy)" - echo " - Change command to 'start' instead of 'start-dev'" - echo " - Set KC_HOSTNAME to your domain" - echo "" - fi - fi - # ---- CADDY WEB SERVER ---- if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_CADDY" ]; then echo "" @@ -4966,23 +4341,6 @@ CADDY_COMPOSE # } # } -# Keycloak -# auth.yourdomain.com { -# log { -# output file /var/log/caddy/keycloak-access.log -# format json -# level INFO -# } -# reverse_proxy localhost:8180 -# header { -# Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" -# X-Frame-Options "SAMEORIGIN" -# X-Content-Type-Options "nosniff" -# X-XSS-Protection "1; mode=block" -# Referrer-Policy "strict-origin-when-cross-origin" -# } -# } - # Add more services here... CADDYFILE echo " ✓ Created example Caddyfile" diff --git a/ubuntu-post-install-26.04.sh b/ubuntu-post-install-26.04.sh index 7c90f0f..8113aa1 100644 --- a/ubuntu-post-install-26.04.sh +++ b/ubuntu-post-install-26.04.sh @@ -1331,7 +1331,7 @@ generate_password() { openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length" } -# Validate password for Keycloak (alphanumeric only, minimum length) +# Validate password (alphanumeric only, minimum length) # Usage: validate_password "password" [min_length] # Returns 0 if valid, 1 if invalid validate_password() { @@ -1344,7 +1344,7 @@ validate_password() { return 1 fi - # Check for special characters (not allowed for Keycloak) + # Check for special characters (not allowed) if echo "$password" | grep -q '[^a-zA-Z0-9]'; then echo " ⚠ Password must contain only letters and numbers (no special characters)" return 1 @@ -2586,7 +2586,6 @@ else [ -d "$DOCKER_DIR/filebrowser" ] && EXISTING_SERVICES[FILEBROWSER]="ON" [ -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" @@ -2627,7 +2626,6 @@ else "FILEBROWSER" "Web-based file manager" ${EXISTING_SERVICES[FILEBROWSER]:-OFF} \ "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} \ @@ -2659,7 +2657,6 @@ else [ -n "${EXISTING_SERVICES[FILEBROWSER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FILEBROWSER \"Web file manager\" ON" [ -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" @@ -2710,7 +2707,6 @@ else : ${INSTALL_FILEBROWSER:="n"} : ${INSTALL_MAGICMIRROR:="n"} : ${INSTALL_ACTUALBUDGET:="n"} - : ${INSTALL_KEYCLOAK:="n"} : ${INSTALL_AUTHELIA:="n"} : ${INSTALL_CADDY:="n"} : ${INSTALL_FAIL2BAN:="n"} @@ -2737,7 +2733,6 @@ else if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then INSTALL_FILEBROWSER="y"; fi 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 @@ -2811,7 +2806,6 @@ else if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then uninstall_service "FileBrowser" "$DOCKER_DIR/filebrowser" "filebrowser"; fi 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 @@ -4231,625 +4225,6 @@ AB_COMPOSE fi # End AB_RECONFIGURE check fi # End INSTALL_ACTUALBUDGET check - # ---- KEYCLOAK ---- - if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_KEYCLOAK" ]; then - echo "" - echo "┌─────────────────────────────────────────────────────────────────┐" - echo "│ KEYCLOAK - Identity and Access Management (IAM) │" - echo "│ SSO, OAuth2, SAML, User Management, MFA │" - echo "│ Port: 8180 (HTTP) - Use reverse proxy for HTTPS │" - echo "└─────────────────────────────────────────────────────────────────┘" - prompt_yn "Install Keycloak? (y/n):" "n" INSTALL_KEYCLOAK - fi - - if [ "$INSTALL_KEYCLOAK" = "y" ] || [ "$INSTALL_KEYCLOAK" = "Y" ]; then - KC_DIR="$DOCKER_DIR/keycloak" - - if [ "$DRY_RUN" = true ]; then - echo "[DRY-RUN] Would create $KC_DIR" - else - echo "Installing Keycloak..." - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "PASSWORD REQUIREMENTS:" - echo " • Minimum 12 characters (16+ recommended)" - echo " • Letters and numbers ONLY (no special characters)" - echo " • Press ENTER for secure auto-generated password" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - - # Prompt for admin password with validation - KC_ADMIN_PASS="" - while true; do - read -s -p "Enter Keycloak admin password [auto-generate]: " KC_ADMIN_PASS - echo "" - - # Generate secure password if user pressed Enter - if [ -z "$KC_ADMIN_PASS" ]; then - KC_ADMIN_PASS=$(generate_password 20) - echo " ✓ Generated secure admin password (saved in .env)" - break - fi - - # Validate password - if validate_password "$KC_ADMIN_PASS" 12; then - echo " ✓ Admin password accepted" - break - fi - echo " Please try again." - done - - # Prompt for database password with validation - KC_DB_PASS="" - while true; do - read -s -p "Enter database password [auto-generate]: " KC_DB_PASS - echo "" - - # Generate secure password if user pressed Enter - if [ -z "$KC_DB_PASS" ]; then - KC_DB_PASS=$(generate_password 32) - echo " ✓ Generated secure database password (saved in .env)" - break - fi - - # Validate password - if validate_password "$KC_DB_PASS" 12; then - echo " ✓ Database password accepted" - break - fi - echo " Please try again." - done - echo "" - - # Ask about production vs development mode - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "DEPLOYMENT MODE:" - echo " • Production: Requires HTTPS via Caddy2 (recommended)" - echo " • Development: HTTP only, relaxed security (testing only)" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - prompt_yn "Use production mode? (requires Caddy2 reverse proxy) (y/n):" "y" KC_PRODUCTION - - KC_HOSTNAME="" - KC_START_CMD="start-dev" - KC_HOSTNAME_STRICT="false" - - if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then - KC_START_CMD="start" - KC_HOSTNAME_STRICT="false" - - echo "" - echo "Enter your Keycloak hostname (e.g., auth.yourdomain.com)" - echo "This should match your Caddy2 configuration." - read -p "Hostname: " KC_HOSTNAME - - if [ -n "$KC_HOSTNAME" ]; then - echo " ✓ Production mode enabled with hostname: $KC_HOSTNAME" - echo " ⚠ Make sure Caddy2 is configured for this domain!" - else - echo " ⚠ No hostname provided - using relaxed mode" - KC_HOSTNAME="" - fi - fi - - mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data" - ensure_docker_dir_ownership "$KC_DIR" - cd "$KC_DIR" - - # Create .env file for sensitive credentials - cat > .env << KC_ENV -# Keycloak Environment Variables -# ⚠ KEEP THIS FILE SECURE - Contains sensitive passwords - -# Admin Credentials -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS - -# Database Credentials -POSTGRES_DB=keycloak -POSTGRES_USER=keycloak -POSTGRES_PASSWORD=$KC_DB_PASS -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak -KC_DB_USERNAME=keycloak -KC_DB_PASSWORD=$KC_DB_PASS - -# Keycloak Configuration -# Proxy settings (v2) - Trust X-Forwarded-* headers from Caddy2 -KC_PROXY_HEADERS=xforwarded -KC_HTTP_ENABLED=true -KC_HOSTNAME_STRICT=$KC_HOSTNAME_STRICT -KC_LOG_LEVEL=INFO -KC_HEALTH_ENABLED=true -KC_METRICS_ENABLED=true -KC_ENV - - # Add hostname to .env if provided - if [ -n "$KC_HOSTNAME" ]; then - echo "KC_HOSTNAME=$KC_HOSTNAME" >> .env - fi - - # Create docker-compose.yml - cat > docker-compose.yml << KC_COMPOSE -name: keycloak - -services: - postgres: - image: postgres:16-alpine - container_name: keycloak-db - restart: unless-stopped - env_file: - - .env - volumes: - - ./postgres-data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U keycloak"] - interval: 10s - timeout: 5s - retries: 5 - - keycloak: - image: quay.io/keycloak/keycloak:latest - container_name: keycloak - restart: unless-stopped - command: - - $KC_START_CMD - env_file: - - .env - ports: - - "8180:8080" - volumes: - - ./data:/opt/keycloak/data - depends_on: - postgres: - condition: service_healthy - labels: - - "io.podman.annotations.label/fail2ban.enable=true" - - "io.podman.annotations.label/fail2ban.filter=caddy-auth" -KC_COMPOSE - - echo "" - echo " ✓ Keycloak configured at $KC_DIR" - echo " ✓ Credentials saved in .env file" - if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then - echo " ✓ Production mode enabled" - else - echo " ℹ Development mode (use production mode for internet-facing deployments)" - fi - echo "" - - # If Caddy is installed/being installed, offer to configure it for Keycloak - if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ] || [ -d "$DOCKER_DIR/caddy" ]; then - echo "" - prompt_yn "Configure Caddy reverse proxy for Keycloak? (y/n):" "y" CONFIGURE_CADDY_KC - - if [ "$CONFIGURE_CADDY_KC" = "y" ] || [ "$CONFIGURE_CADDY_KC" = "Y" ]; then - CADDY_DIR="$DOCKER_DIR/caddy" - - # Ask for domain - prompt_text " Domain for Keycloak (e.g., auth.yourdomain.com):" "auth.localhost" KC_CADDY_DOMAIN - - if [ -f "$CADDY_DIR/Caddyfile" ]; then - # Backup existing Caddyfile - mkdir -p "$CADDY_DIR/backups" - cp "$CADDY_DIR/Caddyfile" "$CADDY_DIR/backups/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)" - echo " ✓ Backed up existing Caddyfile" - - # Check if Keycloak config already exists - if ! grep -q "$KC_CADDY_DOMAIN" "$CADDY_DIR/Caddyfile"; then - # Add Keycloak configuration - cat >> "$CADDY_DIR/Caddyfile" << EOF - -# Keycloak - Identity and Access Management -$KC_CADDY_DOMAIN { - log { - output file /var/log/caddy/keycloak-access.log - format json - level INFO - } - - reverse_proxy localhost:8180 - - # Security headers - header { - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - X-Frame-Options "SAMEORIGIN" - X-Content-Type-Options "nosniff" - X-XSS-Protection "1; mode=block" - Referrer-Policy "strict-origin-when-cross-origin" - } -} -EOF - echo " ✓ Added Keycloak configuration to Caddyfile" - - # Reload Caddy if it's running - if docker ps --format '{{.Names}}' | grep -q "caddy"; then - CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep "caddy" | head -1) - echo " Reloading Caddy configuration..." - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then - echo " ✓ Formatted Caddyfile" - fi - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then - echo " ✓ Caddy reloaded successfully" - echo "" - echo " Keycloak will be available at: https://$KC_CADDY_DOMAIN" - else - echo " ⚠ Failed to reload Caddy - check logs" - echo " Manual reload: cd $CADDY_DIR && docker exec -w /etc/caddy caddy caddy reload" - fi - else - echo " ⚠ Caddy container not running - start it to use this configuration" - fi - else - echo " ℹ Keycloak configuration already exists in Caddyfile" - fi - else - echo " ⚠ Caddyfile not found at $CADDY_DIR/Caddyfile" - echo " You can configure Caddy manually later" - fi - fi - fi - - prompt_yn "Start Keycloak now? (y/n):" "y" START_KC - if [ "$START_KC" = "y" ] || [ "$START_KC" = "Y" ]; then - echo " Starting Keycloak (this may take a minute)..." - docker compose up -d 2>/dev/null && echo " ✓ Keycloak started" || echo " ⚠ Failed to start Keycloak" - - # Automated initial configuration - echo "" - prompt_yn "Configure Keycloak with initial realm and clients? (y/n):" "y" CONFIGURE_KC - - if [ "$CONFIGURE_KC" = "y" ] || [ "$CONFIGURE_KC" = "Y" ]; then - echo "" - echo " Configuring Keycloak..." - echo " This will create a realm and OAuth2 clients for your services." - echo "" - - # Get realm name - prompt_text " Realm name (e.g., homelab, services):" "homelab" KC_REALM - - # Get domain configuration for redirect URIs - echo "" - echo " ──────────────────────────────────────────────────────────────" - echo " DOMAIN CONFIGURATION" - echo " ──────────────────────────────────────────────────────────────" - echo "" - echo " Keycloak needs to know where your services are hosted." - echo "" - echo " Options:" - echo " 1. Local only (http://localhost:PORT)" - echo " 2. Public domain (https://yourdomain.com)" - echo " 3. Both local and public" - echo "" - prompt_text " Enter your setup (1/2/3):" "1" KC_SETUP_TYPE - - KC_DOMAIN="localhost" - KC_PUBLIC_DOMAIN="" - KC_EXTERNAL_SERVICE="" - - if [ "$KC_SETUP_TYPE" = "2" ] || [ "$KC_SETUP_TYPE" = "3" ]; then - echo "" - prompt_text " Your public domain (e.g., example.com):" "" KC_PUBLIC_DOMAIN - - echo "" - echo " ⚠ IMPORTANT: For Keycloak to work with external services," - echo " it MUST be accessible at https://auth.$KC_PUBLIC_DOMAIN" - echo "" - echo " This requires:" - echo " ✓ DNS A record: auth.$KC_PUBLIC_DOMAIN → Your Server IP" - echo " ✓ Caddy reverse proxy configured" - echo " ✓ Ports 80/443 open in firewall" - echo "" - prompt_yn " Is Keycloak accessible at https://auth.$KC_PUBLIC_DOMAIN? (y/n):" "n" KC_DOMAIN_READY - - if [ "$KC_DOMAIN_READY" != "y" ] && [ "$KC_DOMAIN_READY" != "Y" ]; then - echo "" - echo " ⚠ WARNING: Keycloak won't work with external services until" - echo " you configure Caddy and DNS. See KEYCLOAK-SETUP-GUIDE.md" - echo "" - echo " You can still proceed and configure Caddy later." - echo "" - fi - - # Ask about external services (like Pikapod) - echo "" - prompt_yn " Are you using external hosted services (e.g., Pikapod)? (y/n):" "n" KC_HAS_EXTERNAL - - if [ "$KC_HAS_EXTERNAL" = "y" ] || [ "$KC_HAS_EXTERNAL" = "Y" ]; then - echo "" - echo " Enter the URL of your external service (e.g., https://actualbudget-abc.pikapod.net)" - prompt_text " External service URL:" "" KC_EXTERNAL_SERVICE - fi - fi - - if [ "$KC_SETUP_TYPE" = "1" ] || [ "$KC_SETUP_TYPE" = "3" ]; then - KC_DOMAIN="localhost" - fi - - # Wait for Keycloak to be fully ready (can take 30-60 seconds) - echo "" - echo " Waiting for Keycloak to be ready..." - KC_READY=false - for i in {1..60}; do - if docker exec keycloak curl -sf http://localhost:8080/health/ready > /dev/null 2>&1; then - KC_READY=true - echo " ✓ Keycloak is ready" - break - fi - echo -n "." - sleep 2 - done - echo "" - - if [ "$KC_READY" = true ]; then - # Login to Keycloak admin CLI - echo " Logging in to Keycloak admin CLI..." - docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ - --server http://localhost:8080 \ - --realm master \ - --user admin \ - --password "$KC_ADMIN_PASS" > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Logged in to Keycloak" - - # Create realm - echo " Creating realm '$KC_REALM'..." - docker exec keycloak /opt/keycloak/bin/kcadm.sh create realms \ - -s realm="$KC_REALM" \ - -s enabled=true \ - -s displayName="$KC_REALM" \ - -s registrationAllowed=false \ - -s resetPasswordAllowed=true \ - -s rememberMe=true \ - -s loginWithEmailAllowed=true \ - -s duplicateEmailsAllowed=false \ - -s sslRequired=EXTERNAL > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created realm '$KC_REALM'" - fi - - # Create OAuth2 client for ActualBudget - if [ "$INSTALL_ACTUALBUDGET" = "y" ] || [ "$INSTALL_ACTUALBUDGET" = "Y" ]; then - echo " Creating OAuth2 client for ActualBudget..." - AB_CLIENT_SECRET=$(openssl rand -hex 32) - - # Build redirect URIs based on configuration - AB_REDIRECT_URIS='["http://localhost:5006/*","http://localhost:5006/callback"' - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://budget.'$KC_PUBLIC_DOMAIN'/*","https://budget.'$KC_PUBLIC_DOMAIN'/callback"' - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://'$KC_PUBLIC_DOMAIN':5006/*","https://'$KC_PUBLIC_DOMAIN':5006/callback"' - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"'$KC_EXTERNAL_SERVICE'/*","'$KC_EXTERNAL_SERVICE'/callback"' - fi - - AB_REDIRECT_URIS="$AB_REDIRECT_URIS"']' - - # Build web origins - AB_WEB_ORIGINS='["http://localhost:5006"' - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"https://budget.'$KC_PUBLIC_DOMAIN'","https://'$KC_PUBLIC_DOMAIN':5006"' - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"'$KC_EXTERNAL_SERVICE'"' - fi - - AB_WEB_ORIGINS="$AB_WEB_ORIGINS"']' - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \ - -s clientId=actualbudget \ - -s name="ActualBudget" \ - -s description="Personal Finance Management" \ - -s enabled=true \ - -s clientAuthenticatorType=client-secret \ - -s secret="$AB_CLIENT_SECRET" \ - -s publicClient=false \ - -s standardFlowEnabled=true \ - -s directAccessGrantsEnabled=true \ - -s serviceAccountsEnabled=false \ - -s "redirectUris=$AB_REDIRECT_URIS" \ - -s "webOrigins=$AB_WEB_ORIGINS" \ - -s protocol=openid-connect > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created ActualBudget client" - echo " Client ID: actualbudget" - echo " Client Secret: $AB_CLIENT_SECRET" - echo "" - - # Save to file with appropriate URLs - KC_AUTH_URL="http://localhost:8180" - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - KC_AUTH_URL="https://auth.$KC_PUBLIC_DOMAIN" - fi - - cat > "$KC_DIR/actualbudget-oauth.txt" << EOF -ActualBudget OAuth2 Configuration -================================== - -Client ID: actualbudget -Client Secret: $AB_CLIENT_SECRET - -LOCAL DEVELOPMENT: -Authorization URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth -Token URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token -User Info URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo -EOF - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -PRODUCTION (with Caddy at https://auth.$KC_PUBLIC_DOMAIN): -Authorization URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/auth -Token URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/token -User Info URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/userinfo -EOF - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -EXTERNAL SERVICE ($KC_EXTERNAL_SERVICE): -- Use PRODUCTION URLs above -- Keycloak MUST be accessible at: https://auth.$KC_PUBLIC_DOMAIN -- Redirect URI configured: $KC_EXTERNAL_SERVICE/* -EOF - fi - - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -Redirect URIs configured: -- http://localhost:5006/* (local) -EOF - - if [ -n "$KC_PUBLIC_DOMAIN" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF -- https://budget.$KC_PUBLIC_DOMAIN/* (self-hosted) -EOF - fi - - if [ -n "$KC_EXTERNAL_SERVICE" ]; then - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF -- $KC_EXTERNAL_SERVICE/* (external) -EOF - fi - - cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF - -To configure ActualBudget: -1. Go to ActualBudget settings -2. Enable OpenID/OAuth authentication -3. Enter the Client ID and Secret above -4. Use the URLs above based on your setup -EOF - echo " ✓ Saved OAuth config to $KC_DIR/actualbudget-oauth.txt" - fi - fi - - # Create a generic OAuth2 client template for other services - echo " Creating generic OAuth2 client for other services..." - GENERIC_CLIENT_SECRET=$(openssl rand -hex 32) - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \ - -s clientId=generic-app \ - -s name="Generic Application" \ - -s description="Template client for other services" \ - -s enabled=true \ - -s clientAuthenticatorType=client-secret \ - -s secret="$GENERIC_CLIENT_SECRET" \ - -s publicClient=false \ - -s standardFlowEnabled=true \ - -s directAccessGrantsEnabled=true \ - -s 'redirectUris=["http://localhost:*/*","https://'$KC_DOMAIN'/*","https://*.'$KC_DOMAIN'/*"]' \ - -s 'webOrigins=["*"]' \ - -s protocol=openid-connect > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - echo " ✓ Created generic OAuth2 client template" - cat > "$KC_DIR/generic-oauth.txt" << EOF -Generic OAuth2 Client Configuration -==================================== - -Client ID: generic-app -Client Secret: $GENERIC_CLIENT_SECRET - -Use this as a template for other services. You can clone this client -in the Keycloak admin console and modify the redirect URIs. - -Base URLs: -- Authorization: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth -- Token: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token -- User Info: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo - -For production: Replace localhost:8180 with https://auth.$KC_DOMAIN -EOF - echo " ✓ Saved config to $KC_DIR/generic-oauth.txt" - fi - - # Optionally create initial user - echo "" - prompt_yn "Create an initial user in realm '$KC_REALM'? (y/n):" "y" CREATE_USER - - if [ "$CREATE_USER" = "y" ] || [ "$CREATE_USER" = "Y" ]; then - prompt_text " Username:" "$ACTUAL_USER" KC_USERNAME - prompt_text " Email:" "${KC_USERNAME}@${KC_DOMAIN}" KC_EMAIL - prompt_text " First name:" "" KC_FIRSTNAME - prompt_text " Last name:" "" KC_LASTNAME - - echo " Password for $KC_USERNAME:" - read -s KC_USER_PASS - echo "" - - docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r "$KC_REALM" \ - -s username="$KC_USERNAME" \ - -s email="$KC_EMAIL" \ - -s firstName="$KC_FIRSTNAME" \ - -s lastName="$KC_LASTNAME" \ - -s enabled=true \ - -s emailVerified=true > /dev/null 2>&1 - - if [ $? -eq 0 ]; then - # Set password - KC_USER_ID=$(docker exec keycloak /opt/keycloak/bin/kcadm.sh get users -r "$KC_REALM" -q username="$KC_USERNAME" 2>/dev/null | grep -o '"id" : "[^"]*"' | cut -d'"' -f4) - - docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r "$KC_REALM" \ - --username "$KC_USERNAME" \ - --new-password "$KC_USER_PASS" > /dev/null 2>&1 - - echo " ✓ Created user: $KC_USERNAME" - echo " ✓ Password set" - echo "" - echo " This user can now log in to ActualBudget and other services!" - fi - fi - - echo "" - echo " ✓ Keycloak configuration complete!" - echo "" - echo " Next steps:" - echo " 1. Go to http://localhost:8180/admin" - echo " 2. Login with admin / $KC_ADMIN_PASS" - echo " 3. Switch to realm '$KC_REALM' (top-left dropdown)" - echo " 4. Manage users in Users menu" - echo " 5. OAuth configs saved to $KC_DIR/*.txt" - echo "" - - else - echo " ⚠ Failed to login to Keycloak admin CLI" - echo " You can configure Keycloak manually via the web UI" - fi - else - echo " ⚠ Keycloak did not become ready in time" - echo " You can configure it manually after it starts" - fi - fi - fi - - echo "" - echo " Admin console: http://localhost:8180/admin" - echo " Username: admin" - echo " Password: $KC_ADMIN_PASS" - echo " Database: PostgreSQL (./postgres-data)" - if [ -n "$KC_REALM" ]; then - echo " Realm: $KC_REALM" - echo " Config files: $KC_DIR/*.txt" - fi - echo "" - echo " ⚠ For production:" - echo " - Use HTTPS via reverse proxy (Caddy)" - echo " - Change command to 'start' instead of 'start-dev'" - echo " - Set KC_HOSTNAME to your domain" - echo "" - fi - fi - # ---- CADDY WEB SERVER ---- if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_CADDY" ]; then echo "" @@ -4946,23 +4321,6 @@ CADDY_COMPOSE # } # } -# Keycloak -# auth.yourdomain.com { -# log { -# output file /var/log/caddy/keycloak-access.log -# format json -# level INFO -# } -# reverse_proxy localhost:8180 -# header { -# Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" -# X-Frame-Options "SAMEORIGIN" -# X-Content-Type-Options "nosniff" -# X-XSS-Protection "1; mode=block" -# Referrer-Policy "strict-origin-when-cross-origin" -# } -# } - # Add more services here... CADDYFILE echo " ✓ Created example Caddyfile"