95 lines
3.5 KiB
Bash
Executable file
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."
|