#!/usr/bin/env bash
#
# ACME-DNS Docker Setup Script (Production Hardened)
# Generates validated, secure configuration for joohoi/acme-dns
# https://github.com/joohoi/acme-dns
#

set -euo pipefail
IFS=$'\n\t'

# ======================
# CONFIGURATION (EDIT THESE)
# ======================
SETUP_DIR="/var/tmp/acme-dns-setup"
MAIN_DOMAIN="example.com" # Your registered domain (e.g., yoursite.com)
ACME_DNS_SUBDOMAIN="auth" # Creates auth.yoursite.com
ACME_DNS_FQDN="${ACME_DNS_SUBDOMAIN}.${MAIN_DOMAIN}"
PUBLIC_IP="1.2.3.4"                          # Public server IP (MUST be reachable on UDP/TCP 53)
ADMIN_EMAIL="admin@${MAIN_DOMAIN}"           # SOA contact (converted to DNS format)
LE_NOTIFICATION_EMAIL="admin@${MAIN_DOMAIN}" # Let's Encrypt expiry notices

# API Configuration
API_PORT="443"                    # 443 required for letsencrypt modes
API_TLS_MODE="letsencryptstaging" # none | letsencrypt | letsencryptstaging | cert
CUSTOM_TLS_CERT_PRIVKEY="/etc/acme-dns/certs/privkey.pem"
CUSTOM_TLS_CERT_FULLCHAIN="/etc/acme-dns/certs/fullchain.pem"

# Operational
DNS_PORT="53"    # MUST be 53 for public DNS delegation
LOG_LEVEL="info" # error | warning | info | debug

# ======================
# VALIDATION & SAFETY
# ======================
validate_domain() {
  local domain="$1"
  # Basic domain validation (RFC 1035 compliant pattern)
  if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$ ]]; then
    echo "❌ ERROR: Invalid domain format: '${domain}'" >&2
    echo "   Must be a valid domain (e.g., example.com)" >&2
    exit 1
  fi
}

if ! command -v docker &>/dev/null; then
  echo "❌ ERROR: Docker not found. Install Docker first." >&2
  exit 1
fi

# Critical placeholder validation
if [[ "${MAIN_DOMAIN}" == "example.com" || "${PUBLIC_IP}" == "1.2.3.4" ]]; then
  echo -e "\n⚠️  \033[1;31mCRITICAL WARNING\033[0m: Using default placeholder values!" >&2
  echo "   Edit CONFIGURATION section BEFORE proceeding." >&2
  read -p "Continue anyway? (y/N): " -r || exit 1
  [[ ! "$REPLY" =~ ^[Yy]$ ]] && echo "Aborted." && exit 1
fi

# Validate domains
validate_domain "${MAIN_DOMAIN}"
validate_domain "${ACME_DNS_FQDN}"

# Port conflict detection (precise regex to avoid false positives like port 530)
for port in "${DNS_PORT}" "${API_PORT}"; do
  if ss -tuln 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 1} END {exit 0}'; then
    echo "⚠️  WARNING: Host port ${port} is in use!" >&2
    echo "   acme-dns requires exclusive access to these ports." >&2
    read -p "Continue? (y/N): " -r || exit 1
    [[ ! "$REPLY" =~ ^[Yy]$ ]] && exit 1
  fi
done

# ======================
# CONFIG GENERATION (TOML-SAFE)
# ======================
generate_config() {
  local nsadmin="${ADMIN_EMAIL//@/.}" # SOA format: admin@domain.com → admin.domain.com

  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

  # TLS-specific configurations (TOML-safe injection)
  case "${API_TLS_MODE}" in
  letsencrypt | letsencryptstaging)
    cat <<EOF
notification_email = "${LE_NOTIFICATION_EMAIL}"
acme_cache_dir = "api-certs"
EOF
    ;;
  cert)
    cat <<EOF
tls_cert_privkey = "${CUSTOM_TLS_CERT_PRIVKEY}"
tls_cert_fullchain = "${CUSTOM_TLS_CERT_FULLCHAIN}"
EOF
    ;;
  esac

  # SECURITY: Hardcoded CORS policy (avoids TOML syntax errors from variable interpolation)
  cat <<EOF
corsorigins = [
    "https://*.${MAIN_DOMAIN}",
    "https://${MAIN_DOMAIN}"
]
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 DNS service)
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Required for ports <1024
    security_opt:
      - no-new-privileges:true
    # Prevent privilege escalation
    read_only: true
    tmpfs:
      - /tmp
EOF
}

# ======================
# EXECUTION
# ======================
echo -e "\n🔧 \033[1mACME-DNS Setup Generator (Production Hardened)\033[0m"
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 -e "   \033[33m⚠️  CORS restricted to: *.${MAIN_DOMAIN}, ${MAIN_DOMAIN}\033[0m"
echo

# Final confirmation with path safety check
if [[ "${SETUP_DIR}" =~ ^/(bin|etc|usr|var/lib|root) ]]; then
  echo "❌ ERROR: Unsafe SETUP_DIR path detected. Aborting." >&2
  exit 1
fi

read -p "⚠️  This will CREATE/OVERWRITE ${SETUP_DIR}. Confirm? (y/N): " -r || exit 1
[[ ! "$REPLY" =~ ^[Yy]$ ]] && echo "Aborted by user." && exit 0

# Atomic directory setup
rm -rf "${SETUP_DIR}" 2>/dev/null || true
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 DEPLOYMENT GUIDE
# ======================
cat <<EOF

✅ Configuration generated in ${SETUP_DIR}

❗❗ CRITICAL DEPLOYMENT STEPS (NON-NEGOTIABLE) ❗❗

1. 🌐 DNS CONFIGURATION (at registrar for ${MAIN_DOMAIN}):
   | Type | Host          | Value                              | TTL  |
   |------|---------------|------------------------------------|------|
   | A    | ${ACME_DNS_FQDN} | ${PUBLIC_IP}                       | Auto |
   | NS   | ${ACME_DNS_FQDN} | ${ACME_DNS_FQDN}. | Auto |
   
   🔑 MUST-HAVES:
   - NS record VALUE MUST END WITH TRAILING DOT (.)
   - Verify propagation: dig +short NS ${ACME_DNS_FQDN}
   - Wait for global propagation (use https://dnschecker.org)

2. 🔒 PRE-DEPLOY SECURITY CHECKLIST:
   [ ] Edit config/config.cfg:
       • After initial client registrations: set disable_registration=true
       • For production: Change API_TLS_MODE to "letsencrypt" (staging avoids rate limits)
       • Review CORS origins (currently restricted to your domain)
   [ ] If using "cert" mode: Place certs in host's ./config/certs/ and verify paths
   [ ] Backup strategy: ./data/acme-dns.db contains critical registration tokens
   [ ] Firewall: Allow UDP/TCP 53 + TCP ${API_PORT} (sudo ufw allow 53; sudo ufw allow ${API_PORT}/tcp)

3. 🚀 DEPLOY:
   cd "${SETUP_DIR}"
   docker compose up -d          # Modern Docker (v20.10+)
   # Legacy systems: docker-compose up -d

4. ✅ VALIDATE:
   docker logs -f acme-dns       # Confirm "Serving DNS on :53" and "API listening on :${API_PORT}"
   # For staging mode (self-signed):
   curl -k https://localhost:${API_PORT}/health
   # For production mode (valid cert):
   curl https://localhost:${API_PORT}/health

💡 TROUBLESHOOTING:
- Port 53 conflict? → sudo systemctl stop systemd-resolved; sudo systemctl disable systemd-resolved
- "permission denied" on ports? → Ensure NET_BIND_SERVICE capability is present (in docker-compose.yml)
- DNS queries failing? → Check firewall allows UDP 53; verify NS record has trailing dot
- API returns 403? → Check CORS origins match your client's origin exactly

📌 PRODUCTION ESSENTIALS:
• Use "letsencryptstaging" for ALL testing (avoids LE rate limits)
• NEVER expose disable_registration=false permanently
• Monitor ./data/acme-dns.db backups (contains token secrets)
• This service is part of your PKI infrastructure - treat with highest security priority
• Full docs: https://github.com/joohoi/acme-dns/blob/master/README.md
EOF
