aboutsummaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorGlen Huang <heyhgl@gmail.com>2022-01-29 20:54:35 +0800
committerToke Høiland-Jørgensen <toke@toke.dk>2022-08-16 00:32:04 +0200
commite84f6514538f0638507ce8066f801374db6f4150 (patch)
tree9274e85ce94512361980aade53f6b0864b819574 /net
parentb02fea131b7fe0de5bf5132e2e3104eb343aa06f (diff)
acme: use the hotplug system
Signed-off-by: Glen Huang <heyhgl@gmail.com>
Diffstat (limited to 'net')
-rw-r--r--net/acme-acmesh/Makefile69
-rw-r--r--net/acme-acmesh/files/hook.sh125
-rw-r--r--net/acme-common/Makefile67
-rw-r--r--net/acme-common/files/acme.config (renamed from net/acme/files/acme.config)13
-rw-r--r--net/acme-common/files/acme.init9
-rw-r--r--net/acme-common/files/acme.sh142
-rw-r--r--net/acme-common/files/acme.uci-defaults4
-rw-r--r--net/acme-common/files/functions.sh7
-rw-r--r--net/acme/Makefile54
-rw-r--r--net/acme/files/acme.init33
-rw-r--r--net/acme/files/run.sh392
-rw-r--r--net/haproxy/Makefile2
-rw-r--r--net/haproxy/files/acme.hotplug12
-rw-r--r--net/nginx/Makefile5
-rw-r--r--net/nginx/files/acme.hotplug3
15 files changed, 450 insertions, 487 deletions
diff --git a/net/acme-acmesh/Makefile b/net/acme-acmesh/Makefile
new file mode 100644
index 000000000..cd6d4f333
--- /dev/null
+++ b/net/acme-acmesh/Makefile
@@ -0,0 +1,69 @@
+#
+# Copyright (C) 2016 Toke Høiland-Jørgensen
+#
+# This is free software, licensed under the GNU General Public License v3 or
+# later.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=acme-acmesh
+PKG_VERSION:=3.0.1
+PKG_RELEASE:=$(AUTORELEASE)
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=https://codeload.github.com/acmesh-official/acme.sh/tar.gz/$(PKG_VERSION)?
+PKG_HASH:=6212cc0c2bca99a7dd6cbb4236b4c7dd5d1113dab0841e66dae4d307d902a8e6
+PKG_BUILD_DIR:=$(BUILD_DIR)/acme.sh-$(PKG_VERSION)
+
+PKG_MAINTAINER:=Toke Høiland-Jørgensen <toke@toke.dk>
+PKG_LICENSE:=GPL-3.0-only
+PKG_LICENSE_FILES:=LICENSE.md
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/acme-acmesh
+ SECTION:=net
+ CATEGORY:=Network
+ DEPENDS:=+acme-common +wget-ssl +ca-bundle +openssl-util +socat
+ TITLE:=ACME client acme.sh wrapper script
+ URL:=https://acme.sh
+ PKGARCH:=all
+ PROVIDES:=acme-client
+endef
+
+define Package/acme-acmesh/description
+A client for issuing ACME (e.g, Letsencrypt) certificates.
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/acme-acmesh/install
+ $(INSTALL_DIR) $(1)/usr/lib/acme/client
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/acme.sh $(1)/usr/lib/acme/client
+ $(INSTALL_BIN) ./files/hook.sh $(1)/usr/lib/acme/hook
+endef
+
+define Package/acme-acmesh-dnsapi
+ SECTION:=net
+ CATEGORY:=Network
+ DEPENDS:=+acme
+ TITLE:=DNS API integration for ACME (Letsencrypt) client
+ PKGARCH:=all
+endef
+
+define Package/acme-acmesh-dnsapi/description
+ This package provides DNS API integration for ACME (Letsencrypt) client.
+endef
+
+define Package/acme-acmesh-dnsapi/install
+ $(INSTALL_DIR) $(1)/usr/lib/acme/client/dnsapi
+ $(INSTALL_DATA) $(PKG_BUILD_DIR)/dnsapi/*.sh $(1)/usr/lib/acme/client/dnsapi
+endef
+
+$(eval $(call BuildPackage,acme-acmesh))
+$(eval $(call BuildPackage,acme-acmesh-dnsapi))
diff --git a/net/acme-acmesh/files/hook.sh b/net/acme-acmesh/files/hook.sh
new file mode 100644
index 000000000..bd3825b75
--- /dev/null
+++ b/net/acme-acmesh/files/hook.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+set -u
+ACME=/usr/lib/acme/acme.sh
+LOG_TAG=acme-acmesh
+# webroot option deprecated, use the hardcoded value directly in the next major version
+WEBROOT=${webroot:-/var/run/acme/challenge}
+
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+
+# Needed by acme.sh
+export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
+export NO_TIMESTAMP=1
+
+cmd="$1"
+
+case $cmd in
+get)
+ set --
+ [ "$debug" = 1 ] && set -- "$@" --debug
+
+ case $keylength in
+ ec-*)
+ domain_dir="$state_dir/${main_domain}_ecc"
+ set -- "$@" --ecc
+ ;;
+ *)
+ domain_dir="$state_dir/$main_domain"
+ ;;
+ esac
+
+ log info "Running ACME for $main_domain"
+
+ if [ -e "$domain_dir" ]; then
+ if [ "$staging" = 0 ] && grep -q "acme-staging" "$domain_dir/$main_domain.conf"; then
+ mv "$domain_dir" "$domain_dir.staging"
+ log info "Certificates are previously issued from a staging server, but staging option is diabled, moved to $domain_dir.staging."
+ staging_moved=1
+ else
+ set -- "$@" --renew --home "$state_dir" -d "$main_domain"
+ log info "$*"
+ trap 'ACTION=renewed-failed hotplug-call acme;exit 1' INT
+ "$ACME" "$@"
+ status=$?
+ trap - INT
+
+ case $status in
+ 0) ;; # renewed ok, handled by acme.sh hook, ignore.
+ 2) ;; # renew skipped, ignore.
+ *)
+ ACTION=renew-failed hotplug-call acme
+ ;;
+ esac
+ return 0
+ fi
+ fi
+
+ for d in $domains; do
+ set -- "$@" -d "$d"
+ done
+ set -- "$@" --keylength "$keylength" --accountemail "$account_email"
+
+ if [ "$acme_server" ]; then
+ set -- "$@" --server "$acme_server"
+ # default to letsencrypt because the upstream default may change
+ elif [ "$staging" = 1 ]; then
+ set -- "$@" --server letsencrypt_test
+ else
+ set -- "$@" --server letsencrypt
+ fi
+
+ if [ "$days" ]; then
+ set -- "$@" --days "$days"
+ fi
+
+ if [ "$dns" ]; then
+ set -- "$@" --dns "$dns"
+ if [ "$dalias" ]; then
+ set -- "$@" --domain-alias "$dalias"
+ if [ "$calias" ]; then
+ log err "Both domain and challenge aliases are defined. Ignoring the challenge alias."
+ fi
+ elif [ "$calias" ]; then
+ set -- "$@" --challenge-alias "$calias"
+ fi
+ elif [ "$standalone" = 1 ]; then
+ set -- "$@" --standalone --listen-v6
+ else
+ mkdir -p "$WEBROOT"
+ set -- "$@" --webroot "$WEBROOT"
+ fi
+
+ set -- "$@" --issue --home "$state_dir"
+
+ log info "$*"
+ trap 'ACTION=issue-failed hotplug-call acme;exit 1' INT
+ "$ACME" "$@" \
+ --pre-hook 'ACTION=prepare hotplug-call acme' \
+ --renew-hook 'ACTION=renewed hotplug-call acme'
+ status=$?
+ trap - INT
+
+ case $status in
+ 0)
+ ln -s "$domain_dir/$main_domain.cer" /etc/ssl/acme
+ ln -s "$domain_dir/$main_domain.key" /etc/ssl/acme
+ ln -s "$domain_dir/fullchain.cer" "/etc/ssl/acme/$main_domain.fullchain.cer"
+ ln -s "$domain_dir/ca.cer" "/etc/ssl/acme/$main_domain.chain.cer"
+ ACTION=issued hotplug-call acme
+ ;;
+ *)
+ if [ "$staging_moved" = 1 ]; then
+ mv "$domain_dir.staging" "$domain_dir"
+ log err "Staging certificate restored"
+ elif [ -d "$domain_dir" ]; then
+ failed_dir="$domain_dir.failed-$(date +%s)"
+ mv "$domain_dir" "$failed_dir"
+ log err "State moved to $failed_dir"
+ fi
+ ACTION=issue-failed hotplug-call acme
+ return 0
+ ;;
+ esac
+ ;;
+esac
diff --git a/net/acme-common/Makefile b/net/acme-common/Makefile
new file mode 100644
index 000000000..4e69702be
--- /dev/null
+++ b/net/acme-common/Makefile
@@ -0,0 +1,67 @@
+#
+# Copyright (C) 2016 Toke Høiland-Jørgensen
+#
+# This is free software, licensed under the GNU General Public License v3 or
+# later.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=acme-common
+PKG_VERSION:=1.0.0
+
+PKG_MAINTAINER:=Toke Høiland-Jørgensen <toke@toke.dk>
+PKG_LICENSE:=GPL-3.0-only
+PKG_LICENSE_FILES:=LICENSE.md
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/acme-common
+ SECTION:=net
+ CATEGORY:=Network
+ TITLE:=ACME client wrapper common files
+ PKGARCH:=all
+endef
+
+define Package/acme-common/description
+ACME client wrapper common files.
+endef
+
+define Package/acme-common/conffiles
+/etc/config/acme
+/etc/acme
+/etc/ssl/acme
+endef
+
+define Package/acme-common/install
+ $(INSTALL_DIR) $(1)/etc/acme
+ $(INSTALL_DIR) $(1)/etc/config
+ $(INSTALL_CONF) ./files/acme.config $(1)/etc/config/acme
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) ./files/acme.sh $(1)/usr/bin/acme
+ $(INSTALL_DIR) $(1)/usr/lib/acme
+ $(INSTALL_DATA) ./files/functions.sh $(1)/usr/lib/acme
+ $(INSTALL_DIR) $(1)/etc/init.d
+ $(INSTALL_BIN) ./files/acme.init $(1)/etc/init.d/acme
+ $(INSTALL_DIR) $(1)/etc/uci-defaults
+ $(INSTALL_DATA) ./files/acme.uci-defaults $(1)/etc/uci-defaults/acme
+endef
+
+define Package/acme/postinst
+#!/bin/sh
+grep -q '/usr/bin/acme' /etc/crontabs/root 2>/dev/null && exit 0
+echo "0 0 * * * /usr/bin/acme get" >> /etc/crontabs/root
+endef
+
+define Package/acme-common/prerm
+#!/bin/sh
+sed -i '\|/usr/bin/acme|d' /etc/crontabs/root
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+$(eval $(call BuildPackage,acme-common))
diff --git a/net/acme/files/acme.config b/net/acme-common/files/acme.config
index 4a0f9ec77..12bffd60f 100644
--- a/net/acme/files/acme.config
+++ b/net/acme-common/files/acme.config
@@ -6,15 +6,9 @@ config acme
config cert 'example_wildcard'
option enabled 0
option use_staging 1
- option keylength 2048
- option update_uhttpd 1
- option update_nginx 1
- option update_haproxy 0
list domains example.org
list domains sub.example.org
list domains *.sub.example.org
- # option user_setup "path-to-custom-setup.script"
- # option user_cleanup "path-to-custom-cleanup.script"
option dns "dns_freedns"
list credentials 'FREEDNS_User="ssladmin@example.org"'
list credentials 'FREEDNS_Password="1234"'
@@ -24,12 +18,5 @@ config cert 'example_wildcard'
config cert 'example'
option enabled 0
option use_staging 1
- option keylength 2048
- option update_uhttpd 1
- option update_nginx 1
- option update_haproxy 0
list domains example.org
list domains sub.example.org
- option webroot ""
- # option user_setup "path-to-custom-setup.script"
- # option user_cleanup "path-to-custom-cleanup.script"
diff --git a/net/acme-common/files/acme.init b/net/acme-common/files/acme.init
new file mode 100644
index 000000000..e654054f4
--- /dev/null
+++ b/net/acme-common/files/acme.init
@@ -0,0 +1,9 @@
+#!/bin/sh /etc/rc.common
+
+START=80
+USE_PROCD=1
+
+service_triggers() {
+ procd_add_config_trigger config.change acme \
+ /usr/bin/acme get
+}
diff --git a/net/acme-common/files/acme.sh b/net/acme-common/files/acme.sh
new file mode 100644
index 000000000..3dff9f348
--- /dev/null
+++ b/net/acme-common/files/acme.sh
@@ -0,0 +1,142 @@
+#!/bin/sh
+# Wrapper for acme.sh to work on openwrt.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Authors: Toke Høiland-Jørgensen <toke@toke.dk>
+
+export state_dir='/etc/acme'
+export account_email=
+export debug=0
+export challenge_dir='/var/run/acme/challenge'
+NFT_HANDLE=
+HOOK=/usr/lib/acme/hook
+LOG_TAG=acme
+
+# shellcheck source=/dev/null
+. /lib/functions.sh
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+
+cleanup() {
+ log debug "cleaning up"
+ if [ "$NFT_HANDLE" ]; then
+ # $NFT_HANDLE contains the string 'handle XX' so pass it unquoted to nft
+ nft delete rule inet fw4 input $NFT_HANDLE
+ fi
+}
+
+load_options() {
+ section=$1
+
+ # compatibility for old option name
+ config_get_bool use_staging "$section" staging
+ if [ -z "$staging" ]; then
+ config_get_bool staging "$section" staging 0
+ fi
+ export staging
+ config_get calias "$section" calias
+ export calias
+ config_get dalias "$section" dalias
+ export dalias
+ config_get domains "$section" domains
+ export domains
+ export main_domain
+ main_domain="$(first_arg $domains)"
+ config_get keylength "$section" keylength ec-256
+ export keylength
+ config_get dns "$section" dns
+ export dns
+ config_get acme_server "$section" acme_server
+ export acme_server
+ config_get days "$section" days
+ export days
+ config_get standalone "$section" standalone 0
+ export standalone
+
+ config_get webroot "$section" webroot
+ export webroot
+ if [ "$webroot" ]; then
+ log warn "Option \"webroot\" is deprecated, please remove it and change your web server's config so it serves ACME challenge requests from /var/run/acme/challenge."
+ fi
+}
+
+first_arg() {
+ echo "$1"
+}
+
+get_cert() {
+ section=$1
+
+ config_get_bool enabled "$section" enabled 1
+ [ "$enabled" = 1 ] || return
+
+ load_options "$section"
+ if [ -z "$dns" ] && [ "$standalone" = 0 ]; then
+ mkdir -p "$challenge_dir"
+ fi
+
+ if [ "$standalone" = 1 ] && [ -z "$NFT_HANDLE" ]; then
+ if ! NFT_HANDLE=$(nft -a -e insert rule inet fw4 input tcp dport 80 counter accept comment ACME | grep -o 'handle [0-9]\+'); then
+ return 1
+ fi
+ log debug "added nft rule: $NFT_HANDLE"
+ fi
+
+ load_credentials() {
+ eval export "$1"
+ }
+ config_list_foreach "$section" credentials load_credentials
+
+ "$HOOK" get
+}
+
+load_globals() {
+ section=$1
+
+ config_get account_email "$section" account_email
+ if [ -z "$account_email" ]; then
+ log err "account_email option is required"
+ exit 1
+ fi
+
+ config_get state_dir "$section" state_dir "$state_dir"
+ mkdir -p "$state_dir"
+
+ config_get debug "$section" debug "$debug"
+
+ # only look for the first acme section
+ return 1
+}
+
+usage() {
+ cat <<EOF
+Usage: acme <command> [arguments]
+Commands:
+ get issue or renew certificates
+ cert <domain> show certificate matching domain
+EOF
+ exit 1
+}
+
+if [ ! -x "$HOOK" ]; then
+ log err "An ACME client like acme-acmesh or acme-uacme is required, which is not installed."
+ exit 1
+fi
+
+case $1 in
+get)
+ config_load acme
+ config_foreach load_globals acme
+
+ mkdir -p /etc/ssl/acme
+ trap cleanup EXIT
+ config_foreach get_cert cert
+ ;;
+*)
+ usage
+ ;;
+esac
diff --git a/net/acme-common/files/acme.uci-defaults b/net/acme-common/files/acme.uci-defaults
new file mode 100644
index 000000000..0b92c2e26
--- /dev/null
+++ b/net/acme-common/files/acme.uci-defaults
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+grep -q '/usr/bin/acme' /etc/crontabs/root 2>/dev/null && exit 0
+echo "0 0 * * * /usr/bin/acme get" >> /etc/crontabs/root
diff --git a/net/acme-common/files/functions.sh b/net/acme-common/files/functions.sh
new file mode 100644
index 000000000..3325a7ea2
--- /dev/null
+++ b/net/acme-common/files/functions.sh
@@ -0,0 +1,7 @@
+log() {
+ prio="$1"
+ shift
+ if [ "$prio" != debug ] || [ "$debug" = 0 ]; then
+ logger -t "$LOG_TAG" -s -p "daemon.$prio" -- "$@"
+ fi
+}
diff --git a/net/acme/Makefile b/net/acme/Makefile
index f93e1d439..5f260a997 100644
--- a/net/acme/Makefile
+++ b/net/acme/Makefile
@@ -8,13 +8,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=acme
-PKG_VERSION:=3.0.1
-PKG_RELEASE:=$(AUTORELEASE)
-
-PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
-PKG_SOURCE_URL:=https://codeload.github.com/acmesh-official/acme.sh/tar.gz/$(PKG_VERSION)?
-PKG_HASH:=6212cc0c2bca99a7dd6cbb4236b4c7dd5d1113dab0841e66dae4d307d902a8e6
-PKG_BUILD_DIR:=$(BUILD_DIR)/acme.sh-$(PKG_VERSION)
+PKG_VERSION:=4.0.0
PKG_MAINTAINER:=Toke Høiland-Jørgensen <toke@toke.dk>
PKG_LICENSE:=GPL-3.0-only
@@ -25,19 +19,17 @@ include $(INCLUDE_DIR)/package.mk
define Package/acme
SECTION:=net
CATEGORY:=Network
- DEPENDS:=+wget-ssl +ca-bundle +openssl-util +socat
- TITLE:=ACME (Letsencrypt) client
- URL:=https://acme.sh
+ DEPENDS:=+acme-acmesh
+ TITLE:=Shorthand package for acme-acmesh
PKGARCH:=all
endef
define Package/acme/description
- A client for issuing Letsencrypt certificates.
+Shorthand package for acme-acmesh.
endef
-define Package/acme/conffiles
-/etc/config/acme
-/etc/acme
+define Package/acme/install
+ :
endef
define Build/Configure
@@ -46,38 +38,4 @@ endef
define Build/Compile
endef
-define Package/acme/install
- $(INSTALL_DIR) $(1)/etc/acme
- $(INSTALL_DIR) $(1)/etc/config
- $(INSTALL_CONF) ./files/acme.config $(1)/etc/config/acme
- $(INSTALL_DIR) $(1)/etc/init.d
- $(INSTALL_BIN) ./files/acme.init $(1)/etc/init.d/acme
- $(INSTALL_DIR) $(1)/usr/lib/acme
- $(INSTALL_BIN) ./files/run.sh $(1)/usr/lib/acme/run-acme
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/acme.sh $(1)/usr/lib/acme/acme.sh
-endef
-
-define Package/acme/prerm
-#!/bin/sh
-sed -i '/\/etc\/init\.d\/acme start/d' /etc/crontabs/root
-endef
-
-define Package/acme-dnsapi
- SECTION:=net
- CATEGORY:=Network
- DEPENDS:=+acme
- TITLE:=DNS API integration for ACME (Letsencrypt) client
- PKGARCH:=all
-endef
-
-define Package/acme-dnsapi/description
- This package provides DNS API integration for ACME (Letsencrypt) client.
-endef
-
-define Package/acme-dnsapi/install
- $(INSTALL_DIR) $(1)/usr/lib/acme/dnsapi
- $(INSTALL_DATA) $(PKG_BUILD_DIR)/dnsapi/*.sh $(1)/usr/lib/acme/dnsapi
-endef
-
$(eval $(call BuildPackage,acme))
-$(eval $(call BuildPackage,acme-dnsapi))
diff --git a/net/acme/files/acme.init b/net/acme/files/acme.init
deleted file mode 100644
index 6a20ccb91..000000000
--- a/net/acme/files/acme.init
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh /etc/rc.common
-
-USE_PROCD=1
-
-START=50
-SCRIPT=/usr/lib/acme/run-acme
-
-start_service() {
- procd_open_instance
- procd_set_param command $SCRIPT
- procd_set_param file /etc/config/acme
- procd_set_param stdout 1
- procd_set_param stderr 1
- procd_close_instance
-}
-
-reload_service() {
- rc_procd start_service "$@"
- return 0
-}
-
-stop_service() {
- return 0
-}
-
-boot() {
- touch "/var/run/acme_boot"
- start
-}
-
-service_triggers() {
- procd_add_reload_trigger acme
-}
diff --git a/net/acme/files/run.sh b/net/acme/files/run.sh
deleted file mode 100644
index 615cbf22a..000000000
--- a/net/acme/files/run.sh
+++ /dev/null
@@ -1,392 +0,0 @@
-#!/bin/sh
-# Wrapper for acme.sh to work on openwrt.
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 3 of the License, or (at your option) any later
-# version.
-#
-# Author: Toke Høiland-Jørgensen <toke@toke.dk>
-
-CHECK_CRON=$1
-ACME=/usr/lib/acme/acme.sh
-export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
-export NO_TIMESTAMP=1
-
-UHTTPD_LISTEN_HTTP=
-STATE_DIR='/etc/acme'
-ACCOUNT_EMAIL=
-DEBUG=0
-NGINX_WEBSERVER=0
-UPDATE_NGINX=0
-UPDATE_UHTTPD=0
-UPDATE_HAPROXY=0
-NFT_HANDLE=
-USER_CLEANUP=
-
-. /lib/functions.sh
-
-check_cron() {
- [ -f "/etc/crontabs/root" ] && grep -q '/etc/init.d/acme' /etc/crontabs/root && return
- echo "0 0 * * * /etc/init.d/acme start" >> /etc/crontabs/root
- /etc/init.d/cron start
-}
-
-log() {
- logger -t acme -s -p daemon.info -- "$@"
-}
-
-err() {
- logger -t acme -s -p daemon.err -- "$@"
-}
-
-debug() {
- [ "$DEBUG" -eq "1" ] && logger -t acme -s -p daemon.debug -- "$@"
-}
-
-get_listeners() {
- local proto rq sq listen remote state program
- netstat -nptl 2> /dev/null | while read -r proto rq sq listen remote state program; do
- case "$proto#$listen#$program" in
- tcp#*:80#[0-9]*/*) echo -n "${program%% *} " ;;
- esac
- done
-}
-
-run_acme() {
- debug "Running acme.sh as '$ACME $*'"
- $ACME "$@"
-}
-
-pre_checks() {
- main_domain="$1"
-
- log "Running pre checks for $main_domain."
-
- listeners="$(get_listeners)"
-
- debug "port80 listens: $listeners"
-
- for listener in $(get_listeners); do
- pid="${listener%/*}"
- cmd="$(basename $(readlink /proc/$pid/exe))"
-
- case "$cmd" in
- uhttpd)
- if [ -n "$UHTTPD_LISTEN_HTTP" ]; then
- debug "Already handled uhttpd; skipping"
- continue
- fi
-
- debug "Found uhttpd listening on port 80; trying to disable."
-
- UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http)
-
- if [ -z "$UHTTPD_LISTEN_HTTP" ]; then
- err "$main_domain: Unable to find uhttpd listen config."
- err "Manually disable uhttpd or set webroot to continue."
- return 1
- fi
-
- uci set uhttpd.main.listen_http=''
- uci commit uhttpd || return 1
- if ! /etc/init.d/uhttpd reload; then
- uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
- uci commit uhttpd
- return 1
- fi
- ;;
- nginx)
- if [ "$NGINX_WEBSERVER" -eq "1" ]; then
- debug "Already handled nginx; skipping"
- continue
- fi
-
- debug "Found nginx listening on port 80; trying to disable."
- NGINX_WEBSERVER=1
- local tries=0
- while grep -sq "$cmd" "/proc/$pid/cmdline" && kill -0 "$pid"; do
- /etc/init.d/nginx stop
- if [ $tries -gt 10 ]; then
- debug "Can't stop nginx. Terminating script."
- return 1
- fi
- debug "Waiting for nginx to stop..."
- tries=$((tries + 1))
- sleep 1
- done
- ;;
- "")
- debug "Nothing listening on port 80."
- ;;
- *)
- err "$main_domain: Cannot run in standalone mode; another daemon is listening on port 80."
- err "Disable other daemon or set webroot to continue."
- return 1
- ;;
- esac
- done
-
- NFT_HANDLE=$(nft -a -e insert rule inet fw4 input tcp dport 80 counter accept comment ACME | grep -o 'handle [0-9]\+')
- ret=$?
- [ "$ret" -eq "0" ] || return 1
- debug "added nft rule: $NFT_HANDLE"
- return 0
-}
-
-post_checks() {
- log "Running post checks (cleanup)."
- # $NFT_HANDLE contains the string 'handle XX' so pass it unquoted to nft
- [ -n "$NFT_HANDLE" ] && nft delete rule inet fw4 input $NFT_HANDLE
-
- if [ -e /etc/init.d/uhttpd ] && { [ -n "$UHTTPD_LISTEN_HTTP" ] || [ "$UPDATE_UHTTPD" -eq 1 ]; }; then
- if [ -n "$UHTTPD_LISTEN_HTTP" ]; then
- uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
- UHTTPD_LISTEN_HTTP=
- fi
- uci commit uhttpd
- /etc/init.d/uhttpd restart
- fi
-
- if [ -e /etc/init.d/nginx ] && { [ "$NGINX_WEBSERVER" -eq 1 ] || [ "$UPDATE_NGINX" -eq 1 ]; }; then
- NGINX_WEBSERVER=0
- /etc/init.d/nginx restart
- fi
-
- if [ -e /etc/init.d/haproxy ] && [ "$UPDATE_HAPROXY" -eq 1 ] ; then
- /etc/init.d/haproxy restart
- fi
-
- if [ -n "$USER_CLEANUP" ] && [ -f "$USER_CLEANUP" ]; then
- log "Running user-provided cleanup script from $USER_CLEANUP."
- "$USER_CLEANUP" || return 1
- fi
-}
-
-err_out() {
- post_checks
- exit 1
-}
-
-int_out() {
- post_checks
- trap - INT
- kill -INT $$
-}
-
-is_staging() {
- local main_domain
- local domain_dir
- main_domain="$1"
- domain_dir="$2"
-
- grep -q "acme-staging" "${domain_dir}/${main_domain}.conf"
- return $?
-}
-
-issue_cert() {
- local section="$1"
- local acme_args=
- local enabled
- local use_staging
- local update_uhttpd
- local update_nginx
- local update_haproxy
- local keylength
- local keylength_ecc=0
- local domains
- local main_domain
- local moved_staging=0
- local failed_dir
- local webroot
- local dns
- local user_setup
- local user_cleanup
- local ret
- local domain_dir
- local acme_server
- local days
-
- config_get_bool enabled "$section" enabled 0
- config_get_bool use_staging "$section" use_staging
- config_get_bool update_uhttpd "$section" update_uhttpd
- config_get_bool update_nginx "$section" update_nginx
- config_get_bool update_haproxy "$section" update_haproxy
- config_get calias "$section" calias
- config_get dalias "$section" dalias
- config_get domains "$section" domains
- config_get keylength "$section" keylength
- config_get webroot "$section" webroot
- config_get dns "$section" dns
- config_get user_setup "$section" user_setup
- config_get user_cleanup "$section" user_cleanup
- config_get acme_server "$section" acme_server
- config_get days "$section" days
-
- UPDATE_NGINX=$update_nginx
- UPDATE_UHTTPD=$update_uhttpd
- UPDATE_HAPROXY=$update_haproxy
- USER_CLEANUP=$user_cleanup
-
- [ "$enabled" -eq "1" ] || return
-
- [ "$DEBUG" -eq "1" ] && acme_args="$acme_args --debug"
-
- set -- $domains
- main_domain=$1
-
- if [ -n "$user_setup" ] && [ -f "$user_setup" ]; then
- log "Running user-provided setup script from $user_setup."
- "$user_setup" "$main_domain" || return 1
- else
- [ -n "$webroot" ] || [ -n "$dns" ] || pre_checks "$main_domain" || return 1
- fi
-
- if echo "$keylength" | grep -q "^ec-"; then
- domain_dir="$STATE_DIR/${main_domain}_ecc"
- keylength_ecc=1
- else
- domain_dir="$STATE_DIR/${main_domain}"
- fi
-
- log "Running ACME for $main_domain"
-
- handle_credentials() {
- local credential="$1"
- eval export $credential
- }
- config_list_foreach "$section" credentials handle_credentials
-
- if [ -e "$domain_dir" ]; then
- if [ "$use_staging" -eq "0" ] && is_staging "$main_domain" "$domain_dir"; then
- log "Found previous cert issued using staging server. Moving it out of the way."
- mv "$domain_dir" "${domain_dir}.staging"
- moved_staging=1
- else
- log "Found previous cert config. Issuing renew."
- [ "$keylength_ecc" -eq "1" ] && acme_args="$acme_args --ecc"
- run_acme --home "$STATE_DIR" --renew -d "$main_domain" $acme_args && ret=0 || ret=1
- post_checks
- return $ret
- fi
- fi
-
- acme_args="$acme_args $(for d in $domains; do echo -n "-d $d "; done)"
- acme_args="$acme_args --keylength $keylength"
- [ -n "$ACCOUNT_EMAIL" ] && acme_args="$acme_args --accountemail $ACCOUNT_EMAIL"
-
- if [ -n "$acme_server" ]; then
- log "Using custom ACME server URL"
- acme_args="$acme_args --server $acme_server"
- else
- # default to letsencrypt because the upstream default may change
- if [ "$use_staging" -eq "1" ]; then
- acme_args="$acme_args --server letsencrypt_test"
- else
- acme_args="$acme_args --server letsencrypt"
- fi
- fi
-
- if [ -n "$days" ]; then
- log "Renewing after $days days"
- acme_args="$acme_args --days $days"
- fi
-
- if [ -n "$dns" ]; then
- log "Using dns mode"
- acme_args="$acme_args --dns $dns"
- if [ -n "$dalias" ]; then
- log "Using domain alias for dns mode"
- acme_args="$acme_args --domain-alias $dalias"
- if [ -n "$calias" ]; then
- err "Both domain and challenge aliases are defined. Ignoring the challenge alias."
- fi
- elif [ -n "$calias" ]; then
- log "Using challenge alias for dns mode"
- acme_args="$acme_args --challenge-alias $calias"
- fi
- elif [ -z "$webroot" ]; then
- log "Using standalone mode"
- acme_args="$acme_args --standalone --listen-v6"
- else
- if [ ! -d "$webroot" ]; then
- err "$main_domain: Webroot dir '$webroot' does not exist!"
- post_checks
- return 1
- fi
- log "Using webroot dir: $webroot"
- acme_args="$acme_args --webroot $webroot"
- fi
-
- if ! run_acme --home "$STATE_DIR" --issue $acme_args; then
- failed_dir="${domain_dir}.failed-$(date +%s)"
- err "Issuing cert for $main_domain failed. Moving state to $failed_dir"
- [ -d "$domain_dir" ] && mv "$domain_dir" "$failed_dir"
- if [ "$moved_staging" -eq "1" ]; then
- err "Restoring staging certificate"
- mv "${domain_dir}.staging" "${domain_dir}"
- fi
- post_checks
- return 1
- fi
-
- if [ -e /etc/init.d/uhttpd ] && [ "$update_uhttpd" -eq "1" ]; then
- uci set uhttpd.main.key="${domain_dir}/${main_domain}.key"
- uci set uhttpd.main.cert="${domain_dir}/fullchain.cer"
- # commit and reload is in post_checks
- fi
-
- local nginx_updated
- nginx_updated=0
- if command -v nginx-util 2> /dev/null && [ "$update_nginx" -eq "1" ]; then
- nginx_updated=1
- for domain in $domains; do
- nginx-util add_ssl "${domain}" acme "${domain_dir}/fullchain.cer" \
- "${domain_dir}/${main_domain}.key" || nginx_updated=0
- done
- # reload is in post_checks
- fi
-
- if [ "$nginx_updated" -eq "0" ] && [ -w /etc/nginx/nginx.conf ] && [ "$update_nginx" -eq "1" ]; then
- sed -i "s#ssl_certificate\ .*#ssl_certificate ${domain_dir}/fullchain.cer;#g" /etc/nginx/nginx.conf
- sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key ${domain_dir}/${main_domain}.key;#g" /etc/nginx/nginx.conf
- # commit and reload is in post_checks
- fi
-
- if [ -e /etc/init.d/haproxy ] && [ -w /etc/haproxy.cfg ] && [ "$update_haproxy" -eq "1" ]; then
- cat "${domain_dir}/${main_domain}.key" "${domain_dir}/fullchain.cer" > "${domain_dir}/${main_domain}-haproxy.pem"
- sed -i "s#bind :::443 v4v6 ssl crt .* alpn#bind :::443 v4v6 ssl crt ${domain_dir}/${main_domain}-haproxy.pem alpn#g" /etc/haproxy.cfg
- # commit and reload is in post_checks
- fi
-
- post_checks
-}
-
-load_vars() {
- local section="$1"
-
- STATE_DIR=$(config_get "$section" state_dir)
- ACCOUNT_EMAIL=$(config_get "$section" account_email)
- DEBUG=$(config_get "$section" debug)
-}
-
-check_cron
-[ -n "$CHECK_CRON" ] && exit 0
-[ -e "/var/run/acme_boot" ] && rm -f "/var/run/acme_boot" && exit 0
-
-config_load acme
-config_foreach load_vars acme
-
-if [ -z "$STATE_DIR" ] || [ -z "$ACCOUNT_EMAIL" ]; then
- err "state_dir and account_email must be set"
- exit 1
-fi
-
-[ -d "$STATE_DIR" ] || mkdir -p "$STATE_DIR"
-
-trap err_out HUP TERM
-trap int_out INT
-
-config_foreach issue_cert cert
-
-exit 0
diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile
index 3b882d954..bd47c15bb 100644
--- a/net/haproxy/Makefile
+++ b/net/haproxy/Makefile
@@ -122,6 +122,8 @@ define Package/haproxy/install
$(INSTALL_CONF) ./files/haproxy.cfg $(1)/etc/
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/haproxy.init $(1)/etc/init.d/haproxy
+ $(INSTALL_DIR) $(1)/etc/hotplug.d/acme
+ $(INSTALL_DATA) ./files/acme.hotplug $(1)/etc/hotplug.d/acme/00-haproxy
endef
Package/haproxy-nossl/install = $(Package/haproxy/install)
diff --git a/net/haproxy/files/acme.hotplug b/net/haproxy/files/acme.hotplug
new file mode 100644
index 000000000..5a4dc5cdd
--- /dev/null
+++ b/net/haproxy/files/acme.hotplug
@@ -0,0 +1,12 @@
+case $ACTION in
+issued|renewed)
+ cat \
+ "/etc/ssl/acme/$main_domain.fullchain.cer" \
+ "/etc/ssl/acme/$main_domain.key" \
+ >"/etc/ssl/acme/$main_domain.combined.cer"
+ ;;
+esac
+
+if [ "$ACTION" = renewed ]; then
+ /etc/init.d/haproxy reload
+fi
diff --git a/net/nginx/Makefile b/net/nginx/Makefile
index 886c55db7..340de5846 100644
--- a/net/nginx/Makefile
+++ b/net/nginx/Makefile
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=nginx
PKG_VERSION:=1.21.3
-PKG_RELEASE:=1
+PKG_RELEASE:=2
PKG_SOURCE:=nginx-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://nginx.org/download/
@@ -372,6 +372,9 @@ ifeq ($(CONFIG_NGINX_NAXSI),y)
endif
$(if $(CONFIG_NGINX_NAXSI),$($(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-naxsi/naxsi_config/naxsi_core.rules $(1)/etc/nginx))
$(if $(CONFIG_NGINX_NAXSI),$(chmod 0640 $(1)/etc/nginx/naxsi_core.rules))
+
+ $(INSTALL_DIR) $(1)/etc/hotplug.d/acme
+ $(INSTALL_DATA) ./files/acme.hotplug $(1)/etc/hotplug.d/acme/00-nginx
endef
Package/nginx-all-module/install = $(Package/nginx-ssl/install)
diff --git a/net/nginx/files/acme.hotplug b/net/nginx/files/acme.hotplug
new file mode 100644
index 000000000..74f1448d7
--- /dev/null
+++ b/net/nginx/files/acme.hotplug
@@ -0,0 +1,3 @@
+if [ "$ACTION" = renewed ]; then
+ /etc/init.d/nginx reload
+fi