#!/usr/bin/with-contenv 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" # Echo init finish for test runs if [ -n "${TEST_RUN}" ]; then echo '[services.d] done.' fi # 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 that the required variables are set [[ -z "$URL" ]] && \ echo "Please pass your URL as an environment variable in your docker run command. See docker info for more details." && \ sleep infinity # make our folders and links mkdir -p \ /config/{log/letsencrypt,log/fail2ban,etc/letsencrypt,fail2ban,crontabs,dns-conf,geoip2db} \ /var/run/fail2ban rm -rf /etc/letsencrypt ln -s /config/etc/letsencrypt /etc/letsencrypt # copy dns default configs cp -n /defaults/dns-conf/* /config/dns-conf/ chown -R abc:abc /config/dns-conf # copy reverse proxy configs cp -R /defaults/proxy-confs /config/nginx/ # copy/update the fail2ban config defaults to/in /config cp -R /defaults/fail2ban/filter.d /config/fail2ban/ cp -R /defaults/fail2ban/action.d /config/fail2ban/ # if jail.local is missing in /config, copy default [[ ! -f /config/fail2ban/jail.local ]] && \ cp /defaults/jail.local /config/fail2ban/jail.local # Replace fail2ban config with user config [[ -d /etc/fail2ban/filter.d ]] && \ rm -rf /etc/fail2ban/filter.d [[ -d /etc/fail2ban/action.d ]] && \ rm -rf /etc/fail2ban/action.d cp -R /config/fail2ban/filter.d /etc/fail2ban/ cp -R /config/fail2ban/action.d /etc/fail2ban/ cp /defaults/fail2ban/fail2ban.local /etc/fail2ban/ cp /config/fail2ban/jail.local /etc/fail2ban/jail.local # copy crontab and proxy defaults if needed [[ ! -f /config/crontabs/root ]] && \ cp /etc/crontabs/root /config/crontabs/ [[ ! -f /config/nginx/proxy.conf ]] && \ cp /defaults/proxy.conf /config/nginx/proxy.conf [[ ! -f /config/nginx/ssl.conf ]] && \ cp /defaults/ssl.conf /config/nginx/ssl.conf [[ ! -f /config/nginx/ldap.conf ]] && \ cp /defaults/ldap.conf /config/nginx/ldap.conf [[ ! -f /config/nginx/authelia-server.conf ]] && \ cp /defaults/authelia-server.conf /config/nginx/authelia-server.conf [[ ! -f /config/nginx/authelia-location.conf ]] && \ cp /defaults/authelia-location.conf /config/nginx/authelia-location.conf [[ ! -f /config/nginx/geoip2.conf ]] && \ cp /defaults/geoip2.conf /config/nginx/geoip2.conf [[ ! -f /config/www/502.html ]] && cp /defaults/502.html /config/www/502.html # remove lua bits from nginx.conf if not done before if ! grep -q '#Removed lua' /config/nginx/nginx.conf; then echo "Removing lua specific info from nginx.conf" sed -i 's|\tlua_load_resty_core off;|\t#Removed lua. Do not remove this comment|g' /config/nginx/nginx.conf fi # copy pre-generated dhparams or generate if needed [[ ! -f /config/nginx/dhparams.pem ]] && \ cp /defaults/dhparams.pem /config/nginx/dhparams.pem if ! grep -q 'PARAMETERS' "/config/nginx/dhparams.pem"; then curl -o /config/nginx/dhparams.pem -L "https://lsio.ams3.digitaloceanspaces.com/dhparams.pem" fi if ! grep -q 'PARAMETERS' "/config/nginx/dhparams.pem"; then echo "Generating dhparams.pem. This will take a long time. Do not stop the container until this process is completed." openssl dhparam -out /config/nginx/dhparams.pem 4096 fi # check to make sure DNSPLUGIN is selected if dns validation is used [[ "$VALIDATION" = "dns" ]] && [[ ! "$DNSPLUGIN" =~ ^(aliyun|cloudflare|cloudxns|cpanel|digitalocean|directadmin|dnsimple|dnsmadeeasy|domeneshop|gandi|gehirn|google|hetzner|inwx|linode|luadns|netcup|njalla|nsone|ovh|rfc2136|route53|sakuracloud|transip|vultr)$ ]] && \ echo "Please set the DNSPLUGIN variable to a valid plugin name. See docker info for more details." && \ sleep infinity # import user crontabs rm /etc/crontabs/* cp /config/crontabs/* /etc/crontabs/ # 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\" ORIGDUCKDNSTOKEN=\"$DUCKDNSTOKEN\" ORIGCERTPROVIDER=\"$CERTPROVIDER\" ORIGEMAIL=\"$EMAIL\"" > /config/.donoteditthisfile.conf echo "Created .donoteditthisfile.conf" fi # load original config settings # shellcheck disable=SC1091 . /config/.donoteditthisfile.conf # set default validation to http if [ -z "$VALIDATION" ]; then VALIDATION="http" echo "VALIDATION parameter not set; setting it to http" 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 # 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="-d *.${URL}" echo "Wildcard cert for only the subdomains of $URL will be requested" else export URL_REAL="-d *.${URL} -d ${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 -d ${job}.${URL}" done if [ "$ONLY_SUBDOMAINS" = true ]; then URL_REAL="$SUBDOMAINS_REAL" echo "Only subdomains, no URL in cert" else URL_REAL="-d ${URL}${SUBDOMAINS_REAL}" fi echo "Sub-domains processed are: $SUBDOMAINS_REAL" fi else echo "No subdomains defined" URL_REAL="-d $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 -d ${job}" done echo "Extra domains processed are: $EXTRA_DOMAINS_REAL" URL_REAL="$URL_REAL $EXTRA_DOMAINS_REAL" fi # figuring out whether to use e-mail and which if [[ $EMAIL == *@* ]]; then echo "E-mail address entered: ${EMAIL}" EMAILPARAM="-m ${EMAIL} --no-eff-email" else echo "No e-mail address entered or address invalid" EMAILPARAM="--register-unsafely-without-email" 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_domeneshop:||g' /config/dns-conf/domeneshop.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 # setting the validation method to use if [ "$VALIDATION" = "dns" ]; then if [ "$DNSPLUGIN" = "route53" ]; then if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--dns-${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="--dns-${DNSPLUGIN} ${PROPAGATIONPARAM}" elif [[ "$DNSPLUGIN" =~ ^(cpanel)$ ]]; then if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--certbot-dns-${DNSPLUGIN}:${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="-a certbot-dns-${DNSPLUGIN}:${DNSPLUGIN} --certbot-dns-${DNSPLUGIN}:${DNSPLUGIN}-credentials /config/dns-conf/${DNSPLUGIN}.ini ${PROPAGATIONPARAM}" elif [[ "$DNSPLUGIN" =~ ^(gandi)$ ]]; then if [ -n "$PROPAGATION" ];then echo "Gandi dns plugin does not support setting propagation time"; fi PREFCHAL="-a certbot-plugin-${DNSPLUGIN}:dns --certbot-plugin-${DNSPLUGIN}:dns-credentials /config/dns-conf/${DNSPLUGIN}.ini" elif [[ "$DNSPLUGIN" =~ ^(google)$ ]]; then if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--dns-${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="--dns-${DNSPLUGIN} --dns-${DNSPLUGIN}-credentials /config/dns-conf/${DNSPLUGIN}.json ${PROPAGATIONPARAM}" elif [[ "$DNSPLUGIN" =~ ^(aliyun|domeneshop|hetzner|inwx|netcup|njalla|transip|vultr)$ ]]; then if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--dns-${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="-a dns-${DNSPLUGIN} --dns-${DNSPLUGIN}-credentials /config/dns-conf/${DNSPLUGIN}.ini ${PROPAGATIONPARAM}" elif [[ "$DNSPLUGIN" =~ ^(directadmin)$ ]]; then if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="-a ${DNSPLUGIN} --${DNSPLUGIN}-credentials /config/dns-conf/${DNSPLUGIN}.ini ${PROPAGATIONPARAM}" else if [ -n "$PROPAGATION" ];then PROPAGATIONPARAM="--dns-${DNSPLUGIN}-propagation-seconds ${PROPAGATION}"; fi PREFCHAL="--dns-${DNSPLUGIN} --dns-${DNSPLUGIN}-credentials /config/dns-conf/${DNSPLUGIN}.ini ${PROPAGATIONPARAM}" fi echo "${VALIDATION} validation via ${DNSPLUGIN} plugin is selected" elif [ "$VALIDATION" = "tls-sni" ]; then PREFCHAL="--non-interactive --standalone --preferred-challenges http" echo "*****tls-sni validation has been deprecated, attempting http validation instead" elif [ "$VALIDATION" = "duckdns" ]; then PREFCHAL="--non-interactive --manual --preferred-challenges dns --manual-auth-hook /app/duckdns-txt" chmod +x /app/duckdns-txt echo "duckdns validation is selected" 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 URL_REAL="-d *.${URL}" else echo "the resulting certificate will only cover the main domain due to a limitation of duckdns, ie. subdomain.duckdns.org" export URL_REAL="-d ${URL}" fi else PREFCHAL="--non-interactive --standalone --preferred-challenges http" echo "http validation is selected" 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" ] || [ ! "$DUCKDNSTOKEN" = "$ORIGDUCKDNSTOKEN" ] || [ ! "$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 [ "$ORIGONLY_SUBDOMAINS" = "true" ] && [ ! "$ORIGSUBDOMAINS" = "wildcard" ]; then ORIGDOMAIN="$(echo "$ORIGSUBDOMAINS" | tr ',' ' ' | awk '{print $1}').${ORIGURL}" else ORIGDOMAIN="$ORIGURL" fi 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 [[ -f /config/etc/letsencrypt/live/"$ORIGDOMAIN"/fullchain.pem ]] && certbot revoke --non-interactive --cert-path /config/etc/letsencrypt/live/"$ORIGDOMAIN"/fullchain.pem --server $REV_ACMESERVER rm -rf /config/etc/letsencrypt mkdir -p /config/etc/letsencrypt 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\" ORIGDUCKDNSTOKEN=\"$DUCKDNSTOKEN\" ORIGCERTPROVIDER=\"$CERTPROVIDER\" ORIGEMAIL=\"$EMAIL\"" > /config/.donoteditthisfile.conf # alter extension for error message if [ "$DNSPLUGIN" = "google" ]; then FILENAME="$DNSPLUGIN.json" else FILENAME="$DNSPLUGIN.ini" 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 ZEROSSL_EAB="--eab-kid ${ZEROSSL_EAB_KID} --eab-hmac-key ${ZEROSSL_EAB_HMAC_KEY}" fi echo "Generating new certificate" # shellcheck disable=SC2086 certbot certonly --renew-by-default --server $ACMESERVER $ZEROSSL_EAB $PREFCHAL --rsa-key-size 4096 $EMAILPARAM --agree-tos $URL_REAL if [ -d /config/keys/letsencrypt ]; then cd /config/keys/letsencrypt || exit else if [ "$VALIDATION" = "dns" ]; then echo "ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/${FILENAME} file." elif [ "$VALIDATION" = "duckdns" ]; then echo "ERROR: Cert does not exist! Please see the validation error above. Make sure your DUCKDNSTOKEN is correct." 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 openssl pkcs12 -export -out privkey.pfx -inkey privkey.pem -in cert.pem -certfile chain.pem -passout pass: sleep 1 cat {privkey,fullchain}.pem > priv-fullchain-bundle.pem echo "New certificate generated; starting nginx" else echo "Certificate exists; parameters unchanged; starting nginx" fi # create GeoIP2 folder symlink [[ -d /var/lib/libmaxminddb ]] && [[ ! -L /var/lib/libmaxminddb ]] && \ rm -rf /var/lib/libmaxminddb [[ ! -d /var/lib/libmaxminddb ]] && \ ln -s /config/geoip2db /var/lib/libmaxminddb # check GeoIP2 database if [ -n "$MAXMINDDB_LICENSE_KEY" ]; then sed -i "s|.*MAXMINDDB_LICENSE_KEY.*|MAXMINDDB_LICENSE_KEY=\"${MAXMINDDB_LICENSE_KEY}\"|g" /etc/conf.d/libmaxminddb if [ ! -f /var/lib/libmaxminddb/GeoLite2-City.mmdb ]; then echo "Downloading GeoIP2 City database." /etc/periodic/weekly/libmaxminddb fi elif [ -f /var/lib/libmaxminddb/GeoLite2-City.mmdb ]; then echo -e "Currently using the user provided GeoLite2-City.mmdb.\nIf you want to enable weekly auto-updates of the database, retrieve a free license key from MaxMind,\nand add a new env variable \"MAXMINDDB_LICENSE_KEY\", set to your license key." else echo -e "Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,\nand add a new env variable \"MAXMINDDB_LICENSE_KEY\", set to your license key." fi # logfiles needed by fail2ban [[ ! -f /config/log/nginx/error.log ]] && \ touch /config/log/nginx/error.log [[ ! -f /config/log/nginx/access.log ]] && \ touch /config/log/nginx/access.log # permissions chown -R abc:abc \ /config chmod -R 0644 /etc/logrotate.d chmod -R +r /config/log chmod +x /app/le-renew.sh chmod 700 /defaults/dns-conf chmod 600 /defaults/dns-conf/*