diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml
index 727ba315..c1406d91 100644
--- a/.github/workflows/DNS.yml
+++ b/.github/workflows/DNS.yml
@@ -1,5 +1,6 @@
name: DNS
on:
+ workflow_dispatch:
push:
paths:
- 'dnsapi/*.sh'
diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml
index ea446d84..435fd6b5 100644
--- a/.github/workflows/dockerhub.yml
+++ b/.github/workflows/dockerhub.yml
@@ -15,6 +15,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+env:
+ DOCKER_IMAGE: neilpang/acme.sh
jobs:
CheckToken:
@@ -44,6 +46,11 @@ jobs:
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
+ - name: Extract Docker metadata
+ id: meta
+ uses: docker/metadata-action@v5.5.1
+ with:
+ images: ${DOCKER_IMAGE}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: login to docker hub
@@ -51,8 +58,6 @@ jobs:
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: build and push the image
run: |
- DOCKER_IMAGE=neilpang/acme.sh
-
if [[ $GITHUB_REF == refs/tags/* ]]; then
DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}
fi
@@ -66,8 +71,14 @@ jobs:
fi
fi
+ DOCKER_LABELS=()
+ while read -r label; do
+ DOCKER_LABELS+=(--label "${label}")
+ done <<<"${DOCKER_METADATA_OUTPUT_LABELS}"
+
docker buildx build \
--tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
+ "${DOCKER_LABELS[@]}" \
--output "type=image,push=true" \
--build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .
diff --git a/acme.sh b/acme.sh
index 4cf407f2..e39a146b 100755
--- a/acme.sh
+++ b/acme.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-VER=3.0.8
+VER=3.1.0
PROJECT_NAME="acme.sh"
@@ -672,8 +672,10 @@ _hex_dump() {
#0 1 2 3 4 5 6 7 8 9 - _ . ~
#30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e
+#_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"
@@ -883,6 +885,9 @@ _url_encode() {
;;
#other hex
*)
+ if [ "$_upper_hex" = "upper-hex" ]; then
+ _hex_code=$(printf "%s" "$_hex_code" | _upper_case)
+ fi
printf '%%%s' "$_hex_code"
;;
esac
@@ -2361,7 +2366,7 @@ _clear_conf() {
_sdkey="$2"
if [ "$_c_c_f" ]; then
_conf_data="$(cat "$_c_c_f")"
- echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f"
+ echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f"
else
_err "Config file is empty, cannot clear"
fi
@@ -5111,6 +5116,19 @@ $_authorizations_map"
_on_issue_err "$_post_hook" "$vlist"
return 1
fi
+ _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
+ _sleep_overload_retry_sec=$_retryafter
+ if [ "$_sleep_overload_retry_sec" ]; then
+ if [ $_sleep_overload_retry_sec -le 600 ]; then
+ _sleep $_sleep_overload_retry_sec
+ else
+ _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore."
+ _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+ _clearup
+ _on_issue_err "$_post_hook" "$vlist"
+ return 1
+ fi
+ fi
done
done
diff --git a/deploy/ali_cdn.sh b/deploy/ali_cdn.sh
new file mode 100644
index 00000000..70a2e532
--- /dev/null
+++ b/deploy/ali_cdn.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env sh
+# shellcheck disable=SC2034,SC2154
+
+# Script to create certificate to Alibaba Cloud CDN
+#
+# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
+#
+# This deployment required following variables
+# export Ali_Key="ALIACCESSKEY"
+# export Ali_Secret="ALISECRETKEY"
+# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
+#
+# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
+# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com"
+# If you have multiple CDN domains using the same certificate, just
+# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
+#
+# For DCDN, see ali_dcdn deploy hook
+
+Ali_CDN_API="https://cdn.aliyuncs.com/"
+
+ali_cdn_deploy() {
+ _cdomain="$1"
+ _ckey="$2"
+ _ccert="$3"
+ _cca="$4"
+ _cfullchain="$5"
+
+ _debug _cdomain "$_cdomain"
+ _debug _ckey "$_ckey"
+ _debug _ccert "$_ccert"
+ _debug _cca "$_cca"
+ _debug _cfullchain "$_cfullchain"
+
+ # Load dnsapi/dns_ali.sh to reduce the duplicated codes
+ # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+ dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
+ # shellcheck source=/dev/null
+ if ! . "$dnsapi_ali"; then
+ _err "Error loading file $dnsapi_ali. Please check your API file and try again."
+ return 1
+ fi
+
+ _prepare_ali_credentials || return 1
+
+ _getdeployconf DEPLOY_ALI_CDN_DOMAIN
+ if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then
+ _savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN"
+ else
+ DEPLOY_ALI_CDN_DOMAIN="$_cdomain"
+ fi
+
+ # read cert and key files and urlencode both
+ _cert=$(_url_encode upper-hex <"$_cfullchain")
+ _key=$(_url_encode upper-hex <"$_ckey")
+
+ _debug2 _cert "$_cert"
+ _debug2 _key "$_key"
+
+ ## update domain ssl config
+ for domain in $DEPLOY_ALI_CDN_DOMAIN; do
+ _set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
+ if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then
+ _info "Domain $domain certificate has been deployed successfully"
+ fi
+ done
+
+ return 0
+}
+
+# domain pub pri
+_set_cdn_domain_ssl_certificate_query() {
+ endpoint=$Ali_CDN_API
+ query=''
+ query=$query'AccessKeyId='$Ali_Key
+ query=$query'&Action=SetCdnDomainSSLCertificate'
+ query=$query'&CertType=upload'
+ query=$query'&DomainName='$1
+ query=$query'&Format=json'
+ query=$query'&SSLPri='$3
+ query=$query'&SSLProtocol=on'
+ query=$query'&SSLPub='$2
+ query=$query'&SignatureMethod=HMAC-SHA1'
+ query=$query"&SignatureNonce=$(_ali_nonce)"
+ query=$query'&SignatureVersion=1.0'
+ query=$query'&Timestamp='$(_timestamp)
+ query=$query'&Version=2018-05-10'
+}
diff --git a/deploy/ali_dcdn.sh b/deploy/ali_dcdn.sh
new file mode 100644
index 00000000..14ac500a
--- /dev/null
+++ b/deploy/ali_dcdn.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env sh
+# shellcheck disable=SC2034,SC2154
+
+# Script to create certificate to Alibaba Cloud DCDN
+#
+# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
+#
+# This deployment required following variables
+# export Ali_Key="ALIACCESSKEY"
+# export Ali_Secret="ALISECRETKEY"
+# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
+#
+# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
+# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com"
+# If you have multiple CDN domains using the same certificate, just
+# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com"
+#
+# For regular CDN, see ali_cdn deploy hook
+
+Ali_DCDN_API="https://dcdn.aliyuncs.com/"
+
+ali_dcdn_deploy() {
+ _cdomain="$1"
+ _ckey="$2"
+ _ccert="$3"
+ _cca="$4"
+ _cfullchain="$5"
+
+ _debug _cdomain "$_cdomain"
+ _debug _ckey "$_ckey"
+ _debug _ccert "$_ccert"
+ _debug _cca "$_cca"
+ _debug _cfullchain "$_cfullchain"
+
+ # Load dnsapi/dns_ali.sh to reduce the duplicated codes
+ # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+ dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
+ # shellcheck source=/dev/null
+ if ! . "$dnsapi_ali"; then
+ _err "Error loading file $dnsapi_ali. Please check your API file and try again."
+ return 1
+ fi
+
+ _prepare_ali_credentials || return 1
+
+ _getdeployconf DEPLOY_ALI_DCDN_DOMAIN
+ if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then
+ _savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN"
+ else
+ DEPLOY_ALI_DCDN_DOMAIN="$_cdomain"
+ fi
+
+ # read cert and key files and urlencode both
+ _cert=$(_url_encode upper-hex <"$_cfullchain")
+ _key=$(_url_encode upper-hex <"$_ckey")
+
+ _debug2 _cert "$_cert"
+ _debug2 _key "$_key"
+
+ ## update domain ssl config
+ for domain in $DEPLOY_ALI_DCDN_DOMAIN; do
+ _set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
+ if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then
+ _info "Domain $domain certificate has been deployed successfully"
+ fi
+ done
+
+ return 0
+}
+
+# domain pub pri
+_set_dcdn_domain_ssl_certificate_query() {
+ endpoint=$Ali_DCDN_API
+ query=''
+ query=$query'AccessKeyId='$Ali_Key
+ query=$query'&Action=SetDcdnDomainSSLCertificate'
+ query=$query'&CertType=upload'
+ query=$query'&DomainName='$1
+ query=$query'&Format=json'
+ query=$query'&SSLPri='$3
+ query=$query'&SSLProtocol=on'
+ query=$query'&SSLPub='$2
+ query=$query'&SignatureMethod=HMAC-SHA1'
+ query=$query"&SignatureNonce=$(_ali_nonce)"
+ query=$query'&SignatureVersion=1.0'
+ query=$query'&Timestamp='$(_timestamp)
+ query=$query'&Version=2018-01-15'
+}
diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh
index 3ddb8de1..0d01e199 100644
--- a/deploy/synology_dsm.sh
+++ b/deploy/synology_dsm.sh
@@ -113,9 +113,9 @@ synology_dsm_deploy() {
# Default values for scheme, hostname and port
# Defaulting to localhost and http, because it's localhost…
- [ -n "$SYNO_SCHEME" ] || SYNO_SCHEME="http"
- [ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME="localhost"
- [ -n "$SYNO_PORT" ] || SYNO_PORT="5000"
+ [ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http
+ [ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost
+ [ -n "$SYNO_PORT" ] || SYNO_PORT=5000
_savedeployconf SYNO_SCHEME "$SYNO_SCHEME"
_savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME"
_savedeployconf SYNO_PORT "$SYNO_PORT"
diff --git a/deploy/unifi.sh b/deploy/unifi.sh
index a864135e..4d8c058e 100644
--- a/deploy/unifi.sh
+++ b/deploy/unifi.sh
@@ -5,6 +5,15 @@
# - self-hosted Unifi Controller
# - Unifi Cloud Key (Gen1/2/2+)
# - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)
+# - Unifi Dream Machine
+# This has not been tested on other "all-in-one" devices such as
+# UDM Pro or Unifi Express.
+#
+# OS Version v2.0.0+
+# Network Application version 7.0.0+
+# OS version ~3.1 removed java and keytool from the UnifiOS.
+# Using PKCS12 format keystore appears to work fine.
+#
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359
#returns 0 means success, otherwise error.
@@ -74,14 +83,16 @@ unifi_deploy() {
_reload_cmd=""
# Unifi Controller environment (self hosted or any Cloud Key) --
- # auto-detect by file /usr/lib/unifi/data/keystore:
+ # auto-detect by file /usr/lib/unifi/data/keystore
_unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}"
if [ -f "$_unifi_keystore" ]; then
- _info "Installing certificate for Unifi Controller (Java keystore)"
_debug _unifi_keystore "$_unifi_keystore"
if ! _exists keytool; then
- _err "keytool not found"
- return 1
+ _do_keytool=0
+ _info "Installing certificate for Unifi Controller (PKCS12 keystore)."
+ else
+ _do_keytool=1
+ _info "Installing certificate for Unifi Controller (Java keystore)"
fi
if [ ! -w "$_unifi_keystore" ]; then
_err "The file $_unifi_keystore is not writable, please change the permission."
@@ -92,6 +103,7 @@ unifi_deploy() {
_debug "Generate import pkcs12"
_import_pkcs12="$(_mktemp)"
+ _debug "_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root"
_toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
# shellcheck disable=SC2181
if [ "$?" != "0" ]; then
@@ -99,22 +111,57 @@ unifi_deploy() {
return 1
fi
- _debug "Import into keystore: $_unifi_keystore"
- if keytool -importkeystore \
- -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
- -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
- -alias unifi -noprompt; then
- _debug "Import keystore success!"
- rm "$_import_pkcs12"
+ # Save the existing keystore in case something goes wrong.
+ mv -f "${_unifi_keystore}" "${_unifi_keystore}"_original
+ _info "Previous keystore saved to ${_unifi_keystore}_original."
+
+ if [ "$_do_keytool" -eq 1 ]; then
+ _debug "Import into keystore: $_unifi_keystore"
+ if keytool -importkeystore \
+ -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
+ -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
+ -alias unifi -noprompt; then
+ _debug "Import keystore success!"
+ else
+ _err "Error importing into Unifi Java keystore."
+ _err "Please re-run with --debug and report a bug."
+ _info "Restoring original keystore."
+ mv -f "${_unifi_keystore}"_original "${_unifi_keystore}"
+ rm "$_import_pkcs12"
+ return 1
+ fi
else
- _err "Error importing into Unifi Java keystore."
- _err "Please re-run with --debug and report a bug."
- rm "$_import_pkcs12"
- return 1
+ _debug "Copying new keystore to $_unifi_keystore"
+ cp -f "$_import_pkcs12" "$_unifi_keystore"
+ fi
+
+ # Update unifi service for certificate cipher compatibility
+ if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \
+ -in "$_import_pkcs12" \
+ -password pass:aircontrolenterprise \
+ -nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \
+ -noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then
+ cp -f /usr/lib/unifi/data/system.properties /usr/lib/unifi/data/system.properties_original
+ _info "Updating system configuration for cipher compatibility."
+ _info "Saved original system config to /usr/lib/unifi/data/system.properties_original"
+ sed -i '/unifi\.https\.ciphers/d' /usr/lib/unifi/data/system.properties
+ echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>/usr/lib/unifi/data/system.properties
+ sed -i '/unifi\.https\.sslEnabledProtocols/d' /usr/lib/unifi/data/system.properties
+ echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>/usr/lib/unifi/data/system.properties
+ _info "System configuration updated."
fi
+ rm "$_import_pkcs12"
+
+ # Restarting unifi-core will bring up unifi, doing it out of order results in
+ # a certificate error, and breaks wifiman.
+ # Restart if we aren't doing unifi-core, otherwise stop for later restart.
if systemctl -q is-active unifi; then
- _reload_cmd="${_reload_cmd:+$_reload_cmd && }service unifi restart"
+ if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then
+ _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi"
+ else
+ _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl stop unifi"
+ fi
fi
_services_updated="${_services_updated} unifi"
_info "Install Unifi Controller certificate success!"
@@ -165,6 +212,11 @@ unifi_deploy() {
return 1
fi
+ # Save the existing certs in case something goes wrong.
+ cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt
+ cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key
+ _info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt/key."
+
cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt"
cat "$_ckey" >"${_unifi_core_config}/unifi-core.key"
diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh
index 9bdfc20b..0f1626f5 100755
--- a/dnsapi/dns_ali.sh
+++ b/dnsapi/dns_ali.sh
@@ -9,25 +9,19 @@ Options:
Ali_Secret API Secret
'
-Ali_API="https://alidns.aliyuncs.com/"
+# NOTICE:
+# This file is referenced by Alibaba Cloud Services deploy hooks
+# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+# Be careful when modifying this file, especially when making breaking changes for common functions
+
+Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() {
fulldomain=$1
txtvalue=$2
- Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
- Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
- if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
- Ali_Key=""
- Ali_Secret=""
- _err "You don't specify aliyun api key and secret yet."
- return 1
- fi
-
- #save the api key and secret to the account conf file.
- _saveaccountconf_mutable Ali_Key "$Ali_Key"
- _saveaccountconf_mutable Ali_Secret "$Ali_Secret"
+ _prepare_ali_credentials || return 1
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
@@ -52,7 +46,67 @@ dns_ali_rm() {
_clean
}
-#################### Private functions below ##################################
+#################### Alibaba Cloud common functions below ####################
+
+_prepare_ali_credentials() {
+ Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
+ Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
+ if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
+ Ali_Key=""
+ Ali_Secret=""
+ _err "You don't specify aliyun api key and secret yet."
+ return 1
+ fi
+
+ #save the api key and secret to the account conf file.
+ _saveaccountconf_mutable Ali_Key "$Ali_Key"
+ _saveaccountconf_mutable Ali_Secret "$Ali_Secret"
+}
+
+# act ign mtd
+_ali_rest() {
+ act="$1"
+ ign="$2"
+ mtd="${3:-GET}"
+
+ signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
+ signature=$(printf "%s" "$signature" | _url_encode upper-hex)
+ url="$endpoint?Signature=$signature"
+
+ if [ "$mtd" = "GET" ]; then
+ url="$url&$query"
+ response="$(_get "$url")"
+ else
+ response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")"
+ fi
+
+ _ret="$?"
+ _debug2 response "$response"
+ if [ "$_ret" != "0" ]; then
+ _err "Error <$act>"
+ return 1
+ fi
+
+ if [ -z "$ign" ]; then
+ message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
+ if [ "$message" ]; then
+ _err "$message"
+ return 1
+ fi
+ fi
+}
+
+_ali_nonce() {
+ #_head_n 1 "
- return 1
- fi
-
- _debug2 response "$response"
- if [ -z "$2" ]; then
- message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
- if [ "$message" ]; then
- _err "$message"
- return 1
- fi
- fi
-}
-
-_ali_urlencode() {
- _str="$1"
- _str_len=${#_str}
- _u_i=1
- while [ "$_u_i" -le "$_str_len" ]; do
- _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
- case $_str_c in [a-zA-Z0-9.~_-])
- printf "%s" "$_str_c"
- ;;
- *)
- printf "%%%02X" "'$_str_c"
- ;;
- esac
- _u_i="$(_math "$_u_i" + 1)"
- done
-}
-
-_ali_nonce() {
- #_head_n 1 /\>/g")
+ _s=$(echo "$_s" | sed 's/"/\"/g')
+ printf -- %s "$_s"
+}
+
_inwx_login() {
if _inwx_check_cookie; then
@@ -170,6 +179,8 @@ _inwx_login() {
return 0
fi
+ XML_PASS=$(_htmlEscape "$INWX_Password")
+
xml_content=$(printf '
account.login
@@ -193,7 +204,7 @@ _inwx_login() {
- ' "$INWX_User" "$INWX_Password")
+ ' "$INWX_User" "$XML_PASS")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index 7fd0d99c..edc789e1 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -14,6 +14,8 @@ Options:
# User must provide login data and URL to the ISPConfig installation incl. port.
# The remote user in ISPConfig must have access to:
# - DNS txt Functions
+# - DNS zone functions
+# - Client functions
######## Public functions #####################
diff --git a/dnsapi/dns_miab.sh b/dnsapi/dns_miab.sh
index ec9867db..9416c8ce 100644
--- a/dnsapi/dns_miab.sh
+++ b/dnsapi/dns_miab.sh
@@ -16,8 +16,9 @@ Author: Darven Dissek, William Gertz
#Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_miab_add() {
fulldomain=$1
- txtvalue=$2
- _info "Using miab challange add"
+ # Added "value=" and "&ttl=300" to accomodate the new TXT record format used by the MIAB/PMIAB API
+ txtvalue="value=$2&ttl=300"
+ _info "Using miab challenge add"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
@@ -26,7 +27,7 @@ dns_miab_add() {
return 1
fi
- #check domain and seperate into doamin and host
+ #check domain and seperate into domain and host
if ! _get_root "$fulldomain"; then
_err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
return 1
@@ -55,7 +56,7 @@ dns_miab_rm() {
fulldomain=$1
txtvalue=$2
- _info "Using miab challage delete"
+ _info "Using miab challenge delete"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh
index 9df6262e..d5dbbcbc 100755
--- a/dnsapi/dns_nsupdate.sh
+++ b/dnsapi/dns_nsupdate.sh
@@ -20,6 +20,7 @@ dns_nsupdate_add() {
NSUPDATE_SERVER_PORT="${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}"
NSUPDATE_KEY="${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}"
NSUPDATE_ZONE="${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}"
+ NSUPDATE_OPT="${NSUPDATE_OPT:-$(_readaccountconf_mutable NSUPDATE_OPT)}"
_checkKeyFile || return 1
@@ -28,21 +29,25 @@ dns_nsupdate_add() {
_saveaccountconf_mutable NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
_saveaccountconf_mutable NSUPDATE_KEY "${NSUPDATE_KEY}"
_saveaccountconf_mutable NSUPDATE_ZONE "${NSUPDATE_ZONE}"
+ _saveaccountconf_mutable NSUPDATE_OPT "${NSUPDATE_OPT}"
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
+ [ -n "${NSUPDATE_OPT}" ] || NSUPDATE_OPT=""
_info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
if [ -z "${NSUPDATE_ZONE}" ]; then
- nsupdate -k "${NSUPDATE_KEY}" $nsdebug <.*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
@@ -152,7 +152,7 @@ dns_openprovider_rm() {
new_item="$(echo "$item" | sed -n 's/.*
- .*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
diff --git a/dnsapi/dns_timeweb.sh b/dnsapi/dns_timeweb.sh
new file mode 100644
index 00000000..9860872c
--- /dev/null
+++ b/dnsapi/dns_timeweb.sh
@@ -0,0 +1,406 @@
+#!/usr/bin/env sh
+
+# acme.sh DNS API for Timeweb Cloud provider (https://timeweb.cloud).
+#
+# Author: https://github.com/nikolaypronchev.
+#
+# Prerequisites:
+# Timeweb Cloud API JWT token. Obtain one from the Timeweb Cloud control panel
+# ("API and Terraform" section: https://timeweb.cloud/my/api-keys). The JWT token
+# must be provided to this script in one of two ways:
+# 1. As the "TW_Token" variable, for example: "export TW_Token=eyJhbG...zUxMiIs";
+# 2. As a "TW_Token" config entry in acme.sh account config file
+# (usually located at ~/.acme.sh/account.conf by default).
+
+TW_Api="https://api.timeweb.cloud/api/v1"
+
+################ Public functions ################
+
+# Adds an ACME DNS-01 challenge DNS TXT record via the Timeweb Cloud API.
+#
+# Param1: The ACME DNS-01 challenge FQDN.
+# Param2: The value of the ACME DNS-01 challenge TXT record.
+#
+# Example: dns_timeweb_add "_acme-challenge.sub.domain.com" "D-52Wm...4uYM"
+dns_timeweb_add() {
+ _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" started."
+
+ _timeweb_set_acme_fqdn "$1" || return 1
+ _timeweb_set_acme_txt "$2" || return 1
+ _timeweb_check_token || return 1
+ _timeweb_split_acme_fqdn || return 1
+ _timeweb_dns_txt_add || return 1
+
+ _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" finished."
+}
+
+# Removes a DNS TXT record via the Timeweb Cloud API.
+#
+# Param1: The ACME DNS-01 challenge FQDN.
+# Param2: The value of the ACME DNS-01 challenge TXT record.
+#
+# Example: dns_timeweb_rm "_acme-challenge.sub.domain.com" "D-52Wm...4uYM"
+dns_timeweb_rm() {
+ _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" started."
+
+ _timeweb_set_acme_fqdn "$1" || return 1
+ _timeweb_set_acme_txt "$2" || return 1
+ _timeweb_check_token || return 1
+ _timeweb_split_acme_fqdn || return 1
+ _timeweb_get_dns_txt || return 1
+ _timeweb_dns_txt_remove || return 1
+
+ _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" finished."
+}
+
+################ Private functions ################
+
+# Checks and sets the ACME DNS-01 challenge FQDN.
+#
+# Param1: The ACME DNS-01 challenge FQDN.
+#
+# Example: _timeweb_set_acme_fqdn "_acme-challenge.sub.domain.com"
+#
+# Sets the "Acme_Fqdn" variable (_acme-challenge.sub.domain.com)
+_timeweb_set_acme_fqdn() {
+ Acme_Fqdn=$1
+ _debug "Setting ACME DNS-01 challenge FQDN \"$Acme_Fqdn\"."
+ [ -z "$Acme_Fqdn" ] && {
+ _err "ACME DNS-01 challenge FQDN is empty."
+ return 1
+ }
+ return 0
+}
+
+# Checks and sets the value of the ACME DNS-01 challenge TXT record.
+#
+# Param1: Value of the ACME DNS-01 challenge TXT record.
+#
+# Example: _timeweb_set_acme_txt "D-52Wm...4uYM"
+#
+# Sets the "Acme_Txt" variable to the provided value (D-52Wm...4uYM)
+_timeweb_set_acme_txt() {
+ Acme_Txt=$1
+ _debug "Setting the value of the ACME DNS-01 challenge TXT record to \"$Acme_Txt\"."
+ [ -z "$Acme_Txt" ] && {
+ _err "ACME DNS-01 challenge TXT record value is empty."
+ return 1
+ }
+ return 0
+}
+
+# Checks if the Timeweb Cloud API JWT token is present (refer to the script description).
+# Adds or updates the token in the acme.sh account configuration.
+_timeweb_check_token() {
+ _debug "Checking for the presence of the Timeweb Cloud API JWT token."
+
+ TW_Token="${TW_Token:-$(_readaccountconf_mutable TW_Token)}"
+
+ [ -z "$TW_Token" ] && {
+ _err "Timeweb Cloud API JWT token was not found."
+ return 1
+ }
+
+ _saveaccountconf_mutable TW_Token "$TW_Token"
+}
+
+# Divides the ACME DNS-01 challenge FQDN into its main domain and subdomain components.
+_timeweb_split_acme_fqdn() {
+ _debug "Trying to divide \"$Acme_Fqdn\" into its main domain and subdomain components."
+
+ TW_Page_Limit=100
+ TW_Page_Offset=0
+ TW_Domains_Returned=""
+
+ while [ -z "$TW_Domains_Returned" ] || [ "$TW_Domains_Returned" -ge "$TW_Page_Limit" ]; do
+
+ _timeweb_list_domains "$TW_Page_Limit" "$TW_Page_Offset" || return 1
+
+ # Remove the 'subdomains' subarray to prevent confusion with FQDNs.
+
+ TW_Domains=$(
+ echo "$TW_Domains" |
+ sed 's/"subdomains":\[[^]]*]//g'
+ )
+
+ [ -z "$TW_Domains" ] && {
+ _err "Failed to parse the list of domains."
+ return 1
+ }
+
+ while
+ TW_Domain=$(
+ echo "$TW_Domains" |
+ sed -n 's/.*{[^{]*"fqdn":"\([^"]*\)"[^}]*}.*/\1/p'
+ )
+
+ [ -n "$TW_Domain" ] && {
+ _timeweb_is_main_domain "$TW_Domain" && return 0
+
+ TW_Domains=$(
+ echo "$TW_Domains" |
+ sed 's/{\([^{]*"fqdn":"'"$TW_Domain"'"[^}]*\)}//'
+ )
+ continue
+ }
+ do :; done
+
+ TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit")
+ done
+
+ _err "Failed to divide \"$Acme_Fqdn\" into its main domain and subdomain components."
+ return 1
+}
+
+# Searches for a previously added DNS TXT record.
+#
+# Sets the "TW_Dns_Txt_Id" variable.
+_timeweb_get_dns_txt() {
+ _debug "Trying to locate a DNS TXT record with the value \"$Acme_Txt\"."
+
+ TW_Page_Limit=100
+ TW_Page_Offset=0
+ TW_Dns_Records_Returned=""
+
+ while [ -z "$TW_Dns_Records_Returned" ] || [ "$TW_Dns_Records_Returned" -ge "$TW_Page_Limit" ]; do
+
+ _timeweb_list_dns_records "$TW_Page_Limit" "$TW_Page_Offset" || return 1
+
+ while
+ Dns_Record=$(
+ echo "$TW_Dns_Records" |
+ sed -n 's/.*{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}.*/\1/p'
+ )
+
+ [ -n "$Dns_Record" ] && {
+ _timeweb_is_added_txt "$Dns_Record" && return 0
+
+ TW_Dns_Records=$(
+ echo "$TW_Dns_Records" |
+ sed 's/{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}//'
+ )
+ continue
+ }
+ do :; done
+
+ TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit")
+ done
+
+ _err "DNS TXT record was not found."
+ return 1
+}
+
+# Lists domains via the Timeweb Cloud API.
+#
+# Param 1: Limit for listed domains.
+# Param 2: Offset for domains list.
+#
+# Sets the "TW_Domains" variable.
+# Sets the "TW_Domains_Returned" variable.
+_timeweb_list_domains() {
+ _debug "Listing domains via Timeweb Cloud API. Limit: $1, offset: $2."
+
+ export _H1="Authorization: Bearer $TW_Token"
+
+ if ! TW_Domains=$(_get "$TW_Api/domains?limit=$1&offset=$2"); then
+ _err "The request to the Timeweb Cloud API failed."
+ return 1
+ fi
+
+ [ -z "$TW_Domains" ] && {
+ _err "Empty response from the Timeweb Cloud API."
+ return 1
+ }
+
+ TW_Domains_Returned=$(
+ echo "$TW_Domains" |
+ sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/'
+ )
+
+ [ -z "$TW_Domains_Returned" ] && {
+ _err "Failed to extract the total count of domains."
+ return 1
+ }
+
+ [ "$TW_Domains_Returned" -eq "0" ] && {
+ _err "Domains are missing."
+ return 1
+ }
+
+ _debug "Domains returned by Timeweb Cloud API: $TW_Domains_Returned."
+}
+
+# Lists domain DNS records via the Timeweb Cloud API.
+#
+# Param 1: Limit for listed DNS records.
+# Param 2: Offset for DNS records list.
+#
+# Sets the "TW_Dns_Records" variable.
+# Sets the "TW_Dns_Records_Returned" variable.
+_timeweb_list_dns_records() {
+ _debug "Listing domain DNS records via the Timeweb Cloud API. Limit: $1, offset: $2."
+
+ export _H1="Authorization: Bearer $TW_Token"
+
+ if ! TW_Dns_Records=$(_get "$TW_Api/domains/$TW_Main_Domain/dns-records?limit=$1&offset=$2"); then
+ _err "The request to the Timeweb Cloud API failed."
+ return 1
+ fi
+
+ [ -z "$TW_Dns_Records" ] && {
+ _err "Empty response from the Timeweb Cloud API."
+ return 1
+ }
+
+ TW_Dns_Records_Returned=$(
+ echo "$TW_Dns_Records" |
+ sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/'
+ )
+
+ [ -z "$TW_Dns_Records_Returned" ] && {
+ _err "Failed to extract the total count of DNS records."
+ return 1
+ }
+
+ [ "$TW_Dns_Records_Returned" -eq "0" ] && {
+ _err "DNS records are missing."
+ return 1
+ }
+
+ _debug "DNS records returned by Timeweb Cloud API: $TW_Dns_Records_Returned."
+}
+
+# Verifies whether the domain is the primary domain for the ACME DNS-01 challenge FQDN.
+# The requirement is that the provided domain is the top-level domain
+# for the ACME DNS-01 challenge FQDN.
+#
+# Param 1: Domain object returned by Timeweb Cloud API.
+#
+# Sets the "TW_Main_Domain" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "domain.co.uk").
+# Sets the "TW_Subdomains" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "_acme-challenge.s1").
+_timeweb_is_main_domain() {
+ _debug "Checking if \"$1\" is the main domain of the ACME DNS-01 challenge FQDN."
+
+ [ -z "$1" ] && {
+ _debug "Failed to extract FQDN. Skipping domain."
+ return 1
+ }
+
+ ! echo ".$Acme_Fqdn" | grep -qi "\.$1$" && {
+ _debug "Domain does not match the ACME DNS-01 challenge FQDN. Skipping domain."
+ return 1
+ }
+
+ TW_Main_Domain=$1
+ TW_Subdomains=$(
+ echo "$Acme_Fqdn" |
+ sed "s/\.*.\{${#1}\}$//"
+ )
+
+ _debug "Matched domain. ACME DNS-01 challenge FQDN split as [$TW_Subdomains].[$TW_Main_Domain]."
+ return 0
+}
+
+# Verifies whether a DNS record was previously added based on the following criteria:
+# - The value matches the ACME DNS-01 challenge TXT record value;
+# - The record type is TXT;
+# - The subdomain matches the ACME DNS-01 challenge FQDN.
+#
+# Param 1: DNS record object returned by Timeweb Cloud API.
+#
+# Sets the "TW_Dns_Txt_Id" variable.
+_timeweb_is_added_txt() {
+ _debug "Checking if \"$1\" is a previously added DNS TXT record."
+
+ echo "$1" | grep -qv '"type":"TXT"' && {
+ _debug "Not a TXT record. Skipping the record."
+ return 1
+ }
+
+ if [ -n "$TW_Subdomains" ]; then
+ echo "$1" | grep -qvi "\"subdomain\":\"$TW_Subdomains\"" && {
+ _debug "Subdomains do not match. Skipping the record."
+ return 1
+ }
+ else
+ echo "$1" | grep -q '"subdomain\":"..*"' && {
+ _debug "Subdomains do not match. Skipping the record."
+ return 1
+ }
+ fi
+
+ TW_Dns_Txt_Id=$(
+ echo "$1" |
+ sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/'
+ )
+
+ [ -z "$TW_Dns_Txt_Id" ] && {
+ _debug "Failed to extract the DNS record ID. Skipping the record."
+ return 1
+ }
+
+ _debug "Matching DNS TXT record ID is \"$TW_Dns_Txt_Id\"."
+ return 0
+}
+
+# Adds a DNS TXT record via the Timeweb Cloud API.
+_timeweb_dns_txt_add() {
+ _debug "Adding a new DNS TXT record via the Timeweb Cloud API."
+
+ export _H1="Authorization: Bearer $TW_Token"
+ export _H2="Content-Type: application/json"
+
+ if ! TW_Response=$(
+ _post "{
+ \"subdomain\":\"$TW_Subdomains\",
+ \"type\":\"TXT\",
+ \"value\":\"$Acme_Txt\"
+ }" \
+ "$TW_Api/domains/$TW_Main_Domain/dns-records"
+ ); then
+ _err "The request to the Timeweb Cloud API failed."
+ return 1
+ fi
+
+ [ -z "$TW_Response" ] && {
+ _err "An unexpected empty response was received from the Timeweb Cloud API."
+ return 1
+ }
+
+ TW_Dns_Txt_Id=$(
+ echo "$TW_Response" |
+ sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/'
+ )
+
+ [ -z "$TW_Dns_Txt_Id" ] && {
+ _err "Failed to extract the DNS TXT Record ID."
+ return 1
+ }
+
+ _debug "DNS TXT record has been added. ID: \"$TW_Dns_Txt_Id\"."
+}
+
+# Removes a DNS record via the Timeweb Cloud API.
+_timeweb_dns_txt_remove() {
+ _debug "Removing DNS record via the Timeweb Cloud API."
+
+ export _H1="Authorization: Bearer $TW_Token"
+
+ if ! TW_Response=$(
+ _post \
+ "" \
+ "$TW_Api/domains/$TW_Main_Domain/dns-records/$TW_Dns_Txt_Id" \
+ "" \
+ "DELETE"
+ ); then
+ _err "The request to the Timeweb Cloud API failed."
+ return 1
+ fi
+
+ [ -n "$TW_Response" ] && {
+ _err "Received an unexpected response body from the Timeweb Cloud API."
+ return 1
+ }
+
+ _debug "DNS TXT record with ID \"$TW_Dns_Txt_Id\" has been removed."
+}
diff --git a/dnsapi/dns_yandex.sh b/dnsapi/dns_yandex.sh
deleted file mode 100755
index d780459f..00000000
--- a/dnsapi/dns_yandex.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_yandex_info='Yandex Domains
-Site: tech.Yandex.com/domain/
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_yandex
-Options:
- PDD_Token API Token
-Issues: github.com/non7top/acme.sh/issues
-Author:
-'
-
-######## Public functions #####################
-
-#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_yandex_add() {
- fulldomain="${1}"
- txtvalue="${2}"
- _debug "Calling: dns_yandex_add() '${fulldomain}' '${txtvalue}'"
-
- _PDD_credentials || return 1
-
- _PDD_get_domain || return 1
- _debug "Found suitable domain: $domain"
-
- _PDD_get_record_ids || return 1
- _debug "Record_ids: $record_ids"
-
- if [ -n "$record_ids" ]; then
- _info "All existing $subdomain records from $domain will be removed at the very end."
- fi
-
- data="domain=${domain}&type=TXT&subdomain=${subdomain}&ttl=300&content=${txtvalue}"
- uri="https://pddimp.yandex.ru/api2/admin/dns/add"
- result="$(_post "${data}" "${uri}" | _normalizeJson)"
- _debug "Result: $result"
-
- if ! _contains "$result" '"success":"ok"'; then
- if _contains "$result" '"success":"error"' && _contains "$result" '"error":"record_exists"'; then
- _info "Record already exists."
- else
- _err "Can't add $subdomain to $domain."
- return 1
- fi
- fi
-}
-
-#Usage: dns_myapi_rm _acme-challenge.www.domain.com
-dns_yandex_rm() {
- fulldomain="${1}"
- _debug "Calling: dns_yandex_rm() '${fulldomain}'"
-
- _PDD_credentials || return 1
-
- _PDD_get_domain "$fulldomain" || return 1
- _debug "Found suitable domain: $domain"
-
- _PDD_get_record_ids "${domain}" "${subdomain}" || return 1
- _debug "Record_ids: $record_ids"
-
- for record_id in $record_ids; do
- data="domain=${domain}&record_id=${record_id}"
- uri="https://pddimp.yandex.ru/api2/admin/dns/del"
- result="$(_post "${data}" "${uri}" | _normalizeJson)"
- _debug "Result: $result"
-
- if ! _contains "$result" '"success":"ok"'; then
- _info "Can't remove $subdomain from $domain."
- fi
- done
-}
-
-#################### Private functions below ##################################
-
-_PDD_get_domain() {
- subdomain_start=1
- while true; do
- domain_start=$(_math $subdomain_start + 1)
- domain=$(echo "$fulldomain" | cut -d . -f "$domain_start"-)
- subdomain=$(echo "$fulldomain" | cut -d . -f -"$subdomain_start")
-
- _debug "Checking domain $domain"
- if [ -z "$domain" ]; then
- return 1
- fi
-
- uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=$domain"
- result="$(_get "${uri}" | _normalizeJson)"
- _debug "Result: $result"
-
- if _contains "$result" '"success":"ok"'; then
- return 0
- fi
- subdomain_start=$(_math $subdomain_start + 1)
- done
-}
-
-_PDD_credentials() {
- if [ -z "${PDD_Token}" ]; then
- PDD_Token=""
- _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx."
- _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token."
- return 1
- else
- _saveaccountconf PDD_Token "${PDD_Token}"
- fi
- export _H1="PddToken: $PDD_Token"
-}
-
-_PDD_get_record_ids() {
- _debug "Check existing records for $subdomain"
-
- uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${domain}"
- result="$(_get "${uri}" | _normalizeJson)"
- _debug "Result: $result"
-
- if ! _contains "$result" '"success":"ok"'; then
- return 1
- fi
-
- record_ids=$(echo "$result" | _egrep_o "{[^{]*\"subdomain\":\"${subdomain}\"[^}]*}" | sed -n -e 's#.*"record_id": \([0-9]*\).*#\1#p')
-}
diff --git a/dnsapi/dns_yandex360.sh b/dnsapi/dns_yandex360.sh
new file mode 100644
index 00000000..c6b6053d
--- /dev/null
+++ b/dnsapi/dns_yandex360.sh
@@ -0,0 +1,352 @@
+#!/usr/bin/env sh
+# shellcheck disable=SC2034
+dns_yandex360_info='Yandex 360 for Business DNS API.
+Yandex 360 for Business is a digital environment for effective collaboration.
+Site: https://360.yandex.com/
+Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360
+Options:
+ YANDEX360_CLIENT_ID OAuth 2.0 ClientID
+ YANDEX360_CLIENT_SECRET OAuth 2.0 Client secret
+OptionsAlt:
+ YANDEX360_ORG_ID Organization ID. Optional.
+ YANDEX360_ACCESS_TOKEN OAuth 2.0 Access token. Optional.
+Issues: https://github.com/acmesh-official/acme.sh/issues/5213
+Author:
+'
+
+YANDEX360_API_BASE='https://api360.yandex.net/directory/v1'
+YANDEX360_OAUTH_BASE='https://oauth.yandex.ru'
+
+######## Public functions #####################
+
+dns_yandex360_add() {
+ fulldomain="$(_idn "$1")"
+ txtvalue=$2
+ _info 'Using Yandex 360 DNS API'
+
+ if ! _check_variables; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ sub_domain=$(echo "$fulldomain" | sed "s/\.$root_domain$//")
+
+ _debug 'Adding Yandex 360 DNS record for subdomain' "$sub_domain"
+ dns_api_url="${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns"
+ data='{"name":"'"$sub_domain"'","type":"TXT","ttl":60,"text":"'"$txtvalue"'"}'
+
+ response="$(_post "$data" "$dns_api_url" '' 'POST' 'application/json')"
+
+ if _contains "$response" 'recordId'; then
+ return 0
+ else
+ _debug 'Response' "$response"
+ return 1
+ fi
+}
+
+dns_yandex360_rm() {
+ fulldomain="$(_idn "$1")"
+ txtvalue=$2
+ _info 'Using Yandex 360 DNS API'
+
+ if ! _check_variables; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ _debug 'Retrieving 100 records from Yandex 360 DNS'
+ dns_api_url="${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns?perPage=100"
+ response="$(_get "$dns_api_url" '' '')"
+
+ if ! _contains "$response" "$txtvalue"; then
+ _info 'DNS record not found. Nothing to remove.'
+ _debug 'Response' "$response"
+ return 1
+ fi
+
+ response="$(echo "$response" | _normalizeJson)"
+
+ record_id=$(
+ echo "$response" |
+ _egrep_o '\{[^}]*'"${txtvalue}"'[^}]*\}' |
+ _egrep_o '"recordId":[0-9]*' |
+ cut -d':' -f2
+ )
+
+ if [ -z "$record_id" ]; then
+ _err 'Unable to get record ID to remove'
+ return 1
+ fi
+
+ _debug 'Removing DNS record' "$record_id"
+ delete_url="${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns/${record_id}"
+
+ response="$(_post '' "$delete_url" '' 'DELETE')"
+
+ if _contains "$response" '{}'; then
+ return 0
+ else
+ _debug 'Response' "$response"
+ return 1
+ fi
+}
+
+#################### Private functions below ##################################
+
+_check_variables() {
+ YANDEX360_CLIENT_ID="${YANDEX360_CLIENT_ID:-$(_readaccountconf_mutable YANDEX360_CLIENT_ID)}"
+ YANDEX360_CLIENT_SECRET="${YANDEX360_CLIENT_SECRET:-$(_readaccountconf_mutable YANDEX360_CLIENT_SECRET)}"
+ YANDEX360_ORG_ID="${YANDEX360_ORG_ID:-$(_readaccountconf_mutable YANDEX360_ORG_ID)}"
+ YANDEX360_ACCESS_TOKEN="${YANDEX360_ACCESS_TOKEN:-$(_readaccountconf_mutable YANDEX360_ACCESS_TOKEN)}"
+ YANDEX360_REFRESH_TOKEN="${YANDEX360_REFRESH_TOKEN:-$(_readaccountconf_mutable YANDEX360_REFRESH_TOKEN)}"
+
+ if [ -n "$YANDEX360_ACCESS_TOKEN" ]; then
+ _info '========================================='
+ _info ' ATTENTION'
+ _info '========================================='
+ _info 'A manually provided Yandex 360 access token has been detected, which is not recommended.'
+ _info 'Please note that this token is valid for a limited time after issuance.'
+ _info 'It is recommended to obtain the token interactively using acme.sh for one-time setup.'
+ _info 'Subsequent token renewals will be handled automatically.'
+ _info 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
+ _info '========================================='
+
+ _saveaccountconf_mutable YANDEX360_ACCESS_TOKEN "$YANDEX360_ACCESS_TOKEN"
+ export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
+
+ elif [ -z "$YANDEX360_CLIENT_ID" ] || [ -z "$YANDEX360_CLIENT_SECRET" ]; then
+ _err '========================================='
+ _err ' ERROR'
+ _err '========================================='
+ _err 'The required environment variables YANDEX360_CLIENT_ID and YANDEX360_CLIENT_SECRET are not set.'
+ _err 'Alternatively, you can set YANDEX360_ACCESS_TOKEN environment variable.'
+ _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
+ _err '========================================='
+ return 1
+
+ else
+ _saveaccountconf_mutable YANDEX360_CLIENT_ID "$YANDEX360_CLIENT_ID"
+ _saveaccountconf_mutable YANDEX360_CLIENT_SECRET "$YANDEX360_CLIENT_SECRET"
+
+ if [ -n "$YANDEX360_REFRESH_TOKEN" ]; then
+ _debug 'Refresh token found. Attempting to refresh access token.'
+ fi
+
+ _refresh_token || _get_token || return 1
+ fi
+
+ if [ -z "$YANDEX360_ORG_ID" ]; then
+ org_response="$(_get "${YANDEX360_API_BASE}/org" '' '')"
+
+ if _contains "$org_response" '"organizations"'; then
+ org_response="$(echo "$org_response" | _normalizeJson)"
+ YANDEX360_ORG_ID=$(
+ echo "$org_response" |
+ _egrep_o '"id":[[:space:]]*[0-9]+' |
+ cut -d':' -f2
+ )
+ _debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID"
+ else
+ _err '========================================='
+ _err ' ERROR'
+ _err '========================================='
+ _err "Failed to retrieve YANDEX360_ORG_ID automatically."
+ _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
+ _err '========================================='
+ _debug 'Response' "$org_response"
+ return 1
+ fi
+ fi
+
+ return 0
+}
+
+_get_token() {
+ _info "$(__red '=========================================')"
+ _info "$(__red ' NOTICE')"
+ _info "$(__red '=========================================')"
+ _info "$(__red 'Before using the Yandex 360 API, you need to complete an authorization procedure.')"
+ _info "$(__red 'The initial access token is obtained interactively and is a one-time operation.')"
+ _info "$(__red 'Subsequent API requests will be handled automatically.')"
+ _info "$(__red '=========================================')"
+
+ _info 'Initiating device authorization flow'
+ device_code_url="${YANDEX360_OAUTH_BASE}/device/code"
+
+ hostname=$(uname -n)
+ data="client_id=$YANDEX360_CLIENT_ID&device_id=acme.sh ${hostname}&device_name=acme.sh ${hostname}"
+
+ response="$(_post "$data" "$device_code_url" '' 'POST')"
+
+ if ! _contains "$response" 'device_code'; then
+ _err 'Failed to get device code'
+ _debug 'Response' "$response"
+ return 1
+ fi
+
+ response="$(echo "$response" | _normalizeJson)"
+
+ device_code=$(
+ echo "$response" |
+ _egrep_o '"device_code":"[^"]*"' |
+ cut -d'"' -f4
+ )
+ _debug 'Device code' "$device_code"
+
+ user_code=$(
+ echo "$response" |
+ _egrep_o '"user_code":"[^"]*"' |
+ cut -d'"' -f4
+ )
+ _debug 'User code' "$user_code"
+
+ verification_url=$(
+ echo "$response" |
+ _egrep_o '"verification_url":"[^"]*"' |
+ cut -d'"' -f4
+ )
+ _debug 'Verification URL' "$verification_url"
+
+ interval=$(
+ echo "$response" |
+ _egrep_o '"interval":[[:space:]]*[0-9]+' |
+ cut -d':' -f2
+ )
+ _debug 'Polling interval' "$interval"
+
+ _info "$(__red 'Please visit '"$verification_url"' and log in as an organization administrator')"
+ _info "$(__red 'Once logged in, enter the code: '"$user_code"' on the page from the previous step')"
+ _info "$(__red 'Waiting for authorization...')"
+
+ _debug 'Polling for token'
+ token_url="${YANDEX360_OAUTH_BASE}/token"
+
+ while true; do
+ data="grant_type=device_code&code=$device_code&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
+
+ response="$(_post "$data" "$token_url" '' 'POST')"
+
+ if _contains "$response" 'access_token'; then
+ response="$(echo "$response" | _normalizeJson)"
+ YANDEX360_ACCESS_TOKEN=$(
+ echo "$response" |
+ _egrep_o '"access_token":"[^"]*"' |
+ cut -d'"' -f4
+ )
+ YANDEX360_REFRESH_TOKEN=$(
+ echo "$response" |
+ _egrep_o '"refresh_token":"[^"]*"' |
+ cut -d'"' -f4
+ )
+
+ _secure_debug 'Obtained access token' "$YANDEX360_ACCESS_TOKEN"
+ _secure_debug 'Obtained refresh token' "$YANDEX360_REFRESH_TOKEN"
+
+ _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
+
+ export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
+
+ _info 'Access token obtained successfully'
+ return 0
+ elif _contains "$response" 'authorization_pending'; then
+ _debug 'Response' "$response"
+ _debug "Authorization pending. Waiting $interval seconds before next attempt."
+ _sleep "$interval"
+ else
+ _debug 'Response' "$response"
+ _err 'Failed to get access token'
+ return 1
+ fi
+ done
+}
+
+_refresh_token() {
+ token_url="${YANDEX360_OAUTH_BASE}/token"
+
+ data="grant_type=refresh_token&refresh_token=$YANDEX360_REFRESH_TOKEN&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
+
+ response="$(_post "$data" "$token_url" '' 'POST')"
+
+ if _contains "$response" 'access_token'; then
+ response="$(echo "$response" | _normalizeJson)"
+ YANDEX360_ACCESS_TOKEN=$(
+ echo "$response" |
+ _egrep_o '"access_token":"[^"]*"' |
+ cut -d'"' -f4
+ )
+ YANDEX360_REFRESH_TOKEN=$(
+ echo "$response" |
+ _egrep_o '"refresh_token":"[^"]*"' |
+ cut -d'"' -f4
+ )
+
+ _secure_debug 'Received access token' "$YANDEX360_ACCESS_TOKEN"
+ _secure_debug 'Received refresh token' "$YANDEX360_REFRESH_TOKEN"
+
+ _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
+
+ export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
+
+ _info 'Access token refreshed successfully'
+ return 0
+ else
+ _info 'Failed to refresh token. Will attempt to obtain a new one.'
+ _debug 'Response' "$response"
+ return 1
+ fi
+}
+
+_get_root() {
+ domain="$1"
+
+ for org_id in $YANDEX360_ORG_ID; do
+ _debug 'Checking organization ID' "$org_id"
+ domains_api_url="${YANDEX360_API_BASE}/org/${org_id}/domains"
+
+ domains_response="$(_get "$domains_api_url" '' '')"
+
+ if ! _contains "$domains_response" '"domains"'; then
+ _debug 'No domains found for organization' "$org_id"
+ _debug 'Response' "$domains_response"
+ continue
+ fi
+
+ domains_response="$(echo "$domains_response" | _normalizeJson)"
+ domain_names=$(
+ echo "$domains_response" |
+ _egrep_o '"name":"[^"]*"' |
+ cut -d'"' -f4
+ )
+
+ for d in $domain_names; do
+ d="$(_idn "$d")"
+ _debug 'Checking domain' "$d"
+
+ if _endswith "$domain" "$d"; then
+ root_domain="$d"
+ break
+ fi
+ done
+
+ if [ -n "$root_domain" ]; then
+ _debug "Root domain found: $root_domain in organization $org_id"
+
+ YANDEX360_ORG_ID="$org_id"
+ _saveaccountconf_mutable YANDEX360_ORG_ID "$YANDEX360_ORG_ID"
+
+ return 0
+ fi
+ done
+
+ if [ -z "$root_domain" ]; then
+ _err "Could not find a matching root domain for $domain in any organization"
+ return 1
+ fi
+}
diff --git a/notify/bark.sh b/notify/bark.sh
index bbd5bf34..fd9ebd76 100644
--- a/notify/bark.sh
+++ b/notify/bark.sh
@@ -1,32 +1,40 @@
#!/usr/bin/env sh
-#Support iOS Bark Notification
+# Support iOS Bark Notification
-#BARK_API_URL="https://api.day.app/xxxx"
-#BARK_SOUND="yyyy"
-#BARK_GROUP="zzzz"
+# Every parameter explained: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#push
-# subject content statusCode
+# BARK_API_URL="https://api.day.app/xxxx" (required)
+# BARK_GROUP="ACME" (optional)
+# BARK_SOUND="alarm" (optional)
+# BARK_LEVEL="active" (optional)
+# BARK_BADGE=0 (optional)
+# BARK_AUTOMATICALLYCOPY="1" (optional)
+# BARK_COPY="My clipboard Content" (optional)
+# BARK_ICON="https://example.com/icon.png" (optional)
+# BARK_ISARCHIVE="1" (optional)
+# BARK_URL="https://example.com" (optional)
+
+# subject content statusCode
bark_send() {
_subject="$1"
_content="$2"
- _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _statusCode="$3" # 0: success, 1: error, 2: skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
+ _content=$(echo "$_content" | _url_encode)
+ _subject=$(echo "$_subject" | _url_encode)
+
BARK_API_URL="${BARK_API_URL:-$(_readaccountconf_mutable BARK_API_URL)}"
if [ -z "$BARK_API_URL" ]; then
- BARK_API_URL=""
_err "You didn't specify a Bark API URL BARK_API_URL yet."
_err "You can download Bark from App Store and get yours."
return 1
fi
_saveaccountconf_mutable BARK_API_URL "$BARK_API_URL"
- BARK_SOUND="${BARK_SOUND:-$(_readaccountconf_mutable BARK_SOUND)}"
- _saveaccountconf_mutable BARK_SOUND "$BARK_SOUND"
-
BARK_GROUP="${BARK_GROUP:-$(_readaccountconf_mutable BARK_GROUP)}"
if [ -z "$BARK_GROUP" ]; then
BARK_GROUP="ACME"
@@ -35,10 +43,79 @@ bark_send() {
_saveaccountconf_mutable BARK_GROUP "$BARK_GROUP"
fi
- _content=$(echo "$_content" | _url_encode)
- _subject=$(echo "$_subject" | _url_encode)
+ BARK_SOUND="${BARK_SOUND:-$(_readaccountconf_mutable BARK_SOUND)}"
+ if [ -n "$BARK_SOUND" ]; then
+ _saveaccountconf_mutable BARK_SOUND "$BARK_SOUND"
+ fi
+
+ BARK_LEVEL="${BARK_LEVEL:-$(_readaccountconf_mutable BARK_LEVEL)}"
+ if [ -n "$BARK_LEVEL" ]; then
+ _saveaccountconf_mutable BARK_LEVEL "$BARK_LEVEL"
+ fi
+
+ BARK_BADGE="${BARK_BADGE:-$(_readaccountconf_mutable BARK_BADGE)}"
+ if [ -n "$BARK_BADGE" ]; then
+ _saveaccountconf_mutable BARK_BADGE "$BARK_BADGE"
+ fi
+
+ BARK_AUTOMATICALLYCOPY="${BARK_AUTOMATICALLYCOPY:-$(_readaccountconf_mutable BARK_AUTOMATICALLYCOPY)}"
+ if [ -n "$BARK_AUTOMATICALLYCOPY" ]; then
+ _saveaccountconf_mutable BARK_AUTOMATICALLYCOPY "$BARK_AUTOMATICALLYCOPY"
+ fi
+
+ BARK_COPY="${BARK_COPY:-$(_readaccountconf_mutable BARK_COPY)}"
+ if [ -n "$BARK_COPY" ]; then
+ _saveaccountconf_mutable BARK_COPY "$BARK_COPY"
+ fi
+
+ BARK_ICON="${BARK_ICON:-$(_readaccountconf_mutable BARK_ICON)}"
+ if [ -n "$BARK_ICON" ]; then
+ _saveaccountconf_mutable BARK_ICON "$BARK_ICON"
+ fi
+
+ BARK_ISARCHIVE="${BARK_ISARCHIVE:-$(_readaccountconf_mutable BARK_ISARCHIVE)}"
+ if [ -n "$BARK_ISARCHIVE" ]; then
+ _saveaccountconf_mutable BARK_ISARCHIVE "$BARK_ISARCHIVE"
+ fi
+
+ BARK_URL="${BARK_URL:-$(_readaccountconf_mutable BARK_URL)}"
+ if [ -n "$BARK_URL" ]; then
+ _saveaccountconf_mutable BARK_URL "$BARK_URL"
+ fi
+
+ _params=""
+
+ if [ -n "$BARK_SOUND" ]; then
+ _params="$_params&sound=$BARK_SOUND"
+ fi
+ if [ -n "$BARK_GROUP" ]; then
+ _params="$_params&group=$BARK_GROUP"
+ fi
+ if [ -n "$BARK_LEVEL" ]; then
+ _params="$_params&level=$BARK_LEVEL"
+ fi
+ if [ -n "$BARK_BADGE" ]; then
+ _params="$_params&badge=$BARK_BADGE"
+ fi
+ if [ -n "$BARK_AUTOMATICALLYCOPY" ]; then
+ _params="$_params&automaticallyCopy=$BARK_AUTOMATICALLYCOPY"
+ fi
+ if [ -n "$BARK_COPY" ]; then
+ _params="$_params©=$BARK_COPY"
+ fi
+ if [ -n "$BARK_ICON" ]; then
+ _params="$_params&icon=$BARK_ICON"
+ fi
+ if [ -n "$BARK_ISARCHIVE" ]; then
+ _params="$_params&isArchive=$BARK_ISARCHIVE"
+ fi
+ if [ -n "$BARK_URL" ]; then
+ _params="$_params&url=$BARK_URL"
+ fi
+
+ _params=$(echo "$_params" | sed 's/^&//') # remove leading '&' if exists
- response="$(_get "$BARK_API_URL/$_subject/$_content?sound=$BARK_SOUND&group=$BARK_GROUP")"
+ response="$(_get "$BARK_API_URL/$_subject/$_content?$_params")"
if [ "$?" = "0" ] && _contains "$response" "success"; then
_info "Bark API fired success."
diff --git a/notify/teams.sh b/notify/teams.sh
old mode 100644
new mode 100755
index 1bc5ed08..5e8fe6c0
--- a/notify/teams.sh
+++ b/notify/teams.sh
@@ -3,10 +3,6 @@
#Support Microsoft Teams webhooks
#TEAMS_WEBHOOK_URL=""
-#TEAMS_THEME_COLOR=""
-#TEAMS_SUCCESS_COLOR=""
-#TEAMS_ERROR_COLOR=""
-#TEAMS_SKIP_COLOR=""
teams_send() {
_subject="$1"
@@ -14,9 +10,9 @@ teams_send() {
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
- _color_success="2cbe4e" # green
- _color_danger="cb2431" # red
- _color_muted="586069" # gray
+ _color_success="Good"
+ _color_danger="Attention"
+ _color_muted="Accent"
TEAMS_WEBHOOK_URL="${TEAMS_WEBHOOK_URL:-$(_readaccountconf_mutable TEAMS_WEBHOOK_URL)}"
if [ -z "$TEAMS_WEBHOOK_URL" ]; then
@@ -26,26 +22,6 @@ teams_send() {
fi
_saveaccountconf_mutable TEAMS_WEBHOOK_URL "$TEAMS_WEBHOOK_URL"
- TEAMS_THEME_COLOR="${TEAMS_THEME_COLOR:-$(_readaccountconf_mutable TEAMS_THEME_COLOR)}"
- if [ -n "$TEAMS_THEME_COLOR" ]; then
- _saveaccountconf_mutable TEAMS_THEME_COLOR "$TEAMS_THEME_COLOR"
- fi
-
- TEAMS_SUCCESS_COLOR="${TEAMS_SUCCESS_COLOR:-$(_readaccountconf_mutable TEAMS_SUCCESS_COLOR)}"
- if [ -n "$TEAMS_SUCCESS_COLOR" ]; then
- _saveaccountconf_mutable TEAMS_SUCCESS_COLOR "$TEAMS_SUCCESS_COLOR"
- fi
-
- TEAMS_ERROR_COLOR="${TEAMS_ERROR_COLOR:-$(_readaccountconf_mutable TEAMS_ERROR_COLOR)}"
- if [ -n "$TEAMS_ERROR_COLOR" ]; then
- _saveaccountconf_mutable TEAMS_ERROR_COLOR "$TEAMS_ERROR_COLOR"
- fi
-
- TEAMS_SKIP_COLOR="${TEAMS_SKIP_COLOR:-$(_readaccountconf_mutable TEAMS_SKIP_COLOR)}"
- if [ -n "$TEAMS_SKIP_COLOR" ]; then
- _saveaccountconf_mutable TEAMS_SKIP_COLOR "$TEAMS_SKIP_COLOR"
- fi
-
export _H1="Content-Type: application/json"
_subject=$(echo "$_subject" | _json_encode)
@@ -63,16 +39,35 @@ teams_send() {
;;
esac
- _color=$(echo "$_color" | tr -cd 'a-fA-F0-9')
- if [ -z "$_color" ]; then
- _color=$(echo "${TEAMS_THEME_COLOR:-$_color_muted}" | tr -cd 'a-fA-F0-9')
- fi
-
- _data="{\"title\": \"$_subject\","
- if [ -n "$_color" ]; then
- _data="$_data\"themeColor\": \"$_color\", "
- fi
- _data="$_data\"text\": \"$_content\"}"
+ _data="{
+ \"type\": \"message\",
+ \"attachments\": [
+ {
+ \"contentType\": \"application/vnd.microsoft.card.adaptive\",
+ \"contentUrl\": null,
+ \"content\": {
+ \"schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",
+ \"type\": \"AdaptiveCard\",
+ \"version\": \"1.2\",
+ \"body\": [
+ {
+ \"type\": \"TextBlock\",
+ \"size\": \"large\",
+ \"weight\": \"bolder\",
+ \"wrap\": true,
+ \"color\": \"$_color\",
+ \"text\": \"$_subject\"
+ },
+ {
+ \"type\": \"TextBlock\",
+ \"text\": \"$_content\",
+ \"wrap\": true
+ }
+ ]
+ }
+ }
+ ]
+}"
if response=$(_post "$_data" "$TEAMS_WEBHOOK_URL"); then
if ! _contains "$response" error; then