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
The loop lived in a shell-form ENTRYPOINT one-liner: unlintable, uncommentable,
and expanded by docker into two nested shells. It is a file now, and the loop
is CMD with the base image's `certbot` entrypoint reset — CMD is appended to
ENTRYPOINT rather than replacing it, so without the reset the loop would have
arrived as arguments to certbot. In exchange a one-off run replaces the loop
outright and needs no --entrypoint:
docker run --rm -v letsencrypt:/etc/letsencrypt <image> certbot certificates
`wait $(jobs -p)` became a bare `wait`: the command substitution runs in a
subshell that reports the parent's jobs in bash but not in dash, and bare
`wait` waits for every background job in either. A failed pass is now reported
rather than passing silently, and the trap covers INT as well so Ctrl-C in an
interactive run works.
What this does not fix: a signal arriving while certbot is talking to Let's
Encrypt is held until that call returns, because a POSIX shell runs a trap only
after the foreground command finishes. That can outlast docker's ten-second
stop grace. Set stop_grace_period on the service if a clean stop matters.