From 5dbb27ee8b27350c91a9a8ae14ab612791557307 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 21:43:52 +0000 Subject: [PATCH 1/2] Fix Magic Mirror npm setup and add ActualBudget, Keycloak, Caddy/fail2ban support FIXES: - Fix Magic Mirror npm install to run inside Docker container instead of on host - npm (Node Package Manager) commands now execute inside the MagicMirror container where Node.js is installed, preventing errors on hosts without Node.js NEW SERVICES: - Add ActualBudget: Open-source personal finance management with bank sync (SimpleFIN) - Add Keycloak: Identity and Access Management (SSO, OAuth2, SAML, MFA) - Both services integrated into main installation script and available as standalone docker-compose files for existing servers CADDY & FAIL2BAN: - Add caddy-setup-helper.sh: Interactive script to configure Caddy and fail2ban * Detects existing Caddy installation * Automatically backs up Caddyfile with timestamp * Checks for fail2ban support * Provides service integration examples - Add fail2ban filter and jail configurations for Caddy protection - Add comprehensive setup guide (CADDY-FAIL2BAN-SETUP.md) DOCUMENTATION: - Detailed deployment instructions for each service - Reverse proxy configuration examples - Security best practices and headers - Backup/restore procedures - Troubleshooting guides This update enables secure deployment of new services on existing servers with proper Caddy reverse proxy integration and fail2ban protection against attacks. --- CADDY-FAIL2BAN-SETUP.md | 371 ++++++++++++++++++++++++++++++++ caddy-setup-helper.sh | 347 +++++++++++++++++++++++++++++ docker-compose-actualbudget.yml | 60 ++++++ docker-compose-keycloak.yml | 143 ++++++++++++ fail2ban-caddy-filter.conf | 49 +++++ fail2ban-caddy-jail.conf | 82 +++++++ ubuntu-post-install.sh | 185 +++++++++++++++- 7 files changed, 1227 insertions(+), 10 deletions(-) create mode 100644 CADDY-FAIL2BAN-SETUP.md create mode 100755 caddy-setup-helper.sh create mode 100644 docker-compose-actualbudget.yml create mode 100644 docker-compose-keycloak.yml create mode 100644 fail2ban-caddy-filter.conf create mode 100644 fail2ban-caddy-jail.conf diff --git a/CADDY-FAIL2BAN-SETUP.md b/CADDY-FAIL2BAN-SETUP.md new file mode 100644 index 0000000..b179e0f --- /dev/null +++ b/CADDY-FAIL2BAN-SETUP.md @@ -0,0 +1,371 @@ +# Caddy with Fail2ban Setup Guide + +This guide helps you integrate new services with an existing Caddy reverse proxy and set up fail2ban protection. + +## Quick Start + +For servers with Caddy already installed: + +```bash +# Run the automated helper script +./caddy-setup-helper.sh +``` + +This script will: +- ✅ Detect your Caddy installation +- ✅ Locate and backup your Caddyfile +- ✅ Check for fail2ban configuration +- ✅ Provide examples for adding new services + +## Manual Setup + +### 1. Backup Your Caddyfile + +**IMPORTANT:** Always backup before making changes! + +```bash +# Find your Caddyfile location +CADDYFILE=~/docker/caddy/Caddyfile # Adjust path as needed + +# Create backup directory +mkdir -p $(dirname "$CADDYFILE")/backups + +# Backup with timestamp +cp "$CADDYFILE" "$(dirname "$CADDYFILE")/backups/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)" +``` + +### 2. Add New Services to Caddy + +Add these blocks to your Caddyfile: + +#### ActualBudget (Personal Finance) + +```caddy +budget.yourdomain.com { + log { + output file /var/log/caddy/actualbudget-access.log + format json + level INFO + } + + reverse_proxy localhost:5006 + + # 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" + } +} +``` + +#### Keycloak (Identity & Access Management) + +```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" + } +} +``` + +### 3. Reload Caddy Configuration + +After editing the Caddyfile: + +```bash +# Format the Caddyfile (optional but recommended) +docker exec -w /etc/caddy caddy caddy fmt --overwrite + +# Reload Caddy configuration +docker exec -w /etc/caddy caddy caddy reload +``` + +If you get errors, check Caddy logs: +```bash +docker logs caddy +``` + +### 4. Restore from Backup (if needed) + +If something goes wrong: + +```bash +# Find your backup +ls -lah ~/docker/caddy/backups/ + +# Restore the backup +cp ~/docker/caddy/backups/Caddyfile.backup.YYYYMMDD_HHMMSS ~/docker/caddy/Caddyfile + +# Reload Caddy +docker exec -w /etc/caddy caddy caddy reload +docker exec -w /etc/caddy caddy caddy fmt --overwrite +``` + +## Fail2ban Configuration + +### Prerequisites + +1. **Enable JSON logging in Caddy** (shown in examples above) +2. **Install fail2ban** on the host: + ```bash + sudo apt update + sudo apt install fail2ban -y + ``` + +### Installation Steps + +#### Step 1: Install Fail2ban Filter + +```bash +# Copy the filter configuration +sudo cp fail2ban-caddy-filter.conf /etc/fail2ban/filter.d/caddy-auth.conf +``` + +Or create it manually: + +```bash +sudo tee /etc/fail2ban/filter.d/caddy-auth.conf > /dev/null <<'EOF' +[Definition] +failregex = ^.*"remote_ip":"".*"status":(?:401|403|429).*$ + ^.*"remote_addr":".*"status":(?:401|403|429).*$ +ignoreregex = ^.*"remote_ip":"(?:127\.0\.0\.1|::1)".*$ +datepattern = "ts":%%s +EOF +``` + +#### Step 2: Install Fail2ban Jail + +```bash +# Copy the jail configuration +sudo cp fail2ban-caddy-jail.conf /etc/fail2ban/jail.d/caddy.conf +``` + +Or create it manually: + +```bash +sudo tee /etc/fail2ban/jail.d/caddy.conf > /dev/null <<'EOF' +[caddy-auth] +enabled = true +port = http,https +filter = caddy-auth +logpath = /var/log/caddy/access.log + /var/log/caddy/*-access.log +maxretry = 5 +findtime = 600 +bantime = 3600 +action = iptables-multiport[name=CaddyAuth, port="http,https", protocol=tcp] +backend = auto +EOF +``` + +#### Step 3: Create Log Directory + +```bash +# Create log directory if using Docker Caddy +sudo mkdir -p /var/log/caddy +sudo chmod 755 /var/log/caddy + +# If Caddy runs as specific user: +# sudo chown caddy:caddy /var/log/caddy +``` + +#### Step 4: Update Caddy Docker Compose + +Add log volume to your Caddy docker-compose.yml: + +```yaml +services: + caddy: + image: caddy:latest + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + - ./data:/data + - ./config:/config + - /var/log/caddy:/var/log/caddy # Add this line +``` + +Then restart Caddy: +```bash +cd ~/docker/caddy +docker compose down +docker compose up -d +``` + +#### Step 5: Restart Fail2ban + +```bash +sudo systemctl restart fail2ban +sudo systemctl status fail2ban +``` + +### Testing Fail2ban + +```bash +# Check if jail is running +sudo fail2ban-client status caddy-auth + +# Test the filter against your logs +sudo fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-auth.conf + +# View banned IPs +sudo fail2ban-client get caddy-auth banip + +# Manually ban/unban an IP (for testing) +sudo fail2ban-client set caddy-auth banip 1.2.3.4 +sudo fail2ban-client set caddy-auth unbanip 1.2.3.4 +``` + +### Troubleshooting + +#### Fail2ban not detecting attacks + +1. **Check log format:** + ```bash + tail -f /var/log/caddy/access.log + ``` + Ensure it's JSON format with `remote_ip` or `remote_addr` field. + +2. **Test filter manually:** + ```bash + sudo fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-auth.conf --print-all-matched + ``` + +3. **Check fail2ban logs:** + ```bash + sudo tail -f /var/log/fail2ban.log + ``` + +#### Caddy configuration errors + +1. **Validate Caddyfile:** + ```bash + docker exec caddy caddy validate --config /etc/caddy/Caddyfile + ``` + +2. **Check Caddy logs:** + ```bash + docker logs caddy --tail 50 + ``` + +## Advanced Configuration + +### Aggressive Fail2ban Settings + +For tighter security: + +```ini +[caddy-auth] +maxretry = 3 # Ban after 3 attempts (instead of 5) +findtime = 300 # Within 5 minutes (instead of 10) +bantime = 86400 # Ban for 24 hours (instead of 1) +``` + +### Ban Time Increment + +Ban repeat offenders for longer: + +```ini +[caddy-auth] +bantime.increment = true +bantime.factor = 24 +bantime.maxtime = 604800 # Maximum 1 week ban +``` + +### Email Notifications + +Get notified when IPs are banned: + +```ini +[caddy-auth] +action = iptables-multiport[name=CaddyAuth, port="http,https", protocol=tcp] + sendmail-whois[name=CaddyAuth, dest=admin@yourdomain.com] +``` + +### Per-Service Jails + +Create separate jails for different services: + +```ini +[caddy-actualbudget] +enabled = true +port = http,https +filter = caddy-auth +logpath = /var/log/caddy/actualbudget-access.log +maxretry = 3 +bantime = 7200 + +[caddy-keycloak] +enabled = true +port = http,https +filter = caddy-auth +logpath = /var/log/caddy/keycloak-access.log +maxretry = 5 +bantime = 3600 +``` + +## Best Practices + +1. **Always backup before changes** +2. **Test configuration before reloading** (`caddy validate`) +3. **Monitor fail2ban logs** initially to tune settings +4. **Use strong passwords** for admin interfaces +5. **Keep services updated** (`docker compose pull && docker compose up -d`) +6. **Regular backups** of configuration and data +7. **Use HTTPS** via Caddy for all services +8. **Implement rate limiting** in Caddy for API endpoints + +## Quick Reference + +### Common Commands + +```bash +# Caddy +docker exec -w /etc/caddy caddy caddy reload +docker exec -w /etc/caddy caddy caddy fmt --overwrite +docker exec caddy caddy validate --config /etc/caddy/Caddyfile +docker logs caddy --tail 50 + +# Fail2ban +sudo systemctl restart fail2ban +sudo fail2ban-client status caddy-auth +sudo fail2ban-client set caddy-auth unbanip 1.2.3.4 +sudo tail -f /var/log/fail2ban.log + +# Backup +cp ~/docker/caddy/Caddyfile ~/docker/caddy/Caddyfile.backup +``` + +### Service Ports + +- **ActualBudget**: 5006 +- **Keycloak**: 8180 +- **Caddy**: 80 (HTTP), 443 (HTTPS) + +## Support + +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 diff --git a/caddy-setup-helper.sh b/caddy-setup-helper.sh new file mode 100755 index 0000000..e059b28 --- /dev/null +++ b/caddy-setup-helper.sh @@ -0,0 +1,347 @@ +#!/bin/bash + +# Caddy Setup Helper Script +# This script helps manage Caddy configuration, backups, and fail2ban integration +# for dockerized Caddy setups + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo "===============================================" +echo " Caddy Configuration & Fail2ban Setup Helper" +echo "===============================================" +echo "" + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# ============================== +# 1. CHECK IF CADDY IS INSTALLED +# ============================== +print_info "Checking for Caddy installation..." + +CADDY_CONTAINER="" +CADDYFILE_PATH="" + +# Check if Docker is available +if ! command -v docker &> /dev/null; then + print_error "Docker is not installed or not in PATH" + exit 1 +fi + +# Try to find Caddy container +if docker ps --format '{{.Names}}' | grep -q "caddy"; then + CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep "caddy" | head -1) + print_success "Found running Caddy container: $CADDY_CONTAINER" +else + print_warning "No running Caddy container found" + read -p "Is Caddy installed? (y/n): " CADDY_INSTALLED + if [ "$CADDY_INSTALLED" != "y" ] && [ "$CADDY_INSTALLED" != "Y" ]; then + print_info "Caddy is not installed. Please install Caddy first." + echo "" + echo "To install Caddy with Docker:" + echo " mkdir -p ~/docker/caddy" + echo " cd ~/docker/caddy" + echo " # Create docker-compose.yml (see example below)" + echo " docker compose up -d" + exit 0 + fi +fi + +# ============================== +# 2. LOCATE CADDYFILE +# ============================== +print_info "Locating Caddyfile..." + +# Common Caddyfile locations +POSSIBLE_PATHS=( + "$HOME/docker/caddy/Caddyfile" + "$HOME/docker/caddy/caddyfile" + "$HOME/docker/caddy/config/Caddyfile" + "/etc/caddy/Caddyfile" +) + +for path in "${POSSIBLE_PATHS[@]}"; do + if [ -f "$path" ]; then + CADDYFILE_PATH="$path" + print_success "Found Caddyfile at: $CADDYFILE_PATH" + break + fi +done + +if [ -z "$CADDYFILE_PATH" ]; then + print_warning "Caddyfile not found in common locations" + read -p "Enter path to Caddyfile (or press Enter to skip): " CUSTOM_PATH + if [ -n "$CUSTOM_PATH" ] && [ -f "$CUSTOM_PATH" ]; then + CADDYFILE_PATH="$CUSTOM_PATH" + print_success "Using Caddyfile at: $CADDYFILE_PATH" + else + print_error "Cannot proceed without Caddyfile location" + exit 1 + fi +fi + +# ============================== +# 3. BACKUP CADDYFILE +# ============================== +print_info "Creating backup of Caddyfile..." + +BACKUP_DIR=$(dirname "$CADDYFILE_PATH")/backups +mkdir -p "$BACKUP_DIR" + +BACKUP_FILE="$BACKUP_DIR/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)" +cp "$CADDYFILE_PATH" "$BACKUP_FILE" +print_success "Backup created: $BACKUP_FILE" + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " BACKUP RESTORE INSTRUCTIONS" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "To restore this backup:" +echo " 1. Copy backup to original location:" +echo " cp $BACKUP_FILE $CADDYFILE_PATH" +echo "" +echo " 2. Reload Caddy configuration:" +if [ -n "$CADDY_CONTAINER" ]; then + echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" + echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy fmt --overwrite" +else + echo " docker exec -w /etc/caddy caddy caddy reload" + echo " docker exec -w /etc/caddy caddy caddy fmt --overwrite" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# ============================== +# 4. CHECK FOR FAIL2BAN SUPPORT +# ============================== +print_info "Checking Caddy configuration for fail2ban support..." + +NEEDS_FAIL2BAN_CONFIG=false + +# Check if Caddyfile has access logging configured +if ! grep -q "log {" "$CADDYFILE_PATH" && ! grep -q "log " "$CADDYFILE_PATH"; then + print_warning "No logging configuration found in Caddyfile" + NEEDS_FAIL2BAN_CONFIG=true +else + print_success "Logging configuration found" +fi + +# Check for common security headers +if ! grep -q "header" "$CADDYFILE_PATH"; then + print_warning "No security headers configured" +fi + +# ============================== +# 5. OFFER TO ADD FAIL2BAN SUPPORT +# ============================== +if [ "$NEEDS_FAIL2BAN_CONFIG" = true ]; then + echo "" + read -p "Would you like to add fail2ban logging support to Caddyfile? (y/n): " ADD_FAIL2BAN + + if [ "$ADD_FAIL2BAN" = "y" ] || [ "$ADD_FAIL2BAN" = "Y" ]; then + print_info "Adding fail2ban support..." + + # Create a new Caddyfile with fail2ban support + TEMP_CADDYFILE="${CADDYFILE_PATH}.tmp" + + # Add global options if not present + if ! grep -q "{" "$CADDYFILE_PATH" | head -1 | grep -q "^{"; then + cat > "$TEMP_CADDYFILE" << 'EOF' +{ + # Global options + admin off + # Persist config + persist_config off +} + +EOF + fi + + # Append original content + cat "$CADDYFILE_PATH" >> "$TEMP_CADDYFILE" + + # Move temp file to original + mv "$TEMP_CADDYFILE" "$CADDYFILE_PATH" + + print_success "Added global configuration" + print_info "Note: You'll need to add logging to individual sites" + fi +fi + +# ============================== +# 6. PROVIDE FAIL2BAN CONFIGURATION +# ============================== +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " FAIL2BAN CONFIGURATION FOR CADDY" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "To enable fail2ban protection for your Caddy services:" +echo "" +echo "1. Add logging to each site in your Caddyfile:" +echo "" +cat << 'EOF' +yourdomain.com { + # Enable structured logging for fail2ban + log { + output file /var/log/caddy/access.log + format json + level INFO + } + + # Reverse proxy or other directives + reverse_proxy localhost:8080 +} +EOF + +echo "" +echo "2. Create fail2ban filter at /etc/fail2ban/filter.d/caddy-auth.conf:" +echo "" +cat << 'EOF' +[Definition] +failregex = ^.*"remote_ip":"".*"status":(?:401|403|429).*$ + ^.*"remote_addr":".*"status":(?:401|403|429).*$ +ignoreregex = +EOF + +echo "" +echo "3. Create fail2ban jail at /etc/fail2ban/jail.d/caddy.conf:" +echo "" +cat << 'EOF' +[caddy-auth] +enabled = true +port = http,https +filter = caddy-auth +logpath = /var/log/caddy/access.log +maxretry = 5 +findtime = 600 +bantime = 3600 +action = iptables-multiport[name=CaddyAuth, port="http,https", protocol=tcp] +EOF + +echo "" +echo "4. Restart fail2ban:" +echo " sudo systemctl restart fail2ban" +echo " sudo fail2ban-client status caddy-auth" +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# ============================== +# 7. EXAMPLE SITE CONFIGURATIONS +# ============================== +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " ADDING NEW SERVICES TO CADDY" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "Add these blocks to your Caddyfile for your new services:" +echo "" + +# ActualBudget example +echo "# ActualBudget (Personal Finance)" +cat << 'EOF' +budget.yourdomain.com { + log { + output file /var/log/caddy/actualbudget-access.log + format json + level INFO + } + + reverse_proxy localhost:5006 + + # Security headers + header { + # Enable HSTS + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + # Prevent clickjacking + X-Frame-Options "SAMEORIGIN" + # Prevent MIME type sniffing + X-Content-Type-Options "nosniff" + # XSS protection + X-XSS-Protection "1; mode=block" + # Referrer policy + Referrer-Policy "strict-origin-when-cross-origin" + } +} +EOF + +echo "" +echo "# Keycloak (Identity & Access Management)" +cat << 'EOF' +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" + } +} +EOF + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# ============================== +# 8. RELOAD CADDY +# ============================== +if [ -n "$CADDY_CONTAINER" ]; then + read -p "Would you like to reload Caddy configuration now? (y/n): " RELOAD_NOW + + if [ "$RELOAD_NOW" = "y" ] || [ "$RELOAD_NOW" = "Y" ]; then + print_info "Reloading Caddy configuration..." + + if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then + print_success "Caddyfile formatted" + fi + + if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then + print_success "Caddy configuration reloaded successfully" + else + print_error "Failed to reload Caddy configuration" + print_info "Check Caddy logs: docker logs $CADDY_CONTAINER" + fi + fi +fi + +echo "" +print_success "Caddy setup helper completed!" +echo "" +echo "Next steps:" +echo " 1. Review and edit Caddyfile: $CADDYFILE_PATH" +echo " 2. Add site configurations for ActualBudget and Keycloak (see examples above)" +echo " 3. Set up fail2ban filters and jails (see instructions above)" +echo " 4. Test Caddy configuration: docker exec $CADDY_CONTAINER caddy validate --config /etc/caddy/Caddyfile" +echo " 5. Reload Caddy: docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" +echo "" diff --git a/docker-compose-actualbudget.yml b/docker-compose-actualbudget.yml new file mode 100644 index 0000000..b51ccae --- /dev/null +++ b/docker-compose-actualbudget.yml @@ -0,0 +1,60 @@ +# ActualBudget - Open-source personal finance management +# https://actualbudget.org/ +# +# DEPLOYMENT INSTRUCTIONS: +# 1. Create directory: mkdir -p ~/docker/actualbudget +# 2. Copy this file: cp docker-compose-actualbudget.yml ~/docker/actualbudget/docker-compose.yml +# 3. Create data directory: mkdir -p ~/docker/actualbudget/data +# 4. Start the service: cd ~/docker/actualbudget && docker compose up -d +# 5. Access at: http://localhost:5006 +# +# REVERSE PROXY SETUP (with Caddy): +# Add to your Caddyfile: +# budget.yourdomain.com { +# reverse_proxy localhost:5006 +# } +# +# BANK ACCOUNT SYNC: +# ActualBudget supports SimpleFIN (https://simplefin.org/) for bank synchronization. +# You'll need a SimpleFIN account to sync with your bank accounts. +# Setup instructions: https://actualbudget.org/docs/advanced/bank-sync + +name: actualbudget + +services: + actualbudget: + image: actualbudget/actual-server:latest + container_name: actualbudget + restart: unless-stopped + ports: + - "5006:5006" + volumes: + - ./data:/data + environment: + # Set your timezone + - TZ=UTC + # Uncomment and set these for HTTPS/production deployment: + # - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20 + # - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50 + # - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20 + labels: + # Fail2ban support - logs HTTP requests + - "io.podman.annotations.label/fail2ban.enable=true" + - "io.podman.annotations.label/fail2ban.filter=caddy-auth" + +# NOTES: +# - Default port: 5006 +# - Data stored in: ./data +# - First run: Create an account at http://localhost:5006 +# - Bank sync requires SimpleFIN: https://simplefin.org/ +# - Official docs: https://actualbudget.org/docs/ +# +# BACKUP YOUR DATA: +# Regular backups are important! ActualBudget stores data in SQLite. +# docker compose down +# cp -r data data-backup-$(date +%Y%m%d) +# docker compose up -d +# +# UPDATES: +# docker compose pull +# docker compose up -d diff --git a/docker-compose-keycloak.yml b/docker-compose-keycloak.yml new file mode 100644 index 0000000..010de83 --- /dev/null +++ b/docker-compose-keycloak.yml @@ -0,0 +1,143 @@ +# 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. IMPORTANT: Update KEYCLOAK_ADMIN_PASSWORD below! +# 4. Start the service: cd ~/docker/keycloak && docker compose up -d +# 5. Access at: http://localhost:8180/admin (admin console) +# +# 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 + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: keycloak_db_password_CHANGE_THIS + 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 + environment: + # Admin credentials - CHANGE THESE! + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=CHANGE_THIS_SECURE_PASSWORD + + # Database configuration (PostgreSQL) + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak + - KC_DB_USERNAME=keycloak + - KC_DB_PASSWORD=keycloak_db_password_CHANGE_THIS + + # Hostname configuration + # For production, set to your domain: + # - KC_HOSTNAME=auth.yourdomain.com + # - KC_HOSTNAME_STRICT=true + - KC_HOSTNAME_STRICT=false + + # Proxy configuration (required when behind Caddy/nginx) + - KC_PROXY=edge + - KC_HTTP_ENABLED=true + + # Logging + - KC_LOG_LEVEL=INFO + + # Health check + - KC_HEALTH_ENABLED=true + - KC_METRICS_ENABLED=true + 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 +# - Default credentials: admin / CHANGE_THIS_SECURE_PASSWORD +# - Database: PostgreSQL (persistent data in ./postgres-data) +# - For H2 database (dev only), remove postgres service and database env vars +# +# FIRST-TIME SETUP: +# 1. Login to admin console +# 2. Create a realm (e.g., "myrealm") +# 3. Create clients for your applications +# 4. Configure authentication flows +# 5. Add users or configure identity providers (LDAP, SAML, OAuth) +# +# COMMON USE CASES: +# - Single Sign-On (SSO) for multiple applications +# - OAuth2/OIDC provider for custom apps +# - SAML 2.0 identity provider +# - User federation with LDAP/Active Directory +# - Multi-factor authentication (MFA/2FA) +# - Social login (Google, GitHub, etc.) +# +# PRODUCTION CHECKLIST: +# [ ] Change admin password +# [ ] Change database password +# [ ] Set KC_HOSTNAME to your domain +# [ ] Use 'start' instead of 'start-dev' command +# [ ] Configure HTTPS via reverse proxy (Caddy) +# [ ] Enable hostname strict mode +# [ ] Configure backup strategy for PostgreSQL +# [ ] Set up monitoring (metrics on port 9000) +# +# 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/fail2ban-caddy-filter.conf b/fail2ban-caddy-filter.conf new file mode 100644 index 0000000..157fc71 --- /dev/null +++ b/fail2ban-caddy-filter.conf @@ -0,0 +1,49 @@ +# Fail2ban filter for Caddy web server +# +# INSTALLATION: +# 1. Copy this file to: /etc/fail2ban/filter.d/caddy-auth.conf +# sudo cp fail2ban-caddy-filter.conf /etc/fail2ban/filter.d/caddy-auth.conf +# +# 2. Create jail configuration at: /etc/fail2ban/jail.d/caddy.conf +# (See fail2ban-caddy-jail.conf in this directory) +# +# 3. Ensure Caddy is logging in JSON format to /var/log/caddy/access.log +# (See caddy-setup-helper.sh for configuration examples) +# +# 4. Restart fail2ban: +# sudo systemctl restart fail2ban +# +# 5. Check status: +# sudo fail2ban-client status caddy-auth + +[INCLUDES] +before = common.conf + +[Definition] + +# Match failed authentication attempts and forbidden access +# Caddy JSON log format: {"remote_ip":"1.2.3.4","status":401,...} +failregex = ^.*"remote_ip":"".*"status":(?:401|403|429).*$ + ^.*"remote_addr":".*"status":(?:401|403|429).*$ + ^.*"client_ip":"".*"status":(?:401|403|429).*$ + +# Ignore localhost and common false positives +ignoreregex = ^.*"remote_ip":"(?:127\.0\.0\.1|::1)".*$ + ^.*"remote_addr":"(?:127\.0\.0\.1|::1)".*$ + +# Optional: Date/time pattern for log analysis +# Most Caddy JSON logs include "ts" field with Unix timestamp +datepattern = "ts":%%s + +[Init] +journalmatch = _SYSTEMD_UNIT=caddy.service + +# NOTES: +# - This filter looks for HTTP status codes: +# 401 = Unauthorized (failed authentication) +# 403 = Forbidden (access denied) +# 429 = Too Many Requests (rate limiting) +# +# - Adjust the status codes based on your needs +# - For more aggressive blocking, add: 404|500 +# - Test the filter: fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-auth.conf diff --git a/fail2ban-caddy-jail.conf b/fail2ban-caddy-jail.conf new file mode 100644 index 0000000..d334652 --- /dev/null +++ b/fail2ban-caddy-jail.conf @@ -0,0 +1,82 @@ +# Fail2ban jail configuration for Caddy web server +# +# INSTALLATION: +# 1. Copy this file to: /etc/fail2ban/jail.d/caddy.conf +# sudo cp fail2ban-caddy-jail.conf /etc/fail2ban/jail.d/caddy.conf +# +# 2. Ensure the filter is installed: +# sudo cp fail2ban-caddy-filter.conf /etc/fail2ban/filter.d/caddy-auth.conf +# +# 3. Create log directory if it doesn't exist: +# sudo mkdir -p /var/log/caddy +# sudo chown caddy:caddy /var/log/caddy # Or appropriate user +# +# 4. Restart fail2ban: +# sudo systemctl restart fail2ban +# +# 5. Check status: +# sudo fail2ban-client status caddy-auth + +[caddy-auth] +# Enable this jail +enabled = true + +# Ports to protect (HTTP and HTTPS) +port = http,https + +# Filter to use (must match filename in /etc/fail2ban/filter.d/) +filter = caddy-auth + +# Log file to monitor +# Adjust this path if your Caddy logs are elsewhere +logpath = /var/log/caddy/access.log + /var/log/caddy/*-access.log + +# For Docker Caddy, you might need to use Docker logs: +# logpath = /var/lib/docker/containers/*-caddy*/*.log + +# Maximum retry before ban +# 5 attempts within findtime period will trigger a ban +maxretry = 5 + +# Time window (seconds) to count failures +# 600 = 10 minutes +findtime = 600 + +# Ban duration (seconds) +# 3600 = 1 hour +# 86400 = 24 hours +bantime = 3600 + +# Action to take when banning +# iptables-multiport: Block on multiple ports +action = iptables-multiport[name=CaddyAuth, port="http,https", protocol=tcp] + # Optional: Send email notification + # sendmail-whois[name=CaddyAuth, dest=admin@yourdomain.com] + +# Backend to use for monitoring log file +# auto = automatically detect (systemd journal or file polling) +backend = auto + +# OPTIONAL SETTINGS: + +# Increase ban time on repeat offenders +# First ban: 1 hour, second: 24 hours, third: 1 week +# bantime.increment = true +# bantime.factor = 24 +# bantime.maxtime = 604800 # 1 week max + +# Find all jails using this ban +# This enables ban synchronization across jails +# banaction_allports = iptables-allports + +# NOTES: +# - Adjust maxretry, findtime, and bantime based on your security needs +# - More aggressive: maxretry=3, findtime=300, bantime=86400 +# - More lenient: maxretry=10, findtime=1200, bantime=1800 +# +# TESTING: +# - Check if jail is running: sudo fail2ban-client status caddy-auth +# - View banned IPs: sudo fail2ban-client get caddy-auth banip +# - Unban an IP: sudo fail2ban-client set caddy-auth unbanip 1.2.3.4 +# - Test filter: fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-auth.conf diff --git a/ubuntu-post-install.sh b/ubuntu-post-install.sh index 4b0ea45..dd3ecb2 100644 --- a/ubuntu-post-install.sh +++ b/ubuntu-post-install.sh @@ -2763,17 +2763,11 @@ MM_COMPOSE cd .. # Run npm install for each downloaded module + # Note: This runs AFTER container is started, inside the container echo "" - echo " Installing npm dependencies for modules..." - for mod_dir in modules/MMM-*/; do - if [ -d "$mod_dir" ] && [ -f "$mod_dir/package.json" ]; then - mod_name=$(basename "$mod_dir") - echo " Installing dependencies for $mod_name..." - (cd "$mod_dir" && npm install --production 2>/dev/null) && \ - echo " ✓ $mod_name dependencies installed" || \ - echo " ⚠ $mod_name - npm install failed (may work anyway)" - fi - done + echo " Note: Module dependencies will be installed when container starts" + echo " If you need to manually install module dependencies, run:" + echo " docker exec magicmirror-$MM_PORT sh -c 'cd /opt/magic_mirror/modules/ && npm install --production'" echo "" fi else @@ -2877,6 +2871,21 @@ MM_CONFIG MM_PORT=$((8080 + i)) MM_DIR="$DOCKER_DIR/magicmirror-$MM_PORT" (cd "$MM_DIR" && docker compose up -d 2>/dev/null) && echo " ✓ Magic Mirror #$i started (port $MM_PORT)" || echo " ⚠ Failed to start Magic Mirror #$i" + + # Install npm dependencies for third-party modules inside container + if [ -d "$MM_DIR/modules" ]; then + echo " Installing module dependencies inside container..." + sleep 3 # Wait for container to fully start + for mod_dir in "$MM_DIR/modules"/MMM-*/; do + if [ -d "$mod_dir" ] && [ -f "$mod_dir/package.json" ]; then + mod_name=$(basename "$mod_dir") + echo " Installing $mod_name dependencies..." + docker exec magicmirror-$MM_PORT sh -c "cd /opt/magic_mirror/modules/$mod_name && npm install --production" 2>/dev/null && \ + echo " ✓ $mod_name dependencies installed" || \ + echo " ⚠ $mod_name - npm install failed (container may need restart)" + fi + done + fi done fi @@ -2886,6 +2895,162 @@ MM_CONFIG fi fi + # ---- ACTUALBUDGET ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ ACTUALBUDGET - Open-source Personal Finance Management │" + echo "│ Budget tracking with bank account synchronization via SimpleFIN│" + echo "│ Port: 5006 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install ActualBudget? (y/n):" "n" INSTALL_ACTUALBUDGET + + if [ "$INSTALL_ACTUALBUDGET" = "y" ] || [ "$INSTALL_ACTUALBUDGET" = "Y" ]; then + AB_DIR="$DOCKER_DIR/actualbudget" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $AB_DIR" + else + echo "Installing ActualBudget..." + mkdir -p "$AB_DIR/data" + cd "$AB_DIR" + + cat > docker-compose.yml << 'AB_COMPOSE' +name: actualbudget + +services: + actualbudget: + image: actualbudget/actual-server:latest + container_name: actualbudget + restart: unless-stopped + ports: + - "5006:5006" + volumes: + - ./data:/data + environment: + - TZ=UTC + labels: + - "io.podman.annotations.label/fail2ban.enable=true" + - "io.podman.annotations.label/fail2ban.filter=caddy-auth" +AB_COMPOSE + + echo " ✓ ActualBudget configured at $AB_DIR" + + prompt_yn "Start ActualBudget now? (y/n):" "y" START_AB + if [ "$START_AB" = "y" ] || [ "$START_AB" = "Y" ]; then + docker compose up -d 2>/dev/null && echo " ✓ ActualBudget started" || echo " ⚠ Failed to start ActualBudget" + fi + + echo "" + echo " Access at: http://localhost:5006" + echo " Bank sync: https://simplefin.org/ (SimpleFIN account required)" + echo " Data dir: $AB_DIR/data" + echo "" + fi + fi + + # ---- KEYCLOAK ---- + 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 + + 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 "⚠ SECURITY WARNING:" + echo " You MUST change the default admin password!" + echo "" + + read -p "Enter Keycloak admin password [admin123]: " KC_ADMIN_PASS + KC_ADMIN_PASS=${KC_ADMIN_PASS:-admin123} + + read -p "Enter database password [keycloak_db_pass]: " KC_DB_PASS + KC_DB_PASS=${KC_DB_PASS:-keycloak_db_pass} + + mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data" + cd "$KC_DIR" + + cat > docker-compose.yml << KC_COMPOSE +name: keycloak + +services: + postgres: + image: postgres:16-alpine + container_name: keycloak-db + restart: unless-stopped + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: $KC_DB_PASS + 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 + environment: + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak + - KC_DB_USERNAME=keycloak + - KC_DB_PASSWORD=$KC_DB_PASS + - KC_HOSTNAME_STRICT=false + - KC_PROXY=edge + - KC_HTTP_ENABLED=true + - KC_LOG_LEVEL=INFO + - KC_HEALTH_ENABLED=true + - KC_METRICS_ENABLED=true + 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 " ✓ Keycloak configured at $KC_DIR" + + 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" + fi + + echo "" + echo " Admin console: http://localhost:8180/admin" + echo " Username: admin" + echo " Password: $KC_ADMIN_PASS" + echo " Database: PostgreSQL (./postgres-data)" + 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 + # ---- LYRION MUSIC SERVER ---- echo "" echo "┌─────────────────────────────────────────────────────────────────┐" From d3f412dc282a341d7f053fde8e794d1630edf6db Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 22:50:30 +0000 Subject: [PATCH 2/2] Make fail2ban setup fully automated with intelligent error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caddy-setup-helper.sh script now handles everything automatically (after asking for confirmation), only falling back to manual instructions if errors occur. AUTOMATED WORKFLOW: 1. ✅ Backup Caddyfile (ALWAYS FIRST - before any changes) 2. ✅ Check if fail2ban is installed 3. ✅ Install fail2ban if missing (with confirmation) 4. ✅ Create /var/log/caddy directory 5. ✅ Check if Caddy container has log volume mounted 6. ✅ Automatically add log volume to docker-compose.yml if needed 7. ✅ Create fail2ban filter at /etc/fail2ban/filter.d/caddy-auth.conf 8. ✅ Create fail2ban jail at /etc/fail2ban/jail.d/caddy.conf (with custom settings) 9. ✅ Test fail2ban configuration 10. ✅ Restart fail2ban and verify jail is active 11. ✅ Add service configurations (ActualBudget, Keycloak) to Caddyfile 12. ✅ Validate and reload Caddy configuration ERROR HANDLING: - All operations tracked with error messages array - If any step fails, script continues but tracks the failure - At the end, shows all errors encountered - Provides exact manual commands to fix issues - Backup is ALWAYS created before any changes USER EXPERIENCE: - Interactive prompts with sensible defaults - Clear colored output (INFO, SUCCESS, WARNING, ERROR) - Progress feedback at each step - Final summary with useful commands - Only shows manual instructions if automation failed SAFETY FEATURES: - Caddyfile backup before ANY modifications - docker-compose.yml backup before modifications - Validation before reloading Caddy - Test fail2ban config before restart - Restore instructions always shown after backup This matches the integrated experience of other services - fully automated unless something goes wrong, in which case it provides manual steps. --- caddy-setup-helper.sh | 540 +++++++++++++++++++++++++++++------------- 1 file changed, 370 insertions(+), 170 deletions(-) diff --git a/caddy-setup-helper.sh b/caddy-setup-helper.sh index e059b28..efc9dfc 100755 --- a/caddy-setup-helper.sh +++ b/caddy-setup-helper.sh @@ -2,7 +2,7 @@ # Caddy Setup Helper Script # This script helps manage Caddy configuration, backups, and fail2ban integration -# for dockerized Caddy setups +# for dockerized Caddy setups - FULLY AUTOMATED with error handling set -e @@ -35,6 +35,27 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1" } +# Function to ask yes/no questions +ask_yn() { + local prompt="$1" + local default="${2:-n}" + local response + + if [ "$default" = "y" ]; then + read -p "$prompt [Y/n]: " response + response=${response:-y} + else + read -p "$prompt [y/N]: " response + response=${response:-n} + fi + + [[ "$response" =~ ^[Yy]$ ]] +} + +# Track if we need to show manual instructions +SHOW_MANUAL=false +ERROR_MESSAGES=() + # ============================== # 1. CHECK IF CADDY IS INSTALLED # ============================== @@ -50,20 +71,15 @@ if ! command -v docker &> /dev/null; then fi # Try to find Caddy container -if docker ps --format '{{.Names}}' | grep -q "caddy"; then - CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep "caddy" | head -1) +if docker ps --format '{{.Names}}' | grep -iq "caddy"; then + CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep -i "caddy" | head -1) print_success "Found running Caddy container: $CADDY_CONTAINER" else print_warning "No running Caddy container found" - read -p "Is Caddy installed? (y/n): " CADDY_INSTALLED - if [ "$CADDY_INSTALLED" != "y" ] && [ "$CADDY_INSTALLED" != "Y" ]; then + if ! ask_yn "Is Caddy installed?" "n"; then print_info "Caddy is not installed. Please install Caddy first." echo "" - echo "To install Caddy with Docker:" - echo " mkdir -p ~/docker/caddy" - echo " cd ~/docker/caddy" - echo " # Create docker-compose.yml (see example below)" - echo " docker compose up -d" + echo "To install Caddy with Docker, see CADDY-FAIL2BAN-SETUP.md" exit 0 fi fi @@ -91,7 +107,7 @@ done if [ -z "$CADDYFILE_PATH" ]; then print_warning "Caddyfile not found in common locations" - read -p "Enter path to Caddyfile (or press Enter to skip): " CUSTOM_PATH + read -p "Enter path to Caddyfile: " CUSTOM_PATH if [ -n "$CUSTOM_PATH" ] && [ -f "$CUSTOM_PATH" ]; then CADDYFILE_PATH="$CUSTOM_PATH" print_success "Using Caddyfile at: $CADDYFILE_PATH" @@ -101,12 +117,14 @@ if [ -z "$CADDYFILE_PATH" ]; then fi fi +CADDY_DIR=$(dirname "$CADDYFILE_PATH") + # ============================== -# 3. BACKUP CADDYFILE +# 3. BACKUP CADDYFILE (ALWAYS FIRST!) # ============================== print_info "Creating backup of Caddyfile..." -BACKUP_DIR=$(dirname "$CADDYFILE_PATH")/backups +BACKUP_DIR="$CADDY_DIR/backups" mkdir -p "$BACKUP_DIR" BACKUP_FILE="$BACKUP_DIR/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)" @@ -118,150 +136,274 @@ echo "━━━━━━━━━━━━━━━━━━━━━━━━ echo " BACKUP RESTORE INSTRUCTIONS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "To restore this backup:" -echo " 1. Copy backup to original location:" -echo " cp $BACKUP_FILE $CADDYFILE_PATH" -echo "" -echo " 2. Reload Caddy configuration:" +echo " cp $BACKUP_FILE $CADDYFILE_PATH" if [ -n "$CADDY_CONTAINER" ]; then - echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" - echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy fmt --overwrite" -else - echo " docker exec -w /etc/caddy caddy caddy reload" - echo " docker exec -w /etc/caddy caddy caddy fmt --overwrite" + echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" + echo " docker exec -w /etc/caddy $CADDY_CONTAINER caddy fmt --overwrite" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # ============================== -# 4. CHECK FOR FAIL2BAN SUPPORT +# 4. CHECK FOR FAIL2BAN INSTALLATION # ============================== -print_info "Checking Caddy configuration for fail2ban support..." +print_info "Checking for fail2ban installation..." -NEEDS_FAIL2BAN_CONFIG=false - -# Check if Caddyfile has access logging configured -if ! grep -q "log {" "$CADDYFILE_PATH" && ! grep -q "log " "$CADDYFILE_PATH"; then - print_warning "No logging configuration found in Caddyfile" - NEEDS_FAIL2BAN_CONFIG=true +FAIL2BAN_INSTALLED=false +if command -v fail2ban-client &> /dev/null; then + print_success "fail2ban is installed" + FAIL2BAN_INSTALLED=true else - print_success "Logging configuration found" -fi + print_warning "fail2ban is not installed" -# Check for common security headers -if ! grep -q "header" "$CADDYFILE_PATH"; then - print_warning "No security headers configured" -fi + if ask_yn "Would you like to install fail2ban now?" "y"; then + print_info "Installing fail2ban..." -# ============================== -# 5. OFFER TO ADD FAIL2BAN SUPPORT -# ============================== -if [ "$NEEDS_FAIL2BAN_CONFIG" = true ]; then - echo "" - read -p "Would you like to add fail2ban logging support to Caddyfile? (y/n): " ADD_FAIL2BAN - - if [ "$ADD_FAIL2BAN" = "y" ] || [ "$ADD_FAIL2BAN" = "Y" ]; then - print_info "Adding fail2ban support..." - - # Create a new Caddyfile with fail2ban support - TEMP_CADDYFILE="${CADDYFILE_PATH}.tmp" - - # Add global options if not present - if ! grep -q "{" "$CADDYFILE_PATH" | head -1 | grep -q "^{"; then - cat > "$TEMP_CADDYFILE" << 'EOF' -{ - # Global options - admin off - # Persist config - persist_config off -} - -EOF + if sudo apt update && sudo apt install -y fail2ban; then + print_success "fail2ban installed successfully" + FAIL2BAN_INSTALLED=true + else + print_error "Failed to install fail2ban" + ERROR_MESSAGES+=("Failed to install fail2ban - you may need to install it manually") + SHOW_MANUAL=true fi - - # Append original content - cat "$CADDYFILE_PATH" >> "$TEMP_CADDYFILE" - - # Move temp file to original - mv "$TEMP_CADDYFILE" "$CADDYFILE_PATH" - - print_success "Added global configuration" - print_info "Note: You'll need to add logging to individual sites" + else + print_warning "Skipping fail2ban installation" + SHOW_MANUAL=true fi fi # ============================== -# 6. PROVIDE FAIL2BAN CONFIGURATION +# 5. CREATE LOG DIRECTORY # ============================== -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo " FAIL2BAN CONFIGURATION FOR CADDY" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" -echo "To enable fail2ban protection for your Caddy services:" -echo "" -echo "1. Add logging to each site in your Caddyfile:" -echo "" -cat << 'EOF' -yourdomain.com { - # Enable structured logging for fail2ban - log { - output file /var/log/caddy/access.log - format json - level INFO - } +if [ "$FAIL2BAN_INSTALLED" = true ]; then + print_info "Checking Caddy log directory..." - # Reverse proxy or other directives - reverse_proxy localhost:8080 -} -EOF + LOG_DIR="/var/log/caddy" + if [ ! -d "$LOG_DIR" ]; then + if ask_yn "Create $LOG_DIR for Caddy logs?" "y"; then + if sudo mkdir -p "$LOG_DIR" && sudo chmod 755 "$LOG_DIR"; then + print_success "Log directory created: $LOG_DIR" + else + print_error "Failed to create log directory" + ERROR_MESSAGES+=("Failed to create $LOG_DIR - create it manually with: sudo mkdir -p $LOG_DIR && sudo chmod 755 $LOG_DIR") + SHOW_MANUAL=true + fi + fi + else + print_success "Log directory exists: $LOG_DIR" + fi +fi -echo "" -echo "2. Create fail2ban filter at /etc/fail2ban/filter.d/caddy-auth.conf:" -echo "" -cat << 'EOF' -[Definition] +# ============================== +# 6. CHECK CADDY DOCKER COMPOSE FOR LOG VOLUME +# ============================== +if [ "$FAIL2BAN_INSTALLED" = true ] && [ -n "$CADDY_CONTAINER" ]; then + print_info "Checking if Caddy container has log volume mounted..." + + # Check if the container has /var/log/caddy mounted + if docker inspect "$CADDY_CONTAINER" 2>/dev/null | grep -q "/var/log/caddy"; then + print_success "Caddy container has log volume mounted" + else + print_warning "Caddy container does not have /var/log/caddy volume mounted" + + # Check if there's a docker-compose.yml + COMPOSE_FILE="" + for file in "$CADDY_DIR/docker-compose.yml" "$CADDY_DIR/docker-compose.yaml"; do + if [ -f "$file" ]; then + COMPOSE_FILE="$file" + break + fi + done + + if [ -n "$COMPOSE_FILE" ]; then + if ask_yn "Add /var/log/caddy volume to docker-compose.yml?" "y"; then + # Backup docker-compose.yml + cp "$COMPOSE_FILE" "$COMPOSE_FILE.backup.$(date +%Y%m%d_%H%M%S)" + + # Check if volumes section exists + if grep -q "volumes:" "$COMPOSE_FILE"; then + # Add to existing volumes + if ! grep -q "/var/log/caddy" "$COMPOSE_FILE"; then + # Find the volumes section and add our volume + sed -i '/volumes:/a\ - /var/log/caddy:/var/log/caddy' "$COMPOSE_FILE" + print_success "Added log volume to docker-compose.yml" + print_warning "You'll need to restart the Caddy container for this to take effect" + + if ask_yn "Restart Caddy container now?" "n"; then + cd "$CADDY_DIR" + if docker compose down && docker compose up -d; then + print_success "Caddy container restarted" + # Update CADDY_CONTAINER name in case it changed + CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep -i "caddy" | head -1) + else + print_error "Failed to restart Caddy container" + ERROR_MESSAGES+=("Failed to restart Caddy - restart manually with: cd $CADDY_DIR && docker compose restart") + fi + fi + fi + else + print_warning "Could not automatically add volume - docker-compose.yml format is unexpected" + ERROR_MESSAGES+=("Add this volume manually to your Caddy service: /var/log/caddy:/var/log/caddy") + SHOW_MANUAL=true + fi + fi + else + print_warning "No docker-compose.yml found - you may need to add the volume manually" + ERROR_MESSAGES+=("Add log volume to Caddy container: /var/log/caddy:/var/log/caddy") + SHOW_MANUAL=true + fi + fi +fi + +# ============================== +# 7. CREATE FAIL2BAN FILTER +# ============================== +if [ "$FAIL2BAN_INSTALLED" = true ]; then + print_info "Checking fail2ban filter configuration..." + + FILTER_FILE="/etc/fail2ban/filter.d/caddy-auth.conf" + if [ -f "$FILTER_FILE" ]; then + print_success "fail2ban filter already exists: $FILTER_FILE" + else + if ask_yn "Create fail2ban filter for Caddy?" "y"; then + print_info "Creating fail2ban filter..." + + FILTER_CONTENT='[Definition] failregex = ^.*"remote_ip":"".*"status":(?:401|403|429).*$ ^.*"remote_addr":".*"status":(?:401|403|429).*$ -ignoreregex = -EOF +ignoreregex = ^.*"remote_ip":"(?:127\.0\.0\.1|::1)".*$ +datepattern = "ts":%%s' -echo "" -echo "3. Create fail2ban jail at /etc/fail2ban/jail.d/caddy.conf:" -echo "" -cat << 'EOF' -[caddy-auth] + if echo "$FILTER_CONTENT" | sudo tee "$FILTER_FILE" > /dev/null; then + print_success "Created fail2ban filter: $FILTER_FILE" + else + print_error "Failed to create fail2ban filter" + ERROR_MESSAGES+=("Failed to create $FILTER_FILE - create it manually (see CADDY-FAIL2BAN-SETUP.md)") + SHOW_MANUAL=true + fi + fi + fi +fi + +# ============================== +# 8. CREATE FAIL2BAN JAIL +# ============================== +if [ "$FAIL2BAN_INSTALLED" = true ]; then + print_info "Checking fail2ban jail configuration..." + + JAIL_FILE="/etc/fail2ban/jail.d/caddy.conf" + if [ -f "$JAIL_FILE" ]; then + print_success "fail2ban jail already exists: $JAIL_FILE" + else + if ask_yn "Create fail2ban jail for Caddy?" "y"; then + print_info "Creating fail2ban jail..." + + # Ask for custom settings + echo "" + print_info "Fail2ban jail settings (press Enter for defaults):" + read -p " Max retries before ban [5]: " MAXRETRY + MAXRETRY=${MAXRETRY:-5} + + read -p " Find time window in seconds [600]: " FINDTIME + FINDTIME=${FINDTIME:-600} + + read -p " Ban duration in seconds [3600]: " BANTIME + BANTIME=${BANTIME:-3600} + + JAIL_CONTENT="[caddy-auth] enabled = true port = http,https filter = caddy-auth logpath = /var/log/caddy/access.log -maxretry = 5 -findtime = 600 -bantime = 3600 -action = iptables-multiport[name=CaddyAuth, port="http,https", protocol=tcp] -EOF + /var/log/caddy/*-access.log +maxretry = $MAXRETRY +findtime = $FINDTIME +bantime = $BANTIME +action = iptables-multiport[name=CaddyAuth, port=\"http,https\", protocol=tcp] +backend = auto" -echo "" -echo "4. Restart fail2ban:" -echo " sudo systemctl restart fail2ban" -echo " sudo fail2ban-client status caddy-auth" -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + if echo "$JAIL_CONTENT" | sudo tee "$JAIL_FILE" > /dev/null; then + print_success "Created fail2ban jail: $JAIL_FILE" + else + print_error "Failed to create fail2ban jail" + ERROR_MESSAGES+=("Failed to create $JAIL_FILE - create it manually (see CADDY-FAIL2BAN-SETUP.md)") + SHOW_MANUAL=true + fi + fi + fi +fi # ============================== -# 7. EXAMPLE SITE CONFIGURATIONS +# 9. TEST FAIL2BAN CONFIGURATION +# ============================== +if [ "$FAIL2BAN_INSTALLED" = true ]; then + print_info "Testing fail2ban configuration..." + + if sudo fail2ban-client -t &> /dev/null; then + print_success "fail2ban configuration is valid" + else + print_error "fail2ban configuration has errors" + ERROR_MESSAGES+=("fail2ban configuration is invalid - check with: sudo fail2ban-client -t") + SHOW_MANUAL=true + fi +fi + +# ============================== +# 10. RESTART FAIL2BAN +# ============================== +if [ "$FAIL2BAN_INSTALLED" = true ]; then + if ask_yn "Restart fail2ban to apply changes?" "y"; then + print_info "Restarting fail2ban..." + + if sudo systemctl restart fail2ban; then + print_success "fail2ban restarted successfully" + + # Wait a moment for fail2ban to start + sleep 2 + + # Check if caddy-auth jail is running + if sudo fail2ban-client status caddy-auth &> /dev/null; then + print_success "caddy-auth jail is active" + echo "" + print_info "Jail status:" + sudo fail2ban-client status caddy-auth + else + print_warning "caddy-auth jail is not active" + ERROR_MESSAGES+=("caddy-auth jail failed to start - check with: sudo fail2ban-client status") + SHOW_MANUAL=true + fi + else + print_error "Failed to restart fail2ban" + ERROR_MESSAGES+=("Failed to restart fail2ban - check logs with: sudo journalctl -u fail2ban -n 50") + SHOW_MANUAL=true + fi + fi +fi + +# ============================== +# 11. ADD SERVICE CONFIGURATIONS TO CADDYFILE # ============================== echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo " ADDING NEW SERVICES TO CADDY" +echo " ADDING NEW SERVICES TO CADDYFILE" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -echo "Add these blocks to your Caddyfile for your new services:" + +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 "" -# ActualBudget example -echo "# ActualBudget (Personal Finance)" -cat << 'EOF' -budget.yourdomain.com { +if ask_yn "Would you like to add ActualBudget to Caddyfile?" "n"; then + read -p "Enter domain for ActualBudget (e.g., budget.yourdomain.com): " AB_DOMAIN + + if [ -n "$AB_DOMAIN" ]; then + AB_CONFIG=" +# ActualBudget - Personal Finance +$AB_DOMAIN { log { output file /var/log/caddy/actualbudget-access.log format json @@ -272,24 +414,31 @@ budget.yourdomain.com { # Security headers header { - # Enable HSTS - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - # Prevent clickjacking - X-Frame-Options "SAMEORIGIN" - # Prevent MIME type sniffing - X-Content-Type-Options "nosniff" - # XSS protection - X-XSS-Protection "1; mode=block" - # Referrer policy - Referrer-Policy "strict-origin-when-cross-origin" + 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 "" -echo "# Keycloak (Identity & Access Management)" -cat << 'EOF' -auth.yourdomain.com { + if echo "$AB_CONFIG" >> "$CADDYFILE_PATH"; then + print_success "Added ActualBudget configuration to Caddyfile" + else + print_error "Failed to add ActualBudget configuration" + ERROR_MESSAGES+=("Add ActualBudget manually - see CADDY-FAIL2BAN-SETUP.md") + fi + 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 @@ -300,48 +449,99 @@ auth.yourdomain.com { # 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" + 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 "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" - -# ============================== -# 8. RELOAD CADDY -# ============================== -if [ -n "$CADDY_CONTAINER" ]; then - read -p "Would you like to reload Caddy configuration now? (y/n): " RELOAD_NOW - - if [ "$RELOAD_NOW" = "y" ] || [ "$RELOAD_NOW" = "Y" ]; then - print_info "Reloading Caddy configuration..." - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then - print_success "Caddyfile formatted" - fi - - if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then - print_success "Caddy configuration reloaded successfully" + if echo "$KC_CONFIG" >> "$CADDYFILE_PATH"; then + print_success "Added Keycloak configuration to Caddyfile" else - print_error "Failed to reload Caddy configuration" - print_info "Check Caddy logs: docker logs $CADDY_CONTAINER" + 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 +# ============================== +if [ -n "$CADDY_CONTAINER" ]; then + echo "" + if ask_yn "Validate and reload Caddy configuration?" "y"; then + print_info "Validating Caddyfile..." + + # Format first + if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then + print_success "Caddyfile formatted" + fi + + # Validate + if docker exec "$CADDY_CONTAINER" caddy validate --config /etc/caddy/Caddyfile 2>/dev/null; then + print_success "Caddyfile is valid" + + # Reload + print_info "Reloading Caddy configuration..." + if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then + print_success "Caddy configuration reloaded successfully" + else + print_error "Failed to reload Caddy configuration" + ERROR_MESSAGES+=("Failed to reload Caddy - check logs with: docker logs $CADDY_CONTAINER") + SHOW_MANUAL=true + fi + else + print_error "Caddyfile validation failed" + ERROR_MESSAGES+=("Caddyfile has syntax errors - check with: docker exec $CADDY_CONTAINER caddy validate --config /etc/caddy/Caddyfile") + SHOW_MANUAL=true + fi + fi +fi + +# ============================== +# 13. FINAL SUMMARY +# ============================== echo "" -print_success "Caddy setup helper completed!" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " SETUP COMPLETE" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -echo "Next steps:" -echo " 1. Review and edit Caddyfile: $CADDYFILE_PATH" -echo " 2. Add site configurations for ActualBudget and Keycloak (see examples above)" -echo " 3. Set up fail2ban filters and jails (see instructions above)" -echo " 4. Test Caddy configuration: docker exec $CADDY_CONTAINER caddy validate --config /etc/caddy/Caddyfile" -echo " 5. Reload Caddy: docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" + +if [ "$SHOW_MANUAL" = true ]; then + print_warning "Some steps could not be completed automatically" + echo "" + echo "Issues encountered:" + for msg in "${ERROR_MESSAGES[@]}"; do + echo " - $msg" + done + echo "" + print_info "See CADDY-FAIL2BAN-SETUP.md for manual setup instructions" + echo "" +fi + +print_success "Caddyfile backed up to: $BACKUP_FILE" + +if [ "$FAIL2BAN_INSTALLED" = true ]; then + print_success "fail2ban is installed and configured" + echo "" + echo "Useful commands:" + echo " Check jail status: sudo fail2ban-client status caddy-auth" + echo " View banned IPs: sudo fail2ban-client get caddy-auth banip" + echo " Unban IP: sudo fail2ban-client set caddy-auth unbanip 1.2.3.4" + echo " Test filter: sudo fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-auth.conf" +fi + +echo "" +echo "Caddyfile location: $CADDYFILE_PATH" +echo "Backup location: $BACKUP_FILE" +if [ -n "$CADDY_CONTAINER" ]; then + echo "Caddy container: $CADDY_CONTAINER" + echo "Reload Caddy: docker exec -w /etc/caddy $CADDY_CONTAINER caddy reload" + echo "View Caddy logs: docker logs $CADDY_CONTAINER --tail 50" +fi +echo "" +print_success "All done!" echo ""