Merge pull request #6745 from acmesh-official/dev
Some checks failed
DNS / CheckToken (push) Has been cancelled
DragonFlyBSD / DragonFlyBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Linux / Linux (almalinux:latest) (push) Has been cancelled
Linux / Linux (alpine:latest) (push) Has been cancelled
Linux / Linux (archlinux:latest) (push) Has been cancelled
Linux / Linux (debian:latest) (push) Has been cancelled
Linux / Linux (fedora:latest) (push) Has been cancelled
Linux / Linux (gentoo/stage3) (push) Has been cancelled
Linux / Linux (kalilinux/kali) (push) Has been cancelled
Linux / Linux (opensuse/leap:latest) (push) Has been cancelled
Linux / Linux (oraclelinux:8) (push) Has been cancelled
Linux / Linux (ubuntu:latest) (push) Has been cancelled
MacOS / MacOS (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Omnios / Omnios (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Omnios / Omnios (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
PebbleStrict / PebbleStrict (push) Has been cancelled
PebbleStrict / PebbleStrict_IPCert (push) Has been cancelled
Solaris / Solaris (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Solaris / Solaris (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, ) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Has been cancelled
Ubuntu / Ubuntu (ZeroSSL RSA Domain Secure Site CA, ZeroSSL ECC Domain Secure Site CA, githubtest@acme.sh, ZeroSSL.com, ) (push) Has been cancelled
Windows / Windows (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Build DockerHub / CheckToken (push) Has been cancelled
Shellcheck / ShellCheck (push) Has been cancelled
Shellcheck / shfmt (push) Has been cancelled
DNS / Fail (push) Has been cancelled
DNS / Docker (push) Has been cancelled
DNS / MacOS (push) Has been cancelled
DNS / Windows (push) Has been cancelled
DNS / FreeBSD (push) Has been cancelled
DNS / OpenBSD (push) Has been cancelled
DNS / NetBSD (push) Has been cancelled
DNS / DragonFlyBSD (push) Has been cancelled
DNS / Solaris (push) Has been cancelled
DNS / Omnios (push) Has been cancelled
DNS / OpenIndiana (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled

sync
This commit is contained in:
neil
2026-01-06 08:55:01 +01:00
committed by GitHub
13 changed files with 582 additions and 23 deletions

View File

@@ -251,7 +251,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
@@ -302,7 +306,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
@@ -354,7 +362,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
@@ -406,7 +418,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
@@ -464,6 +480,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
Omnios:
@@ -513,6 +534,12 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
OpenIndiana:
@@ -539,7 +566,7 @@ jobs:
- uses: actions/checkout@v4
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openindiana-vm@v0
- uses: vmactions/openindiana-vm@v1
with:
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
sync: nfs
@@ -562,5 +589,12 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -67,5 +67,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -72,5 +72,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -67,5 +67,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -71,5 +71,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -72,5 +72,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -61,7 +61,7 @@ jobs:
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openindiana-vm@v0
- uses: vmactions/openindiana-vm@v1
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
@@ -71,5 +71,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

View File

@@ -73,5 +73,9 @@ jobs:
run: |
cd ../acmetest \
&& ./letest.sh
- name: onError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"

56
acme.sh
View File

@@ -1466,7 +1466,7 @@ _toPkcs() {
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca"
fi
if [ "$?" = "0" ]; then
_savedomainconf "Le_PFXPassword" "$pfxPassword"
_savedomainconf "Le_PFXPassword" "$pfxPassword" "base64"
fi
}
@@ -2783,6 +2783,7 @@ _clearAPI() {
ACME_REVOKE_CERT=""
ACME_NEW_NONCE=""
ACME_AGREEMENT=""
ACME_RENEWAL_INFO=""
}
#server
@@ -2827,6 +2828,9 @@ _initAPI() {
ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
export ACME_AGREEMENT
ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3)
export ACME_RENEWAL_INFO
_debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
_debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
_debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
@@ -2834,6 +2838,7 @@ _initAPI() {
_debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
_debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
_debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE"
_debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO"
if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then
return 0
fi
@@ -4465,7 +4470,7 @@ issue() {
Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)
_debug Le_NextRenewTime "$Le_NextRenewTime"
if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
_valid_to_saved=$(_readdomainconf Le_Valid_to)
_valid_to_saved=$(_readdomainconf Le_Valid_To)
if [ "$_valid_to_saved" ] && ! _startswith "$_valid_to_saved" "+"; then
_info "The domain is set to be valid to: $_valid_to_saved"
_info "It cannot be renewed automatically"
@@ -5450,10 +5455,10 @@ $_authorizations_map"
_savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
#convert to pkcs12
Le_PFXPassword="$(_readdomainconf Le_PFXPassword)"
if [ "$Le_PFXPassword" ]; then
_toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword"
fi
export CERT_PFX_PATH
if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
_savedomainconf "Le_RealCertPath" "$_real_cert"
@@ -5563,6 +5568,10 @@ renew() {
Le_RenewHook="$(_readdomainconf Le_RenewHook)"
Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)"
Le_Certificate_Profile="$(_readdomainconf Le_Certificate_Profile)"
Le_Valid_From="$(_readdomainconf Le_Valid_From)"
Le_Valid_To="$(_readdomainconf Le_Valid_To)"
Le_ExtKeyUse="$(_readdomainconf Le_ExtKeyUse)"
# 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.
@@ -5744,6 +5753,10 @@ signcsr() {
_local_addr="${11}"
_challenge_alias="${12}"
_preferred_chain="${13}"
_valid_f="${14}"
_valid_t="${15}"
_cert_prof="${16}"
_en_key_usage="${17}"
_csrsubj=$(_readSubjectFromCSR "$_csrfile")
if [ "$?" != "0" ]; then
@@ -5787,7 +5800,7 @@ signcsr() {
_info "Copying CSR to: $CSR_PATH"
cp "$_csrfile" "$CSR_PATH"
issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain"
issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain" "$_valid_f" "$_valid_t" "$_cert_prof" "$_en_key_usage"
}
@@ -5840,7 +5853,8 @@ list() {
if [ -z "$_domain" ]; then
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
for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do
[ -d "$di" ] || continue
d=$(basename "$di")
_debug d "$d"
(
@@ -6537,6 +6551,36 @@ deactivate() {
done
}
#cert
_getAKI() {
_cert="$1"
openssl x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ' :'
}
#cert
_getSerial() {
_cert="$1"
openssl x509 -in "$_cert" -serial -noout | cut -d = -f 2
}
#cert
_get_ARI() {
_cert="$1"
_aki=$(_getAKI "$_cert")
_ser=$(_getSerial "$_cert")
_debug2 "_aki" "$_aki"
_debug2 "_ser" "$_ser"
_akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)"
_debug2 "_akiurl" "$_akiurl"
_serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)"
_debug2 "_serurl" "$_serurl"
_ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl"
_get "$_ARI_URL"
}
# Detect profile file if not specified as environment variable
_detect_profile() {
if [ -n "$PROFILE" -a -f "$PROFILE" ]; then
@@ -8112,7 +8156,7 @@ _process() {
deploy "$_domain" "$_deploy_hook" "$_ecc"
;;
signcsr)
signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain"
signcsr "$_csr" "$_webroot" "$_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"
;;
showcsr)
showcsr "$_csr" "$_domain"

View File

@@ -8,6 +8,8 @@
# export QINIU_CDN_DOMAIN="cdn.example.com"
# If you have more than one domain, just
# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
# Optional: force HTTPS redirect (default: false)
# export QINIU_FORCE_HTTPS="true"
QINIU_API_BASE="https://api.qiniu.com"
@@ -44,6 +46,12 @@ qiniu_deploy() {
QINIU_CDN_DOMAIN="$_cdomain"
fi
if [ -z "$QINIU_FORCE_HTTPS" ]; then
QINIU_FORCE_HTTPS="false"
else
_savedomainconf QINIU_FORCE_HTTPS "$QINIU_FORCE_HTTPS"
fi
## upload certificate
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
@@ -69,7 +77,7 @@ qiniu_deploy() {
_debug certId "$_certId"
## update domain ssl config
update_body="{\"certid\":$_certId,\"forceHttps\":false}"
update_body="{\"certid\":$_certId,\"forceHttps\":$QINIU_FORCE_HTTPS}"
for domain in $QINIU_CDN_DOMAIN; do
update_path="/domain/$domain/httpsconf"
update_access_token="$(_make_access_token "$update_path")"

216
dnsapi/dns_qc.sh Executable file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_qc_info='QUIC.cloud
Site: quic.cloud
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_qc
Options:
QC_API_KEY QC API Key
QC_API_EMAIL Your account email
'
QC_Api="https://api.quic.cloud/v2"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_qc_add() {
fulldomain=$1
txtvalue=$2
_debug "Enter dns_qc_add fulldomain: $fulldomain, txtvalue: $txtvalue"
QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}"
QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}"
if [ "$QC_API_KEY" ]; then
_saveaccountconf_mutable QC_API_KEY "$QC_API_KEY"
else
_err "You didn't specify a QUIC.cloud api key as QC_API_KEY."
_err "You can get yours from here https://my.quic.cloud/up/api."
return 1
fi
if ! _contains "$QC_API_EMAIL" "@"; then
_err "It seems that the QC_API_EMAIL=$QC_API_EMAIL is not a valid email address."
_err "Please check and retry."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable QC_API_EMAIL "$QC_API_EMAIL"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain during add"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
_qc_rest GET "zones/${_domain_id}/records"
if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_err "Error failed response from QC GET: $response"
return 1
fi
# For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
# we can not use updating anymore.
# count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
# _debug count "$count"
# if [ "$count" = "0" ]; then
_info "Adding txt record"
if _qc_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":1800}"; then
if _contains "$response" "$txtvalue"; then
_info "Added txt record, OK"
return 0
elif _contains "$response" "Same record already exists"; then
_info "txt record already exists, OK"
return 0
else
_err "Add txt record error: $response"
return 1
fi
fi
_err "Add txt record error: POST failed: $response"
return 1
}
#fulldomain txtvalue
dns_qc_rm() {
fulldomain=$1
txtvalue=$2
_debug "Enter dns_qc_rm fulldomain: $fulldomain, txtvalue: $txtvalue"
QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}"
QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain during rm"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
_qc_rest GET "zones/${_domain_id}/records"
if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_err "Error rm GET response: $response"
return 1
fi
_debug "Pre-jq response:" "$response"
# Do not use jq or subsequent code
#response=$(echo "$response" | jq ".result[] | select(.id) | select(.content == \"$txtvalue\") | select(.type == \"TXT\")")
#_debug "get txt response" "$response"
#if [ "${response}" = "" ]; then
# _info "Don't need to remove txt records."
# return 0
#fi
#record_id=$(echo "$response" | grep \"id\" | awk -F ' ' '{print $2}' | sed 's/,$//')
#_debug "txt record_id" "$record_id"
#Instead of jq
array=$(echo "$response" | grep -o '\[[^]]*\]' | sed 's/^\[\(.*\)\]$/\1/')
if [ -z "$array" ]; then
_err "Expected array in QC response: $response"
return 1
fi
# Temporary file to hold matched content (one per line)
tmpfile=$(_mktemp)
echo "$array" | grep -o '{[^}]*}' | sed 's/^{//;s/}$//' >"$tmpfile"
record_id=""
while IFS= read -r obj || [ -n "$obj" ]; do
if echo "$obj" | grep -q '"TXT"' && echo "$obj" | grep -q '"id"' && echo "$obj" | grep -q "$txtvalue"; then
_debug "response includes" "$obj"
record_id=$(echo "$obj" | sed 's/^\"id\":\([0-9]\+\).*/\1/')
break
fi
done <"$tmpfile"
rm "$tmpfile"
if [ -z "$record_id" ]; then
_info "TXT record, or $txtvalue not found, nothing to remove"
return 0
fi
#End of jq replacement
if ! _qc_rest DELETE "zones/$_domain_id/records/$record_id"; then
_info "Delete txt record error."
return 1
fi
_info "TXT Record ID: $record_id successfully deleted"
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=1
p=1
h=$(printf "%s" "$domain" | cut -d . -f2-)
_debug h "$h"
if [ -z "$h" ]; then
_err "$h ($domain) is an invalid domain"
return 1
fi
if ! _qc_rest GET "zones"; then
_err "qc_rest failed"
return 1
fi
if _contains "$response" "\"name\":\"$h\"" || _contains "$response" "\"name\":\"$h.\""; then
_domain_id=$h
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi
_err "Empty domain_id $h"
return 1
fi
_err "Missing domain_id $h"
return 1
}
_qc_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
email_trimmed=$(echo "$QC_API_EMAIL" | tr -d '"')
token_trimmed=$(echo "$QC_API_KEY" | tr -d '"')
export _H1="Content-Type: application/json"
export _H2="X-Auth-Email: $email_trimmed"
export _H3="X-Auth-Key: $token_trimmed"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$QC_Api/$ep" "" "$m")"
else
response="$(_get "$QC_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

229
dnsapi/dns_virakcloud.sh Executable file
View File

@@ -0,0 +1,229 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_virakcloud_info='VirakCloud DNS API
Site: VirakCloud.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_virakcloud
Options:
VIRAKCLOUD_API_TOKEN VirakCloud API Bearer Token
'
VIRAKCLOUD_API_URL="https://public-api.virakcloud.com/dns"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
#Used to add txt record
dns_virakcloud_add() {
fulldomain=$1
txtvalue=$2
VIRAKCLOUD_API_TOKEN="${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}"
if [ -z "$VIRAKCLOUD_API_TOKEN" ]; then
_err "You haven't configured your VirakCloud API token yet."
_err "Please set VIRAKCLOUD_API_TOKEN environment variable or run:"
_err " export VIRAKCLOUD_API_TOKEN=\"your-api-token\""
return 1
fi
_saveaccountconf_mutable VIRAKCLOUD_API_TOKEN "$VIRAKCLOUD_API_TOKEN"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
if [ "$http_code" = "401" ]; then
return 1
fi
_err "Invalid domain"
return 1
fi
_debug _domain "$_domain"
_debug fulldomain "$fulldomain"
_info "Adding TXT record"
if _virakcloud_rest POST "domains/${_domain}/records" "{\"record\":\"${fulldomain}\",\"type\":\"TXT\",\"ttl\":3600,\"content\":\"${txtvalue}\"}"; then
if echo "$response" | grep -q "success" || echo "$response" | grep -q "\"data\""; then
_info "Added, OK"
return 0
elif echo "$response" | grep -q "already exists" || echo "$response" | grep -q "duplicate"; then
_info "Record already exists, OK"
return 0
else
_err "Add TXT record error."
_err "Response: $response"
return 1
fi
fi
_err "Add TXT record error."
return 1
}
#Usage: fulldomain txtvalue
#Used to remove the txt record after validation
dns_virakcloud_rm() {
fulldomain=$1
txtvalue=$2
VIRAKCLOUD_API_TOKEN="${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}"
if [ -z "$VIRAKCLOUD_API_TOKEN" ]; then
_err "You haven't configured your VirakCloud API token yet."
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
if [ "$http_code" = "401" ]; then
return 1
fi
_err "Invalid domain"
return 1
fi
_debug _domain "$_domain"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_info "Removing TXT record"
_debug "Getting list of records to find content ID"
if ! _virakcloud_rest GET "domains/${_domain}/records" ""; then
return 1
fi
_debug2 "Records response" "$response"
contentid=""
# Extract innermost objects (content objects) which look like {"id":"...","content_raw":"..."}
# We filter for the one containing txtvalue
target_obj=$(echo "$response" | grep -o '{[^}]*}' | grep "$txtvalue" | _head_n 1)
if [ -n "$target_obj" ]; then
contentid=$(echo "$target_obj" | _egrep_o '"id":"[^"]*"' | cut -d '"' -f 4)
fi
if [ -z "$contentid" ]; then
_debug "Could not find matching record ID in response"
_info "Record not found, may have been already removed"
return 0
fi
_debug contentid "$contentid"
if _virakcloud_rest DELETE "domains/${_domain}/records/${fulldomain}/TXT/${contentid}" ""; then
if echo "$response" | grep -q "success" || [ -z "$response" ]; then
_info "Removed, OK"
return 0
elif echo "$response" | grep -q "not found" || echo "$response" | grep -q "404"; then
_info "Record not found, OK"
return 0
else
_err "Remove TXT record error."
_err "Response: $response"
return 1
fi
fi
_err "Remove TXT record error."
return 1
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _domain=domain.com
_get_root() {
domain=$1
i=1
p=1
# Optimization: skip _acme-challenge subdomain to avoid 422 errors
if echo "$domain" | grep -q "^_acme-challenge."; then
i=2
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
return 1
fi
if ! _virakcloud_rest GET "domains/$h" ""; then
http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
if [ "$http_code" = "401" ]; then
return 1
fi
p=$i
i=$(_math "$i" + 1)
continue
fi
if echo "$response" | grep -q "\"name\""; then
_domain="$h"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_virakcloud_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer $VIRAKCLOUD_API_TOKEN"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$VIRAKCLOUD_API_URL/$ep" "" "$m")"
else
response="$(_get "$VIRAKCLOUD_API_URL/$ep")"
fi
_ret="$?"
if [ "$_ret" != "0" ]; then
_err "error on $m $ep"
return 1
fi
http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
_debug "http response code" "$http_code"
if [ "$http_code" = "401" ]; then
_err "VirakCloud API returned 401 Unauthorized."
_err "Your VIRAKCLOUD_API_TOKEN is invalid or expired."
_err "Please check your API token and try again."
return 1
fi
if [ "$http_code" = "403" ]; then
_err "VirakCloud API returned 403 Forbidden."
_err "Your API token does not have permission to access this resource."
return 1
fi
if [ -n "$http_code" ] && [ "$http_code" -ge 400 ]; then
_err "VirakCloud API error. HTTP code: $http_code"
_err "Response: $response"
return 1
fi
_debug2 response "$response"
return 0
}

View File

@@ -46,8 +46,8 @@ pushover_send() {
fi
export _H1="Content-Type: application/json"
_content="$(printf "*%s*\n" "$_content" | _json_encode)"
_subject="$(printf "*%s*\n" "$_subject" | _json_encode)"
_content="$(printf "%s" "$_content" | _json_encode)"
_subject="$(printf "%s" "$_subject" | _json_encode)"
_data="{\"token\": \"$PUSHOVER_TOKEN\",\"user\": \"$PUSHOVER_USER\",\"title\": \"$_subject\",\"message\": \"$_content\",\"sound\": \"$PUSHOVER_SOUND\", \"device\": \"$PUSHOVER_DEVICE\", \"priority\": \"$PUSHOVER_PRIORITY\"}"
response="$(_post "$_data" "$PUSHOVER_URI")"