Compare commits

..
14 Commits
Author SHA1 Message Date
neil c9e9e24e24 _json_encode: fix empty output on Solaris, newline loss in _hex_dump fallback
_hex_dump strips the trailing newline, and Oracle Solaris sed drops an
unterminated last line, so the sed step in _json_encode produced nothing
and every caller got an empty string on Solaris. Capture the hex first and
re-emit it with printf "%s\n".

The _ascii_hex fallback of _hex_dump (used when od/hexdump/xxd are not in
PATH, e.g. Tribblix) captured stdin with $(cat), losing trailing newlines,
and ran cut -c per line, so multi-line input was mis-encoded. Process the
input line by line instead.

Both surfaced by le_test_json_encode (#7212).
2026-10-21 20:22:33 +08:00
neil aef81df5eb dns_gandi_livedns: delete the TXT record when rm removes its last value
rm PUT the remaining values back even when none were left, leaving an
empty TXT rrset behind. Use DELETE (204 on success) in that case; keep
PUT when other values remain, since the rrset can hold values from
other pending validations.

Refs #7347
2026-10-21 18:49:59 +08:00
neil e3004b84a1 dns_gandi_livedns: load saved credentials in rm
Since 329dab9a (3.1.3) the token/key is saved with
_saveaccountconf_mutable, but only _add read it back. rm runs in its
own subshell, so on cron renewal it called the API unauthenticated;
the 401 body has no "code": 401, so _get_root took the first probe as
the zone and the TXT record was never removed.

Fixes #7347
2026-10-21 17:52:45 +08:00
neil 3288c35c2a _json_encode/_json_decode: use printf instead of echo
dash's echo interprets backslash escapes, so the \. MarkdownV2 escapes
built by notify/telegram.sh collapsed to \. in the JSON body and
Telegram rejected the message with "Character '.' is reserved".
bash's echo does not, which is why only dash-based systems were hit.

Fixes #7212
2026-10-21 17:52:08 +08:00
5d07634e0f dns_namecheap: let NAMECHEAP_* env vars override saved credentials (#7344)
Co-authored-by: zorba <zorba-claude@pavlovian.net>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 14:09:31 +08:00
a57c8a45fa Add hp_ews deploy hook for HP printers (#7198)
* Add hp_ews deploy hook for HP printers

Deploys the certificate to an HP printer's Embedded Web Server by building
a PKCS#12 bundle from the key, certificate and CA chain and POSTing it to
/Security/DeviceCertificates/NewCertWithPassword/Upload.

Tested on an HP LaserJet Pro MFP M479fdw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* hp_ews: upload with _post_file and Basic auth

Replace the hand-rolled curl/wget helper with _post_file, as requested
in review. Authenticate with a Basic Authorization header like the other
deploy hooks, and map HP_EWS_INSECURE onto HTTPS_INSECURE, which stays
inside the hook's subshell. Printers that only offer Digest auth are no
longer supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* hp_ews: declare and export _H1 separately (SC2155)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Sean Graham <sean.graham@vizit.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-19 15:23:35 +08:00
Ilya Vassyutovich 0d1d0092b8 dns_yc: use absolute record names (#7338)
Record names were sent relative to the zone, derived from _sub_domain.
That has no valid form when the TXT name is the zone apex, which
happens with --domain-alias pointing at a dedicated zone:

- YC_Zone_ID: _sub_domain ends up empty and the API rejects the
  request ("Length must be in the range of 1 to 254").
- YC_Folder_ID: _get_root returns the first label as _sub_domain, so
  the record lands at <alias>.<alias>. and validation never succeeds.

The API accepts absolute names (trailing dot) everywhere a relative one
is accepted, so send $fulldomain, which is already lowercased and
dot-terminated. Ordinary _acme-challenge.<sub> names keep working.

Supersedes #6862.
2026-10-18 14:34:17 +08:00
Voyo c472636bae Merge pull request #7333 from voyo/add-dns-primedns
Add PrimeDNS DNS API (dns_primedns)
2026-10-16 20:50:38 +08:00
579c994fad Handle DNSimple API response for nonexistent subdomain (#7324)
* handle OAuth response

* revert comment removal

* handle lowercased domain value

* expand error detection logic for all non-expected responses

* shell-independent lowercasing

* revert; domain value already lowercased

* add missing double-quotes

* use zone records endpoint fallback when zone cannot be identified

* add logic missing from previous

---------

Co-authored-by: neil <github@neilpang.com>
Co-authored-by: Mashiro <adadam@qq.com>
Co-authored-by: MBWhitestone <25477219+MBWhitestone@users.noreply.github.com>
Co-authored-by: Pablo <Pablo1@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-16 16:11:30 +08:00
neil f659ba2ea2 Skip conf writes that change nothing
Since #7278 every conf write is a temp file plus a rename, and _setopt
and _clear_conf rewrote the conf even when the content stayed the same:
_savedeployconf clears a legacy key that is almost never there, and
deploy hooks re-save the same values on every run. On storage where
file creation and rename are expensive (ZFS on HDD) a multideploy run
went from about 23 seconds to over 2 minutes.

Return early when the new content equals the old one. A simulated
3-service docker multideploy drops from 96 conf writes to 32.

Fixes #7335
2026-10-15 15:03:18 +08:00
neil f5f2242d60 deploy/ssh: stop the batched remote command at the first failed step
In single-call mode (the default) all steps were joined with ";", so a
failed write did not stop the batch: the remaining files were still
overwritten, DEPLOY_SSH_REMOTE_CMD still ran on the broken file, and the
deploy reported success because only the last exit code counted.

With a POSIX remote shell, steps are now wrapped in { } and joined with
&&, matching multi-call mode. Non-POSIX remote shells (powershell -c,
cmd /c) keep the old ";" join byte for byte.

See #7328
2026-10-14 14:13:21 +08:00
AmadeusAbionandstelar7 d73d68ced4 Add Abion.com DNS API (#7311)
* Add Abion.com DNS API

* Update dns_abion.sh

* dns_abion: keep existing TXT records on add and rm

Continues acmesh-official/acme.sh#6885 by @stelar7 and addresses
the review comments there.

- add: append to the existing TXT values at the name instead of
  replacing them, so wildcard + base certificates work
- rm: remove only the matching value, keep the TTL and comments of
  the other records, and clear the name with null when it is empty
- only read the TXT records of the target name; the old parser also
  picked up TXT records from other names in the zone
- find the zone with GET zones/<name> instead of the paginated zone
  list, and stop with the API error on anything but "Zone not found"
- add Docs line and progress messages, fix shfmt

Co-authored-by: stelar7 <dudedbz@gmail.com>

* dns_abion: lowercase the domain so rm finds the record

* dns_abion: add tracking issue to info block

* dns_abion: align info block with the structural info format

---------

Co-authored-by: stelar7 <dudedbz@gmail.com>
2026-10-14 12:39:44 +08:00
Eyal Kalderon 228a140094 Add deploy hook for Redfish-compatible BMCs (#7276)
* Add `deploy/redfish.sh` and implement Redfish session log in/out

Since `acme.sh` intends to keep third-party dependencies to a minimum,
this implementation will not rely on `redfishtool`.

* Detect BMC manager and replace existing cert with ACME cert

* Reuse `_toPkcs8` defined in `acme.sh`

* Return early if unsupported key algorithms or bit lengths are used

* Improve robustness and UX a bit

* Support opt-in automatic restart of BMC

* Improve log output and inline docs some more

* Remove `test-redfish-deploy-hook.sh` test harness

The business logic has been implemented, so it should be safe to delete
this test harness now and just interact with `acme.sh` directly.

* Trim `_redfish_` prefixes from local variables

Since each deploy hook is executed within its own subshell, there's no
risk of polluting the outer `acme.sh` environment nor any other deploy
hooks that might follow.

* Save deploy configuration changes to disk

* Tweak deploy hook log messages a bit

* Add `DEPLOY_REDFISH_MANAGER` environment variable

* Add function for checking HTTP status code

* Add more helper functions, clean up log output a bit more

* Return early from non-zero `curl` exit codes

* Only upload leaf cert in `--sign-csr` mode

* Fix bug with RSA key compatibility error message

* Remove `_redfish_load_deploy_conf` helper, use consistent var naming

* Add several more helper functions

* Check whether server supports Certificate schema >=1.4.0

Redfish servers with a schema version older than this don't support
uploading private keys + full certificate chains directly. Instead, the
CSR must be generated on the server and signed with `acme.sh --sign-csr`
prior to deployment.

* Clarify environment variable naming a bit

* Don't implicitly opt into `HTTPS_INSECURE`

* Feedback: Fix lower-bound RSA key length check

* Feedback: Verify file existence with `-f` flag

* Feedback: Correctly map `TPM_ECC_CURVE` key length values

* Feedback: Replace exact HTTP 204 checks with 2xx instead

* Feedback: Use POSIX-compliant alternative to `\+`

* Feedback: Get session token from body instead of HTTP header

* Feedback: Save PKCS#8 private key directly to variable

* Feedback: Drop `jq` dependency, handle missing action info

* Fix regression in `_redfish_rest()` error reporting

* Simplify construction of expected certificate JSON string

Confirmed with a real-world test that the value of `_certificate_str`
is unchanged before and after applying this commit. See below:

diff --git a/deploy/redfish.sh b/deploy/redfish.sh
index e485fd60..3459b71a 100644
--- a/deploy/redfish.sh
+++ b/deploy/redfish.sh
@@ -131,6 +131,10 @@ redfish_deploy() {
       printf '%s\n' "${_ckey_pkcs8}"
       cat "${_cfullchain}"
     } | _json_encode)"
+
+    _new_certificate_str="$(echo "${_ckey_pkcs8}" | cat - "${_cfullchain}" | _json_encode)"
+    [ "${_certificate_str}" = "${_new_certificate_str}" ] || return 123
+
     _certificate_type='PEMchain'
   else
     _info "Private key and full certificate chain not available; we must be using a server-generated CSR."

* Include link to `redfish-generate-csr.sh` helper script in error message

* Feedback: Drop use of `paste` and `expr`

* Feedback: Remove stale `_err` invocation

* Feedback: Correctly deal with whitespace after colon

* Feedback: Handle `\n` in username and password in Dash shell

It seems that `echo` in Dash automatically converts `\n` into a line
break, causing usernames and/or passwords containing a literal
backslash-n to reach the BMC with a newline, breaking login. It
seems that `printf '%s'` doesn't have the same issue, so we use those to
format `_username` and `_password` instead of `echo`.

* Feedback: Link to acme.sh wiki instead of Gist

* Feedback: Fix `Certificate` schema version check

* Feedback: Better handle missing key algo/length/curve info

This commit changes how the deploy hook detects which cryptographic
algorithms are supported by the server if not explicitly declared in
`KeyPairAlgorithm`. Instead of blindly assuming RSA, we instead check
for the presence of `KeyBitLength` and/or `KeyCurveId` fields and try to
make an educated guess. If not enough info exists to determine key
compatibility, we fail-open instead of failing closed; the server will
yell at us anyway if `ReplaceCertificate` doesn't support something.

* Feedback: Fix character escaping of username/password

* Feedback: Correctly parse inline `KeyBitLength@Redfish.AllowableValues`

This metadatum is represented as a JSON array of allowed values, not a
numeric range with min/max. Make sure to treat the element in the list
as the maximum, and not assume the list is at most 2 elements long.

* Feedback: Add "\n" to format string for Solaris compatibility

* Feedback: Use POSIX parameter expansion instead of `rev`
2026-10-14 12:38:33 +08:00
Kevin Winter f5aeed1752 dns_cf: support IDN domains (#7322)
* dns_cf: support IDN domains (punycode record lookup, URL-encoded zone lookup)

* dns_cf: convert IDN once at the top of dns_cf_add/dns_cf_rm
2026-10-14 12:37:08 +08:00
11 changed files with 1171 additions and 34 deletions

No files matched your search

+35 -7
View File
@@ -666,8 +666,15 @@ _hex_dump() {
xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " "
else
_debug3 "using _ascii_hex"
str=$(cat)
_ascii_hex "$str"
#line by line: the cut -c in _ascii_hex works per line, and a $(cat)
#capture would drop the trailing newlines (illumos without od in PATH)
while IFS= read -r _hd_line; do
_ascii_hex "$_hd_line"
printf " 0a"
done
if [ -n "$_hd_line" ]; then
_ascii_hex "$_hd_line"
fi
fi
}
@@ -907,7 +914,12 @@ _json_encode() {
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
_debug3 "_json_encode"
_debug3 "_j_str" "$_j_str"
echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
#printf, not echo: dash's echo interprets backslash escapes and would
#collapse the \\ sequences (e.g. Telegram MarkdownV2 escapes, issue 7212)
_j_hex="$(printf "%s\n" "$_j_str" | _hex_dump | _lower_case)"
#the hex line must end with a newline: Solaris sed drops an unterminated
#last line, which emptied the whole output
printf "%s\n" "$_j_hex" | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
#from: http:\/\/ to http://
@@ -915,7 +927,7 @@ _json_decode() {
_j_str="$(sed 's#\\/#/#g')"
_debug3 "_json_decode"
_debug3 "_j_str" "$_j_str"
echo "$_j_str"
printf "%s\n" "$_j_str"
}
#extract the authorization URLs from an order response on stdin, as a
@@ -2914,6 +2926,7 @@ _setopt() {
_err "Cannot read $__conf."
return 1
fi
__old_text="$__text"
#build the new content first and write it once through _write_conf:
#redirecting straight into the conf truncates it before anything is
#written, so a failing sed (#2426) or a failing write (#7247) left a
@@ -2930,6 +2943,13 @@ _setopt() {
_debug3 APP
__text="$__text${__text:+$__nl}$__opt$__sep$__val$__end"
fi
if [ -z "$__sed_err" ] && [ "$__text" = "$__old_text" ]; then
#the value is already saved: callers re-save the same values on every
#run, and each write is a temp file plus a rename, which is slow on
#some storage (#7335)
_debug3 "Unchanged, skip writing $__conf"
return 0
fi
if [ "$__sed_err" ] || ! _write_conf "$__conf" "$__text"; then
_err "Cannot save '$__opt' to $__conf."
return 1
@@ -2959,11 +2979,19 @@ _clear_conf() {
_c_c_f="$1"
_sdkey="$2"
if [ "$_c_c_f" ]; then
_conf_data="$(cat "$_c_c_f")"
_c_c_old="$(cat "$_c_c_f")"
#printf, not echo: dash's builtin echo interprets backslash escapes and
#would corrupt saved values that contain them on every rewrite
if ! _conf_data="$(printf -- "%s\n" "$_conf_data" | sed "/^$_sdkey *=.*$/d")" ||
! _write_conf "$_c_c_f" "$_conf_data"; then
if ! _conf_data="$(printf -- "%s\n" "$_c_c_old" | sed "/^$_sdkey *=.*$/d")"; then
_err "Cannot clear '$_sdkey' in $_c_c_f."
return 1
fi
#the key is not there, the usual case for the legacy keys cleared on
#every _savedeployconf: nothing to write (#7335)
if [ "$_conf_data" = "$_c_c_old" ]; then
return 0
fi
if ! _write_conf "$_c_c_f" "$_conf_data"; then
_err "Cannot clear '$_sdkey' in $_c_c_f."
return 1
fi
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to an HP printer's Embedded Web Server (EWS).
#
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#47-deploy-the-certificate-to-an-hp-printer-ews
#
# Tested on an HP Color LaserJet Pro MFP M479fdw, firmware
# CLRWTRXXXN002.2539E.00. It should work on any modern HP printer whose EWS
# exposes the "Install Certificate" flow at
# /Security/DeviceCertificates/NewCertWithPassword/Upload -- that endpoint
# takes a password-protected PKCS#12 bundle, which this hook builds from the
# issued certificate, its key and the CA chain.
#
# ```sh
# export HP_EWS_PASSWORD=mysecret
# export HP_EWS_INSECURE=1 # needed until the printer serves a trusted cert
# acme.sh --deploy -d printer.example.com --deploy-hook hp_ews
# ```
#
# Environment variables:
# HP_EWS_PASSWORD (required) EWS administrator password.
# HP_EWS_HOST (optional) IP or hostname of the printer.
# Default: the certificate's domain.
# HP_EWS_USERNAME (optional) EWS administrator user. Default: admin
# HP_EWS_PFX_PASSWORD (optional) Password for the generated PKCS#12
# bundle. A random one is used per run if
# unset; set it only if you want the
# bundle to be reproducible.
# HP_EWS_INSECURE (optional) Set to a non-empty value to skip TLS
# verification when talking to the
# printer. Needed on the first deploy,
# because the printer still serves its
# self-signed certificate at that point.
# acme.sh's global HTTPS_INSECURE is
# honoured as well.
#
# The hook authenticates with HTTP Basic auth. Printers whose EWS only offers
# Digest auth are not supported.
#
# Requires openssl, to build the PKCS#12 bundle.
#
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain pfx
hp_ews_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cpfx="$6"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_debug _cpfx "$_cpfx"
_getdeployconf HP_EWS_HOST
_getdeployconf HP_EWS_USERNAME
_getdeployconf HP_EWS_PASSWORD
_getdeployconf HP_EWS_PFX_PASSWORD
_getdeployconf HP_EWS_INSECURE
HP_EWS_HOST="${HP_EWS_HOST:-$_cdomain}"
HP_EWS_USERNAME="${HP_EWS_USERNAME:-admin}"
_debug HP_EWS_HOST "$HP_EWS_HOST"
_debug HP_EWS_USERNAME "$HP_EWS_USERNAME"
_secure_debug HP_EWS_PASSWORD "$HP_EWS_PASSWORD"
_secure_debug HP_EWS_PFX_PASSWORD "$HP_EWS_PFX_PASSWORD"
_debug HP_EWS_INSECURE "$HP_EWS_INSECURE"
if [ -z "$HP_EWS_PASSWORD" ]; then
_err "HP_EWS_PASSWORD is required. Please set the password of the printer's EWS administrator account."
return 1
fi
_savedeployconf HP_EWS_HOST "$HP_EWS_HOST"
_savedeployconf HP_EWS_USERNAME "$HP_EWS_USERNAME"
_savedeployconf HP_EWS_PASSWORD "$HP_EWS_PASSWORD" "base64"
_savedeployconf HP_EWS_PFX_PASSWORD "$HP_EWS_PFX_PASSWORD" "base64"
_savedeployconf HP_EWS_INSECURE "$HP_EWS_INSECURE"
if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
_err "openssl is required by this deploy hook, but was not found."
return 1
fi
# The bundle password only has to survive the single upload below, so a
# fresh random one is used unless the operator asked for a fixed value.
_HP_EWS_PFXPW="$HP_EWS_PFX_PASSWORD"
if [ -z "$_HP_EWS_PFXPW" ]; then
_HP_EWS_PFXPW="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 24)"
if [ -z "$_HP_EWS_PFXPW" ]; then
_err "Could not generate a random password for the PKCS#12 bundle."
return 1
fi
fi
export _HP_EWS_PFXPW
# $6 is only materialised when Le_PFXPassword is set for this domain, and
# it would be built without the CA chain, so build our own bundle instead.
_hp_ews_pfx="$(_mktemp)"
_hp_ews_body="$(_mktemp)"
if [ -z "$_hp_ews_pfx" ] || [ -z "$_hp_ews_body" ]; then
_err "Could not create the temporary files needed for the upload."
_hp_ews_cleanup
return 1
fi
_hp_ews_ret=0
if ! _hp_ews_make_pfx; then
_hp_ews_ret=1
elif ! _hp_ews_upload; then
_hp_ews_ret=1
fi
_hp_ews_cleanup
return "$_hp_ews_ret"
}
#################### Private functions below ##################################
_hp_ews_cleanup() {
[ -n "$_hp_ews_pfx" ] && rm -f "$_hp_ews_pfx"
[ -n "$_hp_ews_body" ] && rm -f "$_hp_ews_body"
unset _HP_EWS_PFXPW
# Do not leak the request header into any hook that runs after this one.
export _H1=""
return 0
}
# Bundle key, certificate and chain into $_hp_ews_pfx, protected by
# $_HP_EWS_PFXPW. The password goes through the environment rather than the
# command line so that it does not show up in ps output.
_hp_ews_make_pfx() {
_info "Building the PKCS#12 bundle"
if ! ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export \
-out "$_hp_ews_pfx" \
-inkey "$_ckey" \
-in "$_ccert" \
-certfile "$_cca" \
-passout env:_HP_EWS_PFXPW; then
_err "Failed to build the PKCS#12 bundle."
return 1
fi
if [ ! -s "$_hp_ews_pfx" ]; then
_err "The PKCS#12 bundle is empty."
return 1
fi
return 0
}
# Build the multipart body in $_hp_ews_body and POST it to the printer.
_hp_ews_upload() {
_hp_ews_url="https://${HP_EWS_HOST}/Security/DeviceCertificates/NewCertWithPassword/Upload"
_hp_ews_boundary="----------------------------acmesh$(${ACME_OPENSSL_BIN:-openssl} rand -hex 16)"
{
printf -- "--%s\r\n" "$_hp_ews_boundary"
printf "Content-Disposition: form-data; name=\"certificate\"; filename=\"%s.pfx\"\r\n" "$_cdomain"
printf "Content-Type: application/x-pkcs12\r\n\r\n"
cat "$_hp_ews_pfx"
printf "\r\n"
printf -- "--%s\r\n" "$_hp_ews_boundary"
printf "Content-Disposition: form-data; name=\"password\"\r\n\r\n%s\r\n" "$_HP_EWS_PFXPW"
printf -- "--%s--\r\n" "$_hp_ews_boundary"
} >"$_hp_ews_body"
_H1="Authorization: Basic $(printf "%s:%s" "$HP_EWS_USERNAME" "$HP_EWS_PASSWORD" | _base64)"
export _H1
if [ "$HP_EWS_INSECURE" ]; then
export HTTPS_INSECURE=1
fi
_info "Uploading the certificate to $_hp_ews_url"
if ! _post_file "$_hp_ews_body" "$_hp_ews_url" "" "POST" "multipart/form-data; boundary=$_hp_ews_boundary" >/dev/null; then
if [ -z "$HTTPS_INSECURE" ]; then
_err "If the printer still serves its self-signed certificate, set HP_EWS_INSECURE=1 and try again."
fi
return 1
fi
# The EWS answers 201 Created on a successful install.
_hp_ews_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
_debug _hp_ews_code "$_hp_ews_code"
if [ "$_hp_ews_code" != "201" ] && [ "$_hp_ews_code" != "200" ]; then
_err "The printer rejected the certificate (HTTP $_hp_ews_code)."
if [ "$_hp_ews_code" = "401" ] || [ "$_hp_ews_code" = "403" ]; then
_err "Check HP_EWS_USERNAME and HP_EWS_PASSWORD. Only HTTP Basic authentication is"
_err "supported; printers that only offer Digest cannot be used with this hook."
fi
return 1
fi
_info "Certificate installed (HTTP $_hp_ews_code). The printer restarts its web server shortly."
return 0
}
+492
View File
@@ -0,0 +1,492 @@
#!/usr/bin/env sh
# Script to deploy certificates to compatible BMCs (baseboard management
# controllers) using the [Redfish API](https://www.dmtf.org/standards/redfish).
#
# Environment Variables:
#
# ```sh
# DEPLOY_REDFISH_HOST="ipmi.example.com" # Required
# DEPLOY_REDFISH_USERNAME="Administrator" # Required
# DEPLOY_REDFISH_PASSWORD="superuser" # Required
# DEPLOY_REDFISH_USE_BASIC_AUTH=0 # Optional (set to "1" to enable)
# DEPLOY_REDFISH_RESTART=0 # Optional (set to "1" to enable)
# DEPLOY_REDFISH_MANAGER= # Optional (e.g. "/redfish/v1/Managers/Self")
# DEPLOY_REDFISH_CERTIFICATE= # Optional (e.g. "/redfish/v1/CertificateService/Certificates/1")
# ```
#
# Compatibility:
#
# Compatible with any IPMI/BMC solution supporting Redfish v1.6.1 or newer (or
# Redfish Schema Bundle 2018.3), as defined in the following documents:
#
# <https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.6.1.pdf>
# <https://www.dmtf.org/sites/default/files/standards/documents/DSP2046_2018.3.pdf>
# <https://www.dmtf.org/sites/default/files/standards/documents/DSP2059_1.0.0.pdf>
#
# This script is confirmed to work on the following systems:
#
# | IPMI Solution | BMC Chipset | Firmware Family | Redfish Version |
# |------------------|----------------|------------------|-----------------|
# | ASUS ASMB12-iKVM | ASPEED AST2600 | AMI MegaRAC SP-X | 1.15.1 |
#
# However, this should also work with HPE iLO 5 v2.42 (or newer), Dell iDRAC9
# v5.0 (or newer), or any ASUS/Lenovo/Supermicro IPMI solution based on AMI
# MegaRAC SP-X v12.x (or newer).
#domain keyfile certfile cafile fullchain
redfish_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"
_getdeployconf DEPLOY_REDFISH_HOST
_getdeployconf DEPLOY_REDFISH_USERNAME
_getdeployconf DEPLOY_REDFISH_PASSWORD
_getdeployconf DEPLOY_REDFISH_USE_BASIC_AUTH
_getdeployconf DEPLOY_REDFISH_RESTART
_getdeployconf DEPLOY_REDFISH_MANAGER
_getdeployconf DEPLOY_REDFISH_CERTIFICATE
if [ -z "${DEPLOY_REDFISH_HOST}" ]; then
_err 'DEPLOY_REDFISH_HOST must be set. Please specify the IP or domain name of the Redfish server.'
return 1
fi
if [ -z "${DEPLOY_REDFISH_USERNAME}" ] || [ -z "${DEPLOY_REDFISH_PASSWORD}" ]; then
_err 'DEPLOY_REDFISH_USERNAME and DEPLOY_REDFISH_PASSWORD must be set.'
return 1
fi
DEPLOY_REDFISH_USE_BASIC_AUTH="${DEPLOY_REDFISH_USE_BASIC_AUTH:-0}"
DEPLOY_REDFISH_RESTART="${DEPLOY_REDFISH_RESTART:-0}"
_debug DEPLOY_REDFISH_HOST "${DEPLOY_REDFISH_HOST}"
_debug DEPLOY_REDFISH_USERNAME "${DEPLOY_REDFISH_USERNAME}"
_secure_debug DEPLOY_REDFISH_PASSWORD "${DEPLOY_REDFISH_PASSWORD}"
_debug DEPLOY_REDFISH_USE_BASIC_AUTH "${DEPLOY_REDFISH_USE_BASIC_AUTH}"
_redfish_log_in "$DEPLOY_REDFISH_USERNAME" "$DEPLOY_REDFISH_PASSWORD" || return 1
# Credentials are proven correct now -- save them, rather than only at the
# very end, so a later step failing doesn't discard a working login.
_savedeployconf DEPLOY_REDFISH_HOST "${DEPLOY_REDFISH_HOST}"
_savedeployconf DEPLOY_REDFISH_USERNAME "${DEPLOY_REDFISH_USERNAME}"
_savedeployconf DEPLOY_REDFISH_PASSWORD "${DEPLOY_REDFISH_PASSWORD}" 'base64'
_savedeployconf DEPLOY_REDFISH_USE_BASIC_AUTH "${DEPLOY_REDFISH_USE_BASIC_AUTH}"
if ! _redfish_get_certificate_service_endpoint; then
_err "Redfish server ${DEPLOY_REDFISH_HOST} doesn't support certificate management API."
return 1
fi
_debug DEPLOY_REDFISH_RESTART "${DEPLOY_REDFISH_RESTART}"
_debug DEPLOY_REDFISH_MANAGER "${DEPLOY_REDFISH_MANAGER}"
_debug DEPLOY_REDFISH_CERTIFICATE "${DEPLOY_REDFISH_CERTIFICATE}"
if [ -n "${DEPLOY_REDFISH_MANAGER}" ]; then
_manager_endpoint="${DEPLOY_REDFISH_MANAGER}"
else
_info 'Identifying REST endpoint of primary Redfish manager.'
_redfish_get_primary_manager_endpoint || return 1
_savedeployconf DEPLOY_REDFISH_MANAGER "${_manager_endpoint}"
fi
if [ -n "${DEPLOY_REDFISH_CERTIFICATE}" ]; then
_certificate_endpoint="${DEPLOY_REDFISH_CERTIFICATE}"
else
_info "Identifying REST endpoint of primary TLS certificate on: ${_manager_endpoint}"
_redfish_get_manager_certificate_endpoint "${_manager_endpoint}" || return 1
fi
if [ -f "${_ckey}" ] && [ -f "${_cfullchain}" ]; then
_info "Uploading private key and full certificate chain to: ${_certificate_endpoint}"
if ! _redfish_supports_full_certificate_chains; then
_err 'Redfish server does not support uploading full certificate chains!'
_err "Please call 'GenerateCSR' API on server first, pass the CSR to 'acme.sh --sign-csr --csr <key.pem>', and try again."
_err 'For more information: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#generate-csr-on-server-optional'
return 1
elif ! _redfish_supports_private_key "${_ckey}" "${_certificate_service_endpoint}"; then
_err "Redfish server does not support this type of private key."
return 1
fi
_ckey_pkcs8="$("${ACME_OPENSSL_BIN:-openssl}" pkcs8 -topk8 -nocrypt -in "${_ckey}")"
# shellcheck disable=SC2181
if [ "$?" != '0' ] || [ -z "${_ckey_pkcs8}" ]; then
_err 'Failed to convert private key to PKCS#8 format!'
return 1
fi
_certificate_str="$(echo "${_ckey_pkcs8}" | cat - "${_cfullchain}" | _json_encode)"
_certificate_type='PEMchain'
else
_info "Private key and full certificate chain not available; we must be using a server-generated CSR."
_info "Uploading only leaf certificate to: ${_certificate_endpoint}"
_certificate_str="$(_json_encode <"${_ccert}")"
_certificate_type='PEM'
fi
_body="$(printf '{"CertificateString":"%s","CertificateType":"%s","CertificateUri":{"@odata.id":"%s"}}' "${_certificate_str}" "${_certificate_type}" "${_certificate_endpoint}")"
_redfish_rest POST "${_certificate_service_endpoint}/Actions/CertificateService.ReplaceCertificate" "${_body}" || return 1
_code="$(_redfish_response_code)"
if ! _startswith "${_code}" '2'; then
_err "Failed to update Redfish server TLS certificate! (HTTP ${_code})"
_err "Response: ${_response}"
return 1
fi
_info 'Successfully updated Redfish server TLS certificate!'
_savedeployconf DEPLOY_REDFISH_CERTIFICATE "${_certificate_endpoint}"
if [ "${DEPLOY_REDFISH_RESTART}" = '1' ]; then
_info 'Attempting to restart BMC gracefully.'
_redfish_attempt_graceful_restart "${_manager_endpoint}" || return 0
_info 'Successfully sent graceful restart command to BMC. After restarting, the new TLS certificate will be active.'
else
_info 'Please wait up to 20 seconds to take effect, or restart the BMC manually.'
fi
_savedeployconf DEPLOY_REDFISH_RESTART "${DEPLOY_REDFISH_RESTART}"
return 0
}
_redfish_rest() {
_method="$1"
_endpoint="$2"
_body="${3:-}"
export _H1='OData-Version: 4.0'
if [ "${_method}" = 'GET' ]; then
_response="$(_get "https://${DEPLOY_REDFISH_HOST}${_endpoint}")"
else
_response="$(_post "${_body}" "https://${DEPLOY_REDFISH_HOST}${_endpoint}" '' "${_method}" "${_body:+application/json}")"
fi
_ret="$?"
_response="$(echo "${_response}" | _normalizeJson)"
if [ "${_ret}" != '0' ]; then
_err "Error while calling ${_method} ${_endpoint}. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
fi
return "${_ret}"
}
_redfish_response_code() {
_egrep_o <"${HTTP_HEADER}" '^HTTP[^ ]* [0-9]+' | _tail_n 1 | tr -d '\r\n' | cut -d ' ' -f 2
}
_redfish_get_odata_id() {
_regex='s/{'
for key in $(echo "$1" | tr '.' '\n'); do
_regex="${_regex}.*\"${key}\":{"
done
sed -n "${_regex}[^}]*\"@odata\\.id\":[ ]*\"\\([^\"]*\\)\".*/\\1/p"
}
_redfish_get_odata_members() {
sed -n 's/.*"Members":[ ]*\[\([^]]*\)\].*/\1/p' | sed 's/,/\n/g' | sed -n 's/.*{"@odata\.id":"\([^"]*\)"}.*/\1/p'
}
_redfish_get_odata_count() {
sed -n 's/.*"Members@odata\.count":[ ]*\([0-9][0-9]*\).*/\1/p'
}
_redfish_log_in() {
_username="$1"
_password="$2"
_redfish_rest GET '/redfish/v1/' || return 1
_info "Authenticating with Redfish API: ${DEPLOY_REDFISH_HOST}"
if [ "${DEPLOY_REDFISH_USE_BASIC_AUTH}" = '1' ]; then
_access_token="$(printf '%s:%s' "${_username}" "${_password}" | _base64)"
export _H2="Authorization: Basic ${_access_token}"
# Verify credentials are valid
_account_service_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'AccountService')"
_redfish_rest GET "${_account_service_endpoint}" || return 1
_code="$(_redfish_response_code)"
if [ "${_code}" != '200' ]; then
_err "Redfish authentication failed (HTTP ${_code})"
_err "Response: ${_response}"
return 1
fi
else
# Create new session
_sessions_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'Links.Sessions')"
_username="$(printf '%s\n' "${_username}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')"
_password="$(printf '%s\n' "${_password}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')"
_body="$(printf '{"UserName":"%s","Password":"%s"}' "${_username}" "${_password}")"
_redfish_rest POST "${_sessions_endpoint}" "${_body}" || return 1
_code="$(_redfish_response_code)"
# Verify authentication succeeded
if [ "${_code}" != '201' ]; then
_err "Redfish authentication failed (HTTP ${_code})"
_err "Response: ${_response}"
if [ "${_code}" = '401' ] && grep -qi '^WWW-Authenticate: Basic.*$' "${HTTP_HEADER}"; then
_err 'NOTE: Server supports Basic auth scheme. Consider setting DEPLOY_REDFISH_USE_BASIC_AUTH=1 and try again.'
fi
return 1
fi
_session="$(echo "${_response}" | _redfish_get_odata_id '')"
_auth_token="$(grep -i '^X-Auth-Token: .*$' "${HTTP_HEADER}" | _tail_n 1 | tr -d ' \r\n' | cut -d ':' -f 2)"
export _H2="X-Auth-Token: ${_auth_token}"
fi
trap '_redfish_log_out "${_session:-}"' EXIT INT
}
_redfish_log_out() {
_session_endpoint="${1:-}"
_info "Logging out of Redfish session on: ${DEPLOY_REDFISH_HOST}"
if [ -n "${_session_endpoint}" ]; then
_redfish_rest DELETE "${_session_endpoint}" || return 1
_code="$(_redfish_response_code)"
if ! _startswith "${_code}" '2'; then
_err "Failed to log out of Redfish server (HTTP ${_code})."
_err "Response: ${_response}"
fi
else
_info 'Using basic HTTP auth, no need to log out of Redfish API.'
fi
export _H1=
export _H2=
}
_redfish_get_certificate_service_endpoint() {
_redfish_rest GET '/redfish/v1/' || return 1
_certificate_service_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'CertificateService')"
[ -n "${_certificate_service_endpoint}" ]
}
_redfish_supports_full_certificate_chains() {
_redfish_rest GET "/redfish/v1/\$metadata" || return 1
_newest_certificate_schema_version="$(echo "${_response}" |
_egrep_o 'Namespace="Certificate\.v[_0-9][_0-9]*"' |
sed -n 's/.*v\([_0-9][_0-9]*\)".*/\1/p' |
sort -n -t _ -k1,1 -k2,2 -k3,3 | _tail_n 1 | tr '_' '.')"
_debug _newest_certificate_schema_version "${_newest_certificate_schema_version}"
_schema_major=${_newest_certificate_schema_version%%.*}
_schema_patch_minor=${_newest_certificate_schema_version#*.}
_schema_minor=${_schema_patch_minor%%.*}
_schema_patch=${_schema_patch_minor#*.}
for var in _schema_major _schema_minor _schema_patch; do
_debug "$var" "$(eval echo "\$$var")"
case "$(eval echo "\$$var")" in
'' | *[!0-9]*) return 1 ;;
*) continue ;;
esac
done
[ "${_schema_major}" -gt '1' ] || { [ "${_schema_major}" -ge '1' ] && [ "${_schema_minor}" -ge '4' ]; }
}
_redfish_get_action() {
unset _action _action_info
_redfish_rest GET "$1" || return 1
_action="$(echo "${_response}" | sed -n "s/{.*\"Actions\":{.*\"#$2\":\({[^}]*}\).*/\1/p")"
_action_info_endpoint="$(echo "${_action}" | sed -n 's/.*"@Redfish\.ActionInfo":"\([^"]*\)".*/\1/p')"
if [ -n "${_action_info_endpoint}" ]; then
_redfish_rest GET "${_action_info_endpoint}" || return 1
_code="$(_redfish_response_code)"
if [ "${_code}" = '200' ]; then
_action_info="${_response}"
fi
fi
}
_redfish_get_action_allowable_values() {
_parameter="$1"
_allowable_values="$(echo "${_action}" |
sed -n "s/.*\"${_parameter}@Redfish\.AllowableValues\":[ ]*\[\([^]]*\)\].*/\1/p" |
tr -d '"')"
if [ -n "${_allowable_values}" ]; then
_debug2 "Found allowable values for '${_parameter}' in '@Redfish.Action': [${_allowable_values}]"
echo "${_allowable_values}"
elif [ -n "${_action_info}" ]; then
_parameter_info="$(echo "${_action_info}" |
sed -n 's/.*"Parameters":[ ]*\[\(\({[^}]*},\{0,1\}\)*\)\].*/\1/p' | _egrep_o '\{[^\}]{1,}\}' |
grep "\"Name\":\"${_parameter}\"")"
if [ -n "${_parameter_info}" ]; then
_debug2 "Found allowable values for '${_parameter}' in @Redfish.ActionInfo: ${_parameter_info}"
_data_type="$(echo "${_parameter_info}" | sed -n 's/.*"DataType":[ ]*"\([^"]*\)".*/\1/p')"
case "${_data_type}" in
String)
echo "${_parameter_info}" | sed -n 's/.*"AllowableValues":[ ]*\[\([^]]*\)\].*/\1/p' | tr -d '"'
;;
Number)
_min_value="$(echo "${_parameter_info}" | sed -n 's/.*"MinimumValue":[ ]*\([0-9]\{1,\}\).*/\1/p')"
_max_value="$(echo "${_parameter_info}" | sed -n 's/.*"MaximumValue":[ ]*\([0-9]\{1,\}\).*/\1/p')"
echo "${_min_value},${_max_value}"
;;
esac
fi
fi
}
_redfish_supports_private_key() {
_private_key="$1"
_certificate_service_endpoint="$2"
if _isRSA "${_private_key}"; then
_key_algo='RSA'
elif _isEcc "${_private_key}"; then
_key_algo='ECDSA'
else
_err 'Private key uses unknown cryptographic algorithm!'
return 1
fi
_redfish_get_action "${_certificate_service_endpoint}" 'CertificateService.GenerateCSR' || return 1
_allowed_key_algos="$(_redfish_get_action_allowable_values 'KeyPairAlgorithm')"
_allowed_key_bit_lengths="$(_redfish_get_action_allowable_values 'KeyBitLength')"
_allowed_key_curve_ids="$(_redfish_get_action_allowable_values 'KeyCurveId')"
if [ -z "${_allowed_key_algos}" ]; then
_contains "${_response}" '"KeyBitLength"' && _allowed_key_algos='TPM_ALG_RSA'
_contains "${_response}" '"KeyCurveId"' && _allowed_key_algos="${_allowed_key_algos},TPM_ALG_ECDSA"
_allowed_key_algos="${_allowed_key_algos#,}"
if [ -z "${_allowed_key_algos}" ]; then
_debug 'Failed to determine server crypto requirements, proceeding blindly.'
return 0
fi
_debug "GenerateCSR does not specify allowed KeyPairAlgorithm(s), so assuming ${_allowed_key_algos}."
fi
_debug _key_algo "${_key_algo}"
_debug _allowed_key_algos "${_allowed_key_algos}"
case "${_key_algo}:${_allowed_key_algos}" in
RSA:*RSA*)
_min_key_length="$(echo "${_allowed_key_bit_lengths}" | cut -d ',' -f 1)"
_max_key_length="${_allowed_key_bit_lengths##*,}"
_debug _min_key_length "${_min_key_length}"
_debug _max_key_length "${_max_key_length}"
if [ -z "${_min_key_length}" ] && [ -z "${_max_key_length}" ]; then
_debug "Server supports RSA private keys but doesn't specify minimum/maximum bit length, proceeding blindly."
return 0
fi
# shellcheck disable=SC2154 # Le_Keylength is set by acme.sh core, not this hook
if [ "${Le_Keylength}" -lt "${_min_key_length:-${Le_Keylength}}" ] || [ "${Le_Keylength}" -gt "${_max_key_length:-${Le_Keylength}}" ]; then
_err "Unsupported RSA private key length ${Le_Keylength}!"
_err "Please re-run acme.sh with --keylength set to a value between ${_min_key_length} and ${_max_key_length}."
return 1
fi
;;
ECDSA:*ECDSA*)
_debug _allowed_key_curve_ids "${_allowed_key_curve_ids}"
if [ -z "${_allowed_key_curve_ids}" ]; then
_debug "Server supports ECDSA private keys but doesn't specify which curves, proceeding blindly."
return 0
fi
# shellcheck disable=SC2154 # Le_Keylength is set by acme.sh core, not this hook
if ! _contains "${_allowed_key_curve_ids}" "TPM_ECC_NIST_P${Le_Keylength#ec-}"; then
_err "Unsupported ECDSA private key type! Server supports only: ${_allowed_key_curve_ids}"
return 1
fi
;;
*)
_err "Unsupported key pair algorithm ${_key_algo}! Server supports only: ${_allowed_key_algos}"
return 1
;;
esac
}
_redfish_get_primary_manager_endpoint() {
_redfish_rest GET '/redfish/v1/' || return 1
_managers_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'Managers')"
_redfish_rest GET "${_managers_endpoint}" || return 1
_num_managers="$(echo "${_response}" | _redfish_get_odata_count)"
if [ "${_num_managers}" != '1' ]; then
_all_managers="$(echo "${_response}" | _redfish_get_odata_members | tr '\n' ',')"
_err "Multiple Redfish managers identified (${_all_managers}), but expected exactly one."
_err 'Please specify the correct manager in DEPLOY_REDFISH_MANAGER.'
return 1
fi
_manager_endpoint="$(echo "${_response}" | _redfish_get_odata_members | _head_n 1)"
[ -n "${_manager_endpoint}" ]
}
_redfish_get_manager_certificate_endpoint() {
_manager_endpoint="$1"
_redfish_rest GET "${_manager_endpoint}" || return 1
_protocol_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'NetworkProtocol')"
_redfish_rest GET "${_protocol_endpoint}/HTTPS/Certificates" || return 1
_num_certificates="$(echo "${_response}" | _redfish_get_odata_count)"
if [ -z "${_num_certificates}" ]; then
_certificate_endpoint="${_protocol_endpoint}/HTTPS/Certificates/1"
return 0
elif [ "${_num_certificates}" != '1' ]; then
_all_certificates="$(echo "${_response}" | _redfish_get_odata_members | tr '\n' ',')"
_err "Multiple web service HTTPS certificates identified (${_all_certificates}), but expected exactly one."
_err "Please specify which certificate should be replaced in DEPLOY_REDFISH_CERTIFICATE."
return 1
fi
_certificate_endpoint="$(echo "${_response}" | _redfish_get_odata_members | _head_n 1)"
[ -n "${_certificate_endpoint}" ]
}
_redfish_attempt_graceful_restart() {
_manager_endpoint="$1"
_redfish_get_action "${_manager_endpoint}" 'Manager.Reset' || return 1
_allowed_reset_types="$(_redfish_get_action_allowable_values 'ResetType')"
if ! _contains "${_allowed_reset_types}" 'GracefulRestart'; then
_err "BMC doesn't support graceful restarts. Please wait up to 20 seconds to take effect, or restart the BMC manually."
return 1
fi
_redfish_rest POST "${_manager_endpoint}/Actions/Manager.Reset" '{"ResetType":"GracefulRestart"}' || return 1
_code="$(_redfish_response_code)"
if ! _startswith "${_code}" '2'; then
_err "Failed to gracefully restart BMC! (HTTP ${_code})"
_err "Response: ${_response}"
return 1
fi
}
+40 -14
View File
@@ -189,6 +189,19 @@ ssh_deploy() {
_info "Required commands batched and sent in single call to remote host"
fi
# With a POSIX remote shell, steps are joined with && so that the first
# failed step stops the rest (no REMOTE_CMD after a failed write). Other
# shells ("powershell -c", "cmd /c") keep the plain ";" join.
_ssh_shell_name="${DEPLOY_SSH_REMOTE_SHELL%% *}"
case "${_ssh_shell_name##*/}" in
sh | bash | dash | ash | ksh | mksh | zsh)
_ssh_fail_fast="yes"
;;
*)
_ssh_fail_fast="no"
;;
esac
_returnCode=0
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
@@ -228,9 +241,9 @@ _ssh_deploy() {
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
# run cleanup on the backup directory, erase all older
# than 180 days (15552000 seconds).
_cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
_ssh_add_step "now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \
then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr"
then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done"
# Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr"
_info "Backup of old certificate files will be placed in remote directory $_backupdir"
_info "Backup directories erased after 180 days."
@@ -245,7 +258,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr if [ -f $DEPLOY_SSH_KEYFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null; fi;"
_ssh_add_step "if [ -f $DEPLOY_SSH_KEYFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null; fi"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
@@ -261,10 +274,9 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
return $_err_code
fi
else
# If file doesn't exist, create it and change its permissions.
_cmdstr="$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;"
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
# If file doesn't exist, create it and change its permissions, then
# ssh echo to the file. One step: "test ! -f" fails if the file exists.
_ssh_add_step "test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE; echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE"
_info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
@@ -282,7 +294,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
_pipe=">>"
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr if [ -f $DEPLOY_SSH_CERTFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null; fi;"
_ssh_add_step "if [ -f $DEPLOY_SSH_CERTFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null; fi"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
@@ -304,7 +316,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;"
_ssh_add_step "echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE"
_info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
@@ -323,7 +335,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
_pipe=">>"
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr if [ -f $DEPLOY_SSH_CAFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null; fi;"
_ssh_add_step "if [ -f $DEPLOY_SSH_CAFILE ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null; fi"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
@@ -348,7 +360,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;"
_ssh_add_step "echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE"
_info "will copy CA file to remote file $DEPLOY_SSH_CAFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
@@ -368,7 +380,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
_pipe=">>"
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr if [ -f $DEPLOY_SSH_FULLCHAIN ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null; fi;"
_ssh_add_step "if [ -f $DEPLOY_SSH_FULLCHAIN ]; then mkdir -p $_backupdir; cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null; fi"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
@@ -396,7 +408,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;"
_ssh_add_step "echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN"
_info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
@@ -419,7 +431,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
_cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;"
_ssh_add_step "$DEPLOY_SSH_REMOTE_CMD"
_info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
@@ -439,6 +451,20 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
return 0
}
#step
_ssh_add_step() {
if [ "$_ssh_fail_fast" = "yes" ]; then
if [ -n "$_cmdstr" ]; then
_cmdstr="$_cmdstr && "
fi
# the newline ends the step even if it ends with a comment, ";" or "&"
_cmdstr="$_cmdstr{ $1
}"
else
_cmdstr="$_cmdstr $1;"
fi
}
#cmd
_ssh_remote_cmd() {
_cmd="$1"
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_abion_info='Abion.com
Site: abion.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_abion
Options:
ABION_API_KEY API Key. Created in the Abion Core web interface.
Issues: github.com/acmesh-official/acme.sh/issues/7325
'
ABION_Api_Endpoint="https://api.abion.com/v1"
##################### Public functions #####################
# Usage: dns_abion_add <domain> <txt record>
# Example: dns_abion_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_abion_add() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
ABION_API_KEY="${ABION_API_KEY:-$(_readaccountconf_mutable ABION_API_KEY)}"
if [ -z "$ABION_API_KEY" ]; then
ABION_API_KEY=""
_err "You need to specify ABION_API_KEY."
return 1
fi
_saveaccountconf_mutable ABION_API_KEY "$ABION_API_KEY"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _abion_get_txt_records; then
return 1
fi
if _contains "$_txt_records" "\"rdata\":\"$txtvalue\""; then
_info "TXT record already exists"
return 0
fi
_records=$(echo "$_txt_records" | tr '\n' ',' | sed 's/,*$//')
if [ -n "$_records" ]; then
_records="$_records,"
fi
_info "Adding record"
if ! _abion_patch_txt "[$_records{\"rdata\":\"$txtvalue\",\"ttl\":60}]"; then
return 1
fi
_info "Added, OK"
return 0
}
# Usage: dns_abion_rm <domain> <txt record>
dns_abion_rm() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
ABION_API_KEY="${ABION_API_KEY:-$(_readaccountconf_mutable ABION_API_KEY)}"
if [ -z "$ABION_API_KEY" ]; then
ABION_API_KEY=""
_err "You need to specify ABION_API_KEY."
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _abion_get_txt_records; then
return 1
fi
if ! _contains "$_txt_records" "\"rdata\":\"$txtvalue\""; then
_info "TXT record not found, nothing to remove"
return 0
fi
_records=$(echo "$_txt_records" | grep -v "\"rdata\":\"$txtvalue\"" | tr '\n' ',' | sed 's/,*$//')
if [ -n "$_records" ]; then
_records="[$_records]"
else
_records="null"
fi
_info "Deleting record"
if ! _abion_patch_txt "$_records"; then
return 1
fi
_info "Deleted, OK"
return 0
}
##################### Private functions #####################
_get_root() {
domain=$1
i=1
p=0
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
return 1
fi
if ! _abion_rest GET "zones/$h"; then
return 1
fi
if _contains "$response" '"type" *: *"zone"'; then
if [ "$p" = "0" ]; then
_sub_domain="@"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
fi
_domain="$h"
return 0
fi
if ! _contains "$response" "Zone not found"; then
_err "$response"
return 1
fi
p="$i"
i=$(_math "$i" + 1)
done
}
_abion_get_txt_records() {
if ! _abion_rest GET "zones/$_domain"; then
return 1
fi
_sub_re=$(printf "%s" "$_sub_domain" | sed 's/[.]/\\./g')
_txt_records=$(echo "$response" | _normalizeJson |
sed -n "s/.*\"$_sub_re\":{//p" |
sed 's/\] *}.*/]/' |
_egrep_o '"TXT":\[[^]]*\]' |
sed 's/^"TXT":\[ *//;s/ *\]$//;s/} *, *{/}\
{/g')
_debug2 _txt_records "$_txt_records"
return 0
}
_abion_patch_txt() {
if ! _abion_rest PATCH "zones/$_domain" "{\"data\":{\"type\":\"zone\",\"id\":\"$_domain\",\"attributes\":{\"records\":{\"$_sub_domain\":{\"TXT\":$1}}}}}"; then
return 1
fi
if ! _contains "$response" '"type" *: *"zone"'; then
_err "Unexpected API response: $response"
return 1
fi
return 0
}
_abion_rest() {
method=$1
endpoint=$2
data=$3
export _H1="X-API-KEY: $ABION_API_KEY"
export _H2="Content-Type: application/json"
if [ "$method" = "GET" ]; then
response="$(_get "$ABION_Api_Endpoint/$endpoint")"
else
response="$(_post "$data" "$ABION_Api_Endpoint/$endpoint" "" "$method")"
fi
if [ "$?" != "0" ]; then
_err "error $endpoint"
return 1
fi
_debug2 response "$response"
return 0
}
+2 -2
View File
@@ -18,7 +18,7 @@ CF_Api="https://api.cloudflare.com/client/v4"
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_cf_add() {
fulldomain=$1
fulldomain="$(_idn "$1")"
txtvalue=$2
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
@@ -109,7 +109,7 @@ dns_cf_add() {
#fulldomain txtvalue
dns_cf_rm() {
fulldomain=$1
fulldomain="$(_idn "$1")"
txtvalue=$2
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
+10 -5
View File
@@ -15,7 +15,7 @@ DNSimple_API="https://api.dnsimple.com/v2"
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dnsimple_add() {
fulldomain=$1
fulldomain=$(echo "$1" | _lower_case)
txtvalue=$2
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
@@ -56,7 +56,7 @@ dns_dnsimple_add() {
# fulldomain
dns_dnsimple_rm() {
fulldomain=$1
fulldomain=$(echo "$1" | _lower_case)
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
@@ -110,9 +110,13 @@ _get_root() {
return 1
fi
if _contains "$response" 'not found'; then
_debug "$h not found"
else
if ! _contains "$response" "\"name\":\"$h\""; then
if ! _dnsimple_rest GET "$_account_id/zones/$h/records?per_page=1"; then
return 1
fi
fi
if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"pagination":'; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$previous")
_domain="$h"
@@ -121,6 +125,7 @@ _get_root() {
return 0
fi
_debug "$h not found"
previous="$i"
i=$(_math "$i" + 1)
+21
View File
@@ -59,6 +59,13 @@ dns_gandi_livedns_rm() {
fulldomain=$1
txtvalue=$2
GANDI_LIVEDNS_KEY="${GANDI_LIVEDNS_KEY:-$(_readaccountconf_mutable GANDI_LIVEDNS_KEY)}"
GANDI_LIVEDNS_TOKEN="${GANDI_LIVEDNS_TOKEN:-$(_readaccountconf_mutable GANDI_LIVEDNS_TOKEN)}"
if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then
_err "No Token or API key (deprecated) specified for Gandi LiveDNS."
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -80,6 +87,20 @@ dns_gandi_livedns_rm() {
_new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g")
_debug "New rrset_values" "$_new_rrset_values"
# Last value removed: delete the record instead of leaving an empty TXT rrset.
if [ "$(echo "$_new_rrset_values" | tr -d ' ')" = "[]" ]; then
if ! _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT"; then
return 1
fi
_gandi_livedns_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$_gandi_livedns_code" != "204" ]; then
_err "Error deleting record: $response"
return 1
fi
_info "Removing record $(__green "success")"
return 0
fi
_gandi_livedns_rest PUT \
"domains/$_domain/records/$_sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" &&
+6 -3
View File
@@ -185,13 +185,14 @@ _get_root_by_getHosts() {
}
_namecheap_set_publicip() {
NAMECHEAP_SOURCEIP="${NAMECHEAP_SOURCEIP:-$(_readaccountconf_mutable NAMECHEAP_SOURCEIP)}"
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_saveaccountconf_mutable NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_debug sourceip "$NAMECHEAP_SOURCEIP"
ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
@@ -252,6 +253,8 @@ _namecheap_parse_host() {
}
_namecheap_check_config() {
NAMECHEAP_API_KEY="${NAMECHEAP_API_KEY:-$(_readaccountconf_mutable NAMECHEAP_API_KEY)}"
NAMECHEAP_USERNAME="${NAMECHEAP_USERNAME:-$(_readaccountconf_mutable NAMECHEAP_USERNAME)}"
if [ -z "$NAMECHEAP_API_KEY" ]; then
_err "No API key specified for Namecheap API."
@@ -265,8 +268,8 @@ _namecheap_check_config() {
return 1
fi
_saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
_saveaccountconf_mutable NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf_mutable NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
return 0
}
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_primedns_info='PrimeDNS
Site: primedns.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_primedns
Options:
PRIMEDNS_API_KEY API Key. Scope acme:name:_acme-challenge.<hostname> for each name you issue (or dns:write)
OptionsAlt:
PRIMEDNS_API_URL API base URL. Optional, default https://api.primedns.net/v1
Issues: github.com/acmesh-official/acme.sh/issues/7330
Author: PrimeDNS <acme@primedns.net>
'
PRIMEDNS_Api_Default="https://api.primedns.net/v1"
######## Public functions #####################
#Usage: dns_primedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_primedns_add() {
fulldomain=$(_idn "$1")
txtvalue=$2
PRIMEDNS_API_KEY="${PRIMEDNS_API_KEY:-$(_readaccountconf_mutable PRIMEDNS_API_KEY)}"
if [ -z "$PRIMEDNS_API_KEY" ]; then
PRIMEDNS_API_KEY=""
_err "You didn't specify a PrimeDNS API key (PRIMEDNS_API_KEY) yet."
_err "Create one in the PrimeDNS panel (Account -> API Keys) with the scope acme:name:_acme-challenge.<hostname> for each name you issue."
return 1
fi
PRIMEDNS_API_URL="${PRIMEDNS_API_URL:-$(_readaccountconf_mutable PRIMEDNS_API_URL)}"
#save the credentials to the account conf file.
_saveaccountconf_mutable PRIMEDNS_API_KEY "$PRIMEDNS_API_KEY"
if [ "$PRIMEDNS_API_URL" ]; then
_saveaccountconf_mutable PRIMEDNS_API_URL "$PRIMEDNS_API_URL"
fi
_info "Using PrimeDNS"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _primedns_rest POST "$fulldomain" "$txtvalue"; then
return 1
fi
_info "Added, OK"
return 0
}
#Usage: dns_primedns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_primedns_rm() {
fulldomain=$(_idn "$1")
txtvalue=$2
PRIMEDNS_API_KEY="${PRIMEDNS_API_KEY:-$(_readaccountconf_mutable PRIMEDNS_API_KEY)}"
if [ -z "$PRIMEDNS_API_KEY" ]; then
PRIMEDNS_API_KEY=""
_err "You didn't specify a PrimeDNS API key (PRIMEDNS_API_KEY) yet."
return 1
fi
PRIMEDNS_API_URL="${PRIMEDNS_API_URL:-$(_readaccountconf_mutable PRIMEDNS_API_URL)}"
_info "Using PrimeDNS"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _primedns_rest DELETE "$fulldomain" "$txtvalue"; then
return 1
fi
_info "Removed, OK"
return 0
}
#################### Private functions below ##################################
#Usage: _primedns_rest POST|DELETE _acme-challenge.www.domain.com txtvalue
#The API creates/removes the TXT record with exactly the name given, so the full
#name acme.sh passes (_acme-challenge.<domain>, or the name of a DNS alias) is
#sent as it is.
_primedns_rest() {
_method=$1
_name=$2
_value=$3
_url="${PRIMEDNS_API_URL:-$PRIMEDNS_Api_Default}/dns/acme-challenge"
#The body is built by hand, so refuse anything that would need JSON escaping.
case "$_name$_value" in
*\"* | *\\*)
_err "Refusing to send '$_name': it contains a quote or a backslash."
return 1
;;
esac
if [ -z "$_name" ] || _is_idn "$_name"; then
_err "Cannot send '$_name': it is not a plain ASCII host name. For an IDN name install idn (acme.sh converts it to punycode) or pass the punycode name."
return 1
fi
_body="{\"name\":\"$_name\",\"value\":\"$_value\"}"
_debug _body "$_body"
_primedns_retried=""
while true; do
#acme.sh sends _H1.._H5 with every later request of this process, so the API key
#header must not stay set once this call is done (it would reach other hosts).
_primedns_old_h1="$_H1"
export _H1="X-API-Key: $PRIMEDNS_API_KEY"
: >"$HTTP_HEADER"
response="$(_post "$_body" "$_url" "" "$_method" "application/json")"
_ret=$?
export _H1="$_primedns_old_h1"
_status=$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d '\r\n')
if [ "$_ret" != "0" ]; then
_err "Error calling the PrimeDNS API ($_method /dns/acme-challenge)."
return 1
fi
response="$(echo "$response" | _normalizeJson)"
_debug response "$response"
_debug _status "$_status"
#Rate limited or temporary server error: wait and try once more.
if [ -z "$_primedns_retried" ] && { [ "$_status" = "429" ] || [ "$_status" = "502" ] || [ "$_status" = "503" ] || [ "$_status" = "504" ]; }; then
_wait="$(_retryafter_seconds <"$HTTP_HEADER")"
case "$_wait" in
'' | *[!0-9]*) _wait=5 ;;
esac
[ "$_wait" -gt 60 ] && _wait=60
_info "PrimeDNS API answered HTTP $_status, retrying in $_wait seconds."
_sleep "$_wait"
_primedns_retried=1
continue
fi
break
done
if _contains "$response" '"success":true'; then
return 0
fi
if [ "$_status" = "401" ]; then
_err "The PrimeDNS API refused the key (HTTP 401): it is wrong, revoked or expired. Check PRIMEDNS_API_KEY."
elif _contains "$response" 'IP_NOT_ALLOWED'; then
_err "This PrimeDNS API key may not be used from this address (allowed_ips). Add the address to the key or run acme.sh from an allowed host."
elif _contains "$response" 'NAME_NOT_ALLOWED'; then
_err "This PrimeDNS API key may not write $_name: add the scope acme:name:$_name to the key, or the zone to its allowed zones."
elif _contains "$response" 'INSUFFICIENT_SCOPE'; then
_err "This PrimeDNS API key needs the scope acme:name:$_name (or dns:write)."
elif _contains "$response" 'DOMAIN_NOT_FOUND'; then
_err "$_name is not in a zone of the PrimeDNS account that owns this API key."
elif [ "$_status" = "429" ]; then
_err "PrimeDNS API rate limit still hit after a retry. Try again in a minute."
else
_err "PrimeDNS API error (HTTP $_status): $response"
fi
return 1
}
+6 -3
View File
@@ -106,13 +106,16 @@ dns_yc_add() {
_debug _domain "$_domain"
_debug "Getting txt records"
if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$fulldomain"; then
_err "Error: $response"
return 1
fi
_info "Adding record"
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
# The absolute name (trailing dot) also covers a record at the zone apex,
# e.g. with --domain-alias, which has no relative form: an empty name is
# rejected by the API.
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
if _contains "$response" "\"done\": *true"; then
_info "Added, OK"
return 0
@@ -167,7 +170,7 @@ dns_yc_rm() {
# upsertRecordSets.deletions removes only the given value from the rrset,
# leaving any other values at the same name (e.g. base + wildcard domain)
# intact -- no need to read the current data set and recompute it.
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"deletions\": [ { \"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
if _contains "$response" "\"done\": *true"; then
_info "Delete, OK"
return 0
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.