#!/usr/bin/env bash # issue-letsencrypt.sh — issue per-name Let's Encrypt certificates via the OVH # DNS-01 challenge (acme.sh) and install them where Caddy serves them. # # Run MANUALLY on the host. One separate certificate per name: # ${BASE_DOMAIN} + .${BASE_DOMAIN} for each sub in LE_SUBDOMAINS. # Certs land in certs//{cert.pem,key.pem} (gitignored) and the host nginx # is reloaded so it picks them up (see nginx/ulicraft.conf.tmpl). # # DNS-01 needs no inbound ports — works for a LAN-only host. It only needs OVH # API credentials with DNS-record write access for the zone. # # .env vars required: # BASE_DOMAIN e.g. ulicraft.net # LE_EMAIL account email for Let's Encrypt # OVH_AK OVH_AS OVH application key + secret # OVH_CK OVH consumer key (obtained on first run — see below) # OVH_END_POINT OVH API endpoint (e.g. ovh-eu) # optional: # LE_SUBDOMAINS space-separated (default: "auth pack distribution www") # LE_STAGING=1 use the LE staging CA (untrusted, for dry runs) # # First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK # empty, run this once — acme.sh prints an authorization URL. Visit it, grant # access, then copy the consumer key it shows into OVH_CK in .env and re-run. # (Or create a token directly: https://eu.api.ovh.com/createToken/ with # GET/POST/PUT/DELETE on /domain/zone/*.) set -euo pipefail cd "$(dirname "$0")/.." # repo root log() { printf '\n\033[1;32m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } # ---- config / preflight ---------------------------------------------------- [ -f .env ] || die ".env not found (copy .env.example and fill it)" # shellcheck disable=SC1091 set -a; . ./.env; set +a : "${BASE_DOMAIN:?BASE_DOMAIN unset in .env}" : "${LE_EMAIL:?LE_EMAIL unset in .env}" : "${OVH_AK:?OVH_AK unset in .env}" : "${OVH_AS:?OVH_AS unset in .env}" : "${OVH_END_POINT:?OVH_END_POINT unset in .env (e.g. ovh-eu)}" # OVH_CK may be empty on the very first run (acme.sh will print the auth URL). export OVH_AK OVH_AS OVH_END_POINT export OVH_CK="${OVH_CK:-}" ACME="${ACME_SH:-$HOME/.acme.sh/acme.sh}" [ -x "$ACME" ] || ACME="$(command -v acme.sh || true)" [ -n "$ACME" ] && [ -x "$ACME" ] || die "acme.sh not found. Install: curl https://get.acme.sh | sh -s email=${LE_EMAIL} then re-run (or set ACME_SH=/path/to/acme.sh)." CERT_DIR="${CERT_DIR:-certs}" read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www}" if [ "${LE_STAGING:-0}" = "1" ]; then SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers" else SERVER="letsencrypt" fi # Reload the host nginx after each install so new certs take effect. Override # RELOAD_CMD if nginx runs elsewhere (e.g. a container or a non-systemd host). RELOAD="${RELOAD_CMD:-sudo systemctl reload nginx} || true" # Full name list: apex + each subdomain. domains=("$BASE_DOMAIN") for s in "${SUBDOMAINS[@]}"; do domains+=("${s}.${BASE_DOMAIN}"); done log "CA=$SERVER names: ${domains[*]}" # Register the ACME account once (idempotent). "$ACME" --register-account -m "$LE_EMAIL" --server "$SERVER" >/dev/null 2>&1 || true # ---- issue + install one cert per name -------------------------------------- issued=0 for d in "${domains[@]}"; do log "issuing $d (OVH DNS-01)" rc=0 "$ACME" --issue --dns dns_ovh --server "$SERVER" --keylength ec-256 -d "$d" || rc=$? # acme.sh: 0 = issued/renewed, 2 = skipped (cert still valid, not due). Both ok. case "$rc" in 0|2) ;; *) die "acme.sh failed for $d (rc=$rc). If this is the first OVH run, follow the consumer-key authorization URL printed above, set OVH_CK in .env, re-run." ;; esac mkdir -p "$CERT_DIR/$d" "$ACME" --install-cert -d "$d" --ecc \ --key-file "$CERT_DIR/$d/key.pem" \ --fullchain-file "$CERT_DIR/$d/cert.pem" \ --reloadcmd "$RELOAD" echo " installed -> $CERT_DIR/$d/{cert.pem,key.pem}" issued=$((issued+1)) done log "done — $issued certificate(s) under $CERT_DIR/" echo " serve them: point nginx at certs// — see nginx/ulicraft.conf.tmpl" echo " renewals: acme.sh installs a cron; --reloadcmd reloads nginx automatically."