#!/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//*.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= 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 " exit 1 fi 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 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."