The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service. feat(scripts): add reset-config to interactive menu + CLI entrypoint The 3m-ui management entrypoint (/usr/local/bin/3m-ui) had an interactive menu but no reset-config option — operators had to know the CLI flag syntax to SSH rescue a panel. Now both paths work: INTERACTIVE MENU (3m-ui with no args) Select option 16 → SSH 救援 / SSH Rescue sub-menu: 1) 仅面板 (listen + SSL) [most common] 2) 面板 + 订阅地址 (public_url) 3) 全量重置 (panel + public + access_profile) 0) 返回 → y/N 确认 → 自动 stop/run binary/start CLI ENTRYPOINT 3m-ui reset-config --panel --yes # scripted rescue 3m-ui reset-config --all --yes # full reset 3m-ui reset-config --help # help (no service touched) 3m-ui help # shows SSH rescue examples CHANGES scripts/3m-ui: - New reset_config() function with interactive scope picker - run_app() extended to pass-through extra args (was single-arg only) - Menu: insert option 16 'SSH 救援重置面板配置', renumber 17-23 - main() case: add 'reset-config' subcommand with stop/run/start cycle; --help short-circuits (read-only, no service touched) - help text: add SSH rescue examples Smoke-tested: dispatch + flag passthrough + scope picker + y/N confirmation all work via fake binary; --help does not stop service.
822 lines
28 KiB
Bash
Executable File
822 lines
28 KiB
Bash
Executable File
#!/bin/sh
|
||
# 3m-ui unified management entrypoint (/usr/local/bin/3m-ui).
|
||
# Application binary: /usr/local/lib/3m-ui/3m-ui-bin
|
||
# Original work for 3m-ui (EPL-2.0). Ideas inspired by common panel ops menus;
|
||
# not a derivative of any third-party management script source.
|
||
|
||
set -eu
|
||
|
||
ROOT="${THREE_M_UI_ROOT:-}"
|
||
BASE="$ROOT/usr/local/lib/3m-ui"
|
||
MANAGER="$BASE/3m-ui.sh"
|
||
INSTALLER="$BASE/install.sh"
|
||
UPDATER="$BASE/update.sh"
|
||
UNINSTALLER="$BASE/uninstall.sh"
|
||
APP_BIN="$BASE/3m-ui-bin"
|
||
CONFIG_FILE="$ROOT/etc/3m-ui/config.yaml"
|
||
DATA_DIR="${THREE_M_UI_DATA_DIR:-}"
|
||
if [ -z "$DATA_DIR" ] && [ -s "$BASE/DATA_DIRECTORY" ]; then
|
||
DATA_DIR="$(cat "$BASE/DATA_DIRECTORY")"
|
||
fi
|
||
DATA_DIR="${DATA_DIR:-$ROOT/var/lib/3m-ui}"
|
||
|
||
# --- helpers -----------------------------------------------------------------
|
||
|
||
say() { printf '%s\n' "$*"; }
|
||
err() { printf '%s\n' "$*" >&2; }
|
||
|
||
need_root() {
|
||
[ "$(id -u)" -eq 0 ] || { err '请使用 root 运行 3m-ui 管理命令。 / Run as root.'; exit 1; }
|
||
}
|
||
|
||
confirm() {
|
||
# usage: confirm "prompt" [default y|n]
|
||
prompt="$1"
|
||
def="${2:-n}"
|
||
printf '%s [%s]: ' "$prompt" "$def"
|
||
IFS= read -r ans || return 1
|
||
[ -z "$ans" ] && ans="$def"
|
||
case "$ans" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
|
||
}
|
||
|
||
pause() {
|
||
printf '\n%s' '按回车返回菜单 / Press Enter to return... '
|
||
IFS= read -r _ || true
|
||
}
|
||
|
||
run_script() {
|
||
script="$1"
|
||
shift
|
||
[ -x "$script" ] || {
|
||
err "管理脚本不存在: $script"
|
||
return 1
|
||
}
|
||
"$script" "$@"
|
||
}
|
||
|
||
run_app() {
|
||
cmd="$1"
|
||
shift
|
||
if [ ! -x "$APP_BIN" ]; then
|
||
err "未找到程序: $APP_BIN"
|
||
return 1
|
||
fi
|
||
mkdir -p "$DATA_DIR"
|
||
(cd "$DATA_DIR" && THREE_M_UI_CONFIG="$CONFIG_FILE" THREE_M_UI_DATA_DIR="$DATA_DIR" "$APP_BIN" "$cmd" "$@")
|
||
}
|
||
|
||
init_system() {
|
||
if [ -d "$ROOT/run/systemd/system" ] && command -v systemctl >/dev/null 2>&1; then
|
||
printf '%s' systemd
|
||
elif command -v rc-service >/dev/null 2>&1; then
|
||
printf '%s' openrc
|
||
else
|
||
printf '%s' unsupported
|
||
fi
|
||
}
|
||
|
||
service_action() {
|
||
action="$1"
|
||
case "$(init_system)" in
|
||
systemd) systemctl "$action" 3m-ui ;;
|
||
openrc) rc-service 3m-ui "$action" ;;
|
||
*) err '未检测到 systemd 或 OpenRC。'; return 1 ;;
|
||
esac
|
||
}
|
||
|
||
service_is_active() {
|
||
case "$(init_system)" in
|
||
systemd) systemctl is-active --quiet 3m-ui ;;
|
||
openrc) rc-service 3m-ui status >/dev/null 2>&1 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
service_enable() {
|
||
case "$(init_system)" in
|
||
systemd)
|
||
if systemctl enable 3m-ui; then
|
||
say '已设置开机自启 / Enabled on boot.'
|
||
else
|
||
err 'enable 失败(服务单元是否已安装?)'
|
||
return 1
|
||
fi
|
||
;;
|
||
openrc)
|
||
rc-update add 3m-ui default && say '已设置开机自启 / Enabled on boot.'
|
||
;;
|
||
*) err '不支持的 init'; return 1 ;;
|
||
esac
|
||
}
|
||
|
||
service_disable() {
|
||
case "$(init_system)" in
|
||
systemd)
|
||
if systemctl disable 3m-ui; then
|
||
say '已取消开机自启 / Disabled on boot.'
|
||
else
|
||
err 'disable 失败'
|
||
return 1
|
||
fi
|
||
;;
|
||
openrc)
|
||
rc-update del 3m-ui default && say '已取消开机自启 / Disabled on boot.'
|
||
;;
|
||
*) err '不支持的 init'; return 1 ;;
|
||
esac
|
||
}
|
||
|
||
yaml_get() {
|
||
# yaml_get KEY_LINE_PREFIX e.g. " port:"
|
||
key="$1"
|
||
if [ ! -f "$CONFIG_FILE" ]; then
|
||
return 1
|
||
fi
|
||
awk -v k="$key" 'index($0, k) == 1 { sub(/^[^:]+:[[:space:]]*/, ""); gsub(/["\047]/, ""); print; exit }' "$CONFIG_FILE"
|
||
}
|
||
|
||
yaml_set() {
|
||
# yaml_set " port:" "8080" — sets or inserts under server:
|
||
key="$1"
|
||
val="$2"
|
||
if [ ! -f "$CONFIG_FILE" ]; then
|
||
err "配置不存在: $CONFIG_FILE"
|
||
return 1
|
||
fi
|
||
tmp="${CONFIG_FILE}.tmp.$$"
|
||
if grep -q "^${key}" "$CONFIG_FILE" 2>/dev/null; then
|
||
# Escape for sed
|
||
esc=$(printf '%s' "$val" | sed -e 's/[\\|&]/\\&/g')
|
||
sed "s|^${key}.*|${key} ${esc}|" "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||
else
|
||
awk -v k="$key" -v v="$val" '
|
||
BEGIN { done=0 }
|
||
{ print }
|
||
/^server:/ && !done { print k " " v; done=1 }
|
||
' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||
fi
|
||
}
|
||
|
||
# --- service ops -------------------------------------------------------------
|
||
|
||
status() {
|
||
case "$(init_system)" in
|
||
systemd) systemctl --no-pager --full status 3m-ui || true ;;
|
||
openrc) rc-service 3m-ui status || true ;;
|
||
*) say 'init: unsupported' ;;
|
||
esac
|
||
printf '\n'
|
||
if service_is_active; then
|
||
say '状态: 运行中 / running'
|
||
else
|
||
say '状态: 未运行 / stopped'
|
||
fi
|
||
case "$(init_system)" in
|
||
systemd)
|
||
if systemctl is-enabled --quiet 3m-ui 2>/dev/null; then
|
||
say '开机自启: 是 / enabled'
|
||
else
|
||
say '开机自启: 否 / disabled'
|
||
fi
|
||
;;
|
||
esac
|
||
if [ -x "$APP_BIN" ]; then
|
||
say "应用程序: $APP_BIN"
|
||
"$APP_BIN" --version 2>/dev/null || true
|
||
else
|
||
say '应用程序二进制不存在。'
|
||
fi
|
||
}
|
||
|
||
logs() {
|
||
case "$(init_system)" in
|
||
systemd)
|
||
if [ "${1:-}" = "-f" ] || [ "${1:-}" = "follow" ]; then
|
||
# Ctrl+C must not abort the interactive menu under set -e
|
||
journalctl -u 3m-ui -f --no-pager || true
|
||
else
|
||
journalctl -u 3m-ui -n 100 --no-pager || true
|
||
fi
|
||
;;
|
||
openrc)
|
||
if [ -f /var/log/3m-ui/3m-ui.log ]; then
|
||
if [ "${1:-}" = "-f" ] || [ "${1:-}" = "follow" ]; then
|
||
tail -n 50 -f /var/log/3m-ui/3m-ui.log || true
|
||
else
|
||
tail -n 100 /var/log/3m-ui/3m-ui.log || true
|
||
fi
|
||
else
|
||
say '未找到日志文件 /var/log/3m-ui/3m-ui.log'
|
||
fi
|
||
;;
|
||
*) say '未找到可用的日志系统。' ;;
|
||
esac
|
||
}
|
||
|
||
version() {
|
||
printed=0
|
||
if [ -x "$APP_BIN" ]; then
|
||
out="$("$APP_BIN" --version 2>/dev/null || true)"
|
||
[ -z "$out" ] && out="$("$APP_BIN" version 2>/dev/null || true)"
|
||
if [ -n "$out" ]; then
|
||
printf '%s\n' "$out"
|
||
printed=1
|
||
fi
|
||
fi
|
||
for f in VERSION CHANNEL MIHOMO_VERSION REPOSITORY; do
|
||
if [ -s "$BASE/$f" ]; then
|
||
case "$f" in
|
||
VERSION) printf 'Installed release: %s\n' "$(cat "$BASE/$f")" ;;
|
||
CHANNEL) printf 'Update channel: %s\n' "$(cat "$BASE/$f")" ;;
|
||
MIHOMO_VERSION) printf 'Bundled Mihomo: %s\n' "$(cat "$BASE/$f")" ;;
|
||
REPOSITORY) printf 'Release source: %s\n' "$(cat "$BASE/$f")" ;;
|
||
esac
|
||
printed=1
|
||
fi
|
||
done
|
||
if [ -x "$APP_BIN" ]; then
|
||
printf 'Binary: %s\n' "$APP_BIN"
|
||
printed=1
|
||
fi
|
||
if [ "$printed" -eq 0 ]; then
|
||
err '3m-ui 未安装或无法读取版本信息。'
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# --- config / access ---------------------------------------------------------
|
||
|
||
show_access() {
|
||
port=$(yaml_get ' port:' 2>/dev/null || true)
|
||
port="${port:-8080}"
|
||
listen=$(yaml_get ' listen:' 2>/dev/null || true)
|
||
pub=$(yaml_get ' public_url:' 2>/dev/null || true)
|
||
web=$(yaml_get ' web_path:' 2>/dev/null || true)
|
||
subp=$(yaml_get ' sub_path:' 2>/dev/null || true)
|
||
subport=$(yaml_get ' sub_port:' 2>/dev/null || true)
|
||
|
||
say "配置文件: $CONFIG_FILE"
|
||
say "面板端口 / port: $port"
|
||
[ -n "$listen" ] && say "监听 / listen: $listen"
|
||
[ -n "$pub" ] && [ "$pub" != "null" ] && say "public_url: $pub"
|
||
[ -n "$web" ] && say "web_path: $web"
|
||
[ -n "$subp" ] && say "sub_path: $subp"
|
||
[ -n "$subport" ] && [ "$subport" != "0" ] && say "sub_port: $subport"
|
||
|
||
path_suffix=""
|
||
case "$web" in
|
||
/*) path_suffix="$web" ;;
|
||
"") path_suffix="" ;;
|
||
*) path_suffix="/$web" ;;
|
||
esac
|
||
|
||
if [ -n "$pub" ] && [ "$pub" != "null" ]; then
|
||
# strip trailing slash for display
|
||
base=$(printf '%s' "$pub" | sed 's|/*$||')
|
||
say "面板访问 / Panel URI: ${base}${path_suffix}/"
|
||
else
|
||
say "面板访问示例 (请换成公网 IP/域名):"
|
||
say " http://<IP>:${port}${path_suffix}/"
|
||
say " https://<域名>/ (若已启用面板 SSL 且监听 443)"
|
||
fi
|
||
say '面板启用 HTTPS 后,请使用面板中配置的域名和 HTTPS 端口。'
|
||
if run_app healthcheck >/dev/null 2>&1; then
|
||
say '面板健康检查通过 / healthcheck OK'
|
||
else
|
||
say '面板健康检查未通过或未就绪 (可稍后重试)'
|
||
fi
|
||
say '管理命令: 3m-ui'
|
||
say '初始密码在首次安装时随机生成;遗失可用菜单「重置管理员密码」。'
|
||
}
|
||
|
||
change_port() {
|
||
if [ ! -f "$CONFIG_FILE" ]; then
|
||
err "配置文件不存在: $CONFIG_FILE"
|
||
return 1
|
||
fi
|
||
current=$(yaml_get ' port:' 2>/dev/null || echo "8080")
|
||
printf '当前面板端口: %s\n' "$current"
|
||
printf '输入新端口 (1-65535, 回车取消): '
|
||
IFS= read -r new_port || return 0
|
||
[ -z "$new_port" ] && return 0
|
||
case "$new_port" in
|
||
''|*[!0-9]*) err '无效端口。'; return 1 ;;
|
||
esac
|
||
[ "$new_port" -ge 1 ] && [ "$new_port" -le 65535 ] || {
|
||
err '端口必须在 1-65535 范围内。'
|
||
return 1
|
||
}
|
||
[ "$new_port" = "$current" ] && { say '端口未变更。'; return 0; }
|
||
yaml_set ' port:' "$new_port"
|
||
say "端口已更改: $current → $new_port"
|
||
say '正在重启 3m-ui...'
|
||
service_action restart
|
||
sleep 1
|
||
if service_is_active; then
|
||
say "✓ 已重启。配置端口: $new_port"
|
||
else
|
||
err '✗ 启动失败,请检查: 3m-ui logs'
|
||
fi
|
||
}
|
||
|
||
set_public_url() {
|
||
if [ ! -f "$CONFIG_FILE" ]; then
|
||
err "配置不存在: $CONFIG_FILE"
|
||
return 1
|
||
fi
|
||
cur=$(yaml_get ' public_url:' 2>/dev/null || true)
|
||
printf '当前 public_url: %s\n' "${cur:-<empty>}"
|
||
printf '输入新的 public_url (例如 https://example.com ,回车取消): '
|
||
IFS= read -r url || return 0
|
||
[ -z "$url" ] && return 0
|
||
yaml_set ' public_url:' "\"$url\""
|
||
say '已写入 public_url。'
|
||
if confirm '是否立即重启面板?' y; then
|
||
service_action restart
|
||
sleep 1
|
||
service_is_active && say '✓ 已重启' || err '✗ 重启后未运行'
|
||
fi
|
||
}
|
||
|
||
set_panel_paths() {
|
||
if [ ! -f "$CONFIG_FILE" ]; then
|
||
err "配置不存在: $CONFIG_FILE"
|
||
return 1
|
||
fi
|
||
say '留空则保持现有值。 / Leave blank to keep current.'
|
||
cur_web=$(yaml_get ' web_path:' 2>/dev/null || true)
|
||
cur_sub=$(yaml_get ' sub_path:' 2>/dev/null || true)
|
||
cur_sp=$(yaml_get ' sub_port:' 2>/dev/null || true)
|
||
cur_listen=$(yaml_get ' listen:' 2>/dev/null || true)
|
||
printf 'web_path (面板路径前缀, 现: %s): ' "${cur_web:-<empty>}"
|
||
IFS= read -r web || true
|
||
printf 'sub_path (订阅路径前缀, 现: %s): ' "${cur_sub:-<empty>}"
|
||
IFS= read -r sub || true
|
||
printf 'sub_port (0=与面板同端口, 现: %s): ' "${cur_sp:-0}"
|
||
IFS= read -r sp || true
|
||
printf 'listen (空=全部接口, 现: %s): ' "${cur_listen:-<empty>}"
|
||
IFS= read -r lis || true
|
||
|
||
changed=0
|
||
if [ -n "${web:-}" ]; then
|
||
yaml_set ' web_path:' "\"$web\""
|
||
changed=1
|
||
fi
|
||
if [ -n "${sub:-}" ]; then
|
||
yaml_set ' sub_path:' "\"$sub\""
|
||
changed=1
|
||
fi
|
||
if [ -n "${sp:-}" ]; then
|
||
case "$sp" in
|
||
''|*[!0-9]*) err 'sub_port 无效' ;;
|
||
*) yaml_set ' sub_port:' "$sp"; changed=1 ;;
|
||
esac
|
||
fi
|
||
if [ -n "${lis:-}" ]; then
|
||
yaml_set ' listen:' "\"$lis\""
|
||
changed=1
|
||
fi
|
||
if [ "$changed" -eq 0 ]; then
|
||
say '未修改。'
|
||
return 0
|
||
fi
|
||
say '已写入配置。'
|
||
if confirm '是否立即重启面板?' y; then
|
||
service_action restart
|
||
sleep 1
|
||
service_is_active && say '✓ 已重启' || err '✗ 重启后未运行'
|
||
fi
|
||
}
|
||
|
||
reset_admin() {
|
||
if [ ! -x "$APP_BIN" ]; then
|
||
err "未找到程序: $APP_BIN"
|
||
return 1
|
||
fi
|
||
say '将随机生成新的管理员密码,并要求下次登录修改密码。'
|
||
if ! confirm '确认继续?' n; then
|
||
say '已取消。'
|
||
return 0
|
||
fi
|
||
run_app reset-admin || return 1
|
||
}
|
||
|
||
reset_config() {
|
||
if [ ! -x "$APP_BIN" ]; then
|
||
err "未找到程序: $APP_BIN"
|
||
return 1
|
||
fi
|
||
say '=== SSH 救援 / SSH Rescue ==='
|
||
say '当面板配置错误(端口/SSL/监听地址)导致 Web 无法访问时,'
|
||
say '可通过此菜单重置为安全默认值,重启后面板以 HTTP 模式启动。'
|
||
say ''
|
||
say '重置范围 / Scope:'
|
||
say ' 1) 仅面板 (listen + SSL) — 最常用,仅 Web 打不开时'
|
||
say ' 2) 面板 + 订阅地址 — 同时清除 public_url'
|
||
say ' 3) 面板 + 订阅 + 节点公网地址 — 全量重置(含 access_profile)'
|
||
say ' 0) 返回 / Cancel'
|
||
printf '选择 [0-3]: '
|
||
IFS= read -r scope || return 0
|
||
case "$scope" in
|
||
1) flags='--panel --yes' ;;
|
||
2) flags='--panel --public --yes' ;;
|
||
3) flags='--all --yes' ;;
|
||
*) say '已取消。'; return 0 ;;
|
||
esac
|
||
say ''
|
||
say "将以 ${flags} 重置。该操作会停止面板、修改 config.yaml + DB,然后重启面板。"
|
||
say '不会删除用户 / 节点 / 流量记录 / 凭证。'
|
||
if ! confirm '确认继续?' n; then
|
||
say '已取消。'
|
||
return 0
|
||
fi
|
||
say ''
|
||
say '停止 3m-ui 服务...'
|
||
service_action stop >/dev/null 2>&1 || true
|
||
if ! run_app reset-config $flags; then
|
||
err '✗ reset-config 失败,请查看上方错误。面板将以旧配置重启。'
|
||
service_action start || true
|
||
return 1
|
||
fi
|
||
say ''
|
||
say '重启 3m-ui 服务...'
|
||
service_action start || true
|
||
sleep 1
|
||
if service_is_active; then
|
||
say '✓ 重置完成。请用 HTTP(不是 HTTPS)访问面板:'
|
||
port=$(yaml_get ' port:' 2>/dev/null || echo "8080")
|
||
say " http://<server-ip>:${port}/"
|
||
say '在 Web UI 重新应用 SSL / 域名 / 订阅地址设置。'
|
||
else
|
||
err '✗ 面板启动失败,请检查: 3m-ui logs'
|
||
fi
|
||
}
|
||
|
||
# --- BBR ---------------------------------------------------------------------
|
||
|
||
bbr_status() {
|
||
if [ "$(uname -s)" != Linux ]; then
|
||
err '仅支持 Linux。'
|
||
return 1
|
||
fi
|
||
sysctl net.ipv4.tcp_congestion_control 2>/dev/null || true
|
||
sysctl net.core.default_qdisc 2>/dev/null || true
|
||
}
|
||
|
||
enable_bbr() {
|
||
if [ "$(uname -s)" != Linux ]; then
|
||
err '仅支持 Linux。'
|
||
return 1
|
||
fi
|
||
if sysctl net.ipv4.tcp_congestion_control 2>/dev/null | grep -q bbr; then
|
||
say 'BBR 已启用。'
|
||
return 0
|
||
fi
|
||
modprobe tcp_bbr 2>/dev/null || true
|
||
if [ ! -f /etc/sysctl.d/99-3m-ui-bbr.conf ]; then
|
||
printf '%s\n' 'net.core.default_qdisc=fq' > /etc/sysctl.d/99-3m-ui-bbr.conf
|
||
printf '%s\n' 'net.ipv4.tcp_congestion_control=bbr' >> /etc/sysctl.d/99-3m-ui-bbr.conf
|
||
fi
|
||
sysctl --system >/dev/null 2>&1 || sysctl -p /etc/sysctl.d/99-3m-ui-bbr.conf 2>/dev/null || true
|
||
bbr_status
|
||
say '已尝试启用 BBR。'
|
||
}
|
||
|
||
disable_bbr() {
|
||
if [ "$(uname -s)" != Linux ]; then
|
||
err '仅支持 Linux。'
|
||
return 1
|
||
fi
|
||
rm -f /etc/sysctl.d/99-3m-ui-bbr.conf
|
||
sysctl -w net.ipv4.tcp_congestion_control=cubic >/dev/null 2>&1 || true
|
||
sysctl -w net.core.default_qdisc=fq_codel >/dev/null 2>&1 || true
|
||
bbr_status
|
||
say '已尝试恢复默认拥塞控制 (cubic)。重启后仍可能被其它 sysctl 覆盖。'
|
||
}
|
||
|
||
bbr_menu() {
|
||
say ''
|
||
say ' 1) 状态 / Status'
|
||
say ' 2) 启用 BBR / Enable'
|
||
say ' 3) 关闭 BBR / Disable'
|
||
say ' 0) 返回'
|
||
printf '选择: '
|
||
IFS= read -r c || return 0
|
||
case "$c" in
|
||
1) bbr_status ;;
|
||
2) enable_bbr ;;
|
||
3) disable_bbr ;;
|
||
*) ;;
|
||
esac
|
||
}
|
||
|
||
# --- Geo / firewall / SSL helpers --------------------------------------------
|
||
|
||
update_geo() {
|
||
# Geo databases influence routing. Do not download a moving "latest" asset
|
||
# from a root-owned management script without a release checksum.
|
||
say 'Geo 数据库请在面板「系统设置 → 网络与 Geo」中更新。'
|
||
say 'Use Settings → Network & Geo in the authenticated panel to update Geo files.'
|
||
say '该入口会显示每个文件的更新结果;管理脚本不会下载未校验的 latest 资产。'
|
||
}
|
||
|
||
open_firewall() {
|
||
say '尝试放行常用端口 (22, 80, 443, 面板端口)...'
|
||
port=$(yaml_get ' port:' 2>/dev/null || echo "8080")
|
||
if command -v ufw >/dev/null 2>&1; then
|
||
ufw allow 22/tcp || true
|
||
ufw allow 80/tcp || true
|
||
ufw allow 443/tcp || true
|
||
ufw allow "${port}/tcp" || true
|
||
say '已向 ufw 提交规则 (请自行 ufw enable / status)。'
|
||
elif command -v firewall-cmd >/dev/null 2>&1; then
|
||
firewall-cmd --permanent --add-port=80/tcp || true
|
||
firewall-cmd --permanent --add-port=443/tcp || true
|
||
firewall-cmd --permanent --add-port="${port}/tcp" || true
|
||
firewall-cmd --reload || true
|
||
say '已尝试 firewalld 放行。'
|
||
else
|
||
say '未检测到 ufw/firewalld。请在云安全组放行 80/443 与面板端口。'
|
||
fi
|
||
}
|
||
|
||
ssl_cert_menu() {
|
||
say ''
|
||
say '面板 HTTPS 推荐在「系统设置 → SSL」使用内置 ACME。'
|
||
say '以下为可选:用外部工具申请 PEM,供节点或反代使用。'
|
||
say ''
|
||
say ' 1) 打印 certbot 命令 (standalone)'
|
||
say ' 2) 打印 acme.sh 命令 (standalone)'
|
||
say ' 3) 打印 Cloudflare DNS (acme.sh) 示例'
|
||
say ' 0) 返回'
|
||
printf '选择: '
|
||
IFS= read -r c || return 0
|
||
printf '域名 (例 dzx.qzz.io): '
|
||
IFS= read -r dom || return 0
|
||
[ -z "$dom" ] && return 0
|
||
printf '邮箱 (可空): '
|
||
IFS= read -r em || true
|
||
case "$c" in
|
||
1)
|
||
say ''
|
||
say '# 申请前请确保 80 未被占用(可临时: systemctl stop 3m-ui)'
|
||
if [ -n "$em" ]; then
|
||
say "certbot certonly --standalone -d $dom --email $em --agree-tos --non-interactive"
|
||
else
|
||
say "certbot certonly --standalone -d $dom --register-unsafely-without-email --agree-tos --non-interactive"
|
||
fi
|
||
say "# 证书: /etc/letsencrypt/live/$dom/fullchain.pem"
|
||
say "# 私钥: /etc/letsencrypt/live/$dom/privkey.pem"
|
||
;;
|
||
2)
|
||
say ''
|
||
say 'curl https://get.acme.sh | sh'
|
||
say "~/.acme.sh/acme.sh --set-default-ca --server letsencrypt"
|
||
say "~/.acme.sh/acme.sh --issue -d $dom --standalone"
|
||
say "~/.acme.sh/acme.sh --install-cert -d $dom \\"
|
||
say " --key-file /etc/3m-ui/certs/${dom}.key \\"
|
||
say " --fullchain-file /etc/3m-ui/certs/${dom}.crt \\"
|
||
say " --reloadcmd \"systemctl restart 3m-ui\""
|
||
;;
|
||
3)
|
||
say ''
|
||
say 'export CF_Token="your_api_token"'
|
||
say "~/.acme.sh/acme.sh --issue -d $dom --dns dns_cf"
|
||
say "# 不占用 80,适合面板已占用 ACME 端口时给节点另签证书"
|
||
;;
|
||
*) ;;
|
||
esac
|
||
}
|
||
|
||
# --- update management script itself -----------------------------------------
|
||
|
||
update_self() {
|
||
# Lifecycle scripts and the panel binary are a single release unit. Routing
|
||
# this action through the installer keeps the checksum, provenance option,
|
||
# rollback snapshot, and health check that protect normal updates.
|
||
say '管理脚本会随已验证的面板版本一起更新。'
|
||
if ! confirm '通过已验证的发布更新 3m-ui(包括管理脚本)?' n; then
|
||
say '已取消。'
|
||
return 0
|
||
fi
|
||
run_script "$UPDATER" || return 1
|
||
}
|
||
|
||
update_to_version() {
|
||
printf '输入版本号 (如 v1.1.1,回车取消): '
|
||
IFS= read -r ver || return 0
|
||
[ -z "$ver" ] && return 0
|
||
case "$ver" in
|
||
v*) ;;
|
||
*) ver="v$ver" ;;
|
||
esac
|
||
say "将更新到 $ver ..."
|
||
run_script "$UPDATER" "$ver" || return 1
|
||
}
|
||
|
||
# --- menu --------------------------------------------------------------------
|
||
|
||
menu() {
|
||
while :; do
|
||
printf '\n'
|
||
printf '%s\n' '╭──────────────────────────────────────────────╮'
|
||
printf '%s\n' '│ 3m-ui 管理控制台 / Management Console │'
|
||
printf '%s\n' '│ Mihomo Core Management UI │'
|
||
printf '%s\n' '╰──────────────────────────────────────────────╯'
|
||
printf '\n'
|
||
printf '%s\n' ' 1. 安装/修复 | Install / Repair'
|
||
printf '%s\n' ' 2. 更新到最新 | Update latest'
|
||
printf '%s\n' ' 3. 更新到指定版本 | Update to version'
|
||
printf '%s\n' ' 4. 运行状态 | Status'
|
||
printf '%s\n' ' 5. 启动 | Start'
|
||
printf '%s\n' ' 6. 重启 | Restart'
|
||
printf '%s\n' ' 7. 停止 | Stop'
|
||
printf '%s\n' ' 8. 开机自启 开/关 | Enable/Disable'
|
||
printf '%s\n' ' 9. 日志 | Logs'
|
||
printf '%s\n' ' 10. 版本 | Version'
|
||
printf '%s\n' ' 11. 访问信息 / URI | Access info'
|
||
printf '%s\n' ' 12. 修改面板端口 | Change port'
|
||
printf '%s\n' ' 13. 设置 public_url | Public URL'
|
||
printf '%s\n' ' 14. 路径/订阅端口 | web_path sub_path sub_port'
|
||
printf '%s\n' ' 15. 重置管理员密码 | Reset admin'
|
||
printf '%s\n' ' 16. SSH 救援重置面板配置 | Reset-config (SSH rescue)'
|
||
printf '%s\n' ' 17. 健康检查 | Healthcheck'
|
||
printf '%s\n' ' 18. BBR | BBR menu'
|
||
printf '%s\n' ' 19. 更新 Geo | Update Geo'
|
||
printf '%s\n' ' 20. 防火墙放行提示 | Firewall'
|
||
printf '%s\n' ' 21. SSL 证书助手 | SSL cert helper'
|
||
printf '%s\n' ' 22. 备份 | Backup'
|
||
printf '%s\n' ' 23. 更新本管理脚本 | Update this script'
|
||
printf '%s\n' ' a. 卸载 | Uninstall'
|
||
printf '%s\n' ' 0. 退出 | Exit'
|
||
printf '\n请选择 / Select: '
|
||
IFS= read -r choice || exit 0
|
||
case "$choice" in
|
||
1) run_script "$INSTALLER" || true; pause ;;
|
||
2) run_script "$UPDATER" || true; pause ;;
|
||
3) update_to_version || true; pause ;;
|
||
4) status || true; pause ;;
|
||
5) service_action start || true; pause ;;
|
||
6) service_action restart || true; pause ;;
|
||
7) service_action stop || true; pause ;;
|
||
8)
|
||
printf 'e=启用 enable / d=禁用 disable: '
|
||
IFS= read -r ed || true
|
||
case "$ed" in
|
||
e|E) service_enable || true ;;
|
||
d|D) service_disable || true ;;
|
||
esac
|
||
pause
|
||
;;
|
||
9)
|
||
printf '跟随输出? [y/N]: '
|
||
IFS= read -r fl || true
|
||
case "$fl" in y|Y) logs -f || true ;; *) logs || true; pause ;; esac
|
||
;;
|
||
10) version || true; pause ;;
|
||
11) show_access || true; pause ;;
|
||
12) change_port || true; pause ;;
|
||
13) set_public_url || true; pause ;;
|
||
14) set_panel_paths || true; pause ;;
|
||
15) reset_admin || true; pause ;;
|
||
16) reset_config || true; pause ;;
|
||
17) run_app healthcheck || true; pause ;;
|
||
18) bbr_menu || true; pause ;;
|
||
19) update_geo || true; pause ;;
|
||
20) open_firewall || true; pause ;;
|
||
21) ssl_cert_menu || true; pause ;;
|
||
22) run_script "$INSTALLER" --backup || true; pause ;;
|
||
23) update_self || true; pause ;;
|
||
a|A) run_script "$UNINSTALLER" || true; pause ;;
|
||
0) exit 0 ;;
|
||
*) say '无效选择,请重新输入。' ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# --- main --------------------------------------------------------------------
|
||
|
||
|
||
channel_cmd() {
|
||
ch_file="$BASE/CHANNEL"
|
||
arg="${1:-}"
|
||
do_update=0
|
||
if [ "$#" -gt 0 ]; then
|
||
shift
|
||
fi
|
||
for a in "$@"; do
|
||
case "$a" in --update|update|-u) do_update=1 ;; esac
|
||
done
|
||
if [ -z "$arg" ]; then
|
||
cur=stable
|
||
if [ -s "$ch_file" ]; then
|
||
cur=$(tr -d '\n\r' < "$ch_file")
|
||
fi
|
||
case "$cur" in pre|prerelease) cur=pre ;; *) cur=stable ;; esac
|
||
say "Current update channel: $cur"
|
||
say "Switch: 3m-ui channel stable | 3m-ui channel pre"
|
||
say "Switch and upgrade now: 3m-ui channel pre --update"
|
||
return 0
|
||
fi
|
||
case "$arg" in
|
||
stable|pre) ;;
|
||
prerelease) arg=pre ;;
|
||
*)
|
||
err "Unknown channel: $arg (use stable or pre)"
|
||
return 1
|
||
;;
|
||
esac
|
||
mkdir -p "$BASE"
|
||
printf '%s\n' "$arg" > "$ch_file"
|
||
say "Update channel set to: $arg (saved in $ch_file)"
|
||
if [ "$do_update" -eq 1 ]; then
|
||
say "Running update on channel $arg ..."
|
||
THREE_M_UI_CHANNEL="$arg" run_script "$UPDATER" || return 1
|
||
else
|
||
say "Next: 3m-ui update # install latest $arg release"
|
||
say " or: 3m-ui channel $arg --update"
|
||
fi
|
||
}
|
||
|
||
main() {
|
||
cmd=${1:-}
|
||
case "$cmd" in
|
||
help|-h|--help) ;;
|
||
*) need_root ;;
|
||
esac
|
||
case "$cmd" in
|
||
status) status ;;
|
||
start) service_action start ;;
|
||
restart) service_action restart ;;
|
||
stop) service_action stop ;;
|
||
enable) service_enable ;;
|
||
disable) service_disable ;;
|
||
logs) shift; logs "$@" ;;
|
||
version) version ;;
|
||
channel) shift; channel_cmd "$@" ;;
|
||
config) shift; run_script "$MANAGER" config "$@" ;;
|
||
backup) shift; run_script "$INSTALLER" --backup "$@" ;;
|
||
restore) shift; run_script "$INSTALLER" --restore "$@" ;;
|
||
init|reset-admin|healthcheck) run_app "$cmd" ;;
|
||
reset-config)
|
||
# reset-config needs the service stopped before the Go binary
|
||
# runs (it writes config.yaml + DB rows the running panel caches).
|
||
# --help / -h short-circuits — no stop/restart needed.
|
||
shift # drop 'reset-config' so $@ holds the flags
|
||
help_only=0
|
||
for a in "$@"; do
|
||
case "$a" in --help|-h) help_only=1 ;; esac
|
||
done
|
||
if [ "$help_only" = 1 ]; then
|
||
run_app reset-config "$@"
|
||
else
|
||
say '停止 3m-ui 服务...'
|
||
service_action stop >/dev/null 2>&1 || true
|
||
if ! run_app reset-config "$@"; then
|
||
err '✗ reset-config 失败,请查看上方错误。'
|
||
service_action start || true
|
||
exit 1
|
||
fi
|
||
say '重启 3m-ui 服务...'
|
||
service_action start || true
|
||
fi
|
||
;;
|
||
update|upgrade)
|
||
shift
|
||
run_script "$UPDATER" "$@"
|
||
;;
|
||
install|fix)
|
||
shift
|
||
run_script "$INSTALLER" "$@"
|
||
;;
|
||
uninstall|remove)
|
||
shift
|
||
run_script "$UNINSTALLER" "$@"
|
||
;;
|
||
menu) menu ;;
|
||
help|-h|--help)
|
||
printf '%s\n' '用法: 3m-ui <命令> [参数]'
|
||
printf '%s\n' ''
|
||
printf '%s\n' '服务: status | start | restart | stop | enable | disable | logs | version | channel'
|
||
printf '%s\n' '配置: config show | config port <端口>'
|
||
printf '%s\n' '安装: update [vX.Y.Z|pre] | channel stable|pre [--update] | install | uninstall [--purge]'
|
||
printf '%s\n' '数据: backup | restore <snapshot.tar.gz> | reset-admin | reset-config | healthcheck'
|
||
printf '%s\n' '其他: menu | help'
|
||
printf '%s\n' ''
|
||
printf '%s\n' 'SSH 救援 / SSH rescue:'
|
||
printf '%s\n' ' 3m-ui reset-config --panel --yes # 重置 listen/SSL,重启后 HTTP 访问'
|
||
printf '%s\n' ' 3m-ui reset-config --all --yes # 全量重置(含 public_url + access_profile)'
|
||
printf '%s\n' ''
|
||
printf '%s\n' '不带参数时进入交互式菜单。'
|
||
;;
|
||
'') menu ;;
|
||
*)
|
||
err "未知命令: $cmd"
|
||
err '运行: 3m-ui help'
|
||
exit 2
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|