fix: the certificate follows CERTBOT_DOMAIN, on every pass

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
This commit is contained in:
2026-08-24 13:13:03 +00:00
parent 36e1865e37
commit e8204bbb4d
5 changed files with 56 additions and 51 deletions
+18 -15
View File
@@ -11,16 +11,15 @@ Certbot, работающий в docker-контейнере циклом пер
# Использование
Контейнер — не разовая команда. Его команда по умолчанию представляет собой
цикл: запустить скрипт перевыпуска, поспать 12 часов и повторить, — поэтому
сертификат он получает при первом старте и дальше поддерживает свежим.
цикл: выполнить проход, поспать 12 часов и повторить, — поэтому сертификат он
получает при первом старте и дальше поддерживает свежим.
Каждый проход делает четыре вещи, по скрипту на каждую:
Каждый проход делает три вещи, по скрипту на каждую:
|Скрипт|Что делает|
|:--|:--|
|`entrypoint.sh`|Сам цикл и PID 1 контейнера. Выполняет проход, спит 12 часов, повторяет; неудачный проход не обрывает цикл, а сообщается и повторяется|
|`1-renew-cert.sh`|Начало прохода. Перевыпускает существующий сертификат либо передаёт управление `0-create-cert.sh`, если сертификата ещё нет|
|`0-create-cert.sh`|Первый запуск: пишет самоподписанную заглушку, чтобы HAProxy смог занять 443, дожидается HAProxy и запрашивает настоящий сертификат|
|`1-ensure-cert.sh`|Сам проход. При первом запуске пишет самоподписанную заглушку, чтобы HAProxy смог занять 443; затем дожидается HAProxy и просит у certbot сертификат ровно на имена из `CERTBOT_DOMAIN` — выпуская его, перевыпуская при изменившемся списке имён либо не трогая вовсе|
|`2-concatenate-cert.sh`|Склеивает `fullchain.pem` и `privkey.pem` в единый `site.pem`, которого ждёт HAProxy|
|`3-update-haproxy-cert.sh`|Устанавливает `site.pem` в *работающий* HAProxy через его runtime API — без перезапуска и без разрыва соединений|
@@ -117,7 +116,7 @@ docker push registry.bitdeals.org/certbot
если перевыпуск начнёт падать, вы узнаете об этом только когда сертификат
истечёт. Следите за сертификатом внешними средствами или задайте переменную.
- **Первый сертификат самоподписанный, и браузеры об этом скажут.** HAProxy не
стартует без `site.pem`, поэтому `0-create-cert.sh` прежде всего пишет
стартует без `site.pem`, поэтому `1-ensure-cert.sh` прежде всего пишет
заглушку. Она заменяется, как только выпущен настоящий сертификат, — но если
выпуск не удался, сайт продолжает отдавать именно заглушку, и сообщение об
этом есть только в журнале контейнера.
@@ -137,9 +136,10 @@ docker push registry.bitdeals.org/certbot
он и пишет.
- **Установка выполняется на каждом проходе, а не при перевыпуске.** Скрипт
склеивает и переустанавливает сертификат независимо от того, сделал ли
`certbot renew` хоть что-нибудь, — дважды в сутки. Вреда нет, но это значит,
что путь «сертификат обновлён» задействуется постоянно и настоящий перевыпуск
ничем не отличается от любого другого прохода. Для этого у certbot есть
`certbot certonly` хоть что-нибудь, — дважды в сутки. Вреда нет, но это
значит, что путь «сертификат обновлён» задействуется постоянно и настоящий
перевыпуск ничем не отличается от любого другого прохода. Для этого у certbot
есть
`--deploy-hook`.
- **`site.pem` всегда заменяется атомарным переименованием** — и на пути
заглушки, и на пути перевыпуска. HAProxy читает этот файл при старте, а
@@ -166,12 +166,15 @@ docker push registry.bitdeals.org/certbot
еженедельной пересборкой по cron означает, что новый выпуск certbot попадает в
реестр, а через Watchtower и в продуктив, без того чтобы кто-либо запускал
сборку. Для воспроизводимых сборок фиксируйте версию тегом.
- **Добавленный в `CERTBOT_DOMAIN` домен сам по себе не приводит к
перевыпуску.** Цикл вызывает `certbot renew`, а тот берёт имена из уже
выданного сертификата и в `CERTBOT_DOMAIN` не заглядывает. Если сертификат на
машине уже есть, выпустите новый набор имён один раз вручную — дальше цикл
будет его поддерживать:
`certbot certonly --standalone -n --agree-tos --http-01-port=380 --cert-name <первый-домен> --expand -d <новый,список>`.
- **`CERTBOT_DOMAIN` перечитывается на каждом проходе, и сертификат следует за
ним.** Проход выполняет `certbot certonly --cert-name <первое-имя>
--keep-until-expiring -d "$CERTBOT_DOMAIN"`, поэтому добавленное имя попадает
в сертификат в течение 12 часов и выпускать его вручную не нужно. В обратную
сторону это работает так же, и вот это — острый край: **убранное** имя
приводит к перевыпуску без него, и на следующем проходе сайт под этим именем
остаётся без TLS. Смена *первого* имени — третий случай: по нему назван
каталог под `/etc/letsencrypt/live`, поэтому рядом со старым сертификатом
заводится новый, а старый перестаёт продлеваться.
- **У Let's Encrypt есть ограничения частоты.** Повторяющиеся неудачные попытки
выпуска на один домен в них засчитываются; проверяйте изменения на
`--server https://acme-staging-v02.api.letsencrypt.org/directory`, прежде чем