Skip to content

Commit 1cd22f3

Browse files
committed
fix(dstack-ingress): reload haproxy only when a certificate was actually renewed
certman.py distinguishes 'renewed' (0) from 'nothing to renew' (2), but renew-certificate.sh collapsed both to exit 0, so the renewal daemon rebuilt the PEMs and reloaded haproxy on every 12-hour check cycle even though certificates renew roughly every 60 days. Propagate exit 2 and make the daemon treat only a real renewal as reload-worthy. entrypoint.sh runs under set -e and calls renew-certificate.sh unguarded during bootstrap, where the certificate has just been issued and the second call now exits 2 — tolerate that explicitly so bootstrap does not abort.
1 parent 9744eab commit 1cd22f3

3 files changed

Lines changed: 56 additions & 14 deletions

File tree

custom-domain/dstack-ingress/scripts/entrypoint.sh

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,11 @@ process_domain() {
356356

357357
set_alias_record "$domain"
358358
set_txt_record "$domain"
359-
renew-certificate.sh "$domain" || echo "First certificate renewal failed for $domain, will retry after set CAA record"
359+
# renew-certificate.sh exits 0 on renewal, 2 when nothing needed renewal
360+
# (not a failure — this script runs under `set -e`), 1 on real failure.
361+
renew-certificate.sh "$domain" || [ $? -eq 2 ] || echo "First certificate renewal failed for $domain, will retry after set CAA record"
360362
set_caa_record "$domain"
361-
renew-certificate.sh "$domain"
363+
renew-certificate.sh "$domain" || [ $? -eq 2 ]
362364
}
363365

364366
bootstrap() {
@@ -400,8 +402,12 @@ else
400402
generate-evidences.sh
401403
fi
402404

403-
# Build combined PEM files for haproxy
405+
# Build combined PEM files for haproxy. Stamp the build so the renewal
406+
# daemon's certs_pending_apply check starts from "everything applied" —
407+
# haproxy loads these fresh PEMs itself at startup, no reload pending.
404408
build-combined-pems.sh
409+
mkdir -p /var/run/haproxy
410+
touch /var/run/haproxy/renewal-applied.stamp
405411

406412
# Generate haproxy config
407413
if [ -n "$ROUTING_MAP" ]; then

custom-domain/dstack-ingress/scripts/renew-certificate.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
88
python3 "$SCRIPT_DIR/certman.py" auto --domain "$DOMAIN" --email "$CERTBOT_EMAIL"
99
CERT_STATUS=$?
1010

11-
if [ $CERT_STATUS -eq 1 ]; then
12-
echo "Certificate management failed" >&2
13-
exit 1
14-
elif [ $CERT_STATUS -eq 2 ]; then
11+
if [ $CERT_STATUS -eq 2 ]; then
1512
echo "No certificates need renewal, skipping evidence generation"
13+
# Propagate "nothing renewed" so the renewal daemon does not rebuild
14+
# PEMs and reload haproxy on every check cycle.
15+
exit 2
16+
elif [ $CERT_STATUS -ne 0 ]; then
17+
# 1 is certman's failure exit; anything else (127 missing interpreter,
18+
# 130 signal, ...) is just as much a failure — never let it fall through
19+
# to "renewed", which would trigger a spurious PEM rebuild + reload.
20+
echo "Certificate management failed (status $CERT_STATUS)" >&2
21+
exit 1
1622
fi
1723

1824
exit 0

custom-domain/dstack-ingress/scripts/renewal-daemon.sh

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
#!/bin/bash
22

3+
# Written after renewed certificates have been fully applied (evidences +
4+
# combined PEMs rebuilt + haproxy reloaded). Certificate material newer than
5+
# this stamp means a renewal has not reached haproxy yet, so the apply step
6+
# retries on the next cycle instead of waiting for the next renewal (~60
7+
# days) — without it, a single failed reload would leave haproxy serving the
8+
# old certificate until it expires. The entrypoint touches the stamp after
9+
# the initial PEM build, so a fresh container start does not trigger a
10+
# spurious reload.
11+
STAMP=/var/run/haproxy/renewal-applied.stamp
12+
13+
certs_pending_apply() {
14+
[ -f "$STAMP" ] || return 0
15+
[ -n "$(find /etc/letsencrypt/live -name fullchain.pem -newer "$STAMP" -print -quit 2>/dev/null)" ]
16+
}
17+
318
while true; do
419
echo "[$(date)] Checking for certificate renewal"
520

@@ -10,28 +25,43 @@ while true; do
1025
while IFS= read -r domain; do
1126
[[ -n "$domain" ]] || continue
1227
echo "[$(date)] Checking renewal for domain: $domain"
13-
if renew-certificate.sh "$domain"; then
28+
renew-certificate.sh "$domain"
29+
status=$?
30+
# 0 = a certificate was actually renewed; 2 = nothing to renew.
31+
# Only a real renewal warrants a PEM rebuild + haproxy reload —
32+
# reloading on every check cycle replaced the worker (and its
33+
# accumulated state) twice a day for no reason.
34+
if [ $status -eq 0 ]; then
1435
renewal_occurred=true
15-
else
16-
echo "Certificate renewal check failed for $domain with status $?"
36+
elif [ $status -ne 2 ]; then
37+
echo "Certificate renewal check failed for $domain with status $status"
1738
fi
1839
done <<<"$all_domains"
1940

20-
if [ "$renewal_occurred" = true ]; then
21-
echo "[$(date)] Generating evidence files after renewals..."
22-
generate-evidences.sh || echo "Evidence generation failed"
41+
if [ "$renewal_occurred" = true ] || certs_pending_apply; then
42+
apply_ok=true
43+
echo "[$(date)] Applying renewed certificates..."
44+
generate-evidences.sh || { echo "Evidence generation failed"; apply_ok=false; }
2345

2446
# Rebuild combined PEM files for haproxy
25-
build-combined-pems.sh || echo "Combined PEM build failed"
47+
build-combined-pems.sh || { echo "Combined PEM build failed"; apply_ok=false; }
2648

2749
# Graceful reload: send SIGUSR2 to haproxy master process
2850
if [ ! -f /var/run/haproxy/haproxy.pid ]; then
2951
echo "HAProxy reload failed: PID file /var/run/haproxy/haproxy.pid not found" >&2
52+
apply_ok=false
3053
elif ! kill -USR2 "$(cat /var/run/haproxy/haproxy.pid)"; then
3154
echo "HAProxy reload failed: SIGUSR2 to PID $(cat /var/run/haproxy/haproxy.pid) failed" >&2
55+
apply_ok=false
3256
else
3357
echo "Certificate renewed and HAProxy reloaded successfully"
3458
fi
59+
60+
if [ "$apply_ok" = true ]; then
61+
touch "$STAMP"
62+
else
63+
echo "[$(date)] Certificate apply incomplete; will retry on the next check cycle"
64+
fi
3565
fi
3666
else
3767
echo "[$(date)] No domains configured for renewal"

0 commit comments

Comments
 (0)