aboutsummaryrefslogtreecommitdiff
path: root/docker/auto-certbot
diff options
context:
space:
mode:
Diffstat (limited to 'docker/auto-certbot')
-rw-r--r--docker/auto-certbot/Dockerfile20
-rwxr-xr-xdocker/auto-certbot/daemon.bash107
-rwxr-xr-xdocker/auto-certbot/get-cert-domains.py38
3 files changed, 165 insertions, 0 deletions
diff --git a/docker/auto-certbot/Dockerfile b/docker/auto-certbot/Dockerfile
new file mode 100644
index 0000000..eeb6475
--- /dev/null
+++ b/docker/auto-certbot/Dockerfile
@@ -0,0 +1,20 @@
+FROM certbot/certbot:latest
+
+ARG CRUPEST_AUTO_CERTBOT_ADDITIONAL_PACKAGES=""
+RUN apk add --no-cache tini coreutils bash ${CRUPEST_AUTO_CERTBOT_ADDITIONAL_PACKAGES} && python -m pip install cryptography
+
+
+ARG CRUPEST_DOMAIN
+ARG CRUPEST_ADDITIONAL_DOMAIN_LIST=""
+ARG CRUPEST_EMAIL
+ARG CRUPEST_AUTO_CERTBOT_POST_HOOK=""
+# install bash
+ENV CRUPEST_DOMAIN=${CRUPEST_DOMAIN}
+ENV CRUPEST_ADDITIONAL_DOMAIN_LIST=${CRUPEST_ADDITIONAL_DOMAIN_LIST}
+ENV CRUPEST_EMAIL=${CRUPEST_EMAIL}
+ENV CRUPEST_AUTO_CERTBOT_POST_HOOK=${CRUPEST_AUTO_CERTBOT_POST_HOOK}
+COPY daemon.bash /daemon.bash
+COPY get-cert-domains.py /get-cert-domains.py
+VOLUME ["/var/www/certbot", "/etc/letsencrypt", "/var/lib/letsencrypt"]
+ENTRYPOINT ["tini", "--"]
+CMD [ "/daemon.bash" ]
diff --git a/docker/auto-certbot/daemon.bash b/docker/auto-certbot/daemon.bash
new file mode 100755
index 0000000..d79387e
--- /dev/null
+++ b/docker/auto-certbot/daemon.bash
@@ -0,0 +1,107 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Check I'm root.
+if [[ $EUID -ne 0 ]]; then
+ echo "This script must be run as root" 1>&2
+ exit 1
+fi
+
+# Check certbot version.
+certbot --version
+
+# Check domain
+if [[ -z "$CRUPEST_DOMAIN" ]]; then
+ echo "CRUPEST_DOMAIN can't be empty!" 1>&2
+ exit 1
+fi
+
+# Check email
+if [[ -z "$CRUPEST_EMAIL" ]]; then
+ echo "CRUPEST_EMAIL can't be empty!" 1>&2
+ exit 2
+fi
+
+# Check CRUPEST_CERT_PATH, default to /etc/letsencrypt/live/$CRUPEST_DOMAIN/fullchain.pem
+if [ -z "$CRUPEST_CERT_PATH" ]; then
+ CRUPEST_CERT_PATH="/etc/letsencrypt/live/$CRUPEST_DOMAIN/fullchain.pem"
+fi
+
+# Check CRUPEST_CERT_PATH exists.
+if [ ! -f "$CRUPEST_CERT_PATH" ]; then
+ echo "Cert file does not exist. You may want to generate it manually with aio script." 1>&2
+ exit 3
+fi
+
+echo "Root domain:" "$CRUPEST_DOMAIN"
+echo "Email:" "$CRUPEST_EMAIL"
+echo "Cert path: ${CRUPEST_CERT_PATH}"
+
+# Check CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is defined.
+if [ -z "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND" ]; then
+ echo "CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is not defined or empty. Will use the default one."
+else
+ printf "CRUPEST_AUTO_CERTBOT_RENEW_COMMAND is defined as:\n%s\n" "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND"
+fi
+
+domains_str="$(/get-cert-domains.py "${CRUPEST_CERT_PATH}")"
+
+printf "Domain list:\n%s\n" "$domains_str"
+
+mapfile -t domains <<< "$domains_str"
+
+for domain in "${domains[@]}"; do
+ domain_options=("${domain_options[@]}" -d "$domain")
+done
+
+options=(-n --agree-tos -m "$CRUPEST_EMAIL" --webroot -w /var/www/certbot "${domain_options[@]}")
+if [ -n "$CRUPEST_AUTO_CERTBOT_POST_HOOK" ]; then
+ printf "You have defined a post hook:\n%s\n" "$CRUPEST_AUTO_CERTBOT_POST_HOOK"
+ options=("${options[@]}" --post-hook "$CRUPEST_AUTO_CERTBOT_POST_HOOK")
+fi
+
+# Use test server to test.
+certbot certonly --force-renewal --test-cert --dry-run "${options[@]}"
+
+function check_and_renew_cert {
+ expire_info=$(openssl x509 -enddate -noout -in "$CRUPEST_CERT_PATH")
+
+ # Get ssl certificate expire date.
+ expire_date=$(echo "$expire_info" | cut -d= -f2)
+
+ echo "SSL certificate expire date: $expire_date"
+
+ # Convert expire date to UNIX timestamp.
+ expire_timestamp="$(date -d "$expire_date" +%s)"
+
+ # Minus expire timestamp with 30 days in UNIX timestamp.
+ renew_timestamp="$((expire_timestamp - 2592000))"
+ echo "Renew SSL certificate at: $(date -d @$renew_timestamp)"
+
+ # Get rest time til renew.
+ rest_time_in_second="$((renew_timestamp - $(date +%s)))"
+ rest_time_in_day=$((rest_time_in_second / 86400))
+ echo "Rest time til renew: $rest_time_in_second seconds, aka, about $rest_time_in_day days"
+
+ # Do we have rest time?
+ if [ $rest_time_in_second -gt 0 ]; then
+ # Sleep 1 hour.
+ echo "I'm going to sleep for 1 day to check again."
+ sleep 1d
+ else
+ # No, renew now.
+ echo "Renewing now..."
+
+ if [ -n "$CRUPEST_AUTO_CERTBOT_RENEW_COMMAND" ]; then
+ $CRUPEST_AUTO_CERTBOT_RENEW_COMMAND
+ else
+ certbot certonly "${options[@]}"
+ fi
+ fi
+}
+
+# Run check_and_renew_cert in infinate loop.
+while true; do
+ check_and_renew_cert
+done
diff --git a/docker/auto-certbot/get-cert-domains.py b/docker/auto-certbot/get-cert-domains.py
new file mode 100755
index 0000000..9bd28c8
--- /dev/null
+++ b/docker/auto-certbot/get-cert-domains.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+from os.path import *
+from cryptography.x509 import *
+from cryptography.x509.oid import ExtensionOID
+
+# Check only one argument
+if len(sys.argv) != 2:
+ print("You should only specify one argument, aka, the path of cert.",
+ file=sys.stderr)
+ exit(1)
+
+cert_path = sys.argv[1]
+
+if not exists(cert_path):
+ print("Cert file does not exist.", file=sys.stderr)
+ exit(2)
+
+if not isfile(cert_path):
+ print("Cert path is not a file.")
+ exit(3)
+
+if not 'CRUPEST_DOMAIN' in os.environ:
+ print("Please set CRUPEST_DOMAIN environment variable to root domain.", file=sys.stderr)
+ exit(4)
+
+root_domain = os.environ['CRUPEST_DOMAIN']
+
+with open(cert_path, 'rb') as f:
+ cert = load_pem_x509_certificate(f.read())
+ ext = cert.extensions.get_extension_for_oid(
+ ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
+ domains: list = ext.value.get_values_for_type(DNSName)
+ domains.remove(root_domain)
+ domains = [root_domain, *domains]
+ print('\n'.join(domains))