#!/usr/bin/env bash # # Issue ONE wildcard certificate covering every tenant subdomain under an apex. # After this, a new tenant at . 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 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 < /etc/nginx/sites-available/marketplaces-tls.conf <> /etc/marketplaces/domains.env echo echo "done. every .$APEX is now served over HTTPS with no further action." echo "verify: curl -I https://anything.$APEX/health"