#!/bin/sh # Issues certificates (one per domain) via the Cloudflare DNS challenge. # # certbot-init.sh -> every domain with a marker file in $DOMAINS_DIR # certbot-init.sh a.example.com -> only the domains given as arguments # # Idempotent: domains that already have a certificate are skipped. # Renewal is NOT handled here; `certbot renew` covers every existing lineage. set -eu DOMAINS_DIR="${DOMAINS_DIR:-/etc/cert-domains.d}" EMAIL="${CERTBOT_EMAIL:-}" TOKEN="${CLOUDFLARE_API_TOKEN:-}" STAGING_ARG="" if [ "${CERTBOT_STAGING:-0}" = "1" ]; then STAGING_ARG="--server https://acme-staging-v02.api.letsencrypt.org/directory" echo "NOTE: using the Let's Encrypt STAGING server (certificates will not be trusted)" fi if [ "$#" -gt 0 ]; then DOMAINS="$*" elif [ -d "$DOMAINS_DIR" ]; then DOMAINS=$(ls -1 "$DOMAINS_DIR" | grep -E '^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}$' || true) else DOMAINS="" fi DOMAINS=$(printf '%s\n' "$DOMAINS" | tr ', \t' '\n\n\n' | grep -v '^$' | sort -u || true) if [ -z "$DOMAINS" ]; then echo "No domains registered in $DOMAINS_DIR; nothing to do" exit 0 fi if [ -z "$EMAIL" ]; then echo "ERROR: CERTBOT_EMAIL not set" exit 1 fi if [ -z "$TOKEN" ]; then echo "ERROR: CLOUDFLARE_API_TOKEN not set" exit 1 fi # Temporary credentials file, shared by all requests and always cleaned up CREDS_FILE=$(mktemp /tmp/cloudflare.XXXXXX) trap 'rm -f "$CREDS_FILE"' EXIT chmod 600 "$CREDS_FILE" printf 'dns_cloudflare_api_token = %s\n' "$TOKEN" > "$CREDS_FILE" FAILED="" ISSUED=0 SKIPPED=0 for DOMAIN in $DOMAINS; do CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" if [ -f "$CERT_PATH" ]; then echo "Certificate for $DOMAIN already exists at $CERT_PATH" SKIPPED=$((SKIPPED + 1)) continue fi echo "Requesting certificate for $DOMAIN via Cloudflare DNS challenge..." # --cert-name pins the lineage directory to the domain name so the # live// path nginx expects never gets a -0001 suffix. # STAGING_ARG is intentionally unquoted (it expands to two words). # shellcheck disable=SC2086 if certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials "$CREDS_FILE" \ --agree-tos \ --no-eff-email \ --email "$EMAIL" \ --non-interactive \ --cert-name "$DOMAIN" \ $STAGING_ARG \ -d "$DOMAIN" && [ -f "$CERT_PATH" ]; then echo "Certificate obtained and verified for $DOMAIN at $CERT_PATH" ISSUED=$((ISSUED + 1)) else echo "ERROR: Failed to obtain certificate for $DOMAIN" FAILED="$FAILED $DOMAIN" fi done echo "Summary: $ISSUED issued, $SKIPPED already present" if [ -n "$FAILED" ]; then echo "ERROR: certificate request failed for:$FAILED" exit 1 fi