From 76933d21c1d67b964171259d199f86d7c5d43d79 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Thu, 4 Jun 2026 17:23:35 +0200 Subject: [PATCH] feat(caddy): integrate Bunny.net DNS and dynamic IP support - Replace Bazarr with NZBGet in Caddyfile routes - Add global DNS provider configuration for ACME DNS-01 challenges - Implement dynamic DNS updater with Bunny.net provider - Add comprehensive security headers and authentication - Update documentation with new requirements and setup instructions - Add .env.example, Dockerfile, cron jobs, and scripts - Modify compose.yml to use local build and add environment variables BREAKING CHANGE: Requires Bunny.net API key and updated Caddyfile configuration --- .env.example | 8 +++ Dockerfile | 15 ++++ README.md | 123 ++++++++++++++++++++++++++------- compose.yml | 5 +- conf/Caddyfile | 141 ++++++++++++++++++++++++++++++++++++++ cron/caddy-ddns-cert.cron | 3 + scripts/ddns-cert-sync.sh | 123 +++++++++++++++++++++++++++++++++ 7 files changed, 394 insertions(+), 24 deletions(-) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 cron/caddy-ddns-cert.cron create mode 100644 scripts/ddns-cert-sync.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cb668e8 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# .env.example - Example environment variables for Caddy Bunny +# Copy this file to .env and fill in your actual values + +# Bunny.net API Key (required for DNS management) +BUNNY_API_KEY=your_bunny_api_key_here + +# Email for Let's Encrypt account (required for TLS certificates) +CADDY_EMAIL=your_email@example.com diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e46a8ec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Custom Caddy Dockerfile with Bunny DNS and Dynamic DNS modules +# Based on official Caddy Alpine image +FROM caddy:2.7.6-alpine + +# Install xcaddy for custom builds +RUN apk add --no-cache xcaddy + +# Build Caddy with required modules +RUN xcaddy build \ + --output /usr/bin/caddy \ + --with github.com/caddy-dns/bunny \ + --with github.com/mholt/caddy-dynamicdns + +# Verify modules are loaded +RUN caddy list-modules | grep -E "dns.providers.bunny|dynamic_dns" && echo "Modules loaded successfully" || exit 1 diff --git a/README.md b/README.md index ab75ac9..476aa6d 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,27 @@ -# Caddy Reverse Proxy Konfiguration +# Caddy Reverse Proxy med Bunny.net DNS och Dynamisk IP -Detta projekt innehåller konfigurationen för en **Docker-installation** av Caddy som fungerar som en reverse proxy för olika tjänster. +Detta projekt innehåller konfigurationen för en **Docker-installation** av Caddy som fungerar som en reverse proxy för olika tjänster, med stöd för **Bunny.net DNS** och **automatisk IP-uppdatering**. ## Beskrivning -Caddy används för att dirigera trafik till olika tjänster som körs i Docker-containrar. Denna konfiguration inkluderar säkerhetsrubriker och autentisering för vissa tjänster. Dessutom har **Flutter** en egen Caddy-installation i sin Docker-container för att hantera specifika behov. -UFW är uppe med följande regler: - To Action From - -- ------ ---- -[ 1] 22/tcp ALLOW IN Anywhere -[ 2] 80/tcp ALLOW IN Anywhere -[ 3] 443/tcp ALLOW IN Anywhere -[ 4] 137,138/udp ALLOW IN 192.168.50.0/24 # Samba lokal UDP -[ 5] 139,445/tcp ALLOW IN 192.168.50.0/24 # Samba lokal TCP -[ 6] 22/tcp (v6) ALLOW IN Anywhere (v6) -[ 7] 443/tcp (v6) ALLOW IN Anywhere (v6) +Caddy används för att dirigera trafik till olika tjänster som körs i Docker-containrar. Denna konfiguration inkluderar: +- Säkerhetsrubriker och autentisering för vissa tjänster. +- **Bunny.net DNS-integration** för ACME DNS-01 challenges (Let's Encrypt-certifikat). +- **Dynamisk DNS-uppdatering** när den publika IP:n ändras. +- **Cron-script** för att övervaka IP-förändringar och säkerställa TLS-funktion. ## Tjänster Följande domäner och tjänster hanteras av Caddy: -- `test.gynther.se` → `recipe-flutter:5000` (Flutter har en egen Caddy-installation i sin Docker-container) +- `test.gynther.se` → `recipe-flutter:5000` - `bazarr.gynther.se` → `bazarr:6767` - `prowlarr.gynther.se` → `prowlarr:9696` - `radarr.gynther.se` → `radarr:7878` - `sonarr.gynther.se` → `sonarr:8989` - `jellyfin.gynther.se` → `jellyfin:8096` - `qbittorrent.gynther.se` → `192.168.50.4:8080` -- `wetty.gynther.se` → `wetty:3001` +- `wetty.gynther.se` → `wetty:3001` (med autentisering) - `portainer.gynther.se` → `portainer:9000` - `gitea.gynther.se` → `192.168.50.2:3002` - `import.gynther.se` → `recipe-import-service:3000` @@ -40,21 +34,104 @@ Följande domäner och tjänster hanteras av Caddy: - **Autentisering**: Vissa tjänster, som `wetty.gynther.se`, kräver basautentisering. - **Säkerhetsrubriker**: Caddy lägger till säkerhetsrubriker för att skydda mot vanliga webbsårbarheter. +- **TLS**: Automatisk certifikatshantering via Let's Encrypt med Bunny.net DNS-01 challenge. ## Installation och Körning -1. Se till att Docker och Docker Compose är installerade. -2. Placera `Caddyfile` i `conf`-mappen. -3. Kör `docker-compose up -d` för att starta tjänsterna. +### Förutsättningar +- Docker och Docker Compose installerade. +- Bunny.net API-nyckel (för DNS-hantering). +- Miljövariabler konfigurerade (se `.env.example`). + +### Steg + +1. **Klona repot och navigera till katalogen:** + ```bash + git clone ssh://git@gitea.gynther.se:2222/nilsjohan/caddy-bunny.git + cd caddy-bunny + ``` + +2. **Skapa en `.env`-fil med nödvändiga variabler:** + ```bash + cp .env.example .env + # Redigera .env och lägg till din Bunny.net API-nyckel och e-post + ``` + +3. **Bygg och starta containrar:** + ```bash + docker-compose build + docker-compose up -d + ``` + +4. **Verifiera att Caddy körs korrekt:** + ```bash + docker-compose logs caddy + docker-compose exec caddy caddy list-modules | grep -E "dns.providers.bunny|dynamic_dns" + ``` + +## Konfiguration + +### Caddyfile +- **Globala inställningar**: + - `acme_dns bunny {env.BUNNY_API_KEY}`: Aktiverar ACME DNS-01 challenge med Bunny.net. + - `dynamic_dns`: Konfigurerar automatisk uppdatering av DNS-records när IP:n ändras. + +- **Site-specifika regler**: Se `conf/Caddyfile` för detaljerad routing. + +### Miljövariabler +- `BUNNY_API_KEY`: Bunny.net API-nyckel (för DNS-hantering). +- `CADDY_EMAIL`: E-postadress för Let's Encrypt-kontot. + +## Dynamisk DNS och IP-övervakning + +### Cron-script +Ett script (`scripts/ddns-cert-sync.sh`) körs periodiskt (var 5:e minut) för att: +1. Kontrollera den publika IP:n. +2. Uppdatera DNS-records om IP:n har ändrats. +3. Starta om Caddy för att säkerställa TLS-synkronisering. +4. Verifiera HTTPS-anslutningar till kritiska domäner. + +### Installation av Cron +1. Kopiera cron-filen till servern: + ```bash + scp cron/caddy-ddns-cert.cron user@server:/etc/cron.d/caddy-ddns-cert + ``` + +2. Säkerställa att scriptet är körbart: + ```bash + chmod +x /app/scripts/ddns-cert-sync.sh + ``` + +## Felsökning + +- **Loggar**: + - Caddy-loggar: `docker-compose logs caddy` + - Cron-loggar: `/var/log/cron-caddy-ddns.log` + - Script-loggar: `/var/log/caddy-ddns-cert-sync.log` + +- **Vanliga problem**: + - **DNS-uppdateringar misslyckas**: Kontrollera att Bunny.net API-nyckeln är korrekt och att zonen och hostnames är korrekt konfigurerade. + - **TLS-fel**: Verifiera att DNS-records har propagerats korrekt och att Caddy har startats om. + - **Caddy startar inte**: Kontrollera att alla nödvändiga moduler är inkluderade i den anpassade builden (`docker-compose exec caddy caddy list-modules`). ## Uppdatering För att uppdatera konfigurationen: 1. Redigera `Caddyfile` efter behov. -2. Starta om Caddy-containern med `docker-compose restart caddy`. +2. Starta om Caddy-containern: + ```bash + docker-compose restart caddy + ``` -## Felsökning +## Rollback -Om något inte fungerar som förväntat: -- Kontrollera loggarna med `docker-compose logs caddy`. -- Se till att alla tjänster som Caddy ska dirigera trafik till är igång och lyssnar på rätt portar. +För att återgå till den tidigare versionen: +1. Återgå till det gamla repot: + ```bash + cd ../caddy + docker-compose up -d + ``` + +## Licens + +MIT diff --git a/compose.yml b/compose.yml index da7db82..58ba95f 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,6 @@ services: caddy: - image: caddy:2.6.4 + build: . container_name: caddy restart: unless-stopped ports: @@ -11,6 +11,9 @@ services: - ./conf:/etc/caddy - caddy_data:/data - caddy_config:/config + environment: + - BUNNY_API_KEY=${BUNNY_API_KEY} + - CADDY_EMAIL=${CADDY_EMAIL} volumes: caddy_data: diff --git a/conf/Caddyfile b/conf/Caddyfile index 98cc0f4..e5e6367 100644 --- a/conf/Caddyfile +++ b/conf/Caddyfile @@ -19,6 +19,147 @@ } } +{ + # Global DNS provider for ACME DNS challenge + acme_dns bunny {env.BUNNY_API_KEY} + + # Dynamic DNS configuration + dynamic_dns { + provider bunny {env.BUNNY_API_KEY} + domains { + gynther.se @ www bazarr prowlarr radarr sonarr jellyfin qbittorrent wetty portainer gitea import recept test nzbget + } + check_interval 5m + ttl 300s + versions ipv4 + } +} + +test.gynther.se { + import common + reverse_proxy recipe-flutter:5000 +} + +nzbget.gynther.se { + import common + reverse_proxy http://192.168.50.4:6789 +} + +prowlarr.gynther.se { + import common + reverse_proxy http://prowlarr:9696 +} + +radarr.gynther.se { + import common + reverse_proxy http://radarr:7878 +} + +sonarr.gynther.se { + import common + reverse_proxy http://sonarr:8989 +} + +jellyfin.gynther.se { + reverse_proxy http://jellyfin:8096 +} + +qbittorrent.gynther.se { + import common + reverse_proxy 192.168.50.4:8080 +} + +wetty.gynther.se { + import auth + import common + redir / /wetty + reverse_proxy wetty:3001 +} + +gitea.gynther.se { + import common + reverse_proxy 192.168.50.2:3002 +} + +# ============================================ +# Import Service (Document Converter) - Standalone UI +# ============================================ +import.gynther.se { + import common + reverse_proxy recipe-import-service:3000 +} + +# ============================================ +# RECIPE APP + IMPORT SERVICE +# ============================================ +recept.gynther.se { + import common + + # === IMPORT SERVICE (Document Converter) === + # Dessa endpoints måste komma FÖRST innan backend reglerna! + handle /api/recipes/import* { + reverse_proxy recipe-import-service:3000 + } + + # === RECIPE FRONTEND PROXY ENDPOINTS === + # Next.js API routes + handle /api/inventory-history-proxy { + reverse_proxy recipe-frontend:3000 + } + + handle /api/admin/merge-preview-proxy { + reverse_proxy recipe-frontend:3000 + } + + handle /api/recipe-preview-proxy { + reverse_proxy recipe-frontend:3000 + } + + # === RECIPE BACKEND API ENDPOINTS === + # Backend körs på port 8080 (från docker-compose) + handle /api/products* { + reverse_proxy recipe-api:8080 + } + + handle /api/inventory* { + reverse_proxy recipe-api:8080 + } + + handle /api/recipes* { + reverse_proxy recipe-api:8080 + } + + # === HEALTH CHECKS === + handle /health { + reverse_proxy recipe-api:8080 + } + + # === CATCH ALL === + # Övriga /api/* går till frontend + handle /api/* { + reverse_proxy recipe-frontend:3000 + } + + # Frontend - catch all remaining routes (port 3000) + reverse_proxy /* recipe-frontend:3000 +} +} + +(common) { + encode gzip zstd + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + X-XSS-Protection "1; mode=block" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" + Cross-Origin-Opener-Policy "same-origin" + Cross-Origin-Resource-Policy "same-origin" + Cross-Origin-Embedder-Policy "require-corp" + } +} + test.gynther.se { import auth import common diff --git a/cron/caddy-ddns-cert.cron b/cron/caddy-ddns-cert.cron new file mode 100644 index 0000000..7180815 --- /dev/null +++ b/cron/caddy-ddns-cert.cron @@ -0,0 +1,3 @@ +# Crontab entry for Caddy Dynamic DNS and Certificate Sync +# Run every 5 minutes to monitor IP changes +*/5 * * * * /bin/bash -c '/app/scripts/ddns-cert-sync.sh >> /var/log/cron-caddy-ddns.log 2>&1' diff --git a/scripts/ddns-cert-sync.sh b/scripts/ddns-cert-sync.sh new file mode 100644 index 0000000..7a30baa --- /dev/null +++ b/scripts/ddns-cert-sync.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# Script: ddns-cert-sync.sh +# Purpose: Monitor public IP changes and ensure Caddy DNS/TLS is synchronized +# Usage: Run via cron (e.g., every 5 minutes) + +set -euo pipefail + +# Configuration +LOCK_FILE="/tmp/caddy-ddns-cert-sync.lock" +STATE_FILE="/var/lib/caddy/last_ip.txt" +LOG_FILE="/var/log/caddy-ddns-cert-sync.log" +CADDY_BIN="/usr/bin/caddy" +IP_CHECK_URL="https://api.ipify.org?format=txt" + +# Logging function +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# Check if script is already running +if [ -f "$LOCK_FILE" ]; then + log "Lock file exists. Another instance is running. Exiting." + exit 0 +fi + +# Create lock file +trap "rm -f '$LOCK_FILE'; exit" INT TERM EXIT +touch "$LOCK_FILE" + +# Get current public IP +get_current_ip() { + curl -s --max-time 10 "$IP_CHECK_URL" || echo "" +} + +# Read last known IP from state file +read_last_ip() { + if [ -f "$STATE_FILE" ]; then + cat "$STATE_FILE" 2>/dev/null || echo "" + else + echo "" + fi +} + +# Write current IP to state file +write_current_ip() { + echo "$1" > "$STATE_FILE" +} + +# Test HTTPS connectivity +test_https() { + local domain="$1" + if curl -s --max-time 10 -o /dev/null -w "%{http_code}" "https://$domain/" | grep -q "^200$"; then + return 0 + else + return 1 + fi +} + +# Main execution +log "Starting IP check and Caddy sync" + +CURRENT_IP=$(get_current_ip) +if [ -z "$CURRENT_IP" ]; then + log "ERROR: Could not determine current public IP. Retrying in next cycle." + rm -f "$LOCK_FILE" + exit 1 +fi + +log "Current public IP: $CURRENT_IP" + +LAST_IP=$(read_last_ip) +if [ -z "$LAST_IP" ]; then + log "No previous IP found. Writing current IP to state file." + write_current_ip "$CURRENT_IP" + rm -f "$LOCK_FILE" + exit 0 +fi + +log "Last known IP: $LAST_IP" + +if [ "$CURRENT_IP" = "$LAST_IP" ]; then + log "IP unchanged. No action needed." + rm -f "$LOCK_FILE" + exit 0 +fi + +# IP has changed +log "IP changed from $LAST_IP to $CURRENT_IP. Triggering Caddy reload and TLS verification." + +# Update state file +write_current_ip "$CURRENT_IP" + +# Reload Caddy to ensure DNS and TLS are synchronized +if [ -x "$CADDY_BIN" ]; then + log "Reloading Caddy..." + if "$CADDY_BIN" reload --config /etc/caddy/Caddyfile; then + log "Caddy reloaded successfully." + else + log "ERROR: Failed to reload Caddy. Attempting restart." + "$CADDY_BIN" stop || true + sleep 2 + "$CADDY_BIN" start --config /etc/caddy/Caddyfile --adapter caddyfile || log "ERROR: Failed to restart Caddy." + fi +else + log "ERROR: Caddy binary not found at $CADDY_BIN" +fi + +# Verify HTTPS for critical domains +log "Running HTTPS verification for critical domains..." +CRITICAL_DOMAINS=("recept.gynther.se" "jellyfin.gynther.se" "wetty.gynther.se") +for domain in "${CRITICAL_DOMAINS[@]}"; do + if test_https "$domain"; then + log "HTTPS test PASSED for $domain" + else + log "WARNING: HTTPS test FAILED for $domain (may be due to DNS propagation)" + fi +done + +log "IP change handling completed." + +# Cleanup +rm -f "$LOCK_FILE" +exit 0 \ No newline at end of file