A pass branched on whether /etc/letsencrypt/live/<first-name> existed:
present, it ran `certbot renew`; absent, it handed over to the creation
script. Only the second path was ever told the names to ask for, so
after the first issuance CERTBOT_DOMAIN stopped reaching certbot
altogether -- renew takes the names off the certificate it already holds.
Editing the variable did nothing, silently, and the README documented the
manual issuance needed to work around it.
The branch is gone. Every pass now runs certonly with the names spelled
out, and certbot decides what that means:
same names, not due -> "Certificate not yet due for renewal; no
action taken", no connection opened, nothing
spent against the rate limits
name added or removed -> reissued with exactly the requested set
due for renewal -> renewed
--keep-until-expiring is what makes the first line true, and --cert-name
(already passed) is what keeps a changed list updating the existing
lineage instead of starting a second one beside it. No --expand is
needed to add a name once the lineage is named.
All three decisions were checked against the live stands: the no-op one
with a real run on testnet2, the other two as dry runs on testnet1 and
testnet2.
With the branch removed, 1-renew-cert.sh is a pass-through and the
numbering finally matches the order things run in -- 0-create-cert.sh
used to execute after 1-renew-cert.sh. So: 1-renew-cert.sh deleted,
0-create-cert.sh renamed to 1-ensure-cert.sh, which is what it now does.
Claude-Session: https://claude.ai/code/session_01BvgYcYPWd1KGABLKViVPSk
83 lines
3.4 KiB
Bash
83 lines
3.4 KiB
Bash
#!/bin/sh
|
|
|
|
# One pass of the loop: make the certificate on the volume the one CERTBOT_DOMAIN
|
|
# asks for, then hand it to the running HAProxy. Every pass, not just the first.
|
|
#
|
|
# `certbot certonly` rather than `certbot renew`, and that is the whole reason
|
|
# this script is the pass: renew takes the names from the certificate it already
|
|
# holds and never reads CERTBOT_DOMAIN, so an edit to the variable stayed
|
|
# invisible until somebody issued the new set by hand. certonly is told the names
|
|
# on every pass, so the certificate follows the variable in both directions.
|
|
#
|
|
# --keep-until-expiring is what makes calling it twice a day free: with the same
|
|
# names and no expiry due, certbot answers "Certificate not yet due for renewal;
|
|
# no action taken" without opening a connection, so nothing is spent against
|
|
# Let's Encrypt's rate limits.
|
|
|
|
set -e
|
|
|
|
if [ ! -f /etc/certificates/site.pem ]; then
|
|
# Self-signed placeholder, so HAProxy can bind :443 before a real certificate
|
|
# exists. Built inside a subshell: this script is sourced, so a bare cd or
|
|
# umask would change the caller's shell too. Relative paths used to resolve
|
|
# against the working directory (/opt/certbot) and left the private key there
|
|
# for good; here they resolve against the volume the key belongs on, and the
|
|
# intermediates are removed before site.pem appears.
|
|
(
|
|
umask 077
|
|
cd /etc/certificates
|
|
openssl req -x509 -newkey rsa:2048 -nodes -days 365 -batch \
|
|
-subj "/CN=${CERTBOT_DOMAIN:-localhost}" \
|
|
-keyout site.key.tmp -out site.crt.tmp
|
|
cat site.key.tmp site.crt.tmp > site.pem.tmp
|
|
rm -f site.key.tmp site.crt.tmp
|
|
# Last and atomic: HAProxy reads site.pem at start-up and must never find
|
|
# it half-written.
|
|
mv site.pem.tmp site.pem
|
|
)
|
|
fi
|
|
|
|
# Wait for haproxy: Let's Encrypt reaches this container's challenge server only
|
|
# through it, so asking for a certificate first would just fail validation. The
|
|
# runtime API socket appearing is the signal — HAProxy creates it once the
|
|
# configuration has parsed and the binds have succeeded. Waiting on the socket
|
|
# rather than a TCP probe also means there is no port left to probe.
|
|
HAPROXY_SOCKET=/var/lib/haproxy/admin.sock
|
|
while [ ! -S "$HAPROXY_SOCKET" ]; do
|
|
echo "Waiting for $HAPROXY_SOCKET..."
|
|
sleep 7
|
|
done
|
|
|
|
if [ -n "$CERTBOT_DOMAIN" ]; then
|
|
|
|
# E-mail for Let's Encrypt expiry notices. Built with set -- so that the flag
|
|
# and its value stay two separate arguments: a single "$CERTBOT_OPTS" word
|
|
# reached certbot as one token ("--email you@example.org") and was rejected
|
|
# as an unknown argument, which failed every issuance with an address set.
|
|
# The entrypoint passes no positional parameters, so there are none to lose.
|
|
if [ -n "$CERTBOT_EMAIL" ]; then
|
|
set -- --email "$CERTBOT_EMAIL"
|
|
else
|
|
set -- --register-unsafely-without-email
|
|
fi
|
|
|
|
# Request certificate. --cert-name pins the lineage to the first name, so a
|
|
# changed list updates that certificate instead of starting a second one
|
|
# beside it; with the lineage named, certbot needs no --expand to accept an
|
|
# added name non-interactively.
|
|
certbot certonly --standalone \
|
|
--non-interactive --agree-tos --http-01-port=380 \
|
|
"$@" \
|
|
--cert-name "${CERTBOT_DOMAIN%%,*}" \
|
|
--keep-until-expiring \
|
|
-d "$CERTBOT_DOMAIN"
|
|
|
|
# Concatenate certificates
|
|
. $(dirname $0)/2-concatenate-cert.sh
|
|
|
|
fi
|
|
|
|
# Update certificates in HAProxy
|
|
. $(dirname $0)/3-update-haproxy-cert.sh
|
|
|