From b1b539695f8e6337421bb65a5a76afb758c8c2ff Mon Sep 17 00:00:00 2001 From: Marvo2011 Date: Mon, 6 Jul 2026 15:30:13 +0200 Subject: [PATCH 01/24] Merge pull request #7026 from Marvo2011/dev Update SelfHost DNS provider --- dnsapi/dns_selfhost.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh index 40cc0210..782a5d5f 100644 --- a/dnsapi/dns_selfhost.sh +++ b/dnsapi/dns_selfhost.sh @@ -7,6 +7,7 @@ Options: SELFHOSTDNS_USERNAME Username SELFHOSTDNS_PASSWORD Password SELFHOSTDNS_MAP Subdomain name + SELFHOSTDNS_UPDATE_URL API url. Optional. Default "https://account.selfhost.de/cgi-bin/api.pl" Issues: github.com/acmesh-official/acme.sh/issues/4291 Author: Marvin Edeler ' @@ -18,9 +19,11 @@ dns_selfhost_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txt" - SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl" + DEFAULT_SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl" # Get values, but don't save until we successfully validated + SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$(_readaccountconf_mutable SELFHOSTDNS_UPDATE_URL)}" + SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$DEFAULT_SELFHOSTDNS_UPDATE_URL}" SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}" SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}" # These values are domain dependent, so read them from there @@ -84,6 +87,11 @@ dns_selfhost_add() { fi fi + # Save api url if different from default + if [ "$DEFAULT_SELFHOSTDNS_UPDATE_URL" != "$SELFHOSTDNS_UPDATE_URL" ]; then + _saveaccountconf_mutable SELFHOSTDNS_UPDATE_URL "$SELFHOSTDNS_UPDATE_URL" + fi + # Now that we know the values are good, save them _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME" _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD" From ca118be754d59ecdfcc5f298d156dcd7fa93a1c3 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 10:02:45 +0800 Subject: [PATCH 02/24] issue.yml: match tracking issue title variants --- .github/workflows/issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index a72c18ab..f72a1f06 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -61,7 +61,7 @@ jobs: // Handled by the Blacklist / Revert Command workflows. return; } - if (issue.title.startsWith("Report bugs to")) { + if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) { // Tracking issue for a third-party dns/deploy/notify api: // no upgrade boilerplate; assign it to the opener and label it. await github.rest.issues.addAssignees({ From cf3eab95ee85fb38fa8e62e1f4c9b35eee14c789 Mon Sep 17 00:00:00 2001 From: lwohn-creo Date: Fri, 10 Jul 2026 04:07:12 +0200 Subject: [PATCH 03/24] Add creoline API as DNS provider (#7100) * New Banner Updated README to include responsive images for dark and light modes. * acme-sh-creoline-as-dns-provider * acme-sh-creoline-as-dns-provider - Review changes implemented according code review * acme-sh-creoline-as-dns-provider - Review changes implemented according second code review, minding --cron * acme-sh-creoline-as-dns-provider - Remove debug code * acme-sh-creoline-as-dns-provider - shfmt formatting according Code of conduct --------- Co-authored-by: neil Co-authored-by: ZeroSSL-Andreas Co-authored-by: Steven Kauschke --- dnsapi/dns_creoline.sh | 181 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 dnsapi/dns_creoline.sh diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh new file mode 100644 index 00000000..9d04af4f --- /dev/null +++ b/dnsapi/dns_creoline.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_creoline_info='creoline +Site: https://www.creoline.com/de +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_creoline +Help: https://help.creoline.com +Options: + creolineApiToken + creolineApiSecret +Issues: github.com/acmesh-official/acme.sh/issues/7103 +' + +creolineApi="https://api.creoline.com/v1" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPB8" +dns_creoline_add() { + fulldomain=$1 + txtvalue=$2 + + creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" + creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" + + if [ -z "$creolineApiToken" ] || [ -z "$creolineApiSecret" ]; then + _err "Error required creoline API Token or creoline API Secret not specified." + _err "Please set it with the Command 'export creolineApiToken=' and 'export creolineApiSecret='." + return 1 + else + _saveaccountconf_mutable creolineApiToken "$creolineApiToken" + _saveaccountconf_mutable creolineApiSecret "$creolineApiSecret" + fi + + _debug "Detecting the root dns zone." + if ! _get_root "$fulldomain"; then + _err "Error on detecting the root dns zone." + return 1 + fi + + _info "Adding record" + if _creoline_rest POST "dns/zone/$_domain/record" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"record\":\"$txtvalue\",\"ttl\":\"60\"}"; then + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 +} + +#fulldomain txtvalue +dns_creoline_rm() { + fulldomain=$1 + txtvalue=$2 + + creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" + creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" + + _debug "Detecting the root dns zone." + if ! _get_root "$fulldomain"; then + _err "Error on detecting the root dns zone." + return 1 + fi + + _info "Getting earlier created txt record." + if ! _creoline_rest GET "dns/zone/$_domain/record/type/TXT/record/$txtvalue"; then + if _contains "$response" "errors" || _contains "$response" "message"; then + _err "Error on getting earlier created txt record." + return 1 + fi + _err "Error on getting earlier created txt record." + return 1 + fi + + record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug "record_id" "$record_id" + + if [ -z "$record_id" ]; then + _err "Error on deleting earlier created txt record. No record id found in response." + return 1 + fi + + _info "Deleting earlier created txt record." + if ! _creoline_rest DELETE "dns/zone/$_domain/record/$record_id"; then + if _contains "$response" "errors" || _contains "$response" "message"; then + _err "Error on deleting earlier created txt record." + return 1 + fi + _err "Error on deleting earlier created txt record." + return 1 + fi + + _info "Deleted, OK" + return 0 +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + if ! _creoline_rest GET "dns/zone/root/$domain"; then + return 1 + fi + + _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug _sub_domain "$_sub_domain" + + _domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug _domain "$_domain" + + if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then + return 1 + fi +} + +_creoline_rest() { + method=$1 + uri="$2" + data="$3" + timestamp=$(_time) + canonical_request="${timestamp}.${creolineApi}/${uri}" + signature_hash=$(printf "%s" "$canonical_request" | _hmac sha256 "$(printf "%s" "$creolineApiSecret" | _hex_dump | tr -d " ")" hex) + + _debug method "$method" + _debug uri "$uri" + _debug data "$data" + + _debug2 timestamp "$timestamp" + _debug2 canonical_request "$canonical_request" + _debug2 signature_hash "$signature_hash" + + token_trimmed=$(echo "$creolineApiToken" | tr -d '"') + hmac_trimmed=$(echo "$signature_hash" | tr -d '"') + + export _H1="Content-Type: application/json" + + if [ "$token_trimmed" ]; then + export _H2="X-Api-Token: $token_trimmed" + fi + + if [ "$hmac_trimmed" ]; then + export _H3="X-Creoline-Api-Signature: $hmac_trimmed" + fi + + if [ "$timestamp" ]; then + export _H4="X-Creoline-Api-Timestamp: $timestamp" + fi + + if [ "$method" != "GET" ]; then + response="$(_post "$data" "$creolineApi/$uri" "" "$method")" + else + response="$(_get "$creolineApi/$uri")" + fi + + if [ "$?" != "0" ]; then + _err "error $uri" + return 1 + fi + + _debug response "$response" + + if _contains "$response" "errors"; then + error=$(echo "$response" | _egrep_o "\"errors\":[[]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | tr -d "[") + _err "Error: $error" + _err "URI:$uri" + return 1 + elif _contains "$response" "message"; then + message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") + _err "Error: $message" + _err "URI:$uri" + return 1 + fi + + return 0 +} From 1324dcd4720274ac328165a54a06f0055b4e93e4 Mon Sep 17 00:00:00 2001 From: invario <67800603+invario@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:10:44 -0700 Subject: [PATCH 04/24] Docker: update crontab used (#7111) Signed-off-by: invario <67800603+invario@users.noreply.github.com> --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 55a9cc67..229e4830 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,8 +81,8 @@ if [ \"\$1\" = \"daemon\" ]; then \n \ echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \ time=\$(date -u \"+%s\") \n \ random_minute=\$((\$time % 60)) \n \ - random_hour=\$((\$time / 60 % 24)) \n \ - echo \"\$random_minute \$random_hour * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \ + random_hour=\$((\$time / 60 % 6)) \n \ + echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \ fi \n \ echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \ exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \ From bed15ba8447e93c3052442900955894d79d8daf7 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 10:23:45 +0800 Subject: [PATCH 05/24] dns_freedns.sh: use grep -E, BRE \| alternation is a GNU extension OpenBSD grep treats \| in a BRE as a literal | character, so _freedns_domain_id never matched any row and every domain lookup failed with "Domain not found". Switch to ERE with -E, keeping the parens escaped so the (.*) suffix branch still requires literal parentheses and does not widen the match (e.g. searching example.com must not match example.company). Reported-by: @katiekloss @boretom Ref: https://github.com/acmesh-official/acme.sh/issues/2305 --- dnsapi/dns_freedns.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 13d9f68b..8ea86c24 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -305,7 +305,7 @@ _freedns_domain_id() { fi domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | - grep "$search_domain\|$search_domain(.*)" | + grep -E "$search_domain|$search_domain\(.*\)" | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' | cut -d = -f 2)" # The above beauty extracts domain ID from the html page... From 83b52e0cd77e3fd189e1508dde9da08b9f50b94f Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 10:38:52 +0800 Subject: [PATCH 06/24] notify/smtp.sh: add --crlf so curl sends CRLF line endings Postfix with smtpd_forbid_bare_newline (default hardening since 3.9, after SMTP smuggling) rejects the message with "521 5.5.2 Error: bare received". RFC 5321 requires CRLF. The python sender is unaffected (smtplib already emits CRLF). fix https://github.com/acmesh-official/acme.sh/issues/7104 --- notify/smtp.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/notify/smtp.sh b/notify/smtp.sh index f5ebebca..a7318692 100644 --- a/notify/smtp.sh +++ b/notify/smtp.sh @@ -200,6 +200,7 @@ _smtp_send_curl() { set -- "$@" \ --upload-file - \ + --crlf \ --mail-from "$SMTP_FROM" \ --max-time "$SMTP_TIMEOUT" From 45c0ad4112557599d5dd1725c7c6048efa563fd1 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 11:05:40 +0800 Subject: [PATCH 07/24] Add _cleardeployconf to clear deploy hook keys from domain conf Mirrors _clearaccountconf_mutable: clears the SAVED_ prefixed key and the legacy unprefixed key. Replaces the local copy in synology_dsm.sh and the direct _cleardomainconf call in multideploy.sh. Closes #4722. Thanks to @sg1888. --- acme.sh | 7 +++++++ deploy/multideploy.sh | 2 +- deploy/synology_dsm.sh | 5 ----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/acme.sh b/acme.sh index 4f1c0336..b072c6cf 100755 --- a/acme.sh +++ b/acme.sh @@ -2594,6 +2594,13 @@ _savedeployconf() { _cleardomainconf "$1" } +#key +_cleardeployconf() { + _cleardomainconf "SAVED_$1" + #remove later + _cleardomainconf "$1" +} + #key _getdeployconf() { _rac_key="$1" diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index ef920f64..375668ec 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -210,7 +210,7 @@ _clear_envs() { echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" - _cleardomainconf "SAVED_$_key" + _cleardeployconf "$_key" unset -v "$_key" done } diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 502bc59b..202e8188 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -424,11 +424,6 @@ _temp_admin_cleanup() { fi } -#_cleardeployconf key -_cleardeployconf() { - _cleardomainconf "SAVED_$1" -} - # key _check2cleardeployconfexp() { _key="$1" From 534a1714dcbb79699407a4f9e0f2137ca92b1eb7 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 11:20:08 +0800 Subject: [PATCH 08/24] dns_me.sh: use LC_ALL=C so the request date header is always English LC_ALL in the environment overrides both LC_TIME and LANG, so LANG=C alone still produced localized day/month names on non-English systems and DNS Made Easy rejected the request date header. An LC_ALL=C command prefix beats every locale variable (same pattern as dns_oci.sh). Fixes #4272. Closes #4271. Thanks to @Nickinthebox. --- dnsapi/dns_me.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh index 163fe8db..0966c5f1 100644 --- a/dnsapi/dns_me.sh +++ b/dnsapi/dns_me.sh @@ -140,7 +140,7 @@ _me_rest() { data="$3" _debug "$ep" - cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") + cdate=$(LC_ALL=C date -u +"%a, %d %b %Y %T %Z") hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) export _H1="x-dnsme-apiKey: $ME_Key" From 90b4795bb1a75712258b523d7bcec9fdd3e2c635 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 11:22:20 +0800 Subject: [PATCH 09/24] issue: strip the trailing dot of a fully-qualified alias domain A trailing dot in --domain-alias/--challenge-alias was passed through to the dnsapi hook verbatim. Providers with exact-match record-name lookups (e.g. Cloudflare's name= filter) then never find the record, so rm never deletes it and relic TXT records accumulate on every issue. Stripping in issue() also fixes certs with a dotted alias already saved in domain.conf. fix https://github.com/acmesh-official/acme.sh/issues/4636 --- acme.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acme.sh b/acme.sh index b072c6cf..84b38068 100755 --- a/acme.sh +++ b/acme.sh @@ -5350,6 +5350,8 @@ $_authorizations_map" fi _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" test "$_d_alias" = "$NO_VALUE" && _d_alias="" + # strip the trailing dot of a fully-qualified alias domain + _d_alias="${_d_alias%.}" _alias_index="$(_math "$_alias_index" + 1)" _debug "_d_alias" "$_d_alias" if [ "$_d_alias" ]; then From fa763db1051e15978aa11b5e8721e7c82ae3314e Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 12:05:28 +0800 Subject: [PATCH 10/24] dns_pleskxml.sh: use grep -F when matching interpolated values fulldomain/txtvalue/root_domain_name were interpolated into grep regex patterns; match them as fixed strings instead. from https://github.com/acmesh-official/acme.sh/pull/7031 --- dnsapi/dns_pleskxml.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 465bcc60..176f329d 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -151,8 +151,8 @@ dns_pleskxml_rm() { # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value recid="$( _value "$reclist" | - grep -i "${fulldomain}." | - grep "${txtvalue}" | + grep -Fi "${fulldomain}." | + grep -F "${txtvalue}" | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" @@ -419,7 +419,7 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep -F "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match From 2af543a358a1ac1cdb316d8d551db6bd41a99550 Mon Sep 17 00:00:00 2001 From: Jan Pieper Date: Fri, 10 Jul 2026 08:42:57 +0200 Subject: [PATCH 11/24] Fix typo (#6924) --- deploy/synology_dsm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 202e8188..e19024c7 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -72,7 +72,7 @@ synology_dsm_deploy() { if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then - _err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." + _err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." _err "Notice: temp admin user authorization method only supports local deployment on DSM." return 1 fi From 660a5e322c24c4975bb416a52de288474ae4aabf Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 18:37:13 +0800 Subject: [PATCH 12/24] deploy/synology_dsm.sh: use grep -Eo '[0-9]+' when extracting error codes grep -o '[0-9]*' can match the empty string; GNU grep skips empty matches but BSD greps handle them differently, breaking the 2FA login flow on OpenBSD. Force a non-empty match at all three sites. from https://github.com/acmesh-official/acme.sh/pull/6725 --- deploy/synology_dsm.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index e19024c7..75497671 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -234,7 +234,7 @@ synology_dsm_deploy() { fi fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" # Account has 2FA-OTP enabled, since error 403 reported. # https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf @@ -269,7 +269,7 @@ synology_dsm_deploy() { _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" fi fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" fi @@ -336,7 +336,7 @@ synology_dsm_deploy() { id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") _debug2 id "$id" - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" if [ -n "$error_code" ]; then if [ "$error_code" -eq 105 ]; then From 2058a77d83c0211e5a5b09cc2924eae6a944b3c3 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 19:37:36 +0800 Subject: [PATCH 13/24] acme.sh: fix variable name typo _excapedAlgnames -> _escapedAltnames from https://github.com/acmesh-official/acme.sh/pull/6547 --- acme.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acme.sh b/acme.sh index 84b38068..6495a90e 100755 --- a/acme.sh +++ b/acme.sh @@ -1443,13 +1443,13 @@ _readSubjectAltNamesFromCSR() { _debug _dnsAltnames "$_dnsAltnames" # escape the wildcard '*' so it is not taken as a regex operator by grep/sed below - _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')" - _debug _excapedAlgnames "$_excapedAlgnames" + _escapedAltnames="$(echo "$_dnsAltnames" | tr '*' '#')" + _debug _escapedAltnames "$_escapedAltnames" _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" _debug _escapedSubject "$_escapedSubject" - if _contains "$_excapedAlgnames," "DNS:$_escapedSubject,"; then + if _contains "$_escapedAltnames," "DNS:$_escapedSubject,"; then _debug "AltNames contains subject" - _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" + _dnsAltnames="$(echo "$_escapedAltnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" _debug _dnsAltnames "$_dnsAltnames" else _debug "AltNames doesn't contain subject" From 98c30912fb5a58f3a50b6263ec0e30574c5ef8c1 Mon Sep 17 00:00:00 2001 From: "Andrew V." Date: Fri, 10 Jul 2026 15:36:42 +0300 Subject: [PATCH 14/24] 2024-12-24 - Ensure that $PDNS_Url has no trailing slash ('/'). (#6171) --- dnsapi/dns_pdns.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh index ec19ad25..847a1af1 100755 --- a/dnsapi/dns_pdns.sh +++ b/dnsapi/dns_pdns.sh @@ -50,6 +50,9 @@ dns_pdns_add() { PDNS_Ttl="$DEFAULT_PDNS_TTL" fi + # Ensure PDNS_Url has no trailing slash ('/') + PDNS_Url="${PDNS_Url%/}" + #save the api addr and key to the account conf file. _saveaccountconf_mutable PDNS_Url "$PDNS_Url" _saveaccountconf_mutable PDNS_ServerId "$PDNS_ServerId" From 58cd667d6573262eb1061b51a18b21f2721e4fb0 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 20:47:20 +0800 Subject: [PATCH 15/24] dns_njalla.sh: accept string record ids when removing records The Njalla API returns record ids as JSON strings now; the numeric-only pattern matched nothing, so the removal never found the record id. Match both quoted and bare ids. from https://github.com/acmesh-official/acme.sh/pull/5121 --- dnsapi/dns_njalla.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh index c410447d..6ce51380 100644 --- a/dnsapi/dns_njalla.sh +++ b/dnsapi/dns_njalla.sh @@ -98,7 +98,7 @@ dns_njalla_rm() { echo "$records" | while read -r record; do record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") - record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \") + record_id=$(echo "$record" | _egrep_o "\"id\":\s?\"?[^\",}]*" | cut -d : -f 2 | tr -d " " | tr -d \") if [ "$_sub_domain" = "$record_name" ]; then if [ "$txtvalue" = "$record_content" ]; then _debug "record_id" "$record_id" From ebde8345ae154e621087341eecd691b26bbc555b Mon Sep 17 00:00:00 2001 From: CV Date: Fri, 10 Jul 2026 14:55:23 +0200 Subject: [PATCH 16/24] dns_ispconfig.sh client_id not numeric at ispconfig v 3.2.7p1 (#4999) Getting client_id failed due to incorrect extraction! At least in version 3.2.7p1 and probably later the plugin is not working any more properly. The result of ```curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"``` is something like this ```Result of _ISPC_ClientGetID: '[Tue Jan 23 11:44:57 CET 2024] Retrying post {"code":"ok","message":"","response":3}[Tue Jan 23 11:44:57 CET 2024] _hcode 0'```. The parsing code does not work properly and leaves a non numeric value such as ```Client ID: '3[Tue Jan 23 11'```. --- dnsapi/dns_ispconfig.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index edc789e1..bd6bfb28 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -136,7 +136,7 @@ _ISPC_getZoneInfo() { curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")" _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'" _debug "Result of _ISPC_ClientGetID: '$curResult'" - client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}') + client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d '[' -f 1 | tr -d '{}') _debug "Client ID: '${client_id}'" case "${client_id}" in '' | *[!0-9]*) From 50e5e771d56a5989e0bc24df3b422059df63669b Mon Sep 17 00:00:00 2001 From: Roman Lumetsberger Date: Fri, 10 Jul 2026 13:02:06 +0000 Subject: [PATCH 17/24] Feature: Support other shells then sh (#4877) * Add support for DEPLOY_SSH_REMOTE_SHELL * allow to configure quoting of remote cmd string * shell check and shellfmt fixes --- deploy/ssh.sh | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/deploy/ssh.sh b/deploy/ssh.sh index 82b0382c..0bf3ee48 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -25,7 +25,8 @@ # export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value # export DEPLOY_SSH_USE_SCP="" yes or no, default to no # export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q" -# +# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c +# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes ######## Public functions ##################### #domain keyfile certfile cafile fullchain @@ -71,6 +72,24 @@ ssh_deploy() { fi _savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" + # REMOTE_SHELL is optional. If not provided then use sh + _migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL + _getdeployconf DEPLOY_SSH_REMOTE_SHELL + _debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" + if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then + DEPLOY_SSH_REMOTE_SHELL="sh -c" + fi + _savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" + + # REMOTE_CMD_QUOTE is optional. If not provided then yes + _migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE + _getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE + _debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" + if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then + DEPLOY_SSH_REMOTE_CMD_QUOTE="yes" + fi + _savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" + # BACKUP is optional. If not provided then default to previously saved value or yes. _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP _getdeployconf DEPLOY_SSH_BACKUP @@ -434,9 +453,13 @@ _ssh_remote_cmd() { _secure_debug "Remote commands to execute: $_cmd" _info "Submitting sequence of commands to remote server by $_ssh_cmd" - # quotations in bash cmd below intended. Squash travis spellcheck error - # shellcheck disable=SC2029 - $_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'" + if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then + # quotations in bash cmd below intended. Squash travis spellcheck error + # shellcheck disable=SC2029 + $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'" + else + $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd" + fi _err_code="$?" if [ "$_err_code" != "0" ]; then From fef90e15e131b8fa2f47dad471aa37dcdba4f772 Mon Sep 17 00:00:00 2001 From: Mike Lei Date: Fri, 10 Jul 2026 21:56:54 +0800 Subject: [PATCH 18/24] Fix name.com DNS API for processing IDNs (#4381) --- dnsapi/dns_namecom.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh index 1ba6a6e5..bd7da0c2 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -15,7 +15,7 @@ Namecom_API="https://api.name.com/v4" #Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_namecom_add() { - fulldomain=$1 + fulldomain=$(_idn "$1") txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" @@ -68,7 +68,7 @@ dns_namecom_add() { #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_namecom_rm() { - fulldomain=$1 + fulldomain=$(_idn "$1") txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" From 2e4acba1055ea7b82e79f4f0ac2c7264796f7b00 Mon Sep 17 00:00:00 2001 From: Sasha Reid Date: Sat, 11 Jul 2026 04:04:49 +0200 Subject: [PATCH 19/24] Microwavenby dns hostinger (#6843) * [Microwavenby--dns_hostinger] Adding initial dns support for Hostinger.com * [Microwavenby--dns_hostinger] Creating a commit now that workflows are enabled * [Microwavenby--dns_hostinger] Correcting shellcheck. Why is this not automatic? * [Microwavenby-dns-hostinger] Responding to comments from Neil * [dns-hostinger] SHfmt and Shellcheck * [dns-hostinger] Writing non-greedy-ish regexes. correcting copypasta --- dnsapi/dns_hostinger.sh | 196 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100755 dnsapi/dns_hostinger.sh diff --git a/dnsapi/dns_hostinger.sh b/dnsapi/dns_hostinger.sh new file mode 100755 index 00000000..665c65da --- /dev/null +++ b/dnsapi/dns_hostinger.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_hostinger_info='Hostinger +Site: Hostinger.com +Domains: hostinger.nl +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hostinger +Options: + HOSTINGER_Token API Key +Issues: https://github.com/acmesh-official/acme.sh/issues/6831 +Author: Sasha Reid +' + +HOSTINGER_Api="https://developers.hostinger.com/api/dns/v1/zones" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_hostinger_add() { + fulldomain=$1 + txtvalue=$2 + + HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" + + if [ -z "$HOSTINGER_Token" ]; then + HOSTINGER_Token="" + _err "You didn't specify a Hostinger API Key yet." + _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" + return 1 + fi + _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting existing records" + _hostinger_rest GET "${_domain}" + + if [ -z "$response" ]; then + _err "Error" + return 1 + fi + + # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so + # we can not use updating anymore. + # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) + # _debug count "$count" + # if [ "$count" = "0" ]; then + _info "Adding record" + if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [{\"content\":\"$txtvalue\"}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":false}"; then + if _contains "$response" "Request accepted"; then + _info "Added, OK" + return 0 + elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || + _contains "$response" 'DNS:4008'; then + _info "Already exists, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 + +} + +#fulldomain txtvalue +dns_hostinger_rm() { + fulldomain=$1 + txtvalue=$2 + + HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" + + if [ -z "$HOSTINGER_Token" ]; then + HOSTINGER_Token="" + _err "You didn't specify a Hostinger API Key yet." + _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" + return 1 + fi + _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting existing records" + _hostinger_rest GET "${_domain}" + + if [ -z "$response" ]; then + _err "Error" + return 1 + fi + + if _contains "$response" "\"name\":\"$_sub_domain\""; then + # Match the record, and make certain it is a TXT record for the domain not another type. Then remove our target record from the list + remaining_records=$(echo "$response" | _normalizeJson | _egrep_o '{"name":"'"$_sub_domain"'","records":\[[^]]+\],"ttl":[0-9]+,"type":"TXT"\}' | _egrep_o "\[.*\]" | sed -E 's#\{"content":"\\"'"$txtvalue"'\\"","is_disabled":false\},?##g') + if [ "$remaining_records" != "[]" ]; then + remaining_json=$(echo "$remaining_records" | _egrep_o '"content":"\\"[^}]+\\""' | sed -E 's/^(.*)$/{\1},/g' | tr -d '\n' | sed 's/,$//') + # We need to set the remaining records back to Hostinger, as we can't partially delete + _info "Removing $txtvalue from $_sub_domain by setting records to ${remaining_json}" + if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [${remaining_json}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":true}"; then + if _contains "$response" "Request accepted"; then + _info "Updated remaining records, OK" + return 0 + elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || + _contains "$response" 'DNS:4008'; then + _info "Already exists, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + # Otherwise delete the TXT record that matches the subdomain + else + if ! _hostinger_rest DELETE "$_domain" "{\"filters\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\"}]}"; then + _err "Delete record error." + return 1 + fi + fi + echo "$response" | grep "Request accepted" >/dev/null + else + _info "Don't need to remove." + fi + +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=1 + p=1 + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + _hostinger_rest GET "$h" + if _contains "$response" "records"; then + if [ "$response" = "[]" ]; then + _debug "Valid subdomains are not the root" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + return 0 + fi + fi + + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_hostinger_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + token_trimmed=$(echo "$HOSTINGER_Token" | tr -d '"') + + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $token_trimmed" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$HOSTINGER_Api/$ep" "" "$m")" + else + response="$(_get "$HOSTINGER_Api/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} From 9366c2e065d1ef006d18fe97965eb85b3a8f3a59 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 11 Jul 2026 11:42:32 +0800 Subject: [PATCH 20/24] dns_cpanel: resolve the most specific zone in _get_root With both domain.tld and sub.domain.tld zones on the account, the first endswith hit could pick the parent zone while cPanel stores the record in the most specific one, so the cleanup never found the record and left an orphaned _acme-challenge TXT entry. Pick the longest matching zone with an exact literal suffix match (_endswith treats the needle as a regex, letting xdomain.tld wrongly match zone domain.tld). https://github.com/acmesh-official/acme.sh/issues/6807 --- dnsapi/dns_cpanel.sh | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh index a6991403..3868c679 100755 --- a/dnsapi/dns_cpanel.sh +++ b/dnsapi/dns_cpanel.sh @@ -38,7 +38,7 @@ dns_cpanel_add() { fi # adding entry _info "Adding the entry" - stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//") + stripped_fulldomain="${fulldomain%.$_domain}" _debug "Adding $stripped_fulldomain to $_domain zone" _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1" if _successful_update; then return 0; fi @@ -128,13 +128,27 @@ _get_root() { _err "Primary domain list not found!" return 1 fi - for _domain in $_domains; do - _debug "Checking if $fulldomain ends with $_domain" - if (_endswith "$fulldomain" "$_domain"); then - _debug "Root domain: $_domain" - return 0 - fi + # Pick the LONGEST matching zone, dot-anchored: with both domain.tld and + # sub.domain.tld zones on the account, cPanel stores the record in the + # most specific zone, so add and rm must both resolve to that one. + _domain="" + for d in $_domains; do + _debug "Checking if $fulldomain ends with $d" + # case with quoted patterns gives an exact literal suffix match; + # _endswith treats the needle as a regex, so its dots would let + # xdomain.tld wrongly match zone domain.tld + case "$fulldomain" in + "$d" | *".$d") + if [ "${#d}" -gt "${#_domain}" ]; then + _domain="$d" + fi + ;; + esac done + if [ -n "$_domain" ]; then + _debug "Root domain: $_domain" + return 0 + fi return 1 } From 2215f1b988dd544f186b9ae05d0a7061bf92b80f Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 11 Jul 2026 11:56:58 +0800 Subject: [PATCH 21/24] notify: clear inherited _H1.._H5 before running each notify hook The dns/deploy hooks export _H1.._H5 in the main process, and the notify hooks run in a subshell that inherits them. A hook that does not overwrite every slot (ntfy without NTFY_TOKEN, slack, telegram, etc.) sent the stale headers with its request, leaking another service's Authorization credentials to the notify endpoint. https://github.com/acmesh-official/acme.sh/issues/6801 --- acme.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/acme.sh b/acme.sh index 6495a90e..9ec92585 100755 --- a/acme.sh +++ b/acme.sh @@ -7765,6 +7765,14 @@ _send_notify() { continue fi if ! ( + # The dns/deploy hooks export _H1.._H5 in the main process, so the + # values are inherited here. Clear them: a stale Authorization header + # from another service must not leak into the notify request. + export _H1="" + export _H2="" + export _H3="" + export _H4="" + export _H5="" if ! . "$_n_hook_file"; then _err "Error loading file $_n_hook_file. Please check your API file and try again." return 1 From 04e0422526fc5723c3ce4fe88c4e39e9af8c7e2d Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 12 Jul 2026 09:30:46 +0800 Subject: [PATCH 22/24] precheck: log the socat recommendation with _info instead of _err Missing socat only matters for standalone mode; the text even says the warning can be ignored. Printing it to stderr made every --upgrade in a cron noisy for DNS-only users who redirect stdout. https://github.com/acmesh-official/acme.sh/issues/6525 --- acme.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acme.sh b/acme.sh index 9ec92585..04002193 100755 --- a/acme.sh +++ b/acme.sh @@ -7420,9 +7420,9 @@ _precheck() { fi if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then - _err "It is recommended to install socat or python first." - _err "We use socat or python for the standalone server, which is used for standalone mode." - _err "If you don't want to use standalone mode, you may ignore this warning." + _info "It is recommended to install socat or python first." + _info "We use socat or python for the standalone server, which is used for standalone mode." + _info "If you don't want to use standalone mode, you may ignore this warning." fi return 0 From 22a5ae3cebe762017baeb3e14f4b3c71d3db6a13 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 12 Jul 2026 09:34:59 +0800 Subject: [PATCH 23/24] dns_cloudns: include the server response in the login error https://github.com/acmesh-official/acme.sh/issues/6520 --- dnsapi/dns_cloudns.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh index 23a219da..2c543271 100755 --- a/dnsapi/dns_cloudns.sh +++ b/dnsapi/dns_cloudns.sh @@ -135,7 +135,7 @@ _dns_cloudns_init_check() { _dns_cloudns_http_api_call "dns/login.json" "" if ! _contains "$response" "\"status\":\"Success\""; then - _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." + _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Server response: $response" return 1 fi From 76811857a09fe7d63036c05555630ff9a1901f13 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 12 Jul 2026 09:35:22 +0800 Subject: [PATCH 24/24] dns_cpanel: quote inner expansion in suffix strip (SC2295) --- dnsapi/dns_cpanel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh index 3868c679..6939c3f1 100755 --- a/dnsapi/dns_cpanel.sh +++ b/dnsapi/dns_cpanel.sh @@ -38,7 +38,7 @@ dns_cpanel_add() { fi # adding entry _info "Adding the entry" - stripped_fulldomain="${fulldomain%.$_domain}" + stripped_fulldomain="${fulldomain%."$_domain"}" _debug "Adding $stripped_fulldomain to $_domain zone" _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1" if _successful_update; then return 0; fi