DealDocumentScreening/deploy/nginx/certbot-init.sh

95 lines
3.5 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# certbot-init.sh — obtain the first Let's Encrypt certificate for the
# «Контракт-чек» edge proxy and (re)start nginx.
#
# 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:-}"
if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
echo "Usage: $0 <domain> <email>"
exit 1
fi
# Resolve NGINX_SERVER_NAME the same way compose does: shell env wins over .env.
# Tolerates quotes, inline comments ("domain # comment"), CRLF and padding —
# a hostname never contains whitespace, so the first token is the value.
configured="${NGINX_SERVER_NAME:-}"
if [[ -z "$configured" && -f .env ]]; then
raw="$(sed -nE 's/^NGINX_SERVER_NAME=//p' .env | tail -n1 | tr -d "\"'\r")"
read -r configured _ <<<"$raw" || true
fi
if [[ "$configured" != "$DOMAIN" ]]; then
echo "ERROR: NGINX_SERVER_NAME must equal ${DOMAIN} (found: '${configured:-<unset>}')." >&2
echo " nginx renders server_name and the certificate path from it." >&2
echo " Fix .env in the project root: NGINX_SERVER_NAME=${DOMAIN}" >&2
echo " (or export NGINX_SERVER_NAME=${DOMAIN} before running this script)" >&2
echo " Run the script from the project root — the dir containing .env." >&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
cert_exists() {
"${DC[@]}" run --rm --entrypoint sh certbot \
-c "test -f ${LE_DIR}/live/${DOMAIN}/fullchain.pem" >/dev/null 2>&1
}
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
# 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."