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
|
set -euo pipefail
|
||||||
|
|
||||||
# certbot-init.sh — obtain the first Let's Encrypt certificate for the
|
# 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
|
# Bootstrap (first run, no certificate yet):
|
||||||
# certbot container is started (see DEPLOY.md).
|
# 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:
|
# Usage:
|
||||||
# chmod +x deploy/nginx/certbot-init.sh
|
# chmod +x deploy/nginx/certbot-init.sh
|
||||||
# ./deploy/nginx/certbot-init.sh your-domain.example.com admin@example.com
|
# ./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:-}"
|
DOMAIN="${1:-}"
|
||||||
EMAIL="${2:-}"
|
EMAIL="${2:-}"
|
||||||
|
|
@ -19,21 +32,54 @@ if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The certbot container in docker-compose mounts these directories.
|
if [[ -f .env ]] && ! grep -qE "^NGINX_SERVER_NAME=${DOMAIN}\s*$" .env; then
|
||||||
# It must be running so certbot can write the webroot challenge response.
|
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
|
CERTBOT_WEBROOT=/var/www/certbot
|
||||||
LE_DIR=/etc/letsencrypt
|
LE_DIR=/etc/letsencrypt
|
||||||
|
|
||||||
docker compose --profile edge run --rm --entrypoint "certbot certonly \
|
cert_exists() {
|
||||||
--webroot \
|
"${DC[@]}" run --rm --entrypoint sh certbot \
|
||||||
--webroot-path ${CERTBOT_WEBROOT} \
|
-c "test -f ${LE_DIR}/live/${DOMAIN}/fullchain.pem" >/dev/null 2>&1
|
||||||
--agree-tos \
|
}
|
||||||
--no-eff-email \
|
|
||||||
--email ${EMAIL} \
|
|
||||||
-d ${DOMAIN} \
|
|
||||||
--non-interactive" certbot
|
|
||||||
|
|
||||||
# Reload nginx so it picks up the new certificate.
|
if cert_exists; then
|
||||||
docker compose --profile edge exec nginx nginx -s reload
|
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
|
image: nginx:alpine
|
||||||
container_name: contract_check-nginx
|
container_name: contract_check-nginx
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
|
||||||
api:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
|
|
@ -448,7 +445,7 @@ services:
|
||||||
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx/conf.d
|
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx/conf.d
|
||||||
NGINX_ENVSUBST_TEMPLATE_SUFFIX: .template
|
NGINX_ENVSUBST_TEMPLATE_SUFFIX: .template
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost/healthz"]
|
test: ["CMD", "wget", "-qO-", "--no-check-certificate", "http://localhost/healthz"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
|
||||||
|
|
@ -509,29 +509,43 @@ docker compose --profile services logs -f bot
|
||||||
NGINX_SERVER_NAME=contract-check.example.com
|
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
|
```bash
|
||||||
# Запуск edge вместе со стеком
|
# Запуск edge вместе со стеком (после получения первого сертификата, см. §13.2)
|
||||||
NGINX_SERVER_NAME=contract-check.example.com \
|
docker compose --profile services --profile edge up -d
|
||||||
docker compose --profile services --profile edge up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 13.2 Первый запуск и получение сертификата
|
### 13.2 Первый запуск и получение сертификата
|
||||||
|
|
||||||
DNS A-запись должна указывать на IP сервера **до** запуска certbot.
|
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
|
```bash
|
||||||
# 1. Запускаем edge без HTTPS (HTTP-челлендж для certbot работает на порту 80)
|
# 1. Запускаем основной стек (nginx пока НЕ стартуем)
|
||||||
NGINX_SERVER_NAME=contract-check.example.com \
|
docker compose --profile services up -d
|
||||||
docker compose --profile edge up -d nginx certbot
|
|
||||||
|
|
||||||
# 2. Получаем первый сертификат
|
# 2. Получаем первый сертификат (standalone, порт 80) и стартуем nginx + renew-sidecar
|
||||||
chmod +x deploy/nginx/certbot-init.sh
|
chmod +x deploy/nginx/certbot-init.sh
|
||||||
./deploy/nginx/certbot-init.sh contract-check.example.com admin@example.com
|
./deploy/nginx/certbot-init.sh contract-check.example.com admin@example.com
|
||||||
|
|
||||||
# 3. Проверяем
|
# 3. Проверяем
|
||||||
https://contract-check.example.com/healthz
|
curl https://contract-check.example.com/healthz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
При повторном запуске скрипт делает renew через webroot (nginx уже отдаёт
|
||||||
|
челленджи) и `nginx -s reload`.
|
||||||
|
|
||||||
По умолчанию `nginx.conf` проксирует:
|
По умолчанию `nginx.conf` проксирует:
|
||||||
- `/api/v1/*`, `/admin/*`
|
- `/api/v1/*`, `/admin/*`
|
||||||
- `/healthz`, `/readyz`
|
- `/healthz`, `/readyz`
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue