certbot init without certificates was fixed.
This commit is contained in:
parent
674c52b6c6
commit
c70ac6e310
3 changed files with 85 additions and 28 deletions
|
|
@ -2,14 +2,27 @@
|
|||
set -euo pipefail
|
||||
|
||||
# certbot-init.sh — obtain the first Let's Encrypt certificate for the
|
||||
# «Контракт-чек» edge proxy and reload nginx.
|
||||
# «Контракт-чек» edge proxy and (re)start nginx.
|
||||
#
|
||||
# Run this once on the VPS after DNS points the domain at this server and the
|
||||
# certbot container is started (see DEPLOY.md).
|
||||
# Bootstrap (first run, no certificate yet):
|
||||
# nginx CANNOT start before a certificate exists — the rendered config
|
||||
# references /etc/letsencrypt/live/<domain>/*.pem and nginx exits if those
|
||||
# files are missing. So the first certificate is obtained in --standalone
|
||||
# mode: certbot binds port 80 itself (nginx is down at this point, so the
|
||||
# port is free). After issuance the script starts nginx; the certbot renew
|
||||
# sidecar then uses the webroot plugin against the running nginx.
|
||||
#
|
||||
# Subsequent runs (certificate already exists): webroot renew via the running
|
||||
# nginx + `nginx -s reload`.
|
||||
#
|
||||
# Usage:
|
||||
# chmod +x deploy/nginx/certbot-init.sh
|
||||
# ./deploy/nginx/certbot-init.sh your-domain.example.com admin@example.com
|
||||
#
|
||||
# Requirements:
|
||||
# - DNS A-record for the domain points at this server
|
||||
# - NGINX_SERVER_NAME=<same domain> is set in .env
|
||||
# - the `services` profile is up (api healthy) — nginx depends on it
|
||||
|
||||
DOMAIN="${1:-}"
|
||||
EMAIL="${2:-}"
|
||||
|
|
@ -19,21 +32,54 @@ if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# The certbot container in docker-compose mounts these directories.
|
||||
# It must be running so certbot can write the webroot challenge response.
|
||||
if [[ -f .env ]] && ! grep -qE "^NGINX_SERVER_NAME=${DOMAIN}\s*$" .env; then
|
||||
echo "ERROR: NGINX_SERVER_NAME in .env must equal ${DOMAIN}." >&2
|
||||
echo " nginx renders server_name and the certificate path from it." >&2
|
||||
echo " Add/fix: NGINX_SERVER_NAME=${DOMAIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# nginx depends on api (service_healthy), and api is gated behind the
|
||||
# `services` profile — both profiles must be enabled for compose to
|
||||
# resolve the dependency graph.
|
||||
DC=(docker compose --profile services --profile edge)
|
||||
|
||||
CERTBOT_WEBROOT=/var/www/certbot
|
||||
LE_DIR=/etc/letsencrypt
|
||||
|
||||
docker compose --profile edge run --rm --entrypoint "certbot certonly \
|
||||
--webroot \
|
||||
--webroot-path ${CERTBOT_WEBROOT} \
|
||||
--agree-tos \
|
||||
--no-eff-email \
|
||||
--email ${EMAIL} \
|
||||
-d ${DOMAIN} \
|
||||
--non-interactive" certbot
|
||||
cert_exists() {
|
||||
"${DC[@]}" run --rm --entrypoint sh certbot \
|
||||
-c "test -f ${LE_DIR}/live/${DOMAIN}/fullchain.pem" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Reload nginx so it picks up the new certificate.
|
||||
docker compose --profile edge exec nginx nginx -s reload
|
||||
if cert_exists; then
|
||||
echo ">>> Certificate already exists — renewing via webroot (nginx serves the challenge)."
|
||||
"${DC[@]}" run --rm --entrypoint "certbot certonly \
|
||||
--webroot \
|
||||
--webroot-path ${CERTBOT_WEBROOT} \
|
||||
--agree-tos \
|
||||
--no-eff-email \
|
||||
--email ${EMAIL} \
|
||||
-d ${DOMAIN} \
|
||||
--non-interactive \
|
||||
--keep-until-expiring" certbot
|
||||
else
|
||||
echo ">>> No certificate yet — obtaining via standalone on port 80."
|
||||
# nginx cannot boot without a certificate (see header), so port 80 is free.
|
||||
"${DC[@]}" stop nginx >/dev/null 2>&1 || true
|
||||
"${DC[@]}" run --rm -p 80:80 --entrypoint "certbot certonly \
|
||||
--standalone \
|
||||
--agree-tos \
|
||||
--no-eff-email \
|
||||
--email ${EMAIL} \
|
||||
-d ${DOMAIN} \
|
||||
--non-interactive" certbot
|
||||
fi
|
||||
|
||||
echo "Certificate issued for ${DOMAIN}. Nginx reloaded."
|
||||
# Start (or re-create) nginx + the certbot renew sidecar.
|
||||
"${DC[@]}" up -d nginx certbot
|
||||
|
||||
# Reload in case nginx was already running with an old certificate.
|
||||
"${DC[@]}" exec nginx nginx -s reload || true
|
||||
|
||||
echo "Certificate issued for ${DOMAIN}. Nginx (re)started."
|
||||
|
|
|
|||
|
|
@ -432,9 +432,6 @@ services:
|
|||
image: nginx:alpine
|
||||
container_name: contract_check-nginx
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
|
@ -448,7 +445,7 @@ services:
|
|||
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx/conf.d
|
||||
NGINX_ENVSUBST_TEMPLATE_SUFFIX: .template
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost/healthz"]
|
||||
test: ["CMD", "wget", "-qO-", "--no-check-certificate", "http://localhost/healthz"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
|
|
|||
|
|
@ -509,29 +509,43 @@ docker compose --profile services logs -f bot
|
|||
NGINX_SERVER_NAME=contract-check.example.com
|
||||
```
|
||||
|
||||
> **Важно:** nginx зависит от `api` (`service_healthy`), а `api` находится в
|
||||
> профиле `services` — поэтому nginx/certbot всегда запускайте с обоими
|
||||
> профилями: `docker compose --profile services --profile edge ...`.
|
||||
> Запуск только `--profile edge` падает с `service "nginx" depends on
|
||||
> undefined service "api"`.
|
||||
|
||||
```bash
|
||||
# Запуск edge вместе со стеком
|
||||
NGINX_SERVER_NAME=contract-check.example.com \
|
||||
docker compose --profile services --profile edge up -d
|
||||
# Запуск edge вместе со стеком (после получения первого сертификата, см. §13.2)
|
||||
docker compose --profile services --profile edge up -d
|
||||
```
|
||||
|
||||
### 13.2 Первый запуск и получение сертификата
|
||||
|
||||
DNS A-запись должна указывать на IP сервера **до** запуска certbot.
|
||||
`NGINX_SERVER_NAME` в `.env` должен совпадать с доменом.
|
||||
|
||||
Nginx **не может стартовать без сертификата** — рендеренный конфиг ссылается на
|
||||
`/etc/letsencrypt/live/<domain>/fullchain.pem`, и nginx падает на старте, если
|
||||
файла нет. Поэтому первый сертификат получается в `--standalone` режиме
|
||||
(certbot сам слушает порт 80, nginx в этот момент не запущен), и только затем
|
||||
nginx стартует. Всё это делает `certbot-init.sh`:
|
||||
|
||||
```bash
|
||||
# 1. Запускаем edge без HTTPS (HTTP-челлендж для certbot работает на порту 80)
|
||||
NGINX_SERVER_NAME=contract-check.example.com \
|
||||
docker compose --profile edge up -d nginx certbot
|
||||
# 1. Запускаем основной стек (nginx пока НЕ стартуем)
|
||||
docker compose --profile services up -d
|
||||
|
||||
# 2. Получаем первый сертификат
|
||||
# 2. Получаем первый сертификат (standalone, порт 80) и стартуем nginx + renew-sidecar
|
||||
chmod +x deploy/nginx/certbot-init.sh
|
||||
./deploy/nginx/certbot-init.sh contract-check.example.com admin@example.com
|
||||
|
||||
# 3. Проверяем
|
||||
https://contract-check.example.com/healthz
|
||||
curl https://contract-check.example.com/healthz
|
||||
```
|
||||
|
||||
При повторном запуске скрипт делает renew через webroot (nginx уже отдаёт
|
||||
челленджи) и `nginx -s reload`.
|
||||
|
||||
По умолчанию `nginx.conf` проксирует:
|
||||
- `/api/v1/*`, `/admin/*`
|
||||
- `/healthz`, `/readyz`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue