Compare commits

..
852 Commits
Author SHA1 Message Date
neil 3661fd86b6 Merge pull request #7132 from acmesh-official/dev
sync
2026-07-17 10:01:37 +08:00
neil 745a42f193 minor 2026-07-16 20:09:10 +08:00
neil 3faf65c46d fix https://github.com/acmesh-official/acme.sh/issues/1940#issuecomment-4971257867 2026-07-15 20:41:35 +08:00
neil 7cc16cd09a Merge pull request #7126 from acmesh-official/dev
sync
2026-07-14 22:35:07 +08:00
neil 1dbabf0da9 valid-to: allow a negative --days to set the renewal margin
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
2026-07-14 21:12:55 +08:00
Avi Miller 2499ee1ab9 fix(dns_oci): read ~/.oci/config before cached account.conf values (#7124)
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>
2026-07-14 20:58:47 +08:00
NotAnotherHelloWorld 1a746d98b8 Deploy certificate to FortiGate firewall using API (#6236)
* 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
2026-07-14 09:02:34 +08:00
neil 4558a8aa98 challenge: use portable [{] literal-brace in _egrep_o patterns (#968)
_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).
2026-07-13 11:59:35 +08:00
neil 1a54307dbf account: keep restored ACCOUNT_URL in the EAB-already-bound path
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
2026-07-13 10:43:08 +08:00
neil 020123d812 dns_infomaniak: log zones response and fail early in _get_zone
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
2026-07-13 08:30:43 +08:00
neil 17964cfd6e installcronjob: Solaris grep takes only one -e, use separate greps
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).
2026-07-12 22:28:56 +08:00
neil d621d6952a fix 2026-07-12 20:18:36 +08:00
neil 5e33e9f5f1 dns_knot: add KNOT_ZONE for delegated subdomain zones
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
2026-07-12 19:22:03 +08:00
neil 3fddea2962 dns_dynu: use mutable conf storage so newly exported credentials win
Same stale-account.conf override as dns_dnsimple: the sourced plain
copy shadowed newly exported Dynu_ClientId/Dynu_Secret.

https://github.com/acmesh-official/acme.sh/issues/3216
2026-07-12 19:21:56 +08:00
neil a6766d4186 dns_dnsimple: use mutable conf storage so a newly exported token wins
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
2026-07-12 19:21:48 +08:00
neil b9ce911eb1 installcronjob: never wipe existing cron jobs when crontab -l fails
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
2026-07-12 19:21:40 +08:00
neil 15a1067f1b update-account: persist the new email into the CA conf
"--update-account -m new@example.com" updated the contact on the CA but
never saved it locally, so CA_EMAIL kept showing the old address on
every later run. Save it in the success path like _regAccount does.

https://github.com/acmesh-official/acme.sh/issues/4673
2026-07-12 17:34:37 +08:00
neil 447dc3c7e7 dns_ali: convert IDN domains to punycode
The Aliyun API only accepts punycode domain names, and a raw UTF-8
domain also breaks the request signature. Same _idn pattern as
dns_namecom.

https://github.com/acmesh-official/acme.sh/issues/4733
2026-07-12 17:21:19 +08:00
Kat Crichton-Seager 5a8c685fd3 fix: dnsexit api rejects a TTL of zero, changed to 1 (minute) (#7107) 2026-07-12 16:37:21 +08:00
amk1969andamk dd6540ce46 dns_ovh specific permission for record removal (#6386)
Co-authored-by: amk <amk@medlin.mikus.sk>
2026-07-12 16:21:00 +08:00
Steven Qiu cd486cfbb9 Add Baidu Cloud CDN deploy hook (#6951)
* 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.
2026-07-12 16:15:59 +08:00
e828b285ad Matthiasvpfr patch 1 (#7108)
* 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>
2026-07-12 16:13:42 +08:00
ACHMAD ALIF NASRULLOH f5e7e6b225 Merge pull request #7114 from achmadalifn4/master
Add notify waha support
2026-07-12 16:10:21 +08:00
neil ebb5cc4981 deploy/synology_dsm.sh: remove bashisms, keep the hook POSIX sh
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.
2026-07-12 16:04:44 +08:00
neil 3989eef5e2 cpanel_uapi: don't spill a redirection error when the key file is absent
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
2026-07-12 13:00:12 +08:00
neil 2c51ac1c27 dns_dynu: include the server response in the authentication error
Same as the dns_cloudns change: a bare "Authentication failed" hides
the actual API error and makes reports undiagnosable.
2026-07-12 12:45:10 +08:00
neil eacf0d6a87 issue bot: tell reporters to redact secrets before posting logs
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.
2026-07-12 12:32:54 +08:00
neil 099d88e6a9 dns_knot: KNOT_KEY takes the TSIG key data, not a file path
The knsupdate "key" statement wants "[alg:]name secret"; the info block
wrongly described the option as a file path.

https://github.com/acmesh-official/acme.sh/issues/6293
2026-07-12 12:22:28 +08:00
neil 897e219743 issue: never schedule the default renewal past the cert expiry
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
2026-07-12 12:12:18 +08:00
neil a9590c5bd7 workflows: run issue/wiki automation only in the upstream repo 2026-07-12 11:49:53 +08:00
neil c0d62eb934 wiki-guard: skip on forks (no <fork>.wiki repo to check out) 2026-07-12 11:43:07 +08:00
neil 44c045b056 notify: add the customscript hook
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
2026-07-12 11:36:45 +08:00
neil e94631de44 dns_pdns: probe zones with the server-side name filter in _get_root
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
2026-07-12 11:20:53 +08:00
neil a49f8c1992 mydevil: replace BSD-only cut -w with tr + plain cut
cut -w (split on whitespace) is a FreeBSD extension unknown to GNU
coreutils; squeeze blanks into tabs with tr first so the field
extraction is POSIX.

https://github.com/acmesh-official/acme.sh/issues/6452
2026-07-12 10:49:49 +08:00
neil 7d0283ca2c dns_zonomi: make the API endpoint configurable via ZM_Api
RimuHosting (which owns zonomi) exposes the identical API at
https://rimuhosting.com/dns/dyndns.jsp; an overridable endpoint serves
both providers with one hook. The default stays zonomi.com and is not
written to the account conf.

https://github.com/acmesh-official/acme.sh/issues/6475
2026-07-12 10:26:40 +08:00
neil 3e64b5e6f5 Merge pull request #7119 from acmesh-official/dev
sync
2026-07-12 10:11:32 +08:00
neil 76811857a0 dns_cpanel: quote inner expansion in suffix strip (SC2295) 2026-07-12 09:35:22 +08:00
neil 22a5ae3ceb dns_cloudns: include the server response in the login error
https://github.com/acmesh-official/acme.sh/issues/6520
2026-07-12 09:34:59 +08:00
neil 04e0422526 precheck: log the socat recommendation with _info instead of _err
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
2026-07-12 09:30:46 +08:00
neil 2215f1b988 notify: clear inherited _H1.._H5 before running each notify hook
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
2026-07-11 11:56:58 +08:00
neil 9366c2e065 dns_cpanel: resolve the most specific zone in _get_root
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
2026-07-11 11:42:32 +08:00
Sasha Reid 2e4acba105 Microwavenby dns hostinger (#6843)
* [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
2026-07-11 10:04:49 +08:00
Mike Lei fef90e15e1 Fix name.com DNS API for processing IDNs (#4381) 2026-07-10 21:56:54 +08:00
Roman Lumetsberger 50e5e771d5 Feature: Support other shells then sh (#4877)
* Add support for DEPLOY_SSH_REMOTE_SHELL

* allow to configure quoting of  remote cmd string

* shell check and shellfmt fixes
2026-07-10 21:02:06 +08:00
CV ebde8345ae dns_ispconfig.sh client_id not numeric at ispconfig v 3.2.7p1 (#4999)
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'```.
2026-07-10 20:55:23 +08:00
neil 58cd667d65 dns_njalla.sh: accept string record ids when removing records
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
2026-07-10 20:47:31 +08:00
Andrew V. 98c30912fb 2024-12-24 - Ensure that $PDNS_Url has no trailing slash ('/'). (#6171) 2026-07-10 20:36:42 +08:00
neil 2058a77d83 acme.sh: fix variable name typo _excapedAlgnames -> _escapedAltnames
from https://github.com/acmesh-official/acme.sh/pull/6547
2026-07-10 19:37:36 +08:00
neil 660a5e322c deploy/synology_dsm.sh: use grep -Eo '[0-9]+' when extracting error codes
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
2026-07-10 18:37:32 +08:00
Jan Pieper 2af543a358 Fix typo (#6924) 2026-07-10 14:42:57 +08:00
neil fa763db105 dns_pleskxml.sh: use grep -F when matching interpolated values
fulldomain/txtvalue/root_domain_name were interpolated into grep
regex patterns; match them as fixed strings instead.

from https://github.com/acmesh-official/acme.sh/pull/7031
2026-07-10 12:05:28 +08:00
neil 90b4795bb1 issue: strip the trailing dot of a fully-qualified alias domain
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
2026-07-10 11:22:20 +08:00
neil 534a1714dc dns_me.sh: use LC_ALL=C so the request date header is always English
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.
2026-07-10 11:20:08 +08:00
neil 45c0ad4112 Add _cleardeployconf to clear deploy hook keys from domain conf
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.
2026-07-10 11:05:40 +08:00
neil 83b52e0cd7 notify/smtp.sh: add --crlf so curl sends CRLF line endings
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
2026-07-10 10:38:52 +08:00
neil bed15ba844 dns_freedns.sh: use grep -E, BRE \| alternation is a GNU extension
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
2026-07-10 10:23:59 +08:00
invario 1324dcd472 Docker: update crontab used (#7111)
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2026-07-10 10:10:44 +08:00
cf3eab95ee Add creoline API as DNS provider (#7100)
* 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>
2026-07-10 10:07:12 +08:00
neil ca118be754 issue.yml: match tracking issue title variants 2026-07-10 10:02:59 +08:00
Marvo2011 b1b539695f Merge pull request #7026 from Marvo2011/dev
Update SelfHost DNS provider
2026-07-06 21:30:13 +08:00
neil 347cc207cd Merge pull request #7096 from acmesh-official/dev
one-click revert and ban
2026-07-06 17:40:51 +08:00
neil cc64a73230 one-click revert and ban 2026-07-06 17:39:56 +08:00
neil 1651a5a609 Merge pull request #7095 from acmesh-official/dev
sync
2026-07-06 16:37:03 +08:00
neil f2b37b32ff add more events 2026-07-06 16:36:16 +08:00
neil 73df21abc4 clean 2026-07-06 16:30:41 +08:00
neil 0c76c1f211 Merge pull request #7094 from acmesh-official/dev
wiki-guard: use WIKI_GUARD_TOKEN (PAT with read:org) to enumerate org…
2026-07-06 16:28:12 +08:00
neil 919492df13 wiki-guard: use WIKI_GUARD_TOKEN (PAT with read:org) to enumerate org write members 2026-07-06 16:27:26 +08:00
neil e00d3cfde3 Merge pull request #7092 from acmesh-official/dev
wiki-guard: log the number of write-access members loaded
2026-07-06 15:24:23 +08:00
neil e4eaa59063 wiki-guard: log the number of write-access members loaded 2026-07-06 15:23:45 +08:00
neil 4187ec23c1 Merge pull request #7091 from acmesh-official/dev
wiki-guard: trust repo/org members with write access in all rule checks
2026-07-06 15:12:22 +08:00
neil 2b5a19d34a wiki-guard: trust repo/org members with write access in all rule checks 2026-07-06 15:11:35 +08:00
neil 9f0ef7abcd Merge pull request #7090 from acmesh-official/dev
sync
2026-07-06 14:28:08 +08:00
neil dc1b06006f issue.yml: assign and label "Report bugs to" tracking issues instead of posting the upgrade boilerplate 2026-07-06 14:27:00 +08:00
neil 8585d9f4a7 wiki-monitor: skip notification for the maintainer's own wiki changes 2026-07-06 14:23:51 +08:00
neil 0e2659b768 Merge pull request #7088 from acmesh-official/dev
add wiki-guard workflow: auto-restore wiki pages deleted or renamed b…
2026-07-06 13:37:00 +08:00
neil ca8ab7f8b5 add wiki-guard workflow: auto-restore wiki pages deleted or renamed by non-maintainer 2026-07-06 13:35:37 +08:00
neil f50401a342 Merge pull request #7086 from acmesh-official/dev
sync
2026-07-06 10:37:29 +08:00
Simon V.andneil ff9b969bdb Add support for Account Key Rollover (#7080)
* 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>
2026-07-06 10:22:36 +08:00
Oliver MuellerandClaude Opus 4.8 1f778e6ef1 deploy/ssh: return non-zero when a server deployment fails (#6795)
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>
2026-07-06 10:11:58 +08:00
PM Extra 58423df3e8 retry failed install and deploy on renew (#7083)
* retry failed install and deploy on renew

* fix notify level for renew retry failures
2026-07-05 23:33:57 +08:00
8f3c1701f3 Add LaoDC DNS API (dns_laodc) (#6974)
* 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>
2026-07-05 23:30:26 +08:00
neil 2a175f97e8 toPkcs8: support --password and re-export the pkcs8 file on renewal (#4134) 2026-07-05 18:04:24 +08:00
neil d3af3315da dnsapi/dns_edgedns: use the system clock for the request timestamp (#3973) 2026-07-05 18:02:34 +08:00
neil 7b6d96387c migrate the legacy ACMEDNS_UPDATE_URL from the account conf (#3899) 2026-07-05 17:48:33 +08:00
neil cabe432539 add bash completion for commands and parameters, installed via --install (#307) 2026-07-05 17:39:50 +08:00
neil 1746fbdb25 support multiple account emails (#1309)
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.
2026-07-05 17:37:40 +08:00
neil d2b3772631 deploy/panos: do not commit when the cert or key import failed (#4716)
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.
2026-07-05 17:13:59 +08:00
neil e964157bff _install_win_taskscheduler: zero-pad the minute in the schtasks /ST value (#4950) 2026-07-05 17:13:24 +08:00
neil 934711e51d notify/aws_ses: add container/instance IAM role auth (IMDSv2)
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
2026-07-05 17:09:51 +08:00
neil 7def43481a dns_regru: require a dot boundary in root zone matching
_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
2026-07-05 16:57:59 +08:00
neil defd64022d dnsapi/dns_namecom: probe the root zone with GetDomain instead of listing all domains
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
2026-07-05 16:53:14 +08:00
neil 4256e3532b _regAccount: error out clearly when the eab-hmac-key cannot be base64-decoded
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
2026-07-05 16:32:57 +08:00
neil 1f94fd7fd5 add Apache workflow to test the --apache mode
Runs le_test_apache from acmetest against Pebble, with Apache
listening on Pebble's HTTP-01 validation port.
2026-07-05 16:31:51 +08:00
neil 507baff2ef deploy/docker: allow setting key file mode and owner in the container
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
2026-07-05 16:30:46 +08:00
neil 504540e67c dnsapi/dns_autodns: escape XML special characters in credentials (#5317) 2026-07-05 16:29:58 +08:00
neil cacafc9c23 add Nginx workflow to test the --nginx mode
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.
2026-07-05 16:18:37 +08:00
laineus 24895a15c8 Add dns_muumuu: muumuu-domain.com DNS API (#7012)
* 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
2026-07-05 16:05:33 +08:00
neil 1cd63e1480 _createcsr: omit CN from the CSR subject when it exceeds 64 characters (#4867) 2026-07-05 16:02:52 +08:00
neil 31b13caf8b DNS.yml: fix workflow warnings
- 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)
2026-07-05 15:51:38 +08:00
neil 1e2cd50fc9 deploy/haproxy: use printf instead of "echo -e" for the stats socket payload
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
2026-07-05 15:44:26 +08:00
Jan Forman 524d96a3a8 Add WEDOS WAPI DNS API (dns_wedos) (#7072)
* 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
2026-07-05 12:44:07 +08:00
Foster Snowhill 0eb5cc8384 dns_desec: fix advertised token variable name (#7081)
This must've been a copy-paste error from `dns_ddnss`.

Fixes: 6b7b5caf54 ("DNS provider API: structured description")
2026-07-05 12:03:48 +08:00
neil 77047eb0ef fix CSR reading on systems without a default openssl.cnf (e.g. NetBSD)
"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.
2026-07-04 23:55:16 +08:00
neil 4978782fb8 renewAll: error out if CERT_HOME is not a directory
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
2026-07-04 23:40:31 +08:00
neil 9764f67619 dns_cn: convert IDN domain to punycode before API calls
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
2026-07-04 21:58:57 +08:00
neil 988afd0f59 _isIPv4: do not glob segments, require exactly 4 octets
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
2026-07-04 21:34:46 +08:00
Ramon b92516f79e add application/json to acmedns (#5066) 2026-07-04 21:07:18 +08:00
neil fbf3b41c54 dns_inwx: fix _get_root false zone match for single-letter subdomains
_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
2026-07-04 20:28:56 +08:00
neil ede9a86d46 Accept both 401 and 403 for deactivated account detection
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
2026-07-04 20:21:27 +08:00
xiaopc 843a7efa7d fix(gcore_cdn): renew login api url (#5143)
https://api.gcore.com/docs/iam#tag/Account
2026-07-04 20:14:32 +08:00
neil d3e12694b9 fix "identifiers are duplicated" when signing a CSR with a wildcard CN also present in SAN
_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
2026-07-04 19:56:37 +08:00
neil a105126063 _date2time: pass date via argv to python to prevent code injection (#6463)
https://github.com/acmesh-official/acme.sh/issues/6463
2026-07-04 19:14:36 +08:00
neil 917bebd460 dns_huaweicloud: add optional HUAWEICLOUD_Region (default ap-southeast-1)
The DNS endpoint and IAM token scope project were hardcoded to
ap-southeast-1, which fails for accounts without that region enabled.

fix https://github.com/acmesh-official/acme.sh/issues/5302
2026-07-04 19:06:46 +08:00
Clément Gouin 33704fc274 Allow creation of ACME account with EAB directly from --issue command (#5087)
* formalized _eab_id and _eab_kid and added EAB parameters to _regAccount on --issue

* Update acme.sh

* Update acme.sh
2026-07-04 18:54:07 +08:00
neil bcbfe25d08 haproxy.sh: use two-argument -header form for LibreSSL (#3438) 2026-07-04 18:50:29 +08:00
neil bbfb6f50ae deploy/cpanel_uapi: strip YAML double quotes around wildcard domains in list_domains output
fix https://github.com/acmesh-official/acme.sh/issues/6115
2026-07-04 18:46:59 +08:00
neil 6df2d9e451 dns_da: document that special characters in DA_Api credentials must be percent-encoded
https://github.com/acmesh-official/acme.sh/issues/3468
2026-07-04 18:19:45 +08:00
neil 1a36823461 https://github.com/acmesh-official/acme.sh/issues/3201 2026-07-04 16:55:49 +08:00
wardhusandWard 8f2a476d21 Add Calrissia.be API (#6811)
Co-authored-by: Ward <ward.hus@calrissia.com>
2026-07-04 16:54:05 +08:00
Simon V.andZeroSSL-Andreas e64529ab50 ARI - Add support for Mass Revocation (#6953)
* 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>
2026-07-04 16:50:55 +08:00
neil 7653eaab31 fix https://github.com/acmesh-official/acme.sh/issues/4879#issuecomment-2942728895 2026-07-04 11:44:32 +08:00
neil 6cd0c00a21 extract authorizations parsing into _authorizations_from_order, fix IPv6 urls (#6326) 2026-07-04 11:23:11 +08:00
neil b4de9e8621 fix docker deploy hook on podman, check exec ExitCode instead of response body (#4977) 2026-07-04 10:49:32 +08:00
neil 0a6abaf8a1 fix https://github.com/acmesh-official/acme.sh/issues/6388 2026-07-04 00:39:52 +08:00
Laurent GrawetandMatt Simerson f4dc9fd9d1 haproxy.sh: allows certificate deployment to multiple hosts (#5180)
* haproxy.sh: allows certificate deployment to multiple hosts

* Update deploy/haproxy.sh

Co-authored-by: Matt Simerson <matt@tnpi.net>

* Update deploy/haproxy.sh

Co-authored-by: Matt Simerson <matt@tnpi.net>

---------

Co-authored-by: Matt Simerson <matt@tnpi.net>
2026-07-04 00:35:02 +08:00
Artur Klauser 2229330c48 Fix typo in synology_dsm.sh (#6406)
Fix typo in an error message.
2026-07-04 00:32:45 +08:00
neil 0df051577c fix https://github.com/acmesh-official/acme.sh/issues/6609 2026-07-04 00:06:26 +08:00
neil 7fb40f0ccf fix https://github.com/acmesh-official/acme.sh/issues/6609 2026-07-03 23:55:23 +08:00
neil 780f2ad5dc dns_ali: do not rely on "_url_encode upper-hex" so the signature works with older bundled libraries (e.g. Proxmox VE) https://github.com/acmesh-official/acme.sh/issues/6272 2026-07-03 23:44:22 +08:00
szakharchenko adf69c4e7e dns_aws: Fix invalid domain logging: _error => _err (#6430)
acme.sh defines _err, not _error.
2026-07-03 23:40:39 +08:00
neil ac5624536b dns_gd: fix root zone detection for API-restricted accounts https://github.com/acmesh-official/acme.sh/issues/4487 2026-07-03 23:34:27 +08:00
neil b974bbd6d6 fix upgrade with a relative --home path https://github.com/acmesh-official/acme.sh/issues/6477 2026-07-03 23:34:27 +08:00
szakharchenko 0ce8c24736 dev_mythic_beasts: Fix header name: Accepts => Accept (#6428) 2026-07-03 23:25:38 +08:00
neil 20254cbaf0 dns_dnsimple: support user tokens (dnsimple_u_*) https://github.com/acmesh-official/acme.sh/issues/6491 2026-07-03 23:02:35 +08:00
neil f4d2db64ef dns_gd: skip readback check when GoDaddy API returns UNKNOWN_DOMAIN https://github.com/acmesh-official/acme.sh/issues/6517 2026-07-03 22:49:40 +08:00
Trekky12 61400500e2 Suppress 'signal process started' message when nginx config is restored (related to issue #4995) (#6747) 2026-07-03 22:31:31 +08:00
neil 92a1b47108 forbid spaces in the --home/--config-home path https://github.com/acmesh-official/acme.sh/issues/2163 2026-07-03 22:26:50 +08:00
neil 5038d12d62 forbid using --days together with --valid-to https://github.com/acmesh-official/acme.sh/pull/6572 2026-07-03 21:55:37 +08:00
magyarsz 7e7c0ee984 Merge pull request #6720 from magyarsz/dev
Fix a logical error in the `renew` function
2026-07-03 21:42:52 +08:00
neil 92bd80c07d fix https://github.com/acmesh-official/acme.sh/issues/6914 2026-07-03 20:07:42 +08:00
neil ad99628e50 fix https://github.com/acmesh-official/acme.sh/issues/6917 2026-07-03 20:04:32 +08:00
neil 992886c4eb Merge pull request #7078 from acmesh-official/dev
sync
2026-07-03 19:56:09 +08:00
neil eabd23a551 fix https://github.com/acmesh-official/acme.sh/issues/6963 2026-07-03 19:38:42 +08:00
neil 1241649501 fix https://github.com/acmesh-official/acme.sh/issues/7009 2026-07-03 19:38:21 +08:00
neil 9900adb007 fix https://github.com/acmesh-official/acme.sh/issues/4756 2026-07-03 19:01:10 +08:00
ZeroSSL-Andreas 01d6d46914 New Banner
Updated README to include responsive images for dark and light modes.
2026-07-03 18:33:44 +08:00
CZECHIA-COMandClaude Opus 4.8 f038958192 fix(dns_czechia): read _normalizeJson input from stdin, not as an argument (#7077)
_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>
2026-07-03 18:12:25 +08:00
fdf528c26c feat: Add Volcano Engine DNS API (#7069)
* 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>
2026-07-03 10:09:32 +00:00
Simon V.andZeroSSL-Andreas ced8d72808 ARI - Run cron job more frequently (#6939)
* 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>
2026-07-03 15:12:32 +08:00
neil 116c05fbff fix https://github.com/acmesh-official/acme.sh/issues/6400 2026-07-02 23:30:56 +08:00
neil 42e13fa797 fix https://github.com/acmesh-official/acme.sh/issues/6851 2026-07-02 23:18:26 +08:00
neil ccd2f04c33 fix https://github.com/acmesh-official/acme.sh/issues/6986 2026-07-02 23:15:41 +08:00
MarFri a50fad865f DNS_IONOS double sending content type & case sensitive mismatch (#7028)
* double sending content type results in error from ionos

* Normalize fulldomain to lowercase in the _ionos_get_record function.
2026-07-02 23:08:59 +08:00
Toni Karppi 5433ea86c8 Add Glesys dnsapi provider (#7059)
* 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
2026-07-02 13:10:29 +08:00
neil e52d75d762 fix https://github.com/acmesh-official/acme.sh/issues/7062 2026-07-02 13:09:59 +08:00
Manwe SulimoandManwe-Sulimo cb40b47a4e Implemented support for Aruba Business DNS API (#7042)
* Add support for arubabusiness api

* Fix formatting

* record names are always converted to lowercase

* Docs

* remove leftover unconditional authentication call
lowercase
urlencoded body + x-www-form-urlencoded content-type
cleanup header variables
cleanup typos
grammar

* Strengthen _ab_rest failure checks
Properly process parallel lists in _ab_dns_record_id
Remove hard fails when a txt record already exists

* fix json parsing

* Fix formatting

---------

Co-authored-by: Manwe-Sulimo <blarghonaut@gmail.com>
2026-07-02 13:06:41 +08:00
pxMan79andneil b039ff3087 fix(dns_baidu): prefer new Baidu DNS API with legacy BCD fallback (#6992)
* 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>
2026-07-02 13:02:30 +08:00
bluenenschlossandbluenenschloss c83eed4994 dns_inwx: fix IDN zone detection without python dependency (#7056)
* 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>
2026-07-02 13:00:49 +08:00
neil 81100db2f3 fix https://github.com/acmesh-official/acme.sh/issues/6498 2026-07-01 21:59:41 +08:00
neil a7ccfcf91d fix 2026-07-01 21:33:24 +08:00
neil d0fcafe29b fix https://github.com/acmesh-official/acme.sh/issues/7035 2026-07-01 21:22:36 +08:00
Jonas Zürcherandneil ae8ef74c4b fix(dns_infomaniak): correctly detect API errors (#7048)
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>
2026-07-01 21:05:07 +08:00
SirNomad49andneil 0925d5c522 Adding custom Port definitions for truenas (#7033)
* closing bracket and adding port for customer installations

* adding savedeployconfig

* fixing shfmt

* changeing

---------

Co-authored-by: neil <github@neilpang.com>
2026-07-01 20:55:55 +08:00
hostup 0d53d29f7e Update dns_hostup.sh to v2 API (#7014)
* Update dns_hostup.sh

Update to v2 api support; developer.hostup.se

* Update dns_hostup.sh
2026-07-01 20:44:21 +08:00
neil c38182897d fix ghostbsd 2026-07-01 18:59:14 +08:00
Alexander Stehlik 2998106bd1 fix(dns_desec): fix rate limit and compatibility issues (#7027)
* 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
2026-07-01 18:54:24 +08:00
Jeroen MoorsandJeroen Moors b3579ff18d Implement support for DNS Level27 (#7043)
* 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>
2026-06-29 01:27:41 +08:00
Jakub Kołodziejczak 0dc97187e1 docs: introduce contributing doc (#7052)
prevents friction and frustrations like in issue #7050

closes #7050
2026-06-29 00:41:15 +08:00
ZeroSSL-Andreas d60c75b4e3 New Banner
Updated README to include responsive images for dark and light modes.
2026-06-19 17:06:57 +02:00
regisvidal-bitmapz 365d2d10f3 Fix dns_namesilo_rm failing to remove TXT record (#6969)
* Fixes #6907
2026-06-19 14:24:20 +02:00
SpeedGriffon db098055de Fix RouterOS deploy (#7034)
* routeros: save ROUTER_OS_ADDITIONAL_SERVICES as base64

* routeros: remove cer_3
2026-06-19 14:23:03 +02:00
neil 4575877d48 add GhostBSD 2026-06-05 23:01:42 +02:00
Bill d98fa53f62 Updated AWS Route53 service endpoint to the dual-stack endpoint (#6994)
* Update to dual-stack service endpoint
2026-06-05 22:06:49 +02:00
aitor422 9b597b3f1b Add CDMON Api (#6984)
* Added CDMon DNS API
2026-06-05 22:02:14 +02:00
rajcz 58d9c8d7f6 acme.sh: validate cert response before writing .cer (#7006) 2026-06-05 19:38:50 +02:00
Adrian Fedoreanu a2f046306e dns_1984hosting: cleanup, memoize zone id (#6978)
* dns_1984hosting: cleanup, memoize zone id, optional OTP
2026-06-05 19:28:08 +02:00
neil b463471951 fix localaddress
https://github.com/acmesh-official/acme.sh/issues/7009#issuecomment-4633681701
2026-06-05 19:22:25 +02:00
neil 0e5f1518aa upgrade 2026-06-04 22:57:50 +02:00
neil 3c848adbb7 Merge pull request #7008 from acmesh-official/dev
sync
2026-06-04 22:50:25 +02:00
neil 2e4e5d7955 add tribblix 2026-06-04 22:34:36 +02:00
terafin c7c903fba3 ci: add GitHub Container Registry (ghcr.io) publishing (#7005) 2026-06-04 21:25:38 +02:00
Thomas dfbe2c5bff fix _getAKI() on OpenBSD (#7007)
The order of the arguments does matter for OpenBSD's grep (bug or
feature).
2026-06-04 21:15:12 +02:00
neil 5713c1d39d remove dns_hetzner.sh
https://github.com/acmesh-official/acme.sh/issues/6990#issuecomment-4576551997
2026-05-30 11:48:06 +02:00
neil b7e9214e2d minor 2026-05-30 11:47:25 +02:00
Jakob Næss 206f4494ac Add Poweradmin DNS API plugin (dns_poweradmin) (#6943)
* Add Poweradmin DNS API plugin (dns_poweradmin)
2026-05-24 22:34:56 +02:00
Simon V. d9ce7fefa1 ARI - Add support for switching ACME Server during renewal (#6983)
* ARI - Add support for switching ACME Server during renewal

https://github.com/acmesh-official/acme.sh/issues/6964

* Restore old condition while adding malformed
2026-05-24 22:25:52 +02:00
Markus Ebner ce07759ced [dnsapi] add IP-Projects dns hook (#6959) 2026-05-24 22:24:00 +02:00
Tom Sommer 010bd1111a Improve Simply.com API (#6933)
Improve Simply.com API (#6933)
2026-05-24 22:20:40 +02:00
neil f265c304da Merge pull request #6968 from acmesh-official/dev
sync
2026-05-16 11:17:24 +02:00
hebbet 85408bfb4d Update renewal condition in acme.sh script 2026-05-16 11:16:20 +02:00
neil 7c91ee1fc0 Merge pull request #6949 from acmesh-official/dev
fix format
2026-05-06 20:59:36 +02:00
neil 1687cbd5b4 fix format 2026-05-06 20:58:54 +02:00
neil d2780eb04e Merge pull request #6948 from acmesh-official/dev
sync
2026-05-06 20:52:11 +02:00
neil eaf4b62ba9 fix for ari 2026-05-06 20:51:49 +02:00
neil 47378b5630 start 3.1.4 2026-05-06 20:51:49 +02:00
neil ac75c54ade support NO_ARI=1
https://github.com/acmesh-official/acme.sh/discussions/6938
2026-05-06 20:51:49 +02:00
neil 36667ab656 fix ari
https://github.com/acmesh-official/acme.sh/issues/6942#issuecomment-4382355708
2026-05-06 20:51:49 +02:00
neil 7b19070d98 fix ari
https://github.com/acmesh-official/acme.sh/issues/6942#issuecomment-4381535765
2026-05-06 20:51:49 +02:00
neil 7985319687 add wiki 2026-05-06 20:51:49 +02:00
neil 7735cdf3ab Merge pull request #6937 from acmesh-official/dev
sync
2026-05-02 10:56:54 +02:00
neil d35876e885 revert 2026-05-02 10:56:13 +02:00
Curd Becker 355b121c79 Add deployment plugin for Windows RDP via OpenSSH (#6925)
* Add deployment plugin for Windows RDP via OpenSSH
2026-05-02 10:56:13 +02:00
Alexander Sulfrian 3230d00c3d Allow renew time relative to the expiration date (#4457)
* Allow renew time relative to the expiration date
2026-05-02 10:56:13 +02:00
neil fe5490e0ec fix OpenIndiana.yml 2026-05-02 10:56:13 +02:00
neil 0d77231350 add --dns-persist-days 2026-05-02 10:56:13 +02:00
neil 8d5a5a0e0d support ARI by default 2026-05-02 10:56:13 +02:00
neil cbb8e9068c support dns-persist-01 2026-05-02 10:56:13 +02:00
nsantorelliandNicolas Santorelli 96f87844cd Add EuroDNS DNS API plugin (dns_eurodns) (#6903)
Co-authored-by: Nicolas Santorelli <nsantorelli@eurodns.com>
2026-05-02 10:56:13 +02:00
Alexey Pakhomov 8e71268d03 Fix write error $_CRONTAB -l in crontab file (#6920) 2026-05-02 10:56:13 +02:00
firestormisp 10e7e458af Add dns_firestorm.sh plugin for Firestorm.ch DNS API (#6829)
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.
2026-05-02 10:56:13 +02:00
neil 76d1377fc1 Merge pull request #6934 from acmesh-official/dev
sync
2026-04-28 22:22:24 +02:00
neil 15a0f52577 fix https://github.com/acmesh-official/acme.sh/issues/6930#issuecomment-4327834577 2026-04-28 09:09:50 +02:00
Adam Bodnar fd6ff7b173 Add dns_cpanel_uapi DNS API plugin (#6878)
* Add dns_cpanel_uapi.sh
2026-04-26 14:29:40 +02:00
neil ed6d1f3447 Merge pull request #6929 from acmesh-official/dev
sync
2026-04-24 23:30:42 +02:00
neil 0b2187ab3f add MidnightBSD 2026-04-24 22:43:25 +02:00
neil 4b8b23bb90 fix ca name 2026-04-24 21:47:27 +02:00
neil d00b2722ee Merge pull request #6889 from achmadalifn4/dev
Add BytePlus ALB deployment script
2026-04-24 21:46:10 +02:00
neil df49dd2ec9 Merge pull request #6928 from acmesh-official/dev
sync
2026-04-24 21:36:58 +02:00
Achmad Alif Nasrulloh e9b0cafac5 Fix byteplus_alb.sh 2026-04-24 11:21:43 +07:00
Antoni Company 9882d534af fix: commit overhaul (#6915)
- 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
2026-04-20 11:28:17 +02:00
neil 539b46adc9 fix https://github.com/acmesh-official/acme.sh/issues/6898#issuecomment-4207794240 2026-04-14 21:19:25 +08:00
ACHMAD ALIF NASRULLOH f89a9a5de3 Add new header variable _H5 in byteplus_alb.sh
Added a new header variable _H5 to the byteplus_alb.sh script.
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 00090d24b8 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 73a682e561 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot a1b94db94d Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 5c94af86f3 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 3843495397 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot a739bf3e3a Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
Achmad Alif Nasrulloh 75642a1252 Update bteplus_alb.sh 2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 668427f285 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot d0e123cb02 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 8587c3e744 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 934870fc77 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 8eea7ca307 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 28f1f07f49 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot ad71a785ec Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot d5c8060a65 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 4178c33524 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 044371b00a Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOHandCopilot 86d98b0461 Update deploy/byteplus_alb.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 21:04:48 +08:00
ACHMAD ALIF NASRULLOH 6efd6d5b5a Add BytePlus ALB deployment script
This script deploys SSL/TLS certificates issued by acme.sh to BytePlus Application Load Balancer (ALB), supporting automatic renewal with zero-downtime certificate rotation.
2026-04-14 21:04:48 +08:00
GNDevProd 454cec6e43 Add Gname.com dnsapi support (#6808)
* add gname dns acme.sh
2026-04-14 20:57:22 +08:00
wangzhizhou afba1455b8 add dnsapi for baidu cloud dns (#6844) 2026-04-14 20:44:30 +08:00
Mitchell van Bijleveld f3e61a8ef4 Don't mark restart http as failed if json returns false because it was not restarted (#6906) 2026-04-14 20:37:50 +08:00
neil 0894955895 fix https://github.com/acmesh-official/acme.sh/pull/6731#issuecomment-3733144962 2026-04-08 22:33:30 +08:00
breviloandOliver Behnke 5b5ef91d88 Fix off-by-one error preventing the final poll to succeed (#6865)
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>
2026-04-08 22:18:26 +08:00
Jordan RussellandJordan Russell 618735d11e [dnsapi] add SiteHost DNS API hook (#6891)
Co-authored-by: Jordan Russell <jordan.russell@sitehost.co.nz>
2026-04-08 22:07:30 +08:00
Lorenz Stechauner 08b2186afe dns_world4you: Adapt to latest record id changes (#6897) 2026-04-08 22:00:42 +08:00
Stefan Bottelier 3509f6404f Add bHosted.nl DNS API (#6864)
Add bHosted.nl DNS API (#6864)
2026-04-08 21:59:09 +08:00
neil 50dbdd781b fix DEFAULT_RENEW
fix https://github.com/acmesh-official/acme.sh/issues/2217#issuecomment-4155894630
2026-04-05 10:46:29 +08:00
neil 346acc3f33 Update copilot-instructions.md 2026-04-05 10:40:35 +08:00
neil a79bdb9ef3 Merge branch 'dev' of https://github.com/acmesh-official/acme.sh into dev 2026-04-04 22:24:13 +08:00
neil 563415d21f Merge pull request #6895 from acmesh-official/dev
sync
2026-04-04 21:19:05 +08:00
neil c2c5c3cdb7 Update copilot-instructions.md 2026-04-04 21:18:36 +08:00
neil 4aeb7bbab0 fix rule 2026-04-04 21:18:36 +08:00
orangepizza 12f639116c Update copilot instruction to match actual PR rule (#6873)
old version had instruction to use bash-only [[ ]] test, remove it and add rules for DNS script writing from https://github.com/acmesh-official/acme.sh/issues/343
2026-04-04 21:18:36 +08:00
Tomáš Pavlič 4cb1c6e1ea [dnsapi] add subreg.cz dns hook (#6848)
* Add DNS hook for subreg.cz
2026-04-04 21:18:36 +08:00
heximcz 9aad08ef14 Add BEST-HOSTING DNS API (#6859)
* Add BEST-HOSTING DNS API
2026-04-04 21:18:36 +08:00
neil 3b503a009c fix https://github.com/acmesh-official/acme.sh/issues/4924#issuecomment-4069887654 2026-04-04 21:18:36 +08:00
neil 13d6496653 fix https://github.com/acmesh-official/acme.sh/issues/6866#issuecomment-4080403721 2026-04-04 21:18:36 +08:00
neil 605299947e format 2026-04-04 21:18:36 +08:00
neil d050f3458b don't switch from test back to production ca 2026-04-04 21:18:36 +08:00
CZECHIA-COM d66264e741 Add dns_czechia DNS API plugin (#6764)
* Create dns_czechia.sh

This PR adds a DNS API plugin for CZECHIA.COM / RegZone (ZONER a.s.).
2026-04-04 21:18:36 +08:00
neil cf9c70a6c7 Update copilot-instructions.md 2026-04-04 21:16:40 +08:00
Florian Heigl ef49a9fd23 Update synology_dsm.sh (#6894)
quote variable name so message isn't missing the variable that needs to be fixed.
this was reported in #2727  (feedback for hook)
2026-04-03 11:38:21 +08:00
neil fe5d2e3ef7 fix rule 2026-04-03 11:33:05 +08:00
orangepizza bf486bb988 Update copilot instruction to match actual PR rule (#6873)
old version had instruction to use bash-only [[ ]] test, remove it and add rules for DNS script writing from https://github.com/acmesh-official/acme.sh/issues/343
2026-03-28 09:26:47 +08:00
Tomáš Pavlič 3c8c735362 [dnsapi] add subreg.cz dns hook (#6848)
* Add DNS hook for subreg.cz
2026-03-22 11:40:44 +08:00
heximcz c397bd6573 Add BEST-HOSTING DNS API (#6859)
* Add BEST-HOSTING DNS API
2026-03-22 11:36:29 +08:00
neil e26ce2f19c fix https://github.com/acmesh-official/acme.sh/issues/4924#issuecomment-4069887654 2026-03-21 13:02:16 +08:00
neil af5e592fe4 fix https://github.com/acmesh-official/acme.sh/issues/6866#issuecomment-4080403721 2026-03-21 10:47:11 +08:00
neil e21be4455f format 2026-03-21 10:41:58 +08:00
neil 5842e6ff4f don't switch from test back to production ca 2026-03-21 10:40:10 +08:00
CZECHIA-COM 8aea731bd4 Add dns_czechia DNS API plugin (#6764)
* Create dns_czechia.sh

This PR adds a DNS API plugin for CZECHIA.COM / RegZone (ZONER a.s.).
2026-03-16 20:17:18 +08:00
neil 5d158b1640 Merge pull request #6857 from acmesh-official/dev
sync
2026-03-13 20:19:12 +08:00
neil 3198c1af6e fix https://github.com/acmesh-official/acme.sh/issues/6856#issuecomment-4054175916 2026-03-13 20:10:22 +08:00
neil fc8a61f10f update 2026-03-13 18:41:02 +08:00
neil d08b4de794 Merge pull request #6832 from acmesh-official/dev
sync
2026-03-08 18:51:51 +08:00
neil cc677ba9f1 minor 2026-03-08 18:43:39 +08:00
Ludwig 03860a4978 dnsapi/dns_me: ignore "already exists" error for multi-domain support (#6830) 2026-03-08 18:37:29 +08:00
neil da5e818907 Merge pull request #6796 from Tra5is/add_technitium_expiry_ttl
Add Expiry TTL option for Technitium DNS API
2026-03-04 15:17:02 +08:00
Travis f0146bd90e shfmt edit 2026-03-03 05:31:51 -08:00
Tra5is 20722ea030 Merge branch 'dev' into add_technitium_expiry_ttl 2026-03-03 04:59:54 -08:00
neil fdd2e4f19a add copilot 2026-03-01 13:13:07 +08:00
neil 89c86efc56 Merge pull request #6793 from ecm75/fix_kemplm
fixed checking for existing domain on loadmaster
2026-02-23 19:37:06 +08:00
Eike-Christian Müller 8ca1c83b95 Fixed missing error return value when certificate upload fails. 2026-02-23 09:24:06 +01:00
neil 8304d3e323 Merge pull request #6800 from pgerber/ipv6-debug
Fix IPv6 URL when trying to fetch challenge ourselves for debugging
2026-02-22 22:35:25 +08:00
Peter Gerber b5bfd08e35 Fix IPv6 URL when trying to fetch challenge ourselves for debugging
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]
2026-02-21 04:03:41 +00:00
Travis 70f9e255d3 Add Expiry TTL option for Technitium DNS API 2026-02-18 09:29:47 -08:00
neil 12f147bf40 Merge pull request #6794 from infinitydev/pvefix
Add error check to Proxmox deploy hooks
2026-02-18 12:14:52 +08:00
infinitydev bef0fdb1ae check Proxmox Backup Server API response for errors 2026-02-17 19:28:25 +01:00
infinitydev 020a4bb5b3 check Proxmox VE API response for errors 2026-02-17 12:12:17 +00:00
emueller acaaca89ab fixed checking for existing domain on loadmaster 2026-02-17 11:03:53 +01:00
neil bdfa988b65 Merge pull request #6436 from invario/localcopy-deploy-hook
Add 'localcopy' deploy-hook
2026-02-14 11:01:40 +08:00
neil 0e93d2bee1 Merge pull request #6786 from alexandergott-afk/dev
Allow more than one DNS server for HA environments #6785
2026-02-13 11:13:50 +08:00
alexandergott-afk 83424e7ba4 Add the information from my last accepted pull that the TSIG key is optional. 2026-02-12 15:32:53 +01:00
alexandergott-afk 5bc44caf50 Allow more than one DNS server for HA environments 2026-02-12 15:27:23 +01:00
alexandergott-afk 47412d1822 Update dns_nsupdate.sh 2026-02-12 15:24:45 +01:00
neil f68a758974 Merge pull request #6775 from alexandergott-afk/master
Add some fixes for dns_nsupdate provider
2026-02-09 20:16:49 +08:00
alexandergott-afk fb4ae10e08 Merge branch 'dev' into master 2026-02-09 10:45:31 +01:00
neil 824e7b3d80 Merge pull request #6709 from invario/docker-non-root-supercronic
Docker with non-root using supercronic
2026-02-09 17:43:28 +08:00
alexandergott-afk 34052e56d7 too many spaces removed 2026-02-09 10:39:27 +01:00
alexandergott-afk 3374c22169 Merge branch 'dev' into master 2026-02-09 10:03:58 +01:00
alexandergott-afk 4807df0c3e Fix Tab 2026-02-09 10:03:26 +01:00
neil b79146281c Merge pull request #6767 from dga-nagra/panos-api-key
Fix bug + Allow panos deploy-hook to use PANOS_KEY
2026-02-07 22:15:28 +08:00
neil ab3093d58c Merge pull request #6472 from UncleVic/master
feat: #6471 add message_thread_id to telegram notifications
2026-02-07 22:11:42 +08:00
neil c21db5c5d5 Change shebang to use env for portability 2026-02-07 22:10:48 +08:00
alexandergott-afk ec0dc40ad8 Merge branch 'dev' into master 2026-02-05 10:39:19 +01:00
alexandergott-afk 7236ba2d7c Update dns_nsupdate.sh 2026-02-05 09:20:51 +01:00
alexandergott-afk b440e49164 Update dns_nsupdate.sh 2026-02-05 09:14:18 +01:00
neil 80e39eb63c Merge pull request #6687 from kapouer/ssh-noscp-permissions
Ssh noscp permissions
2026-02-04 18:49:52 +08:00
dga-nagra 61e986f23c Conditionnaly change permissions (#1) 2026-02-04 11:01:44 +01:00
David Gallay 6a60695549 Allowing panos deploy-hook to only depend on PANOS_KEY.
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.
2026-02-03 10:51:42 +01:00
neil 28c088611a Merge pull request #6440 from jf-lines/dns_infomaniak_API_v2
dns_infomaniak: upgraded to API v2. v1 is deprecated.
2026-02-01 17:15:02 +01:00
JF DAGUIN 274fd53c83 Merge branch 'dev' into dns_infomaniak_API_v2 2026-01-30 23:08:17 +01:00
JF DAGUIN a4595f334e Merge branch 'acmesh-official:master' into dns_infomaniak_API_v2 2026-01-30 23:04:36 +01:00
JF DAGUIN 477277bd2d Rewrite token scope and URL to add one
Updated comments for clarity and formatting.
2026-01-30 23:00:25 +01:00
neil d8053ed6b2 Merge pull request #6760 from OpusDNS/add-opusdns-provider
[CLOUD-31] Add acme.sh OpusDNS provider
2026-01-24 07:51:13 +01:00
neil e44809c180 Update acme.sh 2026-01-24 07:49:18 +01:00
Kilian Ries 9c245eb37a fix: start zone detection from i=1 per acme.sh convention 2026-01-23 09:20:00 +01:00
Kilian Ries 713251c65b Merge branch 'dev' into add-opusdns-provider 2026-01-22 18:34:04 +01:00
Kilian Ries 2e85e6f9bb Fix zone detection: check for dnssec_status instead of name
The error response also contains 'name' in 'zone_name' field,
causing false positives. Check for 'dnssec_status' which only
exists in valid zone responses.
2026-01-22 18:13:51 +01:00
Kilian Ries 9e584e346d Refactor: Add _opusdns_api helper, simplify code
- 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)
2026-01-22 18:13:09 +01:00
Kilian Ries 163eb1acb9 Simplify zone detection with API check
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.
2026-01-22 18:11:01 +01:00
Kilian Ries 25a3ee48df Fix zone pagination parsing
- 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
2026-01-22 17:58:09 +01:00
Kilian Ries 30c9332327 Remove custom DNS propagation check
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.
2026-01-22 17:28:41 +01:00
Kilian Ries dc65223da1 Remove all trailing whitespace for shfmt compliance 2026-01-22 17:10:12 +01:00
Kilian Ries 01c93b9bbd Fix shellcheck and shfmt issues
- Add double quotes around variables to prevent globbing
- Fix parameter expansion quoting in ${domain%.${_zone}}
- Remove trailing whitespace for shfmt compliance
2026-01-22 17:04:43 +01:00
Kilian Ries b4ac517c7b Trigger DNS workflow for OpusDNS testing 2026-01-22 16:58:09 +01:00
Kilian Ries bfd1f9bf6c [CLOUD-31] add acme.sh opusdns provider 2026-01-22 16:43:26 +01:00
neil 0cef5edac2 fix https://github.com/acmesh-official/acme.sh/issues/6196#issuecomment-3777530678 2026-01-21 20:00:27 +01:00
neil 8c384a92bf Merge pull request #6755 from zbindenp/master
Fix dns_cyon to use correct regex
2026-01-18 14:30:53 +01:00
neil ce2d9e9746 Merge pull request #6485 from Fangliding/master
Fix ipv6 cert cannot be found
2026-01-18 14:29:33 +01:00
Patrick Zbinden 282b048557 Fix dns_cyon to use correct regex 2026-01-14 21:05:32 +01:00
neil f39d066ced Merge pull request #6754 from acmesh-official/dev
sync
2026-01-13 21:50:14 +01:00
neil e2882c536b disable notifications for myself
disable notifications for myself
2026-01-13 21:43:46 +01:00
neil 892b3ca219 fix account.conf permission
https://github.com/acmesh-official/acme.sh/issues/6708#issuecomment-3745737079
2026-01-13 21:28:14 +01:00
neil 4de00d4603 Merge pull request #6748 from acmesh-official/dev
sync
2026-01-11 21:50:00 +01:00
neil 40b29c1879 support Haiku OS 2026-01-11 21:37:02 +01:00
neil d57ab0ab7d fix haiku 2026-01-11 21:28:45 +01:00
neil d8c062defb fix haiku 2026-01-11 21:21:08 +01:00
neil 3e36b61823 fix haiku 2026-01-11 21:14:29 +01:00
neil 09009794e0 fix 2026-01-11 21:10:37 +01:00
neil 5e670e0d93 support haiku 2026-01-11 21:02:57 +01:00
neil bc646d120e Merge pull request #6746 from d-kurz/dev
dns_dynv6: Allow challenge strings that contain "id"
2026-01-10 12:54:47 +01:00
neil c58da45917 Merge pull request #6678 from fratoro/master
Update dns_cyon to use unique user-agent and all cookies
2026-01-10 12:52:24 +01:00
Denis Kurz b37867d027 remove unused code 2026-01-10 01:44:07 +01:00
Denis Kurz 880d93f7f7 fix(dynv6): allow 'id' in dns challenge
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.
2026-01-10 01:44:07 +01:00
Denis Kurz 5ad2bea129 fix typos 2026-01-10 01:44:07 +01:00
neil 9e51432a6f Merge pull request #6745 from acmesh-official/dev
sync
2026-01-06 08:55:01 +01:00
neil 903a53991d fix bugs 2026-01-05 22:06:43 +01:00
neil 188aa515ce Merge pull request #6724 from h-haaks/base64-encode-pfx-password
Base64 encode pfx password
2026-01-05 21:46:34 +01:00
neil 6a9776b1f0 Merge pull request #6631 from rperper/dev
QUIC.cloud DNS API support (dns_api.sh)
2026-01-05 21:42:13 +01:00
neil 2092d6061b fix https://github.com/acmesh-official/acme.sh/issues/6736#issuecomment-3707981300 2026-01-05 21:38:37 +01:00
neil 35f99c545c add debug wiki 2026-01-05 21:11:07 +01:00
neil 4b278dc1bb Merge pull request #6735 from JensSpanier/dev
Pushover: Remove asterisks
2026-01-05 20:54:00 +01:00
neil 9bf5179b55 Merge pull request #6739 from moaminsharifi/master
Add Virakcloud dns api
2026-01-05 20:51:43 +01:00
Amin Sharifi ca94221d08 Merge branch 'dev' into master 2026-01-04 18:26:04 +03:30
Amin Sharifi 70462b5ac3 run ~/shfmt -l -w -i 2 dnsapi/dns_virakcloud.sh and Remove unnecessary blank lines in dns_virakcloud.sh 2026-01-04 16:33:37 +03:30
Amin Sharifi ef035248c3 Add VirakCloud DNS API support 2026-01-04 16:29:30 +03:30
Amin Sharifi 877cbe04c9 Add VirakCloud DNS API support with add and remove TXT record functions 2026-01-04 16:18:18 +03:30
Jens Spanier 94c670a759 Remove asterisks and line breaks 2026-01-04 11:49:35 +01:00
neil 045e4dee2e use openindiana-vm@v1 2026-01-02 16:24:06 +01:00
neil 06d9ca61a3 Merge pull request #6728 from jwaterwater/feat/qiniu-force-https-config
Feat/qiniu force https config
2026-01-02 15:36:09 +01:00
neil dc6a996cc3 Merge pull request #6731 from jdevera/posix_globs_for_list
Fix `--list` regression in POSIX / Docker environments
2026-01-02 15:34:27 +01:00
Jacobo de Vera b08bb2ef69 Fix list command for POSIX sh by avoiding brace expansion 2026-01-02 12:16:46 +00:00
风扇滑翔翼 64ac537e72 Merge branch 'dev' into master 2026-01-02 14:52:10 +08:00
风扇滑翔翼 ef2089ceb1 Update directory iteration pattern in acme.sh 2026-01-02 14:48:11 +08:00
Your Name 2ad984d8ad feat(qiniu): make forceHttps configurable via environment variable
Add QINIU_FORCE_HTTPS environment variable (default: false) to allow
configuring HTTPS redirect behavior for CDN domains.
2026-01-01 13:26:02 +00:00
Bob Perper 6a37f23b14 Ran shfmt locally 2025-12-30 16:31:16 -05:00
Bob Perper cf2f9ef251 Missed additional quotes 2025-12-30 16:27:21 -05:00
Bob Perper f9ffdbe407 Initialize record_id 2025-12-30 16:23:01 -05:00
Bob Perper 778b4a38ed Missed several double quote issues 2025-12-30 16:17:03 -05:00
Bob Perper 397c0605e5 Double quote for globbing 2025-12-30 16:11:39 -05:00
Bob Perper b4f30ff026 Updated for shfmt 2025-12-30 16:07:01 -05:00
Bob Perper 0b66acf332 shfmt fixes 2025-12-30 15:54:23 -05:00
Bob Perper 185d92f1e7 shfmt fixes 2025-12-30 15:50:39 -05:00
Bob Perper e031457cfa shfmt fixes 2025-12-30 15:48:29 -05:00
Bob Perper 162cfebbbb Removed jq requirement 2025-12-30 15:36:06 -05:00
Bob Perper 66ef351f36 Merge branch 'dev' of https://github.com/rperper/acme.sh into dev 2025-12-30 15:35:23 -05:00
invario 6a98b9f81e chown /acme.sh to non-root user and set HOME to /acme.sh
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-12-30 12:44:46 -05:00
invario 4219f7b2f6 align logic to acme.sh installcert(), fix perms on non-key files
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-12-30 11:41:50 -05:00
jwaterwater e260b86414 Merge branch 'acmesh-official:master' into master 2025-12-30 21:48:28 +08:00
Hugo Haakseth 21d52b5995 Store pfx password base64 encoded 2025-12-30 10:57:12 +01:00
invarioandKevin Hoser e03f8d3ad6 fix: savedeployconf for DEPLOY_LOCALCOPY_CERTIFICATE
Co-authored-by: Kevin Hoser <45083826+hoser21@users.noreply.github.com>
2025-12-28 12:03:02 -05:00
invario 6f5a0c5d5e have entry.sh (instead of dockerfile) generate crontab file
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-12-28 11:40:18 -05:00
invario 57db388932 Docker with non-root using supercronic
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>
2025-12-28 11:39:59 -05:00
neil d795cb4850 Merge pull request #6717 from acmesh-official/ari
Ari
2025-12-27 12:29:55 +01:00
neil 1bd2922bc3 Merge pull request #6716 from acmesh-official/dev
sync
2025-12-27 11:49:21 +01:00
neil 47f24126f5 Update supported OS table in README
add openindiana
2025-12-27 11:47:16 +01:00
neil 76fdac59bc Add OpenIndiana CI workflow and badge
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.
2025-12-27 11:41:58 +01:00
neil 49a3d586a3 Add OpenIndiana CI workflow
Introduces a GitHub Actions workflow for OpenIndiana to automate testing of shell scripts.
2025-12-27 11:37:39 +01:00
neil 361e7c5ad4 use nfs for vms
use nfs for vms
2025-12-27 11:26:59 +01:00
neil 3ae9711892 Merge pull request #6715 from acmesh-official/dev
sync
2025-12-27 11:22:22 +01:00
neil fc7168e11d change default renew to 30 days
change default renew to 30 days
and fix readme
2025-12-27 11:20:22 +01:00
neil 260df0048b Merge pull request #6241 from tomo2403/multideploy-yaml
Multideploy: Deploy to multiple hooks of the same type
2025-12-27 11:04:17 +01:00
invario 11cae37405 make compatible with both yq versions
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>
2025-12-27 11:02:40 +01:00
tomo 61b59831c4 minor code style adjustments in multideploy script 2025-12-27 11:02:40 +01:00
Jacobo de Vera 96f38655b4 Use file descriptor 3 for main deployment loop
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.
2025-12-27 11:02:40 +01:00
Jacobo de Vera ab7835ec58 Fix eval bug by quoting
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.
2025-12-27 11:02:40 +01:00
Jacobo de Vera b8b1f1e9b4 Remove config logging when checking
Because it causes a mysterious crash and it's honestly not worth it.
2025-12-27 11:02:40 +01:00
Jacobo de Vera 6b66e734a9 Remove explicit save of the deployhook
acme.sh takes care of that
2025-12-27 11:02:40 +01:00
Jacobo de Vera 4f0a4850a6 Remove unnecessary resetting of IFS 2025-12-27 11:02:40 +01:00
Jacobo de Vera 8a78865174 Quote paths to prevent word splitting 2025-12-27 11:02:40 +01:00
Jacobo de Vera 69dd2cf78b Explain _clear_envs rationale 2025-12-27 11:02:40 +01:00
Jacobo de Vera a961e03a59 Explain the use of eval 2025-12-27 11:02:40 +01:00
Jacobo de Vera 1eee4dee9c Update dependency name from yq to yq-go 2025-12-27 11:02:40 +01:00
Jacobo de Vera 1d8788767f Make failure to check file stop the deployment
Before this, checker issues were only logged. This stops the deployment if any configuration is incorrect.
2025-12-27 11:02:40 +01:00
Jacobo de Vera 986a6138eb Fix config file checks
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.
2025-12-27 11:02:40 +01:00
Jacobo de Vera f850e8d0e4 Support spaces in service names
- 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.
2025-12-27 11:02:40 +01:00
tomo d375012c5d fixed yml file env list 2025-12-27 11:02:40 +01:00
tomo 37c25aa107 removed unneeded return value 2025-12-27 11:02:40 +01:00
tomo 7b16526e7f removed dead code 2025-12-27 11:02:40 +01:00
tomo 093f36b4d6 implemented exiting with the number of failed deployments 2025-12-27 11:02:40 +01:00
tomo e5b47f6402 implemented exiting with 1 if at least one deployment fails 2025-12-27 11:02:40 +01:00
tomo a55d40be97 fixed bug with envs due to the use of a wrong function 2025-12-27 11:02:40 +01:00
tomo c1c49d5a01 simplified deploy method 2025-12-27 11:02:40 +01:00
tomo 88e4d64c1a fixed IFS problems for some hooks 2025-12-27 11:02:40 +01:00
tomo 17e0bbcbb6 fixed formatting 2025-12-27 11:02:40 +01:00
tomo 95c7546051 removed configs and implemented specification of deploy file name 2025-12-27 11:02:40 +01:00
tomo c1e17c366f Update links in multideploy.sh 2025-12-27 11:02:40 +01:00
tomo 88cde7be6d fixed missing wiki link 2025-12-27 11:02:40 +01:00
tomo c16e059535 allowed using varaibles in deploy file 2025-12-27 11:02:40 +01:00
tomo 88d4637ee3 added header doc 2025-12-27 11:02:40 +01:00
tomo 74ed0354a3 added docs and enhanced log messages 2025-12-27 11:02:40 +01:00
tomo 2cc5e66517 added docs 2025-12-27 11:02:40 +01:00
tomo ba7c368ee5 fixed IFS problems 2025-12-27 11:02:40 +01:00
tomo 768de270bf improved preprocessing and fixed bug with wrong param of services 2025-12-27 11:02:40 +01:00
tomo db1dc4de0d added debug messages 2025-12-27 11:02:40 +01:00
tomo fb0926dc81 implemented checking for different kinds of deploy file 2025-12-27 11:02:40 +01:00
tomo 34eb2a655a added yq to dockerfile 2025-12-27 11:02:40 +01:00
tomo 23e1a53ec8 implemented deploying to services 2025-12-27 11:02:40 +01:00
tomo 67d58a12e7 implemented handling envs 2025-12-27 11:02:40 +01:00
tomo 0ed5e21232 fixed formatting and private var names 2025-12-27 11:02:40 +01:00
tomo b2eb1d2bbc refactored getting services 2025-12-27 11:02:40 +01:00
tomo 3c184486c3 fixed indents 2025-12-27 11:02:40 +01:00
tomo 5a730bf00d implemented checking deploy file 2025-12-27 11:02:40 +01:00
neil cbd5dae3b4 Merge pull request #6714 from xiagw/fix-ali-cdn-timestamp
fix: update timestamp variable for ali CDN and DCDN SSL certificate queries
2025-12-27 11:00:06 +01:00
xiagw b9c877adb9 fix: update timestamp variable for CDN and DCDN SSL certificate queries 2025-12-27 14:33:03 +08:00
neil d24c7e977e Merge pull request #6697 from sergiustheblack/fix/strongswan
Fix strongswan deploy hook
2025-12-25 13:39:05 +01:00
neil 6a1ff1c0a6 Merge pull request #6710 from erfantkerfan/dev
improve: change sotoon api to v2.1 with simplification
2025-12-25 13:36:06 +01:00
jwaterwater 10126410b6 Merge branch 'dev' into master 2025-12-25 15:09:08 +08:00
jwaterwater f4a575fee1 bug fixed 2025-12-25 14:48:44 +08:00
Erfan Gholizade 383557df61 improve: change sotoon api to v2.1 with simplification 2025-12-24 17:19:17 +03:30
neil dba4be8065 Merge pull request #6696 from taoso/dev
Support list IPv6 address certificate
2025-12-23 21:42:01 +01:00
Bob Perper f1aac43f0f Retry for workflow 2025-12-23 09:10:49 -05:00
Bob Perper 94783f46ad Retry to pass workflow 2025-12-23 07:53:33 -05:00
Bob Perper 1d26d4fc91 Detect missing jq 2025-12-22 16:42:26 -05:00
Bob Perper 7fc45226da Merge branch 'acmesh-official:dev' into dev 2025-12-22 16:38:10 -05:00
neil a670c07caf Merge pull request #6692 from as-kholin/omglol_202512
Minor updates to the Omg.lol DNS API
2025-12-22 20:09:23 +01:00
neil 09cc2bdfa5 Merge pull request #6690 from flybyray/issue6688
inwx API change
2025-12-22 20:06:16 +01:00
neil cc897cab4c Merge pull request #6655 from hostup/dev
Add: Hostup DNS provider
2025-12-22 19:48:07 +01:00
neil 7ba9597928 Merge pull request #6659 from erfantkerfan/master
Added Sotoon dns api
2025-12-22 19:42:12 +01:00
neil d2d862420e Merge pull request #6705 from mq00fc/dev
Fix ali-cdn deploy hook
2025-12-22 19:41:00 +01:00
DreamSlave fd6a14de8a Update timestamp function in ali_dcdn.sh 2025-12-22 17:00:31 +08:00
DreamSlave 0eb40c6ce6 Update timestamp variable in ali_cdn.sh 2025-12-22 17:00:22 +08:00
hostup e321b3c75c Update dns_hostup.sh 2025-12-22 04:53:21 +01:00
hostup 03d8d3bc1b Update dns_hostup.sh 2025-12-22 04:29:50 +01:00
Erfan Gholizade f85de2b0d3 Added Sotoon dns api
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
2025-12-21 11:50:13 +03:30
Robert Rettig 5fb42b7339 remove prior additions which tried to use oathtool in tests 2025-12-20 14:53:01 +01:00
Robert Rettig 1b2630dc0d stop using oathtool 2025-12-20 13:26:38 +01:00
海涛 3fb4c313ec Support list IPv6 address certificate 2025-12-20 10:05:52 +08:00
Robert Rettig 65892453be take a fork of acmetest 2025-12-19 19:23:13 +01:00
Robert Rettig cba0ff8321 Add oathtool to ubuntu package list in plat.conf 2025-12-19 19:09:23 +01:00
Robert Rettig b6523c2301 Install oathtool in container via _setup function 2025-12-19 18:42:28 +01:00
Robert Rettig e92d0a7492 Fix: Install oathtool at runtime in test container 2025-12-19 18:20:56 +01:00
Robert Rettig 0e5aab346f Add oathtool to acmetest Docker image for 2FA support 2025-12-19 18:17:05 +01:00
Robert Rettig a5ad15be02 Add oathtool to Docker job for 2FA support 2025-12-19 18:07:46 +01:00
Robert Rettig 27ebf09c5c Improve _htmlEscape function robustness by using printf instead of echo
small commit to trigger github actions
2025-12-19 18:02:18 +01:00
Robert Rettig 1c65c04b54 update dns_inwx_info 2025-12-19 17:51:13 +01:00
Robert Rettig 987882ea37 fix delete 2025-12-19 17:49:24 +01:00
Sergey Parfenov 00aaed1b14 Fix strongswan deploy hook
Make it more resistant to deploy hooks api change by passing custom arguments first
2025-12-19 18:12:45 +03:00
Robert Rettig e3b1bccb6a Fixes to support INWX again
Fixes #6688
2025-12-18 16:00:55 +00:00
Jérémy Lal 6ca19fb003 Ensure ssh.sh sets 600 permissions on keyfile 2025-12-18 09:55:50 +01:00
as-kholin 85ff92170b Updated comment to be more clear on variable vs.
definition
2025-12-17 17:15:10 -05:00
Gary McGregor 7ac8c6c75b Merge remote-tracking branch 'upstream/dev' into omglol_202512 2025-12-17 16:56:26 -05:00
neil 6004e7f5cd Merge pull request #6675 from imiric/fix/exoscale-dns
fix: update Exoscale DNS script
2025-12-17 20:49:41 +01:00
as-kholin 4a7e5d0720 Updating Auth header to satisfy shellcheck 2025-12-16 15:00:05 -05:00
as-kholin b4042d5ccb Updated checks for empty parameters to actually trigger, and added a validation check against the omg.lol API to confirm address and apikey are good before proceeding 2025-12-16 14:51:42 -05:00
Bob Perper 6f66e294de Yet another try 2025-12-11 15:43:15 -05:00
Bob Perper 5017c12324 Trying verification again 2025-12-11 09:03:38 -05:00
fratoro 70bc5a6fba Update dns_cyon to use unique user-agent and all cookies 2025-12-09 22:57:26 +01:00
fratoro 3063973744 Update dns_cyon to use unique user-agent and all cookies 2025-12-09 16:49:12 +01:00
Bob Perper 1a2071a120 Merge branch 'acmesh-official:dev' into dev 2025-12-09 10:05:22 -05:00
Bob Perper e5dea48d3c Retry pull request with HTTPS_INSECURE=1 2025-12-09 07:40:00 -05:00
Ivan Mirić 1413aa332b fix: update Exoscale DNS script
This updates the Exoscale DNS script to work with v2 of their API.
2025-12-09 09:49:54 +01:00
neil 37cf431e80 Merge pull request #6676 from acmesh-official/dev
sync
2025-12-08 21:17:08 +01:00
neil 0c9d2dafe3 Merge pull request #6638 from pini-gh/fix-6081
dnsapi/dns_gandi_livedns.sh: save Gandi LiveDNS variables with prefix 'SAVED_'
2025-12-08 21:14:48 +01:00
neil e8708a7489 fix solaris 2025-12-08 21:13:00 +01:00
neil 79592c700f Merge pull request #6671 from ufozone/dev
Add mgw-media.de DNS API
2025-12-08 20:14:00 +01:00
Markus G. ad3783170e Fix formatting issues in dns_mgwm.sh script 2025-12-08 19:31:40 +01:00
Gilles Filippini 329dab9a67 Use '_mutable' functions for authentication variables
Fixes #6081.
2025-12-08 19:02:33 +01:00
Markus G. f142f37064 Remove DNS provider information comment
Removed comment about DNS provider information.
2025-12-08 18:49:51 +01:00
Markus G. 0d2955b48d Update documentation links in dns_mgwm.sh 2025-12-08 18:49:51 +01:00
Markus G. 95da407de8 Refactor DNS API script to use new request function 2025-12-08 18:49:51 +01:00
Markus G. 503ca1e9c2 Change MGWM_API_BASE to use IP address 2025-12-08 18:49:51 +01:00
Markus G. d8722c46d9 Consolidate API request logic in dns_mgwm.sh
Refactor DNS API functions to use a unified request handler.
2025-12-08 18:49:51 +01:00
Markus G. 546c2d47d5 Refactor DNS API for mgw-media.de
Updated DNS API script for mgw-media.de to use new base URL and improved API request structure.
2025-12-08 18:49:51 +01:00
Markus G. 2ba615555c Refactor dns_mgwm.sh for improved API interaction
Refactor MGWM API script to improve clarity and functionality. Update API endpoint and streamline credential handling.
2025-12-08 18:49:51 +01:00
Markus G. e94c6be4a1 Update MGWM API endpoint to IPv4 2025-12-08 18:49:51 +01:00
Markus G. 11eaad1fa7 Update API URLs to include .php extension 2025-12-08 18:49:51 +01:00
Markus G. daf7f7c268 Refactor dns_mgwm.sh for better API integration
Refactor DNS API script to improve credential handling and update API endpoint.
2025-12-08 18:49:51 +01:00
ufozone 4965c704d7 Initial commit for mgw-media.de 2025-12-08 18:49:51 +01:00
neil b7fe7a40ba Merge pull request #6333 from EfficientIP-Labs/dns_efficientip
Implement DNS API for Efficientip SOLIDserver
2025-12-08 18:46:59 +01:00
Bob Perper 5fcca7c7e0 Retry correct commit 2025-12-08 08:48:30 -05:00
Bob Perper b0088c82dc Merge branch 'acmesh-official:dev' into dev 2025-12-08 08:44:42 -05:00
Bob Perper 875cf056b7 Submit dns_qc.sh for review 2025-12-08 08:40:44 -05:00
neil 3b2c2b16b2 minor 2025-12-06 11:23:28 +01:00
neil 45cb36f6d9 fix https://github.com/acmesh-official/acme.sh/issues/6246#issuecomment-3610998032 2025-12-05 22:31:45 +01:00
Bob Perper ed1bd01592 Save account information differently 2025-12-05 16:29:49 -05:00
Bob Perper a1857af6de Update error message and secrets 2025-12-05 15:53:17 -05:00
neil 70e965fd55 Merge pull request #6665 from acmesh-official/dev
sync
2025-12-05 21:51:56 +01:00
neil bee01c938a add comment 2025-12-05 21:46:15 +01:00
Bob Perper 6b6d22c5ba shfmt updates 2025-12-05 13:52:59 -05:00
Bob Perper 67a389cbbf Minor change and setup secrets again 2025-12-05 10:35:52 -05:00
Bob Perper d3930639db Updated secrets and put back guards 2025-12-05 08:58:41 -05:00
Bob Perper 65292b010e Merge branch 'acmesh-official:dev' into dev 2025-12-05 08:57:01 -05:00
invario f39a6fe517 Use cat instead of cp for all files
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-12-04 11:06:50 -05:00
invario 2775def93a Use 'cat' instead of 'cp', removed use of temp file, keeps permissions
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-12-03 12:05:19 -05:00
neil 5cbae50ec1 Merge pull request #6651 from stefanriegel/master
Added dns provider for Infoblox Universal DDI
2025-12-02 20:00:09 +01:00
hostup 51b4fa0080 Update dns_hostup.sh 2025-12-01 17:19:16 +01:00
hostup 64a6ea68fa Update dns_hostup.sh 2025-12-01 15:58:36 +01:00
hostup d97b4477b2 Update dns_hostup.sh 2025-12-01 15:55:17 +01:00
hostup b8e394e76a Update dns_hostup.sh
bug fix Omnios fial
2025-12-01 15:48:48 +01:00
hostup 671d542898 chore: trigger DNS API test 2025-12-01 13:49:12 +01:00
hostup 9980ad0fef add HostUp DNS 2025-12-01 13:33:19 +01:00
Stefan 004deaeea1 Merge branch 'dev' into master 2025-11-30 19:28:39 +01:00
Stefan Riegel 36b8ca2bc0 Fix shfmt formatting: Remove trailing whitespace 2025-11-30 00:49:36 +01:00
Stefan Riegel 890ab4a7bb Refactor dns_infoblox_uddi.sh: Fix zone detection and add wildcard cert support
- 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
2025-11-30 00:48:15 +01:00
Stefan Riegel 490b9e2d09 Clean up debug statements 2025-11-29 23:39:43 +01:00
Stefan Riegel ca35e8c118 Fix zone_id extraction to query correct zone 2025-11-29 23:32:28 +01:00
Stefan Riegel eeb91de6a3 Replace jq with shell-based JSON parsing 2025-11-29 23:13:52 +01:00
Stefan Riegel 657b7195d6 Fix Authorization header format 2025-11-29 23:06:26 +01:00
Stefan 054a73f297 fix DNS API 2025-11-29 22:48:06 +01:00
Stefan 5c6d8aacbe Add files via upload 2025-11-29 22:38:02 +01:00
neil ac0df6bc88 start 3.1.3 2025-11-29 16:36:14 +01:00
neil f007b46c1b Merge pull request #6644 from SunMar/patch-1
fix "dns_aws.sh: line 164: _error: command not found" #6443
2025-11-28 20:15:18 +01:00
SunMar c5566eafeb fix "dns_aws.sh: line 164: _error: command not found" #6443 2025-11-28 09:44:50 +01:00
neil 40290ad42a Merge pull request #6643 from acmesh-official/dev
sync
2025-11-27 22:15:40 +01:00
neil 705fbcd570 fix https://github.com/acmesh-official/acme.sh/issues/6124#issuecomment-3586650156 2025-11-27 22:13:28 +01:00
asavin 75ee17aeeb Remove unecessary base64 encoding 2025-11-25 14:47:40 +01:00
ZeroSSL-Andreas 9b30bd5a03 Update README.md 2025-11-25 14:41:31 +01:00
neil 94a163397b Merge pull request #6630 from spin-acompany/feat-panos-filename
Add custom certname for panos
2025-11-23 11:52:02 +01:00
Antoni Company 46a2608783 fix: Renamed filaname to certname
- Changed filename to certname to better reflect the actual issue at hand.
- Restored _cdomain variable to its original place for clarity.
2025-11-22 09:22:45 +00:00
Bob Perper 88e9681481 Additional shfmt issues 2025-11-21 16:26:24 -05:00
Bob Perper 20ef8cd369 Additional shfmt issues 2025-11-21 16:22:42 -05:00
Bob Perper ded539b11c Additional shfmt issues 2025-11-21 16:04:23 -05:00
Bob Perper 5e76ea820c Additional shfmt issues 2025-11-21 16:01:14 -05:00
Bob Perper 90d2ff8fad Better fixes for shfmt errors 2025-11-21 15:27:38 -05:00
Bob Perper 0f42b06b48 Trying again to fix shfmt error 2025-11-21 08:02:59 -05:00
Bob Perper b500ac3dbb Updated secret and dns_qc.sh 2025-11-20 15:38:56 -05:00
neil d2539c3f1a fix
https://github.com/acmesh-official/acme.sh/issues/6402
2025-11-20 21:18:50 +01:00
neil 8faebdc99c Merge pull request #6632 from acmesh-official/dev
sync
2025-11-20 20:56:44 +01:00
Bob Perper 894dfdd5d8 Merge branch 'QUIC.cloud' of github.com:rperper/acme.sh into QUIC.cloud 2025-11-20 08:02:11 -05:00
Bob Perper d0d97a40a6 Commit to force initial test 2025-11-20 08:01:36 -05:00
Bob Perper cf5fd403e8 Removed false wiki page 2025-11-20 08:01:36 -05:00
Bob Perper 72a6a5ce04 Added wiki doc 2025-11-20 08:01:36 -05:00
Bob Perper 9381835a7c QUIC.cloud support for acme.sh 2025-11-20 08:01:36 -05:00
Bob Perper 2c9ba9b3df Merge remote-tracking branch 'upstream/dev' into QUIC.cloud 2025-11-20 07:58:23 -05:00
Antoni Company 3d3053f427 feat: Add custom filename for panos 2025-11-20 10:06:37 +00:00
neil 90e6c9b87b Merge pull request #6629 from WongIong/patch-1
Adapting to Cloudflare's new response
2025-11-19 20:51:34 +01:00
Mason a9f96bf709 Reformat using shfmt 2025-11-20 00:29:44 +08:00
Mason c950b67e4b Adapting to Cloudflare's new response 2025-11-19 23:27:45 +08:00
Bob Perper 9a74c86327 Commit to force initial test 2025-11-19 09:06:55 -05:00
neil 6715320e78 fix https://github.com/acmesh-official/acme.sh/issues/6610
https://github.com/acmesh-official/acme.sh/issues/6617#issuecomment-3546341480
2025-11-19 08:51:25 +01:00
neil 77805086d0 Merge pull request #6563 from PKlapp/dns_hetznercloud
Add Hetzner Cloud DNS provider (dns_hetznercloud)
2025-11-18 21:11:39 +01:00
Bob Perper 692a21ee0d Merge branch 'acmesh-official:master' into QUIC.cloud 2025-11-18 11:25:49 -05:00
neil ff8c12ac75 Merge pull request #6620 from coderjoe/fix/readme-clarify-keylength-documentation 2025-11-17 22:08:20 +01:00
Joe Bauser 0d1f9edf3f README.md clarify keylength arg and ECC default
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
2025-11-17 15:42:13 -05:00
neil 4bfc5aafd3 Merge pull request #6616 from acmesh-official/dev
add logo
2025-11-16 09:31:21 +01:00
neil a6ff1d6924 add logo 2025-11-16 09:30:41 +01:00
neil 7c7c99656a Merge pull request #6612 from acmesh-official/dev
remove ClearLinux
2025-11-15 11:28:02 +01:00
neil 66bad853ae remove ClearLinux 2025-11-15 11:27:04 +01:00
neil 52ac922014 Merge pull request #6611 from acmesh-official/dev
sync
2025-11-15 11:23:56 +01:00
neil 4a7f35dea7 remove clientauth
https://github.com/acmesh-official/acme.sh/issues/6610
2025-11-15 11:12:09 +01:00
EfficientIP-Labs ad2cb507a4 Merge branch 'dev' into dns_efficientip 2025-11-12 14:11:54 +01:00
EfficientIP-Labs dca23a98f1 Merge branch 'acmesh-official:master' into dns_efficientip 2025-11-12 10:50:42 +01:00
neil 8955732c05 Merge pull request #6504 from AnTheMaker/patch-1
[Important DNS Provider Update] Support Nanelo DNS Team- & Workspace-specific API keys
2025-11-09 22:27:44 +01:00
An | Anton Röhm 762b140808 Merge branch 'acmesh-official:master' into patch-1 2025-11-09 20:58:12 +01:00
neil 57f8221bab fix --request-v4/6
https://github.com/acmesh-official/acme.sh/pull/6582
2025-11-09 19:58:25 +01:00
neil b7a575b837 Merge pull request #6582 from Roy-Orbison/request-ip-family
Choose an IP address family for outgoing requests
2025-11-09 19:33:44 +01:00
neil 0a4500e85b Merge pull request #6604 from acmesh-official/dev
sync
2025-11-09 19:15:45 +01:00
neil 9a994e7f36 fix 2025-11-09 19:03:30 +01:00
neil 839d611f64 use ghcr.io/letsencrypt/pebble:latest 2025-11-09 18:50:09 +01:00
neil 421946a923 Merge pull request #6589 from de-es/ntfy
Add priority, tags and title to ntfy notification
2025-11-09 18:11:50 +01:00
neil 1f598a0a79 Merge pull request #6594 from rglidden/truenas_25.10_fix
truenas_ws: Fix TrueNAS deploy fails on TrueNAS 25.10 due to invalid passphrase
2025-11-09 18:10:08 +01:00
neil d5c669bcd2 Merge pull request #6600 from seagleNet/feat/notify_opsgenie
feat: Add notify plugin for opsgenie
2025-11-09 18:09:04 +01:00
neil 32c664d450 Merge pull request #6603 from pvamos/alpine22
Bump Alpine version from 3.21 to 3.22
2025-11-09 18:01:32 +01:00
neil adb46748de Merge pull request #6447 from privacyfr3ak/master
Fix keystore ownership in Unifi deployment - unifi.sh for BSD
2025-11-09 18:00:39 +01:00
privacyfr3ak 59a286b0b7 disable shellcheck 2025-11-08 16:59:10 -05:00
Péter Vámos c5f41479a9 Bump Alpine version from 3.21 to 3.22 2025-11-07 16:16:30 +01:00
neil b694019a40 Merge pull request #6597 from peterlh/master
Updated Curanet API - API Response changed
2025-11-04 22:49:21 +01:00
PrivacyFreak 72259059ef Merge branch 'acmesh-official:master' into master 2025-11-04 15:23:54 +00:00
seagleNet 0fa53d62cb feat: Add notify plugin for opsgenie 2025-11-04 09:35:47 +01:00
PKlapp 47d17c1298 Merge pull request #1 from PKlapp/dns_hetznercloud_improvements
DNS Hetzner Cloud improvements
2025-11-04 01:17:45 +01:00
Peter Lindegaard Hansen d187b982eb Update dns_curanet.sh 2025-11-03 18:14:27 +01:00
Peter Lindegaard Hansen b7c8601540 Update dns_curanet.sh 2025-11-03 16:18:15 +01:00
Richard Glidden 693b1f7a74 Fix TrueNAS deploy fails on TrueNAS 25.10 2025-11-02 23:01:51 -05:00
Philipp Klapp 5f81460504 Wait for Hetzner Cloud DNS actions to complete before returning 2025-11-02 04:07:58 +01:00
Philipp Klapp b65f432ee0 Normalize Hetzner zone names to punycode 2025-11-01 13:44:11 +01:00
Dennis Schmidt 3d21ac4525 CS
Make shfmt happy
2025-10-31 08:41:34 +00:00
Dennis Schmidt 7c5b9a5b92 Add priority, tags and title to ntfy notification
Make the ntfy.sh notifications easier to distinguish at a first glance.
2025-10-31 08:41:34 +00:00
neil 82ff65c3d4 Merge pull request #6575 from JensSpanier/dev
Add deploy hook for KeyHelp API
2025-10-30 21:42:45 +01:00
Bob Perper 68eb6defd3 Removed false wiki page 2025-10-27 12:04:08 -04:00
Bob Perper e25e30dcdd Added wiki doc 2025-10-27 12:00:01 -04:00
Roy Orbitson 48c48cb344 Choose an IP address family for outgoing requests
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.
2025-10-27 17:32:20 +10:30
neil a1f4f9ddb7 Merge pull request #6579 from acmesh-official/dev
sync
2025-10-24 21:15:08 +02:00
Bob Perper 7ca8a9e449 QUIC.cloud support for acme.sh 2025-10-24 11:34:25 -04:00
Jens Spanier 0c7943868d Merge branch 'acmesh-official:dev' into dev 2025-10-21 11:35:19 +02:00
Jens Spanier 3cdce86339 rename to keyhelp_api 2025-10-21 11:34:46 +02:00
Alexis Savin 8cb9713493 Merge branch 'acmesh-official:master' into dns_efficientip 2025-10-18 18:25:40 +02:00
asavin ef76831d37 Addressing #pullrequestreview-3353279982 2025-10-18 14:16:51 +02:00
neil 08246f7005 Merge pull request #6567 from vmichelin/dev
fix #6555 : ovh dns api enable to remove record
2025-10-18 13:11:30 +02:00
neil c377ec69d3 Merge pull request #6571 from DuolaD/master
Fix the issue where Telegram bots would not push notifications.
2025-10-18 12:41:15 +02:00
DuolaD a2c2b7ffee Fixed the issue where Telegram bots would not push notifications. 2025-10-18 11:41:26 +08:00
An 65bd3d67b4 nanelo dns: minor log improvements 2025-10-14 15:51:28 +02:00
asavin c4671272c0 Yet another push to try the test suite 2025-10-14 13:35:15 +02:00
Vmichelin 25c564bae1 fix #6555 : ovh dns api enable to remove record 2025-10-14 10:10:19 +02:00
Philipp Klapp 3c3ec2c97c Script created for Hetzner Cloud 2025-10-13 09:05:59 +02:00
neil 15197455f2 Merge pull request #5061 from b1n23/dev
Add deployhook for CacheFly, Edgio and Netlify
2025-10-11 12:47:40 +02:00
Jens Spanier f7cc72be35 add missing double quotes 2025-10-09 13:28:04 +02:00
Jens Spanier 17da49bb78 add keyhelp deploy hook 2025-10-09 13:16:28 +02:00
neil a5754e9ec4 Merge pull request #6551 from acmesh-official/dev
sync
2025-10-06 12:32:49 +01:00
neil 2584f09083 Merge pull request #6543 from szhu25/add-to-list
Add "Profile" column to --list command
2025-10-06 12:30:49 +01:00
Steven Zhu d439933b52 add Profile column to --list output
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.
2025-09-28 19:20:08 -04:00
neil 094d03bf7a Merge pull request #6542 from szhu25/list-profiles
Implement discovery of CA certificate profiles
2025-09-28 15:42:50 +01:00
Steven Zhu 0f5093c0b7 Remove space 2025-09-27 17:52:44 -04:00
Steven Zhu 80748b9fe0 Quick Patch 2025-09-27 17:37:37 -04:00
Steven Zhu b244c76dd5 Add --list-profiles command to show CA 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]
2025-09-27 17:29:12 -04:00
neil a91ab54449 Merge pull request #6540 from acmesh-official/dev
sync
2025-09-27 22:22:55 +01:00
neil f2dbf56db1 Merge pull request #6514 from zjwangmin/dev
fix bug for #6510
2025-09-27 22:16:14 +01:00
neil e5214ea2e5 add Actalis.com CA 2025-09-27 23:12:05 +02:00
neil 11995b958a add actalis.com CA 2025-09-27 22:57:42 +02:00
neil 493ec4be52 Merge pull request #6442 from JensSpanier/dev
Support certificate profile selection
2025-09-27 21:27:55 +01:00
Jens Spanier 604e6873ba Add short name + wiki link to help 2025-09-22 12:12:17 +02:00
Jens Spanier 5954f0dde5 Change to --cert-profile 2025-09-22 12:11:50 +02:00
neil b8e91036dd Merge pull request #6524 from acmesh-official/dev
sync
2025-09-21 18:06:01 +02:00
neil f22b490a10 remove buypass 2025-09-21 18:04:59 +02:00
neil 49ed112ef5 Merge pull request #6523 from acmesh-official/dev
sync
2025-09-21 17:18:25 +02:00
neil 471e0c05f9 remove mageia 2025-09-20 10:38:43 +02:00
neil c3ec827fdd remove buypass 2025-09-19 20:54:09 +02:00
neil ca7bdd9101 Merge pull request #6424 from rglidden/truenas_ws_remote
truenas_ws: Add ability to deploy to remote TrueNAS server via WebSockets
2025-09-19 20:49:29 +02:00
Richard Glidden 070cd0f4df Use _sleep instead of sleep 2025-09-16 22:19:16 -04:00
EfficientIP-Labs 86f2584162 Merge branch 'acmesh-official:master' into dns_efficientip 2025-09-16 11:15:51 +02:00
Jens Spanier 1b00ced7ad Add --profile as option for selecting certificate profile 2025-09-16 09:20:31 +02:00
Min Wang 44c7473ef9 fix bug for #6510 2025-09-16 15:09:12 +08:00
neil 20c80f553f Merge pull request #6513 from acmesh-official/dev
sync
2025-09-15 20:13:47 +02:00
neil b4a5149ba2 Merge pull request #6503 from benyamin-codez/dnsapi-dns_opnsense-v25.7-refresh-dev
dnsapi/dns_opnsense.sh: Refresh for OPNsense v25.7 series
2025-09-15 19:35:42 +02:00
neil df350e6660 fix format 2025-09-15 19:34:54 +02:00
benyamin-codez d76f4b27b0 dnsapi/dns_opnsense.sh: Refresh for OPNsense v25.7 series
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>
2025-09-15 15:50:48 +10:00
neil a1ea2a5aa6 fix tr
https://github.com/acmesh-official/acme.sh/issues/6511#issuecomment-3282521860
2025-09-14 10:35:34 +02:00
Richard Glidden 8608e9cd3a Save and read variables 2025-09-12 22:22:30 -04:00
Jens Spanier 5f8f7ee576 Merge branch 'acmesh-official:dev' into dev 2025-09-10 09:09:43 +02:00
An d8a92a2e65 switch nanelo api to post requests 2025-09-08 21:32:15 +02:00
An | Anton Röhm 67ed930100 Merge branch 'acmesh-official:master' into patch-1 2025-09-08 21:22:55 +02:00
EfficientIP-Labs 020c52e583 Merge branch 'acmesh-official:master' into dns_efficientip 2025-09-08 09:23:17 +02:00
neil 03dd318265 Merge pull request #6505 from acmesh-official/dev
sync
2025-09-07 20:49:42 +02:00
neil fc3bfda3bd Merge pull request #6487 from aSauerwein/master
add template_stack option to push to device
2025-09-07 20:48:00 +02:00
An | Anton Röhm 7eb0ce6e90 Merge branch 'dev' into patch-1 2025-09-07 17:19:02 +02:00
An | Anton Röhm 5aa964cde9 Formatting
using shfmt to format the code
2025-09-07 17:17:51 +02:00
An | Anton Röhm 31d7264583 Fix pattern matching for best zone selection 2025-09-07 17:14:06 +02:00
An | Anton Röhm 93dc22a71f Support Nanelo DNS Team- & Workspace-specific API keys
Nanelo Team- and Workspace-specific API keys require the "domain" parameter to be set containing the DNS zone name (unlike the Domain-specific API keys).
So I've added a function to detect the root DNS zone and set the required parameter as described here: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide#3-detect-which-part-is-your-root-zone
2025-09-07 16:57:10 +02:00
neil c4d228ad8d Merge pull request #6493 from jdevera/proxmox_response_check
Show proxmox deploy scripts response only on debug
2025-09-07 10:41:24 +02:00
neil 30faf500eb fix https://github.com/acmesh-official/acme.sh/pull/6499#issuecomment-3259771356 2025-09-07 10:09:27 +02:00
neil 26d4bac85f Merge pull request #6501 from fuyufjh/patch-1
Fix sed command in telegram notifier
2025-09-07 10:06:35 +02:00
Eric Fu e0e3cdc316 Fix sed command in telegram notifier 2025-09-06 22:31:50 +08:00
neil 1deb52f86d Merge pull request #6499 from OnyxMsi/ipv6_only_socat_error
socat rejects TCP-LISTEN on ipv6 only networks
2025-09-05 22:19:21 +02:00
neil 39cb87dc4b fix for DragonflyBSD
just move "date -u -j -f" before the linux branch.
2025-09-05 22:08:55 +02:00
Guillaume PELURE 04e2549239 socat rejects TCP-LISTEN on ipv6 only networks 2025-09-02 21:13:38 +02:00
neil e06cdbf0ac Merge pull request #6495 from jdevera/patch-1
Fix diff in wiki notifications (use full clone)
2025-09-02 17:38:41 +02:00
Jacobo de Vera d366b7e4fc Fix diff in wiki notifications (use full clone)
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.
2025-09-01 19:54:36 +01:00
Jacobo de Vera 5aae3333bc Show proxmox deploy scripts response only on debug 2025-08-31 20:44:24 +01:00
Richard Glidden 6d40ac8644 chore: Fix shellcheck errors 2025-08-29 16:24:30 -04:00
Richard Glidden d7c428fc8d feat: Add ability to deploy to remote TrueNAS instances 2025-08-29 16:24:30 -04:00
neil 28f8f56fa3 Merge pull request #6365 from OPPO9008/dev
dnsla api更新
2025-08-28 13:45:15 +08:00
neil b1f6b5314c Merge pull request #5197 from aorith/dev
fix: rage4 - add error 400 and TXT cleanup
2025-08-28 13:42:03 +08:00
asauerwein fdb1e8c2e4 fix usage of H1 header
change to while loop

use global variable for loop

fix if statement to be sh compliant

shfmt
2025-08-20 18:37:25 +02:00
asauerwein 5b02e86334 add template_stack option to push to device 2025-08-20 17:47:36 +02:00
风扇滑翔翼 8713918bdb Fix ipv6 cert cannot be found 2025-08-20 13:00:56 +08:00
neil 10627e4974 Merge pull request #6473 from woutd/constellix-rate-limit
Add sleep before each REST call to Constellix to prevent rate limit
2025-08-19 10:05:37 +08:00
neil 1b89e7c2ec Merge pull request #6476 from TobiasGrave/fix_variomedia_api_2
Variomedia API: Fix DNS challenge TXT record deletion
2025-08-16 00:18:03 +08:00
Tobias Grave bcf0afb25e Variomedia API: Fix DNS deletion issues 2025-08-15 09:02:57 +02:00
wout ab22c8ca1c Convert domain to lower case, needed for Constellix REST API 2025-08-12 19:04:19 +02:00
neil 49513cb2ee Merge pull request #6284 from fornex-com/master
Optimize root domain search through api
2025-08-12 22:31:02 +08:00
wout 1b5e66f9c2 Add sleep before each REST call to Constellix to prevent rate limit 2025-08-12 11:30:16 +02:00
1f486fc9a5 Upload latest dev branch to master (#3)
* 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>
2025-08-12 11:12:09 +03:00
Viktor Polyakov 45c4a98f1d feat: add message_thread_id to telegram notifications 2025-08-11 16:05:09 +03:00
neil 4cdbdf2125 Merge pull request #6391 from Lambiek12/dev
New dnsapi support for OpenProvider.eu using REST API
2025-08-06 19:28:09 +08:00
neil ec5ecabdec Merge pull request #5008 from cjwalsh/patch-1
Fix for empty error objects in response breaking extraction of domain validation types
2025-08-06 19:07:19 +08:00
Ciaran Walsh e391301348 Merge branch 'acmesh-official:master' into patch-1 2025-08-03 22:42:11 +01:00
Ciaran Walsh 49866b6cf4 Merge branch 'dev' into patch-1 2025-08-03 21:48:26 +01:00
invario 3252e0ce2e Add outputs for PFX and PEM
Signed-off-by: invario <67800603+invario@users.noreply.github.com>
2025-07-28 12:14:12 -04:00
Jan-willem van Kampen 09d326de3a Merge branch 'acmesh-official:dev' into dev 2025-07-25 18:49:53 +02:00
Alexis Savin eda8614754 Merge branch 'acmesh-official:master' into dns_efficientip 2025-07-18 12:13:37 +02:00
JF DAGUIN f1209ce06a Merge branch 'dev' into dns_infomaniak_API_v2 2025-07-16 20:39:47 +02:00
PrivacyFreak 06c1911a28 fix keystore ownership read for unifi.sh 2025-07-14 18:48:32 +00:00
neil 8cac773e34 Merge pull request #6444 from davidjb/fix-typo
Fix logged typo when running pre hook
2025-07-13 12:08:19 +08:00
neil 365ec1fd5e Merge pull request #6445 from davidjb/onbeforeissue-error-handling
Run post hook when `_on_before_issue()` errors
2025-07-13 12:07:45 +08:00
David Beitey 40e58ed12d Run post hook when _on_before_issue errors 2025-07-13 11:40:34 +10:00
David Beitey 0c98dc54fe Fix logged typo when running pre hook 2025-07-13 11:19:23 +10:00
Jens Spanier 3b0f624302 Support certificate profile selection 2025-07-10 10:55:05 +02:00
JF DAGUIN 5808b8d176 Merge branch 'dev' into dns_infomaniak_API_v2 2025-07-09 13:43:23 +02:00
pileus-lines c2f8b4d1f2 Update dns_infomaniak.sh
because infomaniak API v1 no longer works
2025-07-09 12:13:17 +02:00
neil 56242423b2 Merge pull request #6434 from stokito/dnsapi_fix_info
DNS API: Fix Structural  Info
2025-07-08 10:47:54 +08:00
invario 014a781426 Create localcopy deploy-hook
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>
2025-07-06 20:12:10 -04:00
Sergey Ponomarev 8113711b7a dnsapi: fix Structured DNS Info
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2025-07-06 01:43:16 +03:00
Sergey Ponomarev 85ec6343ff dnsapi: dns_mijnhost.sh rearrange fields, use user docs instead of API docs
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2025-07-06 01:42:39 +03:00
Sergey Ponomarev daf183e2cc dnsapi: dns_vultr.sh remove empty author
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2025-07-06 01:41:58 +03:00
Sergey Ponomarev c6819cbd6b dnsapi: fix authors: use @ for GitHub profiles
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2025-07-06 01:40:53 +03:00
Sergey Ponomarev 01ed3c3326 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>
2025-07-06 01:36:40 +03:00
Sergey Ponomarev 76b68f7ccb dnsapi: dns_mydnsjp.sh fix author
The @epgdatacapbon was renamed to @tkmsst

Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2025-07-06 01:34:21 +03:00
OPPO9008 2bea808251 Update dns_la.sh 2025-07-02 21:15:46 +08:00
neil 52e051bb02 Merge pull request #6417 from acmesh-official/dev
wiki
2025-06-25 22:15:26 +02:00
neil 74fdf649d3 wiki 2025-06-25 22:14:30 +02:00
neil 2402f463e1 Merge pull request #6416 from acmesh-official/dev
sync
2025-06-25 22:12:27 +02:00
neil 424d33faa0 wiki 2025-06-25 22:11:35 +02:00
neil b64c57fa36 Merge pull request #6414 from acmesh-official/dev
minor
2025-06-25 22:02:02 +02:00
neil 6966b3810d minor 2025-06-25 22:01:11 +02:00
neil 02cb4764c2 Merge pull request #6412 from acmesh-official/dev
minor
2025-06-25 21:52:41 +02:00
neil 89071f7226 minor 2025-06-25 21:51:55 +02:00
neil d6eca62e56 Merge pull request #6411 from acmesh-official/dev
fix for wiki
2025-06-25 21:47:46 +02:00
neil b025e7f0f2 fix for wiki 2025-06-25 21:46:58 +02:00
neil 0f7ecad4e8 Merge pull request #6409 from acmesh-official/dev
sync
2025-06-25 21:34:58 +02:00
neil 4a16aaacb6 add 2025-06-25 21:27:08 +02:00
asavin ca08ce4262 Fixing forgottent openssl ref 2025-06-23 08:59:33 +02:00
neil 21c758c3d3 Merge pull request #6393 from diamondo25/dev-configure-acme_directory-timeout
Configure 10 second timeout to ACME_DIRECTORY API call
2025-06-21 22:19:56 +02:00
neil c338f1a3fa Merge pull request #6404 from laDanz/master
add support for AIX style netstat
2025-06-21 21:52:10 +02:00
laDanz 242085d676 add support for AIX style netstat 2025-06-17 14:05:40 +02:00
keryfan 289d895423 Merge branch 'dev' into master 2025-06-13 17:59:18 +03:00
asavin eb22a84db4 Merge branch 'dns_efficientip' 2025-06-11 16:47:36 +02:00
Alexis Savin d66dd99621 Merge branch 'dev' into dns_efficientip 2025-06-11 16:33:09 +02:00
Erwin Oegema f2b248243c Configure 10 second timeout to ACME_DIRECTORY API call 2025-06-10 10:36:06 +02:00
Lambiek12 fcd358eb71 Resolve spellcheck error 2025-06-08 17:35:09 +02:00
Lambiek12 06d3739a8d Cleanup duplicate debug log output based on DNS test run 2025-06-08 17:29:39 +02:00
Lambiek12 bff1064dbd Add new dnsapi support for OpenProvider.eu using new REST API 2025-06-08 15:39:10 +02:00
OPPO9008 19678db933 Update dns_la.sh 2025-06-06 02:06:27 +08:00
neil 3d8b682380 Merge pull request #6380 from schlegel11/master
Cloudns provider: Fix missing api password encoding
2025-05-31 17:52:44 +02:00
neil 7b76502ee6 Merge pull request #6352 from ecm75/kemploadmaster
Add deploy script for Kemp Loadmaster
2025-05-31 17:30:50 +02:00
Marcel Schlegel 28687ad7c7 Issue 3968: Fix missing api password encoding. 2025-05-31 15:02:25 +02:00
neil d420ff0e56 Merge pull request #5043 from coderjoe/feature/deploy-to-zyxel-gs1900-switches
Add deploy/zyxel_gs1900.sh
2025-05-25 19:05:33 +02:00
asavin 5a085f2514 Addressing #discussion_r2105799190 2025-05-25 18:37:17 +02:00
Alexis Savin e79ee7fb74 Merge branch 'acmesh-official:master' into dns_efficientip 2025-05-25 18:27:39 +02:00
neil ed1adfd878 Merge pull request #6355 from ymol-spraaklab/dev
Set DNS Record TTL to 60 instead of 300 sec
2025-05-24 14:08:57 +02:00
neil 58a7b9ffa7 Merge pull request #6366 from stokito/patch-1
DNS API: fix structural info for new providers
2025-05-24 14:07:19 +02:00
neil af5d046698 Merge pull request #6367 from KincaidYang/dev
docs (dns_tencent) : update documentation links
2025-05-24 14:00:28 +02:00
YANGJINZE 9e75b707ce Merge pull request #1 from KincaidYang/KincaidYang-patch-1
docs (dns_tencent) : update documentation links
2025-05-23 18:03:56 +08:00
YANGJINZE 8241b078ce docs (dns_tencent) : update documentation links 2025-05-23 17:54:56 +08:00
OPPO9008 c8f1e41197 Update dns_la.sh 2025-05-20 20:29:44 +08:00
OPPO9008 cddf098f47 Update dns_la.sh 2025-05-20 20:28:59 +08:00
OPPO9008 500cfbc19c Update dns_la.sh 2025-05-19 21:29:33 +08:00
Sergey Ponomarev 133ae8555a dns_freemyip.sh: fix strutural info 2025-05-19 15:15:46 +03:00
Sergey Ponomarev f132010acb dns_edgecenter.sh: fix structural info 2025-05-19 15:08:54 +03:00
ymol-spraaklab 426305dc33 Merge branch 'dev' into dev 2025-05-19 10:49:48 +02:00
emueller 55282851c4 implemented all suggestions 2025-05-19 09:18:29 +02:00
OPPO9008 9e7d1b9ce7 Update dns_la.sh 2025-05-19 13:16:30 +08:00
OPPO9008 e0da5f1703 Update dns_la.sh 2025-05-19 09:49:21 +08:00
Joe Bauser 4f5a70b804 Apply suggested fixes from shfmt diffs 2025-05-17 21:25:39 -04:00
neil 42bbd1b44a Merge pull request #6359 from acmesh-official/dev
sync
2025-05-17 16:56:42 -04:00
neil fdeaf861e3 Merge pull request #6197 from xvybihal/dns_active24v2
dns_active24: rewrite for supporting new v2 API
2025-05-17 16:35:59 -04:00
neil 8e032a27c4 Merge pull request #6354 from phedoreanu/dev
dns_1984.hosting.sh: fix session cookie name
2025-05-17 15:38:54 -04:00
neil 5bfd43b718 fix pr 2025-05-17 21:28:26 +02:00
neil ce7f4cb9a8 Merge pull request #6356 from Attackwave/deploy/truenas_ws
Deploy/truenas ws
2025-05-17 15:23:03 -04:00
Attackwave 99a4cf9e07 Quickfix TrueNAS 25.04 2025-05-16 22:44:25 +02:00
ymol-spraaklab b82f6801cd Set DNS Record TTL to 60 instead of 300 sec 2025-05-16 15:40:36 +02:00
Adrian Fedoreanu 184cb0b9a8 dns_1984.hosting.sh: fix session cookie name 2025-05-15 16:01:24 +02:00
emueller bf2e99efa6 fixed quoting in kemplm.sh 2025-05-12 10:52:35 +02:00
emueller 7543d5220c fixed kemplm.sh formatting 2025-05-12 10:45:01 +02:00
emueller ca73e1f024 added deploy/kemplm.sh for deploying certs on Kemp Loadmaster 2025-05-12 10:28:35 +02:00
EfficientIP-Labs 718ff3a5f5 Merge branch 'acmesh-official:master' into dns_efficientip 2025-05-09 09:03:45 +02:00
neil c2ccc1f980 Merge pull request #6347 from acmesh-official/dev 2025-05-08 15:18:55 -04:00
keryfan f34d4bd6da Merge branch 'acmesh-official:master' into master 2025-05-08 16:26:10 +02:00
Josef Vybíhal 8a4b436314 Merge branch 'acmesh-official:master' into dns_active24v2 2025-05-05 15:48:45 +02:00
Josef Vybíhal 42aaf7c2a0 dns_active24: rewrite for supporting new v2 API 2025-05-05 15:48:02 +02:00
neil eb00852a71 remove ocsp for letsencrypt server 2025-05-01 13:28:20 +02:00
neil b0535d8b40 Merge pull request #6306 from Meo597/dns-spaceship
Add Spaceship DNS API
2025-04-27 18:38:38 +02:00
Meo597 8b4d93cc14 Spaceship: fix doc 2025-04-28 00:32:46 +08:00
Meo597 e2d0923122 Spaceship: replace ~/.acme.sh with $LE_CONFIG_HOME for configurable paths 2025-04-28 00:18:23 +08:00
Meo597 2928d84339 Spaceship: replace debug with debug2 for detailed output in complex debugging 2025-04-28 00:04:49 +08:00
neil b5e3883891 update 2025-04-26 16:47:29 +02:00
asavin a2e52dadb9 Triggering pipeline with DNS_WILDCARD 2025-04-25 09:59:40 +02:00
asavin ca4cb018d0 Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin c2762d3b6f Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 947e872850 Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 3baa5e145f Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 67fd35127c Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin c421e2ddfc Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 42febe97b5 Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 74ca0fb763 Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin 13631ea2de Triggering another action pipeline 2025-04-25 09:59:40 +02:00
asavin a1eee5923a Disabling SC2034 2025-04-25 09:59:40 +02:00
asavin af92bbca2a Disabling SC2034 2025-04-25 09:59:40 +02:00
asavin c9287071e3 Fixing shellcheck issue 2025-04-25 09:59:40 +02:00
asavin 292026288a Fixing sh syntax 2025-04-25 09:59:40 +02:00
asavin 1f056998f3 Update for testing github action pipeline 2025-04-25 09:59:40 +02:00
asavin 4d7cb7de5f Update for testing github action pipeline 2025-04-25 09:59:40 +02:00
asavin 67855f21d4 Updating issue ID 2025-04-25 09:59:40 +02:00
asavin 8484565e95 Updating Options to meet OptionsAlt pre-requisites 2025-04-25 09:59:40 +02:00
asavin f7d8abe8ea Fixing shellcheck issues 2025-04-25 09:59:40 +02:00
asavin 8ca90297e7 Remove export ? 2025-04-25 09:59:40 +02:00
asavin e08f9080c2 Initial commit 2025-04-25 09:59:40 +02:00
Meo597 d01aefd1eb Spaceship: i starts from 1 2025-04-25 05:24:05 +08:00
Meo597 e1d447847f Spaceship: fix domain conf 2025-04-25 05:21:52 +08:00
asavin 419738fbd5 Triggering pipeline with DNS_WILDCARD 2025-04-24 16:05:20 +02:00
asavin 7f1423dd6f Triggering another action pipeline 2025-04-24 13:33:28 +02:00
asavin 9f09dcd18c Triggering another action pipeline 2025-04-24 11:13:38 +02:00
asavin 91081ade3c Triggering another action pipeline 2025-04-24 10:50:57 +02:00
asavin 4d933c23a8 Triggering another action pipeline 2025-04-24 10:43:46 +02:00
asavin f29bfd995d Triggering another action pipeline 2025-04-24 10:15:56 +02:00
Joe Bauser 24a1b93842 Add deploy/zyxel_gs1900.sh
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
2025-04-23 17:52:12 -04:00
neil bed1c311e3 Merge pull request #6283 from tklk-forks/azure-managed-identities-endpoint
Azure: Use endpoint environment variable for managed identities if set
2025-04-23 21:52:07 +02:00
neil 1ce8d3ae9b start 3.1.2 2025-04-23 21:42:39 +02:00
asavin 30d5d1aea9 Triggering another action pipeline 2025-04-22 18:01:29 +02:00
asavin 7a0450a7f4 Triggering another action pipeline 2025-04-22 17:55:23 +02:00
asavin 5bb09f469f Triggering another action pipeline 2025-04-22 17:06:33 +02:00
asavin 90e9d8ff52 Triggering another action pipeline 2025-04-22 16:38:37 +02:00
asavin 59a43ce5d1 Disabling SC2034 2025-04-22 16:27:36 +02:00
asavin 5bc01aa251 Disabling SC2034 2025-04-22 15:56:49 +02:00
asavin 9eeb979c7b Fixing shellcheck issue 2025-04-22 15:49:41 +02:00
asavin eabd7592fe Fixing sh syntax 2025-04-22 15:41:22 +02:00
asavin e089a3d8a1 Update for testing github action pipeline 2025-04-22 15:31:17 +02:00
asavin 7560375502 Update for testing github action pipeline 2025-04-22 15:23:11 +02:00
asavin 1f77b89266 Updating issue ID 2025-04-22 11:40:01 +02:00
asavin 7c610124d9 Updating Options to meet OptionsAlt pre-requisites 2025-04-22 11:32:23 +02:00
asavin a0c5ef4e6f Fixing shellcheck issues 2025-04-22 11:17:14 +02:00
asavin 218934e767 Remove export ? 2025-04-18 18:06:12 +02:00
asavin c6a9825c0a Initial commit 2025-04-18 17:25:55 +02:00
Meo597 827315e059 Spaceship: valid api response 2025-04-15 21:02:46 +08:00
Meo597 e55a54f3d4 Spaceship: fix get_root 2025-04-15 21:02:46 +08:00
Meo597 5e8b40faf6 Spaceship: fix rm 2025-04-15 21:02:46 +08:00
Meo597 45b9982172 Add Spaceship DNS API 2025-04-15 21:02:45 +08:00
techknowlogick dd29f970a2 Use endpoint environment variable for managed identities if set
Some environments in azure don't use the default metadata endpoint, and instead inject an env var that should be used.
2025-03-31 10:47:49 -04:00
keryfan b6a9a1a7ee Merge branch 'dev' into master 2025-03-31 11:03:27 +03:00
taras-l b38d15adb3 Merge pull request #2 from fornex-com/fornex_dnsapi_fix
Update _get_root url in dnsapi/dns_fornex.sh
2025-03-11 10:21:35 +02:00
kir 29342e036f Update _get_root url in dnsapi/dns_fornex.sh 2025-03-11 08:07:38 +00:00
neil ee661e5d71 fix format 2024-12-10 21:02:54 +01:00
neil 5ddffc9172 fix format 2024-12-10 21:01:37 +01:00
neil 40dd085ef8 Merge pull request #6159 from acmesh-official/dev
fix format
2024-12-10 20:59:27 +01:00
neil d5b5bcef56 support ARI, not finished yet
https://github.com/acmesh-official/acme.sh/issues/4944
2024-12-10 20:54:20 +01:00
b1n23 2f5ea120cb deployhook KeyHelp: fix bug 2024-07-16 00:25:53 +08:00
Manuel Sanchez Pinar 2beb2f5659 fix: rage4 - add error 400 and TXT cleanup
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
2024-07-05 09:23:14 +02:00
b1n23 3f40380c69 deployhook Directadmin: Support for selecting the scheme of DirectAdmin , HTTP or HTTPS 2024-06-03 16:57:51 +08:00
b1n23 1116b73a08 deployhook KeyHelp: Support enabling the Enforce HTTPS option 2024-06-03 16:47:43 +08:00
b1n23 bfba44fbad format adjustment 2024-04-07 12:36:19 +00:00
b1n23 c466f063c8 add newline at end of file 2024-04-01 21:59:12 +08:00
b1n23 295af01687 Add deployhook for KeyHelp 2024-03-28 23:07:14 +08:00
b1n23 e7284df1df Add deployhook for DirectAdmin 2024-03-21 21:44:33 +08:00
b1n23 3b46060caa deployhook Netlify: Support multiple Site ID 2024-03-20 23:06:09 +08:00
b1n23 696182cfa4 deployhook Edgio: Support multiple Environment ID 2024-03-20 23:05:43 +08:00
b1n23 d1a1d1da8f Add deployhook for CacheFly 2024-03-20 21:00:50 +08:00
b1n23 c508984f56 Add deployhook for Edgio 2024-03-20 21:00:50 +08:00
b1n23 54eba51b35 Add deployhook for Netlify 2024-03-20 21:00:50 +08:00
Ciaran Walsh b0ca4435fd 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
2024-02-21 00:21:09 +00:00
188 changed files with 17558 additions and 1613 deletions

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
# 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.
| Instead of | Use |
|---|---|
| `tr '[:upper:]' '[:lower:]'` | `_lower_case()` |
| `tr '[:lower:]' '[:upper:]'` | `_upper_case()` |
| `head -n 1` | `_head_n 1` |
| `openssl dgst` / `openssl` | `_digest()` / `_hmac()` |
| `date` | `_utc_date()` with `sed`/`tr` |
| `curl` / `wget` | `_get()` or `_post()` |
| `sleep` | `_sleep` |
| `base64` / `openssl base64` | `_base64()` |
| `$(( ))` arithmetic | `_math()` |
| `grep -E` / `grep -Po` | `_egrep_o()` |
| `printf` | `echo` |
| `idn` command | `_idn()` / `_is_idn()` |
| `mktemp` | `_mktemp()` |
| `[:space:]` | ` ` |
| `[:alnum:]` | `A-Za-z0-9` |
| `[:alpha:]` | `A-Za-z` |
| `[:digit:]` | `0-9` |
| `awk` | `cut` / `sed` / `while read` loops |
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`.
- **Deploy hooks**: `_savedeployconf` / `_getdeployconf`
- **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)
- Deploy hooks: [deployhooks](https://github.com/acmesh-official/acme.sh/wiki/deployhooks)
- Notification hooks: [notify](https://github.com/acmesh-official/acme.sh/wiki/notify)
- Options: [Options-and-Params](https://github.com/acmesh-official/acme.sh/wiki/Options-and-Params)
- **In-code usage**: add usage examples in the help text of `acme.sh` itself.
- **README**: add website URLs for new DNS providers.
---
## CI and Merge Hygiene
- All CI checks must pass before merge.
- Rebase to the latest `dev` branch frequently -- do not use merge commits.
- Enable GitHub Actions on your fork to catch errors early.
- Run the [DNS API Test](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test) workflow for DNS plugins.
- For Docker changes, ensure the Dockerfile includes any required dependencies.
---
## Debug Logging
- Use `_debug2` (not `_debug3` or other levels) unless there is a specific reason for a different level.
---
## Things to Avoid in Reviews
- Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or `shfmt`.
- Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.
+50
View File
@@ -0,0 +1,50 @@
name: Apache
on:
push:
paths:
- '*.sh'
- '.github/workflows/Apache.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/Apache.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Apache:
runs-on: ubuntu-latest
env:
TestingDomain: example.com
TEST_ACME_Server: https://localhost:14000/dir
HTTPS_INSECURE: 1
TEST_LOCAL: 1
TEST_CA: "Pebble Intermediate CA"
TEST_APACHE: 1
CASE: le_test_apache
steps:
- uses: actions/checkout@v6
- name: Install tools
run: sudo apt-get install -y socat apache2
- name: Run Pebble
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
- name: Set up Pebble
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
- name: Set up Apache
# Apache serves on 5002, which is the HTTP-01 validation port in
# Pebble's default config; acme.sh appends the challenge Alias to
# the main config itself
run: |
echo "Listen 5002" | sudo tee /etc/apache2/ports.conf
sudo sed -i "s/\*:80/*:5002/" /etc/apache2/sites-available/000-default.conf
sudo apache2ctl configtest
sudo systemctl restart apache2
curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://127.0.0.1:5002/ | grep -E "200|403|404"
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: cd ../acmetest && sudo --preserve-env ./letest.sh
+357 -28
View File
@@ -26,9 +26,9 @@ jobs:
id: step_one
run: |
if [ "${{secrets.TokenName1}}" ] ; then
echo "::set-output name=hasToken::true"
echo "hasToken=true" >> "$GITHUB_OUTPUT"
else
echo "::set-output name=hasToken::false"
echo "hasToken=false" >> "$GITHUB_OUTPUT"
fi
- name: Check the value
run: echo ${{ steps.step_one.outputs.hasToken }}
@@ -66,7 +66,7 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Set env file
@@ -114,9 +114,11 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install tools
run: brew install socat
run: |
brew untap aws/tap || true
brew install socat
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
@@ -165,7 +167,7 @@ jobs:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install cygwin base packages with chocolatey
run: |
choco config get cacheLocation
@@ -176,9 +178,14 @@ jobs:
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
shell: cmd
- name: Set ENV
shell: cmd
shell: bash
run: |
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
echo 'PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' >> "$GITHUB_ENV"
# cygwin git sees the runner workspace as owned by another user and
# fails with "dubious ownership" (exit 128) in the checkout post step
echo 'GIT_CONFIG_COUNT=1' >> "$GITHUB_ENV"
echo 'GIT_CONFIG_KEY_0=safe.directory' >> "$GITHUB_ENV"
echo 'GIT_CONFIG_VALUE_0=*' >> "$GITHUB_ENV"
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
@@ -224,15 +231,16 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkg install -y socat curl
usesh: true
copyback: false
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
@@ -251,13 +259,19 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
OpenBSD:
GhostBSD:
runs-on: ubuntu-latest
needs: FreeBSD
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
continue-on-error: true
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
@@ -275,15 +289,16 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v1
- uses: vmactions/ghostbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkg_add socat curl libiconv
prepare: pkg install -y socat curl
usesh: true
copyback: false
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
@@ -302,9 +317,69 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
OpenBSD:
runs-on: ubuntu-latest
needs: GhostBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkg_add socat curl libiconv
usesh: true
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
NetBSD:
runs-on: ubuntu-latest
@@ -326,16 +401,17 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: |
/usr/sbin/pkg_add curl socat
usesh: true
copyback: false
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
@@ -354,7 +430,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
@@ -378,16 +458,18 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: |
pkg install -y curl socat libnghttp2
pkg install -y libnghttp2
pkg install -y curl socat
usesh: true
copyback: false
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
@@ -406,16 +488,76 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
MidnightBSD:
runs-on: ubuntu-latest
needs: DragonFlyBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/midnightbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: mport install socat curl || true
usesh: true
sync: nfs
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
Solaris:
runs-on: ubuntu-latest
needs: DragonFlyBSD
needs: MidnightBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
@@ -434,14 +576,17 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
copyback: false
prepare: pkgutil -y -i socat
sync: nfs
prepare: |
pkgutil -U
pkgutil -y -i socat
run: |
pkg set-mediator -v -I default@1.1 openssl
export PATH=/usr/gnu/bin:$PATH
@@ -462,6 +607,11 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
Omnios:
@@ -485,13 +635,14 @@ jobs:
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/omnios-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
copyback: false
sync: nfs
prepare: pkg install socat
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
@@ -511,5 +662,183 @@ jobs:
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
OpenIndiana:
runs-on: ubuntu-latest
needs: Omnios
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openindiana-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
sync: nfs
prepare: pkg install socat
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
Tribblix:
runs-on: ubuntu-latest
needs: OpenIndiana
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Tribblix doesn't accept the expired ISRG X1 root
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/tribblix-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
sync: nfs
prepare: zap install socat
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
Haiku:
runs-on: ubuntu-latest
needs: Tribblix
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v6
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/haiku-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
sync: rsync
copyback: false
prepare: |
mkdir -p /boot/home/.cache
pkgman install -y cronie
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+13 -7
View File
@@ -31,8 +31,8 @@ jobs:
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -45,8 +45,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -57,15 +57,21 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: |
pkg install -y curl socat libnghttp2
pkg install -y libnghttp2
pkg install -y curl socat
usesh: true
copyback: false
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+11 -6
View File
@@ -37,8 +37,8 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -63,14 +63,19 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install -y socat curl wget
usesh: true
copyback: false
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+82
View File
@@ -0,0 +1,82 @@
name: GhostBSD
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/GhostBSD.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/GhostBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
GhostBSD:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
continue-on-error: true
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/ghostbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install -y socat curl wget
usesh: true
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+84
View File
@@ -0,0 +1,84 @@
name: Haiku
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/Haiku.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/Haiku.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Haiku:
strategy:
fail-fast: false
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/haiku-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: |
mkdir -p /boot/home/.cache
pkgman install -y cronie
sync: rsync
copyback: false
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+9 -2
View File
@@ -26,14 +26,21 @@ jobs:
Linux:
strategy:
matrix:
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"]
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "gentoo/stage3"]
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_PREFERRED_CHAIN: (STAGING)
TEST_ACME_Server: "LetsEncrypt.org_test"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 80
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: |
cd .. \
+10 -3
View File
@@ -31,8 +31,8 @@ jobs:
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-latest
@@ -44,9 +44,16 @@ jobs:
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install tools
run: brew install socat
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 80
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: |
cd .. \
+74
View File
@@ -0,0 +1,74 @@
name: MidnightBSD
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/MidnightBSD.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/MidnightBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
MidnightBSD:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/midnightbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: mport install socat curl wget || true
usesh: true
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+12 -7
View File
@@ -31,8 +31,8 @@ jobs:
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -45,8 +45,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -57,15 +57,20 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: |
/usr/sbin/pkg_add curl socat
usesh: true
copyback: false
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+66
View File
@@ -0,0 +1,66 @@
name: Nginx
on:
push:
paths:
- '*.sh'
- '.github/workflows/Nginx.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/Nginx.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Nginx:
runs-on: ubuntu-latest
env:
TestingDomain: example.com
TEST_ACME_Server: https://localhost:14000/dir
HTTPS_INSECURE: 1
TEST_LOCAL: 1
TEST_CA: "Pebble Intermediate CA"
TEST_NGINX: 1
CASE: le_test_nginx
steps:
- uses: actions/checkout@v6
- name: Install tools
run: sudo apt-get install -y socat nginx
- name: Run Pebble
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
- name: Set up Pebble
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
- name: Set up nginx
# a backend on 8081 plus a site with an aaPanel/BT style
# "location ^~ /" proxy block that shadows plain regex locations
# (regression for #6125); the site listens on 5002, which is the
# HTTP-01 validation port in Pebble's default config
run: |
sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
server {
listen 127.0.0.1:8081;
location / {
default_type text/plain;
return 200 "backend";
}
}
server {
listen 5002 default_server;
server_name example.com;
location ^~ / {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $http_host;
}
}
EOF
sudo nginx -t
sudo systemctl restart nginx
curl -s -H "Host: example.com" http://127.0.0.1:5002/ | grep backend
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: cd ../acmetest && sudo --preserve-env ./letest.sh
+11 -6
View File
@@ -37,8 +37,8 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -63,13 +63,18 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/omnios-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install socat wget
copyback: false
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+11 -6
View File
@@ -37,8 +37,8 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -63,14 +63,19 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg_add socat curl wget libnghttp2
usesh: true
copyback: false
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+80
View File
@@ -0,0 +1,80 @@
name: OpenIndiana
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/OpenIndiana.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/OpenIndiana.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
OpenIndiana:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openindiana-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install socat curl
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+3 -3
View File
@@ -33,7 +33,7 @@ jobs:
TEST_CA: "Pebble Intermediate CA"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install tools
run: sudo apt-get install -y socat
- name: Run Pebble
@@ -58,14 +58,14 @@ jobs:
TEST_IPCERT: 1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install tools
run: sudo apt-get install -y socat
- name: Run Pebble
run: |
docker run --rm -itd --name=pebble \
-e PEBBLE_VA_ALWAYS_VALID=1 \
-p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict
-p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
+14 -7
View File
@@ -37,8 +37,8 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
@@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
@@ -63,13 +63,20 @@ jobs:
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkgutil -y -i socat curl wget
copyback: false
prepare: |
pkgutil -U
pkgutil -y -i socat curl wget
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+79
View File
@@ -0,0 +1,79 @@
name: Tribblix
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/Tribblix.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/Tribblix.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Tribblix:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v6
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/tribblix-vm@v1
with:
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: zap install socat curl wget
sync: nfs
run: |
cd ../acmetest \
&& ./letest.sh
- name: DebugOnError
if: ${{ failure() }}
run: |
echo "See how to debug in VM:"
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
+3 -3
View File
@@ -37,8 +37,8 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
- TEST_ACME_Server: "ZeroSSL.com"
CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
CA: "ZeroSSL RSA Domain Secure Site CA"
CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
CA: "ZeroSSL RSA DV SSL CA 2"
CA_EMAIL: "githubtest@acme.sh"
TEST_PREFERRED_CHAIN: ""
- TEST_ACME_Server: "https://localhost:9000/acme/acme/directory"
@@ -70,7 +70,7 @@ jobs:
TestingDomain: ${{ matrix.TestingDomain }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install tools
run: sudo apt-get install -y socat wget
- name: Start StepCA
+10 -3
View File
@@ -31,8 +31,8 @@ jobs:
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
# CA: "ZeroSSL RSA DV SSL CA 2"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: windows-latest
@@ -49,7 +49,7 @@ jobs:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install cygwin base packages with chocolatey
run: |
choco config get cacheLocation
@@ -67,6 +67,13 @@ jobs:
shell: cmd
run: |
echo "PATH=%PATH%"
- uses: anyvm-org/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 80
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
shell: cmd
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
+114
View File
@@ -0,0 +1,114 @@
name: Blacklist Command
# An issue titled "blacklist: <login-or-email>" opened by the maintainer
# or a write-access member adds that identity to the Blacklist wiki page
# (see wiki-guard.yml) and closes the issue. The wiki-monitor notification
# embeds a prefilled link that opens such an issue in one click.
on:
issues:
types: [opened]
permissions:
contents: write
issues: write
# Share the wiki-guard concurrency group so we never push to the wiki
# at the same time as the guard.
concurrency:
group: wiki-guard
cancel-in-progress: false
jobs:
blacklist:
# Upstream only: forks have no <fork>.wiki repository to push to.
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'blacklist:')
runs-on: ubuntu-latest
steps:
- name: Check authorization
id: auth
run: |
assoc="${{ github.event.issue.author_association }}"
case "$assoc" in
OWNER|MEMBER|COLLABORATOR)
echo "ok=true" >> "$GITHUB_OUTPUT"
;;
*)
echo "issue author is not authorized ($assoc); ignoring"
echo "ok=false" >> "$GITHUB_OUTPUT"
;;
esac
- name: Checkout wiki repository
if: steps.auth.outputs.ok == 'true'
uses: actions/checkout@v7
with:
repository: ${{ github.repository }}.wiki
path: wiki
- name: Add the identity to the blacklist page
if: steps.auth.outputs.ok == 'true'
id: add
env:
TITLE: ${{ github.event.issue.title }}
run: |
target="$(printf '%s' "$TITLE" \
| sed 's/^blacklist:[[:space:]]*//; s/^@//; s/[[:space:]].*$//' \
| tr 'A-Z' 'a-z')"
case "$target" in
''|*[!a-z0-9._+@-]*)
echo "invalid target: '$target'"
echo "result=invalid" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
echo "target=$target" >> "$GITHUB_OUTPUT"
cd wiki
if [ ! -e Blacklist.md ]; then
echo "result=nopage" >> "$GITHUB_OUTPUT"
exit 0
fi
if grep -Fxiq -- "- $target" Blacklist.md; then
echo "result=already" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ -n "$(tail -c1 Blacklist.md)" ]; then
echo >> Blacklist.md
fi
printf -- '- %s\n' "$target" >> Blacklist.md
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Blacklist.md
git commit -m "blacklist $target (requested in #${{ github.event.issue.number }})"
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
echo "result=added" >> "$GITHUB_OUTPUT"
- name: Reply and close
if: steps.auth.outputs.ok == 'true'
uses: actions/github-script@v9
env:
RESULT: ${{ steps.add.outputs.result }}
TARGET: ${{ steps.add.outputs.target }}
with:
script: |
const result = process.env.RESULT;
const target = process.env.TARGET;
const messages = {
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"
});
+18 -4
View File
@@ -41,23 +41,29 @@ jobs:
runs-on: ubuntu-latest
needs: CheckToken
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
permissions:
contents: read
packages: write
steps:
- name: checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v4
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5.5.1
uses: docker/metadata-action@v6
with:
images: ${DOCKER_IMAGE}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v4
- name: login to docker hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: login to ghcr
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: build and push the image
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
@@ -73,6 +79,8 @@ jobs:
fi
fi
echo "DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG}" >>"$GITHUB_ENV"
DOCKER_LABELS=()
while read -r label; do
DOCKER_LABELS+=(--label "${label}")
@@ -84,3 +92,9 @@ jobs:
--output "type=image,push=true" \
--build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .
- name: mirror the image to ghcr (best-effort)
run: |
docker buildx imagetools create \
--tag ghcr.io/${{ github.repository }}:${DOCKER_IMAGE_TAG} \
${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
|| echo "::warning::GHCR mirror failed; Docker Hub publish unaffected"
+115 -5
View File
@@ -2,18 +2,128 @@ name: "Update issues"
on:
issues:
types: [opened]
issue_comment:
types: [created]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v9
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
const item = context.payload.issue || context.payload.pull_request;
// Close on sight anything opened by a user on the wiki Blacklist
// page (maintained by the Wiki Guard workflow).
let blacklist = [];
try {
const res = await fetch(`https://raw.githubusercontent.com/wiki/${context.repo.owner}/${context.repo.repo}/Blacklist.md`);
if (res.ok) {
blacklist = (await res.text()).split("\n")
.filter(l => l.startsWith("- "))
.map(l => l.slice(2).trim().toLowerCase())
.filter(Boolean);
}
} catch (e) {
core.warning(`Failed to fetch the blacklist: ${e}`);
}
// A comment on a closed tracking issue reopens it (the standard
// closing note promises this). Bots, blacklisted users and the
// maintainer's own comments don't reopen.
if (context.eventName === "issue_comment") {
const issue = context.payload.issue;
const commenter = context.payload.comment.user;
if (issue.pull_request || issue.state !== "closed") {
return;
}
if (!/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
return;
}
if (commenter.type === "Bot" ||
commenter.login.toLowerCase() === "neilpang" ||
blacklist.includes(commenter.login.toLowerCase())) {
return;
}
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: "open"
});
return;
}
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."
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."
})
+10 -7
View File
@@ -11,8 +11,9 @@ on:
jobs:
welcome:
runs-on: ubuntu-latest
if: github.actor != 'neilpang'
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
@@ -20,12 +21,14 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
body: `**Welcome**
First thing: don't send PR to the master branch, please send to the dev branch instead.
Please make sure you've read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
Then reply on this message, otherwise, your code will not be reviewed or merged.
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
We look forward to reviewing your Pull request shortly ✨
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
READ ME !!!!!
Read me !!!!!!
First thing: don't send PR to the master branch, please send to the dev branch instead.
Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide).
You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test).
Then reply on this message, otherwise, your code will not be reviewed or merged.
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
`
})
+2 -1
View File
@@ -13,8 +13,9 @@ on:
jobs:
welcome:
runs-on: ubuntu-latest
if: github.actor != 'neilpang'
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
+110
View File
@@ -0,0 +1,110 @@
name: Revert Command
# An issue titled "revert: <wiki-commit-sha>" opened by the maintainer or
# a write-access member reverts that commit in the wiki repository and
# closes the issue. The wiki-monitor notification embeds a prefilled link
# that opens such an issue in one click.
on:
issues:
types: [opened]
permissions:
contents: write
issues: write
# Share the wiki-guard concurrency group so we never push to the wiki
# at the same time as the guard.
concurrency:
group: wiki-guard
cancel-in-progress: false
jobs:
revert:
# Upstream only: forks have no <fork>.wiki repository to push to.
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'revert:')
runs-on: ubuntu-latest
steps:
- name: Check authorization
id: auth
run: |
assoc="${{ github.event.issue.author_association }}"
case "$assoc" in
OWNER|MEMBER|COLLABORATOR)
echo "ok=true" >> "$GITHUB_OUTPUT"
;;
*)
echo "issue author is not authorized ($assoc); ignoring"
echo "ok=false" >> "$GITHUB_OUTPUT"
;;
esac
- name: Checkout wiki repository
if: steps.auth.outputs.ok == 'true'
uses: actions/checkout@v7
with:
repository: ${{ github.repository }}.wiki
path: wiki
fetch-depth: 0
- name: Revert the wiki commit
if: steps.auth.outputs.ok == 'true'
id: revert
env:
TITLE: ${{ github.event.issue.title }}
run: |
target="$(printf '%s' "$TITLE" \
| sed 's/^revert:[[:space:]]*//; s/[[:space:]].*$//' \
| tr 'A-Z' 'a-z')"
case "$target" in
*[!0-9a-f]*|"")
echo "invalid commit sha: '$target'"
echo "result=invalid" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
echo "target=$target" >> "$GITHUB_OUTPUT"
cd wiki
if ! git cat-file -e "$target^{commit}" 2>/dev/null; then
echo "result=notfound" >> "$GITHUB_OUTPUT"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git revert --no-edit "$target"; then
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
echo "result=reverted" >> "$GITHUB_OUTPUT"
else
git revert --abort || true
echo "result=conflict" >> "$GITHUB_OUTPUT"
fi
- name: Reply and close
if: steps.auth.outputs.ok == 'true'
uses: actions/github-script@v9
env:
RESULT: ${{ steps.revert.outputs.result }}
TARGET: ${{ steps.revert.outputs.target }}
with:
script: |
const result = process.env.RESULT;
const target = process.env.TARGET;
const messages = {
reverted: `Wiki commit \`${target}\` has been reverted.`,
conflict: `Reverting \`${target}\` conflicts with later edits; please revert manually from the page history.`,
notfound: `Commit \`${target}\` was not found in the wiki repository.`,
invalid: "Could not parse a commit sha from the issue title."
};
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 === "reverted" ? "completed" : "not_planned"
});
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
ShellCheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Shellcheck
run: sudo apt-get install -y shellcheck
- name: DoShellcheck
@@ -31,7 +31,7 @@ jobs:
shfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install shfmt
run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt
- name: shfmt
+325
View File
@@ -0,0 +1,325 @@
name: Wiki Guard
# Rules enforced here:
# - Only the maintainer and write-access members may delete or rename wiki
# pages. Anyone else doing so gets blacklisted and the page restored to
# its last good revision.
# - Only the maintainer and write-access members may edit the Blacklist
# wiki page. Anyone else touching it gets blacklisted and the page
# reverted.
# - Any wiki change made by a blacklisted identity is reverted.
# A "good" revision is one authored by the maintainer, by this bot, or by
# a non-blacklisted user -- restoring from the deleted commit's parent is
# NOT safe, because vandals replace a page before destroying it and the
# parent would launder their version into a bot commit.
# The gollum event only fires on page create/update, never on deletion,
# so violations are caught by polling the wiki git history.
on:
schedule:
- cron: "*/10 * * * *"
gollum:
# Piggyback on frequent repo activity, because the cron schedule is
# best-effort and often delayed well beyond its interval.
issues:
types: [opened]
issue_comment:
types: [created]
workflow_dispatch:
permissions:
contents: write
issues: write
concurrency:
group: wiki-guard
cancel-in-progress: false
jobs:
guard:
# Forks have no <fork>.wiki repository, so the checkout below would
# fail there -- run only in the upstream repository.
if: github.repository == 'acmesh-official/acme.sh'
runs-on: ubuntu-latest
steps:
- name: Checkout wiki repository
uses: actions/checkout@v7
with:
repository: ${{ github.repository }}.wiki
path: wiki
fetch-depth: 0
- name: Enforce wiki rules
id: guard
env:
# WIKI_GUARD_TOKEN: a PAT with read:org, needed to enumerate
# members whose write access comes via the organization -- the
# repo-scoped GITHUB_TOKEN only sees direct collaborators.
GH_TOKEN: ${{ secrets.WIKI_GUARD_TOKEN || secrets.GITHUB_TOKEN }}
run: |
# Logins with write (push) access to the repository, including
# organization members -- they may delete/rename pages and edit
# the blacklist just like the maintainer. If the API call fails,
# the list stays empty and enforcement falls back to
# maintainer-only, which is the safe direction.
gh api "repos/${GITHUB_REPOSITORY}/collaborators?per_page=100" --paginate \
-q '.[] | select(.permissions.push) | .login' 2>/dev/null \
| tr 'A-Z' 'a-z' | sort -u > writers.txt || true
echo "write-access members loaded: $(wc -l < writers.txt)"
cd wiki
git config core.quotePath false
# Any author email under this domain is the maintainer.
OWNER_DOMAIN="neilpang.com"
# Our own enforcement commits.
BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"
BL_PAGE="Blacklist.md"
# Rolling window; the cron runs every 10 minutes, so 7 days gives
# ample overlap without re-judging old changes the maintainer
# already accepted.
WINDOW="7 days ago"
: > ../actions.txt
: > ../bl_new.txt
is_owner() {
case "$1" in
*@"$OWNER_DOMAIN") return 0 ;;
esac
return 1
}
is_bot() {
[ "$1" = "$BOT_EMAIL" ]
}
author_email() {
git show -s --format=%ae "$1" | tr 'A-Z' 'a-z'
}
# Identity of a commit author: the GitHub login when the email is a
# users.noreply.github.com address, otherwise the email itself.
identity_of() {
case "$1" in
*+*@users.noreply.github.com)
printf '%s\n' "$1" | sed 's/^[^+]*+//; s/@users\.noreply\.github\.com$//'
;;
*@users.noreply.github.com)
printf '%s\n' "$1" | sed 's/@users\.noreply\.github\.com$//'
;;
*)
printf '%s\n' "$1"
;;
esac
}
is_blacklisted() {
grep -Fxq "$1" ../bl_all.txt
}
# Trusted committers: the maintainer (by email domain), this bot,
# and anyone whose GitHub login has write access to the repo.
is_trusted() {
if is_owner "$1" || is_bot "$1"; then
return 0
fi
grep -Fxq "$(identity_of "$1")" ../writers.txt
}
# Newest commit on file $1 authored by a non-blacklisted user.
last_good_for() {
for g in $(git log --format=%H --no-renames -- "$1"); do
gae="$(author_email "$g")"
if is_trusted "$gae"; then
printf '%s\n' "$g"
return 0
fi
gid="$(identity_of "$gae")"
if ! is_blacklisted "$gid" && ! is_blacklisted "$gae"; then
printf '%s\n' "$g"
return 0
fi
done
return 0
}
if [ -e "$BL_PAGE" ]; then
page_existed=1
else
page_existed=""
fi
# ---- 1. Last good version of the blacklist page: the newest
# revision authored by the maintainer or by this bot. Everything
# else on that page is tampering and is discarded.
bl_good_commit=""
for c in $(git log --format=%H --no-renames -- "$BL_PAGE"); do
ae="$(author_email "$c")"
if is_trusted "$ae"; then
bl_good_commit="$c"
break
fi
done
if [ -n "$bl_good_commit" ] && git cat-file -e "$bl_good_commit:$BL_PAGE" 2>/dev/null; then
git show "$bl_good_commit:$BL_PAGE" > ../bl_page.txt
else
{
echo "# Blacklist"
echo ""
echo "Users listed below violated the wiki rules (deleted or renamed"
echo "pages, or tampered with this page). Their new issues and pull"
echo "requests are closed on sight and their wiki edits are reverted"
echo "automatically. Only the maintainer and write-access members"
echo "may edit this page."
echo ""
echo "To pardon a user while their violation is still inside the"
echo "scan window, replace their entry with: pardon: username"
echo ""
} > ../bl_page.txt
fi
sed -n 's/^- *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_good.txt
sed -n 's/^[Pp]ardon: *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_pardon.txt
bl_add() {
if grep -Fxq "$1" ../bl_pardon.txt; then
return 0
fi
if ! grep -Fxq "$1" ../bl_good.txt && ! grep -Fxq "$1" ../bl_new.txt; then
printf '%s\n' "$1" >> ../bl_new.txt
printf '%s\n' "- blacklisted \`$1\`: $2" >> ../actions.txt
fi
}
# ---- 2. Blacklist everyone who deleted or renamed a page.
# --no-renames makes a rename count as a deletion of the old path.
for c in $(git log --since="$WINDOW" --diff-filter=D --no-renames --format=%H); do
ae="$(author_email "$c")"
if is_trusted "$ae"; then
continue
fi
an="$(git show -s --format=%an "$c")"
bl_add "$(identity_of "$ae")" "deleted or renamed pages in $c ($an <$ae>)"
done
# ---- 3. Blacklist everyone else who touched the blacklist page.
# The revert of their tampering falls out of steps 5 and 6.
for c in $(git log --since="$WINDOW" --format=%H --no-renames -- "$BL_PAGE"); do
ae="$(author_email "$c")"
if is_trusted "$ae"; then
continue
fi
an="$(git show -s --format=%an "$c")"
bl_add "$(identity_of "$ae")" "tampered with \`$BL_PAGE\` in $c ($an <$ae>)"
done
sort -u ../bl_new.txt > ../bl_new_u.txt
cat ../bl_good.txt ../bl_new_u.txt | sort -u > ../bl_all.txt
# ---- 4. Restore pages that are currently missing because a
# non-maintainer deleted them, using the last good revision.
git log --since="$WINDOW" --diff-filter=D --no-renames --name-only --format= \
| sort -u \
| while IFS= read -r f; do
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ] || [ -e "$f" ]; then
continue
fi
del="$(git log -1 --diff-filter=D --no-renames --format=%H -- "$f")"
if [ -z "$del" ]; then
continue
fi
ae="$(author_email "$del")"
if is_trusted "$ae"; then
continue
fi
good="$(last_good_for "$f")"
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
git checkout "$good" -- "$f"
printf '%s\n' "- restored \`$f\` (deleted in $del) from its last good revision $good" >> ../actions.txt
fi
done
# ---- 5. Revert every recent change made by a blacklisted
# identity: each touched file goes back to its newest revision
# authored by a non-blacklisted user; a file that has no such
# revision (they created it) is removed.
if [ -s ../bl_all.txt ]; then
for c in $(git log --since="$WINDOW" --format=%H --no-renames); do
ae="$(author_email "$c")"
if is_trusted "$ae"; then
continue
fi
id="$(identity_of "$ae")"
if ! is_blacklisted "$id" && ! is_blacklisted "$ae"; then
continue
fi
git show --name-only --no-renames --format= "$c" \
| while IFS= read -r f; do
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ]; then
continue
fi
good="$(last_good_for "$f")"
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
want="$(git rev-parse "$good:$f")"
have="$(git hash-object -- "$f" 2>/dev/null || echo missing)"
if [ "$want" != "$have" ]; then
git checkout "$good" -- "$f"
printf '%s\n' "- reverted \`$f\` to its last good revision $good (undoing change by \`$id\` in $c)" >> ../actions.txt
fi
elif [ -e "$f" ]; then
git rm -q -- "$f"
printf '%s\n' "- removed \`$f\` created by blacklisted \`$id\` in $c" >> ../actions.txt
fi
done
done
fi
# ---- 6. Regenerate the blacklist page: the last good text plus
# any newly blacklisted identities. This both reverts tampering
# and records new violators; manual edits by the maintainer are
# preserved as the new good text.
cp ../bl_page.txt ../bl_page_new.txt
if [ -s ../bl_page_new.txt ] && [ -n "$(tail -c1 ../bl_page_new.txt)" ]; then
echo >> ../bl_page_new.txt
fi
while IFS= read -r id; do
if [ -n "$id" ] && ! grep -Fxiq -- "- $id" ../bl_page_new.txt; then
printf -- '- %s\n' "$id" >> ../bl_page_new.txt
fi
done < ../bl_new_u.txt
if ! cmp -s ../bl_page_new.txt "$BL_PAGE" 2>/dev/null; then
cp ../bl_page_new.txt "$BL_PAGE"
git add -- "$BL_PAGE"
if [ -n "$page_existed" ] || [ -s ../bl_new_u.txt ]; then
printf '%s\n' "- updated \`$BL_PAGE\`" >> ../actions.txt
fi
fi
# ---- 7. Commit, push, notify.
if [ -n "$(git status --porcelain)" ]; then
git config user.name "github-actions[bot]"
git config user.email "$BOT_EMAIL"
git commit -m "wiki-guard: restore pages and enforce blacklist"
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
fi
if [ -s ../actions.txt ]; then
{
echo "The wiki guard handled the following rule violations:"
echo ""
cat ../actions.txt
echo ""
echo "Blacklist: https://github.com/${GITHUB_REPOSITORY}/wiki/Blacklist"
echo "Wiki: https://github.com/${GITHUB_REPOSITORY}/wiki"
} > ../guard-msg.txt
echo "acted=true" >> "$GITHUB_OUTPUT"
else
echo "No rule violations found."
echo "acted=false" >> "$GITHUB_OUTPUT"
fi
- name: Create issue to notify Neilpang
if: steps.guard.outputs.acted == 'true'
uses: peter-evans/create-issue-from-file@v6
with:
title: "Wiki guard: rule violations handled"
content-filepath: ./guard-msg.txt
assignees: Neilpang
+82
View File
@@ -0,0 +1,82 @@
name: Notify via Issue on Wiki Edit
on:
gollum:
jobs:
notify:
runs-on: ubuntu-latest
if: github.actor != 'neilpang'
steps:
- name: Checkout wiki repository
uses: actions/checkout@v7
with:
repository: ${{ github.repository }}.wiki
path: wiki
fetch-depth: 0
- name: Generate wiki change message
id: msg
run: |
actor="${{ github.actor }}"
sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH")
page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH")
page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH")
page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH")
page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH")
page_summary=$(jq -r '.pages[0].summary' "$GITHUB_EVENT_PATH")
now="$(date '+%Y-%m-%d %H:%M:%S')"
cd wiki
# Skip notification when the change was authored by the
# maintainer himself (any author email under neilpang.com),
# e.g. a direct git push to the wiki repository.
author_email=$(git show -s --format=%ae "$page_sha" 2>/dev/null | tr 'A-Z' 'a-z')
case "$author_email" in
*@neilpang.com)
echo "Change authored by maintainer ($author_email); skipping notification."
echo "notify=false" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1)
if [ -n "$prev_sha" ]; then
git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff
else
echo "(no diff)" > ../wiki.diff
fi
cd ..
{
echo "Wiki edited"
echo -n "User: "
echo "@$actor [$actor]($sender_url)"
echo "Time: $now"
echo "Page: [$page_name]($page_url) (Action: $page_action)"
echo "Comment: $page_summary"
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.)"
echo ""
echo "----"
echo "### diff:"
echo '```diff'
cat wiki.diff
echo '```'
} > wiki-change-msg.txt
echo "notify=true" >> "$GITHUB_OUTPUT"
- name: Create issue to notify Neilpang
if: steps.msg.outputs.notify == 'true'
uses: peter-evans/create-issue-from-file@v6
with:
title: "Wiki edited"
content-filepath: ./wiki-change-msg.txt
assignees: Neilpang
env:
TZ: Asia/Shanghai
+8
View File
@@ -0,0 +1,8 @@
# Contributing
1. Do NOT send pull request to `master` branch.
Please send to `dev` branch instead.
Any PR to `master` branch will NOT be merged.
2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
You will NOT get any review without passing this guide. You also need to fix the CI errors.
+22 -6
View File
@@ -1,4 +1,4 @@
FROM alpine:3.21
FROM alpine:3.23
RUN apk --no-cache add -f \
openssl \
@@ -13,10 +13,15 @@ RUN apk --no-cache add -f \
tar \
libidn \
jq \
cronie
yq-go \
supercronic
ENV LE_WORKING_DIR=/acmebin
ENV LE_CONFIG_HOME=/acme.sh
ENV HOME=/acme.sh
ARG AUTO_UPGRADE=1
ENV AUTO_UPGRADE=$AUTO_UPGRADE
@@ -27,10 +32,13 @@ COPY ./deploy /install_acme.sh/deploy
COPY ./dnsapi /install_acme.sh/dnsapi
COPY ./notify /install_acme.sh/notify
RUN addgroup -g 1000 acme && adduser -h $LE_CONFIG_HOME -s /bin/sh -G acme -D -H -u 1000 acme
RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
RUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh
RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null#> /proc/1/fd/1 2>/proc/1/fd/2#' | crontab -
RUN chown -R acme:acme $LE_CONFIG_HOME
RUN for verb in help \
version \
@@ -64,15 +72,23 @@ RUN for verb in help \
set-default-ca \
set-default-chain \
; do \
printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
printf -- "%b" "#!/usr/bin/env sh\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
; done
RUN printf "%b" '#!'"/usr/bin/env sh\n \
if [ \"\$1\" = \"daemon\" ]; then \n \
exec crond -n -s -m off \n \
if [ ! -f \"\$LE_CONFIG_HOME/crontab\" ]; then \n \
echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \
time=\$(date -u \"+%s\") \n \
random_minute=\$((\$time % 60)) \n \
random_hour=\$((\$time / 60 % 6)) \n \
echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \
fi \n \
echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \
exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \
else \n \
exec -- \"\$@\"\n \
fi\n" >/entry.sh && chmod +x /entry.sh
fi\n" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME
VOLUME /acme.sh
+360 -189
View File
@@ -1,52 +1,89 @@
# An ACME Shell script: acme.sh
<p align="center">
<a href="https://zerossl.com?utm_source=acme-sh">
<picture>
<!-- Dark mode -->
<source
media="(prefers-color-scheme: dark)"
srcset="https://github.com/user-attachments/assets/1308516b-e0cc-496d-b5df-e3932423ead6" />
<!-- Light mode -->
<source
media="(prefers-color-scheme: light)"
srcset="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043" />
<!-- Fallback for environments without media queries -->
<img
alt="ZeroSSL"
src="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043"
height="auto" />
</picture>
</a>
</p>
[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)
[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)
[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)
[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)
[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)
[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)
[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)
[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)
[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)
<h1 align="center">🔐 acme.sh</h1>
<h3 align="center">An ACME Protocol Client Written Purely in Shell</h3>
![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg)
![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg)
![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg)
<p align="center">
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg" alt="FreeBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg" alt="OpenBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg" alt="NetBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg" alt="MacOS"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg" alt="Ubuntu"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg" alt="Windows"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg" alt="Solaris"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg" alt="DragonFlyBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml/badge.svg" alt="MidnightBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg" alt="GhostBSD"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg" alt="Omnios"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg" alt="OpenIndiana"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg" alt="Tribblix"></a>
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg" alt="Haiku"></a>
</p>
<p align="center">
<img src="https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg" alt="Shellcheck">
<img src="https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg" alt="PebbleStrict">
<img src="https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg" alt="DockerHub">
</p>
<p align="center">
<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>
</p>
<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>
[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Docker stars](https://img.shields.io/docker/stars/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
[![Docker pulls](https://img.shields.io/docker/pulls/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
---
## ✨ Features
- 🐚 An ACME protocol client written **purely in Shell** (Unix shell) language
- 📜 Full ACME protocol implementation
- 🔑 Support **ECDSA** certificates
- 🌐 Support **SAN** and **wildcard** certificates
- ⚡ Simple, powerful and very easy to use — only **3 minutes** to learn!
- 🔧 Compatible with **Bash**, **dash** and **sh**
- 🚫 No dependencies on Python
- 🔄 One script to issue, renew and install your certificates automatically
- 👤 **DOES NOT** require `root/sudoer` access
- 🐳 Docker ready
- 🌍 IPv6 ready
- 📧 Cron job notifications for renewal or error
- An ACME protocol client written purely in Shell (Unix shell) language.
- Full ACME protocol implementation.
- Support ECDSA certs
- Support SAN and wildcard certs
- Simple, powerful and very easy to use. You only need 3 minutes to learn it.
- Bash, dash and sh compatible.
- Purely written in Shell with no dependencies on python.
- Just one script to issue, renew and install your certificates automatically.
- DOES NOT require `root/sudoer` access.
- Docker ready
- IPv6 ready
- Cron job notifications for renewal or error etc.
> 💡 It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates.
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates.
<p align="center">
<a href="https://github.com/acmesh-official/acme.sh/wiki"><strong>📚 Wiki</strong></a> •
<a href="https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker"><strong>🐳 Docker Guide</strong></a> •
<a href="https://twitter.com/neilpangxa"><strong>🐦 Twitter</strong></a>
</p>
Wiki: https://github.com/acmesh-official/acme.sh/wiki
---
For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker)
## 🌏 [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
---
# [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
# Who:
## 🏆 Who Uses acme.sh?
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
- [ruby-china.org](https://ruby-china.org/topics/31983)
- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)
@@ -60,7 +97,9 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
- [lnmp.org](https://lnmp.org/)
- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
# Tested OS
---
## 🖥️ Tested OS
| NO | Status| Platform|
|----|-------|---------|
@@ -73,67 +112,81 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
|7|[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD
|8|[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD
|9|[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD
|10|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios
|11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
|12|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|CentOS
|13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|10|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
|11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|ClearLinux
|10|[![MidnightBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml)|MidnightBSD
|11|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios
|12|[![OpenIndiana](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml)|OpenIndiana
|13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
|14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|20|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|21|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](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://acmesh-official.github.io/acmetest/status/proxmox.svg)](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|[![Haiku](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
|26|[![Tribblix](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
|27|[![GhostBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
Check our [testing project](https://github.com/acmesh-official/acmetest):
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
>
> 🖥️ The testing VMs are supported by [vmactions.org](https://vmactions.org)
https://github.com/acmesh-official/acmetest
---
# Supported CA
## 🏛️ Supported CA
- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default)
- Letsencrypt.org CA
- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
- [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA)
- [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA)
- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
- Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA
| CA | Status |
|---|---|
| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | ⭐ **Default** |
| Letsencrypt.org CA | ✅ Supported |
| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | ✅ Supported |
| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | ✅ Supported |
| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | ✅ Supported |
| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | ✅ Supported |
| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | ✅ Supported |
# Supported modes
---
- Webroot mode
- Standalone mode
- Standalone tls-alpn mode
- Apache mode
- Nginx mode
- DNS mode
- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode)
- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode)
## ⚙️ Supported Modes
| Mode | Description |
|------|-------------|
| 📁 Webroot mode | Use existing webroot directory |
| 🖥️ Standalone mode | Built-in webserver on port 80 |
| 🔐 Standalone tls-alpn mode | Built-in webserver on port 443 |
| 🪶 Apache mode | Use Apache for verification |
| ⚡ Nginx mode | Use Nginx for verification |
| 🌐 DNS mode | Use DNS TXT records |
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
# 1. How to install
---
### 1. Install online
## 📖 Usage Guide
Check this project: https://github.com/acmesh-official/get.acme.sh
### 1️⃣ How to Install
#### 📥 Install Online
> Check this project: https://github.com/acmesh-official/get.acme.sh
```bash
curl https://get.acme.sh | sh -s email=my@example.com
```
Or:
**Or:**
```bash
wget -O - https://get.acme.sh | sh -s email=my@example.com
```
### 2. Or, Install from git
#### 📦 Install from Git
Clone this project and launch installation:
@@ -143,11 +196,11 @@ cd ./acme.sh
./acme.sh --install -m my@example.com
```
You `don't have to be root` then, although `it is recommended`.
> 💡 You `don't have to be root` then, although `it is recommended`.
Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install
📚 **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install
The installer will perform 3 actions:
**The installer will perform 3 actions:**
1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.
All certs will be placed in this folder too.
@@ -160,17 +213,19 @@ Cron entry example:
0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
```
After the installation, you must close the current terminal and reopen it to make the alias take effect.
> ⚠️ After the installation, you must close the current terminal and reopen it to make the alias take effect.
Ok, you are ready to issue certs now.
✅ **You are ready to issue certs now!**
Show help message:
**Show help message:**
```sh
root@v1:~# acme.sh -h
acme.sh -h
```
# 2. Just issue a cert
---
### 2️⃣ Issue a Certificate
**Example 1:** Single domain.
@@ -205,17 +260,21 @@ You must point and bind all the domains to the same webroot dir: `/home/wwwroot/
The certs will be placed in `~/.acme.sh/example.com/`
The certs will be renewed automatically every **60** days.
> 🔄 The certs will be renewed automatically every **30** days.
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
> 🔐 The certs will default to **ECC** certificates.
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 3. Install the cert to Apache/Nginx etc.
---
### 3️⃣ Install the Certificate to Apache/Nginx
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.
#### 🪶 Apache Example:
```bash
acme.sh --install-cert -d example.com \
--cert-file /path/to/certfile/in/apache/cert.pem \
@@ -224,7 +283,7 @@ acme.sh --install-cert -d example.com \
--reloadcmd "service apache2 force-reload"
```
**Nginx** example:
#### ⚡ Nginx Example:
```bash
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/in/nginx/key.pem \
@@ -238,91 +297,89 @@ 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.
```bash
acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
```
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 5. Use Standalone ssl server to issue cert
---
**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))**
### 5️⃣ Use Standalone TLS Server to Issue Certificate
Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
> 🔐 Requires root/sudoer or permission to listen on port **443** (TCP)
> ⚠️ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
```bash
acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com
```
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
---
# 6. Use Apache mode
### 6️⃣ Use Apache Mode
**(requires you to be root/sudoer, since it is required to interact with Apache server)**
> 🔐 Requires root/sudoer to interact with Apache server
If you are running a web server, it is recommended to use the `Webroot mode`.
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.
Just set string "apache" as the second argument and it will force use of apache plugin automatically.
```sh
acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
```
**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
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 7. Use Nginx mode
---
**(requires you to be root/sudoer, since it is required to interact with Nginx server)**
### 7️⃣ Use Nginx Mode
> 🔐 Requires root/sudoer to interact with Nginx server
If you are running a web server, it is recommended to use the `Webroot mode`.
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.
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.
Just set string "nginx" as the second argument.
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.
```sh
acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com
```
**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
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 8. Automatic DNS API integration
---
### 8️⃣ Automatic DNS API Integration
If your DNS provider supports API access, we can use that API to automatically issue the certs.
You don't have to do anything manually!
> ✨ **You don't have to do anything manually!**
### Currently acme.sh supports most of the dns providers:
📚 **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi
https://github.com/acmesh-official/acme.sh/wiki/dnsapi
---
# 9. Use DNS manual mode:
### 9️⃣ Use DNS Manual Mode
See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.
@@ -352,70 +409,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 can not 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
Just set the `keylength` parameter with a prefix `ec-`.
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode
For example:
📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
### Single domain ECC certificate
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.
#### 🪄 Step 1: Print the TXT record value
```bash
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256
acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365]
```
### SAN multi domain ECC certificate
Options:
```bash
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256
```
| Flag | Description |
|------|-------------|
| `--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", which is the default key type)**
2. **ec-384 (secp384r1, "ECDSA P-384")**
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
4. **2048 (RSA2048)**
5. **3072 (RSA3072)**
6. **4096 (RSA4096)**
# 11. Issue Wildcard certificates
It's simple, just give a wildcard domain as the `-d` parameter.
You should get an output like:
```sh
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
TXT persist domain:_validation-persist.example.com
TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
```
#### ✍️ Step 2: Add the TXT record to your DNS
Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate.
#### 📜 Step 3: Issue the certificate
```bash
acme.sh --issue -d example.com --dns-persist
```
✅ **Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly.
> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed.
---
### 1️⃣1️⃣ Issue Certificates of Different Key Types (ECC or RSA)
Just set the `keylength` to a valid, supported value.
**Valid values for the `keylength` parameter:**
| Key Length | Description |
|------------|-------------|
| `ec-256` | prime256v1, "ECDSA P-256" ⭐ **Default** |
| `ec-384` | secp384r1, "ECDSA P-384" |
| `ec-521` | secp521r1, "ECDSA P-521" ⚠️ Not supported by Let's Encrypt yet |
| `2048` | RSA 2048-bit |
| `3072` | RSA 3072-bit |
| `4096` | RSA 4096-bit |
**Examples:**
#### Single domain with ECDSA P-384 certificate
```bash
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384
```
#### SAN multi domain with RSA4096 certificate
```bash
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096
```
---
### 1️⃣2️⃣ Issue Wildcard Certificates
It's simple! Just give a wildcard domain as the `-d` parameter:
```sh
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
```
# 12. How to renew the certs
---
No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
### 1️⃣3️⃣ How to Renew Certificates
However, you can also force to renew a cert:
> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below).
However, you can force a renewal:
```sh
acme.sh --renew -d example.com --force
```
or, for ECC cert:
**For ECC cert:**
```sh
acme.sh --renew -d example.com --force --ecc
```
#### 📡 ACME Renewal Information (ARI) — RFC 9773
# 13. How to stop cert renewal
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI
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`:
```sh
acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow'
```
> 💡 If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule — no behavior change.
---
### 1️⃣4️⃣ How to Stop Certificate Renewal
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
@@ -425,73 +566,80 @@ acme.sh --remove -d example.com [--ecc]
The cert/key file is not removed from the disk.
You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself.
> 💡 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually.
---
# 14. How to upgrade `acme.sh`
### 1️⃣5️⃣ How to Upgrade acme.sh
acme.sh is in constant development, so it's strongly recommended to use the latest code.
> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.
You can update acme.sh to the latest code:
**Update to latest:**
```sh
acme.sh --upgrade
```
You can also enable auto upgrade:
**Enable auto upgrade:**
```sh
acme.sh --upgrade --auto-upgrade
```
Then **acme.sh** will be kept up to date automatically.
Disable auto upgrade:
**Disable auto upgrade:**
```sh
acme.sh --upgrade --auto-upgrade 0
```
---
# 15. Issue a cert from an existing CSR
### 1️⃣6️⃣ Issue a Certificate from an Existing CSR
https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
---
# 16. Send notifications in cronjob
### 1️⃣7️⃣ Send Notifications in Cronjob
https://github.com/acmesh-official/acme.sh/wiki/notify
📚 https://github.com/acmesh-official/acme.sh/wiki/notify
---
# 17. Under the Hood
### 1️⃣8️⃣ Under the Hood
Speak ACME language using shell, directly to "Let's Encrypt".
> 🔧 Speak ACME language using shell, directly to "Let's Encrypt".
TODO:
---
### 1️⃣9️⃣ Acknowledgments
# 18. Acknowledgments
| Project | Link |
|---------|------|
| 🙏 Acme-tiny | https://github.com/diafygi/acme-tiny |
| 📜 ACME protocol | https://github.com/ietf-wg-acme/acme |
1. Acme-tiny: https://github.com/diafygi/acme-tiny
2. ACME protocol: https://github.com/ietf-wg-acme/acme
---
## 👥 Contributors
## Contributors
### Code Contributors
### 💻 Code Contributors
This project exists thanks to all the people who contribute.
<a href="https://github.com/acmesh-official/acme.sh/graphs/contributors"><img src="https://opencollective.com/acmesh/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
If you want to become a contributor make sure to read [CONTRIBUTING.md](./CONTRIBUTING.md).
### 💰 Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]
#### Individuals
#### 👤 Individuals
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/individuals.svg?width=890"></a>
#### Organizations
#### 🏢 Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]
@@ -506,20 +654,43 @@ Support this project with your organization. Your logo will show up here with a
<a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
---
### 2️⃣0️⃣ License & Others
# 19. License & Others
📄 **License:** GPLv3
License is GPLv3
⭐ Please **Star** and **Fork** this project!
Please Star and Fork me.
🐛 [Issues](https://github.com/acmesh-official/acme.sh/issues) and 🔀 [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
---
### 2️⃣1️⃣ Donate
# 20. Donate
Your donation makes **acme.sh** better:
> 💝 Your donation makes **acme.sh** better!
1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)
| Method | Link |
|--------|------|
| PayPal / Alipay(支付宝) / Wechat(微信) | [https://donate.acme.sh/](https://donate.acme.sh/) |
[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
📜 [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
---
### 2️⃣2️⃣ About This Repository
> [!NOTE]
> 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.
<p align="center">
<a href="https://zerossl.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://zerossl.com/assets/images/zerossl_logo_white.svg">
<source media="(prefers-color-scheme: light)" srcset="https://zerossl.com/assets/images/zerossl_logo.svg">
<img src="https://zerossl.com/assets/images/zerossl_logo.svg" alt="ZeroSSL" width="256">
</picture>
</a>
</p>
+1373 -217
View File
File diff suppressed because it is too large. Load diff
+341
View File
@@ -0,0 +1,341 @@
# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh
#
# "acme.sh --install" copies this file to the acme.sh home dir and wires
# it into acme.sh.env, so the completion is loaded automatically in new
# bash sessions after installation.
#
# To use it without installing acme.sh, source it from ~/.bashrc, or copy
# it to /usr/share/bash-completion/completions/acme.sh
#
# Zsh users can load it with:
# autoload -U +X bashcompinit && bashcompinit
# . /path/to/acme.sh.completion
# This file may also be sourced by non-bash shells via acme.sh.env,
# so silently do nothing if the "complete" builtin is not available.
if ! command -v complete >/dev/null 2>&1; then
return 0 2>/dev/null || exit 0
fi
# Add each word of $1 that starts with $cur to COMPREPLY.
# The words are read line by line, so that candidates like a wildcard
# domain "*.example.com" are never glob-expanded against the cwd.
_acme_sh_add_matches() {
local _word
while read -r _word; do
[ -n "$_word" ] || continue
case "$_word" in
"$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;;
esac
done <<EOF
$(printf '%s\n' "$1" | tr ' ' '\n')
EOF
return 0
}
_acme_sh_files() {
local _file
while IFS= read -r _file; do
[ -n "$_file" ] || continue
COMPREPLY=("${COMPREPLY[@]}" "$_file")
done <<EOF
$(compgen -f -- "$cur")
EOF
if command -v compopt >/dev/null 2>&1; then
compopt -o filenames 2>/dev/null
fi
return 0
}
_acme_sh_dirs() {
local _dir
while IFS= read -r _dir; do
[ -n "$_dir" ] || continue
COMPREPLY=("${COMPREPLY[@]}" "$_dir")
done <<EOF
$(compgen -d -- "$cur")
EOF
if command -v compopt >/dev/null 2>&1; then
compopt -o filenames 2>/dev/null
fi
return 0
}
# Complete the domains that already have a cert: every directory in the
# config home that contains a "<domain>.conf" file ("_ecc" suffix stripped).
_acme_sh_domains() {
local _dir _name _domains=""
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
for _dir in "$_acme_conf_home"/*/; do
[ -d "$_dir" ] || continue
_name="${_dir%/}"
_name="${_name##*/}"
_name="${_name%_ecc}"
if [ -f "${_dir}${_name}.conf" ]; then
case " $_domains " in
*" $_name "*) ;;
*) _domains="$_domains $_name" ;;
esac
fi
done
_acme_sh_add_matches "$_domains"
}
# Complete hook names from a subfolder of the acme.sh home dir.
# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty.
_acme_sh_hooks() {
local _file _hooks=""
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
for _file in "$_acme_home/$1/$2"*.sh; do
[ -f "$_file" ] || continue
_file="${_file##*/}"
_hooks="$_hooks ${_file%.sh}"
done
_acme_sh_add_matches "$_hooks"
}
_acme_sh_completion() {
local cur prev _acme_home _acme_conf_home
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev=""
if [ "$COMP_CWORD" -gt 0 ]; then
prev="${COMP_WORDS[COMP_CWORD - 1]}"
fi
_acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}"
_acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}"
# The first argument is the command.
if [ "$COMP_CWORD" -eq 1 ]; then
_acme_sh_add_matches "
--help
--version
--install
--install-online
--uninstall
--upgrade
--issue
--deploy
--sign-csr
--show-csr
--install-cert
--renew
--renew-all
--revoke
--remove
--list
--list-profiles
--info
--to-pkcs12
--to-pkcs8
--create-account-key
--create-domain-key
--create-csr
--deactivate
--update-account
--register-account
--deactivate-account
--make-dns-persist-value
--install-cronjob
--uninstall-cronjob
--cron
--set-notify
--set-default-ca
--set-default-chain
"
return 0
fi
# Complete the value of the previous option.
case "$prev" in
-d | --domain | --challenge-alias | --domain-alias)
_acme_sh_domains
return 0
;;
--dns)
# The dns hook argument is optional, keep completing options if the
# current word already looks like one.
case "$cur" in
-*) ;;
*)
_acme_sh_hooks "dnsapi" "dns_"
return 0
;;
esac
;;
--deploy-hook)
_acme_sh_hooks "deploy" ""
return 0
;;
--notify-hook)
_acme_sh_hooks "notify" ""
return 0
;;
--server)
_acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis"
return 0
;;
-k | --keylength | -ak | --accountkeylength)
_acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521"
return 0
;;
--debug)
# Optional argument.
case "$cur" in
-*) ;;
*)
_acme_sh_add_matches "0 1 2 3"
return 0
;;
esac
;;
--log)
# Optional argument.
case "$cur" in
-*) ;;
*)
_acme_sh_files
return 0
;;
esac
;;
--nginx)
# Optional argument.
case "$cur" in
-*) ;;
*)
_acme_sh_files
return 0
;;
esac
;;
--auto-upgrade | --always-force-new-domain-key)
# Optional argument.
case "$cur" in
-*) ;;
*)
_acme_sh_add_matches "0 1"
return 0
;;
esac
;;
--log-level)
_acme_sh_add_matches "1 2"
return 0
;;
--syslog)
_acme_sh_add_matches "0 3 6 7"
return 0
;;
--notify-level)
_acme_sh_add_matches "0 1 2 3"
return 0
;;
--notify-mode)
_acme_sh_add_matches "0 1"
return 0
;;
--revoke-reason)
_acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10"
return 0
;;
--cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin)
_acme_sh_files
return 0
;;
-w | --webroot | --home | --cert-home | --config-home | --ca-path)
_acme_sh_dirs
return 0
;;
-m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days)
# These options take a free-form value, offer nothing.
return 0
;;
esac
# Complete the parameters.
_acme_sh_add_matches "
--accountconf
--accountkey
--accountkeylength
--alpn
--always-force-new-domain-key
--apache
--auto-upgrade
--branch
--ca-bundle
--ca-file
--ca-path
--cert-file
--cert-home
--cert-profile
--challenge-alias
--config-home
--csr
--days
--debug
--deploy-hook
--dns
--dns-persist
--dns-persist-ca-name
--dns-persist-days
--dns-persist-wildcard
--dnssleep
--domain
--domain-alias
--eab-hmac-key
--eab-kid
--ecc
--email
--extended-key-usage
--force
--force-color
--fullchain-file
--home
--httpport
--insecure
--key-file
--keylength
--listen-v4
--listen-v6
--listraw
--local-address
--log
--log-level
--nginx
--no-color
--no-cron
--no-profile
--notify-hook
--notify-level
--notify-mode
--notify-source
--ocsp-must-staple
--openssl-bin
--output-insecure
--password
--post-hook
--pre-hook
--preferred-chain
--reloadcmd
--renew-hook
--revoke-reason
--server
--staging
--standalone
--stateless
--stop-renew-on-error
--syslog
--tlsport
--treat-skip-as-success
--use-wget
--useragent
--valid-from
--valid-to
--webroot
--yes-I-know-dns-manual-mode-enough-go-ahead-please
"
return 0
}
complete -F _acme_sh_completion acme.sh
+1 -1
View File
@@ -83,6 +83,6 @@ _set_cdn_domain_ssl_certificate_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2018-05-10'
}
+1 -1
View File
@@ -83,6 +83,6 @@ _set_dcdn_domain_ssl_certificate_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2018-01-15'
}
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034,SC2154
# Deploy hook: Baidu Cloud CDN
#
# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol
#
# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w
#
# Uses the same credential variables as dnsapi/dns_baidu.sh:
# export Baidu_AK="your-access-key-id"
# export Baidu_SK="your-secret-access-key"
#
# To deploy to a CDN domain different from the certificate CN
# (e.g. wildcard or multi-domain certs):
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com"
#
# Multiple CDN domains sharing the same certificate:
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
BAIDU_CDN_HOST="cdn.baidubce.com"
_BAIDU_CDN_BCE_AUTH_RESULT=""
baidu_cdn_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if ! _baidu_cdn_load_credentials; then
return 1
fi
_getdeployconf DEPLOY_BAIDU_CDN_DOMAIN
if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then
_savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN"
else
DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain"
fi
# Build JSON "domains" array from space-separated domain list
_domains_json=""
for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do
_d_e="$(_baidu_cdn_json_escape "$_d")"
if [ -z "$_domains_json" ]; then
_domains_json="\"${_d_e}\""
else
_domains_json="${_domains_json},\"${_d_e}\""
fi
done
# Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars
_cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)"
case "$_cert_name" in
[A-Za-z]*) ;;
*) _cert_name="c${_cert_name}" ;;
esac
# PEM content is already Base64 inside the -----BEGIN/END----- wrappers.
# The API expects the raw PEM as a JSON string, so newlines must be escaped as \n.
_cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')"
_key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')"
_debug2 _cert_name "$_cert_name"
_debug2 _domains_json "[$_domains_json]"
# Build JSON payload
_payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}"
# Generate BCE v1 authorization header (query string included in canonical request)
_cdn_path="/v2/domain/certificate"
_cdn_query="action=put"
_ts="$(_utc_date | sed 's/ /T/')Z"
_content_type="application/json; charset=utf-8"
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then
_err "Failed to sign request"
return 1
fi
_H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT"
_H2="x-bce-date: $_ts"
_H3="x-bce-content-sha256: $_payload_hash"
_H4="Host: $BAIDU_CDN_HOST"
_H5=""
_url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}"
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
if [ "$?" != "0" ]; then
_err "Failed to call Baidu Cloud CDN API"
return 1
fi
_debug2 response "$response"
if _contains "$response" "\"certId\""; then
_info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN"
return 0
fi
_err "Failed to deploy certificate to Baidu Cloud CDN: $response"
return 1
}
# BCE v1 signing with canonical query string support.
# The CDN endpoint uses ?action=put so it must be included in the canonical request.
_baidu_cdn_bce_auth() {
_method="$1"
_uri="$2"
_query="$3"
_host="$4"
_ts="$5"
_expire="$6"
_ct="$7"
_payload_hash="$8"
_BAIDU_CDN_BCE_AUTH_RESULT=""
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
_canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")"
_host_e="$(printf "%s" "$_host" | _url_encode upper-hex)"
_date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)"
_ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)"
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
_canonical_headers="content-type:${_ct_e}
host:${_host_e}
x-bce-content-sha256:${_hash_e}
x-bce-date:${_date_e}"
_canonical_request="${_method}
${_canonical_uri}
${_query}
${_canonical_headers}"
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
_signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
_signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
_BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
}
_baidu_cdn_load_credentials() {
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")"
Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")"
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
_err "Baidu_AK and Baidu_SK are required"
return 1
fi
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
return 0
}
_baidu_cdn_bce_encode_path() {
_p="$1"
_out=""
if [ "${_p#"/"}" != "$_p" ]; then
_out="/"
fi
_rest="${_p#/}"
while [ -n "$_rest" ]; do
_seg="${_rest%%/*}"
if [ "$_seg" ]; then
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
else
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
fi
fi
if [ "${_rest#*/}" = "$_rest" ]; then
break
fi
_rest="${_rest#*/}"
done
if [ -z "$_out" ]; then
_out="/"
fi
printf "%s" "$_out"
}
_baidu_cdn_trim_ws() {
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
}
_baidu_cdn_json_escape() {
_s="$1"
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
printf "%s" "$_s" |
sed 's/\\/\\\\/g; s/ /\\t/g' |
_baidu_cdn_json_encode
}
_baidu_cdn_json_encode() {
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
_baidu_cdn_hmac_sha256_hexkey() {
_key_hex="$1"
_msg="$2"
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
}
+394
View File
@@ -0,0 +1,394 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034,SC2154
#
# acme.sh deploy hook: BytePlus Application Load Balancer (ALB)
# https://github.com/acmesh-official/acme.sh/wiki/deployhooks
#
# Deploys SSL/TLS certificates issued by acme.sh to BytePlus ALB.
# Supports automatic renewal with zero-downtime certificate rotation
# for certificates that have already been uploaded and have a saved
# BytePlus CertificateId.
#
# ┌─────────────────────────────────────────────────────────────────────┐
# │ FIRST TIME (new domain) │
# │ 1. acme.sh --issue -d example.com -w /var/www/html/ │
# │ 2. Upload/import the certificate to BytePlus ALB manually │
# │ 3. Save/configure the existing CertificateId for this hook │
# │ 4. Manually assign cert to ALB Listener (one-time only) │
# │ │
# │ RENEWAL (fully automatic after CertificateId is configured) │
# │ acme.sh cron triggers renew → deploy hook runs automatically │
# │ → ReplaceCertificate (UpdateMode=new) — single API call │
# │ → All attached listeners updated, old cert auto-deleted │
# └─────────────────────────────────────────────────────────────────────┘
#
# Required environment variables:
# export BYTEPLUS_ACCESS_KEY="AKAPxxxxxxxxxx"
# export BYTEPLUS_SECRET_KEY="your-secret-key"
#
# Optional environment variables:
# export BYTEPLUS_REGION="ap-southeast-3" # default: ap-southeast-3
# export BYTEPLUS_HOST="alb.ap-southeast-3.byteplusapi.com" # custom API host
# export BYTEPLUS_PROJECT_NAME="live" # default: "default" project
# export BYTEPLUS_CERT_NAME="" # default: acme-{domain}-{YYYYMMDD-HHMM}
# export BYTEPLUS_CERT_DESCRIPTION="" # default: empty
# export BYTEPLUS_DELETE_OLD_CERT="true" # default: true — auto-delete after replace
#
# API notes:
# - All BytePlus ALB APIs use GET with query string parameters
# - Request signing: HMAC-SHA256 with signed headers host;x-date
# - PublicKey/PrivateKey are URL-encoded (RFC 3986) in query string
# - ReplaceCertificate with UpdateMode=new uploads + replaces in 1 call
#
# Dependencies: curl, openssl, awk (standard on most Linux)
#
# Docs:
# Signing — https://docs.byteplus.com/en/docs/byteplus-platform/reference-how-to-calculate-a-signature
# ALB API — https://docs.byteplus.com/en/docs/byteplus-alb
# ══════════════════════════════════════════════════════════════════════════════
# Constants
# ══════════════════════════════════════════════════════════════════════════════
# SHA-256 hash of empty string (used for GET requests with no body)
_BYTEPLUS_EMPTY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
# ══════════════════════════════════════════════════════════════════════════════
# Main deploy function — called by acme.sh
# ══════════════════════════════════════════════════════════════════════════════
byteplus_alb_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
# ── 1. Load & validate credentials ──────────────────────────────────────────
# Preserve environment values before _getdeployconf (which may reset them)
_env_project_name="${BYTEPLUS_PROJECT_NAME:-}"
_env_delete_old="${BYTEPLUS_DELETE_OLD_CERT:-}"
_getdeployconf BYTEPLUS_ACCESS_KEY
_getdeployconf BYTEPLUS_SECRET_KEY
_getdeployconf BYTEPLUS_REGION
_getdeployconf BYTEPLUS_HOST
_getdeployconf BYTEPLUS_PROJECT_NAME
_getdeployconf BYTEPLUS_DELETE_OLD_CERT
_getdeployconf BYTEPLUS_CERT_NAME
# Restore from environment if _getdeployconf cleared them
if [ -z "$BYTEPLUS_PROJECT_NAME" ] && [ -n "$_env_project_name" ]; then
_debug "Restoring BYTEPLUS_PROJECT_NAME from environment"
BYTEPLUS_PROJECT_NAME="$_env_project_name"
fi
if [ -z "$BYTEPLUS_DELETE_OLD_CERT" ] && [ -n "$_env_delete_old" ]; then
BYTEPLUS_DELETE_OLD_CERT="$_env_delete_old"
fi
# Validate required credentials
if [ -z "$BYTEPLUS_ACCESS_KEY" ]; then
_err "BYTEPLUS_ACCESS_KEY is not set."
_err "Please run: export BYTEPLUS_ACCESS_KEY=\"your-access-key\""
return 1
fi
if [ -z "$BYTEPLUS_SECRET_KEY" ]; then
_err "BYTEPLUS_SECRET_KEY is not set."
_err "Please run: export BYTEPLUS_SECRET_KEY=\"your-secret-key\""
return 1
fi
# Save credentials for future runs
_savedeployconf BYTEPLUS_ACCESS_KEY "$BYTEPLUS_ACCESS_KEY"
_savedeployconf BYTEPLUS_SECRET_KEY "$BYTEPLUS_SECRET_KEY"
# Region (default: ap-southeast-3)
BYTEPLUS_REGION="${BYTEPLUS_REGION:-ap-southeast-3}"
_savedeployconf BYTEPLUS_REGION "$BYTEPLUS_REGION"
# Project name
if [ -n "$BYTEPLUS_PROJECT_NAME" ]; then
_savedeployconf BYTEPLUS_PROJECT_NAME "$BYTEPLUS_PROJECT_NAME"
_info "Using project: $BYTEPLUS_PROJECT_NAME"
else
_info "WARNING: BYTEPLUS_PROJECT_NAME is not set. Cert will go to 'default' project."
fi
# Delete old cert toggle (default: true)
BYTEPLUS_DELETE_OLD_CERT="${BYTEPLUS_DELETE_OLD_CERT:-true}"
_savedeployconf BYTEPLUS_DELETE_OLD_CERT "$BYTEPLUS_DELETE_OLD_CERT"
# API host — custom override or auto-build from region
if [ -n "$BYTEPLUS_HOST" ]; then
_BYTEPLUS_HOST="$BYTEPLUS_HOST"
_savedeployconf BYTEPLUS_HOST "$BYTEPLUS_HOST"
else
_BYTEPLUS_HOST="alb.${BYTEPLUS_REGION}.byteplusapi.com"
fi
_info "Using API host: $_BYTEPLUS_HOST"
_BYTEPLUS_SERVICE="alb"
# ── 2. Build certificate name ────────────────────────────────────────────────
_date_tag=$(date -u +%Y%m%d-%H%M)
# Replace wildcard * and dots for a valid cert name
_safe_domain=$(echo "$_cdomain" | sed 's/\*\.//g' | sed 's/\./-/g')
# Safe identifier version for deployconf keys: map all non [A-Za-z0-9_] to _
_conf_key=$(echo "$_cdomain" | sed 's/^\*\.//' | sed 's/[^A-Za-z0-9_]/_/g')
if [ -z "$BYTEPLUS_CERT_NAME" ]; then
BYTEPLUS_CERT_NAME="acme-${_safe_domain}-${_date_tag}"
fi
# Enforce BytePlus naming rules: start with letter, max 128 chars
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | sed 's/[^A-Za-z0-9._-]/-/g')
case "$BYTEPLUS_CERT_NAME" in
[A-Za-z]*) ;;
*)
BYTEPLUS_CERT_NAME="a$BYTEPLUS_CERT_NAME"
;;
esac
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | cut -c1-128)
_info "Certificate name: $BYTEPLUS_CERT_NAME"
# ── 3. Read cert and key ─────────────────────────────────────────────────────
# BytePlus requires NO blank lines between PEM blocks in the certificate chain
_public_key=$(sed '/^[[:space:]]*$/d' "$_cfullchain" | tr -d '\r')
_private_key=$(sed '/^[[:space:]]*$/d' "$_ckey" | tr -d '\r')
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
_err "Failed to read certificate or key file."
return 1
fi
# ── 4. Deploy: first-time upload or renewal replace ─────────────────────────
_getdeployconf "BYTEPLUS_CERT_ID_${_conf_key}"
_old_cert_id=$(eval echo "\$BYTEPLUS_CERT_ID_${_conf_key}")
if [ -z "$_old_cert_id" ]; then
_byteplus_first_time_deploy
else
_byteplus_renewal_deploy
fi
# Check if deploy step set _new_cert_id
if [ -z "$_new_cert_id" ]; then
return 1
fi
# ── 5. Save new CertificateId for next renewal ───────────────────────────────
_savedeployconf "BYTEPLUS_CERT_ID_${_conf_key}" "$_new_cert_id"
_info "Saved CertificateId '$_new_cert_id' for domain '$_cdomain'."
return 0
}
# ══════════════════════════════════════════════════════════════════════════════
# Deploy: First time — UploadCertificate
# ══════════════════════════════════════════════════════════════════════════════
_byteplus_first_time_deploy() {
_info "No previous CertificateId found."
_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."
return 1
}
# ══════════════════════════════════════════════════════════════════════════════
# Deploy: Renewal — ReplaceCertificate (UpdateMode=new)
# ══════════════════════════════════════════════════════════════════════════════
_byteplus_renewal_deploy() {
_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."
return 1
}
# ══════════════════════════════════════════════════════════════════════════════
# Delete old certificate (with retry)
# ══════════════════════════════════════════════════════════════════════════════
_byteplus_delete_old_cert() {
_del_cert_id="$1"
_info "Waiting 5s for cert status to settle..."
_sleep 5
_info "Deleting old certificate '$_del_cert_id'..."
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
if echo "$_del_response" | grep -q '"Error"'; then
_info "Delete failed, retrying in 10s..."
_sleep 10
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
if echo "$_del_response" | grep -q '"Error"'; then
_info "Warning: Could not delete old certificate '$_del_cert_id'."
_info "Error: $(_byteplus_extract_error "$_del_response")"
_info "Please remove it manually from BytePlus Console."
else
_info "Old certificate '$_del_cert_id' deleted (retry succeeded)."
fi
else
_info "Old certificate '$_del_cert_id' deleted."
fi
}
# ══════════════════════════════════════════════════════════════════════════════
# JSON response helpers
# ══════════════════════════════════════════════════════════════════════════════
# Extract CertificateId from API response JSON
_byteplus_extract_cert_id() {
echo "$1" | _egrep_o '"CertificateId"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"'
}
# Extract error message from API response JSON
_byteplus_extract_error() {
_code=$(echo "$1" | _egrep_o '"Code"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
_msg=$(echo "$1" | _egrep_o '"Message"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
if [ -n "$_code" ]; then
printf '%s — %s' "$_code" "$_msg"
else
printf '%s' "$1"
fi
}
# ══════════════════════════════════════════════════════════════════════════════
# BytePlus ALB API caller
# ══════════════════════════════════════════════════════════════════════════════
# Usage: _byteplus_alb_api ACTION [param1=val1] [param2=val2] ...
# All parameters sent via GET query string. Signing: HMAC-SHA256, host;x-date.
_byteplus_alb_api() {
_action="$1"
shift
# Build query string — all params go in URL
_query_params="Action=${_action}&Version=2020-04-01"
for _param in "$@"; do
_pname="${_param%%=*}"
_pval="${_param#*=}"
_query_params="${_query_params}&${_pname}=$(_byteplus_urlencode "$_pval")"
done
# Timestamps
_x_date=$(date -u +%Y%m%dT%H%M%SZ)
_date_only=$(date -u +%Y%m%d)
# Sort query params for canonical request
_sorted_query=$(echo "$_query_params" | tr '&' '\n' | LC_ALL=C sort | tr '\n' '&' | sed 's/&$//')
# Canonical headers — only host and x-date
_canonical_headers="host:${_BYTEPLUS_HOST}
x-date:${_x_date}
"
_signed_headers="host;x-date"
# Canonical request
_canonical_request="GET
/
${_sorted_query}
${_canonical_headers}
${_signed_headers}
${_BYTEPLUS_EMPTY_HASH}"
# Do not log _canonical_request because the query string may contain
# URL-encoded certificate or private key material.
# Hash of canonical request
# _digest is provided by acme.sh and works across OpenSSL versions.
_cr_hash=$(printf '%s' "$_canonical_request" | _digest sha256 hex)
# Credential scope
_credential_scope="${_date_only}/${BYTEPLUS_REGION}/${_BYTEPLUS_SERVICE}/request"
# String to sign
_string_to_sign="HMAC-SHA256
${_x_date}
${_credential_scope}
${_cr_hash}"
_debug2 _string_to_sign "$_string_to_sign"
# Signing key derivation (HMAC chain)
# _hmac <algo> <hex-key> reads data from stdin and returns a hex digest.
# acme.sh's _hmac abstracts away OpenSSL version differences, so this works
# on both modern (-mac HMAC -macopt hexkey:) and older (-hmac) OpenSSL builds.
#
# The first step seeds the chain from the raw secret key, so we convert it
# to hex first with _hex_dump (also an acme.sh built-in).
_secret_hex=$(printf '%s' "$BYTEPLUS_SECRET_KEY" | _hex_dump | tr -d ' \n')
_k_date=$(printf '%s' "$_date_only" | _hmac sha256 "$_secret_hex" hex)
_k_region=$(printf '%s' "$BYTEPLUS_REGION" | _hmac sha256 "$_k_date" hex)
_k_service=$(printf '%s' "$_BYTEPLUS_SERVICE" | _hmac sha256 "$_k_region" hex)
_k_signing=$(printf '%s' "request" | _hmac sha256 "$_k_service" hex)
# Final signature
_signature=$(printf '%s' "$_string_to_sign" | _hmac sha256 "$_k_signing" hex)
# Authorization header
_auth="HMAC-SHA256 Credential=${BYTEPLUS_ACCESS_KEY}/${_credential_scope}, SignedHeaders=${_signed_headers}, Signature=${_signature}"
_secure_debug2 _auth "$_auth"
# Send request parameters in the POST body instead of the URL query string.
# This avoids exposing sensitive or large values in debug-logged URLs and
# reduces the risk of exceeding URL length limits.
_url="https://${_BYTEPLUS_HOST}/"
_body="$_sorted_query"
_saved_H1="${_H1:-}"
_saved_H2="${_H2:-}"
_saved_H3="${_H3:-}"
_saved_H4="${_H4:-}"
_saved_H5="${_H5:-}"
_H1="Authorization: ${_auth}"
_H2="X-Date: ${_x_date}"
_H3="Host: ${_BYTEPLUS_HOST}"
_H4="Content-Type: application/x-www-form-urlencoded"
_H5=""
_response="$(_post "$_body" "$_url" "" "POST")"
_request_ret="$?"
_H1="$_saved_H1"
_H2="$_saved_H2"
_H3="$_saved_H3"
_H4="$_saved_H4"
_H5="$_saved_H5"
if [ "$_request_ret" != "0" ]; then
_err "byteplus_alb_api request failed for [$_action]"
return 1
fi
_debug2 "_byteplus_alb_api response [$_action]" "$_response"
printf '%s' "$_response"
}
# ══════════════════════════════════════════════════════════════════════════════
# URL encode (RFC 3986)
# ══════════════════════════════════════════════════════════════════════════════
_byteplus_urlencode() {
printf '%s' "$1" | _url_encode
}
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env sh
# Script to deploy certificate to CacheFly
# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post
# This deployment required following variables
# export CACHEFLY_TOKEN="Your CacheFly API Token"
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
CACHEFLY_API_BASE="https://api.cachefly.com/api/2.5"
cachefly_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$CACHEFLY_TOKEN" ]; then
_err "CACHEFLY_TOKEN is not defined."
return 1
else
_savedomainconf CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
fi
_info "Deploying certificate to CacheFly..."
## upload certificate
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
_request_body="{\"certificate\":\"$string_fullchain\",\"certificateKey\":\"$string_key\"}"
_debug _request_body "$_request_body"
_debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
export _H1="Authorization: Bearer $CACHEFLY_TOKEN"
_response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json")
if _contains "$_response" "message"; then
_err "Error in deploying $_cdomain certificate to CacheFly."
_err "$_response"
return 1
fi
_debug response "$_response"
_info "Domain $_cdomain certificate successfully deployed to CacheFly."
return 0
}
+11 -2
View File
@@ -52,7 +52,15 @@ cpanel_uapi_deploy() {
# read cert and key files and urlencode both
_cert=$(_url_encode <"$_ccert")
_key=$(_url_encode <"$_ckey")
# with --signcsr the private key was never handed to acme.sh, so the key
# file does not exist; skip it instead of spilling a shell redirection
# error on every renewal (cPanel keeps using the already-installed key)
if [ -f "$_ckey" ]; then
_key=$(_url_encode <"$_ckey")
else
_debug "Key file $_ckey does not exist (csr mode), not sending a key."
_key=""
fi
_debug2 _cert "$_cert"
_debug2 _key "$_key"
@@ -194,7 +202,8 @@ __cpanel_parse_response() {
printf("%s%s=%s\n", prefix, $2, $3);
}
}' |
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p'
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' |
sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains)
}
# Load parameter by prefix+name - fallback to default if not set, and save to config
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env sh
# Script to deploy certificate to DirectAdmin
# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key
# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs
# This deployment required following variables
# export DirectAdmin_SCHEME="https" # Optional, https or http, defaults to https
# export DirectAdmin_ENDPOINT="example.com:2222"
# export DirectAdmin_USERNAME="Your DirectAdmin Username"
# export DirectAdmin_KEY="Your DirectAdmin Login Key or Password"
# export DirectAdmin_MAIN_DOMAIN="Your DirectAdmin Main Domain, NOT Subdomain"
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
directadmin_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$DirectAdmin_ENDPOINT" ]; then
_err "DirectAdmin_ENDPOINT is not defined."
return 1
else
_savedomainconf DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
fi
if [ -z "$DirectAdmin_USERNAME" ]; then
_err "DirectAdmin_USERNAME is not defined."
return 1
else
_savedomainconf DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
fi
if [ -z "$DirectAdmin_KEY" ]; then
_err "DirectAdmin_KEY is not defined."
return 1
else
_savedomainconf DirectAdmin_KEY "$DirectAdmin_KEY"
fi
if [ -z "$DirectAdmin_MAIN_DOMAIN" ]; then
_err "DirectAdmin_MAIN_DOMAIN is not defined."
return 1
else
_savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
fi
# Optional SCHEME
_getdeployconf DirectAdmin_SCHEME
# set default values for DirectAdmin_SCHEME
[ -n "${DirectAdmin_SCHEME}" ] || DirectAdmin_SCHEME="https"
_info "Deploying certificate to DirectAdmin..."
# upload certificate
string_cfullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
_request_body="{\"domain\":\"$DirectAdmin_MAIN_DOMAIN\",\"action\":\"save\",\"type\":\"paste\",\"certificate\":\"$string_key\n$string_cfullchain\n\"}"
_debug _request_body "$_request_body"
_debug DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
_debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
_debug DirectAdmin_KEY "$DirectAdmin_KEY"
_debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
_response=$(_post "$_request_body" "$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json")
if _contains "$_response" "error=1"; then
_err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
_err "$_response"
return 1
fi
_info "$_response"
_info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
return 0
}
+43 -3
View File
@@ -3,6 +3,8 @@
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
@@ -71,6 +73,18 @@ docker_deploy() {
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
@@ -112,6 +126,20 @@ docker_deploy() {
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
return 1
fi
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
_info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
_err "Can not change owner of key file in container"
return 1
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
_info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
_err "Can not change mode of key file in container"
return 1
fi
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
@@ -189,10 +217,22 @@ _docker_exec() {
_debug2 cjson "$cjson"
execid="$(echo "$cjson" | cut -d '"' -f 4)"
_debug execid "$execid"
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
#Detach:true is required for podman's docker-compatible API: with
#Detach:false it streams the command output on the connection, so the
#non-empty response was misread as an error (issue #4977). The real
#result is checked via the exec inspect ExitCode below instead.
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
_debug2 ejson "$ejson"
if [ "$ejson" ]; then
_err "$ejson"
_et=0
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
sleep 1
_et="$(_math "$_et" + 1)"
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
done
_debug2 ijson "$ijson"
if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
_err "docker exec error: $ijson"
return 1
fi
else
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to edgio using its API
# https://docs.edg.io/guides/v7/develop/rest_api/authentication
# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
# This deployment required following variables
# export EDGIO_CLIENT_ID="Your Edgio Client ID"
# export EDGIO_CLIENT_SECRET="Your Edgio Client Secret"
# export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID"
# If have more than one Environment ID
# export EDGIO_ENVIRONMENT_ID="ENVIRONMENT_ID_1 ENVIRONMENT_ID_2"
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
edgio_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$EDGIO_CLIENT_ID" ]; then
_err "EDGIO_CLIENT_ID is not defined."
return 1
else
_savedomainconf EDGIO_CLIENT_ID "$EDGIO_CLIENT_ID"
fi
if [ -z "$EDGIO_CLIENT_SECRET" ]; then
_err "EDGIO_CLIENT_SECRET is not defined."
return 1
else
_savedomainconf EDGIO_CLIENT_SECRET "$EDGIO_CLIENT_SECRET"
fi
if [ -z "$EDGIO_ENVIRONMENT_ID" ]; then
_err "EDGIO_ENVIRONMENT_ID is not defined."
return 1
else
_savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID"
fi
_info "Getting access token"
_data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config"
_debug Get_access_token_data "$_data"
_response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded")
_debug Get_access_token_response "$_response"
_access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"')
_debug _access_token "$_access_token"
if [ -z "$_access_token" ]; then
_err "Error in getting access token"
return 1
fi
_info "Uploading certificate"
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do
_data="{\"environment_id\":\"$ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}"
_debug Upload_certificate_data "$_data"
_H1="Authorization: Bearer $_access_token"
_response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json")
if _contains "$_response" "message"; then
_err "Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
_err "$_response"
return 1
fi
_debug Upload_certificate_response "$_response"
_info "Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
done
return 0
}
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env sh
# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate.
#
# FortiGate's native ACME integration does not support wildcard certificates or domain validation,
# and is not supported if you have a custom management web port (eg. DNAT web traffic).
#
# REQUIRED:
# export FGT_HOST="fortigate_hostname-or-ip"
# export FGT_TOKEN="fortigate_api_token"
#
# OPTIONAL:
# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set)
#
# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script.
# `--insecure` is required on first run if not already using a valid SSL certificate on firewall.
# Function to parse a FortiGate API response
_fortigate_parse_response() {
_fortigate_response="$1"
_fortigate_func="$2"
_fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4)
if [ "$_fortigate_status" != "success" ]; then
_err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
return 1
fi
_debug "[$_fortigate_func] Operation successful."
return 0
}
# Function to deploy a base64-encoded certificate to the firewall
_fortigate_deployer() {
_fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n')
_fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n')
_fortigate_payload=$(
cat <<EOF
{
"type": "regular",
"scope": "global",
"certname": "$_fortigate_cert_name",
"key_file_content": "$_fortigate_key_base64",
"file_content": "$_fortigate_cert_base64"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/local/import"
_debug "Uploading certificate via URL: $_fortigate_url"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
_debug "FortiGate API Response: $_fortigate_response"
_fortigate_parse_response "$_fortigate_response" "Deploying certificate" || return 1
}
# Function to upload a CA certificate to the firewall
# FortiGate does not automatically extract the CA from the full chain.
_fortigate_upload_ca_cert() {
_fortigate_ca_base64=$(_base64 <"$_fortigate_cca" | tr -d '\n')
_fortigate_payload=$(
cat <<EOF
{
"import_method": "file",
"scope": "global",
"file_content": "$_fortigate_ca_base64"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/ca/import"
_debug "Uploading CA certificate via URL: $_fortigate_url"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
_debug "FortiGate API CA Response: $_fortigate_response"
# FortiGate error -328 means that the CA certificate already exists.
if echo "$_fortigate_response" | grep -q '"error":[ ]*-328'; then
_debug "CA certificate already exists. Skipping CA upload."
return 0
fi
_fortigate_parse_response "$_fortigate_response" "Deploying CA certificate" || return 1
}
# Function to activate the new certificate
_fortigate_set_active_web_cert() {
_fortigate_payload=$(
cat <<EOF
{
"admin-server-cert": "$_fortigate_cert_name"
}
EOF
)
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/system/global"
_debug "Setting GUI certificate..."
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "PUT" "application/json")
_fortigate_parse_response "$_fortigate_response" "Assigning active certificate" || return 1
}
# Function to clean up the previously deployed certificate
_fortigate_cleanup_previous_certificate() {
_getdeployconf FGT_LAST_CERT
if [ -n "$FGT_LAST_CERT" ] && [ "$FGT_LAST_CERT" != "$_fortigate_cert_name" ]; then
_debug "Found previously deployed certificate: $FGT_LAST_CERT. Deleting it."
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/vpn.certificate/local/${FGT_LAST_CERT}"
_H1="Authorization: Bearer $FGT_TOKEN"
_fortigate_response=$(_post "" "$_fortigate_url" "" "DELETE" "application/json")
_debug "Delete certificate API response: $_fortigate_response"
_fortigate_parse_response "$_fortigate_response" "Delete previous certificate" || return 1
else
_debug "No previous certificate found."
fi
}
# Main deploy-hook function
fortigate_deploy() {
# Include date and time to ensure unique names.
_fortigate_cert_name="$(echo "$1" | sed 's/*/WILDCARD_/g')_$(date -u +"%Y-%m-%d_%H-%M-%S")"
_fortigate_ckey="$2"
_fortigate_cca="$4"
_fortigate_cfullchain="$5"
if [ ! -f "$_fortigate_ckey" ] || [ ! -f "$_fortigate_cfullchain" ]; then
_err "Valid key and/or certificate not found."
return 1
fi
# Save required environment variables if set; otherwise load saved values.
for _fortigate_var in FGT_HOST FGT_TOKEN FGT_PORT; do
if [ -n "$(eval echo "\$$_fortigate_var")" ]; then
_debug "Detected ENV variable $_fortigate_var. Saving to file."
_savedeployconf "$_fortigate_var" "$(eval echo "\$$_fortigate_var")" 1
else
_debug "Attempting to load variable $_fortigate_var from file."
_getdeployconf "$_fortigate_var"
fi
done
if [ -z "$FGT_HOST" ] || [ -z "$FGT_TOKEN" ]; then
_err "FGT_HOST and FGT_TOKEN must be set."
return 1
fi
FGT_PORT="${FGT_PORT:-443}"
_debug "Using FortiGate port: $FGT_PORT"
# Upload the new certificate.
_fortigate_deployer || return 1
# Upload the CA certificate.
if [ -n "$_fortigate_cca" ] && [ -f "$_fortigate_cca" ]; then
_fortigate_upload_ca_cert || return 1
else
_debug "No CA certificate provided."
fi
# Activate the new certificate.
_fortigate_set_active_web_cert || return 1
# Delete the previously deployed certificate only after successful activation.
_fortigate_cleanup_previous_certificate || return 1
# Save the new certificate name for cleanup during the next deployment.
_savedeployconf "FGT_LAST_CERT" "$_fortigate_cert_name" 1
}
+1 -1
View File
@@ -57,7 +57,7 @@ gcore_cdn_deploy() {
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
_debug _request "$_request"
export _H1="Content-Type:application/json"
_response=$(_post "$_request" "https://api.gcore.com/auth/jwt/login")
_response=$(_post "$_request" "https://api.gcore.com/iam/auth/jwt/login")
_debug _response "$_response"
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
_debug _regex "$_regex"
+63 -52
View File
@@ -43,7 +43,8 @@
# needing to reload HAProxy. Default is "no".
#
# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
# address format.
# address format. The certificate can be deployed to a comma separated ',' list
# of hosts ("TCP4:10.0.0.1:1999,TCP4:10.0.0.2:1999")
#
# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
#
@@ -193,7 +194,6 @@ haproxy_deploy() {
_issuer="${_pem}.issuer"
_ocsp="${_pem}.ocsp"
_reload="${Le_Deploy_haproxy_reload}"
_statssock="${Le_Deploy_haproxy_stats_socket}"
_info "Deploying PEM file"
# Create a temporary PEM file
@@ -272,12 +272,18 @@ haproxy_deploy() {
_cafile_argument=""
fi
_debug _cafile_argument "${_cafile_argument}"
# if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
# OpenSSL 1.1+ expects -header Host=value (one argument), while
# LibreSSL keeps the old two-argument form -header Host value at any
# version (3.x/4.x), so it must be detected by name, not by number.
_openssl_name=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f1)
_openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
_debug _openssl_name "${_openssl_name}"
_debug _openssl_version "${_openssl_version}"
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
if [ "${_openssl_name}" = "LibreSSL" ]; then
_header_sep=" "
elif [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
_header_sep="="
else
_header_sep=" "
@@ -327,62 +333,67 @@ haproxy_deploy() {
# Update certificate over HAProxy stats socket or master CLI.
if _exists socat; then
# look for the certificate on the stats socket, to chose between updating or creating one
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
_debug _socat_cert_cmd "${_socat_cert_cmd}"
eval "${_socat_cert_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_newcert="1"
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
eval "${_socat_crtlist_show_cmd}"
IFS=','
for _statssock in ${Le_Deploy_haproxy_stats_socket}; do
# look for the certificate on the stats socket, to choose between updating or creating one
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
_debug _socat_cert_cmd "${_socat_cert_cmd}"
eval "${_socat_cert_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
return "${_ret}"
_newcert="1"
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
eval "${_socat_crtlist_show_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
return "${_ret}"
fi
# create a new certificate
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
_debug _socat_new_cmd "${_socat_new_cmd}"
eval "${_socat_new_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't create '${_pem}' in haproxy"
return "${_ret}"
fi
else
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
fi
# create a new certificate
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
_debug _socat_new_cmd "${_socat_new_cmd}"
eval "${_socat_new_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't create '${_pem}' in haproxy"
return "${_ret}"
fi
else
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
fi
_socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
eval "${_socat_cert_set_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't update '${_pem}' in haproxy"
return "${_ret}"
fi
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
eval "${_socat_cert_commit_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't commit '${_pem}' in haproxy"
return ${_ret}
fi
if [ "${_newcert}" = "1" ]; then
# if this is a new certificate, it needs to be inserted into the crt-list`
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
eval "${_socat_cert_add_cmd}"
# printf %b, not "echo -e": dash's echo has no -e and sends a literal "-e " to the socket.
# "Transaction updated" is replied instead of "created" when an uncommitted transaction exists.
_socat_cert_set_cmd="printf '%b\n' '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -qE 'Transaction (created|updated)'"
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
eval "${_socat_cert_set_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't update '${_pem}' in haproxy"
return "${_ret}"
fi
fi
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
eval "${_socat_cert_commit_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't commit '${_pem}' in haproxy"
return ${_ret}
fi
if [ "${_newcert}" = "1" ]; then
# if this is a new certificate, it needs to be inserted into the crt-list`
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
eval "${_socat_cert_add_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't update '${_pem}' in haproxy"
return "${_ret}"
fi
fi
done
else
_err "'socat' is not available, couldn't update over ${_socketname}"
fi
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env sh
#Here is a script to deploy cert to a Kemp Loadmaster.
#returns 0 means success, otherwise error.
#DEPLOY_KEMP_TOKEN="token"
#DEPLOY_KEMP_URL="https://kemplm.example.com"
######## Public functions #####################
#domain keyfile certfile cafile fullchain
kemplm_deploy() {
_domain="$1"
_key_file="$2"
_cert_file="$3"
_ca_file="$4"
_fullchain_file="$5"
_debug _domain "$_domain"
_debug _key_file "$_key_file"
_debug _cert_file "$_cert_file"
_debug _ca_file "$_ca_file"
_debug _fullchain_file "$_fullchain_file"
if ! _exists jq; then
_err "jq not found"
return 1
fi
# Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename
_kemp_domain=$(echo "${_domain}" | sed 's/\*\.//')
_debug _kemp_domain "$_kemp_domain"
# Read config from saved values or env
_getdeployconf DEPLOY_KEMP_TOKEN
_getdeployconf DEPLOY_KEMP_URL
_debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
_secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
if [ -z "$DEPLOY_KEMP_TOKEN" ]; then
_err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN."
return 1
fi
if [ -z "$DEPLOY_KEMP_URL" ]; then
_err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL."
return 1
fi
# Save current values
_savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
_savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
# Check if certificate is already installed
_info "Check if certificate is already present"
_list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}"
_debug3 _list_request "${_list_request}"
_kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "^${_kemp_domain}$")
_debug2 _kemp_cert_count "${_kemp_cert_count}"
_kemp_replace_cert=1
if [ "${_kemp_cert_count}" -eq 0 ]; then
_kemp_replace_cert=0
_info "Certificate does not exist on Kemp Loadmaster"
else
_info "Certificate already exists on Kemp Loadmaster"
fi
_debug _kemp_replace_cert "${_kemp_replace_cert}"
# Upload new certificate to Kemp Loadmaster
_kemp_upload_cert=$(_mktemp)
cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}"
_info "Uploading certificate to Kemp Loadmaster"
_add_data=$(cat "${_kemp_upload_cert}")
_add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}"
_debug3 _add_request "${_add_request}"
_kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2")
_retval=$?
_debug2 _kemp_post_result "${_kemp_post_result}"
if [ "${_retval}" -eq 0 ]; then
_kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status')
_kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message')
if [ "${_kemp_post_status}" = "ok" ]; then
_info "Upload successful"
else
_err "Upload failed: ${_kemp_post_message}"
_retval=1
fi
else
_err "Upload failed"
_retval=1
fi
rm "${_kemp_upload_cert}"
return $_retval
}
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env sh
# Script to deploy certificate to KeyHelp
# This deployment required following variables
# export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com"
# export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username"
# export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password"
# export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID"
# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID.
# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx
# If have more than one domain name
# export DEPLOY_KEYHELP_DOMAIN_ID="111 222 333"
keyhelp_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$DEPLOY_KEYHELP_BASEURL" ]; then
_err "DEPLOY_KEYHELP_BASEURL is not defined."
return 1
else
_savedomainconf DEPLOY_KEYHELP_BASEURL "$DEPLOY_KEYHELP_BASEURL"
fi
if [ -z "$DEPLOY_KEYHELP_USERNAME" ]; then
_err "DEPLOY_KEYHELP_USERNAME is not defined."
return 1
else
_savedomainconf DEPLOY_KEYHELP_USERNAME "$DEPLOY_KEYHELP_USERNAME"
fi
if [ -z "$DEPLOY_KEYHELP_PASSWORD" ]; then
_err "DEPLOY_KEYHELP_PASSWORD is not defined."
return 1
else
_savedomainconf DEPLOY_KEYHELP_PASSWORD "$DEPLOY_KEYHELP_PASSWORD"
fi
if [ -z "$DEPLOY_KEYHELP_DOMAIN_ID" ]; then
_err "DEPLOY_KEYHELP_DOMAIN_ID is not defined."
return 1
else
_savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID"
fi
# Optional DEPLOY_KEYHELP_ENFORCE_HTTPS
_getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS
# set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS
[ -n "${DEPLOY_KEYHELP_ENFORCE_HTTPS}" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS="1"
_info "Logging in to keyhelp panel"
username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)"
password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)"
_H1="Content-Type: application/x-www-form-urlencoded"
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded" "TRUE")
_cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
# If cookies is not empty then logon successful
if [ -z "$_cookie" ]; then
_err "Fail to get cookie."
return 1
fi
_debug "cookie" "$_cookie"
_info "Uploading certificate"
_date=$(date +"%Y%m%d")
encoded_key="$(_url_encode <"$_ckey")"
encoded_ccert="$(_url_encode <"$_ccert")"
encoded_cca="$(_url_encode <"$_cca")"
certificate_name="$_cdomain-$_date"
_request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca"
_H1="Cookie: $_cookie"
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST")
_message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
_info "_message" "$_message"
if [ -z "$_message" ]; then
_err "Fail to upload certificate."
return 1
fi
for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do
_info "Apply certificate to domain id $DOMAIN_ID"
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID")
cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p')
target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p')
if [ "$target_type" = "directory" ]; then
path=$(echo "$_response" | awk '/name="path"/{getline; print}' | sed -n 's/.*value="\([^"]*\).*/\1/p')
fi
echo "$_response" | grep "is_prefer_https" | grep "checked" >/dev/null
if [ $? -eq 0 ]; then
is_prefer_https=1
else
is_prefer_https=0
fi
echo "$_response" | grep "hsts_enabled" | grep "checked" >/dev/null
if [ $? -eq 0 ]; then
hsts_enabled=1
else
hsts_enabled=0
fi
_debug "cert_value" "$cert_value"
if [ -z "$cert_value" ]; then
_err "Fail to get certificate id."
return 1
fi
_request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS"
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST")
_message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
_info "_message" "$_message"
if [ -z "$_message" ]; then
_err "Fail to apply certificate."
return 1
fi
done
_info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID."
return 0
}
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env sh
keyhelp_api_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
# Read config from saved values or env
_getdeployconf DEPLOY_KEYHELP_HOST
_getdeployconf DEPLOY_KEYHELP_API_KEY
_debug DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
_secure_debug DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
if [ -z "$DEPLOY_KEYHELP_HOST" ]; then
_err "KeyHelp host not found, please define DEPLOY_KEYHELP_HOST."
return 1
fi
if [ -z "$DEPLOY_KEYHELP_API_KEY" ]; then
_err "KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY."
return 1
fi
# Save current values
_savedeployconf DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
_savedeployconf DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
_request_key="$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')"
_request_cert="$(tr '\n' ':' <"$_ccert" | sed 's/:/\\n/g')"
_request_ca="$(tr '\n' ':' <"$_cca" | sed 's/:/\\n/g')"
_request_body="{
\"name\": \"$_cdomain\",
\"components\": {
\"private_key\": \"$_request_key\",
\"certificate\": \"$_request_cert\",
\"ca_certificate\": \"$_request_ca\"
}
}"
_hosts="$(echo "$DEPLOY_KEYHELP_HOST" | tr "," " ")"
_keys="$(echo "$DEPLOY_KEYHELP_API_KEY" | tr "," " ")"
_i=1
for _host in $_hosts; do
_key="$(_getfield "$_keys" "$_i" " ")"
_i="$(_math "$_i" + 1)"
export _H1="X-API-Key: $_key"
_put_url="$_host/api/v2/certificates/name/$_cdomain"
if _post "$_request_body" "$_put_url" "" "PUT" "application/json" >/dev/null; then
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
else
_err "Cannot make PUT request to $_put_url"
return 1
fi
if [ "$_code" = "404" ]; then
_info "$_cdomain not found, creating new entry at $_host"
_post_url="$_host/api/v2/certificates"
if _post "$_request_body" "$_post_url" "" "POST" "application/json" >/dev/null; then
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
else
_err "Cannot make POST request to $_post_url"
return 1
fi
fi
if _startswith "$_code" "2"; then
_info "$_cdomain set at $_host"
else
_err "HTTP status code is $_code"
return 1
fi
done
return 0
}
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env sh
# 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/ )
#
# If the same file is configured for the certificate key
# and the certificate and/or full chain, a combined PEM file will
# be output instead.
#
# Environment variables to be utilized are as follows:
#
# DEPLOY_LOCALCOPY_CERTKEY - /path/to/target/cert.key
# DEPLOY_LOCALCOPY_CERTIFICATE - /path/to/target/cert.cer
# DEPLOY_LOCALCOPY_FULLCHAIN - /path/to/target/fullchain.cer
# DEPLOY_LOCALCOPY_CA - /path/to/target/ca.cer
# DEPLOY_LOCALCOPY_PFX - /path/to/target/cert.pfx
# DEPLOY_LOCALCOPY_RELOADCMD - "echo 'this is my cmd'"
######## Public functions #####################
#domain keyfile certfile cafile fullchain
localcopy_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cpfx="$6"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_debug _cpfx "$_cpfx"
_getdeployconf DEPLOY_LOCALCOPY_CERTIFICATE
_getdeployconf DEPLOY_LOCALCOPY_CERTKEY
_getdeployconf DEPLOY_LOCALCOPY_FULLCHAIN
_getdeployconf DEPLOY_LOCALCOPY_CA
_getdeployconf DEPLOY_LOCALCOPY_RELOADCMD
_getdeployconf DEPLOY_LOCALCOPY_PFX
_combined_target=""
_combined_srccert=""
# Create PEM file
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ] &&
{ [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ] ||
[ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; }; then
_combined_target="$DEPLOY_LOCALCOPY_CERTKEY"
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
_combined_srccert="$_ccert"
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
DEPLOY_LOCALCOPY_CERTIFICATE=""
fi
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
_combined_srccert="$_cfullchain"
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
DEPLOY_LOCALCOPY_FULLCHAIN=""
fi
DEPLOY_LOCALCOPY_CERTKEY=""
_info "Creating combined PEM"
_debug "Creating combined PEM at $_combined_target"
if ! [ -f "$_combined_target" ]; then
touch "$_combined_target" || return 1
chmod 600 "$_combined_target"
fi
if ! cat "$_combined_srccert" "$_ckey" >"$_combined_target"; then
_err "Failed to create PEM file"
return 1
fi
fi
if [ "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
_info "Copying certificate"
_debug "Copying $_ccert to $DEPLOY_LOCALCOPY_CERTIFICATE"
if ! cat "$_ccert" >"$DEPLOY_LOCALCOPY_CERTIFICATE"; then
_err "Failed to copy certificate, aborting."
return 1
fi
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
fi
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
_info "Copying certificate key"
_debug "Copying $_ckey to $DEPLOY_LOCALCOPY_CERTKEY"
if ! [ -f "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
touch "$DEPLOY_LOCALCOPY_CERTKEY" || return 1
chmod 600 "$DEPLOY_LOCALCOPY_CERTKEY"
fi
if ! cat "$_ckey" >"$DEPLOY_LOCALCOPY_CERTKEY"; then
_err "Failed to copy certificate key, aborting."
return 1
fi
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
fi
if [ "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
_info "Copying fullchain"
_debug "Copying $_cfullchain to $DEPLOY_LOCALCOPY_FULLCHAIN"
if ! cat "$_cfullchain" >"$DEPLOY_LOCALCOPY_FULLCHAIN"; then
_err "Failed to copy fullchain, aborting."
return 1
fi
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
fi
if [ "$DEPLOY_LOCALCOPY_CA" ]; then
_info "Copying CA"
_debug "Copying $_cca to $DEPLOY_LOCALCOPY_CA"
if ! cat "$_cca" >"$DEPLOY_LOCALCOPY_CA"; then
_err "Failed to copy CA, aborting."
return 1
fi
_savedeployconf DEPLOY_LOCALCOPY_CA "$DEPLOY_LOCALCOPY_CA"
fi
if [ "$DEPLOY_LOCALCOPY_PFX" ]; then
_info "Copying PFX"
_debug "Copying $_cpfx to $DEPLOY_LOCALCOPY_PFX"
if ! [ -f "$DEPLOY_LOCALCOPY_PFX" ]; then
touch "$DEPLOY_LOCALCOPY_PFX" || return 1
chmod 600 "$DEPLOY_LOCALCOPY_PFX"
fi
if ! cat "$_cpfx" >"$DEPLOY_LOCALCOPY_PFX"; then
_err "Failed to copy PFX, aborting."
return 1
fi
_savedeployconf DEPLOY_LOCALCOPY_PFX "$DEPLOY_LOCALCOPY_PFX"
fi
_reload=$DEPLOY_LOCALCOPY_RELOADCMD
_debug "Running reloadcmd $_reload"
if [ -z "$_reload" ]; then
_info "Reloadcmd not provided, skipping."
else
_info "Reloading"
if eval "$_reload"; then
_info "Reload successful."
_savedeployconf DEPLOY_LOCALCOPY_RELOADCMD "$DEPLOY_LOCALCOPY_RELOADCMD" "base64"
else
_err "Reload failed."
return 1
fi
fi
_info "$(__green "'localcopy' deploy success")"
return 0
}
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env sh
################################################################################
# ACME.sh 3rd party deploy plugin for multiple (same) services
################################################################################
# Authors: tomo2403 (creator), https://github.com/tomo2403
# Updated: 2025-03-01
# Issues: https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403
################################################################################
# Usage (shown values are the examples):
# 1. Set optional environment variables
# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set"
#
# 2. Run command:
# acme.sh --deploy --deploy-hook multideploy -d example.com
################################################################################
# Dependencies:
# - yq
################################################################################
# Return value:
# 0 means success, otherwise error.
################################################################################
MULTIDEPLOY_VERSION="1.0"
# Description: This function handles the deployment of certificates to multiple services.
# It processes the provided certificate files and deploys them according to the
# configuration specified in the multideploy file.
#
# Parameters:
# _cdomain - The domain name for which the certificate is issued.
# _ckey - The private key file for the certificate.
# _ccert - The certificate file.
# _cca - The CA (Certificate Authority) file.
# _cfullchain - The full chain certificate file.
# _cpfx - The PFX (Personal Information Exchange) file.
multideploy_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cpfx="$6"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_debug _cpfx "$_cpfx"
MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}"
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
MULTIDEPLOY_FILENAME="multideploy.yml"
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
else
_savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
_debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
fi
if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then
_err "Failed to preprocess deploy file."
return 1
fi
_debug3 "File" "$file"
# Deploy to services
_deploy_services "$file"
_exitCode="$?"
return "$_exitCode"
}
# Description:
# This function preprocesses the deploy file by checking if 'yq' is installed,
# verifying the existence of the deploy file, and ensuring only one deploy file is present.
# Arguments:
# $@ - Posible deploy file names.
# Usage:
# _preprocess_deployfile "<deploy_file1>" "<deploy_file2>?"
_preprocess_deployfile() {
# Check if yq is installed
if ! command -v yq >/dev/null 2>&1; then
_err "yq is not installed! Please install yq and try again."
return 1
fi
_debug3 "yq is installed."
# Check if deploy file exists
for file in "$@"; do
_debug3 "Checking file" "$DOMAIN_PATH/$file"
if [ -f "$DOMAIN_PATH/$file" ]; then
_debug3 "File found"
if [ -n "$found_file" ]; then
_err "Multiple deploy files found. Please keep only one deploy file."
return 1
fi
found_file="$file"
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."
return 1
fi
if ! _check_deployfile "$DOMAIN_PATH/$found_file"; then
_err "Deploy file is not valid: $DOMAIN_PATH/$found_file"
return 1
fi
echo "$DOMAIN_PATH/$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.
# Usage:
# _check_deployfile "<deploy_file_path>"
_check_deployfile() {
_deploy_file="$1"
_debug2 "check: Deploy file" "$_deploy_file"
# Check version
_deploy_file_version=$(yq -r '.version' "$_deploy_file")
if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then
_err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version."
return 1
fi
_debug2 "check: Deploy file version is compatible: $_deploy_file_version"
# Extract all services from config
_services=$(yq -r '.services[].name' "$_deploy_file")
if [ -z "$_services" ]; then
_err "Config does not have any services to deploy to."
return 1
fi
_debug2 "check: Config has services."
echo "$_services" | while read -r _service; do
_debug3 " - $_service"
done
# Check if extracted services exist in services list
echo "$_services" | while read -r _service; do
_debug2 "check: Checking service: $_service"
# Check if service exists
_service_config=$(yq -r ".services[] | select(.name == \"$_service\")" "$_deploy_file")
if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then
_err "Service '$_service' not found."
return 1
fi
_service_hook=$(echo "$_service_config" | yq -r ".hook" -)
if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then
_err "Service '$_service' does not have a hook."
return 1
fi
_service_environment=$(echo "$_service_config" | yq -r ".environment" -)
if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then
_err "Service '$_service' does not have an environment."
return 1
fi
done
}
# Description: This function takes a list of environment variables in YAML format,
# parses them, and exports each key-value pair as environment variables.
# Arguments:
# $1 - A string containing the list of environment variables in YAML format.
# Usage:
# _export_envs "$env_list"
_export_envs() {
_env_list="$1"
_secure_debug3 "Exporting envs" "$_env_list"
echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do
# Using eval to expand nested variables in the configuration file
_value=$(eval 'echo "'"$_value"'"')
_savedeployconf "$_key" "$_value"
_secure_debug3 "Saved $_key" "$_value"
done
}
# Description:
# This function takes a YAML formatted string of environment variables, parses it,
# and clears each environment variable. It logs the process of clearing each variable.
#
# Note: Environment variables for a hook may be optional and differ between
# services using the same hook.
# If one service sets optional environment variables and another does not, the
# variables may persist and affect subsequent deployments.
# Clearing these variables after each service ensures that only the
# environment variables explicitly specified for each service in the deploy
# file are used.
# Arguments:
# $1 - A YAML formatted string containing environment variable key-value pairs.
# Usage:
# _clear_envs "<yaml_string>"
_clear_envs() {
_env_list="$1"
_secure_debug3 "Clearing envs" "$_env_list"
env_pairs=$(echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value')
echo "$env_pairs" | while IFS='=' read -r _key _value; do
_debug3 "Deleting key" "$_key"
_cleardeployconf "$_key"
unset -v "$_key"
done
}
# Description:
# This function deploys services listed in the deploy configuration file.
# Arguments:
# $1 - The path to the deploy configuration file.
# $2 - The list of services to deploy.
# Usage:
# _deploy_services "<deploy_file_path>" "<services_list>"
_deploy_services() {
_deploy_file="$1"
_debug3 "Deploy file" "$_deploy_file"
_tempfile=$(mktemp)
trap 'rm -f $_tempfile' EXIT
yq -r '.services[].name' "$_deploy_file" >"$_tempfile"
_debug3 "Services" "$(cat "$_tempfile")"
_failedServices=""
_failedCount=0
while read -r _service <&3; do
_debug2 "Service" "$_service"
_hook=$(yq -r ".services[] | select(.name == \"$_service\").hook" "$_deploy_file")
_envs=$(yq -r ".services[] | select(.name == \"$_service\").environment" "$_deploy_file")
_export_envs "$_envs"
if ! _deploy_service "$_service" "$_hook"; then
_failedServices="$_service, $_failedServices"
_failedCount=$((_failedCount + 1))
fi
_clear_envs "$_envs"
done 3<"$_tempfile"
_debug3 "Failed services" "$_failedServices"
_debug2 "Failed count" "$_failedCount"
if [ -n "$_failedServices" ]; then
_info "$(__red "Deployment failed") for services: $_failedServices"
else
_debug "All services deployed successfully."
fi
return "$_failedCount"
}
# Description: Deploys a service using the specified hook.
# Arguments:
# $1 - The name of the service to deploy.
# $2 - The hook to use for deployment.
# Usage:
# _deploy_service <service_name> <hook>
_deploy_service() {
_name="$1"
_hook="$2"
_debug2 "SERVICE" "$_name"
_debug2 "HOOK" "$_hook"
_info "$(__green "Deploying") to '$_name' using '$_hook'"
_deploy "$_cdomain" "$_hook"
}
+3 -1
View File
@@ -54,6 +54,8 @@ mydevil_deploy() {
# Usage: ip=$(mydevil_get_ip domain.com)
# echo $ip
mydevil_get_ip() {
devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
# tr squeezes runs of blanks into one tab so plain cut works everywhere;
# cut -w is BSD-only and unknown to GNU coreutils
devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1
return 0
}
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env sh
# Script to deploy certificate to Netlify
# https://docs.netlify.com/api/get-started/#authentication
# https://open-api.netlify.com/#tag/sniCertificate
# This deployment required following variables
# export Netlify_ACCESS_TOKEN="Your Netlify Access Token"
# export Netlify_SITE_ID="Your Netlify Site ID"
# If have more than one SITE ID
# export Netlify_SITE_ID="SITE_ID_1 SITE_ID_2"
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
netlify_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$Netlify_ACCESS_TOKEN" ]; then
_err "Netlify_ACCESS_TOKEN is not defined."
return 1
else
_savedomainconf Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
fi
if [ -z "$Netlify_SITE_ID" ]; then
_err "Netlify_SITE_ID is not defined."
return 1
else
_savedomainconf Netlify_SITE_ID "$Netlify_SITE_ID"
fi
_info "Deploying certificate to Netlify..."
## upload certificate
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
for SITE_ID in $Netlify_SITE_ID; do
_request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}"
_debug _request_body "$_request_body"
_debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN"
_response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json")
if _contains "$_response" "\"error\""; then
_err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID."
_err "$_response"
return 1
fi
_debug response "$_response"
_info "Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID."
done
return 0
}
+123 -28
View File
@@ -7,20 +7,27 @@
#
# Firewall admin with superuser and IP address is required.
#
# REQURED:
# REQUIRED:
# export PANOS_HOST=""
# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
# export PANOS_PASS=""
#
# OPTIONAL
# export PANOS_TEMPLATE="" #Template Name of panorama managed devices
# export PANOS_TEMPLATE="" # Template Name of panorama managed devices
# export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically
# export PANOS_VSYS="Shared" # name of the vsys to import the certificate
# export PANOS_CERTNAME="" # use a custom certificate name to work around Panorama's 31-character limit
#
# The script will automatically generate a new API key if
# no key is found, or if a saved key has expired or is invalid.
_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds
_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes)
# This function is to parse the XML response from the firewall
parse_response() {
type=$2
_debug "API Response: $1"
if [ "$type" = 'keygen' ]; then
status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
if [ "$status" = "success" ]; then
@@ -30,6 +37,13 @@ parse_response() {
message="PAN-OS Key could not be set."
fi
else
if [ "$type" = 'commit' ]; then
job_id=$(echo "$1" | sed 's/^.*\(<job>\)\(.*\)<\/job>.*/\2/g')
_commit_job_id=$job_id
elif [ "$type" = 'job_status' ]; then
job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*<result>\([^<]*\)<\/result>.*/\1/g')
_commit_job_status=$job_status
fi
status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g')
message=$(echo "$1" | tr -d '\n' | sed 's/.*\(<result>\|<msg>\|<line>\)\([^<]*\).*/\2/g')
_debug "Firewall message: $message"
@@ -44,24 +58,23 @@ parse_response() {
#This function is used to deploy to the firewall
deployer() {
content=""
type=$1 # Types are keytest, keygen, cert, key, commit
type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push
panos_url="https://$_panos_host/api/"
export _H1="Content-Type: application/x-www-form-urlencoded"
#Test API Key by performing a lookup
if [ "$type" = 'keytest' ]; then
_debug "**** Testing saved API Key ****"
_H1="Content-Type: application/x-www-form-urlencoded"
# Get Version Info to test key
content="type=version&key=$_panos_key"
## Exclude all scopes for the empty commit
#_exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network><shared-object>exclude</shared-object>"
#content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>acmekeytest</member></admin></partial></commit>"
#_exclude_scope="<device-and-network>excluded</device-and-network><shared-object>excluded</shared-object>"
#content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>"
fi
# Generate API Key
if [ "$type" = 'keygen' ]; then
_debug "**** Generating new API Key ****"
_H1="Content-Type: application/x-www-form-urlencoded"
content="type=keygen&user=$_panos_user&password=$_panos_pass"
# content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
fi
@@ -77,25 +90,31 @@ deployer() {
if [ "$type" = 'cert' ]; then
panos_url="${panos_url}?type=import"
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
if [ "$_panos_template" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
fi
if [ "$_panos_vsys" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
fi
fi
if [ "$type" = 'key' ]; then
panos_url="${panos_url}?type=import"
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_certname.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
if [ "$_panos_template" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
fi
if [ "$_panos_vsys" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
fi
fi
#Close multipart
content="$content${nl}--$delim--${nl}${nl}"
@@ -106,18 +125,30 @@ deployer() {
# Commit changes
if [ "$type" = 'commit' ]; then
_debug "**** Committing changes ****"
export _H1="Content-Type: application/x-www-form-urlencoded"
#Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!
if [ "$FORCE" ]; then
_debug "Force switch detected. Committing ALL changes to the firewall."
cmd=$(printf "%s" "<commit><partial><force><admin><member>$_panos_user</member></admin></force></partial></commit>" | _url_encode)
cmd=$(printf "%s" "<commit><force><partial><admin><member>$_panos_user</member></admin></partial></force></commit>" | _url_encode)
else
_exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network>"
cmd=$(printf "%s" "<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
cmd=$(printf "%s" "<commit><partial><admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
fi
content="type=commit&action=partial&key=$_panos_key&cmd=$cmd"
fi
# Query job status
if [ "$type" = 'job_status' ]; then
echo "**** Querying job $_commit_job_id status ****"
cmd=$(printf "%s" "<show><jobs><id>$_commit_job_id</id></jobs></show>" | _url_encode)
content="type=op&key=$_panos_key&cmd=$cmd"
fi
# Push changes
if [ "$type" = 'push' ]; then
echo "**** Pushing changes ****"
cmd=$(printf "%s" "<commit-all><template-stack><name>$_panos_template_stack</name><admin><member>$_panos_user</member></admin></template-stack></commit-all>" | _url_encode)
content="type=commit&action=all&key=$_panos_key&cmd=$cmd"
fi
response=$(_post "$content" "$panos_url" "" "POST")
parse_response "$response" "$type"
# Saving response to variables
@@ -126,6 +157,8 @@ deployer() {
if [ "$response_status" = "success" ]; then
_debug "Successfully deployed $type"
return 0
elif [ "$_commit_job_status" ]; then
_debug "Commit Job Status = $_commit_job_status"
else
_err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
_debug "$message"
@@ -173,13 +206,12 @@ panos_deploy() {
fi
# PANOS_KEY
_getdeployconf PANOS_KEY
if [ "$PANOS_KEY" ]; then
_debug "Detected saved key."
_panos_key=$PANOS_KEY
_debug "Detected ENV variable PANOS_KEY. Saving to file."
_savedeployconf PANOS_KEY "$PANOS_KEY" 1
else
_debug "No key detected"
unset _panos_key
_debug "Attempting to load variable PANOS_KEY from file."
_getdeployconf PANOS_KEY
fi
# PANOS_TEMPLATE
@@ -191,11 +223,42 @@ panos_deploy() {
_getdeployconf PANOS_TEMPLATE
fi
# PANOS_TEMPLATE_STACK
if [ "$PANOS_TEMPLATE_STACK" ]; then
_debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file."
_savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1
else
_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
@@ -207,15 +270,22 @@ panos_deploy() {
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."
return 1
elif [ -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."
return 1
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."
return 1
else
# Use certificate name based on the first domain on the certificate if no custom certificate name is set
if [ -z "$_panos_certname" ]; then
_panos_certname="$_cdomain"
_savedeployconf PANOS_CERTNAME "$_panos_certname" 1
fi
# Generate a new API key if no valid API key is found
if [ -z "$_panos_key" ]; then
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."
return 1
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."
return 1
fi
_debug "**** Generating new PANOS API KEY ****"
deployer keygen
_savedeployconf PANOS_KEY "$_panos_key" 1
@@ -226,9 +296,34 @@ panos_deploy() {
_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"
return 1
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."
return 1
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."
return 1
fi
if ! deployer commit; then
return 1
fi
if [ "$_panos_template_stack" ]; then
# try to get job status for 20 times in 30 sec interval
i=0
while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do
deployer job_status
if [ "$_commit_job_status" = "OK" ]; then
echo "Commit finished!"
break
fi
sleep $_COMMIT_WAIT_INTERVAL
i=$((i + 1))
done
deployer push
fi
fi
fi
}
+13 -1
View File
@@ -115,6 +115,18 @@ HEREDOC
_info "Push certificates to server"
export HTTPS_INSECURE=1
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}"
_post "$_json_payload" "$_target_url" "" POST "application/json"
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
response="$(echo "$response" | _json_decode | _normalizeJson)"
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
_retval=$?
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
_debug3 response "$response"
_info "Certificate successfully deployed"
return 0
else
_err "Certificate deployment failed: $message"
_debug "Response" "$response"
return 1
fi
}
+13 -1
View File
@@ -127,6 +127,18 @@ HEREDOC
_info "Push certificates to server"
export HTTPS_INSECURE=1
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
_post "$_json_payload" "$_target_url" "" POST "application/json"
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
response="$(echo "$response" | _json_decode | _normalizeJson)"
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
_retval=$?
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
_debug3 response "$response"
_info "Certificate successfully deployed"
return 0
else
_err "Certificate deployment failed: $message"
_debug "Response" "$response"
return 1
fi
}
+9 -1
View File
@@ -8,6 +8,8 @@
# export QINIU_CDN_DOMAIN="cdn.example.com"
# If you have more than one domain, just
# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
# Optional: force HTTPS redirect (default: false)
# export QINIU_FORCE_HTTPS="true"
QINIU_API_BASE="https://api.qiniu.com"
@@ -44,6 +46,12 @@ qiniu_deploy() {
QINIU_CDN_DOMAIN="$_cdomain"
fi
if [ -z "$QINIU_FORCE_HTTPS" ]; then
QINIU_FORCE_HTTPS="false"
else
_savedomainconf QINIU_FORCE_HTTPS "$QINIU_FORCE_HTTPS"
fi
## upload certificate
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
@@ -69,7 +77,7 @@ qiniu_deploy() {
_debug certId "$_certId"
## update domain ssl config
update_body="{\"certid\":$_certId,\"forceHttps\":false}"
update_body="{\"certid\":$_certId,\"forceHttps\":$QINIU_FORCE_HTTPS}"
for domain in $QINIU_CDN_DOMAIN; do
update_path="/domain/$domain/httpsconf"
update_access_token="$(_make_access_token "$update_path")"
+2 -1
View File
@@ -125,7 +125,7 @@ routeros_deploy() {
_savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT"
_savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD"
_savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD"
_savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES"
_savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" "base64"
# push key to routeros
if ! _scp_certificate "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"; then
@@ -143,6 +143,7 @@ comment=\"generated by routeros deploy script in acme.sh\" \
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
\n/certificate remove [ find name=$_cdomain.cer_2 ];\
\n/certificate remove [ find name=$_cdomain.cer_3 ];\
\ndelay 1;\
\n/certificate import file-name=\\\"$_cdomain.cer\\\" passphrase=\\\"\\\";\
\n/certificate import file-name=\\\"$_cdomain.key\\\" passphrase=\\\"\\\";\
+36 -5
View File
@@ -25,7 +25,8 @@
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
# export DEPLOY_SSH_USE_SCP="" yes or no, default to no
# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
#
# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c
# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes
######## Public functions #####################
#domain keyfile certfile cafile fullchain
@@ -71,6 +72,24 @@ ssh_deploy() {
fi
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
# REMOTE_SHELL is optional. If not provided then use sh
_migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL
_getdeployconf DEPLOY_SSH_REMOTE_SHELL
_debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then
DEPLOY_SSH_REMOTE_SHELL="sh -c"
fi
_savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
# REMOTE_CMD_QUOTE is optional. If not provided then yes
_migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE
_getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE
_debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then
DEPLOY_SSH_REMOTE_CMD_QUOTE="yes"
fi
_savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
# BACKUP is optional. If not provided then default to previously saved value or yes.
_migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
_getdeployconf DEPLOY_SSH_BACKUP
@@ -170,10 +189,16 @@ ssh_deploy() {
_info "Required commands batched and sent in single call to remote host"
fi
_returnCode=0
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
_ssh_deploy
if ! _ssh_deploy; then
# in case of an error, remember it, but keep going for the remaining servers
_returnCode=1
fi
done
return $_returnCode
}
_ssh_deploy() {
@@ -238,6 +263,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
return $_err_code
fi
else
# If file doesn't exist, create it and change its permissions.
_cmdstr="$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;"
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
_info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
@@ -426,9 +453,13 @@ _ssh_remote_cmd() {
_secure_debug "Remote commands to execute: $_cmd"
_info "Submitting sequence of commands to remote server by $_ssh_cmd"
# quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'"
if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then
# quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'"
else
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd"
fi
_err_code="$?"
if [ "$_err_code" != "0" ]; then
+9 -9
View File
@@ -33,7 +33,7 @@ strongswan_deploy() {
return 1
fi
_info _confdir "${_confdir}"
__deploy_cert "$@" "stroke" "${_confdir}"
__deploy_cert "stroke" "${_confdir}" "$@"
${_ipsec} reload
fi
# For modern vici mode
@@ -50,7 +50,7 @@ strongswan_deploy() {
_err "no swanctl config dir is found"
return 1
fi
__deploy_cert "$@" "vici" "${_confdir}"
__deploy_cert "vici" "${_confdir}" "$@"
${_swanctl} --load-creds
fi
if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then
@@ -63,13 +63,13 @@ strongswan_deploy() {
#################### Private functions below ##################################
__deploy_cert() {
_cdomain="${1}"
_ckey="${2}"
_ccert="${3}"
_cca="${4}"
_cfullchain="${5}"
_swan_mode="${6}"
_confdir="${7}"
_swan_mode="${1}"
_confdir="${2}"
_cdomain="${3}"
_ckey="${4}"
_ccert="${5}"
_cca="${6}"
_cfullchain="${7}"
_debug _cdomain "${_cdomain}"
_debug _ckey "${_ckey}"
_debug _ccert "${_ccert}"
+17 -22
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env sh
################################################################################
# ACME.sh 3rd party deploy plugin for Synology DSM
@@ -72,7 +72,7 @@ synology_dsm_deploy() {
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then
_err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
_err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
_err "Notice: temp admin user authorization method only supports local deployment on DSM."
return 1
fi
@@ -234,11 +234,11 @@ synology_dsm_deploy() {
fi
fi
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
_debug2 error_code "$error_code"
# Account has 2FA-OTP enabled, since error 403 reported.
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
if [ "$error_code" == "403" ]; then
if [ "$error_code" = "403" ]; then
if [ -z "$SYNO_DEVICE_NAME" ]; then
printf "Enter device name or leave empty for default (CertRenewal): "
read -r SYNO_DEVICE_NAME
@@ -269,27 +269,27 @@ synology_dsm_deploy() {
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
fi
fi
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
_debug2 error_code "$error_code"
fi
if [ -n "$error_code" ]; then
if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
_cleardeployconf SYNO_DEVICE_ID
_err "Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window."
elif [ "$error_code" == "404" ]; then
_err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
elif [ "$error_code" = "404" ]; then
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
elif [ "$error_code" == "406" ]; then
elif [ "$error_code" = "406" ]; then
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
else
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
fi
elif [ "$error_code" == "400" ]; then
elif [ "$error_code" = "400" ]; then
_err "Failed to authenticate, no such account or incorrect password."
elif [ "$error_code" == "401" ]; then
elif [ "$error_code" = "401" ]; then
_err "Failed to authenticate with a non-existent account."
elif [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then
elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then
_err "Failed to authenticate, the account password has expired or must be changed."
else
_err "Failed to authenticate with error: $error_code."
@@ -322,8 +322,8 @@ synology_dsm_deploy() {
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME"
else
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME"
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD"
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64"
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64"
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
fi
@@ -336,7 +336,7 @@ synology_dsm_deploy() {
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p")
_debug2 id "$id"
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
_debug2 error_code "$error_code"
if [ -n "$error_code" ]; then
if [ "$error_code" -eq 105 ]; then
@@ -353,7 +353,7 @@ synology_dsm_deploy() {
_debug2 SYNO_CREATE "$SYNO_CREATE"
if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then
_err "Unable to find certificate: $SYNO_CERTIFICATE and $SYNO_CREATE is not set."
_err "Unable to find certificate: $SYNO_CERTIFICATE and \$SYNO_CREATE is not set."
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
@@ -387,7 +387,7 @@ synology_dsm_deploy() {
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
_info "Restart HTTP services succeeded."
else
_info "Restart HTTP services failed."
_info "Restart HTTP services not necessary."
fi
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
_logout
@@ -424,11 +424,6 @@ _temp_admin_cleanup() {
fi
}
#_cleardeployconf key
_cleardeployconf() {
_cleardomainconf "SAVED_$1"
}
# key
_check2cleardeployconfexp() {
_key="$1"
+123 -54
View File
@@ -16,7 +16,12 @@
#
# # API KEY
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI"
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
# Optional:
# export DEPLOY_TRUENAS_HOSTNAME="<TRUENAS_HOSTNAME_OR_IP>"
# export DEPLOY_TRUENAS_PROTOCOL="wss" # ws or wss
# export DEPLOY_TRUENAS_PORT="443" # optional, e.g. 80, 443, 8443
#
### Private functions
@@ -39,19 +44,50 @@ _ws_call() {
_debug "_ws_call arg2" "$2"
_debug "_ws_call arg3" "$3"
if [ $# -eq 3 ]; then
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3")
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3")
fi
if [ $# -eq 2 ]; then
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2")
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2")
fi
if [ $# -eq 1 ]; then
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1")
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1")
fi
_debug "_ws_response" "$_ws_response"
printf "%s" "$_ws_response"
return 0
}
# Upload certificate with webclient api
_ws_upload_cert() {
/usr/bin/env python - <<EOF
import sys
from truenas_api_client import Client
with Client(uri="$_ws_uri") as c:
### Login with API key
print("I:Trying to upload new certificate...")
ret = c.call("auth.login_with_api_key", "${DEPLOY_TRUENAS_APIKEY}")
if ret:
### upload certificate
with open('$1', 'r') as file:
fullchain = file.read()
with open('$2', 'r') as file:
privatekey = file.read()
ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey}, job=True)
print("R:" + str(ret["id"]))
sys.exit(0)
else:
print("R:0")
print("E:_ws_upload_cert error!")
sys.exit(7)
EOF
return $?
}
# Check argument is a number
# Usage:
#
@@ -88,7 +124,7 @@ _ws_check_jobid() {
# n/a
_ws_get_job_result() {
while true; do
sleep 2
_sleep 2
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]")
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')"
@@ -129,7 +165,6 @@ _ws_get_job_result() {
# 5: WebUI cert error
# 6: Job error
# 7: WS call error
# 10: No CORE or SCALE detected
#
truenas_ws_deploy() {
_domain="$1"
@@ -147,11 +182,43 @@ truenas_ws_deploy() {
_info "Checking environment variables..."
_getdeployconf DEPLOY_TRUENAS_APIKEY
_getdeployconf DEPLOY_TRUENAS_HOSTNAME
_getdeployconf DEPLOY_TRUENAS_PROTOCOL
_getdeployconf DEPLOY_TRUENAS_PORT
# Check API Key
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable."
return 1
fi
# Check Hostname, default to localhost if not set
if [ -z "$DEPLOY_TRUENAS_HOSTNAME" ]; then
_info "TrueNAS hostname not set. Using 'localhost'."
DEPLOY_TRUENAS_HOSTNAME="localhost"
fi
# Check protocol, default to ws if not set
if [ -z "$DEPLOY_TRUENAS_PROTOCOL" ]; then
_info "TrueNAS protocol not set. Using 'ws'."
DEPLOY_TRUENAS_PROTOCOL="ws"
fi
# Check port, optional
if [ -n "$DEPLOY_TRUENAS_PORT" ]; then
case "$DEPLOY_TRUENAS_PORT" in
'' | *[!0-9]*)
_err "Invalid TrueNAS port '$DEPLOY_TRUENAS_PORT'. DEPLOY_TRUENAS_PORT must be numeric."
return 8
;;
esac
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME:$DEPLOY_TRUENAS_PORT/websocket"
else
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket"
fi
_debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
_debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
_debug _ws_uri "$_ws_uri"
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
_info "Environment variables: OK"
@@ -168,25 +235,22 @@ truenas_ws_deploy() {
if [ "$_ws_response" != "TRUE" ]; then
_err "TrueNAS is not ready."
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL."
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME, DEPLOY_TRUENAS_PROTOCOL and DEPLOY_TRUENAS_PORT."
_err "Verify API key."
return 2
fi
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
_savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
_savedeployconf DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
_savedeployconf DEPLOY_TRUENAS_PORT "$DEPLOY_TRUENAS_PORT"
_info "TrueNAS health: OK"
########## System info
_info "Gather system info..."
_ws_response=$(_ws_call "system.info")
_truenas_system=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 2 | tr '[:lower:]' '[:upper:]')
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 3)
_info "TrueNAS system: $_truenas_system"
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"')
_info "TrueNAS version: $_truenas_version"
if [ "$_truenas_system" != "SCALE" ] && [ "$_truenas_system" != "CORE" ]; then
_err "Cannot gather TrueNAS system. Nor CORE oder SCALE detected."
return 10
fi
########## Gather current certificate
@@ -203,19 +267,26 @@ truenas_ws_deploy() {
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')"
_info "New WebUI certificate name: $_certname"
_debug _certname "$_certname"
_ws_jobid=$(_ws_call "certificate.create" "{\"name\": \"${_certname}\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": \"$(_json_encode <"$_file_fullchain")\", \"privatekey\": \"$(_json_encode <"$_file_key")\", \"passphrase\": \"\"}")
_debug "_ws_jobid" "$_ws_jobid"
if ! _ws_check_jobid "$_ws_jobid"; then
_err "No JobID returned from websocket method."
return 3
fi
_ws_result=$(_ws_get_job_result "$_ws_jobid")
_ws_ret=$?
if [ $_ws_ret -gt 0 ]; then
return $_ws_ret
fi
_debug "_ws_result" "$_ws_result"
_new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
_ws_out=$(_ws_upload_cert "$_file_fullchain" "$_file_key" "$_certname")
echo "$_ws_out" | while IFS= read -r LINE; do
case "$LINE" in
I:*)
_info "${LINE#I:}"
;;
D:*)
_debug "${LINE#D:}"
;;
E*)
_err "${LINE#E:}"
;;
*) ;;
esac
done
_new_certid=$(echo "$_ws_out" | grep 'R:' | cut -d ':' -f 2)
_info "New certificate ID: $_new_certid"
########## FTP
@@ -231,33 +302,31 @@ truenas_ws_deploy() {
########## ix Apps (SCALE only)
if [ "$_truenas_system" = "SCALE" ]; then
_info "Replace app certificates..."
_ws_response=$(_ws_call "app.query")
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
_info "Checking app $_app_name..."
_ws_response=$(_ws_call "app.config" "$_app_name")
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then
_info "App has certificate option, setup new certificate..."
_info "App will be redeployed after updating the certificate."
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}")
_debug "_ws_jobid" "$_ws_jobid"
if ! _ws_check_jobid "$_ws_jobid"; then
_err "No JobID returned from websocket method."
return 3
fi
_ws_result=$(_ws_get_job_result "$_ws_jobid")
_ws_ret=$?
if [ $_ws_ret -gt 0 ]; then
return $_ws_ret
fi
_debug "_ws_result" "$_ws_result"
_info "App certificate replaced."
else
_info "App has no certificate option, skipping..."
_info "Replace app certificates..."
_ws_response=$(_ws_call "app.query")
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
_info "Checking app $_app_name..."
_ws_response=$(_ws_call "app.config" "$_app_name")
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then
_info "App has certificate option, setup new certificate..."
_info "App will be redeployed after updating the certificate."
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}")
_debug "_ws_jobid" "$_ws_jobid"
if ! _ws_check_jobid "$_ws_jobid"; then
_err "No JobID returned from websocket method."
return 3
fi
done
fi
_ws_result=$(_ws_get_job_result "$_ws_jobid")
_ws_ret=$?
if [ $_ws_ret -gt 0 ]; then
return $_ws_ret
fi
_debug "_ws_result" "$_ws_result"
_info "App certificate replaced."
else
_info "App has no certificate option, skipping..."
fi
done
########## WebUI
@@ -273,7 +342,7 @@ truenas_ws_deploy() {
_info "Restarting WebUI..."
_ws_response=$(_ws_call "system.general.ui_restart")
_info "Waiting for UI restart..."
sleep 6
_sleep 15
########## Certificates
+4 -2
View File
@@ -143,8 +143,10 @@ unifi_deploy() {
# correct file ownership according to the directory, the keystore is placed in
_unifi_keystore_dir=$(dirname "${_unifi_keystore}")
_unifi_keystore_dir_owner=$(find "${_unifi_keystore_dir}" -maxdepth 0 -printf '%u\n')
_unifi_keystore_owner=$(find "${_unifi_keystore}" -maxdepth 0 -printf '%u\n')
# shellcheck disable=SC2012
_unifi_keystore_dir_owner=$(ls -ld "${_unifi_keystore_dir}" | awk '{print $3}')
# shellcheck disable=SC2012
_unifi_keystore_owner=$(ls -l "${_unifi_keystore}" | awk '{print $3}')
if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then
_debug "Changing keystore owner to ${_unifi_keystore_dir_owner}"
chown "$_unifi_keystore_dir_owner" "${_unifi_keystore}" >/dev/null 2>&1 # fail quietly if we're not running as root
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env sh
# install a certificate on a Windows host over OpenSSH and bind it to the Remote
# Desktop listener (RDP-Tcp).
#
# One ssh invocation does the whole job:
# * the PFX is built locally, base64'd, and embedded as a string literal
# inside a generated PowerShell script;
# * the script is piped to `powershell.exe -Command -` over ssh. No scp,
# no temp files on the Windows host.
#
# First run:
# export DEPLOY_WIN_RDP_HOST=winserver.example.com
# acme.sh --deploy -d winserver.example.com --deploy-hook windows_rdp
#
# Available variables:
# DEPLOY_WIN_RDP_HOST required SSH host
# DEPLOY_WIN_RDP_USER optional SSH user, must be a local administrator (can also by set via ssh_config)
# DEPLOY_WIN_RDP_PORT optional SSH port, default 22
# DEPLOY_WIN_RDP_SSH_OPTS optional extra ssh options, e.g.
# "-i /root/.ssh/win_id_ed25519 -o StrictHostKeyChecking=yes"
# DEPLOY_WIN_RDP_LISTENER optional RDP listener name, default RDP-Tcp
# DEPLOY_WIN_RDP_RESTART optional "1" to restart TermService after install.
# Active RDP sessions will drop!
windows_rdp_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if ! _exists "ssh"; then
_err "ssh is required but was not found in PATH."
return 1
fi
# ---- configuration ------------------------------------------------------
_getdeployconf DEPLOY_WIN_RDP_HOST
_getdeployconf DEPLOY_WIN_RDP_USER
_getdeployconf DEPLOY_WIN_RDP_PORT
_getdeployconf DEPLOY_WIN_RDP_SSH_OPTS
_getdeployconf DEPLOY_WIN_RDP_LISTENER
_getdeployconf DEPLOY_WIN_RDP_RESTART
if [ -z "$DEPLOY_WIN_RDP_HOST" ]; then
_err "DEPLOY_WIN_RDP_HOST must be set."
return 1
fi
_savedeployconf DEPLOY_WIN_RDP_HOST "$DEPLOY_WIN_RDP_HOST"
[ -n "$DEPLOY_WIN_RDP_USER" ] && _savedeployconf DEPLOY_WIN_RDP_USER "$DEPLOY_WIN_RDP_USER"
[ -n "$DEPLOY_WIN_RDP_PORT" ] && _savedeployconf DEPLOY_WIN_RDP_PORT "$DEPLOY_WIN_RDP_PORT"
[ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ] && _savedeployconf DEPLOY_WIN_RDP_SSH_OPTS "$DEPLOY_WIN_RDP_SSH_OPTS"
[ -n "$DEPLOY_WIN_RDP_LISTENER" ] && _savedeployconf DEPLOY_WIN_RDP_LISTENER "$DEPLOY_WIN_RDP_LISTENER"
[ -n "$DEPLOY_WIN_RDP_RESTART" ] && _savedeployconf DEPLOY_WIN_RDP_RESTART "$DEPLOY_WIN_RDP_RESTART"
_port="${DEPLOY_WIN_RDP_PORT:-22}"
_listener="${DEPLOY_WIN_RDP_LISTENER:-RDP-Tcp}"
if [ -n "$DEPLOY_WIN_RDP_USER" ]; then
_target="$DEPLOY_WIN_RDP_USER@$DEPLOY_WIN_RDP_HOST"
else
_target="$DEPLOY_WIN_RDP_HOST"
fi
_pfx_pass="acme"
# ---- build thumbprint + PFX locally ------------------------------------
_thumb="$(_fingerprint "$_ccert" 'sha1')"
if [ -z "$_thumb" ]; then
_err "Failed to compute certificate thumbprint."
return 1
fi
_debug "Thumbprint: $_thumb"
_debug "Building PFX at $_pfx_file"
_pfx_file="$(_mktemp)"
if ! _toPkcs "$_pfx_file" "$_ckey" "$_ccert" "$_cca" "$_pfx_pass"; then
_err "Failed to build PFX archive."
rm -f "$_pfx_file"
return 1
fi
_pfx_b64=$(_base64 "multiline" <"$_pfx_file")
rm -f "$_pfx_file"
# ---- build installer script --------------------------------------------
if [ "$DEPLOY_WIN_RDP_RESTART" = "1" ]; then
_restart_ps='Restart-Service -Name TermService -Force'
else
_restart_ps='# New RdP connections will pick up the new cert automatically.'
fi
# Escape every literal `$` with `\$` so the shell does not expand it.
# Values substituted from shell: $_pfx_b64, $_pfx_pass, $_thumb, $_listener.
_ps1=$(
cat <<PSEOF
\$ErrorActionPreference = 'Stop'
\$pfxBytes = [Convert]::FromBase64String('${_pfx_b64}')
# Note: It is quite important to use a X509Certificate2Collection here in any case, since we otherwise
# could run into quite a lot of trouble when importing the certificate including its entire chain
# and its private key. Windows might behave arbitrarily and not consistently import the certificate
# at all - unless "Exportable" is included in the storage flags. However, then the certificate seems
# unaccessible to TermService for some weird reasons despite all permissions being set (at least on my
# Win 11 lab machine). This might be some security setting that prevents TermService from working with
# exportable keys? I don't know - importing the entire collection including chain or not always fixes
# the issues.
#
# Note2: If you should have kicked yourself out for some reason, then deleting the certificate will make
# TermService restore the original, self-signed certificate after at least after the second login attempt.
# Deleting the certificate can be easily accomplished via the Powershell, since SSH access will still be
# present in any case - the following command should get you out of trouble:
# \$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\\${_thumb}' | Select-Object -First 1 | Remove-Item
\$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]'MachineKeySet,PersistKeySet'
\$certs = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
\$certs.Import(\$pfxBytes, '${_pfx_pass}', \$flags)
\$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My', 'LocalMachine')
\$store.Open('ReadWrite')
\$store.AddRange(\$certs)
\$store.Close()
Write-Host "Installed certs into LocalMachine\\My"
\$ts = Get-CimInstance -Namespace root/cimv2/terminalservices -ClassName Win32_TSGeneralSetting -Filter "TerminalName='${_listener}'"
if (-not \$ts) { throw "Listener '${_listener}' not found." }
Set-CimInstance -InputObject \$ts -Property @{SSLCertificateSHA1Hash="${_thumb}"}
Write-Host "Listener ${_listener} now uses ${_thumb}"
${_restart_ps}
PSEOF
)
_debug "Powershell script:${_ps1}"
# ---- run over a single ssh connection ----------------------------------
_ssh_opts="-o BatchMode=yes -p $_port"
if [ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ]; then
_ssh_opts="$_ssh_opts $DEPLOY_WIN_RDP_SSH_OPTS"
fi
_info "Deploying to $DEPLOY_WIN_RDP_HOST ..."
# shellcheck disable=SC2086
if ! printf '%s\n' "$_ps1" | ssh $_ssh_opts "$_target" \
'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command -'; then
_err "Remote install failed. Re-run acme.sh with --debug to see the PowerShell output."
return 1
fi
_info "Certificate for $_cdomain deployed and bound to $_listener on $DEPLOY_WIN_RDP_HOST."
return 0
}
+500
View File
@@ -0,0 +1,500 @@
#!/usr/bin/env sh
# Deploy certificates to Zyxel GS1900 series switches
#
# This script uses the https web administration interface in order
# to upload updated certificates to Zyxel GS1900 series switches.
# Only a few models have been tested but untested switches from the
# same model line may work as well. If you test and confirm a switch
# as working please submit a pull request updating this compatibility
# list!
#
# Known Issues:
# 1. This is a consumer grade switch and is a bit underpowered
# the longer the RSA key size the slower your switch web UI
# will be. RSA 2048 will work, RSA 4096 will work but you may
# experience performance problems.
# 2. You must use RSA certificates. The switch will reject EC-256
# and EC-384 certificates in firmware 2.80
# See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/
#
# Current GS1900 Switch Compatibility:
# GS1900-8 - Working as of firmware V2.80
# GS1900-8HP - Untested
# GS1900-10HP - Untested
# GS1900-16 - Untested
# GS1900-24 - Untested
# GS1900-24E - Working as of firmware V2.80
# GS1900-24EP - Untested
# GS1900-24HP - Untested
# GS1900-48 - Untested
# GS1900-48HP - Untested
#
# Prerequisite Setup Steps:
# 1. Install at least firmware V2.80 on your switch
# 2. Enable HTTPS web management on your switch
#
# Usage:
# 1. Ensure the switch has firmware V2.80 or later.
# 2. Ensure the switch has HTTPS management enabled.
# 3. Set the appropriate environment variables for your environment.
#
# DEPLOY_ZYXEL_SWITCH - The switch hostname. (Default: _cdomain)
# DEPLOY_ZYXEL_SWITCH_USER - The webadmin user. (Default: admin)
# DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch.
# DEPLOY_ZYXEL_SWITCH_REBOOT - If "1" reboot after update. (Default: "0")
#
# 4. Run the deployment plugin:
# acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com
#
# returns 0 means success, otherwise error.
#domain keyfile certfile cafile fullchain
zyxel_gs1900_deploy() {
_zyxel_gs1900_minimum_firmware_version="v2.80"
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug2 _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_getdeployconf DEPLOY_ZYXEL_SWITCH
_getdeployconf DEPLOY_ZYXEL_SWITCH_USER
_getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD
_getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT
if [ -z "$DEPLOY_ZYXEL_SWITCH" ]; then
DEPLOY_ZYXEL_SWITCH="$_cdomain"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_USER" ]; then
DEPLOY_ZYXEL_SWITCH_USER="admin"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_PASSWORD" ]; then
DEPLOY_ZYXEL_SWITCH_PASSWORD="1234"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_REBOOT" ]; then
DEPLOY_ZYXEL_SWITCH_REBOOT="0"
fi
_savedeployconf DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
_savedeployconf DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
_savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
_savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
_debug DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
_debug DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
_secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
_debug DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
_zyxel_switch_base_uri="https://${DEPLOY_ZYXEL_SWITCH}"
_info "Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}."
_zyxel_gs1900_deployment_precheck || return $?
_zyxel_gs1900_should_update
if [ "$?" != "0" ]; then
_info "The switch already has our certificate installed. No update required."
return 0
else
_info "The switch does not yet have our certificate installed."
fi
_info "Logging into the switch web interface."
_zyxel_gs1900_login || return $?
_info "Validating the switch is compatible with this deployment process."
_zyxel_gs1900_validate_device_compatibility || return $?
_info "Uploading the certificate."
_zyxel_gs1900_upload_certificate || return $?
if [ "$DEPLOY_ZYXEL_SWITCH_REBOOT" = "1" ]; then
_info "Rebooting the switch."
_zyxel_gs1900_trigger_reboot || return $?
fi
return 0
}
_zyxel_gs1900_deployment_precheck() {
# Initialize the keylength if it isn't already
if [ -z "$Le_Keylength" ]; then
Le_Keylength=""
fi
if _isEccKey "$Le_Keylength"; then
_info "Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!"
_info "You can continue, but your switch may reject your key."
elif [ -n "$Le_Keylength" ] && [ "$Le_Keylength" -gt "2048" ]; then
_info "Warning: Your RSA key length is greater than 2048!"
_info "You can continue, but you may experience performance issues in the web administration interface."
fi
# Check the server for some common failure modes prior to authentication and certificate upload in order to avoid
# sending a certificate when we may not want to.
test_login_response=$(_post "username=test&password=test&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" 2>&1)
test_login_page_exitcode="$?"
_debug3 "Test Login Response: ${test_login_response}"
if [ "$test_login_page_exitcode" -ne "0" ]; then
if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then
_err "The SSL certificate at $_zyxel_switch_base_uri could not be validated."
_err "Please double check your hostname, port, and that you are actually connecting to your switch."
_err "If the problem persists then please ensure that the certificate is not self-signed, has not"
_err "expired, and matches the switch hostname. If you expect validation to fail then you can disable"
_err "certificate validation by running with --insecure."
return 1
elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then
_debug3 "Intentionally ignore curl exit code 56 in our precheck"
else
_err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri."
return 1
fi
fi
}
_zyxel_gs1900_login() {
# Login to the switch and set the appropriate auth cookie in _H1
username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode)
password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode)
login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
if [ "$auth_response" != "OK" ]; then
_err "Login failed due to invalid credentials."
_err "Please double check the configured username and password and try again."
return 1
fi
sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';')
_secure_debug2 "sessionid" "$sessionid"
export _H1="Cookie: $sessionid"
_secure_debug2 "_H1" "$_H1"
return 0
}
_zyxel_gs1900_validate_device_compatibility() {
# Check the switches model and firmware version and throw errors
# if this script isn't compatible.
device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n')
model_name=$(_zyxel_gs1900_get_model "$device_info_html")
_debug2 "model_name" "$model_name"
if [ -z "$model_name" ]; then
_err "Could not find the switch model name."
_err "Please re-run with --debug and report a bug."
return $?
fi
if ! expr "$model_name" : "GS1900-" >/dev/null; then
_err "Switch is an unsupported model: $model_name"
return 1
fi
firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html")
_debug2 "firmware_version" "$firmware_version"
if [ -z "$firmware_version" ]; then
_err "Could not find the switch firmware version."
_err "Please re-run with --debug and report a bug."
return $?
fi
_debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version"
minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version")
_debug2 "minimum_major_version" "$minimum_major_version"
minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version")
_debug2 "minimum_minor_version" "$minimum_minor_version"
_debug2 "firmware_version" "$firmware_version"
firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version")
_debug2 "firmware_major_version" "$firmware_major_version"
firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version")
_debug2 "firmware_minor_version" "$firmware_minor_version"
_ret=0
if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then
_ret=1
elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then
_ret=1
fi
if [ "$_ret" != "0" ]; then
_err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version."
fi
return $?
}
_zyxel_gs1900_should_update() {
# Get the remote certificate serial number
_remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
_debug3 "_remote_cert" "$_remote_cert"
_remote_cert_serial=$(printf "%s" "${_remote_cert}" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial)
_debug2 "_remote_cert_serial" "$_remote_cert_serial"
# Get our certificate serial number
_our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <"${_ccert}")
_debug2 "_our_cert_serial" "$_our_cert_serial"
[ "${_remote_cert_serial}" != "${_our_cert_serial}" ]
}
_zyxel_gs1900_upload_certificate() {
# Generate a PKCS12 certificate with a temporary password since the web interface
# requires a password be present. Then upload that certificate.
temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64)
_secure_debug2 "temp_cert_password" "$temp_cert_password"
temp_pkcs12="$(_mktemp)"
_debug2 "temp_pkcs12" "$temp_pkcs12"
_toPkcs "$temp_pkcs12" "$_ckey" "$_ccert" "$_cca" "$temp_cert_password"
if [ "$?" != "0" ]; then
_err "Failed to generate a pkcs12 certificate."
_err "Please re-run with --debug and report a bug."
# ensure the temporary certificate file is cleaned up
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
return $?
fi
# Load the upload page
upload_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914" | tr -d '\n')
# Get the first instance of XSSID from the upload page
form_xss_value=$(printf "%s" "$upload_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g' | head -n 1)
_secure_debug2 "form_xss_value" "$form_xss_value"
_info "Generating the certificate upload request"
upload_post_request="$(_mktemp)"
upload_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
{
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"XSSID\"\r\n\r\n%s\r\n" "${form_xss_value}"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"http_file\"; filename=\"temp_pkcs12.pfx\"\r\n"
printf "Content-Type: application/pkcs12\r\n\r\n"
cat "${temp_pkcs12}"
printf "\r\n"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"pwd\"\r\n\r\n%s\r\n" "${temp_cert_password}"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"cmd\"\r\n\r\n%s\r\n" "31"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"sysSubmit\"\r\n\r\n%s\r\n" "Import"
printf -- "--%s--\r\n" "${upload_post_boundary}"
} >"${upload_post_request}"
_info "Upload certificate to the switch"
# Unfortunately we cannot rely upon the switch response across switch models
# to return a consistent body return - so we cannot inspect the result of this
# upload to determine success.
upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1)
_debug3 "Upload response: ${upload_response}"
rm "${upload_post_request}"
# Pause for a few seconds to give the switch a chance to process the certificate
# For some reason I've found this to be necessary on my GS1900-24E
_debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate."
sleep "4"
# Check to see whether or not our update was successful
_ret=0
_zyxel_gs1900_should_update
if [ "$?" != "0" ]; then
_info "The certificate was updated successfully"
else
_ret=1
_err "The certificate upload does not appear to have worked."
_err "The remote certificate does not match the certificate we tried to upload."
_err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug."
fi
# ensure the temporary files are cleaned up
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
return $_ret
}
# make the certificate upload request using either
# --data binary with @ for file access in CURL
# or using --post-file for wget to ensure we upload
# the pkcs12 without getting tripped up on null bytes
#
# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker]
_zyxel_upload_pkcs12() {
bodyfilename="$1"
multipartformmarker="$2"
_post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi"
httpmethod="POST"
_postContentType="multipart/form-data; boundary=${multipartformmarker}"
if [ -z "$httpmethod" ]; then
httpmethod="POST"
fi
_debug $httpmethod
_debug "_post_url" "$_post_url"
_debug2 "bodyfilename" "$bodyfilename"
_debug2 "_postContentType" "$_postContentType"
_inithttp
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
_CURL="$_ACME_CURL"
if [ "$HTTPS_INSECURE" ]; then
_CURL="$_CURL --insecure "
fi
if [ "$httpmethod" = "HEAD" ]; then
_CURL="$_CURL -I "
fi
_debug "_CURL" "$_CURL"
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")"
_ret="$?"
if [ "$_ret" != "0" ]; then
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
_err "Here is the curl dump log:"
_err "$(cat "$_CURL_DUMP")"
fi
fi
elif [ "$_ACME_WGET" ]; then
_WGET="$_ACME_WGET"
if [ "$HTTPS_INSECURE" ]; then
_WGET="$_WGET --no-check-certificate "
fi
_debug "_WGET" "$_WGET"
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")"
_ret="$?"
if [ "$_ret" = "8" ]; then
_ret=0
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
fi
if [ "$_ret" != "0" ]; then
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
fi
if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output
cat "$HTTP_HEADER" >&2
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
fi
# remove leading whitespaces from header to match curl format
_sed_i 's/^ //g' "$HTTP_HEADER"
else
_ret="$?"
_err "Neither curl nor wget have been found, cannot make $httpmethod request."
fi
_debug "_ret" "$_ret"
printf "%s" "$response"
return $_ret
}
_zyxel_gs1900_trigger_reboot() {
# Trigger a reboot via the management reboot page in the web ui
reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n')
reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g')
_secure_debug2 "reboot_xss_value" "$reboot_xss_value"
reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded")
reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...")
if [ -z "$reboot_message" ]; then
_err "Failed to trigger switch reboot!"
return 1
fi
return 0
}
# password
_zyxel_gs1900_password_obfuscate() {
# Return the password obfuscated via the same method used by the
# switch's web UI login process
echo "$1" | awk '{
encoded = "";
password = $1;
allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
len = length($1);
pwi = length($1);
for (i=1; i <= (321 - pwi); i++)
{
if (0 == i % 5 && pwi > 0)
{
encoded = (encoded)(substr(password, pwi--, 1));
}
else if (i == 123)
{
if (len < 10)
{
encoded = (encoded)(0);
}
else
{
encoded = (encoded)(int(len / 10));
}
}
else if (i == 289)
{
encoded = (encoded)(len % 10)
}
else
{
encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1))
}
}
printf("%s", encoded);
}'
}
# html label
_zyxel_html_table_lookup() {
# Look up a value in the html representing the status page of the switch
# when provided with the html of the page and the label (i.e. "Model Name:")
html="$1"
label=$(printf "%s" "$2" | tr -d ' ')
lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's/<tr>/\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i "$label" | sed "s/<tr><td>$label<\/td><td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i")
printf "%s" "$lookup_result"
return 0
}
# html
_zyxel_gs1900_get_model() {
html="$1"
model_name=$(_zyxel_html_table_lookup "$html" "Model Name:")
printf "%s" "$model_name"
}
# html
_zyxel_gs1900_get_firmware_version() {
html="$1"
firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+")
printf "%s" "$firmware_version"
}
# version_number
_zyxel_gs1900_parse_major_version() {
printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi'
}
# version_number
_zyxel_gs1900_parse_minor_version() {
printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi'
}
+41 -10
View File
@@ -7,6 +7,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting
Options:
One984HOSTING_Username Username
One984HOSTING_Password Password
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.
Issues: github.com/acmesh-official/acme.sh/issues/2851
Author: Adrian Fedoreanu
'
@@ -124,11 +125,28 @@ _1984hosting_login() {
_debug "Login to 1984Hosting as user $One984HOSTING_Username."
username=$(printf '%s' "$One984HOSTING_Username" | _url_encode)
password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
url="https://1984.hosting/api/auth/"
_get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken"
csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
# When 2FA is enabled, mint a fresh TOTP code from the stored shared secret.
# Empty otpkey is accepted by the server when 2FA is off.
otpkey=""
if [ -n "$One984HOSTING_TOTP_Secret" ]; then
if ! _exists oathtool; then
_err "oathtool is required to use One984HOSTING_TOTP_Secret for 2FA. Please install it."
return 1
fi
otpcode="$(oathtool --base32 --totp "$One984HOSTING_TOTP_Secret" 2>/dev/null)"
if [ -z "$otpcode" ]; then
_err "Failed to generate TOTP code from One984HOSTING_TOTP_Secret."
return 1
fi
otpkey="$(printf '%s' "$otpcode" | _url_encode)"
fi
# Fetch the login page to obtain CSRF and session cookies.
# Note: _get sets the global 'url', so assign the auth URL afterwards.
_get "https://1984.hosting/accounts/login/" >/dev/null
csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then
_err "One or more cookies are empty: '$csrftoken', '$sessionid'."
@@ -140,17 +158,23 @@ _1984hosting_login() {
csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1)
export _H3="X-CSRFToken: $csrf_header"
response="$(_post "username=$username&password=$password&otpkey=" $url)"
url="https://1984.hosting/api/auth/"
response="$(_post "username=$username&password=$password&otpkey=$otpkey" "$url")"
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
if _contains "$response" '"loggedin": true'; then
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
export One984HOSTING_SESSIONID_COOKIE
export One984HOSTING_CSRFTOKEN_COOKIE
_saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username"
_saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password"
if [ -n "$One984HOSTING_TOTP_Secret" ]; then
_saveaccountconf_mutable One984HOSTING_TOTP_Secret "$One984HOSTING_TOTP_Secret"
else
_clearaccountconf_mutable One984HOSTING_TOTP_Secret
fi
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
return 0
@@ -161,6 +185,7 @@ _1984hosting_login() {
_check_credentials() {
One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}"
One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}"
One984HOSTING_TOTP_Secret="${One984HOSTING_TOTP_Secret:-$(_readaccountconf_mutable One984HOSTING_TOTP_Secret)}"
if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
One984HOSTING_Username=""
One984HOSTING_Password=""
@@ -225,9 +250,15 @@ _get_root() {
# Usage: _get_zone_id url domain.com
# Returns zone id for domain.com
# Memoized per-domain so add/rm don't re-fetch the same zone list within a run.
# Keyed on domain (not url) since the url is always the domains listing.
_get_zone_id() {
url=$1
domain=$2
if [ "$_zone_id_for" = "$domain" ] && [ -n "$_zone_id" ]; then
_debug2 _zone_id "$_zone_id (cached)"
return 0
fi
_htmlget "$url" "$domain"
_zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)"
_debug2 _zone_id "$_zone_id"
@@ -235,6 +266,7 @@ _get_zone_id() {
_err "Error getting _zone_id for $2."
return 1
fi
_zone_id_for="$domain"
return 0
}
@@ -257,9 +289,8 @@ _htmlget() {
# Add extra headers to request
_authpost() {
url="https://1984.hosting/domains"
_get_zone_id "$url" "$_domain"
csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")"
_get_zone_id "https://1984.hosting/domains" "$_domain"
csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | sed 's/csrftoken=//' | _head_n 1)"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
export _H2="Referer: https://1984.hosting/domains/$_zone_id"
export _H3="X-CSRFToken: $csrf_header"
+11 -1
View File
@@ -37,6 +37,16 @@ dns_acmedns_add() {
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}"
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}"
#for compatibility: old versions stored ACMEDNS_UPDATE_URL in the account
#conf (issue 3899). Do not clear it here: it must stay available for the
#other domains that have not migrated to their domain conf yet.
if [ -z "$ACMEDNS_BASE_URL" ]; then
_acmedns_update_url="$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)"
if [ "$_acmedns_update_url" ]; then
ACMEDNS_BASE_URL="$(echo "$_acmedns_update_url" | sed 's#/update$##')"
fi
fi
if [ "$ACMEDNS_BASE_URL" = "" ]; then
ACMEDNS_BASE_URL="https://auth.acme-dns.io"
fi
@@ -71,7 +81,7 @@ dns_acmedns_add() {
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
_debug data "$data"
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST" "application/json")"
_debug response "$response"
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
+115 -57
View File
@@ -1,17 +1,17 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_active24_info='Active24.com
Site: Active24.com
dns_active24_info='Active24.cz
Site: Active24.cz
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24
Options:
ACTIVE24_Token API Token
Active24_ApiKey API Key. Called "Identifier" in the Active24 Admin
Active24_ApiSecret API Secret. Called "Secret key" in the Active24 Admin
Issues: github.com/acmesh-official/acme.sh/issues/2059
Author: Milan Pála
'
ACTIVE24_Api="https://api.active24.com"
######## Public functions #####################
Active24_Api="https://rest.active24.cz"
# export Active24_ApiKey=ak48l3h7-ak5d-qn4t-p8gc-b6fs8c3l
# export Active24_ApiSecret=ajvkeo3y82ndsu2smvxy3o36496dcascksldncsq
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
@@ -22,8 +22,8 @@ dns_active24_add() {
_active24_init
_info "Adding txt record"
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."
return 1
else
@@ -31,6 +31,7 @@ dns_active24_add() {
return 0
fi
fi
_err "Add txt record error."
return 1
}
@@ -44,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"
if _contains "$response" "errors"; then
if _contains "$response" "error"; then
_err "Error"
return 1
fi
hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-)
# Note: it might never be more than one record actually, NEEDS more INVESTIGATION
record_ids=$(printf "%s" "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
_debug2 record_ids "$record_ids"
for hash_id in $hash_ids; do
_debug "Removing hash_id" "$hash_id"
if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then
if _contains "$response" "errors"; then
for redord_id in $record_ids; do
_debug "Removing record_id" "$redord_id"
_debug "txtvalue" "$txtvalue"
if _active24_rest DELETE "/v2/service/$_service_id/dns/record/$redord_id" ""; then
if _contains "$response" "error"; then
_err "Unable to remove txt record."
return 1
else
@@ -70,21 +77,15 @@ dns_active24_rm() {
return 1
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=1
p=1
if ! _active24_rest GET "dns/domains/v1"; then
if ! _active24_rest GET "/v1/user/self/service"; then
return 1
fi
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug "h" "$h"
@@ -104,21 +105,98 @@ _get_root() {
return 1
}
_active24_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
_active24_init() {
Active24_ApiKey="${Active24_ApiKey:-$(_readaccountconf_mutable Active24_ApiKey)}"
Active24_ApiSecret="${Active24_ApiSecret:-$(_readaccountconf_mutable Active24_ApiSecret)}"
#Active24_ServiceId="${Active24_ServiceId:-$(_readaccountconf_mutable Active24_ServiceId)}"
export _H1="Authorization: Bearer $ACTIVE24_Token"
if [ "$m" != "GET" ]; then
_debug "data" "$data"
response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")"
else
response="$(_get "$ACTIVE24_Api/$ep")"
if [ -z "$Active24_ApiKey" ] || [ -z "$Active24_ApiSecret" ]; then
Active24_ApiKey=""
Active24_ApiSecret=""
_err "You don't specify Active24 api key and ApiSecret yet."
_err "Please create your key and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable Active24_ApiKey "$Active24_ApiKey"
_saveaccountconf_mutable Active24_ApiSecret "$Active24_ApiSecret"
_debug "A24 API CHECK"
if ! _active24_rest GET "/v2/check"; then
_err "A24 API check failed with: $response"
return 1
fi
if ! echo "$response" | tr -d " " | grep \"verified\":true >/dev/null; then
_err "A24 API check failed with: $response"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_active24_get_service_id "$_domain"
_debug _service_id "$_service_id"
}
_active24_get_service_id() {
_d=$1
if ! _active24_rest GET "/v1/user/self/zone/${_d}"; then
return 1
else
response=$(echo "$response" | _json_decode)
_service_id=$(echo "$response" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
fi
}
_active24_rest() {
m=$1
ep_qs=$2 # with query string
# ep=$2
ep=$(printf "%s" "$ep_qs" | cut -d '?' -f1) # no query string
data="$3"
_debug "A24 $ep"
_debug "A24 $Active24_ApiKey"
_debug "A24 $Active24_ApiSecret"
timestamp=$(_time)
datez=$(date -u +"%Y%m%dT%H%M%SZ")
canonicalRequest="${m} ${ep} ${timestamp}"
signature=$(printf "%s" "$canonicalRequest" | _hmac sha1 "$(printf "%s" "$Active24_ApiSecret" | _hex_dump | tr -d " ")" hex)
authorization64="$(printf "%s:%s" "$Active24_ApiKey" "$signature" | _base64)"
export _H1="Date: ${datez}"
export _H2="Accept: application/json"
export _H3="Content-Type: application/json"
export _H4="Authorization: Basic ${authorization64}"
_debug2 H1 "$_H1"
_debug2 H2 "$_H2"
_debug2 H3 "$_H3"
_debug2 H4 "$_H4"
# _sleep 1
if [ "$m" != "GET" ]; then
_debug2 "${m} $Active24_Api${ep_qs}"
_debug "data" "$data"
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
else
if [ -z "$data" ]; then
_debug2 "GET $Active24_Api${ep_qs}"
response="$(_get "$Active24_Api${ep_qs}")"
else
_debug2 "GET $Active24_Api${ep_qs} with data: ${data}"
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
fi
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
@@ -126,23 +204,3 @@ _active24_rest() {
_debug2 response "$response"
return 0
}
_active24_init() {
ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}"
if [ -z "$ACTIVE24_Token" ]; then
ACTIVE24_Token=""
_err "You didn't specify a Active24 api token yet."
_err "Please create the token and try again."
return 1
fi
_saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
}
+30 -13
View File
@@ -18,7 +18,9 @@ Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() {
fulldomain=$1
# the API only accepts punycode for IDN domains, and a raw UTF-8 domain
# also breaks the request signature (issue 4733)
fulldomain=$(_idn "$1")
txtvalue=$2
_prepare_ali_credentials || return 1
@@ -33,7 +35,7 @@ dns_ali_add() {
}
dns_ali_rm() {
fulldomain=$1
fulldomain=$(_idn "$1")
txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
@@ -69,8 +71,8 @@ _ali_rest() {
ign="$2"
mtd="${3:-GET}"
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
signature=$(printf "%s" "$signature" | _url_encode upper-hex)
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
signature=$(printf "%s" "$signature" | _ali_urlencode_upper)
url="$endpoint?Signature=$signature"
if [ "$mtd" = "GET" ]; then
@@ -96,13 +98,28 @@ _ali_rest() {
fi
}
_ali_nonce() {
#_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
#Not so good...
date +"%s%N" | sed 's/%N//g'
# stdin stdout
# The Aliyun signature requires percent-encoding with upper-case hex.
# Do not use "_url_encode upper-hex" here: this file is also bundled by
# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the
# acme.sh function library ignore the upper-hex argument and output
# lower-case hex, which invalidates the signature.
# https://github.com/acmesh-official/acme.sh/issues/6272
_ali_urlencode_upper() {
{
_url_encode
echo
} | 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'
}
_timestamp() {
_ali_nonce() {
if [ "$ACME_OPENSSL_BIN" ]; then
"$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0
fi
printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32
}
_ali_timestamp() {
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
}
@@ -150,7 +167,7 @@ _check_exist_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&TypeKeyWord=TXT'
query=$query'&Version=2015-01-09'
}
@@ -166,7 +183,7 @@ _add_record_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Type=TXT'
query=$query'&Value='$3
query=$query'&Version=2015-01-09'
@@ -182,7 +199,7 @@ _delete_record_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2015-01-09'
}
@@ -196,7 +213,7 @@ _describe_records_query() {
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2015-01-09'
}
+490
View File
@@ -0,0 +1,490 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_arubabusiness_info='ArubaBusiness
Site: business.aruba.it
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_arubabusiness
Options:
AB_Key Your ArubaBusiness API Key
AB_User Your account user
AB_Pass Your account password
'
#
# A word of warning: as of this writing, api.arubabusiness.it only supports oauth authentication using the "password" grant type.
# If you are REALLY sure you want to use it, it would be wise set up a dedicated technical user without administrative privileges
#
ARUBABUSINESS_API='https://api.arubabusiness.it'
######## Public functions ########
#
# Usage: dns_arubabusiness_add _acme-challenge.www.domain.com aaaabbbbcccc111122223333
#
# Add a new TXT record whose name and value match the given domain and value
#
# Variables
# _full_domain: $1 - the name of the TXT record
# _txt_value: $2 - the value of the TXT record
# _body
# dns_details
# domain_id
# dns_record_id
# response
#
dns_arubabusiness_add() {
_full_domain=$1
_txt_value=$2
if ! _ab_authenticate; then
return 1
fi
if ! _ab_domain_id "$_full_domain"; then
return 1
fi
if _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
# This is very unlikely, but allow the process to use the existing record
_info "A TXT record with name: $_full_domain and value: $_txt_value already exists (id: $dns_record_id)"
return 0
fi
_body="{ \"IdDomain\": $domain_id, \"Type\": \"TXT\", \"Name\": \"$_full_domain\", \"Content\": \"\\\"$_txt_value\\\"\" }"
_debug "Adding TXT record with name: $_full_domain and value: $_txt_value"
if ! _ab_rest POST "api/domains/dns/record" "$_body" || ! _contains "$response" "DomainId"; then
_err "Failed to add TXT record with name: $_full_domain"
return 1
fi
_info "Sleeping 10 seconds to let ArubaBusiness do its magic"
_sleep 10
# Refresh dns details and check that the record was really added
if ! _ab_dns_details "$root_domain"; then
return 1
fi
if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
# This should never happen
_err "The TXT record with name: $_full_domain was not set"
_err "Please check that the dns records are clean"
return 1
fi
_info "Added TXT record with id: $dns_record_id"
return 0
}
#
# Usage: dns_arubabusiness_rm _acme-challenge.www.domain.com aaaabbbbcccc111122223333
#
# Remove the TXT record whose name and value match the given domain and value
#
# Variables
# _full_domain: $1 - the name of the TXT record
# _txt_value: $2 - the value of the TXT record
# dns_details
# dns_record_id
#
dns_arubabusiness_rm() {
_full_domain=$1
_txt_value=$2
if ! _ab_authenticate; then
return 1
fi
if ! _ab_domain_id "$_full_domain"; then
return 1
fi
if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details" || [ -z "$dns_record_id" ]; then
_err "Could not retrieve the record id for: $_full_domain"
return 1
fi
_debug "Deleting TXT record: $dns_record_id"
if ! _ab_rest DELETE "api/domains/dns/record/$dns_record_id" || ! _contains "$response" "DomainId"; then
_err "Failed to delete TXT record: $dns_record_id"
return 1
fi
_info "Deleted TXT record: $dns_record_id"
return 0
}
######## Private functions ########
#
# Usage: _ab_domain_id _acme-challenge.www.domain.com
#
# Split the input domain into subdomain + root domain and get the id of the root domain
#
# Variables
# _full_domain: $1 - the domain whose root needs to be extracted
# _domain_sections
# _current_index
# _candidate_subdomain
# _candidate_domain
# sub_domain
# root_domain
# domain_id
# dns_details: a json containing all dns records registered on the root domain
#
# Example
# _get_root _acme-challenge.www.domain.com
#
# Should return
# sub_domain=_acme-challenge.www
# root_domain=domain.com
# domain_id=123123123123
# dns_details="{JSON_CONTENT}"
#
_ab_domain_id() {
_full_domain=$1
_info "Attempting to retrieve root domain details for: $_full_domain"
_domain_sections=$(_math "$(printf "%s" "$_full_domain" | tr '.' '\n' | wc -l)" + 1)
if [ "$_domain_sections" -lt 1 ]; then
_err "Invalid input $_full_domain"
return 1
fi
_current_index=1
while true; do
_candidate_subdomain=$(if [ "$_current_index" = "1" ]; then printf ""; else printf "%s" "$_full_domain" | cut -d . -f 1-"$(_math "$_current_index" - 1)"; fi)
_candidate_domain=$(printf "%s" "$_full_domain" | cut -d . -f "$_current_index"-"$_domain_sections")
if ! _ab_dns_details "$_candidate_domain"; then
_debug2 "Could not fetch dns details for: $_candidate_domain"
_current_index=$(_math "$_current_index" + 1)
# Fail if there are no candidates left
if [ "$_current_index" -gt "$_domain_sections" ]; then
_err "Could not determine the root domain for: $_full_domain"
return 1
fi
else
sub_domain="$_candidate_subdomain"
root_domain="$_candidate_domain"
# Extract the domain id, which is an integer and contains no commas
domain_id="$(printf "%s" "$dns_details" | _egrep_o '"Id":[^,]*' | _head_n 1 | cut -d : -f 2 | tr -d ' "')"
if [ -z "$domain_id" ]; then
_err "Could not determine the domain id for: $root_domain"
return 1
fi
_debug "Retrieved root domain id: $domain_id"
return 0
fi
done
}
#
# Usage: _ab_dns_record_id _acme-challenge.www.domain.com "aaaabbbbcccc111122223333" "{JSON_CONTENT}"
#
# Extract the record id of the first TXT record whose name and content match the input values
#
# Variables
# _record_name: $1
# _txt_value: $2
# _dns_details: $3 - the json returned by a previous call to '_ab_dns_details() $root_domain'
# _record_ids
# _record_names
# _record_types
# _record_contents
# _record_ids_count
# _record_names_count
# _record_types_count
# _record_contents_count
# _i
# dns_record_id
#
# Notes
# TXT correspond to record type 5
# ArubaBusiness appends a terminating dot (.) to the record name
# The content field may contain the following character sequence: \"
# All record names are always converted to lowercase
#
_ab_dns_record_id() {
_record_name=$1
_txt_value=$2
_dns_details=$3
_record_name_lowercase=$(printf "%s" "$_record_name" | _lower_case)
# Extract the record ids, which are integers and contain no commas, colons or spaces
# The first id is skipped because it refers to the domain id
_record_ids=$(printf "%s" "$_dns_details" | sed 's/"Id":/\n"Id":/g' | _egrep_o '"Id":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
# Extract the record names, which are strings but cannot contain commas, colons, spaces and quotes
# The first name is skipped because it refers to the domain name
_record_names=$(printf "%s" "$_dns_details" | sed 's/"Name":/\n"Name":/g' | _egrep_o '"Name":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' "' | tr '\n' ' ')
# Extract the record types, which are integers (except for the first one) and contain no commas, colons or spaces
# The first type is skipped because it refers to the domain type
_record_types=$(printf "%s" "$_dns_details" | sed 's/"Type":/\n"Type":/g' | _egrep_o '"Type":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
# Extract the record contents, which are strings and may contain no quotes except for TXT records, which must be delimited by two \" literals
# Note: There is no domain related entry here
# Note: A " character is appended at the end of each content to make it easier to process the list later
_record_contents=$(printf "%s" "$_dns_details" | sed 's/"Content":/\n"Content":/g' | sed 's/\\"//g' | _egrep_o '"Content": *"[^"]*"' | cut -d : -f 2- | sed -n 's/"\(.*\)"/\1/p' | tr '\n' '#')
_info "IDS: $_record_ids"
_info "NAMES: $_record_names"
_info "TYPEs: $_record_types"
_info "CONTENTS: $_record_contents"
_record_ids_count=$(printf "%s" "$_record_ids" | tr ' ' '\n' | wc -l)
_record_names_count=$(printf "%s" "$_record_names" | tr ' ' '\n' | wc -l)
_record_types_count=$(printf "%s" "$_record_types" | tr ' ' '\n' | wc -l)
_record_contents_count=$(printf "%s" "$_record_contents" | tr '#' '\n' | wc -l)
_info "Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
if [ "$_record_ids_count" != "$_record_names_count" ] || [ "$_record_ids_count" != "$_record_types_count" ] || [ "$_record_ids_count" != "$_record_contents_count" ]; then
_err "Failed to parse record elements. Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
return 1
fi
_info "Looking for a TXT record matching inputs - name: $_record_name_lowercase value: $_txt_value"
_i=1
while [ "$_i" -le "$_record_ids_count" ]; do
_current_name=$(printf "%s" "$_record_names" | cut -d " " -f "$_i")
_current_type=$(printf "%s" "$_record_types" | cut -d " " -f "$_i")
_current_content=$(printf "%s" "$_record_contents" | cut -d "#" -f "$_i")
if [ "$_record_name_lowercase." = "$_current_name" ] && [ "5" = "$_current_type" ] && [ "$_txt_value" = "$_current_content" ]; then
dns_record_id=$(printf "%s" "$_record_ids" | cut -d " " -f "$_i")
_info "Found matching record with id: $dns_record_id"
return 0
else
_debug2 "Record does not match - type: '$_current_type' name: '$_current_name' value: '$_current_content'; Expected '$_record_name_lowercase.' '5' '$_txt_value'"
fi
_i=$(_math "$_i" + 1)
done
_debug2 "No matching record was found in $_dns_details"
return 1
}
#
# Usage: _ab_dns_details domain.com
#
# Retrieve dns info for the given input domain
#
# Variables
# _domain: $1
# dns_details: the json returned by the call to $ARUBABUSINESS_API/api/domains/dns/$_domain/details (if return status is 0)
# response
#
_ab_dns_details() {
_domain=$1
if ! _ab_rest GET "api/domains/dns/$_domain/details" || ! _contains "$response" "DomainId"; then
return 1
fi
dns_details="$response"
return 0
}
#
# Usage: _ab_authenticate
#
# Read account conf, update domain conf and perform user authentication to acquire an access token
#
# Variables
# AB_Key
# AB_User
# AB_Pass
# AB_Token
#
_ab_authenticate() {
AB_Key="${AB_Key:-$(_readaccountconf_mutable AB_Key)}"
AB_User="${AB_User:-$(_readaccountconf_mutable AB_User)}"
AB_Pass="${AB_Pass:-$(_readaccountconf_mutable AB_Pass)}"
if [ -z "$AB_Key" ] || [ -z "$AB_User" ] || [ -z "$AB_Pass" ]; then
AB_Key=""
AB_User=""
AB_Pass=""
_err "Either the ArubaBusiness API key, the user or the password has not been defined yet."
_err "Please configure them and try again."
return 1
fi
_saveaccountconf_mutable AB_Key "$AB_Key"
_saveaccountconf_mutable AB_User "$AB_User"
_saveaccountconf_mutable AB_Pass "$AB_Pass"
if ! _ab_get_token || [ -z "$AB_Token" ]; then
_err "Failed to acquire an access token"
return 1
fi
return 0
}
#
# Usage: _ab_get_token
#
# Try acquiring a temporary access token. The token should have a 24h lifespan
#
# Variables
# _ab_user_enc
# _ab_pass_enc
# _ab_authdata
# AB_User
# AB_Pass
# AB_Token
# response
# _H2
#
_ab_get_token() {
_ab_user_enc=$(printf "%s" "$AB_User" | _url_encode)
_ab_pass_enc=$(printf "%s" "$AB_Pass" | _url_encode)
_ab_authdata="grant_type=password&username=$_ab_user_enc&password=$_ab_pass_enc"
_H2="Content-Type: application/x-www-form-urlencoded"
if ! _ab_rest POST "auth/token" "$_ab_authdata" || ! _contains "$response" "access_token"; then
_err "Authentication failure"
return 1
fi
AB_Token="$(printf "%s" "$response" | _egrep_o '"access_token":"[^\"]*"' | cut -d : -f 2 | tr -d '"')"
if [ -z "$AB_Token" ]; then
_err "Could not extract access token"
return 1
fi
_debug "Acquired access token"
return 0
}
#
# Usage: _ab_rest POST "example/endpoint" "password=123"
#
# Perform a REST request using the given method, endpoint and data
#
# Variables
# _method: $1 - The http method
# _endpoint: $2 - The api path (relative to $ARUBABUSINESS_API)
# _data: $3 - The body of the request (optional)
# _key_trimmed
# _token_trimmed
# _ret_code
# AB_Key
# AB_Token
# ARUBABUSINESS_API
# _H1
# _H2
# _H3
# _H4
#
_ab_rest() {
_method=$1
_endpoint="$2"
_data="$3"
_key_trimmed=$(printf "%s" "$AB_Key" | tr -d '"')
_token_trimmed=$(printf "%s" "$AB_Token" | tr -d '"')
_H1="Accept: application/json"
if [ -z "$_H2" ]; then
# Default to application/json
_H2="Content-Type: application/json"
fi
if [ "$_key_trimmed" ]; then
_H3="Authorization-Key: $_key_trimmed"
else
_err "Missing Api Key"
_ab_cleanup_headers
return 1
fi
if [ "$_token_trimmed" ]; then
_H4="Authorization: Bearer $_token_trimmed"
else
_debug "No access token set"
fi
if [ "$_method" != "GET" ]; then
response="$(_post "$_data" "$ARUBABUSINESS_API/$_endpoint" "" "$_method")"
else
response="$(_get "$ARUBABUSINESS_API/$_endpoint")"
fi
_ret_code=$?
if [ "$_ret_code" = "0" ] && _ab_call_is_success; then
# Normalize the json response
response="$(printf "%s" "$response" | _normalizeJson)"
_ret_code=0
else
_err "Failed to call endpoint: $_endpoint"
_ret_code=1
fi
_ab_cleanup_headers
return $_ret_code
}
#
# Usage: _ab_cleanup_headers
#
# Unset header variables to avoid interfering with other calls
#
# Variables
# _H1
# _H2
# _H3
# _H4
#
_ab_cleanup_headers() {
# Cleanup request headers
unset _H1 _H2 _H3 _H4 _H5
# Cleanup response headers
if [ -f "$HTTP_HEADER" ]; then
: >"$HTTP_HEADER"
fi
}
#
# Usage: _ab_call_is_success
#
# Check whether a call's response http status is one of 200, 201, 202 or 204 (other 2xx are not handled)
#
# Variables
# _status
# _http_status
# _success_http_codes
# HTTP_HEADER
#
_ab_call_is_success() {
_success_http_codes="200 201 202 204"
if [ -f "$HTTP_HEADER" ]; then
_http_status=$(_egrep_o "^HTTP[\/0-9. ]*" <"$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)
for _status in $_success_http_codes; do
if [ "$_status" = "$_http_status" ]; then
return 0
fi
done
fi
return 1
}
+10 -1
View File
@@ -139,12 +139,21 @@ _get_autodns_zone() {
return 1
}
# Escape the XML special characters (& < > ' ") so that credentials
# containing them do not break the request document (issue 5317).
_autodns_xml_encode() {
sed "s/&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g;s/'/\&apos;/g;s/\"/\&quot;/g"
}
_build_request_auth_xml() {
_autodns_user_xml="$(printf "%s" "$AUTODNS_USER" | _autodns_xml_encode)"
_autodns_password_xml="$(printf "%s" "$AUTODNS_PASSWORD" | _autodns_xml_encode)"
_autodns_context_xml="$(printf "%s" "$AUTODNS_CONTEXT" | _autodns_xml_encode)"
printf "<auth>
<user>%s</user>
<password>%s</password>
<context>%s</context>
</auth>" "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT"
</auth>" "$_autodns_user_xml" "$_autodns_password_xml" "$_autodns_context_xml"
}
# Arguments:
+3 -2
View File
@@ -11,7 +11,8 @@ Options:
# All `_sleep` commands are included to avoid Route53 throttling, see
# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
AWS_HOST="route53.amazonaws.com"
# Updated from "route53.amazonaws.com"
AWS_HOST="route53.global.api.aws"
AWS_URL="https://$AWS_HOST"
AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
@@ -161,7 +162,7 @@ _get_root() {
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g')
_debug "Checking domain: $h"
if [ -z "$h" ]; then
_error "invalid domain"
_err "invalid domain"
return 1
fi
+11 -2
View File
@@ -340,8 +340,17 @@ _azure_getaccess_token() {
if [ "$managedIdentity" = true ]; then
# https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
export _H1="Metadata: true"
response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)"
if [ -n "$IDENTITY_ENDPOINT" ]; then
# Some Azure environments may set IDENTITY_ENDPOINT (formerly MSI_ENDPOINT) to have an alternative metadata endpoint
url="$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/"
headers="X-IDENTITY-HEADER: $IDENTITY_HEADER"
else
url="http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
headers="Metadata: true"
fi
export _H1="$headers"
response="$(_get "$url")"
response="$(echo "$response" | _normalizeJson)"
accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+775
View File
@@ -0,0 +1,775 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
# Global variables for returning results (avoid stdout pollution from logging)
_BAIDU_FIND_RESULT=""
_BAIDU_BCE_AUTH_RESULT=""
: "${BAIDU_LOG_LEVEL:=2}"
_baidu_log_ts() {
date
}
_baidu_log_ge() {
_want="$1"
[ "${BAIDU_LOG_LEVEL:-0}" -ge "$_want" ]
}
_baidu_log() {
_lvl="$1"
_tag="$2"
_msg="$3"
if [ "$_lvl" = "0" ] || _baidu_log_ge "$_lvl"; then
printf -- "[%s] %s %s\n" "$(_baidu_log_ts)" "$_tag" "$_msg"
fi
}
_baidu_err() {
_baidu_log 0 "baidu_bcd.err" "$1"
return 1
}
_baidu_info() {
_baidu_log 1 "baidu_bcd.info" "$1"
return 0
}
_baidu_debug() {
_baidu_log 2 "$1" "$2"
return 0
}
dns_baidu_info='Baidu Cloud BCD DNS
Site: cloud.baidu.com
Docs: https://cloud.baidu.com/doc/BCD/
Signature: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
Options:
Baidu_AK AccessKeyId
Baidu_SK SecretAccessKey
OptionsAlt:
Baidu_BCD_Host API host, default: bcd.baidubce.com
Baidu_DNS_Host New DNS API host, default: dns.baidubce.com
Baidu_API_Preference Engine preference, default: auto
Baidu_BCD_Version API version number, default: 1
Baidu_BCD_Expire Signature expiration seconds, default: 3600
Baidu_View Resolve view, default: DEFAULT
Baidu_Line New DNS line, default: default
Baidu_TTL Resolve ttl seconds, default: 300
Baidu_RM_Max Max records to delete in one run, default: 20
'
BAIDU_BCD_DEFAULT_HOST="bcd.baidubce.com"
BAIDU_DNS_DEFAULT_HOST="dns.baidubce.com"
# --- Public API ---
dns_baidu_add() {
fulldomain=$(_idn "$1")
txtvalue=$2
if ! _baidu_run_with_fallback "add" "$fulldomain" "$txtvalue"; then
_baidu_err "all baidu api engines failed for add: $fulldomain"
return 1
fi
return 0
}
dns_baidu_rm() {
fulldomain=$(_idn "$1")
txtvalue=$2
if ! _baidu_run_with_fallback "rm" "$fulldomain" "$txtvalue"; then
_baidu_err "all baidu api engines failed for delete: $fulldomain"
return 1
fi
return 0
}
_baidu_run_with_fallback() {
_action="$1"
_fulldomain="$2"
_txtvalue="$3"
if ! _baidu_load_credentials; then
_baidu_err "baidu_load_credentials failed"
return 1
fi
for _baidu_api_engine in $(_baidu_engine_order); do
if ! _baidu_prepare_record "$_fulldomain"; then
_baidu_info "prepare failed for engine: $_baidu_api_engine"
continue
fi
if [ "$_action" = "add" ]; then
if _baidu_add_record "$_txtvalue"; then
return 0
fi
else
if _baidu_rm_record "$_txtvalue"; then
return 0
fi
fi
_baidu_info "engine failed, try next if available: $_baidu_api_engine"
done
return 1
}
_baidu_engine_order() {
_pref="$(_lower_case "$(_baidu_trim_ws "${Baidu_API_Preference:-auto}")")"
case "$_pref" in
legacy)
printf "%s" "legacy new"
;;
new)
printf "%s" "new legacy"
;;
*)
printf "%s" "new legacy"
;;
esac
}
_baidu_add_record() {
_txtvalue="$1"
if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
_baidu_err "baidu_find_record_ids failed for add: $_record_domain.$_zone_name"
return 1
fi
_existing_ids="$_BAIDU_FIND_RESULT"
if [ "$_existing_ids" ]; then
_baidu_info "txt exists, skip add: $_record_domain.$_zone_name"
return 0
fi
_ttl="${Baidu_TTL:-300}"
_ttl="$(_baidu_trim_ws "$_ttl")"
case "$_ttl" in
"" | *[!0-9]*)
_ttl="300"
;;
esac
txtvalue="$(_baidu_trim_ws "$_txtvalue")"
_record_domain="$(_baidu_trim_ws "$_record_domain")"
_zone_name="$(_baidu_trim_ws "$_zone_name")"
if [ "$_baidu_api_engine" = "new" ]; then
_line="$(_baidu_trim_ws "${Baidu_Line:-default}")"
if [ -z "$_line" ]; then
_line="default"
fi
_body="$(_baidu_payload_add_txt_dns "$_record_domain" "$txtvalue" "$_ttl" "$_line")"
if ! _baidu_dns_call "POST" "/v1/dns/zone/${_zone_name}/record" "$_body"; then
_baidu_err "baidu_dns_call failed: add record"
return 1
fi
else
_view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")"
_body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")"
if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then
_baidu_err "baidu_bcd_post failed: add record"
return 1
fi
fi
if _baidu_is_api_error "$response"; then
_baidu_err "$response"
return 1
fi
return 0
}
_baidu_rm_record() {
_txtvalue="$1"
if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
_baidu_err "baidu_find_record_ids failed for delete: $_record_domain.$_zone_name"
return 1
fi
_ids="$_BAIDU_FIND_RESULT"
if [ -z "$_ids" ]; then
_baidu_info "no matching txt to delete: $_record_domain.$_zone_name"
return 0
fi
_rm_max="${Baidu_RM_Max:-20}"
_rm_max="$(_baidu_trim_ws "$_rm_max")"
case "$_rm_max" in
"" | *[!0-9]*)
_rm_max="20"
;;
esac
_rm_cnt="$(printf "%s\n" "$_ids" | sed '/^$/d' | wc -l | tr -d ' ')"
if [ "$_rm_cnt" ] && [ "$_rm_cnt" -gt "$_rm_max" ]; then
_baidu_err "Refusing to delete $_rm_cnt records (limit: $_rm_max)"
return 1
fi
for _rid in $_ids; do
if [ "$_baidu_api_engine" = "new" ]; then
if ! _baidu_dns_call "DELETE" "/v1/dns/zone/${_zone_name}/record/${_rid}" ""; then
_baidu_err "baidu_dns_call failed: delete recordId=$_rid"
return 1
fi
else
_body="$(_baidu_payload_delete "$_zone_name" "$_rid")"
if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then
_baidu_err "baidu_bcd_post failed: delete recordId=$_rid"
return 1
fi
if _baidu_is_api_error "$response"; then
_baidu_err "$response"
return 1
fi
fi
done
if [ "$_baidu_api_engine" = "legacy" ]; then
if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
_baidu_err "baidu_find_record_ids failed for delete verify: $_record_domain.$_zone_name"
return 1
fi
_left_ids="$_BAIDU_FIND_RESULT"
if [ -z "$_left_ids" ]; then
return 0
fi
if [ -n "$_left_ids" ]; then
_baidu_err "delete verification failed: $_record_domain.$_zone_name still has TXT records"
return 1
fi
fi
return 0
}
# --- Config / Record Context ---
_baidu_load_credentials() {
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
Baidu_AK="$(_baidu_trim_ws "$Baidu_AK")"
Baidu_SK="$(_baidu_trim_ws "$Baidu_SK")"
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
_baidu_err "Baidu_AK and Baidu_SK are required"
return 1
fi
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
BAIDU_BCD_HOST="${Baidu_BCD_Host:-$BAIDU_BCD_DEFAULT_HOST}"
BAIDU_DNS_HOST="${Baidu_DNS_Host:-$BAIDU_DNS_DEFAULT_HOST}"
BAIDU_BCD_VERSION="${Baidu_BCD_Version:-1}"
return 0
}
_baidu_prepare_record() {
_fulldomain="$1"
if [ "$_baidu_api_engine" = "new" ]; then
if ! _baidu_get_root_dns "$_fulldomain"; then
_baidu_err "Could not find zone by new dns api for $_fulldomain"
return 1
fi
else
if ! _baidu_get_root "$_fulldomain"; then
_baidu_err "Could not find zone by legacy bcd api for $_fulldomain"
return 1
fi
fi
_record_domain="$_sub_domain"
_zone_name="$_domain"
return 0
}
# --- Zone / Records ---
_baidu_get_root() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
_baidu_err "invalid domain: $domain"
return 1
fi
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$h" 1 1)"; then
_baidu_err "baidu_bcd_post failed: list zones"
return 1
fi
if ! _baidu_is_api_error "$response" && (_contains "$response" "\"totalCount\"" || _contains "$response" "\"result\""); then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
if [ "$_sub_domain" = "$_domain" ]; then
_sub_domain="@"
fi
_baidu_info "zone matched: $_domain (host: $_sub_domain)"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
}
_baidu_get_root_dns() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
_baidu_err "invalid domain: $domain"
return 1
fi
if ! _baidu_dns_call "GET" "/v1/dns/zone/${h}/record" ""; then
_baidu_info "baidu_dns_call failed: list zones"
elif ! _baidu_is_api_error "$response" && (_contains "$response" "\"records\"" || _contains "$response" "\"maxKeys\""); then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
if [ "$_sub_domain" = "$_domain" ]; then
_sub_domain="@"
fi
_baidu_info "zone matched by dns api: $_domain (host: $_sub_domain)"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
}
_baidu_find_record_ids_current() {
if [ "$_baidu_api_engine" = "new" ]; then
_baidu_find_record_ids_dns "$@"
else
_baidu_find_record_ids "$@"
fi
}
_baidu_find_record_ids() {
_zone_name="$1"
_record_domain="$2"
_rdtype="$3"
_rdata="$4"
# Reset global result variable
_BAIDU_FIND_RESULT=""
_zone_name_e="$(_baidu_json_escape "$_zone_name")"
_record_domain_e="$(_baidu_json_escape "$_record_domain")"
_rdtype_e="$(_baidu_json_escape "$_rdtype")"
_rdata_e="$(_baidu_json_escape "$_rdata")"
_page=1
_page_size=100
_ids=""
_max_page=""
while true; do
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$_zone_name" "$_page" "$_page_size")"; then
_baidu_err "baidu_bcd_post failed: list records"
return 1
fi
if _baidu_is_api_error "$response"; then
_baidu_err "baidu_bcd error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")"
return 1
fi
_normalized="$(
printf "%s" "$response" | _normalizeJson
)"
if [ -z "$_max_page" ]; then
_total="$(_baidu_parse_totalcount "$_normalized")"
_max_page="$(_baidu_calc_max_page "$_total" "$_page_size")"
fi
_records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
while IFS= read -r _line; do
_id="$(_baidu_match_record_id "$_line" "$_record_domain_e" "$_rdtype_e" "$_rdata_e")"
if [ "$_id" ]; then
_ids="$_ids $_id"
fi
done <<EOF
$_records
EOF
if [ "$_page" -ge "$_max_page" ]; then
break
fi
_page=$(_math "$_page" + 1)
done
# Store result in global variable instead of stdout
_BAIDU_FIND_RESULT="$_ids"
}
_baidu_find_record_ids_dns() {
_zone_name="$1"
_record_domain="$2"
_rdtype="$3"
_rdata="$4"
_BAIDU_FIND_RESULT=""
if ! _baidu_dns_call "GET" "/v1/dns/zone/${_zone_name}/record" ""; then
_baidu_err "baidu_dns_call failed: list records"
return 1
fi
if _baidu_is_api_error "$response"; then
_baidu_err "baidu_dns error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")"
return 1
fi
_normalized="$(printf "%s" "$response" | _normalizeJson)"
_records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
_ids=""
while IFS= read -r _line; do
_id="$(_baidu_match_record_id_dns "$_line" "$_record_domain" "$_rdtype" "$_rdata")"
if [ "$_id" ]; then
_ids="$_ids $_id"
fi
done <<EOF
$_records
EOF
_BAIDU_FIND_RESULT="$_ids"
}
# --- HTTP ---
_baidu_bcd_post() {
_api_path="$1"
_payload="$2"
# BCD API requires JSON payload. Some call sites build fragments; normalize defensively.
_payload="$(_baidu_normalize_payload "$_payload")"
_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
_expire="${Baidu_BCD_Expire:-3600}"
_content_type="application/json; charset=utf-8"
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
_uri="/v${BAIDU_BCD_VERSION}${_api_path}"
if ! _baidu_bce_auth "POST" "$_uri" "" "$BAIDU_BCD_HOST" "$_ts" "$_expire" "$_content_type" "$_payload_hash"; then
_baidu_err "baidu_bcd auth failed"
return 1
fi
_auth="$_BAIDU_BCE_AUTH_RESULT"
if [ -z "$_auth" ]; then
_baidu_err "baidu_bcd auth failed"
return 1
fi
_url="https://${BAIDU_BCD_HOST}${_uri}"
_signed_headers_dbg="$(printf "%s" "$_auth" | cut -d / -f 5)"
_baidu_info "POST ${_uri}"
_baidu_info "signedHeaders: $_signed_headers_dbg"
_baidu_info "payload_sha256: $_payload_hash"
_baidu_debug "baidu_bcd.http.payload" "$(_baidu_dbg_trim "$(_baidu_redact_txt "$_payload")")"
_H1="Authorization: $_auth"
_H2="x-bce-date: $_ts"
_H3="x-bce-content-sha256: $_payload_hash"
_H4="Host: $BAIDU_BCD_HOST"
_H5=""
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
_ret="$?"
_baidu_info "ret: $_ret"
_req_id="$(_baidu_json_get_str "$response" "requestId")"
_code="$(_baidu_json_get_str "$response" "code")"
_msg="$(_baidu_json_get_str "$response" "message")"
_baidu_info "response: requestId=${_req_id:-"-"} code=${_code:-"-"} message=$(_baidu_dbg_trim "${_msg:-"-"}")"
if [ "$_ret" != "0" ]; then
_baidu_err "baidu_bcd_post failed: $_uri"
return 1
fi
return 0
}
_baidu_dns_call() {
_method="$1"
_uri="$2"
_payload="$3"
_content_type="application/json"
_attempt=1
_max_attempts=3
while [ "$_attempt" -le "$_max_attempts" ]; do
_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
if ! _baidu_bce_auth "$_method" "$_uri" "" "$BAIDU_DNS_HOST" "$_ts" "${Baidu_BCD_Expire:-3600}" "$_content_type" "$_payload_hash"; then
_baidu_err "baidu_dns auth failed"
return 1
fi
_auth="$_BAIDU_BCE_AUTH_RESULT"
_url="https://${BAIDU_DNS_HOST}${_uri}"
# Route through acme.sh's _get/_post (they honor _H1.._H5); no raw curl.
_H1="Authorization: $_auth"
_H2="x-bce-date: $_ts"
_H3="x-bce-content-sha256: $_payload_hash"
_H4="Host: $BAIDU_DNS_HOST"
_H5="Content-Type: $_content_type"
if [ "$_method" = "GET" ]; then
response="$(_get "$_url")"
elif [ "$_method" = "DELETE" ]; then
response="$(_post "" "$_url" "" "DELETE")"
else
response="$(_post "$_payload" "$_url")"
fi
_ret="$?"
_baidu_info "${_method} ${_uri} ret=${_ret}"
# Baidu may return a business error (Exception / 平台服务繁忙) inside HTTP 200.
if [ "$_ret" = "0" ] && ! _contains "$response" "\"code\":\"Exception\"" && ! _contains "$response" "平台服务繁忙"; then
return 0
fi
if [ "$_attempt" -lt "$_max_attempts" ]; then
sleep 2
fi
_attempt=$(_math "$_attempt" + 1)
done
return 1
}
# --- Auth / Signing ---
_baidu_bce_auth() {
# Signing algorithm (bce-auth-v1):
# - SigningKey = HMAC-SHA256-HEX(sk, authStringPrefix)
# - Signature = HMAC-SHA256-HEX(SigningKey, CanonicalRequest)
# Reference: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
_method="$1"
_uri="$2"
_query="$3"
_host="$4"
_ts="$5"
_expire="$6"
_ct="$7"
_payload_hash="$8"
_BAIDU_BCE_AUTH_RESULT=""
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
_canonical_uri="$(_baidu_bce_encode_path "$_uri")"
_canonical_query=""
_host_v="$(_baidu_trim_ws "$_host")"
_date_v="$(_baidu_trim_ws "$_ts")"
_ct_v="$(_baidu_trim_ws "$_ct")"
_host_e="$(printf "%s" "$_host_v" | _url_encode upper-hex)"
_date_e="$(printf "%s" "$_date_v" | _url_encode upper-hex)"
_ct_e="$(printf "%s" "$_ct_v" | _url_encode upper-hex)"
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
_canonical_headers="content-type:${_ct_e}
host:${_host_e}
x-bce-content-sha256:${_hash_e}
x-bce-date:${_date_e}"
_canonical_request="${_method}
${_canonical_uri}
${_canonical_query}
${_canonical_headers}"
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
_signing_key="$(_baidu_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
_signature="$(_baidu_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
_baidu_debug "baidu_bcd.auth" "bce_auth"
_baidu_debug "baidu_bcd.auth.auth_prefix" "bce-auth-v1/[ak]/${_ts}/${_expire}/$_signed_headers/[signature]"
_baidu_debug "baidu_bcd.auth.canonical_request_l" "$(printf "%s" "$_canonical_request" | sed -n 'l')"
_baidu_debug "baidu_bcd.auth.signature" "$(printf "%s" "$_signature" | cut -c 1-16)..."
_BAIDU_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
return 0
}
_baidu_bce_encode_path() {
_p="$1"
_out=""
if [ "${_p#"/"}" != "$_p" ]; then
_out="/"
fi
_rest="${_p#/}"
while [ -n "$_rest" ]; do
_seg="${_rest%%/*}"
if [ "$_seg" ]; then
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
else
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
fi
fi
if [ "${_rest#*/}" = "$_rest" ]; then
break
fi
_rest="${_rest#*/}"
done
if [ -z "$_out" ]; then
_out="/"
fi
printf "%s" "$_out"
}
# --- Utils ---
_baidu_trim() {
printf "%s" "$1" | sed 's/^ *//;s/ *$//'
}
_baidu_trim_ws() {
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
}
_baidu_dbg_trim() {
printf "%s" "$1" | tr '\r\n' ' ' | cut -c 1-800
}
_baidu_is_api_error() {
_contains "$1" "\"code\"" && _contains "$1" "\"message\""
}
_baidu_normalize_payload() {
_p="$(_baidu_trim "$(printf "%s" "$1" | tr -d '\r')")"
if [ -z "$_p" ]; then
printf "%s" ""
return 0
fi
case "$_p" in
\{* | \[*)
printf "%s" "$_p"
;;
*)
printf "%s" "{$_p}"
;;
esac
}
_baidu_redact_txt() {
printf "%s" "$1" | sed 's/"rdata" *: *"[^"]*"/"rdata":"[redacted]"/g'
}
_baidu_json_get_str() {
_json="$1"
_key="$2"
printf "%s" "$_json" | _normalizeJson | sed -n "s/.*\"${_key}\" *: *\"\\([^\"]*\\)\".*/\\1/p" | _head_n 1
}
_baidu_json_escape() {
_s="$1"
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
printf "%s" "$_s" |
sed 's/\\/\\\\/g; s/ /\\t/g' |
_baidu_json_encode
}
_baidu_json_encode() {
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
_baidu_payload_list() {
_domain="$(_baidu_json_escape "$1")"
_pageNo="$2"
_pageSize="$3"
printf "%s" "{\"domain\":\"${_domain}\",\"pageNo\":${_pageNo},\"pageSize\":${_pageSize}}"
}
_baidu_payload_add_txt() {
_zoneName="$(_baidu_json_escape "$1")"
_domain="$(_baidu_json_escape "$2")"
_rdata="$(_baidu_json_escape "$3")"
_ttl="$4"
_view="$(_baidu_json_escape "$5")"
printf "%s" "{\"domain\":\"${_domain}\",\"view\":\"${_view}\",\"rdType\":\"TXT\",\"ttl\":${_ttl},\"rdata\":\"${_rdata}\",\"zoneName\":\"${_zoneName}\"}"
}
_baidu_payload_add_txt_dns() {
_rr="$(printf "%s" "$1" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
_value="$(printf "%s" "$2" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
_ttl="$3"
_line="$(printf "%s" "$4" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
printf "%s" "{\"rr\":\"${_rr}\",\"type\":\"TXT\",\"value\":\"${_value}\",\"ttl\":${_ttl},\"line\":\"${_line}\",\"description\":\"acme.sh\"}"
}
_baidu_payload_delete() {
_zoneName="$(_baidu_json_escape "$1")"
_recordId="$2"
printf "%s" "{\"zoneName\":\"${_zoneName}\",\"recordId\":${_recordId}}"
}
_baidu_parse_totalcount() {
_json="$1"
printf "%s" "$_json" | _egrep_o "\"totalCount\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
}
_baidu_calc_max_page() {
_total="$1"
_page_size="$2"
if [ -z "$_total" ]; then
printf "%s" "1"
return 0
fi
_max=$(((_total + _page_size - 1) / _page_size))
if [ "$_max" -lt 1 ]; then
_max=1
fi
printf "%s" "$_max"
}
_baidu_match_record_id() {
_line="$1"
_domain_e="$2"
_rdtype_e="$3"
_rdata_e="$4"
if ! _contains "$_line" "\"recordId\"" || (! _contains "$_line" "\"domain\":\"$_domain_e\"" && ! _contains "$_line" "\"domain\":\"${_domain_e}.\""); then
return 0
fi
if ! _contains "$_line" "\"rdtype\":\"$_rdtype_e\"" && ! _contains "$_line" "\"rdType\":\"$_rdtype_e\""; then
return 0
fi
if [ "$_rdata_e" ] && ! _contains "$_line" "\"rdata\":\"$_rdata_e\""; then
return 0
fi
printf "%s" "$_line" | _egrep_o "\"recordId\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
}
_baidu_match_record_id_dns() {
_line="$1"
_rr="$(printf "%s" "$2" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
_type="$(printf "%s" "$3" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
_value="$(printf "%s" "$4" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
case "$_line" in
*"\"rr\":\"${_rr}\""*"\"type\":\"${_type}\""*"\"value\":\"${_value}\""*)
printf "%s" "$_line" | sed -n 's/.*"id":"\{0,1\}\([^",}]*\)"\{0,1\}.*/\1/p' | _head_n 1
;;
esac
}
_baidu_hmac_sha256_hexkey() {
_key_hex="$1"
_msg="$2"
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
}
+1 -1
View File
@@ -7,7 +7,7 @@ Options:
BEGET_User API user
BEGET_Password API password
Issues: github.com/acmesh-official/acme.sh/issues/6200
Author: ARNik arnik@arnik.ru
Author: ARNik <arnik@arnik.ru>
'
Beget_Api="https://api.beget.com/api"
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_bh_info='Best-Hosting.cz
Site: best-hosting.cz
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bh
Options:
BH_API_USER API User identifier.
BH_API_KEY API Secret key.
Issues: github.com/acmesh-official/acme.sh/issues/6854
Author: @heximcz
'
BH_Api="https://best-hosting.cz/api/v1"
######## Public functions #####################
# Usage: dns_bh_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_bh_add() {
fulldomain=$1
txtvalue=$2
# --- 1. Credentials ---
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
BH_API_USER=""
BH_API_KEY=""
_err "You must specify BH_API_USER and BH_API_KEY."
return 1
fi
_saveaccountconf_mutable BH_API_USER "$BH_API_USER"
_saveaccountconf_mutable BH_API_KEY "$BH_API_KEY"
# --- 2. Add TXT record ---
_info "Adding TXT record for $fulldomain"
json_payload="{\"fulldomain\":\"$fulldomain\",\"txtvalue\":\"$txtvalue\"}"
if ! _bh_rest POST "dns" "$json_payload"; then
_err "Failed to add DNS record."
return 1
fi
_norm_add=$(printf "%s" "$response" | tr -d '[:space:]')
if ! _contains "$_norm_add" '"status":"success"'; then
_err "API error: $response"
return 1
fi
record_id=$(printf "%s" "$_norm_add" | _egrep_o '"id":[0-9]+' | cut -d':' -f2)
_debug record_id "$record_id"
if [ -z "$record_id" ]; then
_err "Could not parse record ID from response."
return 1
fi
# Sanitize key — replace dots and hyphens with underscores
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
# Wildcard support: store space-separated list of IDs
# First call stores "111", second call stores "111 222"
_existing_ids=$(_readdomainconf "$_conf_key")
if [ -z "$_existing_ids" ]; then
_savedomainconf "$_conf_key" "$record_id"
else
_savedomainconf "$_conf_key" "$_existing_ids $record_id"
fi
_info "DNS TXT record added successfully."
return 0
}
# Usage: dns_bh_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_bh_rm() {
fulldomain=$1
txtvalue=$2
# --- 1. Credentials ---
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
BH_API_USER=""
BH_API_KEY=""
_err "You must specify BH_API_USER and BH_API_KEY."
return 1
fi
# Sanitize key — same as in add
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
# --- 2. Load stored record ID(s) ---
_existing_ids=$(_readdomainconf "$_conf_key")
_debug _existing_ids "$_existing_ids"
if [ -z "$_existing_ids" ]; then
_err "Could not find record ID for $fulldomain."
return 1
fi
record_id=""
_remaining_ids=""
# Find the record ID that matches both the name and txtvalue
for _id in $_existing_ids; do
if ! _bh_rest GET "dns/$_id"; then
_debug "Failed to query record id $_id, skipping."
# Keep it in the list so a later run can try again
if [ -z "$_remaining_ids" ]; then
_remaining_ids="$_id"
else
_remaining_ids="$_remaining_ids $_id"
fi
continue
fi
_match_name=0
_match_content=0
_norm_response=$(printf "%s" "$response" | tr -d '[:space:]')
case "$_norm_response" in
*"\"name\":\"$fulldomain\""*)
_match_name=1
;;
esac
case "$_norm_response" in
*"\"content\":\"$txtvalue\""*)
_match_content=1
;;
esac
if [ "$_match_name" -eq 1 ] && [ "$_match_content" -eq 1 ]; then
record_id="$_id"
_debug "Matched record id" "$record_id"
# Do not add this ID to _remaining_ids; it will be deleted
continue
fi
# Not a match — keep ID for potential future cleanups
if [ -z "$_remaining_ids" ]; then
_remaining_ids="$_id"
else
_remaining_ids="$_remaining_ids $_id"
fi
done
if [ -z "$record_id" ]; then
_err "Could not find matching TXT record for $fulldomain with the given value."
return 1
fi
# --- 3. Delete record ---
_info "Removing TXT record for $fulldomain"
if ! _bh_rest DELETE "dns/$record_id"; then
_err "Failed to remove DNS record."
return 1
fi
# Update stored list — remove used ID
if [ -z "$_remaining_ids" ]; then
_cleardomainconf "$_conf_key"
else
_savedomainconf "$_conf_key" "$_remaining_ids"
fi
_info "DNS TXT record removed successfully."
return 0
}
#################### Private functions #####################
_bh_rest() {
m="$1"
ep="$2"
data="$3"
_debug "$ep"
_credentials="$(printf "%s:%s" "$BH_API_USER" "$BH_API_KEY" | _base64)"
export _H1="Authorization: Basic $_credentials"
export _H2="Content-Type: application/json"
export _H3="Accept: application/json"
if [ "$m" = "GET" ]; then
response="$(_get "$BH_Api/$ep")"
else
_debug data "$data"
response="$(_post "$data" "$BH_Api/$ep" "" "$m")"
fi
if [ "$?" != "0" ]; then
_err "Error calling $m $BH_Api/$ep"
return 1
fi
_debug2 response "$response"
return 0
}
+373
View File
@@ -0,0 +1,373 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_bhosted_info='bHosted.nl DNS API
Site: bHosted.nl
Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bhosted
Options:
BHOSTED_Username API username
BHOSTED_Password API password (MD5 hash like bHosted web services example)
BHOSTED_TTL TTL for TXT record (default: 300)
BHOSTED_SLD Optional override (useful for multi-part TLDs like co.uk)
BHOSTED_TLD Optional override (useful for multi-part TLDs like co.uk)
Notes:
- Plugin uses addrecord + delrecord for DNS-01 challenge
- Record ID is retrieved from addrecord XML response and cached for cleanup
'
BHOSTED_API_ROOT="https://webservices.bhosted.com/dns"
############ Public functions #####################
# Usage: dns_bhosted_add _acme-challenge.www.example.com "txt-value"
dns_bhosted_add() {
fulldomain="$1"
txtvalue="$2"
_debug "fulldomain" "$fulldomain"
_debug "txtvalue" "$txtvalue"
_bhosted_load_credentials || return 1
_bhosted_get_root "$fulldomain" || return 1
_info "Adding TXT record: ${_bhosted_name}.${_domain}"
BHOSTED_TTL="${BHOSTED_TTL:-$(_readaccountconf_mutable BHOSTED_TTL)}"
BHOSTED_TTL="${BHOSTED_TTL:-300}"
_saveaccountconf_mutable BHOSTED_TTL "$BHOSTED_TTL"
_bhosted_api_add_txt "$_bhosted_sld" "$_bhosted_tld" "$_bhosted_name" "$txtvalue" "$BHOSTED_TTL" || return 1
# Extract and cache record id in-memory for cleanup in this run
_rec_id="$(_bhosted_extract_id "$response")"
if [ -n "$_rec_id" ]; then
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
_debug "_hash" "$_hash"
_debug "_rec_id" "$_rec_id"
_bhosted_mem_set_id "$_hash" "$_rec_id"
else
_err "TXT record added but no record id found in response."
_err "Cleanup may fail unless bHosted addrecord returns <id>...</id>."
_debug2 "add response" "$response"
return 1
fi
return 0
}
# Usage: dns_bhosted_rm _acme-challenge.www.example.com "txt-value"
dns_bhosted_rm() {
fulldomain="$1"
txtvalue="$2"
_debug "fulldomain" "$fulldomain"
_debug "txtvalue" "$txtvalue"
_bhosted_load_credentials || return 1
_bhosted_get_root "$fulldomain" || return 1
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
_rec_id="$(_bhosted_mem_get_id "$_hash")"
if [ -z "$_rec_id" ]; then
_err "No cached bHosted record id found for cleanup."
_err "Please delete TXT manually in bHosted DNS for: ${_bhosted_name}.${_domain}"
return 1
fi
_info "Removing TXT record id=${_rec_id}: ${_bhosted_name}.${_domain}"
_bhosted_api_del_record "$_bhosted_sld" "$_bhosted_tld" "$_rec_id" || return 1
return 0
}
######## Private functions #####################
_bhosted_load_credentials() {
BHOSTED_Username="${BHOSTED_Username:-$(_readaccountconf_mutable BHOSTED_Username)}"
BHOSTED_Password="${BHOSTED_Password:-$(_readaccountconf_mutable BHOSTED_Password)}"
if [ -z "$BHOSTED_Username" ] || [ -z "$BHOSTED_Password" ]; then
BHOSTED_Username=""
BHOSTED_Password=""
_err "You didn't specify bHosted credentials."
_err "Please export BHOSTED_Username and BHOSTED_Password (MD5 hash)."
return 1
fi
_saveaccountconf_mutable BHOSTED_Username "$BHOSTED_Username"
_saveaccountconf_mutable BHOSTED_Password "$BHOSTED_Password"
return 0
}
# Determine root zone and host part
# Supports simple domains automatically (example.com, example.nl)
# For multi-part TLDs (example.co.uk), set:
# BHOSTED_SLD=example
# BHOSTED_TLD=co.uk
_bhosted_get_root() {
domain="$1"
BHOSTED_SLD="${BHOSTED_SLD:-$(_readdomainconf BHOSTED_SLD)}"
BHOSTED_TLD="${BHOSTED_TLD:-$(_readdomainconf BHOSTED_TLD)}"
if [ -n "$BHOSTED_SLD" ] && [ -n "$BHOSTED_TLD" ]; then
_savedomainconf BHOSTED_SLD "$BHOSTED_SLD"
_savedomainconf BHOSTED_TLD "$BHOSTED_TLD"
_domain="${BHOSTED_SLD}.${BHOSTED_TLD}"
case "$domain" in
*."$_domain") ;;
"$_domain") ;;
*)
_err "BHOSTED_SLD/BHOSTED_TLD do not match requested domain: $domain"
return 1
;;
esac
_bhosted_sld="$BHOSTED_SLD"
_bhosted_tld="$BHOSTED_TLD"
_bhosted_name="${domain%."$_domain"}"
if [ "$_bhosted_name" = "$domain" ]; then
_bhosted_name=""
fi
[ -n "$_bhosted_name" ] || _bhosted_name="@"
_debug "_domain" "$_domain"
_debug "_bhosted_sld" "$_bhosted_sld"
_debug "_bhosted_tld" "$_bhosted_tld"
_debug "_bhosted_name" "$_bhosted_name"
return 0
fi
# Auto-parse: assume last label = tld, label before = sld
# Works for .nl / .com / .org etc.
_bhosted_tld="$(printf "%s" "$domain" | awk -F. '{print $NF}')"
_bhosted_sld="$(printf "%s" "$domain" | awk -F. '{print $(NF-1)}')"
if [ -z "$_bhosted_sld" ] || [ -z "$_bhosted_tld" ]; then
_err "Could not parse SLD/TLD from domain: $domain"
return 1
fi
_domain="${_bhosted_sld}.${_bhosted_tld}"
_bhosted_name="${domain%."$_domain"}"
if [ "$_bhosted_name" = "$domain" ]; then
_bhosted_name=""
fi
[ -n "$_bhosted_name" ] || _bhosted_name="@"
_debug "_domain" "$_domain"
_debug "_bhosted_sld" "$_bhosted_sld"
_debug "_bhosted_tld" "$_bhosted_tld"
_debug "_bhosted_name" "$_bhosted_name"
return 0
}
_bhosted_api_add_txt() {
_sld="$1"
_tld="$2"
_name="$3"
_content="$4"
_ttl="$5"
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
_u_name="$(printf "%s" "$_name" | _url_encode)"
_u_content="$(printf "%s" "$_content" | _url_encode)"
_u_ttl="$(printf "%s" "$_ttl" | _url_encode)"
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&type=TXT&name=${_u_name}&content=${_u_content}&ttl=${_u_ttl}"
_debug "bHosted add endpoint" "${BHOSTED_API_ROOT}/addrecord"
response="$(_post "$_data" "${BHOSTED_API_ROOT}/addrecord")"
_ret="$?"
_debug2 "bHosted add response" "$response"
if [ "$_ret" != "0" ]; then
_err "bHosted addrecord request failed"
return 1
fi
if _bhosted_response_has_error "$response"; then
_err "bHosted addrecord returned an error"
_debug2 "response" "$response"
return 1
fi
return 0
}
_bhosted_api_del_record() {
_sld="$1"
_tld="$2"
_id="$3"
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
_u_id="$(printf "%s" "$_id" | _url_encode)"
_url="${BHOSTED_API_ROOT}/delrecord"
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&id=${_u_id}"
_debug "bHosted delete endpoint" "$_url"
response="$(_post "$_data" "$_url")"
_ret="$?"
_debug2 "bHosted delete response" "$response"
if [ "$_ret" != "0" ]; then
_err "bHosted delrecord request failed"
return 1
fi
if _bhosted_response_has_error "$response"; then
_err "bHosted delrecord returned an error"
_debug2 "response" "$response"
return 1
fi
return 0
}
# Extract XML tag value from response, e.g. <id>12345</id>
_bhosted_xml_value() {
_tag="$1"
_resp="$2"
# Flatten response to simplify parsing
_flat="$(printf "%s" "$_resp" | tr -d '\r\n\t')"
printf "%s" "$_flat" | sed -n "s:.*<${_tag}>\\([^<]*\\)</${_tag}>.*:\\1:p" | _head_n 1
}
# Return code convention:
# return 0 => response HAS error
# return 1 => response has NO error (success)
_bhosted_response_has_error() {
_resp="$1"
# Empty response = error
if [ -z "$_resp" ]; then
_debug "Empty API response"
return 0
fi
# Prefer explicit bHosted XML response fields
if _contains "$_resp" "<response>"; then
_errors="$(_bhosted_xml_value "errors" "$_resp")"
_done="$(_bhosted_xml_value "done" "$_resp")"
_subcommand="$(_bhosted_xml_value "subcommand" "$_resp")"
_id="$(_bhosted_xml_value "id" "$_resp")"
_debug "bHosted XML subcommand" "$_subcommand"
_debug "bHosted XML id" "$_id"
_debug "bHosted XML errors" "$_errors"
_debug "bHosted XML done" "$_done"
# Success according to provided format
if [ "$_errors" = "0" ] && [ "$_done" = "true" ]; then
return 1
fi
_debug "bHosted XML indicates failure"
return 0
fi
# Fallback for unexpected/non-XML responses
_resp_lc="$(_lower_case "$_resp")"
if _contains "$_resp_lc" "error"; then
_debug "Detected 'error' in response"
return 0
fi
if _contains "$_resp_lc" "fout"; then
_debug "Detected 'fout' in response"
return 0
fi
if _contains "$_resp_lc" "invalid"; then
_debug "Detected 'invalid' in response"
return 0
fi
if _contains "$_resp_lc" "failed"; then
_debug "Detected 'failed' in response"
return 0
fi
if _contains "$_resp_lc" "denied"; then
_debug "Detected 'denied' in response"
return 0
fi
# If no explicit error markers found, assume success
return 1
}
# Extract record id from response
# Supports bHosted XML first, then generic fallbacks
_bhosted_extract_id() {
_resp="$1"
# bHosted XML: <id>12345</id>
_id="$(_bhosted_xml_value "id" "$_resp" | tr -cd '0-9')"
if [ -n "$_id" ]; then
printf "%s" "$_id"
return 0
fi
# JSON: "id":12345
_id="$(printf "%s" "$_resp" | _egrep_o '"id"[[:space:]]*:[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
if [ -n "$_id" ]; then
printf "%s" "$_id"
return 0
fi
# key=value: id=12345
_id="$(printf "%s" "$_resp" | _egrep_o '(^|[[:space:][:punct:]])id[[:space:]]*=[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
if [ -n "$_id" ]; then
printf "%s" "$_id"
return 0
fi
# "record id 12345" / "recordid 12345"
_id="$(printf "%s" "$_resp" | _egrep_o '(record[[:space:]]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
if [ -n "$_id" ]; then
printf "%s" "$_id"
return 0
fi
return 1
}
# Create a unique config key for cached record ids
_bhosted_cache_hash() {
_fd="$1"
_tv="$2"
# md5 hex of fulldomain|txtvalue
printf "%s|%s" "$_fd" "$_tv" | _digest md5 hex
}
_bhosted_cache_key() {
_hash="$1"
printf "%s" "BHOSTED_TXT_ID_${_hash}"
}
_bhosted_mem_set_id() {
_hash="$1"
_id="$2"
_key="$(_bhosted_cache_key "$_hash")"
_savedomainconf "$_key" "$_id"
}
_bhosted_mem_get_id() {
_hash="$1"
_key="$(_bhosted_cache_key "$_hash")"
_readdomainconf "$_key"
}
+1 -1
View File
@@ -7,7 +7,7 @@ Options:
BOOKMYNAME_USERNAME Username
BOOKMYNAME_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/3209
Author: Neilpang
Author: @Neilpang
'
######## Public functions #####################
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_calrissia_info='Calrissia.be DNS API
Site: calrissia.be
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_calrissia
Options:
CALRISSIA_TOKEN Personal access token
Issues: github.com/acmesh-official/acme.sh/issues/6809
Author: Ward Hus
'
CALRISSIA_API="https://my.calrissia.com/api"
dns_calrissia_add() {
fulldomain="$1"
txtvalue="$2"
_calrissia_load_token || return 1
if ! _calrissia_get_root "$fulldomain"; then
_err "Unable to find domain in Calrissia account for: $fulldomain"
return 1
fi
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
_info "Adding TXT record for $fulldomain"
_body="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120,\"prio\":0}"
_response="$(_calrissia_request POST "/domain/$_domain_id/record" "$_body")"
if ! _contains "$_response" '"id"'; then
_err "Failed to create TXT record: $_response"
return 1
fi
return 0
}
dns_calrissia_rm() {
fulldomain="$1"
txtvalue="$2"
_calrissia_load_token || return 1
if ! _calrissia_get_root "$fulldomain"; then
_err "Unable to find domain in Calrissia account for: $fulldomain"
return 1
fi
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
# Look the record up from the API instead of relying on local state.
# The record list is embedded in the domain object.
_response="$(_calrissia_request GET "/domain/$_domain_id")"
_debug2 "Response: $_response"
# Split the record objects onto separate lines, then match on both the
# subdomain name and the TXT value to find the record id to delete.
_record_id="$(printf "%s" "$_response" |
tr '{}' '\n' |
grep "\"name\" *: *\"$_sub_domain\"" |
grep "\"content\" *: *\"$txtvalue\"" |
_egrep_o '"id" *: *[0-9]+' |
_head_n 1 |
_egrep_o '[0-9]+')"
if [ -z "$_record_id" ]; then
_info "No matching TXT record found for $fulldomain; nothing to remove"
return 0
fi
_info "Removing TXT record id=$_record_id from domain id=$_domain_id"
if ! _response="$(_calrissia_request DELETE "/domain/$_domain_id/record/$_record_id")" || _contains "$_response" '"error"'; then
_err "Failed to remove TXT record: $_response"
return 1
fi
return 0
}
####################
# Private helpers #
####################
_calrissia_load_token() {
CALRISSIA_TOKEN="${CALRISSIA_TOKEN:-$(_readaccountconf_mutable CALRISSIA_TOKEN)}"
if [ -z "$CALRISSIA_TOKEN" ]; then
_err "CALRISSIA_TOKEN is not set. Generate one at https://identity.calrissia.com under API Keys."
return 1
fi
_saveaccountconf_mutable CALRISSIA_TOKEN "$CALRISSIA_TOKEN"
}
# Sets _domain, _domain_id, _sub_domain for a given FQDN.
_calrissia_get_root() {
_fqdn="$1"
i=1
while true; do
_candidate="$(printf "%s" "$_fqdn" | cut -d . -f "$i"-)"
[ -z "$_candidate" ] && return 1
_debug "Trying root domain: $_candidate"
_response="$(_calrissia_request GET "/domain?full_domain_name=$_candidate")"
_debug2 "Response: $_response"
_domain_id="$(printf "%s" "$_response" |
_egrep_o '"id" *: *[0-9]+' |
_head_n 1 |
_egrep_o '[0-9]+')"
if [ -n "$_domain_id" ]; then
if [ "$i" = "1" ]; then
# The FQDN itself is the zone apex, e.g. a challenge-alias domain.
_sub_domain=""
else
_sub_domain="$(printf "%s" "$_fqdn" | cut -d . -f "1-$((i - 1))")"
fi
_domain="$_candidate"
return 0
fi
i=$((i + 1))
done
}
_calrissia_request() {
_method="$1"
_path="$2"
_body="$3"
export _H1="Authorization: Bearer $CALRISSIA_TOKEN"
export _H2="Accept: application/json"
if [ "$_method" = "GET" ]; then
_get "$CALRISSIA_API$_path"
else
_post "$_body" "$CALRISSIA_API$_path" "" "$_method" "application/json"
fi
}
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_cdmon_info='cdmon
Site: www.cdmon.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cdmon
Options:
CDMON_Key API Key
'
CDMON_Api="https://api-domains.cdmon.services/api-domains"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_cdmon_add() {
fulldomain=$1
txtvalue=$2
CDMON_Key="${CDMON_Key:-$(_readaccountconf_mutable CDMON_Key)}"
if [ -z "$CDMON_Key" ]; then
CDMON_Key=""
_err "You didn't specify your cdmon api key yet."
_err "Please create your key and try again."
return 1
fi
_saveaccountconf_mutable CDMON_Key "$CDMON_Key"
_debug "First, we detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Adding record"
if _cdmon_rest "dnsrecords/create" "{\"data\":{\"type\":\"TXT\",\"domain\":\"$_domain\",\"value\":\"$txtvalue\",\"ttl\":120,\"host\":\"$_sub_domain\"}}"; then
if _contains "$response" "\"status\":\"ok\""; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_cdmon_rm() {
fulldomain=$1
txtvalue=$2
CDMON_Key="${CDMON_Key:-$(_readaccountconf_mutable CDMON_Key)}"
_debug "First, we detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Removing record"
if _cdmon_rest "dnsrecords/delete" "{\"data\":{\"value\":\"$txtvalue\",\"type\":\"TXT\",\"domain\":\"$_domain\",\"host\":\"$_sub_domain\"}}"; then
if _contains "$response" "\"status\":\"ok\""; then
_info "Deleted, OK"
return 0
else
_err "Delete txt record error."
return 1
fi
fi
_err "Delete txt record error."
return 1
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
i=1
p=1
if ! _cdmon_rest "domains/list"; then
return 1
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if _contains "$response" "\"domain\":\"$h\""; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_cdmon_rest() {
ep="$1"
data="$2"
_debug "$ep"
key_trimmed=$(echo "$CDMON_Key" | tr -d '"')
export _H1="Content-Type: application/json"
export _H2="apikey: $key_trimmed"
_debug data "$data"
response="$(_post "$data" "$CDMON_Api/$ep")"
_ret="$?"
unset _H1 _H2
if [ "$_ret" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}
+3 -1
View File
@@ -92,7 +92,9 @@ dns_cf_add() {
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
elif _contains "$response" "The record already exists"; then
elif _contains "$response" "The record already exists" ||
_contains "$response" "An identical record already exists." ||
_contains "$response" '"code":81058'; then
_info "Already exists, OK"
return 0
else
+4 -3
View File
@@ -135,7 +135,7 @@ _dns_cloudns_init_check() {
_dns_cloudns_http_api_call "dns/login.json" ""
if ! _contains "$response" "\"status\":\"Success\""; then
_err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials."
_err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Server response: $response"
return 1
fi
@@ -197,10 +197,11 @@ _dns_cloudns_http_api_call() {
auth_user="auth-id=$CLOUDNS_AUTH_ID"
fi
encoded_password=$(echo "$CLOUDNS_AUTH_PASSWORD" | tr -d "\n\r" | _url_encode)
if [ -z "$2" ]; then
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD"
data="$auth_user&auth-password=$encoded_password"
else
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2"
data="$auth_user&auth-password=$encoded_password&$2"
fi
response="$(_get "$CLOUDNS_API/$method?$data")"
+4 -2
View File
@@ -15,7 +15,8 @@ CN_API="https://beta.api.core-networks.de"
######## Public functions #####################
dns_cn_add() {
fulldomain=$1
# Core-Networks API requires punycode for IDN domains
fulldomain=$(_idn "$1")
txtvalue=$2
if ! _cn_login; then
@@ -58,7 +59,8 @@ dns_cn_add() {
}
dns_cn_rm() {
fulldomain=$1
# Core-Networks API requires punycode for IDN domains
fulldomain=$(_idn "$1")
txtvalue=$2
if ! _cn_login; then
+248
View File
@@ -0,0 +1,248 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_comlaude_info='comlaude.com
Site: comlaude.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_comlaude
Options:
COMLAUDE_USERNAME User account
COMLAUDE_PASSWORD User password
COMLAUDE_API_KEY generated API key
COMLAUDE_GROUP_ID Group ID in comlaude user profile
Get it from the https://www.comlaude.com
Issues: github.com/acmesh-official/acme.sh/issues/7112
'
# ===== CONFIG =====
COMLAUDE_API="https://api.comlaude.com"
########## AUTH ##########
_comlaude_auth() {
_debug "Checking cached ComLaude token"
# Try to get token from account.conf
if [ -z "$COMLAUDE_ACCESS_TOKEN" ]; then
COMLAUDE_ACCESS_TOKEN="$(_readaccountconf_mutable COMLAUDE_ACCESS_TOKEN)"
COMLAUDE_TOKEN_EXPIRY="$(_readaccountconf_mutable COMLAUDE_TOKEN_EXPIRY)"
fi
_now=$(_time)
if [ -n "$COMLAUDE_ACCESS_TOKEN" ] && [ -n "$COMLAUDE_TOKEN_EXPIRY" ] && [ "$_now" -lt "$COMLAUDE_TOKEN_EXPIRY" ]; then
_debug "Using cached ComLaude token (valid ${COMLAUDE_TOKEN_EXPIRY} > ${_now})"
return 0
fi
_info "ComLaude auth..."
_comlaude_body="{\"username\":\"$COMLAUDE_USERNAME\",\"password\":\"$COMLAUDE_PASSWORD\",\"api_key\":\"$COMLAUDE_API_KEY\"}"
_comlaude_response="$(_post "$_comlaude_body" "$COMLAUDE_API/api_login" "" "POST" "application/json")"
if ! _contains "$_comlaude_response" "access_token"; then
_err "Auth failed: $_comlaude_response"
return 1
fi
COMLAUDE_ACCESS_TOKEN=$(echo "$_comlaude_response" | _egrep_o '"access_token":"[^"]*"' | cut -d'"' -f4)
# store expiracy from api reply l'API ("expires_in" in seconds)
_comlaude_expires_in=$(echo "$_comlaude_response" | _egrep_o '"expires_in":[0-9]*' | cut -d: -f2)
[ -z "$_comlaude_expires_in" ] && _comlaude_expires_in=3000 # fallback if no info
COMLAUDE_TOKEN_EXPIRY=$(($(_time) + _comlaude_expires_in - 60)) # margin of 60s to secure renew
_saveaccountconf_mutable COMLAUDE_ACCESS_TOKEN "$COMLAUDE_ACCESS_TOKEN"
_saveaccountconf_mutable COMLAUDE_TOKEN_EXPIRY "$COMLAUDE_TOKEN_EXPIRY"
return 0
}
########## DOMAIN RESOLUTION ##########
_comlaude_get_root() {
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
if [ -z "$COMLAUDE_GROUP_ID" ]; then
_err "Missing COMLAUDE_GROUP_ID"
return 1
fi
_comlaude_input_domain="$1"
_comlaude_input_domain="${_comlaude_input_domain#_acme-challenge.}"
case "$_comlaude_input_domain" in
\*.*) _comlaude_input_domain="${_comlaude_input_domain#*.}" ;;
esac
_debug "Normalized domain: $_comlaude_input_domain"
_comlaude_i=1
while true; do
_comlaude_d=$(printf "%s" "$_comlaude_input_domain" | cut -d . -f "$_comlaude_i-")
[ -z "$_comlaude_d" ] && {
_debug "No matching domain found for $_comlaude_input_domain"
return 1
}
# don't test unnecessary levels
# registered domain : TLD only (no dot after cut).
case "$_comlaude_d" in
*.*) : ;;
*)
_debug "Skipping bare TLD candidate: $_comlaude_d"
_comlaude_i=$((_comlaude_i + 1))
continue
;;
esac
_debug "Checking domain: $_comlaude_d"
_comlaude_retry=0
_comlaude_max_retry=3 # to avoid network errors
_comlaude_DOM_ID=""
_comlaude_Z_ID=""
while [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ]; do
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_debug "Full URL: $COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone"
_comlaude_response="$(_get "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone")"
_H1=""
_debug "RAW response for $_comlaude_d (try $((_comlaude_retry + 1))): $_comlaude_response"
# If empty -> true network issue, we retry
if [ -z "$_comlaude_response" ]; then
_comlaude_retry=$((_comlaude_retry + 1))
[ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
continue
fi
# 404 -> domain not found in that level. no retry : continue
if echo "$_comlaude_response" | grep -q '"status_code":404'; then
_debug "404 for $_comlaude_d, moving to next level (not retrying)"
break
fi
# Domain missing (200 reply, data empty) -> continue
if echo "$_comlaude_response" | grep -q '"data":\[\]'; then
_debug "Empty data for $_comlaude_d, moving to next level"
break
fi
# Extraction via _egrep_o
_comlaude_DOM_ID="$(echo "$_comlaude_response" | _egrep_o '"id":"[^"]*"' | head -n1 | cut -d':' -f2 | tr -d '"')"
_comlaude_Z_ID="$(echo "$_comlaude_response" | _egrep_o '"active_zone":\{"id":"[^"]*"' | _egrep_o '"id":"[^"]*"$' | cut -d':' -f2 | tr -d '"')"
if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
break
fi
# 200 reply but malformed data / noid -> retry transport
_comlaude_retry=$((_comlaude_retry + 1))
[ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
done
_debug "_comlaude_DOM_ID=$_comlaude_DOM_ID"
_debug "_comlaude_Z_ID=$_comlaude_Z_ID"
if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
_comlaude_domain="$_comlaude_d"
_comlaude_domain_id="$_comlaude_DOM_ID"
_comlaude_zone_id="$_comlaude_Z_ID"
return 0
fi
_comlaude_i=$((_comlaude_i + 1))
done
}
########## ADD TXT ##########
dns_comlaude_add() {
fulldomain="$1"
txtvalue="$2"
COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
if [ -z "$COMLAUDE_USERNAME" ] || [ -z "$COMLAUDE_PASSWORD" ] || [ -z "$COMLAUDE_API_KEY" ]; then
_err "You didn't specify ComLaude credentials (COMLAUDE_USERNAME, COMLAUDE_PASSWORD, COMLAUDE_API_KEY)."
return 1
fi
# Backup variable after validation
_saveaccountconf_mutable COMLAUDE_USERNAME "$COMLAUDE_USERNAME"
_saveaccountconf_mutable COMLAUDE_PASSWORD "$COMLAUDE_PASSWORD"
_saveaccountconf_mutable COMLAUDE_API_KEY "$COMLAUDE_API_KEY"
_saveaccountconf_mutable COMLAUDE_GROUP_ID "$COMLAUDE_GROUP_ID"
_info "Adding TXT: $fulldomain"
_comlaude_auth || return 1
_comlaude_get_root "$fulldomain" || return 1
_debug "Root: $_comlaude_domain"
_comlaude_data="{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"value\":\"$txtvalue\",\"ttl\":60}"
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
export _H2="Content-Type: application/json"
_comlaude_response="$(_post "$_comlaude_data" "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records")"
_H1=""
_H2=""
if ! echo "$_comlaude_response" | grep -q '"id"'; then
_err "Failed to create TXT"
_debug "$_comlaude_response"
return 1
fi
return 0
}
########## REMOVE TXT ##########
dns_comlaude_rm() {
fulldomain="$1"
txtvalue="$2"
COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
_info "Removing TXT: $fulldomain"
_comlaude_auth || return 1
_comlaude_get_root "$fulldomain" || return 1
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_comlaude_encoded_name="$(printf '%s' "$fulldomain" | _url_encode)"
_comlaude_encoded_value="$(printf '%s' "$txtvalue" | _url_encode)"
_comlaude_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records?filter[type]=TXT&filter[name]=$_comlaude_encoded_name&filter[value]=$_comlaude_encoded_value"
_comlaude_response="$(_get "$_comlaude_url")"
_H1=""
_debug "Filtered records response: $_comlaude_response"
# first "id" top-level of reply (record itself,
# always on first position of each data[] object)
_comlaude_record_id="$(echo "$_comlaude_response" | _egrep_o '"data":\[\{"id":"[^"]*"' | _egrep_o '"[^"]*"$' | tr -d '"')"
if [ -z "$_comlaude_record_id" ]; then
_info "No matching TXT record found to delete for $fulldomain / $txtvalue"
return 0
fi
_debug "Deleting record $_comlaude_record_id"
export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
_comlaude_del_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records/$_comlaude_record_id"
_comlaude_del_resp="$(_post "" "$_comlaude_del_url" "" "DELETE")"
_H1=""
if echo "$_comlaude_del_resp" | grep -q '"error"'; then
_err "Delete failed for $_comlaude_record_id"
_debug "$_comlaude_del_resp"
return 1
fi
_info "Deleted record $_comlaude_record_id"
return 0
}
+4 -1
View File
@@ -117,7 +117,7 @@ dns_constellix_rm() {
#################### Private functions below ##################################
_get_root() {
domain=$1
domain=$(echo "$1" | _lower_case)
i=2
p=1
_debug "Detecting root zone"
@@ -156,6 +156,9 @@ _constellix_rest() {
data="$3"
_debug "$ep"
# Prevent rate limit
_sleep 2
rdate=$(date +"%s")"000"
hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64)
+21 -7
View File
@@ -38,7 +38,7 @@ dns_cpanel_add() {
fi
# adding entry
_info "Adding the entry"
stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//")
stripped_fulldomain="${fulldomain%."$_domain"}"
_debug "Adding $stripped_fulldomain to $_domain zone"
_myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1"
if _successful_update; then return 0; fi
@@ -128,13 +128,27 @@ _get_root() {
_err "Primary domain list not found!"
return 1
fi
for _domain in $_domains; do
_debug "Checking if $fulldomain ends with $_domain"
if (_endswith "$fulldomain" "$_domain"); then
_debug "Root domain: $_domain"
return 0
fi
# Pick the LONGEST matching zone, dot-anchored: with both domain.tld and
# sub.domain.tld zones on the account, cPanel stores the record in the
# most specific zone, so add and rm must both resolve to that one.
_domain=""
for d in $_domains; do
_debug "Checking if $fulldomain ends with $d"
# case with quoted patterns gives an exact literal suffix match;
# _endswith treats the needle as a regex, so its dots would let
# xdomain.tld wrongly match zone domain.tld
case "$fulldomain" in
"$d" | *".$d")
if [ "${#d}" -gt "${#_domain}" ]; then
_domain="$d"
fi
;;
esac
done
if [ -n "$_domain" ]; then
_debug "Root domain: $_domain"
return 0
fi
return 1
}
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_cpanel_uapi_info='cPanel UAPI
Manage DNS via cPanel UAPI. Works with API tokens and Two-Factor Authentication.
Site: cpanel.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel_uapi
Options:
cPanel_Username Username
cPanel_Apitoken API Token
cPanel_Hostname Server URL. E.g. "https://hostname:port"
cPanel_TTL optional TXT record TTL in seconds. Default: 120
Issues: github.com/acmesh-official/acme.sh/issues/6877
Author: Adam Bodnar
'
######## Public functions #####################
# Used to add txt record
dns_cpanel_uapi_add() {
fulldomain=$1
txtvalue=$2
_info "Adding TXT record via cPanel UAPI"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _cpanel_uapi_get_root; then
_err "No matching root domain for $fulldomain found"
return 1
fi
# Build the record name relative to the zone
_escaped_domain=$(echo "$_domain" | sed 's/\./\\./g')
_record_name=$(echo "$fulldomain" | sed "s/\.${_escaped_domain}$//")
_debug "Record name: $_record_name in zone $_domain"
# Get the current SOA serial (required by mass_edit_zone)
if ! _cpanel_uapi_get_serial "$_domain"; then
_err "Failed to get zone serial for $_domain"
return 1
fi
_debug "Zone serial: $_serial"
# Use configurable TTL, default 120 seconds
_ttl="${cPanel_TTL:-$(_readaccountconf_mutable cPanel_TTL)}"
case "$_ttl" in
"")
_ttl=120
;;
*[!0-9]*)
_debug "Invalid cPanel_TTL provided, falling back to default 120"
_ttl=120
;;
esac
# Build JSON and URL-encode it for the add parameter
_add_json=$(printf '{"dname":"%s","ttl":%s,"record_type":"TXT","data":["%s"]}' "$_record_name" "$_ttl" "$txtvalue")
_debug "add_json: $_add_json"
_add_json_encoded=$(printf '%s' "$_add_json" | _url_encode)
_debug "add_json (encoded): $_add_json_encoded"
if ! _cpanel_uapi_request "execute/DNS/mass_edit_zone?zone=${_domain}&serial=${_serial}&add=${_add_json_encoded}"; then
_err "Request to add TXT record failed for zone $_domain"
return 1
fi
_debug "_result: $_result"
if _contains "$_result" '"status":1'; then
_info "TXT record added successfully"
return 0
fi
_err "Failed to add TXT record."
_err "Response: $_result"
return 1
}
# Used to remove the txt record after validation
dns_cpanel_uapi_rm() {
fulldomain=$1
txtvalue=$2
_info "Removing TXT record via cPanel UAPI"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _cpanel_uapi_get_root; then
_err "No matching root domain for $fulldomain found"
return 1
fi
if ! _cpanel_uapi_findentry; then
_info "Entry doesn't exist, nothing to delete"
return 0
fi
_debug "Deleting record with line_index=$_line_index"
if ! _cpanel_uapi_get_serial "$_domain"; then
_err "Failed to get zone serial for $_domain"
return 1
fi
if ! _cpanel_uapi_request "execute/DNS/mass_edit_zone?zone=${_domain}&serial=${_serial}&remove=${_line_index}"; then
_err "Request to remove TXT record failed for zone $_domain"
return 1
fi
_debug "_result: $_result"
if _contains "$_result" '"status":1'; then
_info "TXT record removed successfully"
return 0
fi
_err "Failed to remove TXT record."
_err "Response: $_result"
return 1
}
#################### Private functions below ##################################
_cpanel_uapi_checkcredentials() {
cPanel_Username="${cPanel_Username:-$(_readaccountconf_mutable cPanel_Username)}"
cPanel_Apitoken="${cPanel_Apitoken:-$(_readaccountconf_mutable cPanel_Apitoken)}"
cPanel_Hostname="${cPanel_Hostname:-$(_readaccountconf_mutable cPanel_Hostname)}"
if [ -z "$cPanel_Username" ] || [ -z "$cPanel_Apitoken" ] || [ -z "$cPanel_Hostname" ]; then
cPanel_Username=""
cPanel_Apitoken=""
cPanel_Hostname=""
_err "You haven't specified cPanel_Username, cPanel_Apitoken, and cPanel_Hostname."
return 1
fi
# Remove trailing slash from hostname if present
cPanel_Hostname=$(echo "$cPanel_Hostname" | sed 's|/$||')
_saveaccountconf_mutable cPanel_Username "$cPanel_Username"
_saveaccountconf_mutable cPanel_Apitoken "$cPanel_Apitoken"
_saveaccountconf_mutable cPanel_Hostname "$cPanel_Hostname"
if [ -n "$cPanel_TTL" ]; then
case "$cPanel_TTL" in
*[!0-9]*)
_info "Ignoring invalid cPanel_TTL: $cPanel_TTL"
cPanel_TTL=""
;;
*)
_saveaccountconf_mutable cPanel_TTL "$cPanel_TTL"
;;
esac
fi
return 0
}
_cpanel_uapi_request() {
export _H1="Authorization: cpanel $cPanel_Username:$cPanel_Apitoken"
_result=$(_get "$cPanel_Hostname/$1")
return $?
}
_cpanel_uapi_get_root() {
if ! _cpanel_uapi_checkcredentials; then return 1; fi
if ! _cpanel_uapi_request "execute/DomainInfo/list_domains"; then
_err "Request to cPanel API failed while listing domains"
return 1
fi
_debug "DomainInfo response length: ${#_result}"
if ! _contains "$_result" '"status":1'; then
_err "cPanel UAPI request failed. Is the API token correct?"
_debug "Response: $_result"
return 1
fi
# Extract main_domain
_main_domain=$(echo "$_result" | _egrep_o '"main_domain":"[^"]*"' | _head_n 1 | sed 's/.*"main_domain":"//;s/"//')
_debug "main_domain: $_main_domain"
# Extract addon_domains (array of strings)
_addon_domains=$(echo "$_result" | _egrep_o '"addon_domains":\[[^]]*\]' | sed 's/.*"addon_domains":\[//;s/\]$//' | _egrep_o '"[a-zA-Z0-9._-]+"' | sed 's/"//g')
_debug "addon_domains: $_addon_domains"
# Build list of all domains to check
_all_domains="$_main_domain $_addon_domains"
_debug "All domains: $_all_domains"
# Find the matching root domain (prefer longest match)
_best_match=""
_best_len=0
for _check_domain in $_all_domains; do
if [ -z "$_check_domain" ]; then continue; fi
if _endswith "$fulldomain" "$_check_domain"; then
_len=${#_check_domain}
if [ "$_len" -gt "$_best_len" ]; then
_best_match="$_check_domain"
_best_len="$_len"
fi
fi
done
if [ -n "$_best_match" ]; then
_domain="$_best_match"
_debug "Root domain: $_domain"
return 0
fi
return 1
}
_cpanel_uapi_get_serial() {
_zone="$1"
if ! _cpanel_uapi_request "execute/DNS/parse_zone?zone=${_zone}"; then
_err "Request to parse zone failed for $_zone"
return 1
fi
# Split JSON records onto separate lines using a POSIX-portable sed literal newline
# (\\n in sed replacement is a GNU/BusyBox extension; a backslash-newline works everywhere)
_soa_line=$(echo "$_result" | sed 's/},{/},\
{/g' | grep '"record_type":"SOA"' | _head_n 1)
_debug "SOA line: $_soa_line"
if [ -z "$_soa_line" ]; then
_err "SOA record not found for zone $_zone"
_debug "parse_zone response: $_result"
return 1
fi
# Extract the third element from data_b64 array (serial is index 2, 0-based)
# data_b64 format: ["ns","admin","SERIAL","refresh","retry","expire","minimum"]
_serial_b64=$(echo "$_soa_line" | _egrep_o '"data_b64":\[[^]]*\]' | sed 's/"data_b64":\[//;s/\]//' | sed 's/"//g' | cut -d',' -f3)
_debug "serial_b64: $_serial_b64"
if [ -z "$_serial_b64" ]; then
_err "Could not extract serial from SOA record"
return 1
fi
_serial=$(printf '%s' "$_serial_b64" | _dbase64)
_debug "Decoded serial: $_serial"
if [ -z "$_serial" ]; then
_err "Failed to decode serial"
return 1
fi
return 0
}
_cpanel_uapi_findentry() {
_debug "Finding TXT entry for $fulldomain with value $txtvalue"
if ! _cpanel_uapi_request "execute/DNS/parse_zone?zone=${_domain}"; then
_err "Request to parse zone failed for $_domain"
return 1
fi
_debug "parse_zone result length: ${#_result}"
# Base64-encode the txtvalue to match against data_b64 in the response
_b64_txtvalue=$(printf '%s' "$txtvalue" | _base64)
_debug "b64_txtvalue: $_b64_txtvalue"
# Split records onto separate lines, find matching TXT record by base64 value
_line_index=$(echo "$_result" | sed 's/},{/},\
{/g' | grep '"record_type":"TXT"' | grep -F "$_b64_txtvalue" | _egrep_o '"line_index":[0-9]+' | _head_n 1 | cut -d: -f2)
_debug "line_index: $_line_index"
if [ -n "$_line_index" ]; then
_debug "Entry found with line_index=$_line_index"
return 0
fi
return 1
}
+181
View File
@@ -0,0 +1,181 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_creoline_info='creoline
Site: https://www.creoline.com/de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_creoline
Help: https://help.creoline.com
Options:
creolineApiToken
creolineApiSecret
Issues: github.com/acmesh-official/acme.sh/issues/7103
'
creolineApi="https://api.creoline.com/v1"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPB8"
dns_creoline_add() {
fulldomain=$1
txtvalue=$2
creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}"
creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}"
if [ -z "$creolineApiToken" ] || [ -z "$creolineApiSecret" ]; then
_err "Error required creoline API Token or creoline API Secret not specified."
_err "Please set it with the Command 'export creolineApiToken=<YourToken>' and 'export creolineApiSecret=<YourSecret>'."
return 1
else
_saveaccountconf_mutable creolineApiToken "$creolineApiToken"
_saveaccountconf_mutable creolineApiSecret "$creolineApiSecret"
fi
_debug "Detecting the root dns zone."
if ! _get_root "$fulldomain"; then
_err "Error on detecting the root dns zone."
return 1
fi
_info "Adding record"
if _creoline_rest POST "dns/zone/$_domain/record" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"record\":\"$txtvalue\",\"ttl\":\"60\"}"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_creoline_rm() {
fulldomain=$1
txtvalue=$2
creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}"
creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}"
_debug "Detecting the root dns zone."
if ! _get_root "$fulldomain"; then
_err "Error on detecting the root dns zone."
return 1
fi
_info "Getting earlier created txt record."
if ! _creoline_rest GET "dns/zone/$_domain/record/type/TXT/record/$txtvalue"; then
if _contains "$response" "errors" || _contains "$response" "message"; then
_err "Error on getting earlier created txt record."
return 1
fi
_err "Error on getting earlier created txt record."
return 1
fi
record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug "record_id" "$record_id"
if [ -z "$record_id" ]; then
_err "Error on deleting earlier created txt record. No record id found in response."
return 1
fi
_info "Deleting earlier created txt record."
if ! _creoline_rest DELETE "dns/zone/$_domain/record/$record_id"; then
if _contains "$response" "errors" || _contains "$response" "message"; then
_err "Error on deleting earlier created txt record."
return 1
fi
_err "Error on deleting earlier created txt record."
return 1
fi
_info "Deleted, OK"
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
if ! _creoline_rest GET "dns/zone/root/$domain"; then
return 1
fi
_sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug _sub_domain "$_sub_domain"
_domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug _domain "$_domain"
if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
return 1
fi
}
_creoline_rest() {
method=$1
uri="$2"
data="$3"
timestamp=$(_time)
canonical_request="${timestamp}.${creolineApi}/${uri}"
signature_hash=$(printf "%s" "$canonical_request" | _hmac sha256 "$(printf "%s" "$creolineApiSecret" | _hex_dump | tr -d " ")" hex)
_debug method "$method"
_debug uri "$uri"
_debug data "$data"
_debug2 timestamp "$timestamp"
_debug2 canonical_request "$canonical_request"
_debug2 signature_hash "$signature_hash"
token_trimmed=$(echo "$creolineApiToken" | tr -d '"')
hmac_trimmed=$(echo "$signature_hash" | tr -d '"')
export _H1="Content-Type: application/json"
if [ "$token_trimmed" ]; then
export _H2="X-Api-Token: $token_trimmed"
fi
if [ "$hmac_trimmed" ]; then
export _H3="X-Creoline-Api-Signature: $hmac_trimmed"
fi
if [ "$timestamp" ]; then
export _H4="X-Creoline-Api-Timestamp: $timestamp"
fi
if [ "$method" != "GET" ]; then
response="$(_post "$data" "$creolineApi/$uri" "" "$method")"
else
response="$(_get "$creolineApi/$uri")"
fi
if [ "$?" != "0" ]; then
_err "error $uri"
return 1
fi
_debug response "$response"
if _contains "$response" "errors"; then
error=$(echo "$response" | _egrep_o "\"errors\":[[]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | tr -d "[")
_err "Error: $error"
_err "URI:$uri"
return 1
elif _contains "$response" "message"; then
message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \")
_err "Error: $message"
_err "URI:$uri"
return 1
fi
return 0
}
+2 -2
View File
@@ -15,7 +15,7 @@ CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains"
CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token"
CURANET_ACCESS_TOKEN=""
######## Public functions #####################
######## Public functions ####################
#Usage: dns_curanet_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_curanet_add() {
@@ -154,7 +154,7 @@ _get_root() {
export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN"
response="$(_get "$CURANET_REST_URL/$h/Records" "" "")"
if [ ! "$(echo "$response" | _egrep_o "Entity not found")" ]; then
if [ ! "$(echo "$response" | _egrep_o "Entity not found|Bad Request")" ]; then
_domain=$h
return 0
fi
+34 -6
View File
@@ -101,6 +101,8 @@ _cyon_load_parameters() {
# This header is required for curl calls.
_H1="X-Requested-With: XMLHttpRequest"
export _H1
_H3="User-Agent: cyon-dns-acmesh/1.0"
export _H3
}
_cyon_print_header() {
@@ -125,7 +127,11 @@ _cyon_print_header() {
}
_cyon_get_cookie_header() {
printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')"
# Extract all cookies from the response headers (case-insensitive)
_cookies="$(grep -i "^set-cookie:" "$HTTP_HEADER" | sed 's/^[Ss]et-[Cc]ookie: //' | sed 's/;.*//' | tr '\n' '; ' | sed 's/; $//')"
if [ -n "$_cookies" ]; then
printf "Cookie: %s" "$_cookies"
fi
}
_cyon_login() {
@@ -155,7 +161,12 @@ _cyon_login() {
_get "https://my.cyon.ch/" >/dev/null
# todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request.
# Update cookie after loading main page (only if new cookies are set)
_new_cookies="$(_cyon_get_cookie_header)"
if [ -n "$_new_cookies" ]; then
_H2="$_new_cookies"
export _H2
fi
# 2FA authentication with OTP?
if [ -n "${CY_OTP_Secret}" ]; then
@@ -184,6 +195,13 @@ _cyon_login() {
fi
_info " success"
# Update cookie after 2FA (only if new cookies are set)
_new_cookies="$(_cyon_get_cookie_header)"
if [ -n "$_new_cookies" ]; then
_H2="$_new_cookies"
export _H2
fi
fi
_info ""
@@ -205,7 +223,17 @@ _cyon_change_domain_env() {
domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')"
_debug "Changing domain environment to ${domain_env}"
gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")"
domain_page_response="$(_get "https://my.cyon.ch/domain/")"
_debug domain_page_response "${domain_page_response}"
# Check if we got an error response (JSON) instead of HTML
if printf "%s" "${domain_page_response}" | grep -q '"iserror":true'; then
_err " $(printf "%s" "${domain_page_response}" | _cyon_get_response_message)"
_err ""
return 1
fi
gloo_item_key="$(printf "%s" "${domain_page_response}" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")"
_debug gloo_item_key "${gloo_item_key}"
domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}"
@@ -304,11 +332,11 @@ _cyon_get_response_message() {
}
_cyon_get_response_status() {
_egrep_o '"status":[a-zA-z0-9]*' | cut -d : -f 2
_egrep_o '"status":[a-zA-Z0-9]*' | cut -d : -f 2
}
_cyon_get_validation_status() {
_egrep_o '"valid":[a-zA-z0-9]*' | cut -d : -f 2
_egrep_o '"valid":[a-zA-Z0-9]*' | cut -d : -f 2
}
_cyon_get_response_success() {
@@ -316,7 +344,7 @@ _cyon_get_response_success() {
}
_cyon_get_environment_change_status() {
_egrep_o '"authenticated":[a-zA-z0-9]*' | cut -d : -f 2
_egrep_o '"authenticated":[a-zA-Z0-9]*' | cut -d : -f 2
}
_cyon_check_if_2fa_missed() {
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env sh
# dns_czechia.sh - CZECHIA.COM/ZONER DNS API for acme.sh (DNS-01)
#
# Documentation: https://api.czechia.com/swagger/index.html
#shellcheck disable=SC2034
dns_czechia_info='[
{"name":"CZ_AuthorizationToken","usage":"Your API token from CZECHIA.COM/Zoner administration.","required":"1"},
{"name":"CZ_Zones","usage":"Managed zones separated by comma or space (e.g. \"example.com\").","required":"1"},
{"name":"CZ_API_BASE","usage":"Defaults to https://api.czechia.com","required":"0"}
]'
dns_czechia_add() {
fulldomain="$1"
txtvalue="$2"
_debug "dns_czechia_add fulldomain='$fulldomain'"
if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then
_err "dns_czechia_add: missing fulldomain or txtvalue"
return 1
fi
_czechia_load_conf || return 1
_current_zone=$(_czechia_pick_zone "$fulldomain")
if [ -z "$_current_zone" ]; then
_err "No matching zone found for $fulldomain. Please check CZ_Zones."
return 1
fi
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
_err "Missing zone or CZ_AuthorizationToken."
return 1
fi
_url="$CZ_API_BASE/api/DNS/$_cz/TXT"
_fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//')
if [ "$_fd" = "$_cz" ]; then
_h="@"
else
# Remove the literal ".<zone>" suffix from _fd, if present
_h=${_fd%."$_cz"}
[ "$_h" = "$_fd" ] && _h="@"
fi
[ -z "$_h" ] && _h="@"
_info "Adding TXT record for $_h in zone $_cz"
_h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g')
_txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g')
_body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}"
_debug "URL: $_url"
_debug "Body: $_body"
export _H1="Content-Type: application/json"
export _H2="AuthorizationToken: $_tk"
_res="$(_post "$_body" "$_url" "" "POST")"
_post_exit="$?"
_debug2 "Response: $_res"
if [ "$_post_exit" -ne 0 ]; then
_err "API request failed. exit code $_post_exit"
return 1
fi
if _contains "$_res" "already exists"; then
_info "Record already exists, skipping."
return 0
fi
_nres="$(printf '%s' "$_res" | _normalizeJson)"
if [ "$?" -ne 0 ] || [ -z "$_nres" ]; then
_nres="$_res"
fi
if _contains "$_nres" "\"status\":4" || _contains "$_nres" "\"status\":5" || _contains "$_nres" "\"errors\""; then
_err "API error: $_res"
return 1
fi
return 0
}
dns_czechia_rm() {
fulldomain="$1"
txtvalue="$2"
_debug "dns_czechia_rm fulldomain='$fulldomain'"
if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then
_err "dns_czechia_rm: missing fulldomain or txtvalue"
return 1
fi
_czechia_load_conf || return 1
_current_zone=$(_czechia_pick_zone "$fulldomain")
if [ -z "$_current_zone" ]; then
_err "No matching zone found for $fulldomain. Please check CZ_Zones configuration."
return 1
fi
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
_err "Missing zone or CZ_AuthorizationToken."
return 1
fi
_url="$CZ_API_BASE/api/DNS/$_cz/TXT"
_fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//')
if [ "$_fd" = "$_cz" ]; then
_h="@"
else
_h=$(printf "%s" "$_fd" | sed "s/\.$_cz$//")
[ "$_h" = "$_fd" ] && _h="@"
fi
[ -z "$_h" ] && _h="@"
_h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g')
_txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g')
_body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}"
_debug "URL: $_url"
_debug "Body: $_body"
export _H1="Content-Type: application/json"
export _H2="AuthorizationToken: $_tk"
_res="$(_post "$_body" "$_url" "" "DELETE")"
_post_exit="$?"
_debug2 "Response: $_res"
if [ "$_post_exit" -ne 0 ]; then
_err "CZECHIA DNS API DELETE request failed for $_fd: exit code $_post_exit, response: $_res"
return 1
fi
_res_normalized=$(printf '%s' "$_res" | _normalizeJson)
if _contains "$_res_normalized" '"isError":true'; then
_err "CZECHIA DNS API reported an error while deleting TXT for $_fd: $_res"
return 1
fi
return 0
}
_czechia_load_conf() {
CZ_AuthorizationToken="${CZ_AuthorizationToken:-$(_readaccountconf_mutable CZ_AuthorizationToken)}"
if [ -z "$CZ_AuthorizationToken" ]; then
_err "Missing CZ_AuthorizationToken"
return 1
fi
CZ_Zones="${CZ_Zones:-$(_readaccountconf_mutable CZ_Zones)}"
if [ -z "$CZ_Zones" ]; then
_err "Missing CZ_Zones"
return 1
fi
CZ_API_BASE="${CZ_API_BASE:-$(_readaccountconf_mutable CZ_API_BASE)}"
[ -z "$CZ_API_BASE" ] && CZ_API_BASE="https://api.czechia.com"
_saveaccountconf_mutable CZ_AuthorizationToken "$CZ_AuthorizationToken"
_saveaccountconf_mutable CZ_Zones "$CZ_Zones"
_saveaccountconf_mutable CZ_API_BASE "$CZ_API_BASE"
return 0
}
_czechia_pick_zone() {
_fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//')
_best_zone=""
_zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g')
for _z in $_zones_space; do
_clean_z=$(printf "%s" "$_z" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
[ -z "$_clean_z" ] && continue
case "$_fd" in
"$_clean_z" | *."$_clean_z")
if [ ${#_clean_z} -gt ${#_best_zone} ]; then
_best_zone="$_clean_z"
fi
;;
esac
done
printf "%s" "$_best_zone"
}
+1 -1
View File
@@ -4,7 +4,7 @@ dns_da_info='DirectAdmin Server API
Site: DirectAdmin.com/api.php
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da
Options:
DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443"
DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443". Special characters in the user/password must be percent-encoded, e.g. "@" -> "%40".
DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept
Issues: github.com/TigerP/acme.sh/issues
'
+1 -1
View File
@@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss
Options:
DDNSS_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2230
Author: RaidenII, helbgd, mod242
Author: @helbgd, @mod242
'
DDNSS_DNS_API="https://ddnss.de/upd.php"
+7 -4
View File
@@ -4,7 +4,7 @@ dns_desec_info='deSEC.io
Site: desec.readthedocs.io/en/latest/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec
Options:
DDNSS_Token API Token
DEDYN_TOKEN API Token
Issues: github.com/acmesh-official/acme.sh/issues/2180
Author: Zheng Qian
'
@@ -39,6 +39,7 @@ dns_desec_add() {
_err "invalid domain"
return 1
fi
_sub_domain=$(echo "$_sub_domain" | _lower_case)
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
@@ -48,7 +49,7 @@ dns_desec_add() {
_desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
@@ -100,7 +101,7 @@ dns_desec_rm() {
_err "invalid domain"
return 1
fi
_sub_domain=$(echo "$_sub_domain" | _lower_case)
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
@@ -110,7 +111,7 @@ dns_desec_rm() {
_desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
@@ -150,6 +151,8 @@ _desec_rest() {
if [ "$m" != "GET" ]; then
_secure_debug2 data "$data"
response="$(_post "$data" "$ep" "" "$m")"
_info "Sleeping 1s to respect deSEC write rate limit"
_sleep 1
else
response="$(_get "$ep")"
fi
+25 -90
View File
@@ -5,14 +5,11 @@ Site: DNSExit.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit
Options:
DNSEXIT_API_KEY API Key
DNSEXIT_AUTH_USER Username
DNSEXIT_AUTH_PASS Password
Issues: github.com/acmesh-official/acme.sh/issues/4719
Author: Samuel Jimenez
'
DNSEXIT_API_URL="https://api.dnsexit.com/dns/"
DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp"
######## Public functions #####################
#Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -28,20 +25,7 @@ dns_dnsexit_add() {
return 1
fi
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"add\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":0,\"overwrite\":false}}"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
_dnsexit_zone_op add ',"ttl":1,"overwrite":false'
}
#Usage: fulldomain txtvalue
@@ -58,54 +42,43 @@ dns_dnsexit_rm() {
return 1
fi
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
_err "$response"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"delete\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"}}"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
_dnsexit_zone_op delete ''
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
# The legacy zone-detection endpoint (update.dnsexit.com/ipupdate/hosts.jsp)
# was shut down by DNSExit and now returns 503, and the JSON API offers no
# zone-list call. So find the root zone by attempting the actual operation at
# each domain level: the API answers "code":0 only when the domain matches a
# zone of the account. https://github.com/acmesh-official/acme.sh/issues/6914
#Usage: _dnsexit_zone_op <add|delete> <extra-json-fields>
_dnsexit_zone_op() {
_op="$1"
_extra="$2"
i=1
while true; do
_domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$_domain"
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100)
_debug _domain "$_domain"
if [ -z "$_domain" ]; then
_err "Could not find the root zone of $fulldomain in your DNSExit account"
return 1
fi
_debug login "$DNSEXIT_AUTH_USER"
_debug password "$DNSEXIT_AUTH_PASS"
_debug domain "$_domain"
_sub_domain="$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//")"
if [ "$_sub_domain" = "$fulldomain" ]; then
_sub_domain=""
fi
_debug _sub_domain "$_sub_domain"
_dnsexit_http "login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain"
if _contains "$response" "0=$_domain"; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0
else
_debug "Go to next level of $_domain"
if _dnsexit_rest "{\"domain\":\"$_domain\",\"$_op\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"$_extra}}"; then
if _contains "$response" "\"code\":0" || _contains "$response" "\"code\": 0"; then
_debug2 _response "$response"
return 0
fi
_debug "Zone $_domain was not accepted, trying the next level" "$response"
fi
i=$(_math "$i" + 1)
done
return 1
}
_dnsexit_rest() {
@@ -136,27 +109,7 @@ _dnsexit_rest() {
return 0
}
_dnsexit_http() {
m=GET
param="$1"
_debug param "$param"
_debug get "$DNSEXIT_HOSTS_URL?$param"
response="$(_get "$DNSEXIT_HOSTS_URL?$param")"
_debug response "$response"
if [ "$?" != "0" ]; then
_err "Error $param"
return 1
fi
_debug2 response "$response"
return 0
}
get_account_info() {
DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}"
if test -z "$DNSEXIT_API_KEY"; then
DNSEXIT_API_KEY=''
@@ -166,23 +119,5 @@ get_account_info() {
_saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY"
DNSEXIT_AUTH_USER="${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}"
if test -z "$DNSEXIT_AUTH_USER"; then
DNSEXIT_AUTH_USER=""
_err 'DNSEXIT_AUTH_USER was not exported'
return 1
fi
_saveaccountconf_mutable DNSEXIT_AUTH_USER "$DNSEXIT_AUTH_USER"
DNSEXIT_AUTH_PASS="${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}"
if test -z "$DNSEXIT_AUTH_PASS"; then
DNSEXIT_AUTH_PASS=""
_err 'DNSEXIT_AUTH_PASS was not exported'
return 1
fi
_saveaccountconf_mutable DNSEXIT_AUTH_PASS "$DNSEXIT_AUTH_PASS"
return 0
}
+1 -1
View File
@@ -7,7 +7,7 @@ Options:
DNSHOME_Subdomain Subdomain
DNSHOME_SubdomainPassword Subdomain Password
Issues: github.com/acmesh-official/acme.sh/issues/3819
Author: dnsHome.de https://github.com/dnsHome-de
Author: @dnsHome-de
'
# Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+37 -8
View File
@@ -5,6 +5,7 @@ Site: DNSimple.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple
Options:
DNSimple_OAUTH_TOKEN OAuth Token
DNSimple_ACCOUNT_ID Account ID. Optional, only needed when the token can access multiple accounts.
Issues: github.com/pho3nixf1re/acme.sh/issues
'
@@ -17,6 +18,7 @@ dns_dnsimple_add() {
fulldomain=$1
txtvalue=$2
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
DNSimple_OAUTH_TOKEN=""
_err "You have not set the dnsimple oauth token yet."
@@ -25,10 +27,10 @@ dns_dnsimple_add() {
fi
# save the oauth token for later
_saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
_saveaccountconf_mutable DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
if ! _get_account_id; then
_err "failed to retrive account id"
_err "failed to retrieve account id"
return 1
fi
@@ -56,8 +58,14 @@ dns_dnsimple_add() {
dns_dnsimple_rm() {
fulldomain=$1
DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
_err "You have not set the dnsimple oauth token yet."
return 1
fi
if ! _get_account_id; then
_err "failed to retrive account id"
_err "failed to retrieve account id"
return 1
fi
@@ -122,13 +130,16 @@ _get_root() {
# returns _account_id
_get_account_id() {
_debug "retrive account id"
if ! _dnsimple_rest GET "whoami"; then
return 1
DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf_mutable DNSimple_ACCOUNT_ID)}"
if [ "$DNSimple_ACCOUNT_ID" ]; then
_saveaccountconf_mutable DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
_account_id="$DNSimple_ACCOUNT_ID"
_debug _account_id "$_account_id"
return 0
fi
if _contains "$response" "\"account\":null"; then
_err "no account associated with this token"
_debug "retrieve account id"
if ! _dnsimple_rest GET "whoami"; then
return 1
fi
@@ -137,7 +148,25 @@ _get_account_id() {
return 1
fi
if _contains "$response" "\"account\":null"; then
# the whoami of a user token (dnsimple_u_*) carries no account,
# so list the accounts the token can access instead
# https://github.com/acmesh-official/acme.sh/issues/6491
if ! _dnsimple_rest GET "accounts"; then
return 1
fi
fi
_account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1)
if [ -z "$_account_id" ]; then
_err "no account associated with this token"
return 1
fi
if [ "$(echo "$_account_id" | wc -l)" -gt 1 ]; then
_err "The token has access to multiple accounts, please pick one and set it explicitly:"
_err "export DNSimple_ACCOUNT_ID=<one of: $(echo "$_account_id" | tr '\n' ' ')>"
return 1
fi
_debug _account_id "$_account_id"
return 0
+1 -1
View File
@@ -5,7 +5,7 @@ Site: www.DuckDNS.org
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns
Options:
DuckDNS_Token API Token
Author: RaidenII
Author: @RaidenII
'
DuckDNS_API="https://www.duckdns.org/update"
+1 -1
View File
@@ -8,7 +8,7 @@ Options:
DYN_Customer Customer
DYN_Username API Username
DYN_Password Secret
Author: Gerd Naschenweng <https://github.com/magicdude4eva>
Author: Gerd Naschenweng <@magicdude4eva>
'
# Dyn Managed DNS API
+10 -6
View File
@@ -23,6 +23,8 @@ dns_dynu_add() {
fulldomain=$1
txtvalue=$2
Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId=""
Dynu_Secret=""
@@ -32,8 +34,8 @@ dns_dynu_add() {
fi
#save the client id and secret to the account conf file.
_saveaccountconf Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf Dynu_Secret "$Dynu_Secret"
_saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token."
@@ -69,6 +71,8 @@ dns_dynu_rm() {
fulldomain=$1
txtvalue=$2
Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId=""
Dynu_Secret=""
@@ -78,8 +82,8 @@ dns_dynu_rm() {
fi
#save the client id and secret to the account conf file.
_saveaccountconf Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf Dynu_Secret "$Dynu_Secret"
_saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
_saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token."
@@ -214,11 +218,11 @@ _dynu_authentication() {
response="$(_get "$Dynu_EndPoint/oauth2/token")"
if [ "$?" != "0" ]; then
_err "Authentication failed."
_err "Authentication failed: no response from $Dynu_EndPoint/oauth2/token"
return 1
fi
if _contains "$response" "Authentication Exception"; then
_err "Authentication failed."
_err "Authentication failed. Server response: $response"
return 1
fi
if _contains "$response" "access_token"; then
Loaded 100 of 188 files, more 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.