Files
dokku/plugins/letsencrypt/command-functions
apple 89e359ae7d
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
更新 letsencrypt
2026-04-25 14:14:39 +08:00

324 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/internal-functions"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-active() {
declare desc="Verify if letsencrypt is active for an app"
declare cmd="letsencrypt:active"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
fn-letsencrypt-is-active "$APP"
}
cmd-letsencrypt-auto-renew() {
declare desc="auto-renew certificates if necessary"
declare cmd="letsencrypt:auto-renew"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
local expiry grace_period
if [ -z "$APP" ]; then
dokku_log_info2 "Auto-renewing all apps..."
local EXIT_CODE=0
# For all apps, sorted by ascending time left until renewal.
# This way, we'll prioritize apps that need to be renewed soon
# if we should hit a rate limit along the way.
# Store the list in a temporary file to avoid subshell issues with pipelines
local temp_file=$(mktemp)
fn-letsencrypt-list-apps-with-expiry | sort -nk5 > "$temp_file"
while IFS=$'\t' read -r -a appExpiry; do
if [[ ${appExpiry[4]} -lt 0 ]]; then
dokku_log_info1 "${appExpiry[0]} needs renewal"
if ! dokku letsencrypt:enable "${appExpiry[0]}"; then
EXIT_CODE=1
fi
else
days_left=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
dokku_log_verbose "${appExpiry[0]} still has $days_left days left before renewal"
fi
done < "$temp_file"
rm -f "$temp_file"
dokku_log_info2 "Finished auto-renewal"
if [[ "$EXIT_CODE" != 0 ]]; then
dokku_log_fail "One or more apps failed to have their certificates renewed"
fi
else
verify_app_name "$APP"
if [[ "$(fn-letsencrypt-is-active "$APP")" != "true" ]]; then
dokku_log_info1 "Letsencrypt not enabled for ${APP}"
return
fi
expiry=$(fn-letsencrypt-expiration "$APP")
grace_period=$(fn-letsencrypt-computed-graceperiod "$APP")
local time_to_renewal=$((expiry - grace_period - $(date +%s)))
if [[ $time_to_renewal -lt 0 ]]; then
dokku_log_info2 "Auto-renew ${APP}..."
dokku letsencrypt:enable "$APP"
else
days_left=$(fn-letsencrypt-format-timediff $time_to_renewal)
dokku_log_verbose "$APP still has $days_left left before renewal"
fi
fi
}
cmd-letsencrypt-cleanup() {
declare desc="clean up unused certificate directories"
declare cmd="letsencrypt:cleanup"
[[ "$1" == "$cmd" ]] && shift 1
local certdir_basename current_config
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
local app_root="$DOKKU_ROOT/$APP"
local le_root="$app_root/letsencrypt"
current_config="$(basename "$(readlink "$le_root/certs/current")")"
if [ -z "$current_config" ] || [[ ! -d "$le_root/certs/$current_config" ]]; then
dokku_log_warn "Cannot resolve the 'current' certificate directory!"
return 1
fi
dokku_log_info2 "Cleaning up stale certificate directories for $APP"
dokku_log_info1 " - current config hash $current_config"
for certdir in $le_root/certs/*; do
certdir_basename="$(basename "$certdir")"
if [[ "$certdir_basename" == "current" ]] || [[ "$certdir_basename" == "$current_config" ]]; then continue; fi
dokku_log_info1 " - stale directory $certdir_basename"
rm -rf "$le_root/certs/$certdir_basename"
done
}
cmd-letsencrypt-cron-job() {
declare desc="Add or remove a cron job that periodically calls auto-renew"
declare cmd="letsencrypt:cron-job"
[[ "$1" == "$cmd" ]] && shift 1
declare FLAG="$1"
if [[ "$FLAG" == "--add" ]]; then
fn-letsencrypt-cron-job-add
elif [[ "$FLAG" == "--remove" ]]; then
fn-letsencrypt-cron-job-remove
else
dokku_log_verbose "Specify --add or --remove to modify the cron-job"
fi
}
cmd-letsencrypt-disable() {
declare desc="Disable letsencrypt for an app"
declare cmd="letsencrypt:disable"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
dokku_log_info1 "Disabling letsencrypt for app"
local le_root="$DOKKU_ROOT/$APP/letsencrypt"
local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls"
dokku_log_verbose "Removing letsencrypt files for $APP"
rm -rf "$le_root"
dokku_log_verbose "Removing SSL endpoint from $APP"
rm -rf "$APP_SSL_PATH"
plugn trigger post-certs-remove "$APP"
plugn trigger post-domains-update "$APP"
dokku_log_info1 "Done"
}
cmd-letsencrypt-enable() {
declare desc="Enable or renew letsencrypt for an app"
declare cmd="letsencrypt:enable"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
if [[ "$APP" == "--all" ]]; then
for app in $(dokku_apps); do
fn-letsencrypt-enable "$app"
done
else
fn-letsencrypt-enable "$APP"
fi
}
cmd-letsencrypt-list() {
declare desc="list letsencrypt-secured apps and certificate expiries"
declare cmd="letsencrypt:list"
[[ "$1" == "$cmd" ]] && shift 1
dokku_col_log_info1_quiet "App name" "Certificate Expiry" "Time before expiry" "Time before renewal"
fn-letsencrypt-list-apps-with-expiry \
| sort -nk2 \
| while IFS=$'\t' read -r -a appExpiry; do
expire_date=$(date -d "@${appExpiry[1]}" +"%F %T")
expire_time=$(fn-letsencrypt-format-timediff "${appExpiry[3]}")
renew_time=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
dokku_col_log_msg "${appExpiry[0]}" "${expire_date}" "${expire_time}" "${renew_time}"
done
}
cmd-letsencrypt-report() {
declare desc="displays a letsencrypt report for one or more apps"
declare cmd="letsencrypt:report"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" INFO_FLAG="$2"
local INSTALLED_APPS
INSTALLED_APPS=$(dokku_apps)
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ -z "$APP" ]]; then
for app in $INSTALLED_APPS; do
cmd-letsencrypt-report-single "$app" "$INFO_FLAG" | tee || true
done
else
cmd-letsencrypt-report-single "$APP" "$INFO_FLAG"
fi
}
cmd-letsencrypt-report-single() {
declare APP="$1" INFO_FLAG="$2"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
verify_app_name "$APP"
local flag_map=(
"--letsencrypt-active: $(fn-letsencrypt-is-active "$APP")"
"--letsencrypt-autorenew: $(fn-letsencrypt-is-autorenew-enabled "$APP")"
"--letsencrypt-computed-dns-provider: $(fn-letsencrypt-computed-dns-provider "$APP")"
"--letsencrypt-global-dns-provider: $(fn-letsencrypt-global-dns-provider)"
"--letsencrypt-dns-provider: $(fn-letsencrypt-dns-provider "$APP")"
"--letsencrypt-computed-email: $(fn-letsencrypt-computed-email "$APP")"
"--letsencrypt-global-email: $(fn-letsencrypt-global-email)"
"--letsencrypt-email: $(fn-letsencrypt-email "$APP")"
"--letsencrypt-expiration: $(fn-letsencrypt-expiration "$APP")"
"--letsencrypt-computed-graceperiod: $(fn-letsencrypt-computed-graceperiod "$APP")"
"--letsencrypt-global-graceperiod: $(fn-letsencrypt-global-graceperiod)"
"--letsencrypt-graceperiod: $(fn-letsencrypt-graceperiod "$APP")"
"--letsencrypt-computed-lego-docker-args: $(fn-letsencrypt-computed-lego-docker-args "$APP")"
"--letsencrypt-global-lego-docker-args: $(fn-letsencrypt-global-lego-docker-args)"
"--letsencrypt-lego-docker-args: $(fn-letsencrypt-lego-docker-args "$APP")"
"--letsencrypt-computed-server: $(fn-letsencrypt-computed-server "$APP")"
"--letsencrypt-global-server: $(fn-letsencrypt-global-server)"
"--letsencrypt-server: $(fn-letsencrypt-server "$APP")"
)
if [[ -z "$INFO_FLAG" ]]; then
dokku_log_info2_quiet "${APP} letsencrypt information"
for flag in "${flag_map[@]}"; do
key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
done
else
local match=false
local value_exists=false
for flag in "${flag_map[@]}"; do
valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
value=${flag#*: }
size="${#value}"
if [[ "$size" -ne 0 ]]; then
echo "$value" && match=true && value_exists=true
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
[[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
fi
}
cmd-letsencrypt-revoke() {
declare desc="Revoke a certificate"
declare cmd="letsencrypt:revoke"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
dokku_log_info2 "Revoke letsencrypt certificate from ${APP}..."
fn-letsencrypt-check-email "$APP"
fn-letsencrypt-acme-revoke "$APP" || true
dokku_log_info1 "Done"
}
cmd-letsencrypt-set() {
declare desc="set or clear a letsencrypt property for an app"
declare cmd="letsencrypt:set"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" KEY="$2" VALUE="$3"
local VALID_KEYS=("dns-provider" "email" "graceperiod" "server" "lego-docker-args")
[[ "$APP" == "--global" ]] || verify_app_name "$APP"
[[ -z "$KEY" ]] && dokku_log_fail "No key specified"
if ! fn-in-array "$KEY" "${VALID_KEYS[@]}" && [[ "$KEY" != dns-provider-* ]]; then
dokku_log_fail "Invalid key specified, valid keys include: dns-provider, dns-provider-*, email, graceperiod, server, lego-docker-args"
fi
if [[ -n "$VALUE" ]]; then
dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}"
fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
else
dokku_log_info2_quiet "Unsetting ${KEY}"
if [[ "$KEY" == "rev-env-var" ]]; then
fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
else
fn-plugin-property-delete "letsencrypt" "$APP" "$KEY"
if [[ "$KEY" == "enabled" ]]; then
fn-plugin-property-destroy "letsencrypt" "$APP"
fi
fi
fi
}