uacme: use acme-common
authorSeo Suchan <redacted>
Wed, 25 Feb 2026 19:39:13 +0000 (04:39 +0900)
committerAlexandru Ardelean <redacted>
Tue, 14 Apr 2026 07:38:06 +0000 (10:38 +0300)
remake uacme hook scripts to base on acme-common,
and implements helper to able to use acme.sh DNS APIs

Signed-off-by: Seo Suchan <redacted>
net/acme-acmesh/Makefile
net/uacme/Makefile
net/uacme/files/acme.config [deleted file]
net/uacme/files/acme.init [deleted file]
net/uacme/files/dnsapi_helper.sh [new file with mode: 0755]
net/uacme/files/dnschalhook.sh [new file with mode: 0755]
net/uacme/files/hook.sh [new file with mode: 0755]
net/uacme/files/httpchalhook.sh [new file with mode: 0755]
net/uacme/files/run.sh [deleted file]

index 637899749f442c9b7f88cf16ae715cbaba43b0e9..fcbb563fce9a9f52dde350f04df678b4982256d8 100644 (file)
@@ -51,7 +51,7 @@ endef
 define Package/acme-acmesh-dnsapi
   SECTION:=net
   CATEGORY:=Network
-  DEPENDS:=+acme-common +PACKAGE_uacme:curl
+  DEPENDS:=+acme
   TITLE:=DNS API integration for ACME (Letsencrypt) client
   PKGARCH:=all
 endef
index 01ed9886a624eee7967e91e2075c2cbe3a4a7f7e..7f348613df7d6e3b3969808eeb83d59fbf647e85 100644 (file)
@@ -19,7 +19,7 @@ PKG_MAINTAINER:=Lucian Cristian <lucian.cristian@gmail.com>
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_LICENSE_FILES:=COPYING
 
-PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-upstream-$(PKG_VERSION)
+PKG_BUILD_DIR:=$(BUILD_DIR)/uacme-upstream-$(PKG_VERSION)
 PKG_INSTALL:=1
 PKG_BUILD_PARALLEL:=1
 
@@ -46,7 +46,7 @@ define Package/uacme
   $(call Package/uacme/Default)
   SECTION:=net
   CATEGORY:=Network
-  DEPENDS:=+libcurl +LIBCURL_WOLFSSL:libmbedtls
+  DEPENDS:=+libcurl +LIBCURL_WOLFSSL:libmbedtls +acme-common
   TITLE:=lightweight client for ACMEv2
   Menu:=1
 endef
@@ -58,6 +58,12 @@ define Package/uacme-ualpn
   URL:=https://github.com/ndilieto/uacme
 endef
 
+define Package/uacme-dnsapi-adapter
+  $(call Package/uacme/Default)
+  DEPENDS:= +uacme +acme-acmesh-dnsapi +curl
+  TITLE:=adapter script for use acme.sh dnsapi with uacme
+endef
+
 define Package/uacme/Default/description
   lightweight client for the RFC8555 ACMEv2 protocol, written in plain C code
   with minimal dependencies (libcurl and one of GnuTLS, OpenSSL or mbedTLS).
@@ -100,30 +106,30 @@ define Package/uacme/install
        $(INSTALL_DIR) \
         $(1)/usr/sbin \
         $(1)/etc/acme \
-        $(1)/etc/config \
-        $(1)/etc/init.d \
-        $(1)/usr/share/uacme
+        $(1)/usr/share/uacme \
+        $(1)/usr/lib/acme/client
 
+       $(INSTALL_BIN) ./files/hook.sh $(1)/usr/lib/acme/hook
+       $(INSTALL_BIN) ./files/httpchalhook.sh $(1)/usr/lib/acme/client/httpchalhook.sh
        $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/uacme $(1)/usr/sbin/uacme
-       $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/share/uacme/uacme.sh $(1)/usr/share/uacme/
-       $(SED) '/^CHALLENGE_PATH=/d' $(1)/usr/share/uacme/uacme.sh
-       $(INSTALL_CONF) ./files/acme.config $(1)/etc/config/acme
-       $(INSTALL_BIN) ./files/run.sh $(1)/usr/share/uacme/run-uacme
-       $(INSTALL_BIN) ./files/acme.init $(1)/etc/init.d/acme
 endef
 
 define Package/uacme-ualpn/install
        $(INSTALL_DIR) \
         $(1)/usr/sbin \
-        $(1)/usr/share/uacme
+        $(1)/usr/share/uacme \
+        $(1)/usr/lib/acme/client
 
        $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/ualpn $(1)/usr/sbin/ualpn
-       $(INSTALL_BIN) $(PKG_BUILD_DIR)/ualpn.sh $(1)/usr/share/uacme/
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/ualpn.sh $(1)/usr/lib/acme/client/ualpn.sh
 endef
 
-define Package/uacme/prerm
-#!/bin/sh
-sed -i '/\/etc\/init\.d\/acme start/d' /etc/crontabs/root
+define Package/uacme-dnsapi-adapter/install
+       $(INSTALL_DIR) \
+        $(1)/usr/lib/acme/client
+
+  $(INSTALL_BIN) ./files/dnschalhook.sh $(1)/usr/lib/acme/client/dnschalhook.sh
+  $(INSTALL_BIN) ./files/dnsapi_helper.sh $(1)/usr/lib/acme/client/dnsapi_helper.sh
 endef
 
 $(eval $(call BuildPackage,uacme))
diff --git a/net/uacme/files/acme.config b/net/uacme/files/acme.config
deleted file mode 100644 (file)
index f79b907..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-config acme
-       option state_dir '/etc/acme'
-       option account_email 'email@example.org'
-       option debug 0
-
-config cert 'example'
-       option enabled 0
-       option use_staging 1
-       option keylength 2048
-       option update_uhttpd 1
-       option update_nginx 1
-       option update_haproxy 1
-       option webroot "/www/.well-known/acme-challenge"
-       # option user_setup "path-to-custom-setup.script"
-       # option user_cleanup "path-to-custom-cleanup.script"
-       list domains example.org
diff --git a/net/uacme/files/acme.init b/net/uacme/files/acme.init
deleted file mode 100644 (file)
index f32a6ad..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/sh /etc/rc.common
-
-USE_PROCD=1
-
-START=50
-SCRIPT=/usr/share/uacme/run-uacme
-
-start_service()
-{
-    procd_open_instance
-    procd_set_param command $SCRIPT
-    procd_set_param file /etc/config/acme
-    procd_set_param stdout 1
-    procd_set_param stderr 1
-    procd_close_instance
-}
-
-reload_service() {
-    rc_procd start_service "$@"
-    return 0
-}
-
-stop_service() {
-    return 0
-}
-
-boot() {
-    touch "/var/run/uacme_boot"
-    start
-}
-
-service_triggers()
-{
-    procd_add_reload_trigger acme
-}
diff --git a/net/uacme/files/dnsapi_helper.sh b/net/uacme/files/dnsapi_helper.sh
new file mode 100755 (executable)
index 0000000..2dd68b6
--- /dev/null
@@ -0,0 +1,1260 @@
+#!/bin/sh
+#functions from acme.sh, GPLv3 applies
+#utility functions acme.sh provieded for DNS API(and itself), 
+#some implementations dffer because uacme/acme.sh difference.
+
+# color functions are ignored because it didn't sent to interactive shell
+__green() {
+  printf -- "%b" "$1"
+}
+
+__red() {
+  printf -- "%b" "$1"
+}
+
+_usage() {
+  __red "$@" >&2
+  printf "\n" >&2
+}
+
+_sleep() {
+    if [ -n "$1" ]; then
+        sleep "$1"
+    fi
+}
+
+_log() {
+  prio="$1"
+       shift
+       if [ "$prio" != debug ] || [ "$debug" = 1 ]; then
+               logger -t "$LOG_TAG" -s -p "daemon.$prio" -- "$@"
+       fi
+}
+_err() {
+    _log err $@
+}
+_info() {
+    _log info $@
+}
+_debug() {
+  if [ $UACME_VERBOSE -ge 1 ]; then
+    _log debug $@
+  fi
+}
+_debug2() {
+  if [ $UACME_VERBOSE -ge 2 ]; then
+    _log debug $@
+  fi
+}
+_debug3() {
+  if [ $UACME_VERBOSE -ge 3 ]; then
+    _log debug $@
+  fi
+}
+
+__USE_TR_TAG=""
+if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ]; then
+  __USE_TR_TAG="1"
+fi
+export __USE_TR_TAG
+
+_upper_case() {
+  if [ "$__USE_TR_TAG" ]; then
+    LANG=C tr '[:lower:]' '[:upper:]'
+  else
+    # shellcheck disable=SC2018,SC2019
+    LANG=C tr '[a-z]' '[A-Z]'
+  fi
+}
+
+_lower_case() {
+  if [ "$__USE_TR_TAG" ]; then
+    LANG=C tr '[:upper:]' '[:lower:]'
+  else
+    # shellcheck disable=SC2018,SC2019
+    LANG=C tr '[A-Z]' '[a-z]'
+  fi
+}
+
+_startswith() {
+  _str="$1"
+  _sub="$2"
+  echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1
+}
+
+_endswith() {
+  _str="$1"
+  _sub="$2"
+  echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
+}
+
+_contains() {
+  _str="$1"
+  _sub="$2"
+  echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
+}
+
+_hasfield() {
+  _str="$1"
+  _field="$2"
+  _sep="$3"
+  if [ -z "$_field" ]; then
+    _usage "Usage: str field  [sep]"
+    return 1
+  fi
+
+  if [ -z "$_sep" ]; then
+    _sep=","
+  fi
+
+  for f in $(echo "$_str" | tr "$_sep" ' '); do
+    if [ "$f" = "$_field" ]; then
+      _debug2 "'$_str' contains '$_field'"
+      return 0 #contains ok
+    fi
+  done
+  _debug2 "'$_str' does not contain '$_field'"
+  return 1 #not contains
+}
+
+# str index [sep]
+_getfield() {
+  _str="$1"
+  _findex="$2"
+  _sep="$3"
+
+  if [ -z "$_findex" ]; then
+    _usage "Usage: str field  [sep]"
+    return 1
+  fi
+
+  if [ -z "$_sep" ]; then
+    _sep=","
+  fi
+
+  _ffi="$_findex"
+  while [ "$_ffi" -gt "0" ]; do
+    _fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
+    if [ "$_fv" ]; then
+      printf -- "%s" "$_fv"
+      return 0
+    fi
+    _ffi="$(_math "$_ffi" - 1)"
+  done
+
+  printf -- "%s" "$_str"
+
+}
+
+_exists() {
+  cmd="$1"
+  if [ -z "$cmd" ]; then
+    _usage "Usage: _exists cmd"
+    return 1
+  fi
+
+  if eval type type >/dev/null 2>&1; then
+    eval type "$cmd" >/dev/null 2>&1
+  elif command >/dev/null 2>&1; then
+    command -v "$cmd" >/dev/null 2>&1
+  else
+    which "$cmd" >/dev/null 2>&1
+  fi
+  ret="$?"
+  _debug3 "$cmd exists=$ret"
+  return $ret
+}
+
+if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then
+  __USE_EGREP=1
+else
+  __USE_EGREP=""
+fi
+
+_egrep_o() {
+  if [ "$__USE_EGREP" ]; then
+    egrep -o -- "$1" 2>/dev/null
+  else
+    sed -n 's/.*\('"$1"'\).*/\1/p'
+  fi
+}
+
+#options file
+_sed_i() {
+  options="$1"
+  filename="$2"
+  sed -i "$options" "$filename"
+}
+
+_math() {
+  _m_opts="$@"
+  printf "%s" "$(($_m_opts))"
+}
+
+#stdin  output hexstr splited by one space
+#input:"abc"
+#output: " 61 62 63"
+_hex_dump() {
+    hexdump -v -e '/1 ""' -e '/1 " %02x" ""'
+}
+
+#url encode, no-preserved chars : see same named function in acme.sh
+#_url_encode [upper-hex]  the encoded hex will be upper-case if the argument upper-hex is followed
+#stdin stdout
+_url_encode() {
+  _upper_hex=$1
+  _hex_str=$(_hex_dump)
+  _debug3 "_url_encode"
+  _debug3 "_hex_str" "$_hex_str"
+  for _hex_code in $_hex_str; do
+    #upper case
+    case "${_hex_code}" in
+    "41")
+      printf "%s" "A"
+      ;;
+    "42")
+      printf "%s" "B"
+      ;;
+    "43")
+      printf "%s" "C"
+      ;;
+    "44")
+      printf "%s" "D"
+      ;;
+    "45")
+      printf "%s" "E"
+      ;;
+    "46")
+      printf "%s" "F"
+      ;;
+    "47")
+      printf "%s" "G"
+      ;;
+    "48")
+      printf "%s" "H"
+      ;;
+    "49")
+      printf "%s" "I"
+      ;;
+    "4a")
+      printf "%s" "J"
+      ;;
+    "4b")
+      printf "%s" "K"
+      ;;
+    "4c")
+      printf "%s" "L"
+      ;;
+    "4d")
+      printf "%s" "M"
+      ;;
+    "4e")
+      printf "%s" "N"
+      ;;
+    "4f")
+      printf "%s" "O"
+      ;;
+    "50")
+      printf "%s" "P"
+      ;;
+    "51")
+      printf "%s" "Q"
+      ;;
+    "52")
+      printf "%s" "R"
+      ;;
+    "53")
+      printf "%s" "S"
+      ;;
+    "54")
+      printf "%s" "T"
+      ;;
+    "55")
+      printf "%s" "U"
+      ;;
+    "56")
+      printf "%s" "V"
+      ;;
+    "57")
+      printf "%s" "W"
+      ;;
+    "58")
+      printf "%s" "X"
+      ;;
+    "59")
+      printf "%s" "Y"
+      ;;
+    "5a")
+      printf "%s" "Z"
+      ;;
+
+      #lower case
+    "61")
+      printf "%s" "a"
+      ;;
+    "62")
+      printf "%s" "b"
+      ;;
+    "63")
+      printf "%s" "c"
+      ;;
+    "64")
+      printf "%s" "d"
+      ;;
+    "65")
+      printf "%s" "e"
+      ;;
+    "66")
+      printf "%s" "f"
+      ;;
+    "67")
+      printf "%s" "g"
+      ;;
+    "68")
+      printf "%s" "h"
+      ;;
+    "69")
+      printf "%s" "i"
+      ;;
+    "6a")
+      printf "%s" "j"
+      ;;
+    "6b")
+      printf "%s" "k"
+      ;;
+    "6c")
+      printf "%s" "l"
+      ;;
+    "6d")
+      printf "%s" "m"
+      ;;
+    "6e")
+      printf "%s" "n"
+      ;;
+    "6f")
+      printf "%s" "o"
+      ;;
+    "70")
+      printf "%s" "p"
+      ;;
+    "71")
+      printf "%s" "q"
+      ;;
+    "72")
+      printf "%s" "r"
+      ;;
+    "73")
+      printf "%s" "s"
+      ;;
+    "74")
+      printf "%s" "t"
+      ;;
+    "75")
+      printf "%s" "u"
+      ;;
+    "76")
+      printf "%s" "v"
+      ;;
+    "77")
+      printf "%s" "w"
+      ;;
+    "78")
+      printf "%s" "x"
+      ;;
+    "79")
+      printf "%s" "y"
+      ;;
+    "7a")
+      printf "%s" "z"
+      ;;
+      #numbers
+    "30")
+      printf "%s" "0"
+      ;;
+    "31")
+      printf "%s" "1"
+      ;;
+    "32")
+      printf "%s" "2"
+      ;;
+    "33")
+      printf "%s" "3"
+      ;;
+    "34")
+      printf "%s" "4"
+      ;;
+    "35")
+      printf "%s" "5"
+      ;;
+    "36")
+      printf "%s" "6"
+      ;;
+    "37")
+      printf "%s" "7"
+      ;;
+    "38")
+      printf "%s" "8"
+      ;;
+    "39")
+      printf "%s" "9"
+      ;;
+    "2d")
+      printf "%s" "-"
+      ;;
+    "5f")
+      printf "%s" "_"
+      ;;
+    "2e")
+      printf "%s" "."
+      ;;
+    "7e")
+      printf "%s" "~"
+      ;;
+    #other hex
+    *)
+      if [ "$_upper_hex" = "upper-hex" ]; then
+        _hex_code=$(printf "%s" "$_hex_code" | _upper_case)
+      fi
+      printf '%%%s' "$_hex_code"
+      ;;
+    esac
+  done
+}
+
+#Usage: multiline
+_base64() {
+  [ "" ] #urgly
+  if _exists ucode; then
+  # I hope throw single line into multiline doesn't break any code
+    ucode -p "b64enc(\"$(cat -)\");"
+  else
+    if [ "$1" ]; then
+      _debug3 "base64 multiline:'$1'"
+      ${ACME_OPENSSL_BIN:-openssl} base64 -e
+    else
+      _debug3 "base64 single line."
+      ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
+    fi
+  fi
+}
+
+#Usage: multiline
+_dbase64() {
+  if _exists ucode; then
+    ucode -p "b64dec(\"$(cat -)\");"
+  else
+    if [ "$1" ]; then
+      ${ACME_OPENSSL_BIN:-openssl} base64 -d
+    else
+      ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
+    fi
+  fi
+}
+
+#Usage: hashalg  [outputhex]
+#Output Base64-encoded digest
+#currnetly only hex option is supported
+_digest() {
+  alg="$1"
+  if [ -z "$alg" ]; then
+    _usage "Usage: _digest hashalg"
+    return 1
+  fi
+
+  outputhex="$2"
+  if _exists ${ACME_OPENSSL_BIN:-openssl}; then
+    if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
+      if [ "$outputhex" ]; then
+        ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
+      else
+        ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
+      fi
+    else
+      _err "$alg is not supported yet"
+      return 1
+    fi
+    return 0
+  else
+    if [ "$outputhex" ]; then
+      case "$alg" in
+      "md5")
+        md5sum | cut -d ' ' -f 1
+        return 0
+      ;;
+      "sha256")
+        sha256sum | cut ' ' -f 1
+        return 0
+      ;;
+      *)
+        _err "$alg is not supported yet"
+        return 1
+      ;;
+      esac
+    else
+      _err "binary mode not supported without Openssl"
+    fi
+  fi
+}
+
+#Usage: hashalg  secret_hex  [outputhex]
+#Output binary hmac
+_hmac() {
+  alg="$1"
+  secret_hex="$2"
+  outputhex="$3"
+
+  if [ -z "$secret_hex" ]; then
+    _usage "Usage: _hmac hashalg secret [outputhex]"
+    return 1
+  fi
+
+  if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
+    if [ "$outputhex" ]; then
+      (${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
+    else
+      ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
+    fi
+  else
+    _err "$alg is not supported yet"
+    return 1
+  fi
+
+}
+
+#keyfile
+_isRSA() {
+  keyfile=$1
+  if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text 2>&1 | grep "^publicExponent:" 2>&1 >/dev/null; then
+    return 0
+  fi
+  return 1
+}
+
+#keyfile
+_isEcc() {
+  keyfile=$1
+  if grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" 2>&1 >/dev/null; then
+    return 0
+  fi
+  return 1
+}
+
+#Usage: keyfile hashalg
+#Output: Base64-encoded signature value
+_sign() {
+  keyfile="$1"
+  alg="$2"
+  if [ -z "$alg" ]; then
+    _usage "Usage: _sign keyfile hashalg"
+    return 1
+  fi
+
+  _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
+
+  if _isRSA "$keyfile" >/dev/null 2>&1; then
+    $_sign_openssl -$alg | _base64
+  elif _isEcc "$keyfile" >/dev/null 2>&1; then
+    if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
+      _err "Sign failed: $_sign_openssl"
+      _err "Key file: $keyfile"
+      _err "Key content: $(wc -l <"$keyfile") lines"
+      return 1
+    fi
+    _debug3 "_signedECText" "$_signedECText"
+    _ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
+    _ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
+    if [ "$__ECC_KEY_LEN" -eq "256" ]; then
+      while [ "${#_ec_r}" -lt "64" ]; do
+        _ec_r="0${_ec_r}"
+      done
+      while [ "${#_ec_s}" -lt "64" ]; do
+        _ec_s="0${_ec_s}"
+      done
+    fi
+    if [ "$__ECC_KEY_LEN" -eq "384" ]; then
+      while [ "${#_ec_r}" -lt "96" ]; do
+        _ec_r="0${_ec_r}"
+      done
+      while [ "${#_ec_s}" -lt "96" ]; do
+        _ec_s="0${_ec_s}"
+      done
+    fi
+    if [ "$__ECC_KEY_LEN" -eq "512" ]; then
+      while [ "${#_ec_r}" -lt "132" ]; do
+        _ec_r="0${_ec_r}"
+      done
+      while [ "${#_ec_s}" -lt "132" ]; do
+        _ec_s="0${_ec_s}"
+      done
+    fi
+    _debug3 "_ec_r" "$_ec_r"
+    _debug3 "_ec_s" "$_ec_s"
+    printf "%s" "$_ec_r$_ec_s" | _h2b | _base64
+  else
+    _err "Unknown key file format."
+    return 1
+  fi
+
+}
+
+_utc_date() {
+  date -u "+%Y-%m-%d %H:%M:%S"
+}
+
+_time() {
+  date -u "+%s"
+}
+
+_mktemp() {
+  if _exists mktemp; then
+    if mktemp 2>/dev/null; then
+      return 0
+    elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then
+      #for Mac osx
+      return 0
+    fi
+  fi
+  if [ -d "/tmp" ]; then
+    echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp"
+    return 0
+  elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then
+    echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp"
+    return 0
+  fi
+  _err "Cannot create temp file."
+}
+
+#clear all the https envs to cause _inithttp() to run next time.
+_resethttp() {
+  __HTTP_INITIALIZED=""
+  _ACME_CURL=""
+  _ACME_WGET=""
+  ACME_HTTP_NO_REDIRECTS=""
+}
+
+_inithttp() {
+
+  if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then
+    HTTP_HEADER="$(_mktemp)"
+    _debug2 HTTP_HEADER "$HTTP_HEADER"
+  fi
+
+  if [ "$__HTTP_INITIALIZED" ]; then
+    if [ "$_ACME_CURL$_ACME_WGET" ]; then
+      _debug2 "Http already initialized."
+      return 0
+    fi
+  fi
+
+  if [ -z "$_ACME_CURL" ] && _exists "curl"; then
+    _ACME_CURL="curl --silent --dump-header $HTTP_HEADER "
+    if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then
+      _ACME_CURL="$_ACME_CURL -L "
+    fi
+    if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then
+      _CURL_DUMP="$(_mktemp)"
+      _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
+    fi
+
+    if [ "$CA_PATH" ]; then
+      _ACME_CURL="$_ACME_CURL --capath $CA_PATH "
+    elif [ "$CA_BUNDLE" ]; then
+      _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
+    fi
+
+    if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then
+      _ACME_CURL="$_ACME_CURL -g "
+    fi
+
+    #don't use --fail-with-body
+    ##from curl 7.76: return fail on HTTP errors but keep the body
+    #if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then
+    #  _ACME_CURL="$_ACME_CURL --fail-with-body "
+    #fi
+  fi
+
+  if [ -z "$_ACME_WGET" ] && _exists "wget"; then
+    _ACME_WGET="wget -q"
+    if [ "$ACME_HTTP_NO_REDIRECTS" ]; then
+      _ACME_WGET="$_ACME_WGET --max-redirect 0 "
+    fi
+    if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+      if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--debug"; then
+        _ACME_WGET="$_ACME_WGET -d "
+      fi
+    fi
+    if [ "$CA_PATH" ]; then
+      _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH "
+    elif [ "$CA_BUNDLE" ]; then
+      _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
+    fi
+
+    #from wget 1.14: do not skip body on 404 error
+    if _contains "$(wget --help 2>&1)" "--content-on-error"; then
+      _ACME_WGET="$_ACME_WGET --content-on-error "
+    fi
+  fi
+
+  __HTTP_INITIALIZED=1
+
+}
+
+# body  url [needbase64] [POST|PUT|DELETE] [ContentType]
+_post() {
+  body="$1"
+  _post_url="$2"
+  needbase64="$3"
+  httpmethod="$4"
+  _postContentType="$5"
+
+  if [ -z "$httpmethod" ]; then
+    httpmethod="POST"
+  fi
+  _debug $httpmethod
+  _debug "_post_url" "$_post_url"
+  _debug2 "body" "$body"
+  _debug2 "_postContentType" "$_postContentType"
+
+  _inithttp
+
+  if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
+    _CURL="$_ACME_CURL"
+    if [ "$HTTPS_INSECURE" ]; then
+      _CURL="$_CURL --insecure  "
+    fi
+    if [ "$httpmethod" = "HEAD" ]; then
+      _CURL="$_CURL -I  "
+    fi
+    _debug "_CURL" "$_CURL"
+    if [ "$needbase64" ]; then
+      if [ "$body" ]; then
+        if [ "$_postContentType" ]; then
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+        else
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+        fi
+      else
+        if [ "$_postContentType" ]; then
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
+        else
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
+        fi
+      fi
+    else
+      if [ "$body" ]; then
+        if [ "$_postContentType" ]; then
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+        else
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+        fi
+      else
+        if [ "$_postContentType" ]; then
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
+        else
+          response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
+        fi
+      fi
+    fi
+    _ret="$?"
+    if [ "$_ret" != "0" ]; then
+      _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
+      if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+        _err "Here is the curl dump log:"
+        _err "$(cat "$_CURL_DUMP")"
+      fi
+    fi
+  elif [ "$_ACME_WGET" ]; then
+    _WGET="$_ACME_WGET"
+    if [ "$HTTPS_INSECURE" ]; then
+      _WGET="$_WGET --no-check-certificate "
+    fi
+    if [ "$httpmethod" = "HEAD" ]; then
+      _WGET="$_WGET --read-timeout=3.0  --tries=2  "
+    fi
+    _debug "_WGET" "$_WGET"
+    if [ "$needbase64" ]; then
+      if [ "$httpmethod" = "POST" ]; then
+        if [ "$_postContentType" ]; then
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+        else
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+        fi
+      else
+        if [ "$_postContentType" ]; then
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+        else
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+        fi
+      fi
+    else
+      if [ "$httpmethod" = "POST" ]; then
+        if [ "$_postContentType" ]; then
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        else
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        fi
+      elif [ "$httpmethod" = "HEAD" ]; then
+        if [ "$_postContentType" ]; then
+          response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        else
+          response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        fi
+      else
+        if [ "$_postContentType" ]; then
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        else
+          response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+        fi
+      fi
+    fi
+    _ret="$?"
+    if [ "$_ret" = "8" ]; then
+      _ret=0
+      _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
+    fi
+    if [ "$_ret" != "0" ]; then
+      _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
+    fi
+    if _contains "$_WGET" " -d "; then
+      # Demultiplex wget debug output
+      cat "$HTTP_HEADER" >&2
+      _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
+    fi
+    # remove leading whitespaces from header to match curl format
+    _sed_i 's/^  //g' "$HTTP_HEADER"
+  else
+    _ret="$?"
+    _err "Neither curl nor wget have been found, cannot make $httpmethod request."
+  fi
+  _debug "_ret" "$_ret"
+  printf "%s" "$response"
+  return $_ret
+}
+
+# url getheader timeout
+_get() {
+  _debug GET
+  url="$1"
+  onlyheader="$2"
+  t="$3"
+  _debug url "$url"
+  _debug "timeout=$t"
+
+  _inithttp
+
+  if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
+    _CURL="$_ACME_CURL"
+    if [ "$HTTPS_INSECURE" ]; then
+      _CURL="$_CURL --insecure  "
+    fi
+    if [ "$t" ]; then
+      _CURL="$_CURL --connect-timeout $t"
+    fi
+    _debug "_CURL" "$_CURL"
+    if [ "$onlyheader" ]; then
+      $_CURL -I --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
+    else
+      $_CURL --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
+    fi
+    ret=$?
+    if [ "$ret" != "0" ]; then
+      _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
+      if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+        _err "Here is the curl dump log:"
+        _err "$(cat "$_CURL_DUMP")"
+      fi
+    fi
+  elif [ "$_ACME_WGET" ]; then
+    _WGET="$_ACME_WGET"
+    if [ "$HTTPS_INSECURE" ]; then
+      _WGET="$_WGET --no-check-certificate "
+    fi
+    if [ "$t" ]; then
+      _WGET="$_WGET --timeout=$t"
+    fi
+    _debug "_WGET" "$_WGET"
+    if [ "$onlyheader" ]; then
+      _wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)"
+      if _contains "$_WGET" " -d "; then
+        # Demultiplex wget debug output
+        echo "$_wget_out" >&2
+        echo "$_wget_out" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^  //g' -
+      fi
+    else
+      $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O - "$url" 2>"$HTTP_HEADER"
+      if _contains "$_WGET" " -d "; then
+        # Demultiplex wget debug output
+        cat "$HTTP_HEADER" >&2
+        _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
+      fi
+      # remove leading whitespaces from header to match curl format
+      _sed_i 's/^  //g' "$HTTP_HEADER"
+    fi
+    ret=$?
+    if [ "$ret" = "8" ]; then
+      ret=0
+      _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
+    fi
+    if [ "$ret" != "0" ]; then
+      _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
+    fi
+  else
+    ret=$?
+    _err "Neither curl nor wget have been found, cannot make GET request."
+  fi
+  _debug "ret" "$ret"
+  return $ret
+}
+
+
+_h_char_2_dec() {
+  _ch=$1
+  case "${_ch}" in
+  a | A)
+    printf "10"
+    ;;
+  b | B)
+    printf "11"
+    ;;
+  c | C)
+    printf "12"
+    ;;
+  d | D)
+    printf "13"
+    ;;
+  e | E)
+    printf "14"
+    ;;
+  f | F)
+    printf "15"
+    ;;
+  *)
+    printf "%s" "$_ch"
+    ;;
+  esac
+
+}
+
+#openwrt have xargs in busybox
+_h2b() {
+  if _exists xxd; then
+    if _contains "$(xxd --help 2>&1)" "assumes -c30"; then
+      if xxd -r -p -c 9999 2>/dev/null; then
+        return
+      fi
+    else
+      if xxd -r -p 2>/dev/null; then
+        return
+      fi
+    fi
+  fi
+
+  hex=$(cat)
+  ic=""
+  jc=""
+  _debug2 _URGLY_PRINTF "$_URGLY_PRINTF"
+  if [ -z "$_URGLY_PRINTF" ]; then
+    if [ "$_ESCAPE_XARGS" ] && _exists xargs; then
+      _debug2 "xargs"
+      echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf
+    else
+      for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do
+        if [ -z "$h" ]; then
+          break
+        fi
+        printf "\x$h%s"
+      done
+    fi
+  else
+    for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do
+      if [ -z "$ic" ]; then
+        ic=$c
+        continue
+      fi
+      jc=$c
+      ic="$(_h_char_2_dec "$ic")"
+      jc="$(_h_char_2_dec "$jc")"
+      printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s"
+      ic=""
+      jc=""
+    done
+  fi
+
+}
+
+_head_n() {
+  head -n "$1"
+}
+
+_is_solaris() {
+  _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS"
+}
+
+_tail_n() {
+  if _is_solaris; then
+    #fix for solaris
+    tail -"$1"
+  else
+    tail -n "$1"
+  fi
+}
+
+_tail_c() {
+  tail -c "$1" 2>/dev/null || tail -"$1"c
+}
+
+#domain
+_is_idn() {
+  _is_idn_d="$1"
+  _debug2 _is_idn_d "$_is_idn_d"
+  _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')
+  _debug2 _idn_temp "$_idn_temp"
+  [ "$_idn_temp" ]
+}
+
+#aa.com
+#aa.com,bb.com,cc.com
+_idn() {
+  __idn_d="$1"
+  if ! _is_idn "$__idn_d"; then
+    printf "%s" "$__idn_d"
+    return 0
+  fi
+
+  if _exists idn; then
+    if _contains "$__idn_d" ','; then
+      _i_first="1"
+      for f in $(echo "$__idn_d" | tr ',' ' '); do
+        [ -z "$f" ] && continue
+        if [ -z "$_i_first" ]; then
+          printf "%s" ","
+        else
+          _i_first=""
+        fi
+        idn --quiet "$f" | tr -d "\r\n"
+      done
+    else
+      idn "$__idn_d" | tr -d "\r\n"
+    fi
+  else
+    _err "Please install idn to process IDN names."
+  fi
+}
+
+_url_replace() {
+  tr '/+' '_-' | tr -d '= '
+}
+
+_normalizeJson() {
+  sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
+}
+
+#setopt "file"  "opt"  "="  "value" [";"]
+_setopt() {
+  __conf="$1"
+  __opt="$2"
+  __sep="$3"
+  __val="$4"
+  __end="$5"
+  if [ -z "$__opt" ]; then
+    _usage usage: _setopt '"file"  "opt"  "="  "value" [";"]'
+    return
+  fi
+  if [ ! -f "$__conf" ]; then
+    touch "$__conf"
+  fi
+  if [ -n "$(_tail_c 1 <"$__conf")" ]; then
+    echo >>"$__conf"
+  fi
+
+  if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
+    _debug3 OK
+    if _contains "$__val" "&"; then
+      __val="$(echo "$__val" | sed 's/&/\\&/g')"
+    fi
+    if _contains "$__val" "|"; then
+      __val="$(echo "$__val" | sed 's/|/\\|/g')"
+    fi
+    text="$(cat "$__conf")"
+    printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
+
+  elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
+    if _contains "$__val" "&"; then
+      __val="$(echo "$__val" | sed 's/&/\\&/g')"
+    fi
+    if _contains "$__val" "|"; then
+      __val="$(echo "$__val" | sed 's/|/\\|/g')"
+    fi
+    text="$(cat "$__conf")"
+    printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
+
+  else
+    _debug3 APP
+    echo "$__opt$__sep$__val$__end" >>"$__conf"
+  fi
+  _debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
+}
+
+#config file related function: most dns scripts still reads form env variables too though
+#_save_conf  file key  value base64encode
+#save to conf
+_save_conf() {
+  _s_c_f="$1"
+  _sdkey="$2"
+  _sdvalue="$3"
+  _b64encode="$4"
+  
+  if [ "$_sdvalue" ] && [ "$_b64encode" ]; then
+    _sdvalue="${B64CONF_START}$(printf "%s" "${_sdvalue}" | _base64)${B64CONF_END}"
+  fi
+  if [ "$_s_c_f" ]; then
+    _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'"
+  else
+    _err "Config file is empty, cannot save $_sdkey=$_sdvalue"
+  fi
+}
+
+#_clear_conf file  key
+_clear_conf() {
+  _c_c_f="$1"
+  _sdkey="$2"
+  if [ "$_c_c_f" ]; then
+    _conf_data="$(cat "$_c_c_f")"
+    echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f"
+  else
+    _err "Config file is empty, cannot clear"
+  fi
+}
+
+#_read_conf file  key
+_read_conf() {
+  _r_c_f="$1"
+  _sdkey="$2"
+  if [ -f "$_r_c_f" ]; then
+    _sdv="$(
+      eval "$(grep "^$_sdkey *=" "$_r_c_f")"
+      eval "printf \"%s\" \"\$$_sdkey\""
+    )"
+    if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then
+      _sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)"
+    fi
+    printf "%s" "$_sdv"
+  else
+    _debug "Config file is empty, cannot read $_sdkey"
+  fi
+}
+
+#_savedomainconf   key  value  base64encode
+#save to domain.conf
+_savedomainconf() {
+  _save_conf "$DOMAIN_CONF" "$@"
+}
+
+#_cleardomainconf   key
+_cleardomainconf() {
+  _clear_conf "$DOMAIN_CONF" "$1"
+}
+
+#_readdomainconf   key
+_readdomainconf() {
+  _read_conf "$DOMAIN_CONF" "$1"
+}
+
+#_migratedomainconf   oldkey  newkey  base64encode
+_migratedomainconf() {
+  _old_key="$1"
+  _new_key="$2"
+  _b64encode="$3"
+  _old_value=$(_readdomainconf "$_old_key")
+  _cleardomainconf "$_old_key"
+  if [ -z "$_old_value" ]; then
+    return 1 # migrated failed: old value is empty
+  fi
+  _new_value=$(_readdomainconf "$_new_key")
+  if [ -n "$_new_value" ]; then
+    _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed."
+    return 1 # migrated failed: old value replaced by new value
+  fi
+  _savedomainconf "$_new_key" "$_old_value" "$_b64encode"
+  _debug "Domain config $_old_key has been migrated to $_new_key."
+}
+
+#_migratedeployconf   oldkey  newkey  base64encode
+_migratedeployconf() {
+  _migratedomainconf "$1" "SAVED_$2" "$3" ||
+    _migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found
+}
+
+#key  value  base64encode
+_savedeployconf() {
+  _savedomainconf "SAVED_$1" "$2" "$3"
+  #remove later
+  _cleardomainconf "$1"
+}
+
+#key
+_getdeployconf() {
+  _rac_key="$1"
+  _rac_value="$(eval echo \$"$_rac_key")"
+  if [ "$_rac_value" ]; then
+    if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
+      _debug2 "trim quotation marks"
+      eval $_rac_key=$_rac_value
+      export $_rac_key
+    fi
+    return 0 # do nothing
+  fi
+  _saved="$(_readdomainconf "SAVED_$_rac_key")"
+  eval $_rac_key=\$_saved
+  export $_rac_key
+}
+
+#_saveaccountconf  key  value  base64encode
+_saveaccountconf() {
+  _save_conf "$ACCOUNT_CONF_PATH" "$@"
+}
+
+#key  value base64encode
+_saveaccountconf_mutable() {
+  _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2" "$3"
+  #remove later
+  _clearaccountconf "$1"
+}
+
+#key
+_readaccountconf() {
+  _read_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_readaccountconf_mutable() {
+  _rac_key="$1"
+  _readaccountconf "SAVED_$_rac_key"
+}
+
+#_clearaccountconf   key
+_clearaccountconf() {
+  _clear_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_clearaccountconf_mutable() {
+  _clearaccountconf "SAVED_$1"
+  #remove later
+  _clearaccountconf "$1"
+}
+
+#_savecaconf  key  value
+_savecaconf() {
+  _save_conf "$CA_CONF" "$1" "$2"
+}
+
+#_readcaconf   key
+_readcaconf() {
+  _read_conf "$CA_CONF" "$1"
+}
+
+#_clearaccountconf   key
+_clearcaconf() {
+  _clear_conf "$CA_CONF" "$1"
+}
\ No newline at end of file
diff --git a/net/uacme/files/dnschalhook.sh b/net/uacme/files/dnschalhook.sh
new file mode 100755 (executable)
index 0000000..e9ca8ae
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+# Copyright (C) 2019-2024 Nicola Di Lieto <nicola.dilieto@gmail.com>
+#
+# This file is part of uacme.
+#
+# uacme is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# uacme is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# Part of this is copied from acme.sh
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ARGS=5
+E_BADARGS=85
+LOG_TAG=acme-uacme-dnshook
+
+if test $# -ne "$ARGS"
+then
+    echo "Usage: $(basename "$0") method type ident token auth" 1>&2
+    exit $E_BADARGS
+fi
+
+METHOD=$1
+TYPE=$2
+IDENT=$3
+TOKEN=$4
+AUTH=$5
+
+if [ "$TYPE" != "dns-01" ]; then
+    echo "skipping $TYPE" 1>&2
+    exit 1
+fi
+
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+. /usr/lib/acme/client/dnsapi_helper.sh
+ACCOUNT_CONF_PATH=$UACME_CONFDIR/accounts.conf
+DOMAIN_CONF_DIR=$UACME_CONFDIR/$IDENT
+DOMAIN_CONF=$DOMAIN_CONF_DIR/dnsapi.conf
+ACMESH_DNSSCIRPT_DIR=${ACMESH_DNSSCIRPT_DIR:-/usr/lib/acme/client/dnsapi}
+
+#import dns hook script 
+dns=${dns:-$(head -n 1 $DOMAIN_CONF_DIR/selected_api)} # use different file to not hurt acme.sh config file struct
+if [ ! -f "$ACMESH_DNSSCIRPT_DIR/$dns.sh" ]; then
+    echo "dns file $dns doesn't exit" 1>&2
+    exit 1
+fi
+. /usr/lib/acme/client/dnsapi/$dns.sh
+echo $dns > "$DOMAIN_CONF_DIR/selected_api"
+case "$METHOD" in
+    "begin")
+        (umask 077 ; touch -a "$DOMAIN_CONF")
+        log info logging $DOMAIN_CONF
+        ${dns}_add _acme-challenge.$IDENT $AUTH
+        RESULT=$?
+        if [ $RESULT -eq 0 ]; then
+            sleep ${dns_wait:-"30s"}
+            exit 0
+        else
+            exit $RESULT
+        fi
+        ;;
+    "done"|"failed")
+        ${dns}_rm _acme-challenge.$IDENT $AUTH
+        exit $?
+        ;;
+    *)
+        echo "$0: invalid method" 1>&2 
+        exit 1
+        ;;
+esac
diff --git a/net/uacme/files/hook.sh b/net/uacme/files/hook.sh
new file mode 100755 (executable)
index 0000000..e090312
--- /dev/null
@@ -0,0 +1,218 @@
+#!/bin/sh
+# Wrapper for uacme to work on openwrt.
+
+set -u
+ACME=/usr/sbin/uacme
+HPROGRAM=/usr/share/uacme/uacme.sh
+LOG_TAG=acme-uacme
+NOTIFY=/usr/lib/acme/notify
+HOOKDIR=/usr/lib/acme
+
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+
+link_certs() {
+       local main_domain
+       local domain_dir
+       domain_dir="$1"
+       main_domain="$2"
+#uacme saves only fullchain as cert.pem
+       (
+               umask 077
+               cat "$domain_dir/cert.pem" "$state_dir/private/$main_domain/key.pem" >"$domain_dir/combined.cer"
+               sed -n '1,/-----END CERTIFICATE-----/p' "$domain_dir/cert.pem" >"$domain_dir/leaf_cert.pem"
+               sed '1,/-----END CERTIFICATE-----/d' "$domain_dir/cert.pem" >"$domain_dir/chain.crt"
+       )
+
+       if [ ! -e "$CERT_DIR/$main_domain.crt" ]; then
+               ln -s "$domain_dir/leaf_cert.pem" "$CERT_DIR/$main_domain.crt"
+       fi
+#uacme doesn't rotate key, and it saves ../private/$main_domain dir
+       if [ ! -e "$CERT_DIR/$main_domain.key" ]; then
+               ln -s "$state_dir/private/$main_domain/key.pem" "$CERT_DIR/$main_domain.key"
+       fi
+       if [ ! -e "$CERT_DIR/$main_domain.fullchain.crt" ]; then
+               ln -s "$domain_dir/cert.pem" "$CERT_DIR/$main_domain.fullchain.crt"
+       fi
+       if [ ! -e "$CERT_DIR/$main_domain.combined.crt" ]; then
+               ln -s "$domain_dir/combined.cer" "$CERT_DIR/$main_domain.combined.crt"
+       fi
+       if [ ! -e "$CERT_DIR/$main_domain.chain.crt" ]; then
+               ln -s "$domain_dir/chain.crt" "$CERT_DIR/$main_domain.chain.crt"
+       fi
+}
+
+#expand acme server short alias
+case $acme_server in
+                               letsencrypt)
+                                       unset acme_server
+                                       ;;
+                               letsencrypt_test)
+                                       acme_server=https://acme-staging-v02.api.letsencrypt.org/directory
+                                       ;;
+                               zerossl)
+                                       acme_server=https://acme.zerossl.com/v2/DV90
+                                       ;;
+                               google)
+                                       acme_server=https://dv.acme-v02.api.pki.goog/directory
+                                       ;;
+                               actalis)
+                                       acme_server=https://acme-api.actalis.com/acme/directory
+                                       ;;
+                               *)
+                                       ;;
+                               esac
+
+case $1 in
+get)
+       #uacme doesn't save account per ca nor it make new account when not registered
+       #while server doesn't care, we record which CAs we have account to reduce noise
+       #using staging var for default letsencrypts.
+       if grep -q "^${acme_server:-$staging}$" $state_dir/accounts; then
+               :
+       else
+               #not found
+               if [ "$acme_server" ]; then
+                               $ACME new $account_email -t EC -y --confdir "$state_dir" -a $acme_server
+                               echo $acme_server >> $state_dir/accounts
+               elif [ "$staging" = 1 ]; then
+                       $ACME new $account_email -t EC -y --confdir "$state_dir" -s
+                       echo $staging >> $state_dir/accounts
+               else
+                       $ACME new $account_email -t EC -y --confdir "$state_dir"
+                       echo $staging >> $state_dir/accounts
+               fi
+       fi
+       set --
+       [ "$debug" = 1 ] && set -- "$@" -v
+#uacme doesn't rotate privkey
+       case $key_type in
+       ec*)
+               keylength=${key_type#ec}
+               domain_dir="$state_dir/$main_domain"
+               set -- "$@" -t EC 
+               ;;
+       rsa*)
+               keylength=${key_type#rsa}
+               domain_dir="$state_dir/$main_domain"
+               ;;
+       esac
+
+       set -- "$@" --bits "$keylength"
+
+       if [ "$acme_server" ]; then
+               set -- "$@" --acme-url "$acme_server"
+       elif [ "$staging" = 1 ]; then
+               set -- "$@" --staging
+       else
+               set -- "$@"
+       fi
+
+       log info "Running ACME for $main_domain with validation_method $validation_method"
+
+       staging_moved=0
+       is_renew=0
+       if [ -e "$domain_dir" ]; then
+               if [ "$staging" = 0 ] && [ -e $domain_dir/this_is_staging ]; then
+                       mv "$domain_dir" "$domain_dir.staging"
+                       mv "$state_dir/private/$main_domain" "$state_dir/private/$main_domain.staging"
+                       log info "Certificates are previously issued from a staging server, but staging option is disabled, moved to $domain_dir.staging."
+                       staging_moved=1
+               else
+                       #this is renewal
+                       is_renew=1
+               fi
+       else
+               log info no prv certificate remembered
+       fi
+
+       if [ "$days" ]; then
+               set -- "$@" --days "$days"
+       fi
+
+       # uacme handles challange select by hook script
+       case "$validation_method" in
+       "alpn")
+               log info "using already running ualpn, it's user's duty to config ualpn server deamon"
+               set -- "$@" -h "$HOOKDIR/client/ualpn.sh"
+               ;;
+       "dns")
+               export dns
+               set -- "$@" -h "$HOOKDIR/client/dnschalhook.sh"
+               if [ "$dalias" ]; then
+                       set -- "$@" --domain-alias "$dalias"
+                       if [ "$calias" ]; then
+                               log err "Both domain and challenge aliases are defined. Ignoring the challenge alias."
+                       fi
+               elif [ "$calias" ]; then
+                       set -- "$@" --challenge-alias "$calias"
+               fi
+               if [ "$dns_wait" ]; then
+                       export dns_wait
+               fi
+               ;;
+       "standalone")
+               set -- "$@" --standalone --listen-v6
+               log err "standalone server is not implmented for uacme"
+               exit 1
+               ;;
+       "webroot")
+               mkdir -p "$CHALLENGE_DIR"
+               export CHALLENGE_DIR
+               set -- "$@" -h "$HOOKDIR/client/httpchalhook.sh"
+               ;;
+       *)
+               log err "Unsupported validation_method $validation_method"
+               ;;
+       esac
+
+       set -- "$@"  --confdir "$state_dir" issue
+               for d in $domains; do
+               set -- "$@" "$d"
+       done
+
+       log info "$ACME $*"
+       trap '$NOTIFY issue-failed;exit 1' INT
+       "$ACME" "$@" 2>&1
+       status=$?
+       trap - INT
+
+       case $status in
+       0)
+               link_certs "$domain_dir" "$main_domain"
+               if [ "$is_renew" = 1 ]; then
+                       $NOTIFY renewed
+               else
+                       $NOTIFY issued
+                       if [ "$staging" = 1 ]; then
+                               touch $domain_dir/this_is_staging
+                       fi
+               fi
+               ;;
+       1)
+               #cert is not due to renewl so don't do anything
+               if [ "$staging_moved" = 1 ]; then
+                       log err "Staging certificate '$domain_dir' restored"
+                       mv "$domain_dir.staging" "$domain_dir"
+               fi
+               log debug "not due to renewal"
+               ;;
+       *)
+               if [ "$is_renew" = 1 ]; then
+                       $NOTIFY renew-failed
+                       exit 1;
+               fi
+               if [ "$staging_moved" = 1 ]; then
+                       log err "Staging certificate '$domain_dir' restored"
+                       mv "$domain_dir.staging" "$domain_dir"
+                       mv "$state_dir/private/$main_domain.staging" "$state_dir/private/$main_domain"
+               elif [ -d "$domain_dir" ]; then
+                       failed_dir="$domain_dir.failed-$(date +%s)"
+                       mv "$domain_dir" "$failed_dir"
+                       log err "State moved to $failed_dir"
+               fi
+               $NOTIFY issue-failed
+               ;;
+       esac
+       ;;
+esac
diff --git a/net/uacme/files/httpchalhook.sh b/net/uacme/files/httpchalhook.sh
new file mode 100755 (executable)
index 0000000..7e63fab
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+# Copyright (C) 2019-2024 Nicola Di Lieto <nicola.dilieto@gmail.com>
+#
+# This file is part of uacme.
+#
+# uacme is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# uacme is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+CHALLENGE_PATH="${CHALLENGE_DIR:-/var/run/acme/challenge}"
+mkdir -p "${CHALLENGE_PATH}/.well-known/acme-challenge"
+ARGS=5
+E_BADARGS=85
+
+if test $# -ne "$ARGS"
+then
+    echo "Usage: $(basename "$0") method type ident token auth" 1>&2
+    exit $E_BADARGS
+fi
+
+METHOD=$1
+TYPE=$2
+IDENT=$3
+TOKEN=$4
+AUTH=$5
+
+case "$METHOD" in
+    "begin")
+        case "$TYPE" in
+            http-01)
+                printf "%s" "${AUTH}" > "${CHALLENGE_PATH}/.well-known/acme-challenge/${TOKEN}"
+                exit $?
+                ;;
+            *)
+                exit 1
+                ;;
+        esac
+        ;;
+
+    "done"|"failed")
+        case "$TYPE" in
+            http-01)
+                rm "${CHALLENGE_PATH}/.well-known/acme-challenge/${TOKEN}"
+                exit $?
+                ;;
+            *)
+                exit 1
+                ;;
+        esac
+        ;;
+
+    *)
+        echo "$0: invalid method" 1>&2
+        exit 1
+esac
diff --git a/net/uacme/files/run.sh b/net/uacme/files/run.sh
deleted file mode 100755 (executable)
index ce82af6..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-#!/bin/sh
-# Wrapper for uacme to work on openwrt.
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 3 of the License, or (at your option) any later
-# version.
-#
-# Initial Author: Toke Høiland-Jørgensen <toke@toke.dk>
-# Adapted for uacme: Lucian Cristian <lucian.cristian@gmail.com>
-# Adapted for custom CA and TLS-ALPN-01: Peter Putzer <openwrt@mundschenk.at>
-
-CHECK_CRON=$1
-
-#check for installed packages, for now, support only one
-if [ -e "/usr/lib/acme/acme.sh" ]; then
-    ACME=/usr/lib/acme/acme.sh
-    APP=acme
-elif [ -e "/usr/sbin/uacme" ]; then
-    ACME=/usr/sbin/uacme
-    HPROGRAM=/usr/share/uacme/uacme.sh
-    APP=uacme
-else
-    echo "Please install ACME or uACME package"
-    return 1
-fi
-
-export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
-export NO_TIMESTAMP=1
-
-UHTTPD_LISTEN_HTTP=
-PRODUCTION_STATE_DIR='/etc/acme'
-STAGING_STATE_DIR='/etc/acme/staging'
-
-ACCOUNT_EMAIL=
-DEBUG=0
-NGINX_WEBSERVER=0
-UPDATE_NGINX=0
-UPDATE_UHTTPD=0
-UPDATE_HAPROXY=0
-FW_RULE=
-USER_CLEANUP=
-ACME_URL=
-ACME_STAGING_URL=
-
-. /lib/functions.sh
-
-check_cron()
-{
-    [ -f "/etc/crontabs/root" ] && grep -q '/etc/init.d/acme' /etc/crontabs/root && return
-    echo "0 0 * * * /etc/init.d/acme start" >> /etc/crontabs/root
-    /etc/init.d/cron start
-}
-
-log()
-{
-    logger -t $APP -s -p daemon.info "$@"
-}
-
-err()
-{
-    logger -t $APP -s -p daemon.err "$@"
-}
-
-debug()
-{
-    [ "$DEBUG" -eq "1" ] && logger -t $APP -s -p daemon.debug "$@"
-}
-
-get_listeners() {
-    local proto rq sq listen remote state program
-    netstat -nptl 2>/dev/null | while read proto listen program; do
-        case "$proto#$listen#$program" in
-            tcp#*:80#[0-9]*/*) echo -n "${program%% *} " ;;
-        esac
-    done
-}
-
-pre_checks()
-{
-    main_domain="$1"
-
-    log "Running pre checks for $main_domain."
-
-    listeners="$(get_listeners)"
-
-    debug "port80 listens: $listeners"
-
-    for listener in $(get_listeners); do
-       pid="${listener%/*}"
-       cmd="${listener#*/}"
-
-       case "$cmd" in
-           uhttpd)
-               debug "Found uhttpd listening on port 80"
-               if [ "$APP" = "acme" ]; then
-                   UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http)
-                   if [ -z "$UHTTPD_LISTEN_HTTP" ]; then
-                       err "$main_domain: Unable to find uhttpd listen config."
-                       err "Manually disable uhttpd or set webroot to continue."
-                       return 1
-                   fi
-                   uci set uhttpd.main.listen_http=''
-                   uci commit uhttpd || return 1
-                   if ! /etc/init.d/uhttpd reload ; then
-                       uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
-                       uci commit uhttpd
-                       return 1
-                   fi
-               fi
-           ;;
-           nginx*)
-               debug "Found nginx listening on port 80"
-               NGINX_WEBSERVER=1
-               if [ "$APP" = "acme" ]; then
-                   local tries=0
-                   while grep -sq "$cmd" "/proc/$pid/cmdline" && kill -0 "$pid"; do
-                   /etc/init.d/nginx stop
-                       if [ $tries -gt 10 ]; then
-                           debug "Can't stop nginx. Terminating script."
-                           return 1
-                       fi
-                   debug "Waiting for nginx to stop..."
-                   tries=$((tries + 1))
-                   sleep 1
-                   done
-               fi
-            ;;
-           "")
-               err "Nothing listening on port 80."
-               err "Standalone mode not supported, setup uhttpd or nginx"
-               return 1
-            ;;
-            *)
-               err "$main_domain: unsupported (apache/haproxy?) daemon is listening on port 80."
-               err "if webroot is set on your current webserver comment line 132 (return 1) from this script."
-               return 1
-            ;;
-       esac
-    done
-
-    FW_RULE=$(uci add firewall rule) || return 1
-    uci set firewall."$FW_RULE".name='uacme: temporarily allow incoming http'
-    uci set firewall."$FW_RULE".enabled='1'
-    uci set firewall."$FW_RULE".target='ACCEPT'
-    uci set firewall."$FW_RULE".src='wan'
-    uci set firewall."$FW_RULE".proto='tcp'
-    uci set firewall."$FW_RULE".dest_port='80'
-    uci commit firewall
-    /etc/init.d/firewall reload
-
-    debug "added firewall rule: $FW_RULE"
-    return 0
-}
-
-post_checks()
-{
-    log "Running post checks (cleanup)."
-    # $FW_RULE contains the string to identify firewall rule created earlier
-    if [ -n "$FW_RULE" ]; then
-       uci delete firewall."$FW_RULE"
-       uci commit firewall
-       /etc/init.d/firewall reload
-    fi
-
-    if [ -e /etc/init.d/uhttpd ] && [ "$UPDATE_UHTTPD" -eq 1 ]; then
-       uci commit uhttpd
-       /etc/init.d/uhttpd reload
-       log "Restarting uhttpd..."
-    fi
-
-    if [ -e /etc/init.d/nginx ] && ( [ "$NGINX_WEBSERVER" -eq 1 ] || [ "$UPDATE_NGINX" -eq 1 ]; ); then
-       NGINX_WEBSERVER=0
-       /etc/init.d/nginx restart
-       log "Restarting nginx..."
-    fi
-
-    if [ -e /etc/init.d/haproxy ] && [ "$UPDATE_HAPROXY" -eq 1 ]; then
-       /etc/init.d/haproxy restart
-       log "Restarting haproxy..."
-    fi
-
-    if [ -n "$USER_CLEANUP" ] && [ -f "$USER_CLEANUP" ]; then
-       log "Running user-provided cleanup script from $USER_CLEANUP."
-       "$USER_CLEANUP" || return 1
-    fi
-}
-
-err_out()
-{
-    post_checks
-    exit 1
-}
-
-int_out()
-{
-    post_checks
-    trap - INT
-    kill -INT $$
-}
-
-is_staging()
-{
-    local main_domain="$1"
-
-    grep -q "acme-staging" "$STATE_DIR/$main_domain/${main_domain}.conf"
-    return $?
-}
-
-issue_cert()
-{
-    local section="$1"
-    local acme_args=
-    local debug=
-    local enabled
-    local use_staging
-    local update_uhttpd
-    local update_nginx
-    local update_haproxy
-    local keylength
-    local domains
-    local main_domain
-    local failed_dir
-    local webroot
-    local dns
-    local tls
-    local user_setup
-    local user_cleanup
-    local ret
-    local staging=
-    local HOOK=
-
-    # reload uci values, as the value of use_staging may have changed
-    config_load acme
-    config_get_bool enabled "$section" enabled 0
-    config_get_bool use_staging "$section" use_staging
-    config_get_bool update_uhttpd "$section" update_uhttpd
-    config_get_bool update_nginx "$section" update_nginx
-    config_get_bool update_haproxy "$section" update_haproxy
-    config_get domains "$section" domains
-    config_get keylength "$section" keylength
-    config_get webroot "$section" webroot
-    config_get dns "$section" dns
-    config_get tls "$section" tls
-    config_get user_setup "$section" user_setup
-    config_get user_cleanup "$section" user_cleanup
-
-    UPDATE_NGINX=$update_nginx
-    UPDATE_UHTTPD=$update_uhttpd
-    UPDATE_HAPROXY=$update_haproxy
-    USER_CLEANUP=$user_cleanup
-
-    [ "$enabled" -eq "1" ] || return 0
-
-    if [ "$APP" = "uacme" ]; then
-       [ "$DEBUG" -eq "1" ] && debug="--verbose --verbose"
-       [ "$tls" -eq "1" ] && HPROGRAM=/usr/share/uacme/ualpn.sh
-    elif [ "$APP" = "acme" ]; then
-       [ "$DEBUG" -eq "1" ] && acme_args="$acme_args --debug"
-    fi
-    if [ "$use_staging" -eq "1" ]; then
-       STATE_DIR="$STAGING_STATE_DIR";
-
-       # Check if we should use a custom stagin URL
-        if [ "$APP" = "uacme" -a -n "$ACME_STAGING_URL" ]; then
-               ACME="$ACME --acme-url $ACME_STAGING_URL"
-       else
-               staging="--staging";
-       fi
-    else
-       STATE_DIR="$PRODUCTION_STATE_DIR";
-       staging="";
-
-       if [ "$APP" = "uacme" -a -n "$ACME_URL" ]; then
-               ACME="$ACME --acme-url $ACME_URL"
-       fi
-    fi
-
-    set -- $domains
-    main_domain=$1
-
-    if [ -n "$user_setup" ] && [ -f "$user_setup" ]; then
-       log "Running user-provided setup script from $user_setup."
-       "$user_setup" "$main_domain" || return 2
-    else
-       [ -n "$webroot" ] || [ -n "$dns" ] || [ -n "$tls" ] || pre_checks "$main_domain" || return 2
-    fi
-
-    log "Running $APP for $main_domain"
-
-    if [ "$APP" = "uacme" ]; then
-       if [ ! -f  "$STATE_DIR/private/key.pem" ]; then
-           log "Create a new ACME account with email $ACCOUNT_EMAIL use staging=$use_staging"
-           $ACME $debug --confdir "$STATE_DIR" $staging --yes new $ACCOUNT_EMAIL
-       fi
-
-       if [ -f "$STATE_DIR/$main_domain/cert.pem" ]; then
-           log "Found previous cert config, use staging=$use_staging. Issuing renew."
-           export CHALLENGE_PATH="$webroot"
-           $ACME $debug --confdir "$STATE_DIR" $staging --never-create issue $domains --hook=$HPROGRAM; ret=$?
-           post_checks
-           return $ret
-       fi
-    fi
-    if [ "$APP" = "acme" ]; then
-       handle_credentials() {
-           local credential="$1"
-           eval export "$credential"
-       }
-       config_list_foreach "$section" credentials handle_credentials
-
-       if [ -e "$STATE_DIR/$main_domain" ]; then
-           if [ "$use_staging" -eq "0" ] && is_staging "$main_domain"; then
-               log "Found previous cert issued using staging server. Moving it out of the way."
-               mv "$STATE_DIR/$main_domain" "$STATE_DIR/$main_domain.staging"
-           else
-               log "Found previous cert config. Issuing renew."
-               $ACME --home "$STATE_DIR" --renew -d "$main_domain" "$acme_args"; ret=$?
-               post_checks
-               return $ret
-           fi
-       fi
-    fi
-
-    acme_args="$acme_args --bits $keylength"
-    acme_args="$acme_args $(for d in $domains; do echo -n " $d "; done)"
-    if [ "$APP" = "acme" ]; then
-       [ -n "$ACCOUNT_EMAIL" ] && acme_args="$acme_args --accountemail $ACCOUNT_EMAIL"
-       [ "$use_staging" -eq "1" ] && acme_args="$acme_args --staging"
-    fi
-    if [ -n "$dns" ]; then
-#TO-DO
-       if [ "$APP" = "acme" ]; then
-           log "Using dns mode"
-           acme_args="$acme_args --dns $dns"
-       else
-           log "Using dns mode, dns-01 is not wrapped yet"
-           return 2
-#          uacme_args="$uacme_args --dns $dns"
-       fi
-    elif [ -n "$tls" ]; then
-       if [ "$APP" = "uacme" ]; then
-            log "Using TLS mode"
-       else
-            log "TLS not supported by $APP"
-            return 2
-       fi
-    elif [ -z "$webroot" ]; then
-       if [ "$APP" = "acme" ]; then
-           log "Using standalone mode"
-           acme_args="$acme_args --standalone --listen-v6"
-       else
-           log "Standalone not supported by $APP"
-           return 2
-       fi
-    else
-       if [ ! -d "$webroot" ]; then
-           err "$main_domain: Webroot dir '$webroot' does not exist!"
-           post_checks
-           return 2
-       fi
-       log "Using webroot dir: $webroot"
-       if [ "$APP" = "uacme" ]; then
-           export CHALLENGE_PATH="$webroot"
-       else
-           acme_args="$acme_args --webroot $webroot"
-       fi
-    fi
-
-    if [ "$APP" = "uacme" ]; then
-       workdir="--confdir"
-       HOOK="--hook=$HPROGRAM"
-    else
-       workdir="--home"
-    fi
-
-    $ACME $debug $workdir "$STATE_DIR" $staging issue $acme_args $HOOK; ret=$?
-    if [ "$ret" -ne 0 ]; then
-       failed_dir="$STATE_DIR/${main_domain}.failed-$(date +%s)"
-       err "Issuing cert for $main_domain failed. Moving state to $failed_dir"
-       [ -d "$STATE_DIR/$main_domain" ] && mv "$STATE_DIR/$main_domain" "$failed_dir"
-       [ -d "$STATE_DIR/private/$main_domain" ] && mv "$STATE_DIR/private/$main_domain" "$failed_dir"
-       post_checks
-       return $ret
-    fi
-
-    if [ -e /etc/init.d/uhttpd ] && [ "$update_uhttpd" -eq "1" ]; then
-       if [ "$APP" = "uacme" ]; then
-           uci set uhttpd.main.key="$STATE_DIR/private/${main_domain}/key.pem"
-           uci set uhttpd.main.cert="$STATE_DIR/${main_domain}/cert.pem"
-       else
-           uci set uhttpd.main.key="$STATE_DIR/${main_domain}/${main_domain}.key"
-           uci set uhttpd.main.cert="$STATE_DIR/${main_domain}/fullchain.cer"
-       fi
-       # commit and reload is in post_checks
-    fi
-
-    local nginx_updated
-    nginx_updated=0
-    if command -v nginx-util 2>/dev/null && [ "$update_nginx" -eq "1" ]; then
-       nginx_updated=1
-       for domain in $domains; do
-           if [ "$APP" = "uacme" ]; then
-               nginx-util add_ssl "${domain}" uacme "$STATE_DIR/${main_domain}/cert.pem" \
-                   "$STATE_DIR/private/${main_domain}/key.pem" || nginx_updated=0
-           else
-               nginx-util add_ssl "${domain}" acme "$STATE_DIR/${main_domain}/fullchain.cer" \
-                   "$STATE_DIR/${main_domain}/${main_domain}.key" || nginx_updated=0
-           fi
-       done
-       # reload is in post_checks
-    fi
-
-    if [ "$nginx_updated" -eq "0" ] && [ -w /etc/nginx/nginx.conf ] && [ "$update_nginx" -eq "1" ]; then
-       if [ "$APP" = "uacme" ]; then
-           sed -i "s#ssl_certificate\ .*#ssl_certificate $STATE_DIR/${main_domain}/cert.pem;#g" /etc/nginx/nginx.conf
-           sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key $STATE_DIR/private/${main_domain}/key.pem;#g" /etc/nginx/nginx.conf
-       else
-           sed -i "s#ssl_certificate\ .*#ssl_certificate $STATE_DIR/${main_domain}/fullchain.cer;#g" /etc/nginx/nginx.conf
-           sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key $STATE_DIR/${main_domain}/${main_domain}.key;#g" /etc/nginx/nginx.conf
-       fi
-       # commit and reload is in post_checks
-    fi
-
-    if [ -e /etc/init.d/haproxy ] && [ "$update_haproxy" -eq 1 ]; then
-       if [ "$APP" = "uacme" ]; then
-           cat $STATE_DIR/${main_domain}/cert.pem $STATE_DIR/private/${main_domain}/key.pem > $STATE_DIR/${main_domain}/full_haproxy.pem
-       else
-           cat $STATE_DIR/${main_domain}/fullchain.cer $STATE_DIR/${main_domain}/${main_domain}.key > $STATE_DIR/${main_domain}/full_haproxy.pem
-       fi
-    fi
-
-    post_checks
-}
-
-issue_cert_with_retries() {
-       local section="$1"
-       local use_staging
-       local retries
-       local use_auto_staging
-       local infinite_retries
-       config_get_bool use_staging "$section" use_staging
-       config_get_bool use_auto_staging "$section" use_auto_staging
-       config_get_bool enabled "$section" enabled
-       config_get retries "$section" retries
-
-       [ -z "$retries" ] && retries=1
-       [ -z "$use_auto_staging" ] && use_auto_staging=0
-       [ "$retries" -eq "0" ] && infinite_retries=1
-       [ "$enabled" -eq "1" ] || return 0
-
-       while true; do
-               issue_cert "$1"; ret=$?
-
-               if [ "$ret" -eq "2" ]; then
-                       # An error occurred while retrieving the certificate.
-                       retries="$((retries-1))"
-
-                       if [ "$use_auto_staging" -eq "1" ] && [ "$use_staging" -eq "0" ]; then
-                               log "Production certificate could not be obtained. Switching to staging server."
-                               use_staging=1
-                               uci set "acme.$1.use_staging=1"
-                               uci commit acme
-                       fi
-
-                       if [ -z "$infinite_retries" ] && [ "$retries" -lt "1" ]; then
-                               log "An error occurred while retrieving the certificate. Retries exceeded."
-                               return "$ret"
-                       fi
-
-                       if [ "$use_staging" -eq "1" ]; then
-                               # The "Failed Validations" limit of LetsEncrypt is 60 per hour. This
-                               # means one failure every minute. Here we wait 2 minutes to be within
-                               # limits for sure.
-                               sleeptime=120
-                       else
-                               # There is a "Failed Validation" limit of LetsEncrypt is 5 failures per
-                               # account, per hostname, per hour. This means one failure every 12
-                               # minutes. Here we wait 25 minutes to be within limits for sure.
-                               sleeptime=1500
-                       fi
-
-                       log "An error occurred while retrieving the certificate. Retrying in $sleeptime seconds."
-                       sleep "$sleeptime"
-                       continue
-               else
-                       if [ "$use_auto_staging" -eq "1" ]; then
-                               if [ "$use_staging" -eq "0" ]; then
-                                       log "Production certificate obtained. Exiting."
-                               else
-                                       log "Staging certificate obtained. Continuing with production server."
-                                       use_staging=0
-                                       uci set "acme.$1.use_staging=0"
-                                       uci commit acme
-                                       continue
-                               fi
-                       fi
-
-                       return "$ret"
-               fi
-       done
-}
-
-load_vars()
-{
-    local section="$1"
-
-    PRODUCTION_STATE_DIR=$(config_get "$section" state_dir)
-    STAGING_STATE_DIR=$PRODUCTION_STATE_DIR/staging
-    ACCOUNT_EMAIL=$(config_get "$section" account_email)
-    DEBUG=$(config_get "$section" debug)
-    ACME_URL=$(config_get "$section" acme_url)
-    ACME_STAGING_URL=$(config_get "$section" acme_staging_url)
-}
-
-if [ -z "$INCLUDE_ONLY" ]; then
-    check_cron
-    [ -n "$CHECK_CRON" ] && exit 0
-    [ -e "/var/run/acme_boot" ] && rm -f "/var/run/acme_boot" && exit 0
-fi
-
-config_load acme
-config_foreach load_vars acme
-
-if [ -z "$PRODUCTION_STATE_DIR" ] || [ -z "$ACCOUNT_EMAIL" ]; then
-    err "state_dir and account_email must be set"
-    exit 1
-fi
-
-[ -d "$PRODUCTION_STATE_DIR" ] || mkdir -p "$PRODUCTION_STATE_DIR"
-[ -d "$STAGING_STATE_DIR" ] || mkdir -p "$STAGING_STATE_DIR"
-
-trap err_out HUP TERM
-trap int_out INT
-
-if [ -z "$INCLUDE_ONLY" ]; then
-    config_foreach issue_cert_with_retries cert
-
-    exit 0
-fi
git clone https://git.99rst.org/PROJECT