mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 04:03:04 +08:00
This commit is contained in:
@@ -143,6 +143,8 @@ OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命
|
|||||||
clashctl add <订阅链接> <名称>
|
clashctl add <订阅链接> <名称>
|
||||||
clashctl use
|
clashctl use
|
||||||
clashctl ls
|
clashctl ls
|
||||||
|
clashctl sub update
|
||||||
|
clashsub update # 兼容旧版命令
|
||||||
clashctl sub
|
clashctl sub
|
||||||
clashctl sub list
|
clashctl sub list
|
||||||
clashctl sub enable <名称>
|
clashctl sub enable <名称>
|
||||||
|
|||||||
@@ -396,6 +396,10 @@ clashls() {
|
|||||||
clashctl ls "$@"
|
clashctl ls "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clashsub() {
|
||||||
|
clashctl sub "$@"
|
||||||
|
}
|
||||||
|
|
||||||
clashselect() {
|
clashselect() {
|
||||||
clashctl select "$@"
|
clashctl select "$@"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ Usage:
|
|||||||
|
|
||||||
📦 Subscription:
|
📦 Subscription:
|
||||||
config show 📡 查看当前订阅
|
config show 📡 查看当前订阅
|
||||||
config regen 🔄 更新当前订阅
|
sub update 🔄 更新当前订阅(兼容:clashsub update)
|
||||||
ls 📜 查看订阅列表
|
ls 📜 查看订阅列表
|
||||||
sub 📡 订阅高级管理(启用 / 禁用 / 重命名 / 删除)
|
sub 📡 订阅管理(更新 / 启用 / 禁用 / 重命名 / 删除)
|
||||||
|
|
||||||
🕹️ Control:
|
🕹️ Control:
|
||||||
clashui 🕹️ 查看 Web 控制台
|
clashui 🕹️ 查看 Web 控制台
|
||||||
@@ -69,6 +69,7 @@ Usage:
|
|||||||
completion 💡 导出 Bash / Zsh 补全脚本
|
completion 💡 导出 Bash / Zsh 补全脚本
|
||||||
|
|
||||||
📌 Advanced Examples:
|
📌 Advanced Examples:
|
||||||
|
clashctl sub update
|
||||||
clashctl sub list
|
clashctl sub list
|
||||||
clashctl sub enable hk
|
clashctl sub enable hk
|
||||||
clashctl sub disable hk
|
clashctl sub disable hk
|
||||||
@@ -7047,6 +7048,12 @@ cmd_sub() {
|
|||||||
prepare
|
prepare
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
|
update)
|
||||||
|
regenerate_config
|
||||||
|
apply_runtime_change_after_config_mutation
|
||||||
|
print_config_regen_feedback
|
||||||
|
print_config_apply_feedback
|
||||||
|
;;
|
||||||
list)
|
list)
|
||||||
shift || true
|
shift || true
|
||||||
cmd_ls "$@"
|
cmd_ls "$@"
|
||||||
@@ -7101,8 +7108,9 @@ cmd_sub() {
|
|||||||
cmd_health "$@"
|
cmd_health "$@"
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
ui_title "📡 订阅高级管理"
|
ui_title "📡 订阅管理"
|
||||||
echo "📜 用法:"
|
echo "📜 用法:"
|
||||||
|
echo " clashctl sub update"
|
||||||
echo " clashctl sub list"
|
echo " clashctl sub list"
|
||||||
echo " clashctl sub use <名称>"
|
echo " clashctl sub use <名称>"
|
||||||
echo " clashctl sub enable <名称>"
|
echo " clashctl sub enable <名称>"
|
||||||
@@ -7112,11 +7120,12 @@ cmd_sub() {
|
|||||||
echo " clashctl sub health [名称]"
|
echo " clashctl sub health [名称]"
|
||||||
echo
|
echo
|
||||||
echo "🧩 说明:"
|
echo "🧩 说明:"
|
||||||
echo " add / use / ls 属于主路径"
|
echo " add / use / ls / sub update 属于主路径"
|
||||||
echo " health 保留为多订阅健康审计"
|
echo " health 保留为多订阅健康审计"
|
||||||
echo " sub 仅用于高级维护操作"
|
echo " 其他 sub 子命令用于高级维护操作"
|
||||||
echo
|
echo
|
||||||
echo "💡 常用动作:"
|
echo "💡 常用动作:"
|
||||||
|
echo " clashctl sub update"
|
||||||
echo " clashctl sub list"
|
echo " clashctl sub list"
|
||||||
echo " clashctl sub health"
|
echo " clashctl sub health"
|
||||||
echo " clashctl sub enable <名称>"
|
echo " clashctl sub enable <名称>"
|
||||||
|
|||||||
@@ -2599,6 +2599,7 @@ install_alias_command_wrappers() {
|
|||||||
clashon \
|
clashon \
|
||||||
clashoff \
|
clashoff \
|
||||||
clashproxy \
|
clashproxy \
|
||||||
|
clashsub \
|
||||||
clashls \
|
clashls \
|
||||||
clashselect \
|
clashselect \
|
||||||
clashui \
|
clashui \
|
||||||
@@ -2897,6 +2898,7 @@ remove_alias_command_wrappers() {
|
|||||||
clashon \
|
clashon \
|
||||||
clashoff \
|
clashoff \
|
||||||
clashproxy \
|
clashproxy \
|
||||||
|
clashsub \
|
||||||
clashls \
|
clashls \
|
||||||
clashselect \
|
clashselect \
|
||||||
clashui \
|
clashui \
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ _clash_for_linux_complete_sub() {
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
|
|
||||||
if [ "$rel_index" -eq 1 ]; then
|
if [ "$rel_index" -eq 1 ]; then
|
||||||
_clash_for_linux_add_matches "$cur" list use set enable disable rename remove rm del health help -h --help
|
_clash_for_linux_add_matches "$cur" update list use set enable disable rename remove rm del health help -h --help
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -339,6 +339,13 @@ _clash_for_linux_complete_command() {
|
|||||||
arg2="${COMP_WORDS[3]:-}"
|
arg2="${COMP_WORDS[3]:-}"
|
||||||
arg3="${COMP_WORDS[4]:-}"
|
arg3="${COMP_WORDS[4]:-}"
|
||||||
;;
|
;;
|
||||||
|
clashsub)
|
||||||
|
canonical="sub"
|
||||||
|
rel_index=$COMP_CWORD
|
||||||
|
arg1="${COMP_WORDS[1]:-}"
|
||||||
|
arg2="${COMP_WORDS[2]:-}"
|
||||||
|
arg3="${COMP_WORDS[3]:-}"
|
||||||
|
;;
|
||||||
clashrelay)
|
clashrelay)
|
||||||
canonical="relay"
|
canonical="relay"
|
||||||
rel_index=$COMP_CWORD
|
rel_index=$COMP_CWORD
|
||||||
@@ -396,6 +403,7 @@ _clash_for_linux_complete_command() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
complete -F _clash_for_linux_complete_command clashctl
|
complete -F _clash_for_linux_complete_command clashctl
|
||||||
|
complete -F _clash_for_linux_complete_command clashsub
|
||||||
complete -F _clash_for_linux_complete_command clashrelay
|
complete -F _clash_for_linux_complete_command clashrelay
|
||||||
complete -F _clash_for_linux_complete_command clashmixin
|
complete -F _clash_for_linux_complete_command clashmixin
|
||||||
complete -F _clash_for_linux_complete_command clashsecret
|
complete -F _clash_for_linux_complete_command clashsecret
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
source_clashctl_for_tests() {
|
||||||
|
set -- ""
|
||||||
|
# Source the real command functions; suppress the no-arg usage printed by the dispatcher.
|
||||||
|
source "$PROJECT_DIR/scripts/core/clashctl.sh" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
source_clashctl_for_tests
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
|
||||||
|
calls_file="$tmp_dir/sub-update-calls"
|
||||||
|
output_file="$tmp_dir/sub-update-output"
|
||||||
|
|
||||||
|
prepare() { :; }
|
||||||
|
regenerate_config() { printf 'regenerate\n' >> "$calls_file"; }
|
||||||
|
apply_runtime_change_after_config_mutation() { printf 'apply\n' >> "$calls_file"; }
|
||||||
|
print_config_regen_feedback() { printf 'regen-feedback\n' >> "$calls_file"; }
|
||||||
|
print_config_apply_feedback() { printf 'apply-feedback\n' >> "$calls_file"; }
|
||||||
|
|
||||||
|
: > "$calls_file"
|
||||||
|
if ! ( cmd_sub update ) > "$output_file" 2>&1; then
|
||||||
|
echo "not ok - clashctl sub update should succeed" >&2
|
||||||
|
sed 's/^/ /' "$output_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_calls="$(printf '%s\n' regenerate apply regen-feedback apply-feedback)"
|
||||||
|
actual_calls="$(cat "$calls_file")"
|
||||||
|
if [ "$actual_calls" != "$expected_calls" ]; then
|
||||||
|
echo "not ok - clashctl sub update should follow the config regen path" >&2
|
||||||
|
printf 'expected:\n%s\nactual:\n%s\n' "$expected_calls" "$actual_calls" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok - clashctl sub update follows the config regen path"
|
||||||
|
|
||||||
|
# Verify both the sourced-shell compatibility function and the installed command wrapper.
|
||||||
|
# shellcheck source=../core/common.sh
|
||||||
|
source "$PROJECT_DIR/scripts/core/common.sh"
|
||||||
|
|
||||||
|
fixture_bin="$tmp_dir/bin"
|
||||||
|
mkdir -p "$fixture_bin"
|
||||||
|
|
||||||
|
PROJECT_DIR="$PROJECT_DIR"
|
||||||
|
alias_source_file() { printf '%s\n' "$PROJECT_DIR/scripts/core/alias.sh"; }
|
||||||
|
command_install_dir() { printf '%s\n' "$fixture_bin"; }
|
||||||
|
|
||||||
|
install_alias_command_wrappers
|
||||||
|
|
||||||
|
if [ ! -x "$fixture_bin/clashsub" ]; then
|
||||||
|
echo "not ok - install should create the clashsub compatibility wrapper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$fixture_bin/clashctl-bin" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
EOF
|
||||||
|
chmod +x "$fixture_bin/clashctl-bin"
|
||||||
|
|
||||||
|
wrapper_output="$(
|
||||||
|
env -i \
|
||||||
|
HOME="$tmp_dir/home" \
|
||||||
|
PATH="$fixture_bin:/usr/bin:/bin" \
|
||||||
|
CLASH_SHELL_AUTO_RESTORE_PROXY=false \
|
||||||
|
"$fixture_bin/clashsub" update
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ "$wrapper_output" != "sub update" ]; then
|
||||||
|
echo "not ok - clashsub update should delegate to clashctl sub update" >&2
|
||||||
|
printf 'actual: %s\n' "$wrapper_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remove_alias_command_wrappers
|
||||||
|
if [ -e "$fixture_bin/clashsub" ]; then
|
||||||
|
echo "not ok - uninstall should remove the clashsub compatibility wrapper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok - clashsub update compatibility wrapper is installed and removed"
|
||||||
|
|
||||||
|
completion_file="$tmp_dir/clashctl-completion.bash"
|
||||||
|
bash "$PROJECT_DIR/scripts/core/clashctl.sh" completion bash > "$completion_file"
|
||||||
|
|
||||||
|
completion_output="$(
|
||||||
|
bash --noprofile --norc -c '
|
||||||
|
source "$1"
|
||||||
|
COMP_WORDS=(clashsub u)
|
||||||
|
COMP_CWORD=1
|
||||||
|
_clash_for_linux_complete_command
|
||||||
|
printf "%s\n" "${COMPREPLY[@]}"
|
||||||
|
' bash "$completion_file"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$completion_output" | grep -Fxq update; then
|
||||||
|
echo "not ok - clashsub should complete the update subcommand" >&2
|
||||||
|
printf 'actual: %s\n' "$completion_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok - clashsub update is available in shell completion"
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
|
||||||
|
test_project="$tmp_dir/project"
|
||||||
|
test_home="$tmp_dir/home"
|
||||||
|
output_file="$tmp_dir/uninstall-output"
|
||||||
|
|
||||||
|
mkdir -p "$test_project" "$test_home" "$test_project/runtime"
|
||||||
|
cp "$PROJECT_DIR/uninstall.sh" "$test_project/uninstall.sh"
|
||||||
|
cp -R "$PROJECT_DIR/scripts" "$test_project/scripts"
|
||||||
|
cp -R "$PROJECT_DIR/config" "$test_project/config"
|
||||||
|
cp "$PROJECT_DIR/.env" "$test_project/.env"
|
||||||
|
|
||||||
|
# Override external effects in the isolated fixture while preserving the real
|
||||||
|
# uninstall control flow and forcing the exact /etc/environment cleanup failure.
|
||||||
|
cat >> "$test_project/scripts/init/script.sh" <<'EOF'
|
||||||
|
boot_proxy_keep_disable() { return 2; }
|
||||||
|
system_proxy_env_file() { printf '%s\n' /etc/environment; }
|
||||||
|
clear_shell_proxy_persist_state() { :; }
|
||||||
|
stop_subconverter() { :; }
|
||||||
|
service_stop() { :; }
|
||||||
|
stop_runtime() { :; }
|
||||||
|
remove_runtime_entry() { :; }
|
||||||
|
remove_clashctl_entry() { :; }
|
||||||
|
remove_clashctl_completion() { :; }
|
||||||
|
remove_shell_alias_entry() { :; }
|
||||||
|
clear_controller_secret() { :; }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if env -i \
|
||||||
|
HOME="$test_home" \
|
||||||
|
PATH="/usr/bin:/bin" \
|
||||||
|
bash "$test_project/uninstall.sh" > "$output_file" 2>&1; then
|
||||||
|
echo "not ok - uninstall should fail when /etc/environment cleanup fails" >&2
|
||||||
|
sed 's/^/ /' "$output_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Fq '[error]' "$output_file" || ! grep -Fq '/etc/environment' "$output_file"; then
|
||||||
|
echo "not ok - uninstall should clearly report the system proxy cleanup error" >&2
|
||||||
|
sed 's/^/ /' "$output_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Fq '[ok] 卸载完成' "$output_file"; then
|
||||||
|
echo "not ok - uninstall must not claim success after system proxy cleanup fails" >&2
|
||||||
|
sed 's/^/ /' "$output_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok - uninstall fails clearly when /etc/environment cleanup fails"
|
||||||
+9
-3
@@ -154,10 +154,11 @@ if [ "$KEEP_RUNTIME" != "true" ]; then
|
|||||||
echo "[ok] 已删除运行目录:$RUNTIME_DIR"
|
echo "[ok] 已删除运行目录:$RUNTIME_DIR"
|
||||||
if [ "$SYSTEM_PROXY_CLOSED" = "true" ]; then
|
if [ "$SYSTEM_PROXY_CLOSED" = "true" ]; then
|
||||||
echo "[ok] 已关闭系统代理持久接管"
|
echo "[ok] 已关闭系统代理持久接管"
|
||||||
else
|
|
||||||
echo "[warn] 未能确认系统代理持久块已关闭,请检查:$(system_proxy_env_file)"
|
|
||||||
fi
|
|
||||||
echo "[ok] 完整卸载:已清理服务、入口、运行目录与 controller secret"
|
echo "[ok] 完整卸载:已清理服务、入口、运行目录与 controller secret"
|
||||||
|
else
|
||||||
|
echo "[error] 系统代理持久块清理失败:$(system_proxy_env_file)" >&2
|
||||||
|
echo "[info] 已清理服务、入口、运行目录与 controller secret,但系统代理仍需处理"
|
||||||
|
fi
|
||||||
print_uninstall_modes_hint
|
print_uninstall_modes_hint
|
||||||
elif [ "$DEV_RESET" = "true" ]; then
|
elif [ "$DEV_RESET" = "true" ]; then
|
||||||
cache_backup_dir="$(mktemp -d)"
|
cache_backup_dir="$(mktemp -d)"
|
||||||
@@ -200,6 +201,11 @@ else
|
|||||||
echo "[info] 保留内容:runtime 数据仍在(按 --keep-runtime 请求)"
|
echo "[info] 保留内容:runtime 数据仍在(按 --keep-runtime 请求)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$SYSTEM_PROXY_CLOSED" != "true" ]; then
|
||||||
|
echo "[error] 卸载未完成:请使用有权限的用户重新执行此脚本,或手动删除 $(system_proxy_env_file) 中的 clash-for-linux 代理块" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[ok] 卸载完成"
|
echo "[ok] 卸载完成"
|
||||||
|
|
||||||
if [ "$REMOVE_PROJECT" = "true" ]; then
|
if [ "$REMOVE_PROJECT" = "true" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user