#!/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="ring-zero.co.uk" # 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="192.237.223.112"                        # public server ip (must be reachable on udp/tcp 53)
admin_email="letsencrypt@${main_domain}"           # soa contact (converted to dns format)
le_notification_email="letsencrypt@${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

# Set cat type:
# Determine which command to use
if command -v batcat >/dev/null; then
  COLOR_CAT="batcat --language=md --style=plain"
elif command -v bat >/dev/null; then
  COLOR_CAT="bat --language=md --style=plain"
else
  COLOR_CAT="cat"
fi

# ======================
# 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
  if [[ ! "$domain" =~ ^([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
  # We check if the port IS found.
  # awk returns 0 (success) if a match is found, triggering the warning.
  if ss -tuln 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {found=1; exit} END {exit !found}'; then
    echo "⚠️  Warning: Host port ${port} is in use!" >&2
    echo "   acme-dns requires exclusive access to these ports." >&2
    read -p "Continue anyway? (y/n): " -n 1 -r
    echo "" # move to a new line
    [[ ! $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
      - ./api-certs:/api-certs
    # 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
# ======================

${COLOR_CAT} <<EOF

🔧 acme-dns setup generator (production hardened)
   setup directory  : ${setup_dir}
   acme-dns domain  : ${acme_dns_fqdn}
   public ip        : ${public_ip}
   admin contact    : ${admin_email} (soa record)
   api mode         : ${api_tls_mode} @ port ${api_port}
   ⚠️  cors restricted to: *.${main_domain}, ${main_domain}

EOF

# 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

# Check that docker is running
if ! docker info >/dev/null 2>&1; then
  echo "Error: Docker is not running. Aborting script."
  exit 1
fi

# atomic directory setup
rm -rf "${setup_dir}" 2>/dev/null || true
mkdir -p "${setup_dir}"/{api-certs,config,data}
chown -R 1000:1000 ./data && chmod 755 ./data   
chown -R 1000:1000 ./api-certs && chmod 755 ./api-certs   
chmod 644 ./config/config.cfg   

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
# ======================
${COLOR_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:
- 🛠️ check for dns services: use sudo netstat -tulnp | grep :53 to see what’s using port 53
- 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
