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 <noreply@anthropic.com>
59 lines
2.8 KiB
Bash
Executable File
59 lines
2.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# First run only: get a certificate for EXT_HOST, then hand off to deploy.sh.
|
|
#
|
|
# This uses the autotrader stack's certbot container and its Let's Encrypt
|
|
# account, so it agrees to the Subscriber Agreement the same way that stack's
|
|
# own bootstrap did (certbot --agree-tos):
|
|
# https://letsencrypt.org/repository/
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
log() { printf '\n\033[1;34m==> %s\033[0m\n' "$1"; }
|
|
fail() { printf '\n\033[1;31mERROR: %s\033[0m\n' "$1" >&2; exit 1; }
|
|
|
|
[ -f .env ] || fail "no .env — copy .env.example to .env and fill it in"
|
|
set -a; . ./.env; set +a
|
|
: "${EXT_HOST:?set EXT_HOST in .env}"
|
|
SHARED_STACK="${SHARED_STACK:-/opt/autotrader/deploy}"
|
|
|
|
# ACME_EMAIL is not duplicated here: it belongs to the account that already
|
|
# holds the other certificates on this box.
|
|
[ -f "$SHARED_STACK/.env" ] || fail "no .env under $SHARED_STACK"
|
|
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.
|
|
#
|
|
# 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:-<unresolved>}"
|
|
[ "$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.
|
|
log "Rendering the HTTP-only config so the challenge can be served"
|
|
envsubst '${EXT_HOST}' < nginx/ext-acme.conf.template > "$SHARED_STACK/nginx/conf.d/ext.conf"
|
|
docker exec nginx nginx -t
|
|
docker exec nginx nginx -s reload
|
|
|
|
log "Requesting a certificate for $EXT_HOST"
|
|
docker compose --project-directory "$SHARED_STACK" -f "$SHARED_STACK/docker-compose.yml" \
|
|
run --rm --entrypoint certbot certbot \
|
|
certonly --webroot -w /var/www/certbot \
|
|
--email "$ACME_EMAIL" --agree-tos --no-eff-email \
|
|
--non-interactive -d "$EXT_HOST" \
|
|
|| fail "certbot failed — check DNS and that port 80 reaches this droplet"
|
|
|
|
log "Switching to the TLS config"
|
|
exec ./deploy.sh
|