mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 12:13:04 +08:00
Add install scope selector
This commit is contained in:
+113
-1
@@ -1145,8 +1145,104 @@ guard_unsafe_sudo_auto_install() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_sudo_available() {
|
||||
command -v sudo >/dev/null 2>&1 || return 1
|
||||
sudo -n true >/dev/null 2>&1 && return 0
|
||||
id -nG 2>/dev/null | tr ' ' '\n' | grep -Eq '^(sudo|wheel|admin)$'
|
||||
}
|
||||
|
||||
install_scope_choice_tty() {
|
||||
[ -t 0 ] && [ -t 2 ]
|
||||
}
|
||||
|
||||
install_prompt_scope_choice() {
|
||||
local selected=0 key
|
||||
local root_label="root 安装(推荐)"
|
||||
local user_label="user 安装"
|
||||
local root_desc="安装到系统命令路径,支持 systemd 后端,适合长期运行、开机自启、全局可用。"
|
||||
local user_desc="不写系统目录,安装到当前用户环境,适合无 root 权限或只给当前用户使用。"
|
||||
|
||||
if ! install_scope_choice_tty; then
|
||||
warn "检测到当前用户可使用 sudo;非交互终端默认使用 user 安装"
|
||||
printf 'ℹ %s\n' "如需 root 安装,请显式执行:sudo bash install.sh system" >&2
|
||||
echo "user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while true; do
|
||||
printf '\033[2J\033[H' >&2
|
||||
echo "检测到当前用户可使用 sudo,请选择安装方式:" >&2
|
||||
echo >&2
|
||||
|
||||
if [ "$selected" -eq 0 ]; then
|
||||
echo "> $root_label" >&2
|
||||
else
|
||||
echo " $root_label" >&2
|
||||
fi
|
||||
echo " $root_desc" >&2
|
||||
echo >&2
|
||||
|
||||
if [ "$selected" -eq 1 ]; then
|
||||
echo "> $user_label" >&2
|
||||
else
|
||||
echo " $user_label" >&2
|
||||
fi
|
||||
echo " $user_desc" >&2
|
||||
echo >&2
|
||||
echo "使用 ↑/↓ 或 j/k 移动,Enter 确认。" >&2
|
||||
|
||||
IFS= read -rsn1 key || {
|
||||
echo "user"
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$key" in
|
||||
"")
|
||||
if [ "$selected" -eq 0 ]; then
|
||||
echo "system"
|
||||
else
|
||||
echo "user"
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
j|J)
|
||||
selected=1
|
||||
;;
|
||||
k|K)
|
||||
selected=0
|
||||
;;
|
||||
$'\033')
|
||||
IFS= read -rsn2 -t 0.1 key || key=""
|
||||
case "$key" in
|
||||
"[A") selected=0 ;;
|
||||
"[B") selected=1 ;;
|
||||
esac
|
||||
;;
|
||||
1)
|
||||
echo "system"
|
||||
return 0
|
||||
;;
|
||||
2)
|
||||
echo "user"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
rerun_install_as_root_system() {
|
||||
local install_script
|
||||
|
||||
install_script="$PROJECT_DIR/install.sh"
|
||||
[ -f "$install_script" ] || die "未找到安装脚本:$install_script"
|
||||
|
||||
echo "👉 将使用 root/system 方式重新执行安装"
|
||||
exec sudo bash "$install_script" system
|
||||
}
|
||||
|
||||
detect_install_scope() {
|
||||
local requested="${1:-auto}"
|
||||
local selected_scope
|
||||
|
||||
case "$requested" in
|
||||
system)
|
||||
@@ -1159,7 +1255,23 @@ detect_install_scope() {
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
INSTALL_SCOPE="system"
|
||||
else
|
||||
INSTALL_SCOPE="user"
|
||||
if install_sudo_available; then
|
||||
selected_scope="$(install_prompt_scope_choice)"
|
||||
case "$selected_scope" in
|
||||
system)
|
||||
rerun_install_as_root_system
|
||||
return $?
|
||||
;;
|
||||
user)
|
||||
INSTALL_SCOPE="user"
|
||||
;;
|
||||
*)
|
||||
die "未知安装选择:$selected_scope"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
INSTALL_SCOPE="user"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# shellcheck source=scripts/core/common.sh
|
||||
source "$PROJECT_DIR/scripts/core/common.sh"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
PROJECT_DIR="$tmp_dir/project"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
touch "$PROJECT_DIR/install.sh"
|
||||
|
||||
reset_scope_state() {
|
||||
INSTALL_SCOPE=""
|
||||
INSTALL_HOME=""
|
||||
RUNTIME_DIR=""
|
||||
BIN_DIR=""
|
||||
LOG_DIR=""
|
||||
SELECTOR_PROMPTED="false"
|
||||
RERUN_AS_ROOT="false"
|
||||
MOCK_IS_ROOT="false"
|
||||
MOCK_SUDO_AVAILABLE="false"
|
||||
MOCK_TTY="false"
|
||||
MOCK_SELECTED_SCOPE="user"
|
||||
: > "$tmp_dir/prompted"
|
||||
: > "$tmp_dir/rerun"
|
||||
}
|
||||
|
||||
id() {
|
||||
if [ "${1:-}" = "-u" ]; then
|
||||
if [ "$MOCK_IS_ROOT" = "true" ]; then
|
||||
printf '0\n'
|
||||
else
|
||||
printf '1000\n'
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
command id "$@"
|
||||
}
|
||||
|
||||
install_sudo_available() {
|
||||
[ "$MOCK_SUDO_AVAILABLE" = "true" ]
|
||||
}
|
||||
|
||||
install_scope_choice_tty() {
|
||||
[ "$MOCK_TTY" = "true" ]
|
||||
}
|
||||
|
||||
install_prompt_scope_choice() {
|
||||
printf 'prompted\n' >> "$tmp_dir/prompted"
|
||||
if ! install_scope_choice_tty; then
|
||||
printf 'user\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "$MOCK_SELECTED_SCOPE"
|
||||
}
|
||||
|
||||
rerun_install_as_root_system() {
|
||||
printf 'rerun\n' >> "$tmp_dir/rerun"
|
||||
return 77
|
||||
}
|
||||
|
||||
assert_equal() {
|
||||
local name="$1"
|
||||
local actual="$2"
|
||||
local expected="$3"
|
||||
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "not ok - $name: got '$actual', expected '$expected'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "ok - $name"
|
||||
}
|
||||
|
||||
reset_scope_state
|
||||
detect_install_scope user
|
||||
assert_equal "explicit user selects user scope" "$INSTALL_SCOPE" "user"
|
||||
assert_equal "explicit user does not prompt" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "0"
|
||||
|
||||
reset_scope_state
|
||||
detect_install_scope system
|
||||
assert_equal "explicit system selects system scope" "$INSTALL_SCOPE" "system"
|
||||
assert_equal "explicit system does not prompt" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "0"
|
||||
|
||||
reset_scope_state
|
||||
MOCK_IS_ROOT="true"
|
||||
detect_install_scope auto
|
||||
assert_equal "root auto selects system scope" "$INSTALL_SCOPE" "system"
|
||||
assert_equal "root auto does not prompt" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "0"
|
||||
|
||||
reset_scope_state
|
||||
detect_install_scope auto
|
||||
assert_equal "non-root without sudo selects user scope" "$INSTALL_SCOPE" "user"
|
||||
assert_equal "non-root without sudo does not prompt" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "0"
|
||||
|
||||
reset_scope_state
|
||||
MOCK_SUDO_AVAILABLE="true"
|
||||
detect_install_scope auto
|
||||
assert_equal "non-root sudo non-tty prompts through default path" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "1"
|
||||
assert_equal "non-root sudo non-tty selects user scope" "$INSTALL_SCOPE" "user"
|
||||
|
||||
reset_scope_state
|
||||
MOCK_SUDO_AVAILABLE="true"
|
||||
MOCK_TTY="true"
|
||||
MOCK_SELECTED_SCOPE="user"
|
||||
detect_install_scope auto
|
||||
assert_equal "non-root sudo tty user choice prompts" "$(wc -l < "$tmp_dir/prompted" | tr -d ' ')" "1"
|
||||
assert_equal "non-root sudo tty user choice selects user scope" "$INSTALL_SCOPE" "user"
|
||||
|
||||
reset_scope_state
|
||||
MOCK_SUDO_AVAILABLE="true"
|
||||
MOCK_TTY="true"
|
||||
MOCK_SELECTED_SCOPE="system"
|
||||
set +e
|
||||
detect_install_scope auto
|
||||
rc=$?
|
||||
set -e
|
||||
assert_equal "non-root sudo tty system choice calls root rerun" "$(wc -l < "$tmp_dir/rerun" | tr -d ' ')" "1"
|
||||
assert_equal "non-root sudo tty system choice stops current flow" "$rc" "77"
|
||||
Reference in New Issue
Block a user