* Add UniFi OS Server deploy hook
Uses UniFi OS Server's local REST API (login, list, upload, activate,
remove superseded) since it stores certificates in its own Postgres
database rather than flat config files, unlike the Cloud Key/UDM
hardware covered by the existing unifi deploy hook. Tested against
real instances on both macOS and Ubuntu 26.04 (self-hosted, remote).
* Address review: portable sed/grep, scoped HTTPS_INSECURE, fingerprint matching
- Replace GNU-only \n in sed replacement with a portable literal newline
(matches dnsapi/dns_cpanel_uapi.sh, dnsapi/dns_glesys.sh); pipe the
list response through _normalizeJson first for consistent formatting.
- Use grep -F for the domain-name match instead of an unescaped BRE --
a wildcard cert name (*.example.com) broke the regex.
- Drop \W (undocumented, GNU-only) from the cookie lookup in favor of
an anchored `^Set-Cookie: *NAME=` match.
- Scope HTTPS_INSECURE=1 inside the hook (matches deploy/proxmoxve.sh,
deploy/fritzbox.sh) instead of requiring the caller to export it for
the whole acme.sh run, which would also disable verification for the
connection to the ACME CA.
- On a duplicate-certificate response, match the existing entry by
fingerprint instead of taking the first name match -- with more than
one stale entry for a domain, the wrong one could get activated.
- Check the list endpoint's response code before proceeding.
- Save username/password with the "base64" flag (matches
deploy/synology_dsm.sh) since _save_conf wraps values in unescaped
single quotes.
* Rework certificate handling: unique names per upload, drop cleanup
Testing against a real UniFi OS Server showed the server enforces name
uniqueness independently of fingerprint uniqueness, and that activation is
exclusive server-wide regardless of name/domain. A unique name per upload
avoids the name-collision path entirely (previously only handled as a
retry-of-identical-content edge case), and removes the need for the
post-hoc cleanup loop, which risked deleting the wrong entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Shorten generated certificate name to Unix epoch seconds
Real-hardware testing showed the UniFi OS Server certificate list's name
column is fixed-width and doesn't wrap, so a full human-readable timestamp
overlaps the Expires column and makes both unreadable. Epoch seconds are
still short enough to fit while remaining unique.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add scoped cleanup of old certificate entries, use _time helper
Per review: dropping cleanup entirely went further than the original bug
required, and left old entries (each holding a private key) accumulating
indefinitely. Since every upload now gets a name unique to its domain and
run, cleanup can safely target only entries whose name starts with that
domain -- entries this hook itself created -- excluding the one just
activated. Also swaps date +%s for the core _time helper, and rewrote the
design comments to make them clearer and match the current behavior
instead of the pre-redesign one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The zone lookup walked the challenge name from the right and ended up
asking netcup for the full "_acme-challenge.<domain>" as a zone name.
That can never be a zone, so netcup answered 4013 "Validation Error",
which replaced the real 5028 "The zone <domain> could not be found" as
the error shown to the user.
Stop one label short of the full name, and fail explicitly when no zone
matched, reporting the last API response plus what to check. Before, a
run where every candidate returned 5028 fell through to logout and
returned success.
socat binds a single family unless told which one: up to 1.7.x the
default IP version for TCP-LISTEN is 4, and 1.8.0 made it "no
preference", which resolves to whatever getaddrinfo and bindv6only
happen to give. So an order carrying both an IPv4 and an IPv6
identifier could never pass both http-01 challenges.
Bind one socket per family instead, with ipv6only on the IPv6 one so
the two do not collide. IPv4-mapped IPv6 addresses are not a portable
alternative, OpenBSD does not support them at all. The IPv6 listener
is best effort, a host without IPv6 still gets the IPv4 one. The
python fallback does the same. --listen-v4 and --listen-v6 keep
forcing a single family, and passing both now means both.
Le_Listen_V4 and Le_Listen_V6 were mutually exclusive in the domain
conf, which silently dropped one of them on renewal, and
_starttlsserver let -4 win when both were set.
Fixes#7185
* feat: added nexdns dnsapi
Adds a DNS-01 hook for NexDNS, an authoritative DNS service with a REST API.
dns_nexdns_add walks the label list to find the zone that owns the challenge
name and creates the TXT record in it. dns_nexdns_rm lists the TXT records at
that name, picks the one carrying exactly this challenge value and deletes it
by id, so a wildcard and its base domain do not remove each other's record.
A 429 is waited out and the request retried, in the shape dns_hetznercloud.sh
and dns_bunny.sh already use.
* dns_nexdns: cap the rate-limit wait, judge success by status, add the tracking issue
_get_root_by_getList() matched the candidate suffix as an unanchored
substring of the whole domains.getList response and never looked at the
IsOurDNS attribute. A domain parked on Namecheap's webhosting DNS is
listed with IsOurDNS="false", yet it was still accepted as the root zone,
so _get_root() returned success and the domains.dns.getHosts probe that
would have found the real zone never ran. Every following getHosts call
was then refused with error 2030288 "not using proper DNS servers" and
the challenge failed with "invalid tld".
Match the exact <Domain Name="..."> entry instead and require
IsOurDNS="true", so a subdomain delegated to Namecheap BasicDNS/FreeDNS
under a parent that is not on Namecheap DNS now resolves to its own zone.
Matching the entry exactly also drops the old substring/regex match, in
which the dots of a domain matched any character.
Fixes#7178
_getdeployconf assigns and exports the variable, it does not print the
value, so wrapping it in a command substitution ran it in a subshell and
always yielded an empty string. A MULTIDEPLOY_FILENAME saved by an
earlier run was therefore never restored on renewal and the hook
silently fell back to multideploy.yml. Call it the same way every other
deploy hook does.
Also treat a MULTIDEPLOY_FILENAME starting with '/' as an absolute path
instead of always resolving it under DOMAIN_PATH, so one deploy file can
live outside the certificate directory and be shared by all domains.
Names without a leading '/' keep resolving under DOMAIN_PATH as before.
_temp_admin_cleanup ran before _logout, so the logout request carried
the session id of an account synouser had already removed and DSM kept
the orphaned entry in Connected Users. Swap the order in both terminal
branches, and add the missing _logout to the two post-login error paths
(CRT list failure, certificate not found without SYNO_CREATE).
_logout overwrites the global $response, so the upload-failure branch
prints its error message before calling it.
Reported by @Bertl75 in #7174
The decision to resume a pending order is keyed on Le_Vlist, but the
decision to keep Le_OrderFinalize/Le_LinkOrder was keyed on the webroot
being exactly "dns". Any other webroot with a saved Le_Vlist skipped
newOrder and then finalized against an empty URL.
Key both on Le_Vlist, and always clear Le_LinkCert, which is per-run
state that is never read back from the saved domain conf.
Fixes#7177
_cyon_delete_txt relied on `printf "%b"` to convert a sed-injected literal
`\n` into a real newline, but `%b` also processes the `\"` escapes that the
JSON response is full of. glibc/bash/dash keep the backslash of such an
undefined escape, FreeBSD's printf (sh builtin and /usr/bin/printf alike)
drops it -- so `data-hash=\"..\"` became `data-hash=".."`, the extraction
regex matched nothing, _dns_entries stayed empty and no TXT record was ever
deleted.
Drop the newline injection and use _egrep_o, which already yields one match
per line, then parse each line with sed.
Also feed the read loop a newline-terminated list: `printf "%s"` left the
last line unterminated, so `read` returned non-zero at EOF and the loop
skipped the final entry on every platform.
Verified identical output on FreeBSD 14.3, Linux/bash and Linux/dash.
Fixes#7169
For -d '*.example.com' the printed record name kept the literal '*' label
(_validation-persist.*.example.com). The CA never queries that name, so
issuance fails with "No TXT record found for DNS-PERSIST-01 challenge".
Per draft-ietf-acme-dns-persist-01 sec 4 and 10.2 the record is published at
the base domain's Validation Domain Name; the wildcard scope comes from
'policy=wildcard' in the record value (sec 5.1), not from a '*' label in the
record name. Strip the leading "*." in a new _dns_persist_txt_name helper,
and imply --dns-persist-wildcard for a wildcard -d, since without
policy=wildcard the printed record can never authorize the wildcard.
Fixes#7168
* Refactor dns_freemyip.sh for clarity and compatibility
Updated dns_freemyip.sh for better readability and compatibility with ASUSWRT-Merlin. Improved error handling and response logging.
* Update author information in dns_freemyip.sh
* replace both loops with POSIX shell counters
replace both loops with POSIX shell counters
* Typo
Typo
* Fix error message for freemyip API request failure
Remove existing token leak. Not my regression.
* Refactor retry logic and improve error handling
* Remove unnecessary blank lines in dns_freemyip.sh
* Clean up dns_freemyip.sh by removing blank lines
Removed unnecessary blank lines in the script to improve readability.
* dns_yc: restore YC_SA_Key_File in dns_yc_rm before signing the JWT
dns_yc_rm() never rebuilt YC_SA_Key_File from YC_SA_Key_File_PEM_b64 /
YC_SA_Key_File_Path like dns_yc_add() does. Per the DNS API dev guide,
add()/rm() run in separate subshells, so rm() must repeat add()'s setup
steps rather than rely on variables set during add().
Without it, when _yc_login() needs a fresh JWT during removal (the IAM
token from the add phase isn't available), it signs with an empty/unset
key path, and openssl fails with "Unknown key file format". The
resulting auth failure then surfaces misleadingly as "invalid domain" in
_get_root, and the TXT record is never deleted.
Verified against a real Yandex Cloud account/zone with --staging: before
the fix, removal failed with the same errors reported in the issue;
after adding the missing key-restoration block, add + remove both
succeed and the TXT record is actually deleted.
* dns_yc: preserve other TXT values when removing one at the same name
dns_yc_rm previously sent the full current data array (all existing
TXT values at the name) to the deletions API, wiping out the whole
rrset instead of only the value being removed. This breaks wildcard +
base domain issuance, where both share the same _acme-challenge name
with two different values: removing the first one deleted both,
leaving nothing for the second removal to find.
* dns_yc: read persisted config from domain conf before account conf
YC_Zone_ID, YC_Folder_ID, YC_SA_ID, YC_SA_Key_ID (zone-ID mode) and
YC_SA_Key_File_PEM_b64/Path were always saved via _savedomainconf
(domain.conf), but only ever read back via _readaccountconf_mutable
(account.conf). Once the env vars were unset, none of these could be
recovered from the saved config, so dns_yc_add/dns_yc_rm failed with
"You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
even though the values had been persisted correctly on the prior run.
* dns_yc: replace grep -Fxv/sed with a portable loop in dns_yc_rm
Solaris's /usr/bin/grep supports neither -F nor -x, so
_remaining_txtvalue was always empty there and the preserve-other-
values logic silently fell back to deleting the whole rrset (with a
grep usage error on stderr on every rm). The sed trailing-comma strip
had a matching issue on Solaris, whose sed drops an unterminated last
line. CI didn't catch this because the fallback path also returns
"done: true". Use a plain for-loop with word splitting instead.
* dns_yc: use upsertRecordSets.deletions to remove a single TXT value
updateRecordSets has no "merges" field (only deletions/additions), so
the previous preserve-other-values logic silently did nothing -- the
TXT record was never actually removed, a regression from before that
change (which at least deleted the whole rrset). CI didn't catch it
because _clearupdns runs dns_yc_rm in a subshell and ignores its exit
code.
upsertRecordSets.deletions removes only the specified value from the
rrset directly, so the getRecordSet read and the remaining-value
recomputation are no longer needed at all.
Verified against a real zone (base + wildcard domain sharing one
_acme-challenge name): adding both values then removing one leaves
the other in place, and removing the second cleans up fully.
* dns_yc: don't delete the user's own key file in YC_SA_Key_File_Path mode
_yc_login unconditionally rm'd $YC_SA_Key_File after signing. That's
fine for the PEM_b64 path, where it's a decoded temp file, but in
YC_SA_Key_File_Path mode it's the user's own persistent key file --
the first successful login permanently deleted it, so every
subsequent dns_yc_rm/renewal hit "Unknown key file format" (the exact
symptom this PR is about, just from a different cause). Track whether
the key file is our own temp copy and only delete it in that case.
Verified with a stubbed _yc_login: a temp-mode key gets removed after
login, a path-mode key survives.
* dns_yc: clear both domain and account conf on invalid config
The failure branch in dns_yc_add only ever called _clearaccountconf,
but YC_Zone_ID/YC_Folder_ID/YC_SA_Key_File_PEM_b64/Path are persisted
via _savedomainconf, and YC_SA_ID/YC_SA_Key_ID may have been saved via
_saveaccountconf_mutable (Folder_ID mode, which stores under a
SAVED_ prefix read back by _readaccountconf_mutable). Clearing only
one store left stale values behind in whichever one wasn't touched.
Verified by seeding both domain.conf and account.conf with leftover
values, then triggering this branch and confirming both config files
end up empty.
Solaris /usr/bin/grep has no -A ("illegal option -- A"), so _getAKI
printed an error to stderr on every cron renewal and returned empty.
The empty AKI silently corrupts the RFC 9773 ARI certID, so ARI is
never available and renewal falls back to the fixed schedule.
Split the pipeline into a testable stdin filter _extractAKI and select
the value line with a portable sed range instead.
Same fix for the two hooks that still used grep -A: dns_world4you.sh
(also replaces the GNU-only "\s" in the same expression) and
deploy/keyhelp.sh (the -A 2 window could truncate the div range that
follows it, so it is just dropped).
https://github.com/acmesh-official/acme.sh/issues/7159
OmniOS native egrep -o infinite-loops emitting empty lines when the
pattern can match the empty string, so `_egrep_o "[^:]*$"` never lets
the pipeline finish and dns_yc hangs until the CI timeout. Require at
least one character instead. `+` is not usable because the sed fallback
in _egrep_o parses BRE.
* feat: add Shelly Gen3+ deploy hook with RFC 7616 HTTP Digest auth
Adds deploy/shelly.sh for deploying Let's Encrypt HTTPS server certificates
to Shelly Gen3+ devices (Gen4 tested) via JSON-RPC over HTTP.
- RFC 7616 SHA-256 HTTP Digest authentication (Authorization header)
- Uploads fullchain.pem and private key via Shelly.PutHTTPServerCert / PutHTTPServerKey
- Auto-reboot support (SHELLY_REBOOT to disable)
- Auth auto-detection: no password = no auth, password = Digest
- Nonce counter (nc) increments per request per RFC 7616
- Tested against Shelly 2PM Gen4 (firmware 2.0.0)
Also adds deploy/test_shelly.sh for self-testing the hook logic without
a real device (mocked _post).
* fix: address review feedback on shelly deploy hook
- Fix _secure_debug calls to use two arguments (label + value)
- Remove bash-only $RANDOM cnonce fallback; openssl always available
- Parse $HTTP_HEADER directly instead of raw curl re-request
- Detect auth via HTTP 401 status line, not empty response body
- Route reboot through _shelly_rpc to rebuild auth header with correct nc
- Remove export HTTPS_INSECURE=1 (no-op for http://, leaks to other hooks)
- Clear _H1 before returning from shelly_deploy
- Prefix all helper variables with _shelly_ to avoid namespace collisions
- Delete deploy/test_shelly.sh (deploy/ files become hook names)
- Fix missing trailing newline
* fix: validate shelly JSON-RPC responses are valid JSON
Non-JSON responses like HTTP 429 'Too Many Requests' would pass
the empty-response and '"error"' checks and be reported as success.
Now reject any response that doesn't start with '{' and contain '"id"'.
* fix: add 1s delay between shelly cert/key clear and upload calls
The Shelly device has a race condition where uploading data immediately
after clearing the existing cert/key returns -103 'Missing required
argument data!'. A 1-second delay fixes this.
* fix: remove clear-before-upload in shelly deploy hook
Shelly auto-removes all three TLS files (cert, key, CA bundle) when any
single one is cleared. The old sequence clear-cert → upload-cert →
clear-key → upload-key resulted in the key clear wiping the newly
uploaded cert, leaving only the key at boot time. The mbedtls
pk_check_pair then silently skipped the HTTPS listener.
Fix: just upload directly (overwrite in place). No clearing needed.
* Fix ShellCheck SC2090 and shfmt in shelly deploy hook
SC2090: false positive on export _H1 (used quoted in _post)
shfmt: no space after "<" in _json_encode redirects
* moved two lines to cover the whole if block
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: cysimons <cysimons@cisco.com>
The success check only grepped "message" from the response body, but
PVE/PBS auth failures return HTTP 401 with an empty body, so wrong or
unauthorized API tokens were reported as "Certificate successfully
deployed". Also _retval captured the exit code of the message pipeline
instead of _post. Check the HTTP status line from $HTTP_HEADER and
capture _post's exit code directly.
fix https://github.com/acmesh-official/acme.sh/issues/7141
When ACME_PACKAGED is set (e.g. exported by a distro package wrapper):
- --install does not copy the script or the hooks into LE_WORKING_DIR;
the cron job and the shell alias point to the packaged script instead
- --upgrade, --install-online and the cron AUTO_UPGRADE path refuse and
point to the system package manager
- --uninstall does not remove the packaged files
https://github.com/acmesh-official/acme.sh/issues/7135
dnsapi/deploy: remove POSIX character classes from sed/grep patterns
Solaris /usr/bin/sed and /usr/bin/grep parse [[:space:]] etc. as a
literal bracket set and silently mis-match. Replace with [ ]* for
JSON matching, a printf-tab bracket for user-input trimming, and
[0-9] for digits; also drop GNU-only sed -r/-E in rage4, selfhost
and selectel, and reuse _strip_blank_lines in byteplus_alb.
A negative --days is anchored to the expiry, so it composes with a
relative --valid-to: "--valid-to +30d --days -7" renews 7 days before
the expiry instead of the hardcoded 1 day. A positive --days and any
--days with a fixed-date --valid-to are still rejected.
https://github.com/acmesh-official/acme.sh/issues/6570
The OCI DNS plugin cached the tenancy, user, region and signing key into
acme.sh's account.conf at issuance and then, on subsequent runs, read those
cached values before consulting ~/.oci/config. A value cached at issuance
therefore permanently shadowed the config file, so editing ~/.oci/config
afterwards (most visibly rotating the API signing key) had no effect and
renewals kept using stale credentials, failing authentication.
Resolve each field in the order: explicit environment variable, then
~/.oci/config when it exists, then the cached account.conf value as a
fallback for env-only installs that have no config file. The signing key
likewise prefers the key_file resolved from the environment or ~/.oci/config
over any cached key.
Signed-off-by: Avi Miller <me@dje.li>
* Deploy certificate to FortiGate firewall using API
* Refactor FortiGate deployment functions
Prefix private functions and working variables and use a timestamped certificate name.
* Replace grep -o for POSIX compatibility
_egrep_o falls back to a BRE sed expression on shells without egrep -o
(Solaris, DD-WRT busybox). A bare "\{" there is a BRE interval operator
and aborts ("sed: command garbled" / "Invalid content of \{\}"), so the
challenge-status-invalid path extracted an empty error object and the
CA's failure reason was lost. Replace the escaped braces with "[{]"/"[}]"
bracket expressions, unambiguous literal braces in both BRE and ERE, at
all four call sites (challenge type/error extraction and profiles).
When re-registering an already-bound EAB account (HTTP 400 "not
awaiting external account binding"), the else branch restored
ACCOUNT_URL from ca.conf but the following unconditional
`export ACCOUNT_URL="$_accUri"` clobbered it with an empty _accUri
(never set on that path), so later signed requests failed with
"A Key ID MUST be specified" / "account URL is empty". Assign the
restored value to _accUri so the shared export uses it.
https://github.com/acmesh-official/acme.sh/issues/3382
The sed in _get_zone passed the raw JSON through when the response
contained no [{"fqdn":", so an API error turned the zone into "{" and
the failure only surfaced later as POST /2/zones/{/records
"method_not_found". Log the response at debug2, error out on
non-success results, and parse fqdn position-independently.
https://github.com/acmesh-official/acme.sh/issues/6851
The "no crontab" whitelist used multiple -e patterns, but Solaris
/usr/bin/grep honors only a single -e, so a fresh install was
refused there. Use one plain grep per message pattern, which every
grep implementation supports (caught by le_test_installcronjob_no_wipe
on the Solaris CI).
The zone cannot be derived from the record name when the Knot server
is only authoritative for a delegated subdomain; let the user name it
explicitly, like NSUPDATE_ZONE.
fixes https://github.com/acmesh-official/acme.sh/issues/2881
The legacy plain _saveaccountconf copy in account.conf is sourced at
startup and silently overrides a newly exported DNSimple_OAUTH_TOKEN,
so rotated tokens never took effect.
fixes https://github.com/acmesh-official/acme.sh/issues/3422
Piping a failed 'crontab -l' straight back into 'crontab -' replaced
the whole crontab with just the acme.sh entry when the listing failed
while jobs existed (seen on cPanel/CloudLinux jailshell). Capture the
listing first and refuse to write unless the failure is the normal
"no crontab for user" case.
https://github.com/acmesh-official/acme.sh/issues/3079
* add Baidu Cloud CDN deploy hook
Code generated by GitHub Copilot with Claude Sonnet 4.6. Tested with local environment by human.
* inline functions
Code generated by OpenAI Codex with GPT-5.5 Sol. Tested with local environment by human.
* Add files via upload
* New Banner
Updated README to include responsive images for dark and light modes.
* Add files via upload
Remove usage of jq and curl
should be compliant with acme.sh api dev guide
* Add files via upload
* Add files via upload
change CR LF to LF
* Add files via upload
missing CR
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
correct auth
* Add files via upload
better manage group_id
* Add files via upload
manage wrong / missing domain
* Add files via upload
strip domain to manage subdomain requests
* Add files via upload
fix wrong id selection in get root
* Add files via upload
fix parsing of IDs
* Add files via upload
correct get_root to better handle unexisting domaines (acmetest)
* Add files via upload
correct token incorrect in auth
* Add files via upload
manage case web api reply is empty
* Add files via upload
try to resolve error when adding unexisting subdmain txt entry
* Add files via upload
correct domain parsing
* Add files via upload
revert changes when adding record (name)
change rm to better handle complex urls
* Add files via upload
correct rm function to better manage records deletion
* Add files via upload
ensure auth variable arent lost during calls
* Add files via upload
try to keep autj variables accross executions
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
fix stripping subdomains
* Add files via upload
* Add files via upload
keep auth token instead of requesting it each time
* Add files via upload
debug
* Add files via upload
* Add files via upload
* Add files via upload
better manage record deletion to avoid orphans
added some debug and checks
* Add files via upload
paginated api support for rm function
* Add files via upload
* Add files via upload
delay to manage api 404
* Add files via upload
enhance parsing of records in rm
* Add files via upload
fix incorrect record detection in rm
* Add files via upload
better manage filters on api to limit errors
* Add files via upload
try to handle 404 when requesting API too frequently
* Add files via upload
* Add files via upload
sleep during auth
* Add files via upload
manage 404 errors in get_root
* Add files via upload
correct variable overide
translate comments
prefix all variables
* Add files via upload
correct variable
* Add files via upload
typo
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
correct variable naming (_comlaude prefix missing)
correct rm return code when non existing record
typo
* Add files via upload
log an info instead of an error if no dns record found in RM function.
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: Matthiasvpfr <matthias.dupont@vetpartners.fr>
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
The hook is sourced by acme.sh, so the bash shebang never takes
effect: under dash, `[ x == y ]` fails with "unexpected operator",
the 403 branch never triggers and 2FA-OTP login is skipped.
Replace `==` with `=` and use the standard sh shebang.
With --signcsr the private key never exists in the cert home, so every
renewal printed ".../domain.key: No such file or directory" from the
shell redirection. Skip the key read in that case; the install_ssl call
already ran with an empty key there and cPanel keeps the installed one.
https://github.com/acmesh-official/acme.sh/issues/6228
Debug logs occasionally contain private keys or tokens (issue 6267);
the code-side leak in the haproxy hook was fixed by #6268, this adds
the missing warning to the auto-comment that asks for logs.
The default schedule was a fixed CertCreateTime + RenewalDays - 1 day,
which passes notAfter entirely for short-lived certs (internal CAs
today, the CA/B SC-081 47-day maximum later) and leaves an expired cert
in place for weeks. Extract the arithmetic into _calc_next_renew_time
and cap it at one day before expiry (one hour for lifetimes of 24h or
less, mirroring --valid-to scheduling). CAs with ARI are unaffected --
the ARI window still overrides afterwards.
https://github.com/acmesh-official/acme.sh/issues/6305
The wiki has documented "21. Set notification for customscript" since
2022 but the implementation (#4193) was never merged, so following the
wiki failed with "Cannot find the hook file". Same interface as
documented: the script gets subject, content and status code as three
arguments. Unlike #4193, the target script is invoked directly instead
of through eval -- the subject/content contain domain names and CA
messages, and eval would allow command injection through them.
https://github.com/acmesh-official/acme.sh/issues/6377
The unfiltered GET /zones lists every zone on the server; with large
installations (100k zones) root-zone detection took minutes per domain.
Probe each walk-up candidate with ?zone=<name> instead (exact match per
the PowerDNS API docs); servers that ignore the parameter return the
full list, which the existing check still handles.
https://github.com/acmesh-official/acme.sh/issues/6382
Missing socat only matters for standalone mode; the text even says the
warning can be ignored. Printing it to stderr made every --upgrade in a
cron noisy for DNS-only users who redirect stdout.
https://github.com/acmesh-official/acme.sh/issues/6525
The dns/deploy hooks export _H1.._H5 in the main process, and the
notify hooks run in a subshell that inherits them. A hook that does
not overwrite every slot (ntfy without NTFY_TOKEN, slack, telegram,
etc.) sent the stale headers with its request, leaking another
service's Authorization credentials to the notify endpoint.
https://github.com/acmesh-official/acme.sh/issues/6801
With both domain.tld and sub.domain.tld zones on the account, the first
endswith hit could pick the parent zone while cPanel stores the record
in the most specific one, so the cleanup never found the record and
left an orphaned _acme-challenge TXT entry. Pick the longest matching
zone with an exact literal suffix match (_endswith treats the needle as
a regex, letting xdomain.tld wrongly match zone domain.tld).
https://github.com/acmesh-official/acme.sh/issues/6807
* [Microwavenby--dns_hostinger] Adding initial dns support for Hostinger.com
* [Microwavenby--dns_hostinger] Creating a commit now that workflows are enabled
* [Microwavenby--dns_hostinger] Correcting shellcheck. Why is this not automatic?
* [Microwavenby-dns-hostinger] Responding to comments from Neil
* [dns-hostinger] SHfmt and Shellcheck
* [dns-hostinger] Writing non-greedy-ish regexes. correcting copypasta
Getting client_id failed due to incorrect extraction!
At least in version 3.2.7p1 and probably later the plugin is not working any more properly. The result of ```curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"``` is something like this ```Result of _ISPC_ClientGetID: '[Tue Jan 23 11:44:57 CET 2024] Retrying post
{"code":"ok","message":"","response":3}[Tue Jan 23 11:44:57 CET 2024] _hcode 0'```. The parsing code does not work properly and leaves a non numeric value such as ```Client ID: '3[Tue Jan 23 11'```.
The Njalla API returns record ids as JSON strings now; the numeric-only
pattern matched nothing, so the removal never found the record id.
Match both quoted and bare ids.
from https://github.com/acmesh-official/acme.sh/pull/5121
grep -o '[0-9]*' can match the empty string; GNU grep skips empty
matches but BSD greps handle them differently, breaking the 2FA
login flow on OpenBSD. Force a non-empty match at all three sites.
from https://github.com/acmesh-official/acme.sh/pull/6725
A trailing dot in --domain-alias/--challenge-alias was passed through
to the dnsapi hook verbatim. Providers with exact-match record-name
lookups (e.g. Cloudflare's name= filter) then never find the record,
so rm never deletes it and relic TXT records accumulate on every issue.
Stripping in issue() also fixes certs with a dotted alias already
saved in domain.conf.
fix https://github.com/acmesh-official/acme.sh/issues/4636
LC_ALL in the environment overrides both LC_TIME and LANG, so LANG=C
alone still produced localized day/month names on non-English systems
and DNS Made Easy rejected the request date header. An LC_ALL=C
command prefix beats every locale variable (same pattern as
dns_oci.sh).
Fixes#4272. Closes#4271. Thanks to @Nickinthebox.
Mirrors _clearaccountconf_mutable: clears the SAVED_ prefixed key and
the legacy unprefixed key. Replaces the local copy in synology_dsm.sh
and the direct _cleardomainconf call in multideploy.sh.
Closes#4722. Thanks to @sg1888.
Postfix with smtpd_forbid_bare_newline (default hardening since 3.9,
after SMTP smuggling) rejects the message with
"521 5.5.2 Error: bare <LF> received". RFC 5321 requires CRLF.
The python sender is unaffected (smtplib already emits CRLF).
fix https://github.com/acmesh-official/acme.sh/issues/7104
OpenBSD grep treats \| in a BRE as a literal | character, so
_freedns_domain_id never matched any row and every domain lookup
failed with "Domain not found". Switch to ERE with -E, keeping the
parens escaped so the (.*) suffix branch still requires literal
parentheses and does not widen the match (e.g. searching example.com
must not match example.company).
Reported-by: @katiekloss @boretom
Ref: https://github.com/acmesh-official/acme.sh/issues/2305
* New Banner
Updated README to include responsive images for dark and light modes.
* acme-sh-creoline-as-dns-provider
* acme-sh-creoline-as-dns-provider - Review changes implemented according code review
* acme-sh-creoline-as-dns-provider - Review changes implemented according second code review, minding --cron
* acme-sh-creoline-as-dns-provider - Remove debug code
* acme-sh-creoline-as-dns-provider - shfmt formatting according Code of conduct
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
Co-authored-by: Steven Kauschke <s.kauschke@creoline.com>
* add wiki
* feat: add support for account key rollover
* Place --update-account-key next to --update-account
* fix shfmt
* fix shfmt
* fix shfmt
* Fix from review
* fix shfmt
* fix from review
* fix review
---------
Co-authored-by: neil <gitpc@neilpang.com>
ssh_deploy() ignored the result of _ssh_deploy and always returned
success, so a failed transfer to one (or all) of the servers in
DEPLOY_SSH_SERVER was silently swallowed. Track the return code across
the loop and return non-zero if any server failed, letting the caller
handle notification.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Added LaoDC API Module
* Cleaned up debug and info
revised get subdomain to filter by TXT records.
* Added commet to _get_root
* Removed PATCH logic of updating acme records as this doesn't work for wildcard DNS.
Revised rm() function to do explicit record matching.
* Revised _get_root() to handle different scenarios.
Fixed _laodc_api() function to check if query failed to run.
added basic json sanitation to handle \ and " in $value
unset _H2 _H3 after call as per request from copilot.
* fixed indentation of case statement block
* fixed condition checking.
_get_root should start at 1 so full fqdn can be tested
$? was being reference after export command failing response checks
removed export txtvalue
fixed docs link and issues link
* Verify key for both add and rm
* fixed dns alias condition check
validate key returns 1 if failed.
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: LaoDC <git@laodc.com>
ACCOUNT_EMAIL / --email now accepts a comma- or space-separated list
and registers all of them as ACME contact entries. The ZeroSSL EAB
endpoint takes a single address, so the first one is used there.
Committing after a failed import leaves a mismatched cert/key pair on
the firewall (PAN-OS does not validate the pair at commit time), which
can lock the admin out of the https management interface.
aws_ses_send calls `_use_container_role || _use_instance_role` when no
static AWS keys are set, but those functions were never defined -- only
_use_metadata was -- so role-based auth silently fell through to the
"no api key" error. Add both, using the current IMDSv2-capable versions
from dns_aws.sh, and set the IMDSv2 token header in _use_metadata so the
credential fetch works on IMDSv2-only instances.
Closes#4742
_get_root matched a registered domain anywhere as a substring of the
challenge domain, so with both "test.com.ru" and "subtest.com.ru" in the
account, issuing for subtest.com.ru wrongly picked test.com.ru as the
root (it is a substring of "sub-test.com.ru"). Anchor the match to a '.'
boundary so a shorter domain no longer matches a longer subdomain label.
Fixes the issue reported in #5036 (thanks @koledas)
Closes#5036
The domain list is paginated at 1000 entries per page and only the
first page was fetched, so accounts with more than 1000 domains never
found the root zone.
fix https://github.com/acmesh-official/acme.sh/issues/5051
An undecodable key (e.g. broken LibreSSL base64 -d -A) used to produce
the cryptic "Usage: _hmac hashalg secret [outputhex]" and an empty EAB
signature that the CA rejects with 403.
https://github.com/acmesh-official/acme.sh/issues/4082
The docker deploy hook copied the key file preserving the source mode
(root:root 0600), so a non-root container service (uid >= 1000) could not
read it. Add DEPLOY_DOCKER_CONTAINER_KEY_MODE and
DEPLOY_DOCKER_CONTAINER_KEY_OWNER, applied via chmod/chown inside the
container after the key is copied and before the reload command.
Closes#5333
Runs le_test_nginx from acmetest against Pebble: nginx listens on
Pebble's HTTP-01 validation port with an aaPanel/BT style
"location ^~ /" reverse proxy block, the regression case of #6125.
* Add dns_muumuu: muumuu-domain.com DNS API
* Fix: remove local keyword for POSIX sh compatibility
* Fix: lowercase fulldomain for API compatibility
* Style: use echo instead of printf for lower_case (consistent with other plugins)
* Fix: prefix rest vars, clear _H4/_H5, guard record_id, update Issues URL
- replace deprecated set-output with GITHUB_OUTPUT
- untap aws/tap before brew install to silence tap trust warning
- inject safe.directory=* for cygwin git so the checkout post step
no longer fails with dubious ownership (exit 128)
dash's echo has no -e flag and sends a literal "-e " prefix to the
socket, so haproxy rejects the command and the hot update always fails
on Debian/Ubuntu (/bin/sh = dash). Also accept "Transaction updated",
which haproxy replies when an uncommitted transaction already exists.
fix https://github.com/acmesh-official/acme.sh/issues/6165
* Add WEDOS WAPI DNS API (dns_wedos)
* dns_wedos: fix response parsing on systems without egrep -o
* dns_wedos: report WAPI auth errors, UTC fallback for hosts ignoring TZ
"openssl req -noout -in" aborts when the default config file is missing;
reading a CSR needs no config, so pass -config /dev/null explicitly.
Stock NetBSD does not install /etc/openssl/openssl.cnf, so --signcsr
never worked there.
With a misconfigured $HOME / CERT_HOME the glob over "$CERT_HOME"/*.*
matches nothing, so renewAll silently does nothing and returns success --
--renew-all / --cron appears to work while renewing no certificates.
Check that CERT_HOME is a directory up front and return 1 with a clear
error instead.
Closes#4508
Core-Networks' API rejects Unicode domain names with "invalid domain";
it requires punycode. dns_cn_add / dns_cn_rm passed the raw challenge
domain straight through, so IDN certs failed at the TXT add step
(issue #4804). Run fulldomain through _idn() in both functions. For
ASCII/punycode input _idn() is a pass-through, so non-IDN domains are
unaffected.
Fixes#4804
The unquoted splitting let a "*" segment expand against files in the
current directory, so "*.*.*.*" could pass as a valid IPv4 address
(issue 4971). The old code also accepted "", "1.2.3", "1.2.3.4.5",
"1..2.3" and bare numbers. Split with IFS under set -f, require 4
octets, and validate each as a 1-3 digit number <= 255.
Based on https://github.com/acmesh-official/acme.sh/pull/4974
fix https://github.com/acmesh-official/acme.sh/issues/4971
_get_root matched the candidate zone with _contains (grep), which treats
the domain as a regex. For "-d g.<zone>" the candidate "g.<zone>" matched
"<string>...<zone>" because '.' matches the '>' after "string" and the 'g'
comes from the "<string>" tag, so "g.<zone>" was wrongly taken as the root
zone (sub=_acme-challenge instead of _acme-challenge.g). Anchor the match
to <string>$h</string> and escape dots so the zone is compared literally.
Fixes#5129
RFC 8555 sec 7.3.6 requires 401 (Unauthorized) when a request is
signed by a deactivated account, which ZeroSSL follows, while
Boulder (Let's Encrypt) historically returns 403. Check both codes
in _regAccount and deactivateaccount.
fix https://github.com/acmesh-official/acme.sh/issues/5138
_contains matches with grep regex, so the '*' in "DNS:*.example.com," never
matched and the subject was appended to the identifiers a second time.
Escape the wildcard before the check, the same way the sed removal already does.
fix https://github.com/acmesh-official/acme.sh/issues/5251
* ARI - Add support for Mass Revocation
* feat: update ARI each time NextRenewTime is not within the suggestedWindow
* Remove _ari_should_renew and add condition on Le_NextRenewTime
* Add support for ARI explanationURL
* Fix debug variable _d_ari
* New Banner
Updated README to include responsive images for dark and light modes.
* multiple fix
* fix
* fix shfmt
* Reset README
---------
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
_normalizeJson reads its JSON from stdin (sed | sed | tr) and ignores
any positional argument. dns_czechia_add() called it as
`_normalizeJson "$_res"`, so the response was discarded and the inner
sed blocked reading from stdin.
When issuing for a single domain, or for a record that already exists,
the "already exists" branch returns early and never reaches this call,
which is why the bug stayed hidden. With multiple domains, the first
record often short-circuits on "already exists" while the next,
freshly-added record reaches the broken call and hangs on interactive
runs (or consumes unrelated stdin non-interactively).
Pipe the response into _normalizeJson via stdin, matching
dns_czechia_rm() and every other dnsapi plugin.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add volcengine dns api
* fix(volcengine): address review findings and fix record matching
Code review fixes:
- fix format string usage in signature computation (use printf %b / %s)
- clear _H1.._H5 header state at start of request to avoid leaking
conditionally-set headers into subsequent requests
- check ListZones return status in _get_root
- document Volcengine_SESSION_TOKEN option and fix duplicate "and" typo
- fix Docs and Issues sections
- remove and update some code comments
Functional fixes:
- stop matching ListRecords results by FQDN string: Volcengine lowercases
the Host/FQDN in responses, so a case-sensitive compare against
$fulldomain failed for mixed-case names, making rm silently skip
deletion and add lose idempotency. ListRecords is already filtered by
ZID+Host+Value+SearchMode:exact, so just extract RecordID from the
result instead.
- reset _record_id at the start of add/rm to avoid stale state leaking
across calls within the same process
- tag created records with Remark "acme.sh" for easier identification
- adjust debug levels: hide Authorization header behind _debug2, surface
response at _debug
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: wenxuan70 <t736660416@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* ARI - Run cron job more frequently
With ACME Renewal Info (RFC9773 §4.3), fetching renewal window should be more frequent, e.g. in case of revocation incident.
"For instance, a server that needs to revoke certificates within 24 hours of notification of a problem might choose to reserve twelve hours for investigation, six hours for clients to fetch updated RenewalInfo objects, and six hours for clients to perform a renewal."
More flexible option is to run the cron job every hour and only refresh ARI when the last one + Retry-After header is in the past.
* Fix cron job schedule for certificate renewal
* Fix random_hour syntax in cron job installation
* Update Windows task scheduler to run more frequently
Add support for randomized hour and update frequency
Ref:
* [/mo](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks-create#to-schedule-a-task-to-run-every-n-hours)
* [/SC HOURLY](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks-create#parameters)
* Add padding for StartTime (/ST) in SCHTASKS.exe
* New Banner
Updated README to include responsive images for dark and light modes.
* rebase
* Reset README
---------
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
* Add Glesys dnsapi provider
* Fix typo in error message for dns_glesys_add
* Use API to get record id in Glesys provider
* Use listrecords API endpoint to find root domain
* Remove record id parsin from add function
* fix(acme): prefer new Baidu DNS API with legacy BCD fallback
Keep the existing BCD implementation and add fallback support for the newer Baidu DNS record API. Prefer the new API by default, then fall back to the legacy BCD API to reduce compatibility risk.
* fix(dns_baidu): route through _get/_post + restore legacy BCD auth headers
Per review: _baidu_dns_call now uses _get/_post with _H1.._H5 (no raw curl, no __HTTP_STATUS__ parsing); _baidu_bcd_post restores _H1.._H5 so the legacy BCD path sends the Authorization signature again (fixes 401).
---------
Co-authored-by: neil <github@neilpang.com>
* dns_inwx: fix IDN zone detection without python dependency
INWX returns zone names in Unicode form (e.g. lünenschloß.de) even when
the domain was registered as an IDN. When acme.sh passes the SAN in
punycode (xn--lnenschlo-o1a42a.de), _contains never matches and
_get_root falls through to the TLD, placing the TXT record in the wrong
zone.
Previous fix used python3 which is not available in all environments
(BusyBox, BSD, minimal containers). Replace with _idn()-based approach:
extract <string> values from the nameserver.list XML response, encode
each via _idn(), and compare to $h. When a match is found, use the
original Unicode zone name for createRecord.
Fixes#7038
* dns_inwx: fix shebang, use _egrep_o, shfmt cleanup
- Revert shebang to #!/usr/bin/env sh (POSIX sh, fixes ShellCheck)
- Replace grep -o with _egrep_o for portability
- shfmt -i 2: drop backslash continuation after pipe, fix indentation
Requested by @neilpang
* fix: drop closing </string> from _egrep_o pattern to avoid sed delimiter collision
---------
Co-authored-by: bluenenschloss <bernd.luenenschloss@7p-group.com>
The add/rm success check never rejected anything: for any non-empty API
response it always reported "Record added"/"Record deleted" and returned
0, so the _err branch was dead code. A valid key looked fine only because
the API call genuinely created the record; an invalid key returning
{"result":"error"} produced the same "Record added" output even though
nothing was created.
Root cause, in:
if [ -n "$response" ]; then
if [ ! "$(echo "$response" | _contains '"result":"success"')" ]; then
- _contains() ignores stdin (it reads only $1 and $2), so the piped
"$response" was discarded.
- The pattern '"result":"success"' was passed as $1 (the haystack),
leaving $2 (the needle) empty, so it ran:
echo '"result":"success"' | grep -- "" >/dev/null 2>&1
grep with an empty pattern always matches.
- That grep output is redirected to /dev/null, so the command
substitution always captured "", making [ ! "" ] always true.
Fix: call _contains "$response" '"result":"success"' directly and branch
on its exit code, so error responses now correctly fail (return 1).
Co-authored-by: neil <github@neilpang.com>
* fix(dns_desec): sleep after DNS record change to prevent rate limit issues
Also: make sure the subname is lowercase to fix tests where
the acmetestXyzRandomName subdomain is used.
* fix: make regexes POSIX-compatible (for OpenBSD)
* chore: use _sleep instead of sleep to follow acme.sh standards
* Add Level27 DNS API support
Implements dns_level27_add and dns_level27_rm for the Level27 (level27.eu) DNS API, used for ACME dns-01 challenges.
- Authenticates with a persistent API key via the Authorization header.
- Resolves the registered zone with domains?filter and exact fullname match (supports DNS alias mode).
- Removes the challenge record by its exact TXT value, leaving other records intact (wildcard-safe).
- Optional LEVEL27_API override for non-default/staging endpoints.
* A little better documentation
---------
Co-authored-by: Jeroen Moors <jeroen.moors@level27.be>
Firestorm.ch is a Swiss hosting provider with managed DNS (PowerDNS).
This plugin allows customers to automate Let's Encrypt DNS-01 challenges
via the Firestorm DNS API.
- Removed scope exclusion for "standard commit".
- If 'device-and-networks' is excluded (previous behaviour), a certificate for Panorama (always outside of a template) will not be committed (imported to the config but never applied to Panorama). Therefore, panos.sh was only working for certificates used in templates and applied to devices, but not for the Panorama certificate itself.
- According to the official documentation and the XML API Browser, there is no 'policy-and-objects' that can be excluded.
- Although it is not mandatory that the user account is solely dedicated to replace certificates and to perform no other type of operations, it is recommended. If such recommendation is applied, the only changes being committed would be in relation to certificates. Therefore, it should be safe not to exclude any scopes.
- Changed the order for "force commit" from '<commit><partial><force>' (unofficial) to '<commit><force><partial>' (official). Both work, but it is recommended to use what is part of the official documentation and/or XML API Browser.
- Removed unofficial 'policy-and-objects' from commented out code (see above).
- Replaced 'exclude' with 'excluded' from commented out code, as per the official documentation. Both work, but see above.
- Replaced 'acmekeytest' with $_panos_user in the commented out code.
Official documentation: https://docs.paloaltonetworks.com/ngfw/api/pan-os-xml-api-request-types-and-actions/commit
XML API Browser: https://<PANOS HOST>/api
When the final poll (`_link_cert_retry` at 29) returns, the status
is never checked again. So even a `valid` status goes unnoticed.
It's a pre-test loop after all.
Co-authored-by: Oliver Behnke <oliver.behnke@aei.mpg.de>
Fixes the following error when debugging is enabled:
[Sat Feb 21 04:00:22 CET 2026] Here is the curl dump log:
[Sat Feb 21 04:00:22 CET 2026] * URL rejected: Port number was not a decimal number between 0 and 65535
* closing connection #-1
IPv6 addresses in URLs need to be written like this:
http://[2001:43:5::250]
Previous version add bugs that were not properly using the _api_key.
It also enforced to provide PANOS_USER and PANOS_PASSWORD which can be very constraining.
PANOS_KEY now has precedence. If not provided, the script falls back to PANOS_USER and PANOS_PASSWORD.
The error response also contains 'name' in 'zone_name' field,
causing false positives. Check for 'dnssec_status' which only
exists in valid zone responses.
- Added _opusdns_api() for all API requests
- Added _opusdns_init() for config initialization
- Removed duplicate code in add/rm functions
- Removed dead code (orphaned lines 173-178)
- Script now 158 lines (was 255, originally 379)
Instead of fetching all zones and matching, iterate through domain
parts and check each against the API until a valid zone is found.
Same approach as GoDaddy DNS plugin.
Example: _acme-challenge.test.example.com
- Try: test.example.com → 404
- Try: example.com → 200 ✓ → zone found!
Script reduced from 304 to 255 lines.
- Fixed jq path: .pagination.has_next_page instead of .has_next_page
- Fixed grep fallback: remove rrsets before extracting zone names
to avoid matching nested 'name' fields
- Simplified has_next_page detection with simple grep -q
- Added debug output for page zones and has_next status
acme.sh handles DNS propagation checking internally via --dnssleep
and _check_dns_record. Custom propagation checks are unnecessary
and can conflict with acme.sh's own timing.
Removed:
- _opusdns_wait_for_propagation() function
- OPUSDNS_Polling_Interval option
- OPUSDNS_Propagation_Timeout option
Script is now consistent with other DNS API implementations
(Cloudflare, AWS, etc.) which don't have custom propagation checks.
If the random dns challenge string happens to contain 'id',
the parsing method passed a broken, mingled mix of the
record's data and id field, instead of just the id. As a result,
deleting the TXT record failed. We now specifically look for
'"id":', which cannot appear as part of the challenge string.
Replaces cronie with supercronic to allow non-root users to have
cronjobs. Creates user/group acme:acme UID:1000/GID:1000 with
home directory pointing to LE_CONFIG_HOME (default: /acme.sh)
'crontab' is generated in LE_CONFIG_HOME which is used by
supercronic.
Note that `acme.sh --installcronjob` and `--uninstallcronjob`
when run as a non-root user will fail but neither of should be
used in `daemon` mode anyway.
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
Introduced a new OpenIndiana job to the DNS GitHub Actions workflow for testing, including necessary environment variables and steps. Updated README to display the OpenIndiana workflow status badge.
kislyuk yq (used by Debian packages) does not accept `yq e` and
also returns strings with double quotes.
mikefarah's yq-go (used by Alpine) accepts `yq e` and `yq`.
replace `yq e` with `yq` and also use `-r` switch to remove double
quoting to ensure uniform return values from both yq versions.
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
Before this, some deployment scripts would interact with STDIN and that would cause this loop to skip some elements. By using descriptor 3 we avoid clashing with the very common stdin and stdout.
Before this, the eval call would try to run some commands (if they were compound commands) in the yaml file on the machine running acme.sh
Eval might not be worth it for the little benefit it brings.
The config file checks were returning okay even when there were errors. The yq tool returns "null" when it cannot find what's queried, but exists with a 0 rc still.
- Prefer using a pipe to `while read`
- But use a temp file when the loop needs to modify variables that need to be used outside the loop, as the pipe creates a subshell and modifications do not survive after the loop exits.
handle the case of metadata 404 in old api domains
fix index of domain start
fix shfmt
Revert "handle the case of metadata 404 in old api domains"
This reverts commit 9fe4616664b897c9891271006e7489b10bb818ca.
fix 404 on dot ad hyphen
fix shfmt
- Added _get_root() helper function for proper zone detection
- Fixed zone ID extraction to match dns/auth_zone/* pattern
- Added _infoblox_rest() wrapper for API calls with proper auth
- Improved error handling for authentication failures
- Added support for wildcard certificates (multiple TXT records)
- Filter by exact txtvalue when deleting records
- Follow acme.sh best practices and conventions
Tested with:
- Standard domain certificates
- Wildcard certificates (*.domain.com)
- Multiple subdomains
- Staging and production Let's Encrypt
Reorder and reword small portions of the keylength documentation and
make the ECC cert default explicitly stated in part 2 to avoid
confusion.
Fixes#6590
Useful where remote endpoints filter requests by IP address, but one's
Internet connection has a stable IP for only one address family,
e.g.: a dynamic IPv6 prefix and a static IPv4 address; or a static IPv6
prefix and CGNAT IPv4.
This commit adds a new "Profile" column to the output of the `--list` command.
The column displays the value of the `Le_Certificate_Profile` variable stored in each domain's respective configuration file. If a profile is not set for a certificate, the column is left empty.
This enhances the utility of the list command by providing more at-a-glance information about each certificate's configuration, which is particularly useful for CAs that support different certificate profiles.
This commit introduces a new command, `--list-profiles`, to allow users to discover the certificate profiles supported by a Certificate Authority.
The command queries the `meta.profiles` object within the ACME directory JSON for the selected server and formats the output for readability. If a CA does not publish profiles in its directory, the command reports that none were found.
Usage:
acme.sh --list-profiles [--server letsencrypt]
Updates the dns_opnsense.sh Bourne shell script for OPNSense v25.7 series:
1. Fixes historical error in rm_record() [used incorrect response variable]
2. Improves debug messaging in rm_record()
3. Fixes _get_root() for change in OPNsense API
* Response is now split into pseudo-rows
* We now iterate through pseudo-rows for matching domainname field
4. Fixes _existingchallenge() for change in OPNsense API
* Fixes unreliable regex for uuid
* Adds domain regex and %domain field
5. Fixes historical error in _existingchallenge() [incorrect variable syntax]
Resolves#6467
Signed-off-by: benyamin-codez <115509179+benyamin-codez@users.noreply.github.com>
The checkout action fetches one single commit, so attempts to find previous states of a page result in error. Adding fetch-depth:0 to the configuration fetches all commits and makes finding the previous commit that changed a page possible in the github action.
* Fix for empty error objects in response breaking extraction of domain validation types
Fix for empty error objects in the response which mess up the extraction of domain validation types due to the closing brace in the error object prematurely matching the end of the search pattern.
This seems to be a recent change with ZeroSSL in particular where "error":{} is being included in responses.
There could potentially be a related issue if there is a complex error object ever returned in the validation check response where an embedded sub-object could lead to an incomplete extraction of the error message, roughly around line 5040.
Adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
* Add new dnsapi support for OpenProvider.eu using new REST API
* Cleanup duplicate debug log output based on DNS test run
* Resolve spellcheck error
* Configure 10 second timeout to ACME_DIRECTORY API call
* add support for AIX style netstat
* add
* fix for wiki
* minor
* minor
* wiki
* wiki
* dnsapi: dns_mydnsjp.sh fix author
The @epgdatacapbon was renamed to @tkmsst
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* dnsapi: dns_ddnss.sh remove RaidenII from authors
He made the DuckDNS script that was used for this script but he can't support the script.
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* dnsapi: fix authors: use @ for GitHub profiles
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* dnsapi: dns_vultr.sh remove empty author
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* dnsapi: dns_mijnhost.sh rearrange fields, use user docs instead of API docs
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* dnsapi: fix Structured DNS Info
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
* Fix logged typo when running pre hook
* Run post hook when _on_before_issue errors
---------
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
Co-authored-by: Ciaran Walsh <ciaran@ciaran-walsh.com>
Co-authored-by: Lambiek12 <algemeen@lambiek12.nl>
Co-authored-by: Erwin Oegema <blablaechthema@hotmail.com>
Co-authored-by: laDanz <cdanzmann@gmail.com>
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: neil <gitpc@neilpang.com>
Co-authored-by: Sergey Ponomarev <stokito@gmail.com>
Co-authored-by: David Beitey <david@davidjb.com>
Co-authored-by: Jan-willem van Kampen <Lambiek12@users.noreply.github.com>
Deploy-hook to very simply copy files to set directories and then execute whatever reloadcmd
the admin needs afterwards. This can be useful for configurations where the "multideploy"
hook (in development) is used or when an admin wants ACME.SH to renew certs but needs to
manually configure deployment via an external script (e.g. The deploy-freenas script for TrueNAS Core/Scale
https://github.com/danb35/deploy-freenas/
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
Add support for deploying to the Zyxel GS1900 line of switches as long
as those switches are running at least firmware V2.80.
Tested on a Zyxel GS1900-8 and GS1900-24E
Resolves#5042
Since the server in mainland China cannot connect to Telegram, add the TELEGRAM_BOT_URLBASE environment variable
Please add the proxy address you set. If this variable is not added, it will be the default value (https://api.telegram.org)
It was reported in issue #6267 that the private key was leaked when
using the DEPLOY_HAPROXY_HOT_UPDATE=yes feature.
Indeed, the debugging code which sends commands to HAProxy was using
_debug even when passing the private key.
This patch fixes the issue by using _secure_debug when doing that.
Exporting SYNO_LOCAL_HOSTNAME=1 only works on the first deploy invocation. Subsequent deploys using the saved config fails because it saves $SYNO_HOSTNAME instead of $SYNO_LOCAL_HOSTNAME.
Extend dns_myapi.sh info with samples.
omglol: Split Address option by a dot to avoid incorrect parsing with a dot from omg.lol
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
# dns_selectel_info='Selectel.com
# Domains: Selectel.ru
# Site: Selectel.com
# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
# Options:
# Variables that must be defined before running
# dns_selectel_info='Selectel.com
# Domains: Selectel.ru
# Site: Selectel.com
# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
# Options:
# Variables that must be defined before running
When acme.sh is run as a non-root user different from the owner of the
webroot directory it is unable to change the owner of the files in
.well-known to that user, causing permissions errors. Avoid this by
making the files world-readable.
These files should pose no disclosure risk since they are sent in
cleartext during the HTTP Identifier Validation Challenge[1] and may
already be exposed by directory enumeration, depending on server
settings. AFAIK they should be safe to expose as world-readable in all
cases.
1. https://ietf-wg-acme.github.io/acme/#rfc.section.7.2FixesNeilpang/acme.sh#32
Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
Description "Based on the omg.lol API" is useless for users so removed.
The link to api moved to comment bellow.
Domains: omg.lol is unnecessary because the DNS provider name is anyway omg.lol.
Site: changed to point to the https://omg.lol site.
Issues: put a link to the support issue.
Remove the useless "Please Read this guide first" comment.
Fix typos.
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
the same pagination bug that happens for accounts with a large number of
domains also happens for DNS zones with a large number of records. The
previous code assumes that all records are returned in a single page.
Changed the code to do an exact match search so that it returns only the few
required records and never paginates replies.
Updated all Microsoft links from old `docs` subdomain to new `learn` subdomain, and fixed a couple that weren't working.
Added missing $wiki variable to print the wiki link in error messages.
Updated spelling and formatting in error messages
Updated a comment and added a TODO as Microsoft has increased the number of allowed Public DNS zones per subscription from 100 to 250, while the function in this script can only handle the old limit of 100.
acme.sh dnsapi/dns_edgedns.sh invokes iconv(1) which is not provided by
OpenBSD in the base system. Adding the libiconv package provides this tool.
This should also help address acmesh-official/acme.sh#4350, albeit indirectly:
it looks like that PR cannot be merged because the OpenBSD test fails despite
the PR not actually changing anything having to do with iconv. That is, I
suspect that the test for OpenBSD failed (or would have failed, had it been
run?) prior to that PR being issued.
(This was previously submitted as
https://github.com/acmesh-official/acmetest/pull/28 )
With nsupdate the rule seems to be: filenames need to be wrapped
in double quotes, while all other options must not use double quotes.
Hence there is no way to resolve the shellcheck offense, because
the key requires quotes, but the other options must not use quotes.
- Removed or moved `_normalizeJson` processing to occur only when needed.
- Corrected usage of `_red` to `__red`.
- Simplified JSON parsing by using more concise `cut` commands.
- Simplify token refresh logic.
The MIAB API requires that the txtvlaue to a TXT record includes the "value=" and "ttl=" components as part of the TXT record when adding a new record.
Ubiquiti removed keytool (and java) from recent releases of Unifi OS. This moves from keytool to openssl's native pkcs12.
Tested on Unifi Dream Machine which runs Unifi OS and a built-in Unifi controller.
Also added backup of existing files prior to change in case anything goes wrong, and update system configuration with compatible ciphers.
In the case where importing the cert and key removes the files from disk
the existing deploy will fail when it tries to remove those files. This
still attempts to remove the files but catches the error and moves on instead
of bombing like before.
Similarly, if the deploy had failed before, subsequent deploys would fail
because the script already existed, so it would not be able to create
the script. This first attempts to remove the script if it exists, and then
creates the script.
The following error happens if the header is set
to 'Content-Type: application/json':
{"statusCode":400,"message":"One or more errors occurred!",
"errors":{"serializerErrors":["The input does not contain
any JSON tokens. Expected the input to start with a valid
JSON token, when isFinalBlock is true. LineNumber:
0 | BytePositionInLine: 0."]}}
Fix TXT removal
when fetching all zones the memory usage can exceede limits and also
cause timeouts.
with this change the zone will be searched via the longest to shortest
match using the get endpoint.
it is related to this bug report: opnsense/plugins#3525
FreeBSD's sed doesn't have the -z option, so empty certificates are delivered to vault when running the script on FreeBSD.
fixes issue #4956
previous code only worked for the first 10 domains on the account (as Linode
API returned a paginated response, with only 10 records).
This change makes an exact search query for each subdomain, completely removing any
need for walking through paginated responses. What makes it work for large
accounts with any number of domains.
Instead of using comments declare info in a special variable.
Then the variable can be used to print the DNS API provider usage.
The usage can be parsed on UI and show all needed inputs for options.
The info is stored in plain string that it's both human-readable and easy to parse:
dns_example_info='API name
An extended description.
Multiline.
Domains: list of alternative domains to find
Site: the dns provider website e.g. example.com
Docs: Link to ACME.sh wiki for the provider
Options:
VARIABLE1 Title for the option1.
VARIABLE2 Title for the option2. Default "default value".
VARIABLE3 Title for the option3. Description to show on UI. Optional.
Issues: Link to a support ticket on https://github.com/acmesh-official/acme.sh
Author: First Lastname <authoremail@example.com>, Another Author <https://github.com/example>;
'
Here:
VARIABLE1 will be required.
VARIABLE2 will be required too but will be populated with a "default value".
VARIABLE3 is optional and can be empty.
A DNS provider may have alternative options like CloudFlare may use API KEY or API Token.
You can use a second section OptionsAlt: section.
Some providers may have alternative names or domains e.g. Aliyun and AlibabaCloud.
Add them to Domains: section.
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
- New CLI param: --extended-key-usage <string>
- When --extended-key-usage is defined:
1. Set [v3_req]extendedKeyUsage to the provided value.
2. Store the value in domain conf Le_ExtKeyUse for reuse.
reduce TTL for the TXT record from 3600 to 300 to have an easier way to replicate changes for the dns-verification in case multiple submissions for a specific record/domain are done within an hour.
Fix for empty error objects in the response which mess up the extraction of domain validation types due to the closing brace in the error object prematurely matching the end of the search pattern.
This seems to be a recent change with ZeroSSL in particular where "error":{} is being included in responses.
There could potentially be a related issue if there is a complex error object ever returned in the validation check response where an embedded sub-object could lead to an incomplete extraction of the error message, roughly around line 5040.
Adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
1. Updated LiveDNS API URL for the new API to allow Personal Access Tokens to work
2. Updated authorization header syntax to allow deprecated API Keys to work with the new API
3. Removed white space in JSON response parsing to match responses returned by the server
By default acme.sh uses the '*' character in the filename for wildcard.
That can be confusing within HAProxy since the * character in front of a
filename in the stat socket is used to specified an uncommitted
transaction.
This patch replace the '*' by a '_' in the filename.
This is only done when using the default filename, the name can still be
forced with an asterisk.
DEPLOY_HAPROXY_MASTER_CLI allows to use the HAProxy master CLI instead
of a stats socket for DEPLOY_HAPROXY_HOT_UPDATE="yes"
The syntax of the master CLI is slightly different, a prefix with the
process number need to be added before any command.
This patch uses ${_cmdpfx} in front of every socat commands which is
filled when the master CLI is used.
DEPLOY_HAPROXY_HOT_UPDATE="yes" now allows to add a new certificate
within HAProxy instead of updating an existing one.
In order to work, the ${DEPLOY_HAPROXY_PEM_PATH} value must be used as a
parameter to the "crt" keyword in the haproxy configuration.
The patch uses the following commands over HAProxy stats socket:
- show ssl cert
- new ssl cert
- set ssl cert
- commit ssl cert
- add ssl crt-list
Since version 2.2, HAProxy is able to update dynamically certificates,
without a reload.
This patch uses socat to push the certificate into HAProxy in order to
achieve hot update. With this method, reloading is not required.
This should be used only to update an existing certificate in haproxy.
2 new variables are available:
- DEPLOY_HAPROXY_HOT_UPDATE="yes" update over the stats socket instead
of reloading
- DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock" set the path on
the stats socket.
At least since 2023-08-25 the cookie is set via `set-cookie' instead the
expecting `Set-Cookie' string. A month earlier it was working.
Ignore the case while matching the cookie.
Fixes: #4763
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
1. Fix the broken logic in (Sorry for including fix commit in same PR, I'm feeling quite tired and would like to go to sleep right away...)
2. Provides new method to obtain credential info for authentication, it will create a temp admin user if SYNO_USE_TEMP_ADMIN is set, instead of requiring the user's own credentials which will be saved in disk.
I do really don't like to have plaintext credentials be saved in disk, and I noticed that you've spent a lot of time fighting with 2FA related stuffs, so why not just get rid of the whole old way. :)
Copied most of the v4 api stuff from DNS_AWS hook (Thanks!)
New tokens added:
AWS_SES_ACCESS_KEY_ID
AWS_SES_SECRET_ACCESS_KEY
AWS_SES_REGION
AWS_SES_TO
AWS_SES_FROM
AWS_SES_FROM_NAME (Optional)
For being able to use e.g. "-v" to use TCP communication with the NSUPDATE_SERVER -> NSUPDATE_OPT has been added.
NSUPDATE_OPT will be plainly added to the command line of nsupdate.
NSUPDATE_OPT will also be saved to conf respectively read from conf
The current script is already broken due to Bind 9.16 -> 9.18 changes
due to their renaming scheme for primary/secondary so do not rely on the
compat layer (which was also broken for other reasons).
https://domains.google/learn/gts-acme/
This is an ACME API for Google Domains customers, which is
different from the Google Cloud Domains API for Google Cloud
customers.
# What's expected
Since in `acme.sh` path strings are concatenated with a hardcoded slash in between, the left operand must never end with a trailing slash for the resulting path to be valid. Otherwise, obviously, the resulting path will have two adjacent slashes in the middle and will not be valid.
# What actually happens
Even though I cannot tell for each of the input params, I know this for sure for the the `--home` argument's value.
If I run `acme.sh` with `--home` argument's value being a path ending in a trailing slash,
```sh
acme.sh ... --debug ... --home /some/path/ ... -d somedomainna.me ...
```
I get the following (distinct) occurrencies of resulting invalid paths containing two adjacent slashes:
```
[...] Using config home:/some/path/
[...] DOMAIN_PATH='/some/path//somedomainna.me'
[...] _CURL='curl --silent --dump-header /some/path//http.header -L -g '
[...] The domain key is here: /some/path//somedomainna.me/somedomainna.me.key
[...] _CURL='curl --silent --dump-header /some/path//http.header -L -g -I '
[...] Your cert is in: /some/path//somedomainna.me/somedomainna.me.cer
[...] Your cert key is in: /some/path//somedomainna.me/somedomainna.me.key
[...] The intermediate CA cert is in: /some/path//somedomainna.me/ca.cer
[...] And the full chain certs is there: /some/path//somedomainna.me/fullchain.cer
```
# Suggested fix
Trim trailing slash in `--home` argument's value from the get-go.
There might be '|' in __val (e.g., SYNO_Password), which will cause that
all content of the conf file is cleared. Fix it by escaping '|'
manually.
Signed-off-by: Adam Tao <tcx4c70@gmail.com>
Added an explicit no error (0) return on the internal _retrieve_miab_env() function. This was causing errors when acme.sh was not run with a debug level.
Slack Incoming webhooks is a legacy custom integration - an outdated
way for teams to integrate with Slack. These integrations lack newer
features and they will be deprecated and possibly removed in the
future. Slack team do not recommend their use. Instead, it's suggested
to use Slack apps.
Plesk SPI return domain names always lower-case. Therefore the search for domain names in the API response must be case-insensitve. Set debug logging to the values that are reallys used for the spi calls.
added comment
Improved error handling when result contains data-structure which might contain another status-flag that is related to the status of the related object and not the api call
Revert "Improved error handling"
This reverts commit fa6df1cfab134d38baad19fc1caa0842f00416d5.
Revert "Revert "Improved error handling""
This reverts commit 5a4b78392f063863ee9f56686f5c429e9376af1b.
In our environment we use DNS manual mode and take the TXT record
output of acme.sh and process it with Ansible to install the records
(then we call renew later when the records have been pushed to the DNS
servers by a whole bunch of other bits).
One problem is that after getting/showing the TXT records, acme.sh
always returns 1. This makes it difficult to tell if there is
actually an error condition.
Since we have set the manual-mode flag, not installing the DNS records
is an expected correct result. This returns a separate error code for
this situation (3), which can be distinguished in automation.
Some CAs auto-validate orders based on account-level rules and do not
require a challenge at all. Sectigo introduced a non-standard challenges
named 'sectigo-dns-01', presumably to work around this issue in certbot.
This also works for non-wildcard domains in acme.sh, but wildcard domains
are rejected because acme.sh hard-codes 'dns-01' as the only allowed
challenge for wildcard domains, which is not offered by Sectigo.
This change simply moves the '"status":"valid"' check up a bit and ignores
challenge type mismatches or missing tokens if no challenge is needed anyway.
When there are errors with namecheap hosts, acme.sh fails with:
dns_namecheap.sh: line 262: _error: command not found
Based on usage elsewhere in the file, I believe this should be _err
The change made in #4057 broke the decoding of OCI_CLI_KEY from
the encoded OCI_CLI_KEY_FILE content so this removes the multiline
parameter to fix it.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
The dns_nederhost DNS API relies on the exact HTTP status code to be
returned (e.g. 204); however, the _get function always returns 200 for a
succesful call when using wget instead of curl. This patch fixes this by
using the _post function for all requests done by dns_nederhost.
The function '_get_root' tries to retrieve the
hostedzone iterating the domains, eg:
1. srv.prod.example.com
2. prod.example.com
3. example.com
This doesn't work if '_acme-challenge' is in it's
own hostedzone for security reasons.
Starting that iteration with '_acme-challenge.srv.prod.example.com'
fixes this issue.
When performing renewals acme.sh checks key length values to determine
if a new key should be created with createDomainKey(). However, older
acme.sh stored key length as an empty value if the default of 2048 was
desired. Now it is explicit and the explict check of 2048 against "" is
causing createDomainKey() to always be called with fails without
--force.
Fix this by converting the keylength value to 2048 if an empty string is
returned from the config file. acme.sh will then write out 2048 updating
old keys and configs to the explicit version.
Issue: 4077
+ShellCheck
+ACME v2 compatible
Example:
- Fist create 2 new TXT records on _acme-challenge.example.com
- Now note the ID in (...) from the edit page behind "_acme-challenge.example.com"
export SELFHOSTDNS_USERNAME=myname
export SELFHOSTDNS_PASSWORD=mypass
export SELFHOSTDNS_RID=id_of_txt_record
export SELFHOSTDNS_RID2=id_of_second_txt_record
acme.sh --issue -d example.com --dns dns_selfhost
In order to stop processing on failure to copy certificate
to remote side, fail on error of scp command.
Signed-off-by: Andreas Bießmann <andreas@biessmann.org>
In order to detect errorneous scripts on remote side, catch return code
and handle it respectively.
Signed-off-by: Andreas Bießmann <andreas@biessmann.org>
Reviewed-by: Ross Shen @sjtuross
This set the owner of script to ssh user, have the comment line in script
as real comment and removes policy since this is set from current user,
at least for RouterOS 7.x.
Signed-off-by: Andreas Bießmann <andreas@biessmann.org>
As the script is applying the fullchain which includes three certificates,
delete all of them before applying updated certificate.
Signed-off-by: Andreas Bießmann <andreas@biessmann.org>
service/get_list returns domains in utf. But if utf, then error Error parsing certificate request: x509: SAN dNSName is malformed
early using my patch by IDN_ITEM="$(echo "${ITEM}" | idn)"
Now replacing by IDN_ITEM="$(_idn "${ITEM}")"
1. Match zone name in response in case multiple items return.
2. Use string '"id"' (single quotation marks added) to check if zone/record exist in _get_zoneid() & _get_recordset_id(). Fix domain can't contain string "id".
(Sensitive _debug Access Token Commented out, For CICD Run)
PR #3673 Fix simply.com API seems abandoned by maintainer and I need this fixed asap
Changes implemented
* Normalize JSON and fix not handling return code correctly
* Add some information to comments
* Fix trailing slash on URIs
* Add 60 second sleep for zone to be written
* Fix parsing record_data and record_type
Previously the deploy hook config was stored in the account config.
This seems odd and adds unnecessary limitations to the hook.
Now we're using the correct _*deployconf() functions to read and
write the deploy hook config.
When generating a CSR in Windows it seems to create a CSR header that looks like "-----BEGIN NEW CERTIFICATE REQUEST-----", but the addition of "NEW" breaks the parsing of the CSR. Making "NEW " optional fixes the problem.
Apparently certbot is tolerant of both forms, see: https://community.letsencrypt.org/t/error-parsing-certificate-request-resolved/40039/6 for more information.
The support for the base image alpine:3.12 will expire in 4 months (https://endoflife.date/alpine), so it would make sense to upgrade to the current version alpine:3.15.
I was able to create the acme.sh image with the new alpine:3.15 version without errors and also create and deploy a certificate, but further testing would be useful.
+ShellCheck
+ACME v2 compatible
Example:
- Fist create 2 new TXT records on _acme-challenge.example.com
- Now note the ID in (...) from the edit page behind "_acme-challenge.example.com"
export SELFHOSTDNS_USERNAME=myname
export SELFHOSTDNS_PASSWORD=mypass
export SELFHOSTDNS_RID=id_of_txt_record
export SELFHOSTDNS_RID2=id_of_second_txt_record
acme.sh --issue -d example.com --dns dns_selfhost
The support for the base image alpine:3.12 will expire in 4 months (https://endoflife.date/alpine), so it would make sense to upgrade to the current version alpine:3.15.
I was able to create the acme.sh image with the new alpine:3.15 version without errors and also create and deploy a certificate, but further testing would be useful.
When generating a CSR in Windows it seems to create a CSR header that looks like "-----BEGIN NEW CERTIFICATE REQUEST-----", but the addition of "NEW" breaks the parsing of the CSR. Making "NEW " optional fixes the problem.
Apparently certbot is tolerant of both forms, see: https://community.letsencrypt.org/t/error-parsing-certificate-request-resolved/40039/6 for more information.
```nginx
include conf.d/*;
include sites-enabled/*;
```
In this situation, after the first recursive `_checkConf` invocation 4 lines below, `$_c_file` does not contain what you expect anymore, and the second lookup checks for `conf.d/sites-enabled/*` which is obviously wrong.
the value for uniqueFormIdTTL is not available or needed anymore.
values for 'aktivPaket' are not needed by the api.
changed endpoint for deletion from `/deleteRecord` to `/dns/record/delete`
Export Le_Domains and Le_Alt so your pre-hook script can run additional checks.
Allows running checks on the domain names before the first call to the ACME API. Thereby not counting against the rate-limit when an issue is going to be problematic.
Supersedes: #3288
URL is now constructed after possible fallback value for Infoblox_View is being set
Infoblox_View is URLencoded to deal with e.g. spaces
Some cleanup, clearer log messages etc.
The plugin will use the following order of precedence:
environment value > file value > default value
See the wiki for details on environment variable names.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
The individual parameters can still be overridden via the
corresponding OCI_CLI environment variable.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
Also reduced the number of environment variables which simplifies
the documentation and requirements. The variable names now match
those used by the OCI CLI.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
With apk `--no-cache` parameter, there is no need to run `apk update`
and manually clean up the cache, apk will update automatically without
leaving local cache files to be cleaned up.
This plugin is has noticeably more required fields than most
other plugins due to the requirement that all requests to
the OCI REST API must be cryptographically signed by the client
using the draft standard proposed in draft-cavage-http-signatures-08[1].
The OCI specific implementation details of the draft standard are
documented in the Developer Guide[2].
NOTE: there is maximum allowed clock skew of five minutes between the
client and the API endpoint. Requests will be denied if the skew is
greater.
This PR also includes a minor tweak to the Solaris job in the DNS
workflow so that it uses the pre-installed GNU tools, curl and OpenSSL 1.1.1.
Without these changes, the signature generation function does not
work on Solaris.
[1]: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-08
[2]: https://docs.oracle.com/en-us/iaas/Content/API/Concepts/signingrequests.htm#five
Signed-off-by: Avi Miller <avi.miller@oracle.com>
This escapes special characters used in POSIX sed to prevent mismatches.
e.g. `SYNO_Certficiate=*.example.com` would not match a description of
"*.example.com" and would look to match any number of double quotes (the
last character in the sed regex prior to certificate description),
followed by any single character, followed by "example", followed by any
character, followed by "com".
After this change, it will properly match `*.example.com` and not
`""zexamplefcom`.
Additionally we now store the certificate description as base64 encoded
to prevent issues with single quotes.
Tested on DSM 7.0-41222 (VDSM) and DSM 6.2.4-25556 (DS1515+).
As noted by @buxm, previous fix didn't work for all versions of DSM 6.
The better fix appears to be simply not outputting the "as_default"
parameter unless we are doing something with the default certificate.
For some DSM installs, it appears that setting the "default" flag to the
string "false" actually sets it to true. This causes Synology to set
the last updated certificate to be the default certificate. Using an
empty string appears to still be accepted as a false-y value for DSMs
where this isn't happening and corrects the behavior in the cases that
it was.
Credit to @Run-King for identifying the fix and @buxm for reporting.
On systems that /bin/sh is served by shells other than bash, or
shells that don't implement the same syntax as the bash printf builtin,
printf -- fails to produce the output necessary for standalone operation.
The test case for this was SmartOS, which uses ksh93 as its /bin/sh.
This change uses the more generic method of passing a format parameter
of a single string, and then the argument to replace it with.
When using vault_cli with a kv2 path, it isn't working. I have the following error:
```
WARNING! The following warnings were returned from Vault:
* Invalid path for a versioned K/V secrets engine. See the API docs for the
appropriate API endpoints to use. If using the Vault CLI, use 'vault kv put'
for this operation.
```
The new way to write data is to use `vault kv put`, it is compatible with kv1 and kv2.
Ref: https://www.vaultproject.io/docs/commands#reading-and-writing-data
```
The original version of K/V used the common read and write operations. A more advanced K/V Version 2 engine was released in Vault 0.10 and introduced the kv get and kv put commands.
```
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
The API now supports a POST route for adding records. Therefore
checking for already existing records and including them in a PATCH
request is no longer necessary.
Add and use _readaccountconf_mutable_default and
_saveaccountconf_mutable_default helpers to capture
common default value handling.
New approach also eliminates need for separate
underscore-prefixed version of each conf var.
* fix: unifi deploy hook also update Cloud Key nginx certs
When running on a Unifi Cloud Key device, also deploy to
/etc/ssl/private/cloudkey.{crt,key} and reload nginx. This
makes the new cert available for the Cloud Key management
app running via nginx on port 443 (as well as the port 8443
Unifi Controller app the deploy hook already supported).
Fixes#3326
* Improve settings documentation comments
* Improve Cloud Key pre-flight error messaging
* Fix typo
* Add support for UnifiOS (Cloud Key Gen2)
Since UnifiOS does not use the Java keystore (like a Unifi
Controller or Cloud Key Gen1 deploy), this also reworks
the settings validation and error messaging somewhat.
* PR review fixes
* Detect unsupported Cloud Key java keystore location
* Don't try to restart inactive services
(and remove extra spaces from reload command)
* Clean up error messages and internal variables
* Change to _getdeployconf/_savedeployconf
* Switch from cp to cat to preserve file permissions
* change arvan api script
* change Author name
* change name actor
* Updated --preferred-chain to issue ISRG properly
To support different openssl crl2pkcs7 help cli format
* dnsapi/pdns: also normalize json response in detecting root zone
* Chain (#3408)
* fix https://github.com/acmesh-official/acme.sh/issues/3384
match the issuer to the root CA cert subject
* fix format
* fix https://github.com/acmesh-official/acme.sh/issues/3384
* remove the alt files. https://github.com/acmesh-official/acme.sh/issues/3384
* upgrade freebsd and solaris
* duckdns - fix "integer expression expected" errors (#3397)
* fix "integer expression expected" errors
* duckdns fix
* Update dns_duckdns.sh
* Update dns_duckdns.sh
* Implement smtp notify hook
Support notifications via direct SMTP server connection.
Uses Python (2.7.x or 3.4+) to communicate with SMTP server.
* Make shfmt happy
(I'm open to better ways of formatting the heredoc
that embeds the Python script.)
* Only save config if send is successful
* Add instructions for reporting bugs
* Prep for curl or Python; clean up SMTP_* variable usage
* Implement curl version of smtp notify-hook
* More than one blank line is an abomination, apparently
I will not try to use whitespace to group code visually
* Fix: Unifi deploy hook support Unifi Cloud Key (#3327)
* fix: unifi deploy hook also update Cloud Key nginx certs
When running on a Unifi Cloud Key device, also deploy to
/etc/ssl/private/cloudkey.{crt,key} and reload nginx. This
makes the new cert available for the Cloud Key management
app running via nginx on port 443 (as well as the port 8443
Unifi Controller app the deploy hook already supported).
Fixes#3326
* Improve settings documentation comments
* Improve Cloud Key pre-flight error messaging
* Fix typo
* Add support for UnifiOS (Cloud Key Gen2)
Since UnifiOS does not use the Java keystore (like a Unifi
Controller or Cloud Key Gen1 deploy), this also reworks
the settings validation and error messaging somewhat.
* PR review fixes
* Detect unsupported Cloud Key java keystore location
* Don't try to restart inactive services
(and remove extra spaces from reload command)
* Clean up error messages and internal variables
* Change to _getdeployconf/_savedeployconf
* Switch from cp to cat to preserve file permissions
* feat: add huaweicloud error handling
* fix: fix freebsd and solaris
* support openssl 3.0
fix https://github.com/acmesh-official/acme.sh/issues/3399
* make the fix for rsa key only
* Use PROJECT_NAME and VER for X-Mailer header
Also add X-Mailer header to Python version
* Add _clearaccountconf_mutable()
* Rework read/save config to not save default values
Add and use _readaccountconf_mutable_default and
_saveaccountconf_mutable_default helpers to capture
common default value handling.
New approach also eliminates need for separate
underscore-prefixed version of each conf var.
* Implement _rfc2822_date helper
* Clean email headers and warn on unsupported address format
Just in case, make sure CR or NL don't end up in
an email header.
* Clarify _readaccountconf_mutable_default
* Add Date email header in Python implementation
* Use email.policy.default in Python 3 implementation
Improves standards compatibility and utf-8 handling
in Python 3.3-3.8. (email.policy.default becomes the
default in Python 3.9.)
* Prefer Python to curl when both available
* Change default SMTP_SECURE to "tls"
Secure by default. Also try to minimize configuration errors.
(Many ESPs/ISPs require STARTTLS, and most support it.)
* Update dns_dp.sh
没有encode中文字符会导致提交失败
* No need to include EC parameters explicitly with the private key.
(they are embedded)
* Fixes response handling and thereby allow issuing of subdomain certs
* Adds comment
* fix https://github.com/acmesh-official/acme.sh/issues/3402
* dnsapi/ionos: Use POST instead of PATCH for adding TXT record
The API now supports a POST route for adding records. Therefore
checking for already existing records and including them in a PATCH
request is no longer necessary.
* fix https://github.com/acmesh-official/acme.sh/issues/3433
* fix https://github.com/acmesh-official/acme.sh/issues/3019
* fix format
* Update dns_servercow.sh to support wildcard certs
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
* Update dns_servercow.sh to support wildcard certs
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
* fix https://github.com/acmesh-official/acme.sh/issues/3312
* fix format
* feat: add dns_porkbun
* fix: prevent rate limit
Co-authored-by: Vahid Fardi <vahid.fardi@snapp.cab>
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: Gnought <1684105+gnought@users.noreply.github.com>
Co-authored-by: manuel <manuel@mausz.at>
Co-authored-by: jerrm <jerrm@users.noreply.github.com>
Co-authored-by: medmunds <medmunds@gmail.com>
Co-authored-by: Mike Edmunds <github@to.mikeedmunds.com>
Co-authored-by: Easton Man <manyang.me@outlook.com>
Co-authored-by: czeming <loser_wind@163.com>
Co-authored-by: Geert Hendrickx <geert@hendrickx.be>
Co-authored-by: Kristian Johansson <kristian.johansson86@gmail.com>
Co-authored-by: Lukas Brocke <lukas@brocke.net>
Co-authored-by: anom-human <80478363+anom-human@users.noreply.github.com>
Co-authored-by: neil <win10@neilpang.com>
Co-authored-by: Quentin Dreyer <quentin.dreyer@rgsystem.com>
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
Updated dns_servercow.sh to support txt records with multiple entries. This supports wildcard certificates that require txt records with the same name and different contents.
The API now supports a POST route for adding records. Therefore
checking for already existing records and including them in a PATCH
request is no longer necessary.
Add and use _readaccountconf_mutable_default and
_saveaccountconf_mutable_default helpers to capture
common default value handling.
New approach also eliminates need for separate
underscore-prefixed version of each conf var.
* fix: unifi deploy hook also update Cloud Key nginx certs
When running on a Unifi Cloud Key device, also deploy to
/etc/ssl/private/cloudkey.{crt,key} and reload nginx. This
makes the new cert available for the Cloud Key management
app running via nginx on port 443 (as well as the port 8443
Unifi Controller app the deploy hook already supported).
Fixes#3326
* Improve settings documentation comments
* Improve Cloud Key pre-flight error messaging
* Fix typo
* Add support for UnifiOS (Cloud Key Gen2)
Since UnifiOS does not use the Java keystore (like a Unifi
Controller or Cloud Key Gen1 deploy), this also reworks
the settings validation and error messaging somewhat.
* PR review fixes
* Detect unsupported Cloud Key java keystore location
* Don't try to restart inactive services
(and remove extra spaces from reload command)
* Clean up error messages and internal variables
* Change to _getdeployconf/_savedeployconf
* Switch from cp to cat to preserve file permissions
acme .sh deploy Scipt for TrueNAS Server that uses the REST API from TrueNAS.
- Authentification with API Key
- If HTTP redirect is configured, automatik switch to HTTPS
- If WebDAV Certificate is the same as Web UI Certificate, Webdav Certificate get also an updated
- If FTP Certificate is the same as Web UI Certificate, FTP Certificate get also an updated
Adding this to at least partially prevent the virtually nonexistent possibility of timestamp and _utc_date() mismatch. If the normal date -u -d does not get converted (looking at you Solaris!), the poor man's method with manipulating the _utc_date() string output kicks in.
Small changes for DSM 6:
All fields (except enable_syno_token as explained below) must either be in the GET params or the POST params, you can't mix GET and POST params
enable_syno_token=yes must be in both the GET and POST params.
If enable_syno_token=yes is only in the POST fields, then DSM6 returns a synotoken of --------. If enable_syno_token=yes is only in the GET params, then it returns no synotoken at all. It must be in both to work.
Need to use /webapi/auth.cgi instead of /webapi/entry.cgi
Verified with DSM 6.2.3-25426 Update 2 and DSM 7.0-40850
This allows us to get the cookie and the token (as it appears to be only in the body in DSM 7.) HTTP_HEADERS is only guarenteed to be output with POST for both wget and curl.
Corrects issue #3285.
The '?' character after a group is not supported in POSIX Basic Regular Expressions. Replacing it with '\{0,1\}' retains the same functionality and also works on non-GNU systems.
I have modified the following things:
Originally, "/data/assets/ssl/" is always appended to the varialbe ${_mailcow_path}. Since I use acme.sh as docker container, I only want to include the mailcow-ssl directory in the acem.sh container and not the complete mailcow directory. So now it is checked if the file generate_config.sh is in the directory (then it is the mailcow root directory, see https://github.com/mailcow/mailcow-dockerized) and only then "/data/assets/ssl/" is appended, in all other cases the passed variable is taken over unchanged.
Because of the RP mailcow/mailcow-dockerized#2443 I have extended the script with ECC certificates.
I adapted the reboot commands as described in the mailcow manual (https://mailcow.github.io/mailcow-dockerized-docs/firststeps-ssl/#how-to-use-your-own-certificate).
The hour for the cron job isn't really random (as is the minute),
but assuming acme.sh installation times are not correlated, neither
will be the resulting cron start times.
Commands have been reordered in showhelp() to a more consistent grouping,
help > version > install > certs > csr > account > cron > other
All option alternatives in _process() case statement have been reordered toshow the canonical variants first, legacy variants after.
- Message of failed test [1]: /root/.acme.sh/acme.sh --deactivate -d testdocker.acme.sh [FAIL]
- Reason of failure: left brace was not escaped. According to the standard [2], if special chars appear first in an ERE, it will produce undefined results.
- egrep from busybox (and thus alpine) take it as an error, but egrep from GNU grep (included in most distros) and *BSD are more tolerant, just ignore it.
- Fix: consider the right brace at the right-hand side of the ERE, the result string will not contain right brace. So the left-hand side should not contain left brace, too.
[1] 446939706e/logs/alpine-latest.out (L119)
[2] 9.4.3 ERE Special Characters, The Open Group Base Specifications. https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html
This provider relies on the the python-openstackclient and
python-designateclient tools be installed and working, with
either password or application credentials loaded in your env.
This provider relies on the the python-openstackclient and
python-designateclient tools be installed and working, with
either password or application credentials loaded in your env.
- Update openSUSE build badge file name (opensuse-leap.svg -> opensuse-leap-latest.svg)
- Update link target of build badge (letest -> acmetest), for item 1-12, 15, 16, 18, 19, 21
- Update Proxmox document link for 5.2 and later
$fulldomain could be just 'domain.duckdns.org' if provided with --domain-alias or '_acme-challenge.domain.duckdns.org' otherwise. In the latter case, '_acme-challenge' is thrown away. Correctly extract 'domain' in both cases.
The current call uses the /domains end-point which lists all domains.
This only returns 100 domains at a time, so for long domain lists you
may not match and find the required ID.
Switch to using the search interface that only returns values matching
the requested domain. This will avoid missing results.
Reported by @jjamfd.
Closes: #2944
I'm actually not entirely sure why/how this worked with curl but not wget, but it did. The short answer is that using a GET does not result in the HTTP_HEADER file being written, instead you must pass in the http_headers param ($2) which will return the HTTP headers as a string. Luckily, the Token is in both the body and the header. We need it and the id (and smid if 2fa) cookie to proceed. So now we parrse the response for that instead of the HTTP_HEADER file.
Interesting side note: wget is fine if the URL contains a \r or \n, but curl will barf on it. So we need to make sure those are stripped from the token as it will be passed in the URL later.
# GitHub Copilot Shell Scripting (sh) Review Instructions for acme.sh
## Overall Goal
Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files) for the [acme.sh](https://github.com/acmesh-official/acme.sh) project. Ensure the code exhibits the highest levels of robustness, security, and portability.
The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.
## Required Output Format
Organize the feedback into a single, structured report, using the three-level marking system:
1.**Critical Issues (Must Fix Before Merge)**
2.**Suggestions (Improvements to Consider)**
3.**Good Practices (Points to Commend)**
---
## Shell Compatibility
- **POSIX sh only** -- all scripts must target `sh`, not `bash`. No bash-isms allowed.
- **Shebang**: always use `#!/usr/bin/env sh` (not `#!/bin/sh`, not `#!/usr/bin/env bash`).
- **Use `return`, never `exit`** -- scripts are sourced, not executed as subprocesses. `exit` would kill the parent shell.
- **Cross-platform**: code must work on Linux, macOS, FreeBSD, Solaris, and BusyBox environments.
---
## Robustness and Error Handling
- **(Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:
-`set -e`: Exit immediately if a command exits with a non-zero status.
-`set -u`: Treat unset variables as an error and exit.
-`set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.
- **Always check return values** of function calls. If an error occurs, there must be a way to stop execution.
- **Return 1** after `_err` messages:
```sh
if [ -z "$VARIABLE" ]; then
_err "VARIABLE is required"
return 1
fi
```
- Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.
---
## Security and Quoting
- **(Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**.
- **(Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.
- Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.
- Avoid `eval` -- warn against and suggest alternatives, as it can lead to arbitrary code execution.
---
## Use Built-in Helper Functions
Never use raw shell commands when acme.sh provides a wrapper function. This is the most critical rule for portability.
When fixing a pattern issue, fix **all instances** in the file, not just the one highlighted.
---
## Forbidden External Tools
Do not use these commands -- they are not portable across all target platforms:
- `jq` (parse JSON with built-in string manipulation)
- `grep -A` (removed throughout the project)
- `grep -Po` (Perl regex not available everywhere)
- `rev`, `xargs`, `iconv`
- If you must depend on an external tool, check with `_exists` first:
```sh
if ! _exists jq; then
_err "jq is required"
return 1
fi
```
- Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.
---
## Configuration Management
Use the correct save/read functions depending on hook type:
- **DNS hooks**: `_readaccountconf_mutable` to read API keys, `_saveaccountconf_mutable` to save them. Do not use `_saveaccountconf` or `_readaccountconf`.
- **Notification hooks**: use account conf functions.
- Save operations should only happen in the correct lifecycle function (e.g., `_issue()`).
- Use environment variables for all configurable values -- do not introduce hardcoded config files.
- Do not clear account conf without a clear reason.
---
## DNS API Conventions
- Read the [DNS API Dev Guide](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide) before writing a DNS plugin.
- Each file under `dnsapi/` must contain a `{filename}_add` function for adding DNS TXT records.
- The `_get_root()` loop counter `i` must start from `1` (not `2`) to support DNS alias mode.
- The `dns_*_rm()` function must remove records **by TXT value**, not by replacing/updating. See [#1261](https://github.com/acmesh-official/acme.sh/issues/1261).
- Preserve the `dns_*_info` metadata variable block in each DNS script header.
---
## Variable Naming
- Use CamelCase with provider prefix: `KINGHOST_Username` (not `KINGHOST_username`).
- Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.
- Avoid confusingly similar names. Prefer one variable with comma-separated values over multiple variables (e.g., `CZ_Zones` with comma support instead of separate `CZ_Zone` and `CZ_Zones`).
- Do not define variables with the same name in different scopes.
- Variables inside functions should be declared using the `local` keyword to avoid unintentionally modifying global state.
---
## Code Style
- Use `shfmt` for formatting -- CI enforces it.
- Reduce indentation where possible.
- Single space, not double spaces.
- No trailing semicolons after `return` statements.
- Add a newline at the end of every file.
- Use `$(command)` over backticks `` `command` `` for command substitution.
---
## Simplicity
- Prefer hardcoded sensible defaults over unnecessary configuration variables (e.g., use `3600` for TTL instead of a `DESEC_TTL` variable).
- Reject over-engineered solutions. If it can be done in one line, do it in one line.
- Follow existing patterns in the codebase -- new hooks should look like existing hooks.
- Respect user choices: do not `chmod` files that already exist; the user's permissions take priority.
---
## Documentation Requirements
Before a PR can be merged, the following documentation must be provided:
- **Wiki page**: add or update the relevant page:
- DNS APIs: [dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) or [dnsapi2](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2)
added: `\`${target}\` has been added to the [Blacklist](https://github.com/${context.repo.owner}/${context.repo.repo}/wiki/Blacklist). The wiki guard will revert their recent wiki changes on its next run.`,
already: `\`${target}\` is already on the blacklist.`,
invalid: "Could not parse a valid login or email from the issue title.",
nopage: "The Blacklist wiki page does not exist."
};
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: messages[result] || "No action taken."
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: "closed",
state_reason: result === "added" ? "completed" : "not_planned"
if (blacklist.includes(item.user.login.toLowerCase())) {
if (context.payload.pull_request) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: item.number,
state: "closed"
});
} else {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
state: "closed",
state_reason: "not_planned"
});
}
return;
}
if (context.payload.pull_request) {
return;
}
const issue = context.payload.issue;
if (issue.title.startsWith("blacklist:") || issue.title.startsWith("revert:")) {
// Handled by the Blacklist / Revert Command workflows.
return;
}
if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
// Tracking issue for a third-party dns/deploy/notify api:
// no upgrade boilerplate; assign it to the opener, label it,
// then close it right away to keep the issue list clean. Any
// later comment reopens it (see the issue_comment handler).
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: [issue.user.login]
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ["3rd party api"]
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: "Closing this tracking issue for now to keep the issue list clean. It remains the place to report problems with this provider -- if you hit a bug, comment here and the issue will be reopened."
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: "closed",
state_reason: "completed"
});
return;
}
await github.rest.issues.createComment({
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. Before posting the log, review it and REDACT any secrets: private keys (`-----BEGIN ... PRIVATE KEY-----` blocks), API tokens and passwords."
echo "[Click here to Revert](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=revert%3A+${page_sha}&body=Revert+wiki+commit+${page_sha}+by+@${actor}.)"
echo ""
echo "[Click here to Blacklist @$actor](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=blacklist%3A+${actor}&body=Blacklist+@${actor},+requested+from+the+wiki+monitor.)"
- if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi
<a href="https://opencollective.com/acmesh" alt="Financial Contributors on Open Collective"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" /></a> [](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-An ACME protocol client written purely in Shell (Unix shell) language.
- Full ACME protocol implementation.
- Support ACME v1 and ACME v2
- Support ACME v2 wildcard certs
- Simple, powerful and very easy to use. You only need 3 minutes to learn it.
- Bash, dash and sh compatible.
- Simplest shell script for Let's Encrypt free certificate client.
- Purely written in Shell with no dependencies on python or the official Let's Encrypt client.
- Just one script to issue, renew and install your certificates automatically.
- DOES NOT require `root/sudoer` access.
- Docker friendly
- IPv6 support
- Cron job notifications for renewal or error etc.
<h1 align="center">🔐 acme.sh</h1>
<h3 align="center">An ACME Protocol Client Written Purely in Shell</h3>
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" alt="Financial Contributors on Open Collective"></a>
<a href="https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/acme-sh/Lobby.svg" alt="Join the chat at Gitter"></a>
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/stars/neilpang/acme.sh.svg" alt="Docker stars"></a>
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/pulls/neilpang/acme.sh.svg" alt="Docker pulls"></a>
|4|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
|8|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Alpine Linux (with curl)
|11|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Kali Linux
|12|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Oracle Linux
|19|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Gentoo Linux
|2|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)|Windows (cygwin with curl, openssl and crontab included)
|15|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|18|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|19|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|21|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
|22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|24|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers.
You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future.
**Apache** example:
> ⚠️ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder — they are for internal use only, the folder structure may change in the future.
@@ -212,95 +301,93 @@ The ownership and permission info of existing files are preserved. You can pre-c
Install/copy the cert/key to the production Apache or Nginx path.
The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`.
> 🔄 The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically.
> ⚠️ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert.
**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.**
---
# 4. Use Standalone server to issue cert
### 4️⃣ Use Standalone Server to Issue Certificate
**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))**
> 🔐 Requires root/sudoer or permission to listen on port **80** (TCP)
Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
> ⚠️ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
**This apache mode is only to issue the cert, it will not change your apache config files.
You will need to configure your website config files to use the cert by yourself.
We don't want to mess with your apache server, don't worry.**
> 💡 **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry!
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
**(requires you to be root/sudoer, since it is required to interact with Nginx server)**
### 7️⃣ Use Nginx Mode
If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`.
> 🔐 Requires root/sudoer to interact with Nginx server
Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your webroot folder.
If you are running a web server, it is recommended to use the `Webroot mode`.
Just set string "nginx" as the second argument.
Particularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder.
It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version.
So, the config is not changed.
It will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed.
**This nginx mode is only to issue the cert, it will not change your nginx config files.
You will need to configure your website config files to use the cert by yourself.
We don't want to mess with your nginx server, don't worry.**
> 💡 **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry!
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
@@ -326,72 +413,154 @@ Then just rerun with `renew` argument:
acme.sh --renew -d example.com
```
Ok, it's done.
✅ **Done!**
**Take care, this is dns manual mode, it cannot be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.**
> ⚠️ **WARNING:** This is DNS manual mode — it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.**
**Please use dns api mode instead.**
---
# 10. Issue ECC certificates
### 🔟 Use DNS Persist Mode
`Let's Encrypt` can now issue **ECDSA** certificates.
Just set the `keylength` parameter with a prefix `ec-`.
DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
| `--server <ca>` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. |
| `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. |
| `--dns-persist-ca-name <name>` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed — you only need to add **any one** of them. |
| `--dns-persist-days <N>` | Adds `persistUntil=<unix-timestamp>` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. |
Please look at the `keylength` parameter above.
Valid values are:
1.**ec-256 (prime256v1, "ECDSA P-256")**
2.**ec-384 (secp384r1, "ECDSA P-384")**
3.**ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
# 11. Issue Wildcard certificates
It's simple, just give a wildcard domain as the `-d` parameter.
If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**:
| What | When | Why |
|------|------|-----|
| 🔍 **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) |
| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant |
| 🔗 **Sends `replaces=<certID>`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 §5) |
| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) |
**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true:
1.`--force` is given
2. The CA's **ARI `suggestedWindow` has started**
3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI)
You can see the resulting next renewal time (already ARI-picked when applicable) in:
```sh
acme.sh --info -d example.com
# Look for: Le_NextRenewTimeStr=...
```
For the live ARI window the CA is currently advertising, run with `--debug 2`:
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]
@@ -482,18 +658,43 @@ Support this project with your organization. Your logo will show up here with a
> This repository is officially maintained by <strong>ZeroSSL</strong> as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!
> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com.
>
> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL.
_err "Refusing to upload certificate material because this hook passes PublicKey/PrivateKey as request parameters."
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
_err "Please upload the certificate to BytePlus manually for the initial deployment, set BYTEPLUS_CERT_ID_${_conf_key} to that CertificateId, and rerun."
_err "This hook stores CertificateId values per domain using deployconf, so the variable name must include the current domain-specific suffix."
_err "This hook must be updated to send PublicKey and PrivateKey in a POST body before automatic first-time upload can be enabled safely."
_info "Replacing old certificate '$_old_cert_id' (UpdateMode=new)..."
_err "Refusing to replace certificate material because this hook passes PublicKey/PrivateKey as request parameters."
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
_err "Please replace the certificate in BytePlus manually for renewal until this hook is updated to send PublicKey and PrivateKey in a POST body safely."
_err "yq is not installed! Please install yq and try again."
return1
fi
_debug3 "yq is installed."
# Check if deploy file exists
found_file=""
for file in "$@";do
if _startswith "$file""/";then
_multideploy_path="$file"
else
_multideploy_path="$DOMAIN_PATH/$file"
fi
_debug3 "Checking file""$_multideploy_path"
if[ -f "$_multideploy_path"];then
_debug3 "File found"
if[ -n "$found_file"];then
_err "Multiple deploy files found. Please keep only one deploy file."
return1
fi
found_file="$_multideploy_path"
else
_debug3 "File not found"
fi
done
if[ -z "$found_file"];then
_err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one."
return1
fi
if ! _check_deployfile "$found_file";then
_err "Deploy file is not valid: $found_file"
return1
fi
echo"$found_file"
}
# Description:
# This function checks the deploy file for version compatibility and the existence of the specified configuration and services.
# Arguments:
# $1 - The path to the deploy configuration file.
# $2 - The name of the deploy configuration to use.
# This deploy hook is tested on OpenMediaVault 5.x. It supports both local and remote deployment.
# The way it works is that if a cert with the matching domain name is not found, it will firstly create a dummy cert to get its uuid, and then replace it with your cert.
#
# DEPLOY_OMV_WEBUI_ADMIN - This is OMV web gui admin account. Default value is admin. It's required as the user parameter (-u) for the omv-rpc command.
# DEPLOY_OMV_HOST and DEPLOY_OMV_SSH_USER are optional. They are used for remote deployment through ssh (support public key authentication only). Per design, OMV web gui admin doesn't have ssh permission, so another account is needed for ssh.
_err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs."
return1
else
_debug "Using saved env variables."
fi
else
_debug "Detected ENV variables to be saved to the deploy conf."
# Encrypt and save user
_savedeployconf PANOS_USER "$PANOS_USER"1
_savedeployconf PANOS_PASS "$PANOS_PASS"1
_savedeployconf PANOS_HOST "$PANOS_HOST"1
_panos_user="$PANOS_USER"
_panos_pass="$PANOS_PASS"
_panos_host="$PANOS_HOST"
# VALID FILE CHECK
if[ ! -f "$_ckey"]||[ ! -f "$_cfullchain"];then
_err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying."
return1
fi
_debug "Let's use username and pass to generate token."
_debug "Attempting to load variable PANOS_TEMPLATE_STACK from file."
_getdeployconf PANOS_TEMPLATE_STACK
fi
# PANOS_TEMPLATE_STACK
if["$PANOS_VSYS"];then
_debug "Detected ENV variable PANOS_VSYS. Saving to file."
_savedeployconf PANOS_VSYS "$PANOS_VSYS"1
else
_debug "Attempting to load variable PANOS_VSYS from file."
_getdeployconf PANOS_VSYS
fi
# PANOS_CERTNAME
if["$PANOS_CERTNAME"];then
_debug "Detected ENV variable PANOS_CERTNAME. Saving to file."
_savedeployconf PANOS_CERTNAME "$PANOS_CERTNAME"1
else
_debug "Attempting to load variable PANOS_CERTNAME from file."
_getdeployconf PANOS_CERTNAME
fi
#Store variables
_panos_host=$PANOS_HOST
_panos_user=$PANOS_USER
_panos_pass=$PANOS_PASS
_panos_key=$PANOS_KEY
_panos_template=$PANOS_TEMPLATE
_panos_template_stack=$PANOS_TEMPLATE_STACK
_panos_vsys=$PANOS_VSYS
_panos_certname=$PANOS_CERTNAME
#Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
if["$_panos_host"]&&["$_panos_key"];then
_debug "**** Testing API KEY ****"
deployer keytest
fi
# Check for valid variables
if[ -z "$_panos_host"];then
_err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs."
return1
else
_debug "Getting PANOS KEY"
deployer keygen
# Use certificate name based on the first domain on the certificate if no custom certificate name is set
# Generate a new API key if no valid API key is found
if[ -z "$_panos_key"];then
_err "Missing apikey."
if[ -z "$_panos_user"];then
_err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs."
return1
elif[ -z "$_panos_pass"];then
_err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs."
return1
fi
_debug "**** Generating new PANOS API KEY ****"
deployer keygen
_savedeployconf PANOS_KEY "$_panos_key"1
fi
# Confirm that a valid key was generated
if[ -z "$_panos_key"];then
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
return1
else
deployer cert
deployer key
deployer commit
# A commit of a failed import would leave a mismatched cert/key pair
# on the firewall and can lock the admin out of the management
# interface, see https://github.com/acmesh-official/acme.sh/issues/4716
if ! deployer cert;then
_err "Cert import failed. Aborting without committing."
return1
fi
if ! deployer key;then
_err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config."
return1
fi
if ! deployer commit;then
return1
fi
if["$_panos_template_stack"];then
# try to get job status for 20 times in 30 sec interval
_err "ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate."
return1
elif["$_ul_version_minor" -lt "13"];then
_err "Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+."
return1
fi
_debug "ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor."
_err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun."
return1
fi
fi
_debug "Creating temp admin user in Synology DSM..."
if synogroup --help | grep -q '\-\-memberadd ';then
_err "Certificate upload rejected as a duplicate (server reported USER_CERTIFICATE_DUPLICATE), but no existing entry matching this fingerprint was found."
_err "Response: $_create_json"
return1
fi
# Reusing the existing entry rather than deleting it and re-uploading
# under today's name+timestamp: the served content is identical either
# way, so replacing it would only cost an extra delete+create round trip
# for no functional benefit. The tradeoff is cosmetic -- this entry keeps
# whatever name it was given whenever it was originally uploaded, so it
# won't reflect today's date in the UI.
_info "Certificate already present as entry $_new_id; reusing it."
One984HOSTING_TOTP_Secret Base32 TOTP shared secret. Required only if the account has 2FA enabled. Requires oathtool. Used to mint the OTP code automatically at login so cron renewals keep working.
if _active24_rest POST "dns/$_domain/txt/v1""{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}";then
if _contains "$response""errors";then
if _active24_rest POST "/v2/service/$_service_id/dns/record""{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}";then
if _contains "$response""error";then
_err "Add txt record error."
return1
else
@@ -24,6 +31,7 @@ dns_active24_add() {
return0
fi
fi
_err "Add txt record error."
return1
}
@@ -37,19 +45,25 @@ dns_active24_rm() {
_active24_init
_debug "Getting txt records"
_active24_rest GET "dns/$_domain/records/v1"
# The API needs to send data in body in order the filter to work
# TODO: web can also add content $txtvalue to filter and then get the id from response
_active24_rest GET "/v2/service/$_service_id/dns/record""{\"page\":1,\"descending\":true,\"sortBy\":\"name\",\"rowsPerPage\":100,\"totalRecords\":0,\"filters\":{\"type\":[\"TXT\"],\"name\":\"${_sub_domain}\"}}"
#_active24_rest GET "/v2/service/$_service_id/dns/record?rowsPerPage=100"
}| sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g'
Some files were not shown because too many files have changed in this diff
Show More
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.