automate/020_acme-dns_docker-compose.sh

218 lines
7.0 KiB
Bash

#!/usr/bin/env bash
# ACME-DNS Docker Setup Script
set -euo pipefail
IFS=$'\n\t'
# ======================
# CONFIGURATION (EDIT THESE)
# ======================
SETUP_DIR="/var/tmp/acme-dns-setup"
MAIN_DOMAIN="example.com" # Base domain for certificates (e.g., yoursite.com)
ACME_DNS_SUBDOMAIN="auth" # Subdomain for acme-dns (creates auth.yoursite.com)
ACME_DNS_FQDN="${ACME_DNS_SUBDOMAIN}.${MAIN_DOMAIN}"
PUBLIC_IP="1.2.3.4" # Public IP of THIS server
ADMIN_EMAIL="admin@${MAIN_DOMAIN}" # For SOA record (converted to DNS format: admin.yoursite.com)
LE_NOTIFICATION_EMAIL="admin@${MAIN_DOMAIN}" # Let's Encrypt notifications (if using LE modes)
# API Configuration
API_PORT="443" # Host port for API (443 recommended for TLS modes)
API_TLS_MODE="letsencryptstaging" # Options: none, letsencrypt, letsencryptstaging, cert
API_CORS_ORIGINS="[\"https://*.${MAIN_DOMAIN}\", \"https://${MAIN_DOMAIN}\"]" # SECURE DEFAULT
CUSTOM_TLS_CERT_PRIVKEY="/etc/acme-dns/certs/privkey.pem"
CUSTOM_TLS_CERT_FULLCHAIN="/etc/acme-dns/certs/fullchain.pem"
# DNS & Logging
DNS_PORT="53" # MUST be 53 for public DNS delegation
LOG_LEVEL="info" # error, warning, info, debug
# ======================
# SAFETY VALIDATIONS
# ======================
if ! command -v docker &>/dev/null; then
echo "❌ ERROR: Docker not found. Install Docker first." >&2
exit 1
fi
# Critical placeholder check
if [[ "${MAIN_DOMAIN}" == "example.com" || "${PUBLIC_IP}" == "1.2.3.4" ]]; then
echo "⚠️ WARNING: Using default placeholder values!" >&2
echo " Edit CONFIGURATION section before proceeding." >&2
read -p "Continue anyway? (y/N): " -r || exit 1
[[ ! "$REPLY" =~ ^[Yy]$ ]] && exit 1
fi
# Port conflict detection (HOST level)
for port in "${DNS_PORT}" "${API_PORT}"; do
if ss -tuln 2>/dev/null | grep -qE ":${port}[^0-9]"; then
echo "⚠️ WARNING: Host port ${port} is already in use!" >&2
echo " acme-dns may fail to start. Resolve conflicts first." >&2
read -p "Continue? (y/N): " -r || exit 1
[[ ! "$REPLY" =~ ^[Yy]$ ]] && exit 1
fi
done
# ======================
# HELPER FUNCTIONS
# ======================
cleanup() {
[[ -d "${SETUP_DIR}" ]] && rm -rf "${SETUP_DIR}" && echo "🧹 Cleaned existing setup directory"
}
generate_config() {
# Convert email to DNS SOA format (admin@domain.com → admin.domain.com)
local nsadmin="${ADMIN_EMAIL//@/.}"
cat <<EOF
[general]
listen = "0.0.0.0:${DNS_PORT}"
protocol = "both"
domain = "${ACME_DNS_FQDN}"
nsname = "${ACME_DNS_FQDN}"
nsadmin = "${nsadmin}"
records = [
"${ACME_DNS_FQDN}. A ${PUBLIC_IP}",
"${ACME_DNS_FQDN}. NS ${ACME_DNS_FQDN}."
]
debug = false
[database]
engine = "sqlite3"
connection = "/var/lib/acme-dns/acme-dns.db"
[api]
ip = "0.0.0.0"
port = "${API_PORT}"
disable_registration = false
tls = "${API_TLS_MODE}"
EOF
# Conditional TLS configuration
case "${API_TLS_MODE}" in
letsencrypt | letsencryptstaging)
cat <<EOF
notification_email = "${LE_NOTIFICATION_EMAIL}"
EOF
;;
cert)
cat <<EOF
tls_cert_privkey = "${CUSTOM_TLS_CERT_PRIVKEY}"
tls_cert_fullchain = "${CUSTOM_TLS_CERT_FULLCHAIN}"
EOF
;;
esac
cat <<EOF
corsorigins = ${API_CORS_ORIGINS}
use_header = false
header_name = "X-Forwarded-For"
[logconfig]
loglevel = "${LOG_LEVEL}"
logtype = "stdout"
logformat = "text"
EOF
}
generate_docker_compose() {
cat <<EOF
version: '3.8'
services:
acme-dns:
image: joohoi/acme-dns:latest
container_name: acme-dns
restart: unless-stopped
ports:
- "${DNS_PORT}:${DNS_PORT}/udp"
- "${DNS_PORT}:${DNS_PORT}/tcp"
- "${API_PORT}:${API_PORT}/tcp"
volumes:
- ./config:/etc/acme-dns:ro
- ./data:/var/lib/acme-dns
# SECURITY HARDENING (critical for public-facing DNS)
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
# Prevent container from accessing host network
network_mode: "default"
EOF
}
# ======================
# EXECUTION FLOW
# ======================
echo "🔧 ACME-DNS Setup Generator (Unified Edition)"
echo " Setup Directory : ${SETUP_DIR}"
echo " ACME-DNS Domain : ${ACME_DNS_FQDN}"
echo " Public IP : ${PUBLIC_IP}"
echo " Admin Contact : ${ADMIN_EMAIL} (SOA record)"
echo " API Mode : ${API_TLS_MODE} @ port ${API_PORT}"
echo " CORS Policy : ${API_CORS_ORIGINS}"
echo
# Interactive confirmation with context
read -p "⚠️ This will create/overwrite ${SETUP_DIR}. Confirm configuration above. Proceed? (y/N): " -r || exit 1
[[ ! "$REPLY" =~ ^[Yy]$ ]] && echo "Aborted by user" && exit 0
cleanup
mkdir -p "${SETUP_DIR}"/{config,data}
docker pull joohoi/acme-dns:latest >/dev/null 2>&1
generate_config >"${SETUP_DIR}/config/config.cfg"
generate_docker_compose >"${SETUP_DIR}/docker-compose.yml"
# ======================
# ACTIONABLE NEXT STEPS (CRITICAL)
# ======================
cat <<EOF
✅ Configuration generated successfully in ${SETUP_DIR}
❗❗ CRITICAL NEXT STEPS (DO NOT SKIP) ❗❗
1. 🌐 CONFIGURE DNS AT REGISTRAR (for ${MAIN_DOMAIN}):
| Type | Host | Value | TTL |
|------|---------------|------------------------------------|------|
| A | ${ACME_DNS_SUBDOMAIN} | ${PUBLIC_IP} | Auto |
| NS | ${ACME_DNS_SUBDOMAIN} | ${ACME_DNS_FQDN}. | Auto |
🔑 MUST-HAVES:
- NS record VALUE MUST END WITH TRAILING DOT (.)
- Verify propagation: dig NS ${ACME_DNS_FQDN}
- Wait 5-60 mins for global propagation
2. 🔒 SECURITY REVIEW (BEFORE STARTING):
- [ ] Edit config/config.cfg:
• Restrict corsorigins to YOUR actual client origins (current: ${MAIN_DOMAIN} subdomains)
• For production: Change API_TLS_MODE to "letsencrypt" (staging avoids rate limits during testing)
• After initial registrations: Set disable_registration=true
- [ ] Verify docker-compose.yml security settings (capabilities restricted)
- [ ] If using "cert" mode: Mount certificate volume and validate paths
3. 🚀 DEPLOY:
cd "${SETUP_DIR}"
docker compose up -d # Modern Docker (space)
# OR
docker-compose up -d # Legacy CLI (hyphen)
4. ✅ VERIFY:
docker logs -f acme-dns # Confirm "Serving DNS" and "API listening on :${API_PORT}"
curl -k https://localhost:${API_PORT}/health # Ignore cert warning for staging
💡 TROUBLESHOOTING:
- Port 53 conflict? → sudo systemctl stop systemd-resolved; sudo systemctl disable systemd-resolved
- DNS not resolving? → Check NS record has trailing dot; use https://dnschecker.org
- API unreachable? → sudo ufw allow ${API_PORT}/tcp; check reverse proxy config
- Staging certs? → Remember to switch to "letsencrypt" mode before production issuance
📌 PRO TIPS:
• Use "letsencryptstaging" for all testing (avoids Let's Encrypt rate limits)
• Firewall MUST allow UDP/TCP 53 and TCP ${API_PORT}
• Backup ./data/acme-dns.db regularly (contains registration tokens)
• This server is a certificate authority component - secure like vault!
EOF