refactor: move the renewal loop into a script, reset the base entrypoint
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.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The renewal loop: run a pass, sleep 12 hours, repeat. PID 1 of the container.
|
||||
#
|
||||
# Deliberately not `set -e`: a pass that fails — Let's Encrypt unreachable,
|
||||
# HAProxy refusing the certificate — must not end the loop, it must be retried
|
||||
# on the next one.
|
||||
#
|
||||
# The trap is not decoration. PID 1 has no default signal dispositions, so a
|
||||
# signal with no handler installed is ignored outright and `docker stop` would
|
||||
# always have to fall through to SIGKILL.
|
||||
#
|
||||
# `sleep &` followed by `wait`, rather than a plain `sleep`: a foreground
|
||||
# command keeps the shell from running a trap until it finishes, so a plain
|
||||
# sleep would leave the container deaf to SIGTERM for up to 12 hours. `wait` is
|
||||
# interruptible. Bare `wait` rather than `wait $(jobs -p)` — 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.
|
||||
#
|
||||
# One thing this cannot fix: a signal arriving while 1-renew-cert.sh is in the
|
||||
# foreground is deferred until that script returns, and an issuance talking to
|
||||
# Let's Encrypt can outlast docker's 10-second stop grace. Give the service a
|
||||
# longer `stop_grace_period` if that matters.
|
||||
|
||||
trap 'exit 0' TERM INT
|
||||
|
||||
while :; do
|
||||
/opt/scripts/1-renew-cert.sh \
|
||||
|| echo "entrypoint: renewal pass failed, retrying in 12h" >&2
|
||||
sleep 12h &
|
||||
wait
|
||||
done
|
||||
Reference in New Issue
Block a user