From 54eba51b35b7ec48f5e4eecfad3139e6a6ed34f4 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:14:00 +0800 Subject: [PATCH 001/125] Add deployhook for Netlify --- deploy/netlify.sh | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 deploy/netlify.sh diff --git a/deploy/netlify.sh b/deploy/netlify.sh new file mode 100644 index 00000000..3b854018 --- /dev/null +++ b/deploy/netlify.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Script to deploy certificate to Netlify +# https://docs.netlify.com/api/get-started/#authentication +# https://open-api.netlify.com/#tag/sniCertificate + +# This deployment required following variables +# export Netlify_ACCESS_TOKEN="Your Netlify Access Token" +# export Netlify_SITE_ID="Your Netlify Site ID" + +# returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +netlify_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$Netlify_ACCESS_TOKEN" ]; then + _err "Netlify_ACCESS_TOKEN is not defined." + return 1 + else + _savedomainconf Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" + fi + if [ -z "$Netlify_SITE_ID" ]; then + _err "Netlify_SITE_ID is not defined." + return 1 + else + _savedomainconf Netlify_SITE_ID "$Netlify_SITE_ID" + fi + + _info "Deploying certificate to Netlify..." + + ## upload certificate + string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') + string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') + string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') + _request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}" + _debug _request_body "$_request_body" + _debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" + export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN" + _response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$Netlify_SITE_ID/ssl" "" "POST" "application/json") + + if _contains "$_response" "\"error\""; then + _err "Error in deploying $_cdomain certificate to Netlify." + _err "$_response" + return 1 + fi + _debug response "$_response" + _info "Domain $_cdomain certificate successfully deployed to Netlify." + return 0 +} \ No newline at end of file From c508984f564fc99235e5d99142f0af6972430d0c Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:16:53 +0800 Subject: [PATCH 002/125] Add deployhook for Edgio --- deploy/edgio.sh | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 deploy/edgio.sh diff --git a/deploy/edgio.sh b/deploy/edgio.sh new file mode 100644 index 00000000..604b00e8 --- /dev/null +++ b/deploy/edgio.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env sh + +# Here is a script to deploy cert to edgio using its API +# https://docs.edg.io/guides/v7/develop/rest_api/authentication +# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts + +# This deployment required following variables +# export EDGIO_CLIENT_ID="Your Edgio Client ID" +# export EDGIO_CLIENT_SECRET="Your Edgio Client Secret" +# export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID" + +#returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +edgio_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$EDGIO_CLIENT_ID" ]; then + _err "EDGIO_CLIENT_ID is not defined." + return 1 + else + _savedomainconf EDGIO_CLIENT_ID "$EDGIO_CLIENT_ID" + fi + + if [ -z "$EDGIO_CLIENT_SECRET" ]; then + _err "EDGIO_CLIENT_SECRET is not defined." + return 1 + else + _savedomainconf EDGIO_CLIENT_SECRET "$EDGIO_CLIENT_SECRET" + fi + + if [ -z "$EDGIO_ENVIRONMENT_ID" ]; then + _err "EDGIO_ENVIRONMENT_ID is not defined." + return 1 + else + _savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID" + fi + + _info "Getting access token" + _data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config" + _debug Get_access_token_data "$_data" + _response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded" ) + _debug Get_access_token_response "$_response" + _access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"') + _debug _access_token "$_access_token" + if [ -z "$_access_token" ]; then + _err "Error in getting access token" + return 1 + fi + + _info "Uploading certificate" + string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') + string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') + string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') + _data="{\"environment_id\":\"$EDGIO_ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}" + _debug Upload_certificate_data "$_data" + _H1="Authorization: Bearer $_access_token" + _response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json") + + if _contains "$_response" "message"; then + _err "Error in deploying $_cdomain certificate to Edgio." + _err "$_response" + return 1 + fi + _debug Upload_certificate_response "$_response" + _info "Domain $_cdomain certificate successfully deployed to Edgio." + return 0 +} \ No newline at end of file From d1a1d1da8f550bf80a8339a7c15735619e581a2b Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:16:44 +0800 Subject: [PATCH 003/125] Add deployhook for CacheFly --- deploy/cachefly.sh | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 deploy/cachefly.sh diff --git a/deploy/cachefly.sh b/deploy/cachefly.sh new file mode 100644 index 00000000..0e436d26 --- /dev/null +++ b/deploy/cachefly.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env sh + +# Script to deploy certificate to CacheFly +# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post + +# This deployment required following variables +# export CACHEFLY_TOKEN="Your CacheFly API Token" + +# returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +CACHEFLY_API_BASE="https://api.cachefly.com/api/2.5" + +cachefly_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$CACHEFLY_TOKEN" ]; then + _err "CACHEFLY_TOKEN is not defined." + return 1 + else + _savedomainconf CACHEFLY_TOKEN "$CACHEFLY_TOKEN" + fi + + _info "Deploying certificate to CacheFly..." + + ## upload certificate + string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n') + string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') + + _request_body="{\"certificate\":\"$string_fullchain\",\"certificateKey\":\"$string_key\"}" + _debug _request_body "$_request_body" + _debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN" + export _H1="Authorization: Bearer $CACHEFLY_TOKEN" + _response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json") + + if _contains "$_response" "message"; then + _err "Error in deploying $_cdomain certificate to CacheFly." + _err "$_response" + return 1 + fi + _debug response "$_response" + _info "Domain $_cdomain certificate successfully deployed to CacheFly." + return 0 +} \ No newline at end of file From 696182cfa4ceff50cd7c5b05a15cc591c55173bd Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:05:43 +0800 Subject: [PATCH 004/125] deployhook Edgio: Support multiple Environment ID --- deploy/edgio.sh | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/deploy/edgio.sh b/deploy/edgio.sh index 604b00e8..aadaa98a 100644 --- a/deploy/edgio.sh +++ b/deploy/edgio.sh @@ -9,7 +9,10 @@ # export EDGIO_CLIENT_SECRET="Your Edgio Client Secret" # export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID" -#returns 0 means success, otherwise error. +# If have more than one Environment ID +# export EDGIO_ENVIRONMENT_ID="ENVIRONMENT_ID_1 ENVIRONMENT_ID_2" + +# returns 0 means success, otherwise error. ######## Public functions ##################### @@ -64,17 +67,20 @@ edgio_deploy() { string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - _data="{\"environment_id\":\"$EDGIO_ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}" - _debug Upload_certificate_data "$_data" - _H1="Authorization: Bearer $_access_token" - _response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json") - if _contains "$_response" "message"; then - _err "Error in deploying $_cdomain certificate to Edgio." - _err "$_response" - return 1 - fi - _debug Upload_certificate_response "$_response" - _info "Domain $_cdomain certificate successfully deployed to Edgio." + for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do + _data="{\"environment_id\":\"$ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}" + _debug Upload_certificate_data "$_data" + _H1="Authorization: Bearer $_access_token" + _response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json") + if _contains "$_response" "message"; then + _err "Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID." + _err "$_response" + return 1 + fi + _debug Upload_certificate_response "$_response" + _info "Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID." + done + return 0 } \ No newline at end of file From 3b46060caa7a94e04926099ba32118efd07cc116 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:06:09 +0800 Subject: [PATCH 005/125] deployhook Netlify: Support multiple Site ID --- deploy/netlify.sh | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/deploy/netlify.sh b/deploy/netlify.sh index 3b854018..2ff9bcb6 100644 --- a/deploy/netlify.sh +++ b/deploy/netlify.sh @@ -8,6 +8,9 @@ # export Netlify_ACCESS_TOKEN="Your Netlify Access Token" # export Netlify_SITE_ID="Your Netlify Site ID" +# If have more than one SITE ID +# export Netlify_SITE_ID="SITE_ID_1 SITE_ID_2" + # returns 0 means success, otherwise error. ######## Public functions ##################### @@ -45,18 +48,22 @@ netlify_deploy() { string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - _request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}" - _debug _request_body "$_request_body" - _debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" - export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN" - _response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$Netlify_SITE_ID/ssl" "" "POST" "application/json") - if _contains "$_response" "\"error\""; then - _err "Error in deploying $_cdomain certificate to Netlify." - _err "$_response" - return 1 - fi - _debug response "$_response" - _info "Domain $_cdomain certificate successfully deployed to Netlify." + for SITE_ID in $Netlify_SITE_ID; do + _request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}" + _debug _request_body "$_request_body" + _debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" + export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN" + _response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json") + + if _contains "$_response" "\"error\""; then + _err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID." + _err "$_response" + return 1 + fi + _debug response "$_response" + _info "Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID." + done + return 0 } \ No newline at end of file From e7284df1df2eb586ec89bf69e19086779d63ff02 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:44:33 +0800 Subject: [PATCH 006/125] Add deployhook for DirectAdmin --- deploy/directadmin.sh | 80 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 deploy/directadmin.sh diff --git a/deploy/directadmin.sh b/deploy/directadmin.sh new file mode 100644 index 00000000..23d46df9 --- /dev/null +++ b/deploy/directadmin.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env sh + +# Script to deploy certificate to DirectAdmin +# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key +# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs + +# This deployment required following variables +# export DirectAdmin_ENDPOINT="example.com:2222" +# export DirectAdmin_USERNAME="Your DirectAdmin Username" +# export DirectAdmin_KEY="Your DirectAdmin Login Key or Password" +# export DirectAdmin_MAIN_DOMAIN="Your DirectAdmin Main Domain, NOT Subdomain" + +# returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +directadmin_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$DirectAdmin_ENDPOINT" ]; then + _err "DirectAdmin_ENDPOINT is not defined." + return 1 + else + _savedomainconf DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT" + fi + if [ -z "$DirectAdmin_USERNAME" ]; then + _err "DirectAdmin_USERNAME is not defined." + return 1 + else + _savedomainconf DirectAdmin_USERNAME "$DirectAdmin_USERNAME" + fi + if [ -z "$DirectAdmin_KEY" ]; then + _err "DirectAdmin_KEY is not defined." + return 1 + else + _savedomainconf DirectAdmin_KEY "$DirectAdmin_KEY" + fi + if [ -z "$DirectAdmin_MAIN_DOMAIN" ]; then + _err "DirectAdmin_MAIN_DOMAIN is not defined." + return 1 + else + _savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" + fi + + _info "Deploying certificate to DirectAdmin..." + + # upload certificate + string_cfullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n') + string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') + + _request_body="{\"domain\":\"$DirectAdmin_MAIN_DOMAIN\",\"action\":\"save\",\"type\":\"paste\",\"certificate\":\"$string_key\n$string_cfullchain\n\"}" + _debug _request_body "$_request_body" + _debug DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT" + _debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME" + _debug DirectAdmin_KEY "$DirectAdmin_KEY" + _debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" + _response=$(_post "$_request_body" "https://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json") + + if _contains "$_response" "error=1"; then + _err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." + _err "$_response" + return 1 + fi + + _info "$_response" + _info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." + + return 0 +} \ No newline at end of file From 295af0168753caf491a86745f0c8ef6b6bc207be Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:07:14 +0800 Subject: [PATCH 007/125] Add deployhook for KeyHelp --- deploy/keyhelp.sh | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 deploy/keyhelp.sh diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh new file mode 100644 index 00000000..b792f021 --- /dev/null +++ b/deploy/keyhelp.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env sh + +# Script to deploy certificate to KeyHelp +# This deployment required following variables +# export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com" +# export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username" +# export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password" +# export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID" + +# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID. +# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx + +# If have more than one domain name +# export DEPLOY_KEYHELP_DOMAIN_ID="111 222 333" + +keyhelp_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$DEPLOY_KEYHELP_BASEURL" ]; then + _err "DEPLOY_KEYHELP_BASEURL is not defined." + return 1 + else + _savedomainconf DEPLOY_KEYHELP_BASEURL "$DEPLOY_KEYHELP_BASEURL" + fi + + if [ -z "$DEPLOY_KEYHELP_USERNAME" ]; then + _err "DEPLOY_KEYHELP_USERNAME is not defined." + return 1 + else + _savedomainconf DEPLOY_KEYHELP_USERNAME "$DEPLOY_KEYHELP_USERNAME" + fi + + if [ -z "$DEPLOY_KEYHELP_PASSWORD" ]; then + _err "DEPLOY_KEYHELP_PASSWORD is not defined." + return 1 + else + _savedomainconf DEPLOY_KEYHELP_PASSWORD "$DEPLOY_KEYHELP_PASSWORD" + fi + + if [ -z "$DEPLOY_KEYHELP_DOMAIN_ID" ]; then + _err "DEPLOY_KEYHELP_DOMAIN_ID is not defined." + return 1 + else + _savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID" + fi + + _info "Logging in to keyhelp panel" + username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)" + password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)" + _H1="Content-Type: application/x-www-form-urlencoded" + _response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded" "TRUE") + _cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" + + # If cookies is not empty then logon successful + if [ -z "$_cookie" ]; then + _err "Fail to get cookie." + return 1 + fi + _debug "cookie" "$_cookie" + + _info "Uploading certificate" + _date=$(date +"%Y%m%d") + encoded_key="$(_url_encode <"$_ckey")" + encoded_ccert="$(_url_encode <"$_ccert")" + encoded_cca="$(_url_encode <"$_cca")" + certificate_name="$_cdomain-$_date" + + _request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca" + _H1="Cookie: $_cookie" + _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST") + _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') + _info "_message" "$_message" + if [ -z "$_message" ]; then + _err "Fail to upload certificate." + return 1 + fi + + for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do + _info "Apply certificate to domain id $DOMAIN_ID" + _response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID") + cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p') + target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p') + _debug "cert_value" "$cert_value" + if [ -z "$cert_value" ]; then + _err "Fail to get certificate id." + return 1 + fi + + _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&certificate_type=custom&certificate_id=$cert_value" + _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST") + _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') + _info "_message" "$_message" + if [ -z "$_message" ]; then + _err "Fail to apply certificate." + return 1 + fi + done + + _info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID." + return 0 +} \ No newline at end of file From c466f063c82ed23e695612d946ac9dcf98f71a1c Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:59:12 +0800 Subject: [PATCH 008/125] add newline at end of file --- deploy/cachefly.sh | 2 +- deploy/directadmin.sh | 2 +- deploy/edgio.sh | 2 +- deploy/keyhelp.sh | 2 +- deploy/netlify.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/cachefly.sh b/deploy/cachefly.sh index 0e436d26..325b2230 100644 --- a/deploy/cachefly.sh +++ b/deploy/cachefly.sh @@ -53,4 +53,4 @@ cachefly_deploy() { _debug response "$_response" _info "Domain $_cdomain certificate successfully deployed to CacheFly." return 0 -} \ No newline at end of file +} diff --git a/deploy/directadmin.sh b/deploy/directadmin.sh index 23d46df9..84818f93 100644 --- a/deploy/directadmin.sh +++ b/deploy/directadmin.sh @@ -77,4 +77,4 @@ directadmin_deploy() { _info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." return 0 -} \ No newline at end of file +} diff --git a/deploy/edgio.sh b/deploy/edgio.sh index aadaa98a..1b0569cb 100644 --- a/deploy/edgio.sh +++ b/deploy/edgio.sh @@ -83,4 +83,4 @@ edgio_deploy() { done return 0 -} \ No newline at end of file +} diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index b792f021..58f13152 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -108,4 +108,4 @@ keyhelp_deploy() { _info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID." return 0 -} \ No newline at end of file +} diff --git a/deploy/netlify.sh b/deploy/netlify.sh index 2ff9bcb6..fb254a32 100644 --- a/deploy/netlify.sh +++ b/deploy/netlify.sh @@ -66,4 +66,4 @@ netlify_deploy() { done return 0 -} \ No newline at end of file +} From bfba44fbadc142a0f8dd87e107953e17970a4a90 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:36:19 +0000 Subject: [PATCH 009/125] format adjustment --- deploy/cachefly.sh | 2 +- deploy/edgio.sh | 6 +++--- deploy/keyhelp.sh | 8 ++++---- deploy/netlify.sh | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/deploy/cachefly.sh b/deploy/cachefly.sh index 325b2230..7841b20b 100644 --- a/deploy/cachefly.sh +++ b/deploy/cachefly.sh @@ -44,7 +44,7 @@ cachefly_deploy() { _debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN" export _H1="Authorization: Bearer $CACHEFLY_TOKEN" _response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json") - + if _contains "$_response" "message"; then _err "Error in deploying $_cdomain certificate to CacheFly." _err "$_response" diff --git a/deploy/edgio.sh b/deploy/edgio.sh index 1b0569cb..1acd0c8f 100644 --- a/deploy/edgio.sh +++ b/deploy/edgio.sh @@ -23,7 +23,7 @@ edgio_deploy() { _ccert="$3" _cca="$4" _cfullchain="$5" - + _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" @@ -50,11 +50,11 @@ edgio_deploy() { else _savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID" fi - + _info "Getting access token" _data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config" _debug Get_access_token_data "$_data" - _response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded" ) + _response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded") _debug Get_access_token_response "$_response" _access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"') _debug _access_token "$_access_token" diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index 58f13152..839b874c 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -92,8 +92,8 @@ keyhelp_deploy() { target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p') _debug "cert_value" "$cert_value" if [ -z "$cert_value" ]; then - _err "Fail to get certificate id." - return 1 + _err "Fail to get certificate id." + return 1 fi _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&certificate_type=custom&certificate_id=$cert_value" @@ -101,8 +101,8 @@ keyhelp_deploy() { _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') _info "_message" "$_message" if [ -z "$_message" ]; then - _err "Fail to apply certificate." - return 1 + _err "Fail to apply certificate." + return 1 fi done diff --git a/deploy/netlify.sh b/deploy/netlify.sh index fb254a32..8d25f74c 100644 --- a/deploy/netlify.sh +++ b/deploy/netlify.sh @@ -48,14 +48,14 @@ netlify_deploy() { string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - + for SITE_ID in $Netlify_SITE_ID; do _request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}" _debug _request_body "$_request_body" _debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN" _response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json") - + if _contains "$_response" "\"error\""; then _err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID." _err "$_response" From 1116b73a08aae1b58c8edb6fbb016d82ac3364c1 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:47:43 +0800 Subject: [PATCH 010/125] deployhook KeyHelp: Support enabling the Enforce HTTPS option --- deploy/keyhelp.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index 839b874c..0750e415 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -5,6 +5,7 @@ # export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com" # export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username" # export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password" +# export DEPLOY_KEYHELP_ENFORCE_HTTPS="1" # 0 or 1, input 1 to enable Enforce HTTP to HTTPS redirection. # export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID" # Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID. @@ -54,6 +55,11 @@ keyhelp_deploy() { _savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID" fi + # Optional DEPLOY_KEYHELP_ENFORCE_HTTPS + _getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS + # set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS + [ -n "${DEPLOY_KEYHELP_ENFORCE_HTTPS}" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS="1" + _info "Logging in to keyhelp panel" username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)" password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)" @@ -96,7 +102,7 @@ keyhelp_deploy() { return 1 fi - _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&certificate_type=custom&certificate_id=$cert_value" + _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS" _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST") _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') _info "_message" "$_message" From 3f40380c69d75bbf09bdb9e4cdb1c007fe437655 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:57:51 +0800 Subject: [PATCH 011/125] deployhook Directadmin: Support for selecting the scheme of DirectAdmin , HTTP or HTTPS --- deploy/directadmin.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deploy/directadmin.sh b/deploy/directadmin.sh index 84818f93..3f60a088 100644 --- a/deploy/directadmin.sh +++ b/deploy/directadmin.sh @@ -5,6 +5,7 @@ # https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs # This deployment required following variables +# export DirectAdmin_SCHEME="https" # Optional, https or http, defaults to https # export DirectAdmin_ENDPOINT="example.com:2222" # export DirectAdmin_USERNAME="Your DirectAdmin Username" # export DirectAdmin_KEY="Your DirectAdmin Login Key or Password" @@ -53,6 +54,11 @@ directadmin_deploy() { _savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" fi + # Optional SCHEME + _getdeployconf DirectAdmin_SCHEME + # set default values for DirectAdmin_SCHEME + [ -n "${DirectAdmin_SCHEME}" ] || DirectAdmin_SCHEME="https" + _info "Deploying certificate to DirectAdmin..." # upload certificate @@ -65,7 +71,7 @@ directadmin_deploy() { _debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME" _debug DirectAdmin_KEY "$DirectAdmin_KEY" _debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" - _response=$(_post "$_request_body" "https://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json") + _response=$(_post "$_request_body" "$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json") if _contains "$_response" "error=1"; then _err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." From 2f5ea120cb18d56d9d21da07034cb679457b3c94 Mon Sep 17 00:00:00 2001 From: b1n23 <97284148+b1n23@users.noreply.github.com> Date: Tue, 16 Jul 2024 00:25:53 +0800 Subject: [PATCH 012/125] deployhook KeyHelp: fix bug --- deploy/keyhelp.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index 0750e415..97f9c21c 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -5,7 +5,6 @@ # export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com" # export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username" # export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password" -# export DEPLOY_KEYHELP_ENFORCE_HTTPS="1" # 0 or 1, input 1 to enable Enforce HTTP to HTTPS redirection. # export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID" # Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID. @@ -96,13 +95,28 @@ keyhelp_deploy() { _response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID") cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p') target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p') + if [ "$target_type" = "directory" ]; then + path=$(echo "$_response" | awk '/name="path"/{getline; print}' | sed -n 's/.*value="\([^"]*\).*/\1/p') + fi + echo "$_response" | grep "is_prefer_https" | grep "checked" >/dev/null + if [ $? -eq 0 ]; then + is_prefer_https=1 + else + is_prefer_https=0 + fi + echo "$_response" | grep "hsts_enabled" | grep "checked" >/dev/null + if [ $? -eq 0 ]; then + hsts_enabled=1 + else + hsts_enabled=0 + fi _debug "cert_value" "$cert_value" if [ -z "$cert_value" ]; then _err "Fail to get certificate id." return 1 fi - _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS" + _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS" _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST") _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') _info "_message" "$_message" From c6a9825c0a2c6c4852a869a9cbf4864d1e270ccb Mon Sep 17 00:00:00 2001 From: asavin Date: Fri, 18 Apr 2025 17:25:55 +0200 Subject: [PATCH 013/125] 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 014/125] 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 015/125] 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 016/125] 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 017/125] 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 018/125] 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 019/125] 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 020/125] 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 021/125] 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 022/125] 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 023/125] 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 024/125] 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 025/125] 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 026/125] 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 027/125] 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 028/125] 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 029/125] 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 030/125] 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 031/125] 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 032/125] 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 033/125] 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 034/125] 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 035/125] 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 036/125] 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 037/125] 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 038/125] 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 039/125] 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 040/125] 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 041/125] 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 042/125] 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 043/125] 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 044/125] 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 045/125] 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 046/125] 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 047/125] 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 048/125] 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 049/125] 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 050/125] 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 051/125] 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 052/125] 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 053/125] 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 054/125] 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 055/125] 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 056/125] 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 057/125] 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 058/125] 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 059/125] 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 060/125] 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 061/125] 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 062/125] 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 063/125] 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 064/125] 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 25c564bae1efb7d01388b63752976957aa5dfb6b Mon Sep 17 00:00:00 2001 From: Vmichelin Date: Tue, 14 Oct 2025 10:10:19 +0200 Subject: [PATCH 065/125] fix #6555 : ovh dns api enable to remove record --- dnsapi/dns_ovh.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index 24ad0904..9f2cd23f 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -201,7 +201,7 @@ dns_ovh_rm() { if ! _ovh_rest GET "domain/zone/$_domain/record/$rid"; then return 1 fi - if _contains "$response" "\"target\":\"$txtvalue\""; then + if _contains "$response" "$txtvalue"; then _debug "Found txt id:$rid" if ! _ovh_rest DELETE "domain/zone/$_domain/record/$rid"; then return 1 From c4671272c0987d2de0caebafb0216f5e061c9eb7 Mon Sep 17 00:00:00 2001 From: asavin Date: Tue, 14 Oct 2025 13:35:15 +0200 Subject: [PATCH 066/125] 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 067/125] 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 a2c2b7ffee184d3e8f4c4ccf63685ffdb68232c0 Mon Sep 17 00:00:00 2001 From: DuolaD Date: Sat, 18 Oct 2025 11:41:26 +0800 Subject: [PATCH 068/125] Fixed the issue where Telegram bots would not push notifications. --- notify/telegram.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notify/telegram.sh b/notify/telegram.sh index 7da05729..4ed50a65 100644 --- a/notify/telegram.sh +++ b/notify/telegram.sh @@ -34,8 +34,8 @@ telegram_send() { fi _saveaccountconf_mutable TELEGRAM_BOT_URLBASE "$TELEGRAM_BOT_URLBASE" - _subject="$(printf "%s" "$_subject" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([-_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" - _content="$(printf "%s" "$_content" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([-_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" + _subject="$(printf "%s" "$_subject" | sed -E 's/([][()~`>#+=|{}.!*_\\-])/\\\\\1/g')" + _content="$(printf "%s" "$_content" | sed -E 's/([][()~`>#+=|{}.!*_\\-])/\\\\\1/g')" _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)" _data="{\"text\": \"$_content\", " _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", " From ef76831d37161af4876202f0cde2596956aab9fb Mon Sep 17 00:00:00 2001 From: asavin Date: Sat, 18 Oct 2025 14:16:51 +0200 Subject: [PATCH 069/125] 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 070/125] 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 071/125] 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 072/125] 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 073/125] 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 074/125] 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 075/125] 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 076/125] 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 077/125] 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 078/125] 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 079/125] 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 080/125] 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 081/125] 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 082/125] 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 083/125] 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 084/125] 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 085/125] 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 086/125] 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 087/125] 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 088/125] 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 089/125] 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 090/125] 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 091/125] 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 092/125] 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 093/125] 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 094/125] 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 095/125] 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 096/125] 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 097/125] 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 098/125] 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 099/125] 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 100/125] 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 102/125] 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 103/125] 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 104/125] 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 105/125] 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 106/125] 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 107/125] 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 bee01c938a234021d4aa8d6bcb2cb6d421262573 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 5 Dec 2025 21:46:05 +0100 Subject: [PATCH 108/125] 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 109/125] 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 110/125] 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 111/125] 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 112/125] 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 113/125] 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 114/125] 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 115/125] 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 116/125] 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 117/125] 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 118/125] 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 119/125] 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 120/125] 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 121/125] 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 122/125] 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 123/125] 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 124/125] 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 125/125] 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 }