Commit Graph
6818 Commits
  • Fix multideploy MULTIDEPLOY_FILENAME conf read and allow an absolute path
    _getdeployconf assigns and exports the variable, it does not print the
    value, so wrapping it in a command substitution ran it in a subshell and
    always yielded an empty string. A MULTIDEPLOY_FILENAME saved by an
    earlier run was therefore never restored on renewal and the hook
    silently fell back to multideploy.yml. Call it the same way every other
    deploy hook does.
    
    Also treat a MULTIDEPLOY_FILENAME starting with '/' as an absolute path
    instead of always resolving it under DOMAIN_PATH, so one deploy file can
    live outside the certificate directory and be shared by all domains.
    Names without a leading '/' keep resolving under DOMAIN_PATH as before.
  • Fix synology_dsm logging out after the temp admin is already deleted
    _temp_admin_cleanup ran before _logout, so the logout request carried
    the session id of an account synouser had already removed and DSM kept
    the orphaned entry in Connected Users. Swap the order in both terminal
    branches, and add the missing _logout to the two post-login error paths
    (CRT list failure, certificate not found without SYNO_CREATE).
    
    _logout overwrites the global $response, so the upload-failure branch
    prints its error message before calling it.
    
    Reported by @Bertl75 in #7174
  • Fix empty finalize URL when resuming a saved DNS-manual order
    The decision to resume a pending order is keyed on Le_Vlist, but the
    decision to keep Le_OrderFinalize/Le_LinkOrder was keyed on the webroot
    being exactly "dns". Any other webroot with a saved Le_Vlist skipped
    newOrder and then finalized against an empty URL.
    
    Key both on Le_Vlist, and always clear Le_LinkCert, which is per-run
    state that is never read back from the saved domain conf.
    
    Fixes #7177
  • Fix dns_cyon cleanup failing on FreeBSD
    _cyon_delete_txt relied on `printf "%b"` to convert a sed-injected literal
    `\n` into a real newline, but `%b` also processes the `\"` escapes that the
    JSON response is full of. glibc/bash/dash keep the backslash of such an
    undefined escape, FreeBSD's printf (sh builtin and /usr/bin/printf alike)
    drops it -- so `data-hash=\"..\"` became `data-hash=".."`, the extraction
    regex matched nothing, _dns_entries stayed empty and no TXT record was ever
    deleted.
    
    Drop the newline injection and use _egrep_o, which already yields one match
    per line, then parse each line with sed.
    
    Also feed the read loop a newline-terminated list: `printf "%s"` left the
    last line unterminated, so `read` returned non-zero at EOF and the loop
    skipped the final entry on every platform.
    
    Verified identical output on FreeBSD 14.3, Linux/bash and Linux/dash.
    
    Fixes #7169
  • Fix --make-dns-persist-value printing a wildcard TXT record name
    For -d '*.example.com' the printed record name kept the literal '*' label
    (_validation-persist.*.example.com). The CA never queries that name, so
    issuance fails with "No TXT record found for DNS-PERSIST-01 challenge".
    
    Per draft-ietf-acme-dns-persist-01 sec 4 and 10.2 the record is published at
    the base domain's Validation Domain Name; the wildcard scope comes from
    'policy=wildcard' in the record value (sec 5.1), not from a '*' label in the
    record name. Strip the leading "*." in a new _dns_persist_txt_name helper,
    and imply --dns-persist-wildcard for a wildcard -d, since without
    policy=wildcard the printed record can never authorize the wildcard.
    
    Fixes #7168
  • Refactor dns_freemyip.sh for enhanced compatibility (#7166)
    * Refactor dns_freemyip.sh for clarity and compatibility
    
    Updated dns_freemyip.sh for better readability and compatibility with ASUSWRT-Merlin. Improved error handling and response logging.
    
    * Update author information in dns_freemyip.sh
    
    * replace both loops with POSIX shell counters
    
    replace both loops with POSIX shell counters
    
    * Typo
    
    Typo
    
    * Fix error message for freemyip API request failure
    
    Remove existing token leak. Not my regression.
    
    * Refactor retry logic and improve error handling
    
    * Remove unnecessary blank lines in dns_freemyip.sh
    
    * Clean up dns_freemyip.sh by removing blank lines
    
    Removed unnecessary blank lines in the script to improve readability.
  • dns_yc: fix TXT record removal failing with "Unknown key file format" (#7150)
    * dns_yc: restore YC_SA_Key_File in dns_yc_rm before signing the JWT
    
    dns_yc_rm() never rebuilt YC_SA_Key_File from YC_SA_Key_File_PEM_b64 /
    YC_SA_Key_File_Path like dns_yc_add() does. Per the DNS API dev guide,
    add()/rm() run in separate subshells, so rm() must repeat add()'s setup
    steps rather than rely on variables set during add().
    
    Without it, when _yc_login() needs a fresh JWT during removal (the IAM
    token from the add phase isn't available), it signs with an empty/unset
    key path, and openssl fails with "Unknown key file format". The
    resulting auth failure then surfaces misleadingly as "invalid domain" in
    _get_root, and the TXT record is never deleted.
    
    Verified against a real Yandex Cloud account/zone with --staging: before
    the fix, removal failed with the same errors reported in the issue;
    after adding the missing key-restoration block, add + remove both
    succeed and the TXT record is actually deleted.
    
    * dns_yc: preserve other TXT values when removing one at the same name
    
    dns_yc_rm previously sent the full current data array (all existing
    TXT values at the name) to the deletions API, wiping out the whole
    rrset instead of only the value being removed. This breaks wildcard +
    base domain issuance, where both share the same _acme-challenge name
    with two different values: removing the first one deleted both,
    leaving nothing for the second removal to find.
    
    * dns_yc: read persisted config from domain conf before account conf
    
    YC_Zone_ID, YC_Folder_ID, YC_SA_ID, YC_SA_Key_ID (zone-ID mode) and
    YC_SA_Key_File_PEM_b64/Path were always saved via _savedomainconf
    (domain.conf), but only ever read back via _readaccountconf_mutable
    (account.conf). Once the env vars were unset, none of these could be
    recovered from the saved config, so dns_yc_add/dns_yc_rm failed with
    "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
    even though the values had been persisted correctly on the prior run.
    
    * dns_yc: replace grep -Fxv/sed with a portable loop in dns_yc_rm
    
    Solaris's /usr/bin/grep supports neither -F nor -x, so
    _remaining_txtvalue was always empty there and the preserve-other-
    values logic silently fell back to deleting the whole rrset (with a
    grep usage error on stderr on every rm). The sed trailing-comma strip
    had a matching issue on Solaris, whose sed drops an unterminated last
    line. CI didn't catch this because the fallback path also returns
    "done: true". Use a plain for-loop with word splitting instead.
    
    * dns_yc: use upsertRecordSets.deletions to remove a single TXT value
    
    updateRecordSets has no "merges" field (only deletions/additions), so
    the previous preserve-other-values logic silently did nothing -- the
    TXT record was never actually removed, a regression from before that
    change (which at least deleted the whole rrset). CI didn't catch it
    because _clearupdns runs dns_yc_rm in a subshell and ignores its exit
    code.
    
    upsertRecordSets.deletions removes only the specified value from the
    rrset directly, so the getRecordSet read and the remaining-value
    recomputation are no longer needed at all.
    
    Verified against a real zone (base + wildcard domain sharing one
    _acme-challenge name): adding both values then removing one leaves
    the other in place, and removing the second cleans up fully.
    
    * dns_yc: don't delete the user's own key file in YC_SA_Key_File_Path mode
    
    _yc_login unconditionally rm'd $YC_SA_Key_File after signing. That's
    fine for the PEM_b64 path, where it's a decoded temp file, but in
    YC_SA_Key_File_Path mode it's the user's own persistent key file --
    the first successful login permanently deleted it, so every
    subsequent dns_yc_rm/renewal hit "Unknown key file format" (the exact
    symptom this PR is about, just from a different cause). Track whether
    the key file is our own temp copy and only delete it in that case.
    
    Verified with a stubbed _yc_login: a temp-mode key gets removed after
    login, a path-mode key survives.
    
    * dns_yc: clear both domain and account conf on invalid config
    
    The failure branch in dns_yc_add only ever called _clearaccountconf,
    but YC_Zone_ID/YC_Folder_ID/YC_SA_Key_File_PEM_b64/Path are persisted
    via _savedomainconf, and YC_SA_ID/YC_SA_Key_ID may have been saved via
    _saveaccountconf_mutable (Folder_ID mode, which stores under a
    SAVED_ prefix read back by _readaccountconf_mutable). Clearing only
    one store left stale values behind in whichever one wasn't touched.
    
    Verified by seeding both domain.conf and account.conf with leftover
    values, then triggering this branch and confirming both config files
    end up empty.
  • fix: grep -A is not portable, breaks ARI on Solaris
    Solaris /usr/bin/grep has no -A ("illegal option -- A"), so _getAKI
    printed an error to stderr on every cron renewal and returned empty.
    The empty AKI silently corrupts the RFC 9773 ARI certID, so ARI is
    never available and renewal falls back to the fixed schedule.
    
    Split the pipeline into a testable stdin filter _extractAKI and select
    the value line with a portable sed range instead.
    
    Same fix for the two hooks that still used grep -A: dns_world4you.sh
    (also replaces the GNU-only "\s" in the same expression) and
    deploy/keyhelp.sh (the -A 2 window could truncate the div range that
    follows it, so it is just dropped).
    
    https://github.com/acmesh-official/acme.sh/issues/7159
  • fix dns_yc: avoid empty-matchable _egrep_o pattern that hangs OmniOS
    OmniOS native egrep -o infinite-loops emitting empty lines when the
    pattern can match the empty string, so `_egrep_o "[^:]*$"` never lets
    the pipeline finish and dns_yc hangs until the CI timeout. Require at
    least one character instead. `+` is not usable because the sed fallback
    in _egrep_o parses BRE.
  • Feat: Shelly deploy hook for firmware 2.0.0+ (#7145)
    * feat: add Shelly Gen3+ deploy hook with RFC 7616 HTTP Digest auth
    
    Adds deploy/shelly.sh for deploying Let's Encrypt HTTPS server certificates
    to Shelly Gen3+ devices (Gen4 tested) via JSON-RPC over HTTP.
    
    - RFC 7616 SHA-256 HTTP Digest authentication (Authorization header)
    - Uploads fullchain.pem and private key via Shelly.PutHTTPServerCert / PutHTTPServerKey
    - Auto-reboot support (SHELLY_REBOOT to disable)
    - Auth auto-detection: no password = no auth, password = Digest
    - Nonce counter (nc) increments per request per RFC 7616
    - Tested against Shelly 2PM Gen4 (firmware 2.0.0)
    
    Also adds deploy/test_shelly.sh for self-testing the hook logic without
    a real device (mocked _post).
    
    * fix: address review feedback on shelly deploy hook
    
    - Fix _secure_debug calls to use two arguments (label + value)
    - Remove bash-only $RANDOM cnonce fallback; openssl always available
    - Parse $HTTP_HEADER directly instead of raw curl re-request
    - Detect auth via HTTP 401 status line, not empty response body
    - Route reboot through _shelly_rpc to rebuild auth header with correct nc
    - Remove export HTTPS_INSECURE=1 (no-op for http://, leaks to other hooks)
    - Clear _H1 before returning from shelly_deploy
    - Prefix all helper variables with _shelly_ to avoid namespace collisions
    - Delete deploy/test_shelly.sh (deploy/ files become hook names)
    - Fix missing trailing newline
    
    * fix: validate shelly JSON-RPC responses are valid JSON
    
    Non-JSON responses like HTTP 429 'Too Many Requests' would pass
    the empty-response and '"error"' checks and be reported as success.
    Now reject any response that doesn't start with '{' and contain '"id"'.
    
    * fix: add 1s delay between shelly cert/key clear and upload calls
    
    The Shelly device has a race condition where uploading data immediately
    after clearing the existing cert/key returns -103 'Missing required
    argument data!'. A 1-second delay fixes this.
    
    * fix: remove clear-before-upload in shelly deploy hook
    
    Shelly auto-removes all three TLS files (cert, key, CA bundle) when any
    single one is cleared. The old sequence clear-cert → upload-cert →
    clear-key → upload-key resulted in the key clear wiping the newly
    uploaded cert, leaving only the key at boot time. The mbedtls
    pk_check_pair then silently skipped the HTTPS listener.
    
    Fix: just upload directly (overwrite in place). No clearing needed.
    
    * Fix ShellCheck SC2090 and shfmt in shelly deploy hook
    
    SC2090: false positive on export _H1 (used quoted in _post)
    shfmt: no space after "<" in _json_encode redirects
    
    * moved  two lines to cover the whole if block
    
    ---------
    
    Co-authored-by: neil <github@neilpang.com>
    Co-authored-by: cysimons <cysimons@cisco.com>
  • fix proxmoxve/proxmoxbs deploy: fail on non-2xx API response
    The success check only grepped "message" from the response body, but
    PVE/PBS auth failures return HTTP 401 with an empty body, so wrong or
    unauthorized API tokens were reported as "Certificate successfully
    deployed". Also _retval captured the exit code of the message pipeline
    instead of _post. Check the HTTP status line from $HTTP_HEADER and
    capture _post's exit code directly.
    
    fix https://github.com/acmesh-official/acme.sh/issues/7141
  • fix cpanel_uapi: pass --user to DomainInfo list_domains when run as root
    The auto mode sitelist query was missing the --user branch that the
    install_ssl calls already have, so deploy always failed under root.
    fix https://github.com/acmesh-official/acme.sh/issues/7139
  • feat: add ACME_PACKAGED for distro-packaged installs
    When ACME_PACKAGED is set (e.g. exported by a distro package wrapper):
    - --install does not copy the script or the hooks into LE_WORKING_DIR;
      the cron job and the shell alias point to the packaged script instead
    - --upgrade, --install-online and the cron AUTO_UPGRADE path refuse and
      point to the system package manager
    - --uninstall does not remove the packaged files
    
    https://github.com/acmesh-official/acme.sh/issues/7135
  • fix bug for solaris.
    dnsapi/deploy: remove POSIX character classes from sed/grep patterns
    
    Solaris /usr/bin/sed and /usr/bin/grep parse [[:space:]] etc. as a
    literal bracket set and silently mis-match. Replace with [ ]* for
    JSON matching, a printf-tab bracket for user-input trimming, and
    [0-9] for digits; also drop GNU-only sed -r/-E in rage4, selfhost
    and selectel, and reuse _strip_blank_lines in byteplus_alb.
  • Add newline at end of waha.sh
    Fix missing newline at end of file.
  • 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
  • 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>
  • 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
  • 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).
  • 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
  • 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
  • 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).
  • fix
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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.
  • 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>
  • 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.