ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/subpack/net/nginx-util/Makefile b/external/subpack/net/nginx-util/Makefile
new file mode 100644
index 0000000..b1d9c5e
--- /dev/null
+++ b/external/subpack/net/nginx-util/Makefile
@@ -0,0 +1,135 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=nginx-util
+PKG_VERSION:=1.6
+PKG_RELEASE:=1
+PKG_MAINTAINER:=Peter Stadler <peter.stadler@student.uibk.ac.at>
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+CMAKE_OPTIONS+= -DUBUS=y
+CMAKE_OPTIONS+= -DVERSION=$(PKG_VERSION)
+
+
+define Package/nginx-ssl-util/default
+  SECTION:=net
+  CATEGORY:=Network
+  SUBMENU:=Web Servers/Proxies
+  TITLE:=Nginx configurator including SSL
+  DEPENDS:=+libstdcpp +libuci +libubus +libubox +libpthread +libopenssl
+  # TODO: remove after a transition period (together with below and pkg nginx):
+  # It actually removes nginx-util (replacing it by a dummy pkg) to avoid
+  # conflicts with nginx-ssl-util*
+  DEPENDS+= +nginx-util
+  EXTRA_DEPENDS:=nginx-util (>=1.4-2)
+endef
+
+
+define Package/nginx-ssl-util
+  $(Package/nginx-ssl-util/default)
+  TITLE+= (using PCRE)
+  DEPENDS+= +libpcre
+  CONFLICTS:=nginx-ssl-util-nopcre,
+endef
+
+
+define Package/nginx-ssl-util-nopcre
+  $(Package/nginx-ssl-util/default)
+  TITLE+= (using <regex>)
+  CONFLICTS:=nginx-ssl-util
+endef
+
+
+define Package/nginx-ssl-util/default/description
+  Utility that builds dynamically LAN listen directives for Nginx.
+  Furthermore, it manages SSL directives for its server parts and can create
+  corresponding (self-signed) certificates.
+endef
+
+
+Package/nginx-ssl-util/description = \
+  $(Package/nginx-ssl-util/default/description) \
+  It uses the PCRE library for performance.
+
+
+Package/nginx-ssl-util-nopcre/description = \
+  $(Package/nginx-ssl-util/default/description) \
+  It uses the standard regex library of C++.
+
+
+define Package/nginx-ssl-util/install/default
+	$(INSTALL_DIR) $(1)/etc/nginx/conf.d/
+
+	$(INSTALL_CONF) ./files/uci.conf.template $(1)/etc/nginx/
+	$(LN) /var/lib/nginx/uci.conf $(1)/etc/nginx/uci.conf
+
+	$(INSTALL_CONF) ./files/restrict_locally $(1)/etc/nginx/
+
+	$(INSTALL_DIR) $(1)/etc/config/
+	$(INSTALL_CONF) ./files/nginx.config $(1)/etc/config/nginx
+
+ifneq ($(CONFIG_IPV6),y) # the used IPv6 directives have `::` in them:
+	$(SED) "/::/d" $(1)/etc/nginx/restrict_locally
+	$(SED) "/::/d" $(1)/etc/config/nginx
+endif
+endef
+
+
+define Package/nginx-ssl-util/install
+	$(call Package/nginx-ssl-util/install/default, $(1))
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-ssl-util $(1)/usr/bin/nginx-util
+endef
+
+
+define Package/nginx-ssl-util-nopcre/install
+	$(call Package/nginx-ssl-util/install/default, $(1))
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-ssl-util-nopcre \
+		$(1)/usr/bin/nginx-util
+endef
+
+
+define Package/nginx-ssl-util/prerm
+#!/bin/sh
+
+[ -n "$${IPKG_INSTROOT}" ] && exit 0
+[ "$${PKG_UPGRADE}" = "1" ] && exit 0
+case "$$(/sbin/uci get "nginx.global.uci_enable" 2>/dev/null)" in
+	1|on|true|yes|enabled) ;;
+	*) exit 0;;
+esac
+
+eval "$$(/usr/bin/nginx-util get_env)" &&
+[ "$$(/sbin/uci get "nginx.$${LAN_NAME}.$${MANAGE_SSL}" 2>/dev/null)" = \
+	"self-signed" ] &&
+cd "/etc/nginx" &&
+rm -f "$$(/sbin/uci get "nginx.$${LAN_NAME}.ssl_certificate")" \
+	"$$(/sbin/uci get "nginx.$${LAN_NAME}.ssl_certificate_key")"
+
+exit 0
+endef
+
+
+Package/nginx-ssl-util-nopcre/prerm = $(Package/nginx-ssl-util/prerm)
+
+
+$(eval $(call BuildPackage,nginx-ssl-util))
+$(eval $(call BuildPackage,nginx-ssl-util-nopcre))
+
+
+# TODO: remove after a transition period (together with above and pkg nginx):
+# It replaces nginx-util by a dummy pkg for a smooth upgrade of nginx*
+
+define Package/nginx-util
+  TITLE:=Dummy package for removing nginx-util when upgrading.
+  DEPENDS:=+libstdcpp +libubus +libubox +libpthread
+  PKGARCH:=all
+endef
+
+define Package/nginx-util/install
+	$(INSTALL_DIR) $(1)/usr/bin
+endef
+
+$(eval $(call BuildPackage,nginx-util))
diff --git a/external/subpack/net/nginx-util/files/README.sh b/external/subpack/net/nginx-util/files/README.sh
new file mode 100755
index 0000000..7ca1729
--- /dev/null
+++ b/external/subpack/net/nginx-util/files/README.sh
@@ -0,0 +1,475 @@
+#!/bin/sh
+# This is a template copy it by: ./README.sh | xclip -selection c
+# to https://openwrt.org/docs/guide-user/services/webserver/nginx#configuration
+
+
+NGINX_UTIL="/usr/bin/nginx-util"
+
+EXAMPLE_COM="example.com"
+
+MSG="
+/* Created by the following bash script that includes the source of some files:
+ * https://github.com/openwrt/packages/net/nginx-util/files/README.sh
+ */"
+
+eval $("${NGINX_UTIL}" get_env)
+
+code() {
+    local file
+    [ $# -gt 1 ] && file="$2" || file="$(basename "$1")"
+    printf "<file nginx %s>\n%s</file>" "$1" "$(cat "${file}")";
+}
+
+ifConfEcho() {
+    sed -nE "s/^\s*$1=\s*(\S*)\s*\\\\$/\n$2 \"\1\";/p" ../../nginx/Makefile;
+}
+
+cat <<EOF
+
+
+
+
+
+===== Configuration =====${MSG}
+
+
+
+The official Documentation contains a
+[[https://docs.nginx.com/nginx/admin-guide/|Admin Guide]].
+Here we will look at some often used configuration parts and how we handle them
+at OpenWrt.
+At different places there are references to the official
+[[https://docs.nginx.com/nginx/technical-specs/|Technical Specs]]
+for further reading.
+
+**tl;dr:** When starting Nginx by ''/etc/init.d/nginx'', it creates its main
+configuration dynamically based on a minimal template and the
+[[docs:guide-user:base-system:uci|🡒UCI]] configuration.
+
+The UCI ''/etc/config/nginx'' contains initially:
+| ''config server '${LAN_NAME}''' | \
+Default server for the LAN, which includes all ''${CONF_DIR}*.locations''. |
+| ''config server '_redirect2ssl''' | \
+Redirects inexistent URLs to HTTPS. |
+
+It enables also the ''${CONF_DIR}'' directory for further configuration:
+| ''${CONF_DIR}\$NAME.conf'' | \
+Is included in the main configuration. \
+It is prioritized over a UCI ''config server '\$NAME' ''. |
+| ''${CONF_DIR}\$NAME.locations'' | \
+Is include in the ''${LAN_NAME}'' server and can be re-used for others, too. |
+| ''$(dirname "${CONF_DIR}")/restrict_locally'' | \
+Is include in the ''${LAN_NAME}'' server and allows only accesses from LAN. |
+
+Setup configuration (for a server ''\$NAME''):
+| ''$(basename ${NGINX_UTIL}) [${ADD_SSL_FCT}|del_ssl] \$NAME''  | \
+Add/remove a self-signed certificate and corresponding directives. |
+| ''uci set nginx.\$NAME.access_log='logd openwrt''' | \
+Writes accesses to Openwrt’s \
+[[docs:guide-user:base-system:log.essentials|🡒logd]]. |
+| ''uci set nginx.\$NAME.error_log='logd' '' | \
+Writes errors to Openwrt’s \
+[[docs:guide-user:base-system:log.essentials|🡒logd]]. |
+| ''uci [set|add_list] nginx.\$NAME.key='value' '' | \
+Becomes a ''key value;'' directive if the //key// does not start with //uci_//. |
+| ''uci set nginx.\$NAME=[disable|server]'' |\
+Disable/enable inclusion in the dynamic conf.|
+| ''uci set nginx.global.uci_enable=false'' | \
+Use a custom ''${NGINX_CONF}'' rather than a dynamic conf. |
+
+
+
+==== Basic ====${MSG}
+
+
+We modify the configuration by changing servers saved in the UCI configuration
+at ''/etc/config/nginx'' and/or by creating different configuration files in the
+''${CONF_DIR}'' directory.
+These files use the file extensions ''.locations'' and ''.conf'' plus ''.crt''
+and ''.key'' for SSL certificates and keys.((
+We can disable a single configuration file in ''${CONF_DIR}'' by giving it
+another extension, e.g., by adding ''.disabled''.))
+For the new configuration to take effect, we must reload it by:
+
+<code bash>service nginx reload</code>
+
+For OpenWrt we use a special initial configuration, which is explained in the
+section [[#openwrt_s_defaults|🡓OpenWrt’s Defaults]].
+So, we can make a site available at a specific URL in the **LAN** by creating a
+''.locations'' file in the directory ''${CONF_DIR}''.
+Such a file consists just of some
+[[https://nginx.org/en/docs/http/ngx_http_core_module.html#location|
+location blocks]].
+Under the latter link, you can find also the official documentation for all
+available directives of the HTTP core of Nginx.
+Look for //location// in the Context list.
+
+The following example provides a simple template, see at the end for
+different [[#locations_for_apps|🡓Locations for Apps]]((look for
+[[https://github.com/search?utf8=%E2%9C%93&q=repo%3Aopenwrt%2Fpackages
++extension%3Alocations&type=Code&ref=advsearch&l=&l=|
+other packages using a .locations file]], too.)):
+
+<code nginx ${CONF_DIR}example.locations>
+location /ex/am/ple {
+	access_log off; # default: not logging accesses.
+	# access_log /proc/self/fd/1 openwrt; # use logd (init forwards stdout).
+	# error_log stderr; # default: logging to logd (init forwards stderr).
+	error_log /dev/null; # disable error logging after config file is read.
+	# (state path of a file for access_log/error_log to the file instead.)
+	index index.html;
+}
+# location /eg/static { … }
+</code>
+
+All location blocks in all ''.locations'' files must use different URLs,
+since they are all included in the ''${LAN_NAME}'' server that is part of the
+[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]].((
+We reserve the ''location /'' for making LuCI available under the root URL,
+e.g. [[https://192.168.1.1/|192.168.1.1/]].
+All other sites shouldn’t use the root ''location /'' without suffix.))
+We should use the root URL for other sites than LuCI only on **other** domain
+names, e.g., we could make a site available at https://${EXAMPLE_COM}/.
+In order to do that, we create [[#new_server_parts|🡓New Server Parts]] for all
+domain names.
+We can also activate SSL thereby, see
+[[#ssl_server_parts|🡓SSL Server Parts]].
+We use such server parts also for publishing sites to the internet (WAN)
+instead of making them available just locally (in the LAN).
+
+Via ''${CONF_DIR}*.conf'' files we can add directives to the //http// part of
+the configuration.
+If you would change the configuration ''$(basename "${UCI_CONF}").template''
+instead, it is not updated to new package's versions anymore.
+Although it is not recommended, you can also disable the whole UCI config and
+create your own ''${NGINX_CONF}''; then invoke:
+
+<code bash>uci set nginx.global.uci_enable=false</code>
+
+
+
+==== New Server Parts ====${MSG}
+
+
+For making the router reachable from the WAN at a registered domain name,
+it is not enough letting the
+[[docs:guide-user:firewall:firewall_configuration|🡒firewall]] accept requests
+(typically on ports 80 and 443) and giving the name server the internet IP
+address of the router (maybe updated automatically by a
+[[docs:guide-user:services:ddns:client|🡒DDNS Client]]).
+
+We also need to set up virtual hosting for this domain name by creating an
+appropriate server section in ''/etc/config/nginx''
+(or in a ''${CONF_DIR}*.conf'' file, which cannot be changed using UCI).
+All such parts are included in the main configuration of OpenWrt
+([[#openwrt_s_defaults|🡓OpenWrt’s Defaults]]).
+
+In the server part, we state the domain as
+[[https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name|
+server_name]].
+The link points to the same document as for the location blocks in the
+[[#basic|🡑Basic Configuration]]: the official documentation for all available
+directives of the HTTP core of Nginx.
+This time look for //server// in the Context list, too.
+The server part should also contain similar location blocks as
+++before.|
+We can re-include a ''.locations'' file that is included in the server part for
+the LAN by default.
+Then the site is reachable under the same path at both domains, e.g. by
+https://192.168.1.1/ex/am/ple as well as by https://${EXAMPLE_COM}/ex/am/ple.
+++
+
+We can add directives to a server in the UCI configuration by invoking
+''uci [set|add_list] nginx.${EXAMPLE_COM//./_}.key=value''.
+If the //key// is not starting with //uci_//, it becomes a ''key value;''
+++directive.|
+Although the UCI config does not support nesting like Nginx, we can add a whole
+block as //value//.
+++
+
+We cannot use dots in a //key// name other than in the //value//.
+In the following example we replace the dot in //${EXAMPLE_COM}// by an
+underscore for the UCI name of the server, but not for Nginx's //server_name//:
+
+<code bash>
+uci add nginx server &&
+uci rename nginx.@server[-1]=${EXAMPLE_COM//./_} &&
+uci add_list nginx.${EXAMPLE_COM//./_}.listen='80' &&
+uci add_list nginx.${EXAMPLE_COM//./_}.listen='[::]:80' &&
+uci set nginx.${EXAMPLE_COM//./_}.server_name='${EXAMPLE_COM}' &&
+uci add_list nginx.${EXAMPLE_COM//./_}.include=\
+'$(basename ${CONF_DIR})/${EXAMPLE_COM}.locations'
+# uci add_list nginx.${EXAMPLE_COM//./_}.location='/ { … }' \
+# root location for this server.
+</code>
+
+We can disable respective re-enable this server again by:
+
+<code bash>
+uci set nginx.${EXAMPLE_COM//./_}=disable # respective: \
+uci set nginx.${EXAMPLE_COM//./_}=server
+</code>
+
+These changes are made in the RAM (and can be used until a reboot), we can save
+them permanently by:
+
+<code bash>uci commit nginx</code>
+
+For creating a similar ''${CONF_DIR}${EXAMPLE_COM}.conf'', we can adopt the
+following:
+
+<code nginx ${CONF_DIR}${EXAMPLE_COM}.conf>
+server {
+	listen 80;
+	listen [::]:80;
+	server_name ${EXAMPLE_COM};
+	include '$(basename ${CONF_DIR})/${EXAMPLE_COM}.locations';
+	# location / { … } # root location for this server.
+}
+</code>
+
+[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]] include the UCI server
+''config server '_redirect2ssl' ''.
+It  acts as //default_server// for HTTP and redirects requests for inexistent
+URLs to HTTPS.
+For making another domain name accessible to all addresses, the corresponding
+server part should listen on port //80// and contain the FQDN as
+//server_name//, cf. the official documentation on
+[[https://nginx.org/en/docs/http/request_processing.html|request_processing]].
+
+Furthermore, there is a UCI server named ''${LAN_NAME}''.
+It is the //default_server// for HTTPS and allows connections from LAN only.
+It includes the file ''$(dirname "${CONF_DIR}")/restrict_locally'' with
+appropriate //allow/deny// directives, cf. the official documentation on
+[[https://nginx.org/en/docs/http/ngx_http_access_module.html|limiting access]].
+
+
+
+==== SSL Server Parts ====${MSG}
+
+
+For enabling HTTPS for a domain we need a SSL certificate as well as its key and
+add them by the directives //ssl_certificate// respective
+//ssl_certificate_key// to the server part of the domain
+([[https://nginx.org/en/docs/http/configuring_https_servers.html#sni|TLS SNI]]
+is supported by default).
+The rest of the configuration is similar as for general
+[[#new_server_parts|🡑New Server Parts]].
+We only have to adjust the listen directives by adding the //ssl// parameter and
+changing the port from //80// to //443//.
+
+The official documentation of the SSL module contains an
+[[https://nginx.org/en/docs/http/ngx_http_ssl_module.html#example|
+example]] with some optimizations.
+We can extend an existing UCI server section similarly, e.g., for the above
+''config server '${EXAMPLE_COM//./_}' '' we invoke:
+
+<code bash>
+# Instead of 'del_list' the listen* entries, we could use '443 ssl' beforehand.
+uci del_list nginx.${EXAMPLE_COM//./_}.listen='80' &&
+uci del_list nginx.${EXAMPLE_COM//./_}.listen='[::]:80' &&
+uci add_list nginx.${EXAMPLE_COM//./_}.listen='443 ssl' &&
+uci add_list nginx.${EXAMPLE_COM//./_}.listen='[::]:443 ssl' &&
+uci set nginx.${EXAMPLE_COM//./_}.ssl_certificate=\
+'${CONF_DIR}${EXAMPLE_COM}.crt' &&
+uci set nginx.${EXAMPLE_COM//./_}.ssl_certificate_key=\
+'${CONF_DIR}${EXAMPLE_COM}.key' &&
+uci set nginx.${EXAMPLE_COM//./_}.ssl_session_cache=\
+'${SSL_SESSION_CACHE_ARG}' &&
+uci set nginx.${EXAMPLE_COM//./_}.ssl_session_timeout=\
+'${SSL_SESSION_TIMEOUT_ARG}' &&
+uci commit nginx
+</code>
+
+For making the server in ''${CONF_DIR}${EXAMPLE_COM}.conf'' available
+via SSL, we can make similar changes there.
+
+The following command creates a **self-signed** SSL certificate and changes the
+corresponding configuration:
+
+<code bash>$(basename "${NGINX_UTIL}") ${ADD_SSL_FCT} ${EXAMPLE_COM}</code>
+
+  - If a ''$(basename "${CONF_DIR}")/${EXAMPLE_COM}.conf'' file exists, it\
+    adds //ssl_*// directives and changes the //listen// directives there.\
+    Else it does that similarly to the example above for a ++selected UCI\
+    server.| Hereby it searches the UCI config first for a server with the\
+    given name and then for a server whose //server_name// contains the name.\
+    For //${EXAMPLE_COM}// it is the latter as a UCI key cannot have dots.++
+  - It checks if there is a certificate with key for '${EXAMPLE_COM}' that is\
+    valid for at least 13 months or tries to create a self-signed one.
+  - When cron is activated, it installs a cron job for renewing the self-signed\
+    certificate every year if needed, too. We can activate cron by: \
+    <code bash>service cron enable && service cron start</code>
+
+This can be undone by invoking:
+
+<code bash>$(basename "${NGINX_UTIL}") del_ssl ${EXAMPLE_COM}</code>
+
+For using an SSL certificate and key that are managed otherwise, there is:
+
+<code bash>$(basename "${NGINX_UTIL}") add_ssl ${EXAMPLE_COM} "\$MANAGER" \
+"/absolute/path/to/crt" "/absolute/path/to/key"</code>
+
+It only adds //ssl_*// directives and changes the //listen// directives in
+the appropriate configuration, but does not create or change the certificate
+or its key. This can be reverted by:
+
+<code bash>$(basename "${NGINX_UTIL}") del_ssl ${EXAMPLE_COM} "\$MANAGER"</code>
+
+For example [[https://github.com/ndilieto/uacme|uacme]] or
+[[https://github.com/Neilpang/acme.sh|acme.sh]] can be used for creating an SSL
+certificate signed by Let’s Encrypt and changing the config
+++accordingly.|
+They call ''$(basename "${NGINX_UTIL}") add_ssl \$FQDN acme \$CRT \$KEY''
+internally.++
+We can install them by:
+
+<code bash>
+opkg update && opkg install uacme #or: acme #and for LuCI: luci-app-acme
+</code>
+
+[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]] include a UCI server for the LAN:
+''config server '${LAN_NAME}' ''.
+It has //ssl_*// directives prepared for a self-signed((Let’s Encrypt (and other
+CAs) cannot sign certificates of a **local** server.))
+SSL certificate, which is created on the first start of Nginx.
+The server listens on all addresses, is the //default_server// for HTTPS and
+allows connections from LAN only (by including the file ''restrict_locally''
+with //allow/deny// directives, cf. the official documentation on
+[[https://nginx.org/en/docs/http/ngx_http_access_module.html|limiting access]]).
+
+For making another domain name accessible to all addresses, the corresponding
+SSL server part should listen on port //443// and contain the FQDN as
+//server_name//, cf. the official documentation on
+[[https://nginx.org/en/docs/http/request_processing.html|request_processing]].
+
+Furthermore, there is also a UCI server named ''_redirect2ssl'', which listens
+on all addresses, acts as //default_server// for HTTP and redirects requests for
+inexistent URLs to HTTPS.
+
+
+
+==== OpenWrt’s Defaults ====${MSG}
+
+
+Since Nginx is compiled with these presets, we can pretend that the main
+configuration will always contain the following directives
+(though we can overwrite them):
+
+<code nginx>$(ifConfEcho --pid-path pid)\
+$(ifConfEcho --lock-path lock_file)\
+$(ifConfEcho --error-log-path error_log)\
+$(false && ifConfEcho --http-log-path access_log)\
+$(ifConfEcho --http-proxy-temp-path proxy_temp_path)\
+$(ifConfEcho --http-client-body-temp-path client_body_temp_path)\
+$(ifConfEcho --http-fastcgi-temp-path fastcgi_temp_path)\
+</code>
+
+When starting or reloading the Nginx service, the ''/etc/init.d/nginx'' script
+sets also the following directives
+(so we cannot change them in the used configuration file):
+
+<code nginx>
+daemon off; # procd expects services to run in the foreground
+</code>
+
+Then, the init sript creates the main configuration
+''$(basename "${UCI_CONF}")'' dynamically from the template:
+
+$(code "${UCI_CONF}.template")
+
+So, the access log is turned off by default and we can look at the error log
+by ''logread'', as init.d script forwards stderr and stdout to the
+[[docs:guide-user:base-system:log.essentials|🡒runtime log]].
+We can set the //error_log// and //access_log// to files, where the log
+messages are forwarded to instead (after the configuration is read).
+And for redirecting the access log of a //server// or //location// to the logd,
+too, we insert the following directive in the corresponding block:
+
+<code nginx>	access_log /proc/self/fd/1 openwrt;</code>
+
+If we setup a server through UCI, we can use the options //error_log// and/or
+//access_log// also with the special path
+++'logd'.|
+When initializing the Nginx service, this special path is replaced by //stderr//
+respective ///proc/self/fd/1// (which are forwarded to the runtime log).
+++
+
+For creating the configuration from the template shown above, Nginx’s init
+script replaces the comment ''#UCI_HTTP_CONFIG'' by all UCI servers.
+For each server section in the the UCI configuration, it basically copies all
+options into a Nginx //server { … }// part, in detail:
+  * Options starting with ''uci_'' are skipped. Currently there is only\
+  the ''option ${MANAGE_SSL}=…'' in ++usage.| It is set to\
+  //'self-signed'// when invoking\
+  ''$(basename ${NGINX_UTIL}) ${ADD_SSL_FCT} \$NAME''.\
+  Then the corresponding certificate is re-newed if it is about to expire.\
+  All those certificates are checked on the initialization of the Nginx service\
+  and if Cron is available, it is deployed for checking them annually, too.++
+  * All other lists or options of the form ''key='value' '' are written\
+  one-to-one as ''key value;'' directives to the configuration file.\
+  Just the path //logd// has a special meaning for the logging directives\
+  (described in the previous paragraph).
+
+The init.d script of Nginx uses the //$(basename ${NGINX_UTIL})// for creating
+the configuration file
+++in RAM.|
+The main configuration ''${UCI_CONF}'' is a symbolic link to this place
+(it is a dead link if the Nginx service is not running).
+++
+
+We could use a custom configuration created at ''${NGINX_CONF}'' instead of the
+dynamic configuration, too.((
+For using a custom configuration at ''${NGINX_CONF}'', we execute
+<code bash>uci set nginx.global.uci_enable='false' </code>
+Then the rest of the UCI config is ignored and //init.d// will not create the
+main configuration dynamically from the template anymore.
+Invoking ''$(basename ${NGINX_UTIL}) [${ADD_SSL_FCT}|del_ssl] \$FQDN''
+will still try to change a server in ''$(basename "${CONF_DIR}")/\$FQDN.conf''
+(this is less reliable than for a UCI config as it uses regular expressions, not
+a complete parser for the Nginx configuration).))
+This is not encouraged since you cannot setup servers using UCI anymore.
+Rather, we can put custom configuration parts to ''.conf'' files in the
+''${CONF_DIR}'' directory.
+The main configuration pulls in all ''$(basename "${CONF_DIR}")/*.conf'' files
+into the //http {…}// block behind the created UCI servers.
+
+The initial UCI config is enabled and contains two server section:
+
+$(code "/etc/config/nginx" "nginx.config")
+
+While the LAN server is the //default_server// for HTTPS, the server
+redirecting requests for an inexistent ''server_name'' from HTTP to HTTPS acts
+as //default_server// if there is ++no other|;
+it uses an invalid name for that, more in the official documentation on
+[[https://nginx.org/en/docs/http/request_processing.html|request_processing]]
+++.
+
+The LAN server pulls in all ''.locations'' files from the directory
+''${CONF_DIR}''.
+We can install the location parts of different sites there (see
+[[#basic|🡑Basic Configuration]]) and re-include them into other servers.
+This is needed especially for making them available to the WAN
+([[#new_server_parts|🡑New Server Parts]]).
+The LAN server listens for all addresses on port //443// and restricts the
+access to local addresses by including:
+$(code "$(dirname "${CONF_DIR}")/restrict_locally")
+
+When starting or reloading the Nginx service, the init.d looks which UCI servers
+have set ''option ${MANAGE_SSL} 'self-signed' '', e.g. the LAN server.
+For all those servers it checks if there is a certificate that is still valid
+for 13 months or (re-)creates a self-signed one.
+If there is any such server, it installs also a cron job that checks the
+corresponding certificates once a year.
+The option ''${MANAGE_SSL}'' is set to //'self-signed'// respectively removed
+from a UCI server named ''${EXAMPLE_COM//./_}'' by the following
+(see [[#ssl_server_parts|🡑SSL Server Parts]], too):
+
+<code bash>
+$(basename ${NGINX_UTIL}) ${ADD_SSL_FCT} ${EXAMPLE_COM//./_} \
+# respectively: \
+$(basename ${NGINX_UTIL}) del_ssl ${EXAMPLE_COM//./_}
+</code>
+
+
+EOF
diff --git a/external/subpack/net/nginx-util/files/nginx.config b/external/subpack/net/nginx-util/files/nginx.config
new file mode 100644
index 0000000..4f07ae1
--- /dev/null
+++ b/external/subpack/net/nginx-util/files/nginx.config
@@ -0,0 +1,22 @@
+
+config main global
+	option uci_enable 'true'
+
+config server '_lan'
+	list listen '443 ssl default_server'
+	list listen '[::]:443 ssl default_server'
+	option server_name '_lan'
+	list include 'restrict_locally'
+	list include 'conf.d/*.locations'
+	option uci_manage_ssl 'self-signed'
+	option ssl_certificate '/etc/nginx/conf.d/_lan.crt'
+	option ssl_certificate_key '/etc/nginx/conf.d/_lan.key'
+	option ssl_session_cache 'shared:SSL:32k'
+	option ssl_session_timeout '64m'
+	option access_log 'off; # logd openwrt'
+
+config server '_redirect2ssl'
+	list listen '80'
+	list listen '[::]:80'
+	option server_name '_redirect2ssl'
+	option return '302 https://$host$request_uri'
diff --git a/external/subpack/net/nginx-util/files/restrict_locally b/external/subpack/net/nginx-util/files/restrict_locally
new file mode 100644
index 0000000..0b791cd
--- /dev/null
+++ b/external/subpack/net/nginx-util/files/restrict_locally
@@ -0,0 +1,10 @@
+	allow ::1;
+	allow fc00::/7;
+	allow fec0::/10;
+	allow fe80::/10;
+	allow 127.0.0.0/8;
+	allow 10.0.0.0/8;
+	allow 172.16.0.0/12;
+	allow 192.168.0.0/16;
+	allow 169.254.0.0/16;
+	deny all;
diff --git a/external/subpack/net/nginx-util/files/uci.conf.template b/external/subpack/net/nginx-util/files/uci.conf.template
new file mode 100644
index 0000000..1c611d9
--- /dev/null
+++ b/external/subpack/net/nginx-util/files/uci.conf.template
@@ -0,0 +1,32 @@
+# Consider using UCI or creating files in /etc/nginx/conf.d/ for configuration.
+# Parsing UCI configuration is skipped if uci set nginx.global.uci_enable=false
+# For details see: https://openwrt.org/docs/guide-user/services/webserver/nginx
+
+worker_processes auto;
+
+user root;
+
+events {}
+
+http {
+	access_log off;
+	log_format openwrt
+		'$request_method $scheme://$host$request_uri => $status'
+		' (${body_bytes_sent}B in ${request_time}s) <- $http_referer';
+
+	include mime.types;
+	default_type application/octet-stream;
+	sendfile on;
+
+	client_max_body_size 128M;
+	large_client_header_buffers 2 1k;
+
+	gzip on;
+	gzip_vary on;
+	gzip_proxied any;
+
+	root /www;
+
+	#UCI_HTTP_CONFIG
+	include conf.d/*.conf;
+}
diff --git a/external/subpack/net/nginx-util/src/.clang-format b/external/subpack/net/nginx-util/src/.clang-format
new file mode 100644
index 0000000..3c1ba73
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/.clang-format
@@ -0,0 +1,171 @@
+---
+Language:        Cpp
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveMacros: false
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: true
+# AllowShortEnumsOnASingleLine: true
+AllowShortLambdasOnASingleLine: All
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: Always
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: false
+# BitFieldColonSpacing: After
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: MultiLine
+  AfterEnum:       false
+  AfterFunction:   true
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     true
+  BeforeElse:      true
+  # BeforeLambdaBody: true
+  # BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: None
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     100
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '^<ext/.*\.h>'
+    Priority:        2
+    SortPriority:    0
+  - Regex:           '^<.*\.h>'
+    Priority:        1
+    SortPriority:    0
+  - Regex:           '^<.*'
+    Priority:        2
+    SortPriority:    0
+  - Regex:           '.*'
+    Priority:        3
+    SortPriority:    0
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IncludeIsMainSourceRegex: ''
+IndentCaseLabels: true
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Left
+RawStringFormats:
+  - Language:        Cpp
+    Delimiters:
+      - cc
+      - CC
+      - cpp
+      - Cpp
+      - CPP
+      - 'c++'
+      - 'C++'
+    CanonicalDelimiter: ''
+    BasedOnStyle:    google
+  - Language:        TextProto
+    Delimiters:
+      - pb
+      - PB
+      - proto
+      - PROTO
+    EnclosingFunctions:
+      - EqualsProto
+      - EquivToProto
+      - PARSE_PARTIAL_TEXT_PROTO
+      - PARSE_TEST_PROTO
+      - PARSE_TEXT_PROTO
+      - ParseTextOrDie
+      - ParseTextProtoOrDie
+    CanonicalDelimiter: ''
+    BasedOnStyle:    google
+ReflowComments:  true
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles:  false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+Standard:        Auto
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+...
+
diff --git a/external/subpack/net/nginx-util/src/.clang-tidy b/external/subpack/net/nginx-util/src/.clang-tidy
new file mode 100644
index 0000000..c34ab6a
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/.clang-tidy
@@ -0,0 +1,407 @@
+---
+Checks:          'clang-diagnostic-*,clang-analyzer-*,*,-fuchsia-*,-misc-definitions-in-headers,-llvm-header-guard,-*-qualified-auto,-llvm-include-order'
+WarningsAsErrors: ''
+HeaderFilterRegex: '.*'
+AnalyzeTemporaryDtors: false
+FormatStyle:     file
+CheckOptions:
+  - key:             abseil-string-find-startswith.AbseilStringsMatchHeader
+    value:           'absl/strings/match.h'
+  - key:             abseil-string-find-startswith.IncludeStyle
+    value:           llvm
+  - key:             abseil-string-find-startswith.StringLikeClasses
+    value:           '::std::basic_string'
+  - key:             bugprone-argument-comment.CommentBoolLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentCharacterLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentFloatLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentIntegerLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentNullPtrs
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentStringLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.CommentUserDefinedLiterals
+    value:           '0'
+  - key:             bugprone-argument-comment.IgnoreSingleArgument
+    value:           '0'
+  - key:             bugprone-argument-comment.StrictMode
+    value:           '0'
+  - key:             bugprone-assert-side-effect.AssertMacros
+    value:           assert
+  - key:             bugprone-assert-side-effect.CheckFunctionCalls
+    value:           '0'
+  - key:             bugprone-dangling-handle.HandleClasses
+    value:           'std::basic_string_view;std::experimental::basic_string_view'
+  - key:             bugprone-dynamic-static-initializers.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             bugprone-exception-escape.FunctionsThatShouldNotThrow
+    value:           ''
+  - key:             bugprone-exception-escape.IgnoredExceptions
+    value:           ''
+  - key:             bugprone-misplaced-widening-cast.CheckImplicitCasts
+    value:           '0'
+  - key:             bugprone-not-null-terminated-result.WantToUseSafeFunctions
+    value:           '1'
+  - key:             bugprone-signed-char-misuse.CharTypdefsToIgnore
+    value:           ''
+  - key:             bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant
+    value:           '1'
+  - key:             bugprone-sizeof-expression.WarnOnSizeOfConstant
+    value:           '1'
+  - key:             bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression
+    value:           '0'
+  - key:             bugprone-sizeof-expression.WarnOnSizeOfThis
+    value:           '1'
+  - key:             bugprone-string-constructor.LargeLengthThreshold
+    value:           '8388608'
+  - key:             bugprone-string-constructor.WarnOnLargeLength
+    value:           '1'
+  - key:             bugprone-suspicious-enum-usage.StrictMode
+    value:           '0'
+  - key:             bugprone-suspicious-missing-comma.MaxConcatenatedTokens
+    value:           '5'
+  - key:             bugprone-suspicious-missing-comma.RatioThreshold
+    value:           '0.200000'
+  - key:             bugprone-suspicious-missing-comma.SizeThreshold
+    value:           '5'
+  - key:             bugprone-suspicious-string-compare.StringCompareLikeFunctions
+    value:           ''
+  - key:             bugprone-suspicious-string-compare.WarnOnImplicitComparison
+    value:           '1'
+  - key:             bugprone-suspicious-string-compare.WarnOnLogicalNotComparison
+    value:           '0'
+  - key:             bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit
+    value:           '16'
+  - key:             bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField
+    value:           '1'
+  - key:             bugprone-unused-return-value.CheckedFunctions
+    value:           '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty'
+  - key:             cert-dcl16-c.IgnoreMacros
+    value:           '1'
+  - key:             cert-dcl16-c.NewSuffixes
+    value:           'L;LL;LU;LLU'
+  - key:             cert-dcl59-cpp.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             cert-err09-cpp.CheckThrowTemporaries
+    value:           '1'
+  - key:             cert-err61-cpp.CheckThrowTemporaries
+    value:           '1'
+  - key:             cert-msc32-c.DisallowedSeedTypes
+    value:           'time_t,std::time_t'
+  - key:             cert-msc51-cpp.DisallowedSeedTypes
+    value:           'time_t,std::time_t'
+  - key:             cert-oop11-cpp.IncludeStyle
+    value:           llvm
+  - key:             cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
+    value:           '0'
+  - key:             cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues
+    value:           '1.0;100.0;'
+  - key:             cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues
+    value:           '1;2;3;4;'
+  - key:             cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal
+    value:           '0'
+  - key:             cppcoreguidelines-explicit-virtual-functions.FinalSpelling
+    value:           final
+  - key:             cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
+    value:           '1'
+  - key:             cppcoreguidelines-explicit-virtual-functions.OverrideSpelling
+    value:           override
+  - key:             cppcoreguidelines-macro-usage.AllowedRegexp
+    value:           '^DEBUG_*'
+  - key:             cppcoreguidelines-macro-usage.CheckCapsOnly
+    value:           '0'
+  - key:             cppcoreguidelines-macro-usage.IgnoreCommandLineMacros
+    value:           '1'
+  - key:             cppcoreguidelines-no-malloc.Allocations
+    value:           '::malloc;::calloc'
+  - key:             cppcoreguidelines-no-malloc.Deallocations
+    value:           '::free'
+  - key:             cppcoreguidelines-no-malloc.Reallocations
+    value:           '::realloc'
+  - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
+    value:           '1'
+  - key:             cppcoreguidelines-owning-memory.LegacyResourceConsumers
+    value:           '::free;::realloc;::freopen;::fclose'
+  - key:             cppcoreguidelines-owning-memory.LegacyResourceProducers
+    value:           '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile'
+  - key:             cppcoreguidelines-pro-bounds-constant-array-index.GslHeader
+    value:           ''
+  - key:             cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle
+    value:           '0'
+  - key:             cppcoreguidelines-pro-type-member-init.IgnoreArrays
+    value:           '0'
+  - key:             cppcoreguidelines-pro-type-member-init.UseAssignment
+    value:           '0'
+  - key:             cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions
+    value:           '0'
+  - key:             cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
+    value:           '0'
+  - key:             google-build-namespaces.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             google-global-names-in-headers.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             google-readability-braces-around-statements.ShortStatementLines
+    value:           '1'
+  - key:             google-readability-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             google-readability-function-size.VariableThreshold
+    value:           '4294967295'
+  - key:             google-readability-namespace-comments.ShortNamespaceLines
+    value:           '10'
+  - key:             google-readability-namespace-comments.SpacesBeforeComments
+    value:           '2'
+  - key:             google-runtime-int.SignedTypePrefix
+    value:           int
+  - key:             google-runtime-int.TypeSuffix
+    value:           ''
+  - key:             google-runtime-int.UnsignedTypePrefix
+    value:           uint
+  - key:             google-runtime-references.WhiteListTypes
+    value:           ''
+  - key:             hicpp-braces-around-statements.ShortStatementLines
+    value:           '0'
+  - key:             hicpp-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.StatementThreshold
+    value:           '800'
+  - key:             hicpp-function-size.VariableThreshold
+    value:           '4294967295'
+  - key:             hicpp-member-init.IgnoreArrays
+    value:           '0'
+  - key:             hicpp-member-init.UseAssignment
+    value:           '0'
+  - key:             hicpp-move-const-arg.CheckTriviallyCopyableMove
+    value:           '1'
+  - key:             hicpp-multiway-paths-covered.WarnOnMissingElse
+    value:           '0'
+  - key:             hicpp-named-parameter.IgnoreFailedSplit
+    value:           '0'
+  - key:             hicpp-no-malloc.Allocations
+    value:           '::malloc;::calloc'
+  - key:             hicpp-no-malloc.Deallocations
+    value:           '::free'
+  - key:             hicpp-no-malloc.Reallocations
+    value:           '::realloc'
+  - key:             hicpp-signed-bitwise.IgnorePositiveIntegerLiterals
+    value:           '0'
+  - key:             hicpp-special-member-functions.AllowMissingMoveFunctions
+    value:           '0'
+  - key:             hicpp-special-member-functions.AllowSoleDefaultDtor
+    value:           '0'
+  - key:             hicpp-uppercase-literal-suffix.IgnoreMacros
+    value:           '1'
+  - key:             hicpp-uppercase-literal-suffix.NewSuffixes
+    value:           ''
+  - key:             hicpp-use-auto.MinTypeNameLength
+    value:           '5'
+  - key:             hicpp-use-auto.RemoveStars
+    value:           '0'
+  - key:             hicpp-use-emplace.ContainersWithPushBack
+    value:           '::std::vector;::std::list;::std::deque'
+  - key:             hicpp-use-emplace.SmartPointers
+    value:           '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
+  - key:             hicpp-use-emplace.TupleMakeFunctions
+    value:           '::std::make_pair;::std::make_tuple'
+  - key:             hicpp-use-emplace.TupleTypes
+    value:           '::std::pair;::std::tuple'
+  - key:             hicpp-use-equals-default.IgnoreMacros
+    value:           '1'
+  - key:             hicpp-use-equals-delete.IgnoreMacros
+    value:           '1'
+  - key:             hicpp-use-noexcept.ReplacementString
+    value:           ''
+  - key:             hicpp-use-noexcept.UseNoexceptFalse
+    value:           '1'
+  - key:             hicpp-use-nullptr.NullMacros
+    value:           ''
+  - key:             hicpp-use-override.AllowOverrideAndFinal
+    value:           '0'
+  - key:             hicpp-use-override.FinalSpelling
+    value:           final
+  - key:             hicpp-use-override.IgnoreDestructors
+    value:           '0'
+  - key:             hicpp-use-override.OverrideSpelling
+    value:           override
+  - key:             llvm-namespace-comment.ShortNamespaceLines
+    value:           '1'
+  - key:             llvm-namespace-comment.SpacesBeforeComments
+    value:           '1'
+  - key:             misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
+    value:           '1'
+  - key:             misc-unused-parameters.StrictMode
+    value:           '0'
+  - key:             modernize-loop-convert.MaxCopySize
+    value:           '16'
+  - key:             modernize-loop-convert.MinConfidence
+    value:           reasonable
+  - key:             modernize-loop-convert.NamingStyle
+    value:           CamelCase
+  - key:             modernize-make-shared.IgnoreMacros
+    value:           '1'
+  - key:             modernize-make-shared.IncludeStyle
+    value:           '0'
+  - key:             modernize-make-shared.MakeSmartPtrFunction
+    value:           'std::make_shared'
+  - key:             modernize-make-shared.MakeSmartPtrFunctionHeader
+    value:           memory
+  - key:             modernize-make-unique.IgnoreMacros
+    value:           '1'
+  - key:             modernize-make-unique.IncludeStyle
+    value:           '0'
+  - key:             modernize-make-unique.MakeSmartPtrFunction
+    value:           'std::make_unique'
+  - key:             modernize-make-unique.MakeSmartPtrFunctionHeader
+    value:           memory
+  - key:             modernize-pass-by-value.IncludeStyle
+    value:           llvm
+  - key:             modernize-pass-by-value.ValuesOnly
+    value:           '0'
+  - key:             modernize-raw-string-literal.ReplaceShorterLiterals
+    value:           '0'
+  - key:             modernize-replace-auto-ptr.IncludeStyle
+    value:           llvm
+  - key:             modernize-replace-random-shuffle.IncludeStyle
+    value:           llvm
+  - key:             modernize-use-auto.MinTypeNameLength
+    value:           '5'
+  - key:             modernize-use-auto.RemoveStars
+    value:           '0'
+  - key:             modernize-use-default-member-init.IgnoreMacros
+    value:           '1'
+  - key:             modernize-use-default-member-init.UseAssignment
+    value:           '0'
+  - key:             modernize-use-emplace.ContainersWithPushBack
+    value:           '::std::vector;::std::list;::std::deque'
+  - key:             modernize-use-emplace.SmartPointers
+    value:           '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
+  - key:             modernize-use-emplace.TupleMakeFunctions
+    value:           '::std::make_pair;::std::make_tuple'
+  - key:             modernize-use-emplace.TupleTypes
+    value:           '::std::pair;::std::tuple'
+  - key:             modernize-use-equals-default.IgnoreMacros
+    value:           '1'
+  - key:             modernize-use-equals-delete.IgnoreMacros
+    value:           '1'
+  - key:             modernize-use-nodiscard.ReplacementString
+    value:           '[[nodiscard]]'
+  - key:             modernize-use-noexcept.ReplacementString
+    value:           ''
+  - key:             modernize-use-noexcept.UseNoexceptFalse
+    value:           '1'
+  - key:             modernize-use-nullptr.NullMacros
+    value:           'NULL'
+  - key:             modernize-use-override.AllowOverrideAndFinal
+    value:           '0'
+  - key:             modernize-use-override.FinalSpelling
+    value:           final
+  - key:             modernize-use-override.IgnoreDestructors
+    value:           '0'
+  - key:             modernize-use-override.OverrideSpelling
+    value:           override
+  - key:             modernize-use-transparent-functors.SafeMode
+    value:           '0'
+  - key:             modernize-use-using.IgnoreMacros
+    value:           '1'
+  - key:             objc-forbidden-subclassing.ForbiddenSuperClassNames
+    value:           'ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView'
+  - key:             openmp-exception-escape.IgnoredExceptions
+    value:           ''
+  - key:             performance-faster-string-find.StringLikeClasses
+    value:           'std::basic_string'
+  - key:             performance-for-range-copy.AllowedTypes
+    value:           ''
+  - key:             performance-for-range-copy.WarnOnAllAutoCopies
+    value:           '0'
+  - key:             performance-inefficient-string-concatenation.StrictMode
+    value:           '0'
+  - key:             performance-inefficient-vector-operation.EnableProto
+    value:           '0'
+  - key:             performance-inefficient-vector-operation.VectorLikeClasses
+    value:           '::std::vector'
+  - key:             performance-move-const-arg.CheckTriviallyCopyableMove
+    value:           '1'
+  - key:             performance-move-constructor-init.IncludeStyle
+    value:           llvm
+  - key:             performance-no-automatic-move.AllowedTypes
+    value:           ''
+  - key:             performance-type-promotion-in-math-fn.IncludeStyle
+    value:           llvm
+  - key:             performance-unnecessary-copy-initialization.AllowedTypes
+    value:           ''
+  - key:             performance-unnecessary-value-param.AllowedTypes
+    value:           ''
+  - key:             performance-unnecessary-value-param.IncludeStyle
+    value:           llvm
+  - key:             portability-simd-intrinsics.Std
+    value:           ''
+  - key:             portability-simd-intrinsics.Suggest
+    value:           '0'
+  - key:             readability-braces-around-statements.ShortStatementLines
+    value:           '0'
+  - key:             readability-else-after-return.WarnOnUnfixable
+    value:           '1'
+  - key:             readability-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             readability-function-size.VariableThreshold
+    value:           '4294967295'
+  - key:             readability-identifier-naming.IgnoreFailedSplit
+    value:           '0'
+  - key:             readability-implicit-bool-conversion.AllowIntegerConditions
+    value:           '0'
+  - key:             readability-implicit-bool-conversion.AllowPointerConditions
+    value:           '0'
+  - key:             readability-inconsistent-declaration-parameter-name.IgnoreMacros
+    value:           '1'
+  - key:             readability-inconsistent-declaration-parameter-name.Strict
+    value:           '0'
+  - key:             readability-magic-numbers.IgnoredFloatingPointValues
+    value:           '1.0;100.0;'
+  - key:             readability-magic-numbers.IgnoredIntegerValues
+    value:           '1;2;3;4;'
+  - key:             readability-redundant-member-init.IgnoreBaseInCopyConstructors
+    value:           '0'
+  - key:             readability-redundant-smartptr-get.IgnoreMacros
+    value:           '1'
+  - key:             readability-redundant-string-init.StringNames
+    value:           '::std::basic_string'
+  - key:             readability-simplify-boolean-expr.ChainedConditionalAssignment
+    value:           '0'
+  - key:             readability-simplify-boolean-expr.ChainedConditionalReturn
+    value:           '0'
+  - key:             readability-simplify-subscript-expr.Types
+    value:           '::std::basic_string;::std::basic_string_view;::std::vector;::std::array'
+  - key:             readability-static-accessed-through-instance.NameSpecifierNestingThreshold
+    value:           '3'
+  - key:             readability-uppercase-literal-suffix.IgnoreMacros
+    value:           '1'
+  - key:             readability-uppercase-literal-suffix.NewSuffixes
+    value:           ''
+  - key:             zircon-temporary-objects.Names
+    value:           ''
+...
+
diff --git a/external/subpack/net/nginx-util/src/CMakeLists.txt b/external/subpack/net/nginx-util/src/CMakeLists.txt
new file mode 100644
index 0000000..2adff1c
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/CMakeLists.txt
@@ -0,0 +1,62 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(nginx-util CXX)
+SET(CMAKE_CXX_STANDARD 17)
+
+INCLUDE(CheckFunctionExists)
+
+ADD_DEFINITIONS(-Os -Wall -Werror -Wextra -g3)
+ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations -Wshadow)
+
+SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+
+FIND_PATH(uci_include_dir uci.h)
+FIND_LIBRARY(uci NAMES uci)
+INCLUDE_DIRECTORIES(${uci_include_dir})
+
+FIND_PATH(ubox_include_dir libubox/blobmsg.h)
+FIND_LIBRARY(ubox NAMES ubox)
+INCLUDE_DIRECTORIES(${ubox_include_dir})
+
+IF(UBUS)
+
+ADD_COMPILE_DEFINITIONS(VERSION=${VERSION})
+
+FIND_PATH(ubus_include_dir libubus.h)
+FIND_LIBRARY(ubus NAMES ubus)
+INCLUDE_DIRECTORIES(${ubus_include_dir})
+
+ADD_EXECUTABLE(nginx-ssl-util nginx-util.cpp)
+TARGET_LINK_LIBRARIES(nginx-ssl-util ${uci} ${ubox} ${ubus} pthread ssl crypto pcre)
+INSTALL(TARGETS nginx-ssl-util RUNTIME DESTINATION bin)
+
+ADD_EXECUTABLE(nginx-ssl-util-nopcre nginx-util.cpp)
+TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-nopcre PUBLIC -DNO_PCRE)
+TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre ${uci} ${ubox} ${ubus} pthread ssl crypto)
+INSTALL(TARGETS nginx-ssl-util-nopcre RUNTIME DESTINATION bin)
+
+ELSE()
+
+ADD_COMPILE_DEFINITIONS(VERSION=0)
+
+CONFIGURE_FILE(test-px5g.sh test-px5g.sh COPYONLY)
+CONFIGURE_FILE(test-nginx-util.sh test-nginx-util.sh COPYONLY)
+CONFIGURE_FILE(test-nginx-util-root.sh test-nginx-util-root.sh COPYONLY)
+CONFIGURE_FILE(../files/nginx.config config-nginx-ssl COPYONLY)
+CONFIGURE_FILE(../files/uci.conf.template uci.conf.template COPYONLY)
+
+ADD_EXECUTABLE(px5g px5g.cpp)
+TARGET_LINK_LIBRARIES(px5g ssl crypto)
+INSTALL(TARGETS px5g RUNTIME DESTINATION bin)
+
+ADD_EXECUTABLE(nginx-ssl-util-noubus nginx-util.cpp)
+TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-noubus PUBLIC -DNO_UBUS)
+TARGET_LINK_LIBRARIES(nginx-ssl-util-noubus ${uci} ${ubox} pthread ssl crypto pcre)
+INSTALL(TARGETS nginx-ssl-util-noubus RUNTIME DESTINATION bin)
+
+ADD_EXECUTABLE(nginx-ssl-util-nopcre-noubus nginx-util.cpp)
+TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-nopcre-noubus PUBLIC -DNO_PCRE -DNO_UBUS)
+TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre-noubus ${uci} ${ubox} pthread ssl crypto)
+INSTALL(TARGETS nginx-ssl-util-nopcre-noubus RUNTIME DESTINATION bin)
+
+ENDIF()
diff --git a/external/subpack/net/nginx-util/src/LICENSE b/external/subpack/net/nginx-util/src/LICENSE
new file mode 100644
index 0000000..bddd690
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/LICENSE
@@ -0,0 +1,23 @@
+/* Copyright 2020 Peter Stadler
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp b/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp
new file mode 100644
index 0000000..5a64b00
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp
@@ -0,0 +1,1124 @@
+#ifndef __NGINX_SSL_UTIL_HPP
+#define __NGINX_SSL_UTIL_HPP
+
+#ifdef NO_PCRE
+#include <regex>
+namespace rgx = std;
+#else
+#include "regex-pcre.hpp"
+#endif
+
+#include "nginx-util.hpp"
+#include "px5g-openssl.hpp"
+
+#ifndef NO_UBUS
+static constexpr auto UBUS_TIMEOUT = 1000;
+#endif
+
+// once a year:
+static constexpr auto CRON_INTERVAL = std::string_view{"3 3 12 12 *"};
+
+static constexpr auto LAN_SSL_LISTEN = std::string_view{"/var/lib/nginx/lan_ssl.listen"};
+
+static constexpr auto LAN_SSL_LISTEN_DEFAULT =  // TODO(pst) deprecate
+    std::string_view{"/var/lib/nginx/lan_ssl.listen.default"};
+
+static constexpr auto ADD_SSL_FCT = std::string_view{"add_ssl"};
+
+static constexpr auto SSL_SESSION_CACHE_ARG = [](const std::string_view & /*name*/) -> std::string {
+    return "shared:SSL:32k";
+};
+
+static constexpr auto SSL_SESSION_TIMEOUT_ARG = std::string_view{"64m"};
+
+using _Line = std::array<std::string (*)(const std::string&, const std::string&), 2>;
+
+class Line {
+  private:
+    _Line _line;
+
+  public:
+    explicit Line(const _Line& line) noexcept : _line{line} {}
+
+    template <const _Line&... xn>
+    static auto build() noexcept -> Line
+    {
+        return Line{_Line{[](const std::string& p, const std::string& b) -> std::string {
+                              return (... + xn[0](p, b));
+                          },
+                          [](const std::string& p, const std::string& b) -> std::string {
+                              return (... + xn[1](p, b));
+                          }}};
+    }
+
+    [[nodiscard]] auto STR(const std::string& param, const std::string& begin) const -> std::string
+    {
+        return _line[0](param, begin);
+    }
+
+    [[nodiscard]] auto RGX() const -> rgx::regex
+    {
+        return rgx::regex{_line[1]("", "")};
+    }
+};
+
+auto get_if_missed(const std::string& conf,
+                   const Line& LINE,
+                   const std::string& val,
+                   const std::string& indent = "\n    ",
+                   bool compare = true) -> std::string;
+
+auto replace_if(const std::string& conf,
+                const rgx::regex& rgx,
+                const std::string& val,
+                const std::string& insert) -> std::string;
+
+auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port)
+    -> std::string;
+
+auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool;
+
+auto contains(const std::string& sentence, const std::string& word) -> bool;
+
+auto get_uci_section_for_name(const std::string& name) -> uci::section;
+
+void add_ssl_if_needed(const std::string& name);
+
+void add_ssl_if_needed(const std::string& name,
+                       std::string_view manage,
+                       std::string_view crt,
+                       std::string_view key);
+
+void install_cron_job(const Line& CRON_LINE, const std::string& name = "");
+
+void remove_cron_job(const Line& CRON_LINE, const std::string& name = "");
+
+auto del_ssl_legacy(const std::string& name) -> bool;
+
+void del_ssl(const std::string& name);
+
+void del_ssl(const std::string& name, std::string_view manage);
+
+auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool;
+
+inline void check_ssl(const uci::package& pkg)
+{
+    if (!check_ssl(pkg, is_enabled(pkg))) {
+#ifndef NO_UBUS
+        if (ubus::call("service", "list", UBUS_TIMEOUT).filter("nginx")) {
+            call("/etc/init.d/nginx", "reload");
+            std::cerr << "Reload Nginx.\n";
+        }
+#endif
+    }
+}
+
+constexpr auto _begin = _Line{
+    [](const std::string& /*param*/, const std::string& begin) -> std::string { return begin; },
+
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        return R"([{;](?:\s*#[^\n]*(?=\n))*(\s*))";
+    }};
+
+constexpr auto _space = _Line{[](const std::string& /*param*/, const std::string &
+                                 /*begin*/) -> std::string { return std::string{" "}; },
+
+                              [](const std::string& /*param*/, const std::string &
+                                 /*begin*/) -> std::string { return R"(\s+)"; }};
+
+constexpr auto _newline = _Line{
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        return std::string{"\n"};
+    },
+
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        return std::string{"(\n)"};
+    }  // capture it as _end captures it, too.
+};
+
+constexpr auto _end =
+    _Line{[](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+              return std::string{";"};
+          },
+
+          [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+              return std::string{R"(\s*(;(?:[\t ]*#[^\n]*)?))"};
+          }};
+
+template <char clim = '\0'>
+static constexpr auto _capture = _Line{
+    [](const std::string& param, const std::string & /*begin*/) -> std::string {
+        return '\'' + param + '\'';
+    },
+
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        const auto lim = clim == '\0' ? std::string{"\\s"} : std::string{clim};
+        return std::string{R"(((?:(?:"[^"]*")|(?:[^'")"} + lim + "][^" + lim + "]*)|(?:'[^']*'))+)";
+    }};
+
+template <const std::string_view& strptr, char clim = '\0'>
+static constexpr auto _escape = _Line{
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        return clim == '\0' ? std::string{strptr.data()} : clim + std::string{strptr.data()} + clim;
+    },
+
+    [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string {
+        std::string ret{};
+        for (char c : strptr) {
+            switch (c) {
+                case '^':
+                    ret += '\\';
+                    ret += c;
+                    break;
+                case '_':
+                case '-':
+                    ret += c;
+                    break;
+                default:
+                    if ((isalpha(c) != 0) || (isdigit(c) != 0)) {
+                        ret += c;
+                    }
+                    else {
+                        ret += std::string{"["} + c + "]";
+                    }
+            }
+        }
+        return "(?:" + ret + "|'" + ret + "'" + "|\"" + ret + "\"" + ")";
+    }};
+
+constexpr std::string_view _check_ssl = "check_ssl";
+
+constexpr std::string_view _server_name = "server_name";
+
+constexpr std::string_view _listen = "listen";
+
+constexpr std::string_view _include = "include";
+
+constexpr std::string_view _ssl_certificate = "ssl_certificate";
+
+constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key";
+
+constexpr std::string_view _ssl_session_cache = "ssl_session_cache";
+
+constexpr std::string_view _ssl_session_timeout = "ssl_session_timeout";
+
+// For a compile time regex lib, this must be fixed, use one of these options:
+// * Hand craft or macro concat them (loosing more or less flexibility).
+// * Use Macro concatenation of __VA_ARGS__ with the help of:
+//   https://p99.gforge.inria.fr/p99-html/group__preprocessor__for.html
+// * Use constexpr---not available for strings or char * for now---look at lib.
+
+static const auto CRON_CHECK =
+    Line::build<_space, _escape<NGINX_UTIL>, _space, _escape<_check_ssl, '\''>, _newline>();
+
+static const auto CRON_CMD = Line::build<_space,
+                                         _escape<NGINX_UTIL>,
+                                         _space,
+                                         _escape<ADD_SSL_FCT, '\''>,
+                                         _space,
+                                         _capture<>,
+                                         _newline>();
+
+static const auto NGX_SERVER_NAME =
+    Line::build<_begin, _escape<_server_name>, _space, _capture<';'>, _end>();
+
+static const auto NGX_INCLUDE_LAN_LISTEN =
+    Line::build<_begin, _escape<_include>, _space, _escape<LAN_LISTEN, '\''>, _end>();
+
+static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT =
+    Line::build<_begin, _escape<_include>, _space, _escape<LAN_LISTEN_DEFAULT, '\''>, _end>();
+
+static const auto NGX_INCLUDE_LAN_SSL_LISTEN =
+    Line::build<_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN, '\''>, _end>();
+
+static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT =
+    Line::build<_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN_DEFAULT, '\''>, _end>();
+
+static const auto NGX_SSL_CRT =
+    Line::build<_begin, _escape<_ssl_certificate>, _space, _capture<';'>, _end>();
+
+static const auto NGX_SSL_KEY =
+    Line::build<_begin, _escape<_ssl_certificate_key>, _space, _capture<';'>, _end>();
+
+static const auto NGX_SSL_SESSION_CACHE =
+    Line::build<_begin, _escape<_ssl_session_cache>, _space, _capture<';'>, _end>();
+
+static const auto NGX_SSL_SESSION_TIMEOUT =
+    Line::build<_begin, _escape<_ssl_session_timeout>, _space, _capture<';'>, _end>();
+
+static const auto NGX_LISTEN = Line::build<_begin, _escape<_listen>, _space, _capture<';'>, _end>();
+
+static const auto NGX_PORT_80 = std::array<const char*, 2>{
+    R"(^\s*([^:]*:|\[[^\]]*\]:)?80(\s|$|;))",
+    "$01443 ssl$2",
+};
+
+static const auto NGX_PORT_443 = std::array<const char*, 2>{
+    R"(^\s*([^:]*:|\[[^\]]*\]:)?443(\s.*)?\sssl(\s|$|;))",
+    "$0180$2$3",
+};
+
+// ------------------------- implementation: ----------------------------------
+
+auto get_if_missed(const std::string& conf,
+                   const Line& LINE,
+                   const std::string& val,
+                   const std::string& indent,
+                   bool compare) -> std::string
+{
+    if (!compare || val.empty()) {
+        return rgx::regex_search(conf, LINE.RGX()) ? "" : LINE.STR(val, indent);
+    }
+
+    rgx::smatch match;  // assuming last capture has the value!
+
+    for (auto pos = conf.begin(); rgx::regex_search(pos, conf.end(), match, LINE.RGX());
+         pos += match.position(0) + match.length(0))
+    {
+        const std::string value = match.str(match.size() - 2);
+
+        if (value == val || value == "'" + val + "'" || value == '"' + val + '"') {
+            return "";
+        }
+    }
+
+    return LINE.STR(val, indent);
+}
+
+auto replace_if(const std::string& conf,
+                const rgx::regex& rgx,
+                const std::string& val,
+                const std::string& insert) -> std::string
+{
+    std::string ret{};
+    auto pos = conf.begin();
+
+    auto skip = 0;
+    for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, rgx);
+         pos += match.position(match.size() - 1))
+    {
+        auto i = match.size() - 2;
+        const std::string value = match.str(i);
+
+        bool compare = !val.empty();
+        if (compare && value != val && value != "'" + val + "'" && value != '"' + val + '"') {
+            ret.append(pos + skip, pos + match.position(i) + match.length(i));
+            skip = 0;
+        }
+        else {
+            ret.append(pos + skip, pos + match.position(match.size() > 2 ? 1 : 0));
+            ret += insert;
+            skip = 1;
+        }
+    }
+
+    ret.append(pos + skip, conf.end());
+    return ret;
+}
+
+auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port)
+    -> std::string
+{
+    std::string ret{};
+    auto pos = conf.begin();
+
+    for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, NGX_LISTEN.RGX());
+         pos += match.position(match.size() - 1))
+    {
+        auto i = match.size() - 2;
+        ret.append(pos, pos + match.position(i));
+        ret += rgx::regex_replace(match.str(i), rgx::regex{ngx_port[0]}, ngx_port[1]);
+    }
+
+    ret.append(pos, conf.end());
+    return ret;
+}
+
+inline void add_ssl_directives_to(const std::string& name)
+{
+    const std::string prefix = std::string{CONF_DIR} + name;
+
+    const std::string const_conf = read_file(prefix + ".conf");
+
+    rgx::smatch match;  // captures str(1)=indentation spaces, str(2)=server name
+    for (auto pos = const_conf.begin();
+         rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX());
+         pos += match.position(0) + match.length(0))
+    {
+        if (!contains(match.str(2), name)) {
+            continue;
+        }  // else:
+
+        const std::string indent = match.str(1);
+
+        auto adds = std::string{};
+
+        adds += get_if_missed(const_conf, NGX_SSL_CRT, prefix + ".crt", indent);
+
+        adds += get_if_missed(const_conf, NGX_SSL_KEY, prefix + ".key", indent);
+
+        adds += get_if_missed(const_conf, NGX_SSL_SESSION_CACHE, SSL_SESSION_CACHE_ARG(name),
+                              indent, false);
+
+        adds += get_if_missed(const_conf, NGX_SSL_SESSION_TIMEOUT,
+                              std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false);
+
+        pos += match.position(0) + match.length(0);
+        std::string conf =
+            std::string(const_conf.begin(), pos) + adds + std::string(pos, const_conf.end());
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX(), "",
+                          NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.STR("", indent));
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX(), "",
+                          NGX_INCLUDE_LAN_SSL_LISTEN.STR("", indent));
+
+        conf = replace_listen(conf, NGX_PORT_80);
+
+        if (conf != const_conf) {
+            write_file(prefix + ".conf", conf);
+            std::cerr << "Added SSL directives to " << prefix << ".conf\n";
+        }
+
+        return;
+    }
+
+    auto errmsg = std::string{"add_ssl_directives_to error: "};
+    errmsg += "cannot add SSL directives to " + name + ".conf, missing: ";
+    errmsg += NGX_SERVER_NAME.STR(name, "\n    ") + "\n";
+    throw std::runtime_error(errmsg);
+}
+
+template <typename T>
+inline auto num2hex(T bytes) -> std::array<char, 2 * sizeof(bytes) + 1>
+{
+    constexpr auto n = 2 * sizeof(bytes);
+    std::array<char, n + 1> str{};
+
+    for (size_t i = 0; i < n; ++i) {
+        static const std::array<char, 17> hex{"0123456789ABCDEF"};
+        static constexpr auto get = 0x0fU;
+        str.at(i) = hex.at(bytes & get);
+
+        static constexpr auto move = 4U;
+        bytes >>= move;
+    }
+
+    str[n] = '\0';
+    return str;
+}
+
+template <typename T>
+inline auto get_nonce(const T salt = 0) -> T
+{
+    T nonce = 0;
+
+    std::ifstream urandom{"/dev/urandom"};
+
+    static constexpr auto move = 6U;
+
+    constexpr size_t steps = (sizeof(nonce) * 8 - 1) / move + 1;
+
+    for (size_t i = 0; i < steps; ++i) {
+        if (!urandom.good()) {
+            throw std::runtime_error("get_nonce error");
+        }
+        nonce = (nonce << move) + static_cast<unsigned>(urandom.get());
+    }
+
+    nonce ^= salt;
+
+    return nonce;
+}
+
+inline void create_ssl_certificate(const std::string& crtpath,
+                                   const std::string& keypath,
+                                   const int days = 792)
+{
+    size_t nonce = 0;
+
+    try {
+        nonce = get_nonce(nonce);
+    }
+
+    catch (...) {  // the address of a variable should be random enough:
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) sic:
+        nonce += reinterpret_cast<size_t>(&crtpath);
+    }
+
+    auto noncestr = num2hex(nonce);
+
+    const auto tmpcrtpath = crtpath + ".new-" + noncestr.data();
+    const auto tmpkeypath = keypath + ".new-" + noncestr.data();
+
+    try {
+        auto pkey = gen_eckey(NID_secp384r1);
+
+        write_key(pkey, tmpkeypath);
+
+        std::string subject{"/C=ZZ/ST=Somewhere/L=None/CN=OpenWrt/O=OpenWrt"};
+        subject += noncestr.data();
+
+        selfsigned(pkey, days, subject, tmpcrtpath);
+
+        static constexpr auto to_seconds = 24 * 60 * 60;
+        static constexpr auto leeway = 42;
+        if (!checkend(tmpcrtpath, days * to_seconds - leeway)) {
+            throw std::runtime_error("bug: created certificate is not valid!!");
+        }
+    }
+    catch (...) {
+        std::cerr << "create_ssl_certificate error: ";
+        std::cerr << "cannot create selfsigned certificate, ";
+        std::cerr << "removing temporary files ..." << std::endl;
+
+        if (remove(tmpcrtpath.c_str()) != 0) {
+            auto errmsg = "\t cannot remove " + tmpcrtpath;
+            perror(errmsg.c_str());
+        }
+
+        if (remove(tmpkeypath.c_str()) != 0) {
+            auto errmsg = "\t cannot remove " + tmpkeypath;
+            perror(errmsg.c_str());
+        }
+
+        throw;
+    }
+
+    if (rename(tmpcrtpath.c_str(), crtpath.c_str()) != 0 ||
+        rename(tmpkeypath.c_str(), keypath.c_str()) != 0)
+    {
+        auto errmsg = std::string{"create_ssl_certificate warning: "};
+        errmsg += "cannot move " + tmpcrtpath + " to " + crtpath;
+        errmsg += " or " + tmpkeypath + " to " + keypath + ", continuing ... ";
+        perror(errmsg.c_str());
+    }
+
+    std::cerr << "Created self-signed SSL certificate '" << crtpath;
+    std::cerr << "' with key '" << keypath << "'.\n";
+}
+
+auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool
+{
+    {  // paths are relative to dir:
+        auto dir = std::string_view{"/etc/nginx"};
+        auto crt_rel = crtpath[0] != '/';
+        auto key_rel = keypath[0] != '/';
+        if ((crt_rel || key_rel) && (chdir(dir.data()) != 0)) {
+            auto errmsg = std::string{"check_ssl_certificate error: entering "};
+            errmsg += dir;
+            perror(errmsg.c_str());
+            errmsg += " (need to change directory since the given ";
+            errmsg += crt_rel ? "ssl_certificate '" + crtpath : std::string{};
+            errmsg += crt_rel && key_rel ? "' and " : "";
+            errmsg += key_rel ? "ssl_certificate_key '" + keypath : std::string{};
+            errmsg += crt_rel && key_rel ? "' are" : "' is a";
+            errmsg += " relative path";
+            errmsg += crt_rel && key_rel ? "s)" : ")";
+            throw std::runtime_error(errmsg);
+        }
+    }
+
+    constexpr auto remaining_seconds = (365 + 32) * 24 * 60 * 60;
+    constexpr auto validity_days = 3 * (365 + 31);
+
+    bool is_valid = true;
+
+    if (access(keypath.c_str(), R_OK) != 0 || access(crtpath.c_str(), R_OK) != 0) {
+        is_valid = false;
+    }
+
+    else {
+        try {
+            if (!checkend(crtpath, remaining_seconds)) {
+                is_valid = false;
+            }
+        }
+        catch (...) {  // something went wrong, maybe it is in DER format:
+            try {
+                if (!checkend(crtpath, remaining_seconds, false)) {
+                    is_valid = false;
+                }
+            }
+            catch (...) {  // it has neither DER nor PEM format, rebuild.
+                is_valid = false;
+            }
+        }
+    }
+
+    if (!is_valid) {
+        create_ssl_certificate(crtpath, keypath, validity_days);
+    }
+
+    return is_valid;
+}
+
+auto contains(const std::string& sentence, const std::string& word) -> bool
+{
+    auto pos = sentence.find(word);
+    if (pos == std::string::npos) {
+        return false;
+    }
+    if (pos != 0 && (isgraph(sentence[pos - 1]) != 0)) {
+        return false;
+    }
+    if (isgraph(sentence[pos + word.size()]) != 0) {
+        return false;
+    }
+    // else:
+    return true;
+}
+
+auto get_uci_section_for_name(const std::string& name) -> uci::section
+{
+    auto pkg = uci::package{"nginx"};  // let it throw.
+
+    auto uci_enabled = is_enabled(pkg);
+
+    if (uci_enabled) {
+        for (auto sec : pkg) {
+            if (sec.name() == name) {
+                return sec;
+            }
+        }
+        // try interpreting 'name' as FQDN:
+        for (auto sec : pkg) {
+            for (auto opt : sec) {
+                if (opt.name() == "server_name") {
+                    for (auto itm : opt) {
+                        if (contains(itm.name(), name)) {
+                            return sec;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    auto errmsg = std::string{"lookup error: neither there is a file named '"};
+    errmsg += std::string{CONF_DIR} + name + ".conf' nor the UCI config has ";
+    if (uci_enabled) {
+        errmsg += "a nginx server with section name or 'server_name': " + name;
+    }
+    else {
+        errmsg += "been enabled by:\n\tuci set nginx.global.uci_enable=true";
+    }
+    throw std::runtime_error(errmsg);
+}
+
+inline auto add_ssl_to_config(const std::string& name,
+                              const std::string_view manage = "self-signed",
+                              const std::string_view crt = "",
+                              const std::string_view key = "")
+{
+    auto sec = get_uci_section_for_name(name);  // let it throw.
+    auto secname = sec.name();
+
+    struct {
+        std::string crt;
+        std::string key;
+    } ret;
+
+    std::cerr << "Adding SSL directives to UCI server: nginx." << secname << "\n";
+
+    std::cerr << "\t" << MANAGE_SSL << "='" << manage << "'\n";
+    sec.set(MANAGE_SSL.data(), manage.data());
+
+    if (!crt.empty() && !key.empty()) {
+        sec.set("ssl_certificate", crt.data());
+        std::cerr << "\tssl_certificate='" << crt << "'\n";
+        sec.set("ssl_certificate_key", key.data());
+        std::cerr << "\tssl_certificate_key='" << key << "'\n";
+    }
+
+    auto cache = false;
+    auto timeout = false;
+    for (auto opt : sec) {
+        if (opt.name() == "ssl_session_cache") {
+            cache = true;
+            continue;
+        }  // else:
+
+        if (opt.name() == "ssl_session_timeout") {
+            timeout = true;
+            continue;
+        }
+
+        // else:
+        for (auto itm : opt) {
+            if (opt.name() == "ssl_certificate_key") {
+                ret.key = itm.name();
+            }
+
+            else if (opt.name() == "ssl_certificate") {
+                ret.crt = itm.name();
+            }
+
+            else if (opt.name() == "listen") {
+                auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_80[0]}, NGX_PORT_80[1]);
+                if (val != itm.name()) {
+                    std::cerr << "\t" << opt.name() << "='" << val << "' (replacing)\n";
+                    itm.rename(val.c_str());
+                }
+            }
+        }
+    }
+
+    if (ret.crt.empty()) {
+        ret.crt = std::string{CONF_DIR} + name + ".crt";
+        std::cerr << "\tssl_certificate='" << ret.crt << "'\n";
+        sec.set("ssl_certificate", ret.crt.c_str());
+    }
+
+    if (ret.key.empty()) {
+        ret.key = std::string{CONF_DIR} + name + ".key";
+        std::cerr << "\tssl_certificate_key='" << ret.key << "'\n";
+        sec.set("ssl_certificate_key", ret.key.c_str());
+    }
+
+    if (!cache) {
+        std::cerr << "\tssl_session_cache='" << SSL_SESSION_CACHE_ARG(name) << "'\n";
+        sec.set("ssl_session_cache", SSL_SESSION_CACHE_ARG(name).data());
+    }
+
+    if (!timeout) {
+        std::cerr << "\tssl_session_timeout='" << SSL_SESSION_TIMEOUT_ARG << "'\n";
+        sec.set("ssl_session_timeout", SSL_SESSION_TIMEOUT_ARG.data());
+    }
+
+    sec.commit();
+
+    return ret;
+}
+
+void install_cron_job(const Line& CRON_LINE, const std::string& name)
+{
+    static const char* filename = "/etc/crontabs/root";
+
+    std::string conf{};
+    try {
+        conf = read_file(filename);
+    }
+    catch (const std::ifstream::failure&) { /* is ok if not found, create. */
+    }
+
+    const std::string add = get_if_missed(conf, CRON_LINE, name);
+
+    if (add.length() > 0) {
+#ifndef NO_UBUS
+        if (!ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) {
+            std::string errmsg{"install_cron_job error: "};
+            errmsg += "Cron unavailable to re-create the ssl certificate";
+            errmsg += (name.empty() ? std::string{"s\n"} : " for '" + name + "'\n");
+            throw std::runtime_error(errmsg);
+        }  // else active with or without instances:
+#endif
+
+        const auto* pre = (conf.length() == 0 || conf.back() == '\n' ? "" : "\n");
+        write_file(filename, pre + std::string{CRON_INTERVAL} + add, std::ios::app);
+
+#ifndef NO_UBUS
+        call("/etc/init.d/cron", "reload");
+#endif
+
+        std::cerr << "Rebuild the self-signed SSL certificate";
+        std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'");
+        std::cerr << " annually with cron." << std::endl;
+    }
+}
+
+void add_ssl_if_needed(const std::string& name)
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+    if (access(legacypath.c_str(), R_OK) == 0) {
+        add_ssl_directives_to(name);  // let it throw.
+
+        const auto crtpath = std::string{CONF_DIR} + name + ".crt";
+        const auto keypath = std::string{CONF_DIR} + name + ".key";
+        check_ssl_certificate(crtpath, keypath);  // let it throw.
+
+        try {
+            install_cron_job(CRON_CMD, name);
+        }
+        catch (...) {
+            std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild ";
+            std::cerr << "the self-signed SSL certificate for " << name << "\n";
+        }
+        return;
+    }  // else:
+
+    auto paths = add_ssl_to_config(name);  // let it throw.
+
+    check_ssl_certificate(paths.crt, paths.key);  // let it throw.
+
+    try {
+        install_cron_job(CRON_CHECK);
+    }
+    catch (...) {
+        std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild ";
+        std::cerr << "the self-signed SSL certificates.\n";
+    }
+}
+
+void add_ssl_if_needed(const std::string& name,
+                       const std::string_view manage,
+                       const std::string_view crt,
+                       const std::string_view key)
+{
+    if (crt[0] != '/') {
+        auto errmsg = std::string{"add_ssl_if_needed error: ssl_certificate "};
+        errmsg += "path cannot be relative '" + std::string{crt} + "'";
+        throw std::runtime_error(errmsg);
+    }
+
+    if (key[0] != '/') {
+        auto errmsg = std::string{"add_ssl_if_needed error: path to ssl_key "};
+        errmsg += "cannot be relative '" + std::string{key} + "'";
+        throw std::runtime_error(errmsg);
+    }
+
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        add_ssl_to_config(name, manage, crt, key);  // let it throw.
+        return;
+    }  // else:
+
+    // symlink crt+key to the paths that add_ssl_directives_to uses (if needed):
+
+    auto crtpath = std::string{CONF_DIR} + name + ".crt";
+    if (crtpath != crt && /* then */ symlink(crt.data(), crtpath.c_str()) != 0) {
+        auto errmsg = std::string{"add_ssl_if_needed error: cannot link "};
+        errmsg += "ssl_certificate " + crtpath + " -> " + crt.data() + " (";
+        errmsg += std::to_string(errno) + "): " + std::strerror(errno);
+        throw std::runtime_error(errmsg);
+    }
+
+    auto keypath = std::string{CONF_DIR} + name + ".key";
+    if (keypath != key && /* then */ symlink(key.data(), keypath.c_str()) != 0) {
+        auto errmsg = std::string{"add_ssl_if_needed error: cannot link "};
+        errmsg += "ssl_certificate_key " + keypath + " -> " + key.data() + " (";
+        errmsg += std::to_string(errno) + "): " + std::strerror(errno);
+        throw std::runtime_error(errmsg);
+    }
+
+    add_ssl_directives_to(name);  // let it throw.
+}
+
+void remove_cron_job(const Line& CRON_LINE, const std::string& name)
+{
+    static const char* filename = "/etc/crontabs/root";
+
+    const auto const_conf = read_file(filename);
+
+    bool changed = false;
+    auto conf = std::string{};
+
+    size_t prev = 0;
+    size_t curr = 0;
+    while ((curr = const_conf.find('\n', prev)) != std::string::npos) {
+        auto line = const_conf.substr(prev, curr - prev + 1);
+
+        if (line == replace_if(line, CRON_LINE.RGX(), name, "")) {
+            conf += line;
+        }
+        else {
+            changed = true;
+        }
+
+        prev = curr + 1;
+    }
+
+    if (changed) {
+        write_file(filename, conf);
+
+        std::cerr << "Do not rebuild the self-signed SSL certificate";
+        std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'");
+        std::cerr << " annually with cron anymore." << std::endl;
+
+#ifndef NO_UBUS
+        if (ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) {
+            call("/etc/init.d/cron", "reload");
+        }
+#endif
+    }
+}
+
+inline void del_ssl_directives_from(const std::string& name)
+{
+    const std::string prefix = std::string{CONF_DIR} + name;
+
+    const std::string const_conf = read_file(prefix + ".conf");
+
+    rgx::smatch match;  // captures str(1)=indentation spaces, str(2)=server name
+    for (auto pos = const_conf.begin();
+         rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX());
+         pos += match.position(0) + match.length(0))
+    {
+        if (!contains(match.str(2), name)) {
+            continue;
+        }  // else:
+
+        const std::string indent = match.str(1);
+
+        std::string conf = const_conf;
+
+        conf = replace_listen(conf, NGX_PORT_443);
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX(), "",
+                          NGX_INCLUDE_LAN_LISTEN_DEFAULT.STR("", indent));
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX(), "",
+                          NGX_INCLUDE_LAN_LISTEN.STR("", indent));
+
+        // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix:
+        conf = replace_if(conf, NGX_SSL_CRT.RGX(), prefix + ".crt", "");
+
+        // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix:
+        conf = replace_if(conf, NGX_SSL_KEY.RGX(), prefix + ".key", "");
+
+        conf = replace_if(conf, NGX_SSL_SESSION_CACHE.RGX(), "", "");
+
+        conf = replace_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX(), "", "");
+
+        if (conf != const_conf) {
+            write_file(prefix + ".conf", conf);
+            std::cerr << "Deleted SSL directives from " << prefix << ".conf\n";
+        }
+
+        return;
+    }
+
+    auto errmsg = std::string{"del_ssl_directives_from error: "};
+    errmsg += "cannot delete SSL directives from " + name + ".conf, missing: ";
+    errmsg += NGX_SERVER_NAME.STR(name, "\n    ") + "\n";
+    throw std::runtime_error(errmsg);
+}
+
+inline auto del_ssl_from_config(const std::string& name,
+                                const std::string_view manage = "self-signed")
+{
+    auto sec = get_uci_section_for_name(name);  // let it throw.
+    auto secname = sec.name();
+
+    struct {
+        std::string crt;
+        std::string key;
+    } ret;
+
+    std::cerr << "Deleting SSL directives from UCI server: nginx." << secname << "\n";
+
+    auto manage_match = false;
+    for (auto opt : sec) {
+        for (auto itm : opt) {
+            if (opt.name() == "ssl_certificate_key") {
+                ret.key = itm.name();
+            }
+
+            else if (opt.name() == "ssl_certificate") {
+                ret.crt = itm.name();
+            }
+
+            else if (opt.name() == "ssl_session_cache" || opt.name() == "ssl_session_timeout") {
+            }
+
+            else if (opt.name() == MANAGE_SSL && itm.name() == manage) {
+                manage_match = true;
+            }
+
+            else if (opt.name() == "listen") {
+                auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_443[0]}, NGX_PORT_443[1]);
+                if (val != itm.name()) {
+                    std::cerr << "\t" << opt.name() << " (set back to '" << val << "')\n";
+                    itm.rename(val.c_str());
+                }
+                continue; /* not deleting opt, look at other itm : opt */
+            }
+
+            else {
+                continue; /* not deleting opt, look at other itm : opt */
+            }
+
+            // Delete matching opt (not skipped by continue):
+            std::cerr << "\t" << opt.name() << " (was '" << itm.name() << "')\n";
+            opt.del();
+            break;
+        }
+    }
+    if (manage_match) {
+        sec.commit();
+        return ret;
+    }  // else:
+
+    auto errmsg = std::string{"del_ssl error: not changing config wihtout: "};
+    errmsg += "uci set nginx." + secname + "." + MANAGE_SSL.data() + "='" + manage.data();
+    errmsg += "'";
+    throw std::runtime_error(errmsg);
+}
+
+auto del_ssl_legacy(const std::string& name) -> bool
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        return false;
+    }
+
+    try {
+        remove_cron_job(CRON_CMD, name);
+    }
+    catch (...) {
+        std::cerr << "del_ssl warning: cannot remove cron job rebuilding ";
+        std::cerr << "the self-signed SSL certificate for " << name << "\n";
+    }
+
+    try {
+        del_ssl_directives_from(name);
+    }
+    catch (...) {
+        std::cerr << "del_ssl error: ";
+        std::cerr << "cannot delete SSL directives from " << name << ".conf\n";
+        throw;
+    }
+
+    return true;
+}
+
+void del_ssl(const std::string& name)
+{
+    auto crtpath = std::string{};
+    auto keypath = std::string{};
+
+    if (del_ssl_legacy(name)) {  // let it throw.
+        crtpath = std::string{CONF_DIR} + name + ".crt";
+        keypath = std::string{CONF_DIR} + name + ".key";
+    }
+
+    else {
+        auto paths = del_ssl_from_config(name);  // let it throw.
+        crtpath = paths.crt;
+        keypath = paths.key;
+    }
+
+    if (remove(crtpath.c_str()) != 0) {
+        auto errmsg = "del_ssl warning: cannot remove " + crtpath;
+        perror(errmsg.c_str());
+    }
+
+    if (remove(keypath.c_str()) != 0) {
+        auto errmsg = "del_ssl warning: cannot remove " + keypath;
+        perror(errmsg.c_str());
+    }
+}
+
+void del_ssl(const std::string& name, const std::string_view manage)
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        del_ssl_from_config(name, manage);  // let it throw.
+        return;
+    }  // else:
+
+    del_ssl_directives_from(name);  // let it throw.
+
+    for (const auto* ext : {".crt", ".key"}) {
+        struct stat sb {};
+
+        auto path = std::string{CONF_DIR} + name + ext;
+
+        // managed version of add_ssl_if_needed created symlinks (if needed):
+        // NOLINTNEXTLINE(hicpp-signed-bitwise) S_ISLNK macro:
+        if (lstat(path.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) {
+            if (remove(path.c_str()) != 0) {
+                auto errmsg = "del_ssl warning: cannot remove " + path;
+                perror(errmsg.c_str());
+            }
+        }
+    }
+}
+
+auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool
+{
+    auto are_valid = true;
+    auto is_enabled_and_at_least_one_has_manage_ssl = false;
+
+    if (is_enabled) {
+        for (auto sec : pkg) {
+            if (sec.anonymous() || sec.type() != "server") {
+                continue;
+            }  // else:
+
+            const auto legacypath = std::string{CONF_DIR} + sec.name() + ".conf";
+            if (access(legacypath.c_str(), R_OK) == 0) {
+                continue;
+            }  // else:
+
+            auto keypath = std::string{};
+            auto crtpath = std::string{};
+            auto self_signed = false;
+
+            for (auto opt : sec) {
+                for (auto itm : opt) {
+                    if (opt.name() == "ssl_certificate_key") {
+                        keypath = itm.name();
+                    }
+
+                    else if (opt.name() == "ssl_certificate") {
+                        crtpath = itm.name();
+                    }
+
+                    else if (opt.name() == MANAGE_SSL) {
+                        if (itm.name() == "self-signed") {
+                            self_signed = true;
+                        }
+
+                        // else if (itm.name()=="???") { /* manage other */ }
+
+                        else {
+                            continue;
+                        }  // no supported manage_ssl string.
+
+                        is_enabled_and_at_least_one_has_manage_ssl = true;
+                    }
+                }
+            }
+
+            if (self_signed && !crtpath.empty() && !keypath.empty()) {
+                try {
+                    if (!check_ssl_certificate(crtpath, keypath)) {
+                        are_valid = false;
+                    }
+                }
+                catch (...) {
+                    std::cerr << "check_ssl warning: cannot build certificate '";
+                    std::cerr << crtpath << "' or key '" << keypath << "'.\n";
+                }
+            }
+        }
+    }
+
+    auto suffix = std::string_view{" the cron job checking the managed SSL certificates.\n"};
+
+    if (is_enabled_and_at_least_one_has_manage_ssl) {
+        try {
+            install_cron_job(CRON_CHECK);
+        }
+        catch (...) {
+            std::cerr << "check_ssl warning: cannot install" << suffix;
+        }
+    }
+
+    else if (access("/etc/crontabs/root", R_OK) == 0) {
+        try {
+            remove_cron_job(CRON_CHECK);
+        }
+        catch (...) {
+            std::cerr << "check_ssl warning: cannot remove" << suffix;
+        }
+    }  // else: do nothing
+
+    return are_valid;
+}
+
+#endif
diff --git a/external/subpack/net/nginx-util/src/nginx-util.cpp b/external/subpack/net/nginx-util/src/nginx-util.cpp
new file mode 100644
index 0000000..3b4ad8c
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/nginx-util.cpp
@@ -0,0 +1,378 @@
+#include <iostream>
+#include <numeric>
+
+#include "nginx-ssl-util.hpp"
+#include "nginx-util.hpp"
+
+static auto constexpr file_comment_auto_created =
+    std::string_view{"# This file is re-created when Nginx starts.\n"};
+
+// TODO(pst) replace it with blobmsg_get_string if upstream takes const:
+#ifndef NO_UBUS
+static inline auto _pst_get_string(const blob_attr* attr) -> char*
+{
+    return static_cast<char*>(blobmsg_data(attr));
+}
+#endif
+
+void create_lan_listen()  // create empty files for compatibility:
+{
+    // TODO(pst): replace by dummies after transitioning nginx config to UCI:
+    std::vector<std::string> ips;
+
+#ifndef NO_UBUS
+    try {
+        auto loopback_status = ubus::call("network.interface.loopback", "status");
+
+        for (const auto* ip : loopback_status.filter("ipv4-address", "", "address")) {
+            ips.emplace_back(_pst_get_string(ip));
+        }
+
+        for (const auto* ip : loopback_status.filter("ipv6-address", "", "address")) {
+            ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
+        }
+    }
+    catch (const std::runtime_error&) { /* do nothing about it */
+    }
+
+    try {
+        auto lan_status = ubus::call("network.interface.lan", "status");
+
+        for (const auto* ip : lan_status.filter("ipv4-address", "", "address")) {
+            ips.emplace_back(_pst_get_string(ip));
+        }
+
+        for (const auto* ip : lan_status.filter("ipv6-address", "", "address")) {
+            ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
+        }
+
+        for (const auto* ip :
+             lan_status.filter("ipv6-prefix-assignment", "", "local-address", "address")) {
+            ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
+        }
+    }
+    catch (const std::runtime_error&) { /* do nothing about it */
+    }
+#else
+    ips.emplace_back("127.0.0.1");
+#endif
+
+    std::string listen = std::string{file_comment_auto_created};
+    std::string listen_default = std::string{file_comment_auto_created};
+    for (const auto& ip : ips) {
+        listen += "\tlisten " + ip + ":80;\n";
+        listen_default += "\tlisten " + ip + ":80 default_server;\n";
+    }
+    write_file(LAN_LISTEN, listen);
+    write_file(LAN_LISTEN_DEFAULT, listen_default);
+
+    std::string ssl_listen = std::string{file_comment_auto_created};
+    std::string ssl_listen_default = std::string{file_comment_auto_created};
+    for (const auto& ip : ips) {
+        ssl_listen += "\tlisten " + ip + ":443 ssl;\n";
+        ssl_listen_default += "\tlisten " + ip + ":443 ssl default_server;\n";
+    }
+    write_file(LAN_SSL_LISTEN, ssl_listen);
+    write_file(LAN_SSL_LISTEN_DEFAULT, ssl_listen_default);
+}
+
+inline auto change_if_starts_with(const std::string_view& subject,
+                                  const std::string_view& prefix,
+                                  const std::string_view& substitute,
+                                  const std::string_view& seperator = " \t\n;") -> std::string
+{
+    auto view = subject;
+    view = view.substr(view.find_first_not_of(seperator));
+    if (view.rfind(prefix, 0) == 0) {
+        if (view.size() == prefix.size()) {
+            return std::string{substitute};
+        }
+        view = view.substr(prefix.size());
+        if (seperator.find(view[0]) != std::string::npos) {
+            auto ret = std::string{substitute};
+            ret += view;
+            return ret;
+        }
+    }
+    return std::string{subject};
+}
+
+inline auto create_server_conf(const uci::section& sec, const std::string& indent = "")
+    -> std::string
+{
+    auto secname = sec.name();
+
+    auto legacypath = std::string{CONF_DIR} + secname + ".conf";
+    if (access(legacypath.c_str(), R_OK) == 0) {
+        auto message = std::string{"skipped UCI server 'nginx."} + secname;
+        message += "' as it could conflict with: " + legacypath + "\n";
+
+        // TODO(pst) std::cerr<<"create_server_conf notice: "<<message;
+
+        return indent + "# " + message;
+    }  // else:
+
+    auto conf = indent + "server { #see uci show 'nginx." + secname + "'\n";
+
+    for (auto opt : sec) {
+        for (auto itm : opt) {
+            if (opt.name().rfind("uci_", 0) == 0) {
+                continue;
+            }
+            // else: standard opt.name()
+
+            auto val = itm.name();
+
+            if (opt.name() == "error_log") {
+                val = change_if_starts_with(val, "logd", "/proc/self/fd/1");
+            }
+
+            else if (opt.name() == "access_log") {
+                val = change_if_starts_with(val, "logd", "stderr");
+            }
+
+            conf += indent + "\t" + opt.name() + " " + itm.name() + ";\n";
+        }
+    }
+
+    conf += indent + "}\n";
+
+    return conf;
+}
+
+void init_uci(const uci::package& pkg)
+{
+    auto conf = std::string{file_comment_auto_created};
+
+    static const auto uci_http_config = std::string_view{"#UCI_HTTP_CONFIG\n"};
+
+    const auto tmpl = read_file(std::string{UCI_CONF} + ".template");
+    auto pos = tmpl.find(uci_http_config);
+
+    if (pos == std::string::npos) {
+        conf += tmpl;
+    }
+
+    else {
+        const auto index = tmpl.find_last_not_of(" \t", pos - 1);
+
+        const auto before = tmpl.begin() + index + 1;
+        const auto middle = tmpl.begin() + pos;
+        const auto after = middle + uci_http_config.length();
+
+        conf.append(tmpl.begin(), before);
+
+        const auto indent = std::string{before, middle};
+        for (auto sec : pkg) {
+            if (sec.type() == std::string_view{"server"}) {
+                conf += create_server_conf(sec, indent) + "\n";
+            }
+        }
+
+        conf.append(after, tmpl.end());
+    }
+
+    write_file(VAR_UCI_CONF, conf);
+}
+
+auto is_enabled(const uci::package& pkg) -> bool
+{
+    for (auto sec : pkg) {
+        if (sec.type() != std::string_view{"main"}) {
+            continue;
+        }
+        if (sec.name() != std::string_view{"global"}) {
+            continue;
+        }
+        for (auto opt : sec) {
+            if (opt.name() != "uci_enable") {
+                continue;
+            }
+            for (auto itm : opt) {
+                if (itm) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+/*
+ * ___________main_thread________________|______________thread_1________________
+ *  create_lan_listen() or do nothing    | config = uci::package("nginx")
+ *  if config_enabled (set in thread_1): | config_enabled = is_enabled(config)
+ *  then init_uci(config)                | check_ssl(config, config_enabled)
+ */
+void init_lan()
+{
+    std::exception_ptr ex;
+    std::unique_ptr<uci::package> config;
+    bool config_enabled = false;
+    std::mutex configuring;
+
+    configuring.lock();
+    auto thrd = std::thread([&config, &config_enabled, &configuring, &ex] {
+        try {
+            config = std::make_unique<uci::package>("nginx");
+            config_enabled = is_enabled(*config);
+            configuring.unlock();
+            check_ssl(*config, config_enabled);
+        }
+        catch (...) {
+            std::cerr << "init_lan error: checking UCI file /etc/config/nginx\n";
+            ex = std::current_exception();
+        }
+    });
+
+    try {
+        create_lan_listen();
+    }
+    catch (...) {
+        std::cerr << "init_lan error: cannot create listen files of local IPs.\n";
+        ex = std::current_exception();
+    }
+
+    configuring.lock();
+    if (config_enabled) {
+        try {
+            init_uci(*config);
+        }
+        catch (...) {
+            std::cerr << "init_lan error: cannot create " << VAR_UCI_CONF << " from ";
+            std::cerr << UCI_CONF << ".template using UCI file /etc/config/nginx\n";
+            ex = std::current_exception();
+        }
+    }
+
+    thrd.join();
+    if (ex) {
+        std::rethrow_exception(ex);
+    }
+}
+
+void get_env()
+{
+    std::cout << "UCI_CONF="
+              << "'" << UCI_CONF << "'" << std::endl;
+    std::cout << "NGINX_CONF="
+              << "'" << NGINX_CONF << "'" << std::endl;
+    std::cout << "CONF_DIR="
+              << "'" << CONF_DIR << "'" << std::endl;
+    std::cout << "LAN_NAME="
+              << "'" << LAN_NAME << "'" << std::endl;
+    std::cout << "LAN_LISTEN="
+              << "'" << LAN_LISTEN << "'" << std::endl;
+    std::cout << "LAN_SSL_LISTEN="
+              << "'" << LAN_SSL_LISTEN << "'" << std::endl;
+    std::cout << "SSL_SESSION_CACHE_ARG="
+              << "'" << SSL_SESSION_CACHE_ARG(LAN_NAME) << "'" << std::endl;
+    std::cout << "SSL_SESSION_TIMEOUT_ARG="
+              << "'" << SSL_SESSION_TIMEOUT_ARG << "'\n";
+    std::cout << "ADD_SSL_FCT="
+              << "'" << ADD_SSL_FCT << "'" << std::endl;
+    std::cout << "MANAGE_SSL="
+              << "'" << MANAGE_SSL << "'" << std::endl;
+}
+
+auto main(int argc, char* argv[]) -> int
+{
+    // TODO(pst): use std::span when available:
+    auto args = std::basic_string_view<char*>{argv, static_cast<size_t>(argc)};
+
+    auto cmds = std::array{
+        std::array<std::string_view, 2>{"init_lan", ""},
+        std::array<std::string_view, 2>{"get_env", ""},
+        std::array<std::string_view, 2>{
+            ADD_SSL_FCT, "server_name [manager /path/to/ssl_certificate /path/to/ssl_key]"},
+        std::array<std::string_view, 2>{"del_ssl", "server_name [manager]"},
+        std::array<std::string_view, 2>{"check_ssl", ""},
+    };
+
+    try {
+        if (argc == 2 && args[1] == cmds[0][0]) {
+            init_lan();
+        }
+
+        else if (argc == 2 && args[1] == cmds[1][0]) {
+            get_env();
+        }
+
+        else if (argc == 3 && args[1] == cmds[2][0]) {
+            add_ssl_if_needed(std::string{args[2]});
+        }
+
+        // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 6
+        else if (argc == 6 && args[1] == cmds[2][0]) {
+            // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 5
+            add_ssl_if_needed(std::string{args[2]}, args[3], args[4], args[5]);
+        }
+
+        else if (argc == 3 && args[1] == cmds[3][0]) {
+            del_ssl(std::string{args[2]});
+        }
+
+        else if (argc == 4 && args[1] == cmds[3][0]) {
+            del_ssl(std::string{args[2]}, args[3]);
+        }
+
+        else if (argc == 2 && args[1] == cmds[3][0])  // TODO(pst) deprecate
+        {
+            try {
+                auto name = std::string{LAN_NAME};
+                if (del_ssl_legacy(name)) {
+                    auto crtpath = std::string{CONF_DIR} + name + ".crt";
+                    remove(crtpath.c_str());
+                    auto keypath = std::string{CONF_DIR} + name + ".key";
+                    remove(keypath.c_str());
+                }
+            }
+            catch (...) { /* do nothing. */
+            }
+        }
+
+        else if (argc == 2 && args[1] == cmds[4][0]) {
+            check_ssl(uci::package{"nginx"});
+        }
+
+        else {
+            std::cerr << "Tool for creating Nginx configuration files (";
+#ifdef VERSION
+            std::cerr << "version " << VERSION << " ";
+#endif
+            std::cerr << "with libuci, ";
+#ifndef NO_UBUS
+            std::cerr << "libubus, ";
+#endif
+            std::cerr << "libopenssl, ";
+#ifndef NO_PCRE
+            std::cerr << "PCRE, ";
+#endif
+            std::cerr << "pthread and libstdcpp)." << std::endl;
+
+            auto usage =
+                std::accumulate(cmds.begin(), cmds.end(), std::string{"usage: "} + *argv + " [",
+                                [](const auto& use, const auto& cmd) {
+                                    return use + std::string{cmd[0]} + (cmd[1].empty() ? "" : " ") +
+                                           std::string{cmd[1]} + "|";
+                                });
+            usage[usage.size() - 1] = ']';
+            std::cerr << usage << std::endl;
+
+            throw std::runtime_error("main error: argument not recognized");
+        }
+
+        return 0;
+    }
+
+    catch (const std::exception& e) {
+        std::cerr << " * " << *argv << " " << e.what() << "\n";
+    }
+
+    catch (...) {
+        std::cerr << " * * " << *argv;
+        perror(" main error");
+    }
+
+    return 1;
+}
diff --git a/external/subpack/net/nginx-util/src/nginx-util.hpp b/external/subpack/net/nginx-util/src/nginx-util.hpp
new file mode 100644
index 0000000..fb5119f
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/nginx-util.hpp
@@ -0,0 +1,143 @@
+#ifndef __NGINX_UTIL_H
+#define __NGINX_UTIL_H
+
+#include <array>
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <string>
+#include <string_view>
+// #include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <thread>
+#include <vector>
+
+#ifndef NO_UBUS
+#include "ubus-cxx.hpp"
+#endif
+
+#include "uci-cxx.hpp"
+
+static constexpr auto NGINX_UTIL = std::string_view{"/usr/bin/nginx-util"};
+
+static constexpr auto VAR_UCI_CONF = std::string_view{"/var/lib/nginx/uci.conf"};
+
+static constexpr auto UCI_CONF = std::string_view{"/etc/nginx/uci.conf"};
+
+static constexpr auto NGINX_CONF = std::string_view{"/etc/nginx/nginx.conf"};
+
+static constexpr auto CONF_DIR = std::string_view{"/etc/nginx/conf.d/"};
+
+static constexpr auto LAN_NAME = std::string_view{"_lan"};
+
+static auto constexpr MANAGE_SSL = std::string_view{"uci_manage_ssl"};
+
+static constexpr auto LAN_LISTEN = std::string_view{"/var/lib/nginx/lan.listen"};
+
+static constexpr auto LAN_LISTEN_DEFAULT =  // TODO(pst) deprecate
+    std::string_view{"/var/lib/nginx/lan.listen.default"};
+
+// mode: optional ios::binary and/or ios::app (default ios::trunc)
+void write_file(const std::string_view& name,
+                const std::string& str,
+                std::ios_base::openmode flag = std::ios::trunc);
+
+// mode: optional ios::binary (internally ios::ate|ios::in)
+auto read_file(const std::string_view& name, std::ios_base::openmode mode = std::ios::in)
+    -> std::string;
+
+// all S must be convertible to const char[]
+template <typename... S>
+auto call(const std::string& program, S... args) -> pid_t;
+
+void create_lan_listen();
+
+void init_uci(const uci::package& pkg);
+
+auto is_enabled(const uci::package& pkg) -> bool;
+
+void init_lan();
+
+void get_env();
+
+// --------------------- partial implementation: ------------------------------
+
+void write_file(const std::string_view& name,
+                const std::string& str,
+                const std::ios_base::openmode flag)
+{
+    auto tmp = std::string{name};
+
+    if ((flag & std::ios::ate) == 0 && (flag & std::ios::app) == 0) {
+        tmp += ".tmp-XXXXXX";
+        auto fd = mkstemp(&tmp[0]);
+        if (fd == -1 || close(fd) != 0) {
+            throw std::runtime_error("write_file error: cannot access " + tmp);
+        }
+    }
+
+    try {
+        std::ofstream file(tmp.data(), flag);
+        if (!file.good()) {
+            throw std::ofstream::failure("write_file error: cannot open " + std::string{tmp});
+        }
+
+        file << str << std::flush;
+
+        file.close();
+    }
+    catch (...) {
+        if (tmp != name) {
+            remove(tmp.c_str());
+        }  // remove can fail.
+        throw;
+    }
+
+    if (rename(tmp.c_str(), name.data()) != 0) {
+        throw std::runtime_error("write_file error: cannot move " + tmp + " to " + name.data());
+    }
+}
+
+auto read_file(const std::string_view& name, const std::ios_base::openmode mode) -> std::string
+{
+    std::ifstream file(name.data(), mode | std::ios::ate);
+    if (!file.good()) {
+        throw std::ifstream::failure("read_file error: cannot open " + std::string{name});
+    }
+
+    std::string ret{};
+    const size_t size = file.tellg();
+    ret.reserve(size);
+
+    file.seekg(0);
+    ret.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+
+    file.close();
+    return ret;
+}
+
+template <typename... S>
+auto call(const char* program, S... args) -> pid_t
+{
+    pid_t pid = fork();
+
+    if (pid == 0) {  // child:
+        std::array<char*, sizeof...(args) + 2> argv = {strdup(program), strdup(args)..., nullptr};
+
+        execv(program, argv.data());  // argv cannot be const char * const[]!
+
+        _exit(EXIT_FAILURE);  // exec never returns.
+    }
+    else if (pid > 0) {  // parent:
+        return pid;
+    }
+
+    std::string errmsg = "call error: cannot fork (";
+    errmsg += std::to_string(errno) + "): " + std::strerror(errno);
+    throw std::runtime_error(errmsg);
+}
+
+#endif
diff --git a/external/subpack/net/nginx-util/src/px5g-openssl.hpp b/external/subpack/net/nginx-util/src/px5g-openssl.hpp
new file mode 100644
index 0000000..7c79bad
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/px5g-openssl.hpp
@@ -0,0 +1,410 @@
+#ifndef _PX5G_OPENSSL_HPP
+#define _PX5G_OPENSSL_HPP
+
+// #define OPENSSL_API_COMPAT 0x10102000L
+#include <fcntl.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <unistd.h>
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+static constexpr auto rsa_min_modulus_bits = 512;
+
+using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
+
+using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>;
+
+auto checkend(const std::string& crtpath, time_t seconds = 0, bool use_pem = true) -> bool;
+
+auto gen_eckey(int curve) -> EVP_PKEY_ptr;
+
+auto gen_rsakey(int keysize, BN_ULONG exponent = RSA_F4) -> EVP_PKEY_ptr;
+
+void write_key(const EVP_PKEY_ptr& pkey, const std::string& keypath = "", bool use_pem = true);
+
+auto subject2name(const std::string& subject) -> X509_NAME_ptr;
+
+void selfsigned(const EVP_PKEY_ptr& pkey,
+                int days,
+                const std::string& subject = "",
+                const std::string& crtpath = "",
+                bool use_pem = true);
+
+// ------------------------- implementation: ----------------------------------
+
+inline auto print_error(const char* str, const size_t /*len*/, void* errmsg) -> int
+{
+    *static_cast<std::string*>(errmsg) += str;
+    return 0;
+}
+
+// wrapper for clang-tidy:
+inline auto _BIO_new_fp(FILE* stream, const bool use_pem, const bool close = false) -> BIO*
+{
+    return BIO_new_fp(stream,  // NOLINTNEXTLINE(hicpp-signed-bitwise) macros:
+                      (use_pem ? BIO_FP_TEXT : 0) | (close ? BIO_CLOSE : BIO_NOCLOSE));
+}
+
+auto checkend(const std::string& crtpath, const time_t seconds, const bool use_pem) -> bool
+{
+    BIO* bio = crtpath.empty() ? _BIO_new_fp(stdin, use_pem)
+                               : BIO_new_file(crtpath.c_str(), (use_pem ? "r" : "rb"));
+
+    X509* x509 = nullptr;
+
+    if (bio != nullptr) {
+        x509 = use_pem ? PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr)
+                       : d2i_X509_bio(bio, nullptr);
+        BIO_free(bio);
+    }
+
+    if (x509 == nullptr) {
+        std::string errmsg{"checkend error: unable to load certificate\n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    time_t checktime = time(nullptr) + seconds;
+    auto cmp = X509_cmp_time(X509_get0_notAfter(x509), &checktime);
+
+    X509_free(x509);
+
+    return (cmp >= 0);
+}
+
+auto gen_eckey(const int curve) -> EVP_PKEY_ptr
+{
+    EC_GROUP* group = curve != 0 ? EC_GROUP_new_by_curve_name(curve) : nullptr;
+
+    if (group == nullptr) {
+        std::string errmsg{"gen_eckey error: cannot build group for curve id "};
+        errmsg += std::to_string(curve) + "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
+
+    EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED);
+
+    auto* eckey = EC_KEY_new();
+
+    if (eckey != nullptr) {
+        if ((EC_KEY_set_group(eckey, group) == 0) || (EC_KEY_generate_key(eckey) == 0)) {
+            EC_KEY_free(eckey);
+            eckey = nullptr;
+        }
+    }
+
+    EC_GROUP_free(group);
+
+    if (eckey == nullptr) {
+        std::string errmsg{"gen_eckey error: cannot build key with curve id "};
+        errmsg += std::to_string(curve) + "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free};
+
+    // EVP_PKEY_assign_EC_KEY is a macro casting eckey to char *:
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+    if (!EVP_PKEY_assign_EC_KEY(pkey.get(), eckey)) {
+        EC_KEY_free(eckey);
+        std::string errmsg{"gen_eckey error: cannot assign EC key to EVP\n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    return pkey;
+}
+
+auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr
+{
+    if (keysize < rsa_min_modulus_bits || keysize > OPENSSL_RSA_MAX_MODULUS_BITS) {
+        std::string errmsg{"gen_rsakey error: RSA keysize ("};
+        errmsg += std::to_string(keysize) + ") out of range [512..";
+        errmsg += std::to_string(OPENSSL_RSA_MAX_MODULUS_BITS) + "]";
+        throw std::runtime_error(errmsg);
+    }
+    auto* bignum = BN_new();
+
+    if (bignum == nullptr) {
+        std::string errmsg{"gen_rsakey error: cannot get big number struct\n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    auto* rsa = RSA_new();
+
+    if (rsa != nullptr) {
+        if ((BN_set_word(bignum, exponent) == 0) ||
+            (RSA_generate_key_ex(rsa, keysize, bignum, nullptr) == 0))
+        {
+            RSA_free(rsa);
+            rsa = nullptr;
+        }
+    }
+
+    BN_free(bignum);
+
+    if (rsa == nullptr) {
+        std::string errmsg{"gen_rsakey error: cannot create RSA key with size"};
+        errmsg += std::to_string(keysize) + " and exponent ";
+        errmsg += std::to_string(exponent) + "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free};
+
+    // EVP_PKEY_assign_RSA is a macro casting rsa to char *:
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+    if (!EVP_PKEY_assign_RSA(pkey.get(), rsa)) {
+        RSA_free(rsa);
+        std::string errmsg{"gen_rsakey error: cannot assign RSA key to EVP\n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    return pkey;
+}
+
+void write_key(const EVP_PKEY_ptr& pkey, const std::string& keypath, const bool use_pem)
+{
+    BIO* bio = nullptr;
+
+    if (keypath.empty()) {
+        bio = _BIO_new_fp(stdout, use_pem);
+    }
+
+    else {  // BIO_new_file(keypath.c_str(), (use_pem ? "w" : "wb") );
+
+        static constexpr auto mask = 0600;
+        // auto fd = open(keypath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mask);
+        // creat has no cloexec, alt. triggers cppcoreguidelines-pro-type-vararg
+        // NOLINTNEXTLINE(android-cloexec-creat)
+        auto fd = creat(keypath.c_str(), mask);  // the same without va_args.
+
+        if (fd >= 0) {
+            auto* fp = fdopen(fd, (use_pem ? "w" : "wb"));
+
+            if (fp != nullptr) {
+                bio = _BIO_new_fp(fp, use_pem, true);
+                if (bio == nullptr) {
+                    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) fp owns fd:
+                    fclose(fp);
+                }
+            }
+            else {
+                close(fd);
+            }
+        }
+    }
+
+    if (bio == nullptr) {
+        std::string errmsg{"write_key error: cannot open "};
+        errmsg += keypath.empty() ? "stdout" : keypath;
+        errmsg += "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    int len = 0;
+
+    auto* key = pkey.get();
+    switch (EVP_PKEY_base_id(key)) {  // use same format as px5g:
+        case EVP_PKEY_EC:
+            len = use_pem ? PEM_write_bio_ECPrivateKey(bio, EVP_PKEY_get0_EC_KEY(key), nullptr,
+                                                       nullptr, 0, nullptr, nullptr)
+                          : i2d_ECPrivateKey_bio(bio, EVP_PKEY_get0_EC_KEY(key));
+            break;
+        case EVP_PKEY_RSA:
+            len = use_pem ? PEM_write_bio_RSAPrivateKey(bio, EVP_PKEY_get0_RSA(key), nullptr,
+                                                        nullptr, 0, nullptr, nullptr)
+                          : i2d_RSAPrivateKey_bio(bio, EVP_PKEY_get0_RSA(key));
+            break;
+        default:
+            len = use_pem
+                      ? PEM_write_bio_PrivateKey(bio, key, nullptr, nullptr, 0, nullptr, nullptr)
+                      : i2d_PrivateKey_bio(bio, key);
+    }
+
+    BIO_free_all(bio);
+
+    if (len == 0) {
+        std::string errmsg{"write_key error: cannot write EVP pkey to "};
+        errmsg += keypath.empty() ? "stdout" : keypath;
+        errmsg += "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+}
+
+auto subject2name(const std::string& subject) -> X509_NAME_ptr
+{
+    if (!subject.empty() && subject[0] != '/') {
+        throw std::runtime_error("subject2name errror: not starting with /");
+    }
+
+    X509_NAME_ptr name = {X509_NAME_new(), ::X509_NAME_free};
+
+    if (!name) {
+        std::string errmsg{"subject2name error: cannot create X509 name \n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    if (subject.empty()) {
+        return name;
+    }
+
+    int prev = 1;
+    std::string type{};
+    char chr = '=';
+    for (int i = 0; subject[i] != 0;) {
+        ++i;
+        if (subject[i] == '\\' && subject[++i] == '\0') {
+            throw std::runtime_error("subject2name errror: escape at the end");
+        }
+        if (subject[i] != chr && subject[i] != '\0') {
+            continue;
+        }
+        if (chr == '=') {
+            type = subject.substr(prev, i - prev);
+            chr = '/';
+        }
+        else {
+            auto nid = OBJ_txt2nid(type.c_str());
+            if (nid == NID_undef) {
+                // skip unknown entries (silently?).
+            }
+            else {
+                const auto* val =  // X509_NAME_add_entry_by_NID wants it unsigned:
+                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                    reinterpret_cast<const unsigned char*>(&subject[prev]);
+
+                int len = i - prev;
+
+                if (X509_NAME_add_entry_by_NID(
+                        name.get(), nid,
+                        MBSTRING_ASC,  // NOLINT(hicpp-signed-bitwise) is macro
+                        val, len, -1, 0) == 0)
+                {
+                    std::string errmsg{"subject2name error: cannot add "};
+                    errmsg += "/" + type + "=" + subject.substr(prev, len) + "\n";
+                    ERR_print_errors_cb(print_error, &errmsg);
+                    throw std::runtime_error(errmsg);
+                }
+            }
+            chr = '=';
+        }
+        prev = i + 1;
+    }
+
+    return name;
+}
+
+void selfsigned(const EVP_PKEY_ptr& pkey,
+                const int days,
+                const std::string& subject,
+                const std::string& crtpath,
+                const bool use_pem)
+{
+    auto* x509 = X509_new();
+
+    if (x509 == nullptr) {
+        std::string errmsg{"selfsigned error: cannot create X509 structure\n"};
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+
+    auto freeX509_and_throw = [&x509](const std::string& what) {
+        X509_free(x509);
+        std::string errmsg{"selfsigned error: cannot set "};
+        errmsg += what + " in X509 certificate\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    };
+
+    if (X509_set_version(x509, 2) == 0) {
+        freeX509_and_throw("version");
+    }
+
+    if (X509_set_pubkey(x509, pkey.get()) == 0) {
+        freeX509_and_throw("pubkey");
+    }
+
+    if ((X509_gmtime_adj(X509_getm_notBefore(x509), 0) == nullptr) ||
+        (X509_time_adj_ex(X509_getm_notAfter(x509), days, 0, nullptr) == nullptr))
+    {
+        freeX509_and_throw("times");
+    }
+
+    X509_NAME_ptr name{nullptr, ::X509_NAME_free};
+
+    try {
+        name = subject2name(subject);
+    }
+    catch (...) {
+        X509_free(x509);
+        throw;
+    }
+
+    if (X509_set_subject_name(x509, name.get()) == 0) {
+        freeX509_and_throw("subject");
+    }
+
+    if (X509_set_issuer_name(x509, name.get()) == 0) {
+        freeX509_and_throw("issuer");
+    }
+
+    auto* bignum = BN_new();
+
+    if (bignum == nullptr) {
+        freeX509_and_throw("serial (creating big number struct)");
+    }
+
+    static const auto BITS = 159;
+    if (BN_rand(bignum, BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 0) {
+        BN_free(bignum);
+        freeX509_and_throw("serial (creating random number)");
+    }
+
+    if (BN_to_ASN1_INTEGER(bignum, X509_get_serialNumber(x509)) == nullptr) {
+        BN_free(bignum);
+        freeX509_and_throw("random serial");
+    }
+
+    BN_free(bignum);
+
+    if (X509_sign(x509, pkey.get(), EVP_sha256()) == 0) {
+        freeX509_and_throw("signing digest");
+    }
+
+    BIO* bio = crtpath.empty() ? _BIO_new_fp(stdout, use_pem)
+                               : BIO_new_file(crtpath.c_str(), (use_pem ? "w" : "wb"));
+
+    int len = 0;
+
+    if (bio != nullptr) {
+        len = use_pem ? PEM_write_bio_X509(bio, x509) : i2d_X509_bio(bio, x509);
+        BIO_free_all(bio);
+    }
+
+    X509_free(x509);
+
+    if (len == 0) {
+        std::string errmsg{"selfsigned error: cannot write certificate to "};
+        errmsg += crtpath.empty() ? "stdout" : crtpath;
+        errmsg += "\n";
+        ERR_print_errors_cb(print_error, &errmsg);
+        throw std::runtime_error(errmsg);
+    }
+}
+
+#endif
diff --git a/external/subpack/net/nginx-util/src/px5g.cpp b/external/subpack/net/nginx-util/src/px5g.cpp
new file mode 100644
index 0000000..4edfb3b
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/px5g.cpp
@@ -0,0 +1,458 @@
+#include <unistd.h>
+#include <array>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <string_view>
+#include "px5g-openssl.hpp"
+
+class argv_view {  // TODO(pst): use std::span when available.
+
+  private:
+    std::basic_string_view<const char*> data;
+
+  public:
+    argv_view(const argv_view&) = delete;
+
+    argv_view(argv_view&&) = delete;
+
+    auto operator=(const argv_view&) -> argv_view& = delete;
+
+    auto operator=(argv_view &&) -> argv_view& = delete;
+
+    argv_view(const char** argv, int argc) : data{argv, static_cast<size_t>(argc)} {}
+
+    inline auto operator[](size_t pos) const -> std::string_view
+    {
+        return std::string_view{data[pos]};
+    }
+
+    [[nodiscard]] inline constexpr auto size() const noexcept -> size_t
+    {
+        return data.size();
+    }
+
+    ~argv_view() = default;
+};
+
+static const auto default_validity = 30;
+
+auto checkend(const argv_view& argv) -> int;
+
+void eckey(const argv_view& argv);
+
+void rsakey(const argv_view& argv);
+
+void selfsigned(const argv_view& argv);
+
+inline auto parse_int(const std::string_view& arg) -> int
+{
+    size_t pos = 0;
+    int ret = stoi(std::string{arg}, &pos);
+    if (pos < arg.size()) {
+        throw std::runtime_error("number has trailing char");
+    }
+    return ret;
+}
+
+inline auto parse_curve(const std::string_view& name) -> int
+{
+    if (name == "P-384") {
+        return NID_secp384r1;
+    }
+    if (name == "P-521") {
+        return NID_secp521r1;
+    }
+    if (name == "P-256" || name == "secp256r1") {
+        return NID_X9_62_prime256v1;
+    }
+    if (name == "secp192r1") {
+        return NID_X9_62_prime192v1;
+    }
+    return OBJ_sn2nid(name.data());
+    // not: if (curve == 0) { curve = EC_curve_nist2nid(name.c_str()); }
+}
+
+auto checkend(const argv_view& argv) -> int
+{
+    bool use_pem = true;
+    std::string crtpath{};
+    time_t seconds = 0;
+
+    for (size_t i = 2; i < argv.size(); ++i) {
+        if (argv[i] == "-der") {
+            use_pem = false;
+        }
+        else if (argv[i] == "-in") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("checkend error: -in misses filename");
+            }
+
+            if (!crtpath.empty()) {
+                if (argv[i] == crtpath) {
+                    std::cerr << "checkend warning: repeated same -in file\n";
+                }
+                else {
+                    throw std::runtime_error("checkend error: more than one -in file");
+                }
+            }
+
+            crtpath = argv[i];
+        }
+
+        else if (argv[i][0] == '-') {
+            std::cerr << "checkend warning: skipping option " << argv[i] << std::endl;
+        }
+        else {  // main option:
+            intmax_t num = 0;
+
+            try {
+                num = parse_int(argv[i]);
+            }
+            catch (...) {
+                auto errmsg = std::string{"checkend error: invalid time "};
+                errmsg += argv[i];
+                std::throw_with_nested(std::runtime_error(errmsg));
+            }
+
+            seconds = static_cast<time_t>(num);
+
+            if (num != static_cast<intmax_t>(seconds)) {
+                auto errmsg = std::string{"checkend error: time too big "};
+                errmsg += argv[i];
+                throw std::runtime_error(errmsg);
+            }
+        }
+    }
+
+    bool valid = checkend(crtpath, seconds, use_pem);
+    std::cout << "Certificate will" << (valid ? " not " : " ") << "expire" << std::endl;
+
+    return (valid ? 0 : 1);
+}
+
+void eckey(const argv_view& argv)
+{
+    bool has_main_option = false;
+    bool use_pem = true;
+    std::string keypath{};
+    int curve = NID_X9_62_prime256v1;
+
+    for (size_t i = 2; i < argv.size(); ++i) {
+        if (argv[i] == "-der") {
+            use_pem = false;
+        }
+        else if (argv[i] == "-out") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("eckey error: -out misses filename");
+            }
+
+            if (!keypath.empty()) {
+                if (argv[i] == keypath) {
+                    std::cerr << "eckey warning: repeated same -out file\n";
+                }
+                else {
+                    throw std::runtime_error("eckey error: more than one -out file");
+                }
+            }
+
+            keypath = argv[i];
+        }
+
+        else if (argv[i][0] == '-') {
+            std::cerr << "eckey warning: skipping option " << argv[i] << std::endl;
+        }
+        else {  // main option:
+
+            if (has_main_option) {
+                throw std::runtime_error("eckey error: more than one main option");
+            }  // else:
+            has_main_option = true;
+
+            curve = parse_curve(argv[i]);
+        }
+    }
+
+    write_key(gen_eckey(curve), keypath, use_pem);
+}
+
+void rsakey(const argv_view& argv)
+{
+    bool has_main_option = false;
+    bool use_pem = true;
+    std::string keypath{};
+    BN_ULONG exponent = RSA_F4;
+    int keysize = rsa_min_modulus_bits;
+
+    for (size_t i = 2; i < argv.size(); ++i) {
+        if (argv[i] == "-der") {
+            use_pem = false;
+        }
+        else if (argv[i] == "-3") {
+            exponent = 3;
+        }
+        else if (argv[i] == "-out") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("rsakey error: -out misses filename");
+            }
+
+            if (!keypath.empty()) {
+                if (argv[i] == keypath) {
+                    std::cerr << "rsakey warning: repeated -out file" << std::endl;
+                }
+                else {
+                    throw std::runtime_error("rsakey error: more than one -out file");
+                }
+            }
+
+            keypath = argv[i];
+        }
+
+        else if (argv[i][0] == '-') {
+            std::cerr << "rsakey warning: skipping option " << argv[i] << std::endl;
+        }
+        else {  // main option:
+
+            if (has_main_option) {
+                throw std::runtime_error("rsakey error: more than one keysize");
+            }  // else:
+            has_main_option = true;
+
+            try {
+                keysize = parse_int(argv[i]);
+            }
+            catch (...) {
+                std::string errmsg{"rsakey error: invalid keysize "};
+                errmsg += argv[i];
+                std::throw_with_nested(std::runtime_error(errmsg));
+            }
+        }
+    }
+
+    write_key(gen_rsakey(keysize, exponent), keypath, use_pem);
+}
+
+void selfsigned(const argv_view& argv)
+{
+    bool use_pem = true;
+    int days = default_validity;
+    std::string keypath{};
+    std::string crtpath{};
+    std::string subject{};
+
+    bool use_rsa = true;
+    int keysize = rsa_min_modulus_bits;
+    BN_ULONG exponent = RSA_F4;
+
+    int curve = NID_X9_62_prime256v1;
+
+    for (size_t i = 2; i < argv.size(); ++i) {
+        if (argv[i] == "-der") {
+            use_pem = false;
+        }
+        else if (argv[i] == "-days") {
+            ++i;
+            try {
+                days = parse_int(argv[i]);
+            }
+            catch (...) {
+                std::string errmsg{"selfsigned error: not a number for -days "};
+                errmsg += argv[i].substr(4);
+                std::throw_with_nested(std::runtime_error(errmsg));
+            }
+        }
+
+        else if (argv[i] == "-newkey") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("selfsigned error: -newkey misses algorithm option");
+            }
+
+            static constexpr auto rsa_prefix = std::string_view{"rsa:"};
+
+            if (argv[i] == "ec") {
+                use_rsa = false;
+            }
+            else if (argv[i].rfind(rsa_prefix, 0) == 0) {
+                use_rsa = true;
+                try {
+                    keysize = parse_int(argv[i].substr(rsa_prefix.size()));
+                }
+                catch (...) {
+                    std::string errmsg{"selfsigned error: invalid keysize "};
+                    errmsg += argv[i].substr(4);
+                    std::throw_with_nested(std::runtime_error(errmsg));
+                }
+            }
+            else {
+                throw std::runtime_error("selfsigned error: invalid algorithm");
+            }
+        }
+
+        else if (argv[i] == "-pkeyopt") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("selfsigned error: -pkeyopt misses value");
+            }
+
+            static constexpr auto curve_prefix = std::string_view{"ec_paramgen_curve:"};
+
+            if (argv[i].rfind(curve_prefix, 0) != 0) {
+                throw std::runtime_error("selfsigned error: -pkeyopt invalid");
+            }
+
+            curve = parse_curve(argv[i].substr(curve_prefix.size()));
+        }
+
+        else if (argv[i] == "-keyout") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("selfsigned error: -keyout misses path");
+            }
+
+            if (!keypath.empty()) {
+                if (argv[i] == keypath) {
+                    std::cerr << "selfsigned warning: repeated -keyout file\n";
+                }
+                else {
+                    throw std::runtime_error("selfsigned error: more than one -keyout file");
+                }
+            }
+
+            keypath = argv[i];
+        }
+
+        else if (argv[i] == "-out") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("selfsigned error: -out misses filename");
+            }
+
+            if (!crtpath.empty()) {
+                if (argv[i] == crtpath) {
+                    std::cerr << "selfsigned warning: repeated same -out file\n";
+                }
+                else {
+                    throw std::runtime_error("selfsigned error: more than one -out file");
+                }
+            }
+
+            crtpath = argv[i];
+        }
+
+        else if (argv[i] == "-subj") {
+            ++i;
+
+            if (i >= argv.size()) {
+                throw std::runtime_error("selfsigned error: -subj misses value");
+            }
+
+            if (!subject.empty()) {
+                if (argv[i] == subject) {
+                    std::cerr << "selfsigned warning: repeated same -subj\n";
+                }
+                else {
+                    throw std::runtime_error("selfsigned error: more than one -subj value");
+                }
+            }
+
+            subject = argv[i];
+        }
+
+        else {
+            std::cerr << "selfsigned warning: skipping option " << argv[i] << std::endl;
+        }
+    }
+
+    auto pkey = use_rsa ? gen_rsakey(keysize, exponent) : gen_eckey(curve);
+
+    selfsigned(pkey, days, subject, crtpath, use_pem);
+
+    if (!keypath.empty()) {
+        write_key(pkey, keypath, use_pem);
+    }
+}
+
+auto main(int argc, const char** argv) -> int
+{
+    auto args = argv_view{argv, argc};
+
+    auto cmds = std::array{
+        std::array<std::string, 2>{"checkend",
+                                   " [-der] [-in certificate_path] [seconds_remaining]"},
+        std::array<std::string, 2>{"eckey", " [-der] [-out key_path] [curve_name]"},
+        std::array<std::string, 2>{"rsakey", " [-der] [-out key_path] [-3] [key_size]"},
+        std::array<std::string, 2>{
+            "selfsigned",
+            " [-der] [-keyout key_path] [-out certificate_path]"
+            " [-newkey ec|rsa:key_size] [-pkeyopt ec_paramgen_curve:name]"
+            " [-days validity] [-subj /C=.../ST=.../L=.../O=.../CN=.../... ]"},
+    };
+
+    try {
+        if (argc < 2) {
+            throw std::runtime_error("error: no argument");
+        }
+
+        if (args[1] == cmds[0][0]) {
+            return checkend(args);
+        }
+
+        if (args[1] == cmds[1][0]) {
+            eckey(args);
+        }
+
+        else if (args[1] == cmds[2][0]) {
+            rsakey(args);
+        }
+
+        else if (args[1] == cmds[3][0]) {
+            selfsigned(args);
+        }
+
+        else {
+            throw std::runtime_error("error: argument not recognized");
+        }
+    }
+
+    catch (const std::exception& e) {
+        auto usage = std::accumulate(
+            cmds.begin(), cmds.end(), std::string{"usage: \n"},
+            [=](const auto& use, const auto& cmd) {
+                return use + std::string{4, ' '} + *argv + " " + cmd[0] + cmd[1] + "\n";
+            });
+
+        std::cerr << usage << std::flush;
+
+        auto print_nested = [](auto&& self, const std::exception& outer, int depth = 0) -> void {
+            std::cerr << std::string(depth, '\t') << outer.what() << std::endl;
+            try {
+                std::rethrow_if_nested(outer);
+            }
+            catch (const std::exception& inner) {
+                self(self, inner, depth + 1);
+            }
+        };
+
+        print_nested(print_nested, e);
+
+        return 1;
+    }
+
+    catch (...) {
+        std::cerr << *argv << " unknown error." << std::endl;
+        return 2;
+    }
+
+    return 0;
+}
diff --git a/external/subpack/net/nginx-util/src/regex-pcre.hpp b/external/subpack/net/nginx-util/src/regex-pcre.hpp
new file mode 100644
index 0000000..f63d5f9
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/regex-pcre.hpp
@@ -0,0 +1,483 @@
+#ifndef __REGEXP_PCRE_HPP
+#define __REGEXP_PCRE_HPP
+
+#include <pcre.h>
+#include <array>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace rgx {
+/* partially implement the std::regex interface using PCRE for performance
+ * (=> pass "match" as non-const reference)
+ */
+
+namespace regex_constants {
+enum error_type {
+    _enum_error_collate,
+    _enum_error_ctype,
+    _enum_error_escape,
+    _enum_error_backref,
+    _enum_error_brack,
+    _enum_error_paren,
+    _enum_error_brace,
+    _enum_error_badbrace,
+    _enum_error_range,
+    _enum_error_space,
+    _enum_error_badrepeat,
+    _enum_error_complexity,
+    _enum_error_stack,
+    _enum_error_last
+};
+static const error_type error_collate(_enum_error_collate);
+static const error_type error_ctype(_enum_error_ctype);
+static const error_type error_escape(_enum_error_escape);
+static const error_type error_backref(_enum_error_backref);
+static const error_type error_brack(_enum_error_brack);
+static const error_type error_paren(_enum_error_paren);
+static const error_type error_brace(_enum_error_brace);
+static const error_type error_badbrace(_enum_error_badbrace);
+static const error_type error_range(_enum_error_range);
+static const error_type error_space(_enum_error_space);
+static const error_type error_badrepeat(_enum_error_badrepeat);
+static const error_type error_complexity(_enum_error_complexity);
+static const error_type error_stack(_enum_error_stack);
+}  // namespace regex_constants
+
+class regex_error : public std::runtime_error {
+  private:
+    regex_constants::error_type errcode;
+
+  public:
+    explicit regex_error(regex_constants::error_type code, const char* what = "regex error")
+        : runtime_error(what), errcode(code)
+    {}
+
+    [[nodiscard]] auto virtual code() const -> regex_constants::error_type;
+};
+
+[[nodiscard]] auto regex_error::code() const -> regex_constants::error_type
+{
+    return errcode;
+}
+
+class regex {
+  private:
+    int errcode = 0;
+
+    const char* errptr = nullptr;
+
+    int erroffset = 0;
+
+    pcre* const re = nullptr;
+
+    static const std::array<regex_constants::error_type, 86> errcode_pcre2regex;
+
+    static const auto BASE = 10;
+
+  public:
+    inline regex() = default;
+
+    inline regex(const regex&) = delete;
+
+    inline regex(regex&&) = default;
+
+    inline auto operator=(const regex&) -> regex& = delete;
+
+    inline auto operator=(regex &&) -> regex& = delete;
+
+    explicit regex(const std::string& str) : regex(str.c_str()) {}
+
+    explicit regex(const char* const str)
+        : re{pcre_compile2(str, 0, &errcode, &errptr, &erroffset, nullptr)}
+    {
+        if (re == nullptr) {
+            std::string what = std::string("regex error: ") + errptr + '\n';
+            what += "    '" + std::string{str} + "'\n";
+            what += "     " + std::string(erroffset, ' ') + '^';
+
+            throw regex_error(errcode_pcre2regex.at(errcode), what.c_str());
+        }
+    }
+
+    ~regex()
+    {
+        if (re != nullptr) {
+            pcre_free(re);
+        }
+    }
+
+    inline auto operator()() const -> const pcre*
+    {
+        return re;
+    }
+};
+
+class smatch {
+    friend auto regex_search(std::string::const_iterator begin,
+                             std::string::const_iterator end,
+                             smatch& match,      // NOLINT(google-runtime-references)
+                             const regex& rgx);  // match std::regex interface.
+
+  private:
+    std::string::const_iterator begin;
+
+    std::string::const_iterator end;
+
+    std::vector<int> vec{};
+
+    int n = 0;
+
+  public:
+    [[nodiscard]] inline auto position(int i = 0) const
+    {
+        return (i < 0 || i >= n) ? std::string::npos : vec[2 * i];
+    }
+
+    [[nodiscard]] inline auto length(int i = 0) const
+    {
+        return (i < 0 || i >= n) ? 0 : vec[2 * i + 1] - vec[2 * i];
+    }
+
+    [[nodiscard]] auto str(int i = 0) const -> std::string
+    {  // should we throw?
+        if (i < 0 || i >= n) {
+            return "";
+        }
+        int x = vec[2 * i];
+        if (x < 0) {
+            return "";
+        }
+        int y = vec[2 * i + 1];
+        return std::string{begin + x, begin + y};
+    }
+
+    [[nodiscard]] auto format(const std::string& fmt) const;
+
+    [[nodiscard]] auto size() const -> int
+    {
+        return n;
+    }
+
+    [[nodiscard]] inline auto empty() const
+    {
+        return n < 0;
+    }
+
+    [[nodiscard]] inline auto ready() const
+    {
+        return !vec.empty();
+    }
+};
+
+inline auto regex_search(const std::string& subj, const regex& rgx);
+
+auto regex_replace(const std::string& subj, const regex& rgx, const std::string& insert);
+
+inline auto regex_search(const std::string& subj,
+                         smatch& match,      // NOLINT(google-runtime-references)
+                         const regex& rgx);  // match std::regex interface.
+
+auto regex_search(std::string::const_iterator begin,
+                  std::string::const_iterator end,
+                  smatch& match,      // NOLINT(google-runtime-references)
+                  const regex& rgx);  // match std::regex interface.
+
+// ------------------------- implementation: ----------------------------------
+
+inline auto regex_search(const std::string& subj, const regex& rgx)
+{
+    if (rgx() == nullptr) {
+        throw std::runtime_error("regex_search error: no regex given");
+    }
+    int n =
+        pcre_exec(rgx(), nullptr, subj.c_str(), static_cast<int>(subj.length()), 0, 0, nullptr, 0);
+    return n >= 0;
+}
+
+auto regex_search(const std::string::const_iterator begin,
+                  const std::string::const_iterator end,
+                  smatch& match,
+                  const regex& rgx)
+{
+    if (rgx() == nullptr) {
+        throw std::runtime_error("regex_search error: no regex given");
+    }
+
+    int sz = 0;
+    pcre_fullinfo(rgx(), nullptr, PCRE_INFO_CAPTURECOUNT, &sz);
+    sz = 3 * (sz + 1);
+
+    match.vec.reserve(sz);
+
+    const char* subj = &*begin;
+    int len = static_cast<int>(&*end - subj);
+
+    match.begin = begin;
+    match.end = end;
+
+    match.n = pcre_exec(rgx(), nullptr, subj, len, 0, 0, &match.vec[0], sz);
+
+    if (match.n < 0) {
+        return false;
+    }
+    if (match.n == 0) {
+        match.n = sz / 3;
+    }
+
+    return true;
+}
+
+inline auto regex_search(const std::string& subj, smatch& match, const regex& rgx)
+{
+    return regex_search(subj.begin(), subj.end(), match, rgx);
+}
+
+auto smatch::format(const std::string& fmt) const
+{
+    std::string ret{};
+    size_t index = 0;
+
+    size_t pos = 0;
+    while ((pos = fmt.find('$', index)) != std::string::npos) {
+        ret.append(fmt, index, pos - index);
+        index = pos + 1;
+
+        char chr = fmt[index++];
+        switch (chr) {
+            case '&':  // match
+                ret += str(0);
+                break;
+
+            case '`':  // prefix
+                ret.append(begin, begin + vec[0]);
+                break;
+
+            case '\'':  // suffix
+                ret.append(begin + vec[1], end);
+                break;
+
+            default:
+                if (isdigit(chr) != 0) {  // one or two digits => submatch:
+                    int num = chr - '0';
+                    chr = fmt[index];
+                    if (isdigit(chr) != 0) {  // second digit:
+                        ++index;
+                        static const auto base = 10;
+                        num = num * base + chr - '0';
+                    }
+                    ret += str(num);
+                    break;
+                }  // else:
+
+                ret += '$';
+                [[fallthrough]];
+
+            case '$':  // escaped
+                ret += chr;
+        }
+    }
+    ret.append(fmt, index);
+    return ret;
+}
+
+auto regex_replace(const std::string& subj, const regex& rgx, const std::string& insert)
+{
+    if (rgx() == nullptr) {
+        throw std::runtime_error("regex_replace error: no regex given");
+    }
+
+    std::string ret{};
+    auto pos = subj.begin();
+
+    for (smatch match; regex_search(pos, subj.end(), match, rgx);
+         pos += match.position(0) + match.length(0))
+    {
+        ret.append(pos, pos + match.position(0));
+        ret.append(match.format(insert));
+    }
+
+    ret.append(pos, subj.end());
+    return ret;
+}
+
+// ------------ There is only the translation table below : -------------------
+
+const std::array<regex_constants::error_type, 86> regex::errcode_pcre2regex = {
+    //   0  no error
+    regex_constants::error_type::_enum_error_last,
+    //   1  \ at end of pattern
+    regex_constants::error_escape,
+    //   2  \c at end of pattern
+    regex_constants::error_escape,
+    //   3  unrecognized character follows \ .
+    regex_constants::error_escape,
+    //   4  numbers out of order in {} quantifier
+    regex_constants::error_badbrace,
+    //   5  number too big in {} quantifier
+    regex_constants::error_badbrace,
+    //   6  missing terminating  for character class
+    regex_constants::error_brack,
+    //   7  invalid escape sequence in character class
+    regex_constants::error_escape,
+    //   8  range out of order in character class
+    regex_constants::error_range,
+    //   9  nothing to repeat
+    regex_constants::error_badrepeat,
+    //  10  [this code is not in use
+    regex_constants::error_type::_enum_error_last,
+    //  11  internal error: unexpected repeat
+    regex_constants::error_badrepeat,
+    //  12  unrecognized character after (? or (?-
+    regex_constants::error_backref,
+    //  13  POSIX named classes are supported only within a class
+    regex_constants::error_range,
+    //  14  missing )
+    regex_constants::error_paren,
+    //  15  reference to non-existent subpattern
+    regex_constants::error_backref,
+    //  16  erroffset passed as NULL
+    regex_constants::error_type::_enum_error_last,
+    //  17  unknown option bit(s) set
+    regex_constants::error_type::_enum_error_last,
+    //  18  missing ) after comment
+    regex_constants::error_paren,
+    //  19  [this code is not in use
+    regex_constants::error_type::_enum_error_last,
+    //  20  regular expression is too large
+    regex_constants::error_space,
+    //  21  failed to get memory
+    regex_constants::error_stack,
+    //  22  unmatched parentheses
+    regex_constants::error_paren,
+    //  23  internal error: code overflow
+    regex_constants::error_stack,
+    //  24  unrecognized character after (?<
+    regex_constants::error_backref,
+    //  25  lookbehind assertion is not fixed length
+    regex_constants::error_backref,
+    //  26  malformed number or name after (?(
+    regex_constants::error_backref,
+    //  27  conditional group contains more than two branches
+    regex_constants::error_backref,
+    //  28  assertion expected after (?(
+    regex_constants::error_backref,
+    //  29  (?R or (?[+-digits must be followed by )
+    regex_constants::error_backref,
+    //  30  unknown POSIX class name
+    regex_constants::error_ctype,
+    //  31  POSIX collating elements are not supported
+    regex_constants::error_collate,
+    //  32  this version of PCRE is compiled without UTF support
+    regex_constants::error_collate,
+    //  33  [this code is not in use
+    regex_constants::error_type::_enum_error_last,
+    //  34  character value in \x{} or \o{} is too large
+    regex_constants::error_escape,
+    //  35  invalid condition (?(0)
+    regex_constants::error_backref,
+    //  36  \C not allowed in lookbehind assertion
+    regex_constants::error_escape,
+    //  37  PCRE does not support \L, \l, \N{name}, \U, or \u
+    regex_constants::error_escape,
+    //  38  number after (?C is > 255
+    regex_constants::error_backref,
+    //  39  closing ) for (?C expected
+    regex_constants::error_paren,
+    //  40  recursive call could loop indefinitely
+    regex_constants::error_complexity,
+    //  41  unrecognized character after (?P
+    regex_constants::error_backref,
+    //  42  syntax error in subpattern name (missing terminator)
+    regex_constants::error_paren,
+    //  43  two named subpatterns have the same name
+    regex_constants::error_backref,
+    //  44  invalid UTF-8 string (specifically UTF-8)
+    regex_constants::error_collate,
+    //  45  support for \P, \p, and \X has not been compiled
+    regex_constants::error_escape,
+    //  46  malformed \P or \p sequence
+    regex_constants::error_escape,
+    //  47  unknown property name after \P or \p
+    regex_constants::error_escape,
+    //  48  subpattern name is too long (maximum 32 characters)
+    regex_constants::error_backref,
+    //  49  too many named subpatterns (maximum 10000)
+    regex_constants::error_complexity,
+    //  50  [this code is not in use
+    regex_constants::error_type::_enum_error_last,
+    //  51  octal value is greater than \377 in 8-bit non-UTF-8 mode
+    regex_constants::error_escape,
+    //  52  internal error: overran compiling workspace
+    regex_constants::error_type::_enum_error_last,
+    //  53  internal error: previously-checked referenced subpattern not found
+    regex_constants::error_type::_enum_error_last,
+    //  54  DEFINE group contains more than one branch
+    regex_constants::error_backref,
+    //  55  repeating a DEFINE group is not allowed
+    regex_constants::error_backref,
+    //  56  inconsistent NEWLINE options
+    regex_constants::error_escape,
+    //  57  \g is not followed by a braced, angle-bracketed, or quoted name/number or by a plain
+    //  number
+    regex_constants::error_backref,
+    //  58  a numbered reference must not be zero
+    regex_constants::error_backref,
+    //  59  an argument is not allowed for (*ACCEPT), (*FAIL), or (*COMMIT)
+    regex_constants::error_complexity,
+    //  60  (*VERB) not recognized or malformed
+    regex_constants::error_complexity,
+    //  61  number is too big
+    regex_constants::error_complexity,
+    //  62  subpattern name expected
+    regex_constants::error_backref,
+    //  63  digit expected after (?+
+    regex_constants::error_backref,
+    //  64   is an invalid data character in JavaScript compatibility mode
+    regex_constants::error_escape,
+    //  65  different names for subpatterns of the same number are not allowed
+    regex_constants::error_backref,
+    //  66  (*MARK) must have an argument
+    regex_constants::error_complexity,
+    //  67  this version of PCRE is not compiled with Unicode property support
+    regex_constants::error_collate,
+    //  68  \c must be followed by an ASCII character
+    regex_constants::error_escape,
+    //  69  \k is not followed by a braced, angle-bracketed, or quoted name
+    regex_constants::error_backref,
+    //  70  internal error: unknown opcode in find_fixedlength()
+    regex_constants::error_type::_enum_error_last,
+    //  71  \N is not supported in a class
+    regex_constants::error_ctype,
+    //  72  too many forward references
+    regex_constants::error_backref,
+    //  73  disallowed Unicode code point (>= 0xd800 && <= 0xdfff)
+    regex_constants::error_escape,
+    //  74  invalid UTF-16 string (specifically UTF-16)
+    regex_constants::error_collate,
+    //  75  name is too long in (*MARK), (*PRUNE), (*SKIP), or (*THEN)
+    regex_constants::error_complexity,
+    //  76  character value in \u.... sequence is too large
+    regex_constants::error_escape,
+    //  77  invalid UTF-32 string (specifically UTF-32)
+    regex_constants::error_collate,
+    //  78  setting UTF is disabled by the application
+    regex_constants::error_collate,
+    //  79  non-hex character in \x{} (closing brace missing?)
+    regex_constants::error_escape,
+    //  80  non-octal character in \o{} (closing brace missing?)
+    regex_constants::error_escape,
+    //  81  missing opening brace after \o
+    regex_constants::error_brace,
+    //  82  parentheses are too deeply nested
+    regex_constants::error_complexity,
+    //  83  invalid range in character class
+    regex_constants::error_range,
+    //  84  group name must start with a non-digit
+    regex_constants::error_backref,
+    //  85  parentheses are too deeply nested (stack check)
+    regex_constants::error_stack};
+
+}  // namespace rgx
+
+#endif
diff --git a/external/subpack/net/nginx-util/src/test-nginx-util-root.sh b/external/subpack/net/nginx-util/src/test-nginx-util-root.sh
new file mode 100644
index 0000000..eb8ca56
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/test-nginx-util-root.sh
@@ -0,0 +1,609 @@
+#!/bin/sh
+
+PRINT_PASSED=2
+
+NGINX_UTIL="/usr/bin/nginx-util"
+
+ORIG=".original-test-nginx-util-root"
+
+mkdir -p /tmp/.uci/
+
+uci commit nginx || { printf "Error invoking: uci commit\n Exit."; exit 2; }
+
+
+pst_exit() {
+    printf "\nExit: Recovering original settings ... "
+
+    uci revert nginx
+
+    cd "/etc/config/" && rm "nginx" && mv "nginx.${ORIG}" "nginx" ||
+    printf "\n%s: not moved %s to %s\n" "/etc/config/" "nginx${ORIG}" "nginx"
+
+    cd "/etc/crontabs/" && rm "root" && mv "root${ORIG}" "root" ||
+    printf "\n%s: not moved %s to %s\n" "/etc/crontabs/" "root${ORIG}" "root"
+
+    cd "$(dirname "${CONF_DIR}")" && rm -r "${CONF_DIR}" &&
+    mv "$(basename "${CONF_DIR}")${ORIG}" "$(basename "${CONF_DIR}")" ||
+    printf "\n%s: not moved %s to %s\n" "$(dirname "${CONF_DIR}")" \
+        "$(basename "${CONF_DIR}")${ORIG}" "$(basename "${CONF_DIR}")"
+
+    printf "done.\n"
+
+    exit "$1"
+}
+
+
+mkdir -p "/etc/config/" && touch "/etc/config/nginx"
+
+cd "/etc/config/" && [ ! -e "nginx${ORIG}" ] && cp "nginx" "nginx.${ORIG}" || {
+    printf "\n%s: not copied %s to %s\n" "/etc/config/" "nginx" "nginx${ORIG}"
+    pst_exit 3
+}
+
+uci set nginx.global.uci_enable=1
+
+
+mkdir -p "/etc/crontabs/" && touch "/etc/crontabs/root"
+
+cd "/etc/crontabs/" && [ ! -e "root${ORIG}" ] && mv "root" "root${ORIG}" || {
+    printf "\n%s: not moved %s to %s\n" "/etc/crontabs/" "root${ORIG}" "root"
+    pst_exit 4
+}
+
+touch "/etc/crontabs/root"
+
+
+# ----------------------------------------------------------------------------
+
+__esc_newlines() {
+    echo "${1}" | sed -E 's/$/\\n/' | tr -d '\n' | sed -E 's/\\n$/\n/'
+}
+
+__esc_sed_rhs() {
+    __esc_newlines "${1}" |  sed -E 's/[&/\]/\\&/g'
+}
+
+_sed_rhs() {
+    __esc_sed_rhs "$(echo "${1}" | sed -E "s/[$]/$(__esc_sed_rhs "${2}")/g")"
+}
+
+__esc_regex() {
+    __esc_newlines "${1}" | sed -E 's/[^^_a-zA-Z0-9-]/[&]/g; s/\^/\\^/g'
+}
+
+_regex() {
+    __esc_regex "${1}" | sed -E -e 's/^(\[\s])*/^\\s*/' \
+        -e 's/(\[\s])+\[[*]]/(\\s.*)?/g' \
+        -e 's/(\[\s])+/\\s+/g' \
+        -e 's/(\[\s])*\[[;]]/\\s*;/g' \
+        -e "s/\[['\"]]/['\"]?/g" \
+        -e "s/\[[$]]/$(__esc_sed_rhs "$(__esc_regex "${2}")")/g"
+}
+
+_echo_sed() {
+    echo "" | sed -E "c${1}"
+}
+
+
+fileauto="# This file is re-created when Nginx starts."
+
+setpoint_init_lan() {
+    echo "${fileauto}"
+
+    sed -n -E '/^\s*#UCI_HTTP_CONFIG\s*$/q;p' "${UCI_CONF}.template"
+
+    local rhs="\t}\n\n\tserver { #see uci show 'nginx.\1'"
+    uci -n export nginx \
+    | sed -E -e "s/'//g" \
+        -e '/^\s*package\s+nginx\s*$/d' \
+        -e '/^\s*config\s+main\s/d' \
+        -e "s/^\s*config\s+server\s+(.*)$/$rhs/g" \
+        -e 's/^\s*list\s/\t\t/g' \
+        -e 's/^\s*option\s/\t\t/g' \
+        -e 's/^\s*uci_listen_locally\s+/\t\tlisten 127.0.0.1:/g' \
+        -e '/^\s*uci_/d' \
+        -e '/^$/d' -e "s/[^'\n]$/&;/g" \
+    | sed "1,2d"
+    printf "\t}\n\n"
+
+    sed -E '1,/^\s*#UCI_HTTP_CONFIG\s*$/ d' "${UCI_CONF}.template"
+}
+
+
+setpoint_add_ssl() {
+    local indent="\n$1"
+    local name="$2"
+    local default=""
+    [ "${name}" = "${LAN_NAME}" ] && default=".default"
+    local prefix="${CONF_DIR}${name}"
+
+    local ADDS=""
+    local CONF
+    CONF="$(sed -E \
+        -e "s/$(_regex "${NGX_INCLUDE}" "${LAN_LISTEN}${default}")/$1$(\
+                _sed_rhs "${NGX_INCLUDE}" "${LAN_SSL_LISTEN}${default}")/g" \
+        -e "s/^(\s*listen\s+)([^:]*:|\[[^]]*\]:)?80(\s|$|;)/\1\2443 ssl\3/g" \
+            "${prefix}.sans" 2>/dev/null)"
+    echo "${CONF}" | grep -qE "$(_regex "${NGX_SSL_CRT}" "${prefix}")" \
+    || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_SSL_CRT}" "${prefix}")"
+    echo "${CONF}" | grep -qE "$(_regex "${NGX_SSL_KEY}" "${prefix}")" \
+    || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_SSL_KEY}" "${prefix}")"
+    echo "${CONF}" | grep -qE "^\s*ssl_session_cache\s" \
+    || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_SSL_SESSION_CACHE}" "${name}")"
+    echo "${CONF}" | grep -qE "^\s*ssl_session_timeout\s" \
+    || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_SSL_SESSION_TIMEOUT}" "")"
+
+    if [ -n "${ADDS}" ]
+    then
+        ADDS="$(echo "${ADDS}" | sed -E 's/^\\n//')"
+        echo "${CONF}" | grep -qE "$(_regex "${NGX_SERVER_NAME}" "${name}")" \
+        && echo "${CONF}" \
+            | sed -E "/$(_regex "${NGX_SERVER_NAME}" "${name}")/a\\${ADDS}" \
+            > "${prefix}.with" \
+        && _echo_sed "Added directives to ${prefix}.with:\n${ADDS}" \
+        && return 0 \
+        || _echo_sed "Cannot add directives to ${prefix}.sans, missing:\
+            \n$(_sed_rhs "${NGX_SERVER_NAME}" "${name}")\n${ADDS}"
+        return 1
+    fi
+    return 0
+}
+
+# ----------------------------------------------------------------------------
+
+test_setpoint() {
+    [ "$(cat "$1")" = "$2" ] && return
+    echo "$1:"; cat "$1"
+    echo "differs from setpoint:"; echo "$2"
+    [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1
+}
+
+
+test_existence() {
+    if [ "$2" -eq "0" ]
+    then
+        [ ! -f "$1" ] && echo "$1 missing!" &&
+        [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1
+    else
+        [ -f "$1" ] && echo "$1 existing!" &&
+        [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1
+    fi
+}
+
+
+test() {
+    eval "$1 2>/dev/null >/dev/null"
+    if [ "$?" -eq "$2" ]
+    then
+        [ "${PRINT_PASSED}" -gt 0 ] \
+        && printf "%-72s%-1s\n" "$1" "2>/dev/null >/dev/null (-> $2?) passed."
+    else
+        printf "%-72s%-1s\n" "$1" "2>/dev/null >/dev/null (-> $2?) failed!!!"
+        [ "${PRINT_PASSED}" -gt 0 ] && printf "\n### Snip:\n" && eval "$1"
+        [ "${PRINT_PASSED}" -gt 0 ] && printf "### Snap.\n"
+        [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1
+    fi
+}
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s get_env ...\n" "${NGINX_UTIL}"
+
+
+eval $("${NGINX_UTIL}" get_env)
+test '[ -n "${UCI_CONF}" ]' 0
+test '[ -n "${NGINX_CONF}" ]' 0
+test '[ -n "${CONF_DIR}" ]' 0
+test '[ -n "${LAN_NAME}" ]' 0
+test '[ -n "${LAN_LISTEN}" ]' 0
+test '[ -n "${LAN_SSL_LISTEN}" ]' 0
+test '[ -n "${SSL_SESSION_CACHE_ARG}" ]' 0
+test '[ -n "${SSL_SESSION_TIMEOUT_ARG}" ]' 0
+test '[ -n "${ADD_SSL_FCT}" ]' 0
+test '[ -n "${MANAGE_SSL}" ]' 0
+
+mkdir -p "$(dirname "${LAN_LISTEN}")"
+
+mkdir -p "${CONF_DIR}"
+
+cd "$(dirname "${CONF_DIR}")" && [ ! -e "$(basename "${CONF_DIR}")${ORIG}" ] &&
+mv "$(basename "${CONF_DIR}")" "$(basename "${CONF_DIR}")${ORIG}" ||
+{
+    printf "\n%s: not moved %s to %s\n" "$(dirname "${CONF_DIR}")" \
+        "$(basename "${CONF_DIR}")" "$(basename "${CONF_DIR}")${ORIG}"
+    pst_exit 3
+}
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nPrepare files in %s ...\n" "${CONF_DIR}"
+
+mkdir -p "${CONF_DIR}"
+
+cd "${CONF_DIR}" || pst_exit 2
+
+NGX_INCLUDE="include '\$';"
+NGX_SERVER_NAME="server_name * '\$' *;"
+NGX_SSL_CRT="ssl_certificate '\$.crt';"
+NGX_SSL_KEY="ssl_certificate_key '\$.key';"
+NGX_SSL_SESSION_CACHE="ssl_session_cache '$(echo "${SSL_SESSION_CACHE_ARG}" \
+    | sed -E "s/$(__esc_regex "${LAN_NAME}")/\$/")';"
+NGX_SSL_SESSION_TIMEOUT="ssl_session_timeout '${SSL_SESSION_TIMEOUT_ARG}';"
+
+cat > "${LAN_NAME}.sans" <<EOF
+# default_server for the LAN addresses getting the IPs by:
+# ifstatus lan | jsonfilter -e '@["ipv4-address","ipv6-address"].*.address'
+server {
+    include '${LAN_LISTEN}.default';
+    server_name ${LAN_NAME};
+    include conf.d/*.locations;
+}
+EOF
+CONFS="${CONFS} ${LAN_NAME}:0"
+
+cat > minimal.sans <<EOF
+server {
+    server_name minimal;
+}
+EOF
+CONFS="${CONFS} minimal:0"
+
+cat > listens.sans <<EOF
+server {
+    listen 80;
+    listen 81;
+    listen hostname:80;
+    listen hostname:81;
+    listen [::]:80;
+    listen [::]:81;
+    listen 1.3:80;
+#    listen 1.3:80;
+    listen 1.3:81;
+    listen [1::3]:80;
+    listen [1::3]:81;
+    server_name listens;
+}
+EOF
+CONFS="${CONFS} listens:0"
+
+cat > normal.sans <<EOF
+server {
+    include '${LAN_LISTEN}';
+    server_name normal;
+}
+EOF
+CONFS="${CONFS} normal:0"
+
+cat > acme.sans <<EOF
+server {
+    listen 80;
+    include '${LAN_LISTEN}';
+    server_name acme;
+}
+EOF
+CONFS="${CONFS} acme:0"
+
+cat > more_server.sans <<EOF
+server {
+    # include '${LAN_LISTEN}';
+    server_name normal;
+}
+server {
+    include '${LAN_LISTEN}';
+    server_name more_server;
+}
+EOF
+CONFS="${CONFS} more_server:0"
+
+cat > more_names.sans <<EOF
+server {
+    include '${LAN_LISTEN}';
+    include '${LAN_LISTEN}';
+    include '${LAN_LISTEN}';
+    not include '${LAN_LISTEN}';
+    server_name example.com more_names example.org;
+}
+EOF
+CONFS="${CONFS} more_names:0"
+
+cat > different_name.sans <<EOF
+server {
+    include '${LAN_LISTEN}';
+    server_name minimal;
+}
+EOF
+CONFS="${CONFS} different_name:1"
+
+cat > comments.sans <<EOF
+server { # comment1
+    # comment2
+    include '${LAN_LISTEN}';
+    server_name comments;
+    # comment3
+} # comment4
+EOF
+CONFS="${CONFS} comments:0"
+
+cat > name_comment.sans <<EOF
+server {
+    include '${LAN_LISTEN}';
+    server_name name_comment; # comment
+}
+EOF
+CONFS="${CONFS} name_comment:0"
+
+cat > tab.sans <<EOF
+server {
+	include '${LAN_LISTEN}';
+	server_name tab;
+}
+EOF
+CONFS="${CONFS} tab:0"
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nSetup files in %s ...\n" "${CONF_DIR}"
+
+
+for conf in ${CONFS}
+do test 'setpoint_add_ssl "    " '"${conf%:*}" "${conf#*:}"
+done
+
+test 'setpoint_add_ssl "\t" tab' 0 # fixes wrong indentation.
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting Cron ... \n"
+
+
+echo -n "prefix" >"/etc/crontabs/root"
+test '"${NGINX_UTIL}" add_ssl _lan' 0
+echo "postfix" >>"/etc/crontabs/root"
+test_setpoint "/etc/crontabs/root" "prefix
+3 3 12 12 * ${NGINX_UTIL} 'check_ssl'
+postfix"
+
+test '"${NGINX_UTIL}" del_ssl _lan' 0
+test_setpoint "/etc/crontabs/root" "prefix
+3 3 12 12 * ${NGINX_UTIL} 'check_ssl'
+postfix"
+
+test '"${NGINX_UTIL}" check_ssl' 0
+test_setpoint "/etc/crontabs/root" "prefix
+postfix"
+
+test '"${NGINX_UTIL}" add_ssl _lan' 0
+test_setpoint "/etc/crontabs/root" "prefix
+postfix
+3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+
+rm -f "/etc/crontabs/root"
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n'
+
+echo -n "prefix" >"/etc/crontabs/root"
+cp "minimal.sans" "minimal.conf"
+
+test '"${NGINX_UTIL}" add_ssl minimal' 0
+echo "postfix" >>"/etc/crontabs/root"
+test_setpoint "/etc/crontabs/root" "prefix
+3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'minimal'
+postfix"
+
+test '"${NGINX_UTIL}" del_ssl minimal' 0
+test_setpoint "/etc/crontabs/root" "prefix
+postfix"
+
+rm -f "/etc/crontabs/root"
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s init_lan ...\n" "${NGINX_UTIL}"
+
+
+rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf"
+rm -f "$(readlink "${UCI_CONF}")"
+
+test '"${NGINX_UTIL}" init_lan' 0
+test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)"
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\twith temporary UCI config ... \n'
+
+UCI_ADDED="$(uci add nginx server)" &&
+uci set nginx.@server[-1].server_name='temp' &&
+uci add_list nginx.@server[-1].listen='81 default_server' &&
+uci add_list nginx.@server[-1].listen='80' &&
+echo "UCI: nginx.${UCI_ADDED} added."
+
+rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf"
+rm -f "$(readlink "${UCI_CONF}")"
+
+test '"${NGINX_UTIL}" init_lan' 0
+test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)"
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n'
+
+cp "${LAN_NAME}.sans" "${LAN_NAME}.conf"
+touch "_redirect2ssl.conf" "${UCI_ADDED}.conf"
+rm -f "$(readlink "${UCI_CONF}")"
+test '"${NGINX_UTIL}" init_lan' 0
+
+skipped() {
+    printf "\t# skipped UCI server 'nginx.%s'" "$1"
+    printf " as it could conflict with: %s%s.conf\n\n" "${CONF_DIR}" "$1"
+}
+rhs="$(skipped "$LAN_NAME" && skipped _redirect2ssl && skipped "${UCI_ADDED}")"
+sed -E -e "s/^\t#UCI_HTTP_CONFIG$/$(__esc_sed_rhs "$rhs")\n/" \
+    -e 's/\\n/\n/g' -e "1i${fileauto}" "${UCI_CONF}.template" >"uci.setpoint"
+
+test_setpoint "${UCI_CONF}" "$(cat "uci.setpoint")"
+test_setpoint "/etc/crontabs/root" ""
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s add_ssl ...\n" "${NGINX_UTIL}"
+
+
+test '[ "${ADD_SSL_FCT}" = "add_ssl" ] ' 0
+
+rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf"
+rm -f "$(readlink "${UCI_CONF}")"
+test 'uci set nginx._lan.uci_manage_ssl="self-signed"' 0
+"${NGINX_UTIL}" del_ssl "${LAN_NAME}" 2>/dev/null
+test_setpoint "/etc/crontabs/root" ""
+test_existence "${LAN_NAME}.crt" 1
+test_existence "${LAN_NAME}.key" 1
+test '"${NGINX_UTIL}" add_ssl '"${UCI_ADDED}"' acme \
+    '"${CONF_DIR}${UCI_ADDED}.crt"' '"${CONF_DIR}${UCI_ADDED}.key"'     ' 0
+test_setpoint "/etc/crontabs/root" ""
+test_existence "${UCI_ADDED}.crt" 1
+test_existence "${UCI_ADDED}.key" 1
+test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+test_existence "${LAN_NAME}.crt" 0
+test_existence "${LAN_NAME}.key" 0
+test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+test '"${NGINX_UTIL}" add_ssl inexistent' 1
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+test '"${NGINX_UTIL}" init_lan' 0
+test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)"
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'"
+test_existence "${UCI_ADDED}.crt" 1
+test_existence "${UCI_ADDED}.key" 1
+test_existence "${LAN_NAME}.crt" 0
+test_existence "${LAN_NAME}.key" 0
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n'
+
+cp different_name.sans different_name.with
+
+cp "/etc/crontabs/root" "cron.setpoint"
+for conf in ${CONFS}; do
+    name="${conf%:*}"
+    [ "${name}" = "acme" ] && continue
+    [ "${name}" = "different_name" ] ||
+    echo "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' '${name}'" >>"cron.setpoint"
+    cp "${name}.sans" "${name}.conf"
+    test '"${NGINX_UTIL}" add_ssl '"${name}" "${conf#*:}"
+    test_setpoint "${name}.conf" "$(cat "${name}.with")"
+    test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+    [ "${name}" = "different_name" ] || test_existence "${name}.crt" 0
+    [ "${name}" = "different_name" ] || test_existence "${name}.key" 0
+done
+
+cp acme.sans acme.conf
+test '"${NGINX_UTIL}" add_ssl acme acme /path/to/crt /path/to/key' 0
+test_setpoint "acme.conf" "$(cat "acme.with")"
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+test_existence "acme.crt" 1
+test_existence "acme.key" 1
+
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s del_ssl ...\n" "${NGINX_UTIL}"
+
+
+sed -E -e 's/443 ssl/80/' -e '/[^2]ssl/d' "/etc/config/nginx" >"config.setpoint"
+
+cp "/etc/crontabs/root" "cron.setpoint"
+rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf"
+test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+test_existence "${LAN_NAME}.crt" 1
+test_existence "${LAN_NAME}.key" 1
+test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 1
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+
+rm -f "$(readlink "${UCI_CONF}")"
+sed -E "/$(__esc_regex "'check_ssl'")/d" "/etc/crontabs/root" >"cron.setpoint"
+test '"${NGINX_UTIL}" init_lan' 0
+test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)"
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+
+touch "${UCI_ADDED}.crt" "${UCI_ADDED}.key"
+test '"${NGINX_UTIL}" del_ssl "'${UCI_ADDED}'" acme' 0
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+test_existence "${UCI_ADDED}.crt" 0
+test_existence "${UCI_ADDED}.key" 0
+
+test '"${NGINX_UTIL}" del_ssl inexistent' 1
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+
+test_setpoint "/etc/config/nginx" "$(cat "config.setpoint")"
+test '"${NGINX_UTIL}" add_ssl "'${UCI_ADDED}'" acme \
+    '"${CONF_DIR}${UCI_ADDED}.crt"' '"${CONF_DIR}${UCI_ADDED}.key"'     ' 0
+test '"${NGINX_UTIL}" add_ssl "'$(uci get "nginx.${UCI_ADDED}.server_name")'"' 0
+test '"${NGINX_UTIL}" del_ssl "'$(uci get "nginx.${UCI_ADDED}.server_name")'"' 0
+rm -f "$(readlink "${UCI_CONF}")"
+sed -E "/$(__esc_regex "'check_ssl'")/d" "/etc/crontabs/root" >"cron.setpoint"
+test '"${NGINX_UTIL}" init_lan' 0
+test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)"
+test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+test_existence "${UCI_ADDED}.crt" 1
+test_existence "${UCI_ADDED}.key" 1
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n'
+
+for conf in ${CONFS}; do
+    name="${conf%:*}"
+    [ "${name}" = "acme" ] && continue
+    sed -E "/$(__esc_regex "'${name}'")/d" "/etc/crontabs/root" >"cron.setpoint"
+    touch "${name}.crt" "${name}.key"
+    cp "${name}.with" "${name}.conf"
+    test '"${NGINX_UTIL}" del_ssl '"${name}" "${conf#*:}"
+    test_setpoint "${name}.conf" "$(cat "${name}.sans")"
+    test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")"
+    [ "${name}" = "different_name" ] && rm "${name}.crt" "${name}.key"
+    test_existence "${name}.crt" 1
+    test_existence "${name}.key" 1
+done
+test_setpoint "/etc/crontabs/root" ""
+
+test '"${NGINX_UTIL}" del_ssl acme acme' 0
+test_existence "acme.crt" 1
+test_existence "acme.key" 1
+
+cp acme.with acme.conf
+touch acme.crt acme.key
+echo "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'acme'" >>"/etc/crontabs/root"
+test '"${NGINX_UTIL}" del_ssl acme acme' 0
+test_setpoint "acme.conf" "$(cat "acme.sans")"
+test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'acme'"
+test_existence "acme.crt" 0
+test_existence "acme.key" 0
+"${NGINX_UTIL}" del_ssl acme 2>/dev/null
+test_setpoint "/etc/crontabs/root" ""
+test_existence "acme.crt" 1
+test_existence "acme.key" 1
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting without UCI ... \n"
+
+rm -f "$(readlink "${UCI_CONF}")"
+
+test 'uci set nginx.global.uci_enable=0' 0
+
+test '"${NGINX_UTIL}" init_lan' 0
+
+test '[ -e "$(readlink '"${UCI_CONF}"')" ]' 1
+
+cp "${LAN_NAME}.sans" "${LAN_NAME}.conf"
+test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0
+test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0
+test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0
+test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0
+
+test 'rm "${LAN_NAME}.conf"' 0
+test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 1
+test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 1
+
+
+
+pst_exit 0
diff --git a/external/subpack/net/nginx-util/src/test-nginx-util.sh b/external/subpack/net/nginx-util/src/test-nginx-util.sh
new file mode 100755
index 0000000..6f8d072
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/test-nginx-util.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+printf "Initializing tests ...\n"
+
+fakechroot=""
+
+[ -x "/usr/bin/fakechroot" ] && fakechroot="/usr/bin/fakechroot" \
+|| [ "$(id -u)" -eq 0 ] || { \
+    printf "Error: Testing needs fakechroot or whoami=root for chroot."
+    return 1
+}
+
+TMPROOT="$(mktemp -d "/tmp/test-nginx-util-XXXXXX")"
+
+ln -s /bin "${TMPROOT}/bin"
+
+mkdir -p "${TMPROOT}/etc/crontabs/"
+
+mkdir -p "${TMPROOT}/etc/config/"
+cp "./config-nginx-ssl" "${TMPROOT}/etc/config/nginx"
+
+mkdir -p "${TMPROOT}/etc/nginx/"
+cp "./uci.conf.template" "${TMPROOT}/etc/nginx/uci.conf.template"
+ln -s "${TMPROOT}/var/lib/nginx/uci.conf" "${TMPROOT}/etc/nginx/uci.conf"
+
+mkdir -p "${TMPROOT}/usr/bin/"
+cp "/usr/local/bin/uci" "${TMPROOT}/usr/bin/"
+cp "./test-nginx-util-root.sh" "${TMPROOT}/usr/bin/"
+
+
+printf "\n\n******* Testing nginx-ssl-util-noubus *******\n"
+
+cp "./nginx-ssl-util-noubus" "${TMPROOT}/usr/bin/nginx-util"
+
+"${fakechroot}" /bin/chroot "${TMPROOT}" \
+    /bin/sh -c "/usr/bin/test-nginx-util-root.sh" ||
+{
+    echo "!!! Error: $?"
+    rm -r "${TMPROOT}"
+    exit 1
+}
+
+
+printf "\n\n******* Testing nginx-ssl-util-nopcre-noubus *******\n"
+
+cp "./nginx-ssl-util-nopcre-noubus" "${TMPROOT}/usr/bin/nginx-util"
+
+"${fakechroot}" /bin/chroot "${TMPROOT}" \
+    /bin/sh -c "/usr/bin/test-nginx-util-root.sh" ||
+{
+    echo "!!! Error: $?"
+    rm -r "${TMPROOT}"
+    exit 1
+}
+
+
+rm -r "${TMPROOT}"
diff --git a/external/subpack/net/nginx-util/src/test-px5g.sh b/external/subpack/net/nginx-util/src/test-px5g.sh
new file mode 100755
index 0000000..486b9ae
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/test-px5g.sh
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+PRINT_PASSED=2
+
+printf "Initializing tests ...\n"
+
+OPENSSL_PEM="$(mktemp)"
+OPENSSL_DER="$(mktemp)"
+
+NONCE=$(dd if=/dev/urandom bs=1 count=4 2>/dev/null | hexdump -e '1/1 "%02x"')
+SUBJECT="/C=ZZ/ST=Somewhere/L=None/O=OpenWrt'$NONCE'/CN=OpenWrt"
+
+openssl req -x509 -nodes -days 1 -keyout /dev/null 2>/dev/null \
+    -out "$OPENSSL_PEM" -subj "$SUBJECT" \
+|| ( printf "error: generating PEM certificate with openssl"; return 1)
+openssl req -x509 -nodes -days 1 -keyout /dev/null 2>/dev/null \
+    -out "$OPENSSL_DER" -outform der -subj "$SUBJECT" \
+|| ( printf "error: generating DER certificate with openssl"; return 1)
+
+
+test() {
+    eval "$1 >/dev/null "
+    if [ $? -eq "$2" ]
+    then
+        [ "${PRINT_PASSED}" -gt 0 ] \
+        && printf "%-72s%-1s\n" "$1" ">/dev/null (-> $2?) passed."
+    else
+        printf "%-72s%-1s\n" "$1" ">/dev/null (-> $2?) failed!!!"
+        [ "${PRINT_PASSED}" -gt 1 ] && exit 1
+    fi
+}
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting openssl itself ...\n"
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * right PEM:\n"
+test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 0                         ' 0
+test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 86300                     ' 0
+test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 86400                     ' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * right DER:\n"
+test 'cat "$OPENSSL_DER" | openssl x509 -checkend 0    -inform der          ' 0
+test 'cat "$OPENSSL_DER" | openssl x509 -checkend 86300 -inform der         ' 0
+test 'cat "$OPENSSL_DER" | openssl x509 -checkend 86400 -inform der         ' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * wrong:\n"
+test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 0 -inform der  2>/dev/null' 1
+test 'cat "$OPENSSL_DER" | openssl x509 -checkend 0              2>/dev/null' 1
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g checkend ...\n"
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * right PEM:\n"
+test 'cat "$OPENSSL_PEM" | ./px5g checkend 0                                ' 0
+test 'cat "$OPENSSL_PEM" | ./px5g checkend 86300                            ' 0
+test 'cat "$OPENSSL_PEM" | ./px5g checkend 86400                            ' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * right DER:\n"
+test 'cat "$OPENSSL_DER" | ./px5g checkend -der 0                           ' 0
+test 'cat "$OPENSSL_DER" | ./px5g checkend -der 86300                       ' 0
+test 'cat "$OPENSSL_DER" | ./px5g checkend -der 86400                       ' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * in option:\n"
+test 'cat "$OPENSSL_DER" | ./px5g checkend -in /proc/self/fd/0 -der 0       ' 0
+test 'cat "$OPENSSL_DER" | ./px5g checkend -der -in /proc/self/fd/0 99      ' 0
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * wrong:\n"
+test 'cat "$OPENSSL_PEM" | ./px5g checkend -der 0                2>/dev/null' 1
+test 'cat "$OPENSSL_DER" | ./px5g checkend 0                     2>/dev/null' 1
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g eckey ...\n"
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * standard curves:\n"
+test './px5g eckey P-256        | openssl ec -check              2>/dev/null' 0
+test './px5g eckey P-384        | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp384r1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp256r1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp256k1    | openssl ec -check              2>/dev/null' 0
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * more curves:\n"
+test './px5g eckey P-521        | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp521r1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp224r1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp224k1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp192r1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey secp192k1    | openssl ec -check              2>/dev/null' 0
+test './px5g eckey brainpoolP512r1        | openssl ec -check    2>/dev/null' 0
+test './px5g eckey brainpoolP384r1        | openssl ec -check    2>/dev/null' 0
+test './px5g eckey brainpoolP256r1        | openssl ec -check    2>/dev/null' 0
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * other options:\n"
+test './px5g eckey -out /proc/self/fd/1   | openssl ec -check    2>/dev/null' 0
+test './px5g eckey -der         | openssl ec -check -inform der  2>/dev/null' 0
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g rsakey ...\n"
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * standard exponent:\n"
+test './px5g rsakey             | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 512         | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 1024        | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 2048        | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 4096        | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 1111        | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey 0                                            2>/dev/null' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * small exponent:\n"
+test './px5g rsakey -3          | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 512      | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 1024     | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 2048     | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 4096     | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 1111     | openssl rsa -check             2>/dev/null' 0
+test './px5g rsakey -3 0                                         2>/dev/null' 1
+
+[ "$PRINT_PASSED" -gt 1 ] && printf "  * other options:\n"
+test './px5g rsakey -out /proc/self/fd/1  | openssl rsa -check   2>/dev/null' 0
+test './px5g rsakey -der        | openssl rsa -check -inform der 2>/dev/null' 0
+
+
+[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g selfsigned ...\n"
+
+test './px5g selfsigned -der | openssl x509 -checkend 0 -inform der         ' 0
+test './px5g selfsigned -days 1               | openssl x509 -checkend 0    ' 0
+test './px5g selfsigned -days 1               | openssl x509 -checkend 86300' 0
+test './px5g selfsigned -days 1               | openssl x509 -checkend 86400' 1
+test './px5g selfsigned -out /proc/self/fd/1  | openssl x509 -checkend 0    ' 0
+test './px5g selfsigned -newkey rsa:666       | openssl x509 -checkend 0    ' 0
+test './px5g selfsigned -newkey ec            | openssl x509 -checkend 0    ' 0
+test './px5g selfsigned -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 \
+      | openssl x509 -checkend 0                                        ' 0
+test './px5g selfsigned -subj "$SUBJECT" | openssl x509 -noout \
+      -subject -nameopt compat | grep -q subject="$SUBJECT" 2>/dev/null' 0
+test './px5g selfsigned -out /dev/null -keyout /proc/self/fd/1 \
+      | openssl rsa -check 2>/dev/null                                  ' 0
+
+
+rm "$OPENSSL_PEM" "$OPENSSL_DER"
diff --git a/external/subpack/net/nginx-util/src/ubus-cxx.cpp b/external/subpack/net/nginx-util/src/ubus-cxx.cpp
new file mode 100644
index 0000000..5fa29c7
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/ubus-cxx.cpp
@@ -0,0 +1,140 @@
+#include <iostream>
+
+#include "ubus-cxx.hpp"
+
+inline void example_for_checking_if_there_is_a_key()
+{
+    if (ubus::call("service", "list").filter("cron")) {
+        std::cout << "Cron is active (with or without instances) " << std::endl;
+    }
+}
+
+inline void example_for_getting_values()
+{
+    auto lan_status = ubus::call("network.interface.lan", "status");
+    for (const auto* t : lan_status.filter("ipv6-address", "", "address")) {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+        auto* x = const_cast<blob_attr*>(t);
+        std::cout << "[" << blobmsg_get_string(x) << "] ";
+    }
+    for (const auto* t : lan_status.filter("ipv4-address", "").filter("address")) {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+        auto* x = const_cast<blob_attr*>(t);
+        std::cout << blobmsg_get_string(x) << " ";
+    }
+    std::cout << std::endl;
+}
+
+inline void example_for_sending_message()
+{
+    auto set_arg = [](blob_buf* buf) -> int { return blobmsg_add_string(buf, "config", "nginx"); };
+    for (const auto* t : ubus::call("uci", "get", set_arg).filter("values")) {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+        auto* x = const_cast<blob_attr*>(t);
+        std::cout << blobmsg_get_string(x) << "\n";
+    }
+}
+
+inline void example_for_exploring()
+{
+    ubus::strings keys{"ipv4-address", "", ""};
+    for (const auto* t : ubus::call("network.interface.lan", "status").filter(keys)) {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+        auto* x = const_cast<blob_attr*>(t);
+        std::cout << blobmsg_name(x) << ": ";
+        switch (blob_id(x)) {
+            case BLOBMSG_TYPE_UNSPEC: std::cout << "[unspecified]"; break;
+            case BLOBMSG_TYPE_ARRAY: std::cout << "[array]"; break;
+            case BLOBMSG_TYPE_TABLE: std::cout << "[table]"; break;
+            case BLOBMSG_TYPE_STRING: std::cout << blobmsg_get_string(x); break;
+            case BLOBMSG_TYPE_INT64: std::cout << blobmsg_get_u64(x); break;
+            case BLOBMSG_TYPE_INT32: std::cout << blobmsg_get_u32(x); break;
+            case BLOBMSG_TYPE_INT16: std::cout << blobmsg_get_u16(x); break;
+            case BLOBMSG_TYPE_BOOL: std::cout << blobmsg_get_bool(x); break;
+            case BLOBMSG_TYPE_DOUBLE: std::cout << blobmsg_get_double(x); break;
+            default: std::cout << "[unknown]";
+        }
+        std::cout << std::endl;
+    }
+}
+
+inline void example_for_recursive_exploring()
+{  // output like from the original ubus call:
+    const auto explore = [](auto message) -> void {
+        auto end = message.end();
+        auto explore_internal = [&end](auto& explore_ref, auto it, size_t depth = 1) -> void {
+            std::cout << std::endl;
+            bool first = true;
+            for (; it != end; ++it) {
+                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+                auto* attr = const_cast<blob_attr*>(*it);
+                if (first) {
+                    first = false;
+                }
+                else {
+                    std::cout << ",\n";
+                }
+                std::cout << std::string(depth, '\t');
+                std::string name = blobmsg_name(attr);
+                if (!name.empty()) {
+                    std::cout << "\"" << name << "\": ";
+                }
+                switch (blob_id(attr)) {
+                    case BLOBMSG_TYPE_UNSPEC: std::cout << "(unspecified)"; break;
+                    case BLOBMSG_TYPE_ARRAY:
+                        std::cout << "[";
+                        explore_ref(explore_ref, ubus::iterator{attr}, depth + 1);
+                        std::cout << "\n" << std::string(depth, '\t') << "]";
+                        break;
+                    case BLOBMSG_TYPE_TABLE:
+                        std::cout << "{";
+                        explore_ref(explore_ref, ubus::iterator{attr}, depth + 1);
+                        std::cout << "\n" << std::string(depth, '\t') << "}";
+                        break;
+                    case BLOBMSG_TYPE_STRING:
+                        std::cout << "\"" << blobmsg_get_string(attr) << "\"";
+                        break;
+                    case BLOBMSG_TYPE_INT64: std::cout << blobmsg_get_u64(attr); break;
+                    case BLOBMSG_TYPE_INT32: std::cout << blobmsg_get_u32(attr); break;
+                    case BLOBMSG_TYPE_INT16: std::cout << blobmsg_get_u16(attr); break;
+                    case BLOBMSG_TYPE_BOOL:
+                        std::cout << (blobmsg_get_bool(attr) ? "true" : "false");
+                        break;
+                    case BLOBMSG_TYPE_DOUBLE: std::cout << blobmsg_get_double(attr); break;
+                    default: std::cout << "(unknown)"; break;
+                }
+            }
+        };
+        std::cout << "{";
+        explore_internal(explore_internal, message.begin());
+        std::cout << "\n}" << std::endl;
+    };
+    explore(ubus::call("network.interface.lan", "status"));
+}
+
+auto main() -> int
+{
+    try {
+        example_for_checking_if_there_is_a_key();
+
+        example_for_getting_values();
+
+        example_for_sending_message();
+
+        example_for_exploring();
+
+        example_for_recursive_exploring();
+
+        return 0;
+    }
+
+    catch (const std::exception& e) {
+        std::cerr << e.what() << std::endl;
+    }
+
+    catch (...) {
+        perror("main error");
+    }
+
+    return 1;
+}
diff --git a/external/subpack/net/nginx-util/src/ubus-cxx.hpp b/external/subpack/net/nginx-util/src/ubus-cxx.hpp
new file mode 100644
index 0000000..6c193cf
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/ubus-cxx.hpp
@@ -0,0 +1,354 @@
+#ifndef _UBUS_CXX_HPP
+#define _UBUS_CXX_HPP
+
+#include <libubus.h>
+#include <cassert>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+
+#ifndef NDEBUG
+#include <iostream>
+#endif
+
+namespace ubus {
+
+static constexpr int call_timeout = 500;
+
+using msg_ptr = std::shared_ptr<const blob_attr>;
+
+using strings = std::vector<std::string>;
+
+inline auto concat(strings dest)
+{
+    return dest;
+}
+
+template <class... Strings>
+inline auto concat(strings dest, strings src, Strings... more)
+{
+    dest.reserve(dest.size() + src.size());
+    dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)),
+                std::make_move_iterator(std::end(src)));
+    return concat(std::move(dest), std::move(more)...);
+}
+
+template <class S, class... Strings>
+inline auto concat(strings dest, S src, Strings... more)
+{
+    dest.emplace_back(std::move(src));
+    return concat(std::move(dest), std::move(more)...);
+}
+
+class iterator {
+  private:
+    const strings& keys;
+
+    const size_t n = 0;
+
+    size_t i = 0;
+
+    const blob_attr* pos = nullptr;
+
+    std::unique_ptr<iterator> cur{};
+
+    iterator* parent = nullptr;
+
+    size_t rem = 0;
+
+    [[nodiscard]] inline auto matches() const -> bool
+    {
+        return (keys[i].empty() || blobmsg_name(cur->pos) == keys[i]);
+    }
+
+    explicit iterator(iterator* par)
+        : keys{par->keys}, n{par->n}, pos{par->pos}, cur{this}, parent{par}
+    {
+        if (pos != nullptr) {
+            rem = blobmsg_data_len(pos);
+            pos = static_cast<blob_attr*>(blobmsg_data(pos));
+        }
+    }
+
+  public:
+    explicit iterator(const blob_attr* msg, const strings& key_filter = {""})
+        : keys{key_filter}, n{keys.size() - 1}, pos{msg}, cur{this}
+    {
+        if (pos != nullptr) {
+            rem = blobmsg_data_len(pos);
+            pos = static_cast<blob_attr*>(blobmsg_data(pos));
+
+            if (rem == 0) {
+                pos = nullptr;
+            }
+            else if (i != n || !matches()) {
+                ++*this;
+            }
+        }
+    }
+
+    inline iterator(iterator&&) noexcept = default;
+
+    inline iterator(const iterator&) = delete;
+
+    inline auto operator=(const iterator&) -> iterator& = delete;
+
+    inline auto operator=(iterator &&) -> iterator& = delete;
+
+    inline auto operator*()
+    {
+        return cur->pos;
+    }
+
+    inline auto operator!=(const iterator& rhs)
+    {
+        return (cur->rem != rhs.cur->rem || cur->pos != rhs.cur->pos);
+    }
+
+    auto operator++() -> iterator&;
+
+    inline ~iterator()
+    {
+        if (cur.get() == this) {
+            static_cast<void>(cur.release());
+        }
+    }
+};
+
+class message {
+  private:
+    const msg_ptr msg{};  // initialized by callback.
+
+    const strings keys{};
+
+  public:
+    inline explicit message(msg_ptr message_ptr, strings key_filter = {""})
+        : msg{std::move(message_ptr)}, keys{std::move(key_filter)}
+    {}
+
+    inline message(message&&) = default;
+
+    inline message(const message&) = delete;
+
+    inline auto operator=(message &&) -> message& = delete;
+
+    inline auto operator=(const message&) -> message& = delete;
+
+    [[nodiscard]] inline auto begin() const -> iterator
+    {
+        return iterator{msg.get(), keys};
+    }
+
+    [[nodiscard]] inline auto end() const -> iterator
+    {
+        return iterator{nullptr, keys};
+    }
+
+    inline explicit operator bool() const
+    {
+        return begin() != end();
+    }
+
+    template <class... Strings>
+    auto filter(Strings... key_filter)
+    {
+        strings both{};
+        if (keys.size() != 1 || !keys[0].empty()) {
+            both = keys;
+        }
+        both = concat(std::move(both), std::move(key_filter)...);
+        return std::move(message{msg, std::move(both)});
+    }
+
+    inline ~message() = default;
+};
+
+class lock_shared_resources {
+  private:
+    static std::mutex inuse;
+
+  public:
+    inline lock_shared_resources()
+    {
+        inuse.lock();
+    }
+
+    inline lock_shared_resources(lock_shared_resources&&) noexcept = default;
+
+    inline lock_shared_resources(const lock_shared_resources&) = delete;
+
+    inline auto operator=(const lock_shared_resources&) -> auto& = delete;
+
+    inline auto operator=(lock_shared_resources &&) -> auto&& = delete;
+
+    // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+    inline auto get_context() -> ubus_context*  // is member to enforce inuse.
+    {
+        static auto ubus_freeing = [](ubus_context* ctx) { ubus_free(ctx); };
+        static std::unique_ptr<ubus_context, decltype(ubus_freeing)> lazy_ctx{ubus_connect(nullptr),
+                                                                              ubus_freeing};
+
+        if (!lazy_ctx) {  // it could be available on a later call:
+
+            lazy_ctx.reset(ubus_connect(nullptr));
+
+            if (!lazy_ctx) {
+                throw std::runtime_error("ubus error: cannot connect context");
+            }
+        }
+
+        return lazy_ctx.get();
+    }
+
+    // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+    inline auto get_blob_buf() -> blob_buf*  // is member to enforce inuse.
+    {
+        static blob_buf buf;
+
+        static auto blob_buf_freeing = [](blob_buf* b) { blob_buf_free(b); };
+        static std::unique_ptr<blob_buf, decltype(blob_buf_freeing)>
+            created_to_free_on_the_end_of_life{&buf, blob_buf_freeing};
+
+        blob_buf_init(&buf, 0);
+
+        return &buf;
+    }
+
+    inline ~lock_shared_resources()
+    {
+        inuse.unlock();
+    }
+};
+
+template <class F>
+auto call(const char* path, const char* method, F set_arguments, int timeout = call_timeout)
+    -> message;
+
+inline auto call(const char* path, const char* method, int timeout = call_timeout) -> message
+{
+    return call(
+        path, method, [](blob_buf* /*buf*/) { return 0; }, timeout);
+}
+
+inline auto call(const char* path, int timeout = call_timeout) -> message
+{
+    return call(path, "", timeout);
+}
+
+// ------------------------- implementation: ----------------------------------
+
+std::mutex lock_shared_resources::inuse;
+
+inline auto iterator::operator++() -> iterator&
+{
+    for (;;) {
+#ifndef NDEBUG
+        std::cout << std::string(i, '>') << " look for " << keys[i] << " at ";
+        std::cout << blobmsg_name(cur->pos) << std::endl;
+#endif
+
+        auto id = blob_id(cur->pos);
+        if ((id == BLOBMSG_TYPE_TABLE || id == BLOBMSG_TYPE_ARRAY) && i < n && matches() &&
+            blobmsg_data_len(cur->pos) > 0)
+        {  // immmerge:
+            ++i;
+
+            auto* tmp = cur.release();
+
+            struct new_iterator : public iterator  // use private constructor:
+            {
+                explicit new_iterator(iterator* par) : iterator{par} {}
+            };
+            cur = std::make_unique<new_iterator>(tmp);
+        }
+        else {
+            while (true) {
+                cur->rem -= blob_pad_len(cur->pos);
+                cur->pos = blob_next(cur->pos);
+                auto len = blob_pad_len(cur->pos);
+
+                if (cur->rem > 0 && len <= cur->rem && len >= sizeof(blob_attr)) {
+                    break;
+                }
+
+                // emerge:
+                auto* tmp = cur->parent;
+
+                if (tmp == nullptr) {
+                    cur->pos = nullptr;
+                    return *cur;
+                }
+
+                cur.reset(tmp);
+
+                --i;
+            }
+        }
+        if (i == n && matches()) {
+            return *cur;
+        }
+    }
+}
+
+template <class F>
+inline auto call(const char* path, const char* method, F set_arguments, int timeout) -> message
+{
+    auto shared = lock_shared_resources{};
+
+    auto* ctx = shared.get_context();
+
+    uint32_t id = 0;
+    int err = ubus_lookup_id(ctx, path, &id);
+
+    if (err == 0) {  // call
+        ubus_request request{};
+
+        auto* buf = shared.get_blob_buf();
+        err = set_arguments(buf);
+        if (err == 0) {
+            err = ubus_invoke_async(ctx, id, method, buf->head, &request);
+        }
+
+        if (err == 0) {
+            msg_ptr message_ptr;
+
+            /* Cannot capture message_ptr, the lambda would be another type.
+             * Pass a location where to save the message as priv pointer when
+             * invoking and get it back here:
+             */
+            request.priv = &message_ptr;
+
+            request.data_cb = [](ubus_request* req, int /*type*/, blob_attr* msg) {
+                if (req == nullptr || msg == nullptr) {
+                    return;
+                }
+
+                auto* saved = static_cast<msg_ptr*>(req->priv);
+                if (saved == nullptr || *saved) {
+                    return;
+                }
+
+                saved->reset(blob_memdup(msg), free);
+                if (!*saved) {
+                    throw std::bad_alloc();
+                }
+            };
+
+            err = ubus_complete_request(ctx, &request, timeout);
+
+            if (err == 0) {
+                return message{message_ptr};
+            }
+        }
+    }
+
+    std::string errmsg = "ubus::call error: cannot invoke";
+    errmsg += " (" + std::to_string(err) + ") " + path + " " + method;
+    throw std::runtime_error(errmsg);
+}
+
+}  // namespace ubus
+
+#endif
diff --git a/external/subpack/net/nginx-util/src/uci-cxx.cpp b/external/subpack/net/nginx-util/src/uci-cxx.cpp
new file mode 100644
index 0000000..c880548
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/uci-cxx.cpp
@@ -0,0 +1,22 @@
+#include <iostream>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "uci-cxx.hpp"
+
+auto main() -> int
+{
+    uci::element p = uci::package{"nginx"};
+    std::cout << "package " << p.name() << "\n\n";
+    for (auto s : p) {
+        std::cout << "config " << s.type() << " '" << s.name() << "'\n";
+        for (auto o : s) {
+            for (auto i : o) {
+                std::cout << "\t" << o.type() << " " << o.name() << " '" << i.name() << "'\n";
+            }
+        }
+        std::cout << "\n";
+    }
+}
diff --git a/external/subpack/net/nginx-util/src/uci-cxx.hpp b/external/subpack/net/nginx-util/src/uci-cxx.hpp
new file mode 100644
index 0000000..4598890
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/uci-cxx.hpp
@@ -0,0 +1,474 @@
+#ifndef _UCI_CXX_HPP
+#define _UCI_CXX_HPP
+
+#include <uci.h>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+
+namespace uci {
+
+template <class T>
+class iterator {  // like uci_foreach_element_safe.
+
+  private:
+    const uci_ptr& _ptr;
+
+    uci_element* _it = nullptr;
+
+    uci_element* _next = nullptr;
+
+    // wrapper for clang-tidy
+    inline auto _list_to_element(const uci_list* cur) -> uci_element*
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        return list_to_element(cur);  // macro casting container=pointer-offset.
+    }
+
+  public:
+    inline explicit iterator(const uci_ptr& ptr, const uci_list* cur)
+        : _ptr{ptr}, _it{_list_to_element(cur)}
+    {
+        _next = _list_to_element(_it->list.next);
+    }
+
+    inline iterator(iterator&&) noexcept = default;
+
+    inline iterator(const iterator&) = delete;
+
+    inline auto operator=(const iterator&) -> iterator& = delete;
+
+    inline auto operator=(iterator &&) -> iterator& = delete;
+
+    auto operator*() -> T
+    {
+        return T{_ptr, _it};
+    }
+
+    inline auto operator!=(const iterator& rhs) -> bool
+    {
+        return (&_it->list != &rhs._it->list);
+    }
+
+    inline auto operator++() -> iterator&
+    {
+        _it = _next;
+        _next = _list_to_element(_next->list.next);
+        return *this;
+    }
+
+    inline ~iterator() = default;
+};
+
+class locked_context {
+  private:
+    static std::mutex inuse;
+
+  public:
+    inline locked_context()
+    {
+        inuse.lock();
+    }
+
+    inline locked_context(locked_context&&) noexcept = default;
+
+    inline locked_context(const locked_context&) = delete;
+
+    inline auto operator=(const locked_context&) -> locked_context& = delete;
+
+    inline auto operator=(locked_context &&) -> locked_context& = delete;
+
+    // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+    inline auto get() -> uci_context*  // is member to enforce inuse
+    {
+        static auto free_ctx = [](uci_context* ctx) { uci_free_context(ctx); };
+        static std::unique_ptr<uci_context, decltype(free_ctx)> lazy_ctx{uci_alloc_context(),
+                                                                         free_ctx};
+
+        if (!lazy_ctx) {  // it could be available on a later call:
+            lazy_ctx.reset(uci_alloc_context());
+            if (!lazy_ctx) {
+                throw std::runtime_error("uci error: cannot allocate context");
+            }
+        }
+
+        return lazy_ctx.get();
+    }
+
+    inline ~locked_context()
+    {
+        inuse.unlock();
+    }
+};
+
+template <class T>
+class element {
+  private:
+    uci_list* _begin = nullptr;
+
+    uci_list* _end = nullptr;
+
+    uci_ptr _ptr{};
+
+  protected:
+    [[nodiscard]] inline auto ptr() -> uci_ptr&
+    {
+        return _ptr;
+    }
+
+    [[nodiscard]] inline auto ptr() const -> const uci_ptr&
+    {
+        return _ptr;
+    }
+
+    void init_begin_end(uci_list* begin, uci_list* end)
+    {
+        _begin = begin;
+        _end = end;
+    }
+
+    inline explicit element(const uci_ptr& pre, uci_element* last) : _ptr{pre}
+    {
+        _ptr.last = last;
+    }
+
+    inline explicit element() = default;
+
+  public:
+    inline element(element&&) noexcept = default;
+
+    inline element(const element&) = delete;
+
+    inline auto operator=(const element&) -> element& = delete;
+
+    inline auto operator=(element &&) -> element& = delete;
+
+    auto operator[](std::string_view key) const -> T;
+
+    [[nodiscard]] inline auto name() const -> std::string
+    {
+        return _ptr.last->name;
+    }
+
+    void rename(const char* value) const;
+
+    void commit() const;
+
+    [[nodiscard]] inline auto begin() const -> iterator<T>
+    {
+        return iterator<T>{_ptr, _begin};
+    }
+
+    [[nodiscard]] inline auto end() const -> iterator<T>
+    {
+        return iterator<T>{_ptr, _end};
+    }
+
+    inline ~element() = default;
+};
+
+class section;
+
+class option;
+
+class item;
+
+class package : public element<section> {
+  public:
+    inline package(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().p = uci_to_package(ptr().last);  // macro casting pointer-offset.
+        ptr().package = ptr().last->name;
+
+        auto* end = &ptr().p->sections;
+        auto* begin = end->next;
+        init_begin_end(begin, end);
+    }
+
+    explicit package(const char* name);
+
+    auto set(const char* key, const char* type) const -> section;
+};
+
+class section : public element<option> {
+  public:
+    inline section(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().s = uci_to_section(ptr().last);  // macro casting pointer-offset.
+        ptr().section = ptr().last->name;
+
+        auto* end = &ptr().s->options;
+        auto* begin = end->next;
+        init_begin_end(begin, end);
+    }
+
+    auto set(const char* key, const char* value) const -> option;
+
+    void del();
+
+    [[nodiscard]] inline auto anonymous() const -> bool
+    {
+        return ptr().s->anonymous;
+    }
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return ptr().s->type;
+    }
+};
+
+class option : public element<item> {
+  public:
+    inline option(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast)
+        ptr().o = uci_to_option(ptr().last);  // macro casting pointer-offset.
+        ptr().option = ptr().last->name;
+
+        if (ptr().o->type == UCI_TYPE_LIST) {  // use union ptr().o->v as list:
+            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
+            auto* end = &ptr().o->v.list;
+            auto* begin = end->next;
+            init_begin_end(begin, end);
+        }
+        else {
+            auto* begin = &ptr().last->list;
+            auto* end = begin->next;
+            init_begin_end(begin, end);
+        }
+    }
+
+    void del();
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option");
+    }
+};
+
+class item : public element<item> {
+  public:
+    inline item(const uci_ptr& pre, uci_element* last) : element{pre, last}
+    {
+        ptr().value = ptr().last->name;
+    }
+
+    [[nodiscard]] inline auto type() const -> std::string
+    {
+        return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option");
+    }
+
+    [[nodiscard]] inline auto name() const -> std::string
+    {
+        return (ptr().last->type == UCI_TYPE_ITEM
+                    ? ptr().last->name
+                    :
+                    // else: use union ptr().o->v as string:
+                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
+                    ptr().o->v.string);
+    }
+
+    inline explicit operator bool() const
+    {
+        const auto x = std::string_view{name()};
+
+        if (x == "0" || x == "off" || x == "false" || x == "no" || x == "disabled") {
+            return false;
+        }
+
+        if (x == "1" || x == "on" || x == "true" || x == "yes" || x == "enabled") {
+            return true;
+        }
+
+        auto errmsg = std::string{"uci_error: item is not bool "} + name();
+        throw std::runtime_error(errmsg);
+    }
+
+    void rename(const char* value) const;
+};
+
+// ------------------------- implementation: ----------------------------------
+
+std::mutex locked_context::inuse{};
+
+inline auto uci_error(uci_context* ctx, const char* prefix = nullptr) -> std::runtime_error
+{
+    char* errmsg = nullptr;
+    uci_get_errorstr(ctx, &errmsg, prefix);
+
+    std::unique_ptr<char, decltype(&std::free)> auto_free{errmsg, std::free};
+    return std::runtime_error{errmsg};
+}
+
+template <class T>
+auto element<T>::operator[](std::string_view key) const -> T
+{
+    for (auto elmt : *this) {
+        if (elmt.name() == key) {
+            return elmt;
+        }
+    }
+
+    auto errmsg = std::string{"uci error: cannot find "}.append(key);
+    throw uci_error(locked_context{}.get(), errmsg.c_str());
+}
+
+template <class T>
+void element<T>::rename(const char* value) const
+{
+    if (value == name()) {
+        return;
+    }
+
+    auto ctx = locked_context{};
+    auto tmp_ptr = uci_ptr{_ptr};
+    tmp_ptr.value = value;
+    if (uci_rename(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename "}.append(name());
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+template <class T>
+void element<T>::commit() const
+{
+    auto ctx = locked_context{};
+    // TODO(pst) use when possible:
+    // if (uci_commit(ctx.get(), &_ptr.p, true) != 0) {
+    //    auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package;
+    //    throw uci_error(ctx.get(), errmsg.c_str());
+    // }
+    auto err = uci_save(ctx.get(), _ptr.p);
+    if (err == 0) {
+        uci_package* tmp_pkg = nullptr;
+        uci_context* tmp_ctx = uci_alloc_context();
+        err = (tmp_ctx == nullptr ? 1 : 0);
+        if (err == 0) {
+            err = uci_load(tmp_ctx, _ptr.package, &tmp_pkg);
+        }
+        if (err == 0) {
+            err = uci_commit(tmp_ctx, &tmp_pkg, false);
+        }
+        if (err == 0) {
+            err = uci_unload(tmp_ctx, tmp_pkg);
+        }
+        if (tmp_ctx != nullptr) {
+            uci_free_context(tmp_ctx);
+        }
+    }
+
+    if (err != 0) {
+        auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package;
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+package::package(const char* name)
+{
+    auto ctx = locked_context{};
+
+    auto* pkg = uci_lookup_package(ctx.get(), name);
+    if (pkg == nullptr) {
+        if (uci_load(ctx.get(), name, &pkg) != 0) {
+            auto errmsg = std::string{"uci error: cannot load package "} + name;
+            throw uci_error(ctx.get(), errmsg.c_str());
+        }
+    }
+
+    ptr().package = name;
+    ptr().p = pkg;
+    ptr().last = &pkg->e;
+
+    auto* end = &ptr().p->sections;
+    auto* begin = end->next;
+    init_begin_end(begin, end);
+}
+
+auto package::set(const char* key, const char* type) const -> section
+{
+    auto ctx = locked_context{};
+
+    auto tmp_ptr = uci_ptr{ptr()};
+    tmp_ptr.section = key;
+    tmp_ptr.value = type;
+    if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot set section "} + type + "'" + key +
+                      "' in package " + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    return section{ptr(), tmp_ptr.last};
+}
+
+auto section::set(const char* key, const char* value) const -> option
+{
+    auto ctx = locked_context{};
+
+    auto tmp_ptr = uci_ptr{ptr()};
+    tmp_ptr.option = key;
+    tmp_ptr.value = value;
+    if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot set option "} + key + "'" + value +
+                      "' in package " + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    return option{ptr(), tmp_ptr.last};
+}
+
+void section::del()
+{
+    auto ctx = locked_context{};
+    if (uci_delete(ctx.get(), &ptr()) != 0) {
+        auto errmsg = std::string{"uci error: cannot delete section "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+void option::del()
+{
+    auto ctx = locked_context{};
+    if (uci_delete(ctx.get(), &ptr()) != 0) {
+        auto errmsg = std::string{"uci error: cannot delete option "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+void item::rename(const char* value) const
+{
+    if (value == name()) {
+        return;
+    }
+
+    auto ctx = locked_context{};
+    auto tmp_ptr = uci_ptr{ptr()};
+
+    if (tmp_ptr.last->type != UCI_TYPE_ITEM) {
+        tmp_ptr.value = value;
+        if (uci_set(ctx.get(), &tmp_ptr) != 0) {
+            auto errmsg = std::string{"uci error: cannot rename item "} + name();
+            throw uci_error(ctx.get(), errmsg.c_str());
+        }
+        return;
+    }  // else:
+
+    tmp_ptr.value = tmp_ptr.last->name;
+    if (uci_del_list(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename (del) "} + name();
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+
+    tmp_ptr.value = value;
+    if (uci_add_list(ctx.get(), &tmp_ptr) != 0) {
+        auto errmsg = std::string{"uci error: cannot rename (add) "} + value;
+        throw uci_error(ctx.get(), errmsg.c_str());
+    }
+}
+
+}  // namespace uci
+
+#endif