From c6a9825c0a2c6c4852a869a9cbf4864d1e270ccb Mon Sep 17 00:00:00 2001 From: asavin Date: Fri, 18 Apr 2025 17:25:55 +0200 Subject: [PATCH 001/137] Initial commit --- dnsapi/dns_efficientip.sh | 125 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 dnsapi/dns_efficientip.sh diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh new file mode 100755 index 00000000..fe5538bd --- /dev/null +++ b/dnsapi/dns_efficientip.sh @@ -0,0 +1,125 @@ +#!/bin/sh +export dns_efficientip_info='efficientip.com +Site: https://efficientip.com/ +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip +Options: + EfficientIP_Creds HTTP Basic Authentication credentials. E.g. "username:password" + EfficientIP_Token_Key Alternative API token key identifier, prefered over basic authentication. + EfficientIP_Token_Secret Alternative API token secret, required when using a token key. + EfficientIP_Server EfficientIP SOLIDserver Management IP or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server. + EfficientIP_View Name of the DNS view (optional). +Issues: github.com/acmesh-official/acme.sh/issues/ +Author: EfficientIP-Labs +' + +dns_efficientip_add() { + + fulldomain=$1 + txtvalue=$2 + + _info "Using EfficientIP API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if ([ -z "$EfficientIP_Creds" ] && ([ -z "$EfficientIP_Token_Key" ] || [ -z "$EfficientIP_Token_Secret" ])) || [ -z "$EfficientIP_Server" ]; then + EfficientIP_Creds="" + EfficientIP_Token_Key="" + EfficientIP_Token_Secret="" + EfficientIP_Server="" + _err "You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server)." + _err "Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname" + _err "or if you want to use Token instead set via EXPORT EfficientIP_Token_Key=yourkey" + _err "and EXPORT EfficientIP_Token_Secret=yoursecret" + _err "and try again." + return 1 + fi + + _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" + _saveaccountconf EfficientIP_Token_Key "${EfficientIP_Token_Key}" + _saveaccountconf EfficientIP_Token_Secret "${EfficientIP_Token_Secret}" + _saveaccountconf EfficientIP_Server "${EfficientIP_Server}" + _saveaccountconf EfficientIP_DNS_Name "${EfficientIP_DNS_Name}" + _saveaccountconf EfficientIP_View "${EfficientIP_View}" + + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + + export _H1="Accept-Language:en-US" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + + if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" + fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" + fi + + if [ -z "${EfficientIP_Token_Secret}" ] || [ -z "${EfficientIP_Token_Key}" ]; then + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + export _H2="Authorization: Basic ${EfficientIP_CredsEncoded}" + else + TS=$(date +%s) + Sig=$(printf "%b\n$TS\nPOST\n$baseurlnObject" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") + export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" + export _H3="X-SDS-TS: ${TS}" + fi + + result="$(_post "" "${baseurlnObject}" "" "POST")" + + if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then + _info "Successfully created the txt record" + return 0 + else + _err "Error encountered during record addition" + _err "${result}" + return 1 + fi +} + +dns_efficientip_rm() { + + fulldomain=$1 + txtvalue=$2 + + _info "Using EfficientIP API" + _debug fulldomain "${fulldomain}" + _debug txtvalue "${txtvalue}" + + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + + export _H1="Accept-Language:en-US" + + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_delete?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" + fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" + fi + + if [ -z "$EfficientIP_Token_Secret" ] || [ -z "$EfficientIP_Token_Key" ]; then + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + export _H2="Authorization: Basic $EfficientIP_CredsEncoded" + else + TS=$(date +%s) + Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig" | _base64) + export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" + export _H3="X-SDS-TS: $TS" + fi + + result="$(_post "" "${baseurlnObject}" "" "DELETE")" + + if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then + _info "Successfully deleted the txt record" + return 0 + else + _err "Error encountered during record delete" + _err "${result}" + return 1 + fi +} \ No newline at end of file From 218934e76722697be3c258a7205daf8c1b5e26c0 Mon Sep 17 00:00:00 2001 From: asavin Date: Fri, 18 Apr 2025 18:06:12 +0200 Subject: [PATCH 002/137] Remove export ? --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index fe5538bd..9c06514c 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -1,5 +1,5 @@ #!/bin/sh -export dns_efficientip_info='efficientip.com +dns_efficientip_info='efficientip.com Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip Options: From a0c5ef4e6fb9acc47ac6b56bf29081b8b4cbb6ce Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:17:14 +0200 Subject: [PATCH 003/137] Fixing shellcheck issues --- dnsapi/dns_efficientip.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9c06514c..d04aec5d 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,6 @@ Author: EfficientIP-Labs ' dns_efficientip_add() { - fulldomain=$1 txtvalue=$2 @@ -22,7 +21,7 @@ dns_efficientip_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - if ([ -z "$EfficientIP_Creds" ] && ([ -z "$EfficientIP_Token_Key" ] || [ -z "$EfficientIP_Token_Secret" ])) || [ -z "$EfficientIP_Server" ]; then + if ([ -z "${EfficientIP_Creds}" ] && ([ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ])) || [ -z "${EfficientIP_Server}" ]; then EfficientIP_Creds="" EfficientIP_Token_Key="" EfficientIP_Token_Secret="" @@ -35,6 +34,16 @@ dns_efficientip_add() { return 1 fi + if [ -z "${EfficientIP_DNS_Name}" ]; then + EfficientIP_DNS_Name="" + fi; + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + + if [ -z "${EfficientIP_View}" ]; then + EfficientIP_View="" + fi; + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" _saveaccountconf EfficientIP_Token_Key "${EfficientIP_Token_Key}" _saveaccountconf EfficientIP_Token_Secret "${EfficientIP_Token_Secret}" @@ -42,15 +51,13 @@ dns_efficientip_add() { _saveaccountconf EfficientIP_DNS_Name "${EfficientIP_DNS_Name}" _saveaccountconf EfficientIP_View "${EfficientIP_View}" - EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) - EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) - export _H1="Accept-Language:en-US" - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=${fulldomain}&rr_value1=${txtvalue}" if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" fi From 7c610124d9cb6f2427bbf8357c2f5d20917426bb Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:32:23 +0200 Subject: [PATCH 004/137] Updating Options to meet OptionsAlt pre-requisites --- dnsapi/dns_efficientip.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index d04aec5d..89fb48f5 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -4,11 +4,16 @@ Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip Options: EfficientIP_Creds HTTP Basic Authentication credentials. E.g. "username:password" - EfficientIP_Token_Key Alternative API token key identifier, prefered over basic authentication. + EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. + EfficientIP_View Name of the DNS view hosting the zone. Optional. +OptionsAlt: + EfficientIP_Token_Key Alternative API token key, prefered over basic authentication. EfficientIP_Token_Secret Alternative API token secret, required when using a token key. - EfficientIP_Server EfficientIP SOLIDserver Management IP or FQDN. - EfficientIP_DNS_Name Name of the DNS smart or server. - EfficientIP_View Name of the DNS view (optional). + EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. + EfficientIP_View Name of the DNS view hosting the zone. Optional. + Issues: github.com/acmesh-official/acme.sh/issues/ Author: EfficientIP-Labs ' From 1f77b8926680008b2c11038598f120be8b12edce Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:40:01 +0200 Subject: [PATCH 005/137] Updating issue ID --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 89fb48f5..c6638fcc 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,7 @@ OptionsAlt: EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. EfficientIP_View Name of the DNS view hosting the zone. Optional. -Issues: github.com/acmesh-official/acme.sh/issues/ +Issues: github.com/acmesh-official/acme.sh/issues/6325 Author: EfficientIP-Labs ' From 75603755023b9fba0d6710fbe391f99fc7c577ec Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:23:11 +0200 Subject: [PATCH 006/137] Update for testing github action pipeline --- dnsapi/dns_efficientip.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index c6638fcc..eb19fe38 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -78,13 +78,17 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - result="$(_post "" "${baseurlnObject}" "" "POST")" + if [ -n "${GITHUB_ACTIONS+1}" ]; then + result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" + else + result="$(_post "" "${baseurlnObject}" "" "POST")" + fi; if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Successfully created the txt record" + _info "Record successfully created" return 0 else - _err "Error encountered during record addition" + _err "Error creating the record" _err "${result}" return 1 fi @@ -124,13 +128,17 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - result="$(_post "" "${baseurlnObject}" "" "DELETE")" + if [ -n "${GITHUB_ACTIONS+1}" ]; then + result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" + else + result="$(_post "" "${baseurlnObject}" "" "DELETE")" + fi if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Successfully deleted the txt record" + _info "Record successfully deleted" return 0 else - _err "Error encountered during record delete" + _err "Error deleting the record" _err "${result}" return 1 fi From e089a3d8a152aaff32bd3cc3e7d786226a949901 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:31:17 +0200 Subject: [PATCH 007/137] Update for testing github action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index eb19fe38..b39d8fba 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -33,9 +33,9 @@ dns_efficientip_add() { EfficientIP_Server="" _err "You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server)." _err "Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname" - _err "or if you want to use Token instead set via EXPORT EfficientIP_Token_Key=yourkey" + _err "or if you want to use Token instead EXPORT EfficientIP_Token_Key=yourkey" _err "and EXPORT EfficientIP_Token_Secret=yoursecret" - _err "and try again." + _err "then try again." return 1 fi From eabd7592fe3942bc38b501c2cd25a25eda9c56ac Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:41:22 +0200 Subject: [PATCH 008/137] Fixing sh syntax --- dnsapi/dns_efficientip.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index b39d8fba..ab946e3e 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -41,12 +41,14 @@ dns_efficientip_add() { if [ -z "${EfficientIP_DNS_Name}" ]; then EfficientIP_DNS_Name="" - fi; + fi + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) if [ -z "${EfficientIP_View}" ]; then EfficientIP_View="" - fi; + fi + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" @@ -82,7 +84,7 @@ dns_efficientip_add() { result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "POST")" - fi; + fi if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully created" @@ -113,6 +115,7 @@ dns_efficientip_rm() { if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" fi @@ -142,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} \ No newline at end of file +} From 9eeb979c7bfdc9ed6a5455223e10f0761691029b Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:49:41 +0200 Subject: [PATCH 009/137] Fixing shellcheck issue --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index ab946e3e..292d6b5e 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -26,7 +26,7 @@ dns_efficientip_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - if ([ -z "${EfficientIP_Creds}" ] && ([ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ])) || [ -z "${EfficientIP_Server}" ]; then + if { [ -z "${EfficientIP_Creds}" ] && { [ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ]; }; } || [ -z "${EfficientIP_Server}" ]; then EfficientIP_Creds="" EfficientIP_Token_Key="" EfficientIP_Token_Secret="" From 5bc01aa2518bdde413bc8a0dffce705d1c6d602c Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:56:49 +0200 Subject: [PATCH 010/137] Disabling SC2034 --- dnsapi/dns_efficientip.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 292d6b5e..9dc2e374 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -1,4 +1,5 @@ #!/bin/sh +# shellcheck disable=SC2034 dns_efficientip_info='efficientip.com Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip From 59a43ce5d1cea2803b3f3d138b5459463c7d253b Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 16:27:36 +0200 Subject: [PATCH 011/137] Disabling SC2034 --- dnsapi/dns_efficientip.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9dc2e374..d1fdccf6 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,6 @@ OptionsAlt: EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. EfficientIP_View Name of the DNS view hosting the zone. Optional. - Issues: github.com/acmesh-official/acme.sh/issues/6325 Author: EfficientIP-Labs ' From 90e9d8ff52bd26c2dd99f6b5d71ccd099a8d9389 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 16:38:37 +0200 Subject: [PATCH 012/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index d1fdccf6..9a73303f 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -145,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} +} \ No newline at end of file From 5bb09f469f96eb6c3cf9d72a4ac504c409484f51 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 17:06:33 +0200 Subject: [PATCH 013/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9a73303f..bed5c1d6 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -80,7 +80,7 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - if [ -n "${GITHUB_ACTIONS+1}" ]; then + if [ -n "${TEST_DNS+1}" ]; then result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "POST")" @@ -131,7 +131,7 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - if [ -n "${GITHUB_ACTIONS+1}" ]; then + if [ -n "${TEST_DNS+1}" ]; then result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "DELETE")" @@ -145,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} \ No newline at end of file +} From 7a0450a7f466b21e37c452aa1bac8e343c448391 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 17:55:23 +0200 Subject: [PATCH 014/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index bed5c1d6..546b8dc2 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -90,7 +90,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the record" + _err "Error creating the DNS record" _err "${result}" return 1 fi @@ -141,7 +141,7 @@ dns_efficientip_rm() { _info "Record successfully deleted" return 0 else - _err "Error deleting the record" + _err "Error deleting the DNS record" _err "${result}" return 1 fi From 30d5d1aea9a9825eca1bcdc4898a162bb2b8f621 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 18:01:29 +0200 Subject: [PATCH 015/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 546b8dc2..a44cab98 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -80,11 +80,7 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - if [ -n "${TEST_DNS+1}" ]; then - result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" - else - result="$(_post "" "${baseurlnObject}" "" "POST")" - fi + result="$(_post "" "${baseurlnObject}" "" "POST")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully created" @@ -131,11 +127,7 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - if [ -n "${TEST_DNS+1}" ]; then - result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" - else - result="$(_post "" "${baseurlnObject}" "" "DELETE")" - fi + result="$(_post "" "${baseurlnObject}" "" "DELETE")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully deleted" From f29bfd995d3204398d7d7346f25092de01a39efc Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:15:56 +0200 Subject: [PATCH 016/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index a44cab98..997f58f4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record" + _err "Error creating the DNS record using EIP" _err "${result}" return 1 fi From 4d933c23a8660ab472445f22f688c97eb3c97170 Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:43:46 +0200 Subject: [PATCH 017/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 997f58f4..bd95eb42 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record using EIP" + _err "Error creating the DNS record EIP" _err "${result}" return 1 fi From 91081ade3c82949c9a492232d9cb042966dcb507 Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:50:57 +0200 Subject: [PATCH 018/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index bd95eb42..997f58f4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record EIP" + _err "Error creating the DNS record using EIP" _err "${result}" return 1 fi From 9f09dcd18cb6538875f5937199dc1358b6b270cb Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 11:13:38 +0200 Subject: [PATCH 019/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 997f58f4..e9d190b7 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record using EIP" + _err "Error creating the DNS record with EIP" _err "${result}" return 1 fi From 7f1423dd6f77a073359a2300408e111b5ee1176c Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 13:33:28 +0200 Subject: [PATCH 020/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index e9d190b7..f2eaaf3b 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -59,7 +59,7 @@ dns_efficientip_add() { _saveaccountconf EfficientIP_View "${EfficientIP_View}" export _H1="Accept-Language:en-US" - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=${fulldomain}&rr_value1=${txtvalue}" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_ttl=300&rr_name=${fulldomain}&rr_value1=${txtvalue}" if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record with EIP" + _err "Error creating the DNS record" _err "${result}" return 1 fi From 419738fbd5e3f7ad67f67a322e2f2cac18658dfd Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 16:05:20 +0200 Subject: [PATCH 021/137] Triggering pipeline with DNS_WILDCARD --- dnsapi/dns_efficientip.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index f2eaaf3b..afcd9af4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -83,10 +83,10 @@ dns_efficientip_add() { result="$(_post "" "${baseurlnObject}" "" "POST")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Record successfully created" + _info "DNS record successfully created" return 0 else - _err "Error creating the DNS record" + _err "Error creating DNS record" _err "${result}" return 1 fi @@ -130,10 +130,10 @@ dns_efficientip_rm() { result="$(_post "" "${baseurlnObject}" "" "DELETE")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Record successfully deleted" + _info "DNS Record successfully deleted" return 0 else - _err "Error deleting the DNS record" + _err "Error deleting DNS record" _err "${result}" return 1 fi From e08f9080c2c90e933052c4047dc6be5f37f65783 Mon Sep 17 00:00:00 2001 From: asavin Date: Fri, 18 Apr 2025 17:25:55 +0200 Subject: [PATCH 022/137] Initial commit --- dnsapi/dns_efficientip.sh | 125 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 dnsapi/dns_efficientip.sh diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh new file mode 100755 index 00000000..fe5538bd --- /dev/null +++ b/dnsapi/dns_efficientip.sh @@ -0,0 +1,125 @@ +#!/bin/sh +export dns_efficientip_info='efficientip.com +Site: https://efficientip.com/ +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip +Options: + EfficientIP_Creds HTTP Basic Authentication credentials. E.g. "username:password" + EfficientIP_Token_Key Alternative API token key identifier, prefered over basic authentication. + EfficientIP_Token_Secret Alternative API token secret, required when using a token key. + EfficientIP_Server EfficientIP SOLIDserver Management IP or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server. + EfficientIP_View Name of the DNS view (optional). +Issues: github.com/acmesh-official/acme.sh/issues/ +Author: EfficientIP-Labs +' + +dns_efficientip_add() { + + fulldomain=$1 + txtvalue=$2 + + _info "Using EfficientIP API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if ([ -z "$EfficientIP_Creds" ] && ([ -z "$EfficientIP_Token_Key" ] || [ -z "$EfficientIP_Token_Secret" ])) || [ -z "$EfficientIP_Server" ]; then + EfficientIP_Creds="" + EfficientIP_Token_Key="" + EfficientIP_Token_Secret="" + EfficientIP_Server="" + _err "You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server)." + _err "Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname" + _err "or if you want to use Token instead set via EXPORT EfficientIP_Token_Key=yourkey" + _err "and EXPORT EfficientIP_Token_Secret=yoursecret" + _err "and try again." + return 1 + fi + + _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" + _saveaccountconf EfficientIP_Token_Key "${EfficientIP_Token_Key}" + _saveaccountconf EfficientIP_Token_Secret "${EfficientIP_Token_Secret}" + _saveaccountconf EfficientIP_Server "${EfficientIP_Server}" + _saveaccountconf EfficientIP_DNS_Name "${EfficientIP_DNS_Name}" + _saveaccountconf EfficientIP_View "${EfficientIP_View}" + + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + + export _H1="Accept-Language:en-US" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + + if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" + fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" + fi + + if [ -z "${EfficientIP_Token_Secret}" ] || [ -z "${EfficientIP_Token_Key}" ]; then + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + export _H2="Authorization: Basic ${EfficientIP_CredsEncoded}" + else + TS=$(date +%s) + Sig=$(printf "%b\n$TS\nPOST\n$baseurlnObject" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") + export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" + export _H3="X-SDS-TS: ${TS}" + fi + + result="$(_post "" "${baseurlnObject}" "" "POST")" + + if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then + _info "Successfully created the txt record" + return 0 + else + _err "Error encountered during record addition" + _err "${result}" + return 1 + fi +} + +dns_efficientip_rm() { + + fulldomain=$1 + txtvalue=$2 + + _info "Using EfficientIP API" + _debug fulldomain "${fulldomain}" + _debug txtvalue "${txtvalue}" + + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + + export _H1="Accept-Language:en-US" + + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_delete?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" + fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then + baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" + fi + + if [ -z "$EfficientIP_Token_Secret" ] || [ -z "$EfficientIP_Token_Key" ]; then + EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) + export _H2="Authorization: Basic $EfficientIP_CredsEncoded" + else + TS=$(date +%s) + Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig" | _base64) + export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" + export _H3="X-SDS-TS: $TS" + fi + + result="$(_post "" "${baseurlnObject}" "" "DELETE")" + + if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then + _info "Successfully deleted the txt record" + return 0 + else + _err "Error encountered during record delete" + _err "${result}" + return 1 + fi +} \ No newline at end of file From 8ca90297e730305f00ac645d03180446efc20dc2 Mon Sep 17 00:00:00 2001 From: asavin Date: Fri, 18 Apr 2025 18:06:12 +0200 Subject: [PATCH 023/137] Remove export ? --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index fe5538bd..9c06514c 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -1,5 +1,5 @@ #!/bin/sh -export dns_efficientip_info='efficientip.com +dns_efficientip_info='efficientip.com Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip Options: From f7d8abe8ea94a1673c50e86b9479140e3ba69342 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:17:14 +0200 Subject: [PATCH 024/137] Fixing shellcheck issues --- dnsapi/dns_efficientip.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9c06514c..d04aec5d 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,6 @@ Author: EfficientIP-Labs ' dns_efficientip_add() { - fulldomain=$1 txtvalue=$2 @@ -22,7 +21,7 @@ dns_efficientip_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - if ([ -z "$EfficientIP_Creds" ] && ([ -z "$EfficientIP_Token_Key" ] || [ -z "$EfficientIP_Token_Secret" ])) || [ -z "$EfficientIP_Server" ]; then + if ([ -z "${EfficientIP_Creds}" ] && ([ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ])) || [ -z "${EfficientIP_Server}" ]; then EfficientIP_Creds="" EfficientIP_Token_Key="" EfficientIP_Token_Secret="" @@ -35,6 +34,16 @@ dns_efficientip_add() { return 1 fi + if [ -z "${EfficientIP_DNS_Name}" ]; then + EfficientIP_DNS_Name="" + fi; + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) + + if [ -z "${EfficientIP_View}" ]; then + EfficientIP_View="" + fi; + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) + _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" _saveaccountconf EfficientIP_Token_Key "${EfficientIP_Token_Key}" _saveaccountconf EfficientIP_Token_Secret "${EfficientIP_Token_Secret}" @@ -42,15 +51,13 @@ dns_efficientip_add() { _saveaccountconf EfficientIP_DNS_Name "${EfficientIP_DNS_Name}" _saveaccountconf EfficientIP_View "${EfficientIP_View}" - EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) - EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) - export _H1="Accept-Language:en-US" - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=${fulldomain}&rr_value1=${txtvalue}" if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" fi From 8484565e951845e47d24901dd0123a6dc73520cf Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:32:23 +0200 Subject: [PATCH 025/137] Updating Options to meet OptionsAlt pre-requisites --- dnsapi/dns_efficientip.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index d04aec5d..89fb48f5 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -4,11 +4,16 @@ Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip Options: EfficientIP_Creds HTTP Basic Authentication credentials. E.g. "username:password" - EfficientIP_Token_Key Alternative API token key identifier, prefered over basic authentication. + EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. + EfficientIP_View Name of the DNS view hosting the zone. Optional. +OptionsAlt: + EfficientIP_Token_Key Alternative API token key, prefered over basic authentication. EfficientIP_Token_Secret Alternative API token secret, required when using a token key. - EfficientIP_Server EfficientIP SOLIDserver Management IP or FQDN. - EfficientIP_DNS_Name Name of the DNS smart or server. - EfficientIP_View Name of the DNS view (optional). + EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. + EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. + EfficientIP_View Name of the DNS view hosting the zone. Optional. + Issues: github.com/acmesh-official/acme.sh/issues/ Author: EfficientIP-Labs ' From 67855f21d45136741a4efdde94990a3b7a9acaed Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 11:40:01 +0200 Subject: [PATCH 026/137] Updating issue ID --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 89fb48f5..c6638fcc 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,7 @@ OptionsAlt: EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. EfficientIP_View Name of the DNS view hosting the zone. Optional. -Issues: github.com/acmesh-official/acme.sh/issues/ +Issues: github.com/acmesh-official/acme.sh/issues/6325 Author: EfficientIP-Labs ' From 4d7cb7de5f78fa788927eaa89dd863bb66e8d7dd Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:23:11 +0200 Subject: [PATCH 027/137] Update for testing github action pipeline --- dnsapi/dns_efficientip.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index c6638fcc..eb19fe38 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -78,13 +78,17 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - result="$(_post "" "${baseurlnObject}" "" "POST")" + if [ -n "${GITHUB_ACTIONS+1}" ]; then + result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" + else + result="$(_post "" "${baseurlnObject}" "" "POST")" + fi; if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Successfully created the txt record" + _info "Record successfully created" return 0 else - _err "Error encountered during record addition" + _err "Error creating the record" _err "${result}" return 1 fi @@ -124,13 +128,17 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - result="$(_post "" "${baseurlnObject}" "" "DELETE")" + if [ -n "${GITHUB_ACTIONS+1}" ]; then + result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" + else + result="$(_post "" "${baseurlnObject}" "" "DELETE")" + fi if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Successfully deleted the txt record" + _info "Record successfully deleted" return 0 else - _err "Error encountered during record delete" + _err "Error deleting the record" _err "${result}" return 1 fi From 1f056998f3017ae63848cb763a546f57251900a2 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:31:17 +0200 Subject: [PATCH 028/137] Update for testing github action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index eb19fe38..b39d8fba 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -33,9 +33,9 @@ dns_efficientip_add() { EfficientIP_Server="" _err "You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server)." _err "Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname" - _err "or if you want to use Token instead set via EXPORT EfficientIP_Token_Key=yourkey" + _err "or if you want to use Token instead EXPORT EfficientIP_Token_Key=yourkey" _err "and EXPORT EfficientIP_Token_Secret=yoursecret" - _err "and try again." + _err "then try again." return 1 fi From 292026288af0d2f0e5cea0bbb304b1ecf4e14e6c Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:41:22 +0200 Subject: [PATCH 029/137] Fixing sh syntax --- dnsapi/dns_efficientip.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index b39d8fba..ab946e3e 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -41,12 +41,14 @@ dns_efficientip_add() { if [ -z "${EfficientIP_DNS_Name}" ]; then EfficientIP_DNS_Name="" - fi; + fi + EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) if [ -z "${EfficientIP_View}" ]; then EfficientIP_View="" - fi; + fi + EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" @@ -82,7 +84,7 @@ dns_efficientip_add() { result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "POST")" - fi; + fi if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully created" @@ -113,6 +115,7 @@ dns_efficientip_rm() { if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" fi + if [ "${EfficientIP_ViewEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" fi @@ -142,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} \ No newline at end of file +} From c9287071e3a836ae0c2deaf02e2c3de7879f2d71 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:49:41 +0200 Subject: [PATCH 030/137] Fixing shellcheck issue --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index ab946e3e..292d6b5e 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -26,7 +26,7 @@ dns_efficientip_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - if ([ -z "${EfficientIP_Creds}" ] && ([ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ])) || [ -z "${EfficientIP_Server}" ]; then + if { [ -z "${EfficientIP_Creds}" ] && { [ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ]; }; } || [ -z "${EfficientIP_Server}" ]; then EfficientIP_Creds="" EfficientIP_Token_Key="" EfficientIP_Token_Secret="" From af92bbca2ac10b9e7ecf4c042fb696cdfeb67d46 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 15:56:49 +0200 Subject: [PATCH 031/137] Disabling SC2034 --- dnsapi/dns_efficientip.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 292d6b5e..9dc2e374 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -1,4 +1,5 @@ #!/bin/sh +# shellcheck disable=SC2034 dns_efficientip_info='efficientip.com Site: https://efficientip.com/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip From a1eee5923a5b4f947bd9fea8b4be351f548d203f Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 16:27:36 +0200 Subject: [PATCH 032/137] Disabling SC2034 --- dnsapi/dns_efficientip.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9dc2e374..d1fdccf6 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -14,7 +14,6 @@ OptionsAlt: EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. EfficientIP_View Name of the DNS view hosting the zone. Optional. - Issues: github.com/acmesh-official/acme.sh/issues/6325 Author: EfficientIP-Labs ' From 13631ea2de465e44153d0da0e685e228aee7c427 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 16:38:37 +0200 Subject: [PATCH 033/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index d1fdccf6..9a73303f 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -145,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} +} \ No newline at end of file From 74ca0fb76307370b3e40f6b014af893c5fb0c4ae Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 17:06:33 +0200 Subject: [PATCH 034/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 9a73303f..bed5c1d6 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -80,7 +80,7 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - if [ -n "${GITHUB_ACTIONS+1}" ]; then + if [ -n "${TEST_DNS+1}" ]; then result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "POST")" @@ -131,7 +131,7 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - if [ -n "${GITHUB_ACTIONS+1}" ]; then + if [ -n "${TEST_DNS+1}" ]; then result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" else result="$(_post "" "${baseurlnObject}" "" "DELETE")" @@ -145,4 +145,4 @@ dns_efficientip_rm() { _err "${result}" return 1 fi -} \ No newline at end of file +} From 42febe97b56071054a208acc0b0d415ac9010fe2 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 17:55:23 +0200 Subject: [PATCH 035/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index bed5c1d6..546b8dc2 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -90,7 +90,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the record" + _err "Error creating the DNS record" _err "${result}" return 1 fi @@ -141,7 +141,7 @@ dns_efficientip_rm() { _info "Record successfully deleted" return 0 else - _err "Error deleting the record" + _err "Error deleting the DNS record" _err "${result}" return 1 fi From c421e2ddfcea8cc0996a39a157ebabb4ff55cb91 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 22 Apr 2025 18:01:29 +0200 Subject: [PATCH 036/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 546b8dc2..a44cab98 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -80,11 +80,7 @@ dns_efficientip_add() { export _H3="X-SDS-TS: ${TS}" fi - if [ -n "${TEST_DNS+1}" ]; then - result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" - else - result="$(_post "" "${baseurlnObject}" "" "POST")" - fi + result="$(_post "" "${baseurlnObject}" "" "POST")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully created" @@ -131,11 +127,7 @@ dns_efficientip_rm() { export _H3="X-SDS-TS: $TS" fi - if [ -n "${TEST_DNS+1}" ]; then - result="$(printf "[{\"ret_oid\": \"%d\"}]" "42")" - else - result="$(_post "" "${baseurlnObject}" "" "DELETE")" - fi + result="$(_post "" "${baseurlnObject}" "" "DELETE")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then _info "Record successfully deleted" From 67fd35127c714a9392aeec3efb672220f7c3dc22 Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:15:56 +0200 Subject: [PATCH 037/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index a44cab98..997f58f4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record" + _err "Error creating the DNS record using EIP" _err "${result}" return 1 fi From 3baa5e145f56aaab785150762c24d4599728d72d Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:43:46 +0200 Subject: [PATCH 038/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 997f58f4..bd95eb42 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record using EIP" + _err "Error creating the DNS record EIP" _err "${result}" return 1 fi From 947e872850c07f7b534f447a42b022c712013ea6 Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 10:50:57 +0200 Subject: [PATCH 039/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index bd95eb42..997f58f4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record EIP" + _err "Error creating the DNS record using EIP" _err "${result}" return 1 fi From c2762d3b6f91a158ba88fe5d627cd1097a67383a Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 11:13:38 +0200 Subject: [PATCH 040/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 997f58f4..e9d190b7 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record using EIP" + _err "Error creating the DNS record with EIP" _err "${result}" return 1 fi From ca4cb018d07c996f73118c8354dbfc263058711f Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 13:33:28 +0200 Subject: [PATCH 041/137] Triggering another action pipeline --- dnsapi/dns_efficientip.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index e9d190b7..f2eaaf3b 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -59,7 +59,7 @@ dns_efficientip_add() { _saveaccountconf EfficientIP_View "${EfficientIP_View}" export _H1="Accept-Language:en-US" - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_name=${fulldomain}&rr_value1=${txtvalue}" + baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_ttl=300&rr_name=${fulldomain}&rr_value1=${txtvalue}" if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" @@ -86,7 +86,7 @@ dns_efficientip_add() { _info "Record successfully created" return 0 else - _err "Error creating the DNS record with EIP" + _err "Error creating the DNS record" _err "${result}" return 1 fi From a2e52dadb96ace9a69b4fccbcb15dd97d289c6df Mon Sep 17 00:00:00 2001 From: asavin Date: Thu, 24 Apr 2025 16:05:20 +0200 Subject: [PATCH 042/137] Triggering pipeline with DNS_WILDCARD --- dnsapi/dns_efficientip.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index f2eaaf3b..afcd9af4 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -83,10 +83,10 @@ dns_efficientip_add() { result="$(_post "" "${baseurlnObject}" "" "POST")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Record successfully created" + _info "DNS record successfully created" return 0 else - _err "Error creating the DNS record" + _err "Error creating DNS record" _err "${result}" return 1 fi @@ -130,10 +130,10 @@ dns_efficientip_rm() { result="$(_post "" "${baseurlnObject}" "" "DELETE")" if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "Record successfully deleted" + _info "DNS Record successfully deleted" return 0 else - _err "Error deleting the DNS record" + _err "Error deleting DNS record" _err "${result}" return 1 fi From 5a085f25142ce84f51b7f7d18674352fefa5e56e Mon Sep 17 00:00:00 2001 From: asavin Date: Sun, 25 May 2025 18:36:57 +0200 Subject: [PATCH 043/137] Addressing #discussion_r2105799190 --- acme.sh | 2 +- dnsapi/dns_efficientip.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index e9eb6b94..93e18a5c 100755 --- a/acme.sh +++ b/acme.sh @@ -1017,7 +1017,7 @@ _digest() { outputhex="$2" - if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then + if [ "$alg" = "sha3-256" ] || [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then if [ "$outputhex" ]; then ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' else diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index afcd9af4..7dcac294 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -74,7 +74,7 @@ dns_efficientip_add() { export _H2="Authorization: Basic ${EfficientIP_CredsEncoded}" else TS=$(date +%s) - Sig=$(printf "%b\n$TS\nPOST\n$baseurlnObject" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + Sig=$(printf "%b\n$TS\nPOST\n$baseurlnObject" "${EfficientIP_Token_Secret}" | _digest sha3-256 hex) EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" export _H3="X-SDS-TS: ${TS}" From ca08ce42626d98cf7d9112f1b41c771218b2d23c Mon Sep 17 00:00:00 2001 From: asavin Date: Mon, 23 Jun 2025 08:59:33 +0200 Subject: [PATCH 044/137] Fixing forgottent openssl ref --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 7dcac294..4a09c5bb 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -121,7 +121,7 @@ dns_efficientip_rm() { export _H2="Authorization: Basic $EfficientIP_CredsEncoded" else TS=$(date +%s) - Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | openssl dgst -sha3-256 | cut -d '=' -f 2 | tr -d ' ') + Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | _digest sha3-256 hex) EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig" | _base64) export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" export _H3="X-SDS-TS: $TS" From 06c1911a2830c7ccb7d08cf3ec210921bfbb5038 Mon Sep 17 00:00:00 2001 From: PrivacyFreak <220089342+privacyfr3ak@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:48:32 +0000 Subject: [PATCH 045/137] fix keystore ownership read for unifi.sh --- deploy/unifi.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/unifi.sh b/deploy/unifi.sh index 1f274236..2af46b4a 100644 --- a/deploy/unifi.sh +++ b/deploy/unifi.sh @@ -143,8 +143,8 @@ unifi_deploy() { # correct file ownership according to the directory, the keystore is placed in _unifi_keystore_dir=$(dirname "${_unifi_keystore}") - _unifi_keystore_dir_owner=$(find "${_unifi_keystore_dir}" -maxdepth 0 -printf '%u\n') - _unifi_keystore_owner=$(find "${_unifi_keystore}" -maxdepth 0 -printf '%u\n') + _unifi_keystore_dir_owner=$(ls -ld "${_unifi_keystore_dir}" | awk '{print $3}') + _unifi_keystore_owner=$(ls -l "${_unifi_keystore}" | awk '{print $3}') if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then _debug "Changing keystore owner to ${_unifi_keystore_dir_owner}" chown "$_unifi_keystore_dir_owner" "${_unifi_keystore}" >/dev/null 2>&1 # fail quietly if we're not running as root From 93dc22a71f62e28b28788974f5474c6331e9fb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=20=7C=20Anton=20R=C3=B6hm?= <18481195+AnTheMaker@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:57:10 +0200 Subject: [PATCH 046/137] Support Nanelo DNS Team- & Workspace-specific API keys Nanelo Team- and Workspace-specific API keys require the "domain" parameter to be set containing the DNS zone name (unlike the Domain-specific API keys). So I've added a function to detect the root DNS zone and set the required parameter as described here: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide#3-detect-which-part-is-your-root-zone --- dnsapi/dns_nanelo.sh | 62 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh index 1ab47a89..cc3573a0 100644 --- a/dnsapi/dns_nanelo.sh +++ b/dnsapi/dns_nanelo.sh @@ -27,8 +27,16 @@ dns_nanelo_add() { fi _saveaccountconf_mutable NANELO_TOKEN "$NANELO_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" + _info "Adding TXT record to ${fulldomain}" - response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")" + response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}")" if _contains "${response}" 'success'; then return 0 fi @@ -51,8 +59,16 @@ dns_nanelo_rm() { fi _saveaccountconf_mutable NANELO_TOKEN "$NANELO_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" + _info "Deleting resource record $fulldomain" - response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")" + response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}")" if _contains "${response}" 'success'; then return 0 fi @@ -60,3 +76,45 @@ dns_nanelo_rm() { _err "${response}" return 1 } + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com + +_get_root() { + fulldomain=$1 + + # Fetch all zones from Nanelo + response="$(_get "$NANELO_API$NANELO_TOKEN/dns/getzones")" || return 1 + + # Extract "zones" array into space-separated list + zones=$(echo "$response" \ + | tr -d ' \n' \ + | sed -n 's/.*"zones":\[\([^]]*\)\].*/\1/p' \ + | tr -d '"' \ + | tr , ' ') + _debug zones "$zones" + + bestzone="" + for z in $zones; do + case "$fulldomain" in + *.$z|$z) + if [ ${#z} -gt ${#bestzone} ]; then + bestzone=$z + fi + ;; + esac + done + + if [ -z "$bestzone" ]; then + _err "No matching zone found for $fulldomain" + return 1 + fi + + _domain="$bestzone" + _sub_domain=$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//") + + return 0 +} From 31d72645838a1781dc5e38c0b1291e94b30ad0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=20=7C=20Anton=20R=C3=B6hm?= <18481195+AnTheMaker@users.noreply.github.com> Date: Sun, 7 Sep 2025 17:14:06 +0200 Subject: [PATCH 047/137] Fix pattern matching for best zone selection --- dnsapi/dns_nanelo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh index cc3573a0..abf3cf47 100644 --- a/dnsapi/dns_nanelo.sh +++ b/dnsapi/dns_nanelo.sh @@ -100,7 +100,7 @@ _get_root() { bestzone="" for z in $zones; do case "$fulldomain" in - *.$z|$z) + *."$z"|"$z") if [ ${#z} -gt ${#bestzone} ]; then bestzone=$z fi From 5aa964cde997a091b47af6a0b355f236d8631391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=20=7C=20Anton=20R=C3=B6hm?= <18481195+AnTheMaker@users.noreply.github.com> Date: Sun, 7 Sep 2025 17:14:40 +0200 Subject: [PATCH 048/137] Formatting using shfmt to format the code --- dnsapi/dns_nanelo.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh index abf3cf47..8bb95136 100644 --- a/dnsapi/dns_nanelo.sh +++ b/dnsapi/dns_nanelo.sh @@ -90,21 +90,21 @@ _get_root() { response="$(_get "$NANELO_API$NANELO_TOKEN/dns/getzones")" || return 1 # Extract "zones" array into space-separated list - zones=$(echo "$response" \ - | tr -d ' \n' \ - | sed -n 's/.*"zones":\[\([^]]*\)\].*/\1/p' \ - | tr -d '"' \ - | tr , ' ') + zones=$(echo "$response" | + tr -d ' \n' | + sed -n 's/.*"zones":\[\([^]]*\)\].*/\1/p' | + tr -d '"' | + tr , ' ') _debug zones "$zones" bestzone="" for z in $zones; do case "$fulldomain" in - *."$z"|"$z") - if [ ${#z} -gt ${#bestzone} ]; then - bestzone=$z - fi - ;; + *."$z" | "$z") + if [ ${#z} -gt ${#bestzone} ]; then + bestzone=$z + fi + ;; esac done From d8a92a2e658d4ea120dd71d41928e55dc713deb9 Mon Sep 17 00:00:00 2001 From: An <18481195+AnTheMaker@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:27:57 +0200 Subject: [PATCH 049/137] switch nanelo api to post requests --- dnsapi/dns_nanelo.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh index 8bb95136..23f306a7 100644 --- a/dnsapi/dns_nanelo.sh +++ b/dnsapi/dns_nanelo.sh @@ -36,7 +36,7 @@ dns_nanelo_add() { _debug _domain "$_domain" _info "Adding TXT record to ${fulldomain}" - response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}")" + response="$(_post "" "$NANELO_API$NANELO_TOKEN/dns/addrecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}" "" "" "")" if _contains "${response}" 'success'; then return 0 fi @@ -68,7 +68,7 @@ dns_nanelo_rm() { _debug _domain "$_domain" _info "Deleting resource record $fulldomain" - response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}")" + response="$(_post "" "$NANELO_API$NANELO_TOKEN/dns/deleterecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}" "" "" "")" if _contains "${response}" 'success'; then return 0 fi From 17da49bb782b797209531cb00cfcc8c3ee0370a8 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Thu, 9 Oct 2025 13:16:28 +0200 Subject: [PATCH 050/137] add keyhelp deploy hook --- deploy/keyhelp.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 deploy/keyhelp.sh diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh new file mode 100644 index 00000000..224a7ea8 --- /dev/null +++ b/deploy/keyhelp.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env sh + +keyhelp_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + + # Read config from saved values or env + _getdeployconf DEPLOY_KEYHELP_HOST + _getdeployconf DEPLOY_KEYHELP_API_KEY + + _debug DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST" + _secure_debug DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY" + + if [ -z "$DEPLOY_KEYHELP_HOST" ]; then + _err "KeyHelp host not found, please define DEPLOY_KEYHELP_HOST." + return 1 + fi + if [ -z "$DEPLOY_KEYHELP_API_KEY" ]; then + _err "KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY." + return 1 + fi + + # Save current values + _savedeployconf DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST" + _savedeployconf DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY" + + _request_key="$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')" + _request_cert="$(tr '\n' ':' <"$_ccert" | sed 's/:/\\n/g')" + _request_ca="$(tr '\n' ':' <"$_cca" | sed 's/:/\\n/g')" + + _request_body="{ + \"name\": \"$_cdomain\", + \"components\": { + \"private_key\": \"$_request_key\", + \"certificate\": \"$_request_cert\", + \"ca_certificate\": \"$_request_ca\" + } + }" + + _hosts="$(echo "$DEPLOY_KEYHELP_HOST" | tr "," " ")" + _keys="$(echo "$DEPLOY_KEYHELP_API_KEY" | tr "," " ")" + _i=1 + + for _host in $_hosts; do + _key="$(_getfield "$_keys" "$_i" " ")" + _i="$(_math $_i + 1)" + + export _H1="X-API-Key: $_key" + + _put_url="$_host/api/v2/certificates/name/$_cdomain" + if _post "$_request_body" "$_put_url" "" "PUT" "application/json" >/dev/null; then + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" + else + _err "Cannot make PUT request to $_put_url" + return 1 + fi + + if [ "$_code" = "404" ]; then + _info "$_cdomain not found, creating new entry at $_host" + + _post_url="$_host/api/v2/certificates" + if _post "$_request_body" "$_post_url" "" "POST" "application/json" >/dev/null; then + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" + else + _err "Cannot make POST request to $_post_url" + return 1 + fi + fi + + if _startswith "$_code" "2"; then + _info "$_cdomain set at $_host" + else + _err "HTTP status code is $_code" + return 1 + fi + done + + return 0 +} From f7cc72be354c9cf90fc16e270fa0f7bb01ea1825 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Thu, 9 Oct 2025 13:28:04 +0200 Subject: [PATCH 051/137] add missing double quotes --- deploy/keyhelp.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index 224a7ea8..944ca5aa 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -50,7 +50,7 @@ keyhelp_deploy() { for _host in $_hosts; do _key="$(_getfield "$_keys" "$_i" " ")" - _i="$(_math $_i + 1)" + _i="$(_math "$_i" + 1)" export _H1="X-API-Key: $_key" From 3c3ec2c97cde17b1566db80526ceaf504310d002 Mon Sep 17 00:00:00 2001 From: Philipp Klapp Date: Mon, 13 Oct 2025 09:05:59 +0200 Subject: [PATCH 052/137] Script created for Hetzner Cloud --- dnsapi/dns_hetznercloud.sh | 431 +++++++++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 dnsapi/dns_hetznercloud.sh diff --git a/dnsapi/dns_hetznercloud.sh b/dnsapi/dns_hetznercloud.sh new file mode 100644 index 00000000..e5b5b0d6 --- /dev/null +++ b/dnsapi/dns_hetznercloud.sh @@ -0,0 +1,431 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_hetznercloud_info='Hetzner Cloud DNS +Site: Hetzner.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hetznercloud +Options: + HETZNER_TOKEN API token for the Hetzner Cloud DNS API +Optional: + HETZNER_TTL Custom TTL for new TXT rrsets (default 120) + HETZNER_API Override API endpoint (default https://api.hetzner.cloud/v1) +Issues: github.com/acmesh-official/acme.sh/issues +' + +HETZNERCLOUD_API_DEFAULT="https://api.hetzner.cloud/v1" +HETZNERCLOUD_TTL_DEFAULT=120 + +######## Public functions ##################### + +dns_hetznercloud_add() { + fulldomain="$(_idn "${1}")" + txtvalue="${2}" + + _info "Using Hetzner Cloud DNS API to add record" + + if ! _hetznercloud_init; then + return 1 + fi + + if ! _hetznercloud_prepare_zone "${fulldomain}"; then + _err "Unable to determine Hetzner Cloud zone for ${fulldomain}" + return 1 + fi + + if ! _hetznercloud_get_rrset; then + return 1 + fi + + if [ "${_hetznercloud_last_http_code}" = "200" ]; then + if _hetznercloud_rrset_contains_value "${txtvalue}"; then + _info "TXT record already present; nothing to do." + return 0 + fi + elif [ "${_hetznercloud_last_http_code}" != "404" ]; then + _hetznercloud_log_http_error "Failed to query existing TXT rrset" "${_hetznercloud_last_http_code}" + return 1 + fi + + add_payload="$(_hetznercloud_build_add_payload "${txtvalue}")" + if [ -z "${add_payload}" ]; then + _err "Failed to build request payload." + return 1 + fi + + if ! _hetznercloud_api POST "${_hetznercloud_rrset_action_add}" "${add_payload}"; then + return 1 + fi + + case "${_hetznercloud_last_http_code}" in + 200 | 201 | 202 | 204) + _info "Hetzner Cloud TXT record added." + return 0 + ;; + 401 | 403) + _err "Hetzner Cloud DNS API authentication failed (HTTP ${_hetznercloud_last_http_code}). Check HETZNER_TOKEN for the new API." + _hetznercloud_log_http_error "" "${_hetznercloud_last_http_code}" + return 1 + ;; + 409 | 422) + _hetznercloud_log_http_error "Hetzner Cloud DNS rejected the add_records request" "${_hetznercloud_last_http_code}" + return 1 + ;; + *) + _hetznercloud_log_http_error "Hetzner Cloud DNS add_records request failed" "${_hetznercloud_last_http_code}" + return 1 + ;; + esac +} + +dns_hetznercloud_rm() { + fulldomain="$(_idn "${1}")" + txtvalue="${2}" + + _info "Using Hetzner Cloud DNS API to remove record" + + if ! _hetznercloud_init; then + return 1 + fi + + if ! _hetznercloud_prepare_zone "${fulldomain}"; then + _err "Unable to determine Hetzner Cloud zone for ${fulldomain}" + return 1 + fi + + if ! _hetznercloud_get_rrset; then + return 1 + fi + + if [ "${_hetznercloud_last_http_code}" = "404" ]; then + _info "TXT rrset does not exist; nothing to remove." + return 0 + fi + + if [ "${_hetznercloud_last_http_code}" != "200" ]; then + _hetznercloud_log_http_error "Failed to query existing TXT rrset" "${_hetznercloud_last_http_code}" + return 1 + fi + + if _hetznercloud_rrset_contains_value "${txtvalue}"; then + remove_payload="$(_hetznercloud_build_remove_payload "${txtvalue}")" + if [ -z "${remove_payload}" ]; then + _err "Failed to build remove_records payload." + return 1 + fi + if ! _hetznercloud_api POST "${_hetznercloud_rrset_action_remove}" "${remove_payload}"; then + return 1 + fi + case "${_hetznercloud_last_http_code}" in + 200 | 201 | 202 | 204) + _info "Hetzner Cloud TXT record removed." + return 0 + ;; + 401 | 403) + _err "Hetzner Cloud DNS API authentication failed (HTTP ${_hetznercloud_last_http_code}). Check HETZNER_TOKEN for the new API." + _hetznercloud_log_http_error "" "${_hetznercloud_last_http_code}" + return 1 + ;; + 404) + _info "TXT rrset already absent after remove action." + return 0 + ;; + 409 | 422) + _hetznercloud_log_http_error "Hetzner Cloud DNS rejected the remove_records request" "${_hetznercloud_last_http_code}" + return 1 + ;; + *) + _hetznercloud_log_http_error "Hetzner Cloud DNS remove_records request failed" "${_hetznercloud_last_http_code}" + return 1 + ;; + esac + else + _info "TXT value not present; nothing to remove." + return 0 + fi +} + +#################### Private functions ################################## + +_hetznercloud_init() { + HETZNER_TOKEN="${HETZNER_TOKEN:-$(_readaccountconf_mutable HETZNER_TOKEN)}" + if [ -z "${HETZNER_TOKEN}" ]; then + _err "The environment variable HETZNER_TOKEN must be set for the Hetzner Cloud DNS API." + return 1 + fi + HETZNER_TOKEN=$(echo "${HETZNER_TOKEN}" | tr -d '"') + _saveaccountconf_mutable HETZNER_TOKEN "${HETZNER_TOKEN}" + + HETZNER_API="${HETZNER_API:-$(_readaccountconf_mutable HETZNER_API)}" + if [ -z "${HETZNER_API}" ]; then + HETZNER_API="${HETZNERCLOUD_API_DEFAULT}" + fi + _saveaccountconf_mutable HETZNER_API "${HETZNER_API}" + + HETZNER_TTL="${HETZNER_TTL:-$(_readaccountconf_mutable HETZNER_TTL)}" + if [ -z "${HETZNER_TTL}" ]; then + HETZNER_TTL="${HETZNERCLOUD_TTL_DEFAULT}" + fi + ttl_check=$(printf "%s" "${HETZNER_TTL}" | tr -d '0-9') + if [ -n "${ttl_check}" ]; then + _err "HETZNER_TTL must be an integer value." + return 1 + fi + _saveaccountconf_mutable HETZNER_TTL "${HETZNER_TTL}" + + return 0 +} + +_hetznercloud_prepare_zone() { + _hetznercloud_zone_id="" + _hetznercloud_zone_name="" + _hetznercloud_zone_name_lc="" + _hetznercloud_rr_name="" + _hetznercloud_rrset_path="" + _hetznercloud_rrset_action_add="" + _hetznercloud_rrset_action_remove="" + fulldomain_lc=$(printf "%s" "${1}" | sed 's/\.$//' | _lower_case) + + i=2 + p=1 + while true; do + candidate=$(printf "%s" "${fulldomain_lc}" | cut -d . -f "${i}"-100) + if [ -z "${candidate}" ]; then + return 1 + fi + + if _hetznercloud_get_zone_by_candidate "${candidate}"; then + zone_name_lc="${_hetznercloud_zone_name_lc}" + if [ "${fulldomain_lc}" = "${zone_name_lc}" ]; then + _hetznercloud_rr_name="@" + else + suffix=".${zone_name_lc}" + if _endswith "${fulldomain_lc}" "${suffix}"; then + _hetznercloud_rr_name="${fulldomain_lc%"${suffix}"}" + else + _hetznercloud_rr_name="${fulldomain_lc}" + fi + fi + _hetznercloud_rrset_path=$(printf "%s" "${_hetznercloud_rr_name}" | _url_encode) + _hetznercloud_rrset_action_add="/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT/actions/add_records" + _hetznercloud_rrset_action_remove="/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT/actions/remove_records" + return 0 + fi + p=${i} + i=$(_math "${i}" + 1) + done +} + +_hetznercloud_get_zone_by_candidate() { + candidate="${1}" + zone_key=$(printf "%s" "${candidate}" | sed 's/[^A-Za-z0-9]/_/g') + zone_conf_key="HETZNERCLOUD_ZONE_ID_for_${zone_key}" + + cached_zone_id=$(_readdomainconf "${zone_conf_key}") + if [ -n "${cached_zone_id}" ]; then + if _hetznercloud_api GET "/zones/${cached_zone_id}"; then + if [ "${_hetznercloud_last_http_code}" = "200" ]; then + zone_data=$(printf "%s" "${response}" | _normalizeJson | sed 's/^{"zone"://' | sed 's/}$//') + if _hetznercloud_parse_zone_fields "${zone_data}"; then + zone_name_lc=$(printf "%s" "${_hetznercloud_zone_name}" | _lower_case) + if [ "${zone_name_lc}" = "${candidate}" ]; then + return 0 + fi + fi + elif [ "${_hetznercloud_last_http_code}" = "404" ]; then + _cleardomainconf "${zone_conf_key}" + fi + else + return 1 + fi + fi + + if _hetznercloud_api GET "/zones/${candidate}"; then + if [ "${_hetznercloud_last_http_code}" = "200" ]; then + zone_data=$(printf "%s" "${response}" | _normalizeJson | sed 's/^{"zone"://' | sed 's/}$//') + if _hetznercloud_parse_zone_fields "${zone_data}"; then + zone_name_lc=$(printf "%s" "${_hetznercloud_zone_name}" | _lower_case) + if [ "${zone_name_lc}" = "${candidate}" ]; then + _savedomainconf "${zone_conf_key}" "${_hetznercloud_zone_id}" + return 0 + fi + fi + elif [ "${_hetznercloud_last_http_code}" != "404" ]; then + _hetznercloud_log_http_error "Hetzner Cloud zone lookup failed" "${_hetznercloud_last_http_code}" + return 1 + fi + else + return 1 + fi + + encoded_candidate=$(printf "%s" "${candidate}" | _url_encode) + if ! _hetznercloud_api GET "/zones?name=${encoded_candidate}"; then + return 1 + fi + if [ "${_hetznercloud_last_http_code}" != "200" ]; then + if [ "${_hetznercloud_last_http_code}" = "404" ]; then + return 1 + fi + _hetznercloud_log_http_error "Hetzner Cloud zone search failed" "${_hetznercloud_last_http_code}" + return 1 + fi + + zone_data=$(_hetznercloud_extract_zone_from_list "${response}" "${candidate}") + if [ -z "${zone_data}" ]; then + return 1 + fi + if ! _hetznercloud_parse_zone_fields "${zone_data}"; then + return 1 + fi + _savedomainconf "${zone_conf_key}" "${_hetznercloud_zone_id}" + return 0 +} + +_hetznercloud_parse_zone_fields() { + zone_json="${1}" + if [ -z "${zone_json}" ]; then + return 1 + fi + normalized=$(printf "%s" "${zone_json}" | _normalizeJson) + zone_id=$(printf "%s" "${normalized}" | _egrep_o '"id":[^,}]*' | _head_n 1 | cut -d : -f 2 | tr -d ' "') + zone_name=$(printf "%s" "${normalized}" | _egrep_o '"name":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + if [ -z "${zone_id}" ] || [ -z "${zone_name}" ]; then + return 1 + fi + _hetznercloud_zone_id="${zone_id}" + _hetznercloud_zone_name="${zone_name}" + _hetznercloud_zone_name_lc=$(printf "%s" "${zone_name}" | sed 's/\.$//' | _lower_case) + return 0 +} + +_hetznercloud_extract_zone_from_list() { + list_response=$(printf "%s" "${1}" | _normalizeJson) + candidate="${2}" + escaped_candidate=$(_hetznercloud_escape_regex "${candidate}") + printf "%s" "${list_response}" | _egrep_o "{[^{}]*\"name\":\"${escaped_candidate}\"[^{}]*}" | _head_n 1 +} + +_hetznercloud_escape_regex() { + printf "%s" "${1}" | sed 's/\\/\\\\/g' | sed 's/\./\\./g' | sed 's/-/\\-/g' +} + +_hetznercloud_get_rrset() { + if [ -z "${_hetznercloud_zone_id}" ] || [ -z "${_hetznercloud_rrset_path}" ]; then + return 1 + fi + if ! _hetznercloud_api GET "/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT"; then + return 1 + fi + return 0 +} + +_hetznercloud_rrset_contains_value() { + wanted_value="${1}" + normalized=$(printf "%s" "${response}" | _normalizeJson) + escaped_value=$(_hetznercloud_escape_value "${wanted_value}") + search_pattern="\"value\":\"\\\\\"${escaped_value}\\\\\"\"" + if _contains "${normalized}" "${search_pattern}"; then + return 0 + fi + return 1 +} + +_hetznercloud_build_add_payload() { + value="${1}" + escaped_value=$(_hetznercloud_escape_value "${value}") + printf '{"ttl":%s,"records":[{"value":"\\"%s\\""}]}' "${HETZNER_TTL}" "${escaped_value}" +} + +_hetznercloud_build_remove_payload() { + value="${1}" + escaped_value=$(_hetznercloud_escape_value "${value}") + printf '{"records":[{"value":"\\"%s\\""}]}' "${escaped_value}" +} + +_hetznercloud_escape_value() { + printf "%s" "${1}" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' +} + +_hetznercloud_error_message() { + if [ -z "${response}" ]; then + return 1 + fi + message=$(printf "%s" "${response}" | _normalizeJson | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + if [ -n "${message}" ]; then + printf "%s" "${message}" + return 0 + fi + return 1 +} + +_hetznercloud_log_http_error() { + context="${1}" + code="${2}" + message="$(_hetznercloud_error_message)" + if [ -n "${context}" ]; then + if [ -n "${message}" ]; then + _err "${context} (HTTP ${code}): ${message}" + else + _err "${context} (HTTP ${code})" + fi + else + if [ -n "${message}" ]; then + _err "Hetzner Cloud DNS API error (HTTP ${code}): ${message}" + else + _err "Hetzner Cloud DNS API error (HTTP ${code})" + fi + fi +} + +_hetznercloud_api() { + method="${1}" + ep="${2}" + data="${3}" + retried="${4}" + + if [ -z "${method}" ]; then + method="GET" + fi + + if ! _startswith "${ep}" "/"; then + ep="/${ep}" + fi + url="${HETZNER_API}${ep}" + + export _H1="Authorization: Bearer ${HETZNER_TOKEN}" + export _H2="Accept: application/json" + export _H3="" + export _H4="" + export _H5="" + + : >"${HTTP_HEADER}" + + if [ "${method}" = "GET" ]; then + response="$(_get "${url}")" + else + if [ -z "${data}" ]; then + data="{}" + fi + response="$(_post "${data}" "${url}" "" "${method}" "application/json")" + fi + ret="${?}" + + _hetznercloud_last_http_code=$(grep "^HTTP" "${HTTP_HEADER}" | _tail_n 1 | cut -d " " -f 2 | tr -d '\r\n') + + if [ "${ret}" != "0" ]; then + return 1 + fi + + if [ "${_hetznercloud_last_http_code}" = "429" ] && [ "${retried}" != "retried" ]; then + retry_after=$(grep -i "^Retry-After" "${HTTP_HEADER}" | _tail_n 1 | cut -d : -f 2 | tr -d ' \r') + if [ -z "${retry_after}" ]; then + retry_after=1 + fi + _info "Hetzner Cloud DNS API rate limit hit; retrying in ${retry_after} seconds." + _sleep "${retry_after}" + if ! _hetznercloud_api "${method}" "${ep}" "${data}" "retried"; then + return 1 + fi + return 0 + fi + + return 0 +} From c4671272c0987d2de0caebafb0216f5e061c9eb7 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 14 Oct 2025 13:35:15 +0200 Subject: [PATCH 053/137] Yet another push to try the test suite --- dnsapi/dns_efficientip.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index 4a09c5bb..a5f6b09d 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -93,7 +93,6 @@ dns_efficientip_add() { } dns_efficientip_rm() { - fulldomain=$1 txtvalue=$2 From 65bd3d67b41d975019f194bdf3cb9e01680ba771 Mon Sep 17 00:00:00 2001 From: An <18481195+AnTheMaker@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:51:28 +0200 Subject: [PATCH 054/137] nanelo dns: minor log improvements --- dnsapi/dns_nanelo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh index 23f306a7..0c42989b 100644 --- a/dnsapi/dns_nanelo.sh +++ b/dnsapi/dns_nanelo.sh @@ -59,7 +59,7 @@ dns_nanelo_rm() { fi _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN" - _debug "First detect the root zone" + _debug "First, let's detect the root zone:" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 From ef76831d37161af4876202f0cde2596956aab9fb Mon Sep 17 00:00:00 2001 From: asavin Date: Sat, 18 Oct 2025 14:16:51 +0200 Subject: [PATCH 055/137] Addressing #pullrequestreview-3353279982 --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index a5f6b09d..f12a2a85 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh # shellcheck disable=SC2034 dns_efficientip_info='efficientip.com Site: https://efficientip.com/ From 3cdce86339d8ace8ba62c4ed756138bad669457d Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Tue, 21 Oct 2025 11:34:46 +0200 Subject: [PATCH 056/137] rename to keyhelp_api --- deploy/{keyhelp.sh => keyhelp_api.sh} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename deploy/{keyhelp.sh => keyhelp_api.sh} (99%) diff --git a/deploy/keyhelp.sh b/deploy/keyhelp_api.sh similarity index 99% rename from deploy/keyhelp.sh rename to deploy/keyhelp_api.sh index 944ca5aa..75e9d951 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp_api.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -keyhelp_deploy() { +keyhelp_api_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" From 48c48cb344d9f748dbc3c0dab1d7b1a62e5de7ba Mon Sep 17 00:00:00 2001 From: Roy Orbitson Date: Mon, 27 Oct 2025 16:02:33 +1030 Subject: [PATCH 057/137] Choose an IP address family for outgoing requests Useful where remote endpoints filter requests by IP address, but one's Internet connection has a stable IP for only one address family, e.g.: a dynamic IPv6 prefix and a static IPv4 address; or a static IPv6 prefix and CGNAT IPv4. --- acme.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/acme.sh b/acme.sh index 7caec290..98b827e8 100755 --- a/acme.sh +++ b/acme.sh @@ -1897,6 +1897,11 @@ _inithttp() { if [ -z "$_ACME_CURL" ] && _exists "curl"; then _ACME_CURL="curl --silent --dump-header $HTTP_HEADER " + if [ "$ACME_USE_IPV6_REQUESTS" ]; then + _ACME_CURL="$_ACME_CURL --ipv6 " + elif [ "$ACME_USE_IPV4_REQUESTS" ]; then + _ACME_CURL="$_ACME_CURL --ipv4 " + fi if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then _ACME_CURL="$_ACME_CURL -L " fi @@ -1924,6 +1929,11 @@ _inithttp() { if [ -z "$_ACME_WGET" ] && _exists "wget"; then _ACME_WGET="wget -q" + if [ "$ACME_USE_IPV6_REQUESTS" ]; then + _ACME_WGET="$_ACME_WGET --inet6-only " + elif [ "$ACME_USE_IPV4_REQUESTS" ]; then + _ACME_WGET="$_ACME_WGET --inet4-only " + fi if [ "$ACME_HTTP_NO_REDIRECTS" ]; then _ACME_WGET="$_ACME_WGET --max-redirect 0 " fi @@ -7076,6 +7086,8 @@ Parameters: --alpn Use standalone alpn mode. --stateless Use stateless mode. See: $_STATELESS_WIKI + --request-v4 Force client requests to use ipv4. + --request-v6 Force client requests to use ipv6. --apache Use Apache mode. --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. @@ -7255,6 +7267,20 @@ _processAccountConf() { _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET" fi + if [ "$_request_v6" ]; then + _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$_request_v6" + _clearaccountconf "ACME_USE_IPV4_REQUESTS" + elif [ "$ACME_USE_IPV6_REQUESTS" ]; then + _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$ACME_USE_IPV6_REQUESTS" + _clearaccountconf "ACME_USE_IPV4_REQUESTS" + elif [ "$_request_v4" ]; then + _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$_request_v4" + _clearaccountconf "ACME_USE_IPV6_REQUESTS" + elif [ "$ACME_USE_IPV4_REQUESTS" ]; then + _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$ACME_USE_IPV4_REQUESTS" + _clearaccountconf "ACME_USE_IPV6_REQUESTS" + fi + } _checkSudo() { @@ -7420,6 +7446,8 @@ _process() { _local_address="" _log_level="" _auto_upgrade="" + _request_v4="" + _request_v6="" _listen_v4="" _listen_v6="" _openssl_bin="" @@ -7885,6 +7913,18 @@ _process() { fi AUTO_UPGRADE="$_auto_upgrade" ;; + --request-v4) + _request_v4="1" + ACME_USE_IPV4_REQUESTS="1" + _request_v6="" + ACME_USE_IPV6_REQUESTS="" + ;; + --request-v6) + _request_v6="1" + ACME_USE_IPV6_REQUESTS="1" + _request_v4="" + ACME_USE_IPV4_REQUESTS="" + ;; --listen-v4) _listen_v4="1" Le_Listen_V4="$_listen_v4" From 7c5b9a5b922e5bed34f64fda6b58b257820955b9 Mon Sep 17 00:00:00 2001 From: Dennis Schmidt Date: Thu, 30 Oct 2025 09:17:13 +0000 Subject: [PATCH 058/137] Add priority, tags and title to ntfy notification Make the ntfy.sh notifications easier to distinguish at a first glance. --- notify/ntfy.sh | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/notify/ntfy.sh b/notify/ntfy.sh index 21e39559..ecb65879 100644 --- a/notify/ntfy.sh +++ b/notify/ntfy.sh @@ -14,6 +14,13 @@ ntfy_send() { _debug "_content" "$_content" _debug "_statusCode" "$_statusCode" + _priority_default="default" + _priority_error="high" + + _tag_success="white_check_mark" + _tag_error="warning" + _tag_info="information_source" + NTFY_URL="${NTFY_URL:-$(_readaccountconf_mutable NTFY_URL)}" if [ "$NTFY_URL" ]; then _saveaccountconf_mutable NTFY_URL "$NTFY_URL" @@ -30,7 +37,26 @@ ntfy_send() { export _H1="Authorization: Bearer $NTFY_TOKEN" fi - _data="${_subject}. $_content" + case "$_statusCode" in + 0) + _priority="$_priority_default" + _tag="$_tag_success" + ;; + 1) + _priority="$_priority_error" + _tag="$_tag_error" + ;; + 2) + _priority="$_priority_default" + _tag="$_tag_info" + ;; + esac + + export _H2="Priority: $_priority" + export _H3="Tags: $_tag" + export _H4="Title: $PROJECT_NAME: $_subject" + + _data="$_content" response="$(_post "$_data" "$NTFY_URL/$NTFY_TOPIC" "" "POST" "")" if [ "$?" = "0" ] && _contains "$response" "expires"; then From 3d21ac4525b2b60bd5efd1b9d23e9822a8be0ded Mon Sep 17 00:00:00 2001 From: Dennis Schmidt Date: Fri, 31 Oct 2025 08:40:34 +0000 Subject: [PATCH 059/137] CS Make shfmt happy --- notify/ntfy.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/notify/ntfy.sh b/notify/ntfy.sh index ecb65879..3a788a84 100644 --- a/notify/ntfy.sh +++ b/notify/ntfy.sh @@ -38,18 +38,18 @@ ntfy_send() { fi case "$_statusCode" in - 0) - _priority="$_priority_default" - _tag="$_tag_success" - ;; - 1) - _priority="$_priority_error" - _tag="$_tag_error" - ;; - 2) - _priority="$_priority_default" - _tag="$_tag_info" - ;; + 0) + _priority="$_priority_default" + _tag="$_tag_success" + ;; + 1) + _priority="$_priority_error" + _tag="$_tag_error" + ;; + 2) + _priority="$_priority_default" + _tag="$_tag_info" + ;; esac export _H2="Priority: $_priority" From b65f432ee02b1ad45224e97cf89e5144680b9d41 Mon Sep 17 00:00:00 2001 From: Philipp Klapp Date: Sat, 1 Nov 2025 13:44:11 +0100 Subject: [PATCH 060/137] Normalize Hetzner zone names to punycode --- dnsapi/dns_hetznercloud.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_hetznercloud.sh b/dnsapi/dns_hetznercloud.sh index e5b5b0d6..e53ac23a 100644 --- a/dnsapi/dns_hetznercloud.sh +++ b/dnsapi/dns_hetznercloud.sh @@ -290,9 +290,15 @@ _hetznercloud_parse_zone_fields() { if [ -z "${zone_id}" ] || [ -z "${zone_name}" ]; then return 1 fi + zone_name_trimmed=$(printf "%s" "${zone_name}" | sed 's/\.$//') + if zone_name_ascii=$(_idn "${zone_name_trimmed}"); then + zone_name="${zone_name_ascii}" + else + zone_name="${zone_name_trimmed}" + fi _hetznercloud_zone_id="${zone_id}" _hetznercloud_zone_name="${zone_name}" - _hetznercloud_zone_name_lc=$(printf "%s" "${zone_name}" | sed 's/\.$//' | _lower_case) + _hetznercloud_zone_name_lc=$(printf "%s" "${zone_name}" | _lower_case) return 0 } From 5f8146050464c9f0c6ae69160e672ad3bce388b3 Mon Sep 17 00:00:00 2001 From: Philipp Klapp Date: Sun, 2 Nov 2025 04:07:58 +0100 Subject: [PATCH 061/137] Wait for Hetzner Cloud DNS actions to complete before returning --- dnsapi/dns_hetznercloud.sh | 156 +++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/dnsapi/dns_hetznercloud.sh b/dnsapi/dns_hetznercloud.sh index e53ac23a..4a7eea90 100644 --- a/dnsapi/dns_hetznercloud.sh +++ b/dnsapi/dns_hetznercloud.sh @@ -8,11 +8,13 @@ Options: Optional: HETZNER_TTL Custom TTL for new TXT rrsets (default 120) HETZNER_API Override API endpoint (default https://api.hetzner.cloud/v1) + HETZNER_MAX_ATTEMPTS Number of 1s polls to wait for async actions (default 120) Issues: github.com/acmesh-official/acme.sh/issues ' HETZNERCLOUD_API_DEFAULT="https://api.hetzner.cloud/v1" HETZNERCLOUD_TTL_DEFAULT=120 +HETZNER_MAX_ATTEMPTS_DEFAULT=120 ######## Public functions ##################### @@ -57,6 +59,9 @@ dns_hetznercloud_add() { case "${_hetznercloud_last_http_code}" in 200 | 201 | 202 | 204) + if ! _hetznercloud_handle_action_response "TXT record add"; then + return 1 + fi _info "Hetzner Cloud TXT record added." return 0 ;; @@ -116,6 +121,9 @@ dns_hetznercloud_rm() { fi case "${_hetznercloud_last_http_code}" in 200 | 201 | 202 | 204) + if ! _hetznercloud_handle_action_response "TXT record remove"; then + return 1 + fi _info "Hetzner Cloud TXT record removed." return 0 ;; @@ -171,6 +179,17 @@ _hetznercloud_init() { fi _saveaccountconf_mutable HETZNER_TTL "${HETZNER_TTL}" + HETZNER_MAX_ATTEMPTS="${HETZNER_MAX_ATTEMPTS:-$(_readaccountconf_mutable HETZNER_MAX_ATTEMPTS)}" + if [ -z "${HETZNER_MAX_ATTEMPTS}" ]; then + HETZNER_MAX_ATTEMPTS="${HETZNER_MAX_ATTEMPTS_DEFAULT}" + fi + attempts_check=$(printf "%s" "${HETZNER_MAX_ATTEMPTS}" | tr -d '0-9') + if [ -n "${attempts_check}" ]; then + _err "HETZNER_MAX_ATTEMPTS must be an integer value." + return 1 + fi + _saveaccountconf_mutable HETZNER_MAX_ATTEMPTS "${HETZNER_MAX_ATTEMPTS}" + return 0 } @@ -435,3 +454,140 @@ _hetznercloud_api() { return 0 } + +_hetznercloud_handle_action_response() { + context="${1}" + if [ -z "${response}" ]; then + return 0 + fi + + normalized=$(printf "%s" "${response}" | _normalizeJson) + + failed_message="" + if failed_message=$(_hetznercloud_extract_failed_action_message "${normalized}"); then + if [ -n "${failed_message}" ]; then + _err "Hetzner Cloud DNS ${context} failed: ${failed_message}" + else + _err "Hetzner Cloud DNS ${context} failed." + fi + return 1 + fi + + action_ids="" + if action_ids=$(_hetznercloud_extract_action_ids "${normalized}"); then + for action_id in ${action_ids}; do + if [ -z "${action_id}" ]; then + continue + fi + if ! _hetznercloud_wait_for_action "${action_id}" "${context}"; then + return 1 + fi + done + fi + + return 0 +} + +_hetznercloud_extract_failed_action_message() { + normalized="${1}" + failed_section=$(printf "%s" "${normalized}" | _egrep_o '"failed_actions":\[[^]]*\]') + if [ -z "${failed_section}" ]; then + return 1 + fi + if _contains "${failed_section}" '"failed_actions":[]'; then + return 1 + fi + message=$(printf "%s" "${failed_section}" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + if [ -n "${message}" ]; then + printf "%s" "${message}" + else + printf "%s" "${failed_section}" + fi + return 0 +} + +_hetznercloud_extract_action_ids() { + normalized="${1}" + actions_section=$(printf "%s" "${normalized}" | _egrep_o '"actions":\[[^]]*\]') + if [ -z "${actions_section}" ]; then + return 1 + fi + action_ids=$(printf "%s" "${actions_section}" | _egrep_o '"id":[0-9]*' | cut -d : -f 2 | tr -d '"' | tr '\n' ' ') + action_ids=$(printf "%s" "${action_ids}" | tr -s ' ') + action_ids=$(printf "%s" "${action_ids}" | sed 's/^ //;s/ $//') + if [ -z "${action_ids}" ]; then + return 1 + fi + printf "%s" "${action_ids}" + return 0 +} + +_hetznercloud_wait_for_action() { + action_id="${1}" + context="${2}" + attempts="0" + + while true; do + if ! _hetznercloud_api GET "/actions/${action_id}"; then + return 1 + fi + if [ "${_hetznercloud_last_http_code}" != "200" ]; then + _hetznercloud_log_http_error "Hetzner Cloud DNS action ${action_id} query failed" "${_hetznercloud_last_http_code}" + return 1 + fi + + normalized=$(printf "%s" "${response}" | _normalizeJson) + action_status=$(_hetznercloud_action_status_from_normalized "${normalized}") + + if [ -z "${action_status}" ]; then + _err "Hetzner Cloud DNS ${context} action ${action_id} returned no status." + return 1 + fi + + if [ "${action_status}" = "success" ]; then + return 0 + fi + + if [ "${action_status}" = "error" ]; then + if action_error=$(_hetznercloud_action_error_from_normalized "${normalized}"); then + _err "Hetzner Cloud DNS ${context} action ${action_id} failed: ${action_error}" + else + _err "Hetzner Cloud DNS ${context} action ${action_id} failed." + fi + return 1 + fi + + attempts=$(_math "${attempts}" + 1) + if [ "${attempts}" -ge "${HETZNER_MAX_ATTEMPTS}" ]; then + _err "Hetzner Cloud DNS ${context} action ${action_id} did not complete after ${HETZNER_MAX_ATTEMPTS} attempts." + return 1 + fi + + _sleep 1 + done +} + +_hetznercloud_action_status_from_normalized() { + normalized="${1}" + status=$(printf "%s" "${normalized}" | _egrep_o '"status":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + printf "%s" "${status}" +} + +_hetznercloud_action_error_from_normalized() { + normalized="${1}" + error_section=$(printf "%s" "${normalized}" | _egrep_o '"error":{[^}]*}') + if [ -z "${error_section}" ]; then + return 1 + fi + message=$(printf "%s" "${error_section}" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + if [ -n "${message}" ]; then + printf "%s" "${message}" + return 0 + fi + code=$(printf "%s" "${error_section}" | _egrep_o '"code":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') + if [ -n "${code}" ]; then + printf "%s" "${code}" + return 0 + fi + return 1 +} From 693b1f7a74a52ed3a1499f64b9ebfe2bfc70f0ad Mon Sep 17 00:00:00 2001 From: Richard Glidden Date: Sun, 2 Nov 2025 22:50:55 -0500 Subject: [PATCH 062/137] Fix TrueNAS deploy fails on TrueNAS 25.10 --- deploy/truenas_ws.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh index d334853e..df34f927 100644 --- a/deploy/truenas_ws.sh +++ b/deploy/truenas_ws.sh @@ -71,7 +71,7 @@ with Client(uri="$_ws_uri") as c: fullchain = file.read() with open('$2', 'r') as file: privatekey = file.read() - ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey, "passphrase": ""}, job=True) + ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey}, job=True) print("R:" + str(ret["id"])) sys.exit(0) else: From b7c8601540d1296e5409387f171c1898620b8c38 Mon Sep 17 00:00:00 2001 From: Peter Lindegaard Hansen Date: Mon, 3 Nov 2025 16:18:15 +0100 Subject: [PATCH 063/137] Update dns_curanet.sh --- dnsapi/dns_curanet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_curanet.sh b/dnsapi/dns_curanet.sh index f57afa1f..42bc28f2 100644 --- a/dnsapi/dns_curanet.sh +++ b/dnsapi/dns_curanet.sh @@ -154,7 +154,7 @@ _get_root() { export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" response="$(_get "$CURANET_REST_URL/$h/Records" "" "")" - if [ ! "$(echo "$response" | _egrep_o "Entity not found")" ]; then + if [ ! "$(echo "$response" | _egrep_o "Entity not found|Bad Request")" ]; then _domain=$h return 0 fi From d187b982eb922c4ba09ec8085655a760794f7edf Mon Sep 17 00:00:00 2001 From: Peter Lindegaard Hansen Date: Mon, 3 Nov 2025 18:14:27 +0100 Subject: [PATCH 064/137] Update dns_curanet.sh --- dnsapi/dns_curanet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_curanet.sh b/dnsapi/dns_curanet.sh index 42bc28f2..0ef03fea 100644 --- a/dnsapi/dns_curanet.sh +++ b/dnsapi/dns_curanet.sh @@ -15,7 +15,7 @@ CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains" CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token" CURANET_ACCESS_TOKEN="" -######## Public functions ##################### +######## Public functions #################### #Usage: dns_curanet_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_curanet_add() { From 0fa53d62cbfcea7ae5626f3d90bd66e4bef2c731 Mon Sep 17 00:00:00 2001 From: seagleNet Date: Tue, 4 Nov 2025 09:35:47 +0100 Subject: [PATCH 065/137] feat: Add notify plugin for opsgenie --- notify/opsgenie.sh | 130 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 notify/opsgenie.sh diff --git a/notify/opsgenie.sh b/notify/opsgenie.sh new file mode 100644 index 00000000..d352a18c --- /dev/null +++ b/notify/opsgenie.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env sh + +#Support OpsGenie API integration + +#OPSGENIE_API_KEY="" Required, opsgenie api key +#OPSGENIE_REGION="" Optional, opsgenie region, can be EU or US (default: US) +#OPSGENIE_PRIORITY_SUCCESS="" Optional, opsgenie priority for success (default: P5) +#OPSGENIE_PRIORITY_ERROR="" Optional, opsgenie priority for error (default: P2) +#OPSGENIE_PRIORITY_SKIP="" Optional, opsgenie priority for renew skipped (default: P5) + +_OPSGENIE_AVAIL_REGION="US,EU" +_OPSGENIE_AVAIL_PRIORITIES="P1,P2,P3,P4,P5" + +opsgenie_send() { + _subject="$1" + _content="$2" + _status_code="$3" #0: success, 1: error, 2($RENEW_SKIP): skipped + + OPSGENIE_API_KEY="${OPSGENIE_API_KEY:-$(_readaccountconf_mutable OPSGENIE_API_KEY)}" + if [ -z "$OPSGENIE_API_KEY" ]; then + OPSGENIE_API_KEY="" + _err "You didn't specify an OpsGenie API key OPSGENIE_API_KEY yet." + return 1 + fi + _saveaccountconf_mutable OPSGENIE_API_KEY "$OPSGENIE_API_KEY" + export _H1="Authorization: GenieKey $OPSGENIE_API_KEY" + + OPSGENIE_REGION="${OPSGENIE_REGION:-$(_readaccountconf_mutable OPSGENIE_REGION)}" + if [ -z "$OPSGENIE_REGION" ]; then + OPSGENIE_REGION="US" + _info "The OPSGENIE_REGION is not set, so use the default US as regeion." + elif ! _hasfield "$_OPSGENIE_AVAIL_REGION" "$OPSGENIE_REGION"; then + _err "The OPSGENIE_REGION \"$OPSGENIE_REGION\" is not available, should be one of $_OPSGENIE_AVAIL_REGION" + OPSGENIE_REGION="" + return 1 + else + _saveaccountconf_mutable OPSGENIE_REGION "$OPSGENIE_REGION" + fi + + OPSGENIE_PRIORITY_SUCCESS="${OPSGENIE_PRIORITY_SUCCESS:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_SUCCESS)}" + if [ -z "$OPSGENIE_PRIORITY_SUCCESS" ]; then + OPSGENIE_PRIORITY_SUCCESS="P5" + _info "The OPSGENIE_PRIORITY_SUCCESS is not set, so use the default P5 as priority." + elif ! _hasfield "$_OPSGENIE_AVAIL_PRIORITIES" "$OPSGENIE_PRIORITY_SUCCESS"; then + _err "The OPSGENIE_PRIORITY_SUCCESS \"$OPSGENIE_PRIORITY_SUCCESS\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES" + OPSGENIE_PRIORITY_SUCCESS="" + return 1 + else + _saveaccountconf_mutable OPSGENIE_PRIORITY_SUCCESS "$OPSGENIE_PRIORITY_SUCCESS" + fi + + OPSGENIE_PRIORITY_ERROR="${OPSGENIE_PRIORITY_ERROR:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_ERROR)}" + if [ -z "$OPSGENIE_PRIORITY_ERROR" ]; then + OPSGENIE_PRIORITY_ERROR="P2" + _info "The OPSGENIE_PRIORITY_ERROR is not set, so use the default P2 as priority." + elif ! _hasfield "$_OPSGENIE_AVAIL_PRIORITIES" "$OPSGENIE_PRIORITY_ERROR"; then + _err "The OPSGENIE_PRIORITY_ERROR \"$OPSGENIE_PRIORITY_ERROR\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES" + OPSGENIE_PRIORITY_ERROR="" + return 1 + else + _saveaccountconf_mutable OPSGENIE_PRIORITY_ERROR "$OPSGENIE_PRIORITY_ERROR" + fi + + OPSGENIE_PRIORITY_SKIP="${OPSGENIE_PRIORITY_SKIP:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_SKIP)}" + if [ -z "$OPSGENIE_PRIORITY_SKIP" ]; then + OPSGENIE_PRIORITY_SKIP="P5" + _info "The OPSGENIE_PRIORITY_SKIP is not set, so use the default P5 as priority." + elif ! _hasfield "$_OPSGENIE_AVAIL_PRIORITIES" "$OPSGENIE_PRIORITY_SKIP"; then + _err "The OPSGENIE_PRIORITY_SKIP \"$OPSGENIE_PRIORITY_SKIP\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES" + OPSGENIE_PRIORITY_SKIP="" + return 1 + else + _saveaccountconf_mutable OPSGENIE_PRIORITY_SKIP "$OPSGENIE_PRIORITY_SKIP" + fi + + case "$OPSGENIE_REGION" in + "US") + _opsgenie_url="https://api.opsgenie.com/v2/alerts" + ;; + "EU") + _opsgenie_url="https://api.eu.opsgenie.com/v2/alerts" + ;; + *) + _err "opsgenie region error." + return 1 + ;; + esac + + case $_status_code in + 0) + _priority=$OPSGENIE_PRIORITY_SUCCESS + ;; + 1) + _priority=$OPSGENIE_PRIORITY_ERROR + ;; + 2) + _priority=$OPSGENIE_PRIORITY_SKIP + ;; + *) + _priority=$OPSGENIE_PRIORITY_ERROR + ;; + esac + + _subject_json=$(echo "$_subject" | _json_encode) + _content_json=$(echo "$_content" | _json_encode) + _subject_underscore=$(echo "$_subject" | sed 's/ /_/g') + _alias_json=$(echo "acme.sh-$(hostname)-$_subject_underscore-$(date +%Y%m%d)" | base64 --wrap=0 | _json_encode) + + _data="{ + \"message\": \"$_subject_json\", + \"alias\": \"$_alias_json\", + \"description\": \"$_content_json\", + \"tags\": [ + \"acme.sh\", + \"host:$(hostname)\" + ], + \"entity\": \"$(hostname -f)\", + \"priority\": \"$_priority\" +}" + + if response=$(_post "$_data" "$_opsgenie_url" "" "" "application/json"); then + if ! _contains "$response" error; then + _info "opsgenie send success." + return 0 + fi + fi + _err "opsgenie send error." + _err "$response" + return 1 +} From c5f41479a909f1a7cad58b74dcaedc880c7fc495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20V=C3=A1mos?= Date: Fri, 7 Nov 2025 16:16:30 +0100 Subject: [PATCH 066/137] Bump Alpine version from 3.21 to 3.22 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7523f0af..d8f8b265 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 RUN apk --no-cache add -f \ openssl \ From 59a286b0b76b088915455ead9e9015037d5fd579 Mon Sep 17 00:00:00 2001 From: privacyfr3ak <220089342+privacyfr3ak@users.noreply.github.com> Date: Sat, 8 Nov 2025 16:59:10 -0500 Subject: [PATCH 067/137] disable shellcheck --- deploy/unifi.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/unifi.sh b/deploy/unifi.sh index 2af46b4a..1d13e04f 100644 --- a/deploy/unifi.sh +++ b/deploy/unifi.sh @@ -143,7 +143,9 @@ unifi_deploy() { # correct file ownership according to the directory, the keystore is placed in _unifi_keystore_dir=$(dirname "${_unifi_keystore}") + # shellcheck disable=SC2012 _unifi_keystore_dir_owner=$(ls -ld "${_unifi_keystore_dir}" | awk '{print $3}') + # shellcheck disable=SC2012 _unifi_keystore_owner=$(ls -l "${_unifi_keystore}" | awk '{print $3}') if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then _debug "Changing keystore owner to ${_unifi_keystore_dir_owner}" From 839d611f642ee5e739ecb6006e0347d2067d2ccf Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 9 Nov 2025 18:50:09 +0100 Subject: [PATCH 068/137] use ghcr.io/letsencrypt/pebble:latest --- .github/workflows/PebbleStrict.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml index b0326332..af6aab4f 100644 --- a/.github/workflows/PebbleStrict.yml +++ b/.github/workflows/PebbleStrict.yml @@ -65,7 +65,7 @@ jobs: run: | docker run --rm -itd --name=pebble \ -e PEBBLE_VA_ALWAYS_VALID=1 \ - -p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict + -p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest From 9a994e7f36532169da970dcada70b537bfdb6128 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 9 Nov 2025 19:03:30 +0100 Subject: [PATCH 069/137] fix --- .github/workflows/PebbleStrict.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml index af6aab4f..729874ce 100644 --- a/.github/workflows/PebbleStrict.yml +++ b/.github/workflows/PebbleStrict.yml @@ -65,7 +65,7 @@ jobs: run: | docker run --rm -itd --name=pebble \ -e PEBBLE_VA_ALWAYS_VALID=1 \ - -p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict + -p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest From 57f8221bab028429b0a699464d7f4bf40c61701b Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 9 Nov 2025 19:58:25 +0100 Subject: [PATCH 070/137] fix --request-v4/6 https://github.com/acmesh-official/acme.sh/pull/6582 --- acme.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/acme.sh b/acme.sh index 98b827e8..8ac9b366 100755 --- a/acme.sh +++ b/acme.sh @@ -7086,8 +7086,6 @@ Parameters: --alpn Use standalone alpn mode. --stateless Use stateless mode. See: $_STATELESS_WIKI - --request-v4 Force client requests to use ipv4. - --request-v6 Force client requests to use ipv6. --apache Use Apache mode. --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. @@ -7149,6 +7147,8 @@ Parameters: --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. --listen-v4 Force standalone/tls server to listen at ipv4. --listen-v6 Force standalone/tls server to listen at ipv6. + --request-v4 Force client requests to use ipv4 to connect to the CA server. + --request-v6 Force client requests to use ipv6 to connect to the CA server. --openssl-bin Specifies a custom openssl bin location. --use-wget Force to use wget, if you have both curl and wget installed. --yes-I-know-dns-manual-mode-enough-go-ahead-please Force use of dns manual mode. @@ -7270,15 +7270,19 @@ _processAccountConf() { if [ "$_request_v6" ]; then _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$_request_v6" _clearaccountconf "ACME_USE_IPV4_REQUESTS" - elif [ "$ACME_USE_IPV6_REQUESTS" ]; then - _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$ACME_USE_IPV6_REQUESTS" - _clearaccountconf "ACME_USE_IPV4_REQUESTS" + ACME_USE_IPV4_REQUESTS= elif [ "$_request_v4" ]; then _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$_request_v4" _clearaccountconf "ACME_USE_IPV6_REQUESTS" + ACME_USE_IPV6_REQUESTS= + elif [ "$ACME_USE_IPV6_REQUESTS" ]; then + _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$ACME_USE_IPV6_REQUESTS" + _clearaccountconf "ACME_USE_IPV4_REQUESTS" + ACME_USE_IPV4_REQUESTS= elif [ "$ACME_USE_IPV4_REQUESTS" ]; then _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$ACME_USE_IPV4_REQUESTS" _clearaccountconf "ACME_USE_IPV6_REQUESTS" + ACME_USE_IPV6_REQUESTS= fi } From 4a7f35dea7f8a91545976245e89730bc3f3ffeb1 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 15 Nov 2025 11:12:00 +0100 Subject: [PATCH 071/137] remove clientauth https://github.com/acmesh-official/acme.sh/issues/6610 --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 8ac9b366..95337cc8 100755 --- a/acme.sh +++ b/acme.sh @@ -1271,7 +1271,7 @@ _createcsr() { _savedomainconf Le_ExtKeyUse "$Le_ExtKeyUse" printf "\nextendedKeyUsage=$Le_ExtKeyUse\n" >>"$csrconf" else - printf "\nextendedKeyUsage=serverAuth,clientAuth\n" >>"$csrconf" + printf "\nextendedKeyUsage=serverAuth\n" >>"$csrconf" fi if [ "$acmeValidationv1" ]; then From 66bad853aec3778303feec693bf9194644e27f36 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 15 Nov 2025 11:27:04 +0100 Subject: [PATCH 072/137] remove ClearLinux --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f7038f59..e6a8966a 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) |18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux |19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia |10|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux -|11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|ClearLinux |22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 |23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) |24|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) From a6ff1d69248868531daf0390e5747f50cdefd735 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 16 Nov 2025 09:30:41 +0100 Subject: [PATCH 073/137] add logo --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e6a8966a..6953cc71 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![zerossl.com](https://github.com/user-attachments/assets/7531085e-399b-4ac2-82a2-90d14a0b7f05)](https://zerossl.com/?fromacme.sh) + # An ACME Shell script: acme.sh [![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml) From 0d1f9edf3fb57b901da0c2f21cd593b4f683e2ba Mon Sep 17 00:00:00 2001 From: Joe Bauser Date: Mon, 17 Nov 2025 15:24:40 -0500 Subject: [PATCH 074/137] README.md clarify keylength arg and ECC default Reorder and reword small portions of the keylength documentation and make the ECC cert default explicitly stated in part 2 to avoid confusion. Fixes #6590 --- README.md | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6953cc71..05656044 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,8 @@ The certs will be placed in `~/.acme.sh/example.com/` The certs will be renewed automatically every **60** days. +The certs will default to ECC certificates. + More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert @@ -359,36 +361,33 @@ Ok, it's done. **Please use dns api mode instead.** -# 10. Issue ECC certificates +# 10. Issue certificates of different key types and lengths (ECC or RSA) + +Just set the `keylength` to a valid, supported, value. + +Valid values for the `keylength` parameter are: -Just set the `keylength` parameter with a prefix `ec-`. +1. **ec-256 (prime256v1, "ECDSA P-256", which is the default key type)** +2. **ec-384 (secp384r1, "ECDSA P-384")** +3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** +4. **2048 (RSA2048)** +5. **3072 (RSA3072)** +6. **4096 (RSA4096)** For example: -### Single domain ECC certificate +### Single domain with ECDSA P-384 certificate ```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 +acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384 ``` -### SAN multi domain ECC certificate +### SAN multi domain with RSA4096 certificate ```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 +acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096 ``` -Please look at the `keylength` parameter above. - -Valid values are: - -1. **ec-256 (prime256v1, "ECDSA P-256", which is the default key type)** -2. **ec-384 (secp384r1, "ECDSA P-384")** -3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** -4. **2048 (RSA2048)** -5. **3072 (RSA3072)** -6. **4096 (RSA4096)** - - # 11. Issue Wildcard certificates It's simple, just give a wildcard domain as the `-d` parameter. From 6715320e78ea1baa4ec1437166f73215b3fbc2a2 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 18 Nov 2025 21:41:43 +0100 Subject: [PATCH 075/137] fix https://github.com/acmesh-official/acme.sh/issues/6610 https://github.com/acmesh-official/acme.sh/issues/6617#issuecomment-3546341480 --- acme.sh | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/acme.sh b/acme.sh index 95337cc8..00d2d2d5 100755 --- a/acme.sh +++ b/acme.sh @@ -1250,7 +1250,7 @@ _idn() { fi } -#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 +#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 extendedUsage _createcsr() { _debug _createcsr domain="$1" @@ -1259,6 +1259,7 @@ _createcsr() { csr="$4" csrconf="$5" acmeValidationv1="$6" + extusage="$7" _debug2 domain "$domain" _debug2 domainlist "$domainlist" _debug2 csrkey "$csrkey" @@ -1267,11 +1268,10 @@ _createcsr() { printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]" >"$csrconf" - if [ "$Le_ExtKeyUse" ]; then - _savedomainconf Le_ExtKeyUse "$Le_ExtKeyUse" - printf "\nextendedKeyUsage=$Le_ExtKeyUse\n" >>"$csrconf" + if [ "$extusage" ]; then + printf "\nextendedKeyUsage=$extusage\n" >>"$csrconf" else - printf "\nextendedKeyUsage=serverAuth\n" >>"$csrconf" + printf "\nextendedKeyUsage=serverAuth,clientAuth\n" >>"$csrconf" fi if [ "$acmeValidationv1" ]; then @@ -4445,6 +4445,7 @@ issue() { _valid_from="${16}" _valid_to="${17}" _certificate_profile="${18}" + _extended_key_usage="${19}" if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" @@ -4589,12 +4590,25 @@ issue() { return 1 fi fi - if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then + _keyusage="$_extended_key_usage" + if [ "$Le_API" = "$CA_GOOGLE" ] || [ "$Le_API" = "$CA_GOOGLE_TEST" ]; then + if [ -z "$_keyusage" ]; then + #https://github.com/acmesh-official/acme.sh/issues/6610 + #google accepts serverauth only + _keyusage="serverAuth" + fi + fi + if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" "" "$_keyusage"; then _err "Error creating CSR." _clearup _on_issue_err "$_post_hook" return 1 fi + if [ "$_extended_key_usage" ]; then + _savedomainconf "Le_ExtKeyUse" "$_extended_key_usage" + else + _cleardomainconf "Le_ExtKeyUse" + fi fi _savedomainconf "Le_Keylength" "$_key_length" @@ -5553,7 +5567,7 @@ renew() { _cleardomainconf Le_OCSP_Staple fi fi - issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" + issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" "$Le_ExtKeyUse" res="$?" if [ "$res" != "0" ]; then return "$res" @@ -7469,6 +7483,7 @@ _process() { _valid_from="" _valid_to="" _certificate_profile="" + _extended_key_usage="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -7864,7 +7879,7 @@ _process() { shift ;; --extended-key-usage) - Le_ExtKeyUse="$2" + _extended_key_usage="$2" shift ;; --ocsp-must-staple | --ocsp) @@ -8081,7 +8096,7 @@ _process() { uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) - issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" "$_certificate_profile" + issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" "$_certificate_profile" "$_extended_key_usage" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" From c950b67e4b8e87bd72a6ea762a704883633095c3 Mon Sep 17 00:00:00 2001 From: Mason <36799194+WongIong@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:27:45 +0800 Subject: [PATCH 076/137] Adapting to Cloudflare's new response --- dnsapi/dns_cf.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 736742f3..b2749972 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -92,7 +92,9 @@ dns_cf_add() { if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 - elif _contains "$response" "The record already exists"; then + elif _contains "$response" "The record already exists" \ + || _contains "$response" "An identical record already exists." \ + || _contains "$response" '"code":81058'; then _info "Already exists, OK" return 0 else From a9f96bf7097f37fe52b1a79b9b41582f6ff37632 Mon Sep 17 00:00:00 2001 From: Mason <36799194+WongIong@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:29:44 +0800 Subject: [PATCH 077/137] Reformat using shfmt --- dnsapi/dns_cf.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index b2749972..7b383c43 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -92,9 +92,9 @@ dns_cf_add() { if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 - elif _contains "$response" "The record already exists" \ - || _contains "$response" "An identical record already exists." \ - || _contains "$response" '"code":81058'; then + elif _contains "$response" "The record already exists" || + _contains "$response" "An identical record already exists." || + _contains "$response" '"code":81058'; then _info "Already exists, OK" return 0 else From 3d3053f4277b0218faae29a3adf19b13d32709af Mon Sep 17 00:00:00 2001 From: Antoni Company Date: Thu, 20 Nov 2025 10:06:37 +0000 Subject: [PATCH 078/137] feat: Add custom filename for panos --- deploy/panos.sh | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/deploy/panos.sh b/deploy/panos.sh index a9232e79..8f911fba 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -16,6 +16,7 @@ # export PANOS_TEMPLATE="" # Template Name of panorama managed devices # export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically # export PANOS_VSYS="Shared" # name of the vsys to import the certificate +# export PANOS_FILENAME="" # use a custom filename to work around Panorama's 31-character limit # # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. @@ -89,7 +90,7 @@ deployer() { if [ "$type" = 'cert' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_filename" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" @@ -103,11 +104,11 @@ deployer() { if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_filename" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_filename.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi @@ -168,7 +169,6 @@ deployer() { # This is the main function that will call the other functions to deploy everything. panos_deploy() { - _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename _ckey="$2" _cfullchain="$5" @@ -242,6 +242,15 @@ panos_deploy() { _getdeployconf PANOS_VSYS fi + # PANOS_FILENAME + if [ "$PANOS_FILENAME" ]; then + _debug "Detected ENV variable PANOS_FILENAME. Saving to file." + _savedeployconf PANOS_FILENAME "$PANOS_FILENAME" 1 + else + _debug "Attempting to load variable PANOS_FILENAME from file." + _getdeployconf PANOS_FILENAME + fi + #Store variables _panos_host=$PANOS_HOST _panos_user=$PANOS_USER @@ -249,6 +258,7 @@ panos_deploy() { _panos_template=$PANOS_TEMPLATE _panos_template_stack=$PANOS_TEMPLATE_STACK _panos_vsys=$PANOS_VSYS + _panos_filename=$PANOS_FILENAME #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. if [ "$_panos_host" ] && [ "$_panos_key" ]; then @@ -267,6 +277,12 @@ panos_deploy() { _err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs." return 1 else + # Use filename based on the first domain on the certificate if no custom filename is set + if [ -z "$_panos_filename" ]; then + _panos_filename=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename + _savedeployconf PANOS_FILENAME "$_panos_filename" 1 + fi + # Generate a new API key if no valid API key is found if [ -z "$_panos_key" ]; then _debug "**** Generating new PANOS API KEY ****" From d2539c3f1aefb9cf0d7d29f488fb37f28b690188 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 20 Nov 2025 21:18:40 +0100 Subject: [PATCH 079/137] fix https://github.com/acmesh-official/acme.sh/issues/6402 --- acme.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/acme.sh b/acme.sh index 00d2d2d5..6578d414 100755 --- a/acme.sh +++ b/acme.sh @@ -5242,6 +5242,16 @@ $_authorizations_map" return 1 fi break + elif _contains "$response" "\"ready\""; then + _info "Order status is 'ready', let's sleep and retry." + _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') + _debug "_retryafter" "$_retryafter" + if [ "$_retryafter" ]; then + _info "Sleeping for $_retryafter seconds then retrying" + _sleep $_retryafter + else + _sleep 2 + fi elif _contains "$response" "\"processing\""; then _info "Order status is 'processing', let's sleep and retry." _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') From 46a2608783ea647042ea2a6d06c1c0e37bd91f05 Mon Sep 17 00:00:00 2001 From: Antoni Company Date: Sat, 22 Nov 2025 09:22:32 +0000 Subject: [PATCH 080/137] fix: Renamed filaname to certname - Changed filename to certname to better reflect the actual issue at hand. - Restored _cdomain variable to its original place for clarity. --- deploy/panos.sh | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/deploy/panos.sh b/deploy/panos.sh index 8f911fba..c54d21fe 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -16,7 +16,7 @@ # export PANOS_TEMPLATE="" # Template Name of panorama managed devices # export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically # export PANOS_VSYS="Shared" # name of the vsys to import the certificate -# export PANOS_FILENAME="" # use a custom filename to work around Panorama's 31-character limit +# export PANOS_CERTNAME="" # use a custom certificate name to work around Panorama's 31-character limit # # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. @@ -90,7 +90,7 @@ deployer() { if [ "$type" = 'cert' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_filename" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" @@ -104,11 +104,11 @@ deployer() { if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_filename" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_filename.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_certname.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi @@ -169,6 +169,7 @@ deployer() { # This is the main function that will call the other functions to deploy everything. panos_deploy() { + _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename _ckey="$2" _cfullchain="$5" @@ -242,13 +243,13 @@ panos_deploy() { _getdeployconf PANOS_VSYS fi - # PANOS_FILENAME - if [ "$PANOS_FILENAME" ]; then - _debug "Detected ENV variable PANOS_FILENAME. Saving to file." - _savedeployconf PANOS_FILENAME "$PANOS_FILENAME" 1 + # PANOS_CERTNAME + if [ "$PANOS_CERTNAME" ]; then + _debug "Detected ENV variable PANOS_CERTNAME. Saving to file." + _savedeployconf PANOS_CERTNAME "$PANOS_CERTNAME" 1 else - _debug "Attempting to load variable PANOS_FILENAME from file." - _getdeployconf PANOS_FILENAME + _debug "Attempting to load variable PANOS_CERTNAME from file." + _getdeployconf PANOS_CERTNAME fi #Store variables @@ -258,7 +259,7 @@ panos_deploy() { _panos_template=$PANOS_TEMPLATE _panos_template_stack=$PANOS_TEMPLATE_STACK _panos_vsys=$PANOS_VSYS - _panos_filename=$PANOS_FILENAME + _panos_certname=$PANOS_CERTNAME #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. if [ "$_panos_host" ] && [ "$_panos_key" ]; then @@ -277,10 +278,10 @@ panos_deploy() { _err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs." return 1 else - # Use filename based on the first domain on the certificate if no custom filename is set - if [ -z "$_panos_filename" ]; then - _panos_filename=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename - _savedeployconf PANOS_FILENAME "$_panos_filename" 1 + # Use certificate name based on the first domain on the certificate if no custom certificate name is set + if [ -z "$_panos_certname" ]; then + _panos_certname="$_cdomain" + _savedeployconf PANOS_CERTNAME "$_panos_certname" 1 fi # Generate a new API key if no valid API key is found From 9b30bd5a0356c83ad5c02d3040ae8daee4dbdbea Mon Sep 17 00:00:00 2001 From: ZeroSSL-Andreas Date: Tue, 25 Nov 2025 14:41:31 +0100 Subject: [PATCH 081/137] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 05656044..4afd90a8 100644 --- a/README.md +++ b/README.md @@ -523,3 +523,20 @@ Your donation makes **acme.sh** better: 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) + +# 21. About this repository + +> [!NOTE] +> This repository is officially maintained by ZeroSSL as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community! +> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com. +> +> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL. +

+ + + + + ZeroSSL + + +

From 75ee17aeeb9560cf45b0193efa48f4f46bcdbaab Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 25 Nov 2025 14:47:26 +0100 Subject: [PATCH 082/137] Remove unecessary base64 encoding --- dnsapi/dns_efficientip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh index f12a2a85..a485849a 100755 --- a/dnsapi/dns_efficientip.sh +++ b/dnsapi/dns_efficientip.sh @@ -121,7 +121,7 @@ dns_efficientip_rm() { else TS=$(date +%s) Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | _digest sha3-256 hex) - EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig" | _base64) + EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" export _H3="X-SDS-TS: $TS" fi From 705fbcd570dfec12b9851cdd9b047020c60e5185 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 27 Nov 2025 22:13:18 +0100 Subject: [PATCH 083/137] fix https://github.com/acmesh-official/acme.sh/issues/6124#issuecomment-3586650156 --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index d8f8b265..88edc4a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,8 @@ RUN apk --no-cache add -f \ jq \ cronie +ENV LE_WORKING_DIR=/acmebin + ENV LE_CONFIG_HOME=/acme.sh ARG AUTO_UPGRADE=1 @@ -30,7 +32,7 @@ COPY ./notify /install_acme.sh/notify RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/ -RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null#> /proc/1/fd/1 2>/proc/1/fd/2#' | crontab - +RUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null#> /proc/1/fd/1 2>/proc/1/fd/2#' | crontab - RUN for verb in help \ version \ @@ -64,7 +66,7 @@ RUN for verb in help \ set-default-ca \ set-default-chain \ ; do \ - printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ + printf -- "%b" "#!/usr/bin/env sh\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ ; done RUN printf "%b" '#!'"/usr/bin/env sh\n \ @@ -72,7 +74,7 @@ if [ \"\$1\" = \"daemon\" ]; then \n \ exec crond -n -s -m off \n \ else \n \ exec -- \"\$@\"\n \ -fi\n" >/entry.sh && chmod +x /entry.sh +fi\n" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME VOLUME /acme.sh From c5566eafebaa04edd30074058e2eefba5b5bfc1e Mon Sep 17 00:00:00 2001 From: SunMar Date: Fri, 28 Nov 2025 09:44:50 +0100 Subject: [PATCH 084/137] fix "dns_aws.sh: line 164: _error: command not found" #6443 --- dnsapi/dns_aws.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index c88c9d9c..b76d69c2 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -161,7 +161,7 @@ _get_root() { h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g') _debug "Checking domain: $h" if [ -z "$h" ]; then - _error "invalid domain" + _err "invalid domain" return 1 fi From ac0df6bc885db5f67e2ccebecfacbedd011974f4 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 29 Nov 2025 16:36:14 +0100 Subject: [PATCH 085/137] start 3.1.3 --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 6578d414..da67fa14 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=3.1.2 +VER=3.1.3 PROJECT_NAME="acme.sh" From 5c6d8aacbeeb4063822064c27bdaf4a144975cb7 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 29 Nov 2025 22:38:02 +0100 Subject: [PATCH 086/137] Add files via upload --- dnsapi/dns_infoblox_uddi.sh | 220 ++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 dnsapi/dns_infoblox_uddi.sh diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh new file mode 100644 index 00000000..545ce41d --- /dev/null +++ b/dnsapi/dns_infoblox_uddi.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_infoblox_uddi_info='Infoblox UDDI +Site: Infoblox.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infoblox_uddi +Options: + Infoblox_UDDI_Key API Key for Infoblox UDDI + Infoblox_Portal URL, e.g. "csp.infoblox.com" or "csp.eu.infoblox.com" +Issues: github.com/acmesh-official/acme.sh/issues +Author: Stefan Riegel +' + +######## Public functions ##################### + +#Usage: dns_infoblox_uddi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_infoblox_uddi_add() { + fulldomain=$1 + txtvalue=$2 + + Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" + Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" + + _info "Using Infoblox UDDI API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if [ -z "$Infoblox_UDDI_Key" ] || [ -z "$Infoblox_Portal" ]; then + Infoblox_UDDI_Key="" + Infoblox_Portal="" + _err "You didn't specify the Infoblox UDDI key or server (Infoblox_UDDI_Key; Infoblox_Portal)." + _err "Please set them via EXPORT Infoblox_UDDI_Key=your_key, EXPORT Infoblox_Portal=csp.infoblox.com and try again." + return 1 + fi + + _saveaccountconf_mutable Infoblox_UDDI_Key "$Infoblox_UDDI_Key" + _saveaccountconf_mutable Infoblox_Portal "$Infoblox_Portal" + + export _H1="Authorization: token $Infoblox_UDDI_Key" + export _H2="Content-Type: application/json" + + zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" + _debug "Fetching zones from: $zone_url" + zone_result="$(_get "$zone_url")" + _debug2 "zone_result: $zone_result" + + if [ "$?" != "0" ]; then + _err "Error fetching zones from Infoblox API" + return 1 + fi + + fulldomain_no_acme=$(echo "$fulldomain" | sed 's/^_acme-challenge\.//') + _debug "Looking for zone matching domain: $fulldomain_no_acme" + + zone_fqdn="" + temp_domain="$fulldomain_no_acme" + + while [ -n "$temp_domain" ]; do + _debug "Checking if '$temp_domain' is a zone..." + if echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\"" || echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\.\""; then + zone_fqdn="$temp_domain" + _debug "Found matching zone: $zone_fqdn" + break + fi + temp_domain=$(echo "$temp_domain" | sed 's/^[^.]*\.//') + if ! echo "$temp_domain" | grep -q '\.'; then + break + fi + done + + if [ -z "$zone_fqdn" ]; then + _err "Could not determine zone for domain $fulldomain" + _err "Available zones: $(echo "$zone_result" | _egrep_o '"fqdn":"[^"]*"' | sed 's/"fqdn":"//;s/"//')" + return 1 + fi + + zone_id=$(echo "$zone_result" | jq -r '(.results // .)[] | select(.fqdn == "'"$zone_fqdn"'" or .fqdn == "'"$zone_fqdn"'.") | .id' | head -1) + + _debug "zone_id: $zone_id" + + if [ -z "$zone_id" ]; then + _err "Could not find zone ID for $zone_fqdn" + _debug "Zone result: $zone_result" + return 1 + fi + + _debug "Extracting name_in_zone from fulldomain='$fulldomain' with zone_fqdn='$zone_fqdn'" + name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//") + _debug "name_in_zone after removing zone: '$name_in_zone'" + name_in_zone=$(echo "$name_in_zone" | sed 's/\.$//') + _debug "name_in_zone final: '$name_in_zone'" + + baseurl="https://$Infoblox_Portal/api/ddi/v1/dns/record" + + body="{\"type\":\"TXT\",\"name_in_zone\":\"$name_in_zone\",\"zone\":\"$zone_id\",\"ttl\":120,\"inheritance_sources\":{\"ttl\":{\"action\":\"override\"}},\"rdata\":{\"text\":\"$txtvalue\"}}" + + _debug "POST URL: $baseurl" + _debug "POST body: $body" + result="$(_post "$body" "$baseurl" "" "POST")" + _debug "POST result: $result" + + if echo "$result" | grep -q '"id"'; then + record_id=$(echo "$result" | _egrep_o '"id":"[^"]*"' | head -1 | sed 's/"id":"\([^"]*\)"/\1/') + _info "Successfully created TXT record with ID: $record_id" + return 0 + else + _err "Error encountered during record addition" + _err "Response: $result" + return 1 + fi +} + +#Usage: dns_infoblox_uddi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_infoblox_uddi_rm() { + fulldomain=$1 + txtvalue=$2 + + Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" + Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" + + if [ -z "$Infoblox_UDDI_Key" ] || [ -z "$Infoblox_Portal" ]; then + _err "Credentials not found" + return 1 + fi + + _info "Using Infoblox UDDI API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + export _H1="Authorization: token $Infoblox_UDDI_Key" + export _H2="Content-Type: application/json" + + zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" + _debug "Fetching zones from: $zone_url" + zone_result="$(_get "$zone_url")" + _debug2 "zone_result: $zone_result" + + if [ "$?" != "0" ]; then + _err "Error fetching zones from Infoblox API" + return 1 + fi + + fulldomain_no_acme=$(echo "$fulldomain" | sed 's/^_acme-challenge\.//') + _debug "Looking for zone matching domain: $fulldomain_no_acme" + + zone_fqdn="" + temp_domain="$fulldomain_no_acme" + + while [ -n "$temp_domain" ]; do + _debug "Checking if '$temp_domain' is a zone..." + if echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\"" || echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\.\""; then + zone_fqdn="$temp_domain" + _debug "Found matching zone: $zone_fqdn" + break + fi + temp_domain=$(echo "$temp_domain" | sed 's/^[^.]*\.//') + if ! echo "$temp_domain" | grep -q '\.'; then + break + fi + done + + if [ -z "$zone_fqdn" ]; then + _err "Could not determine zone for domain $fulldomain" + _err "Available zones: $(echo "$zone_result" | _egrep_o '"fqdn":"[^"]*"' | sed 's/"fqdn":"//;s/"//')" + return 1 + fi + + zone_id=$(echo "$zone_result" | jq -r '(.results // .)[] | select(.fqdn == "'"$zone_fqdn"'" or .fqdn == "'"$zone_fqdn"'.") | .id' | head -1) + + _debug "zone_id: $zone_id" + + if [ -z "$zone_id" ]; then + _err "Could not find zone ID for $zone_fqdn" + _debug "Zone result: $zone_result" + return 1 + fi + + name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//" | sed 's/\.$//') + _debug "name_in_zone: $name_in_zone" + + filter="type eq 'TXT' and name_in_zone eq '$name_in_zone' and zone eq '$zone_id'" + filter_encoded=$(_url_encode "$filter") + geturl="https://$Infoblox_Portal/api/ddi/v1/dns/record?_filter=$filter_encoded" + _debug "GET URL: $geturl" + + result="$(_get "$geturl")" + _debug "GET result: $result" + + if echo "$result" | grep -q '"results":'; then + record_count=$(echo "$result" | jq -r '.results | length') + _debug "Found $record_count result(s)" + + record_id=$(echo "$result" | jq -r '.results[] | select(.rdata.text == "'"$txtvalue"'") | .id' | head -1) + + if [ -n "$record_id" ]; then + record_uuid=$(echo "$record_id" | sed 's/.*\/\([a-f0-9-]*\)$/\1/') + _debug "Found record UUID: $record_uuid" + + delurl="https://$Infoblox_Portal/api/ddi/v1/dns/record/$record_uuid" + _debug "DELETE URL: $delurl" + rmResult="$(_post "" "$delurl" "" "DELETE")" + + if [ -z "$rmResult" ] || [ "$rmResult" = "{}" ]; then + _info "Successfully deleted the txt record" + return 0 + else + _err "Error occurred during txt record delete" + _err "Response: $rmResult" + return 1 + fi + else + _err "Record to delete didn't match an existing record (no matching txtvalue found)" + _debug "Looking for txtvalue: $txtvalue" + return 1 + fi + else + _err "Record to delete didn't match an existing record (no results found)" + _debug "Response: $result" + return 1 + fi +} From 657b7195d6427c0bc2110c609b0a27365361597f Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sat, 29 Nov 2025 23:06:20 +0100 Subject: [PATCH 088/137] Fix Authorization header format --- dnsapi/dns_infoblox_uddi.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index 545ce41d..c49a7f82 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -35,7 +35,7 @@ dns_infoblox_uddi_add() { _saveaccountconf_mutable Infoblox_UDDI_Key "$Infoblox_UDDI_Key" _saveaccountconf_mutable Infoblox_Portal "$Infoblox_Portal" - export _H1="Authorization: token $Infoblox_UDDI_Key" + export _H1="Authorization: Token $Infoblox_UDDI_Key" export _H2="Content-Type: application/json" zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" @@ -126,7 +126,7 @@ dns_infoblox_uddi_rm() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - export _H1="Authorization: token $Infoblox_UDDI_Key" + export _H1="Authorization: Token $Infoblox_UDDI_Key" export _H2="Content-Type: application/json" zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" From eeb91de6a369e209558147f49dbe9a98b08a9a0e Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sat, 29 Nov 2025 23:13:52 +0100 Subject: [PATCH 089/137] Replace jq with shell-based JSON parsing --- dnsapi/dns_infoblox_uddi.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index c49a7f82..674090be 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -73,7 +73,7 @@ dns_infoblox_uddi_add() { return 1 fi - zone_id=$(echo "$zone_result" | jq -r '(.results // .)[] | select(.fqdn == "'"$zone_fqdn"'" or .fqdn == "'"$zone_fqdn"'.") | .id' | head -1) + zone_id=$(echo "$zone_result" | _egrep_o '"id":"dns/auth_zone/[^"]*"' | _egrep_o 'dns/auth_zone/[^"]*' | _head_n 1) _debug "zone_id: $zone_id" @@ -164,7 +164,7 @@ dns_infoblox_uddi_rm() { return 1 fi - zone_id=$(echo "$zone_result" | jq -r '(.results // .)[] | select(.fqdn == "'"$zone_fqdn"'" or .fqdn == "'"$zone_fqdn"'.") | .id' | head -1) + zone_id=$(echo "$zone_result" | _egrep_o '"id":"dns/auth_zone/[^"]*"' | _egrep_o 'dns/auth_zone/[^"]*' | _head_n 1) _debug "zone_id: $zone_id" @@ -186,10 +186,8 @@ dns_infoblox_uddi_rm() { _debug "GET result: $result" if echo "$result" | grep -q '"results":'; then - record_count=$(echo "$result" | jq -r '.results | length') - _debug "Found $record_count result(s)" - - record_id=$(echo "$result" | jq -r '.results[] | select(.rdata.text == "'"$txtvalue"'") | .id' | head -1) + record_id=$(echo "$result" | _egrep_o '"id":"dns/record/[^"]*"' | _egrep_o 'dns/record/[^"]*' | _head_n 1) + _debug "Found record_id: $record_id" if [ -n "$record_id" ]; then record_uuid=$(echo "$record_id" | sed 's/.*\/\([a-f0-9-]*\)$/\1/') From ca35e8c1189b2aa75cf022913154c10aaee30732 Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sat, 29 Nov 2025 23:32:28 +0100 Subject: [PATCH 090/137] Fix zone_id extraction to query correct zone --- dnsapi/dns_infoblox_uddi.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index 674090be..8dfeab5f 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -73,7 +73,14 @@ dns_infoblox_uddi_add() { return 1 fi - zone_id=$(echo "$zone_result" | _egrep_o '"id":"dns/auth_zone/[^"]*"' | _egrep_o 'dns/auth_zone/[^"]*' | _head_n 1) + # Fetch exact zone_id for the matched fqdn using server-side filtering + filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" + filter_encoded=$(_url_encode "$filter") + zone_query="$zone_url?_filter=$filter_encoded" + _debug "Fetching zone_id with filter: $zone_query" + zone_lookup="$(_get "$zone_query")" + _debug2 "zone_lookup: $zone_lookup" + zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') _debug "zone_id: $zone_id" @@ -164,7 +171,14 @@ dns_infoblox_uddi_rm() { return 1 fi - zone_id=$(echo "$zone_result" | _egrep_o '"id":"dns/auth_zone/[^"]*"' | _egrep_o 'dns/auth_zone/[^"]*' | _head_n 1) + # Fetch exact zone_id for the matched fqdn using server-side filtering + filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" + filter_encoded=$(_url_encode "$filter") + zone_query="$zone_url?_filter=$filter_encoded" + _debug "Fetching zone_id with filter: $zone_query" + zone_lookup="$(_get "$zone_query")" + _debug2 "zone_lookup: $zone_lookup" + zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') _debug "zone_id: $zone_id" @@ -177,7 +191,7 @@ dns_infoblox_uddi_rm() { name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//" | sed 's/\.$//') _debug "name_in_zone: $name_in_zone" - filter="type eq 'TXT' and name_in_zone eq '$name_in_zone' and zone eq '$zone_id'" + filter="type eq 'TXT' and name_in_zone eq '$name_in_zone' and zone eq '$zone_id' and rdata.text eq '$txtvalue'" filter_encoded=$(_url_encode "$filter") geturl="https://$Infoblox_Portal/api/ddi/v1/dns/record?_filter=$filter_encoded" _debug "GET URL: $geturl" @@ -186,7 +200,7 @@ dns_infoblox_uddi_rm() { _debug "GET result: $result" if echo "$result" | grep -q '"results":'; then - record_id=$(echo "$result" | _egrep_o '"id":"dns/record/[^"]*"' | _egrep_o 'dns/record/[^"]*' | _head_n 1) + record_id=$(echo "$result" | _egrep_o '"id":"dns/record/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') _debug "Found record_id: $record_id" if [ -n "$record_id" ]; then From 490b9e2d09d998cee6736176e8c7eefa08b6029b Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sat, 29 Nov 2025 23:39:43 +0100 Subject: [PATCH 091/137] Clean up debug statements --- dnsapi/dns_infoblox_uddi.sh | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index 8dfeab5f..ebf4484c 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -39,7 +39,6 @@ dns_infoblox_uddi_add() { export _H2="Content-Type: application/json" zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" - _debug "Fetching zones from: $zone_url" zone_result="$(_get "$zone_url")" _debug2 "zone_result: $zone_result" @@ -77,12 +76,11 @@ dns_infoblox_uddi_add() { filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" filter_encoded=$(_url_encode "$filter") zone_query="$zone_url?_filter=$filter_encoded" - _debug "Fetching zone_id with filter: $zone_query" zone_lookup="$(_get "$zone_query")" _debug2 "zone_lookup: $zone_lookup" zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') - _debug "zone_id: $zone_id" + _debug zone_id "$zone_id" if [ -z "$zone_id" ]; then _err "Could not find zone ID for $zone_fqdn" @@ -90,20 +88,16 @@ dns_infoblox_uddi_add() { return 1 fi - _debug "Extracting name_in_zone from fulldomain='$fulldomain' with zone_fqdn='$zone_fqdn'" name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//") - _debug "name_in_zone after removing zone: '$name_in_zone'" name_in_zone=$(echo "$name_in_zone" | sed 's/\.$//') - _debug "name_in_zone final: '$name_in_zone'" + _debug name_in_zone "$name_in_zone" baseurl="https://$Infoblox_Portal/api/ddi/v1/dns/record" body="{\"type\":\"TXT\",\"name_in_zone\":\"$name_in_zone\",\"zone\":\"$zone_id\",\"ttl\":120,\"inheritance_sources\":{\"ttl\":{\"action\":\"override\"}},\"rdata\":{\"text\":\"$txtvalue\"}}" - _debug "POST URL: $baseurl" - _debug "POST body: $body" result="$(_post "$body" "$baseurl" "" "POST")" - _debug "POST result: $result" + _debug2 result "$result" if echo "$result" | grep -q '"id"'; then record_id=$(echo "$result" | _egrep_o '"id":"[^"]*"' | head -1 | sed 's/"id":"\([^"]*\)"/\1/') @@ -137,7 +131,6 @@ dns_infoblox_uddi_rm() { export _H2="Content-Type: application/json" zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" - _debug "Fetching zones from: $zone_url" zone_result="$(_get "$zone_url")" _debug2 "zone_result: $zone_result" @@ -175,12 +168,11 @@ dns_infoblox_uddi_rm() { filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" filter_encoded=$(_url_encode "$filter") zone_query="$zone_url?_filter=$filter_encoded" - _debug "Fetching zone_id with filter: $zone_query" zone_lookup="$(_get "$zone_query")" _debug2 "zone_lookup: $zone_lookup" zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') - _debug "zone_id: $zone_id" + _debug zone_id "$zone_id" if [ -z "$zone_id" ]; then _err "Could not find zone ID for $zone_fqdn" @@ -189,15 +181,14 @@ dns_infoblox_uddi_rm() { fi name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//" | sed 's/\.$//') - _debug "name_in_zone: $name_in_zone" + _debug name_in_zone "$name_in_zone" filter="type eq 'TXT' and name_in_zone eq '$name_in_zone' and zone eq '$zone_id' and rdata.text eq '$txtvalue'" filter_encoded=$(_url_encode "$filter") geturl="https://$Infoblox_Portal/api/ddi/v1/dns/record?_filter=$filter_encoded" - _debug "GET URL: $geturl" result="$(_get "$geturl")" - _debug "GET result: $result" + _debug2 result "$result" if echo "$result" | grep -q '"results":'; then record_id=$(echo "$result" | _egrep_o '"id":"dns/record/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') @@ -205,10 +196,9 @@ dns_infoblox_uddi_rm() { if [ -n "$record_id" ]; then record_uuid=$(echo "$record_id" | sed 's/.*\/\([a-f0-9-]*\)$/\1/') - _debug "Found record UUID: $record_uuid" + _debug record_uuid "$record_uuid" delurl="https://$Infoblox_Portal/api/ddi/v1/dns/record/$record_uuid" - _debug "DELETE URL: $delurl" rmResult="$(_post "" "$delurl" "" "DELETE")" if [ -z "$rmResult" ] || [ "$rmResult" = "{}" ]; then @@ -221,7 +211,6 @@ dns_infoblox_uddi_rm() { fi else _err "Record to delete didn't match an existing record (no matching txtvalue found)" - _debug "Looking for txtvalue: $txtvalue" return 1 fi else From 890ab4a7bbfc9e950ba0ae103796e066442c25ee Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sun, 30 Nov 2025 00:48:15 +0100 Subject: [PATCH 092/137] Refactor dns_infoblox_uddi.sh: Fix zone detection and add wildcard cert support - Added _get_root() helper function for proper zone detection - Fixed zone ID extraction to match dns/auth_zone/* pattern - Added _infoblox_rest() wrapper for API calls with proper auth - Improved error handling for authentication failures - Added support for wildcard certificates (multiple TXT records) - Filter by exact txtvalue when deleting records - Follow acme.sh best practices and conventions Tested with: - Standard domain certificates - Wildcard certificates (*.domain.com) - Multiple subdomains - Staging and production Let's Encrypt --- dnsapi/dns_infoblox_uddi.sh | 289 +++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 133 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index ebf4484c..54cfe47b 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -10,6 +10,8 @@ Issues: github.com/acmesh-official/acme.sh/issues Author: Stefan Riegel ' +Infoblox_UDDI_Api="https://" + ######## Public functions ##################### #Usage: dns_infoblox_uddi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -38,76 +40,42 @@ dns_infoblox_uddi_add() { export _H1="Authorization: Token $Infoblox_UDDI_Key" export _H2="Content-Type: application/json" - zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" - zone_result="$(_get "$zone_url")" - _debug2 "zone_result: $zone_result" - - if [ "$?" != "0" ]; then - _err "Error fetching zones from Infoblox API" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" return 1 fi - - fulldomain_no_acme=$(echo "$fulldomain" | sed 's/^_acme-challenge\.//') - _debug "Looking for zone matching domain: $fulldomain_no_acme" - - zone_fqdn="" - temp_domain="$fulldomain_no_acme" - - while [ -n "$temp_domain" ]; do - _debug "Checking if '$temp_domain' is a zone..." - if echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\"" || echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\.\""; then - zone_fqdn="$temp_domain" - _debug "Found matching zone: $zone_fqdn" - break - fi - temp_domain=$(echo "$temp_domain" | sed 's/^[^.]*\.//') - if ! echo "$temp_domain" | grep -q '\.'; then - break + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting existing txt records" + _infoblox_rest GET "dns/record?_filter=type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'" + + _info "Adding record" + body="{\"type\":\"TXT\",\"name_in_zone\":\"$_sub_domain\",\"zone\":\"$_domain_id\",\"ttl\":120,\"inheritance_sources\":{\"ttl\":{\"action\":\"override\"}},\"rdata\":{\"text\":\"$txtvalue\"}}" + + if _infoblox_rest POST "dns/record" "$body"; then + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + return 0 + elif _contains "$response" '"error"'; then + # Check if record already exists + if _contains "$response" "already exists" || _contains "$response" "duplicate"; then + _info "Already exists, OK" + return 0 + else + _err "Add txt record error." + _err "Response: $response" + return 1 + fi + else + _info "Added, OK" + return 0 fi - done - - if [ -z "$zone_fqdn" ]; then - _err "Could not determine zone for domain $fulldomain" - _err "Available zones: $(echo "$zone_result" | _egrep_o '"fqdn":"[^"]*"' | sed 's/"fqdn":"//;s/"//')" - return 1 - fi - - # Fetch exact zone_id for the matched fqdn using server-side filtering - filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" - filter_encoded=$(_url_encode "$filter") - zone_query="$zone_url?_filter=$filter_encoded" - zone_lookup="$(_get "$zone_query")" - _debug2 "zone_lookup: $zone_lookup" - zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') - - _debug zone_id "$zone_id" - - if [ -z "$zone_id" ]; then - _err "Could not find zone ID for $zone_fqdn" - _debug "Zone result: $zone_result" - return 1 - fi - - name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//") - name_in_zone=$(echo "$name_in_zone" | sed 's/\.$//') - _debug name_in_zone "$name_in_zone" - - baseurl="https://$Infoblox_Portal/api/ddi/v1/dns/record" - - body="{\"type\":\"TXT\",\"name_in_zone\":\"$name_in_zone\",\"zone\":\"$zone_id\",\"ttl\":120,\"inheritance_sources\":{\"ttl\":{\"action\":\"override\"}},\"rdata\":{\"text\":\"$txtvalue\"}}" - - result="$(_post "$body" "$baseurl" "" "POST")" - _debug2 result "$result" - - if echo "$result" | grep -q '"id"'; then - record_id=$(echo "$result" | _egrep_o '"id":"[^"]*"' | head -1 | sed 's/"id":"\([^"]*\)"/\1/') - _info "Successfully created TXT record with ID: $record_id" - return 0 - else - _err "Error encountered during record addition" - _err "Response: $result" - return 1 fi + _err "Add txt record error." + return 1 } #Usage: dns_infoblox_uddi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -130,92 +98,147 @@ dns_infoblox_uddi_rm() { export _H1="Authorization: Token $Infoblox_UDDI_Key" export _H2="Content-Type: application/json" - zone_url="https://$Infoblox_Portal/api/ddi/v1/dns/auth_zone" - zone_result="$(_get "$zone_url")" - _debug2 "zone_result: $zone_result" - - if [ "$?" != "0" ]; then - _err "Error fetching zones from Infoblox API" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" return 1 fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" - fulldomain_no_acme=$(echo "$fulldomain" | sed 's/^_acme-challenge\.//') - _debug "Looking for zone matching domain: $fulldomain_no_acme" + _debug "Getting txt records to delete" + # Filter by txtvalue to support wildcard certs (multiple TXT records) + filter="type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'%20and%20rdata.text%20eq%20'$txtvalue'" + _infoblox_rest GET "dns/record?_filter=$filter" - zone_fqdn="" - temp_domain="$fulldomain_no_acme" + if ! _contains "$response" '"results"'; then + _info "Don't need to remove, record not found." + return 0 + fi - while [ -n "$temp_domain" ]; do - _debug "Checking if '$temp_domain' is a zone..." - if echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\"" || echo "$zone_result" | grep -q "\"fqdn\":\"$temp_domain\.\""; then - zone_fqdn="$temp_domain" - _debug "Found matching zone: $zone_fqdn" - break - fi - temp_domain=$(echo "$temp_domain" | sed 's/^[^.]*\.//') - if ! echo "$temp_domain" | grep -q '\.'; then - break - fi - done + record_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4) + _debug "record_id" "$record_id" - if [ -z "$zone_fqdn" ]; then - _err "Could not determine zone for domain $fulldomain" - _err "Available zones: $(echo "$zone_result" | _egrep_o '"fqdn":"[^"]*"' | sed 's/"fqdn":"//;s/"//')" - return 1 + if [ -z "$record_id" ]; then + _info "Don't need to remove, record not found." + return 0 fi - # Fetch exact zone_id for the matched fqdn using server-side filtering - filter="fqdn eq '$zone_fqdn.' or fqdn eq '$zone_fqdn'" - filter_encoded=$(_url_encode "$filter") - zone_query="$zone_url?_filter=$filter_encoded" - zone_lookup="$(_get "$zone_query")" - _debug2 "zone_lookup: $zone_lookup" - zone_id=$(echo "$zone_lookup" | _egrep_o '"id":"dns/auth_zone/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') - - _debug zone_id "$zone_id" + # Extract UUID from the full record ID (format: dns/record/uuid) + record_uuid=$(echo "$record_id" | sed 's|.*/||') + _debug "record_uuid" "$record_uuid" - if [ -z "$zone_id" ]; then - _err "Could not find zone ID for $zone_fqdn" - _debug "Zone result: $zone_result" + if ! _infoblox_rest DELETE "dns/record/$record_uuid"; then + _err "Delete record error." return 1 fi - name_in_zone=$(echo "$fulldomain" | sed "s/\.$zone_fqdn\$//" | sed 's/\.$//') - _debug name_in_zone "$name_in_zone" - - filter="type eq 'TXT' and name_in_zone eq '$name_in_zone' and zone eq '$zone_id' and rdata.text eq '$txtvalue'" - filter_encoded=$(_url_encode "$filter") - geturl="https://$Infoblox_Portal/api/ddi/v1/dns/record?_filter=$filter_encoded" - - result="$(_get "$geturl")" - _debug2 result "$result" - - if echo "$result" | grep -q '"results":'; then - record_id=$(echo "$result" | _egrep_o '"id":"dns/record/[^\"]*"' | _head_n 1 | sed 's/.*"id":"\([^\"]*\)".*/\1/') - _debug "Found record_id: $record_id" + _info "Removed record successfully" + return 0 +} - if [ -n "$record_id" ]; then - record_uuid=$(echo "$record_id" | sed 's/.*\/\([a-f0-9-]*\)$/\1/') - _debug record_uuid "$record_uuid" +#################### Private functions below ################################## + +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=dns/auth_zone/xxxx-xxxx +_get_root() { + domain=$1 + i=1 + p=1 + + # Remove _acme-challenge prefix if present + domain_no_acme=$(echo "$domain" | sed 's/^_acme-challenge\.//') + + while true; do + h=$(printf "%s" "$domain_no_acme" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then + # not valid + return 1 + fi - delurl="https://$Infoblox_Portal/api/ddi/v1/dns/record/$record_uuid" - rmResult="$(_post "" "$delurl" "" "DELETE")" + # Query for the zone with both trailing dot and without + filter="fqdn%20eq%20'$h.'%20or%20fqdn%20eq%20'$h'" + if ! _infoblox_rest GET "dns/auth_zone?_filter=$filter"; then + # API error - don't continue if we get auth errors + if _contains "$response" "401" || _contains "$response" "Authorization"; then + _err "Authentication failed. Please check your Infoblox_UDDI_Key." + return 1 + fi + # For other errors, continue to parent domain + p=$i + i=$((i + 1)) + continue + fi - if [ -z "$rmResult" ] || [ "$rmResult" = "{}" ]; then - _info "Successfully deleted the txt record" + # Check if response contains results (even if empty) + if _contains "$response" '"results"'; then + # Extract zone ID - must match the pattern dns/auth_zone/... + zone_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4) + if [ -n "$zone_id" ]; then + # Found the zone + _domain="$h" + _domain_id="$zone_id" + + # Calculate subdomain + if [ "$_domain" = "$domain" ]; then + _sub_domain="" + else + _cutlength=$((${#domain} - ${#_domain} - 1)) + _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength") + fi + return 0 - else - _err "Error occurred during txt record delete" - _err "Response: $rmResult" - return 1 fi - else - _err "Record to delete didn't match an existing record (no matching txtvalue found)" - return 1 fi + + p=$i + i=$((i + 1)) + done + + return 1 +} + +# _infoblox_rest GET "dns/record?_filter=..." +# _infoblox_rest POST "dns/record" "{json body}" +# _infoblox_rest DELETE "dns/record/uuid" +_infoblox_rest() { + method=$1 + ep="$2" + data="$3" + + _debug "$ep" + + # Ensure credentials are available (when called from _get_root) + Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" + Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" + + Infoblox_UDDI_Api="https://$Infoblox_Portal/api/ddi/v1" + export _H1="Authorization: Token $Infoblox_UDDI_Key" + export _H2="Content-Type: application/json" + + # Debug (masked) + _tok_len=$(printf "%s" "$Infoblox_UDDI_Key" | wc -c | tr -d ' \n') + _debug2 "Auth header set" "Token len=${_tok_len} on $Infoblox_Portal" + + if [ "$method" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$Infoblox_UDDI_Api/$ep" "" "$method")" else - _err "Record to delete didn't match an existing record (no results found)" - _debug "Response: $result" + response="$(_get "$Infoblox_UDDI_Api/$ep")" + fi + + _ret="$?" + _debug2 response "$response" + + if [ "$_ret" != "0" ]; then + _err "Error: $ep" return 1 fi + + return 0 } From 36b8ca2bc07d0e46dd65fc8d8365d9ca1797d786 Mon Sep 17 00:00:00 2001 From: Stefan Riegel Date: Sun, 30 Nov 2025 00:49:36 +0100 Subject: [PATCH 093/137] Fix shfmt formatting: Remove trailing whitespace --- dnsapi/dns_infoblox_uddi.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index 54cfe47b..4b15088a 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -183,7 +183,7 @@ _get_root() { # Found the zone _domain="$h" _domain_id="$zone_id" - + # Calculate subdomain if [ "$_domain" = "$domain" ]; then _sub_domain="" @@ -191,7 +191,7 @@ _get_root() { _cutlength=$((${#domain} - ${#_domain} - 1)) _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength") fi - + return 0 fi fi From 9980ad0fef9634b105c59711dd5f470a4b35f080 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:33:19 +0100 Subject: [PATCH 094/137] add HostUp DNS --- dnsapi/dns_hostup.sh | 473 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 dnsapi/dns_hostup.sh diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh new file mode 100644 index 00000000..4da0dd9d --- /dev/null +++ b/dnsapi/dns_hostup.sh @@ -0,0 +1,473 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034,SC2154 + +dns_hostup_info='HostUp DNS +Site: hostup.se +Docs: https://hostup.se/en/support/api-autentisering/ +Options: + HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. + HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api). + HOSTUP_TTL Optional. TTL for TXT records (default: 60 seconds). + HOSTUP_ZONE_ID Optional. Force a specific zone ID (skip auto-detection). +Author: HostUp (https://cloud.hostup.se/contact/en) +' + +HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api" +HOSTUP_DEFAULT_TTL=60 + +# Public: add TXT record +# Usage: dns_hostup_add _acme-challenge.example.com "txt-value" +dns_hostup_add() { + fulldomain="$1" + txtvalue="$2" + + _info "Using HostUp DNS API" + + if ! _hostup_init; then + return 1 + fi + + if ! _hostup_detect_zone "$fulldomain"; then + _err "Unable to determine HostUp zone for $fulldomain" + return 1 + fi + + record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")" + record_name="$(_hostup_sanitize_name "$record_name")" + record_value="$(_hostup_json_escape "$txtvalue")" + + ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" + + _debug "zone_id" "$HOSTUP_ZONE_ID" + _debug "zone_domain" "$HOSTUP_ZONE_DOMAIN" + _debug "record_name" "$record_name" + _debug "ttl" "$ttl" + + request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$record_value\",\"ttl\":$ttl}" + + if ! _hostup_rest "POST" "/dns/zones/$HOSTUP_ZONE_ID/records" "$request_body"; then + return 1 + fi + + if ! _contains "$_hostup_response" '"success":true'; then + _err "HostUp DNS API: failed to create TXT record for $fulldomain" + _debug2 "_hostup_response" "$_hostup_response" + return 1 + fi + + record_id="$(_hostup_extract_record_id "$_hostup_response")" + if [ -n "$record_id" ]; then + _hostup_save_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_id" + _debug "hostup_saved_record_id" "$record_id" + fi + + _info "Added TXT record for $fulldomain" + return 0 +} + +# Public: remove TXT record +# Usage: dns_hostup_rm _acme-challenge.example.com "txt-value" +dns_hostup_rm() { + fulldomain="$1" + txtvalue="$2" + + _info "Using HostUp DNS API" + + if ! _hostup_init; then + return 1 + fi + + if ! _hostup_detect_zone "$fulldomain"; then + _err "Unable to determine HostUp zone for $fulldomain" + return 1 + fi + + record_name_fqdn="$(_hostup_fqdn "$fulldomain")" + record_value="$txtvalue" + + record_id_cached="$(_hostup_get_saved_record_id "$HOSTUP_ZONE_ID" "$fulldomain")" + if [ -n "$record_id_cached" ]; then + _debug "hostup_record_id_cached" "$record_id_cached" + if _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$record_id_cached"; then + _info "Deleted TXT record $record_id_cached" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + HOSTUP_ZONE_ID="" + return 0 + fi + fi + + if ! _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$record_value"; then + _info "TXT record not found for $record_name_fqdn. Skipping removal." + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + return 0 + fi + + _debug "Deleting record" "$HOSTUP_RECORD_ID" + + if ! _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$HOSTUP_RECORD_ID"; then + return 1 + fi + + _info "Deleted TXT record $HOSTUP_RECORD_ID" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + HOSTUP_ZONE_ID="" + return 0 +} + +########################## +# Private helper methods # +########################## + +_hostup_init() { + HOSTUP_API_KEY="${HOSTUP_API_KEY:-$(_readaccountconf_mutable HOSTUP_API_KEY)}" + HOSTUP_API_BASE="${HOSTUP_API_BASE:-$(_readaccountconf_mutable HOSTUP_API_BASE)}" + HOSTUP_TTL="${HOSTUP_TTL:-$(_readaccountconf_mutable HOSTUP_TTL)}" + HOSTUP_ZONE_ID="${HOSTUP_ZONE_ID:-$(_readaccountconf_mutable HOSTUP_ZONE_ID)}" + + if [ -z "$HOSTUP_API_BASE" ]; then + HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT" + fi + + if [ -z "$HOSTUP_API_KEY" ]; then + HOSTUP_API_KEY="" + _err "HOSTUP_API_KEY is not set." + _err "Please export your HostUp API key with read:dns and write:dns scopes." + return 1 + fi + + _saveaccountconf_mutable HOSTUP_API_KEY "$HOSTUP_API_KEY" + _saveaccountconf_mutable HOSTUP_API_BASE "$HOSTUP_API_BASE" + + if [ -n "$HOSTUP_TTL" ]; then + _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL" + fi + + if [ -n "$HOSTUP_ZONE_ID" ]; then + _saveaccountconf_mutable HOSTUP_ZONE_ID "$HOSTUP_ZONE_ID" + fi + + return 0 +} + +_hostup_detect_zone() { + fulldomain="$1" + + if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then + return 0 + fi + + HOSTUP_ZONE_DOMAIN="" + _debug "hostup_full_domain" "$fulldomain" + + if [ -n "$HOSTUP_ZONE_ID" ] && [ -z "$HOSTUP_ZONE_DOMAIN" ]; then + # Attempt to fetch domain name for provided zone ID + if _hostup_fetch_zone_details "$HOSTUP_ZONE_ID"; then + return 0 + fi + HOSTUP_ZONE_ID="" + fi + + if ! _hostup_load_zones; then + return 1 + fi + + _domain_candidate="$(printf "%s" "$fulldomain" | tr 'A-Z' 'a-z')" + _debug "hostup_initial_candidate" "$_domain_candidate" + + while [ -n "$_domain_candidate" ]; do + _debug "hostup_zone_candidate" "$_domain_candidate" + if _hostup_lookup_zone "$_domain_candidate"; then + HOSTUP_ZONE_DOMAIN="$_lookup_zone_domain" + HOSTUP_ZONE_ID="$_lookup_zone_id" + return 0 + fi + + if ! printf "%s" "$_domain_candidate" | _contains "."; then + break + fi + + _domain_candidate="${_domain_candidate#*.}" + done + + HOSTUP_ZONE_ID="" + return 1 +} + +_hostup_record_name() { + fulldomain="$1" + zonedomain="$2" + + # Remove trailing dot, if any + fulldomain="${fulldomain%.}" + zonedomain="${zonedomain%.}" + + if [ "$fulldomain" = "$zonedomain" ]; then + printf "%s" "@" + return 0 + fi + + suffix=".$zonedomain" + case "$fulldomain" in + *"$suffix") + printf "%s" "${fulldomain%$suffix}" + ;; + *) + # Domain not within zone, fall back to full host + printf "%s" "$fulldomain" + ;; + esac +} + +_hostup_sanitize_name() { + name="$1" + + if [ -z "$name" ] || [ "$name" = "." ]; then + printf "%s" "@" + return 0 + fi + + # Remove any trailing dot + name="${name%.}" + printf "%s" "$name" +} + +_hostup_fqdn() { + domain="$1" + printf "%s" "${domain%.}" +} + +_hostup_fetch_zone_details() { + zone_id="$1" + + if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then + return 1 + fi + + zonedomain="$(printf "%s" "$_hostup_response" | _egrep_o '"domain":"[^"]*"' | sed -n '1p' | cut -d ':' -f 2 | tr -d '"')" + if [ -n "$zonedomain" ]; then + HOSTUP_ZONE_DOMAIN="$zonedomain" + return 0 + fi + + return 1 +} + +_hostup_load_zones() { + if ! _hostup_rest "GET" "/dns/zones" ""; then + return 1 + fi + + HOSTUP_ZONES_CACHE="" + data="$(printf "%s" "$_hostup_response" | tr '{' '\n')" + + while IFS= read -r line; do + case "$line" in + *'"domain_id"'*'"domain"'*) + zone_id="$(printf "%s" "$line" | _hostup_json_extract "domain_id")" + zone_domain="$(printf "%s" "$line" | _hostup_json_extract "domain")" + if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then + HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id} +" + _debug "hostup_zone_loaded" "$zone_domain|$zone_id" + fi + ;; + esac + done < Date: Mon, 1 Dec 2025 15:48:48 +0100 Subject: [PATCH 096/137] Update dns_hostup.sh bug fix Omnios fial --- dnsapi/dns_hostup.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 4da0dd9d..8d0600f7 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -182,9 +182,10 @@ _hostup_detect_zone() { return 0 fi - if ! printf "%s" "$_domain_candidate" | _contains "."; then - break - fi + case "$_domain_candidate" in + *.*) ;; + *) break ;; + esac _domain_candidate="${_domain_candidate#*.}" done From d97b4477b2dcf2753ea0afbd24eea8487625aed2 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:55:17 +0100 Subject: [PATCH 097/137] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 8d0600f7..3547f006 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(printf "%s" "$fulldomain" | tr 'A-Z' 'a-z')" + _domain_candidate="$(printf "%s" "$fulldomain" | tr '[:upper:]' '[:lower:]')" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -181,10 +181,10 @@ _hostup_detect_zone() { HOSTUP_ZONE_ID="$_lookup_zone_id" return 0 fi - + case "$_domain_candidate" in - *.*) ;; - *) break ;; + *.*) ;; + *) break ;; esac _domain_candidate="${_domain_candidate#*.}" @@ -210,7 +210,7 @@ _hostup_record_name() { suffix=".$zonedomain" case "$fulldomain" in *"$suffix") - printf "%s" "${fulldomain%$suffix}" + printf "%s" "${fulldomain%"$suffix"}" ;; *) # Domain not within zone, fall back to full host @@ -371,7 +371,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$domain" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } @@ -449,7 +449,7 @@ _hostup_rest() { _debug2 "_hostup_response" "$_hostup_response" case "$http_status" in - 200|201|204) return 0 ;; + 200 | 201 | 204) return 0 ;; 401) _err "HostUp API returned 401 Unauthorized. Check HOSTUP_API_KEY scopes and IP restrictions." return 1 From 64a6ea68fa704ce4df35b03cff7c29fe531c38b3 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:58:36 +0100 Subject: [PATCH 098/137] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 3547f006..ca49096f 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -181,7 +181,7 @@ _hostup_detect_zone() { HOSTUP_ZONE_ID="$_lookup_zone_id" return 0 fi - + case "$_domain_candidate" in *.*) ;; *) break ;; From 51b4fa00800ae2aad88a81fe76783d374044318c Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:19:16 +0100 Subject: [PATCH 099/137] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 49 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index ca49096f..347f34d1 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -319,10 +319,14 @@ _hostup_find_record() { records="$(printf "%s" "$_hostup_response" | tr '{' '\n')" while IFS= read -r line; do - case "$line" in + # Normalize line to make TXT value matching reliable + line_clean="$(printf "%s" "$line" | tr -d '\r\n')" + line_value_clean="$(printf "%s" "$line_clean" | sed 's/\\"//g')" + + case "$line_clean" in *'"type":"TXT"'*'"name"'*'"value"'*) - name_value="$(printf "%s" "$line" | _hostup_json_extract "name")" - record_value="$(printf "%s" "$line" | _hostup_json_extract "value")" + name_value="$(_hostup_json_extract "name" "$line_clean")" + record_value="$(_hostup_json_extract "value" "$line_value_clean")" _debug "hostup_record_raw" "$record_value" if [ "${record_value#\"}" != "$record_value" ] && [ "${record_value%\"}" != "$record_value" ]; then @@ -337,7 +341,7 @@ _hostup_find_record() { _debug "hostup_record_value" "$record_value" if [ "$name_value" = "$fqdn" ] && [ "$record_value" = "$txtvalue" ]; then - record_id="$(printf "%s" "$line" | _hostup_json_extract "id")" + record_id="$(_hostup_json_extract "id" "$line_clean")" if [ -n "$record_id" ]; then HOSTUP_RECORD_ID="$record_id" return 0 @@ -354,13 +358,30 @@ EOF _hostup_json_extract() { key="$1" - printf "%s" "$line" | - _egrep_o "\"$key\":\"[^\"]*\"" | - head -n1 | - cut -d : -f2- | - sed 's/^"//' | - sed 's/"$//' | - sed 's/\\"/"/g' + input="${2:-$line}" + + # First try to extract quoted values (strings) + quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | head -n1)" + if [ -n "$quoted_match" ]; then + printf "%s" "$quoted_match" | + cut -d : -f2- | + sed 's/^"//' | + sed 's/"$//' | + sed 's/\\"/"/g' + return 0 + fi + + # Fallback for unquoted values (e.g., numeric IDs) + unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | head -n1)" + if [ -n "$unquoted_match" ]; then + printf "%s" "$unquoted_match" | + cut -d : -f2- | + tr -d '", ' | + tr -d '\r\n' + return 0 + fi + + return 1 } _hostup_json_escape() { @@ -398,6 +419,12 @@ _hostup_clear_record_id() { } _hostup_extract_record_id() { + record_id="$(_hostup_json_extract "id" "$1")" + if [ -n "$record_id" ]; then + printf "%s" "$record_id" + return 0 + fi + printf "%s" "$1" | _egrep_o '"id":[0-9]+' | head -n1 | cut -d: -f2 } From bee01c938a234021d4aa8d6bcb2cb6d421262573 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 5 Dec 2025 21:46:05 +0100 Subject: [PATCH 100/137] add comment --- .github/workflows/wiki-monitor.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index b0332775..a79d70a4 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -22,6 +22,7 @@ jobs: page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH") page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH") page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH") + page_summary=$(jq -r '.pages[0].summary' "$GITHUB_EVENT_PATH") now="$(date '+%Y-%m-%d %H:%M:%S')" cd wiki @@ -35,9 +36,11 @@ jobs: { echo "Wiki edited" echo -n "User: " - echo "[$actor]($sender_url)" + echo "@$actor [$actor]($sender_url)" echo "Time: $now" echo "Page: [$page_name]($page_url) (Action: $page_action)" + echo "Comment: $page_summary" + echo "[Click here to Revert](${page_url}/_history)" echo "" echo "----" echo "### diff:" From 45cb36f6d909dc5ed5b5534088aed088b9c3e55f Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 5 Dec 2025 22:31:45 +0100 Subject: [PATCH 101/137] fix https://github.com/acmesh-official/acme.sh/issues/6246#issuecomment-3610998032 --- dnsapi/dns_ali.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh index 53a82f91..cbee773e 100755 --- a/dnsapi/dns_ali.sh +++ b/dnsapi/dns_ali.sh @@ -97,9 +97,10 @@ _ali_rest() { } _ali_nonce() { - #_head_n 1 /dev/null && return 0 + fi + printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32 } _timestamp() { From 3b2c2b16b2472c986f16a00eb24f27456f57a318 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 Dec 2025 11:23:28 +0100 Subject: [PATCH 102/137] minor --- dnsapi/dns_ali.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh index cbee773e..90196c69 100755 --- a/dnsapi/dns_ali.sh +++ b/dnsapi/dns_ali.sh @@ -103,7 +103,7 @@ _ali_nonce() { printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32 } -_timestamp() { +_ali_timestamp() { date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ" } @@ -151,7 +151,7 @@ _check_exist_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&TypeKeyWord=TXT' query=$query'&Version=2015-01-09' } @@ -167,7 +167,7 @@ _add_record_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Type=TXT' query=$query'&Value='$3 query=$query'&Version=2015-01-09' @@ -183,7 +183,7 @@ _delete_record_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2015-01-09' } @@ -197,7 +197,7 @@ _describe_records_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2015-01-09' } From 4965c704d7ea39675bc4131b04264611dd6f1aaa Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 7 Dec 2025 15:07:50 +0100 Subject: [PATCH 103/137] Initial commit for mgw-media.de --- dnsapi/dns_mgwm.sh | 112 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 dnsapi/dns_mgwm.sh diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh new file mode 100644 index 00000000..7715b645 --- /dev/null +++ b/dnsapi/dns_mgwm.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 + +# DNS provider information for acme.sh +dns_mgwm_info='mgw-media.de +Site: mgw-media.de +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mgwm +Options: + MGWM_CUSTOMER Your customer number + MGWM_API_HASH Your API Hash +Issues: github.com/acmesh-official/acme.sh +' + +# Base URL for the mgw-media.de API +MGWM_API_BASE="https://api.mgw-media.de/record" + +######## Public functions ##################### +# This function is called by acme.sh to add a TXT record. +dns_mgwm_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using mgw-media.de DNS API for domain $fulldomain" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + # Call private function to load and save environment variables and set up the Basic Auth Header. + if ! _mgwm_init_env; then + return 1 + fi + + # Construct the API URL for adding a record. + _add_url="${MGWM_API_BASE}/add/${fulldomain}/txt/${txtvalue}" + _debug "Calling MGWM ADD URL: ${_add_url}" + + # Execute the HTTP GET request with the Authorization Header. + # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. + response="$(_get "" "$_add_url" "" "GET" "$_H1")" + _debug "MGWM add response: $response" + + # Check the API response for success. The API returns "OK" on success. + if [ "$response" = "OK" ]; then + _info "TXT record for $fulldomain successfully added via MGWM API." + _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. + return 0 + else + _err "mgwm_add: Failed to add TXT record for $fulldomain. Unexpected API Response: '$response'" + return 1 + fi +} + +# This function is called by acme.sh to remove a TXT record after validation. +dns_mgwm_rm() { + fulldomain=$1 + txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed. + + _info "Removing TXT record for $fulldomain using mgw-media.de DNS API" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + # Call private function to load and save environment variables and set up the Basic Auth Header. + if ! _mgwm_init_env; then + return 1 + fi + + # Construct the API URL for removing a record. + # To delete a specific record by its value (as required by ACME v2 for multiple TXT records), + # the txtvalue must be part of the URL, similar to the add action. + _rm_url="${MGWM_API_BASE}/rm/${fulldomain}/txt/${txtvalue}" + _debug "Calling MGWM RM URL: ${_rm_url}" + + # Execute the HTTP GET request with the Authorization Header. + response="$(_get "" "$_rm_url" "" "GET" "$_H1")" + _debug "MGWM rm response: $response" + + # Check the API response for success. The API returns "OK" on success. + if [ "$response" = "OK" ]; then + _info "TXT record for $fulldomain successfully removed via MGWM API." + return 0 + else + _err "mgwm_rm: Failed to remove TXT record for $fulldomain. Unexpected API Response: '$response'" + return 1 + fi +} + +#################### Private functions below ################################## + +# _mgwm_init_env() loads the mgw-media.de API credentials (customer number and hash) +# from environment variables or acme.sh's configuration, saves them, and +# prepares the global _H1 variable for Basic Authorization header. +_mgwm_init_env() { + # Load credentials from environment or acme.sh config + MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" + MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" + + # Check if credentials are set + if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then + _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." + _err "Please check these environment variables and try again." + return 1 + fi + + # Save credentials for automatic renewal and future calls + _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" + _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" + + # Create the Basic Auth Header. acme.sh's _base64 function is used for encoding. + _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" + export _H1="Authorization: Basic $_credentials" + _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials + return 0 +} From daf7f7c268cc33170e40321ee5b02d96fc2b9960 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:29:24 +0100 Subject: [PATCH 104/137] Refactor dns_mgwm.sh for better API integration Refactor DNS API script to improve credential handling and update API endpoint. --- dnsapi/dns_mgwm.sh | 99 +++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index 7715b645..b3e21726 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -2,17 +2,18 @@ # shellcheck disable=SC2034 # DNS provider information for acme.sh -dns_mgwm_info='mgw-media.de +dns_mgwm_info='MGW-MEDIA.DE Site: mgw-media.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mgwm Options: - MGWM_CUSTOMER Your customer number - MGWM_API_HASH Your API Hash + MGWM_CUSTOMER Your customer number (username for Basic Auth). + MGWM_API_HASH Your API Hash (password for Basic Auth). Issues: github.com/acmesh-official/acme.sh +Author: (Your Name or generated by AI) ' -# Base URL for the mgw-media.de API -MGWM_API_BASE="https://api.mgw-media.de/record" +# Base endpoint for the MGW-MEDIA.DE API (parameters will be added as query strings) +MGWM_API_ENDPOINT="https://api.mgw-media.de/record" ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. @@ -24,16 +25,32 @@ dns_mgwm_add() { _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" - # Call private function to load and save environment variables and set up the Basic Auth Header. - if ! _mgwm_init_env; then + # Load credentials from environment or acme.sh config + MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" + MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" + + # Check if credentials are set + if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then + _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." + _err "Please check these environment variables and try again." return 1 fi - # Construct the API URL for adding a record. - _add_url="${MGWM_API_BASE}/add/${fulldomain}/txt/${txtvalue}" + # Save credentials for automatic renewal and future calls + _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" + _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" + + # Create the Basic Auth Header directly in this function's scope + _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" + # Export _H1 so _get function can pick it up + export _H1="Authorization: Basic $_credentials" + _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials + + # Construct the API URL for adding a record with query parameters + _add_url="${MGWM_API_ENDPOINT}?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" _debug "Calling MGWM ADD URL: ${_add_url}" - # Execute the HTTP GET request with the Authorization Header. + # Execute the HTTP GET request with the Authorization Header (_H1) # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. response="$(_get "" "$_add_url" "" "GET" "$_H1")" _debug "MGWM add response: $response" @@ -52,24 +69,39 @@ dns_mgwm_add() { # This function is called by acme.sh to remove a TXT record after validation. dns_mgwm_rm() { fulldomain=$1 - txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed. + txtvalue=$2 # This value is not used by the RM API in this case. _info "Removing TXT record for $fulldomain using mgw-media.de DNS API" _debug "fulldomain: $fulldomain" - _debug "txtvalue: $txtvalue" + _debug "txtvalue: $txtvalue" # Still logging for completeness, but not used in URL - # Call private function to load and save environment variables and set up the Basic Auth Header. - if ! _mgwm_init_env; then + # Load credentials from environment or acme.sh config + MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" + MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" + + # Check if credentials are set + if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then + _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." + _err "Please check these environment variables and try again." return 1 fi - # Construct the API URL for removing a record. - # To delete a specific record by its value (as required by ACME v2 for multiple TXT records), - # the txtvalue must be part of the URL, similar to the add action. - _rm_url="${MGWM_API_BASE}/rm/${fulldomain}/txt/${txtvalue}" + # Save credentials (important for future renewals if not saved by add function) + _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" + _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" + + # Create the Basic Auth Header directly in this function's scope + _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" + # Export _H1 so _get function can pick it up + export _H1="Authorization: Basic $_credentials" + _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials + + # Construct the API URL for removing a record with query parameters + # The RM API from mgw-media.de does not expect a 'content' parameter. + _rm_url="${MGWM_API_ENDPOINT}?action=rm&fulldomain=${fulldomain}&type=txt" _debug "Calling MGWM RM URL: ${_rm_url}" - # Execute the HTTP GET request with the Authorization Header. + # Execute the HTTP GET request with the Authorization Header (_H1) response="$(_get "" "$_rm_url" "" "GET" "$_H1")" _debug "MGWM rm response: $response" @@ -84,29 +116,6 @@ dns_mgwm_rm() { } #################### Private functions below ################################## - -# _mgwm_init_env() loads the mgw-media.de API credentials (customer number and hash) -# from environment variables or acme.sh's configuration, saves them, and -# prepares the global _H1 variable for Basic Authorization header. -_mgwm_init_env() { - # Load credentials from environment or acme.sh config - MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" - MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" - - # Check if credentials are set - if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then - _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." - _err "Please check these environment variables and try again." - return 1 - fi - - # Save credentials for automatic renewal and future calls - _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" - _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" - - # Create the Basic Auth Header. acme.sh's _base64 function is used for encoding. - _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" - export _H1="Authorization: Basic $_credentials" - _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - return 0 -} +# The _mgwm_init_env function has been inlined into dns_mgwm_add and dns_mgwm_rm +# to ensure credentials and the Authorization header are set correctly within +# each function's sub-shell context. From 11eaad1fa742d4d55e7a3bc17675b581e2281b59 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:32:30 +0100 Subject: [PATCH 105/137] Update API URLs to include .php extension --- dnsapi/dns_mgwm.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index b3e21726..42c6d93a 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -47,7 +47,7 @@ dns_mgwm_add() { _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials # Construct the API URL for adding a record with query parameters - _add_url="${MGWM_API_ENDPOINT}?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" + _add_url="${MGWM_API_ENDPOINT}.php?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" _debug "Calling MGWM ADD URL: ${_add_url}" # Execute the HTTP GET request with the Authorization Header (_H1) @@ -98,7 +98,7 @@ dns_mgwm_rm() { # Construct the API URL for removing a record with query parameters # The RM API from mgw-media.de does not expect a 'content' parameter. - _rm_url="${MGWM_API_ENDPOINT}?action=rm&fulldomain=${fulldomain}&type=txt" + _rm_url="${MGWM_API_ENDPOINT}.php?action=rm&fulldomain=${fulldomain}&type=txt" _debug "Calling MGWM RM URL: ${_rm_url}" # Execute the HTTP GET request with the Authorization Header (_H1) From e94c6be4a1c877a06afcc0c1b4530b864b777bd5 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:41:27 +0100 Subject: [PATCH 106/137] Update MGWM API endpoint to IPv4 --- dnsapi/dns_mgwm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index 42c6d93a..a3aabb53 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -13,7 +13,7 @@ Author: (Your Name or generated by AI) ' # Base endpoint for the MGW-MEDIA.DE API (parameters will be added as query strings) -MGWM_API_ENDPOINT="https://api.mgw-media.de/record" +MGWM_API_ENDPOINT="https://ipv4.api.mgw-media.de/record" ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. From 2ba615555cf214edc447e4435bb20ee30b4340a9 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:59:04 +0100 Subject: [PATCH 107/137] Refactor dns_mgwm.sh for improved API interaction Refactor MGWM API script to improve clarity and functionality. Update API endpoint and streamline credential handling. --- dnsapi/dns_mgwm.sh | 108 +++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index a3aabb53..c02b7a6d 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -9,11 +9,12 @@ Options: MGWM_CUSTOMER Your customer number (username for Basic Auth). MGWM_API_HASH Your API Hash (password for Basic Auth). Issues: github.com/acmesh-official/acme.sh -Author: (Your Name or generated by AI) +Author: Generated by AI (with user input) ' -# Base endpoint for the MGW-MEDIA.DE API (parameters will be added as query strings) -MGWM_API_ENDPOINT="https://ipv4.api.mgw-media.de/record" +# Direct endpoint for the PHP script with query parameters +# This variable replaces MGWM_API_BASE when using query-parameter-based URLs directly. +MGWM_API_ENDPOINT="https://api.mgw-media.de/record.php" ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. @@ -25,34 +26,20 @@ dns_mgwm_add() { _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" - # Load credentials from environment or acme.sh config - MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" - MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" - - # Check if credentials are set - if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then - _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." - _err "Please check these environment variables and try again." + # Call private function to load and save environment variables and set up the Basic Auth Header. + if ! _mgwm_init_env; then return 1 fi - # Save credentials for automatic renewal and future calls - _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" - _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" - - # Create the Basic Auth Header directly in this function's scope - _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" - # Export _H1 so _get function can pick it up - export _H1="Authorization: Basic $_credentials" - _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - - # Construct the API URL for adding a record with query parameters - _add_url="${MGWM_API_ENDPOINT}.php?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" + # Construct the API URL using query parameters. + # This targets the record.php script directly, passing action, fulldomain, type, and content. + _add_url="${MGWM_API_ENDPOINT}?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" _debug "Calling MGWM ADD URL: ${_add_url}" - # Execute the HTTP GET request with the Authorization Header (_H1) - # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. - response="$(_get "" "$_add_url" "" "GET" "$_H1")" + # Execute the HTTP GET request. + # Correct parameters for _get(): url="$1", onlyheader="$2", t="$3" + # The Authorization Header (_H1) is automatically picked up by _get() from the environment. + response="$(_get "$_add_url" "" "")" # <-- KORRIGIERTER AUFRUF VON _get() _debug "MGWM add response: $response" # Check the API response for success. The API returns "OK" on success. @@ -69,12 +56,44 @@ dns_mgwm_add() { # This function is called by acme.sh to remove a TXT record after validation. dns_mgwm_rm() { fulldomain=$1 - txtvalue=$2 # This value is not used by the RM API in this case. + txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed. _info "Removing TXT record for $fulldomain using mgw-media.de DNS API" _debug "fulldomain: $fulldomain" - _debug "txtvalue: $txtvalue" # Still logging for completeness, but not used in URL + _debug "txtvalue: $txtvalue" + + # Call private function to load and save environment variables and set up the Basic Auth Header. + if ! _mgwm_init_env; then + return 1 + fi + # Construct the API URL for removing a record. + # This targets the record.php script directly, passing action, fulldomain, type, and content. + _rm_url="${MGWM_API_ENDPOINT}?action=rm&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" + _debug "Calling MGWM RM URL: ${_rm_url}" + + # Execute the HTTP GET request. + # Correct parameters for _get(): url="$1", onlyheader="$2", t="$3" + # The Authorization Header (_H1) is automatically picked up by _get() from the environment. + response="$(_get "$_rm_url" "" "")" # <-- KORRIGIERTER AUFRUF VON _get() + _debug "MGWM rm response: $response" + + # Check the API response for success. The API returns "OK" on success. + if [ "$response" = "OK" ]; then + _info "TXT record for $fulldomain successfully removed via MGWM API." + return 0 + else + _err "mgwm_rm: Failed to remove TXT record for $fulldomain. Unexpected API Response: '$response'" + return 1 + fi +} + +#################### Private functions below ################################## + +# _mgwm_init_env() loads the mgw-media.de API credentials (customer number and hash) +# from environment variables or acme.sh's configuration, saves them, and +# prepares the global _H1 variable for Basic Authorization header. +_mgwm_init_env() { # Load credentials from environment or acme.sh config MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" @@ -86,36 +105,19 @@ dns_mgwm_rm() { return 1 fi - # Save credentials (important for future renewals if not saved by add function) + # Save credentials for automatic renewal and future calls _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" - # Create the Basic Auth Header directly in this function's scope + # Create the Basic Auth Header. acme.sh's _base64 function is used for encoding. _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" - # Export _H1 so _get function can pick it up export _H1="Authorization: Basic $_credentials" _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - - # Construct the API URL for removing a record with query parameters - # The RM API from mgw-media.de does not expect a 'content' parameter. - _rm_url="${MGWM_API_ENDPOINT}.php?action=rm&fulldomain=${fulldomain}&type=txt" - _debug "Calling MGWM RM URL: ${_rm_url}" - - # Execute the HTTP GET request with the Authorization Header (_H1) - response="$(_get "" "$_rm_url" "" "GET" "$_H1")" - _debug "MGWM rm response: $response" - - # Check the API response for success. The API returns "OK" on success. - if [ "$response" = "OK" ]; then - _info "TXT record for $fulldomain successfully removed via MGWM API." - return 0 - else - _err "mgwm_rm: Failed to remove TXT record for $fulldomain. Unexpected API Response: '$response'" - return 1 - fi + return 0 } -#################### Private functions below ################################## -# The _mgwm_init_env function has been inlined into dns_mgwm_add and dns_mgwm_rm -# to ensure credentials and the Authorization header are set correctly within -# each function's sub-shell context. +# The _get_root function, often found in other acme.sh DNS API scripts, +# is not necessary for the MGW-MEDIA.DE API. +# The MGW-MEDIA.DE API directly accepts the complete FQDN (fulldomain) +# in its URL path and handles the extraction of the subdomain and root domain internally. +# Therefore, no custom _get_root implementation is needed here. From 546c2d47d5b6a740eb7cbf874e81529106a1e66d Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:11:42 +0100 Subject: [PATCH 108/137] Refactor DNS API for mgw-media.de Updated DNS API script for mgw-media.de to use new base URL and improved API request structure. --- dnsapi/dns_mgwm.sh | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index c02b7a6d..f38c184e 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -2,19 +2,17 @@ # shellcheck disable=SC2034 # DNS provider information for acme.sh -dns_mgwm_info='MGW-MEDIA.DE +dns_mgwm_info='mgw-media.de Site: mgw-media.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mgwm Options: - MGWM_CUSTOMER Your customer number (username for Basic Auth). - MGWM_API_HASH Your API Hash (password for Basic Auth). + MGWM_CUSTOMER Your customer number + MGWM_API_HASH Your API Hash Issues: github.com/acmesh-official/acme.sh -Author: Generated by AI (with user input) ' -# Direct endpoint for the PHP script with query parameters -# This variable replaces MGWM_API_BASE when using query-parameter-based URLs directly. -MGWM_API_ENDPOINT="https://api.mgw-media.de/record.php" +# Base URL for the mgw-media.de API +MGWM_API_BASE="https://api.mgw-media.de/record" ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. @@ -31,15 +29,14 @@ dns_mgwm_add() { return 1 fi - # Construct the API URL using query parameters. - # This targets the record.php script directly, passing action, fulldomain, type, and content. - _add_url="${MGWM_API_ENDPOINT}?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" + # Construct the API URL for adding a record. + #_add_url="${MGWM_API_BASE}/add/${fulldomain}/txt/${txtvalue}" + _add_url="${MGWM_API_BASE}.php?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" _debug "Calling MGWM ADD URL: ${_add_url}" - # Execute the HTTP GET request. - # Correct parameters for _get(): url="$1", onlyheader="$2", t="$3" - # The Authorization Header (_H1) is automatically picked up by _get() from the environment. - response="$(_get "$_add_url" "" "")" # <-- KORRIGIERTER AUFRUF VON _get() + # Execute the HTTP GET request with the Authorization Header. + # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. + response="$(_get "$_add_url")" _debug "MGWM add response: $response" # Check the API response for success. The API returns "OK" on success. @@ -68,14 +65,14 @@ dns_mgwm_rm() { fi # Construct the API URL for removing a record. - # This targets the record.php script directly, passing action, fulldomain, type, and content. - _rm_url="${MGWM_API_ENDPOINT}?action=rm&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" + # To delete a specific record by its value (as required by ACME v2 for multiple TXT records), + # the txtvalue must be part of the URL, similar to the add action. + #_rm_url="${MGWM_API_BASE}/rm/${fulldomain}/txt/${txtvalue}" + _rm_url="${MGWM_API_BASE}.php?action=rm&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" _debug "Calling MGWM RM URL: ${_rm_url}" - # Execute the HTTP GET request. - # Correct parameters for _get(): url="$1", onlyheader="$2", t="$3" - # The Authorization Header (_H1) is automatically picked up by _get() from the environment. - response="$(_get "$_rm_url" "" "")" # <-- KORRIGIERTER AUFRUF VON _get() + # Execute the HTTP GET request with the Authorization Header. + response="$(_get "$_rm_url")" _debug "MGWM rm response: $response" # Check the API response for success. The API returns "OK" on success. @@ -115,9 +112,3 @@ _mgwm_init_env() { _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials return 0 } - -# The _get_root function, often found in other acme.sh DNS API scripts, -# is not necessary for the MGW-MEDIA.DE API. -# The MGW-MEDIA.DE API directly accepts the complete FQDN (fulldomain) -# in its URL path and handles the extraction of the subdomain and root domain internally. -# Therefore, no custom _get_root implementation is needed here. From d8722c46d9b8ed938122ef54ec89e440878263ac Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:26:29 +0100 Subject: [PATCH 109/137] Consolidate API request logic in dns_mgwm.sh Refactor DNS API functions to use a unified request handler. --- dnsapi/dns_mgwm.sh | 100 ++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index f38c184e..ca48584c 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -1,6 +1,5 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 - # DNS provider information for acme.sh dns_mgwm_info='mgw-media.de Site: mgw-media.de @@ -10,87 +9,66 @@ Options: MGWM_API_HASH Your API Hash Issues: github.com/acmesh-official/acme.sh ' - # Base URL for the mgw-media.de API MGWM_API_BASE="https://api.mgw-media.de/record" - ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. dns_mgwm_add() { fulldomain=$1 txtvalue=$2 - - _info "Using mgw-media.de DNS API for domain $fulldomain" + _info "Using mgw-media.de DNS API for domain $fulldomain (add record)" _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" - # Call private function to load and save environment variables and set up the Basic Auth Header. - if ! _mgwm_init_env; then - return 1 - fi - - # Construct the API URL for adding a record. - #_add_url="${MGWM_API_BASE}/add/${fulldomain}/txt/${txtvalue}" - _add_url="${MGWM_API_BASE}.php?action=add&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" - _debug "Calling MGWM ADD URL: ${_add_url}" - - # Execute the HTTP GET request with the Authorization Header. - # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. - response="$(_get "$_add_url")" - _debug "MGWM add response: $response" - - # Check the API response for success. The API returns "OK" on success. - if [ "$response" = "OK" ]; then + # Call the new private function to handle the API request. + # The 'add' action, fulldomain, type 'txt' and txtvalue are passed. + if _mgwm_perform_api_request "add" "$fulldomain" "txt" "$txtvalue"; then _info "TXT record for $fulldomain successfully added via MGWM API." _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. return 0 else - _err "mgwm_add: Failed to add TXT record for $fulldomain. Unexpected API Response: '$response'" + # Error message already logged by _mgwm_perform_api_request, but a specific one here helps. + _err "mgwm_add: Failed to add TXT record for $fulldomain." return 1 fi } - # This function is called by acme.sh to remove a TXT record after validation. dns_mgwm_rm() { fulldomain=$1 txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed. - - _info "Removing TXT record for $fulldomain using mgw-media.de DNS API" + _info "Removing TXT record for $fulldomain using mgw-media.de DNS API (remove record)" _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" - # Call private function to load and save environment variables and set up the Basic Auth Header. - if ! _mgwm_init_env; then - return 1 - fi - - # Construct the API URL for removing a record. - # To delete a specific record by its value (as required by ACME v2 for multiple TXT records), - # the txtvalue must be part of the URL, similar to the add action. - #_rm_url="${MGWM_API_BASE}/rm/${fulldomain}/txt/${txtvalue}" - _rm_url="${MGWM_API_BASE}.php?action=rm&fulldomain=${fulldomain}&type=txt&content=${txtvalue}" - _debug "Calling MGWM RM URL: ${_rm_url}" - - # Execute the HTTP GET request with the Authorization Header. - response="$(_get "$_rm_url")" - _debug "MGWM rm response: $response" - - # Check the API response for success. The API returns "OK" on success. - if [ "$response" = "OK" ]; then + # Call the new private function to handle the API request. + # The 'rm' action, fulldomain, type 'txt' and txtvalue are passed. + if _mgwm_perform_api_request "rm" "$fulldomain" "txt" "$txtvalue"; then _info "TXT record for $fulldomain successfully removed via MGWM API." return 0 else - _err "mgwm_rm: Failed to remove TXT record for $fulldomain. Unexpected API Response: '$response'" + # Error message already logged by _mgwm_perform_api_request, but a specific one here helps. + _err "mgwm_rm: Failed to remove TXT record for $fulldomain." return 1 fi } - #################### Private functions below ################################## -# _mgwm_init_env() loads the mgw-media.de API credentials (customer number and hash) -# from environment variables or acme.sh's configuration, saves them, and -# prepares the global _H1 variable for Basic Authorization header. -_mgwm_init_env() { +# _mgwm_perform_api_request() encapsulates the API call logic, including +# loading credentials, setting the Authorization header, and executing the request. +# Arguments: +# $1: action (e.g., "add", "rm") +# $2: fulldomain +# $3: type (e.g., "txt") +# $4: content (the txtvalue) +_mgwm_perform_api_request() { + _action="$1" + _fulldomain="$2" + _type="$3" + _content="$4" + + _debug "Calling _mgwm_perform_api_request for action: $_action, domain: $_fulldomain, type: $_type, content: $_content" + + # --- Start of _mgwm_init_env logic (now embedded here) --- # Load credentials from environment or acme.sh config MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" @@ -110,5 +88,25 @@ _mgwm_init_env() { _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" export _H1="Authorization: Basic $_credentials" _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - return 0 + # --- End of _mgwm_init_env logic --- + + # Construct the API URL based on the action and provided parameters. + _request_url="${MGWM_API_BASE}.php?action=${_action}&fulldomain=${_fulldomain}&type=${_type}&content=${_content}" + _debug "Constructed MGWM API URL for action '$_action': ${_request_url}" + + # Execute the HTTP GET request with the Authorization Header. + # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. + response="$(_get "$_request_url")" + _debug "MGWM API response for action '$_action': $response" + + # Check the API response for success. The API returns "OK" on success. + if [ "$response" = "OK" ]; then + _info "MGWM API action '$_action' for record '$_fulldomain' successful." + return 0 + else + _err "mgwm_perform_api_request: Failed API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" + return 1 + fi } + +# The original _mgwm_init_env function is now removed as its logic is integrated into _mgwm_perform_api_request. From 503ca1e9c277721c095aee8b86a6f35c18498ff1 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:34:01 +0100 Subject: [PATCH 110/137] Change MGWM_API_BASE to use IP address --- dnsapi/dns_mgwm.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index ca48584c..5fa6c216 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -10,7 +10,8 @@ Options: Issues: github.com/acmesh-official/acme.sh ' # Base URL for the mgw-media.de API -MGWM_API_BASE="https://api.mgw-media.de/record" +#MGWM_API_BASE="https://api.mgw-media.de/record" +MGWM_API_BASE="http://217.114.220.70/record" ######## Public functions ##################### # This function is called by acme.sh to add a TXT record. dns_mgwm_add() { @@ -88,6 +89,7 @@ _mgwm_perform_api_request() { _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" export _H1="Authorization: Basic $_credentials" _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials + export _H2="Host: api.mgw-media.de" # --- End of _mgwm_init_env logic --- # Construct the API URL based on the action and provided parameters. From 95da407de86f37d021aa07d44010072c8da01327 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:02:51 +0100 Subject: [PATCH 111/137] Refactor DNS API script to use new request function --- dnsapi/dns_mgwm.sh | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index 5fa6c216..bb0cb650 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -10,9 +10,10 @@ Options: Issues: github.com/acmesh-official/acme.sh ' # Base URL for the mgw-media.de API -#MGWM_API_BASE="https://api.mgw-media.de/record" -MGWM_API_BASE="http://217.114.220.70/record" +MGWM_API_BASE="https://api.mgw-media.de/record" + ######## Public functions ##################### + # This function is called by acme.sh to add a TXT record. dns_mgwm_add() { fulldomain=$1 @@ -23,12 +24,12 @@ dns_mgwm_add() { # Call the new private function to handle the API request. # The 'add' action, fulldomain, type 'txt' and txtvalue are passed. - if _mgwm_perform_api_request "add" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully added via MGWM API." + if _mgwm_request "add" "$fulldomain" "txt" "$txtvalue"; then + _info "TXT record for $fulldomain successfully added via mgw-media.de API." _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. return 0 else - # Error message already logged by _mgwm_perform_api_request, but a specific one here helps. + # Error message already logged by _mgwm_request, but a specific one here helps. _err "mgwm_add: Failed to add TXT record for $fulldomain." return 1 fi @@ -43,33 +44,32 @@ dns_mgwm_rm() { # Call the new private function to handle the API request. # The 'rm' action, fulldomain, type 'txt' and txtvalue are passed. - if _mgwm_perform_api_request "rm" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully removed via MGWM API." + if _mgwm_request "rm" "$fulldomain" "txt" "$txtvalue"; then + _info "TXT record for $fulldomain successfully removed via mgw-media.de API." return 0 else - # Error message already logged by _mgwm_perform_api_request, but a specific one here helps. + # Error message already logged by _mgwm_request, but a specific one here helps. _err "mgwm_rm: Failed to remove TXT record for $fulldomain." return 1 fi } #################### Private functions below ################################## -# _mgwm_perform_api_request() encapsulates the API call logic, including +# _mgwm_request() encapsulates the API call logic, including # loading credentials, setting the Authorization header, and executing the request. # Arguments: # $1: action (e.g., "add", "rm") # $2: fulldomain # $3: type (e.g., "txt") # $4: content (the txtvalue) -_mgwm_perform_api_request() { +_mgwm_request() { _action="$1" _fulldomain="$2" _type="$3" _content="$4" - _debug "Calling _mgwm_perform_api_request for action: $_action, domain: $_fulldomain, type: $_type, content: $_content" + _debug "Calling _mgwm_request for action: $_action, domain: $_fulldomain, type: $_type, content: $_content" - # --- Start of _mgwm_init_env logic (now embedded here) --- # Load credentials from environment or acme.sh config MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" @@ -89,26 +89,22 @@ _mgwm_perform_api_request() { _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" export _H1="Authorization: Basic $_credentials" _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - export _H2="Host: api.mgw-media.de" - # --- End of _mgwm_init_env logic --- # Construct the API URL based on the action and provided parameters. - _request_url="${MGWM_API_BASE}.php?action=${_action}&fulldomain=${_fulldomain}&type=${_type}&content=${_content}" - _debug "Constructed MGWM API URL for action '$_action': ${_request_url}" + _request_url="${MGWM_API_BASE}/${_action}/${_fulldomain}/${_type}/${_content}" + _debug "Constructed mgw-media.de API URL for action '$_action': ${_request_url}" # Execute the HTTP GET request with the Authorization Header. # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. response="$(_get "$_request_url")" - _debug "MGWM API response for action '$_action': $response" + _debug "mgw-media.de API response for action '$_action': $response" # Check the API response for success. The API returns "OK" on success. if [ "$response" = "OK" ]; then - _info "MGWM API action '$_action' for record '$_fulldomain' successful." + _info "mgw-media.de API action '$_action' for record '$_fulldomain' successful." return 0 else - _err "mgwm_perform_api_request: Failed API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" + _err "Failed mgw-media.de API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" return 1 fi } - -# The original _mgwm_init_env function is now removed as its logic is integrated into _mgwm_perform_api_request. From 0d2955b48d51846bc86ed4259d679569e80dccf5 Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:11:40 +0100 Subject: [PATCH 112/137] Update documentation links in dns_mgwm.sh --- dnsapi/dns_mgwm.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index bb0cb650..28f1342d 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -3,11 +3,11 @@ # DNS provider information for acme.sh dns_mgwm_info='mgw-media.de Site: mgw-media.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mgwm +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mgwm Options: MGWM_CUSTOMER Your customer number MGWM_API_HASH Your API Hash -Issues: github.com/acmesh-official/acme.sh +Issues: github.com/acmesh-official/acme.sh/issues/6669 ' # Base URL for the mgw-media.de API MGWM_API_BASE="https://api.mgw-media.de/record" From f142f37064c6d0ea837a77c4a08499a38d0cbeaa Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:11:56 +0100 Subject: [PATCH 113/137] Remove DNS provider information comment Removed comment about DNS provider information. --- dnsapi/dns_mgwm.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index 28f1342d..618e90e2 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -1,6 +1,5 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 -# DNS provider information for acme.sh dns_mgwm_info='mgw-media.de Site: mgw-media.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mgwm From 329dab9a67b48b43e31ab3b8ebb7acf511399b0a Mon Sep 17 00:00:00 2001 From: Gilles Filippini Date: Sun, 16 Nov 2025 15:07:15 +0100 Subject: [PATCH 114/137] Use '_mutable' functions for authentication variables Fixes #6081. --- dnsapi/dns_gandi_livedns.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh index 0516fee9..aaef07bf 100644 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -23,6 +23,8 @@ dns_gandi_livedns_add() { fulldomain=$1 txtvalue=$2 + GANDI_LIVEDNS_KEY="${GANDI_LIVEDNS_KEY:-$(_readaccountconf_mutable GANDI_LIVEDNS_KEY)}" + GANDI_LIVEDNS_TOKEN="${GANDI_LIVEDNS_TOKEN:-$(_readaccountconf_mutable GANDI_LIVEDNS_TOKEN)}" if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then _err "No Token or API key (deprecated) specified for Gandi LiveDNS." _err "Create your token or key and export it as GANDI_LIVEDNS_KEY or GANDI_LIVEDNS_TOKEN respectively" @@ -31,11 +33,11 @@ dns_gandi_livedns_add() { # Keep only one secret in configuration if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then - _saveaccountconf GANDI_LIVEDNS_TOKEN "$GANDI_LIVEDNS_TOKEN" - _clearaccountconf GANDI_LIVEDNS_KEY + _saveaccountconf_mutable GANDI_LIVEDNS_TOKEN "$GANDI_LIVEDNS_TOKEN" + _clearaccountconf_mutable GANDI_LIVEDNS_KEY elif [ -n "$GANDI_LIVEDNS_KEY" ]; then - _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" - _clearaccountconf GANDI_LIVEDNS_TOKEN + _saveaccountconf_mutable GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" + _clearaccountconf_mutable GANDI_LIVEDNS_TOKEN fi _debug "First detect the root zone" From ad3783170e70653033f88de7ead7c44c990c9f9d Mon Sep 17 00:00:00 2001 From: "Markus G." <29913712+ufozone@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:31:40 +0100 Subject: [PATCH 115/137] Fix formatting issues in dns_mgwm.sh script --- dnsapi/dns_mgwm.sh | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh index 618e90e2..57679127 100644 --- a/dnsapi/dns_mgwm.sh +++ b/dnsapi/dns_mgwm.sh @@ -24,13 +24,13 @@ dns_mgwm_add() { # Call the new private function to handle the API request. # The 'add' action, fulldomain, type 'txt' and txtvalue are passed. if _mgwm_request "add" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully added via mgw-media.de API." - _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. - return 0 + _info "TXT record for $fulldomain successfully added via mgw-media.de API." + _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. + return 0 else - # Error message already logged by _mgwm_request, but a specific one here helps. - _err "mgwm_add: Failed to add TXT record for $fulldomain." - return 1 + # Error message already logged by _mgwm_request, but a specific one here helps. + _err "mgwm_add: Failed to add TXT record for $fulldomain." + return 1 fi } # This function is called by acme.sh to remove a TXT record after validation. @@ -44,12 +44,12 @@ dns_mgwm_rm() { # Call the new private function to handle the API request. # The 'rm' action, fulldomain, type 'txt' and txtvalue are passed. if _mgwm_request "rm" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully removed via mgw-media.de API." - return 0 + _info "TXT record for $fulldomain successfully removed via mgw-media.de API." + return 0 else - # Error message already logged by _mgwm_request, but a specific one here helps. - _err "mgwm_rm: Failed to remove TXT record for $fulldomain." - return 1 + # Error message already logged by _mgwm_request, but a specific one here helps. + _err "mgwm_rm: Failed to remove TXT record for $fulldomain." + return 1 fi } #################### Private functions below ################################## @@ -100,10 +100,10 @@ _mgwm_request() { # Check the API response for success. The API returns "OK" on success. if [ "$response" = "OK" ]; then - _info "mgw-media.de API action '$_action' for record '$_fulldomain' successful." - return 0 + _info "mgw-media.de API action '$_action' for record '$_fulldomain' successful." + return 0 else - _err "Failed mgw-media.de API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" - return 1 + _err "Failed mgw-media.de API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" + return 1 fi } From e8708a748903f7d52ac07b3281755de5345dacd5 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 8 Dec 2025 21:12:49 +0100 Subject: [PATCH 116/137] fix solaris --- .github/workflows/DNS.yml | 4 +++- .github/workflows/Solaris.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index be9d3aae..ccce2ff6 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -441,7 +441,9 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false - prepare: pkgutil -y -i socat + prepare: | + pkgutil -U + pkgutil -y -i socat run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml index 95bcd8d1..0ba3d2eb 100644 --- a/.github/workflows/Solaris.yml +++ b/.github/workflows/Solaris.yml @@ -66,7 +66,9 @@ jobs: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" - prepare: pkgutil -y -i socat curl wget + prepare: | + pkgutil -U + pkgutil -y -i socat curl wget copyback: false run: | cd ../acmetest \ From 1413aa332bb48d6cb60a4ef7114fe0be57f3e303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= <1009277+imiric@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:12:27 +0100 Subject: [PATCH 117/137] fix: update Exoscale DNS script This updates the Exoscale DNS script to work with v2 of their API. --- dnsapi/dns_exoscale.sh | 226 ++++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 94 deletions(-) mode change 100755 => 100644 dnsapi/dns_exoscale.sh diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh old mode 100755 new mode 100644 index 6898ce38..ddd526a4 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -8,9 +8,9 @@ Options: EXOSCALE_SECRET_KEY API Secret key ' -EXOSCALE_API=https://api.exoscale.com/dns/v1 +EXOSCALE_API="https://api-ch-gva-2.exoscale.com/v2" -######## Public functions ##################### +######## Public functions ######## # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record @@ -18,159 +18,197 @@ dns_exoscale_add() { fulldomain=$1 txtvalue=$2 - if ! _checkAuth; then + _debug "Using Exoscale DNS v2 API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if ! _check_auth; then return 1 fi - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + root_domain_id=$(_get_root_domain_id "$fulldomain") + if [ -z "$root_domain_id" ]; then + _err "Unable to determine root domain ID for $fulldomain" return 1 fi + _debug root_domain_id "$root_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" + # Always get the subdomain part first + sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") + _debug sub_domain "$sub_domain" - _info "Adding record" - if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - return 0 - fi + # Build the record name properly + if [ -z "$sub_domain" ]; then + record_name="_acme-challenge" + else + record_name="_acme-challenge.$sub_domain" fi - _err "Add txt record error." - return 1 + payload=$(printf '{"name":"%s","type":"TXT","content":"%s","ttl":120}' "$record_name" "$txtvalue") + _debug payload "$payload" + + response=$(_exoscale_rest POST "/dns-domain/${root_domain_id}/record" "$payload") + if _contains "$response" "\"id\""; then + _info "TXT record added successfully." + return 0 + else + _err "Error adding TXT record: $response" + return 1 + fi } -# Usage: fulldomain txtvalue -# Used to remove the txt record after validation dns_exoscale_rm() { fulldomain=$1 - txtvalue=$2 - if ! _checkAuth; then + _debug "Using Exoscale DNS v2 API for removal" + _debug fulldomain "$fulldomain" + + if ! _check_auth; then return 1 fi - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + root_domain_id=$(_get_root_domain_id "$fulldomain") + if [ -z "$root_domain_id" ]; then + _err "Unable to determine root domain ID for $fulldomain" return 1 fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token" - if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then - _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") + record_name="_acme-challenge" + sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") + if [ -n "$sub_domain" ]; then + record_name="_acme-challenge.$sub_domain" fi - if [ -z "$_record_id" ]; then - _err "Can not get record id to remove." + record_id=$(_find_record_id "$root_domain_id" "$record_name") + if [ -z "$record_id" ]; then + _err "TXT record not found for deletion." return 1 fi - _debug "Deleting record $_record_id" - - if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then - _err "Delete record error." + response=$(_exoscale_rest DELETE "/dns-domain/$root_domain_id/record/$record_id") + if _contains "$response" "\"state\":\"success\""; then + _info "TXT record deleted successfully." + return 0 + else + _err "Error deleting TXT record: $response" return 1 fi - - return 0 } -#################### Private functions below ################################## +######## Private helpers ######## -_checkAuth() { +_check_auth() { EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" - if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then - EXOSCALE_API_KEY="" - EXOSCALE_SECRET_KEY="" - _err "You don't specify Exoscale application key and application secret yet." - _err "Please create you key and try again." + _err "EXOSCALE_API_KEY and EXOSCALE_SECRET_KEY must be set." return 1 fi - _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" - return 0 } -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=sdjkglgdfewsdfg -# _domain_token=sdjkglgdfewsdfg -_get_root() { - - if ! _exoscale_rest GET "domains"; then - return 1 - fi - +_get_root_domain_id() { domain=$1 - i=2 - p=1 + i=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 - - if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") - _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") - if [ "$_domain_token" ] && [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 + candidate=$(printf "%s" "$domain" | cut -d . -f "${i}-100") + [ -z "$candidate" ] && return 1 + _debug "Trying root domain candidate: $candidate" + domains=$(_exoscale_rest GET "/dns-domain") + # Extract from dns-domains array + result=$(echo "$domains" | _egrep_o '"dns-domains":\[.*\]' | _egrep_o '\{"id":"[^"]*","created-at":"[^"]*","unicode-name":"[^"]*"\}' | while read -r item; do + name=$(echo "$item" | _egrep_o '"unicode-name":"[^"]*"' | cut -d'"' -f4) + id=$(echo "$item" | _egrep_o '"id":"[^"]*"' | cut -d'"' -f4) + if [ "$name" = "$candidate" ]; then + echo "$id" + break fi - return 1 + done) + if [ -n "$result" ]; then + echo "$result" + return 0 fi - p=$i i=$(_math "$i" + 1) done - return 1 } -# returns response +_get_sub_domain() { + fulldomain=$1 + root_id=$2 + root_info=$(_exoscale_rest GET "/dns-domain/$root_id") + _debug root_info "$root_info" + root_name=$(echo "$root_info" | _egrep_o "\"unicode-name\":\"[^\"]*\"" | cut -d\" -f4) + sub=${fulldomain%%."$root_name"} + + if [ "$sub" = "_acme-challenge" ]; then + echo "" + else + # Remove _acme-challenge. prefix to get the actual subdomain + echo "${sub#_acme-challenge.}" + fi +} + +_find_record_id() { + root_id=$1 + name=$2 + records=$(_exoscale_rest GET "/dns-domain/$root_id/record") + + # Convert search name to lowercase for case-insensitive matching + name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]') + + echo "$records" | _egrep_o '\{[^}]*"name":"[^"]*"[^}]*\}' | while read -r record; do + record_name=$(echo "$record" | _egrep_o '"name":"[^"]*"' | cut -d'"' -f4) + record_name_lower=$(echo "$record_name" | tr '[:upper:]' '[:lower:]') + if [ "$record_name_lower" = "$name_lower" ]; then + echo "$record" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d'"' -f4 + break + fi + done +} + +_exoscale_sign() { + k=$1 + shift + hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ') + printf %s "$@" | _hmac sha256 "$hex_key" +} + _exoscale_rest() { method=$1 - path="$2" - data="$3" - token="$4" - request_url="$EXOSCALE_API/$path" - _debug "$path" + path=$2 + data=$3 - export _H1="Accept: application/json" + url="${EXOSCALE_API}${path}" + expiration=$(_math "$(date +%s)" + 300) # 5m from now - if [ "$token" ]; then - export _H2="X-DNS-Domain-Token: $token" - else - export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY" - fi + # Build the message with the actual body or empty line + message=$(printf "%s %s\n%s\n\n\n%s" "$method" "/v2$path" "$data" "$expiration") + signature=$(_exoscale_sign "$EXOSCALE_SECRET_KEY" "$message" | _base64) + auth="EXO2-HMAC-SHA256 credential=${EXOSCALE_API_KEY},expires=${expiration},signature=${signature}" + + _debug "API request: $method $url" + _debug "Signed message: [$message]" + _debug "Authorization header: [$auth]" + + export _H1="Accept: application/json" + export _H2="Authorization: ${auth}" if [ "$data" ] || [ "$method" = "DELETE" ]; then export _H3="Content-Type: application/json" _debug data "$data" - response="$(_post "$data" "$request_url" "" "$method")" + response="$(_post "$data" "$url" "" "$method")" else - response="$(_get "$request_url" "" "" "$method")" + response="$(_get "$url" "" "" "$method")" fi - if [ "$?" != "0" ]; then - _err "error $request_url" + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + _err "error $url" return 1 fi _debug2 response "$response" + echo "$response" return 0 } From b4042d5ccb5082643f4e4eb3ac63bf9437205f4a Mon Sep 17 00:00:00 2001 From: as-kholin Date: Tue, 16 Dec 2025 14:51:42 -0500 Subject: [PATCH 118/137] Updated checks for empty parameters to actually trigger, and added a validation check against the omg.lol API to confirm address and apikey are good before proceeding --- dnsapi/dns_omglol.sh | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index df080bcf..ddde8f2d 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -35,7 +35,7 @@ dns_omglol_add() { _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ ! $? ]; then + if [ 1 = $? ]; then return 1 fi @@ -67,7 +67,7 @@ dns_omglol_rm() { _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ ! $? ]; then + if [ 1 = $? ]; then return 1 fi @@ -100,18 +100,48 @@ omg_validate() { fi _endswith "$fulldomain" "omg.lol" - if [ ! $? ]; then + if [ 1 = $? ]; then _err "Domain name requested is not under omg.lol" return 1 fi _endswith "$fulldomain" "$omg_address.omg.lol" - if [ ! $? ]; then + if [ 1 = $? ]; then _err "Domain name is not a subdomain of provided omg.lol address $omg_address" return 1 fi - _debug "Required environment parameters are all present" + omg_testconnect "$omg_apikey" "$omg_address" + if [ 1 = $? ]; then + _err "Authentication to omg.lol for address $omg_address using provided API key failed" + return 1 + fi + + _debug "Required environment parameters are all present and validated" +} + +# Validate that the address and API key are both correct and associated to each other +omg_testconnect() { + omg_apikey=$1 + omg_address=$2 + + _debug2 "Function" "omg_testconnect" + _secure_debug2 "omg.lol API key" "$omg_apikey" + _debug2 "omg.lol Address" "$omg_address" + + export _H1=$(_createAuthHeader "$omg_apikey") + endpoint="https://api.omg.lol/address/$omg_address/info" + _debug2 "Endpoint for validation" "$endpoint" + + response=$(_get "$endpoint" "" 30) + + _jsonResponseCheck "$response" "status_code" 200 + if [ 1 = $? ]; then + _debug2 "Failed to query omg.lol for $omg_address with provided API key" + _secure_debug2 "API Key" "omg_apikey" + _secure_debug3 "Raw response" "$response" + return 1 + fi } # Add (or modify) an entry for a new ACME query From 4a7e5d07209fa3d50ac9ecf3b7df2febef903084 Mon Sep 17 00:00:00 2001 From: as-kholin Date: Tue, 16 Dec 2025 15:00:05 -0500 Subject: [PATCH 119/137] Updating Auth header to satisfy shellcheck --- dnsapi/dns_omglol.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index ddde8f2d..652cfd15 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -129,7 +129,8 @@ omg_testconnect() { _secure_debug2 "omg.lol API key" "$omg_apikey" _debug2 "omg.lol Address" "$omg_address" - export _H1=$(_createAuthHeader "$omg_apikey") + authheader="$(_createAuthHeader "$omg_apikey")" + export _H1="$authheader" endpoint="https://api.omg.lol/address/$omg_address/info" _debug2 "Endpoint for validation" "$endpoint" From 85ff92170b1810fe7005c65f240c2b34f0cfe468 Mon Sep 17 00:00:00 2001 From: as-kholin Date: Wed, 17 Dec 2025 17:15:10 -0500 Subject: [PATCH 120/137] Updated comment to be more clear on variable vs. definition --- dnsapi/dns_omglol.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index 652cfd15..fd38d046 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -4,8 +4,8 @@ dns_omglol_info='omg.lol Site: omg.lol Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol Options: - OMG_ApiKey API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account - OMG_Address Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard + OMG_ApiKey - API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account + OMG_Address - Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard Issues: github.com/acmesh-official/acme.sh/issues/5299 Author: @Kholin ' From e3b1bccb6aa5e1cda2a1d65c5bae66cec54edb30 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Thu, 18 Dec 2025 16:00:55 +0000 Subject: [PATCH 121/137] Fixes to support INWX again Fixes #6688 --- dnsapi/dns_inwx.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 808fc3a9..89ab67ee 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -125,7 +125,7 @@ dns_inwx_rm() { if ! printf "%s" "$response" | grep "count" >/dev/null; then _info "Do not need to delete record" else - _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') + _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') _info "Deleting record" _inwx_delete_record "$_record_id" fi @@ -324,7 +324,7 @@ _inwx_delete_record() { id - %s + %s @@ -362,7 +362,7 @@ _inwx_update_record() { id - %s + %s From 987882ea37f28b581b44614d93b0a9ab2f9823e4 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 17:49:24 +0100 Subject: [PATCH 122/137] fix delete --- dnsapi/dns_inwx.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 89ab67ee..afe2c465 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -110,11 +110,17 @@ dns_inwx_rm() { %s + + content + + %s + + - ' "$_domain" "$_sub_domain") + ' "$_domain" "$_sub_domain" "$txtvalue") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! _contains "$response" "Command completed successfully"; then From 1c65c04b54b56a24c6f45d122b594c126b6a45c2 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 17:51:13 +0100 Subject: [PATCH 123/137] update dns_inwx_info --- dnsapi/dns_inwx.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index afe2c465..d83e08d6 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -6,6 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx Options: INWX_User Username INWX_Password Password + INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional) ' # Dependencies: From 27ebf09c5c3834011e29d5adf0f12d0f43540107 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:02:18 +0100 Subject: [PATCH 124/137] Improve _htmlEscape function robustness by using printf instead of echo small commit to trigger github actions --- dnsapi/dns_inwx.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index d83e08d6..a7ce9769 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -172,10 +172,10 @@ _inwx_check_cookie() { _htmlEscape() { _s="$1" - _s=$(echo "$_s" | sed "s/&/&/g") - _s=$(echo "$_s" | sed "s//\>/g") - _s=$(echo "$_s" | sed 's/"/\"/g') + _s=$(printf '%s' "$_s" | sed "s/&/&/g") + _s=$(printf '%s' "$_s" | sed "s//\>/g") + _s=$(printf '%s' "$_s" | sed 's/"/\"/g') printf -- %s "$_s" } From a5ad15be0212cefc2ffa99bcd4c611d15576d4ee Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:07:46 +0100 Subject: [PATCH 125/137] Add oathtool to Docker job for 2FA support --- .github/workflows/DNS.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index ccce2ff6..8f7ebd56 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -67,6 +67,8 @@ jobs: TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 + - name: Install oathtool + run: sudo apt-get update && sudo apt-get install -y oathtool - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file @@ -116,7 +118,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install tools - run: brew install socat + run: brew install socat oath-toolkit - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -173,7 +175,7 @@ jobs: shell: cmd - name: Install cygwin additional packages run: | - C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,oath-toolkit shell: cmd - name: Set ENV shell: cmd @@ -230,7 +232,7 @@ jobs: - uses: vmactions/freebsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg install -y socat curl + prepare: pkg install -y socat curl oath-toolkit usesh: true copyback: false run: | @@ -281,7 +283,7 @@ jobs: - uses: vmactions/openbsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg_add socat curl libiconv + prepare: pkg_add socat curl libiconv oath-toolkit usesh: true copyback: false run: | @@ -333,7 +335,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - /usr/sbin/pkg_add curl socat + /usr/sbin/pkg_add curl socat oath-toolkit usesh: true copyback: false run: | @@ -385,7 +387,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - pkg install -y curl socat libnghttp2 + pkg install -y curl socat libnghttp2 oath-toolkit usesh: true copyback: false run: | @@ -443,7 +445,7 @@ jobs: copyback: false prepare: | pkgutil -U - pkgutil -y -i socat + pkgutil -y -i socat oathtool run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH @@ -494,7 +496,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false - prepare: pkg install socat + prepare: pkg install socat oath-toolkit run: | if [ "${{ secrets.TokenName1}}" ] ; then export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" From 0e5aab346f7958fdbe35ffa6a133614a59826a3f Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:17:05 +0100 Subject: [PATCH 126/137] Add oathtool to acmetest Docker image for 2FA support --- .github/workflows/DNS.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 8f7ebd56..781c13fb 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -67,10 +67,15 @@ jobs: TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - - name: Install oathtool - run: sudo apt-get update && sudo apt-get install -y oathtool - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Ensure oathtool in test container + run: | + cd ../acmetest + # Add oathtool installation to acmetest Dockerfile if it exists + if [ -f Dockerfile ]; then + sed -i 's/tzdata/tzdata oath-toolkit-oathtool/g' Dockerfile + fi - name: Set env file run: | cd ../acmetest From e92d0a74926855b607fdfb948eafe6bf44e0fdfa Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:20:56 +0100 Subject: [PATCH 127/137] Fix: Install oathtool at runtime in test container --- .github/workflows/DNS.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 781c13fb..e6319c09 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,13 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Ensure oathtool in test container + - name: Install oathtool in test container run: | cd ../acmetest - # Add oathtool installation to acmetest Dockerfile if it exists - if [ -f Dockerfile ]; then - sed -i 's/tzdata/tzdata oath-toolkit-oathtool/g' Dockerfile - fi + # Modify letest.sh to install oathtool at runtime + sed -i '/TEST_LOCAL skip setup/a apt-get update -qq && apt-get install -y -qq oathtool > /dev/null 2>&1 || true' letest.sh - name: Set env file run: | cd ../acmetest From b6523c230110f5477f3d4a45a020e4b99d230e69 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:42:28 +0100 Subject: [PATCH 128/137] Install oathtool in container via _setup function --- .github/workflows/DNS.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index e6319c09..64fa85d2 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,11 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Install oathtool in test container + - name: Patch acmetest to install oathtool in container run: | cd ../acmetest - # Modify letest.sh to install oathtool at runtime - sed -i '/TEST_LOCAL skip setup/a apt-get update -qq && apt-get install -y -qq oathtool > /dev/null 2>&1 || true' letest.sh + # Add oathtool installation before the test runs inside container + sed -i '/^_setup() {$/a \ if command -v apt-get >/dev/null 2>&1; then\n apt-get update -qq && apt-get install -y -qq oathtool >/dev/null 2>&1 || true\n fi' letest.sh - name: Set env file run: | cd ../acmetest From cba0ff832116b2fc322c3dd51401322789a62d4a Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 19:09:23 +0100 Subject: [PATCH 129/137] Add oathtool to ubuntu package list in plat.conf --- .github/workflows/DNS.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 64fa85d2..84cebf94 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,11 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Patch acmetest to install oathtool in container + - name: Add oathtool to ubuntu package list run: | cd ../acmetest - # Add oathtool installation before the test runs inside container - sed -i '/^_setup() {$/a \ if command -v apt-get >/dev/null 2>&1; then\n apt-get update -qq && apt-get install -y -qq oathtool >/dev/null 2>&1 || true\n fi' letest.sh + # Add oathtool to the ubuntu platform package list + sed -i 's/unzip,openssl,cron,socat,curl,idn,wget/unzip,openssl,cron,socat,curl,idn,wget,oathtool/' plat.conf - name: Set env file run: | cd ../acmetest From 65892453be0cff43cf1b6f996f5cbc2707d540d7 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 19:23:13 +0100 Subject: [PATCH 130/137] take a fork of acmetest --- .github/workflows/DNS.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 84cebf94..5650c9e8 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -68,12 +68,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Add oathtool to ubuntu package list - run: | - cd ../acmetest - # Add oathtool to the ubuntu platform package list - sed -i 's/unzip,openssl,cron,socat,curl,idn,wget/unzip,openssl,cron,socat,curl,idn,wget,oathtool/' plat.conf + run: cd .. && git clone --depth=1 https://github.com/flybyray/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file run: | cd ../acmetest From 1b2630dc0d4030f4507bcfeba60bdc2e102c1410 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Sat, 20 Dec 2025 13:26:38 +0100 Subject: [PATCH 131/137] stop using oathtool --- .github/workflows/DNS.yml | 2 +- dnsapi/dns_inwx.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 5650c9e8..39d2a121 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -68,7 +68,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/flybyray/acmetest.git && cp -r acme.sh acmetest/ + run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file run: | cd ../acmetest diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index a7ce9769..08809bf0 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx Options: INWX_User Username INWX_Password Password - INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional) + INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional requires oathtool) ' # Dependencies: From 5fb42b7339b82d3397b2003dedd3302b439d745c Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Sat, 20 Dec 2025 14:53:01 +0100 Subject: [PATCH 132/137] remove prior additions which tried to use oathtool in tests --- .github/workflows/DNS.yml | 16 ++++++++-------- dnsapi/dns_inwx.sh | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 39d2a121..ccce2ff6 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -116,7 +116,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install tools - run: brew install socat oath-toolkit + run: brew install socat - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -173,7 +173,7 @@ jobs: shell: cmd - name: Install cygwin additional packages run: | - C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,oath-toolkit + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git shell: cmd - name: Set ENV shell: cmd @@ -230,7 +230,7 @@ jobs: - uses: vmactions/freebsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg install -y socat curl oath-toolkit + prepare: pkg install -y socat curl usesh: true copyback: false run: | @@ -281,7 +281,7 @@ jobs: - uses: vmactions/openbsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg_add socat curl libiconv oath-toolkit + prepare: pkg_add socat curl libiconv usesh: true copyback: false run: | @@ -333,7 +333,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - /usr/sbin/pkg_add curl socat oath-toolkit + /usr/sbin/pkg_add curl socat usesh: true copyback: false run: | @@ -385,7 +385,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - pkg install -y curl socat libnghttp2 oath-toolkit + pkg install -y curl socat libnghttp2 usesh: true copyback: false run: | @@ -443,7 +443,7 @@ jobs: copyback: false prepare: | pkgutil -U - pkgutil -y -i socat oathtool + pkgutil -y -i socat run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH @@ -494,7 +494,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false - prepare: pkg install socat oath-toolkit + prepare: pkg install socat run: | if [ "${{ secrets.TokenName1}}" ] ; then export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 08809bf0..2cf91404 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -172,10 +172,10 @@ _inwx_check_cookie() { _htmlEscape() { _s="$1" - _s=$(printf '%s' "$_s" | sed "s/&/&/g") - _s=$(printf '%s' "$_s" | sed "s//\>/g") - _s=$(printf '%s' "$_s" | sed 's/"/\"/g') + _s=$(echo "$_s" | sed "s/&/&/g") + _s=$(echo "$_s" | sed "s//\>/g") + _s=$(echo "$_s" | sed 's/"/\"/g') printf -- %s "$_s" } From f85de2b0d3560bab9fb553b53e9564edcad1b01d Mon Sep 17 00:00:00 2001 From: Erfan Gholizade Date: Wed, 3 Dec 2025 18:26:58 +0330 Subject: [PATCH 133/137] Added Sotoon dns api handle the case of metadata 404 in old api domains fix index of domain start fix shfmt Revert "handle the case of metadata 404 in old api domains" This reverts commit 9fe4616664b897c9891271006e7489b10bb818ca. fix 404 on dot ad hyphen fix shfmt --- dnsapi/dns_sotoon.sh | 319 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 dnsapi/dns_sotoon.sh diff --git a/dnsapi/dns_sotoon.sh b/dnsapi/dns_sotoon.sh new file mode 100644 index 00000000..4a0fc034 --- /dev/null +++ b/dnsapi/dns_sotoon.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_sotoon_info='Sotoon.ir +Site: Sotoon.ir +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon +Options: + Sotoon_Token API Token + Sotoon_WorkspaceUUID Workspace UUID + Sotoon_WorkspaceName Workspace Name +Issues: github.com/acmesh-official/acme.sh/issues/6656 +Author: Erfan Gholizade +' + +SOTOON_API_URL="https://api.sotoon.ir/delivery/v2/global" + +######## Public functions ##################### + +#Adding the txt record for validation. +#Usage: dns_sotoon_add fulldomain TXT_record +#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_sotoon_add() { + fulldomain=$1 + txtvalue=$2 + _info_sotoon "Using Sotoon" + + Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" + Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" + Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" + + if [ -z "$Sotoon_Token" ]; then + _err_sotoon "You didn't specify \"Sotoon_Token\" token yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/tokens" + return 1 + fi + if [ -z "$Sotoon_WorkspaceUUID" ]; then + _err_sotoon "You didn't specify \"Sotoon_WorkspaceUUID\" Workspace UUID yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" + return 1 + fi + if [ -z "$Sotoon_WorkspaceName" ]; then + _err_sotoon "You didn't specify \"Sotoon_WorkspaceName\" Workspace Name yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" + return 1 + fi + + #save the info to the account conf file. + _saveaccountconf_mutable Sotoon_Token "$Sotoon_Token" + _saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID" + _saveaccountconf_mutable Sotoon_WorkspaceName "$Sotoon_WorkspaceName" + + _debug_sotoon "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err_sotoon "invalid domain" + return 1 + fi + + _info_sotoon "Adding record" + + _debug_sotoon _domain_id "$_domain_id" + _debug_sotoon _sub_domain "$_sub_domain" + _debug_sotoon _domain "$_domain" + + # First, GET the current domain zone to check for existing TXT records + # This is needed for wildcard certs which require multiple TXT values + _info_sotoon "Checking for existing TXT records" + if ! _sotoon_rest GET "$_domain_id"; then + _err_sotoon "Failed to get domain zone" + return 1 + fi + + # Check if there are existing TXT records for this subdomain + _existing_txt="" + if _contains "$response" "\"$_sub_domain\""; then + _debug_sotoon "Found existing records for $_sub_domain" + # Extract existing TXT values from the response + # The format is: "_acme-challenge":[{"TXT":"value1","type":"TXT","ttl":10},{"TXT":"value2",...}] + _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") + _debug_sotoon "Existing TXT records: $_existing_txt" + fi + + # Build the new record entry + _new_record="{\"TXT\":\"$txtvalue\",\"type\":\"TXT\",\"ttl\":120}" + + # If there are existing records, append to them; otherwise create new array + if [ -n "$_existing_txt" ] && [ "$_existing_txt" != "[]" ] && [ "$_existing_txt" != "null" ]; then + # Check if this exact TXT value already exists (avoid duplicates) + if _contains "$_existing_txt" "\"$txtvalue\""; then + _info_sotoon "TXT record already exists, skipping" + return 0 + fi + # Remove the closing bracket and append new record + _combined_records="$(echo "$_existing_txt" | sed 's/]$//'),$_new_record]" + _debug_sotoon "Combined records: $_combined_records" + else + # No existing records, create new array + _combined_records="[$_new_record]" + fi + + # Prepare the DNS record data in Kubernetes CRD format + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_combined_records}}}" + + _debug_sotoon "DNS record payload: $_dns_record" + + # Use PATCH to update/add the record to the domain zone + _info_sotoon "Updating domain zone $_domain_id with TXT record" + if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then + if _contains "$response" "$txtvalue" || _contains "$response" "\"$_sub_domain\""; then + _info_sotoon "Added, OK" + return 0 + else + _debug_sotoon "Response: $response" + _err_sotoon "Add txt record error." + return 1 + fi + fi + + _err_sotoon "Add txt record error." + return 1 +} + +#Remove the txt record after validation. +#Usage: dns_sotoon_rm fulldomain TXT_record +#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_sotoon_rm() { + fulldomain=$1 + txtvalue=$2 + _info_sotoon "Using Sotoon" + _debug_sotoon fulldomain "$fulldomain" + _debug_sotoon txtvalue "$txtvalue" + + Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" + Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" + Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" + + _debug_sotoon "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err_sotoon "invalid domain" + return 1 + fi + _debug_sotoon _domain_id "$_domain_id" + _debug_sotoon _sub_domain "$_sub_domain" + _debug_sotoon _domain "$_domain" + + _info_sotoon "Removing TXT record" + + # First, GET the current domain zone to check for existing TXT records + if ! _sotoon_rest GET "$_domain_id"; then + _err_sotoon "Failed to get domain zone" + return 1 + fi + + # Check if there are existing TXT records for this subdomain + _existing_txt="" + if _contains "$response" "\"$_sub_domain\""; then + _debug_sotoon "Found existing records for $_sub_domain" + _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") + _debug_sotoon "Existing TXT records: $_existing_txt" + fi + + # If no existing records, nothing to remove + if [ -z "$_existing_txt" ] || [ "$_existing_txt" = "[]" ] || [ "$_existing_txt" = "null" ]; then + _info_sotoon "No TXT records found, nothing to remove" + return 0 + fi + + # Remove the specific TXT value from the array + # This handles the case where there are multiple TXT values (wildcard certs) + _remaining_records=$(echo "$_existing_txt" | sed "s/{\"TXT\":\"$txtvalue\"[^}]*},*//g" | sed 's/,]/]/g' | sed 's/\[,/[/g') + _debug_sotoon "Remaining records after removal: $_remaining_records" + + # If no records remain, set to null to remove the subdomain entirely + if [ "$_remaining_records" = "[]" ] || [ -z "$_remaining_records" ]; then + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":null}}}" + else + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_remaining_records}}}" + fi + + _debug_sotoon "Remove record payload: $_dns_record" + + # Use PATCH to remove the record from the domain zone + if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then + _info_sotoon "Record removed, OK" + return 0 + else + _debug_sotoon "Response: $response" + _err_sotoon "Error removing record" + return 1 + fi +} + +#################### Private functions below ################################## + +_get_root() { + domain=$1 + i=1 + p=1 + + _debug_sotoon "Getting root domain for: $domain" + _debug_sotoon "Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID" + _debug_sotoon "Sotoon WorkspaceName: $Sotoon_WorkspaceName" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug_sotoon "Checking domain part: $h" + + if [ -z "$h" ]; then + #not valid + _err_sotoon "Could not find valid domain" + return 1 + fi + + _debug_sotoon "Fetching domain zones from Sotoon API" + if ! _sotoon_rest GET ""; then + _err_sotoon "Failed to get domain zones from Sotoon API" + _err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID, and Sotoon_WorkspaceName" + return 1 + fi + + _debug2_sotoon "API Response: $response" + + # Check if the response contains our domain + # Sotoon API uses Kubernetes CRD format with spec.origin for domain matching + if _contains "$response" "\"origin\":\"$h\""; then + _debug_sotoon "Found domain by origin: $h" + + # In Kubernetes CRD format, the metadata.name is the resource identifier + # The name can be either: + # 1. Same as origin + # 2. Origin with dots replaced by hyphens + # We check both patterns in the response to determine which one exists + + # Convert origin to hyphenated version for checking + _h_hyphenated=$(echo "$h" | tr '.' '-') + + # Check if the hyphenated name exists in the response + if _contains "$response" "\"name\":\"$_h_hyphenated\""; then + _domain_id="$_h_hyphenated" + _debug_sotoon "Found domain ID (hyphenated): $_domain_id" + # Check if the origin itself is used as name + elif _contains "$response" "\"name\":\"$h\""; then + _domain_id="$h" + _debug_sotoon "Found domain ID (same as origin): $_domain_id" + else + # Fallback: use the hyphenated version (more common) + _domain_id="$_h_hyphenated" + _debug_sotoon "Using hyphenated domain ID as fallback: $_domain_id" + fi + + if [ -n "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + _debug_sotoon "Domain ID (metadata.name): $_domain_id" + _debug_sotoon "Sub domain: $_sub_domain" + _debug_sotoon "Domain (origin): $_domain" + return 0 + fi + _err_sotoon "Found domain $h but could not extract domain ID" + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_sotoon_rest() { + mtd="$1" + resource_id="$2" + data="$3" + + token_trimmed=$(echo "$Sotoon_Token" | tr -d '"') + + # Construct the API endpoint + _api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/namespaces/$Sotoon_WorkspaceName/domainzones" + + if [ -n "$resource_id" ]; then + _api_path="$_api_path/$resource_id" + fi + + _debug_sotoon "API Path: $_api_path" + _debug_sotoon "Method: $mtd" + + # Set authorization header - Sotoon API uses Bearer token + export _H1="Authorization: Bearer $token_trimmed" + + if [ "$mtd" = "GET" ]; then + # GET request + _debug_sotoon "GET" "$_api_path" + response="$(_get "$_api_path")" + elif [ "$mtd" = "PATCH" ]; then + # PATCH Request + export _H2="Content-Type: application/merge-patch+json" + _debug_sotoon data "$data" + response="$(_post "$data" "$_api_path" "" "$mtd")" + else + _err_sotoon "Unknown method: $mtd" + return 1 + fi + + _debug2_sotoon response "$response" + return 0 +} + +#Wrappers for logging +_info_sotoon() { + _info "[Sotoon]" "$@" +} + +_err_sotoon() { + _err "[Sotoon]" "$@" +} + +_debug_sotoon() { + _debug "[Sotoon]" "$@" +} + +_debug2_sotoon() { + _debug2 "[Sotoon]" "$@" +} From 03d8d3bc1b20fe356feb823069424868e149898e Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:29:50 +0100 Subject: [PATCH 134/137] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 347f34d1..af722297 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(printf "%s" "$fulldomain" | tr '[:upper:]' '[:lower:]')" + _domain_candidate="$(_lower_case "$fulldomain")" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -361,7 +361,7 @@ _hostup_json_extract() { input="${2:-$line}" # First try to extract quoted values (strings) - quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | head -n1)" + quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | _head_n 1)" if [ -n "$quoted_match" ]; then printf "%s" "$quoted_match" | cut -d : -f2- | @@ -372,7 +372,7 @@ _hostup_json_extract() { fi # Fallback for unquoted values (e.g., numeric IDs) - unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | head -n1)" + unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | _head_n 1)" if [ -n "$unquoted_match" ]; then printf "%s" "$unquoted_match" | cut -d : -f2- | @@ -392,7 +392,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$(_lower_case "$domain")" | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } @@ -425,7 +425,7 @@ _hostup_extract_record_id() { return 0 fi - printf "%s" "$1" | _egrep_o '"id":[0-9]+' | head -n1 | cut -d: -f2 + printf "%s" "$1" | _egrep_o '"id":[0-9]+' | _head_n 1 | cut -d: -f2 } _hostup_delete_record_by_id() { From e321b3c75c0eabe6341f9d7b5643a9e9ef4019a6 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:53:21 +0100 Subject: [PATCH 135/137] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index af722297..b3211069 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -3,7 +3,7 @@ dns_hostup_info='HostUp DNS Site: hostup.se -Docs: https://hostup.se/en/support/api-autentisering/ +Docs: https://developer.hostup.se/ Options: HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api). @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(_lower_case "$fulldomain")" + _domain_candidate="$(printf "%s" "$fulldomain" | _lower_case)" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -392,7 +392,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$(_lower_case "$domain")" | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$domain" | _lower_case | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } From 0eb40c6ce6ce5c0bf17f39893e1b6ea4056ae362 Mon Sep 17 00:00:00 2001 From: DreamSlave <50408721+mq00fc@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:00:22 +0800 Subject: [PATCH 136/137] Update timestamp variable in ali_cdn.sh --- deploy/ali_cdn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ali_cdn.sh b/deploy/ali_cdn.sh index 70a2e532..3c28674e 100644 --- a/deploy/ali_cdn.sh +++ b/deploy/ali_cdn.sh @@ -83,6 +83,6 @@ _set_cdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-05-10' } From fd6a14de8a69193a5367dfaae71857d804a83528 Mon Sep 17 00:00:00 2001 From: DreamSlave <50408721+mq00fc@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:00:31 +0800 Subject: [PATCH 137/137] Update timestamp function in ali_dcdn.sh --- deploy/ali_dcdn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ali_dcdn.sh b/deploy/ali_dcdn.sh index 14ac500a..27d3a726 100644 --- a/deploy/ali_dcdn.sh +++ b/deploy/ali_dcdn.sh @@ -83,6 +83,6 @@ _set_dcdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-01-15' }