acme: use the hotplug system
authorGlen Huang <heyhgl@gmail.com>
Sat, 29 Jan 2022 12:54:35 +0000 (20:54 +0800)
committerToke Høiland-Jørgensen <toke@toke.dk>
Mon, 15 Aug 2022 22:32:04 +0000 (00:32 +0200)
Signed-off-by: Glen Huang <heyhgl@gmail.com>
16 files changed:
net/acme-acmesh/Makefile [new file with mode: 0644]
net/acme-acmesh/files/hook.sh [new file with mode: 0644]
net/acme-common/Makefile [new file with mode: 0644]
net/acme-common/files/acme.config [new file with mode: 0644]
net/acme-common/files/acme.init [new file with mode: 0644]
net/acme-common/files/acme.sh [new file with mode: 0644]
net/acme-common/files/acme.uci-defaults [new file with mode: 0644]
net/acme-common/files/functions.sh [new file with mode: 0644]
net/acme/Makefile
net/acme/files/acme.config [deleted file]
net/acme/files/acme.init [deleted file]
net/acme/files/run.sh [deleted file]
net/haproxy/Makefile
net/haproxy/files/acme.hotplug [new file with mode: 0644]
net/nginx/Makefile
net/nginx/files/acme.hotplug [new file with mode: 0644]

diff --git a/net/acme-acmesh/Makefile b/net/acme-acmesh/Makefile
new file mode 100644 (file)
index 0000000..cd6d4f3
--- /dev/null
@@ -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 (file)
index 0000000..bd3825b
--- /dev/null
@@ -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 (file)
index 0000000..4e69702
--- /dev/null
@@ -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-common/files/acme.config b/net/acme-common/files/acme.config
new file mode 100644 (file)
index 0000000..12bffd6
--- /dev/null
@@ -0,0 +1,22 @@
+config acme
+       option state_dir '/etc/acme'
+       option account_email 'email@example.org'
+       option debug 0
+
+config cert 'example_wildcard'
+       option enabled 0
+       option use_staging 1
+       list domains example.org
+       list domains sub.example.org
+       list domains *.sub.example.org
+       option dns "dns_freedns"
+       list credentials 'FREEDNS_User="ssladmin@example.org"'
+       list credentials 'FREEDNS_Password="1234"'
+       option calias "example.com"
+       option dalias "dalias.example.com"
+
+config cert 'example'
+       option enabled 0
+       option use_staging 1
+       list domains example.org
+       list domains sub.example.org
diff --git a/net/acme-common/files/acme.init b/net/acme-common/files/acme.init
new file mode 100644 (file)
index 0000000..e654054
--- /dev/null
@@ -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 (file)
index 0000000..3dff9f3
--- /dev/null
@@ -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 (file)
index 0000000..0b92c2e
--- /dev/null
@@ -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 (file)
index 0000000..3325a7e
--- /dev/null
@@ -0,0 +1,7 @@
+log() {
+       prio="$1"
+       shift
+       if [ "$prio" != debug ] || [ "$debug" = 0 ]; then
+               logger -t "$LOG_TAG" -s -p "daemon.$prio" -- "$@"
+       fi
+}
index f93e1d439a3d82aeb9dee0031f188533dc7afa28..5f260a99762aebc8b025c5166368c073ae131ab8 100644 (file)
@@ -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.config b/net/acme/files/acme.config
deleted file mode 100644 (file)
index 4a0f9ec..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-config acme
-       option state_dir '/etc/acme'
-       option account_email 'email@example.org'
-       option debug 0
-
-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"'
-       option calias "example.com"
-       option dalias "dalias.example.com"
-
-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/files/acme.init b/net/acme/files/acme.init
deleted file mode 100644 (file)
index 6a20ccb..0000000
+++ /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 (file)
index 615cbf2..0000000
+++ /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
index 3b882d9543ac0c34073a3d7f415baa32f173552d..bd47c15bbe0fd284e1d17ec9c5d7ae967710221a 100644 (file)
@@ -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 (file)
index 0000000..5a4dc5c
--- /dev/null
@@ -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
index 886c55db7c26d46c89c0ed79e0791b3d7c4862a3..340de5846f0de40a72b2054ee3b4eca824cfc743 100644 (file)
@@ -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 (file)
index 0000000..74f1448
--- /dev/null
@@ -0,0 +1,3 @@
+if [ "$ACTION" = renewed ]; then
+       /etc/init.d/nginx reload
+fi