fix: restore subscription update compatibility
Text Encoding / utf8 (push) Successful in 1m31s

This commit is contained in:
Arvin
2026-07-17 10:20:31 +08:00
parent 06697222d1
commit e1c9ee49f0
8 changed files with 205 additions and 8 deletions
+2
View File
@@ -143,6 +143,8 @@ OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命
clashctl add <订阅链接> <名称>
clashctl use
clashctl ls
clashctl sub update
clashsub update # 兼容旧版命令
clashctl sub
clashctl sub list
clashctl sub enable <名称>
+4
View File
@@ -396,6 +396,10 @@ clashls() {
clashctl ls "$@"
}
clashsub() {
clashctl sub "$@"
}
clashselect() {
clashctl select "$@"
}
+14 -5
View File
@@ -39,9 +39,9 @@ Usage:
📦 Subscription:
config show 📡 查看当前订阅
config regen 🔄 更新当前订阅
sub update 🔄 更新当前订阅(兼容:clashsub update)
ls 📜 查看订阅列表
sub 📡 订阅高级管理(启用 / 禁用 / 重命名 / 删除)
sub 📡 订阅管理(更新 / 启用 / 禁用 / 重命名 / 删除)
🕹️ Control:
clashui 🕹️ 查看 Web 控制台
@@ -69,6 +69,7 @@ Usage:
completion 💡 导出 Bash / Zsh 补全脚本
📌 Advanced Examples:
clashctl sub update
clashctl sub list
clashctl sub enable hk
clashctl sub disable hk
@@ -7047,6 +7048,12 @@ cmd_sub() {
prepare
case "${1:-}" in
update)
regenerate_config
apply_runtime_change_after_config_mutation
print_config_regen_feedback
print_config_apply_feedback
;;
list)
shift || true
cmd_ls "$@"
@@ -7101,8 +7108,9 @@ cmd_sub() {
cmd_health "$@"
;;
"")
ui_title "📡 订阅高级管理"
ui_title "📡 订阅管理"
echo "📜 用法:"
echo " clashctl sub update"
echo " clashctl sub list"
echo " clashctl sub use <名称>"
echo " clashctl sub enable <名称>"
@@ -7112,11 +7120,12 @@ cmd_sub() {
echo " clashctl sub health [名称]"
echo
echo "🧩 说明:"
echo " add / use / ls 属于主路径"
echo " add / use / ls / sub update 属于主路径"
echo " health 保留为多订阅健康审计"
echo " sub 仅用于高级维护操作"
echo " 其他 sub 子命令用于高级维护操作"
echo
echo "💡 常用动作:"
echo " clashctl sub update"
echo " clashctl sub list"
echo " clashctl sub health"
echo " clashctl sub enable <名称>"
+2
View File
@@ -2599,6 +2599,7 @@ install_alias_command_wrappers() {
clashon \
clashoff \
clashproxy \
clashsub \
clashls \
clashselect \
clashui \
@@ -2897,6 +2898,7 @@ remove_alias_command_wrappers() {
clashon \
clashoff \
clashproxy \
clashsub \
clashls \
clashselect \
clashui \
+9 -1
View File
@@ -224,7 +224,7 @@ _clash_for_linux_complete_sub() {
COMPREPLY=()
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
fi
@@ -339,6 +339,13 @@ _clash_for_linux_complete_command() {
arg2="${COMP_WORDS[3]:-}"
arg3="${COMP_WORDS[4]:-}"
;;
clashsub)
canonical="sub"
rel_index=$COMP_CWORD
arg1="${COMP_WORDS[1]:-}"
arg2="${COMP_WORDS[2]:-}"
arg3="${COMP_WORDS[3]:-}"
;;
clashrelay)
canonical="relay"
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 clashsub
complete -F _clash_for_linux_complete_command clashrelay
complete -F _clash_for_linux_complete_command clashmixin
complete -F _clash_for_linux_complete_command clashsecret
+109
View File
@@ -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
View File
@@ -154,10 +154,11 @@ if [ "$KEEP_RUNTIME" != "true" ]; then
echo "[ok] 已删除运行目录:$RUNTIME_DIR"
if [ "$SYSTEM_PROXY_CLOSED" = "true" ]; then
echo "[ok] 已关闭系统代理持久接管"
else
echo "[warn] 未能确认系统代理持久块已关闭,请检查:$(system_proxy_env_file)"
fi
echo "[ok] 完整卸载:已清理服务、入口、运行目录与 controller secret"
else
echo "[error] 系统代理持久块清理失败:$(system_proxy_env_file)" >&2
echo "[info] 已清理服务、入口、运行目录与 controller secret,但系统代理仍需处理"
fi
print_uninstall_modes_hint
elif [ "$DEV_RESET" = "true" ]; then
cache_backup_dir="$(mktemp -d)"
@@ -200,6 +201,11 @@ else
echo "[info] 保留内容:runtime 数据仍在(按 --keep-runtime 请求)"
fi
if [ "$SYSTEM_PROXY_CLOSED" != "true" ]; then
echo "[error] 卸载未完成:请使用有权限的用户重新执行此脚本,或手动删除 $(system_proxy_env_file) 中的 clash-for-linux 代理块" >&2
exit 1
fi
echo "[ok] 卸载完成"
if [ "$REMOVE_PROJECT" = "true" ]; then