#!/bin/sh set -u ACME=/usr/lib/acme/client/acme.sh LOG_TAG=acme-acmesh NOTIFY=/usr/lib/acme/notify # shellcheck source=net/acme/files/functions.sh . /usr/lib/acme/functions.sh # Needed by acme.sh export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt export NO_TIMESTAMP=1 link_certs() { local main_domain local domain_dir domain_dir="$1" main_domain="$2" ( umask 077 cat "$domain_dir/fullchain.cer" "$domain_dir/$main_domain.key" >"$domain_dir/combined.cer" ) if [ ! -e "$CERT_DIR/$main_domain.crt" ]; then ln -s "$domain_dir/$main_domain.cer" "$CERT_DIR/$main_domain.crt" fi if [ ! -e "$CERT_DIR/$main_domain.key" ]; then ln -s "$domain_dir/$main_domain.key" "$CERT_DIR/$main_domain.key" fi if [ ! -e "$CERT_DIR/$main_domain.fullchain.crt" ]; then ln -s "$domain_dir/fullchain.cer" "$CERT_DIR/$main_domain.fullchain.crt" fi if [ ! -e "$CERT_DIR/$main_domain.combined.crt" ]; then ln -s "$domain_dir/combined.cer" "$CERT_DIR/$main_domain.combined.crt" fi if [ ! -e "$CERT_DIR/$main_domain.chain.crt" ]; then ln -s "$domain_dir/ca.cer" "$CERT_DIR/$main_domain.chain.crt" fi } handle_signal() { local notify_op=$1 local label_op=$2 wait_notify() { # wait for acme.sh child job to die, *then* notify about status wait log warn "$label_op aborted: $main_domain" $NOTIFY "${notify_op}-failed" exit 1 } trap wait_notify TERM # try to kill the cgroup local cgroup=$(cut -d : -f 3 /proc/$$/cgroup) if [[ "$cgroup" == '/services/acme/*' ]]; then # send SIGTERM to all processes in this process's cgroup. this # relies on procd's having set up the cgroup for the instance. read -r -d '' pids < /sys/fs/cgroup${cgroup}/cgroup.procs kill -TERM $pids 2> /dev/null fi # if we're here, either the cgroup wasn't as exected to be set up by # procd or killing the cgroup PIDs failed. try to kill the process # group, assuming this process is the group leader. this is actually # unlikely since procd doesn't set service PGIDs (so they aren't group # leaders). kill -TERM -$$ 2> /dev/null # if we're here, cgroup-based killing was avoided or didn't work and # PGID-based killing didn't work. fall back to the raciest option. trap "" TERM term_descendants() { local pids=$@ local pid= # `pgrep -P` returns nothing if given a non-existent PID # (even if the PID has live children), so children must # be killed first for pid in $pids; do term_descendants $(pgrep -P "$pid") kill -TERM "$pid" 2> /dev/null done } term_descendants $(jobs -p) wait_notify } case $1 in get) set -- [ "$debug" = 1 ] && set -- "$@" --debug case $key_type in ec*) keylength=${key_type/ec/ec-} domain_dir="$state_dir/${main_domain}_ecc" set -- "$@" --ecc ;; rsa*) keylength=${key_type#rsa} domain_dir="$state_dir/$main_domain" ;; esac log info "Running ACME for $main_domain with validation_method $validation_method" staging_moved=0 if [ -e "$domain_dir" ]; then if [ "$staging" = 0 ] && grep -q "acme-staging" "$domain_dir/$main_domain.conf"; then mv "$domain_dir" "$domain_dir.staging" log info "Certificates are previously issued from a staging server, but staging option is disabled, moved to $domain_dir.staging." staging_moved=1 else set -- "$@" --renew --home "$state_dir" -d "$main_domain" log info "$ACME $*" trap "handle_signal renew Renewal" INT TERM $ACME "$@" & wait $! status=$? trap - INT TERM case $status in 0) link_certs "$domain_dir" "$main_domain" $NOTIFY renewed exit ;; 2) # renew skipped, ignore. exit ;; *) $NOTIFY renew-failed exit 1 ;; esac fi fi for d in $domains; do set -- "$@" -d "$d" done set -- "$@" --keylength "$keylength" --accountemail "$account_email" if [ "$acme_server" ]; then set -- "$@" --server "$acme_server" # default to letsencrypt because the upstream default may change elif [ "$staging" = 1 ]; then set -- "$@" --server letsencrypt_test else set -- "$@" --server letsencrypt fi if [ "$days" ]; then set -- "$@" --days "$days" fi case "$validation_method" in "dns") set -- "$@" --dns "$dns" if [ "$dalias" ]; then set -- "$@" --domain-alias "$dalias" if [ "$calias" ]; then log err "Both domain and challenge aliases are defined. Ignoring the challenge alias." fi elif [ "$calias" ]; then set -- "$@" --challenge-alias "$calias" fi if [ "$dns_wait" ]; then set -- "$@" --dnssleep "$dns_wait" fi ;; "standalone") set -- "$@" --standalone --listen-v6 --httpport "$listen_port" ;; "alpn") set -- "$@" --alpn --listen-v6 --tlsport "$listen_port" ;; "webroot") mkdir -p "$CHALLENGE_DIR" set -- "$@" --webroot "$CHALLENGE_DIR" ;; *) log err "Unsupported validation_method $validation_method" ;; esac set -- "$@" --issue --home "$state_dir" log info "$ACME $*" trap "handle_signal issue Issuance" INT TERM "$ACME" "$@" \ --pre-hook "$NOTIFY prepare" \ --renew-hook "$NOTIFY renewed" & wait $! status=$? trap - INT TERM case $status in 0) link_certs "$domain_dir" "$main_domain" $NOTIFY issued ;; *) if [ "$staging_moved" = 1 ]; then mv "$domain_dir.staging" "$domain_dir" log err "Staging certificate restored" elif [ -d "$domain_dir" ]; then failed_dir="$domain_dir.failed-$(date +%s)" mv "$domain_dir" "$failed_dir" log err "State moved to $failed_dir" fi $NOTIFY issue-failed ;; esac ;; esac