#!/usr/bin/env bash
#
# Issue ONE wildcard certificate covering every tenant subdomain under an apex.
# After this, a new tenant at <slug>.<apex> needs no certificate work at all —
# DNS record, and it is live over HTTPS immediately.
#
#   setup-wildcard-tls.sh --apex marketplaces.example.com --email ops@example.com --dns cloudflare
#   setup-wildcard-tls.sh --apex marketplaces.example.com --email ops@example.com --dns manual
#
# Wildcards require DNS-01 validation — HTTP-01 cannot issue them. That means
# certbot must create a _acme-challenge TXT record, which needs either a DNS
# provider plugin (automatic, renews unattended) or manual intervention every
# 60-90 days. Prefer a plugin. Use manual only to prove the idea out.
#
# Tenants on their OWN domains are not covered by a wildcard; those are handled
# per-domain by sync-domains.sh.

set -euo pipefail

APEX=""; EMAIL=""; DNS_PLUGIN="manual"; CREDS=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --apex)  APEX="$2"; shift 2 ;;
    --email) EMAIL="$2"; shift 2 ;;
    --dns)   DNS_PLUGIN="$2"; shift 2 ;;
    --creds) CREDS="$2"; shift 2 ;;
    *) echo "unknown argument: $1" >&2; exit 2 ;;
  esac
done

[[ $EUID -eq 0 ]] || { echo "must run as root" >&2; exit 1; }
[[ -n "$APEX"  ]] || { echo "--apex is required" >&2; exit 2; }
[[ -n "$EMAIL" ]] || { echo "--email is required" >&2; exit 2; }

export DEBIAN_FRONTEND=noninteractive
CERT_ARGS=(-d "$APEX" -d "*.$APEX")

case "$DNS_PLUGIN" in
  cloudflare)
    apt-get install -y -qq python3-certbot-dns-cloudflare
    [[ -n "$CREDS" ]] || { echo "--creds <file> required for cloudflare (contains the API token)" >&2; exit 2; }
    chmod 600 "$CREDS"
    CERT_ARGS+=(--dns-cloudflare --dns-cloudflare-credentials "$CREDS" --dns-cloudflare-propagation-seconds 30)
    ;;
  route53)
    apt-get install -y -qq python3-certbot-dns-route53
    CERT_ARGS+=(--dns-route53)   # credentials come from the instance role or ~/.aws
    ;;
  manual)
    cat >&2 <<'WARN'
WARNING: manual DNS-01.

certbot will print a TXT record for you to create by hand, and will do so again
at every renewal (every 60-90 days). Unattended renewal will NOT work. This is
acceptable to prove the setup out; it is not acceptable as the steady state.

Hostinger has no certbot plugin. If DNS lives there, the options are: move DNS
to a provider with a plugin (Cloudflare is free and takes minutes), or drive
issuance from the Phase 9 domain-automation API instead.

WARN
    CERT_ARGS+=(--manual --preferred-challenges dns)
    ;;
  *)
    echo "unsupported --dns: $DNS_PLUGIN (cloudflare|route53|manual)" >&2; exit 2 ;;
esac

echo "==> issuing wildcard for $APEX and *.$APEX via $DNS_PLUGIN"
certbot certonly "${CERT_ARGS[@]}" \
  --agree-tos --email "$EMAIL" --keep-until-expiring \
  $([[ "$DNS_PLUGIN" != "manual" ]] && echo --non-interactive)

LIVE="/etc/letsencrypt/live/$APEX"
[[ -f "$LIVE/fullchain.pem" ]] || { echo "certificate not found at $LIVE" >&2; exit 1; }

echo "==> nginx: TLS on the catch-all, so every subdomain is served immediately"
cat > /etc/nginx/snippets/marketplaces-wildcard-tls.conf <<SNIPPET
# Managed by setup-wildcard-tls.sh
ssl_certificate     $LIVE/fullchain.pem;
ssl_certificate_key $LIVE/privkey.pem;
ssl_protocols       TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling        on;
ssl_stapling_verify on;
SNIPPET

cat > /etc/nginx/sites-available/marketplaces-tls.conf <<NGINX
# Wildcard TLS catch-all for *.$APEX
# Any tenant subdomain is served here with no per-tenant configuration.
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    http2 on;
    server_name $APEX *.$APEX;

    include /etc/nginx/snippets/marketplaces-wildcard-tls.conf;

    root /srv/marketplaces/current/frontend;
    index index.html;

    location = /index.html {
        add_header Cache-Control "no-store, must-revalidate" always;
        try_files \$uri =404;
    }
    location ~* \.(js|css|woff2?|png|jpe?g|svg|gif|webp|avif|ico)\$ {
        expires 1y;
        add_header Cache-Control "public, immutable" always;
        try_files \$uri =404;
    }
    location /health { access_log off; return 200 "ok\n"; add_header Content-Type text/plain; }
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host              \$host;
        proxy_set_header X-Real-IP         \$remote_addr;
        proxy_set_header X-Forwarded-For   \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
    location / { try_files \$uri \$uri/ /index.html; }

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1024;
}
NGINX

ln -sfn /etc/nginx/sites-available/marketplaces-tls.conf /etc/nginx/sites-enabled/marketplaces-tls.conf
nginx -t
systemctl reload nginx
systemctl enable --now certbot.timer

# Tell sync-domains.sh which names it can skip.
mkdir -p /etc/marketplaces
if [[ -f /etc/marketplaces/domains.env ]]; then
  sed -i '/^WILDCARD_APEX=/d' /etc/marketplaces/domains.env
fi
echo "WILDCARD_APEX=$APEX" >> /etc/marketplaces/domains.env

echo
echo "done. every <slug>.$APEX is now served over HTTPS with no further action."
echo "verify: curl -I https://anything.$APEX/health"
