mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 02:06:16 +00:00
1685 lines
54 KiB
Bash
Executable File
1685 lines
54 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
readonly EPCTL_NAME="epctl"
|
||
readonly REPO_OWNER="GMWalletApp"
|
||
readonly REPO_NAME="epusdt"
|
||
readonly SERVICE_NAME="epusdt"
|
||
readonly SYSTEM_USER="epusdt"
|
||
readonly INSTALL_DIR="/opt/epusdt"
|
||
readonly BIN_PATH="${INSTALL_DIR}/epusdt"
|
||
readonly CONFIG_PATH="${INSTALL_DIR}/.env"
|
||
readonly CONFIG_EXAMPLE_PATH="${INSTALL_DIR}/.env.example"
|
||
readonly WWW_PATH="${INSTALL_DIR}/www"
|
||
readonly CACHE_ROOT="/tmp/epusdt"
|
||
readonly SYSTEMD_UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
||
readonly SELF_INSTALL_PATH="/usr/local/bin/${EPCTL_NAME}"
|
||
readonly DEFAULT_APP_URI="http://127.0.0.1:8000"
|
||
readonly DEFAULT_LISTEN="127.0.0.1:8000"
|
||
readonly SCRIPT_SOURCE_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
|
||
|
||
COLOR_RESET=""
|
||
COLOR_BOLD=""
|
||
COLOR_DIM=""
|
||
COLOR_CYAN=""
|
||
COLOR_GREEN=""
|
||
COLOR_YELLOW=""
|
||
COLOR_RED=""
|
||
COLOR_BLUE=""
|
||
|
||
ACTIVE_LANG=""
|
||
POSITIONAL_ARGS=()
|
||
|
||
detect_default_language() {
|
||
local raw normalized
|
||
|
||
raw="${EPCTL_LANG:-}"
|
||
if [[ -z "${raw}" ]]; then
|
||
printf 'zh\n'
|
||
return
|
||
fi
|
||
|
||
normalized="${raw,,}"
|
||
case "${normalized}" in
|
||
zh|zh-*|zh_*|cn|chs|cht|中文)
|
||
printf 'zh\n'
|
||
;;
|
||
en|en-*|en_*|english)
|
||
printf 'en\n'
|
||
;;
|
||
*)
|
||
printf '%s\n' "${raw}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
set_language() {
|
||
local requested normalized fallback_lang
|
||
|
||
requested="${1:-$(detect_default_language)}"
|
||
normalized="${requested,,}"
|
||
fallback_lang="${ACTIVE_LANG:-$(detect_default_language)}"
|
||
case "${normalized}" in
|
||
zh|zh-*|zh_*|cn|chs|cht|中文)
|
||
ACTIVE_LANG="zh"
|
||
;;
|
||
en|en-*|en_*|english)
|
||
ACTIVE_LANG="en"
|
||
;;
|
||
*)
|
||
ACTIVE_LANG="${fallback_lang}"
|
||
die "$(trf unsupported_language "${requested}")"
|
||
;;
|
||
esac
|
||
|
||
export EPCTL_LANG="${ACTIVE_LANG}"
|
||
}
|
||
|
||
trf() {
|
||
local key="$1"
|
||
shift || true
|
||
local template
|
||
|
||
case "${ACTIVE_LANG}:${key}" in
|
||
en:label_warn) template='warn' ;;
|
||
zh:label_warn) template='警告' ;;
|
||
en:label_ok) template='ok' ;;
|
||
zh:label_ok) template='完成' ;;
|
||
en:label_error) template='error' ;;
|
||
zh:label_error) template='错误' ;;
|
||
en:only_linux) template='this script only supports Linux' ;;
|
||
zh:only_linux) template='本脚本仅支持 Linux' ;;
|
||
en:missing_dependency) template='missing dependency: %s' ;;
|
||
zh:missing_dependency) template='缺少依赖:%s' ;;
|
||
en:root_required) template='this command must be run as root' ;;
|
||
zh:root_required) template='此命令必须以 root 身份执行' ;;
|
||
en:release_tag_required) template='release tag is required' ;;
|
||
zh:release_tag_required) template='必须提供 release tag' ;;
|
||
en:release_tag_invalid) template='--tag must match a release tag such as v1.0.6' ;;
|
||
zh:release_tag_invalid) template='--tag 必须是有效的 release tag,例如 v1.0.6' ;;
|
||
en:unsupported_arch) template='unsupported architecture: %s' ;;
|
||
zh:unsupported_arch) template='不支持的架构:%s' ;;
|
||
en:checksum_missing) template='checksum entry for %s not found in %s' ;;
|
||
zh:checksum_missing) template='在 %s 中找不到 %s 的校验项' ;;
|
||
en:latest_release_resolve_failed) template='failed to resolve the latest release tag from GitHub' ;;
|
||
zh:latest_release_resolve_failed) template='无法从 GitHub 解析最新 release tag' ;;
|
||
en:using_latest_without_confirmation) template='using latest release tag %s without confirmation because EPCTL_ASSUME_YES=1' ;;
|
||
zh:using_latest_without_confirmation) template='检测到 EPCTL_ASSUME_YES=1,直接使用最新 release tag %s,不再二次确认' ;;
|
||
en:latest_tag_no_tty) template='no --tag provided and no interactive terminal available; pass --tag explicitly or set EPCTL_ASSUME_YES=1' ;;
|
||
zh:latest_tag_no_tty) template='未提供 --tag,且当前不是交互终端;请显式传入 --tag,或设置 EPCTL_ASSUME_YES=1' ;;
|
||
en:latest_prompt) template='latest release tag is %s. Continue? [y/N] ' ;;
|
||
zh:latest_prompt) template='检测到最新 release tag 为 %s,是否继续?[y/N] ' ;;
|
||
en:cancelled) template='cancelled' ;;
|
||
zh:cancelled) template='已取消' ;;
|
||
en:cached_release_files) template='using cached release files in %s' ;;
|
||
zh:cached_release_files) template='使用缓存中的 release 文件:%s' ;;
|
||
en:downloading_archive) template='downloading %s' ;;
|
||
zh:downloading_archive) template='正在下载 %s' ;;
|
||
en:checksum_skipped) template='sha256sum not found; skipped checksum verification' ;;
|
||
zh:checksum_skipped) template='未检测到 sha256sum,已跳过校验' ;;
|
||
en:archive_missing_binary) template='release archive is missing the epusdt binary' ;;
|
||
zh:archive_missing_binary) template='release 压缩包中缺少 epusdt 二进制文件' ;;
|
||
en:archive_missing_env_example) template='release archive is missing .env.example' ;;
|
||
zh:archive_missing_env_example) template='release 压缩包中缺少 .env.example' ;;
|
||
en:keeping_existing_config) template='keeping existing config at %s' ;;
|
||
zh:keeping_existing_config) template='保留现有配置文件:%s' ;;
|
||
en:service_failed_active) template='service %s failed to become active' ;;
|
||
zh:service_failed_active) template='服务 %s 未能成功进入 active 状态' ;;
|
||
en:service_installed) template='installed %s to %s from %s' ;;
|
||
zh:service_installed) template='已将 %s 安装到 %s,来源版本:%s' ;;
|
||
en:service_upgraded_restarted) template='updated %s in %s from %s and restarted the service' ;;
|
||
zh:service_upgraded_restarted) template='已将 %s 在 %s 升级到 %s,并完成服务重启' ;;
|
||
en:upgrade_existing_config_required) template='upgrade requires an existing config file at %s; run install first' ;;
|
||
zh:upgrade_existing_config_required) template='升级要求 %s 已存在;请先执行 install' ;;
|
||
en:upgrade_restart_flag_conflict) template='cannot combine --prompt-restart and --no-restart' ;;
|
||
zh:upgrade_restart_flag_conflict) template='--prompt-restart 和 --no-restart 不能同时使用' ;;
|
||
en:upgrade_deploy_failed_rollback_start) template='upgrading %s failed before restart; attempting to roll back the release files' ;;
|
||
zh:upgrade_deploy_failed_rollback_start) template='升级 %s 在重启前失败,正在尝试回滚 release 文件' ;;
|
||
en:upgrade_deploy_failed_rolled_back) template='upgrading %s to %s failed before restart; previous release files were restored' ;;
|
||
zh:upgrade_deploy_failed_rolled_back) template='升级 %s 到 %s 在重启前失败,已恢复到之前的 release 文件' ;;
|
||
en:upgrade_deploy_failed_rollback_failed) template='upgrading %s to %s failed before restart, and rollback also failed' ;;
|
||
zh:upgrade_deploy_failed_rollback_failed) template='升级 %s 到 %s 在重启前失败,且回滚也失败了' ;;
|
||
en:upgrade_restart_failed_rollback_start) template='restart failed after upgrading %s; attempting to roll back the release files' ;;
|
||
zh:upgrade_restart_failed_rollback_start) template='升级 %s 后重启失败,正在尝试回滚 release 文件' ;;
|
||
en:upgrade_restart_failed_rolled_back) template='restart failed after upgrading %s to %s; previous release files were restored' ;;
|
||
zh:upgrade_restart_failed_rolled_back) template='升级 %s 到 %s 后重启失败,已恢复到之前的 release 文件' ;;
|
||
en:upgrade_restart_failed_rollback_failed) template='restart failed after upgrading %s to %s, and rollback also failed' ;;
|
||
zh:upgrade_restart_failed_rollback_failed) template='升级 %s 到 %s 后重启失败,且回滚也失败了' ;;
|
||
en:self_install_current) template='%s already points to the current script' ;;
|
||
zh:self_install_current) template='%s 已经指向当前脚本,无需重复安装' ;;
|
||
en:self_install_done) template='installed %s into /usr/local/bin' ;;
|
||
zh:self_install_done) template='已将 %s 安装到 /usr/local/bin' ;;
|
||
en:config_not_found) template='config file not found: %s' ;;
|
||
zh:config_not_found) template='找不到配置文件:%s' ;;
|
||
en:http_listen_not_found) template='http_listen not found in %s' ;;
|
||
zh:http_listen_not_found) template='在 %s 中找不到 http_listen' ;;
|
||
en:http_listen_parse_failed) template='failed to parse the port from http_listen=%s' ;;
|
||
zh:http_listen_parse_failed) template='无法从 http_listen=%s 中解析端口' ;;
|
||
en:curl_partial_body) template='curl failed and returned a partial response body:' ;;
|
||
zh:curl_partial_body) template='curl 请求失败,并返回了部分响应体:' ;;
|
||
en:init_password_http_failed) template='init-password request failed with HTTP %s: %s' ;;
|
||
zh:init_password_http_failed) template='init-password 请求失败,HTTP %s:%s' ;;
|
||
en:response_body_label) template='response body:' ;;
|
||
zh:response_body_label) template='响应体:' ;;
|
||
en:response_body_empty) template='response body is empty' ;;
|
||
zh:response_body_empty) template='响应体为空' ;;
|
||
en:lines_positive_integer) template='--lines must be a positive integer' ;;
|
||
zh:lines_positive_integer) template='--lines 必须是正整数' ;;
|
||
en:state_not_installed) template='not installed' ;;
|
||
zh:state_not_installed) template='未安装' ;;
|
||
en:state_no_systemctl) template='unit exists, but systemctl is unavailable' ;;
|
||
zh:state_no_systemctl) template='unit 文件已存在,但 systemctl 不可用' ;;
|
||
en:state_no_systemd) template='unit exists, but systemd is unavailable' ;;
|
||
zh:state_no_systemd) template='unit 文件已存在,但 systemd 不可用' ;;
|
||
en:state_active) template='active' ;;
|
||
zh:state_active) template='运行中' ;;
|
||
en:state_inactive) template='installed but inactive' ;;
|
||
zh:state_inactive) template='已安装,但未运行' ;;
|
||
en:menu_title) template='EPCTL Console' ;;
|
||
zh:menu_title) template='EPCTL 控制台' ;;
|
||
en:menu_subtitle) template='Linux binary manager for epusdt' ;;
|
||
zh:menu_subtitle) template='面向 epusdt 的 Linux 二进制管理工具' ;;
|
||
en:label_service) template='service' ;;
|
||
zh:label_service) template='服务状态' ;;
|
||
en:label_install_dir) template='install dir' ;;
|
||
zh:label_install_dir) template='安装目录' ;;
|
||
en:label_config) template='config' ;;
|
||
zh:label_config) template='配置文件' ;;
|
||
en:menu_section_setup) template='Setup and release management' ;;
|
||
zh:menu_section_setup) template='部署与发布' ;;
|
||
en:menu_section_runtime) template='Runtime inspection' ;;
|
||
zh:menu_section_runtime) template='运行查看' ;;
|
||
en:menu_section_tools) template='Language and help' ;;
|
||
zh:menu_section_tools) template='语言与帮助' ;;
|
||
en:menu_badge_direct) template='direct' ;;
|
||
zh:menu_badge_direct) template='直达' ;;
|
||
en:menu_badge_confirm) template='confirm' ;;
|
||
zh:menu_badge_confirm) template='确认' ;;
|
||
en:menu_exit) template='Quit' ;;
|
||
zh:menu_exit) template='退出' ;;
|
||
en:menu_hint_direct) template='Direct items run immediately after selection.' ;;
|
||
zh:menu_hint_direct) template='带 [直达] 的项目会在选择后直接执行。' ;;
|
||
en:menu_hint_confirm) template='Confirm items show an extra confirmation step first.' ;;
|
||
zh:menu_hint_confirm) template='带 [确认] 的项目会先显示确认步骤。' ;;
|
||
en:command_completed) template='command completed' ;;
|
||
zh:command_completed) template='命令执行完成' ;;
|
||
en:command_failed_exit) template='command failed with exit code %s' ;;
|
||
zh:command_failed_exit) template='命令执行失败,退出码:%s' ;;
|
||
en:invalid_selection) template='invalid selection: %s' ;;
|
||
zh:invalid_selection) template='无效选择:%s' ;;
|
||
en:arg_requires_value) template='%s requires a value' ;;
|
||
zh:arg_requires_value) template='%s 需要一个参数值' ;;
|
||
en:unknown_arg) template='unknown argument for %s: %s' ;;
|
||
zh:unknown_arg) template='%s 命令不支持该参数:%s' ;;
|
||
en:unknown_command) template='unknown command: %s' ;;
|
||
zh:unknown_command) template='未知命令:%s' ;;
|
||
en:input_release_tag) template='Release tag (blank = latest)' ;;
|
||
zh:input_release_tag) template='Release tag(留空则使用 latest)' ;;
|
||
en:input_upgrade_tag) template='Upgrade tag (blank = latest)' ;;
|
||
zh:input_upgrade_tag) template='升级目标 tag(留空则使用 latest)' ;;
|
||
en:input_app_uri) template='app_uri' ;;
|
||
zh:input_app_uri) template='app_uri' ;;
|
||
en:input_http_listen) template='http_listen' ;;
|
||
zh:input_http_listen) template='http_listen' ;;
|
||
en:input_log_lines) template='Log lines' ;;
|
||
zh:input_log_lines) template='日志行数' ;;
|
||
en:press_enter_continue) template='Press Enter to return to the menu...' ;;
|
||
zh:press_enter_continue) template='按回车返回菜单...' ;;
|
||
en:menu_select_action) template='Select an action [0-10]: ' ;;
|
||
zh:menu_select_action) template='请选择操作 [0-10]:' ;;
|
||
en:menu_language_prompt) template='Choose a language / 请选择语言: 1) 中文 2) English' ;;
|
||
zh:menu_language_prompt) template='请选择语言 / Choose a language:1)中文 2)English' ;;
|
||
en:menu_language_input) template='Language selection(语言选择)' ;;
|
||
zh:menu_language_input) template='语言选择(Language selection)' ;;
|
||
en:menu_confirm_prompt) template='Continue? / 是否继续? [y/N] ' ;;
|
||
zh:menu_confirm_prompt) template='是否继续?/ Continue? [y/N] ' ;;
|
||
en:upgrade_restart_prompt) template='Release files are updated. Restart %s now? [Y/n] ' ;;
|
||
zh:upgrade_restart_prompt) template='Release 文件已更新,是否立即重启 %s?[Y/n] ' ;;
|
||
en:menu_cancelled_back) template='Cancelled and returned to the menu / 已取消,返回菜单' ;;
|
||
zh:menu_cancelled_back) template='已取消,返回菜单 / Cancelled and returned to the menu' ;;
|
||
en:language_switched) template='Language switched to %s / 语言已切换为 %s' ;;
|
||
zh:language_switched) template='语言已切换为 %s / Language switched to %s' ;;
|
||
en:language_name_zh) template='中文' ;;
|
||
zh:language_name_zh) template='中文' ;;
|
||
en:language_name_en) template='English' ;;
|
||
zh:language_name_en) template='English' ;;
|
||
en:unsupported_language) template='unsupported language: %s' ;;
|
||
zh:unsupported_language) template='不支持的语言:%s' ;;
|
||
en:current_language) template='current language' ;;
|
||
zh:current_language) template='当前语言' ;;
|
||
en:base_image_label) template='base image' ;;
|
||
zh:base_image_label) template='基础镜像' ;;
|
||
en:action_download_title) template='Download release package' ;;
|
||
zh:action_download_title) template='下载 release 包' ;;
|
||
en:action_download_desc) template='This will download the selected GitHub release archive into %s, and verify SHA256 when sha256sum is available. It will not install or start the service.' ;;
|
||
zh:action_download_desc) template='此操作会把选定的 GitHub release 压缩包下载到 %s,并在本机有 sha256sum 时校验 SHA256。它不会安装,也不会启动服务。' ;;
|
||
en:action_install_title) template='Install or refresh the service' ;;
|
||
zh:action_install_title) template='安装或刷新服务' ;;
|
||
en:action_install_desc) template='This will install epusdt into %s, create or reuse %s, write %s, and enable %s through systemd. Existing .env will be kept.' ;;
|
||
zh:action_install_desc) template='此操作会把 epusdt 安装到 %s,创建或复用 %s,写入 %s,并通过 systemd 启用 %s。若已有 .env,则会保留。' ;;
|
||
en:action_upgrade_title) template='Upgrade the service' ;;
|
||
zh:action_upgrade_title) template='升级服务' ;;
|
||
en:action_upgrade_desc) template='This will download and install a newer release into %s, keep the existing .env configuration, and then ask whether to restart %s. The direct CLI command restarts by default unless you pass --no-restart.' ;;
|
||
zh:action_upgrade_desc) template='此操作会下载并安装较新的 release 到 %s,保留现有 .env 配置,然后再询问是否重启 %s。直接执行 CLI 时默认会重启,除非显式传入 --no-restart。' ;;
|
||
en:action_upgrade_restart_notice) template='Restarting %s will briefly interrupt the service and switch the running process to the new binary.' ;;
|
||
zh:action_upgrade_restart_notice) template='重启 %s 会带来短暂服务中断,并让运行中的进程切换到新的二进制文件。' ;;
|
||
en:action_upgrade_restart_skipped) template='Release files for %s were updated, but the service was not restarted. Run `systemctl restart %s` manually to apply the new binary.' ;;
|
||
zh:action_upgrade_restart_skipped) template='已更新 %s 的 release 文件,但没有重启服务。请手动执行 `systemctl restart %s` 以应用新的二进制文件。' ;;
|
||
en:action_upgrade_restart_no_tty) template='--prompt-restart requires an interactive terminal. Re-run in a TTY, use plain `upgrade` to restart immediately, or pass `--no-restart` to skip the restart.' ;;
|
||
zh:action_upgrade_restart_no_tty) template='--prompt-restart 需要交互终端。请在 TTY 中重新执行,或直接使用默认 `upgrade` 立即重启,或传入 `--no-restart` 跳过重启。' ;;
|
||
en:action_self_install_title) template='Install epctl into PATH' ;;
|
||
zh:action_self_install_title) template='安装 epctl 到 PATH' ;;
|
||
en:action_self_install_desc) template='This will install %s into /usr/local/bin so it can be called from anywhere. Use %s to force Chinese or %s to force English.' ;;
|
||
zh:action_self_install_desc) template='此操作会把 %s 安装到 /usr/local/bin,便于在任意目录直接调用。之后可用 %s 强制中文,或用 %s 强制英文。' ;;
|
||
en:action_show_config_title) template='Show active config' ;;
|
||
zh:action_show_config_title) template='查看当前配置' ;;
|
||
en:action_show_config_desc) template='This will print the full active configuration file %s to the terminal. Sensitive values will be visible.' ;;
|
||
zh:action_show_config_desc) template='此操作会把当前生效的配置文件 %s 完整输出到终端,其中敏感配置也会直接显示。' ;;
|
||
en:action_init_password_title) template='Request initial admin password' ;;
|
||
zh:action_init_password_title) template='获取初始化密码' ;;
|
||
en:action_init_password_desc) template='This will call the local HTTP route /admin/api/v1/auth/init-password based on http_listen in %s. It does not read the database directly.' ;;
|
||
zh:action_init_password_desc) template='此操作会根据 %s 中的 http_listen,请求本地 HTTP 路由 /admin/api/v1/auth/init-password。它不会直接读取数据库。' ;;
|
||
en:action_status_title) template='Show service status' ;;
|
||
zh:action_status_title) template='查看服务状态' ;;
|
||
en:action_status_desc) template='This will run systemctl status for %s and print the current runtime state, recent logs, and unit summary.' ;;
|
||
zh:action_status_desc) template='此操作会对 %s 执行 systemctl status,并输出当前运行状态、最近日志和 unit 摘要。' ;;
|
||
en:action_logs_title) template='Show recent logs' ;;
|
||
zh:action_logs_title) template='查看最近日志' ;;
|
||
en:action_logs_desc) template='This will read recent journal logs for %s. You can choose how many lines to print next.' ;;
|
||
zh:action_logs_desc) template='此操作会读取 %s 的最近 journal 日志。下一步你可以选择要输出多少行。' ;;
|
||
en:action_language_title) template='Switch language(切换语言)' ;;
|
||
zh:action_language_title) template='Switch language(切换语言)' ;;
|
||
en:action_language_desc) template='This only changes the current interactive interface language. It does not change the service config or installation files. / 此操作只会切换当前交互界面的语言,不会修改服务配置,也不会改动安装文件。' ;;
|
||
zh:action_language_desc) template='此操作只会切换当前交互界面的语言,不会修改服务配置,也不会改动安装文件。 / This only changes the current interactive interface language. It does not change the service config or installation files.' ;;
|
||
en:action_help_title) template='Show help' ;;
|
||
zh:action_help_title) template='查看帮助' ;;
|
||
en:action_help_desc) template='This will print the full command help, including all non-interactive commands and global options.' ;;
|
||
zh:action_help_desc) template='此操作会输出完整命令帮助,包括所有非交互命令和全局参数。' ;;
|
||
*)
|
||
template="missing translation: ${key}"
|
||
;;
|
||
esac
|
||
|
||
printf -- "${template}" "$@"
|
||
}
|
||
|
||
init_colors() {
|
||
if [[ -n "${NO_COLOR:-}" || "${EPCTL_NO_COLOR:-}" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
if [[ -t 1 || -t 2 ]]; then
|
||
COLOR_RESET=$'\033[0m'
|
||
COLOR_BOLD=$'\033[1m'
|
||
COLOR_DIM=$'\033[2m'
|
||
COLOR_CYAN=$'\033[36m'
|
||
COLOR_GREEN=$'\033[32m'
|
||
COLOR_YELLOW=$'\033[33m'
|
||
COLOR_RED=$'\033[31m'
|
||
COLOR_BLUE=$'\033[34m'
|
||
fi
|
||
}
|
||
|
||
log() {
|
||
printf '%b[%s]%b %s\n' "${COLOR_CYAN}${COLOR_BOLD}" "${EPCTL_NAME}" "${COLOR_RESET}" "$*" >&2
|
||
}
|
||
|
||
warn() {
|
||
printf '%b[%s] %s:%b %s\n' "${COLOR_YELLOW}${COLOR_BOLD}" "${EPCTL_NAME}" "$(trf label_warn)" "${COLOR_RESET}" "$*" >&2
|
||
}
|
||
|
||
success() {
|
||
printf '%b[%s] %s:%b %s\n' "${COLOR_GREEN}${COLOR_BOLD}" "${EPCTL_NAME}" "$(trf label_ok)" "${COLOR_RESET}" "$*" >&2
|
||
}
|
||
|
||
die() {
|
||
printf '%b[%s] %s:%b %s\n' "${COLOR_RED}${COLOR_BOLD}" "${EPCTL_NAME}" "$(trf label_error)" "${COLOR_RESET}" "$*" >&2
|
||
exit 1
|
||
}
|
||
|
||
usage() {
|
||
if [[ "${ACTIVE_LANG}" == "zh" ]]; then
|
||
cat <<'EOF'
|
||
用法:
|
||
epctl [--lang zh|en] [--no-color]
|
||
epctl zh
|
||
epctl en
|
||
epctl [--lang zh|en] menu
|
||
epctl [--lang zh|en] download [--tag <tag>]
|
||
epctl [--lang zh|en] install [--tag <tag>] [--app-uri URL] [--listen ADDR:PORT]
|
||
epctl [--lang zh|en] upgrade [--tag <tag>] [--prompt-restart|--no-restart]
|
||
epctl [--lang zh|en] self-install
|
||
epctl [--lang zh|en] show-config
|
||
epctl [--lang zh|en] init-password
|
||
epctl [--lang zh|en] status
|
||
epctl [--lang zh|en] logs [--lines N]
|
||
|
||
全局选项:
|
||
--lang zh|en 切换界面语言
|
||
--no-color 禁用彩色输出
|
||
快捷方式 `epctl zh` 直接进入中文;`epctl en` 直接进入英文
|
||
|
||
命令说明:
|
||
menu 进入交互式菜单;直接执行 `epctl` 也会进入
|
||
download 下载 GitHub Release 包到 /tmp/epusdt/<tag>/
|
||
install 安装或刷新 /opt/epusdt,并注册 epusdt.service
|
||
upgrade 升级到新的 release,保留现有 .env;默认直接重启,菜单会改为先询问
|
||
self-install 安装 epctl 到 /usr/local/bin
|
||
show-config 输出当前生效的 /opt/epusdt/.env
|
||
init-password 通过本地 HTTP 路由请求初始化管理员密码
|
||
status 显示 epusdt 的 systemd 状态
|
||
logs 显示最近的服务日志
|
||
|
||
说明:
|
||
- 仅支持 Linux
|
||
- 仅支持二进制安装
|
||
- install / upgrade / self-install 会写入 /opt、/etc/systemd、/usr/local/bin
|
||
- 需要提权的命令会自动通过 sudo 重新执行
|
||
- tag 需要是真实的 GitHub release tag,例如 v1.0.6
|
||
- 未提供 --tag 时,会自动解析 latest,并要求二次确认
|
||
- upgrade 只接受已有安装;不会创建新的 .env
|
||
- upgrade 默认会在文件替换后立即重启服务
|
||
- upgrade --prompt-restart 会在交互终端下询问是否重启
|
||
- upgrade --no-restart 只替换文件,不重启服务
|
||
EOF
|
||
else
|
||
cat <<'EOF'
|
||
Usage:
|
||
epctl [--lang zh|en] [--no-color]
|
||
epctl zh
|
||
epctl en
|
||
epctl [--lang zh|en] menu
|
||
epctl [--lang zh|en] download [--tag <tag>]
|
||
epctl [--lang zh|en] install [--tag <tag>] [--app-uri URL] [--listen ADDR:PORT]
|
||
epctl [--lang zh|en] upgrade [--tag <tag>] [--prompt-restart|--no-restart]
|
||
epctl [--lang zh|en] self-install
|
||
epctl [--lang zh|en] show-config
|
||
epctl [--lang zh|en] init-password
|
||
epctl [--lang zh|en] status
|
||
epctl [--lang zh|en] logs [--lines N]
|
||
|
||
Global options:
|
||
--lang zh|en Select the interface language
|
||
--no-color Disable colored output
|
||
Shortcut `epctl zh` enters Chinese directly; `epctl en` enters English directly
|
||
|
||
Command summary:
|
||
menu Open the interactive menu; `epctl` with no arguments does this too
|
||
download Download a GitHub release archive into /tmp/epusdt/<tag>/
|
||
install Install or refresh /opt/epusdt and register epusdt.service
|
||
upgrade Upgrade to a newer release while keeping the existing .env; direct CLI restarts by default, menu mode asks first
|
||
self-install Install epctl into /usr/local/bin
|
||
show-config Print the active /opt/epusdt/.env file
|
||
init-password Request the initial admin password from the local HTTP route
|
||
status Show the systemd status of epusdt
|
||
logs Show recent service logs
|
||
|
||
Notes:
|
||
- Linux only
|
||
- Binary installation only
|
||
- install / upgrade / self-install write into /opt, /etc/systemd, and /usr/local/bin
|
||
- privileged commands automatically re-exec through sudo
|
||
- tags must match real GitHub release tags such as v1.0.6
|
||
- if --tag is omitted, epctl resolves the latest release and asks for confirmation
|
||
- upgrade requires an existing install and does not create a new .env
|
||
- upgrade restarts the service by default after files are replaced
|
||
- upgrade --prompt-restart asks whether to restart in an interactive terminal
|
||
- upgrade --no-restart replaces files only and prints a manual restart warning
|
||
EOF
|
||
fi
|
||
}
|
||
|
||
parse_global_options() {
|
||
POSITIONAL_ARGS=()
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--lang)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--lang")"
|
||
set_language "$2"
|
||
shift 2
|
||
;;
|
||
--lang=*)
|
||
set_language "${1#*=}"
|
||
shift
|
||
;;
|
||
--no-color)
|
||
export EPCTL_NO_COLOR=1
|
||
shift
|
||
;;
|
||
*)
|
||
POSITIONAL_ARGS+=("$1")
|
||
shift
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
require_linux() {
|
||
[[ "$(uname -s)" == "Linux" ]] || die "$(trf only_linux)"
|
||
}
|
||
|
||
have_command() {
|
||
command -v "$1" >/dev/null 2>&1
|
||
}
|
||
|
||
require_command() {
|
||
have_command "$1" || die "$(trf missing_dependency "$1")"
|
||
}
|
||
|
||
is_interactive_terminal() {
|
||
[[ -t 0 && -t 1 ]]
|
||
}
|
||
|
||
require_root() {
|
||
[[ "${EUID}" -eq 0 ]] || die "$(trf root_required)"
|
||
}
|
||
|
||
ensure_root_via_sudo() {
|
||
if [[ "${EUID}" -eq 0 ]]; then
|
||
return
|
||
fi
|
||
|
||
require_command sudo
|
||
exec sudo -- "${SCRIPT_SOURCE_PATH}" --lang "${ACTIVE_LANG}" "$@"
|
||
}
|
||
|
||
require_release_tag() {
|
||
local tag="$1"
|
||
[[ -n "${tag}" ]] || die "$(trf release_tag_required)"
|
||
[[ "${tag}" == v* ]] || die "$(trf release_tag_invalid)"
|
||
}
|
||
|
||
map_arch() {
|
||
case "$(uname -m)" in
|
||
x86_64|amd64)
|
||
printf 'amd64\n'
|
||
;;
|
||
aarch64|arm64)
|
||
printf 'arm64\n'
|
||
;;
|
||
*)
|
||
die "$(trf unsupported_arch "$(uname -m)")"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
asset_version_from_tag() {
|
||
local tag="$1"
|
||
printf '%s\n' "${tag#v}"
|
||
}
|
||
|
||
cache_dir_for_tag() {
|
||
local tag="$1"
|
||
printf '%s/%s\n' "${CACHE_ROOT}" "${tag}"
|
||
}
|
||
|
||
archive_name_for_tag() {
|
||
local tag="$1"
|
||
local arch="$2"
|
||
printf 'epusdt-%s-linux-%s.tar.gz\n' "$(asset_version_from_tag "${tag}")" "${arch}"
|
||
}
|
||
|
||
release_base_url_for_tag() {
|
||
local tag="$1"
|
||
printf 'https://github.com/%s/%s/releases/download/%s\n' "${REPO_OWNER}" "${REPO_NAME}" "${tag}"
|
||
}
|
||
|
||
download_file() {
|
||
local url="$1"
|
||
local destination="$2"
|
||
local partial="${destination}.part"
|
||
rm -f "${partial}"
|
||
curl --fail --location --silent --show-error --output "${partial}" "${url}"
|
||
mv "${partial}" "${destination}"
|
||
}
|
||
|
||
verify_checksum() {
|
||
local sums_path="$1"
|
||
local archive_path="$2"
|
||
local archive_name
|
||
local checksum_line
|
||
|
||
archive_name="$(basename "${archive_path}")"
|
||
checksum_line="$(grep -F " ${archive_name}" "${sums_path}" || true)"
|
||
[[ -n "${checksum_line}" ]] || die "$(trf checksum_missing "${archive_name}" "$(basename "${sums_path}")")"
|
||
|
||
(
|
||
cd "$(dirname "${archive_path}")"
|
||
printf '%s\n' "${checksum_line}" | sha256sum -c - >&2
|
||
)
|
||
}
|
||
|
||
ensure_download_dependencies() {
|
||
require_command curl
|
||
require_command grep
|
||
require_command sed
|
||
}
|
||
|
||
latest_release_tag() {
|
||
local api_url="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest"
|
||
local response tag
|
||
|
||
ensure_download_dependencies
|
||
response="$(curl --fail --location --silent --show-error "${api_url}")"
|
||
tag="$(printf '%s\n' "${response}" | sed -n 's/^[[:space:]]*"tag_name":[[:space:]]*"\([^"]\+\)".*$/\1/p' | head -n 1)"
|
||
[[ -n "${tag}" ]] || die "$(trf latest_release_resolve_failed)"
|
||
require_release_tag "${tag}"
|
||
printf '%s\n' "${tag}"
|
||
}
|
||
|
||
is_affirmative_reply() {
|
||
local reply="${1:-}"
|
||
reply="${reply,,}"
|
||
|
||
case "${reply}" in
|
||
y|yes|ok|true|1|s|shi|是|确认|確認)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
confirm_latest_release_tag() {
|
||
local tag="$1"
|
||
local reply
|
||
|
||
if [[ "${EPCTL_ASSUME_YES:-}" == "1" ]]; then
|
||
log "$(trf using_latest_without_confirmation "${tag}")"
|
||
printf '%s\n' "${tag}"
|
||
return
|
||
fi
|
||
|
||
if [[ ! -t 0 ]]; then
|
||
die "$(trf latest_tag_no_tty)"
|
||
fi
|
||
|
||
printf '%b[%s]%b %s' "${COLOR_CYAN}${COLOR_BOLD}" "${EPCTL_NAME}" "${COLOR_RESET}" "$(trf latest_prompt "${tag}")" >&2
|
||
read -r reply
|
||
if is_affirmative_reply "${reply}"; then
|
||
printf '%s\n' "${tag}"
|
||
return
|
||
fi
|
||
|
||
die "$(trf cancelled)"
|
||
}
|
||
|
||
resolve_effective_tag() {
|
||
local requested_tag="$1"
|
||
local latest_tag
|
||
|
||
if [[ -n "${requested_tag}" ]]; then
|
||
require_release_tag "${requested_tag}"
|
||
printf '%s\n' "${requested_tag}"
|
||
return
|
||
fi
|
||
|
||
latest_tag="$(latest_release_tag)"
|
||
confirm_latest_release_tag "${latest_tag}"
|
||
}
|
||
|
||
download_release() {
|
||
local tag="$1"
|
||
local arch archive_name release_base_url cache_dir archive_path sums_path
|
||
|
||
require_linux
|
||
ensure_download_dependencies
|
||
|
||
arch="$(map_arch)"
|
||
archive_name="$(archive_name_for_tag "${tag}" "${arch}")"
|
||
release_base_url="$(release_base_url_for_tag "${tag}")"
|
||
cache_dir="$(cache_dir_for_tag "${tag}")"
|
||
archive_path="${cache_dir}/${archive_name}"
|
||
sums_path="${cache_dir}/SHA256SUMS"
|
||
|
||
mkdir -p "${cache_dir}"
|
||
|
||
if [[ -f "${archive_path}" && -f "${sums_path}" ]]; then
|
||
log "$(trf cached_release_files "${cache_dir}")"
|
||
else
|
||
log "$(trf downloading_archive "${archive_name}")"
|
||
download_file "${release_base_url}/${archive_name}" "${archive_path}"
|
||
download_file "${release_base_url}/SHA256SUMS" "${sums_path}"
|
||
fi
|
||
|
||
if have_command sha256sum; then
|
||
verify_checksum "${sums_path}" "${archive_path}"
|
||
else
|
||
warn "$(trf checksum_skipped)"
|
||
fi
|
||
|
||
printf '%s\n' "${archive_path}"
|
||
}
|
||
|
||
escape_sed_replacement() {
|
||
printf '%s' "$1" | sed -e 's/[&|\\]/\\&/g'
|
||
}
|
||
|
||
set_env_key() {
|
||
local file="$1"
|
||
local key="$2"
|
||
local value="$3"
|
||
local escaped
|
||
|
||
escaped="$(escape_sed_replacement "${value}")"
|
||
if grep -q "^${key}=" "${file}"; then
|
||
sed -i "s|^${key}=.*$|${key}=${escaped}|" "${file}"
|
||
else
|
||
printf '%s=%s\n' "${key}" "${value}" >> "${file}"
|
||
fi
|
||
}
|
||
|
||
ensure_system_user() {
|
||
if ! getent group "${SYSTEM_USER}" >/dev/null 2>&1; then
|
||
groupadd --system "${SYSTEM_USER}"
|
||
fi
|
||
if ! id -u "${SYSTEM_USER}" >/dev/null 2>&1; then
|
||
useradd --system --gid "${SYSTEM_USER}" --home-dir "${INSTALL_DIR}" --shell /usr/sbin/nologin "${SYSTEM_USER}"
|
||
fi
|
||
}
|
||
|
||
extract_release_archive() {
|
||
local tag="$1"
|
||
local archive_path="$2"
|
||
local extract_dir
|
||
|
||
extract_dir="$(cache_dir_for_tag "${tag}")/extract"
|
||
rm -rf "${extract_dir}"
|
||
mkdir -p "${extract_dir}"
|
||
tar -xzf "${archive_path}" -C "${extract_dir}"
|
||
[[ -f "${extract_dir}/epusdt" ]] || die "$(trf archive_missing_binary)"
|
||
[[ -f "${extract_dir}/.env.example" ]] || die "$(trf archive_missing_env_example)"
|
||
printf '%s\n' "${extract_dir}"
|
||
}
|
||
|
||
create_default_config_if_missing() {
|
||
local app_uri="$1"
|
||
local listen="$2"
|
||
|
||
if [[ -f "${CONFIG_PATH}" ]]; then
|
||
log "$(trf keeping_existing_config "${CONFIG_PATH}")"
|
||
return
|
||
fi
|
||
|
||
cp "${CONFIG_EXAMPLE_PATH}" "${CONFIG_PATH}"
|
||
set_env_key "${CONFIG_PATH}" "install" "false"
|
||
set_env_key "${CONFIG_PATH}" "app_uri" "${app_uri}"
|
||
set_env_key "${CONFIG_PATH}" "http_listen" "${listen}"
|
||
chmod 0644 "${CONFIG_PATH}"
|
||
}
|
||
|
||
write_systemd_unit() {
|
||
cat > "${SYSTEMD_UNIT_PATH}" <<EOF
|
||
[Unit]
|
||
Description=epusdt service
|
||
After=network-online.target
|
||
Wants=network-online.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=${SYSTEM_USER}
|
||
Group=${SYSTEM_USER}
|
||
WorkingDirectory=${INSTALL_DIR}
|
||
ExecStart=${BIN_PATH} http start
|
||
Restart=always
|
||
RestartSec=3
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
}
|
||
|
||
render_service_debug_info() {
|
||
systemctl status "${SERVICE_NAME}" --no-pager || true
|
||
journalctl -u "${SERVICE_NAME}" --no-pager -n 100 || true
|
||
}
|
||
|
||
prepare_release_extract() {
|
||
local tag="$1"
|
||
local archive_path
|
||
|
||
archive_path="$(download_release "${tag}")"
|
||
extract_release_archive "${tag}" "${archive_path}"
|
||
}
|
||
|
||
deploy_release_payload() {
|
||
local extract_dir="$1"
|
||
|
||
ensure_system_user
|
||
install -d -m 0755 "${INSTALL_DIR}"
|
||
install -m 0755 "${extract_dir}/epusdt" "${BIN_PATH}"
|
||
install -m 0644 "${extract_dir}/.env.example" "${CONFIG_EXAMPLE_PATH}"
|
||
}
|
||
|
||
finalize_install_dir_ownership() {
|
||
chown -R "${SYSTEM_USER}:${SYSTEM_USER}" "${INSTALL_DIR}"
|
||
}
|
||
|
||
refresh_service_unit() {
|
||
write_systemd_unit
|
||
systemctl daemon-reload
|
||
}
|
||
|
||
ensure_existing_upgrade_config() {
|
||
[[ -f "${CONFIG_PATH}" ]] || die "$(trf upgrade_existing_config_required "${CONFIG_PATH}")"
|
||
}
|
||
|
||
create_upgrade_backup_dir() {
|
||
local tag="$1"
|
||
local backup_dir
|
||
|
||
backup_dir="$(cache_dir_for_tag "${tag}")/backup-current"
|
||
rm -rf "${backup_dir}"
|
||
mkdir -p "${backup_dir}"
|
||
printf '%s\n' "${backup_dir}"
|
||
}
|
||
|
||
backup_release_files() {
|
||
local backup_dir="$1"
|
||
|
||
[[ -f "${BIN_PATH}" ]] && cp -p "${BIN_PATH}" "${backup_dir}/epusdt"
|
||
[[ -f "${CONFIG_EXAMPLE_PATH}" ]] && cp -p "${CONFIG_EXAMPLE_PATH}" "${backup_dir}/.env.example"
|
||
[[ -f "${SYSTEMD_UNIT_PATH}" ]] && cp -p "${SYSTEMD_UNIT_PATH}" "${backup_dir}/${SERVICE_NAME}.service"
|
||
}
|
||
|
||
restore_release_files() {
|
||
local backup_dir="$1"
|
||
|
||
install -d -m 0755 "${INSTALL_DIR}"
|
||
|
||
if [[ -f "${backup_dir}/epusdt" ]]; then
|
||
install -m 0755 "${backup_dir}/epusdt" "${BIN_PATH}"
|
||
else
|
||
rm -f "${BIN_PATH}"
|
||
fi
|
||
|
||
if [[ -f "${backup_dir}/.env.example" ]]; then
|
||
install -m 0644 "${backup_dir}/.env.example" "${CONFIG_EXAMPLE_PATH}"
|
||
else
|
||
rm -f "${CONFIG_EXAMPLE_PATH}"
|
||
fi
|
||
|
||
if [[ -f "${backup_dir}/${SERVICE_NAME}.service" ]]; then
|
||
install -m 0644 "${backup_dir}/${SERVICE_NAME}.service" "${SYSTEMD_UNIT_PATH}"
|
||
else
|
||
rm -f "${SYSTEMD_UNIT_PATH}"
|
||
fi
|
||
|
||
finalize_install_dir_ownership
|
||
systemctl daemon-reload
|
||
}
|
||
|
||
apply_upgrade_release() {
|
||
local extract_dir="$1"
|
||
|
||
deploy_release_payload "${extract_dir}"
|
||
finalize_install_dir_ownership
|
||
refresh_service_unit
|
||
}
|
||
|
||
restart_service_and_verify() {
|
||
local tag="$1"
|
||
local backup_dir="${2:-}"
|
||
|
||
if systemctl restart "${SERVICE_NAME}" && systemctl is-active --quiet "${SERVICE_NAME}"; then
|
||
log "$(trf service_upgraded_restarted "${SERVICE_NAME}" "${INSTALL_DIR}" "${tag}")"
|
||
return 0
|
||
fi
|
||
|
||
render_service_debug_info
|
||
|
||
if [[ -n "${backup_dir}" ]]; then
|
||
warn "$(trf upgrade_restart_failed_rollback_start "${SERVICE_NAME}")"
|
||
if ! restore_release_files "${backup_dir}"; then
|
||
die "$(trf upgrade_restart_failed_rollback_failed "${SERVICE_NAME}" "${tag}")"
|
||
fi
|
||
if systemctl restart "${SERVICE_NAME}" && systemctl is-active --quiet "${SERVICE_NAME}"; then
|
||
die "$(trf upgrade_restart_failed_rolled_back "${SERVICE_NAME}" "${tag}")"
|
||
fi
|
||
render_service_debug_info
|
||
die "$(trf upgrade_restart_failed_rollback_failed "${SERVICE_NAME}" "${tag}")"
|
||
fi
|
||
|
||
die "$(trf service_failed_active "${SERVICE_NAME}")"
|
||
}
|
||
|
||
handle_upgrade_restart() {
|
||
local tag="$1"
|
||
local restart_mode="$2"
|
||
local backup_dir="$3"
|
||
local reply=""
|
||
|
||
case "${restart_mode}" in
|
||
restart)
|
||
restart_service_and_verify "${tag}" "${backup_dir}"
|
||
;;
|
||
prompt)
|
||
if ! is_interactive_terminal; then
|
||
die "$(trf action_upgrade_restart_no_tty)"
|
||
fi
|
||
|
||
warn "$(trf action_upgrade_restart_notice "${SERVICE_NAME}")"
|
||
printf '%b[%s]%b %s' "${COLOR_BOLD}${COLOR_CYAN}" "${EPCTL_NAME}" "${COLOR_RESET}" "$(trf upgrade_restart_prompt "${SERVICE_NAME}")" >&2
|
||
if ! read -r reply; then
|
||
reply="n"
|
||
fi
|
||
|
||
if [[ -z "${reply}" ]] || is_affirmative_reply "${reply}"; then
|
||
restart_service_and_verify "${tag}" "${backup_dir}"
|
||
return 0
|
||
fi
|
||
|
||
warn "$(trf action_upgrade_restart_skipped "${SERVICE_NAME}" "${SERVICE_NAME}")"
|
||
;;
|
||
skip)
|
||
warn "$(trf action_upgrade_restart_skipped "${SERVICE_NAME}" "${SERVICE_NAME}")"
|
||
;;
|
||
*)
|
||
die "$(trf upgrade_restart_flag_conflict)"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
command_download() {
|
||
local tag=""
|
||
local effective_tag archive_path
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--tag)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--tag")"
|
||
tag="$2"
|
||
shift 2
|
||
;;
|
||
--tag=*)
|
||
tag="${1#*=}"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "download" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
effective_tag="$(resolve_effective_tag "${tag}")"
|
||
archive_path="$(download_release "${effective_tag}")"
|
||
printf '%s\n' "${archive_path}"
|
||
}
|
||
|
||
command_install() {
|
||
local tag=""
|
||
local app_uri="${DEFAULT_APP_URI}"
|
||
local listen="${DEFAULT_LISTEN}"
|
||
local original_args=("$@")
|
||
local effective_tag
|
||
local extract_dir
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--tag)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--tag")"
|
||
tag="$2"
|
||
shift 2
|
||
;;
|
||
--tag=*)
|
||
tag="${1#*=}"
|
||
shift
|
||
;;
|
||
--app-uri)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--app-uri")"
|
||
app_uri="$2"
|
||
shift 2
|
||
;;
|
||
--app-uri=*)
|
||
app_uri="${1#*=}"
|
||
shift
|
||
;;
|
||
--listen)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--listen")"
|
||
listen="$2"
|
||
shift 2
|
||
;;
|
||
--listen=*)
|
||
listen="${1#*=}"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "install" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
ensure_root_via_sudo install "${original_args[@]}"
|
||
|
||
require_root
|
||
require_linux
|
||
require_command tar
|
||
require_command install
|
||
require_command systemctl
|
||
require_command cp
|
||
require_command grep
|
||
require_command sed
|
||
require_command getent
|
||
require_command groupadd
|
||
require_command useradd
|
||
require_command chown
|
||
|
||
effective_tag="$(resolve_effective_tag "${tag}")"
|
||
extract_dir="$(prepare_release_extract "${effective_tag}")"
|
||
deploy_release_payload "${extract_dir}"
|
||
create_default_config_if_missing "${app_uri}" "${listen}"
|
||
finalize_install_dir_ownership
|
||
refresh_service_unit
|
||
systemctl enable --now "${SERVICE_NAME}"
|
||
|
||
if ! systemctl is-active --quiet "${SERVICE_NAME}"; then
|
||
render_service_debug_info
|
||
die "$(trf service_failed_active "${SERVICE_NAME}")"
|
||
fi
|
||
|
||
log "$(trf service_installed "${SERVICE_NAME}" "${INSTALL_DIR}" "${effective_tag}")"
|
||
}
|
||
|
||
command_self_install() {
|
||
local original_args=("$@")
|
||
|
||
require_linux
|
||
require_command install
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "self-install" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
ensure_root_via_sudo self-install "${original_args[@]}"
|
||
|
||
if [[ "${SCRIPT_SOURCE_PATH}" == "${SELF_INSTALL_PATH}" ]]; then
|
||
chmod 0755 "${SELF_INSTALL_PATH}"
|
||
log "$(trf self_install_current "${SELF_INSTALL_PATH}")"
|
||
else
|
||
install -m 0755 "${SCRIPT_SOURCE_PATH}" "${SELF_INSTALL_PATH}"
|
||
fi
|
||
|
||
log "$(trf self_install_done "${EPCTL_NAME}")"
|
||
}
|
||
|
||
command_upgrade() {
|
||
local tag=""
|
||
local restart_mode="restart"
|
||
local original_args=("$@")
|
||
local effective_tag
|
||
local extract_dir backup_dir
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--tag)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--tag")"
|
||
tag="$2"
|
||
shift 2
|
||
;;
|
||
--tag=*)
|
||
tag="${1#*=}"
|
||
shift
|
||
;;
|
||
--prompt-restart)
|
||
[[ "${restart_mode}" == "restart" ]] || die "$(trf upgrade_restart_flag_conflict)"
|
||
restart_mode="prompt"
|
||
shift
|
||
;;
|
||
--no-restart)
|
||
[[ "${restart_mode}" == "restart" ]] || die "$(trf upgrade_restart_flag_conflict)"
|
||
restart_mode="skip"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "upgrade" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
ensure_root_via_sudo upgrade "${original_args[@]}"
|
||
|
||
require_root
|
||
require_linux
|
||
require_command tar
|
||
require_command install
|
||
require_command systemctl
|
||
require_command cp
|
||
require_command grep
|
||
require_command sed
|
||
require_command getent
|
||
require_command groupadd
|
||
require_command useradd
|
||
require_command chown
|
||
|
||
effective_tag="$(resolve_effective_tag "${tag}")"
|
||
ensure_existing_upgrade_config
|
||
extract_dir="$(prepare_release_extract "${effective_tag}")"
|
||
backup_dir="$(create_upgrade_backup_dir "${effective_tag}")"
|
||
backup_release_files "${backup_dir}"
|
||
if ! apply_upgrade_release "${extract_dir}"; then
|
||
warn "$(trf upgrade_deploy_failed_rollback_start "${SERVICE_NAME}")"
|
||
if restore_release_files "${backup_dir}"; then
|
||
die "$(trf upgrade_deploy_failed_rolled_back "${SERVICE_NAME}" "${effective_tag}")"
|
||
fi
|
||
die "$(trf upgrade_deploy_failed_rollback_failed "${SERVICE_NAME}" "${effective_tag}")"
|
||
fi
|
||
handle_upgrade_restart "${effective_tag}" "${restart_mode}" "${backup_dir}"
|
||
}
|
||
|
||
command_show_config() {
|
||
require_linux
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "show-config" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
[[ -f "${CONFIG_PATH}" ]] || die "$(trf config_not_found "${CONFIG_PATH}")"
|
||
cat "${CONFIG_PATH}"
|
||
}
|
||
|
||
resolve_http_listen() {
|
||
[[ -f "${CONFIG_PATH}" ]] || die "$(trf config_not_found "${CONFIG_PATH}")"
|
||
local listen
|
||
|
||
listen="$(grep -E '^http_listen=' "${CONFIG_PATH}" | tail -n 1 | cut -d'=' -f2- || true)"
|
||
[[ -n "${listen}" ]] || die "$(trf http_listen_not_found "${CONFIG_PATH}")"
|
||
printf '%s\n' "${listen}"
|
||
}
|
||
|
||
init_password_url() {
|
||
local listen="$1"
|
||
local host port
|
||
|
||
if [[ "${listen}" =~ ^\[([^]]+)\]:(.+)$ ]]; then
|
||
host="${BASH_REMATCH[1]}"
|
||
port="${BASH_REMATCH[2]}"
|
||
elif [[ "${listen}" == :* ]]; then
|
||
host="127.0.0.1"
|
||
port="${listen#:}"
|
||
else
|
||
host="${listen%:*}"
|
||
port="${listen##*:}"
|
||
fi
|
||
|
||
case "${host}" in
|
||
""|"0.0.0.0"|"::"|"[::]")
|
||
host="127.0.0.1"
|
||
;;
|
||
esac
|
||
|
||
[[ -n "${port}" ]] || die "$(trf http_listen_parse_failed "${listen}")"
|
||
printf 'http://%s:%s/admin/api/v1/auth/init-password\n' "${host}" "${port}"
|
||
}
|
||
|
||
command_init_password() {
|
||
local listen url body_file http_code curl_rc
|
||
|
||
require_linux
|
||
require_command curl
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "init-password" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
listen="$(resolve_http_listen)"
|
||
url="$(init_password_url "${listen}")"
|
||
body_file="$(mktemp)"
|
||
if http_code="$(curl --silent --show-error --location --output "${body_file}" --write-out '%{http_code}' "${url}")"; then
|
||
:
|
||
else
|
||
curl_rc="$?"
|
||
if [[ -s "${body_file}" ]]; then
|
||
warn "$(trf curl_partial_body)"
|
||
cat "${body_file}" >&2
|
||
printf '\n' >&2
|
||
fi
|
||
rm -f "${body_file}"
|
||
return "${curl_rc}"
|
||
fi
|
||
|
||
if [[ "${http_code}" =~ ^2[0-9][0-9]$ ]]; then
|
||
cat "${body_file}"
|
||
rm -f "${body_file}"
|
||
return 0
|
||
fi
|
||
|
||
warn "$(trf init_password_http_failed "${http_code}" "${url}")"
|
||
if [[ -s "${body_file}" ]]; then
|
||
printf '%b%s%b\n' "${COLOR_YELLOW}${COLOR_BOLD}" "$(trf response_body_label)" "${COLOR_RESET}" >&2
|
||
cat "${body_file}" >&2
|
||
printf '\n' >&2
|
||
else
|
||
warn "$(trf response_body_empty)"
|
||
fi
|
||
rm -f "${body_file}"
|
||
return 1
|
||
}
|
||
|
||
command_status() {
|
||
local original_args=("$@")
|
||
|
||
require_linux
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "status" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
ensure_root_via_sudo status "${original_args[@]}"
|
||
require_root
|
||
require_command systemctl
|
||
systemctl status "${SERVICE_NAME}" --no-pager
|
||
}
|
||
|
||
command_logs() {
|
||
local lines="100"
|
||
local original_args=("$@")
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--lines)
|
||
[[ $# -ge 2 ]] || die "$(trf arg_requires_value "--lines")"
|
||
lines="$2"
|
||
shift 2
|
||
;;
|
||
--lines=*)
|
||
lines="${1#*=}"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "$(trf unknown_arg "logs" "$1")"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
[[ "${lines}" =~ ^[1-9][0-9]*$ ]] || die "$(trf lines_positive_integer)"
|
||
require_linux
|
||
ensure_root_via_sudo logs "${original_args[@]}"
|
||
require_root
|
||
require_command journalctl
|
||
journalctl -u "${SERVICE_NAME}" --no-pager -q -n "${lines}"
|
||
}
|
||
|
||
prompt_input() {
|
||
local label="$1"
|
||
local default_value="${2:-}"
|
||
local input
|
||
|
||
if [[ -n "${default_value}" ]]; then
|
||
printf '%b%s%b [%s]: ' "${COLOR_BOLD}" "${label}" "${COLOR_RESET}" "${default_value}" >&2
|
||
else
|
||
printf '%b%s%b: ' "${COLOR_BOLD}" "${label}" "${COLOR_RESET}" >&2
|
||
fi
|
||
|
||
read -r input || return 1
|
||
if [[ -z "${input}" ]]; then
|
||
printf '%s\n' "${default_value}"
|
||
else
|
||
printf '%s\n' "${input}"
|
||
fi
|
||
}
|
||
|
||
pause_for_menu() {
|
||
printf '\n'
|
||
printf '%b%s%b' "${COLOR_DIM}" "$(trf press_enter_continue)" "${COLOR_RESET}"
|
||
read -r _ || true
|
||
}
|
||
|
||
print_rule() {
|
||
local char="${1:--}"
|
||
local width="${2:-64}"
|
||
local line
|
||
|
||
printf -v line '%*s' "${width}" ''
|
||
line="${line// /${char}}"
|
||
printf '%b%s%b\n' "${COLOR_DIM}" "${line}" "${COLOR_RESET}"
|
||
}
|
||
|
||
format_badge() {
|
||
local text="$1"
|
||
local color="${2:-${COLOR_BLUE}}"
|
||
printf '%b[%s]%b' "${color}${COLOR_BOLD}" "${text}" "${COLOR_RESET}"
|
||
}
|
||
|
||
format_service_state_badge() {
|
||
local state="$1"
|
||
local color="${COLOR_RED}"
|
||
|
||
case "${state}" in
|
||
"$(trf state_active)")
|
||
color="${COLOR_GREEN}"
|
||
;;
|
||
"$(trf state_inactive)")
|
||
color="${COLOR_YELLOW}"
|
||
;;
|
||
esac
|
||
|
||
format_badge "${state}" "${color}"
|
||
}
|
||
|
||
format_menu_item_badge() {
|
||
local kind="$1"
|
||
local label color
|
||
|
||
case "${kind}" in
|
||
direct)
|
||
label="$(trf menu_badge_direct)"
|
||
color="${COLOR_GREEN}"
|
||
;;
|
||
confirm)
|
||
label="$(trf menu_badge_confirm)"
|
||
color="${COLOR_YELLOW}"
|
||
;;
|
||
*)
|
||
return 0
|
||
;;
|
||
esac
|
||
|
||
format_badge "${label}" "${color}"
|
||
}
|
||
|
||
print_menu_meta_line() {
|
||
local label="$1"
|
||
local value="$2"
|
||
printf ' %b%s%b %s\n' "${COLOR_DIM}" "${label}:" "${COLOR_RESET}" "${value}"
|
||
}
|
||
|
||
print_menu_section_title() {
|
||
printf '\n%b%s%b\n' "${COLOR_BLUE}${COLOR_BOLD}" "$1" "${COLOR_RESET}"
|
||
}
|
||
|
||
print_menu_item() {
|
||
local index="$1"
|
||
local title="$2"
|
||
local mode="${3:-}"
|
||
|
||
printf ' %b%2s%b. %s' "${COLOR_CYAN}${COLOR_BOLD}" "${index}" "${COLOR_RESET}" "${title}"
|
||
if [[ -n "${mode}" ]]; then
|
||
printf ' %s' "$(format_menu_item_badge "${mode}")"
|
||
fi
|
||
printf '\n'
|
||
}
|
||
|
||
print_menu_action_preview() {
|
||
local title="$1"
|
||
local description="$2"
|
||
|
||
printf '\n' >&2
|
||
print_rule "-" 64 >&2
|
||
printf '%b> %s%b\n' "${COLOR_BLUE}${COLOR_BOLD}" "${title}" "${COLOR_RESET}" >&2
|
||
printf '%s\n' "${description}" >&2
|
||
print_rule "-" 64 >&2
|
||
}
|
||
|
||
confirm_menu_action() {
|
||
local title="$1"
|
||
local description="$2"
|
||
local reply
|
||
|
||
print_menu_action_preview "${title}" "${description}"
|
||
printf '%b%s%b' "${COLOR_BOLD}" "$(trf menu_confirm_prompt)" "${COLOR_RESET}" >&2
|
||
read -r reply || return 1
|
||
if is_affirmative_reply "${reply}"; then
|
||
return 0
|
||
fi
|
||
|
||
warn "$(trf menu_cancelled_back)"
|
||
pause_for_menu
|
||
return 1
|
||
}
|
||
|
||
service_state_summary() {
|
||
if [[ ! -f "${SYSTEMD_UNIT_PATH}" ]]; then
|
||
printf '%s\n' "$(trf state_not_installed)"
|
||
return
|
||
fi
|
||
|
||
if ! have_command systemctl; then
|
||
printf '%s\n' "$(trf state_no_systemctl)"
|
||
return
|
||
fi
|
||
|
||
if [[ ! -d /run/systemd/system ]]; then
|
||
printf '%s\n' "$(trf state_no_systemd)"
|
||
return
|
||
fi
|
||
|
||
if systemctl is-active --quiet "${SERVICE_NAME}" >/dev/null 2>&1; then
|
||
printf '%s\n' "$(trf state_active)"
|
||
return
|
||
fi
|
||
|
||
printf '%s\n' "$(trf state_inactive)"
|
||
}
|
||
|
||
print_menu_header() {
|
||
local current_lang_label service_state
|
||
|
||
if [[ "${ACTIVE_LANG}" == "zh" ]]; then
|
||
current_lang_label="$(trf language_name_zh)"
|
||
else
|
||
current_lang_label="$(trf language_name_en)"
|
||
fi
|
||
|
||
service_state="$(service_state_summary)"
|
||
|
||
printf '\n'
|
||
print_rule "=" 64
|
||
printf '%b%s%b\n' "${COLOR_CYAN}${COLOR_BOLD}" "$(trf menu_title)" "${COLOR_RESET}"
|
||
printf '%b%s%b\n' "${COLOR_DIM}" "$(trf menu_subtitle)" "${COLOR_RESET}"
|
||
print_rule "-" 64
|
||
print_menu_meta_line "$(trf label_service)" "$(format_service_state_badge "${service_state}")"
|
||
print_menu_meta_line "$(trf label_install_dir)" "${INSTALL_DIR}"
|
||
print_menu_meta_line "$(trf label_config)" "${CONFIG_PATH}"
|
||
print_menu_meta_line "$(trf current_language)" "$(format_badge "${current_lang_label}" "${COLOR_CYAN}")"
|
||
print_rule "-" 64
|
||
}
|
||
|
||
print_menu_options() {
|
||
print_menu_section_title "$(trf menu_section_setup)"
|
||
print_menu_item "1" "$(trf action_download_title)" "confirm"
|
||
print_menu_item "2" "$(trf action_install_title)" "confirm"
|
||
print_menu_item "3" "$(trf action_upgrade_title)" "confirm"
|
||
print_menu_item "4" "$(trf action_self_install_title)" "confirm"
|
||
|
||
print_menu_section_title "$(trf menu_section_runtime)"
|
||
print_menu_item "5" "$(trf action_show_config_title)" "direct"
|
||
print_menu_item "6" "$(trf action_init_password_title)" "confirm"
|
||
print_menu_item "7" "$(trf action_status_title)" "direct"
|
||
print_menu_item "8" "$(trf action_logs_title)" "direct"
|
||
|
||
print_menu_section_title "$(trf menu_section_tools)"
|
||
print_menu_item "9" "$(trf action_language_title)" "direct"
|
||
print_menu_item "10" "$(trf action_help_title)" "direct"
|
||
print_menu_item "0" "$(trf menu_exit)"
|
||
|
||
print_rule "-" 64
|
||
printf '%b%s%b\n' "${COLOR_DIM}" "$(trf menu_hint_direct)" "${COLOR_RESET}"
|
||
printf '%b%s%b\n' "${COLOR_DIM}" "$(trf menu_hint_confirm)" "${COLOR_RESET}"
|
||
}
|
||
|
||
run_menu_command() {
|
||
local rc
|
||
|
||
printf '\n'
|
||
if EPCTL_LANG="${ACTIVE_LANG}" "${SCRIPT_SOURCE_PATH}" --lang "${ACTIVE_LANG}" "$@"; then
|
||
success "$(trf command_completed)"
|
||
else
|
||
rc="$?"
|
||
warn "$(trf command_failed_exit "${rc}")"
|
||
fi
|
||
|
||
pause_for_menu
|
||
}
|
||
|
||
interactive_download() {
|
||
local tag
|
||
|
||
confirm_menu_action \
|
||
"$(trf action_download_title)" \
|
||
"$(trf action_download_desc "${CACHE_ROOT}")" || return 0
|
||
|
||
tag="$(prompt_input "$(trf input_release_tag)")" || return 0
|
||
if [[ -n "${tag}" ]]; then
|
||
run_menu_command download --tag "${tag}"
|
||
else
|
||
run_menu_command download
|
||
fi
|
||
}
|
||
|
||
interactive_install() {
|
||
local tag app_uri listen
|
||
|
||
confirm_menu_action \
|
||
"$(trf action_install_title)" \
|
||
"$(trf action_install_desc "${INSTALL_DIR}" "${SYSTEM_USER}" "${SYSTEMD_UNIT_PATH}" "${SERVICE_NAME}")" || return 0
|
||
|
||
tag="$(prompt_input "$(trf input_release_tag)")" || return 0
|
||
app_uri="$(prompt_input "$(trf input_app_uri)" "${DEFAULT_APP_URI}")" || return 0
|
||
listen="$(prompt_input "$(trf input_http_listen)" "${DEFAULT_LISTEN}")" || return 0
|
||
|
||
if [[ -n "${tag}" ]]; then
|
||
run_menu_command install --tag "${tag}" --app-uri "${app_uri}" --listen "${listen}"
|
||
else
|
||
run_menu_command install --app-uri "${app_uri}" --listen "${listen}"
|
||
fi
|
||
}
|
||
|
||
interactive_upgrade() {
|
||
local tag
|
||
|
||
confirm_menu_action \
|
||
"$(trf action_upgrade_title)" \
|
||
"$(trf action_upgrade_desc "${INSTALL_DIR}" "${SERVICE_NAME}")" || return 0
|
||
|
||
tag="$(prompt_input "$(trf input_upgrade_tag)")" || return 0
|
||
if [[ -n "${tag}" ]]; then
|
||
run_menu_command upgrade --tag "${tag}" --prompt-restart
|
||
else
|
||
run_menu_command upgrade --prompt-restart
|
||
fi
|
||
}
|
||
|
||
interactive_logs() {
|
||
local lines
|
||
|
||
print_menu_action_preview \
|
||
"$(trf action_logs_title)" \
|
||
"$(trf action_logs_desc "${SERVICE_NAME}")"
|
||
|
||
lines="$(prompt_input "$(trf input_log_lines)" "100")" || return 0
|
||
run_menu_command logs --lines "${lines}"
|
||
}
|
||
|
||
interactive_switch_language() {
|
||
local choice
|
||
|
||
print_menu_action_preview \
|
||
"$(trf action_language_title)" \
|
||
"$(trf action_language_desc)"
|
||
|
||
printf '\n%b%s%b\n' "${COLOR_BOLD}" "$(trf menu_language_prompt)" "${COLOR_RESET}"
|
||
choice="$(prompt_input "$(trf menu_language_input)")" || return 0
|
||
case "${choice}" in
|
||
1|zh|ZH|cn|CN|中文)
|
||
set_language "zh"
|
||
success "$(trf language_switched "$(trf language_name_zh)" "$(trf language_name_zh)")"
|
||
;;
|
||
2|en|EN|english|English|英文|英语)
|
||
set_language "en"
|
||
success "$(trf language_switched "$(trf language_name_en)" "$(trf language_name_en)")"
|
||
;;
|
||
*)
|
||
warn "$(trf invalid_selection "${choice}")"
|
||
;;
|
||
esac
|
||
pause_for_menu
|
||
}
|
||
|
||
interactive_menu() {
|
||
local choice
|
||
|
||
require_linux
|
||
while true; do
|
||
print_menu_header
|
||
print_menu_options
|
||
printf '%b%s%b' "${COLOR_BOLD}" "$(trf menu_select_action)" "${COLOR_RESET}"
|
||
read -r choice || return 0
|
||
|
||
case "${choice}" in
|
||
1)
|
||
interactive_download
|
||
;;
|
||
2)
|
||
interactive_install
|
||
;;
|
||
3)
|
||
interactive_upgrade
|
||
;;
|
||
4)
|
||
if confirm_menu_action \
|
||
"$(trf action_self_install_title)" \
|
||
"$(trf action_self_install_desc "${EPCTL_NAME}" "${EPCTL_NAME} --lang zh" "${EPCTL_NAME} --lang en")"; then
|
||
run_menu_command self-install
|
||
fi
|
||
;;
|
||
5)
|
||
print_menu_action_preview \
|
||
"$(trf action_show_config_title)" \
|
||
"$(trf action_show_config_desc "${CONFIG_PATH}")"
|
||
run_menu_command show-config
|
||
;;
|
||
6)
|
||
if confirm_menu_action \
|
||
"$(trf action_init_password_title)" \
|
||
"$(trf action_init_password_desc "${CONFIG_PATH}")"; then
|
||
run_menu_command init-password
|
||
fi
|
||
;;
|
||
7)
|
||
print_menu_action_preview \
|
||
"$(trf action_status_title)" \
|
||
"$(trf action_status_desc "${SERVICE_NAME}")"
|
||
run_menu_command status
|
||
;;
|
||
8)
|
||
interactive_logs
|
||
;;
|
||
9)
|
||
interactive_switch_language
|
||
;;
|
||
10)
|
||
print_menu_action_preview \
|
||
"$(trf action_help_title)" \
|
||
"$(trf action_help_desc)"
|
||
usage
|
||
pause_for_menu
|
||
;;
|
||
0|q|Q|quit|QUIT|exit|EXIT)
|
||
return 0
|
||
;;
|
||
*)
|
||
warn "$(trf invalid_selection "${choice}")"
|
||
pause_for_menu
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
main() {
|
||
local command="${1:-}"
|
||
shift || true
|
||
|
||
case "${command}" in
|
||
zh|zh-cn|zh_cn|cn|中文)
|
||
set_language "zh"
|
||
if [[ $# -gt 0 ]]; then
|
||
main "$@"
|
||
elif is_interactive_terminal; then
|
||
interactive_menu
|
||
else
|
||
usage
|
||
fi
|
||
;;
|
||
en|en-us|en_us|english)
|
||
set_language "en"
|
||
if [[ $# -gt 0 ]]; then
|
||
main "$@"
|
||
elif is_interactive_terminal; then
|
||
interactive_menu
|
||
else
|
||
usage
|
||
fi
|
||
;;
|
||
menu|interactive)
|
||
interactive_menu
|
||
;;
|
||
download)
|
||
command_download "$@"
|
||
;;
|
||
install)
|
||
command_install "$@"
|
||
;;
|
||
upgrade)
|
||
command_upgrade "$@"
|
||
;;
|
||
self-install)
|
||
command_self_install "$@"
|
||
;;
|
||
show-config)
|
||
command_show_config "$@"
|
||
;;
|
||
init-password)
|
||
command_init_password "$@"
|
||
;;
|
||
status)
|
||
command_status "$@"
|
||
;;
|
||
logs)
|
||
command_logs "$@"
|
||
;;
|
||
-h|--help|help)
|
||
usage
|
||
;;
|
||
"")
|
||
if is_interactive_terminal; then
|
||
interactive_menu
|
||
else
|
||
usage
|
||
exit 1
|
||
fi
|
||
;;
|
||
*)
|
||
die "$(trf unknown_command "${command}")"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
set_language ""
|
||
parse_global_options "$@"
|
||
init_colors
|
||
main "${POSITIONAL_ARGS[@]}"
|