From da756422d9734d1d704e0cbcd248c17c30cd4f90 Mon Sep 17 00:00:00 2001
From: Yuqian Yang <crupest@crupest.life>
Date: Fri, 21 Feb 2025 00:36:11 +0800
Subject: feat(nginx): move certbot to nginx.

---
 docker/auto-certbot/Dockerfile          |  20 ------
 docker/auto-certbot/daemon.bash         | 107 --------------------------------
 docker/auto-certbot/get-cert-domains.py |  38 ------------
 docker/nginx/Dockerfile                 |   3 +
 docker/nginx/certbot.bash               |   9 +++
 docker/nginx/nginx-wrapper.bash         |   7 +++
 templates/docker-compose.yaml.template  |  27 +-------
 7 files changed, 22 insertions(+), 189 deletions(-)
 delete mode 100644 docker/auto-certbot/Dockerfile
 delete mode 100755 docker/auto-certbot/daemon.bash
 delete mode 100755 docker/auto-certbot/get-cert-domains.py
 create mode 100644 docker/nginx/certbot.bash
 create mode 100644 docker/nginx/nginx-wrapper.bash

diff --git a/docker/auto-certbot/Dockerfile b/docker/auto-certbot/Dockerfile
deleted file mode 100644
index eeb6475..0000000
--- a/docker/auto-certbot/Dockerfile
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100755
index d79387e..0000000
--- a/docker/auto-certbot/daemon.bash
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/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
deleted file mode 100755
index 9bd28c8..0000000
--- a/docker/auto-certbot/get-cert-domains.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/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))
diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile
index 86052b9..67d41d1 100644
--- a/docker/nginx/Dockerfile
+++ b/docker/nginx/Dockerfile
@@ -7,3 +7,6 @@ RUN pnpm install --frozen-lockfile && pnpm run build
 FROM nginx:mainline
 COPY --from=build-www /sites/www/dist /srv/www
 ADD sites/www/favicon.ico /srv/www/favicon.ico
+RUN apt update && apt-get install -y tini certbot && rm -rf /var/lib/apt/lists/*
+ADD --chmod=755 certbot.bash nginx-wrapper.bash /app/
+CMD ["/usr/bin/tini", "--", "/app/nginx-wrapper.bash"]
diff --git a/docker/nginx/certbot.bash b/docker/nginx/certbot.bash
new file mode 100644
index 0000000..0b8e3b7
--- /dev/null
+++ b/docker/nginx/certbot.bash
@@ -0,0 +1,9 @@
+#!/usr/bin/bash
+
+set -e
+
+while true; do
+    certbot renew --deploy-hook "nginx -s reload"
+    echo "Sleep one day before next certbot renew."
+    sleep 1d
+done
diff --git a/docker/nginx/nginx-wrapper.bash b/docker/nginx/nginx-wrapper.bash
new file mode 100644
index 0000000..bd566aa
--- /dev/null
+++ b/docker/nginx/nginx-wrapper.bash
@@ -0,0 +1,7 @@
+#!/usr/bin/bash
+
+set -e
+
+/app/certbot.bash &
+
+nginx "-g" "daemon off;"
diff --git a/templates/docker-compose.yaml.template b/templates/docker-compose.yaml.template
index 9005d5e..ea3e425 100644
--- a/templates/docker-compose.yaml.template
+++ b/templates/docker-compose.yaml.template
@@ -23,8 +23,10 @@ services:
     volumes:
       - "./generated/nginx/conf.d:/etc/nginx/conf.d:ro"
       - "./generated/nginx/common:/etc/nginx/common:ro"
-      - "./data/certbot/certs:/etc/letsencrypt:ro"
+      - "./data/certbot/certs:/etc/letsencrypt"
       - "./data/certbot/webroot:/srv/acme:ro"
+      - "./data/certbot/data:/var/lib/letsencrypt"
+      - "./data/certbot/webroot:/var/www/certbot"
       - "blog-public:/srv/www/blog:ro"
     restart: on-failure:3
 
@@ -42,29 +44,6 @@ services:
       - "./generated/v2ray-config.json:/etc/v2fly/config.json:ro"
     restart: on-failure:3
 
-  auto-certbot:
-    pull_policy: build
-    depends_on:
-      - nginx
-    build:
-      context: ./docker/auto-certbot
-      dockerfile: Dockerfile
-      pull: true
-      args:
-        - CRUPEST_DOMAIN=@@CRUPEST_DOMAIN@@
-        - CRUPEST_EMAIL=@@CRUPEST_EMAIL@@
-        - CRUPEST_AUTO_CERTBOT_ADDITIONAL_PACKAGES=docker-cli
-        - CRUPEST_AUTO_CERTBOT_POST_HOOK=docker restart nginx
-      tags:
-        - "crupest/auto-certbot:latest"
-    volumes:
-      - "./data/certbot/certs:/etc/letsencrypt"
-      - "./data/certbot/data:/var/lib/letsencrypt"
-      - "./data/certbot/webroot:/var/www/certbot"
-      # map docker socket to allow auto-certbot to restart nginx
-      - "/var/run/docker.sock:/var/run/docker.sock"
-    restart: on-failure:3
-
   auto-backup:
     pull_policy: build
     build:
-- 
cgit v1.2.3