Commit Graph
761 Commits
  • deploy/ruckus: rename _post_file to avoid clashing with the core helper
    Core acme.sh now defines a _post_file() function. Functions and variables
    live in separate namespaces in POSIX sh, so this was not a real conflict,
    but the identical name is confusing to read. Use _post_upfile, matching the
    _post_action / _post_boundary / _post_data locals already in this function.
  • Add JetKVM SSH deploy hook (#7254)
    * Add JetKVM SSH deploy hook
    
    Adds deploy/jetkvm.sh to deploy a certificate to a JetKVM
    (https://jetkvm.com) KVM-over-IP device over plain SSH, writing the
    cert/key via a small POSIX shell script piped to the remote "sh" (JetKVM
    has no scp/SFTP server), staged under temp names and atomically renamed
    into place so a dropped connection can't leave the device with a
    mismatched cert/key pair for its own HTTPS listener. Defaults target
    JetKVM's confirmed "Custom" TLS storage path/filenames and default the
    post-upload command to "reboot", since JetKVM has no hot-reload for a new
    certificate.
    
    This factors out the SSH upload logic originally proposed in
    opnsense/plugins#5621 (an OPNsense ACME Client plugin automation) per
    maintainer feedback there, so it can be reused as a small config instead
    of plugin-specific code: https://github.com/opnsense/plugins/pull/5621#issuecomment-5570647257
    
    * Fix restart-command exit-code handling in jetkvm.sh
    
    The default restart command ("reboot") tears down the very SSH
    connection running it, which real hardware testing shows makes ssh's
    own exit code unreliable: it can come back as either a clean 0 or a
    connection-reset 255 for the exact same successful reboot depending on
    timing. The hook previously trusted that single exit code directly, so
    a fully successful, unattended cron renewal could be reported as a
    failed deploy.
    
    Split into two SSH calls: the first uploads and stages the cert/key and
    its exit code is trusted as-is (no reboot risk there). The second runs
    the restart command and is judged by whether a marker line printed
    *before* that command shows up in the captured output -- if the marker
    is missing, the call never really ran (real failure); if it's present,
    only a clean exit or 255 (connection dropped, expected) counts as
    success, while any other exit code is treated as the restart command's
    own genuine failure (e.g. 127 = command not found).
    
    Also: a blank DEPLOY_JETKVM_RESTART_CMD now falls back to "reboot"
    rather than silently skipping the restart -- previously there was no
    way to actually configure "no restart command", since the hook coerced
    any blank value (including one the user deliberately set) back to
    "reboot" on every run. Skipping it now requires the explicit sentinel
    DEPLOY_JETKVM_RESTART_CMD="none".
    
    * Verify JetKVM HTTPS Mode is "Custom" before uploading
    
    Uploading a certificate that the device's active HTTPS Mode won't even
    serve was previously a silent no-op -- the write would succeed but never
    take effect until a human noticed and fixed the mode themselves.
    
    JetKVM's own JSON-RPC getTLSState/setTLSState calls require an
    authenticated WebRTC session (see jetkvm/kvm#1240 and the still-open
    jetkvm/kvm#1515), so there's no documented/headless way to query this.
    Its firmware (web_tls.go / config.go in jetkvm/kvm) does persist the
    mode as a plain JSON field, "tls_mode" (values "", "self-signed", or
    "custom"), in /userdata/kvm_config.json -- confirmed against a real
    device, including that its busybox grep handles the -E/[[:space:]]
    regex used here.
    
    The check runs as the first step of the existing upload SSH call (no
    extra round trip), exits a dedicated code (3) if "tls_mode" isn't
    "custom", and the hook surfaces that as a specific, actionable error
    pointing at the device's web UI setting, distinct from a generic upload
    failure. Since the underlying config file/field is just as undocumented
    as everything else this hook depends on, DEPLOY_JETKVM_REQUIRE_CUSTOM_MODE=no
    opts out entirely in case a future firmware version changes the format.
    
    Also tightens two things noticed while adding this: the "Uploading
    certificate..." info log no longer prints before a call that might
    immediately fail the mode check, and the remote script's own error
    echo (redundant with the local hook's more detailed _err message) is
    dropped.
    
    Confirmed end-to-end against a real JetKVM device: the regex correctly
    matched the device's actual tls_mode=custom, and a full run of the
    updated hook (mode check included) succeeded.
    
    * Address maintainer review: hardcode firmware constants, drop local temp files, fix POSIX portability
    
    Per @neilpang's review (acmesh-official/acme.sh#7254):
    
    1. Drop [[:space:]]/-E from the tls_mode grep -- not portable (Solaris
       sed/grep read it as a literal bracket set); the compact and indented
       JSON forms are both covered by a plain space with '*'.
    2. Use the core _time() wrapper instead of `date +%s || echo 0` -- the
       fallback was dead code (a date binary that doesn't understand %s
       still exits 0), and _time() is the idiom every other hook/dnsapi
       script already uses for this.
    3. Drop the local temp files entirely for both the upload and restart
       SSH calls. The upload script is now built in a variable and piped
       directly into `ssh ... sh` (same pattern as deploy/windows_rdp.sh);
       $? after the pipeline is still ssh's own exit code. This keeps the
       private key off local disk and removes the _mktemp/chmod/rm dance.
    4. Refuse to deploy when the key or fullchain file is empty (e.g. a
       --signcsr-only run) instead of uploading an empty key file and
       rebooting the device.
    5. Use `printf '%s\n'`, not `echo`, for every generated script line --
       dash's echo interprets backslash escapes, so the remote script's
       content would otherwise depend on which /bin/sh happens to run
       acme.sh.
    6. Save DEPLOY_JETKVM_SSH_CMD and DEPLOY_JETKVM_RESTART_CMD with
       _savedeployconf's "base64" flag (as deploy/docker.sh does for its
       own reload command), since a value containing a single quote would
       otherwise break the saved domain.conf line.
    7. Distinguish "config file missing/unreadable" from "HTTPS Mode isn't
       Custom" with separate exit codes -- grep's own exit 2 for a missing
       file was previously funneled into the same "not custom" error,
       misdiagnosing the actual problem. Also stopped suggesting
       REQUIRE_CUSTOM_MODE=no in that error message: following it silently
       turns every future deploy into a no-op once persisted to domain.conf.
    8. Hardcode the remote path, filenames, chmod values and config file
       path as constants instead of DEPLOY_JETKVM_* variables. They're
       firmware facts on a single-purpose, single-root appliance, not user
       configuration -- and since _savedeployconf pins whatever value is
       first used into domain.conf, a firmware-side correction to one of
       these later would never reach anyone who'd already deployed once.
       Only USER/HOST/PORT/SSH_CMD/RESTART_CMD/REQUIRE_CUSTOM_MODE remain.
    9. Run the restart command detached (nohup sh -c 'sleep N; $CMD' &) so
       the ssh call returns as soon as it's launched, before the device
       actually reboots, instead of racing the connection teardown. This
       also removes the marker/case-based "0 or 255" exit-code logic
       entirely, along with the bug it had: a connection dropping after the
       marker printed but before the restart command actually ran was
       previously reported as a successful deploy. The tradeoff (also
       called out inline and in the PR description): a restart command that
       fails after being launched can no longer be detected, only a failure
       to launch it at all.
    10. Use fixed temp filenames for the staged cert/key (not one new name
        per run) plus a `trap ... EXIT` in the generated script, so any
        abort (the mode check, a write failure under `set -e`) cleans up
        instead of leaving another stray key-bearing file on the device.
    11. Trimmed the header: removed the marker/0|255 rationale (obsoleted by
        #9), corrected the "typically overnight" claim about when the
        restart actually runs, and added a wiki reference.
    
    Not yet done: a deployhooks wiki entry (acmesh-official/acme.sh#7254's
    point 12) -- flagged in the PR thread since only a repo collaborator can
    edit that wiki.
    
    Verified: shellcheck (no exclusions needed anymore) and shfmt -i 2
    clean; a local smoke-test harness (stubbed acme.sh core, a fake ssh
    that redirects the hardcoded device paths into a scratch directory)
    covering a clean deploy with byte-exact content/permissions and no
    leftover staged files, RESTART_CMD=none, HTTPS Mode not "custom",
    the config file missing entirely (now a distinct error),
    REQUIRE_CUSTOM_MODE=no, an empty key file (--signcsr case), and a
    failed restart-command launch.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01M7rTpUF3btoBXSZ95Psjh7
    
    * Fix restart-command quoting and correct the failure-detection comment
    
    Per @neilpang's second review round:
    
    1. DEPLOY_JETKVM_RESTART_CMD was interpolated unescaped inside the
       detached command's own single-quoted "sh -c '...'" wrapper. A value
       containing a single quote (e.g. "sh -c 'sync; reboot'") broke that
       quoting, splitting the string so only part of the intended command
       ran, un-detached. Escape embedded single quotes (the standard
       '\'' substitution) before nesting the value, matching how a value
       with no quotes at all still behaves identically. Verified against
       sh and dash directly, and with a new local smoke-test case that
       actually executes the generated detached command and confirms both
       halves of a quoted restart command run intact.
    
    2. The comment claiming "only a failure to launch it at all is caught
       below" was wrong: since the restart command runs as an unwaited
       background job (nohup ... &), the remote sh returns 0 as soon as
       that job is launched, regardless of whether nohup, sh, or the
       restart command itself actually exist or succeed -- measured 0 in
       both cases. Reworded so the comment describes what's actually
       caught (an outright SSH connection failure) instead of implying a
       guarantee the code doesn't provide. No behavior change from this
       half of the fix, comment-only.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01M7rTpUF3btoBXSZ95Psjh7
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
  • deploy/ssh: create the backup directory only when there is a file to back up
    Follow-up to 9249c892 (#7249). The mkdir -p ran unconditionally before
    the guarded cp calls, so a first deploy left an empty backup directory
    behind. Move the mkdir into each guarded block.
  • Truenas 26 deploy fixes (#7205)
    * Works with TrueNAS 26.0.0-BETA3 now
    
    * Updated to work with new and old versions of TrueNAS
    
    * shfmt fix for my changes
    
    ---------
    
    Co-authored-by: Bill Weiss <github@e.billweiss.net>
  • Both confirmed and fixed in dev.
    1. The four backup cp calls (KEYFILE/CERTFILE/CAFILE/FULLCHAIN) ran
       unguarded. With USE_SCP=yes MULTI_CALL is implicit, so each cp is its
       own ssh call and a missing source aborted the deploy. In batched mode
       it was masked because the exit code is that of the last command.
       Each cp is now wrapped in a remote [ -f ] test.
    2. deploy/ssh.sh tested DEPLOY_SSH_FULLCHAIN = "yes" instead of
       DEPLOY_SSH_MULTI_CALL (since 2017). Effect was only that the
       fullchain backup got deferred to the next batch. Fixed as well.
    
    Please upgrade with acme.sh --upgrade -b dev and retest.
  • TrueNAS deploy script witch websocat instead of Python midclt internal command (#7216)
    * Add files via upload
    
    TrueNAS deploy script for SCALE/CORE using websocket (websocat binary)
    It is recommend to use a wildcard certificate
    
    Tested with TrueNAS SCALE 25.10 (API "wss://host/api/current", JSON-RPC 2.0).
    Unlike "truenas_ws" hook, this script does NOT use midclt, the truenas_api_client Python package.
    It only depends on:
    - jq
    - websocat  (a static binary you deploy)
    
    Why: avoids installing a Python environment / TrueNAS package on OPNsense just to push a certificate.
    
    IMPORTANT: This script is written in pure POSIX sh (no coproc, no bash arrays).
    
    * Update truenas_websocat.sh
    
    Mistake on port and procotol.
    
    * Update truenas_websocat.sh
    
    Adjustment on the "Why"
    
    * Add files via upload
    
    * Update truenas_websocat.sh
    
    * Update truenas_websocat.sh
    
    Apply shellcheck disable=SC2016 to avoid false positive.
    
    * Update truenas_websocat.sh
  • unifios: extract JSON-split helper, document RSA/ECC name collision (#7200)
    * Extract _uos_split_json helper, document RSA/ECC name-prefix collision
    
    Per neilpang's non-blocking review notes on #7184: the _normalizeJson +
    split-into-lines block was duplicated at both call sites, now shared via
    _uos_split_json(). Also documents (without changing behavior, since it's
    harmless today) that an RSA and ECC deploy of the same domain share the
    generated name's prefix, each removing the other's entry on cleanup --
    citing haproxy.sh/lighttpd.sh's existing .rsa/.ecdsa suffix pattern as
    the fix if this ever needs addressing.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    * Replace grep -F with a portable matcher, fix RSA/ECC name collision
    
    grep -F isn't on Solaris, and dropping it naively breaks matching:
    wildcard domains and dots collide as regex. _uos_grep_literal replaces
    both call sites with a case-based literal match instead.
    
    _uos_name now includes the key type, so RSA and ECC deploys of the
    same domain no longer share a cleanup scope.
    
    Per neilpang's review on #7200.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    * Fix echo's \n handling in _uos_grep_literal, drop unneeded Le_Keylength guard
    
    echo does not behave consistently across different environments. dash
    interprets literal \n in a line, splitting it.  printf '%s\n' does not and matches
    _uos_split_json's existing pattern. printf behaves more consistently across
    environments and is generally preferred over echo.
    
    Le_Keylength guard was a no-op and didn't help under set -u either;
    _isEccKey already handles empty. Kept the shellcheck warning suppressed
    inline instead of assigning to a core Le_* var.
    
    Per neilpang's review on #7200.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
  • deploy/unifios: document UniFi OS hardware support, not just self-hosted
    The certificate REST API this hook drives is UniFi OS's own, not
    specific to the self-hosted UniFi OS Server: user reports confirm it on
    a UDM Pro (UniFi OS 5.1.26) and a UCG Fiber (5.0.16). Reframe the scope
    around the endpoint rather than the product line, state that the choice
    between unifi and unifios is local/SSH file access vs remote REST API,
    and note that the management port is 11443 on UniFi OS Server but 443
    on hardware, so DEPLOY_UNIFIOS_HOST must be set there.
  • Add UniFi OS Server deploy hook (#7184)
    * Add UniFi OS Server deploy hook
    
    Uses UniFi OS Server's local REST API (login, list, upload, activate,
    remove superseded) since it stores certificates in its own Postgres
    database rather than flat config files, unlike the Cloud Key/UDM
    hardware covered by the existing unifi deploy hook. Tested against
    real instances on both macOS and Ubuntu 26.04 (self-hosted, remote).
    
    * Address review: portable sed/grep, scoped HTTPS_INSECURE, fingerprint matching
    
    - Replace GNU-only \n in sed replacement with a portable literal newline
      (matches dnsapi/dns_cpanel_uapi.sh, dnsapi/dns_glesys.sh); pipe the
      list response through _normalizeJson first for consistent formatting.
    - Use grep -F for the domain-name match instead of an unescaped BRE --
      a wildcard cert name (*.example.com) broke the regex.
    - Drop \W (undocumented, GNU-only) from the cookie lookup in favor of
      an anchored `^Set-Cookie: *NAME=` match.
    - Scope HTTPS_INSECURE=1 inside the hook (matches deploy/proxmoxve.sh,
      deploy/fritzbox.sh) instead of requiring the caller to export it for
      the whole acme.sh run, which would also disable verification for the
      connection to the ACME CA.
    - On a duplicate-certificate response, match the existing entry by
      fingerprint instead of taking the first name match -- with more than
      one stale entry for a domain, the wrong one could get activated.
    - Check the list endpoint's response code before proceeding.
    - Save username/password with the "base64" flag (matches
      deploy/synology_dsm.sh) since _save_conf wraps values in unescaped
      single quotes.
    
    * Rework certificate handling: unique names per upload, drop cleanup
    
    Testing against a real UniFi OS Server showed the server enforces name
    uniqueness independently of fingerprint uniqueness, and that activation is
    exclusive server-wide regardless of name/domain. A unique name per upload
    avoids the name-collision path entirely (previously only handled as a
    retry-of-identical-content edge case), and removes the need for the
    post-hoc cleanup loop, which risked deleting the wrong entry.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    * Shorten generated certificate name to Unix epoch seconds
    
    Real-hardware testing showed the UniFi OS Server certificate list's name
    column is fixed-width and doesn't wrap, so a full human-readable timestamp
    overlaps the Expires column and makes both unreadable. Epoch seconds are
    still short enough to fit while remaining unique.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    * Add scoped cleanup of old certificate entries, use _time helper
    
    Per review: dropping cleanup entirely went further than the original bug
    required, and left old entries (each holding a private key) accumulating
    indefinitely. Since every upload now gets a name unique to its domain and
    run, cleanup can safely target only entries whose name starts with that
    domain -- entries this hook itself created -- excluding the one just
    activated. Also swaps date +%s for the core _time helper, and rewrote the
    design comments to make them clearer and match the current behavior
    instead of the pre-redesign one.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
  • add deploy hook support for ikuai (#6456)
    * add deploy hook support for ikuai
    
    * fix shellcheck warn and shfmt the code
    
    * 1.fix config load 2.use _secre_debug2 to log password 3.use fullchain to deploy 4.fix hardcode id 5.change shebang
    
    * fix miss ; after cookie
    
    * 1.fix shfmt ; 2.fix IKUAI_CERT_ID conf load; 3.correct IKUAI_CERT_ID description
    
    * fix some log msg
    
    * fix shfmt
  • 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: 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
  • 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
  • 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.
  • 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
  • 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.
  • 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.
  • 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
  • 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
  • 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
  • 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
  • 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.
  • 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>
  • 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.
  • 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
  • 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
  • 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>
  • Fix typo in synology_dsm.sh (#6406)
    Fix typo in an error message.
  • 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>
  • Fix RouterOS deploy (#7034)
    * routeros: save ROUTER_OS_ADDITIONAL_SERVICES as base64
    
    * routeros: remove cer_3
  • Add deployment plugin for Windows RDP via OpenSSH (#6925)
    * Add deployment plugin for Windows RDP via OpenSSH
  • Merge pull request #6889 from achmadalifn4/dev
    Add BytePlus ALB deployment script
  • 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
  • Add new header variable _H5 in byteplus_alb.sh
    Added a new header variable _H5 to the byteplus_alb.sh script.