From cabe432539f0acbdac954c48175d20d259ab9aaf Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 5 Jul 2026 17:39:50 +0800 Subject: [PATCH] add bash completion for commands and parameters, installed via --install (#307) --- acme.sh | 11 ++ acme.sh.completion | 341 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 acme.sh.completion diff --git a/acme.sh b/acme.sh index 931ab750..4735b830 100755 --- a/acme.sh +++ b/acme.sh @@ -7236,6 +7236,10 @@ _installalias() { _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile" fi _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" + if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then + #the completion file does nothing when sourced by a non-bash shell + _setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\"" + fi _profile="$(_detect_profile)" if [ "$_profile" ]; then @@ -7353,6 +7357,11 @@ install() { _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" + if [ -f "$PROJECT_ENTRY.completion" ]; then + cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/" + _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion" + fi + if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then _installalias "$_c_home" fi @@ -7430,6 +7439,7 @@ uninstall() { _uninstallalias rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" + rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself." } @@ -7739,6 +7749,7 @@ Parameters: --config-home Specifies the home dir to save all the configurations. --useragent Specifies the user agent string. it will be saved for future use too. -m, --email Specifies the account email, only valid for the '--install' and '--update-account' command. + Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com' --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. Negative values could be used to specify a number of days relative to the expiration date of the certificate. diff --git a/acme.sh.completion b/acme.sh.completion new file mode 100644 index 00000000..26cb88da --- /dev/null +++ b/acme.sh.completion @@ -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 </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 </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 ".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