platform-tooling/scripts/security/issue-letsencrypt-cert.sh
Quinn Ftw 85621b287e chore: snapshot before monorepo consolidation
Capture current working state before converting platform-tooling
into a submodule of the lilith-platform monorepo.
2026-01-29 07:04:39 -08:00

186 lines
5.9 KiB
Bash
Executable file

#!/bin/bash
# =============================================================================
# Issue Let's Encrypt Certificate via DNS-01 Challenge (PowerDNS)
# =============================================================================
#
# Uses acme.sh with PowerDNS API for DNS-01 validation.
# Works for VPN-only domains since no HTTP access needed.
#
# Prerequisites (on target host):
# curl https://get.acme.sh | sh -s email=admin@atlilith.com
# source ~/.bashrc
#
# Usage:
# ./issue-letsencrypt-cert.sh <host> <cert-name> <domain1> [domain2] [domain3] ...
#
# Examples:
# # Staging certs for atlilith.com (on black)
# ./issue-letsencrypt-cert.sh black next.atlilith.com \
# next.atlilith.com next.status.atlilith.com next.www.atlilith.com
#
# # Staging certs for trustedmeet.com (on black)
# ./issue-letsencrypt-cert.sh black next.trustedmeet.com \
# next.trustedmeet.com next.www.trustedmeet.com
#
# # Production certs (on 0)
# ./issue-letsencrypt-cert.sh 0 atlilith.com \
# atlilith.com www.atlilith.com status.atlilith.com
#
# =============================================================================
set -euo pipefail
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# =============================================================================
# Configuration
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VAULT_DIR="${SCRIPT_DIR}/../../../vault"
POWERDNS_CREDS="${VAULT_DIR}/dns-servers-powerdns.txt"
# PowerDNS API configuration (ns1.nasty.sh)
PDNS_URL="http://10.0.0.11:8081" # VPN IP for ns1
# Certificate installation paths per host
declare -A CERT_PATHS=(
["black"]="/bigdisk/forgejo/ssl"
["0"]="/etc/nginx/ssl"
)
# Nginx reload commands per host
declare -A RELOAD_CMDS=(
["black"]="docker exec forgejo-nginx nginx -s reload"
["0"]="systemctl reload nginx"
)
# =============================================================================
# Argument parsing
# =============================================================================
if [[ $# -lt 3 ]]; then
echo "Usage: $0 <host> <cert-name> <domain1> [domain2] [domain3] ..."
echo ""
echo "Arguments:"
echo " host Target host (black, 0)"
echo " cert-name Certificate name (used for file naming)"
echo " domain* Domain names to include in certificate (first is primary)"
echo ""
echo "Examples:"
echo " $0 black next.atlilith.com next.atlilith.com next.status.atlilith.com"
echo " $0 black next.trustedmeet.com next.trustedmeet.com next.www.trustedmeet.com"
echo " $0 0 atlilith.com atlilith.com www.atlilith.com status.atlilith.com"
exit 1
fi
TARGET_HOST="$1"
CERT_NAME="$2"
shift 2
DOMAINS=("$@")
PRIMARY_DOMAIN="${DOMAINS[0]}"
# Validate host
if [[ ! -v "CERT_PATHS[$TARGET_HOST]" ]]; then
log_error "Unknown host: $TARGET_HOST"
log_error "Supported hosts: ${!CERT_PATHS[*]}"
exit 1
fi
INSTALL_PATH="${CERT_PATHS[$TARGET_HOST]}"
RELOAD_CMD="${RELOAD_CMDS[$TARGET_HOST]}"
# =============================================================================
# Load PowerDNS credentials
# =============================================================================
if [[ ! -f "$POWERDNS_CREDS" ]]; then
log_error "PowerDNS credentials not found: $POWERDNS_CREDS"
exit 1
fi
# Extract API key from vault file (format: "API Key: <key>")
PDNS_API_KEY=$(grep -oP 'API Key:\s*\K[a-f0-9]+' "$POWERDNS_CREDS" | head -1)
if [[ -z "$PDNS_API_KEY" ]]; then
log_error "Could not extract PowerDNS API key from $POWERDNS_CREDS"
exit 1
fi
log_info "Loaded PowerDNS API credentials"
# =============================================================================
# Build domain arguments
# =============================================================================
DOMAIN_ARGS=""
for domain in "${DOMAINS[@]}"; do
DOMAIN_ARGS="$DOMAIN_ARGS -d $domain"
done
log_info "Certificate: $CERT_NAME"
log_info "Domains: ${DOMAINS[*]}"
log_info "Target host: $TARGET_HOST"
log_info "Install path: $INSTALL_PATH"
# =============================================================================
# Issue certificate on target host
# =============================================================================
log_info "Issuing Let's Encrypt certificate via DNS-01 challenge..."
ssh "$TARGET_HOST" bash -s <<EOF
set -euo pipefail
export PDNS_Url="$PDNS_URL"
export PDNS_ServerId="localhost"
export PDNS_Token="$PDNS_API_KEY"
# Check if acme.sh is installed
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
echo "Installing acme.sh..."
curl https://get.acme.sh | sh -s email=admin@atlilith.com
source ~/.bashrc
fi
# Issue certificate
~/.acme.sh/acme.sh --issue --dns dns_pdns $DOMAIN_ARGS --force
# Install certificate
~/.acme.sh/acme.sh --install-cert -d "$PRIMARY_DOMAIN" \
--key-file "$INSTALL_PATH/$CERT_NAME.key" \
--fullchain-file "$INSTALL_PATH/$CERT_NAME.crt" \
--reloadcmd "$RELOAD_CMD"
echo "Certificate installed successfully!"
EOF
log_success "Certificate issued and installed: $CERT_NAME"
log_info "Files on $TARGET_HOST:"
log_info " - $INSTALL_PATH/$CERT_NAME.crt"
log_info " - $INSTALL_PATH/$CERT_NAME.key"
# =============================================================================
# Verify certificate
# =============================================================================
log_info "Verifying certificate..."
ssh "$TARGET_HOST" "openssl x509 -in '$INSTALL_PATH/$CERT_NAME.crt' -noout -text | grep -E '(Subject:|DNS:)'"
log_success "Done! Certificate is ready for use."
echo ""
echo "Nginx configuration:"
echo " ssl_certificate $INSTALL_PATH/$CERT_NAME.crt;"
echo " ssl_certificate_key $INSTALL_PATH/$CERT_NAME.key;"