Skip to content

Commit 715c2a8

Browse files
committed
Add Zsh current-shell proxy support
Load shell proxy functions from system-wide Zsh startup files, keep project lookup portable across Bash and Zsh, and cover enable, login restore, disable, and uninstall behavior.\n\nRefs #291\n\nCo-authored-by: Junle Chen <601122452@qq.com>
1 parent 89a2976 commit 715c2a8

3 files changed

Lines changed: 173 additions & 8 deletions

File tree

scripts/core/alias.sh

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ _clashctl_real_on_target() {
3838

3939
_clash_alias_project_dir() {
4040
local self_dir
41+
42+
if [ -n "${CLASH_FOR_LINUX_PROJECT_DIR:-}" ]; then
43+
echo "$CLASH_FOR_LINUX_PROJECT_DIR"
44+
return 0
45+
fi
46+
4147
self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
4248
echo "$self_dir"
4349
}
@@ -56,10 +62,17 @@ _clash_alias_yq_bin() {
5662

5763
_clash_alias_env_value() {
5864
local key="$1"
59-
local env_file
65+
local env_file value
66+
67+
case "$key" in
68+
""|*[!A-Za-z0-9_]*)
69+
return 0
70+
;;
71+
esac
6072

61-
if [ -n "${!key:-}" ]; then
62-
printf '%s' "${!key}"
73+
eval "value=\${$key:-}"
74+
if [ -n "${value:-}" ]; then
75+
printf '%s' "$value"
6376
return 0
6477
fi
6578

scripts/core/common.sh

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,7 +2633,7 @@ cleanup_legacy_shell_entries() {
26332633
}
26342634

26352635
install_shell_alias_entry() {
2636-
local profile_file alias_file shell_rc bash_completion_file zsh_completion_file home_dir
2636+
local profile_file alias_file shell_rc bash_completion_file zsh_completion_file home_dir system_zsh_rc
26372637

26382638
cleanup_legacy_shell_entries
26392639

@@ -2650,9 +2650,10 @@ cat > "$profile_file" <<EOF
26502650
# clash-for-linux shell entry
26512651
export PATH="$(command_install_dir):\$PATH"
26522652
2653-
if [ -n "\${BASH_VERSION:-}" ] \
2654-
&& [ -z "\${CLASH_FOR_LINUX_SHELL_LOADED:-}" ] \
2655-
&& [ -r "$alias_file" ]; then
2653+
if [ -z "\${CLASH_FOR_LINUX_SHELL_LOADED:-}" ] \
2654+
&& [ -r "$alias_file" ] \
2655+
&& { [ -n "\${BASH_VERSION:-}" ] || [ -n "\${ZSH_VERSION:-}" ]; }; then
2656+
export CLASH_FOR_LINUX_PROJECT_DIR="$PROJECT_DIR"
26562657
CLASH_FOR_LINUX_SHELL_LOADED="1"
26572658
source "$alias_file"
26582659
fi
@@ -2676,6 +2677,13 @@ EOF
26762677
install_rc_source_block "$shell_rc" "$profile_file"
26772678
done
26782679

2680+
if [ "$INSTALL_SCOPE" = "system" ]; then
2681+
system_zsh_rc="$(system_zsh_rc_file 2>/dev/null || true)"
2682+
if [ -n "${system_zsh_rc:-}" ]; then
2683+
install_rc_source_block "$system_zsh_rc" "$profile_file"
2684+
fi
2685+
fi
2686+
26792687
install_alias_command_wrappers
26802688
}
26812689

@@ -2700,8 +2708,16 @@ remove_clashctl_entry() {
27002708
}
27012709

27022710
remove_shell_alias_entry() {
2703-
local profile_file
2711+
local profile_file system_zsh_rc
27042712
profile_file="$(profile_entry_file)"
2713+
2714+
if [ "$INSTALL_SCOPE" = "system" ]; then
2715+
system_zsh_rc="$(system_zsh_rc_file 2>/dev/null || true)"
2716+
if [ -n "${system_zsh_rc:-}" ]; then
2717+
remove_rc_source_block "$system_zsh_rc"
2718+
fi
2719+
fi
2720+
27052721
rm -f "$profile_file" 2>/dev/null || true
27062722

27072723
if [ "$INSTALL_SCOPE" = "user" ]; then
@@ -2766,6 +2782,19 @@ profile_entry_file() {
27662782
fi
27672783
}
27682784

2785+
system_zsh_rc_file() {
2786+
local candidate
2787+
2788+
for candidate in /etc/zsh/zshrc /etc/zshrc; do
2789+
if [ -f "$candidate" ]; then
2790+
echo "$candidate"
2791+
return 0
2792+
fi
2793+
done
2794+
2795+
return 1
2796+
}
2797+
27692798
install_rc_source_block() {
27702799
local profile_file="$1"
27712800
local source_target="$2"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
6+
ZSH_BIN="${ZSH_BIN:-$(command -v zsh 2>/dev/null || true)}"
7+
8+
if [ -z "$ZSH_BIN" ] || [ ! -x "$ZSH_BIN" ]; then
9+
echo "skip - zsh is not available"
10+
exit 0
11+
fi
12+
13+
# shellcheck source=../core/common.sh
14+
source "$PROJECT_DIR/scripts/core/common.sh"
15+
16+
tmp_dir="$(mktemp -d)"
17+
trap 'rm -rf "$tmp_dir"' EXIT
18+
19+
test_project="$tmp_dir/project"
20+
FIXTURE_PROFILE_FILE="$tmp_dir/clash-for-linux.sh"
21+
FIXTURE_ALIAS_FILE="$test_project/scripts/core/alias.sh"
22+
FIXTURE_BIN_DIR="$tmp_dir/bin"
23+
FIXTURE_ZSH_RC="$tmp_dir/etc/zsh/zshrc"
24+
25+
mkdir -p "$(dirname "$FIXTURE_ALIAS_FILE")" "$test_project/runtime" "$FIXTURE_BIN_DIR"
26+
cp "$PROJECT_DIR/scripts/core/alias.sh" "$FIXTURE_ALIAS_FILE"
27+
28+
cat > "$test_project/runtime/config.yaml" <<'YAML'
29+
mixed-port: 7890
30+
YAML
31+
32+
cat > "$FIXTURE_BIN_DIR/clashctl-bin" <<'EOF'
33+
#!/usr/bin/env bash
34+
exit 0
35+
EOF
36+
chmod +x "$FIXTURE_BIN_DIR/clashctl-bin"
37+
38+
PROJECT_DIR="$test_project"
39+
INSTALL_SCOPE="system"
40+
cleanup_legacy_shell_entries() { :; }
41+
profile_entry_file() { echo "$FIXTURE_PROFILE_FILE"; }
42+
alias_source_file() { echo "$FIXTURE_ALIAS_FILE"; }
43+
bash_completion_entry_file() { echo "$tmp_dir/completion.bash"; }
44+
zsh_completion_entry_file() { echo "$tmp_dir/completion.zsh"; }
45+
command_install_dir() { echo "$FIXTURE_BIN_DIR"; }
46+
user_home_dir() { echo "$tmp_dir/home"; }
47+
system_zsh_rc_file() { echo "$FIXTURE_ZSH_RC"; }
48+
install_alias_command_wrappers() { :; }
49+
50+
install_shell_alias_entry
51+
52+
if ! output="$(
53+
env -i \
54+
HOME="$tmp_dir/home" \
55+
PATH="$FIXTURE_BIN_DIR:/usr/bin:/bin" \
56+
ZDOTDIR="$tmp_dir/home" \
57+
"$ZSH_BIN" -dfc '
58+
source "$1"
59+
[ "$(whence -w clashon 2>/dev/null || true)" = "clashon: function" ] || {
60+
echo "clashon is not a zsh function" >&2
61+
exit 1
62+
}
63+
64+
clashon >/dev/null
65+
printf "%s\n" "$http_proxy"
66+
printf "%s\n" "$all_proxy"
67+
' zsh "$FIXTURE_ZSH_RC" 2>&1
68+
)"; then
69+
echo "not ok - zsh should load current-shell proxy functions" >&2
70+
printf '%s\n' "$output" >&2
71+
exit 1
72+
fi
73+
74+
expected="$(printf '%s\n' \
75+
'http://127.0.0.1:7890' \
76+
'socks5://127.0.0.1:7890')"
77+
78+
if [ "$output" != "$expected" ]; then
79+
echo "not ok - zsh proxy variables did not update in the current shell" >&2
80+
printf 'expected:\n%s\nactual:\n%s\n' "$expected" "$output" >&2
81+
exit 1
82+
fi
83+
84+
if ! restore_output="$(
85+
env -i \
86+
HOME="$tmp_dir/home" \
87+
PATH="$FIXTURE_BIN_DIR:/usr/bin:/bin" \
88+
ZDOTDIR="$tmp_dir/home" \
89+
"$ZSH_BIN" -dfc '
90+
source "$1"
91+
printf "%s\n" "$http_proxy"
92+
93+
clashoff >/dev/null
94+
if [ -n "${http_proxy+x}" ]; then
95+
echo "set"
96+
else
97+
echo "unset"
98+
fi
99+
' zsh "$FIXTURE_ZSH_RC" 2>&1
100+
)"; then
101+
echo "not ok - zsh should restore and clear current-shell proxy variables" >&2
102+
printf '%s\n' "$restore_output" >&2
103+
exit 1
104+
fi
105+
106+
restore_expected="$(printf '%s\n' \
107+
'http://127.0.0.1:7890' \
108+
'unset')"
109+
110+
if [ "$restore_output" != "$restore_expected" ]; then
111+
echo "not ok - a new zsh session did not restore or clear proxy variables" >&2
112+
printf 'expected:\n%s\nactual:\n%s\n' "$restore_expected" "$restore_output" >&2
113+
exit 1
114+
fi
115+
116+
remove_shell_alias_entry
117+
118+
if grep -Fq '# >>> clash-for-linux >>>' "$FIXTURE_ZSH_RC"; then
119+
echo "not ok - uninstall should remove the system zsh source block" >&2
120+
exit 1
121+
fi
122+
123+
echo "ok - zsh updates, restores, and clears current-shell proxy variables"

0 commit comments

Comments
 (0)