218 lines
7.0 KiB
Bash
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
|