#!/usr/bin/with-contenv bash # shellcheck shell=bash # Display variables for troubleshooting echo -e "Variables set:\\n\ PUID=${PUID}\\n\ PGID=${PGID}\\n\ TZ=${TZ}\\n\ URL=${URL}\\n\ SUBDOMAINS=${SUBDOMAINS}\\n\ EXTRA_DOMAINS=${EXTRA_DOMAINS}\\n\ ONLY_SUBDOMAINS=${ONLY_SUBDOMAINS}\\n\ VALIDATION=${VALIDATION}\\n\ CERTPROVIDER=${CERTPROVIDER}\\n\ DNSPLUGIN=${DNSPLUGIN}\\n\ EMAIL=${EMAIL}\\n\ STAGING=${STAGING}\\n" # Sanitize variables SANED_VARS=(DNSPLUGIN EMAIL EXTRA_DOMAINS ONLY_SUBDOMAINS STAGING SUBDOMAINS URL VALIDATION CERTPROVIDER) for i in "${SANED_VARS[@]}"; do export echo "${i}"="${!i//\"/}" export echo "${i}"="$(echo "${!i}" | tr '[:upper:]' '[:lower:]')" done # check to make sure DNSPLUGIN is selected if dns validation is used if [[ "${VALIDATION}" = "dns" ]] && [[ ! "${DNSPLUGIN}" =~ ^(acmedns|aliyun|azure|cloudflare|cpanel|desec|digitalocean|directadmin|dnsimple|dnsmadeeasy|dnspod|do|domeneshop|duckdns|dynu|gandi|gehirn|godaddy|google|he|hetzner|infomaniak|inwx|ionos|linode|loopia|luadns|netcup|njalla|nsone|ovh|porkbun|rfc2136|route53|sakuracloud|standalone|transip|vultr)$ ]]; then echo "Please set the DNSPLUGIN variable to a valid plugin name. See docker info for more details." sleep infinity fi # call option with parameters: $1=name $2=value $3=file function set_ini_value() { name=${1//\//\\/} value=${2//\//\\/} sed -i \ -e '/^#\?\(\s*'"${name}"'\s*=\s*\).*/{s//\1'"${value}"'/;:a;n;ba;q}' \ -e '$a'"${name}"'='"${value}" "${3}" } # ensure config files exist touch /config/etc/letsencrypt/cli.ini set_ini_value "agree-tos" "true" /config/etc/letsencrypt/cli.ini # copy dns default configs cp -n /defaults/dns-conf/* /config/dns-conf/ lsiown -R abc:abc /config/dns-conf # copy default renewal hooks chmod -R +x /defaults/etc/letsencrypt/renewal-hooks cp -nR /defaults/etc/letsencrypt/renewal-hooks/* /config/etc/letsencrypt/renewal-hooks/ lsiown -R abc:abc /config/etc/letsencrypt/renewal-hooks # replace nginx service location in renewal hooks find /config/etc/letsencrypt/renewal-hooks/ -type f -exec sed -i 's|/run/service/nginx|/run/service/svc-nginx|g' {} \; find /config/etc/letsencrypt/renewal-hooks/ -type f -exec sed -i 's|/var/run/s6/services/nginx|/run/service/svc-nginx|g' {} \; # create original config file if it doesn't exist, move non-hidden legacy file to hidden if [[ -f "/config/donoteditthisfile.conf" ]]; then mv /config/donoteditthisfile.conf /config/.donoteditthisfile.conf fi if [[ ! -f "/config/.donoteditthisfile.conf" ]]; then echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf echo "Created .donoteditthisfile.conf" fi # load original config settings # shellcheck source=/dev/null . /config/.donoteditthisfile.conf # setting ORIGDOMAIN for use in revoke sections if [[ "${ORIGONLY_SUBDOMAINS}" = "true" ]] && [[ ! "${ORIGSUBDOMAINS}" = "wildcard" ]]; then ORIGDOMAIN="$(echo "${ORIGSUBDOMAINS}" | tr ',' ' ' | awk '{print $1}').${ORIGURL}" else ORIGDOMAIN="${ORIGURL}" fi # update plugin names in dns conf inis sed -i 's|^certbot[-_]dns[-_]aliyun:||g' /config/dns-conf/aliyun.ini sed -i 's|^certbot[-_]dns[-_]cpanel:||g' /config/dns-conf/cpanel.ini sed -i 's|^dns[-_]cpanel[-_]|cpanel_|g' /config/dns-conf/cpanel.ini sed -i 's|^directadmin[-_]|dns_directadmin_|g' /config/dns-conf/directadmin.ini sed -i 's|^certbot[-_]dns[-_]domeneshop:||g' /config/dns-conf/domeneshop.ini sed -i 's|^certbot[-_]plugin[-_]gandi:dns[-_]|dns_gandi_|g' /config/dns-conf/gandi.ini sed -i 's|^certbot[-_]dns[-_]inwx:||g' /config/dns-conf/inwx.ini sed -i 's|^certbot[-_]dns[-_]transip:||g' /config/dns-conf/transip.ini # update plugin names in renewal conf if [[ -f "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" ]] && [[ "${ORIGVALIDATION}" = "dns" ]]; then if [[ "${ORIGDNSPLUGIN}" =~ ^(aliyun)$ ]]; then sed -i 's|^authenticator = certbot[-_]dns[-_]aliyun:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]dns[-_]aliyun:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(cpanel)$ ]]; then sed -i 's|^authenticator = certbot[-_]dns[-_]cpanel:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]dns[-_]cpanel:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^authenticator = dns[-_]cpanel|authenticator = cpanel|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^dns[-_]cpanel[-_]|cpanel_|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(directadmin)$ ]]; then sed -i 's|^authenticator = directadmin|authenticator = dns-directadmin|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^directadmin[-_]|dns_directadmin_|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(domeneshop)$ ]]; then sed -i 's|^authenticator = certbot[-_]dns[-_]domeneshop:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]dns[-_]domeneshop:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(gandi)$ ]]; then sed -i 's|^authenticator = certbot[-_]plugin[-_]gandi:dns|authenticator = dns-gandi|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]plugin[-_]gandi:dns[-_]|dns_gandi_|g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(inwx)$ ]]; then sed -i 's|^authenticator = certbot[-_]dns[-_]inwx:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]dns[-_]inwx:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi if [[ "${ORIGDNSPLUGIN}" =~ ^(transip)$ ]]; then sed -i 's|^authenticator = certbot[-_]dns[-_]transip:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" sed -i 's|^certbot[-_]dns[-_]transip:||g' "/config/etc/letsencrypt/renewal/${ORIGDOMAIN}.conf" fi fi # set default validation to http if [[ -z "${VALIDATION}" ]]; then VALIDATION="http" echo "VALIDATION parameter not set; setting it to http" fi # set duckdns validation to dns if [[ "${VALIDATION}" = "duckdns" ]]; then VALIDATION="dns" DNSPLUGIN="duckdns" if [[ -n "${DUCKDNSTOKEN}" ]] && ! grep -q "dns_duckdns_token=${DUCKDNSTOKEN}$" /config/dns-conf/duckdns.ini; then sed -i "s|^dns_duckdns_token=.*|dns_duckdns_token=${DUCKDNSTOKEN}|g" /config/dns-conf/duckdns.ini fi fi if [[ "${VALIDATION}" = "dns" ]] && [[ "${DNSPLUGIN}" = "duckdns" ]]; then if [[ "${SUBDOMAINS}" = "wildcard" ]]; then echo "the resulting certificate will only cover the subdomains due to a limitation of duckdns, so it is advised to set the root location to use www.subdomain.duckdns.org" export ONLY_SUBDOMAINS=true else echo "the resulting certificate will only cover the main domain due to a limitation of duckdns, ie. subdomain.duckdns.org" export SUBDOMAINS="" fi export EXTRA_DOMAINS="" fi # setting the symlink for key location rm -rf /config/keys/letsencrypt if [[ "${ONLY_SUBDOMAINS}" = "true" ]] && [[ ! "${SUBDOMAINS}" = "wildcard" ]]; then DOMAIN="$(echo "${SUBDOMAINS}" | tr ',' ' ' | awk '{print $1}').${URL}" ln -s ../etc/letsencrypt/live/"${DOMAIN}" /config/keys/letsencrypt else ln -s ../etc/letsencrypt/live/"${URL}" /config/keys/letsencrypt fi # checking for changes in cert variables, revoking certs if necessary if [[ ! "${URL}" = "${ORIGURL}" ]] || [[ ! "${SUBDOMAINS}" = "${ORIGSUBDOMAINS}" ]] || [[ ! "${ONLY_SUBDOMAINS}" = "${ORIGONLY_SUBDOMAINS}" ]] || [[ ! "${EXTRA_DOMAINS}" = "${ORIGEXTRA_DOMAINS}" ]] || [[ ! "${VALIDATION}" = "${ORIGVALIDATION}" ]] || [[ ! "${DNSPLUGIN}" = "${ORIGDNSPLUGIN}" ]] || [[ ! "${PROPAGATION}" = "${ORIGPROPAGATION}" ]] || [[ ! "${STAGING}" = "${ORIGSTAGING}" ]] || [[ ! "${CERTPROVIDER}" = "${ORIGCERTPROVIDER}" ]]; then echo "Different validation parameters entered than what was used before. Revoking and deleting existing certificate, and an updated one will be created" if [[ "${ORIGCERTPROVIDER}" = "zerossl" ]] && [[ -n "${ORIGEMAIL}" ]]; then REV_EAB_CREDS=$(curl -s https://api.zerossl.com/acme/eab-credentials-email --data "email=${ORIGEMAIL}") REV_ZEROSSL_EAB_KID=$(echo "${REV_EAB_CREDS}" | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_kid'])") REV_ZEROSSL_EAB_HMAC_KEY=$(echo "${REV_EAB_CREDS}" | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_hmac_key'])") if [[ -z "${REV_ZEROSSL_EAB_KID}" ]] || [[ -z "${REV_ZEROSSL_EAB_HMAC_KEY}" ]]; then echo "Unable to retrieve EAB credentials from ZeroSSL. Check the outgoing connections to api.zerossl.com and dns. Sleeping." sleep infinity fi REV_ACMESERVER=("https://acme.zerossl.com/v2/DV90" "--eab-kid" "${REV_ZEROSSL_EAB_KID}" "--eab-hmac-key" "${REV_ZEROSSL_EAB_HMAC_KEY}") elif [[ "${ORIGSTAGING}" = "true" ]]; then REV_ACMESERVER=("https://acme-staging-v02.api.letsencrypt.org/directory") else REV_ACMESERVER=("https://acme-v02.api.letsencrypt.org/directory") fi if [[ -f /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem ]]; then certbot revoke --non-interactive --cert-path /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem --server "${REV_ACMESERVER[@]}" || true fi rm -rf /config/etc/letsencrypt/{accounts,archive,live,renewal} fi # saving new variables echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf # Check if the cert is using the old LE root cert, revoke and regen if necessary if [[ -f "/config/keys/letsencrypt/chain.pem" ]] && { [[ "${CERTPROVIDER}" == "letsencrypt" ]] || [[ "${CERTPROVIDER}" == "" ]]; } && [[ "${STAGING}" != "true" ]] && ! openssl x509 -in /config/keys/letsencrypt/chain.pem -noout -issuer | grep -q "ISRG Root X"; then echo "The cert seems to be using the old LE root cert, which is no longer valid. Deleting and revoking." REV_ACMESERVER=("https://acme-v02.api.letsencrypt.org/directory") if [[ -f /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem ]]; then certbot revoke --non-interactive --cert-path /config/etc/letsencrypt/live/"${ORIGDOMAIN}"/fullchain.pem --server "${REV_ACMESERVER[@]}" || true fi rm -rf /config/etc/letsencrypt/{accounts,archive,live,renewal} fi # if zerossl is selected or staging is set to true, use the relevant server if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ "${STAGING}" = "true" ]]; then echo "ZeroSSL does not support staging mode, ignoring STAGING variable" fi if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then echo "ZeroSSL is selected as the cert provider, registering cert with ${EMAIL}" ACMESERVER="https://acme.zerossl.com/v2/DV90" elif [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -z "${EMAIL}" ]]; then echo "ZeroSSL is selected as the cert provider, but the e-mail address has not been entered. Please visit https://zerossl.com, register a new account and set the account e-mail address in the EMAIL environment variable" sleep infinity elif [[ "${STAGING}" = "true" ]]; then echo "NOTICE: Staging is active" echo "Using Let's Encrypt as the cert provider" ACMESERVER="https://acme-staging-v02.api.letsencrypt.org/directory" else echo "Using Let's Encrypt as the cert provider" ACMESERVER="https://acme-v02.api.letsencrypt.org/directory" fi set_ini_value "server" "${ACMESERVER}" /config/etc/letsencrypt/cli.ini # figuring out url only vs url & subdomains vs subdomains only if [[ -n "${SUBDOMAINS}" ]]; then echo "SUBDOMAINS entered, processing" if [[ "${SUBDOMAINS}" = "wildcard" ]]; then if [[ "${ONLY_SUBDOMAINS}" = true ]]; then export URL_REAL="*.${URL}" echo "Wildcard cert for only the subdomains of ${URL} will be requested" else export URL_REAL="*.${URL},${URL}" echo "Wildcard cert for ${URL} will be requested" fi else echo "SUBDOMAINS entered, processing" for job in $(echo "${SUBDOMAINS}" | tr "," " "); do export SUBDOMAINS_REAL="${SUBDOMAINS_REAL},${job}.${URL}" done if [[ "${ONLY_SUBDOMAINS}" = true ]]; then URL_REAL="${SUBDOMAINS_REAL}" echo "Only subdomains, no URL in cert" else URL_REAL="${URL}${SUBDOMAINS_REAL}" fi echo "Sub-domains processed are: ${SUBDOMAINS_REAL}" fi else echo "No subdomains defined" URL_REAL="${URL}" fi # add extra domains if [[ -n "${EXTRA_DOMAINS}" ]]; then echo "EXTRA_DOMAINS entered, processing" for job in $(echo "${EXTRA_DOMAINS}" | tr "," " "); do export EXTRA_DOMAINS_REAL="${EXTRA_DOMAINS_REAL},${job}" done echo "Extra domains processed are: ${EXTRA_DOMAINS_REAL}" URL_REAL="${URL_REAL}${EXTRA_DOMAINS_REAL}" fi set_ini_value "domains" "${URL_REAL}" /config/etc/letsencrypt/cli.ini # figuring out whether to use e-mail and which if [[ ${EMAIL} == *@* ]]; then echo "E-mail address entered: ${EMAIL}" set_ini_value "email" "${EMAIL}" /config/etc/letsencrypt/cli.ini set_ini_value "no-eff-email" "true" /config/etc/letsencrypt/cli.ini set_ini_value "register-unsafely-without-email" "false" /config/etc/letsencrypt/cli.ini else echo "No e-mail address entered or address invalid" set_ini_value "register-unsafely-without-email" "true" /config/etc/letsencrypt/cli.ini fi # alter extension for error message if [[ "${DNSPLUGIN}" = "google" ]]; then DNSCREDENTIALFILE="/config/dns-conf/${DNSPLUGIN}.json" else DNSCREDENTIALFILE="/config/dns-conf/${DNSPLUGIN}.ini" fi # setting the validation method to use if [[ "${VALIDATION}" = "dns" ]]; then set_ini_value "preferred-challenges" "dns" /config/etc/letsencrypt/cli.ini set_ini_value "authenticator" "dns-${DNSPLUGIN}" /config/etc/letsencrypt/cli.ini set_ini_value "dns-${DNSPLUGIN}-credentials" "${DNSCREDENTIALFILE}" /config/etc/letsencrypt/cli.ini if [[ -n "${PROPAGATION}" ]]; then set_ini_value "dns-${DNSPLUGIN}-propagation-seconds" "${PROPAGATION}" /config/etc/letsencrypt/cli.ini; fi # plugins that don't support setting credentials file if [[ "${DNSPLUGIN}" =~ ^(route53|standalone)$ ]]; then sed "/^dns-${DNSPLUGIN}-credentials /d" /config/etc/letsencrypt/cli.ini fi # plugins that don't support setting propagation if [[ "${DNSPLUGIN}" =~ ^(azure|gandi|standalone)$ ]]; then if [[ -n "${PROPAGATION}" ]]; then echo "${DNSPLUGIN} dns plugin does not support setting propagation time"; fi sed "/^dns-${DNSPLUGIN}-propagation-seconds /d" /config/etc/letsencrypt/cli.ini fi # plugins that use old parameter naming convention if [[ "${DNSPLUGIN}" =~ ^(cpanel|directadmin)$ ]]; then sed "/^dns-${DNSPLUGIN}-credentials /d" /config/etc/letsencrypt/cli.ini sed "/^dns-${DNSPLUGIN}-propagation-seconds /d" /config/etc/letsencrypt/cli.ini set_ini_value "authenticator" "${DNSPLUGIN}" /config/etc/letsencrypt/cli.ini set_ini_value "${DNSPLUGIN}-credentials" "${DNSCREDENTIALFILE}" /config/etc/letsencrypt/cli.ini if [[ -n "${PROPAGATION}" ]]; then set_ini_value "${DNSPLUGIN}-propagation-seconds" "${PROPAGATION}" /config/etc/letsencrypt/cli.ini; fi fi # don't restore txt records when using DuckDNS plugin if [[ "${DNSPLUGIN}" =~ ^(duckdns)$ ]]; then set_ini_value "dns-${DNSPLUGIN}-no-txt-restore" "true" /config/etc/letsencrypt/cli.ini fi echo "${VALIDATION} validation via ${DNSPLUGIN} plugin is selected" elif [[ "${VALIDATION}" = "tls-sni" ]]; then set_ini_value "preferred-challenges" "http" /config/etc/letsencrypt/cli.ini set_ini_value "authenticator" "standalone" /config/etc/letsencrypt/cli.ini echo "*****tls-sni validation has been deprecated, attempting http validation instead" else set_ini_value "preferred-challenges" "http" /config/etc/letsencrypt/cli.ini set_ini_value "authenticator" "standalone" /config/etc/letsencrypt/cli.ini echo "http validation is selected" fi # generating certs if necessary if [[ ! -f "/config/keys/letsencrypt/fullchain.pem" ]]; then if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ -n "${EMAIL}" ]]; then echo "Retrieving EAB from ZeroSSL" EAB_CREDS=$(curl -s https://api.zerossl.com/acme/eab-credentials-email --data "email=${EMAIL}") ZEROSSL_EAB_KID=$(echo "${EAB_CREDS}" | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_kid'])") ZEROSSL_EAB_HMAC_KEY=$(echo "${EAB_CREDS}" | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_hmac_key'])") if [[ -z "${ZEROSSL_EAB_KID}" ]] || [[ -z "${ZEROSSL_EAB_HMAC_KEY}" ]]; then echo "Unable to retrieve EAB credentials from ZeroSSL. Check the outgoing connections to api.zerossl.com and dns. Sleeping." sleep infinity fi set_ini_value "eab-kid" "${ZEROSSL_EAB_KID}" /config/etc/letsencrypt/cli.ini set_ini_value "eab-hmac-key" "${ZEROSSL_EAB_HMAC_KEY}" /config/etc/letsencrypt/cli.ini fi echo "Generating new certificate" certbot certonly --non-interactive --renew-by-default if [[ ! -d /config/keys/letsencrypt ]]; then if [[ "${VALIDATION}" = "dns" ]]; then echo "ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the ${DNSCREDENTIALFILE} file." else echo "ERROR: Cert does not exist! Please see the validation error above. The issue may be due to incorrect dns or port forwarding settings. Please fix your settings and recreate the container" fi sleep infinity fi run-parts /config/etc/letsencrypt/renewal-hooks/deploy/ echo "New certificate generated; starting nginx" else echo "Certificate exists; parameters unchanged; starting nginx" fi # if certbot generated key exists, remove self-signed cert and replace it with symlink to live cert if [[ -d /config/keys/letsencrypt ]]; then rm -rf /config/keys/cert.crt ln -s ./letsencrypt/fullchain.pem /config/keys/cert.crt rm -rf /config/keys/cert.key ln -s ./letsencrypt/privkey.pem /config/keys/cert.key fi