#!/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 <&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} <