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
This commit is contained in:
Nils-Johan Gynther
2026-06-04 17:23:35 +02:00
parent d553094a88
commit 76933d21c1
7 changed files with 394 additions and 24 deletions
+8
View File
@@ -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
+15
View File
@@ -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
+100 -23
View File
@@ -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 ## 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. Caddy används för att dirigera trafik till olika tjänster som körs i Docker-containrar. Denna konfiguration inkluderar:
UFW är uppe med följande regler: - Säkerhetsrubriker och autentisering för vissa tjänster.
To Action From - **Bunny.net DNS-integration** för ACME DNS-01 challenges (Let's Encrypt-certifikat).
-- ------ ---- - **Dynamisk DNS-uppdatering** när den publika IP:n ändras.
[ 1] 22/tcp ALLOW IN Anywhere - **Cron-script** för att övervaka IP-förändringar och säkerställa TLS-funktion.
[ 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)
## Tjänster ## Tjänster
Följande domäner och tjänster hanteras av Caddy: 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` - `bazarr.gynther.se``bazarr:6767`
- `prowlarr.gynther.se``prowlarr:9696` - `prowlarr.gynther.se``prowlarr:9696`
- `radarr.gynther.se``radarr:7878` - `radarr.gynther.se``radarr:7878`
- `sonarr.gynther.se``sonarr:8989` - `sonarr.gynther.se``sonarr:8989`
- `jellyfin.gynther.se``jellyfin:8096` - `jellyfin.gynther.se``jellyfin:8096`
- `qbittorrent.gynther.se``192.168.50.4:8080` - `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` - `portainer.gynther.se``portainer:9000`
- `gitea.gynther.se``192.168.50.2:3002` - `gitea.gynther.se``192.168.50.2:3002`
- `import.gynther.se``recipe-import-service:3000` - `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. - **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. - **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 ## Installation och Körning
1. Se till att Docker och Docker Compose är installerade. ### Förutsättningar
2. Placera `Caddyfile` i `conf`-mappen. - Docker och Docker Compose installerade.
3. Kör `docker-compose up -d` för att starta tjänsterna. - 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 ## Uppdatering
För att uppdatera konfigurationen: För att uppdatera konfigurationen:
1. Redigera `Caddyfile` efter behov. 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: För att återgå till den tidigare versionen:
- Kontrollera loggarna med `docker-compose logs caddy`. 1. Återgå till det gamla repot:
- Se till att alla tjänster som Caddy ska dirigera trafik till är igång och lyssnar på rätt portar. ```bash
cd ../caddy
docker-compose up -d
```
## Licens
MIT
+4 -1
View File
@@ -1,6 +1,6 @@
services: services:
caddy: caddy:
image: caddy:2.6.4 build: .
container_name: caddy container_name: caddy
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -11,6 +11,9 @@ services:
- ./conf:/etc/caddy - ./conf:/etc/caddy
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
environment:
- BUNNY_API_KEY=${BUNNY_API_KEY}
- CADDY_EMAIL=${CADDY_EMAIL}
volumes: volumes:
caddy_data: caddy_data:
+141
View File
@@ -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 { test.gynther.se {
import auth import auth
import common import common
+3
View File
@@ -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'
+123
View File
@@ -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