Skip to content
Open
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7ca8a9e
QUIC.cloud support for acme.sh
rperper Oct 24, 2025
e25e30d
Added wiki doc
rperper Oct 27, 2025
68eb6de
Removed false wiki page
rperper Oct 27, 2025
692a21e
Merge branch 'acmesh-official:master' into QUIC.cloud
rperper Nov 18, 2025
9a74c86
Commit to force initial test
rperper Nov 19, 2025
2c9ba9b
Merge remote-tracking branch 'upstream/dev' into QUIC.cloud
rperper Nov 20, 2025
9381835
QUIC.cloud support for acme.sh
rperper Oct 24, 2025
72a6a5c
Added wiki doc
rperper Oct 27, 2025
cf5fd40
Removed false wiki page
rperper Oct 27, 2025
d0d97a4
Commit to force initial test
rperper Nov 19, 2025
894dfdd
Merge branch 'QUIC.cloud' of github.com:rperper/acme.sh into QUIC.cloud
rperper Nov 20, 2025
b500ac3
Updated secret and dns_qc.sh
rperper Nov 20, 2025
0f42b06
Trying again to fix shfmt error
rperper Nov 21, 2025
90d2ff8
Better fixes for shfmt errors
rperper Nov 21, 2025
5e76ea8
Additional shfmt issues
rperper Nov 21, 2025
ded539b
Additional shfmt issues
rperper Nov 21, 2025
20ef8cd
Additional shfmt issues
rperper Nov 21, 2025
88e9681
Additional shfmt issues
rperper Nov 21, 2025
65292b0
Merge branch 'acmesh-official:dev' into dev
rperper Dec 5, 2025
d393063
Updated secrets and put back guards
rperper Dec 5, 2025
67a389c
Minor change and setup secrets again
rperper Dec 5, 2025
6b6d22c
shfmt updates
rperper Dec 5, 2025
a1857af
Update error message and secrets
rperper Dec 5, 2025
ed1bd01
Save account information differently
rperper Dec 5, 2025
875cf05
Submit dns_qc.sh for review
rperper Dec 8, 2025
b0088c8
Merge branch 'acmesh-official:dev' into dev
rperper Dec 8, 2025
5fcca7c
Retry correct commit
rperper Dec 8, 2025
e5dea48
Retry pull request with HTTPS_INSECURE=1
rperper Dec 9, 2025
1a2071a
Merge branch 'acmesh-official:dev' into dev
rperper Dec 9, 2025
5017c12
Trying verification again
rperper Dec 11, 2025
6f66e29
Yet another try
rperper Dec 11, 2025
7fc4522
Merge branch 'acmesh-official:dev' into dev
rperper Dec 22, 2025
1d26d4f
Detect missing jq
rperper Dec 22, 2025
94783f4
Retry to pass workflow
rperper Dec 23, 2025
f1aac43
Retry for workflow
rperper Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions dnsapi/dns_qc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#!/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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check "if ! _exists jq " _err ""; and return 1

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neilpang I'm a little confused. Do you want me to verify that fulldomain and/or txtvalue are not JSON? Or that the response is JSON (perhaps right before line 106?).

Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ! _exists jq; then

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neilpang Thanks for the explanation. I've pushed the update

_debug "Enter dns_qc_add fulldomain: $fulldomain, txtvalue: $txtvalue"
if ! _exists jq; then
_err "In dns_qc jq not found."
return 1
fi

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

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."
else
record_id=$(echo "$response" | grep \"id\" | awk -F ' ' '{print $2}' | sed 's/,$//')
_debug "txt record_id" "$record_id"
if [ -z "$record_id" ]; then
_err "Can not get txt record id to remove. Run in debug mode."
return 1
fi
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"
fi
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
}
Loading