mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-13 12:33:30 +02:00
Merge pull request #7126 from acmesh-official/dev
Some checks failed
Apache / Apache (push) Has been cancelled
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
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (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
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Nginx / Nginx (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
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (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 DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, 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 / GhostBSD (push) Has been cancelled
DNS / OpenBSD (push) Has been cancelled
DNS / NetBSD (push) Has been cancelled
DNS / DragonFlyBSD (push) Has been cancelled
DNS / MidnightBSD (push) Has been cancelled
DNS / Solaris (push) Has been cancelled
DNS / Omnios (push) Has been cancelled
DNS / OpenIndiana (push) Has been cancelled
DNS / Tribblix (push) Has been cancelled
DNS / Haiku (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled
Some checks failed
Apache / Apache (push) Has been cancelled
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
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (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
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Nginx / Nginx (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
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (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 DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, 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 / GhostBSD (push) Has been cancelled
DNS / OpenBSD (push) Has been cancelled
DNS / NetBSD (push) Has been cancelled
DNS / DragonFlyBSD (push) Has been cancelled
DNS / MidnightBSD (push) Has been cancelled
DNS / Solaris (push) Has been cancelled
DNS / Omnios (push) Has been cancelled
DNS / OpenIndiana (push) Has been cancelled
DNS / Tribblix (push) Has been cancelled
DNS / Haiku (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled
sync
This commit is contained in:
commit
7cc16cd09a
23 changed files with 1010 additions and 92 deletions
3
.github/workflows/blacklist-command.yml
vendored
3
.github/workflows/blacklist-command.yml
vendored
|
|
@ -21,7 +21,8 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
blacklist:
|
||||
if: startsWith(github.event.issue.title, 'blacklist:')
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'blacklist:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
|
|
|
|||
2
.github/workflows/issue.yml
vendored
2
.github/workflows/issue.yml
vendored
|
|
@ -82,5 +82,5 @@ jobs:
|
|||
issue_number: issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you."
|
||||
body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you. Before posting the log, review it and REDACT any secrets: private keys (`-----BEGIN ... PRIVATE KEY-----` blocks), API tokens and passwords."
|
||||
})
|
||||
3
.github/workflows/revert-command.yml
vendored
3
.github/workflows/revert-command.yml
vendored
|
|
@ -21,7 +21,8 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
revert:
|
||||
if: startsWith(github.event.issue.title, 'revert:')
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'revert:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
|
|
|
|||
3
.github/workflows/wiki-guard.yml
vendored
3
.github/workflows/wiki-guard.yml
vendored
|
|
@ -37,6 +37,9 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
guard:
|
||||
# Forks have no <fork>.wiki repository, so the checkout below would
|
||||
# fail there -- run only in the upstream repository.
|
||||
if: github.repository == 'acmesh-official/acme.sh'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout wiki repository
|
||||
|
|
|
|||
138
acme.sh
138
acme.sh
|
|
@ -1968,6 +1968,53 @@ _utc_date() {
|
|||
date -u "+%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
#Usage: _calc_next_renew_time createtime renewaldays [endtime]
|
||||
#Prints createtime + renewaldays*86400 - 86400, capped so it never passes
|
||||
#the certificate expiry: with short-lived certs (internal CAs, upcoming
|
||||
#CA/B SC-081 47-day maximum) a fixed RenewalDays would otherwise schedule
|
||||
#the renewal after notAfter and leave an expired cert in place.
|
||||
#The cap is one day before endtime, or one hour before for certs whose
|
||||
#lifetime is 24 hours or less, mirroring the --valid-to scheduling.
|
||||
_calc_next_renew_time() {
|
||||
_cnrt_create="$1"
|
||||
_cnrt_days="$2"
|
||||
_cnrt_end="$3"
|
||||
_cnrt_next=$(_math "$_cnrt_create" + "$_cnrt_days" \* 24 \* 60 \* 60 - 86400)
|
||||
if [ -z "$_cnrt_end" ]; then
|
||||
printf "%s" "$_cnrt_next"
|
||||
return 0
|
||||
fi
|
||||
if [ "$(_math "$_cnrt_end" - "$_cnrt_create")" -gt 86400 ]; then
|
||||
_cnrt_cap=$(_math "$_cnrt_end" - 86400)
|
||||
else
|
||||
_cnrt_cap=$(_math "$_cnrt_end" - 3600)
|
||||
fi
|
||||
if [ "$_cnrt_next" -gt "$_cnrt_cap" ]; then
|
||||
_cnrt_next="$_cnrt_cap"
|
||||
fi
|
||||
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() {
|
||||
if _exists mktemp; then
|
||||
if mktemp 2>/dev/null; then
|
||||
|
|
@ -4098,7 +4145,7 @@ _regAccount() {
|
|||
fi
|
||||
_savecaconf "ACCOUNT_URL" "$_accUri"
|
||||
else
|
||||
ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
|
||||
_accUri="$(_readcaconf ACCOUNT_URL)"
|
||||
fi
|
||||
export ACCOUNT_URL="$_accUri"
|
||||
|
||||
|
|
@ -4152,6 +4199,12 @@ updateaccount() {
|
|||
if [ "$code" = '200' ]; then
|
||||
echo "$response" >"$ACCOUNT_JSON_PATH"
|
||||
_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)"
|
||||
_info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
|
||||
|
|
@ -5271,7 +5324,7 @@ $_authorizations_map"
|
|||
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
|
||||
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"
|
||||
|
||||
if [ -z "$keyauthorization" -a -z "$entry" ]; then
|
||||
|
|
@ -5603,7 +5656,7 @@ $_authorizations_map"
|
|||
status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||
_debug2 status "$status"
|
||||
if _contains "$status" "invalid"; then
|
||||
error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
|
||||
error="$(echo "$response" | _egrep_o '"error":[{][^}]*')"
|
||||
_debug2 error "$error"
|
||||
errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
|
||||
_debug2 errordetail "$errordetail"
|
||||
|
|
@ -5917,19 +5970,8 @@ $_authorizations_map"
|
|||
_info "It cannot be renewed automatically"
|
||||
_info "See: $_VALIDITY_WIKI"
|
||||
else
|
||||
_now=$(_time)
|
||||
_debug2 "_now" "$_now"
|
||||
_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
|
||||
Le_NextRenewTime=$(_calc_validto_renew_time "$Le_NextRenewTime" "$Le_RenewalDays" "$(_time)")
|
||||
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
|
||||
fi
|
||||
elif [ "$Le_RenewalDays" -lt "0" ]; then
|
||||
_enddate_value=$(_enddate "$CERT_PATH")
|
||||
|
|
@ -5946,8 +5988,12 @@ $_authorizations_map"
|
|||
Le_NextRenewTime=$(_math "$_endtime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
|
||||
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
|
||||
else
|
||||
Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
|
||||
Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
|
||||
_endtime_for_cap=""
|
||||
_enddate_value=$(_enddate "$CERT_PATH")
|
||||
if [ "$?" = "0" ] && [ "$_enddate_value" ]; then
|
||||
_endtime_for_cap=$(_ssldate2time "$_enddate_value")
|
||||
fi
|
||||
Le_NextRenewTime=$(_calc_next_renew_time "$Le_CertCreateTime" "$Le_RenewalDays" "$_endtime_for_cap")
|
||||
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
|
||||
fi
|
||||
|
||||
|
|
@ -6544,7 +6590,7 @@ list_profiles() {
|
|||
fi
|
||||
|
||||
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
|
||||
_info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint."
|
||||
|
|
@ -6966,15 +7012,39 @@ installcronjob() {
|
|||
return 1
|
||||
fi
|
||||
_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
|
||||
_CRONTAB_STDIN="$_CRONTAB --"
|
||||
else
|
||||
_CRONTAB_STDIN="$_CRONTAB -"
|
||||
fi
|
||||
$_CRONTAB -l 2>/dev/null | {
|
||||
cat
|
||||
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"
|
||||
{
|
||||
if [ "$_cron_entries" ]; then
|
||||
echo "$_cron_entries"
|
||||
fi
|
||||
echo "$_cron_entry"
|
||||
} | $_CRONTAB_STDIN
|
||||
fi
|
||||
if [ "$?" != "0" ]; then
|
||||
|
|
@ -7203,7 +7273,7 @@ _deactivate() {
|
|||
_debug "Trigger validation."
|
||||
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
|
||||
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"
|
||||
if [ -z "$entry" ]; then
|
||||
_err "$d: Cannot get domain token"
|
||||
|
|
@ -7989,6 +8059,7 @@ Parameters:
|
|||
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.
|
||||
--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.
|
||||
--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.
|
||||
|
|
@ -8983,13 +9054,20 @@ _process() {
|
|||
|
||||
_debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
|
||||
|
||||
# --days and --valid-to are mutually exclusive by design: --valid-to pins
|
||||
# the cert lifetime and the renewal time follows the expiry, so a
|
||||
# creation-based --days schedule can not apply.
|
||||
# --valid-to pins the cert lifetime, so a creation-anchored (positive)
|
||||
# --days schedule can not apply and is rejected. A negative --days is
|
||||
# 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
|
||||
_err "--days can not be used together with --valid-to."
|
||||
_err "With --valid-to, the renewal time is derived from the expiry time automatically."
|
||||
return 1
|
||||
if ! _startswith "$_valid_to" "+"; then
|
||||
_err "--days can not be used together with a fixed-date --valid-to: such a cert can not be renewed automatically."
|
||||
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
|
||||
|
||||
if [ "$DEBUG" ]; then
|
||||
|
|
|
|||
222
deploy/baidu_cdn.sh
Normal file
222
deploy/baidu_cdn.sh
Normal 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
|
||||
}
|
||||
|
|
@ -52,7 +52,15 @@ cpanel_uapi_deploy() {
|
|||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode <"$_ccert")
|
||||
_key=$(_url_encode <"$_ckey")
|
||||
# with --signcsr the private key was never handed to acme.sh, so the key
|
||||
# file does not exist; skip it instead of spilling a shell redirection
|
||||
# error on every renewal (cPanel keeps using the already-installed key)
|
||||
if [ -f "$_ckey" ]; then
|
||||
_key=$(_url_encode <"$_ckey")
|
||||
else
|
||||
_debug "Key file $_ckey does not exist (csr mode), not sending a key."
|
||||
_key=""
|
||||
fi
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
|
|
|||
175
deploy/fortigate.sh
Normal file
175
deploy/fortigate.sh
Normal 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
|
||||
}
|
||||
|
|
@ -54,6 +54,8 @@ mydevil_deploy() {
|
|||
# Usage: ip=$(mydevil_get_ip domain.com)
|
||||
# echo $ip
|
||||
mydevil_get_ip() {
|
||||
devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
|
||||
# tr squeezes runs of blanks into one tab so plain cut works everywhere;
|
||||
# cut -w is BSD-only and unknown to GNU coreutils
|
||||
devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
################################################################################
|
||||
# ACME.sh 3rd party deploy plugin for Synology DSM
|
||||
|
|
@ -238,7 +238,7 @@ synology_dsm_deploy() {
|
|||
_debug2 error_code "$error_code"
|
||||
# Account has 2FA-OTP enabled, since error 403 reported.
|
||||
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
|
||||
if [ "$error_code" == "403" ]; then
|
||||
if [ "$error_code" = "403" ]; then
|
||||
if [ -z "$SYNO_DEVICE_NAME" ]; then
|
||||
printf "Enter device name or leave empty for default (CertRenewal): "
|
||||
read -r SYNO_DEVICE_NAME
|
||||
|
|
@ -274,22 +274,22 @@ synology_dsm_deploy() {
|
|||
fi
|
||||
|
||||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
|
||||
elif [ "$error_code" == "404" ]; then
|
||||
elif [ "$error_code" = "404" ]; then
|
||||
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
|
||||
elif [ "$error_code" == "406" ]; then
|
||||
elif [ "$error_code" = "406" ]; then
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
|
||||
else
|
||||
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
|
||||
fi
|
||||
elif [ "$error_code" == "400" ]; then
|
||||
elif [ "$error_code" = "400" ]; then
|
||||
_err "Failed to authenticate, no such account or incorrect password."
|
||||
elif [ "$error_code" == "401" ]; then
|
||||
elif [ "$error_code" = "401" ]; then
|
||||
_err "Failed to authenticate with a non-existent account."
|
||||
elif [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then
|
||||
elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then
|
||||
_err "Failed to authenticate, the account password has expired or must be changed."
|
||||
else
|
||||
_err "Failed to authenticate with error: $error_code."
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ Ali_DNS_API="https://alidns.aliyuncs.com/"
|
|||
|
||||
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
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
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
|
@ -33,7 +35,7 @@ dns_ali_add() {
|
|||
}
|
||||
|
||||
dns_ali_rm() {
|
||||
fulldomain=$1
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
|
||||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
|
||||
|
|
|
|||
248
dnsapi/dns_comlaude.sh
Normal file
248
dnsapi/dns_comlaude.sh
Normal 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
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ dns_dnsexit_add() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_dnsexit_zone_op add ',"ttl":0,"overwrite":false'
|
||||
_dnsexit_zone_op add ',"ttl":1,"overwrite":false'
|
||||
}
|
||||
|
||||
#Usage: fulldomain txtvalue
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ dns_dnsimple_add() {
|
|||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
|
||||
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
|
||||
DNSimple_OAUTH_TOKEN=""
|
||||
_err "You have not set the dnsimple oauth token yet."
|
||||
|
|
@ -26,7 +27,7 @@ dns_dnsimple_add() {
|
|||
fi
|
||||
|
||||
# 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
|
||||
_err "failed to retrieve account id"
|
||||
|
|
@ -57,6 +58,12 @@ dns_dnsimple_add() {
|
|||
dns_dnsimple_rm() {
|
||||
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
|
||||
_err "failed to retrieve account id"
|
||||
return 1
|
||||
|
|
@ -123,9 +130,9 @@ _get_root() {
|
|||
|
||||
# returns _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
|
||||
_saveaccountconf DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
|
||||
_saveaccountconf_mutable DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
|
||||
_account_id="$DNSimple_ACCOUNT_ID"
|
||||
_debug _account_id "$_account_id"
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ dns_dynu_add() {
|
|||
fulldomain=$1
|
||||
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
|
||||
Dynu_ClientId=""
|
||||
Dynu_Secret=""
|
||||
|
|
@ -32,8 +34,8 @@ dns_dynu_add() {
|
|||
fi
|
||||
|
||||
#save the client id and secret to the account conf file.
|
||||
_saveaccountconf Dynu_ClientId "$Dynu_ClientId"
|
||||
_saveaccountconf Dynu_Secret "$Dynu_Secret"
|
||||
_saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
|
||||
_saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
|
||||
|
||||
if [ -z "$Dynu_Token" ]; then
|
||||
_info "Getting Dynu token."
|
||||
|
|
@ -69,6 +71,8 @@ dns_dynu_rm() {
|
|||
fulldomain=$1
|
||||
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
|
||||
Dynu_ClientId=""
|
||||
Dynu_Secret=""
|
||||
|
|
@ -78,8 +82,8 @@ dns_dynu_rm() {
|
|||
fi
|
||||
|
||||
#save the client id and secret to the account conf file.
|
||||
_saveaccountconf Dynu_ClientId "$Dynu_ClientId"
|
||||
_saveaccountconf Dynu_Secret "$Dynu_Secret"
|
||||
_saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
|
||||
_saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
|
||||
|
||||
if [ -z "$Dynu_Token" ]; then
|
||||
_info "Getting Dynu token."
|
||||
|
|
@ -214,11 +218,11 @@ _dynu_authentication() {
|
|||
|
||||
response="$(_get "$Dynu_EndPoint/oauth2/token")"
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Authentication failed."
|
||||
_err "Authentication failed: no response from $Dynu_EndPoint/oauth2/token"
|
||||
return 1
|
||||
fi
|
||||
if _contains "$response" "Authentication Exception"; then
|
||||
_err "Authentication failed."
|
||||
_err "Authentication failed. Server response: $response"
|
||||
return 1
|
||||
fi
|
||||
if _contains "$response" "access_token"; then
|
||||
|
|
|
|||
|
|
@ -182,7 +182,11 @@ dns_infomaniak_rm() {
|
|||
_get_zone() {
|
||||
domain="$1"
|
||||
# Whatever the domain is, you can get the fqdn with the following.
|
||||
# shellcheck disable=SC1004
|
||||
response=$(_get "${INFOMANIAK_API_URL}/2/domains/${domain}/zones" | sed 's/.*\[{"fqdn"\:"\(.*\)/\1/')
|
||||
echo "${response%%\"*}"
|
||||
response=$(_get "${INFOMANIAK_API_URL}/2/domains/${domain}/zones")
|
||||
_debug2 "_get_zone response" "$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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ Site: www.knot-dns.cz/docs/2.5/html/man_knsupdate.html
|
|||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot
|
||||
Options:
|
||||
KNOT_SERVER Server hostname. Default: "localhost".
|
||||
KNOT_KEY File path to TSIG key
|
||||
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
|
||||
|
|
@ -21,6 +22,9 @@ dns_knot_add() {
|
|||
# save the dns server and key to the account.conf file.
|
||||
_saveaccountconf KNOT_SERVER "${KNOT_SERVER}"
|
||||
_saveaccountconf KNOT_KEY "${KNOT_KEY}"
|
||||
if [ -n "${KNOT_ZONE}" ]; then
|
||||
_saveaccountconf KNOT_ZONE "${KNOT_ZONE}"
|
||||
fi
|
||||
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Domain does not exist."
|
||||
|
|
@ -84,6 +88,13 @@ EOF
|
|||
# _domain=domain.com
|
||||
_get_root() {
|
||||
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=$(_math "$i" - 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -115,12 +115,15 @@ _oci_config() {
|
|||
_clearaccountconf_mutable OCI_CLI_PROFILE
|
||||
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
|
||||
_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
|
||||
|
||||
if [ -z "$OCI_CLI_TENANCY" ]; then
|
||||
|
|
@ -128,41 +131,47 @@ _oci_config() {
|
|||
return 1
|
||||
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
|
||||
_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
|
||||
if [ -z "$OCI_CLI_USER" ]; then
|
||||
_err "Error: unable to read OCI_CLI_USER from config file or environment variable."
|
||||
return 1
|
||||
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
|
||||
_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
|
||||
if [ -z "$OCI_CLI_REGION" ]; then
|
||||
_err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
|
||||
return 1
|
||||
fi
|
||||
|
||||
OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
|
||||
if [ -z "$OCI_CLI_KEY" ]; then
|
||||
_clearaccountconf_mutable OCI_CLI_KEY
|
||||
OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
|
||||
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
|
||||
if [ -z "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
|
||||
OCI_CLI_KEY_FILE=$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")
|
||||
fi
|
||||
if [ "$OCI_CLI_KEY" ]; then
|
||||
_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
|
||||
|
||||
if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ _ovh_authentication() {
|
|||
_H3=""
|
||||
_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")"
|
||||
_debug3 response "$response"
|
||||
|
|
|
|||
|
|
@ -189,19 +189,23 @@ _get_root() {
|
|||
domain=$1
|
||||
i=1
|
||||
|
||||
if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then
|
||||
_zones_response=$(echo "$response" | _normalizeJson)
|
||||
fi
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
|
||||
if _contains "$_zones_response" "\"name\":\"$h.\""; then
|
||||
_domain="$h."
|
||||
if [ -z "$h" ]; then
|
||||
_domain="=2E"
|
||||
# Probe each candidate zone with the server-side name filter instead of
|
||||
# listing every zone: with large installations (100k zones) the
|
||||
# unfiltered list takes minutes. Servers that ignore the parameter
|
||||
# return the full list, which the check below still handles.
|
||||
# https://doc.powerdns.com/authoritative/http-api/zone.html
|
||||
if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones?zone=$h."; then
|
||||
_zones_response=$(echo "$response" | _normalizeJson)
|
||||
if _contains "$_zones_response" "\"name\":\"$h.\""; then
|
||||
_domain="$h."
|
||||
if [ -z "$h" ]; then
|
||||
_domain="=2E"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "$h" ]; then
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ Site: zonomi.com
|
|||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_zonomi
|
||||
Options:
|
||||
ZM_Key API Key
|
||||
OptionsAlt:
|
||||
ZM_Api API endpoint. Default: "https://zonomi.com/app/dns/dyndns.jsp". For RimuHosting use "https://rimuhosting.com/dns/dyndns.jsp".
|
||||
'
|
||||
|
||||
ZM_Api="https://zonomi.com/app/dns/dyndns.jsp"
|
||||
ZM_Api_Default="https://zonomi.com/app/dns/dyndns.jsp"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
|
|
@ -28,6 +30,8 @@ dns_zonomi_add() {
|
|||
#save the api key to the account conf file.
|
||||
_saveaccountconf_mutable ZM_Key "$ZM_Key"
|
||||
|
||||
_zm_init_api
|
||||
|
||||
_info "Get existing txt records for $fulldomain"
|
||||
if ! _zm_request "action=QUERY&name=$fulldomain"; then
|
||||
_err "error"
|
||||
|
|
@ -64,11 +68,27 @@ dns_zonomi_rm() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_zm_init_api
|
||||
|
||||
_zm_request "action=DELETE&type=TXT&name=$fulldomain"
|
||||
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
# resolve the API endpoint: zonomi by default, overridable for providers
|
||||
# sharing the same API on another host (e.g. RimuHosting)
|
||||
_zm_init_api() {
|
||||
ZM_Api="${ZM_Api:-$(_readaccountconf_mutable ZM_Api)}"
|
||||
if [ -z "$ZM_Api" ]; then
|
||||
ZM_Api="$ZM_Api_Default"
|
||||
fi
|
||||
_debug2 ZM_Api "$ZM_Api"
|
||||
if [ "$ZM_Api" != "$ZM_Api_Default" ]; then
|
||||
_saveaccountconf_mutable ZM_Api "$ZM_Api"
|
||||
fi
|
||||
}
|
||||
|
||||
#qstr
|
||||
_zm_request() {
|
||||
qstr="$1"
|
||||
|
|
|
|||
44
notify/customscript.sh
Normal file
44
notify/customscript.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Support calling a custom script for notifications
|
||||
#
|
||||
# export CUSTOMSCRIPT_PATH="/usr/local/bin/acme-notification.sh"
|
||||
#
|
||||
# The script is called with three arguments:
|
||||
# $1 subject
|
||||
# $2 content
|
||||
# $3 status code (0: success, 1: error, 2: skipped)
|
||||
|
||||
customscript_send() {
|
||||
_subject="$1"
|
||||
_content="$2"
|
||||
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
|
||||
_debug "_subject" "$_subject"
|
||||
_debug "_content" "$_content"
|
||||
_debug "_statusCode" "$_statusCode"
|
||||
|
||||
CUSTOMSCRIPT_PATH="${CUSTOMSCRIPT_PATH:-$(_readaccountconf_mutable CUSTOMSCRIPT_PATH)}"
|
||||
if [ -z "$CUSTOMSCRIPT_PATH" ]; then
|
||||
_err "You didn't specify the custom script path CUSTOMSCRIPT_PATH yet."
|
||||
return 1
|
||||
fi
|
||||
if ! _exists "$CUSTOMSCRIPT_PATH"; then
|
||||
_err "The custom script $CUSTOMSCRIPT_PATH does not exist or is not executable."
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable CUSTOMSCRIPT_PATH "$CUSTOMSCRIPT_PATH"
|
||||
|
||||
# Invoke directly, never through eval: the subject and content contain
|
||||
# domain names and CA messages, eval would allow command injection.
|
||||
_customscript_result="$("$CUSTOMSCRIPT_PATH" "$_subject" "$_content" "$_statusCode" 2>&1)"
|
||||
_customscript_rc="$?"
|
||||
_debug2 "_customscript_result" "$_customscript_result"
|
||||
|
||||
if [ "$_customscript_rc" != "0" ]; then
|
||||
_err "custom script execution error ($_customscript_rc): $_customscript_result"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "custom script executed successfully."
|
||||
return 0
|
||||
}
|
||||
75
notify/waha.sh
Executable file
75
notify/waha.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#Support WAHA (WhatsApp HTTP API) - free, self-hosted WhatsApp API
|
||||
#https://waha.devlike.pro/
|
||||
|
||||
#Required:
|
||||
#WAHA_URL="http://localhost:3000"
|
||||
#WAHA_CHAT_ID="1234567890@c.us"
|
||||
|
||||
#Optional:
|
||||
#WAHA_API_KEY=""
|
||||
#WAHA_SESSION="default"
|
||||
|
||||
waha_send() {
|
||||
_subject="$1"
|
||||
_content="$2"
|
||||
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
|
||||
_debug "_subject" "$_subject"
|
||||
_debug "_content" "$_content"
|
||||
_debug "_statusCode" "$_statusCode"
|
||||
|
||||
WAHA_URL="${WAHA_URL:-$(_readaccountconf_mutable WAHA_URL)}"
|
||||
if [ -z "$WAHA_URL" ]; then
|
||||
WAHA_URL=""
|
||||
_err "You didn't specify the WAHA server url WAHA_URL yet."
|
||||
_err "Example: export WAHA_URL=\"http://localhost:3000\""
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable WAHA_URL "$WAHA_URL"
|
||||
|
||||
WAHA_CHAT_ID="${WAHA_CHAT_ID:-$(_readaccountconf_mutable WAHA_CHAT_ID)}"
|
||||
if [ -z "$WAHA_CHAT_ID" ]; then
|
||||
WAHA_CHAT_ID=""
|
||||
_err "You didn't specify the WhatsApp chat id WAHA_CHAT_ID yet."
|
||||
_err "Example: export WAHA_CHAT_ID=\"1234567890@c.us\""
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable WAHA_CHAT_ID "$WAHA_CHAT_ID"
|
||||
|
||||
WAHA_API_KEY="${WAHA_API_KEY:-$(_readaccountconf_mutable WAHA_API_KEY)}"
|
||||
if [ "$WAHA_API_KEY" ]; then
|
||||
_saveaccountconf_mutable WAHA_API_KEY "$WAHA_API_KEY"
|
||||
fi
|
||||
|
||||
WAHA_SESSION="${WAHA_SESSION:-$(_readaccountconf_mutable WAHA_SESSION)}"
|
||||
if [ -z "$WAHA_SESSION" ]; then
|
||||
WAHA_SESSION="default"
|
||||
else
|
||||
_saveaccountconf_mutable WAHA_SESSION "$WAHA_SESSION"
|
||||
fi
|
||||
|
||||
_content=$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)
|
||||
|
||||
_data="{\"chatId\": \"$WAHA_CHAT_ID\", "
|
||||
_data="$_data\"text\": \"$_content\", "
|
||||
_data="$_data\"session\": \"$WAHA_SESSION\"}"
|
||||
|
||||
_debug "_data" "$_data"
|
||||
|
||||
export _H1="Content-Type: application/json"
|
||||
if [ "$WAHA_API_KEY" ]; then
|
||||
export _H2="X-Api-Key: $WAHA_API_KEY"
|
||||
fi
|
||||
|
||||
_waha_url="${WAHA_URL}/api/sendText"
|
||||
response="$(_post "$_data" "$_waha_url" "" "POST" "application/json")"
|
||||
|
||||
if [ "$?" = "0" ] && _contains "$response" "\"id\""; then
|
||||
_info "waha send success."
|
||||
return 0
|
||||
fi
|
||||
_err "waha send error."
|
||||
_err "$response"
|
||||
return 1
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue