From 35f932f5f25ccc7fcf1d5af0cc732d9d27df19bf Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 15 Jul 2025 19:12:22 -0700 Subject: [PATCH 1/3] Move letsencrypt scripts to work on boot Allows ssl and letsencrypt templates to run on boot via initscripts This change adds the decision to configure https or letsencrypt to be at runtime rather than at build time via env vars. Under the hood, these are the commands, just migrated to shellscripts that run when a container boots. Runs on existence of ENABLE_SSL (base ssl template) or LETSENCRYPT_ACCOUNT_EMAIL (ssl template+letsencrypt template) Both cases checks and errors on blank hostname. --- templates/web.letsencrypt.ssl.template.yml | 110 +++++++++++---------- templates/web.ssl.template.yml | 56 ++++++----- 2 files changed, 89 insertions(+), 77 deletions(-) diff --git a/templates/web.letsencrypt.ssl.template.yml b/templates/web.letsencrypt.ssl.template.yml index 04412af37..4d1819ea8 100644 --- a/templates/web.letsencrypt.ssl.template.yml +++ b/templates/web.letsencrypt.ssl.template.yml @@ -1,26 +1,23 @@ env: - LETSENCRYPT_DIR: "/shared/letsencrypt" DISCOURSE_FORCE_HTTPS: true -hooks: - after_ssl: - - exec: - cmd: - - if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV variable is required and has not been set."; exit 1; fi - - /bin/bash -c "if [[ ! \"$LETSENCRYPT_ACCOUNT_EMAIL\" =~ ([^@]+)@([^\.]+) ]]; then echo \"LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address\"; exit 1; fi" - - - exec: - cmd: - - cd /root && git clone --branch 3.0.6 --depth 1 https://github.com/acmesh-official/acme.sh.git && cd /root/acme.sh - - touch /var/spool/cron/crontabs/root - - install -d -m 0755 -g root -o root $LETSENCRYPT_DIR - - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log" - - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade - - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt - - - file: - path: "/etc/nginx/letsencrypt.conf" - contents: | +run: + - exec: + cmd: + - cd /opt && git clone --branch 3.0.6 --depth 1 https://github.com/acmesh-official/acme.sh.git + - file: + path: "/usr/local/bin/configure-letsencrypt" + chmod: "+x" + contents: | + #!/bin/bash + touch /var/spool/cron/crontabs/root + LETSENCRYPT_DIR="/shared/letsencrypt" + install -d -m 0755 -g root -o root $LETSENCRYPT_DIR + cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log" + cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade + cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt + + cat << EOF > /etc/nginx/letsencrypt.conf user www-data; worker_processes auto; daemon on; @@ -50,20 +47,34 @@ hooks: } } } + EOF - - file: - path: /etc/runit/1.d/letsencrypt - chmod: "+x" - contents: | + sed -Ei "s/^#?ACCOUNT_EMAIL=.+/ACCOUNT_EMAIL=${LETSENCRYPT_ACCOUNT_EMAIL}/" \ + /shared/letsencrypt/account.conf + + sed -Ei "s/ssl_certificate .+/ssl_certificate \/shared\/ssl\/${DISCOURSE_HOSTNAME}.cer;\ + ssl_certificate \/shared\/ssl\/${DISCOURSE_HOSTNAME}_ecc.cer;/" \ + /etc/nginx/conf.d/outlets/server/20-https.conf + sed -Ei "s/ssl_certificate_key .+/ssl_certificate_key \/shared\/ssl\/${DISCOURSE_HOSTNAME}.key; \ + ssl_certificate_key \/shared\/ssl\/${DISCOURSE_HOSTNAME}_ecc.key;/" \ + /etc/nginx/conf.d/outlets/server/20-https.conf + + exec /usr/local/bin/letsencrypt + + - file: + path: /usr/local/bin/letsencrypt + chmod: "+x" + contents: | #!/bin/bash + LETSENCRYPT_DIR="/shared/letsencrypt" /usr/sbin/nginx -c /etc/nginx/letsencrypt.conf issue_cert() { - LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh --issue $2 -d $$ENV_DISCOURSE_HOSTNAME --keylength $1 -w /var/www/discourse/public + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ${LETSENCRYPT_DIR}/acme.sh --issue $2 -d ${DISCOURSE_HOSTNAME} --keylength $1 -w /var/www/discourse/public } cert_exists() { - [[ "$(cd $$ENV_LETSENCRYPT_DIR/$$ENV_DISCOURSE_HOSTNAME$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")" ]] + [[ "$(cd ${LETSENCRYPT_DIR}/${DISCOURSE_HOSTNAME}$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")" ]] } ######################################################## @@ -76,11 +87,11 @@ hooks: issue_cert "4096" "--force" fi - LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \ + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ${LETSENCRYPT_DIR}/acme.sh \ --installcert \ - -d $$ENV_DISCOURSE_HOSTNAME \ - --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer \ - --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key \ + -d ${DISCOURSE_HOSTNAME} \ + --fullchainpath /shared/ssl/${DISCOURSE_HOSTNAME}.cer \ + --keypath /shared/ssl/${DISCOURSE_HOSTNAME}.key \ --reloadcmd "sv reload nginx" ######################################################## @@ -93,11 +104,11 @@ hooks: issue_cert "ec-256" "--force" fi - LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \ + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ${LETSENCRYPT_DIR}/acme.sh \ --installcert --ecc \ - -d $$ENV_DISCOURSE_HOSTNAME \ - --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer \ - --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key \ + -d ${DISCOURSE_HOSTNAME} \ + --fullchainpath /shared/ssl/${DISCOURSE_HOSTNAME}_ecc.cer \ + --keypath /shared/ssl/${DISCOURSE_HOSTNAME}_ecc.key \ --reloadcmd "sv reload nginx" if cert_exists "" || cert_exists "_ecc"; then @@ -106,22 +117,15 @@ hooks: /usr/sbin/nginx -c /etc/nginx/letsencrypt.conf -s stop - - replace: - filename: /shared/letsencrypt/account.conf - from: /#?ACCOUNT_EMAIL=.+/ - to: | - ACCOUNT_EMAIL=$$ENV_LETSENCRYPT_ACCOUNT_EMAIL - - - replace: - filename: "/etc/nginx/conf.d/outlets/server/20-https.conf" - from: /ssl_certificate.+/ - to: | - ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer; - ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer; - - - replace: - filename: "/etc/nginx/conf.d/outlets/server/20-https.conf" - from: /ssl_certificate_key.+/ - to: | - ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key; - ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key; +hooks: + after_ssl: + - file: + path: /etc/runit/1.d/install-ssl + chmod: "+x" + contents: | + #!/bin/bash + if [ -z "$DISCOURSE_HOSTNAME" ]; then echo "DISCOURSE_HOSTNAME expected"; exit 1; fi + if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV not set. Skipping Let's Encrypt setup."; exit 0; fi + if [[ ! "$LETSENCRYPT_ACCOUNT_EMAIL" =~ ([^@]+)@([^\.]+) ]]; then echo "LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address"; exit 1; fi + /usr/local/bin/configure-ssl + exec /usr/local/bin/configure-letsencrypt diff --git a/templates/web.ssl.template.yml b/templates/web.ssl.template.yml index 71e6fe041..93776f82d 100644 --- a/templates/web.ssl.template.yml +++ b/templates/web.ssl.template.yml @@ -1,21 +1,31 @@ run: - - exec: - cmd: - - "mkdir -p /shared/ssl/" - file: - path: "/etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf" + path: /etc/runit/1.d/install-ssl + hook: ssl + chmod: "+x" + contents: | + #!/bin/bash + if [ -z "$DISCOURSE_HOSTNAME" ]; then echo "DISCOURSE_HOSTNAME expected"; exit 1; fi + if [ ! -z "$ENABLE_SSL" ]; then + exec /usr/local/bin/configure-ssl + fi + + - file: + path: "/usr/local/bin/configure-ssl" + chmod: "+x" contents: | + #!/bin/bash + mkdir -p /shared/ssl/ + cat << EOF > /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf server { listen 80; - return 301 https://$$ENV_DISCOURSE_HOSTNAME$request_uri; + return 301 https://${DISCOURSE_HOSTNAME}$request_uri; } - - file: - path: "/etc/nginx/conf.d/outlets/server/10-http.conf" - contents: "" - - file: - hook: ssl - path: "/etc/nginx/conf.d/outlets/server/20-https.conf" - contents: | + EOF + + > /etc/nginx/conf.d/outlets/server/10-http.conf + + cat << EOF > /etc/nginx/conf.d/outlets/server/20-https.conf listen 443 ssl; http2 on; @@ -32,17 +42,15 @@ run: add_header Strict-Transport-Security 'max-age=31536000'; - if ($http_host != $$ENV_DISCOURSE_HOSTNAME) { - rewrite (.*) https://$$ENV_DISCOURSE_HOSTNAME$1 permanent; + if (\$http_host != ${DISCOURSE_HOSTNAME}) { + rewrite (.*) https://${DISCOURSE_HOSTNAME}\$1 permanent; } - - file: - path: "/etc/nginx/conf.d/outlets/discourse/20-https.conf" - contents: | + EOF + + cat << EOF > /etc/nginx/conf.d/outlets/discourse/20-https.conf add_header Strict-Transport-Security 'max-age=31536000'; - - exec: - cmd: - - |- - if [ -f "/proc/net/if_inet6" ] ; then - sed -i 's/listen 80;/listen 80;\nlisten [::]:80;/g' /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf - sed -i 's/listen 443 ssl;/listen 443 ssl;\nlisten [::]:443 ssl;/g' /etc/nginx/conf.d/outlets/server/20-https.conf - fi + EOF + if [ -f "/proc/net/if_inet6" ] ; then + sed -i 's/listen 80;/listen 80;\nlisten [::]:80;/g' /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf + sed -i 's/listen 443 ssl;/listen 443 ssl;\nlisten [::]:443 ssl;/g' /etc/nginx/conf.d/outlets/server/20-https.conf + fi From b17b2ffabfbdf67736d861787ed45a33ef7a9f26 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Thu, 24 Jul 2025 00:17:23 -0700 Subject: [PATCH 2/3] DEV: update readability split up if statements. Add spaces around heredoc. use install /dev/null rather than empty redirect to clear a file. --- templates/web.letsencrypt.ssl.template.yml | 15 ++++++++++++--- templates/web.ssl.template.yml | 11 ++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/templates/web.letsencrypt.ssl.template.yml b/templates/web.letsencrypt.ssl.template.yml index 4d1819ea8..8bc877873 100644 --- a/templates/web.letsencrypt.ssl.template.yml +++ b/templates/web.letsencrypt.ssl.template.yml @@ -124,8 +124,17 @@ hooks: chmod: "+x" contents: | #!/bin/bash - if [ -z "$DISCOURSE_HOSTNAME" ]; then echo "DISCOURSE_HOSTNAME expected"; exit 1; fi - if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV not set. Skipping Let's Encrypt setup."; exit 0; fi - if [[ ! "$LETSENCRYPT_ACCOUNT_EMAIL" =~ ([^@]+)@([^\.]+) ]]; then echo "LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address"; exit 1; fi + if [ -z "$DISCOURSE_HOSTNAME" ]; then + echo "DISCOURSE_HOSTNAME expected" + exit 1 + fi + if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then + echo "LETSENCRYPT_ACCOUNT_EMAIL ENV not set. Skipping Let's Encrypt setup." + exit 0 + fi + if [[ ! "$LETSENCRYPT_ACCOUNT_EMAIL" =~ ([^@]+)@([^\.]+) ]]; then + echo "LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address" + exit 1 + fi /usr/local/bin/configure-ssl exec /usr/local/bin/configure-letsencrypt diff --git a/templates/web.ssl.template.yml b/templates/web.ssl.template.yml index 93776f82d..912e12fab 100644 --- a/templates/web.ssl.template.yml +++ b/templates/web.ssl.template.yml @@ -5,8 +5,11 @@ run: chmod: "+x" contents: | #!/bin/bash - if [ -z "$DISCOURSE_HOSTNAME" ]; then echo "DISCOURSE_HOSTNAME expected"; exit 1; fi - if [ ! -z "$ENABLE_SSL" ]; then + if [ -z "$DISCOURSE_HOSTNAME" ]; then + echo "DISCOURSE_HOSTNAME expected" + exit 1 + fi + if [ -n "$ENABLE_SSL" ]; then exec /usr/local/bin/configure-ssl fi @@ -16,6 +19,7 @@ run: contents: | #!/bin/bash mkdir -p /shared/ssl/ + cat << EOF > /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf server { listen 80; @@ -23,7 +27,7 @@ run: } EOF - > /etc/nginx/conf.d/outlets/server/10-http.conf + install /dev/null /etc/nginx/conf.d/outlets/server/10-http.conf cat << EOF > /etc/nginx/conf.d/outlets/server/20-https.conf listen 443 ssl; @@ -50,6 +54,7 @@ run: cat << EOF > /etc/nginx/conf.d/outlets/discourse/20-https.conf add_header Strict-Transport-Security 'max-age=31536000'; EOF + if [ -f "/proc/net/if_inet6" ] ; then sed -i 's/listen 80;/listen 80;\nlisten [::]:80;/g' /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf sed -i 's/listen 443 ssl;/listen 443 ssl;\nlisten [::]:443 ssl;/g' /etc/nginx/conf.d/outlets/server/20-https.conf From 89bc8dfb66fc9c376455dd83a54f937f558088ea Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Thu, 24 Jul 2025 00:29:47 -0700 Subject: [PATCH 3/3] DEV: Install acme.sh using curl rather than git --- templates/web.letsencrypt.ssl.template.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/web.letsencrypt.ssl.template.yml b/templates/web.letsencrypt.ssl.template.yml index 8bc877873..616ff548d 100644 --- a/templates/web.letsencrypt.ssl.template.yml +++ b/templates/web.letsencrypt.ssl.template.yml @@ -4,7 +4,8 @@ env: run: - exec: cmd: - - cd /opt && git clone --branch 3.0.6 --depth 1 https://github.com/acmesh-official/acme.sh.git + - curl https://raw.githubusercontent.com/acmesh-official/acme.sh/3.0.6/acme.sh > /opt/acme.sh + - chmod +x /opt/acme.sh - file: path: "/usr/local/bin/configure-letsencrypt" chmod: "+x" @@ -13,9 +14,10 @@ run: touch /var/spool/cron/crontabs/root LETSENCRYPT_DIR="/shared/letsencrypt" install -d -m 0755 -g root -o root $LETSENCRYPT_DIR - cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log" - cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade - cd /opt/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt + cd /opt + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log" + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade + LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt cat << EOF > /etc/nginx/letsencrypt.conf user www-data;