From 270ae0245ff9c76fb1948a05348779a93c960ed3 Mon Sep 17 00:00:00 2001 From: Fabio Date: Wed, 19 Feb 2025 21:19:29 +0800 Subject: [PATCH] first commit --- .dockerignore | 2 + .gitignore | 1 + Dockerfile | 19 +++++ LICENSE | 21 +++++ README.md | 39 +++++++++ docker-compose.yml | 27 ++++++ scripts/auth.sh | 3 + scripts/auth2.sh | 3 + scripts/cleanup.sh | 2 + scripts/cleanup2.sh | 2 + scripts/script-post.sh | 17 ++++ scripts/script-pre.sh | 10 +++ scripts/start.sh | 190 +++++++++++++++++++++++++++++++++++++++++ scripts/start_orig.sh | 155 +++++++++++++++++++++++++++++++++ 14 files changed, 491 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100755 scripts/auth.sh create mode 100755 scripts/auth2.sh create mode 100755 scripts/cleanup.sh create mode 100755 scripts/cleanup2.sh create mode 100755 scripts/script-post.sh create mode 100755 scripts/script-pre.sh create mode 100755 scripts/start.sh create mode 100755 scripts/start_orig.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ae4e1ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +* +!scripts/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caf01ec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dev/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..badf9bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# Base image +FROM ubuntu:latest + +# Maintainer information +LABEL maintainer="Maksim Stojkovic " \ + org.label-schema.vcs-url="https://github.com/maksimstojkovic/docker-letsencrypt" + +# Install tools required +RUN apt update +RUN apt upgrade -y +RUN apt install bash certbot curl python3-certbot-nginx -y jq + +# Copy scripts +WORKDIR /scripts +COPY ./scripts /scripts +RUN chmod -R +x /scripts + +# Image starting command +CMD ["/bin/bash", "/scripts/start.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..acd6273 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Maksim Stojkovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ad5f4a --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Let's Encrypt for Duck DNS + +[![Build Status](https://github.com/maksimstojkovic/docker-letsencrypt/actions/workflows/docker-build.yml/badge.svg)](https://github.com/maksimstojkovic/docker-letsencrypt) +[![Docker Pulls](https://img.shields.io/docker/pulls/maksimstojkovic/letsencrypt)](https://hub.docker.com/r/maksimstojkovic/letsencrypt) +[![Docker Stars](https://img.shields.io/docker/stars/maksimstojkovic/letsencrypt)](https://hub.docker.com/r/maksimstojkovic/letsencrypt) +[![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/maksimstojkovic/letsencrypt)](https://hub.docker.com/r/maksimstojkovic/letsencrypt) +[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/maksimstojkovic/letsencrypt)](https://hub.docker.com/r/maksimstojkovic/letsencrypt) + +Automatically generates Let's Encrypt certificates using a lightweight Docker container without requiring any ports to be exposed for DNS challenges. + +## Environment Variables + +* `DUCKDNS_TOKEN`: Duck DNS account token (obtained from [Duck DNS](https://www.duckdns.org)) (*required*) +* `DUCKDNS_DOMAIN`: Full Duck DNS domain (e.g. `test.duckdns.org`) (*required*) +* `LETSENCRYPT_DOMAIN`: Domain to generate SSL cert for. By default the SSL certificate is generated for `DUCKDNS_DOMAIN` (optional) +* `LETSENCRYPT_WILDCARD`: `true` or `false`, indicating whether the SSL certificate should be for subdomains *only* of `LETSENCRYPT_DOMAIN` (i.e. `*.test.duckdns.org`), or for the main domain *only* (i.e. `test.duckdns.org`) (optional, default: `false`) +* `LETSENCRYPT_EMAIL`: Email used for certificate renewal notifications (optional) +* `LETSENCRYPT_CHAIN`: Preferred certificate chain (e.g. `ISRG Root X1`, see [https://letsencrypt.org/certificates](https://letsencrypt.org/certificates/) for more details) (optional) +* `TESTING`: `true` or `false`, indicating whether a staging SSL certificate should be generated or not (optional, default: `false`) +* `UID`: User ID to apply to Let's Encrypt files generated (optional, recommended, default: `0` - root) +* `GID`: Group ID to apply to Let's Encrypt files generated (optional, recommended, default: `0` - root) + +## Notes + +* The `DUCKDNS_DOMAIN` should already be pointing to the server with a dynamic IP. The [maksimstojkovic/duckdns](https://github.com/maksimstojkovic/docker-duckdns) image can be used to automatically update the IP address. +* The format of `DUCKDNS_DOMAIN` should be `.duckdns.org`, regardless of the value of `LETSENCRYPT_WILDCARD`. +* To use `LETSENCRYPT_DOMAIN` feature, the following DNS records need to be created for ACME authentication (records should not be proxied): + +| Type | Name | Value | Condition | +|-------|----------------------------------------|------------------------------------|-----------------------------------| +| CNAME | `*.` | `` | `LETSENCRYPT_WILDCARD` == `true` | +| CNAME | `` | `` | `LETSENCRYPT_WILDCARD` == `false` | +| CNAME | `_acme-challenge.` | `_acme-challenge.` | | + +## Volumes + +* `:/etc/letsencrypt`: A named or host volume which allows SSL certificates to persist and be accessed by other containers + +**Note:** To use the `` host volume in another container, mount it as read-only for those containers. The `` host volume should be read-write enabled for the Letsencrypt container. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..73dc015 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '2.4' + +services: + duckdns: + image: maksimstojkovic/duckdns + container_name: duckdns + environment: + - DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + - DUCKDNS_DOMAIN=test.duckdns.org + - DUCKDNS_DELAY=5 #optional + restart: unless-stopped + + letsencrypt: + image: maksimstojkovic/letsencrypt + container_name: letsencrypt + volumes: + - ./certs:/etc/letsencrypt + environment: + - DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + - DUCKDNS_DOMAIN=test.duckdns.org + - LETSENCRYPT_DOMAIN= #optional + - LETSENCRYPT_WILDCARD=false #optional + - LETSENCRYPT_EMAIL= #optional + - TESTING=false #optional + - UID=0 #optional + - GID=0 #optional + restart: unless-stopped diff --git a/scripts/auth.sh b/scripts/auth.sh new file mode 100755 index 0000000..5ee98c9 --- /dev/null +++ b/scripts/auth.sh @@ -0,0 +1,3 @@ +#!/bin/sh +[[ "$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN%.duckdns.org}&token=${DUCKDNS_TOKEN}&txt=${CERTBOT_VALIDATION}")" = "OK" ]] + diff --git a/scripts/auth2.sh b/scripts/auth2.sh new file mode 100755 index 0000000..06f6663 --- /dev/null +++ b/scripts/auth2.sh @@ -0,0 +1,3 @@ +#!/bin/sh +[[ "$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN2%.duckdns.org}&token=${DUCKDNS_TOKEN}&txt=${CERTBOT_VALIDATION}")" = "OK" ]] + diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 0000000..da92885 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,2 @@ +#!/bin/sh +[[ "$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN%.duckdns.org}&token=${DUCKDNS_TOKEN}&txt=${CERTBOT_VALIDATION}&clear=true")" = "OK" ]] diff --git a/scripts/cleanup2.sh b/scripts/cleanup2.sh new file mode 100755 index 0000000..c3549b6 --- /dev/null +++ b/scripts/cleanup2.sh @@ -0,0 +1,2 @@ +#!/bin/sh +[[ "$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN2%.duckdns.org}&token=${DUCKDNS_TOKEN}&txt=${CERTBOT_VALIDATION}&clear=true")" = "OK" ]] diff --git a/scripts/script-post.sh b/scripts/script-post.sh new file mode 100755 index 0000000..ae06dba --- /dev/null +++ b/scripts/script-post.sh @@ -0,0 +1,17 @@ +#!/bin/bash +#Get domainID: +dns=$(curl -X GET https://api.dynu.com/v2/dns -H "accept: application/json" -H "API-Key: $DYNU_API_KEY") +domainID=$(echo $dns | jq ".domains[] | select(.name==\"$CERTBOT_DOMAIN\")" | jq '.id') + +while + records=$(curl -s -X GET "https://api.dynu.com/v2/dns/$domainID/record" -H "accept: application/json" -H "API-Key: $DYNU_API_KEY") + identifier=$(echo $records | jq '.dnsRecords[] | select(.nodeName=="_acme-challenge")' | jq '.id' | head -n 1) + if [ ! -z "$identifier" ] + then + echo "Delete: $identifier" + curl -s -X DELETE "https://api.dynu.com/v2/dns/$domainID/record/$identifier" -H "accept: application/json" -H "API-Key: $DYNU_API_KEY" + fi + [[ ! -z "$identifier" ]] +do + continue +done diff --git a/scripts/script-pre.sh b/scripts/script-pre.sh new file mode 100755 index 0000000..dd75b5d --- /dev/null +++ b/scripts/script-pre.sh @@ -0,0 +1,10 @@ +#!/bin/bash +#Get domainID: +dns=$(curl -X GET https://api.dynu.com/v2/dns -H "accept: application/json" -H "API-Key: $DYNU_API_KEY") +domainID=$(echo $dns | jq ".domains[] | select(.name==\"$CERTBOT_DOMAIN\")" | jq '.id') + + +#Create record +resultCreate=$(curl -s -X POST "https://api.dynu.com/v2/dns/$domainID/record" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"nodeName\":\"_acme-challenge\",\"recordType\":\"TXT\",\"ttl\":60,\"state\":true,\"group\":\"\",\"textData\":\"$CERTBOT_VALIDATION\"}" -H "API-Key: $DYNU_API_KEY") +echo $resultCreate +sleep 30 diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 0000000..094cba7 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,190 @@ +#!/bin/sh + +# Check variables DUCKDNS_TOKEN, DUCKDNS_DOMAIN +if [ -z "$DUCKDNS_TOKEN" ] || [ "$DUCKDNS_TOKEN" = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" ]; then + echo "ERROR: Variable DUCKDNS_TOKEN is unset or still its default value" + exit 1 +fi + +if [ -z "$DUCKDNS_DOMAIN" ]; then + echo "ERROR: Variable DUCKDNS_DOMAIN is unset or still its default value" + exit 1 +fi + +# Print email notice if applicable +if [ -z "$LETSENCRYPT_EMAIL" ]; then + echo "WARNING: You will not receive SSL certificate expiration notices" +fi + +# Set LETSENCRYPT_DOMAIN to DUCKDNS_DOMAIN if not specified +if [ -z "$LETSENCRYPT_DOMAIN" ]; then + echo "INFO: LETSENCRYPT_DOMAIN is unset, using DUCKDNS_DOMAIN" + LETSENCRYPT_DOMAIN=$DUCKDNS_DOMAIN +fi + +# Set certificate url based on LETSENCRYPT_WILDCARD value +if [ "$LETSENCRYPT_WILDCARD" = "true" ]; then + echo "INFO: A wildcard SSL certificate will be created" + LETSENCRYPT_DOMAIN="*.$LETSENCRYPT_DOMAIN" +fi + +# Set LETSENCRYPT_DOMAIN to DUCKDNS_DOMAIN if not specified +if [ -z "$LETSENCRYPT_DOMAIN2" ]; then + echo "INFO: LETSENCRYPT_DOMAIN2 is unset, using DUCKDNS_DOMAIN" + LETSENCRYPT_DOMAIN2=$DUCKDNS_DOMAIN2 +fi + +# Set certificate url based on LETSENCRYPT_WILDCARD value +if [ "$LETSENCRYPT_WILDCARD" = "true" ]; then + echo "INFO: A wildcard SSL certificate will be created" + LETSENCRYPT_DOMAIN2="*.$LETSENCRYPT_DOMAIN2" +else + LETSENCRYPT_WILDCARD="false" +fi + +# Set default preferred chain if no value specified +if [ -z "$LETSENCRYPT_CHAIN" ]; then + echo "INFO: LETSENCRYPT_CHAIN is unset, using default chain" + LETSENCRYPT_CHAIN="default" +fi + +# Set user and group ID's for files +if [ -z "$UID" ]; then + echo "INFO: No UID specified, using root UID of 0" + UID=0 +fi + +if [ -z "$GID" ]; then + echo "INFO: No GID specified, using root GID of 0" + GID=0 +fi + +# Print variables +echo "DUCKDNS_TOKEN: $DUCKDNS_TOKEN" +echo "DUCKDNS_DOMAIN: $DUCKDNS_DOMAIN" +echo "LETSENCRYPT_DOMAIN: $LETSENCRYPT_DOMAIN" +echo "LETSENCRYPT_EMAIL: $LETSENCRYPT_EMAIL" +echo "LETSENCRYPT_WILDCARD: $LETSENCRYPT_WILDCARD" +echo "LETSENCRYPT_CHAIN: $LETSENCRYPT_CHAIN" +echo "TESTING: $TESTING" +echo "UID: $UID" +echo "GID: $GID" + +if [ -z "$LETSENCRYPT_EMAIL" ]; then + EMAIL_PARAM="--register-unsafely-without-email" +else + EMAIL_PARAM="-m $LETSENCRYPT_EMAIL --no-eff-email" +fi + +if [ "$LETSENCRYPT_CHAIN" = "default" ]; then + unset CHAIN_PARAM +else + CHAIN_PARAM=( --preferred-chain "$LETSENCRYPT_CHAIN" ) +fi + +if [ "$TESTING" = "true" ]; then + echo "INFO: Generating staging certificate" + TEST_PARAM="--test-cert" +else + unset TEST_PARAM +fi + +echo "certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth.sh \ + --manual-cleanup-hook /scripts/cleanup.sh \ + ${CHAIN_PARAM[@]} $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM" + +# Create certificates +certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth.sh \ + --manual-cleanup-hook /scripts/cleanup.sh \ + "${CHAIN_PARAM[@]}" $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM + +echo "certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth2.sh \ + --manual-cleanup-hook /scripts/cleanup2.sh \ + ${CHAIN_PARAM[@]} $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN2 \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM" + +# Create certificates +certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth2.sh \ + --manual-cleanup-hook /scripts/cleanup2.sh \ + "${CHAIN_PARAM[@]}" $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN2 \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM + +chown -R $UID:$GID /etc/letsencrypt + +echo "certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN -d *.$DYNU_DOMAIN" + +certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN -d *.$DYNU_DOMAIN + +echo "certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN2 -d *.$DYNU_DOMAIN2" + +certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN2 -d *.$DYNU_DOMAIN2 +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}" +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/fullchain.pem" +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/privkey.pem" + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${LETSENCRYPT_DOMAIN#\*\.}" +else + echo "SUCCESS: Create SSL certificates for ${LETSENCRYPT_DOMAIN#\*\.}" +fi + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN2#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN2#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN2#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${LETSENCRYPT_DOMAIN2#\*\.}" +else + echo "SUCCESS: Create SSL certificates for ${LETSENCRYPT_DOMAIN2#\*\.}" +fi + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${DYNU_DOMAIN#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${DYNU_DOMAIN#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${DYNU_DOMAIN#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${DYNU_DOMAIN}" +else + echo "SUCCESS: Create SSL certificates for ${DYNU_DOMAIN}" +fi + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${DYNU_DOMAIN2#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${DYNU_DOMAIN2#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${DYNU_DOMAIN2#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${DYNU_DOMAIN2}" +else + echo "SUCCESS: Create SSL certificates for ${DYNU_DOMAIN2}" +fi + +# Check if certificates require renewal twice a day +while :; do + # Wait for a random period within the next 12 hours + + LETSENCRYPT_DELAY=$(shuf -i 1-720 -n 1) + echo "Sleeping for $(($LETSENCRYPT_DELAY / 60)) hour(s) and $(($LETSENCRYPT_DELAY % 60)) minute(s)" + sleep $((${LETSENCRYPT_DELAY} * 60)) # Convert to seconds + + echo "INFO: Attempting SSL certificate renewal" + certbot --manual-public-ip-logging-ok renew + chown -R $UID:$GID /etc/letsencrypt +done diff --git a/scripts/start_orig.sh b/scripts/start_orig.sh new file mode 100755 index 0000000..eb2994f --- /dev/null +++ b/scripts/start_orig.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +# Check variables DUCKDNS_TOKEN, DUCKDNS_DOMAIN +if [ -z "$DUCKDNS_TOKEN" ] || [ "$DUCKDNS_TOKEN" = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" ]; then + echo "ERROR: Variable DUCKDNS_TOKEN is unset or still its default value" + exit 1 +fi + +if [ -z "$DUCKDNS_DOMAIN" ]; then + echo "ERROR: Variable DUCKDNS_DOMAIN is unset or still its default value" + exit 1 +fi + +# Print email notice if applicable +if [ -z "$LETSENCRYPT_EMAIL" ]; then + echo "WARNING: You will not receive SSL certificate expiration notices" +fi + +# Set LETSENCRYPT_DOMAIN to DUCKDNS_DOMAIN if not specified +if [ -z "$LETSENCRYPT_DOMAIN" ]; then + echo "INFO: LETSENCRYPT_DOMAIN is unset, using DUCKDNS_DOMAIN" + LETSENCRYPT_DOMAIN=$DUCKDNS_DOMAIN +fi + +# Set certificate url based on LETSENCRYPT_WILDCARD value +if [ "$LETSENCRYPT_WILDCARD" = "true" ]; then + echo "INFO: A wildcard SSL certificate will be created" + LETSENCRYPT_DOMAIN="*.$LETSENCRYPT_DOMAIN" +else + LETSENCRYPT_WILDCARD="false" +fi + +# Set default preferred chain if no value specified +if [ -z "$LETSENCRYPT_CHAIN" ]; then + echo "INFO: LETSENCRYPT_CHAIN is unset, using default chain" + LETSENCRYPT_CHAIN="default" +fi + +# Set user and group ID's for files +if [ -z "$UID" ]; then + echo "INFO: No UID specified, using root UID of 0" + UID=0 +fi + +if [ -z "$GID" ]; then + echo "INFO: No GID specified, using root GID of 0" + GID=0 +fi + +# Print variables +echo "DUCKDNS_TOKEN: $DUCKDNS_TOKEN" +echo "DUCKDNS_DOMAIN: $DUCKDNS_DOMAIN" +echo "LETSENCRYPT_DOMAIN: $LETSENCRYPT_DOMAIN" +echo "LETSENCRYPT_EMAIL: $LETSENCRYPT_EMAIL" +echo "LETSENCRYPT_WILDCARD: $LETSENCRYPT_WILDCARD" +echo "LETSENCRYPT_CHAIN: $LETSENCRYPT_CHAIN" +echo "TESTING: $TESTING" +echo "UID: $UID" +echo "GID: $GID" + +if [ -z "$LETSENCRYPT_EMAIL" ]; then + EMAIL_PARAM="--register-unsafely-without-email" +else + EMAIL_PARAM="-m $LETSENCRYPT_EMAIL --no-eff-email" +fi + +if [ "$LETSENCRYPT_CHAIN" = "default" ]; then + unset CHAIN_PARAM +else + CHAIN_PARAM=( --preferred-chain "$LETSENCRYPT_CHAIN" ) +fi + +if [ "$TESTING" = "true" ]; then + echo "INFO: Generating staging certificate" + TEST_PARAM="--test-cert" +else + unset TEST_PARAM +fi + +echo "certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth.sh \ + --manual-cleanup-hook /scripts/cleanup.sh \ + ${CHAIN_PARAM[@]} $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM" + +# Create certificates +certbot certonly --manual --preferred-challenges dns \ + --manual-auth-hook /scripts/auth.sh \ + --manual-cleanup-hook /scripts/cleanup.sh \ + "${CHAIN_PARAM[@]}" $EMAIL_PARAM -d $LETSENCRYPT_DOMAIN \ + --agree-tos --manual-public-ip-logging-ok --keep $TEST_PARAM + +chown -R $UID:$GID /etc/letsencrypt + +echo "certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN -d *.$DYNU_DOMAIN" + +certbot certonly --manual-public-ip-logging-ok --non-interactive --agree-tos \ + --email $EMAIL --manual --preferred-challenges=dns \ + --manual-auth-hook /scripts/script-pre.sh --manual-cleanup-hook /scripts/script-post.sh \ + -d $DYNU_DOMAIN -d *.$DYNU_DOMAIN + +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}" +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/fullchain.pem" +echo "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/privkey.pem" + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${LETSENCRYPT_DOMAIN}" +else + echo "SUCCESS: Create SSL certificates for ${LETSENCRYPT_DOMAIN}" +fi +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${LETSENCRYPT_DOMAIN#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${LETSENCRYPT_DOMAIN}" +else + echo "SUCCESS: Create SSL certificates for ${LETSENCRYPT_DOMAIN}" +fi + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${DUCKDNS_DOMAIN#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${DUCKDNS_DOMAIN#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${DUCKDNS_DOMAIN#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${DUCKDNS_DOMAIN}" +else + echo "SUCCESS: Create SSL certificates for ${DUCKDNS_DOMAIN}" +fi + +# Check for successful certificate generation +if [ ! -d "/etc/letsencrypt/live/${DUCKDNS_DOMAIN2#\*\.}" ] || \ + [ ! -f "/etc/letsencrypt/live/${DUCKDNS_DOMAIN2#\*\.}/fullchain.pem" ] || \ + [ ! -f "/etc/letsencrypt/live/${DUCKDNS_DOMAIN2#\*\.}/privkey.pem" ]; then + echo "ERROR: Failed to create SSL certificates for ${DUCKDNS2_DOMAIN}" +else + echo "SUCCESS: Create SSL certificates for ${DUCKDNS2_DOMAIN}" +fi + +# Check if certificates require renewal twice a day +while :; do + # Wait for a random period within the next 12 hours + + LETSENCRYPT_DELAY=$(shuf -i 1-720 -n 1) + echo "Sleeping for $(($LETSENCRYPT_DELAY / 60)) hour(s) and $(($LETSENCRYPT_DELAY % 60)) minute(s)" + sleep $((${LETSENCRYPT_DELAY} * 60)) # Convert to seconds + + echo "INFO: Attempting SSL certificate renewal" + certbot --manual-public-ip-logging-ok renew + chown -R $UID:$GID /etc/letsencrypt +done