supports dns alias

This commit is contained in:
Martin Berghaus
2025-10-25 10:12:46 +02:00
parent 2b3da35e19
commit d676bd33e2
3 changed files with 98 additions and 28 deletions

View File

@@ -10,8 +10,11 @@ services:
environment: environment:
AUTOACME_CONTAINER_HOSTNAME: ${HOSTNAME:?"HINT - You may run 'export HOSTNAME' first."} AUTOACME_CONTAINER_HOSTNAME: ${HOSTNAME:?"HINT - You may run 'export HOSTNAME' first."}
AUTOACME_GIT_REPOSITORY_VIA_SSH: 'ssh://git@git.your-domain.net/your-repo.git' AUTOACME_GIT_REPOSITORY_VIA_SSH: 'ssh://git@git.your-domain.net/your-repo.git'
# Optionally you can set a path inside the git repository # Optionally you can set a path inside the git repository, requires a repository.
AUTOACME_PATH_IN_GIT_REPOSITORY: 'hosts/all/etc/ssl/domains/' AUTOACME_PATH_IN_GIT_REPOSITORY: 'hosts/all/etc/ssl/domains/'
# Optionally you can define a alias domain, see: https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode
AUTOACME_CHALLENGE_ALIAS: 'alias-domain.net'
# See: https://github.com/acmesh-official/acme.sh/wiki/dnsapi and search your provider like 'hetzner' e.g. # See: https://github.com/acmesh-official/acme.sh/wiki/dnsapi and search your provider like 'hetzner' e.g.
AUTOACME_DNS_PROVIDER: 'dns_hetzner' AUTOACME_DNS_PROVIDER: 'dns_hetzner'
HETZNER_Token: 'your-token' HETZNER_Token: 'your-token'

View File

@@ -171,26 +171,57 @@ function isExpiringSoon(){
return 1 return 1
} }
function single(){ function printChallengeAliasOption() {
local _ACME_FILE _DOMAIN _DOMAIN_FOLDER _MODE _OPTIONS _RESULT_CERTS local _CHALLENGE_ALIAS_FILE _DOMAIN _MODE _RESULT_CERTS
_RESULT_CERTS="${RESULT_CERTS:?"printChallengeAliasOption(): Missing global parameter RESULT_CERTS"}"
_MODE="${1:?"printChallengeAliasOption(): Missing first parameter MODE"}"
_DOMAIN="${2:?"printChallengeAliasOption: Missing second parameter DOMAIN"}"
_CHALLENGE_ALIAS_FILE="${_RESULT_CERTS}_.${_DOMAIN}/challenge-alias"
readonly _CHALLENGE_ALIAS_FILE _DOMAIN _MODE _RESULT_CERTS
# store given challenge-alias and print CHALLENGE_ALIAS_OPTION
[ "${_MODE}" = "dnsWithAlias" ] \
&& echo "${ACME_CHALLENGE_ALIAS:?"printChallengeAliasOption(): Missing global parameter ACME_CHALLENGE_ALIAS"}" > "${_CHALLENGE_ALIAS_FILE}" \
&& echo " --challenge-alias ${ACME_CHALLENGE_ALIAS}" \
&& return 0
# read stored challenge-alias and print CHALLENGE_ALIAS_OPTION
[ "${_MODE}" = "dns" ] \
&& [ -f "${_CHALLENGE_ALIAS_FILE}" ] \
&& echo " --challenge-alias $(cat "${_CHALLENGE_ALIAS_FILE}")" \
&& return 0
return 0
}
function printDomainFolder() {
local _DOMAIN _DOMAIN_FOLDER _MODE _RESULT_CERTS
_RESULT_CERTS="${RESULT_CERTS:?"single(): Missing global parameter RESULT_CERTS"}" _RESULT_CERTS="${RESULT_CERTS:?"single(): Missing global parameter RESULT_CERTS"}"
_ACME_FILE="${ACME_FILE:?"single(): Missing global parameter ACME_FILE"}"
_MODE="${1:?"single(): Missing first parameter MODE"}" _MODE="${1:?"single(): Missing first parameter MODE"}"
_DOMAIN="${2:?"single(): Missing second parameter DOMAIN"}" _DOMAIN="${2:?"single(): Missing second parameter DOMAIN"}"
[ "${_MODE}" = "dns" ] \ [ "${_MODE}" = "dns" ] \
&& _DOMAIN_FOLDER="${_RESULT_CERTS}_.${_DOMAIN}" && _DOMAIN_FOLDER="${_RESULT_CERTS}_.${_DOMAIN}"
[ "${_MODE}" = "dnsWithAlias" ] \
&& _DOMAIN_FOLDER="${_RESULT_CERTS}_.${_DOMAIN}"
[ "${_MODE}" = "http" ] \ [ "${_MODE}" = "http" ] \
&& _DOMAIN_FOLDER="${_RESULT_CERTS}${_DOMAIN}" && _DOMAIN_FOLDER="${_RESULT_CERTS}${_DOMAIN}"
readonly _DOMAIN _DOMAIN_FOLDER _MODE _RESULT_CERTS
# always --force because we check expiring on ourself echo "${_DOMAIN_FOLDER}" \
# _OPTIONS="--issue --force --test" && return 0
_OPTIONS="--issue --force"
[ "${_MODE}" = "dns" ] \ return 1
&& _OPTIONS="${_OPTIONS} --dns ${AUTOACME_DNS_PROVIDER:?"single(): Missing global parameter AUTOACME_DNS_PROVIDER"} --domain *.${_DOMAIN}" }
[ "${_MODE}" = "http" ] \
&& _OPTIONS="${_OPTIONS} --webroot /var/www/letsencrypt" function single(){
readonly _ACME_FILE _DOMAIN _DOMAIN_FOLDER _MODE _OPTIONS _RESULT_CERTS local _ACME_FILE _DOMAIN _MODE _RESULT_CERTS
_RESULT_CERTS="${RESULT_CERTS:?"single(): Missing global parameter RESULT_CERTS"}"
_ACME_FILE="${ACME_FILE:?"single(): Missing global parameter ACME_FILE"}"
_MODE="${1:?"single(): Missing first parameter MODE"}"
_DOMAIN="${2:?"single(): Missing second parameter DOMAIN"}"
readonly _ACME_FILE _DOMAIN _MODE _RESULT_CERTS
! [ -f "${_ACME_FILE}" ] \ ! [ -f "${_ACME_FILE}" ] \
&& echo "Program 'acme.sh' seams not to be installed. Try run 'renewCerts.sh --setup'." \ && echo "Program 'acme.sh' seams not to be installed. Try run 'renewCerts.sh --setup'." \
@@ -200,6 +231,10 @@ function single(){
! checkConfigViaHttp "${_MODE}" "${_DOMAIN}" \ ! checkConfigViaHttp "${_MODE}" "${_DOMAIN}" \
&& return 1 && return 1
local _DOMAIN_FOLDER
_DOMAIN_FOLDER="$(printDomainFolder "${_MODE}" "${_DOMAIN}")"
readonly _DOMAIN_FOLDER
# create folder for results # create folder for results
! [ -d "${_DOMAIN_FOLDER}" ] \ ! [ -d "${_DOMAIN_FOLDER}" ] \
&& echo -n "Creating folder '${_DOMAIN_FOLDER}'... " \ && echo -n "Creating folder '${_DOMAIN_FOLDER}'... " \
@@ -216,12 +251,27 @@ function single(){
[ -f "${_DOMAIN_FOLDER}/private.key" ] \ [ -f "${_DOMAIN_FOLDER}/private.key" ] \
&& cp --preserve=mode,ownership "${_DOMAIN_FOLDER}/private.key" "${_DOMAIN_FOLDER}/private.key.bak" && cp --preserve=mode,ownership "${_DOMAIN_FOLDER}/private.key" "${_DOMAIN_FOLDER}/private.key.bak"
local _OPTIONS
# always --force because we check expiring on ourself
# _OPTIONS="--issue --force --test"
_OPTIONS="--issue --force"
[ "${_MODE}" = "dns" ] \
&& _OPTIONS="${_OPTIONS}$(printChallengeAliasOption "${_MODE}" "${_DOMAIN}")" \
&& _OPTIONS="${_OPTIONS} --dns ${AUTOACME_DNS_PROVIDER:?"single(): Missing global parameter AUTOACME_DNS_PROVIDER"} --domain *.${_DOMAIN}"
[ "${_MODE}" = "dnsWithAlias" ] \
&& _OPTIONS="${_OPTIONS}$(printChallengeAliasOption "${_MODE}" "${_DOMAIN}")" \
&& _OPTIONS="${_OPTIONS} --dns ${AUTOACME_DNS_PROVIDER:?"single(): Missing global parameter AUTOACME_DNS_PROVIDER"} --domain *.${_DOMAIN}"
[ "${_MODE}" = "http" ] \
&& _OPTIONS="${_OPTIONS} --webroot /var/www/letsencrypt"
readonly _OPTIONS
${_ACME_FILE} ${_OPTIONS} \ ${_ACME_FILE} ${_OPTIONS} \
--domain "${_DOMAIN}" \ --domain "${_DOMAIN}" \
--server "letsencrypt" \ --server "letsencrypt" \
--keylength "ec-384" \ --keylength "ec-384" \
--fullchain-file "${_DOMAIN_FOLDER}/fullchain.crt" \ --fullchain-file "${_DOMAIN_FOLDER}/fullchain.crt" \
--key-file "${_DOMAIN_FOLDER}/private.key" \ --key-file "${_DOMAIN_FOLDER}/private.key" \
&& openssl pkcs12 -export -in "${_DOMAIN_FOLDER}/fullchain.crt" -inkey "${_DOMAIN_FOLDER}/private.key" -out "${_DOMAIN_FOLDER}/bundle.pkx" -passout pass: \
&& echo "Certificate of domain '${_DOMAIN}' was updated." \ && echo "Certificate of domain '${_DOMAIN}' was updated." \
&& tryGitPush "${_DOMAIN}" \ && tryGitPush "${_DOMAIN}" \
&& return 0 && return 0
@@ -303,8 +353,11 @@ function setup(){
function usage(){ function usage(){
echo echo
echo 'Commands:' echo 'Commands:'
echo ' (--dns|--http) --own [--force] : Iterates all domains found in RESULT_CERTS.' echo ' (--dns|--http) --own [--force] : Iterates all domains found in RESULT_CERTS.'
echo ' (--dns|--http) --single DOMAIN [--force] : Issues a certificate for the given domain.' echo
echo ' --dns --single DOMAIN [--force] : Issues a certificate for the given domain using DNS mode.'
echo ' --dns-withAlias --single DOMAIN [--force] : Issues a certificate for the given domain using DNS mode with challange alias.'
echo ' --http --single DOMAIN [--force] : Issues a certificate for the given domain using HTTP mode.'
echo echo
echo 'Current environment:' echo 'Current environment:'
echo " Full name of this script: OWN_FULLNAME='${OWN_FULLNAME}'" echo " Full name of this script: OWN_FULLNAME='${OWN_FULLNAME}'"
@@ -313,6 +366,7 @@ function usage(){
echo " Tar file containing the setup of 'acme.sh': ACME_TAR_FILE='${ACME_TAR_FILE}'" echo " Tar file containing the setup of 'acme.sh': ACME_TAR_FILE='${ACME_TAR_FILE}'"
echo " Setup file of 'acme.sh' after extraction: ACME_SETUP_FILE='${ACME_SETUP_FILE}'" echo " Setup file of 'acme.sh' after extraction: ACME_SETUP_FILE='${ACME_SETUP_FILE}'"
echo " Full name of the installed script 'acme.sh': ACME_FILE='${ACME_FILE}'" echo " Full name of the installed script 'acme.sh': ACME_FILE='${ACME_FILE}'"
echo " 'acme.sh' will this alias domain in dns-mode: ACME_CHALLENGE_ALIAS='${ACME_CHALLENGE_ALIAS}'"
echo " Output:" echo " Output:"
echo " Path were the issued certificate are saved: RESULT_CERTS='${RESULT_CERTS}'" echo " Path were the issued certificate are saved: RESULT_CERTS='${RESULT_CERTS}'"
@@ -327,15 +381,16 @@ function main(){
&& source "/autoACME.env" \ && source "/autoACME.env" \
&& echo "Environment '/autoACME.env' loaded." && echo "Environment '/autoACME.env' loaded."
local ACME_FILE ACME_VERSION ACME_SETUP_FILE ACME_TAR_FILE OWN_FULLNAME RESULT_CERTS local ACME_CHALLENGE_ALIAS ACME_FILE ACME_VERSION ACME_SETUP_FILE ACME_TAR_FILE OWN_FULLNAME RESULT_CERTS
OWN_FULLNAME="$(readlink -e ${0})" OWN_FULLNAME="$(readlink -e ${0})"
ACME_CHALLENGE_ALIAS="${AUTOACME_CHALLENGE_ALIAS:-""}"
ACME_FILE="/root/.acme.sh/acme.sh" ACME_FILE="/root/.acme.sh/acme.sh"
ACME_VERSION="acme.sh-3.1.1" ACME_VERSION="acme.sh-3.1.1"
ACME_SETUP_FILE="/tmp/acme.sh-setup/${ACME_VERSION}/acme.sh" ACME_SETUP_FILE="/tmp/acme.sh-setup/${ACME_VERSION}/acme.sh"
ACME_TAR_FILE="${OWN_FULLNAME%/*}/${ACME_VERSION}.tar.gz" ACME_TAR_FILE="${OWN_FULLNAME%/*}/${ACME_VERSION}.tar.gz"
RESULT_CERTS="${AUTOACME_RESULT_CERTS%/}" #Removes shortest matching pattern '/' from the end RESULT_CERTS="${AUTOACME_RESULT_CERTS%/}" #Removes shortest matching pattern '/' from the end
RESULT_CERTS="${RESULT_CERTS:-"/etc/nginx/ssl"}/" RESULT_CERTS="${RESULT_CERTS:-"/etc/nginx/ssl"}/"
readonly ACME_FILE ACME_VERSION ACME_SETUP_FILE ACME_TAR_FILE OWN_FULLNAME RESULT_CERTS readonly ACME_CHALLENGE_ALIAS ACME_FILE ACME_VERSION ACME_SETUP_FILE ACME_TAR_FILE OWN_FULLNAME RESULT_CERTS
local REPOSITORY_URL local REPOSITORY_URL
isGitRepository \ isGitRepository \
@@ -363,6 +418,11 @@ function main(){
single "dns" "${3}" "${4}" \ single "dns" "${3}" "${4}" \
&& return 0 && return 0
;; ;;
--dns-withAlias--single)
echo "Issue single certificate '${3}' at $(date +%F_%T) via DNS using a challange alias:"
single "dnsWithAlias" "${3}" "${4}" \
&& return 0
;;
--http--single) --http--single)
echo "Issue single certificate '${3}' at $(date +%F_%T) via HTTP:" echo "Issue single certificate '${3}' at $(date +%F_%T) via HTTP:"
single "http" "${3}" "${4}" \ single "http" "${3}" "${4}" \

View File

@@ -7,22 +7,28 @@ function createEnvironmentFile() {
readonly _ENVIRONMENT_FILE _REPOSITORY_FOLDER readonly _ENVIRONMENT_FILE _REPOSITORY_FOLDER
# Save environment for cronjob # Save environment for cronjob
export -p | grep -v -E "(HOME|OLDPWD|PWD|SHLVL)" > "${_ENVIRONMENT_FILE}" export -p | grep -v -E "(HOME|OLDPWD|PWD|SHLVL)" > "${_ENVIRONMENT_FILE}" \
&& echo "SUCCESS: there values were exported into file: '${_ENVIRONMENT_FILE}'" \
&& echo " - AUTOACME_CONTAINER_HOSTNAME: ${AUTOACME_CONTAINER_HOSTNAME}" \
&& echo " - AUTOACME_DNS_PROVIDER: ${AUTOACME_DNS_PROVIDER}" \
&& echo " - AUTOACME_CHALLENGE_ALIAS: ${AUTOACME_CHALLENGE_ALIAS}" \
&& echo " (additional the DNS provider specific values were added)" \
&& echo " - AUTOACME_GIT_REPOSITORY_VIA_SSH: ${AUTOACME_GIT_REPOSITORY_VIA_SSH}" \
&& echo " - AUTOACME_PATH_IN_GIT_REPOSITORY: ${AUTOACME_PATH_IN_GIT_REPOSITORY}"
[ "${AUTOACME_GIT_REPOSITORY_VIA_SSH}" == "" ] \ [ "${AUTOACME_GIT_REPOSITORY_VIA_SSH}" == "" ] \
&& echo "declare -x AUTOACME_RESULT_CERTS=\"${AUTOACME_REPOSITORY_FOLDER#/}\"" >> "${_ENVIRONMENT_FILE}" \ && echo "declare -x AUTOACME_RESULT_CERTS=\"${AUTOACME_REPOSITORY_FOLDER#/}\"" >> "${_ENVIRONMENT_FILE}" \
&& echo "SUCCESS: saved environment (without git) into file '${_ENVIRONMENT_FILE}'." \ && echo "SUCCESS: added AUTOACME_RESULT_CERTS (without git) into file '${_ENVIRONMENT_FILE}'." \
&& return 0 && echo " - AUTOACME_RESULT_CERTS: ${AUTOACME_REPOSITORY_FOLDER#/}" \
&& echo " (depends on if there is a git repo and the path for the certs in it)"
echo "declare -x AUTOACME_RESULT_CERTS=\"${AUTOACME_REPOSITORY_FOLDER}${AUTOACME_PATH_IN_GIT_REPOSITORY#/}\"" >> "${_ENVIRONMENT_FILE}" \ ! [ "${AUTOACME_GIT_REPOSITORY_VIA_SSH}" == "" ] \
&& echo "SUCCESS: saved environment (with git) into file '${_ENVIRONMENT_FILE}'." \ && echo "declare -x AUTOACME_RESULT_CERTS=\"${AUTOACME_REPOSITORY_FOLDER}${AUTOACME_PATH_IN_GIT_REPOSITORY#/}\"" >> "${_ENVIRONMENT_FILE}" \
&& return 0 && echo "SUCCESS: added AUTOACME_RESULT_CERTS (with git) into file '${_ENVIRONMENT_FILE}'." \
&& echo " - AUTOACME_RESULT_CERTS: ${AUTOACME_REPOSITORY_FOLDER}${AUTOACME_PATH_IN_GIT_REPOSITORY#/}" \
&& echo " (depends on if there is a git repo and the path for the certs in it)"
echo return 0
echo "FAILED: something went wrong during the creation of the environment file: '${_ENVIRONMENT_FILE}'..."
echo " This file is mandantory to use 'renewCerts.sh' with cron."
echo
return 1
} }
function ensureThereAreSSHKeys() { function ensureThereAreSSHKeys() {
@@ -118,7 +124,8 @@ function prepareThisRuntimeForUsingGitOrIgnore() {
&& echo \ && echo \
&& return 0 && return 0
ensureThereAreSSHKeys \ echo \
&& ensureThereAreSSHKeys \
&& ensureGitIsInstalled \ && ensureGitIsInstalled \
&& ensureRepositoryIsAvailableAndWritable \ && ensureRepositoryIsAvailableAndWritable \
&& return 0 && return 0