From a55ba1ccfce4d3fbb709ba425aeaee78a3985559 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Mon, 31 Aug 2026 13:14:42 -0500 Subject: [PATCH] Let the TLS bootstrap past a stale local resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The droplet kept a cached answer for the new hostname long after the record was live everywhere else, and the DNS check is a courtesy — Let's Encrypt resolves the name itself. SKIP_DNS_CHECK=1 skips it, matching the autotrader stack's script. Co-Authored-By: Claude Opus 5 --- deploy/bootstrap-tls.sh | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/deploy/bootstrap-tls.sh b/deploy/bootstrap-tls.sh index 29dd261..dd657c5 100755 --- a/deploy/bootstrap-tls.sh +++ b/deploy/bootstrap-tls.sh @@ -23,14 +23,21 @@ ACME_EMAIL=$(grep -E '^ACME_EMAIL=' "$SHARED_STACK/.env" | cut -d= -f2-) [ -n "$ACME_EMAIL" ] || fail "ACME_EMAIL is not set in $SHARED_STACK/.env" # Let's Encrypt rate-limits failures, so check DNS before spending an attempt. -log "Checking DNS" -ip=$(curl -fsS --max-time 5 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address 2>/dev/null \ - || curl -fsS --max-time 5 https://api.ipify.org 2>/dev/null) \ - || fail "could not determine this droplet's public IP" -got=$(getent ahostsv4 "$EXT_HOST" 2>/dev/null | awk 'NR==1{print $1}') || true -echo " droplet: $ip" -echo " $EXT_HOST -> ${got:-}" -[ "$got" = "$ip" ] || fail "$EXT_HOST does not resolve to $ip — fix DNS and wait for it to propagate" +# +# This box's own resolver is the one thing this cannot really trust: a record +# that used to point somewhere else stays cached here long after the world can +# see the new one. SKIP_DNS_CHECK=1 is for exactly that — Let's Encrypt resolves +# the name itself anyway, so the check is a courtesy, not a gate. +if [ "${SKIP_DNS_CHECK:-0}" != "1" ]; then + log "Checking DNS" + ip=$(curl -fsS --max-time 5 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address 2>/dev/null \ + || curl -fsS --max-time 5 https://api.ipify.org 2>/dev/null) \ + || fail "could not determine this droplet's public IP" + got=$(getent ahostsv4 "$EXT_HOST" 2>/dev/null | awk 'NR==1{print $1}') || true + echo " droplet: $ip" + echo " $EXT_HOST -> ${got:-}" + [ "$got" = "$ip" ] || fail "$EXT_HOST does not resolve to $ip here — fix DNS, wait for it to propagate, or re-run with SKIP_DNS_CHECK=1 if you know this box's cache is stale" +fi # The challenge is served over plain HTTP by the shared nginx, which needs a # server block for this name before it will answer for it.