Merge branch 'dev' into master

This commit is contained in:
ACHMAD ALIF NASRULLOH 2026-07-16 11:27:57 +07:00 committed by GitHub
commit 24501ac0ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 805 additions and 67 deletions

116
acme.sh
View file

@ -1995,6 +1995,26 @@ _calc_next_renew_time() {
printf "%s" "$_cnrt_next" printf "%s" "$_cnrt_next"
} }
#Usage: _calc_validto_renew_time notaftertime renewaldays now
#Prints the next renew time for a cert issued with a relative --valid-to.
#A negative renewaldays is anchored to the expiry: notaftertime +
#renewaldays*86400. Otherwise the cert renews one day before the expiry,
#or one hour before for certs whose lifetime is 24 hours or less.
_calc_validto_renew_time() {
_cvrt_end="$1"
_cvrt_days="$2"
_cvrt_now="$3"
if [ "$_cvrt_days" ] && [ "$_cvrt_days" -lt 0 ]; then
_math "$_cvrt_end" + "$_cvrt_days" \* 24 \* 60 \* 60
return 0
fi
if [ "$(_math "$_cvrt_end" - "$_cvrt_now")" -gt 86400 ]; then
_math "$_cvrt_end" - 86400
else
_math "$_cvrt_end" - 3600
fi
}
_mktemp() { _mktemp() {
if _exists mktemp; then if _exists mktemp; then
if mktemp 2>/dev/null; then if mktemp 2>/dev/null; then
@ -4125,7 +4145,7 @@ _regAccount() {
fi fi
_savecaconf "ACCOUNT_URL" "$_accUri" _savecaconf "ACCOUNT_URL" "$_accUri"
else else
ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)" _accUri="$(_readcaconf ACCOUNT_URL)"
fi fi
export ACCOUNT_URL="$_accUri" export ACCOUNT_URL="$_accUri"
@ -4179,6 +4199,12 @@ updateaccount() {
if [ "$code" = '200' ]; then if [ "$code" = '200' ]; then
echo "$response" >"$ACCOUNT_JSON_PATH" echo "$response" >"$ACCOUNT_JSON_PATH"
_info "Account update success for $_accUri." _info "Account update success for $_accUri."
# persist the effective mailbox like _regAccount does; otherwise
# "--update-account -m new@..." updates the CA but the local conf
# keeps showing the old address (issue 4673)
if [ "$_email" ]; then
_savecaconf "CA_EMAIL" "$_email"
fi
ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
_info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
@ -5298,7 +5324,7 @@ $_authorizations_map"
fi fi
# 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 # 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'"[^\}]*')" entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
_debug entry "$entry" _debug entry "$entry"
if [ -z "$keyauthorization" -a -z "$entry" ]; then if [ -z "$keyauthorization" -a -z "$entry" ]; then
@ -5630,7 +5656,7 @@ $_authorizations_map"
status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
_debug2 status "$status" _debug2 status "$status"
if _contains "$status" "invalid"; then if _contains "$status" "invalid"; then
error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" error="$(echo "$response" | _egrep_o '"error":[{][^}]*')"
_debug2 error "$error" _debug2 error "$error"
errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
_debug2 errordetail "$errordetail" _debug2 errordetail "$errordetail"
@ -5812,7 +5838,7 @@ $_authorizations_map"
return 1 return 1
fi fi
echo "$response" >"$CERT_PATH" echo "$response" | _strip_blank_lines >"$CERT_PATH"
_split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
if [ -z "$_preferred_chain" ]; then if [ -z "$_preferred_chain" ]; then
_preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN) _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN)
@ -5839,7 +5865,7 @@ $_authorizations_map"
_relcert="$CERT_PATH.alt" _relcert="$CERT_PATH.alt"
_relfullchain="$CERT_FULLCHAIN_PATH.alt" _relfullchain="$CERT_FULLCHAIN_PATH.alt"
_relca="$CA_CERT_PATH.alt" _relca="$CA_CERT_PATH.alt"
echo "$response" >"$_relcert" echo "$response" | _strip_blank_lines >"$_relcert"
_split_cert_chain "$_relcert" "$_relfullchain" "$_relca" _split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
if [ "$DEBUG" ]; then if [ "$DEBUG" ]; then
_debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")" _debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")"
@ -5944,19 +5970,8 @@ $_authorizations_map"
_info "It cannot be renewed automatically" _info "It cannot be renewed automatically"
_info "See: $_VALIDITY_WIKI" _info "See: $_VALIDITY_WIKI"
else else
_now=$(_time) Le_NextRenewTime=$(_calc_validto_renew_time "$Le_NextRenewTime" "$Le_RenewalDays" "$(_time)")
_debug2 "_now" "$_now" Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
_lifetime=$(_math $Le_NextRenewTime - $_now)
_debug2 "_lifetime" "$_lifetime"
if [ $_lifetime -gt 86400 ]; then
#if lifetime is logner than one day, it will renew one day before
Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
else
#if lifetime is less than 24 hours, it will renew one hour before
Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
fi
fi fi
elif [ "$Le_RenewalDays" -lt "0" ]; then elif [ "$Le_RenewalDays" -lt "0" ]; then
_enddate_value=$(_enddate "$CERT_PATH") _enddate_value=$(_enddate "$CERT_PATH")
@ -6057,6 +6072,15 @@ $_authorizations_map"
} }
#in_out_cert out_fullchain out_ca #in_out_cert out_fullchain out_ca
#Reads a PEM chain from stdin, prints it without the blank lines.
#Some CAs (Let's Encrypt) separate the certificates of a chain with a blank
#line, others (ZeroSSL) don't. The blank lines are valid PEM (RFC 7468), but
#some devices and APIs reject them, so the certs are stored back to back.
#https://github.com/acmesh-official/acme.sh/issues/1940
_strip_blank_lines() {
sed '/^[[:space:]]*$/d'
}
_split_cert_chain() { _split_cert_chain() {
_certf="$1" _certf="$1"
_fullchainf="$2" _fullchainf="$2"
@ -6575,7 +6599,7 @@ list_profiles() {
fi fi
normalized_response=$(echo "$response" | _normalizeJson) normalized_response=$(echo "$response" | _normalizeJson)
profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *\{[^\}]*\}') profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *[{][^}]*[}]')
if [ -z "$profiles_json" ]; then if [ -z "$profiles_json" ]; then
_info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint." _info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint."
@ -6997,15 +7021,39 @@ installcronjob() {
return 1 return 1
fi fi
_info "Installing cron job" _info "Installing cron job"
if ! $_CRONTAB -l 2>/dev/null | grep "$PROJECT_ENTRY --cron"; then _cron_entry="$random_minute $random_hour,$(_math "$random_hour" + 6),$(_math "$random_hour" + 12),$(_math "$random_hour" + 18) * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
_cron_entries="$($_CRONTAB -l 2>/dev/null)"
if [ "$?" != "0" ]; then
#when the user has no crontab yet, crontab -l also exits non-zero;
#only that case may proceed with an empty list. Any other listing
#failure must abort: piping an incomplete list back into 'crontab -'
#would wipe the user's existing cron jobs (issue 3079)
_cron_list_err="$($_CRONTAB -l 2>&1 >/dev/null)"
#separate greps: BRE alternation \| is a GNU extension and Solaris
#grep takes only a single -e pattern
if echo "$_cron_list_err" | grep -i "no crontab" >/dev/null ||
echo "$_cron_list_err" | grep -i "no fcrontab" >/dev/null ||
echo "$_cron_list_err" | grep -i "can't open" >/dev/null; then
_cron_entries=""
else
_err "Can not list the current cron jobs: $_cron_list_err"
_err "Refusing to install the cron job, that could wipe your existing cron jobs."
_err "Please add this cron job manually:"
_err "$_cron_entry"
return 1
fi
fi
if ! echo "$_cron_entries" | grep "$PROJECT_ENTRY --cron"; then
if _exists uname && uname -a | grep SunOS >/dev/null; then if _exists uname && uname -a | grep SunOS >/dev/null; then
_CRONTAB_STDIN="$_CRONTAB --" _CRONTAB_STDIN="$_CRONTAB --"
else else
_CRONTAB_STDIN="$_CRONTAB -" _CRONTAB_STDIN="$_CRONTAB -"
fi fi
$_CRONTAB -l 2>/dev/null | { {
cat if [ "$_cron_entries" ]; then
echo "$random_minute $random_hour,$(_math $random_hour + 6),$(_math $random_hour + 12),$(_math $random_hour + 18) * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" echo "$_cron_entries"
fi
echo "$_cron_entry"
} | $_CRONTAB_STDIN } | $_CRONTAB_STDIN
fi fi
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
@ -7234,7 +7282,7 @@ _deactivate() {
_debug "Trigger validation." _debug "Trigger validation."
vtype="$(_getIdType "$_d_domain")" vtype="$(_getIdType "$_d_domain")"
# 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 # 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'"[^\}]*')" entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
_debug entry "$entry" _debug entry "$entry"
if [ -z "$entry" ]; then if [ -z "$entry" ]; then
_err "$d: Cannot get domain token" _err "$d: Cannot get domain token"
@ -8020,6 +8068,7 @@ Parameters:
Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com' Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com'
--accountkey <file> Specifies the account key path, only valid for the '--install' command. --accountkey <file> Specifies the account key path, only valid for the '--install' command.
--days <ndays> Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. --days <ndays> Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
A negative value renews that many days before the cert expiry.
Negative values could be used to specify a number of days relative to the expiration date of the certificate. Negative values could be used to specify a number of days relative to the expiration date of the certificate.
--httpport <port> Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. --httpport <port> Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
--tlsport <port> Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --tlsport <port> Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
@ -9014,13 +9063,20 @@ _process() {
_debug2 LE_WORKING_DIR "$LE_WORKING_DIR" _debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
# --days and --valid-to are mutually exclusive by design: --valid-to pins # --valid-to pins the cert lifetime, so a creation-anchored (positive)
# the cert lifetime and the renewal time follows the expiry, so a # --days schedule can not apply and is rejected. A negative --days is
# creation-based --days schedule can not apply. # anchored to the expiry and composes with a relative --valid-to: the
# cert renews that many days before the expiry.
if [ "$_days" ] && [ "$_valid_to" ]; then if [ "$_days" ] && [ "$_valid_to" ]; then
_err "--days can not be used together with --valid-to." if ! _startswith "$_valid_to" "+"; then
_err "With --valid-to, the renewal time is derived from the expiry time automatically." _err "--days can not be used together with a fixed-date --valid-to: such a cert can not be renewed automatically."
return 1 return 1
fi
if ! _startswith "$_days" "-"; then
_err "A positive --days can not be used together with --valid-to, the renewal time is derived from the expiry time."
_err "Use a negative --days to renew that many days before the expiry, or omit --days to renew 1 day before the expiry."
return 1
fi
fi fi
if [ "$DEBUG" ]; then if [ "$DEBUG" ]; then

222
deploy/baidu_cdn.sh Normal file
View file

@ -0,0 +1,222 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034,SC2154
# Deploy hook: Baidu Cloud CDN
#
# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol
#
# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w
#
# Uses the same credential variables as dnsapi/dns_baidu.sh:
# export Baidu_AK="your-access-key-id"
# export Baidu_SK="your-secret-access-key"
#
# To deploy to a CDN domain different from the certificate CN
# (e.g. wildcard or multi-domain certs):
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com"
#
# Multiple CDN domains sharing the same certificate:
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
BAIDU_CDN_HOST="cdn.baidubce.com"
_BAIDU_CDN_BCE_AUTH_RESULT=""
baidu_cdn_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if ! _baidu_cdn_load_credentials; then
return 1
fi
_getdeployconf DEPLOY_BAIDU_CDN_DOMAIN
if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then
_savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN"
else
DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain"
fi
# Build JSON "domains" array from space-separated domain list
_domains_json=""
for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do
_d_e="$(_baidu_cdn_json_escape "$_d")"
if [ -z "$_domains_json" ]; then
_domains_json="\"${_d_e}\""
else
_domains_json="${_domains_json},\"${_d_e}\""
fi
done
# Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars
_cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)"
case "$_cert_name" in
[A-Za-z]*) ;;
*) _cert_name="c${_cert_name}" ;;
esac
# PEM content is already Base64 inside the -----BEGIN/END----- wrappers.
# The API expects the raw PEM as a JSON string, so newlines must be escaped as \n.
_cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')"
_key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')"
_debug2 _cert_name "$_cert_name"
_debug2 _domains_json "[$_domains_json]"
# Build JSON payload
_payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}"
# Generate BCE v1 authorization header (query string included in canonical request)
_cdn_path="/v2/domain/certificate"
_cdn_query="action=put"
_ts="$(_utc_date | sed 's/ /T/')Z"
_content_type="application/json; charset=utf-8"
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then
_err "Failed to sign request"
return 1
fi
_H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT"
_H2="x-bce-date: $_ts"
_H3="x-bce-content-sha256: $_payload_hash"
_H4="Host: $BAIDU_CDN_HOST"
_H5=""
_url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}"
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
if [ "$?" != "0" ]; then
_err "Failed to call Baidu Cloud CDN API"
return 1
fi
_debug2 response "$response"
if _contains "$response" "\"certId\""; then
_info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN"
return 0
fi
_err "Failed to deploy certificate to Baidu Cloud CDN: $response"
return 1
}
# BCE v1 signing with canonical query string support.
# The CDN endpoint uses ?action=put so it must be included in the canonical request.
_baidu_cdn_bce_auth() {
_method="$1"
_uri="$2"
_query="$3"
_host="$4"
_ts="$5"
_expire="$6"
_ct="$7"
_payload_hash="$8"
_BAIDU_CDN_BCE_AUTH_RESULT=""
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
_canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")"
_host_e="$(printf "%s" "$_host" | _url_encode upper-hex)"
_date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)"
_ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)"
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
_canonical_headers="content-type:${_ct_e}
host:${_host_e}
x-bce-content-sha256:${_hash_e}
x-bce-date:${_date_e}"
_canonical_request="${_method}
${_canonical_uri}
${_query}
${_canonical_headers}"
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
_signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
_signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
_BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
}
_baidu_cdn_load_credentials() {
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")"
Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")"
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
_err "Baidu_AK and Baidu_SK are required"
return 1
fi
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
return 0
}
_baidu_cdn_bce_encode_path() {
_p="$1"
_out=""
if [ "${_p#"/"}" != "$_p" ]; then
_out="/"
fi
_rest="${_p#/}"
while [ -n "$_rest" ]; do
_seg="${_rest%%/*}"
if [ "$_seg" ]; then
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
else
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
fi
fi
if [ "${_rest#*/}" = "$_rest" ]; then
break
fi
_rest="${_rest#*/}"
done
if [ -z "$_out" ]; then
_out="/"
fi
printf "%s" "$_out"
}
_baidu_cdn_trim_ws() {
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
}
_baidu_cdn_json_escape() {
_s="$1"
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
printf "%s" "$_s" |
sed 's/\\/\\\\/g; s/ /\\t/g' |
_baidu_cdn_json_encode
}
_baidu_cdn_json_encode() {
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
_baidu_cdn_hmac_sha256_hexkey() {
_key_hex="$1"
_msg="$2"
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
}

175
deploy/fortigate.sh Normal file
View file

@ -0,0 +1,175 @@
#!/usr/bin/env sh
# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate.
#
# FortiGate's native ACME integration does not support wildcard certificates or domain validation,
# and is not supported if you have a custom management web port (eg. DNAT web traffic).
#
# REQUIRED:
# export FGT_HOST="fortigate_hostname-or-ip"
# export FGT_TOKEN="fortigate_api_token"
#
# OPTIONAL:
# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set)
#
# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script.
# `--insecure` is required on first run if not already using a valid SSL certificate on firewall.
# Function to parse a FortiGate API response
_fortigate_parse_response() {
_fortigate_response="$1"
_fortigate_func="$2"
_fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4)
if [ "$_fortigate_status" != "success" ]; then
_err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
return 1
fi
_debug "[$_fortigate_func] Operation successful."
return 0
}
# Function to deploy a base64-encoded certificate to the firewall
_fortigate_deployer() {
_fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n')
_fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n')
_fortigate_payload=$(
cat <<EOF
{
"type": "regular",
"scope": "global",
"certname": "$_fortigate_cert_name",
"key_file_content": "$_fortigate_key_base64",
"file_content": "$_fortigate_cert_base64"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/local/import"
_debug "Uploading certificate via URL: $_fortigate_url"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
_debug "FortiGate API Response: $_fortigate_response"
_fortigate_parse_response "$_fortigate_response" "Deploying certificate" || return 1
}
# Function to upload a CA certificate to the firewall
# FortiGate does not automatically extract the CA from the full chain.
_fortigate_upload_ca_cert() {
_fortigate_ca_base64=$(_base64 <"$_fortigate_cca" | tr -d '\n')
_fortigate_payload=$(
cat <<EOF
{
"import_method": "file",
"scope": "global",
"file_content": "$_fortigate_ca_base64"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/ca/import"
_debug "Uploading CA certificate via URL: $_fortigate_url"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
_debug "FortiGate API CA Response: $_fortigate_response"
# FortiGate error -328 means that the CA certificate already exists.
if echo "$_fortigate_response" | grep -q '"error":[ ]*-328'; then
_debug "CA certificate already exists. Skipping CA upload."
return 0
fi
_fortigate_parse_response "$_fortigate_response" "Deploying CA certificate" || return 1
}
# Function to activate the new certificate
_fortigate_set_active_web_cert() {
_fortigate_payload=$(
cat <<EOF
{
"admin-server-cert": "$_fortigate_cert_name"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/system/global"
_debug "Setting GUI certificate..."
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "PUT" "application/json")
_fortigate_parse_response "$_fortigate_response" "Assigning active certificate" || return 1
}
# Function to clean up the previously deployed certificate
_fortigate_cleanup_previous_certificate() {
_getdeployconf FGT_LAST_CERT
if [ -n "$FGT_LAST_CERT" ] && [ "$FGT_LAST_CERT" != "$_fortigate_cert_name" ]; then
_debug "Found previously deployed certificate: $FGT_LAST_CERT. Deleting it."
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/vpn.certificate/local/${FGT_LAST_CERT}"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "" "$_fortigate_url" "" "DELETE" "application/json")
_debug "Delete certificate API response: $_fortigate_response"
_fortigate_parse_response "$_fortigate_response" "Delete previous certificate" || return 1
else
_debug "No previous certificate found."
fi
}
# Main deploy-hook function
fortigate_deploy() {
# Include date and time to ensure unique names.
_fortigate_cert_name="$(echo "$1" | sed 's/*/WILDCARD_/g')_$(date -u +"%Y-%m-%d_%H-%M-%S")"
_fortigate_ckey="$2"
_fortigate_cca="$4"
_fortigate_cfullchain="$5"
if [ ! -f "$_fortigate_ckey" ] || [ ! -f "$_fortigate_cfullchain" ]; then
_err "Valid key and/or certificate not found."
return 1
fi
# Save required environment variables if set; otherwise load saved values.
for _fortigate_var in FGT_HOST FGT_TOKEN FGT_PORT; do
if [ -n "$(eval echo "\$$_fortigate_var")" ]; then
_debug "Detected ENV variable $_fortigate_var. Saving to file."
_savedeployconf "$_fortigate_var" "$(eval echo "\$$_fortigate_var")" 1
else
_debug "Attempting to load variable $_fortigate_var from file."
_getdeployconf "$_fortigate_var"
fi
done
if [ -z "$FGT_HOST" ] || [ -z "$FGT_TOKEN" ]; then
_err "FGT_HOST and FGT_TOKEN must be set."
return 1
fi
FGT_PORT="${FGT_PORT:-443}"
_debug "Using FortiGate port: $FGT_PORT"
# Upload the new certificate.
_fortigate_deployer || return 1
# Upload the CA certificate.
if [ -n "$_fortigate_cca" ] && [ -f "$_fortigate_cca" ]; then
_fortigate_upload_ca_cert || return 1
else
_debug "No CA certificate provided."
fi
# Activate the new certificate.
_fortigate_set_active_web_cert || return 1
# Delete the previously deployed certificate only after successful activation.
_fortigate_cleanup_previous_certificate || return 1
# Save the new certificate name for cleanup during the next deployment.
_savedeployconf "FGT_LAST_CERT" "$_fortigate_cert_name" 1
}

View file

@ -18,7 +18,9 @@ Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() { dns_ali_add() {
fulldomain=$1 # the API only accepts punycode for IDN domains, and a raw UTF-8 domain
# also breaks the request signature (issue 4733)
fulldomain=$(_idn "$1")
txtvalue=$2 txtvalue=$2
_prepare_ali_credentials || return 1 _prepare_ali_credentials || return 1
@ -33,7 +35,7 @@ dns_ali_add() {
} }
dns_ali_rm() { dns_ali_rm() {
fulldomain=$1 fulldomain=$(_idn "$1")
txtvalue=$2 txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"

248
dnsapi/dns_comlaude.sh Normal file
View file

@ -0,0 +1,248 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_comlaude_info='comlaude.com
Site: comlaude.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_comlaude
Options:
COMLAUDE_USERNAME User account
COMLAUDE_PASSWORD User password
COMLAUDE_API_KEY generated API key
COMLAUDE_GROUP_ID Group ID in comlaude user profile
Get it from the https://www.comlaude.com
Issues: github.com/acmesh-official/acme.sh/issues/7112
'
# ===== CONFIG =====
COMLAUDE_API="https://api.comlaude.com"
########## AUTH ##########
_comlaude_auth() {
_debug "Checking cached ComLaude token"
# Try to get token from account.conf
if [ -z "$COMLAUDE_ACCESS_TOKEN" ]; then
COMLAUDE_ACCESS_TOKEN="$(_readaccountconf_mutable COMLAUDE_ACCESS_TOKEN)"
COMLAUDE_TOKEN_EXPIRY="$(_readaccountconf_mutable COMLAUDE_TOKEN_EXPIRY)"
fi
_now=$(_time)
if [ -n "$COMLAUDE_ACCESS_TOKEN" ] && [ -n "$COMLAUDE_TOKEN_EXPIRY" ] && [ "$_now" -lt "$COMLAUDE_TOKEN_EXPIRY" ]; then
_debug "Using cached ComLaude token (valid ${COMLAUDE_TOKEN_EXPIRY} > ${_now})"
return 0
fi
_info "ComLaude auth..."
_comlaude_body="{\"username\":\"$COMLAUDE_USERNAME\",\"password\":\"$COMLAUDE_PASSWORD\",\"api_key\":\"$COMLAUDE_API_KEY\"}"
_comlaude_response="$(_post "$_comlaude_body" "$COMLAUDE_API/api_login" "" "POST" "application/json")"
if ! _contains "$_comlaude_response" "access_token"; then
_err "Auth failed: $_comlaude_response"
return 1
fi
COMLAUDE_ACCESS_TOKEN=$(echo "$_comlaude_response" | _egrep_o '"access_token":"[^"]*"' | cut -d'"' -f4)
# store expiracy from api reply l'API ("expires_in" in seconds)
_comlaude_expires_in=$(echo "$_comlaude_response" | _egrep_o '"expires_in":[0-9]*' | cut -d: -f2)
[ -z "$_comlaude_expires_in" ] && _comlaude_expires_in=3000 # fallback if no info
COMLAUDE_TOKEN_EXPIRY=$(($(_time) + _comlaude_expires_in - 60)) # margin of 60s to secure renew
_saveaccountconf_mutable COMLAUDE_ACCESS_TOKEN "$COMLAUDE_ACCESS_TOKEN"
_saveaccountconf_mutable COMLAUDE_TOKEN_EXPIRY "$COMLAUDE_TOKEN_EXPIRY"
return 0
}
########## DOMAIN RESOLUTION ##########
_comlaude_get_root() {
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
if [ -z "$COMLAUDE_GROUP_ID" ]; then
_err "Missing COMLAUDE_GROUP_ID"
return 1
fi
_comlaude_input_domain="$1"
_comlaude_input_domain="${_comlaude_input_domain#_acme-challenge.}"
case "$_comlaude_input_domain" in
\*.*) _comlaude_input_domain="${_comlaude_input_domain#*.}" ;;
esac
_debug "Normalized domain: $_comlaude_input_domain"
_comlaude_i=1
while true; do
_comlaude_d=$(printf "%s" "$_comlaude_input_domain" | cut -d . -f "$_comlaude_i-")
[ -z "$_comlaude_d" ] && {
_debug "No matching domain found for $_comlaude_input_domain"
return 1
}
# don't test unnecessary levels
# registered domain : TLD only (no dot after cut).
case "$_comlaude_d" in
*.*) : ;;
*)
_debug "Skipping bare TLD candidate: $_comlaude_d"
_comlaude_i=$((_comlaude_i + 1))
continue
;;
esac
_debug "Checking domain: $_comlaude_d"
_comlaude_retry=0
_comlaude_max_retry=3 # to avoid network errors
_comlaude_DOM_ID=""
_comlaude_Z_ID=""
while [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ]; do
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_debug "Full URL: $COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone"
_comlaude_response="$(_get "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone")"
_H1=""
_debug "RAW response for $_comlaude_d (try $((_comlaude_retry + 1))): $_comlaude_response"
# If empty -> true network issue, we retry
if [ -z "$_comlaude_response" ]; then
_comlaude_retry=$((_comlaude_retry + 1))
[ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
continue
fi
# 404 -> domain not found in that level. no retry : continue
if echo "$_comlaude_response" | grep -q '"status_code":404'; then
_debug "404 for $_comlaude_d, moving to next level (not retrying)"
break
fi
# Domain missing (200 reply, data empty) -> continue
if echo "$_comlaude_response" | grep -q '"data":\[\]'; then
_debug "Empty data for $_comlaude_d, moving to next level"
break
fi
# Extraction via _egrep_o
_comlaude_DOM_ID="$(echo "$_comlaude_response" | _egrep_o '"id":"[^"]*"' | head -n1 | cut -d':' -f2 | tr -d '"')"
_comlaude_Z_ID="$(echo "$_comlaude_response" | _egrep_o '"active_zone":\{"id":"[^"]*"' | _egrep_o '"id":"[^"]*"$' | cut -d':' -f2 | tr -d '"')"
if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
break
fi
# 200 reply but malformed data / noid -> retry transport
_comlaude_retry=$((_comlaude_retry + 1))
[ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
done
_debug "_comlaude_DOM_ID=$_comlaude_DOM_ID"
_debug "_comlaude_Z_ID=$_comlaude_Z_ID"
if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
_comlaude_domain="$_comlaude_d"
_comlaude_domain_id="$_comlaude_DOM_ID"
_comlaude_zone_id="$_comlaude_Z_ID"
return 0
fi
_comlaude_i=$((_comlaude_i + 1))
done
}
########## ADD TXT ##########
dns_comlaude_add() {
fulldomain="$1"
txtvalue="$2"
COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
if [ -z "$COMLAUDE_USERNAME" ] || [ -z "$COMLAUDE_PASSWORD" ] || [ -z "$COMLAUDE_API_KEY" ]; then
_err "You didn't specify ComLaude credentials (COMLAUDE_USERNAME, COMLAUDE_PASSWORD, COMLAUDE_API_KEY)."
return 1
fi
# Backup variable after validation
_saveaccountconf_mutable COMLAUDE_USERNAME "$COMLAUDE_USERNAME"
_saveaccountconf_mutable COMLAUDE_PASSWORD "$COMLAUDE_PASSWORD"
_saveaccountconf_mutable COMLAUDE_API_KEY "$COMLAUDE_API_KEY"
_saveaccountconf_mutable COMLAUDE_GROUP_ID "$COMLAUDE_GROUP_ID"
_info "Adding TXT: $fulldomain"
_comlaude_auth || return 1
_comlaude_get_root "$fulldomain" || return 1
_debug "Root: $_comlaude_domain"
_comlaude_data="{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"value\":\"$txtvalue\",\"ttl\":60}"
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
export _H2="Content-Type: application/json"
_comlaude_response="$(_post "$_comlaude_data" "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records")"
_H1=""
_H2=""
if ! echo "$_comlaude_response" | grep -q '"id"'; then
_err "Failed to create TXT"
_debug "$_comlaude_response"
return 1
fi
return 0
}
########## REMOVE TXT ##########
dns_comlaude_rm() {
fulldomain="$1"
txtvalue="$2"
COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
_info "Removing TXT: $fulldomain"
_comlaude_auth || return 1
_comlaude_get_root "$fulldomain" || return 1
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_comlaude_encoded_name="$(printf '%s' "$fulldomain" | _url_encode)"
_comlaude_encoded_value="$(printf '%s' "$txtvalue" | _url_encode)"
_comlaude_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records?filter[type]=TXT&filter[name]=$_comlaude_encoded_name&filter[value]=$_comlaude_encoded_value"
_comlaude_response="$(_get "$_comlaude_url")"
_H1=""
_debug "Filtered records response: $_comlaude_response"
# first "id" top-level of reply (record itself,
# always on first position of each data[] object)
_comlaude_record_id="$(echo "$_comlaude_response" | _egrep_o '"data":\[\{"id":"[^"]*"' | _egrep_o '"[^"]*"$' | tr -d '"')"
if [ -z "$_comlaude_record_id" ]; then
_info "No matching TXT record found to delete for $fulldomain / $txtvalue"
return 0
fi
_debug "Deleting record $_comlaude_record_id"
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_comlaude_del_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records/$_comlaude_record_id"
_comlaude_del_resp="$(_post "" "$_comlaude_del_url" "" "DELETE")"
_H1=""
if echo "$_comlaude_del_resp" | grep -q '"error"'; then
_err "Delete failed for $_comlaude_record_id"
_debug "$_comlaude_del_resp"
return 1
fi
_info "Deleted record $_comlaude_record_id"
return 0
}

View file

@ -25,7 +25,7 @@ dns_dnsexit_add() {
return 1 return 1
fi fi
_dnsexit_zone_op add ',"ttl":0,"overwrite":false' _dnsexit_zone_op add ',"ttl":1,"overwrite":false'
} }
#Usage: fulldomain txtvalue #Usage: fulldomain txtvalue

View file

@ -18,6 +18,7 @@ dns_dnsimple_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
DNSimple_OAUTH_TOKEN="" DNSimple_OAUTH_TOKEN=""
_err "You have not set the dnsimple oauth token yet." _err "You have not set the dnsimple oauth token yet."
@ -26,7 +27,7 @@ dns_dnsimple_add() {
fi fi
# save the oauth token for later # save the oauth token for later
_saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" _saveaccountconf_mutable DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
if ! _get_account_id; then if ! _get_account_id; then
_err "failed to retrieve account id" _err "failed to retrieve account id"
@ -57,6 +58,12 @@ dns_dnsimple_add() {
dns_dnsimple_rm() { dns_dnsimple_rm() {
fulldomain=$1 fulldomain=$1
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
_err "You have not set the dnsimple oauth token yet."
return 1
fi
if ! _get_account_id; then if ! _get_account_id; then
_err "failed to retrieve account id" _err "failed to retrieve account id"
return 1 return 1
@ -123,9 +130,9 @@ _get_root() {
# returns _account_id # returns _account_id
_get_account_id() { _get_account_id() {
DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf DNSimple_ACCOUNT_ID)}" DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf_mutable DNSimple_ACCOUNT_ID)}"
if [ "$DNSimple_ACCOUNT_ID" ]; then if [ "$DNSimple_ACCOUNT_ID" ]; then
_saveaccountconf DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID" _saveaccountconf_mutable DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
_account_id="$DNSimple_ACCOUNT_ID" _account_id="$DNSimple_ACCOUNT_ID"
_debug _account_id "$_account_id" _debug _account_id "$_account_id"
return 0 return 0

View file

@ -23,6 +23,8 @@ dns_dynu_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId="" Dynu_ClientId=""
Dynu_Secret="" Dynu_Secret=""
@ -32,8 +34,8 @@ dns_dynu_add() {
fi fi
#save the client id and secret to the account conf file. #save the client id and secret to the account conf file.
_saveaccountconf Dynu_ClientId "$Dynu_ClientId" _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf Dynu_Secret "$Dynu_Secret" _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token." _info "Getting Dynu token."
@ -69,6 +71,8 @@ dns_dynu_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId="" Dynu_ClientId=""
Dynu_Secret="" Dynu_Secret=""
@ -78,8 +82,8 @@ dns_dynu_rm() {
fi fi
#save the client id and secret to the account conf file. #save the client id and secret to the account conf file.
_saveaccountconf Dynu_ClientId "$Dynu_ClientId" _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf Dynu_Secret "$Dynu_Secret" _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token." _info "Getting Dynu token."

View file

@ -182,7 +182,11 @@ dns_infomaniak_rm() {
_get_zone() { _get_zone() {
domain="$1" domain="$1"
# Whatever the domain is, you can get the fqdn with the following. # Whatever the domain is, you can get the fqdn with the following.
# shellcheck disable=SC1004 response=$(_get "${INFOMANIAK_API_URL}/2/domains/${domain}/zones")
response=$(_get "${INFOMANIAK_API_URL}/2/domains/${domain}/zones" | sed 's/.*\[{"fqdn"\:"\(.*\)/\1/') _debug2 "_get_zone response" "$response"
echo "${response%%\"*}" if ! _contains "$response" '"result":"success"'; then
_err "cannot get zones for ${domain}, response: ${response}"
return 1
fi
echo "$response" | _egrep_o '"fqdn" *: *"[^"]*"' | _head_n 1 | cut -d '"' -f 4
} }

View file

@ -6,6 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot
Options: Options:
KNOT_SERVER Server hostname. Default: "localhost". KNOT_SERVER Server hostname. Default: "localhost".
KNOT_KEY TSIG key data, not a file path. knsupdate "key" statement format: "[alg:]name secret". E.g. "hmac-sha256:acme_key BASE64SECRET=" KNOT_KEY TSIG key data, not a file path. knsupdate "key" statement format: "[alg:]name secret". E.g. "hmac-sha256:acme_key BASE64SECRET="
KNOT_ZONE Zone name. Optional, set it when the challenge record lives in a delegated subdomain zone. Default: the parent domain of the challenge record.
' '
# See also dns_nsupdate.sh # See also dns_nsupdate.sh
@ -21,6 +22,9 @@ dns_knot_add() {
# save the dns server and key to the account.conf file. # save the dns server and key to the account.conf file.
_saveaccountconf KNOT_SERVER "${KNOT_SERVER}" _saveaccountconf KNOT_SERVER "${KNOT_SERVER}"
_saveaccountconf KNOT_KEY "${KNOT_KEY}" _saveaccountconf KNOT_KEY "${KNOT_KEY}"
if [ -n "${KNOT_ZONE}" ]; then
_saveaccountconf KNOT_ZONE "${KNOT_ZONE}"
fi
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "Domain does not exist." _err "Domain does not exist."
@ -84,6 +88,13 @@ EOF
# _domain=domain.com # _domain=domain.com
_get_root() { _get_root() {
domain=$1 domain=$1
# a delegated subdomain zone cannot be derived from the record name;
# let the user name the zone explicitly (issue 2881)
if [ -n "${KNOT_ZONE}" ]; then
_domain="${KNOT_ZONE%.}"
_debug "Using KNOT_ZONE zone" "${_domain}"
return 0
fi
i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
i=$(_math "$i" - 1) i=$(_math "$i" - 1)

View file

@ -115,12 +115,15 @@ _oci_config() {
_clearaccountconf_mutable OCI_CLI_PROFILE _clearaccountconf_mutable OCI_CLI_PROFILE
fi fi
OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}" if [ -z "$OCI_CLI_TENANCY" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_TENANCY=$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")
fi
if [ -z "$OCI_CLI_TENANCY" ]; then
OCI_CLI_TENANCY=$(_readaccountconf_mutable OCI_CLI_TENANCY)
fi
if [ "$OCI_CLI_TENANCY" ]; then if [ "$OCI_CLI_TENANCY" ]; then
_saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY" _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
fi fi
if [ -z "$OCI_CLI_TENANCY" ]; then if [ -z "$OCI_CLI_TENANCY" ]; then
@ -128,41 +131,47 @@ _oci_config() {
return 1 return 1
fi fi
OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}" if [ -z "$OCI_CLI_USER" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_USER=$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")
fi
if [ -z "$OCI_CLI_USER" ]; then
OCI_CLI_USER=$(_readaccountconf_mutable OCI_CLI_USER)
fi
if [ "$OCI_CLI_USER" ]; then if [ "$OCI_CLI_USER" ]; then
_saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER" _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
fi fi
if [ -z "$OCI_CLI_USER" ]; then if [ -z "$OCI_CLI_USER" ]; then
_err "Error: unable to read OCI_CLI_USER from config file or environment variable." _err "Error: unable to read OCI_CLI_USER from config file or environment variable."
return 1 return 1
fi fi
OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}" if [ -z "$OCI_CLI_REGION" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_REGION=$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")
fi
if [ -z "$OCI_CLI_REGION" ]; then
OCI_CLI_REGION=$(_readaccountconf_mutable OCI_CLI_REGION)
fi
if [ "$OCI_CLI_REGION" ]; then if [ "$OCI_CLI_REGION" ]; then
_saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION" _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
_debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
fi fi
if [ -z "$OCI_CLI_REGION" ]; then if [ -z "$OCI_CLI_REGION" ]; then
_err "Error: unable to read OCI_CLI_REGION from config file or environment variable." _err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
return 1 return 1
fi fi
OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}" if [ -z "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
if [ -z "$OCI_CLI_KEY" ]; then OCI_CLI_KEY_FILE=$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")
_clearaccountconf_mutable OCI_CLI_KEY fi
OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}" if [ "$OCI_CLI_KEY" ]; then
if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
_debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
_saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
fi
else
_saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY" _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
elif [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
_debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
_saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
else
OCI_CLI_KEY=$(_readaccountconf_mutable OCI_CLI_KEY)
fi fi
if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then

View file

@ -224,7 +224,7 @@ _ovh_authentication() {
_H3="" _H3=""
_H4="" _H4=""
_ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "GET","path": "/domain/zone/*/record/*"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}'
response="$(_post "$_ovhdata" "$OVH_API/auth/credential")" response="$(_post "$_ovhdata" "$OVH_API/auth/credential")"
_debug3 response "$response" _debug3 response "$response"

View file

@ -71,4 +71,4 @@ waha_send() {
_err "waha send error." _err "waha send error."
_err "$response" _err "$response"
return 1 return 1
} }