ci: make TLS provisioning dynamic as domains are added

Adding a domain was a manual per-domain script run. With domains arriving
continuously that does not hold, so certificate issuance is now automatic.

HTTP already needed no work: the nginx catch-all serves any Host and the SPA
resolves its tenant from that header. Only TLS needed a name-by-name step.

Two mechanisms:

- setup-wildcard-tls.sh issues one DNS-01 wildcard for *.<apex>, after which a
  new tenant subdomain is live over HTTPS with zero certificate work.
- sync-domains.sh reconciles tenant-owned domains against a desired list on a
  10-minute timer: issues what is missing, skips certificates with >30 days
  left, skips names already covered by the wildcard, waits out unpropagated
  DNS, and caps issuance per run so a bad source cannot burn the weekly ACME
  budget.

Safety properties worth stating: a failed fetch of the desired list aborts the
run rather than reading as "remove every domain"; removing a domain disables
its server block but keeps the certificate, so re-adding is instant; malformed
hostnames are rejected before reaching certbot or an nginx server_name.

The source is pluggable - a file today, the Phase 9 domain registry once it
exists, whose MarketplaceDomain statuses already match what this needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-18 12:21:39 +04:00
parent 28861953c8
commit c721120e85
6 changed files with 511 additions and 9 deletions

View File

@@ -29,7 +29,7 @@ done
echo "==> packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq nginx certbot python3-certbot-nginx rsync ufw
apt-get install -y -qq nginx certbot python3-certbot-nginx rsync ufw jq curl openssl
echo "==> deploy user: $DEPLOY_USER"
if ! id -u "$DEPLOY_USER" >/dev/null 2>&1; then
@@ -133,6 +133,45 @@ nginx -t
systemctl enable --now nginx
systemctl reload nginx
echo "==> dynamic domain reconciler"
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
install -d -m 755 "$BASE/bin" /etc/marketplaces "/var/lib/marketplaces"
if [[ -f "$SRC_DIR/sync-domains.sh" ]]; then
install -m 755 "$SRC_DIR/sync-domains.sh" "$BASE/bin/sync-domains.sh"
if [[ ! -f /etc/marketplaces/domains.env ]]; then
cat > /etc/marketplaces/domains.env <<'ENVFILE'
# Where the desired domain list comes from.
# file:/etc/marketplaces/domains.txt one hostname per line
# https://api.example.com/api/admin/v2/domains JSON, once the backend exists
DOMAINS_SOURCE=file:/etc/marketplaces/domains.txt
# Required: certbot expiry notices.
CERTBOT_EMAIL=
# Cap per run so a bad source cannot burn the weekly ACME budget in one pass.
MAX_ISSUE_PER_RUN=10
# Set by setup-wildcard-tls.sh. Subdomains of this apex skip per-domain issuance.
#WILDCARD_APEX=
ENVFILE
chmod 600 /etc/marketplaces/domains.env
fi
touch /etc/marketplaces/domains.txt
if [[ -d "$SRC_DIR/systemd" ]]; then
install -m 644 "$SRC_DIR/systemd/marketplaces-domains.service" /etc/systemd/system/
install -m 644 "$SRC_DIR/systemd/marketplaces-domains.timer" /etc/systemd/system/
systemctl daemon-reload
# Not started yet: CERTBOT_EMAIL is still blank. Enable it after filling in
# /etc/marketplaces/domains.env, or the first run just fails on every tick.
echo " timer installed but NOT started - set CERTBOT_EMAIL first, then:"
echo " systemctl enable --now marketplaces-domains.timer"
fi
else
echo " sync-domains.sh not found next to this script - skipping"
fi
echo "==> sudoers: let the deploy user reload nginx, nothing else"
cat > /etc/sudoers.d/marketplaces-deploy <<SUDO
$DEPLOY_USER ALL=(root) NOPASSWD: /bin/systemctl reload nginx