324 lines
11 KiB
Bash
Executable File
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
|
|
}
|