From b0ca4435fdbee019ac72f8df0cb304e1de5deffc Mon Sep 17 00:00:00 2001 From: Ciaran Walsh Date: Wed, 21 Feb 2024 00:21:09 +0000 Subject: [PATCH 001/127] Fix for empty error objects in response breaking extraction of domain validation types Fix for empty error objects in the response which mess up the extraction of domain validation types due to the closing brace in the error object prematurely matching the end of the search pattern. This seems to be a recent change with ZeroSSL in particular where "error":{} is being included in responses. There could potentially be a related issue if there is a complex error object ever returned in the validation check response where an embedded sub-object could lead to an incomplete extraction of the error message, roughly around line 5040. Adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 --- acme.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 38ccaade..34ac49e8 100755 --- a/acme.sh +++ b/acme.sh @@ -4722,7 +4722,8 @@ $_authorizations_map" _debug keyauthorization "$keyauthorization" fi - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$keyauthorization" -a -z "$entry" ]; then @@ -6283,7 +6284,8 @@ _deactivate() { fi _debug "Trigger validation." vtype="$(_getIdType "$_d_domain")" - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then _err "Error, can not get domain token $d" 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 002/127] 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 003/127] 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 004/127] 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 005/127] 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 006/127] 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 007/127] 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 008/127] 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 009/127] 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 010/127] 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 011/127] 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 012/127] 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 2beb2f5659e968423f91c8db2d634d85177fff2a Mon Sep 17 00:00:00 2001 From: Manuel Sanchez Pinar Date: Thu, 4 Jul 2024 14:03:20 +0200 Subject: [PATCH 013/127] fix: rage4 - add error 400 and TXT cleanup The following error happens if the header is set to 'Content-Type: application/json': {"statusCode":400,"message":"One or more errors occurred!", "errors":{"serializerErrors":["The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. LineNumber: 0 | BytePositionInLine: 0."]}} Fix TXT removal --- dnsapi/dns_rage4.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh index ad312759..c27fbc5f 100755 --- a/dnsapi/dns_rage4.sh +++ b/dnsapi/dns_rage4.sh @@ -42,6 +42,14 @@ dns_rage4_add() { _debug _domain_id "$_domain_id" _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1" + + # Response after adding a TXT record should be something like this: + # {"status":true,"id":28160443,"error":null} + if ! _contains "$response" '"error":null' >/dev/null; then + _err "Error while adding TXT record: '$response'" + return 1 + fi + return 0 } @@ -63,7 +71,12 @@ dns_rage4_rm() { _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" - _record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p') + _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p') + if [ -z "$_record_id" ]; then + _err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'." + return 1 + fi + _rage4_rest "deleterecord/?id=${_record_id}" return 0 } @@ -105,8 +118,7 @@ _rage4_rest() { token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"') auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64) - export _H1="Content-Type: application/json" - export _H2="Authorization: Basic $auth" + export _H1="Authorization: Basic $auth" response="$(_get "$RAGE4_Api$ep")" 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 014/127] 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 29342e036f5b3e821be3770686bdef022623ef61 Mon Sep 17 00:00:00 2001 From: kir Date: Tue, 11 Mar 2025 08:07:38 +0000 Subject: [PATCH 015/127] Update _get_root url in dnsapi/dns_fornex.sh --- dnsapi/dns_fornex.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_fornex.sh b/dnsapi/dns_fornex.sh index 91e5491b..dcaa2297 100644 --- a/dnsapi/dns_fornex.sh +++ b/dnsapi/dns_fornex.sh @@ -95,7 +95,7 @@ _get_root() { return 1 fi - if ! _rest GET "dns/domain/"; then + if ! _rest GET "dns/domain/?q=$h"; then return 1 fi From ca73e1f024992fdc1ec7a1fe4383a9d477526bdf Mon Sep 17 00:00:00 2001 From: emueller Date: Mon, 12 May 2025 10:28:35 +0200 Subject: [PATCH 016/127] added deploy/kemplm.sh for deploying certs on Kemp Loadmaster --- deploy/kemplm.sh | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100755 deploy/kemplm.sh diff --git a/deploy/kemplm.sh b/deploy/kemplm.sh new file mode 100755 index 00000000..937cbbca --- /dev/null +++ b/deploy/kemplm.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env sh + +#Here is a script to deploy cert to a Kemp Loadmaster. + +#returns 0 means success, otherwise error. + +#DEPLOY_KEMP_TOKEN="token" +#DEPLOY_KEMP_URL="https://kemplm.example.com" + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +kemplm_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 ! _exists jq; then + _err "jq not found" + fi + + # Rename wildcard certs, kemp accepts only alphanumeric names + _kemp_domain=$(echo "${_cdomain}" | sed 's/\*/wildcard/') + _debug _kemp_domain "$_kemp_domain" + + # Clear traces of incorrectly stored values + _clearaccountconf DEPLOY_KEMP_TOKEN + _clearaccountconf DEPLOY_KEMP_URL + + # Read config from saved values or env + _getdeployconf DEPLOY_KEMP_TOKEN + _getdeployconf DEPLOY_KEMP_URL + + _debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" + _secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" + + if [ -z "$DEPLOY_KEMP_TOKEN" ]; then + _err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN." + return 1 + fi + if [ -z "$DEPLOY_KEMP_URL" ]; then + _err "Kemp Loadmaster url is not found, please define DEPLOY_KEMP_URL." + return 1 + fi + + # Save current values + _savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" + _savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" + + # Do not check for a valid SSL certificate + export HTTPS_INSECURE=1 + + # Check if certificate is already installed + _info "Check if certificate is already present" + _post_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}" + _debug3 _post_request "${_post_request}" + _kemp_cert_count=$(_post "${_post_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "${_kemp_domain}") + _debug2 _kemp_cert_count "${_kemp_cert_count}" + + _kemp_replace_cert=1 + if [ "${_kemp_cert_count}" -eq 0 ]; then + _kemp_replace_cert=0 + _info "Certificate does not exist on Kemp Loadmaster" + else + _info "Certificate already exists on Kemp Loadmaster" + fi + _debug _kemp_replace_cert "${_kemp_replace_cert}" + + # Upload new certificate to Kemp Loadmaster + _kemp_upload_cert=$(_mktemp) + cat "${_cfullchain}" "${_ckey}" | base64 -w 0 > "${_kemp_upload_cert}" + + _info "Uploading certificate to Kemp Loadmaster" + _post_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"$(cat ${_kemp_upload_cert})\"}" + _debug3 _post_request "${_post_request}" + _kemp_post_result=$(_post "${_post_request}" "${DEPLOY_KEMP_URL}/accessv2") + _retval=$? + _debug2 _kemp_post_result "${_kemp_post_result}" + if [ "${_retval}" -eq 0 ]; then + _kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status') + _kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message') + if [ "${_kemp_post_status}" = "ok" ]; then + _info "Upload successful" + else + _err "Upload failed: ${_kemp_post_message}" + fi + else + _err "Upload failed" + _retval=1 + fi + + rm "${_kemp_upload_cert}" + + return $retval +} From 7543d5220cfa01f42041e8d95ecc952b81e92987 Mon Sep 17 00:00:00 2001 From: emueller Date: Mon, 12 May 2025 10:45:01 +0200 Subject: [PATCH 017/127] fixed kemplm.sh formatting --- deploy/kemplm.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/kemplm.sh b/deploy/kemplm.sh index 937cbbca..3f762d75 100755 --- a/deploy/kemplm.sh +++ b/deploy/kemplm.sh @@ -23,11 +23,11 @@ kemplm_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - if ! _exists jq; then - _err "jq not found" - fi + if ! _exists jq; then + _err "jq not found" + fi - # Rename wildcard certs, kemp accepts only alphanumeric names + # Rename wildcard certs, kemp accepts only alphanumeric names _kemp_domain=$(echo "${_cdomain}" | sed 's/\*/wildcard/') _debug _kemp_domain "$_kemp_domain" @@ -76,7 +76,7 @@ kemplm_deploy() { # Upload new certificate to Kemp Loadmaster _kemp_upload_cert=$(_mktemp) - cat "${_cfullchain}" "${_ckey}" | base64 -w 0 > "${_kemp_upload_cert}" + cat "${_cfullchain}" "${_ckey}" | base64 -w 0 >"${_kemp_upload_cert}" _info "Uploading certificate to Kemp Loadmaster" _post_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"$(cat ${_kemp_upload_cert})\"}" From bf2e99efa69b76bc5e495a73cbcf97221dce40ff Mon Sep 17 00:00:00 2001 From: emueller Date: Mon, 12 May 2025 10:52:35 +0200 Subject: [PATCH 018/127] fixed quoting in kemplm.sh --- deploy/kemplm.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/kemplm.sh b/deploy/kemplm.sh index 3f762d75..fbe25cd8 100755 --- a/deploy/kemplm.sh +++ b/deploy/kemplm.sh @@ -79,7 +79,8 @@ kemplm_deploy() { cat "${_cfullchain}" "${_ckey}" | base64 -w 0 >"${_kemp_upload_cert}" _info "Uploading certificate to Kemp Loadmaster" - _post_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"$(cat ${_kemp_upload_cert})\"}" + _post_data=$(cat "${_kemp_upload_cert}") + _post_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_post_data}\"}" _debug3 _post_request "${_post_request}" _kemp_post_result=$(_post "${_post_request}" "${DEPLOY_KEMP_URL}/accessv2") _retval=$? @@ -99,5 +100,5 @@ kemplm_deploy() { rm "${_kemp_upload_cert}" - return $retval + return $_retval } From e0da5f170304dc373a4a647d83fbe0a13a53ec7e Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Mon, 19 May 2025 09:49:21 +0800 Subject: [PATCH 019/127] Update dns_la.sh --- dnsapi/dns_la.sh | 109 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 26 deletions(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index f19333c4..97437897 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -1,14 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_la_info='dns.la -Site: dns.la -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la -Options: - LA_Id API ID - LA_Key API key -Issues: github.com/acmesh-official/acme.sh/issues/4257 -' +# LA_Id="123" +# LA_Sk="456" +# LA_Token="" +# +#Site: dns.la +#Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la +#Options: +#我的账户 API 密钥 中获取 APIID APISecret +# LA_Id APIID +# LA_Key APISecret +# LA_Token 用冒号连接 APIID APISecret 再base64生成 +#Issues: github.com/acmesh-official/acme.sh/issues/4257 +#' LA_Api="https://api.dns.la/api" ######## Public functions ##################### @@ -19,18 +23,23 @@ dns_la_add() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" + LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" + _log "LA_Id=$LA_Id" + _log "LA_Sk=$LA_Sk" - if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then + if [ -z "$LA_Id" ] || [ -z "$LA_Sk" ]; then LA_Id="" - LA_Key="" + LA_Sk="" _err "You didn't specify a dnsla api id and key yet." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable LA_Id "$LA_Id" - _saveaccountconf_mutable LA_Key "$LA_Key" + _saveaccountconf_mutable LA_Sk "$LA_Sk" + + # generate dnsla token + _la_token _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -42,11 +51,13 @@ dns_la_add() { _debug _domain "$_domain" _info "Adding record" - if _la_rest "record.ashx?cmd=create&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue&recordline="; then - if _contains "$response" '"resultid":'; then + + # record type is enum in new api, 16 for TXT + if _la_post "{\"domainId\":\"$_domain_id\",\"type\":16,\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":600}" "record"; then + if _contains "$response" '"id":'; then _info "Added, OK" return 0 - elif _contains "$response" '"code":532'; then + elif _contains "$response" '"msg":"与已有记录冲突"'; then _info "Already exists, OK" return 0 else @@ -54,7 +65,7 @@ dns_la_add() { return 1 fi fi - _err "Add txt record error." + _err "Add txt record failed." return 1 } @@ -65,7 +76,9 @@ dns_la_rm() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" + LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" + + _la_token _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -77,27 +90,29 @@ dns_la_rm() { _debug _domain "$_domain" _debug "Getting txt records" - if ! _la_rest "record.ashx?cmd=listn&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue"; then + # record type is enum in new api, 16 for TXT + if ! _la_get "recordList?pageIndex=1&pageSize=10&domainId=$_domain_id&host=$_sub_domain&type=16&data=$txtvalue"; then _err "Error" return 1 fi - if ! _contains "$response" '"recordid":'; then + if ! _contains "$response" '"id":'; then _info "Don't need to remove." return 0 fi - record_id=$(printf "%s" "$response" | grep '"recordid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n') + record_id=$(printf "%s" "$response" | grep '"id":' | head -n1 | sed 's/.*"id": *"\([^"]*\)".*/\1/') _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi - if ! _la_rest "record.ashx?cmd=remove&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&recordid=$record_id"; then + # remove record in new api is RESTful + if ! _la_post "" "record?id=$record_id" "DELETE"; then _err "Delete record error." return 1 fi - _contains "$response" '"code":300' + _contains "$response" '"code":200' } @@ -119,12 +134,13 @@ _get_root() { return 1 fi - if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then + if ! _la_get "domain?domain=$h"; then return 1 fi - if _contains "$response" '"domainid":'; then - _domain_id=$(printf "%s" "$response" | grep '"domainid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n') + if _contains "$response" '"domain":'; then + _domain_id=$(echo "$response" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p') + _log "_domain_id" "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" @@ -143,6 +159,21 @@ _la_rest() { url="$LA_Api/$1" _debug "$url" + if ! response="$(_get "$url" "Authorization: Basic $LA_Token" | tr -d ' ' | tr "}" ",")"; then + _err "Error: $url" + return 1 + fi + + _debug2 response "$response" + return 0 +} + +_la_get() { + url="$LA_Api/$1" + _debug "$url" + + export _H1="Authorization: Basic $LA_Token" + if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then _err "Error: $url" return 1 @@ -151,3 +182,29 @@ _la_rest() { _debug2 response "$response" return 0 } + +# Usage: _la_post body url [POST|PUT|DELETE] +_la_post() { + body=$1 + url="$LA_Api/$2" + http_method=$3 + _debug "$body" + _debug "$url" + + export _H1="Authorization: Basic $LA_Token" + + if ! response="$(_post "$body" "$url" "" "$http_method")"; then + _err "Error: $url" + return 1 + fi + + _debug2 response "$response" + return 0 +} + +_la_token() { + LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | base64 -w 0) + _debug "$LA_Token" + + return 0 +} From 9e7d1b9ce75373c4233790527054925f42da5d16 Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Mon, 19 May 2025 13:16:30 +0800 Subject: [PATCH 020/127] Update dns_la.sh --- dnsapi/dns_la.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index 97437897..ba8ebcac 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -203,7 +203,7 @@ _la_post() { } _la_token() { - LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | base64 -w 0) + LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | _base64) _debug "$LA_Token" return 0 From 55282851c4a890369bece6f3c5b8082f91f2d1ad Mon Sep 17 00:00:00 2001 From: emueller Date: Mon, 19 May 2025 09:18:29 +0200 Subject: [PATCH 021/127] implemented all suggestions --- deploy/kemplm.sh | 52 +++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/deploy/kemplm.sh b/deploy/kemplm.sh index fbe25cd8..e44e06dc 100755 --- a/deploy/kemplm.sh +++ b/deploy/kemplm.sh @@ -11,30 +11,27 @@ #domain keyfile certfile cafile fullchain kemplm_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" + _domain="$1" + _key_file="$2" + _cert_file="$3" + _ca_file="$4" + _fullchain_file="$5" + + _debug _domain "$_domain" + _debug _key_file "$_key_file" + _debug _cert_file "$_cert_file" + _debug _ca_file "$_ca_file" + _debug _fullchain_file "$_fullchain_file" if ! _exists jq; then _err "jq not found" + return 1 fi - # Rename wildcard certs, kemp accepts only alphanumeric names - _kemp_domain=$(echo "${_cdomain}" | sed 's/\*/wildcard/') + # Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename + _kemp_domain=$(echo "${_domain}" | sed 's/\*\.//') _debug _kemp_domain "$_kemp_domain" - # Clear traces of incorrectly stored values - _clearaccountconf DEPLOY_KEMP_TOKEN - _clearaccountconf DEPLOY_KEMP_URL - # Read config from saved values or env _getdeployconf DEPLOY_KEMP_TOKEN _getdeployconf DEPLOY_KEMP_URL @@ -47,7 +44,7 @@ kemplm_deploy() { return 1 fi if [ -z "$DEPLOY_KEMP_URL" ]; then - _err "Kemp Loadmaster url is not found, please define DEPLOY_KEMP_URL." + _err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL." return 1 fi @@ -55,14 +52,11 @@ kemplm_deploy() { _savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" _savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" - # Do not check for a valid SSL certificate - export HTTPS_INSECURE=1 - # Check if certificate is already installed _info "Check if certificate is already present" - _post_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}" - _debug3 _post_request "${_post_request}" - _kemp_cert_count=$(_post "${_post_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "${_kemp_domain}") + _list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}" + _debug3 _list_request "${_list_request}" + _kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "${_kemp_domain}") _debug2 _kemp_cert_count "${_kemp_cert_count}" _kemp_replace_cert=1 @@ -76,13 +70,13 @@ kemplm_deploy() { # Upload new certificate to Kemp Loadmaster _kemp_upload_cert=$(_mktemp) - cat "${_cfullchain}" "${_ckey}" | base64 -w 0 >"${_kemp_upload_cert}" + cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}" _info "Uploading certificate to Kemp Loadmaster" - _post_data=$(cat "${_kemp_upload_cert}") - _post_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_post_data}\"}" - _debug3 _post_request "${_post_request}" - _kemp_post_result=$(_post "${_post_request}" "${DEPLOY_KEMP_URL}/accessv2") + _add_data=$(cat "${_kemp_upload_cert}") + _add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}" + _debug3 _add_request "${_add_request}" + _kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2") _retval=$? _debug2 _kemp_post_result "${_kemp_post_result}" if [ "${_retval}" -eq 0 ]; then From 500cfbc19c08feab8763fd141181a5820290747e Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Mon, 19 May 2025 21:29:33 +0800 Subject: [PATCH 022/127] Update dns_la.sh --- dnsapi/dns_la.sh | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index ba8ebcac..7c3765cd 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -2,17 +2,16 @@ # LA_Id="123" # LA_Sk="456" -# LA_Token="" -# -#Site: dns.la -#Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la -#Options: -#我的账户 API 密钥 中获取 APIID APISecret -# LA_Id APIID -# LA_Key APISecret -# LA_Token 用冒号连接 APIID APISecret 再base64生成 -#Issues: github.com/acmesh-official/acme.sh/issues/4257 -#' +# shellcheck disable=SC2034 +LA_Token='dns.la +Site: dns.la +Docs: https://www.dns.la/docs/ApiDoc +Options: + LA_Id APIID + LA_Key APISecret + LA_Token 用冒号连接 APIID APISecret 再base64生成 +Issues: github.com/acmesh-official/acme.sh/issues/4257 +' LA_Api="https://api.dns.la/api" ######## Public functions ##################### From cddf098f47cde203dee97335176ef04bc7f3cdcc Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Tue, 20 May 2025 20:28:59 +0800 Subject: [PATCH 023/127] Update dns_la.sh --- dnsapi/dns_la.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index 7c3765cd..651b74c0 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -5,10 +5,10 @@ # shellcheck disable=SC2034 LA_Token='dns.la Site: dns.la -Docs: https://www.dns.la/docs/ApiDoc +Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la Options: LA_Id APIID - LA_Key APISecret + LA_Sk APISecret LA_Token 用冒号连接 APIID APISecret 再base64生成 Issues: github.com/acmesh-official/acme.sh/issues/4257 ' From c8f1e4119719a911087dc266090ca04eb7dd20c1 Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Tue, 20 May 2025 20:29:44 +0800 Subject: [PATCH 024/127] Update dns_la.sh --- dnsapi/dns_la.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index 651b74c0..c2934b54 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -5,7 +5,7 @@ # shellcheck disable=SC2034 LA_Token='dns.la Site: dns.la -Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la Options: LA_Id APIID LA_Sk APISecret From 28687ad7c7ac849d3201f6c7d8add0065718b60e Mon Sep 17 00:00:00 2001 From: Marcel Schlegel Date: Sat, 31 May 2025 15:02:25 +0200 Subject: [PATCH 025/127] Issue 3968: Fix missing api password encoding. --- dnsapi/dns_cloudns.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh index 8bb0e00d..23a219da 100755 --- a/dnsapi/dns_cloudns.sh +++ b/dnsapi/dns_cloudns.sh @@ -197,10 +197,11 @@ _dns_cloudns_http_api_call() { auth_user="auth-id=$CLOUDNS_AUTH_ID" fi + encoded_password=$(echo "$CLOUDNS_AUTH_PASSWORD" | tr -d "\n\r" | _url_encode) if [ -z "$2" ]; then - data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD" + data="$auth_user&auth-password=$encoded_password" else - data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" + data="$auth_user&auth-password=$encoded_password&$2" fi response="$(_get "$CLOUDNS_API/$method?$data")" From 19678db9333f901219befcbd388fc454aa6b7119 Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Fri, 6 Jun 2025 02:06:27 +0800 Subject: [PATCH 026/127] Update dns_la.sh --- dnsapi/dns_la.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index c2934b54..772f8845 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -3,7 +3,7 @@ # LA_Id="123" # LA_Sk="456" # shellcheck disable=SC2034 -LA_Token='dns.la +dns_la_info='dns.la Site: dns.la Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la Options: From bff1064dbd205db86832c876151304b97bf9d78f Mon Sep 17 00:00:00 2001 From: Lambiek12 Date: Sun, 8 Jun 2025 15:39:10 +0200 Subject: [PATCH 027/127] Add new dnsapi support for OpenProvider.eu using new REST API --- dnsapi/dns_openprovider_rest.sh | 193 ++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 dnsapi/dns_openprovider_rest.sh diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh new file mode 100644 index 00000000..a6725fcc --- /dev/null +++ b/dnsapi/dns_openprovider_rest.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_openprovider_rest_info='OpenProvider (REST) +Domains: OpenProvider.com +Site: OpenProvider.eu +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest +Options: + OPENPROVIDER_REST_USERNAME Openprovider Account Username + OPENPROVIDER_REST_PASSWORD Openprovider Account Password +Issues: github.com/acmesh-official/acme.sh/issues/6122 +Author: Lambiek12 +' + +OPENPROVIDER_API_URL="https://api.openprovider.eu/v1beta" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_openprovider_rest_add() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + addzonerecordrequestparameters="dns/zones/$_domain_name" + addzonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"add\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"$txtvalue\"}]}}" + + if _openprovider_rest PUT "$addzonerecordrequestparameters" "$addzonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + elif _contains "$response" "\"Duplicate record\""; then + _debug "Record already existed" + return 0 + else + _err "Adding TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Adding TXT record failed due to errors." + return 1 +} + +# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to remove the txt record after validation +dns_openprovider_rest_rm() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + removezonerecordrequestparameters="dns/zones/$_domain_name" + removezonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"remove\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"\\\"$txtvalue\\\"\"}]}}" + + if _openprovider_rest PUT "$removezonerecordrequestparameters" "$removezonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + else + _err "Removing TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Removing TXT record failed due to errors." + return 1 +} + +#################### OpenProvider API common functions #################### +_openprovider_prepare_credentials() { + OPENPROVIDER_REST_USERNAME="${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}" + OPENPROVIDER_REST_PASSWORD="${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}" + + if [ -z "$OPENPROVIDER_REST_USERNAME" ] || [ -z "$OPENPROVIDER_REST_PASSWORD" ]; then + OPENPROVIDER_REST_USERNAME="" + OPENPROVIDER_REST_PASSWORD="" + _err "You didn't specify the Openprovider username or password yet." + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable OPENPROVIDER_REST_USERNAME "$OPENPROVIDER_REST_USERNAME" + _saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD "$OPENPROVIDER_REST_PASSWORD" +} + +_openprovider_rest() { + httpmethod=$1 + queryparameters=$2 + requestbody=$3 + + _openprovider_rest_login + if [ -z "$openproviderauthtoken" ]; then + _err "Unable to fetch authentication token from Openprovider API." + return 1 + fi + + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $openproviderauthtoken" + + _debug httpmethod "$httpmethod" + _debug requestfullurl "$OPENPROVIDER_API_URL/$queryparameters" + _debug queryparameters "$queryparameters" + + if [ "$httpmethod" != "GET" ]; then + _debug requestbody "$requestbody" + + response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" + else + response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")" + fi + + if [ "$?" != "0" ]; then + _err "No valid parameters supplied for Openprovider API: Error $queryparameters" + return 1 + fi + + _debug2 response "$response" + + return 0 +} + +_openprovider_rest_login() { + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + + loginrequesturl="$OPENPROVIDER_API_URL/auth/login" + loginrequestbody="{\"ip\":\"0.0.0.0\",\"password\":\"$OPENPROVIDER_REST_PASSWORD\",\"username\":\"$OPENPROVIDER_REST_USERNAME\"}" + loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")" + + openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" + _debug openproviderauthtoken "$openproviderauthtoken" + + export openproviderauthtoken +} + +#################### Private functions ################################## + +# Usage: _get_dns_zone _acme-challenge.www.domain.com +# Returns: +# _domain_id=123456789 +# _domain_name=domain.com +# _sub_domain=_acme-challenge.www +_get_dns_zone() { + domain=$1 + i=1 + p=1 + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + if [ -z "$h" ]; then + # Empty value not allowed + return 1 + fi + + if ! _openprovider_rest GET "dns/zones/$h" ""; then + return 1 + fi + + if _contains "$response" "\"name\":\"$h\""; then + _domain_id="$(printf "%s\n" "$response" | _egrep_o '"id" *: *[^,]*' | _head_n 1 | sed 's#^"id" *: *##')" + _debug _domain_id "$_domain_id" + + _domain_name="$h" + _debug _domain_name "$_domain_name" + + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _debug _sub_domain "$_sub_domain" + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + + return 1 +} From 06d3739a8dc9b235aadc0460d7ddcbb2e867a04e Mon Sep 17 00:00:00 2001 From: Lambiek12 Date: Sun, 8 Jun 2025 17:29:39 +0200 Subject: [PATCH 028/127] Cleanup duplicate debug log output based on DNS test run --- dnsapi/dns_openprovider_rest.sh | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh index a6725fcc..3b8d20d2 100644 --- a/dnsapi/dns_openprovider_rest.sh +++ b/dnsapi/dns_openprovider_rest.sh @@ -113,14 +113,8 @@ _openprovider_rest() { export _H1="Content-Type: application/json" export _H2="Accept: application/json" export _H3="Authorization: Bearer $openproviderauthtoken" - - _debug httpmethod "$httpmethod" - _debug requestfullurl "$OPENPROVIDER_API_URL/$queryparameters" - _debug queryparameters "$queryparameters" - + if [ "$httpmethod" != "GET" ]; then - _debug requestbody "$requestbody" - response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" else response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")" @@ -145,7 +139,6 @@ _openprovider_rest_login() { loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")" openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" - _debug openproviderauthtoken "$openproviderauthtoken" export openproviderauthtoken } From fcd358eb71e0c8ca49b11e2fbca133c9c5937844 Mon Sep 17 00:00:00 2001 From: Lambiek12 Date: Sun, 8 Jun 2025 17:35:09 +0200 Subject: [PATCH 029/127] Resolve spellcheck error --- dnsapi/dns_openprovider_rest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh index 3b8d20d2..210dc6fc 100644 --- a/dnsapi/dns_openprovider_rest.sh +++ b/dnsapi/dns_openprovider_rest.sh @@ -113,7 +113,7 @@ _openprovider_rest() { export _H1="Content-Type: application/json" export _H2="Accept: application/json" export _H3="Authorization: Bearer $openproviderauthtoken" - + if [ "$httpmethod" != "GET" ]; then response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" else From f2b248243c1de3f1ddb740cab828f1eb580d744a Mon Sep 17 00:00:00 2001 From: Erwin Oegema Date: Tue, 10 Jun 2025 10:36:06 +0200 Subject: [PATCH 030/127] Configure 10 second timeout to ACME_DIRECTORY API call --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index e9eb6b94..b8630742 100755 --- a/acme.sh +++ b/acme.sh @@ -2761,7 +2761,7 @@ _initAPI() { _request_retry_times=0 while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do _request_retry_times=$(_math "$_request_retry_times" + 1) - response=$(_get "$_api_server") + response=$(_get "$_api_server" "" 10) if [ "$?" != "0" ]; then _debug2 "response" "$response" _info "Cannot init API for: $_api_server." From 242085d6765e43ad08a6b1863ec586c789a3f483 Mon Sep 17 00:00:00 2001 From: laDanz Date: Tue, 17 Jun 2025 14:05:40 +0200 Subject: [PATCH 031/127] add support for AIX style netstat --- acme.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acme.sh b/acme.sh index e9eb6b94..e8378754 100755 --- a/acme.sh +++ b/acme.sh @@ -1401,6 +1401,12 @@ _ss() { return 0 fi + if [ "$(uname)" = "AIX" ]; then + _debug "Using: AIX netstat" + netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port " + return 0 + fi + if _exists "netstat"; then _debug "Using: netstat" if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then From 4a16aaacb6a47d60dee9da2cd4f20fb101f5a47b Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 21:27:08 +0200 Subject: [PATCH 032/127] add --- .github/workflows/wiki-monitor.yml | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/wiki-monitor.yml diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml new file mode 100644 index 00000000..b86347cf --- /dev/null +++ b/.github/workflows/wiki-monitor.yml @@ -0,0 +1,32 @@ +name: Notify via Issue on Wiki Edit + +on: + gollum: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Generate wiki change message + run: | + echo "Wiki page:" > wiki-change-msg.txt + echo "User: ${{ github.actor }}" >> wiki-change-msg.txt + echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" >> wiki-change-msg.txt + echo "" >> wiki-change-msg.txt + for page in $(jq -r '.gollum.pages[].html_url' "$GITHUB_EVENT_PATH"); do + echo "Path: $page" >> wiki-change-msg.txt + done + + - name: Create issue to notify Neilpang + uses: peter-evans/create-issue-from-file@v5 + with: + title: "Wiki page" + content-filepath: ./wiki-change-msg.txt + assignees: Neilpang + env: + TZ: Asia/Shanghai + + + + + From b025e7f0f280b33bbca418fa53a6634f4adcdb4f Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 21:46:58 +0200 Subject: [PATCH 033/127] fix for wiki --- .github/workflows/wiki-monitor.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index b86347cf..1955043e 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -9,18 +9,17 @@ jobs: steps: - name: Generate wiki change message run: | - echo "Wiki page:" > wiki-change-msg.txt + sudo apt-get update && sudo apt-get install -y jq + echo "Wiki page edited" > wiki-change-msg.txt echo "User: ${{ github.actor }}" >> wiki-change-msg.txt echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" >> wiki-change-msg.txt echo "" >> wiki-change-msg.txt - for page in $(jq -r '.gollum.pages[].html_url' "$GITHUB_EVENT_PATH"); do - echo "Path: $page" >> wiki-change-msg.txt - done + jq -r '.gollum.pages[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt - name: Create issue to notify Neilpang uses: peter-evans/create-issue-from-file@v5 with: - title: "Wiki page" + title: "Wiki edited" content-filepath: ./wiki-change-msg.txt assignees: Neilpang env: From 89071f722642248e54df7f627ed40943c5b622c7 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 21:51:55 +0200 Subject: [PATCH 034/127] minor --- .github/workflows/wiki-monitor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index 1955043e..c9432841 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -14,7 +14,8 @@ jobs: echo "User: ${{ github.actor }}" >> wiki-change-msg.txt echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" >> wiki-change-msg.txt echo "" >> wiki-change-msg.txt - jq -r '.gollum.pages[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt + cat "$GITHUB_EVENT_PATH" + jq -r '.gollum.pages // [] | .[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt - name: Create issue to notify Neilpang uses: peter-evans/create-issue-from-file@v5 From 6966b3810ddf13fc0874116dfb8060aa58177e97 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 22:01:11 +0200 Subject: [PATCH 035/127] minor --- .github/workflows/wiki-monitor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index c9432841..c29d92c7 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -15,7 +15,7 @@ jobs: echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" >> wiki-change-msg.txt echo "" >> wiki-change-msg.txt cat "$GITHUB_EVENT_PATH" - jq -r '.gollum.pages // [] | .[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt + jq -r '.pages // [] | .[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt - name: Create issue to notify Neilpang uses: peter-evans/create-issue-from-file@v5 From 424d33faa082e3f41789ee5ec2ee7e3e280fcdc4 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 22:11:35 +0200 Subject: [PATCH 036/127] wiki --- .github/workflows/wiki-monitor.yml | 42 +++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index c29d92c7..1e216f2e 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -7,15 +7,43 @@ jobs: notify: runs-on: ubuntu-latest steps: + - name: Checkout wiki repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + - name: Generate wiki change message run: | - sudo apt-get update && sudo apt-get install -y jq - echo "Wiki page edited" > wiki-change-msg.txt - echo "User: ${{ github.actor }}" >> wiki-change-msg.txt - echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" >> wiki-change-msg.txt - echo "" >> wiki-change-msg.txt - cat "$GITHUB_EVENT_PATH" - jq -r '.pages // [] | .[] | "Page: \(.html_url) (action: \(.action))"' "$GITHUB_EVENT_PATH" >> wiki-change-msg.txt + actor="${{ github.actor }}" + sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") + page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") + 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") + now="$(date '+%Y-%m-%d %H:%M:%S')" + + cd wiki + prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) + if [ -n "$prev_sha" ]; then + git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff + else + echo "(no diff)" > ../wiki.diff + fi + cd .. + { + echo "Wiki edited" + echo -n "User: " + echo "[$actor]($sender_url)" + echo "Time: $now" + echo "Page: [$page_name]($page_url) (Action: $page_action)" + echo "" + echo "----" + echo "### diff:" + echo '```diff' + cat wiki.diff + echo '```' + } > wiki-change-msg.txt - name: Create issue to notify Neilpang uses: peter-evans/create-issue-from-file@v5 From 74fdf649d3883f1fee6fdb0128f8f146ef9c9e83 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 25 Jun 2025 22:14:30 +0200 Subject: [PATCH 037/127] wiki --- .github/workflows/wiki-monitor.yml | 58 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index 1e216f2e..89497580 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -15,35 +15,35 @@ jobs: - name: Generate wiki change message run: | - actor="${{ github.actor }}" - sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") - page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") - 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") - now="$(date '+%Y-%m-%d %H:%M:%S')" - - cd wiki - prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) - if [ -n "$prev_sha" ]; then - git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff - else - echo "(no diff)" > ../wiki.diff - fi - cd .. - { - echo "Wiki edited" - echo -n "User: " - echo "[$actor]($sender_url)" - echo "Time: $now" - echo "Page: [$page_name]($page_url) (Action: $page_action)" - echo "" - echo "----" - echo "### diff:" - echo '```diff' - cat wiki.diff - echo '```' - } > wiki-change-msg.txt + actor="${{ github.actor }}" + sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") + page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") + 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") + now="$(date '+%Y-%m-%d %H:%M:%S')" + + cd wiki + prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) + if [ -n "$prev_sha" ]; then + git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff + else + echo "(no diff)" > ../wiki.diff + fi + cd .. + { + echo "Wiki edited" + echo -n "User: " + echo "[$actor]($sender_url)" + echo "Time: $now" + echo "Page: [$page_name]($page_url) (Action: $page_action)" + echo "" + echo "----" + echo "### diff:" + echo '```diff' + cat wiki.diff + echo '```' + } > wiki-change-msg.txt - name: Create issue to notify Neilpang uses: peter-evans/create-issue-from-file@v5 From 2bea808251d3e0c65fab47dafba0fec636128a6a Mon Sep 17 00:00:00 2001 From: OPPO9008 <41640509+OPPO9008@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:15:46 +0800 Subject: [PATCH 038/127] Update dns_la.sh --- dnsapi/dns_la.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index 772f8845..9cb6327e 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -100,7 +100,7 @@ dns_la_rm() { return 0 fi - record_id=$(printf "%s" "$response" | grep '"id":' | head -n1 | sed 's/.*"id": *"\([^"]*\)".*/\1/') + record_id=$(printf "%s" "$response" | grep '"id":' | _head_n 1 | sed 's/.*"id": *"\([^"]*\)".*/\1/') _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." From 76b68f7ccb3063a0d065ab1009d70156d3f5d135 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:34:21 +0300 Subject: [PATCH 039/127] dnsapi: dns_mydnsjp.sh fix author The @epgdatacapbon was renamed to @tkmsst Signed-off-by: Sergey Ponomarev --- dnsapi/dns_mydnsjp.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh index 336c4889..4dfffaaa 100755 --- a/dnsapi/dns_mydnsjp.sh +++ b/dnsapi/dns_mydnsjp.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp Options: MYDNSJP_MasterID Master ID MYDNSJP_Password Password -Author: epgdatacapbon +Author: @tkmsst ' ######## Public functions ##################### From 01ed3c332648104652c6b7af3ff4518ebe3a32f2 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:36:40 +0300 Subject: [PATCH 040/127] dnsapi: dns_ddnss.sh remove RaidenII from authors He made the DuckDNS script that was used for this script but he can't support the script. Signed-off-by: Sergey Ponomarev --- dnsapi/dns_ddnss.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh index 118b148b..a624a268 100644 --- a/dnsapi/dns_ddnss.sh +++ b/dnsapi/dns_ddnss.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss Options: DDNSS_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2230 -Author: RaidenII, helbgd, mod242 +Author: helbgd, mod242 ' DDNSS_DNS_API="https://ddnss.de/upd.php" From c6819cbd6b40d95e1b0f03273ee6a598cd9794a6 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:40:53 +0300 Subject: [PATCH 041/127] dnsapi: fix authors: use @ for GitHub profiles Signed-off-by: Sergey Ponomarev --- dnsapi/dns_bookmyname.sh | 2 +- dnsapi/dns_ddnss.sh | 2 +- dnsapi/dns_dnshome.sh | 2 +- dnsapi/dns_duckdns.sh | 2 +- dnsapi/dns_dyn.sh | 2 +- dnsapi/dns_dynv6.sh | 2 +- dnsapi/dns_easydns.sh | 2 +- dnsapi/dns_freedns.sh | 2 +- dnsapi/dns_joker.sh | 2 +- dnsapi/dns_mijnhost.sh | 2 +- dnsapi/dns_namecom.sh | 2 +- dnsapi/dns_namesilo.sh | 2 +- dnsapi/dns_pleskxml.sh | 2 +- dnsapi/dns_schlundtech.sh | 2 +- dnsapi/dns_spaceship.sh | 2 +- dnsapi/dns_tele3.sh | 2 +- dnsapi/dns_timeweb.sh | 2 +- dnsapi/dns_udr.sh | 2 +- dnsapi/dns_vscale.sh | 2 +- dnsapi/dns_websupport.sh | 2 +- dnsapi/dns_world4you.sh | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dnsapi/dns_bookmyname.sh b/dnsapi/dns_bookmyname.sh index 668cf074..cf3f1e3e 100644 --- a/dnsapi/dns_bookmyname.sh +++ b/dnsapi/dns_bookmyname.sh @@ -7,7 +7,7 @@ Options: BOOKMYNAME_USERNAME Username BOOKMYNAME_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3209 -Author: Neilpang +Author: @Neilpang ' ######## Public functions ##################### diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh index a624a268..0ac353d4 100644 --- a/dnsapi/dns_ddnss.sh +++ b/dnsapi/dns_ddnss.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss Options: DDNSS_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2230 -Author: helbgd, mod242 +Author: @helbgd, @mod242 ' DDNSS_DNS_API="https://ddnss.de/upd.php" diff --git a/dnsapi/dns_dnshome.sh b/dnsapi/dns_dnshome.sh index 59828796..6d583246 100755 --- a/dnsapi/dns_dnshome.sh +++ b/dnsapi/dns_dnshome.sh @@ -7,7 +7,7 @@ Options: DNSHOME_Subdomain Subdomain DNSHOME_SubdomainPassword Subdomain Password Issues: github.com/acmesh-official/acme.sh/issues/3819 -Author: dnsHome.de https://github.com/dnsHome-de +Author: @dnsHome-de ' # Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" diff --git a/dnsapi/dns_duckdns.sh b/dnsapi/dns_duckdns.sh index 71594873..33d401b0 100755 --- a/dnsapi/dns_duckdns.sh +++ b/dnsapi/dns_duckdns.sh @@ -5,7 +5,7 @@ Site: www.DuckDNS.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns Options: DuckDNS_Token API Token -Author: RaidenII +Author: @RaidenII ' DuckDNS_API="https://www.duckdns.org/update" diff --git a/dnsapi/dns_dyn.sh b/dnsapi/dns_dyn.sh index 94201923..9b1a97a2 100644 --- a/dnsapi/dns_dyn.sh +++ b/dnsapi/dns_dyn.sh @@ -8,7 +8,7 @@ Options: DYN_Customer Customer DYN_Username API Username DYN_Password Secret -Author: Gerd Naschenweng +Author: Gerd Naschenweng <@magicdude4eva> ' # Dyn Managed DNS API diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh index 76af17f5..0c9491f8 100644 --- a/dnsapi/dns_dynv6.sh +++ b/dnsapi/dns_dynv6.sh @@ -8,7 +8,7 @@ Options: OptionsAlt: KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6" Issues: github.com/acmesh-official/acme.sh/issues/2702 -Author: StefanAbl +Author: @StefanAbl ' dynv6_api="https://dynv6.com/api/v2" diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh index 1c96ac8f..423def2b 100644 --- a/dnsapi/dns_easydns.sh +++ b/dnsapi/dns_easydns.sh @@ -7,7 +7,7 @@ Options: EASYDNS_Token API Token EASYDNS_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/2647 -Author: Neilpang, wurzelpanzer +Author: @Neilpang, wurzelpanzer ' # API Documentation: https://sandbox.rest.easydns.net:3001/ diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 114f30e0..13d9f68b 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -7,7 +7,7 @@ Options: FREEDNS_User Username FREEDNS_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2305 -Author: David Kerr +Author: David Kerr <@dkerr64> ' ######## Public functions ##################### diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh index 1fe33c67..401471be 100644 --- a/dnsapi/dns_joker.sh +++ b/dnsapi/dns_joker.sh @@ -7,7 +7,7 @@ Options: JOKER_USERNAME Username JOKER_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2840 -Author: +Author: @aattww ' JOKER_API="https://svc.joker.com/nic/replace" diff --git a/dnsapi/dns_mijnhost.sh b/dnsapi/dns_mijnhost.sh index 9dafc702..52a81632 100644 --- a/dnsapi/dns_mijnhost.sh +++ b/dnsapi/dns_mijnhost.sh @@ -5,7 +5,7 @@ Domains: mijn.host Site: mijn.host Docs: https://mijn.host/api/doc/ Issues: https://github.com/acmesh-official/acme.sh/issues/6177 -Author: peterv99 +Author: @peterv99 Options: MIJNHOST_API_KEY API Key ' diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh index 44549c9e..1062c849 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecom Options: Namecom_Username Username Namecom_Token API Token -Author: RaidenII +Author: @RaidenII ' ######## Public functions ##################### diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh index b31e32a1..5d47a59a 100755 --- a/dnsapi/dns_namesilo.sh +++ b/dnsapi/dns_namesilo.sh @@ -5,7 +5,7 @@ Site: NameSilo.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo Options: Namesilo_Key API Key -Author: meowthink +Author: @meowthink ' #Utilize API to finish dns-01 verifications. diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 6b38abcb..465bcc60 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -8,7 +8,7 @@ Options: pleskxml_user Username pleskxml_pass Password Issues: github.com/acmesh-official/acme.sh/issues/2577 -Author: Stilez, +Author: @Stilez, @romanlum ' ## Plesk XML API described at: diff --git a/dnsapi/dns_schlundtech.sh b/dnsapi/dns_schlundtech.sh index 6d2930a2..21930110 100644 --- a/dnsapi/dns_schlundtech.sh +++ b/dnsapi/dns_schlundtech.sh @@ -7,7 +7,7 @@ Options: SCHLUNDTECH_USER Username SCHLUNDTECH_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2246 -Author: +Author: @mod242 ' SCHLUNDTECH_API="https://gateway.schlundtech.de" diff --git a/dnsapi/dns_spaceship.sh b/dnsapi/dns_spaceship.sh index 770e22cc..5e92a4fe 100644 --- a/dnsapi/dns_spaceship.sh +++ b/dnsapi/dns_spaceship.sh @@ -8,7 +8,7 @@ Options: SPACESHIP_API_SECRET Spaceship API Secret SPACESHIP_ROOT_DOMAIN (Optional) Manually specify the root domain if auto-detection fails Issues: github.com/acmesh-official/acme.sh/issues/6304 -Author: Meow +Author: Meow <@Meo597> ' # Spaceship API diff --git a/dnsapi/dns_tele3.sh b/dnsapi/dns_tele3.sh index e5974951..3a3ccf8c 100644 --- a/dnsapi/dns_tele3.sh +++ b/dnsapi/dns_tele3.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3 Options: TELE3_Key API Key TELE3_Secret API Secret -Author: Roman Blizik +Author: Roman Blizik <@par-pa> ' TELE3_API="https://www.tele3.cz/acme/" diff --git a/dnsapi/dns_timeweb.sh b/dnsapi/dns_timeweb.sh index 544564ea..7040ac9a 100644 --- a/dnsapi/dns_timeweb.sh +++ b/dnsapi/dns_timeweb.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb Options: TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys Issues: github.com/acmesh-official/acme.sh/issues/5140 -Author: Nikolay Pronchev +Author: Nikolay Pronchev <@nikolaypronchev> ' TW_Api="https://api.timeweb.cloud/api/v1" diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh index f9772e10..656a0557 100644 --- a/dnsapi/dns_udr.sh +++ b/dnsapi/dns_udr.sh @@ -7,7 +7,7 @@ Options: UDR_USER Username UDR_PASS Password Issues: github.com/acmesh-official/acme.sh/issues/3923 -Author: Andreas Scherer +Author: Andreas Scherer <@andischerer> ' UDR_API="https://api.domainreselling.de/api/call.cgi" diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh index c3915c69..faf3105d 100755 --- a/dnsapi/dns_vscale.sh +++ b/dnsapi/dns_vscale.sh @@ -5,7 +5,7 @@ Site: vscale.io Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale Options: VSCALE_API_KEY API Key -Author: Alex Loban +Author: Alex Loban <@LAV45> ' VSCALE_API_URL="https://api.vscale.io/v1" diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh index bfc4b23a..2374afc3 100644 --- a/dnsapi/dns_websupport.sh +++ b/dnsapi/dns_websupport.sh @@ -7,7 +7,7 @@ Options: WS_ApiKey API Key. Called "Identifier" in the WS Admin WS_ApiSecret API Secret. Called "Secret key" in the WS Admin Issues: github.com/acmesh-official/acme.sh/issues/3486 -Author: trgo.sk , akulumbeg +Author: trgo.sk <@trgosk>, @akulumbeg ' # Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index 46cdc4fe..dc295330 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -7,7 +7,7 @@ Options: WORLD4YOU_USERNAME Username WORLD4YOU_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3269 -Author: Lorenz Stechauner +Author: Lorenz Stechauner <@NerLOR> ' WORLD4YOU_API="https://my.world4you.com/en" From daf183e2cc8b1e11ecb754fd8eddd56c1b954c12 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:41:58 +0300 Subject: [PATCH 042/127] dnsapi: dns_vultr.sh remove empty author Signed-off-by: Sergey Ponomarev --- dnsapi/dns_vultr.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh index 61ec3f60..4002e5de 100644 --- a/dnsapi/dns_vultr.sh +++ b/dnsapi/dns_vultr.sh @@ -6,7 +6,6 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr Options: VULTR_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/2374 -Author: ' VULTR_Api="https://api.vultr.com/v2" From 85ec6343ff2f4388bf4e5cae77d5481f5dfcb46d Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:42:39 +0300 Subject: [PATCH 043/127] dnsapi: dns_mijnhost.sh rearrange fields, use user docs instead of API docs Signed-off-by: Sergey Ponomarev --- dnsapi/dns_mijnhost.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_mijnhost.sh b/dnsapi/dns_mijnhost.sh index 52a81632..9f5e7710 100644 --- a/dnsapi/dns_mijnhost.sh +++ b/dnsapi/dns_mijnhost.sh @@ -1,16 +1,15 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 dns_mijnhost_info='mijn.host -Domains: mijn.host Site: mijn.host -Docs: https://mijn.host/api/doc/ -Issues: https://github.com/acmesh-official/acme.sh/issues/6177 -Author: @peterv99 +Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mijnhost Options: MIJNHOST_API_KEY API Key +Issues: github.com/acmesh-official/acme.sh/issues/6177 +Author: @peterv99 ' -######## Public functions ###################### Constants for your mijn-host API +######## Public functions ###################### MIJNHOST_API="https://mijn.host/api/v2" # Add TXT record for domain verification From 8113711b7aff45ac243492c1b3f49282727ac1b1 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sun, 6 Jul 2025 01:43:16 +0300 Subject: [PATCH 044/127] dnsapi: fix Structured DNS Info Signed-off-by: Sergey Ponomarev --- dnsapi/dns_beget.sh | 2 +- dnsapi/dns_he_ddns.sh | 1 + dnsapi/dns_selectel.sh | 38 ++++++++++++++++---------------------- dnsapi/dns_spaceship.sh | 6 +++--- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index aa43caed..5f3b1eb1 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -7,7 +7,7 @@ Options: BEGET_User API user BEGET_Password API password Issues: github.com/acmesh-official/acme.sh/issues/6200 -Author: ARNik arnik@arnik.ru +Author: ARNik ' Beget_Api="https://api.beget.com/api" diff --git a/dnsapi/dns_he_ddns.sh b/dnsapi/dns_he_ddns.sh index cd7d1ec2..1fe9a7fd 100644 --- a/dnsapi/dns_he_ddns.sh +++ b/dnsapi/dns_he_ddns.sh @@ -5,6 +5,7 @@ Site: dns.he.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns Options: HE_DDNS_KEY The DDNS key +Issues: https://github.com/acmesh-official/acme.sh/issues/5238 Author: Markku Leiniö ' diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 434bc483..565f541b 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -1,27 +1,21 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 - -# dns_selectel_info='Selectel.com -# Domains: Selectel.ru -# Site: Selectel.com -# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel -# Options: -# Variables that must be defined before running -# SL_Ver can take one of the values 'v1' or 'v2', default is 'v1' -# SL_Ver='v1', when using version API legacy (v1) -# SL_Ver='v2', when using version API actual (v2) -# when using API version v1, i.e. SL_Ver is 'v1' or not defined: -# SL_Key - API Key, required -# when using API version v2: -# SL_Ver - required as 'v2' -# SL_Login_ID - account ID, required -# SL_Project_Name - name project, required -# SL_Login_Name - service user name, required -# SL_Pswd - service user password, required -# SL_Expire - token lifetime in minutes (0-1440), default 1400 minutes -# -# Issues: github.com/acmesh-official/acme.sh/issues/5126 -# +dns_selectel_info='Selectel.com +Domains: Selectel.ru +Site: Selectel.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel +Options: For old API version v1 (deprecated) + SL_Ver API version. Use "v1". + SL_Key API Key +OptionsAlt: For the current API version v2 + SL_Ver API version. Use "v2". + SL_Login_ID Account ID + SL_Project_Name Project name + SL_Login_Name Service user name + SL_Pswd Service user password + SL_Expire Token lifetime. In minutes (0-1440). Default "1400" +Issues: github.com/acmesh-official/acme.sh/issues/5126 +' SL_Api="https://api.selectel.ru/domains" auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens" diff --git a/dnsapi/dns_spaceship.sh b/dnsapi/dns_spaceship.sh index 5e92a4fe..8fff4037 100644 --- a/dnsapi/dns_spaceship.sh +++ b/dnsapi/dns_spaceship.sh @@ -4,9 +4,9 @@ dns_spaceship_info='Spaceship.com Site: Spaceship.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_spaceship Options: - SPACESHIP_API_KEY Spaceship API Key - SPACESHIP_API_SECRET Spaceship API Secret - SPACESHIP_ROOT_DOMAIN (Optional) Manually specify the root domain if auto-detection fails + SPACESHIP_API_KEY API Key + SPACESHIP_API_SECRET API Secret + SPACESHIP_ROOT_DOMAIN Root domain. Manually specify the root domain if auto-detection fails. Optional. Issues: github.com/acmesh-official/acme.sh/issues/6304 Author: Meow <@Meo597> ' From 3b0f624302294c3d817e5c3218a263905dff9f67 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Thu, 10 Jul 2025 10:55:05 +0200 Subject: [PATCH 045/127] Support certificate profile selection --- acme.sh | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index d70e323b..46c1124c 100755 --- a/acme.sh +++ b/acme.sh @@ -4416,6 +4416,7 @@ issue() { _preferred_chain="${15}" _valid_from="${16}" _valid_to="${17}" + _certificate_profile="${18}" if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" @@ -4491,6 +4492,11 @@ issue() { else _cleardomainconf "Le_Preferred_Chain" fi + if [ "$_certificate_profile" ]; then + _savedomainconf "Le_Certificate_Profile" "$_certificate_profile" + else + _cleardomainconf "Le_Certificate_Profile" + fi Le_API="$ACME_DIRECTORY" _savedomainconf "Le_API" "$Le_API" @@ -4622,6 +4628,9 @@ issue() { if [ "$_notAfter" ]; then _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" fi + if [ "$_certificate_profile" ]; then + _newOrderObj="$_newOrderObj,\"profile\": \"$_certificate_profile\"" + fi _debug "STEP 1, Ordering a Certificate" if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then _err "Error creating new order." @@ -5503,6 +5512,7 @@ renew() { Le_PostHook="$(_readdomainconf Le_PostHook)" Le_RenewHook="$(_readdomainconf Le_RenewHook)" Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)" + Le_Certificate_Profile="$(_readdomainconf Le_Certificate_Profile)" # When renewing from an old version, the empty Le_Keylength means 2048. # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over # time but an empty value implies 2048 specifically. @@ -5517,7 +5527,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" + 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" res="$?" if [ "$res" != "0" ]; then return "$res" @@ -6989,6 +6999,8 @@ Parameters: If no match, the default offered chain will be used. (default: empty) See: $_PREFERRED_CHAIN_WIKI + --certificate-profile If the CA offers profiles, select the desired profile + --valid-to Request the NotAfter field of the cert. See: $_VALIDITY_WIKI --valid-from Request the NotBefore field of the cert. @@ -7364,6 +7376,7 @@ _process() { _preferred_chain="" _valid_from="" _valid_to="" + _certificate_profile="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -7682,6 +7695,10 @@ _process() { _valid_to="$2" shift ;; + --certificate-profile) + _certificate_profile="$2" + shift + ;; --httpport) _httpport="$2" Le_HTTPPort="$_httpport" @@ -7957,7 +7974,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" + 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" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" From 0c98dc54fee5194e5240e9e71e6d13f105894cb8 Mon Sep 17 00:00:00 2001 From: David Beitey Date: Sun, 13 Jul 2025 11:18:10 +1000 Subject: [PATCH 046/127] Fix logged typo when running pre hook --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index d70e323b..252d0227 100755 --- a/acme.sh +++ b/acme.sh @@ -3513,7 +3513,7 @@ _on_before_issue() { _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then - _info "Runing pre hook:'$_chk_pre_hook'" + _info "Running pre hook:'$_chk_pre_hook'" if ! ( export Le_Domain="$_chk_main_domain" export Le_Alt="$_chk_alt_domains" From 40e58ed12d14319f5e355055b7a1c973519dedcb Mon Sep 17 00:00:00 2001 From: David Beitey Date: Sun, 13 Jul 2025 11:40:34 +1000 Subject: [PATCH 047/127] Run post hook when _on_before_issue errors --- acme.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/acme.sh b/acme.sh index d70e323b..8ce5bacc 100755 --- a/acme.sh +++ b/acme.sh @@ -4502,6 +4502,7 @@ issue() { if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." + _on_issue_err "$_post_hook" return 1 fi 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 048/127] 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 1f486fc9a524ead28d4b801f450b097eb3a58311 Mon Sep 17 00:00:00 2001 From: keryfan <35259207+keryfan@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:12:09 +0300 Subject: [PATCH 049/127] Upload latest dev branch to master (#3) * Fix for empty error objects in response breaking extraction of domain validation types Fix for empty error objects in the response which mess up the extraction of domain validation types due to the closing brace in the error object prematurely matching the end of the search pattern. This seems to be a recent change with ZeroSSL in particular where "error":{} is being included in responses. There could potentially be a related issue if there is a complex error object ever returned in the validation check response where an embedded sub-object could lead to an incomplete extraction of the error message, roughly around line 5040. Adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 * Add new dnsapi support for OpenProvider.eu using new REST API * Cleanup duplicate debug log output based on DNS test run * Resolve spellcheck error * Configure 10 second timeout to ACME_DIRECTORY API call * add support for AIX style netstat * add * fix for wiki * minor * minor * wiki * wiki * dnsapi: dns_mydnsjp.sh fix author The @epgdatacapbon was renamed to @tkmsst Signed-off-by: Sergey Ponomarev * dnsapi: dns_ddnss.sh remove RaidenII from authors He made the DuckDNS script that was used for this script but he can't support the script. Signed-off-by: Sergey Ponomarev * dnsapi: fix authors: use @ for GitHub profiles Signed-off-by: Sergey Ponomarev * dnsapi: dns_vultr.sh remove empty author Signed-off-by: Sergey Ponomarev * dnsapi: dns_mijnhost.sh rearrange fields, use user docs instead of API docs Signed-off-by: Sergey Ponomarev * dnsapi: fix Structured DNS Info Signed-off-by: Sergey Ponomarev * Fix logged typo when running pre hook * Run post hook when _on_before_issue errors --------- Signed-off-by: Sergey Ponomarev Co-authored-by: Ciaran Walsh Co-authored-by: Lambiek12 Co-authored-by: Erwin Oegema Co-authored-by: laDanz Co-authored-by: neil Co-authored-by: neil Co-authored-by: Sergey Ponomarev Co-authored-by: David Beitey Co-authored-by: Jan-willem van Kampen --- .github/workflows/wiki-monitor.yml | 60 ++++++++++ acme.sh | 17 ++- dnsapi/dns_beget.sh | 2 +- dnsapi/dns_bookmyname.sh | 2 +- dnsapi/dns_ddnss.sh | 2 +- dnsapi/dns_dnshome.sh | 2 +- dnsapi/dns_duckdns.sh | 2 +- dnsapi/dns_dyn.sh | 2 +- dnsapi/dns_dynv6.sh | 2 +- dnsapi/dns_easydns.sh | 2 +- dnsapi/dns_freedns.sh | 2 +- dnsapi/dns_he_ddns.sh | 1 + dnsapi/dns_joker.sh | 2 +- dnsapi/dns_mijnhost.sh | 9 +- dnsapi/dns_mydnsjp.sh | 2 +- dnsapi/dns_namecom.sh | 2 +- dnsapi/dns_namesilo.sh | 2 +- dnsapi/dns_openprovider_rest.sh | 186 +++++++++++++++++++++++++++++ dnsapi/dns_pleskxml.sh | 2 +- dnsapi/dns_schlundtech.sh | 2 +- dnsapi/dns_selectel.sh | 38 +++--- dnsapi/dns_spaceship.sh | 8 +- dnsapi/dns_tele3.sh | 2 +- dnsapi/dns_timeweb.sh | 2 +- dnsapi/dns_udr.sh | 2 +- dnsapi/dns_vscale.sh | 2 +- dnsapi/dns_vultr.sh | 1 - dnsapi/dns_websupport.sh | 2 +- dnsapi/dns_world4you.sh | 2 +- 29 files changed, 305 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/wiki-monitor.yml create mode 100644 dnsapi/dns_openprovider_rest.sh diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml new file mode 100644 index 00000000..89497580 --- /dev/null +++ b/.github/workflows/wiki-monitor.yml @@ -0,0 +1,60 @@ +name: Notify via Issue on Wiki Edit + +on: + gollum: + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Checkout wiki repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + + - name: Generate wiki change message + run: | + actor="${{ github.actor }}" + sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") + page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") + 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") + now="$(date '+%Y-%m-%d %H:%M:%S')" + + cd wiki + prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) + if [ -n "$prev_sha" ]; then + git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff + else + echo "(no diff)" > ../wiki.diff + fi + cd .. + { + echo "Wiki edited" + echo -n "User: " + echo "[$actor]($sender_url)" + echo "Time: $now" + echo "Page: [$page_name]($page_url) (Action: $page_action)" + echo "" + echo "----" + echo "### diff:" + echo '```diff' + cat wiki.diff + echo '```' + } > wiki-change-msg.txt + + - name: Create issue to notify Neilpang + uses: peter-evans/create-issue-from-file@v5 + with: + title: "Wiki edited" + content-filepath: ./wiki-change-msg.txt + assignees: Neilpang + env: + TZ: Asia/Shanghai + + + + + diff --git a/acme.sh b/acme.sh index e9eb6b94..d9ae208a 100755 --- a/acme.sh +++ b/acme.sh @@ -1401,6 +1401,12 @@ _ss() { return 0 fi + if [ "$(uname)" = "AIX" ]; then + _debug "Using: AIX netstat" + netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port " + return 0 + fi + if _exists "netstat"; then _debug "Using: netstat" if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then @@ -2761,7 +2767,7 @@ _initAPI() { _request_retry_times=0 while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do _request_retry_times=$(_math "$_request_retry_times" + 1) - response=$(_get "$_api_server") + response=$(_get "$_api_server" "" 10) if [ "$?" != "0" ]; then _debug2 "response" "$response" _info "Cannot init API for: $_api_server." @@ -3507,7 +3513,7 @@ _on_before_issue() { _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then - _info "Runing pre hook:'$_chk_pre_hook'" + _info "Running pre hook:'$_chk_pre_hook'" if ! ( export Le_Domain="$_chk_main_domain" export Le_Alt="$_chk_alt_domains" @@ -4496,6 +4502,7 @@ issue() { if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." + _on_issue_err "$_post_hook" return 1 fi @@ -4755,7 +4762,8 @@ $_authorizations_map" _debug keyauthorization "$keyauthorization" fi - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$keyauthorization" -a -z "$entry" ]; then @@ -6344,7 +6352,8 @@ _deactivate() { fi _debug "Trigger validation." vtype="$(_getIdType "$_d_domain")" - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then _err "$d: Cannot get domain token" diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index aa43caed..5f3b1eb1 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -7,7 +7,7 @@ Options: BEGET_User API user BEGET_Password API password Issues: github.com/acmesh-official/acme.sh/issues/6200 -Author: ARNik arnik@arnik.ru +Author: ARNik ' Beget_Api="https://api.beget.com/api" diff --git a/dnsapi/dns_bookmyname.sh b/dnsapi/dns_bookmyname.sh index 668cf074..cf3f1e3e 100644 --- a/dnsapi/dns_bookmyname.sh +++ b/dnsapi/dns_bookmyname.sh @@ -7,7 +7,7 @@ Options: BOOKMYNAME_USERNAME Username BOOKMYNAME_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3209 -Author: Neilpang +Author: @Neilpang ' ######## Public functions ##################### diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh index 118b148b..0ac353d4 100644 --- a/dnsapi/dns_ddnss.sh +++ b/dnsapi/dns_ddnss.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss Options: DDNSS_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2230 -Author: RaidenII, helbgd, mod242 +Author: @helbgd, @mod242 ' DDNSS_DNS_API="https://ddnss.de/upd.php" diff --git a/dnsapi/dns_dnshome.sh b/dnsapi/dns_dnshome.sh index 59828796..6d583246 100755 --- a/dnsapi/dns_dnshome.sh +++ b/dnsapi/dns_dnshome.sh @@ -7,7 +7,7 @@ Options: DNSHOME_Subdomain Subdomain DNSHOME_SubdomainPassword Subdomain Password Issues: github.com/acmesh-official/acme.sh/issues/3819 -Author: dnsHome.de https://github.com/dnsHome-de +Author: @dnsHome-de ' # Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" diff --git a/dnsapi/dns_duckdns.sh b/dnsapi/dns_duckdns.sh index 71594873..33d401b0 100755 --- a/dnsapi/dns_duckdns.sh +++ b/dnsapi/dns_duckdns.sh @@ -5,7 +5,7 @@ Site: www.DuckDNS.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns Options: DuckDNS_Token API Token -Author: RaidenII +Author: @RaidenII ' DuckDNS_API="https://www.duckdns.org/update" diff --git a/dnsapi/dns_dyn.sh b/dnsapi/dns_dyn.sh index 94201923..9b1a97a2 100644 --- a/dnsapi/dns_dyn.sh +++ b/dnsapi/dns_dyn.sh @@ -8,7 +8,7 @@ Options: DYN_Customer Customer DYN_Username API Username DYN_Password Secret -Author: Gerd Naschenweng +Author: Gerd Naschenweng <@magicdude4eva> ' # Dyn Managed DNS API diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh index 76af17f5..0c9491f8 100644 --- a/dnsapi/dns_dynv6.sh +++ b/dnsapi/dns_dynv6.sh @@ -8,7 +8,7 @@ Options: OptionsAlt: KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6" Issues: github.com/acmesh-official/acme.sh/issues/2702 -Author: StefanAbl +Author: @StefanAbl ' dynv6_api="https://dynv6.com/api/v2" diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh index 1c96ac8f..423def2b 100644 --- a/dnsapi/dns_easydns.sh +++ b/dnsapi/dns_easydns.sh @@ -7,7 +7,7 @@ Options: EASYDNS_Token API Token EASYDNS_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/2647 -Author: Neilpang, wurzelpanzer +Author: @Neilpang, wurzelpanzer ' # API Documentation: https://sandbox.rest.easydns.net:3001/ diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 114f30e0..13d9f68b 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -7,7 +7,7 @@ Options: FREEDNS_User Username FREEDNS_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2305 -Author: David Kerr +Author: David Kerr <@dkerr64> ' ######## Public functions ##################### diff --git a/dnsapi/dns_he_ddns.sh b/dnsapi/dns_he_ddns.sh index cd7d1ec2..1fe9a7fd 100644 --- a/dnsapi/dns_he_ddns.sh +++ b/dnsapi/dns_he_ddns.sh @@ -5,6 +5,7 @@ Site: dns.he.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns Options: HE_DDNS_KEY The DDNS key +Issues: https://github.com/acmesh-official/acme.sh/issues/5238 Author: Markku Leiniö ' diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh index 1fe33c67..401471be 100644 --- a/dnsapi/dns_joker.sh +++ b/dnsapi/dns_joker.sh @@ -7,7 +7,7 @@ Options: JOKER_USERNAME Username JOKER_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2840 -Author: +Author: @aattww ' JOKER_API="https://svc.joker.com/nic/replace" diff --git a/dnsapi/dns_mijnhost.sh b/dnsapi/dns_mijnhost.sh index 9dafc702..9f5e7710 100644 --- a/dnsapi/dns_mijnhost.sh +++ b/dnsapi/dns_mijnhost.sh @@ -1,16 +1,15 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 dns_mijnhost_info='mijn.host -Domains: mijn.host Site: mijn.host -Docs: https://mijn.host/api/doc/ -Issues: https://github.com/acmesh-official/acme.sh/issues/6177 -Author: peterv99 +Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mijnhost Options: MIJNHOST_API_KEY API Key +Issues: github.com/acmesh-official/acme.sh/issues/6177 +Author: @peterv99 ' -######## Public functions ###################### Constants for your mijn-host API +######## Public functions ###################### MIJNHOST_API="https://mijn.host/api/v2" # Add TXT record for domain verification diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh index 336c4889..4dfffaaa 100755 --- a/dnsapi/dns_mydnsjp.sh +++ b/dnsapi/dns_mydnsjp.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp Options: MYDNSJP_MasterID Master ID MYDNSJP_Password Password -Author: epgdatacapbon +Author: @tkmsst ' ######## Public functions ##################### diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh index 44549c9e..1062c849 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecom Options: Namecom_Username Username Namecom_Token API Token -Author: RaidenII +Author: @RaidenII ' ######## Public functions ##################### diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh index b31e32a1..5d47a59a 100755 --- a/dnsapi/dns_namesilo.sh +++ b/dnsapi/dns_namesilo.sh @@ -5,7 +5,7 @@ Site: NameSilo.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo Options: Namesilo_Key API Key -Author: meowthink +Author: @meowthink ' #Utilize API to finish dns-01 verifications. diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh new file mode 100644 index 00000000..210dc6fc --- /dev/null +++ b/dnsapi/dns_openprovider_rest.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_openprovider_rest_info='OpenProvider (REST) +Domains: OpenProvider.com +Site: OpenProvider.eu +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest +Options: + OPENPROVIDER_REST_USERNAME Openprovider Account Username + OPENPROVIDER_REST_PASSWORD Openprovider Account Password +Issues: github.com/acmesh-official/acme.sh/issues/6122 +Author: Lambiek12 +' + +OPENPROVIDER_API_URL="https://api.openprovider.eu/v1beta" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_openprovider_rest_add() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + addzonerecordrequestparameters="dns/zones/$_domain_name" + addzonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"add\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"$txtvalue\"}]}}" + + if _openprovider_rest PUT "$addzonerecordrequestparameters" "$addzonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + elif _contains "$response" "\"Duplicate record\""; then + _debug "Record already existed" + return 0 + else + _err "Adding TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Adding TXT record failed due to errors." + return 1 +} + +# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to remove the txt record after validation +dns_openprovider_rest_rm() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + removezonerecordrequestparameters="dns/zones/$_domain_name" + removezonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"remove\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"\\\"$txtvalue\\\"\"}]}}" + + if _openprovider_rest PUT "$removezonerecordrequestparameters" "$removezonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + else + _err "Removing TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Removing TXT record failed due to errors." + return 1 +} + +#################### OpenProvider API common functions #################### +_openprovider_prepare_credentials() { + OPENPROVIDER_REST_USERNAME="${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}" + OPENPROVIDER_REST_PASSWORD="${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}" + + if [ -z "$OPENPROVIDER_REST_USERNAME" ] || [ -z "$OPENPROVIDER_REST_PASSWORD" ]; then + OPENPROVIDER_REST_USERNAME="" + OPENPROVIDER_REST_PASSWORD="" + _err "You didn't specify the Openprovider username or password yet." + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable OPENPROVIDER_REST_USERNAME "$OPENPROVIDER_REST_USERNAME" + _saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD "$OPENPROVIDER_REST_PASSWORD" +} + +_openprovider_rest() { + httpmethod=$1 + queryparameters=$2 + requestbody=$3 + + _openprovider_rest_login + if [ -z "$openproviderauthtoken" ]; then + _err "Unable to fetch authentication token from Openprovider API." + return 1 + fi + + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $openproviderauthtoken" + + if [ "$httpmethod" != "GET" ]; then + response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" + else + response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")" + fi + + if [ "$?" != "0" ]; then + _err "No valid parameters supplied for Openprovider API: Error $queryparameters" + return 1 + fi + + _debug2 response "$response" + + return 0 +} + +_openprovider_rest_login() { + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + + loginrequesturl="$OPENPROVIDER_API_URL/auth/login" + loginrequestbody="{\"ip\":\"0.0.0.0\",\"password\":\"$OPENPROVIDER_REST_PASSWORD\",\"username\":\"$OPENPROVIDER_REST_USERNAME\"}" + loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")" + + openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" + + export openproviderauthtoken +} + +#################### Private functions ################################## + +# Usage: _get_dns_zone _acme-challenge.www.domain.com +# Returns: +# _domain_id=123456789 +# _domain_name=domain.com +# _sub_domain=_acme-challenge.www +_get_dns_zone() { + domain=$1 + i=1 + p=1 + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + if [ -z "$h" ]; then + # Empty value not allowed + return 1 + fi + + if ! _openprovider_rest GET "dns/zones/$h" ""; then + return 1 + fi + + if _contains "$response" "\"name\":\"$h\""; then + _domain_id="$(printf "%s\n" "$response" | _egrep_o '"id" *: *[^,]*' | _head_n 1 | sed 's#^"id" *: *##')" + _debug _domain_id "$_domain_id" + + _domain_name="$h" + _debug _domain_name "$_domain_name" + + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _debug _sub_domain "$_sub_domain" + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + + return 1 +} diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 6b38abcb..465bcc60 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -8,7 +8,7 @@ Options: pleskxml_user Username pleskxml_pass Password Issues: github.com/acmesh-official/acme.sh/issues/2577 -Author: Stilez, +Author: @Stilez, @romanlum ' ## Plesk XML API described at: diff --git a/dnsapi/dns_schlundtech.sh b/dnsapi/dns_schlundtech.sh index 6d2930a2..21930110 100644 --- a/dnsapi/dns_schlundtech.sh +++ b/dnsapi/dns_schlundtech.sh @@ -7,7 +7,7 @@ Options: SCHLUNDTECH_USER Username SCHLUNDTECH_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2246 -Author: +Author: @mod242 ' SCHLUNDTECH_API="https://gateway.schlundtech.de" diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 434bc483..565f541b 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -1,27 +1,21 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 - -# dns_selectel_info='Selectel.com -# Domains: Selectel.ru -# Site: Selectel.com -# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel -# Options: -# Variables that must be defined before running -# SL_Ver can take one of the values 'v1' or 'v2', default is 'v1' -# SL_Ver='v1', when using version API legacy (v1) -# SL_Ver='v2', when using version API actual (v2) -# when using API version v1, i.e. SL_Ver is 'v1' or not defined: -# SL_Key - API Key, required -# when using API version v2: -# SL_Ver - required as 'v2' -# SL_Login_ID - account ID, required -# SL_Project_Name - name project, required -# SL_Login_Name - service user name, required -# SL_Pswd - service user password, required -# SL_Expire - token lifetime in minutes (0-1440), default 1400 minutes -# -# Issues: github.com/acmesh-official/acme.sh/issues/5126 -# +dns_selectel_info='Selectel.com +Domains: Selectel.ru +Site: Selectel.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel +Options: For old API version v1 (deprecated) + SL_Ver API version. Use "v1". + SL_Key API Key +OptionsAlt: For the current API version v2 + SL_Ver API version. Use "v2". + SL_Login_ID Account ID + SL_Project_Name Project name + SL_Login_Name Service user name + SL_Pswd Service user password + SL_Expire Token lifetime. In minutes (0-1440). Default "1400" +Issues: github.com/acmesh-official/acme.sh/issues/5126 +' SL_Api="https://api.selectel.ru/domains" auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens" diff --git a/dnsapi/dns_spaceship.sh b/dnsapi/dns_spaceship.sh index 770e22cc..8fff4037 100644 --- a/dnsapi/dns_spaceship.sh +++ b/dnsapi/dns_spaceship.sh @@ -4,11 +4,11 @@ dns_spaceship_info='Spaceship.com Site: Spaceship.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_spaceship Options: - SPACESHIP_API_KEY Spaceship API Key - SPACESHIP_API_SECRET Spaceship API Secret - SPACESHIP_ROOT_DOMAIN (Optional) Manually specify the root domain if auto-detection fails + SPACESHIP_API_KEY API Key + SPACESHIP_API_SECRET API Secret + SPACESHIP_ROOT_DOMAIN Root domain. Manually specify the root domain if auto-detection fails. Optional. Issues: github.com/acmesh-official/acme.sh/issues/6304 -Author: Meow +Author: Meow <@Meo597> ' # Spaceship API diff --git a/dnsapi/dns_tele3.sh b/dnsapi/dns_tele3.sh index e5974951..3a3ccf8c 100644 --- a/dnsapi/dns_tele3.sh +++ b/dnsapi/dns_tele3.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3 Options: TELE3_Key API Key TELE3_Secret API Secret -Author: Roman Blizik +Author: Roman Blizik <@par-pa> ' TELE3_API="https://www.tele3.cz/acme/" diff --git a/dnsapi/dns_timeweb.sh b/dnsapi/dns_timeweb.sh index 544564ea..7040ac9a 100644 --- a/dnsapi/dns_timeweb.sh +++ b/dnsapi/dns_timeweb.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb Options: TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys Issues: github.com/acmesh-official/acme.sh/issues/5140 -Author: Nikolay Pronchev +Author: Nikolay Pronchev <@nikolaypronchev> ' TW_Api="https://api.timeweb.cloud/api/v1" diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh index f9772e10..656a0557 100644 --- a/dnsapi/dns_udr.sh +++ b/dnsapi/dns_udr.sh @@ -7,7 +7,7 @@ Options: UDR_USER Username UDR_PASS Password Issues: github.com/acmesh-official/acme.sh/issues/3923 -Author: Andreas Scherer +Author: Andreas Scherer <@andischerer> ' UDR_API="https://api.domainreselling.de/api/call.cgi" diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh index c3915c69..faf3105d 100755 --- a/dnsapi/dns_vscale.sh +++ b/dnsapi/dns_vscale.sh @@ -5,7 +5,7 @@ Site: vscale.io Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale Options: VSCALE_API_KEY API Key -Author: Alex Loban +Author: Alex Loban <@LAV45> ' VSCALE_API_URL="https://api.vscale.io/v1" diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh index 61ec3f60..4002e5de 100644 --- a/dnsapi/dns_vultr.sh +++ b/dnsapi/dns_vultr.sh @@ -6,7 +6,6 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr Options: VULTR_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/2374 -Author: ' VULTR_Api="https://api.vultr.com/v2" diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh index bfc4b23a..2374afc3 100644 --- a/dnsapi/dns_websupport.sh +++ b/dnsapi/dns_websupport.sh @@ -7,7 +7,7 @@ Options: WS_ApiKey API Key. Called "Identifier" in the WS Admin WS_ApiSecret API Secret. Called "Secret key" in the WS Admin Issues: github.com/acmesh-official/acme.sh/issues/3486 -Author: trgo.sk , akulumbeg +Author: trgo.sk <@trgosk>, @akulumbeg ' # Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index 46cdc4fe..dc295330 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -7,7 +7,7 @@ Options: WORLD4YOU_USERNAME Username WORLD4YOU_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3269 -Author: Lorenz Stechauner +Author: Lorenz Stechauner <@NerLOR> ' WORLD4YOU_API="https://my.world4you.com/en" From 1b5e66f9c2e4907408c05164010d7cd4422d6051 Mon Sep 17 00:00:00 2001 From: wout Date: Wed, 23 Jul 2025 10:12:32 +0200 Subject: [PATCH 050/127] Add sleep before each REST call to Constellix to prevent rate limit --- dnsapi/dns_constellix.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dnsapi/dns_constellix.sh b/dnsapi/dns_constellix.sh index 6a50e199..480541ed 100644 --- a/dnsapi/dns_constellix.sh +++ b/dnsapi/dns_constellix.sh @@ -156,6 +156,9 @@ _constellix_rest() { data="$3" _debug "$ep" + # Prevent rate limit + _sleep 2 + rdate=$(date +"%s")"000" hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64) From ab22c8ca1cb89cda5e47d510de10e12ffabd39a0 Mon Sep 17 00:00:00 2001 From: wout Date: Tue, 12 Aug 2025 19:04:19 +0200 Subject: [PATCH 051/127] Convert domain to lower case, needed for Constellix REST API --- dnsapi/dns_constellix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_constellix.sh b/dnsapi/dns_constellix.sh index 480541ed..7251f8b2 100644 --- a/dnsapi/dns_constellix.sh +++ b/dnsapi/dns_constellix.sh @@ -117,7 +117,7 @@ dns_constellix_rm() { #################### Private functions below ################################## _get_root() { - domain=$1 + domain=$(echo "$1" | _lower_case) i=2 p=1 _debug "Detecting root zone" From bcf0afb25ef9f159da397db73f1050c3b92f56d0 Mon Sep 17 00:00:00 2001 From: Tobias Grave Date: Fri, 15 Aug 2025 09:02:57 +0200 Subject: [PATCH 052/127] Variomedia API: Fix DNS deletion issues --- dnsapi/dns_variomedia.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh index fa38bbb6..4620b854 100644 --- a/dnsapi/dns_variomedia.sh +++ b/dnsapi/dns_variomedia.sh @@ -74,7 +74,7 @@ dns_variomedia_rm() { return 1 fi - _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" + _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f3 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep -i "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." From 5b02e8633441ece592bc2b9f21bd52b7ea38af25 Mon Sep 17 00:00:00 2001 From: asauerwein Date: Wed, 20 Aug 2025 17:47:36 +0200 Subject: [PATCH 053/127] add template_stack option to push to device --- deploy/panos.sh | 75 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/deploy/panos.sh b/deploy/panos.sh index 0dc1b2f0..2ed6a230 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -7,20 +7,26 @@ # # Firewall admin with superuser and IP address is required. # -# REQURED: +# REQUIRED: # export PANOS_HOST="" # export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role # export PANOS_PASS="" # # OPTIONAL -# export PANOS_TEMPLATE="" #Template Name of panorama managed devices +# 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 # # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. + + + # This function is to parse the XML response from the firewall parse_response() { type=$2 + _debug "API Response: $1" if [ "$type" = 'keygen' ]; then status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g') if [ "$status" = "success" ]; then @@ -30,6 +36,13 @@ parse_response() { message="PAN-OS Key could not be set." fi else + if [ "$type" = 'commit' ]; then + job_id=$(echo "$1" | sed 's/^.*\(\)\(.*\)<\/job>.*/\2/g') + _commit_job_id=$job_id + elif [ "$type" = 'job_status' ]; then + job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*\([^<]*\)<\/result>.*/\1/g') + _commit_job_status=$job_status + fi status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g') message=$(echo "$1" | tr -d '\n' | sed 's/.*\(\|\|\)\([^<]*\).*/\2/g') _debug "Firewall message: $message" @@ -44,7 +57,7 @@ parse_response() { #This function is used to deploy to the firewall deployer() { content="" - type=$1 # Types are keytest, keygen, cert, key, commit + type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push panos_url="https://$_panos_host/api/" #Test API Key by performing a lookup @@ -84,6 +97,9 @@ deployer() { if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi + if [ "$_panos_vsys" ]; then + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" + fi fi if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" @@ -96,6 +112,9 @@ deployer() { if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi + if [ "$_panos_vsys" ]; then + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" + fi fi #Close multipart content="$content${nl}--$delim--${nl}${nl}" @@ -118,6 +137,22 @@ deployer() { content="type=commit&action=partial&key=$_panos_key&cmd=$cmd" fi + # Query job status + if [ "$type" = 'job_status' ]; then + echo "**** Querying job $_commit_job_id status ****" + H1="Content-Type: application/x-www-form-urlencoded" + cmd=$(printf "%s" "$_commit_job_id" | _url_encode) + content="type=op&key=$_panos_key&cmd=$cmd" + fi + + # Push changes + if [ "$type" = 'push' ]; then + echo "**** Pushing changes ****" + H1="Content-Type: application/x-www-form-urlencoded" + cmd=$(printf "%s" "$_panos_template_stack$_panos_user" | _url_encode) + content="type=commit&action=all&key=$_panos_key&cmd=$cmd" + fi + response=$(_post "$content" "$panos_url" "" "POST") parse_response "$response" "$type" # Saving response to variables @@ -126,6 +161,8 @@ deployer() { if [ "$response_status" = "success" ]; then _debug "Successfully deployed $type" return 0 + elif [ "$_commit_job_status" ]; then + _debug "Commit Job Status = $_commit_job_status" else _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot." _debug "$message" @@ -191,11 +228,31 @@ panos_deploy() { _getdeployconf PANOS_TEMPLATE fi + # PANOS_TEMPLATE_STACK + if [ "$PANOS_TEMPLATE_STACK" ]; then + _debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file." + _savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1 + else + _debug "Attempting to load variable PANOS_TEMPLATE_STACK from file." + _getdeployconf PANOS_TEMPLATE_STACK + fi + + # PANOS_TEMPLATE_STACK + if [ "$PANOS_VSYS" ]; then + _debug "Detected ENV variable PANOS_VSYS. Saving to file." + _savedeployconf PANOS_VSYS "$PANOS_VSYS" 1 + else + _debug "Attempting to load variable PANOS_VSYS from file." + _getdeployconf PANOS_VSYS + fi + #Store variables _panos_host=$PANOS_HOST _panos_user=$PANOS_USER _panos_pass=$PANOS_PASS _panos_template=$PANOS_TEMPLATE + _panos_template_stack=$PANOS_TEMPLATE_STACK + _panos_vsys=$PANOS_VSYS #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. if [ "$_panos_host" ] && [ "$_panos_key" ]; then @@ -229,6 +286,18 @@ panos_deploy() { deployer cert deployer key deployer commit + if [ "$_panos_template_stack" ]; then + # try to get job status for 20 times in 30 sec interval + for ((i = 0 ; i < 20 ; i++ )); do + deployer job_status + if [[ "$_commit_job_status" == "OK" ]]; then + echo "Commit finished!" + break + fi + sleep 30 + done + deployer push + fi fi fi } From fdb1e8c2e46143a84aa8d26d49d5c1dce50aea74 Mon Sep 17 00:00:00 2001 From: asauerwein Date: Wed, 20 Aug 2025 18:37:25 +0200 Subject: [PATCH 054/127] fix usage of H1 header change to while loop use global variable for loop fix if statement to be sh compliant shfmt --- deploy/panos.sh | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/deploy/panos.sh b/deploy/panos.sh index 2ed6a230..a9232e79 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -20,8 +20,8 @@ # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. - - +_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds +_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes) # This function is to parse the XML response from the firewall parse_response() { @@ -59,11 +59,11 @@ deployer() { content="" type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push panos_url="https://$_panos_host/api/" + export _H1="Content-Type: application/x-www-form-urlencoded" #Test API Key by performing a lookup if [ "$type" = 'keytest' ]; then _debug "**** Testing saved API Key ****" - _H1="Content-Type: application/x-www-form-urlencoded" # Get Version Info to test key content="type=version&key=$_panos_key" ## Exclude all scopes for the empty commit @@ -74,7 +74,6 @@ deployer() { # Generate API Key if [ "$type" = 'keygen' ]; then _debug "**** Generating new API Key ****" - _H1="Content-Type: application/x-www-form-urlencoded" content="type=keygen&user=$_panos_user&password=$_panos_pass" # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}" fi @@ -99,7 +98,7 @@ deployer() { fi if [ "$_panos_vsys" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" - fi + fi fi if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" @@ -114,7 +113,7 @@ deployer() { fi if [ "$_panos_vsys" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" - fi + fi fi #Close multipart content="$content${nl}--$delim--${nl}${nl}" @@ -125,7 +124,6 @@ deployer() { # Commit changes if [ "$type" = 'commit' ]; then _debug "**** Committing changes ****" - export _H1="Content-Type: application/x-www-form-urlencoded" #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution! if [ "$FORCE" ]; then _debug "Force switch detected. Committing ALL changes to the firewall." @@ -140,7 +138,6 @@ deployer() { # Query job status if [ "$type" = 'job_status' ]; then echo "**** Querying job $_commit_job_id status ****" - H1="Content-Type: application/x-www-form-urlencoded" cmd=$(printf "%s" "$_commit_job_id" | _url_encode) content="type=op&key=$_panos_key&cmd=$cmd" fi @@ -148,7 +145,6 @@ deployer() { # Push changes if [ "$type" = 'push' ]; then echo "**** Pushing changes ****" - H1="Content-Type: application/x-www-form-urlencoded" cmd=$(printf "%s" "$_panos_template_stack$_panos_user" | _url_encode) content="type=commit&action=all&key=$_panos_key&cmd=$cmd" fi @@ -288,13 +284,15 @@ panos_deploy() { deployer commit if [ "$_panos_template_stack" ]; then # try to get job status for 20 times in 30 sec interval - for ((i = 0 ; i < 20 ; i++ )); do - deployer job_status - if [[ "$_commit_job_status" == "OK" ]]; then - echo "Commit finished!" - break - fi - sleep 30 + i=0 + while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do + deployer job_status + if [ "$_commit_job_status" = "OK" ]; then + echo "Commit finished!" + break + fi + sleep $_COMMIT_WAIT_INTERVAL + i=$((i + 1)) done deployer push fi From d7c428fc8d6bb123b890ec669b606eaf620b9c25 Mon Sep 17 00:00:00 2001 From: Richard Glidden Date: Mon, 30 Jun 2025 14:14:26 -0400 Subject: [PATCH 055/127] feat: Add ability to deploy to remote TrueNAS instances --- deploy/truenas_ws.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh index bdc1b846..74a46530 100644 --- a/deploy/truenas_ws.sh +++ b/deploy/truenas_ws.sh @@ -39,13 +39,13 @@ _ws_call() { _debug "_ws_call arg2" "$2" _debug "_ws_call arg3" "$3" if [ $# -eq 3 ]; then - _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") + _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") fi if [ $# -eq 2 ]; then - _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") + _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") fi if [ $# -eq 1 ]; then - _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1") + _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1") fi _debug "_ws_response" "$_ws_response" printf "%s" "$_ws_response" @@ -60,7 +60,7 @@ _ws_upload_cert() { import sys from truenas_api_client import Client -with Client() as c: +with Client(uri="$_ws_uri") as c: ### Login with API key print("I:Trying to upload new certificate...") @@ -175,6 +175,16 @@ truenas_ws_deploy() { _debug _file_ca "$_file_ca" _debug _file_fullchain "$_file_fullchain" + ########## Default values for hostname and protocol + [ -n "${DEPLOY_TRUENAS_HOSTNAME}" ] || DEPLOY_TRUENAS_HOSTNAME="localhost" + [ -n "${DEPLOY_TRUENAS_PROTOCOL}" ] || DEPLOY_TRUENAS_PROTOCOL="ws" + + _debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" + _debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL" + + _ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket" + _debug _ws_uri "$_ws_uri" + ########## Environment check _info "Checking environment variables..." @@ -304,7 +314,7 @@ truenas_ws_deploy() { _info "Restarting WebUI..." _ws_response=$(_ws_call "system.general.ui_restart") _info "Waiting for UI restart..." - sleep 6 + sleep 15 ########## Certificates From 6d40ac86449cdb80b52c6ffb647eac560174c84c Mon Sep 17 00:00:00 2001 From: Richard Glidden Date: Mon, 30 Jun 2025 14:51:36 -0400 Subject: [PATCH 056/127] chore: Fix shellcheck errors --- deploy/truenas_ws.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh index 74a46530..ea6fc7e6 100644 --- a/deploy/truenas_ws.sh +++ b/deploy/truenas_ws.sh @@ -39,13 +39,13 @@ _ws_call() { _debug "_ws_call arg2" "$2" _debug "_ws_call arg3" "$3" if [ $# -eq 3 ]; then - _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") + _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") fi if [ $# -eq 2 ]; then - _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") + _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") fi if [ $# -eq 1 ]; then - _ws_response=$(midclt --uri $_ws_uri -K "$DEPLOY_TRUENAS_APIKEY" call "$1") + _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1") fi _debug "_ws_response" "$_ws_response" printf "%s" "$_ws_response" From 5aae3333bc653340c45981b6bd798ec90509301a Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 20:44:24 +0100 Subject: [PATCH 057/127] Show proxmox deploy scripts response only on debug --- deploy/proxmoxbs.sh | 12 +++++++++++- deploy/proxmoxve.sh | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh index d1146454..e8528e8f 100644 --- a/deploy/proxmoxbs.sh +++ b/deploy/proxmoxbs.sh @@ -115,6 +115,16 @@ HEREDOC _info "Push certificates to server" export HTTPS_INSECURE=1 export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}" - _post "$_json_payload" "$_target_url" "" POST "application/json" + response=$(_post "$_json_payload" "$_target_url" "" POST "application/json") + _retval=$? + if [ "${_retval}" -eq 0 ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed" + _debug "Response" "$response" + return 1 + fi } diff --git a/deploy/proxmoxve.sh b/deploy/proxmoxve.sh index f9de590c..8c67f7de 100644 --- a/deploy/proxmoxve.sh +++ b/deploy/proxmoxve.sh @@ -127,6 +127,16 @@ HEREDOC _info "Push certificates to server" export HTTPS_INSECURE=1 export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}" - _post "$_json_payload" "$_target_url" "" POST "application/json" + response=$(_post "$_json_payload" "$_target_url" "" POST "application/json") + _retval=$? + if [ "${_retval}" -eq 0 ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed" + _debug "Response" "$response" + return 1 + fi } From d366b7e4fc7799bcb1a43213e2566096e1c19a28 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Mon, 1 Sep 2025 19:54:36 +0100 Subject: [PATCH 058/127] Fix diff in wiki notifications (use full clone) The checkout action fetches one single commit, so attempts to find previous states of a page result in error. Adding fetch-depth:0 to the configuration fetches all commits and makes finding the previous commit that changed a page possible in the github action. --- .github/workflows/wiki-monitor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index 89497580..b0332775 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -12,6 +12,7 @@ jobs: with: repository: ${{ github.repository }}.wiki path: wiki + fetch-depth: 0 - name: Generate wiki change message run: | @@ -58,3 +59,4 @@ jobs: + From 04e254923939d0d600cd8b29dfe3b34ca7421052 Mon Sep 17 00:00:00 2001 From: Guillaume PELURE Date: Tue, 2 Sep 2025 21:13:38 +0200 Subject: [PATCH 059/127] socat rejects TCP-LISTEN on ipv6 only networks --- acme.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index d70e323b..5c4d6543 100755 --- a/acme.sh +++ b/acme.sh @@ -2538,15 +2538,17 @@ _startserver() { _NC="socat" if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" + SOCAT_OPTIONS=TCP6-LISTEN else _NC="$_NC -4" + SOCAT_OPTIONS=TCP4-LISTEN fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then _NC="$_NC -d -d -v" fi - SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork + SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork #Adding bind to local-address if [ "$ncaddr" ]; then From 39cb87dc4bf481460ddba4d2754f07d351da07e4 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 5 Sep 2025 22:08:55 +0200 Subject: [PATCH 060/127] fix for DragonflyBSD just move "date -u -j -f" before the linux branch. --- acme.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acme.sh b/acme.sh index d9ae208a..bfd26bd6 100755 --- a/acme.sh +++ b/acme.sh @@ -1811,6 +1811,10 @@ _time() { # 2022-04-01 08:10:33 to 1648800633 #or 2022-04-01T08:10:33Z to 1648800633 _date2time() { + #Mac/BSD + if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then + return + fi #Linux if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return @@ -1820,10 +1824,6 @@ _date2time() { if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi - #Mac/BSD - if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then - return - fi #Omnios if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then return From e0e3cdc316f75af54e411f844093a9cff08781ac Mon Sep 17 00:00:00 2001 From: Eric Fu Date: Sat, 6 Sep 2025 22:31:50 +0800 Subject: [PATCH 061/127] Fix sed command in telegram notifier --- notify/telegram.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notify/telegram.sh b/notify/telegram.sh index ccbd1533..c0621ae7 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 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" + _content="$(printf "%s" "$_content" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)" _data="{\"text\": \"$_content\", " _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", " From 30faf500eb4f9394c3d43dbbba5eda43e4947e34 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Sep 2025 10:09:27 +0200 Subject: [PATCH 062/127] fix https://github.com/acmesh-official/acme.sh/pull/6499#issuecomment-3259771356 --- acme.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index d335990c..3316b25f 100755 --- a/acme.sh +++ b/acme.sh @@ -2539,9 +2539,11 @@ _startserver() { if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" SOCAT_OPTIONS=TCP6-LISTEN - else + elif [ "$Le_Listen_V4" ]; then _NC="$_NC -4" SOCAT_OPTIONS=TCP4-LISTEN + else + SOCAT_OPTIONS=TCP-LISTEN fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then 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 063/127] 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 064/127] 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 065/127] 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 066/127] 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 8608e9cd3a49c1a9f948e19ea3a62fac62a24aef Mon Sep 17 00:00:00 2001 From: Richard Glidden Date: Fri, 12 Sep 2025 22:22:30 -0400 Subject: [PATCH 067/127] Save and read variables --- deploy/truenas_ws.sh | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh index ea6fc7e6..a204d352 100644 --- a/deploy/truenas_ws.sh +++ b/deploy/truenas_ws.sh @@ -175,25 +175,31 @@ truenas_ws_deploy() { _debug _file_ca "$_file_ca" _debug _file_fullchain "$_file_fullchain" - ########## Default values for hostname and protocol - [ -n "${DEPLOY_TRUENAS_HOSTNAME}" ] || DEPLOY_TRUENAS_HOSTNAME="localhost" - [ -n "${DEPLOY_TRUENAS_PROTOCOL}" ] || DEPLOY_TRUENAS_PROTOCOL="ws" - - _debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" - _debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL" - - _ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket" - _debug _ws_uri "$_ws_uri" - ########## Environment check _info "Checking environment variables..." _getdeployconf DEPLOY_TRUENAS_APIKEY + _getdeployconf DEPLOY_TRUENAS_HOSTNAME + _getdeployconf DEPLOY_TRUENAS_PROTOCOL # Check API Key if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then _err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable." return 1 fi + # Check Hostname, default to localhost if not set + if [ -z "$DEPLOY_TRUENAS_HOSTNAME" ]; then + _info "TrueNAS hostname not set. Using 'localhost'." + DEPLOY_TRUENAS_HOSTNAME="localhost" + fi + # Check protocol, default to ws if not set + if [ -z "$DEPLOY_TRUENAS_PROTOCOL" ]; then + _info "TrueNAS protocol not set. Using 'ws'." + DEPLOY_TRUENAS_PROTOCOL="ws" + fi + _ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket" + _debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" + _debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL" + _debug _ws_uri "$_ws_uri" _secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" _info "Environment variables: OK" @@ -215,6 +221,8 @@ truenas_ws_deploy() { return 2 fi _savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" + _savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" + _savedeployconf DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL" _info "TrueNAS health: OK" ########## System info From a1ea2a5aa6a63c1c99daffc66c1463e3a2ac88ab Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 14 Sep 2025 10:35:21 +0200 Subject: [PATCH 068/127] fix tr https://github.com/acmesh-official/acme.sh/issues/6511#issuecomment-3282521860 --- acme.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/acme.sh b/acme.sh index 3316b25f..02986a33 100755 --- a/acme.sh +++ b/acme.sh @@ -436,14 +436,28 @@ _secure_debug3() { fi } +__USE_TR_TAG="" +if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ] ; then + __USE_TR_TAG="1" +fi +export __USE_TR_TAG + _upper_case() { + if [ "$__USE_TR_TAG" ]; then + LANG=C tr '[:lower:]' '[:upper:]' + else # shellcheck disable=SC2018,SC2019 - tr '[a-z]' '[A-Z]' + LANG=C tr '[a-z]' '[A-Z]' + fi } _lower_case() { - # shellcheck disable=SC2018,SC2019 - tr '[A-Z]' '[a-z]' + if [ "$__USE_TR_TAG" ]; then + LANG=C tr '[:upper:]' '[:lower:]' + else + # shellcheck disable=SC2018,SC2019 + LANG=C tr '[A-Z]' '[a-z]' + fi } _startswith() { From d76f4b27b0f2eae8b75d41459eff6e51094045ca Mon Sep 17 00:00:00 2001 From: benyamin-codez <115509179+benyamin-codez@users.noreply.github.com> Date: Sun, 7 Sep 2025 23:54:45 +1000 Subject: [PATCH 069/127] dnsapi/dns_opnsense.sh: Refresh for OPNsense v25.7 series Updates the dns_opnsense.sh Bourne shell script for OPNSense v25.7 series: 1. Fixes historical error in rm_record() [used incorrect response variable] 2. Improves debug messaging in rm_record() 3. Fixes _get_root() for change in OPNsense API * Response is now split into pseudo-rows * We now iterate through pseudo-rows for matching domainname field 4. Fixes _existingchallenge() for change in OPNsense API * Fixes unreliable regex for uuid * Adds domain regex and %domain field 5. Fixes historical error in _existingchallenge() [incorrect variable syntax] Resolves #6467 Signed-off-by: benyamin-codez <115509179+benyamin-codez@users.noreply.github.com> --- dnsapi/dns_opnsense.sh | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh index d1e9c0ac..a11cfae5 100755 --- a/dnsapi/dns_opnsense.sh +++ b/dnsapi/dns_opnsense.sh @@ -110,15 +110,16 @@ rm_record() { if _existingchallenge "$_domain" "$_host" "$new_challenge"; then # Delete if _opns_rest "POST" "/record/delRecord/${_uuid}" "\{\}"; then - if echo "$_return_str" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then - _opns_rest "POST" "/service/reconfigure" "{}" + if echo "$response" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then _debug "Record deleted" + _opns_rest "POST" "/service/reconfigure" "{}" + _debug "Service reconfigured" else _err "Error deleting record $_host from domain $fulldomain" return 1 fi else - _err "Error deleting record $_host from domain $fulldomain" + _err "Error requesting deletion of record $_host from domain $fulldomain" return 1 fi else @@ -150,14 +151,17 @@ _get_root() { return 1 fi _debug h "$h" - id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2) - if [ -n "$id" ]; then - _debug id "$id" - _host=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain="${h}" - _domainid="${id}" - return 0 - fi + lines=$(echo "$_domain_response" | sed 's/{/\n/g') + for line in $lines; do + id=$(echo "$line" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",.*\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2) + if [ -n "$id" ]; then + _debug id "$id" + _host=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain="${h}" + _domainid="${id}" + return 0 + fi + done p=$i i=$(_math "$i" + 1) done @@ -206,13 +210,13 @@ _existingchallenge() { return 1 fi _uuid="" - _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[^\"]*\",\"enabled\":\"[01]\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2) + _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"[01]\",\"domain\":\"[a-z0-9\-]*\",\"%domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2) if [ -n "$_uuid" ]; then _debug uuid "$_uuid" return 0 fi - _debug "${2}.$1{1} record not found" + _debug "${2}.${1} record not found" return 1 } From df350e6660df5b61e353a5daed7d7a1ee1f5ba4a Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 15 Sep 2025 19:34:54 +0200 Subject: [PATCH 070/127] fix format --- acme.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 02986a33..9105a0a4 100755 --- a/acme.sh +++ b/acme.sh @@ -437,7 +437,7 @@ _secure_debug3() { } __USE_TR_TAG="" -if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ] ; then +if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ]; then __USE_TR_TAG="1" fi export __USE_TR_TAG @@ -446,7 +446,7 @@ _upper_case() { if [ "$__USE_TR_TAG" ]; then LANG=C tr '[:lower:]' '[:upper:]' else - # shellcheck disable=SC2018,SC2019 + # shellcheck disable=SC2018,SC2019 LANG=C tr '[a-z]' '[A-Z]' fi } From 44c7473ef97bcae83e829a4a7e6b2ddcfc0d8369 Mon Sep 17 00:00:00 2001 From: Min Wang Date: Tue, 16 Sep 2025 15:09:12 +0800 Subject: [PATCH 071/127] fix bug for #6510 --- notify/telegram.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notify/telegram.sh b/notify/telegram.sh index c0621ae7..7da05729 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 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([-_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" + _content="$(printf "%s" "$_content" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([-_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)" _data="{\"text\": \"$_content\", " _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", " From 1b00ced7adf0c9ffe39c2f5e883395f7abefc2cc Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Tue, 16 Sep 2025 09:20:31 +0200 Subject: [PATCH 072/127] Add `--profile` as option for selecting certificate profile --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index ead7602b..7a8e4e9b 100755 --- a/acme.sh +++ b/acme.sh @@ -7702,7 +7702,7 @@ _process() { _valid_to="$2" shift ;; - --certificate-profile) + --certificate-profile | --profile) _certificate_profile="$2" shift ;; From 070cd0f4dfe983d093e1661aab8b73678cf5b259 Mon Sep 17 00:00:00 2001 From: Richard Glidden Date: Tue, 16 Sep 2025 22:19:16 -0400 Subject: [PATCH 073/127] Use _sleep instead of sleep --- deploy/truenas_ws.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh index a204d352..d334853e 100644 --- a/deploy/truenas_ws.sh +++ b/deploy/truenas_ws.sh @@ -121,7 +121,7 @@ _ws_check_jobid() { # n/a _ws_get_job_result() { while true; do - sleep 2 + _sleep 2 _ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]") if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then _ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')" @@ -322,7 +322,7 @@ truenas_ws_deploy() { _info "Restarting WebUI..." _ws_response=$(_ws_call "system.general.ui_restart") _info "Waiting for UI restart..." - sleep 15 + _sleep 15 ########## Certificates From c3ec827fdd87ae7a595f7c84aa7395a52321f6df Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 19 Sep 2025 20:54:09 +0200 Subject: [PATCH 074/127] remove buypass --- acme.sh | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/acme.sh b/acme.sh index 9105a0a4..4d849845 100755 --- a/acme.sh +++ b/acme.sh @@ -23,9 +23,6 @@ _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory" CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" -CA_BUYPASS="https://api.buypass.com/acme/directory" -CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory" - CA_ZEROSSL="https://acme.zerossl.com/v2/DV90" _ZERO_EAB_ENDPOINT="https://api.zerossl.com/acme/eab-credentials-email" @@ -42,14 +39,12 @@ CA_NAMES=" ZeroSSL.com,zerossl LetsEncrypt.org,letsencrypt LetsEncrypt.org_test,letsencrypt_test,letsencrypttest -BuyPass.com,buypass -BuyPass.com_test,buypass_test,buypasstest SSL.com,sslcom Google.com,google Google.com_test,googletest,google_test " -CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" +CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" @@ -5478,10 +5473,6 @@ renew() { _info "Switching back to $CA_LETSENCRYPT_V2" Le_API="$CA_LETSENCRYPT_V2" ;; - "$CA_BUYPASS_TEST") - _info "Switching back to $CA_BUYPASS" - Le_API="$CA_BUYPASS" - ;; "$CA_GOOGLE_TEST") _info "Switching back to $CA_GOOGLE" Le_API="$CA_GOOGLE" From 471e0c05f9b69dd2ae2ce7c7968b7f497334c18d Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 20 Sep 2025 10:38:43 +0200 Subject: [PATCH 075/127] remove mageia --- .github/workflows/Linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml index c74e9d3e..f3352a41 100644 --- a/.github/workflows/Linux.yml +++ b/.github/workflows/Linux.yml @@ -26,7 +26,7 @@ jobs: Linux: strategy: matrix: - os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"] + os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "gentoo/stage3"] runs-on: ubuntu-latest env: TEST_LOCAL: 1 From f22b490a10bf07317096511e59d7791fc13b4fb7 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 21 Sep 2025 18:04:59 +0200 Subject: [PATCH 076/127] remove buypass --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9a5c106b..a8cb5403 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,6 @@ https://github.com/acmesh-official/acmetest - [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default) - Letsencrypt.org CA -- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA) - [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) - [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) - [Pebble strict Mode](https://github.com/letsencrypt/pebble) From 5954f0dde54488d68bdd4c92ea2f0ab8ea1ee869 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Mon, 22 Sep 2025 12:11:50 +0200 Subject: [PATCH 077/127] Change to `--cert-profile` --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 7a8e4e9b..d157c2a4 100755 --- a/acme.sh +++ b/acme.sh @@ -7702,7 +7702,7 @@ _process() { _valid_to="$2" shift ;; - --certificate-profile | --profile) + --certificate-profile | --cert-profile) _certificate_profile="$2" shift ;; From 604e6873ba43609dc686f869e5c42a7d31547030 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Mon, 22 Sep 2025 12:12:17 +0200 Subject: [PATCH 078/127] Add short name + wiki link to help --- acme.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index d157c2a4..ef3c592d 100755 --- a/acme.sh +++ b/acme.sh @@ -180,6 +180,8 @@ _VALIDITY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Validity" _DNSCHECK_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnscheck" +_PROFILESELECTION_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Profile-selection" + _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" @@ -7006,7 +7008,8 @@ Parameters: If no match, the default offered chain will be used. (default: empty) See: $_PREFERRED_CHAIN_WIKI - --certificate-profile If the CA offers profiles, select the desired profile + --cert-profile, --certificate-profile If the CA offers profiles, select the desired profile + See: $_PROFILESELECTION_WIKI --valid-to Request the NotAfter field of the cert. See: $_VALIDITY_WIKI From 11995b958ab560b2e8e60c816ba79200163047e9 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 27 Sep 2025 22:57:42 +0200 Subject: [PATCH 079/127] add actalis.com CA --- acme.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index e895bbdc..23b03039 100755 --- a/acme.sh +++ b/acme.sh @@ -32,6 +32,8 @@ CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc" CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory" CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory" +CA_ACTALIS="https://acme-api.actalis.com/acme/directory" + DEFAULT_CA=$CA_ZEROSSL DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST @@ -42,9 +44,10 @@ LetsEncrypt.org_test,letsencrypt_test,letsencrypttest SSL.com,sslcom Google.com,google Google.com_test,googletest,google_test +Actalis.com,actalis.com,actalis " -CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" +CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST,$CA_ACTALIS" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" From e5214ea2e58c9a7fd5fb95f651a5cfc1193787f4 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 27 Sep 2025 23:12:05 +0200 Subject: [PATCH 080/127] add Actalis.com CA --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a8cb5403..f7038f59 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ https://github.com/acmesh-official/acmetest - Letsencrypt.org CA - [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) - [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) +- [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) - [Pebble strict Mode](https://github.com/letsencrypt/pebble) - Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA From b244c76dd5f316c55812c9ebd41012625e58e5a0 Mon Sep 17 00:00:00 2001 From: Steven Zhu Date: Sat, 27 Sep 2025 17:29:12 -0400 Subject: [PATCH 081/127] Add --list-profiles command to show CA profiles This commit introduces a new command, `--list-profiles`, to allow users to discover the certificate profiles supported by a Certificate Authority. The command queries the `meta.profiles` object within the ACME directory JSON for the selected server and formats the output for readability. If a CA does not publish profiles in its directory, the command reports that none were found. Usage: acme.sh --list-profiles [--server letsencrypt] --- acme.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/acme.sh b/acme.sh index 23b03039..24ebc5d4 100755 --- a/acme.sh +++ b/acme.sh @@ -5838,6 +5838,49 @@ list() { } +list_profiles() { + _initpath + _initAPI + + _l_server_url="$ACME_DIRECTORY" + _l_server_name="$(_getCAShortName "$_l_server_url")" + _info "Fetching profiles from $_l_server_name ($_l_server_url)..." + + # _initAPI fetches the directory, so we just need to parse its response. + response=$(_get "$_l_server_url" "" 10) + if [ "$?" != "0" ]; then + _err "Failed to connect to CA directory: $_l_server_url" + return 1 + fi + + # Isolate the profiles object using the script's regex tool + profiles_json=$(echo "$response" | _egrep_o '"profiles" *: *\{[^\}]*\}') + + if [ -z "$profiles_json" ]; then + _info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint." + return 0 + fi + + # Strip the outer layer to get the key-value pairs + profiles_kv=$(echo "$profiles_json" | sed 's/"profiles" *: *{//' | sed 's/}$//' | tr ',' '\n') + + printf "\n%-15s %s\n" "name" "info" + printf -- "--------------------------------------------------------------------\n" + + _old_IFS="$IFS" + IFS=' +' + for pair in $profiles_kv; do + # Trim quotes and whitespace + _name=$(echo "$pair" | cut -d: -f1 | tr -d '" \t') + _info_url=$(echo "$pair" | cut -d: -f2- | sed 's/^ *//' | tr -d '"') + printf "%-15s %s\n" "$_name" "$_info_url" + done + IFS="$_old_IFS" + + return 0 +} + _deploy() { _d="$1" _hooks="$2" @@ -7498,6 +7541,9 @@ _process() { --set-default-chain) _CMD="setdefaultchain" ;; + --list-profiles) + _CMD="list_profiles" + ;; -d | --domain) _dvalue="$2" @@ -8063,6 +8109,9 @@ _process() { setdefaultchain) setdefaultchain "$_preferred_chain" ;; + list_profiles) + list_profiles + ;; *) if [ "$_CMD" ]; then _err "Invalid command: $_CMD" From 80748b9fe0a3e9af6f8db7b97fb9bea69e8d7206 Mon Sep 17 00:00:00 2001 From: Steven Zhu Date: Sat, 27 Sep 2025 17:37:37 -0400 Subject: [PATCH 082/127] Quick Patch --- acme.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/acme.sh b/acme.sh index 24ebc5d4..308db1aa 100755 --- a/acme.sh +++ b/acme.sh @@ -5846,15 +5846,14 @@ list_profiles() { _l_server_name="$(_getCAShortName "$_l_server_url")" _info "Fetching profiles from $_l_server_name ($_l_server_url)..." - # _initAPI fetches the directory, so we just need to parse its response. response=$(_get "$_l_server_url" "" 10) if [ "$?" != "0" ]; then _err "Failed to connect to CA directory: $_l_server_url" return 1 fi - # Isolate the profiles object using the script's regex tool - profiles_json=$(echo "$response" | _egrep_o '"profiles" *: *\{[^\}]*\}') + normalized_response=$(echo "$response" | _normalizeJson) + profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *\{[^\}]*\}') if [ -z "$profiles_json" ]; then _info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint." From 0f5093c0b7555c149ae2d7ab01d548dfd7844b2c Mon Sep 17 00:00:00 2001 From: Steven Zhu Date: Sat, 27 Sep 2025 17:52:44 -0400 Subject: [PATCH 083/127] Remove space --- acme.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 308db1aa..e5a0885f 100755 --- a/acme.sh +++ b/acme.sh @@ -7542,7 +7542,7 @@ _process() { ;; --list-profiles) _CMD="list_profiles" - ;; + ;; -d | --domain) _dvalue="$2" @@ -8110,7 +8110,7 @@ _process() { ;; list_profiles) list_profiles - ;; + ;; *) if [ "$_CMD" ]; then _err "Invalid command: $_CMD" From d439933b52a0f251afaaa780acecff85d6eb3c29 Mon Sep 17 00:00:00 2001 From: Steven Zhu Date: Sun, 28 Sep 2025 19:20:08 -0400 Subject: [PATCH 084/127] add Profile column to --list output This commit adds a new "Profile" column to the output of the `--list` command. The column displays the value of the `Le_Certificate_Profile` variable stored in each domain's respective configuration file. If a profile is not set for a certificate, the column is left empty. This enhances the utility of the list command by providing more at-a-glance information about each certificate's configuration, which is particularly useful for CAs that support different certificate profiles. --- acme.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index e5a0885f..7caec290 100755 --- a/acme.sh +++ b/acme.sh @@ -5804,7 +5804,7 @@ list() { _sep="|" if [ "$_raw" ]; then if [ -z "$_domain" ]; then - printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew" + printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Profile${_sep}CA${_sep}Created${_sep}Renew" fi for di in "${CERT_HOME}"/*.*/; do d=$(basename "$di") @@ -5819,7 +5819,7 @@ list() { . "$DOMAIN_CONF" _ca="$(_getCAShortName "$Le_API")" if [ -z "$_domain" ]; then - printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" + printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$Le_Certificate_Profile${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" else if [ "$_domain" = "$d" ]; then cat "$DOMAIN_CONF" From 17da49bb782b797209531cb00cfcc8c3ee0370a8 Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Thu, 9 Oct 2025 13:16:28 +0200 Subject: [PATCH 085/127] 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 086/127] 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 087/127] 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 088/127] 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 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 089/127] 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 090/127] 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 3cdce86339d8ace8ba62c4ed756138bad669457d Mon Sep 17 00:00:00 2001 From: Jens Spanier Date: Tue, 21 Oct 2025 11:34:46 +0200 Subject: [PATCH 091/127] 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 092/127] 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 093/127] 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 094/127] 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 095/127] 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 096/127] 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 097/127] 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 098/127] 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 099/127] 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 100/127] 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 101/127] 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 102/127] 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 103/127] 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 104/127] 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 105/127] 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 106/127] 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 107/127] 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 108/127] 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 109/127] 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 110/127] 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 111/127] 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 112/127] 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 113/127] 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 114/127] 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 115/127] 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 116/127] 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 705fbcd570dfec12b9851cdd9b047020c60e5185 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 27 Nov 2025 22:13:18 +0100 Subject: [PATCH 117/127] 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 118/127] 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 119/127] 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 120/127] 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 122/127] 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 123/127] 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 124/127] 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 125/127] 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 126/127] 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 127/127] 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