mirror of
https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion
synced 2024-11-22 16:32:48 +01:00
550 lines
23 KiB
Bash
Executable File
550 lines
23 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# shellcheck source=functions.sh
|
|
source /app/functions.sh
|
|
|
|
CERTS_UPDATE_INTERVAL="${CERTS_UPDATE_INTERVAL:-3600}"
|
|
ACME_CA_URI="${ACME_CA_URI:-"https://acme-v02.api.letsencrypt.org/directory"}"
|
|
ACME_CA_TEST_URI="https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
DEFAULT_KEY_SIZE="${DEFAULT_KEY_SIZE:-4096}"
|
|
RENEW_PRIVATE_KEYS="$(lc "${RENEW_PRIVATE_KEYS:-true}")"
|
|
|
|
# Backward compatibility environment variable
|
|
REUSE_PRIVATE_KEYS="$(lc "${REUSE_PRIVATE_KEYS:-false}")"
|
|
|
|
function strip_wildcard {
|
|
# Remove wildcard prefix if present
|
|
# https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#wildcard-certificates
|
|
local -r domain="${1?missing domain argument}"
|
|
if [[ "${domain:0:2}" == "*." ]]; then
|
|
echo "${domain:2}"
|
|
else
|
|
echo "$domain"
|
|
fi
|
|
}
|
|
|
|
function create_link {
|
|
local -r source=${1?missing source argument}
|
|
local -r target=${2?missing target argument}
|
|
if [[ -f "$target" ]] && [[ "$(readlink "$target")" == "$source" ]]; then
|
|
set_ownership_and_permissions "$target"
|
|
[[ "$DEBUG" == 1 ]] && echo "$target already linked to $source"
|
|
return 1
|
|
else
|
|
ln -sf "$source" "$target" \
|
|
&& set_ownership_and_permissions "$target"
|
|
fi
|
|
}
|
|
|
|
function create_links {
|
|
local -r base_domain=${1?missing base_domain argument}
|
|
local domain=${2?missing base_domain argument}
|
|
domain="$(strip_wildcard "$domain")"
|
|
|
|
if [[ ! -f "/etc/nginx/certs/$base_domain/fullchain.pem" || \
|
|
! -f "/etc/nginx/certs/$base_domain/key.pem" ]]; then
|
|
return 1
|
|
fi
|
|
local return_code=1
|
|
create_link "./$base_domain/fullchain.pem" "/etc/nginx/certs/$domain.crt"
|
|
return_code=$(( return_code & $? ))
|
|
create_link "./$base_domain/key.pem" "/etc/nginx/certs/$domain.key"
|
|
return_code=$(( return_code & $? ))
|
|
if [[ -f "/etc/nginx/certs/dhparam.pem" ]]; then
|
|
create_link ./dhparam.pem "/etc/nginx/certs/$domain.dhparam.pem"
|
|
return_code=$(( return_code & $? ))
|
|
fi
|
|
if [[ -f "/etc/nginx/certs/$base_domain/chain.pem" ]]; then
|
|
create_link "./$base_domain/chain.pem" "/etc/nginx/certs/$domain.chain.pem"
|
|
return_code=$(( return_code & $? ))
|
|
fi
|
|
return $return_code
|
|
}
|
|
|
|
function cleanup_links {
|
|
local -a LETSENCRYPT_CONTAINERS
|
|
local -a LETSENCRYPT_STANDALONE_CERTS
|
|
local -a ENABLED_DOMAINS
|
|
local -a SYMLINKED_DOMAINS
|
|
local -a DISABLED_DOMAINS
|
|
|
|
# Create an array containing domains for which a symlinked certificate
|
|
# exists in /etc/nginx/certs (excluding default cert).
|
|
for symlinked_domain in /etc/nginx/certs/*.crt; do
|
|
[[ -L "$symlinked_domain" ]] || continue
|
|
symlinked_domain="${symlinked_domain##*/}"
|
|
symlinked_domain="${symlinked_domain%*.crt}"
|
|
[[ "$symlinked_domain" != "default" ]] || continue
|
|
SYMLINKED_DOMAINS+=("$symlinked_domain")
|
|
done
|
|
[[ "$DEBUG" == 1 ]] && echo "Symlinked domains: ${SYMLINKED_DOMAINS[*]}"
|
|
|
|
# Create an array containing domains that are considered
|
|
# enabled (ie present on /app/letsencrypt_service_data or /app/letsencrypt_user_data).
|
|
[[ -f /app/letsencrypt_service_data ]] && source /app/letsencrypt_service_data
|
|
[[ -f /app/letsencrypt_user_data ]] && source /app/letsencrypt_user_data
|
|
LETSENCRYPT_CONTAINERS+=( "${LETSENCRYPT_STANDALONE_CERTS[@]}" )
|
|
for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do
|
|
local -n hosts_array="LETSENCRYPT_${cid}_HOST"
|
|
for domain in "${hosts_array[@]}"; do
|
|
domain="$(strip_wildcard "$domain")"
|
|
# Add domain to the array storing currently enabled domains.
|
|
ENABLED_DOMAINS+=("$domain")
|
|
done
|
|
done
|
|
[[ "$DEBUG" == 1 ]] && echo "Enabled domains: ${ENABLED_DOMAINS[*]}"
|
|
|
|
# Create an array containing only domains for which a symlinked private key exists
|
|
# in /etc/nginx/certs but that no longer have a corresponding LETSENCRYPT_HOST set
|
|
# on an active container or on /app/letsencrypt_user_data
|
|
if [[ ${#SYMLINKED_DOMAINS[@]} -gt 0 ]]; then
|
|
mapfile -t DISABLED_DOMAINS < <(echo "${SYMLINKED_DOMAINS[@]}" \
|
|
"${ENABLED_DOMAINS[@]}" \
|
|
"${ENABLED_DOMAINS[@]}" \
|
|
| tr ' ' '\n' | sort | uniq -u)
|
|
fi
|
|
[[ "$DEBUG" == 1 ]] && echo "Disabled domains: ${DISABLED_DOMAINS[*]}"
|
|
|
|
|
|
# Remove disabled domains symlinks if present.
|
|
# Return 1 if nothing was removed and 0 otherwise.
|
|
if [[ ${#DISABLED_DOMAINS[@]} -gt 0 ]]; then
|
|
[[ "$DEBUG" == 1 ]] && echo "Some domains are disabled :"
|
|
for disabled_domain in "${DISABLED_DOMAINS[@]}"; do
|
|
[[ "$DEBUG" == 1 ]] && echo "Checking domain ${disabled_domain}"
|
|
cert_folder="$(readlink -f "/etc/nginx/certs/${disabled_domain}.crt")"
|
|
# If the dotfile is absent, skip domain.
|
|
if [[ ! -e "${cert_folder%/*}/.companion" ]]; then
|
|
[[ "$DEBUG" == 1 ]] && echo "No .companion file found in ${cert_folder}. ${disabled_domain} is not managed by acme-companion. Skipping domain."
|
|
continue
|
|
else
|
|
[[ "$DEBUG" == 1 ]] && echo "${disabled_domain} is managed by acme-companion. Removing unused symlinks."
|
|
fi
|
|
|
|
for extension in .crt .key .dhparam.pem .chain.pem; do
|
|
file="${disabled_domain}${extension}"
|
|
if [[ -n "${file// }" ]] && [[ -L "/etc/nginx/certs/${file}" ]]; then
|
|
[[ "$DEBUG" == 1 ]] && echo "Removing /etc/nginx/certs/${file}"
|
|
rm -f "/etc/nginx/certs/${file}"
|
|
fi
|
|
done
|
|
done
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function update_cert {
|
|
local cid="${1:?}"
|
|
local -n hosts_array="LETSENCRYPT_${cid}_HOST"
|
|
# First domain will be our base domain
|
|
local base_domain="${hosts_array[0]}"
|
|
|
|
local wildcard_certificate='false'
|
|
if [[ "${base_domain:0:2}" == "*." ]]; then
|
|
wildcard_certificate='true'
|
|
fi
|
|
|
|
local should_restart_container='false'
|
|
|
|
# Base CLI parameters array, used for both --register-account and --issue
|
|
local -a params_base_arr
|
|
params_base_arr+=(--log /dev/null)
|
|
params_base_arr+=(--useragent "nginx-proxy/acme-companion/$COMPANION_VERSION (acme.sh/$ACMESH_VERSION)")
|
|
[[ "$DEBUG" == 1 ]] && params_base_arr+=(--debug 2)
|
|
|
|
# Alternative trusted root CA path, used for test with Pebble
|
|
if [[ -n "${CA_BUNDLE// }" ]]; then
|
|
if [[ -f "$CA_BUNDLE" ]]; then
|
|
params_base_arr+=(--ca-bundle "$CA_BUNDLE")
|
|
[[ "$DEBUG" == 1 ]] && echo "Debug: acme.sh will use $CA_BUNDLE as trusted root CA."
|
|
else
|
|
echo "Warning: the path to the alternate CA bundle ($CA_BUNDLE) is not valid, using default Alpine trust store."
|
|
fi
|
|
fi
|
|
|
|
# CLI parameters array used for --register-account
|
|
local -a params_register_arr
|
|
|
|
# CLI parameters array used for --issue
|
|
local -a params_issue_arr
|
|
|
|
# ACME challenge type
|
|
local -n acme_challenge="ACME_${cid}_CHALLENGE"
|
|
if [[ -z "${acme_challenge}" ]]; then
|
|
acme_challenge="${ACME_CHALLENGE:-HTTP-01}"
|
|
fi
|
|
|
|
if [[ "$acme_challenge" == "HTTP-01" ]]; then
|
|
# HTTP-01 challenge
|
|
if [[ "$wildcard_certificate" == 'true' ]]; then
|
|
echo "Error: wildcard certificates (${base_domain}) can't be obtained with HTTP-01 challenge"
|
|
return 1
|
|
fi
|
|
params_issue_arr+=(--webroot /usr/share/nginx/html)
|
|
elif [[ "$acme_challenge" == "DNS-01" ]]; then
|
|
# DNS-01 challenge
|
|
local acmesh_dns_config_used='none'
|
|
|
|
local default_acmesh_dns_api="${DEFAULT_ACMESH_DNS_API_CONFIG[DNS_API]}"
|
|
[[ -n "$default_acmesh_dns_api" ]] && acmesh_dns_config_used='default'
|
|
|
|
local -n acmesh_dns_config="ACMESH_${cid}_DNS_API_CONFIG"
|
|
local acmesh_dns_api="${acmesh_dns_config[DNS_API]}"
|
|
[[ -n "$acmesh_dns_api" ]] && acmesh_dns_config_used='container'
|
|
|
|
local -a dns_api_keys
|
|
|
|
case "$acmesh_dns_config_used" in
|
|
'default')
|
|
params_issue_arr+=(--dns "$default_acmesh_dns_api")
|
|
# Loop over defined variable for default acme.sh DNS api config
|
|
for key in "${!DEFAULT_ACMESH_DNS_API_CONFIG[@]}"; do
|
|
[[ "$key" == "DNS_API" ]] && continue
|
|
dns_api_keys+=("$key")
|
|
local value="${DEFAULT_ACMESH_DNS_API_CONFIG[$key]}"
|
|
local -x "$key"="$value"
|
|
done
|
|
;;
|
|
'container')
|
|
params_issue_arr+=(--dns "$acmesh_dns_api")
|
|
# Loop over defined variable for per container acme.sh DNS api config
|
|
for key in "${!acmesh_dns_config[@]}"; do
|
|
[[ "$key" == "DNS_API" ]] && continue
|
|
dns_api_keys+=("$key")
|
|
local value="${acmesh_dns_config[$key]}"
|
|
local -x "$key"="$value"
|
|
done
|
|
;;
|
|
*)
|
|
echo "Error: missing acme.sh DNS API for DNS challenge"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
echo "Info: DNS challenge using $acmesh_dns_api DNS API with the following keys: ${dns_api_keys[*]} (${acmesh_dns_config_used} config)"
|
|
else
|
|
echo "Error: unknown ACME challenge method: $acme_challenge"
|
|
return 1
|
|
fi
|
|
|
|
local -n cert_keysize="LETSENCRYPT_${cid}_KEYSIZE"
|
|
if [[ -z "$cert_keysize" ]] || \
|
|
[[ ! "$cert_keysize" =~ ^('2048'|'3072'|'4096'|'ec-256'|'ec-384')$ ]]; then
|
|
cert_keysize=$DEFAULT_KEY_SIZE
|
|
fi
|
|
params_issue_arr+=(--keylength "$cert_keysize")
|
|
|
|
# OCSP-Must-Staple extension
|
|
local -n ocsp="ACME_${cid}_OCSP"
|
|
if [[ $(lc "$ocsp") == true ]]; then
|
|
params_issue_arr+=(--ocsp-must-staple)
|
|
fi
|
|
|
|
local -n accountemail="LETSENCRYPT_${cid}_EMAIL"
|
|
local config_home
|
|
# If we don't have a LETSENCRYPT_EMAIL from the proxied container
|
|
# and DEFAULT_EMAIL is set to a non empty value, use the latter.
|
|
if [[ -z "$accountemail" ]]; then
|
|
if [[ -n "${DEFAULT_EMAIL// }" ]]; then
|
|
accountemail="$DEFAULT_EMAIL"
|
|
else
|
|
unset accountemail
|
|
fi
|
|
fi
|
|
if [[ -n "${accountemail// }" ]]; then
|
|
# If we got an email, use it with the corresponding config home
|
|
config_home="/etc/acme.sh/$accountemail"
|
|
else
|
|
# If we did not get any email at all, use the default (empty mail) config
|
|
config_home="/etc/acme.sh/default"
|
|
fi
|
|
|
|
local -n acme_ca_uri="ACME_${cid}_CA_URI"
|
|
if [[ -z "$acme_ca_uri" ]]; then
|
|
# Use default or user provided ACME end point
|
|
acme_ca_uri="$ACME_CA_URI"
|
|
fi
|
|
# LETSENCRYPT_TEST overrides LETSENCRYPT_ACME_CA_URI
|
|
local -n test_certificate="LETSENCRYPT_${cid}_TEST"
|
|
if [[ $(lc "$test_certificate") == true ]]; then
|
|
# Use Let's Encrypt ACME V2 staging end point
|
|
acme_ca_uri="$ACME_CA_TEST_URI"
|
|
fi
|
|
|
|
# Set relevant --server parameter and ca folder name
|
|
params_base_arr+=(--server "$acme_ca_uri")
|
|
|
|
# Reproduce acme.sh logic to determine the ca account folder path
|
|
local ca_host_dir
|
|
ca_host_dir="$(echo "$acme_ca_uri" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
|
|
local ca_path_dir
|
|
ca_path_dir="$(echo "$acme_ca_uri" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)"
|
|
|
|
local relative_certificate_dir
|
|
if [[ "$wildcard_certificate" == 'true' ]]; then
|
|
relative_certificate_dir="wildcard_${base_domain:2}"
|
|
else
|
|
relative_certificate_dir="$base_domain"
|
|
fi
|
|
# If we're going to use one of LE stating endpoints ...
|
|
if [[ "$acme_ca_uri" =~ ^https://acme-staging.* ]]; then
|
|
# Unset accountemail
|
|
# force config dir to 'staging'
|
|
unset accountemail
|
|
config_home="/etc/acme.sh/staging"
|
|
# Prefix test certificate directory with _test_
|
|
relative_certificate_dir="_test_${relative_certificate_dir}"
|
|
fi
|
|
|
|
local absolute_certificate_dir="/etc/nginx/certs/$relative_certificate_dir"
|
|
params_issue_arr+=( \
|
|
--cert-file "${absolute_certificate_dir}/cert.pem" \
|
|
--key-file "${absolute_certificate_dir}/key.pem" \
|
|
--ca-file "${absolute_certificate_dir}/chain.pem" \
|
|
--fullchain-file "${absolute_certificate_dir}/fullchain.pem" \
|
|
)
|
|
|
|
[[ ! -d "$config_home" ]] && mkdir -p "$config_home"
|
|
params_base_arr+=(--config-home "$config_home")
|
|
local account_file="${config_home}/ca/${ca_host_dir}/${ca_path_dir}/account.json"
|
|
|
|
# External Account Binding (EAB)
|
|
local -n eab_kid="ACME_${cid}_EAB_KID"
|
|
local -n eab_hmac_key="ACME_${cid}_EAB_HMAC_KEY"
|
|
if [[ ! -f "$account_file" ]]; then
|
|
if [[ -n "${eab_kid}" && -n "${eab_hmac_key}" ]]; then
|
|
# Register the ACME account with the per container EAB credentials.
|
|
params_register_arr+=(--eab-kid "$eab_kid" --eab-hmac-key "$eab_hmac_key")
|
|
elif [[ -n "${ACME_EAB_KID// }" && -n "${ACME_EAB_HMAC_KEY// }" ]]; then
|
|
# We don't have per-container EAB kid and hmac key or Zero SSL API key.
|
|
# Register the ACME account with the default EAB credentials.
|
|
params_register_arr+=(--eab-kid "$ACME_EAB_KID" --eab-hmac-key "$ACME_EAB_HMAC_KEY")
|
|
elif [[ -n "${accountemail// }" ]]; then
|
|
# We don't have per container nor default EAB credentials, register a new account.
|
|
params_register_arr+=(--accountemail "$accountemail")
|
|
fi
|
|
fi
|
|
|
|
# Zero SSL
|
|
if [[ "$acme_ca_uri" == "https://acme.zerossl.com/v2/DV90" ]]; then
|
|
# Test if we already have:
|
|
# - an account file
|
|
# - the --accountemail account registration parameter
|
|
# - the --eab-kid and --eab-hmac-key account registration parameters
|
|
local account_ok='false'
|
|
if [[ -f "$account_file" ]]; then
|
|
account_ok='true'
|
|
elif in_array '--accountemail' 'params_register_arr'; then
|
|
account_ok='true'
|
|
elif in_array '--eab-kid' 'params_register_arr' && in_array '--eab-hmac-key' 'params_register_arr'; then
|
|
account_ok='true'
|
|
fi
|
|
|
|
if [[ $account_ok == 'false' ]]; then
|
|
local -n zerossl_api_key="ZEROSSL_${cid}_API_KEY"
|
|
if [[ -z "$zerossl_api_key" ]]; then
|
|
# Try using the default API key
|
|
zerossl_api_key="${ZEROSSL_API_KEY:-}"
|
|
fi
|
|
|
|
if [[ -n "${zerossl_api_key// }" ]]; then
|
|
# Generate a set of ACME EAB credentials using the ZeroSSL API.
|
|
local zerossl_api_response
|
|
if zerossl_api_response="$(curl -s -X POST "https://api.zerossl.com/acme/eab-credentials?access_key=${zerossl_api_key}")"; then
|
|
if [[ "$(jq -r .success <<< "$zerossl_api_response")" == 'true' ]]; then
|
|
eab_kid="$(jq -r .eab_kid <<< "$zerossl_api_response")"
|
|
eab_hmac_key="$(jq -r .eab_hmac_key <<< "$zerossl_api_response")"
|
|
params_register_arr+=(--eab-kid "$eab_kid" --eab-hmac-key "$eab_hmac_key")
|
|
[[ "$DEBUG" == 1 ]] && echo "Successfull EAB credentials request against the ZeroSSL API, got the following EAB kid : ${eab_kid}"
|
|
else
|
|
# The JSON response body indicated an unsuccesfull API call.
|
|
echo "Warning: the EAB credentials request against the ZeroSSL API was not successfull."
|
|
fi
|
|
else
|
|
# curl failed.
|
|
echo "Warning: curl failed to make an HTTP POST request to https://api.zerossl.com/acme/eab-credentials."
|
|
fi
|
|
else
|
|
# We don't have a Zero SSL ACME account, EAB credentials, a ZeroSSL API key or an account email :
|
|
# skip certificate account registration and certificate issuance.
|
|
echo "Error: usage of ZeroSSL require an email bound account. No EAB credentials, ZeroSSL API key or email were provided for this certificate, creation aborted."
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Account registration and update if required
|
|
if [[ ! -f "$account_file" ]]; then
|
|
params_register_arr=("${params_base_arr[@]}" "${params_register_arr[@]}")
|
|
[[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --register-account with the following parameters : ${params_register_arr[*]}"
|
|
acme.sh --register-account "${params_register_arr[@]}"
|
|
fi
|
|
if [[ -n "${accountemail// }" ]] && ! grep -q "mailto:$accountemail" "$account_file"; then
|
|
local -a params_update_arr=("${params_base_arr[@]}" --accountemail "$accountemail")
|
|
[[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --update-account with the following parameters : ${params_update_arr[*]}"
|
|
acme.sh --update-account "${params_update_arr[@]}"
|
|
fi
|
|
|
|
# If we still don't have an account.json file by this point, we've got an issue
|
|
if [[ ! -f "$account_file" ]]; then
|
|
echo "Error: no ACME account was found or registered for $accountemail and $acme_ca_uri, certificate creation aborted."
|
|
return 1
|
|
fi
|
|
|
|
# acme.sh pre and post hooks
|
|
local -n acme_pre_hook="ACME_${cid}_PRE_HOOK"
|
|
if [[ -n "${acme_pre_hook}" ]]; then
|
|
# Use per-container pre hook
|
|
params_issue_arr+=(--pre-hook "$acme_pre_hook")
|
|
elif [[ -n ${ACME_PRE_HOOK// } ]]; then
|
|
# Use default pre hook
|
|
params_issue_arr+=(--pre-hook "$ACME_PRE_HOOK")
|
|
fi
|
|
|
|
local -n acme_post_hook="ACME_${cid}_POST_HOOK"
|
|
if [[ -n "${acme_post_hook}" ]]; then
|
|
# Use per-container post hook
|
|
params_issue_arr+=(--post-hook "$acme_post_hook")
|
|
elif [[ -n ${ACME_POST_HOOK// } ]]; then
|
|
# Use default post hook
|
|
params_issue_arr+=(--post-hook "$ACME_POST_HOOK")
|
|
fi
|
|
|
|
local -n acme_preferred_chain="ACME_${cid}_PREFERRED_CHAIN"
|
|
if [[ -n "${acme_preferred_chain}" ]]; then
|
|
# Using amce.sh --preferred-chain to select alternate chain.
|
|
params_issue_arr+=(--preferred-chain "$acme_preferred_chain")
|
|
fi
|
|
if [[ "$RENEW_PRIVATE_KEYS" != 'false' && "$REUSE_PRIVATE_KEYS" != 'true' ]]; then
|
|
params_issue_arr+=(--always-force-new-domain-key)
|
|
fi
|
|
[[ "${2:-}" == "--force-renew" ]] && params_issue_arr+=(--force)
|
|
|
|
# Create directory for the first domain
|
|
mkdir -p "$absolute_certificate_dir"
|
|
set_ownership_and_permissions "$absolute_certificate_dir"
|
|
|
|
for domain in "${hosts_array[@]}"; do
|
|
# Add all the domains to certificate
|
|
params_issue_arr+=(--domain "$domain")
|
|
# If enabled, add location configuration for the domain
|
|
if [[ "$acme_challenge" == "HTTP-01" ]] && parse_true "${ACME_HTTP_CHALLENGE_LOCATION:=false}"; then
|
|
add_location_configuration "$domain" || reload_nginx
|
|
fi
|
|
done
|
|
|
|
params_issue_arr=("${params_base_arr[@]}" "${params_issue_arr[@]}")
|
|
[[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --issue with the following parameters : ${params_issue_arr[*]}"
|
|
echo "Creating/renewal $base_domain certificates... (${hosts_array[*]})"
|
|
acme.sh --issue "${params_issue_arr[@]}"
|
|
|
|
local acmesh_return=$?
|
|
|
|
# 0 = success, 2 = RENEW_SKIP
|
|
if [[ $acmesh_return == 0 || $acmesh_return == 2 ]]; then
|
|
for domain in "${hosts_array[@]}"; do
|
|
create_links "$relative_certificate_dir" "$domain" \
|
|
&& should_reload_nginx='true' \
|
|
&& should_restart_container='true'
|
|
done
|
|
echo "${COMPANION_VERSION:-}" > "${absolute_certificate_dir}/.companion"
|
|
set_ownership_and_permissions "${absolute_certificate_dir}/.companion"
|
|
# Make private key root readable only
|
|
for file in cert.pem key.pem chain.pem fullchain.pem; do
|
|
local file_path="${absolute_certificate_dir}/${file}"
|
|
[[ -e "$file_path" ]] && set_ownership_and_permissions "$file_path"
|
|
done
|
|
local acme_private_key
|
|
acme_private_key="$(find /etc/acme.sh -name "*.key" -path "*${hosts_array[0]}*")"
|
|
[[ -e "$acme_private_key" ]] && set_ownership_and_permissions "$acme_private_key"
|
|
# Queue nginx reload if a certificate was issued or renewed
|
|
[[ $acmesh_return -eq 0 ]] \
|
|
&& should_reload_nginx='true' \
|
|
&& should_restart_container='true'
|
|
fi
|
|
|
|
# Restart container if certs are updated and the respective environmental variable is set
|
|
local -n restart_container="LETSENCRYPT_${cid}_RESTART_CONTAINER"
|
|
if [[ $(lc "$restart_container") == true ]] && [[ "$should_restart_container" == 'true' ]]; then
|
|
echo "Restarting container (${cid})..."
|
|
docker_restart "${cid}"
|
|
fi
|
|
|
|
for domain in "${hosts_array[@]}"; do
|
|
if [[ -f "/etc/nginx/conf.d/standalone-cert-$domain.conf" ]]; then
|
|
[[ "$DEBUG" == 1 ]] && echo "Debug: removing standalone configuration file /etc/nginx/conf.d/standalone-cert-$domain.conf"
|
|
rm -f "/etc/nginx/conf.d/standalone-cert-$domain.conf" && should_reload_nginx='true'
|
|
fi
|
|
done
|
|
|
|
}
|
|
|
|
function update_certs {
|
|
local -a LETSENCRYPT_CONTAINERS
|
|
local -a LETSENCRYPT_STANDALONE_CERTS
|
|
|
|
pushd /etc/nginx/certs > /dev/null || return
|
|
check_nginx_proxy_container_run || return
|
|
|
|
# Load relevant container settings
|
|
if [[ -f /app/letsencrypt_service_data ]]; then
|
|
source /app/letsencrypt_service_data
|
|
else
|
|
echo "Warning: /app/letsencrypt_service_data not found, skipping data from containers."
|
|
fi
|
|
|
|
# Load settings for standalone certs
|
|
if [[ -f /app/letsencrypt_user_data ]]; then
|
|
if source /app/letsencrypt_user_data; then
|
|
for cid in "${LETSENCRYPT_STANDALONE_CERTS[@]}"; do
|
|
local -n hosts_array="LETSENCRYPT_${cid}_HOST"
|
|
|
|
local -n acme_challenge="ACME_${cid}_CHALLENGE"
|
|
acme_challenge="${acme_challenge:-HTTP-01}"
|
|
|
|
if [[ "$acme_challenge" == "HTTP-01" ]]; then
|
|
for domain in "${hosts_array[@]}"; do
|
|
add_standalone_configuration "$domain"
|
|
done
|
|
fi
|
|
done
|
|
reload_nginx
|
|
LETSENCRYPT_CONTAINERS+=( "${LETSENCRYPT_STANDALONE_CERTS[@]}" )
|
|
else
|
|
echo "Warning: could not source /app/letsencrypt_user_data, skipping user data"
|
|
fi
|
|
fi
|
|
|
|
should_reload_nginx='false'
|
|
for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do
|
|
# Pass the eventual --force-renew arg to update_cert() as second arg
|
|
update_cert "$cid" "${1:-}"
|
|
done
|
|
|
|
cleanup_links && should_reload_nginx='true'
|
|
|
|
[[ "$should_reload_nginx" == 'true' ]] && reload_nginx
|
|
|
|
popd > /dev/null || return
|
|
}
|
|
|
|
# Allow the script functions to be sourced without starting the Service Loop.
|
|
if [ "${1}" == "--source-only" ]; then
|
|
return 0
|
|
fi
|
|
|
|
pid=
|
|
# Service Loop: When this script exits, start it again.
|
|
trap '[[ $pid ]] && kill $pid; exec $0' EXIT
|
|
trap 'trap - EXIT' INT TERM
|
|
|
|
update_certs "$@"
|
|
|
|
# Wait some amount of time
|
|
echo "Sleep for ${CERTS_UPDATE_INTERVAL}s"
|
|
sleep $CERTS_UPDATE_INTERVAL & pid=$!
|
|
wait
|
|
pid=
|