mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 04:03:04 +08:00
Fix reported install, select, and zsh completion bugs
This commit is contained in:
@@ -1515,6 +1515,22 @@ print_select_feedback() {
|
||||
ui_blank
|
||||
}
|
||||
|
||||
select_usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
clashctl select
|
||||
clashctl select <策略组> <节点>
|
||||
|
||||
Examples:
|
||||
clashctl select
|
||||
clashctl select "🚀 节点选择" "日本 01"
|
||||
|
||||
Notes:
|
||||
不带参数时进入交互选择。
|
||||
带参数时直接切换指定策略组到指定节点。
|
||||
EOF
|
||||
}
|
||||
|
||||
print_sub_enable_feedback() {
|
||||
local name="$1"
|
||||
local health fail_count
|
||||
@@ -5743,7 +5759,7 @@ tun_unit_capability_text() {
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
content="$(systemctl --user cat "$unit" 2>/dev/null || true)"
|
||||
fi
|
||||
unit_file="$HOME/.config/systemd/user/$unit"
|
||||
unit_file="$(user_home_dir)/.config/systemd/user/$unit"
|
||||
;;
|
||||
*)
|
||||
echo "非 systemd 后端,不适用 unit capability 声明"
|
||||
@@ -6746,9 +6762,15 @@ cmd_health() {
|
||||
}
|
||||
|
||||
cmd_select() {
|
||||
prepare
|
||||
case "${1:-}" in
|
||||
-h|--help|help)
|
||||
select_usage
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "${1:-}" ]; then
|
||||
prepare
|
||||
print_select_context || return $?
|
||||
proxy_select_interactive_guarded
|
||||
return 0
|
||||
@@ -7727,6 +7749,28 @@ proxy_select_interactive_guarded() {
|
||||
done
|
||||
}
|
||||
|
||||
proxy_select_direct() {
|
||||
local group="${1:-}"
|
||||
local node="${2:-}"
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
die_usage "select 参数不合法" "clashctl select <策略组> <节点>"
|
||||
fi
|
||||
|
||||
prepare
|
||||
|
||||
if ! status_is_running; then
|
||||
die_state "代理内核未运行" "clashon"
|
||||
fi
|
||||
|
||||
if ! proxy_controller_reachable 2>/dev/null; then
|
||||
die_state "控制器不可访问" "clashctl doctor"
|
||||
fi
|
||||
|
||||
proxy_group_select "$group" "$node"
|
||||
print_select_feedback "$group"
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
shift || true
|
||||
|
||||
|
||||
+55
-17
@@ -22,6 +22,31 @@ warn() { printf '⚠ %s\n' "$*" >&2; }
|
||||
error() { printf '✘ %s\n' "$*" >&2; }
|
||||
die() { error "$*"; exit 1; }
|
||||
|
||||
user_home_dir() {
|
||||
local home_value uid
|
||||
|
||||
if [ -n "${HOME:-}" ]; then
|
||||
printf '%s\n' "$HOME"
|
||||
return 0
|
||||
fi
|
||||
|
||||
uid="$(id -u 2>/dev/null || true)"
|
||||
if [ -n "${uid:-}" ] && command -v getent >/dev/null 2>&1; then
|
||||
home_value="$(getent passwd "$uid" 2>/dev/null | awk -F: 'NR == 1 {print $6}')"
|
||||
fi
|
||||
|
||||
if [ -z "${home_value:-}" ] && [ -n "${uid:-}" ] && [ -r /etc/passwd ]; then
|
||||
home_value="$(awk -F: -v uid="$uid" '$3 == uid {print $6; exit}' /etc/passwd 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -z "${home_value:-}" ]; then
|
||||
die_state "无法确定当前用户 HOME" "请先设置 HOME 后重试:export HOME=/path/to/home"
|
||||
fi
|
||||
|
||||
export HOME="$home_value"
|
||||
printf '%s\n' "$home_value"
|
||||
}
|
||||
|
||||
ui_color() {
|
||||
local color="$1"
|
||||
local msg="$2"
|
||||
@@ -1282,7 +1307,7 @@ detect_install_scope() {
|
||||
if [ "$INSTALL_SCOPE" = "system" ]; then
|
||||
INSTALL_HOME="${CLASH_INSTALL_HOME:-/opt/clash-for-linux}"
|
||||
else
|
||||
INSTALL_HOME="${CLASH_INSTALL_HOME:-$HOME/.local/share/clash-for-linux}"
|
||||
INSTALL_HOME="${CLASH_INSTALL_HOME:-$(user_home_dir)/.local/share/clash-for-linux}"
|
||||
fi
|
||||
|
||||
RUNTIME_DIR="$PROJECT_DIR/runtime"
|
||||
@@ -2571,12 +2596,15 @@ runtime_backend() {
|
||||
}
|
||||
|
||||
shell_profile_file() {
|
||||
local home_dir
|
||||
|
||||
if [ "$INSTALL_SCOPE" = "system" ]; then
|
||||
echo "/etc/profile.d/clash-for-linux.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "$HOME/.bashrc"
|
||||
home_dir="$(user_home_dir)"
|
||||
echo "$home_dir/.bashrc"
|
||||
}
|
||||
|
||||
alias_source_file() {
|
||||
@@ -2584,10 +2612,13 @@ alias_source_file() {
|
||||
}
|
||||
|
||||
completion_dir() {
|
||||
local home_dir
|
||||
|
||||
if [ "$INSTALL_SCOPE" = "system" ]; then
|
||||
echo "/etc/profile.d"
|
||||
else
|
||||
echo "$HOME/.config/clash-for-linux"
|
||||
home_dir="$(user_home_dir)"
|
||||
echo "$home_dir/.config/clash-for-linux"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -2616,7 +2647,7 @@ clashctl_bin_entry_target() {
|
||||
}
|
||||
|
||||
ensure_command_install_dir_in_shell_path() {
|
||||
local install_dir shell_rc
|
||||
local install_dir shell_rc home_dir
|
||||
|
||||
install_dir="$(command_install_dir)"
|
||||
|
||||
@@ -2626,7 +2657,8 @@ ensure_command_install_dir_in_shell_path() {
|
||||
;;
|
||||
esac
|
||||
|
||||
for shell_rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
home_dir="$(user_home_dir)"
|
||||
for shell_rc in "$home_dir/.bashrc" "$home_dir/.zshrc" "$home_dir/.profile"; do
|
||||
[ -n "${shell_rc:-}" ] || continue
|
||||
touch "$shell_rc"
|
||||
if ! grep -Fq "$install_dir" "$shell_rc" 2>/dev/null; then
|
||||
@@ -2699,9 +2731,10 @@ EOF
|
||||
}
|
||||
|
||||
cleanup_legacy_shell_entries() {
|
||||
local shell_rc
|
||||
local shell_rc home_dir
|
||||
|
||||
for shell_rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
home_dir="$(user_home_dir)"
|
||||
for shell_rc in "$home_dir/.bashrc" "$home_dir/.zshrc" "$home_dir/.profile"; do
|
||||
[ -f "$shell_rc" ] || continue
|
||||
|
||||
sed -i '\|/root/clashctl/scripts/cmd/clashctl.sh|d' "$shell_rc" 2>/dev/null || true
|
||||
@@ -2711,7 +2744,7 @@ cleanup_legacy_shell_entries() {
|
||||
}
|
||||
|
||||
install_shell_alias_entry() {
|
||||
local profile_file alias_file shell_rc bash_completion_file zsh_completion_file
|
||||
local profile_file alias_file shell_rc bash_completion_file zsh_completion_file home_dir
|
||||
|
||||
cleanup_legacy_shell_entries
|
||||
|
||||
@@ -2747,7 +2780,8 @@ esac
|
||||
EOF
|
||||
chmod +x "$profile_file"
|
||||
|
||||
for shell_rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
home_dir="$(user_home_dir)"
|
||||
for shell_rc in "$home_dir/.bashrc" "$home_dir/.zshrc" "$home_dir/.profile"; do
|
||||
install_rc_source_block "$shell_rc" "$profile_file"
|
||||
done
|
||||
|
||||
@@ -2780,8 +2814,9 @@ remove_shell_alias_entry() {
|
||||
rm -f "$profile_file" 2>/dev/null || true
|
||||
|
||||
if [ "$INSTALL_SCOPE" = "user" ]; then
|
||||
local shell_rc
|
||||
for shell_rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
local shell_rc home_dir
|
||||
home_dir="$(user_home_dir)"
|
||||
for shell_rc in "$home_dir/.bashrc" "$home_dir/.zshrc" "$home_dir/.profile"; do
|
||||
[ -f "$shell_rc" ] || continue
|
||||
awk -v profile="$profile_file" '
|
||||
index($0, profile) == 0 { print }
|
||||
@@ -2803,18 +2838,21 @@ install_runtime_ready() {
|
||||
}
|
||||
|
||||
shell_rc_files() {
|
||||
local home_dir
|
||||
|
||||
if [ "$INSTALL_SCOPE" = "system" ]; then
|
||||
echo "/etc/profile.d/clash-for-linux.sh"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "$HOME/.bashrc"
|
||||
echo "$HOME/.zshrc"
|
||||
echo "$HOME/.profile"
|
||||
home_dir="$(user_home_dir)"
|
||||
echo "$home_dir/.bashrc"
|
||||
echo "$home_dir/.zshrc"
|
||||
echo "$home_dir/.profile"
|
||||
}
|
||||
|
||||
user_local_bin_dir() {
|
||||
echo "$HOME/.local/bin"
|
||||
echo "$(user_home_dir)/.local/bin"
|
||||
}
|
||||
|
||||
command_install_dir() {
|
||||
@@ -2825,7 +2863,7 @@ command_install_dir() {
|
||||
fi
|
||||
echo "/usr/local/bin"
|
||||
else
|
||||
echo "$HOME/.local/bin"
|
||||
echo "$(user_home_dir)/.local/bin"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -2833,7 +2871,7 @@ profile_entry_file() {
|
||||
if [ "$INSTALL_SCOPE" = "system" ]; then
|
||||
echo "/etc/profile.d/clash-for-linux.sh"
|
||||
else
|
||||
echo "$HOME/.config/clash-for-linux/profile.sh"
|
||||
echo "$(user_home_dir)/.config/clash-for-linux/profile.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -410,10 +410,10 @@ completion_emit_bash_script() {
|
||||
|
||||
completion_emit_zsh_script() {
|
||||
cat <<'EOF'
|
||||
# Ensure compinit is loaded before bashcompinit (provides compdef)
|
||||
if ! command -v compinit >/dev/null 2>&1; then
|
||||
autoload -Uz compinit && compinit -u 2>/dev/null
|
||||
if [ -z "${ZSH_VERSION:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
command -v compdef >/dev/null 2>&1 || return 0
|
||||
autoload -Uz bashcompinit 2>/dev/null || return 0
|
||||
bashcompinit >/dev/null 2>&1 || return 0
|
||||
EOF
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
output="$(
|
||||
env -u HOME bash -c '
|
||||
set -euo pipefail
|
||||
PROJECT_DIR="'"$PROJECT_DIR"'"
|
||||
source "$PROJECT_DIR/scripts/core/common.sh"
|
||||
init_project_context "$PROJECT_DIR"
|
||||
detect_install_scope user
|
||||
completion_dir
|
||||
profile_entry_file
|
||||
command_install_dir
|
||||
'
|
||||
)"
|
||||
|
||||
if printf '%s\n' "$output" | grep -Eq '/\.config/clash-for-linux|/\.local/bin'; then
|
||||
echo "ok - user install paths resolve when HOME is unset"
|
||||
else
|
||||
echo "not ok - missing user install path output" >&2
|
||||
printf '%s\n' "$output" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/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 "$PROJECT_DIR/scripts/core/clashctl.sh" >/dev/null
|
||||
}
|
||||
|
||||
source_clashctl_for_tests
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
prepare() { :; }
|
||||
status_is_running() { return 0; }
|
||||
proxy_controller_reachable() { return 0; }
|
||||
print_select_feedback() { printf 'feedback:%s\n' "$1"; }
|
||||
die_usage() {
|
||||
printf 'usage:%s\nnext:%s\n' "$1" "${2:-}"
|
||||
exit 2
|
||||
}
|
||||
die_state() {
|
||||
printf 'state:%s\nnext:%s\n' "$1" "${2:-}"
|
||||
exit 3
|
||||
}
|
||||
|
||||
run_help_case() {
|
||||
local output rc
|
||||
|
||||
set +e
|
||||
output="$(cmd_select --help 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "not ok - select help should succeed" >&2
|
||||
printf '%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! printf '%s\n' "$output" | grep -Fq "clashctl select <策略组> <节点>"; then
|
||||
echo "not ok - select help missing direct usage" >&2
|
||||
printf '%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "ok - select help prints direct usage"
|
||||
}
|
||||
|
||||
run_direct_case() {
|
||||
local output
|
||||
|
||||
proxy_group_select() {
|
||||
printf '%s -> %s\n' "$1" "$2" > "$tmp_dir/selected"
|
||||
}
|
||||
|
||||
output="$(cmd_select "🚀 节点选择" "日本 01")"
|
||||
|
||||
if [ "$(cat "$tmp_dir/selected")" != "🚀 节点选择 -> 日本 01" ]; then
|
||||
echo "not ok - direct select did not pass group and node through" >&2
|
||||
cat "$tmp_dir/selected" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! printf '%s\n' "$output" | grep -Fq "feedback:🚀 节点选择"; then
|
||||
echo "not ok - direct select did not print feedback" >&2
|
||||
printf '%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "ok - direct select delegates to proxy_group_select"
|
||||
}
|
||||
|
||||
run_bad_args_case() {
|
||||
local output rc
|
||||
|
||||
set +e
|
||||
output="$(cmd_select only-group 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 2 ]; then
|
||||
echo "not ok - bad direct select args should fail usage" >&2
|
||||
printf 'rc=%s\n%s\n' "$rc" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "ok - direct select validates arguments"
|
||||
}
|
||||
|
||||
run_help_case
|
||||
run_direct_case
|
||||
run_bad_args_case
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
completion="$(bash "$PROJECT_DIR/scripts/core/clashctl.sh" completion zsh)"
|
||||
|
||||
if printf '%s\n' "$completion" | grep -Eq '(^|[[:space:]])compinit([[:space:]]|$)'; then
|
||||
echo "not ok - zsh completion should not run compinit from project startup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s\n' "$completion" | grep -Fq 'bashcompinit'; then
|
||||
echo "not ok - zsh completion should still bridge bash completions when available" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s\n' "$completion" | grep -Fq 'command -v compdef'; then
|
||||
echo "not ok - zsh completion should guard compdef availability" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "ok - zsh completion is non-invasive"
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
install_systemd_user_entry() {
|
||||
local user_dir unit_file
|
||||
user_dir="$HOME/.config/systemd/user"
|
||||
local user_dir unit_file home_dir
|
||||
home_dir="$(user_home_dir)"
|
||||
user_dir="$home_dir/.config/systemd/user"
|
||||
unit_file="$user_dir/$(service_unit_name)"
|
||||
|
||||
mkdir -p "$user_dir"
|
||||
@@ -14,9 +15,9 @@ After=default.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart=$HOME/.local/bin/clashctl start-direct
|
||||
ExecStop=$HOME/.local/bin/clashctl stop-direct
|
||||
ExecReload=$HOME/.local/bin/clashctl restart-direct
|
||||
ExecStart=$home_dir/.local/bin/clashctl start-direct
|
||||
ExecStop=$home_dir/.local/bin/clashctl stop-direct
|
||||
ExecReload=$home_dir/.local/bin/clashctl restart-direct
|
||||
PIDFile=$RUNTIME_DIR/mihomo.pid
|
||||
WorkingDirectory=$PROJECT_DIR
|
||||
Restart=on-failure
|
||||
@@ -38,8 +39,9 @@ EOF
|
||||
}
|
||||
|
||||
remove_systemd_user_entry() {
|
||||
local unit_file
|
||||
unit_file="$HOME/.config/systemd/user/$(service_unit_name)"
|
||||
local unit_file home_dir
|
||||
home_dir="$(user_home_dir)"
|
||||
unit_file="$home_dir/.config/systemd/user/$(service_unit_name)"
|
||||
|
||||
if [ -f "$unit_file" ]; then
|
||||
systemctl --user disable "$(service_unit_name)" >/dev/null 2>&1 || true
|
||||
|
||||
Reference in New Issue
Block a user