mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
- Added panel printing functions (print_panel, print_subheader) to install-server.sh and install.sh, for improved interface display. - Introduced the ensure_command_access function to automatically link the drip command to system paths (e.g., /usr/local/bin), improving command availability. - Added complete uninstallation logic: Added uninstall.sh script, supporting client-side, server-side, or both uninstallation. - install.sh supports triggering the uninstallation process with the --uninstall parameter and enhanced PATH cleanup mechanism. - Unified multilingual prompts and optimized user experience.
449 lines
12 KiB
Bash
449 lines
12 KiB
Bash
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Drip unified uninstaller (client + server)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Colors
|
|
# -----------------------------------------------------------------------------
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Runtime options
|
|
# -----------------------------------------------------------------------------
|
|
LANG_CODE="${LANG_CODE:-zh}" # zh | en (UI language)
|
|
AUTO_YES=false # true => auto-confirm prompts
|
|
TARGET="prompt" # client | server | both | prompt
|
|
|
|
# Prefer prompting/reading from real TTY to avoid "stuck" when piped (curl | bash)
|
|
TTY="/dev/tty"
|
|
|
|
has_tty() { [[ -e "$TTY" ]] && [[ -r "$TTY" ]] && [[ -w "$TTY" ]]; }
|
|
|
|
say() { echo -e "$*"; }
|
|
say_info() { say "${BLUE}[INFO]${NC} $*"; }
|
|
say_ok() { say "${GREEN}[✓]${NC} $*"; }
|
|
say_warn() { say "${YELLOW}[!]${NC} $*"; }
|
|
say_err() { say "${RED}[✗]${NC} $*"; }
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# TTY helpers
|
|
# -----------------------------------------------------------------------------
|
|
# Write raw text to TTY if available (so prompts are always visible and never
|
|
# interpreted as printf format strings).
|
|
tty_put() {
|
|
if has_tty; then
|
|
printf "%b" "$1" > "$TTY"
|
|
else
|
|
printf "%b" "$1"
|
|
fi
|
|
}
|
|
|
|
# Read user input from TTY first, fallback to stdin
|
|
tty_read() {
|
|
local __var="$1"
|
|
local __tmp=""
|
|
if has_tty; then
|
|
IFS= read -r __tmp < "$TTY" || true
|
|
else
|
|
IFS= read -r __tmp || true
|
|
fi
|
|
printf -v "$__var" "%s" "$__tmp"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# i18n messages (UI strings only)
|
|
# -----------------------------------------------------------------------------
|
|
msg() {
|
|
local key="$1"
|
|
if [[ "$LANG_CODE" == "zh" ]]; then
|
|
case "$key" in
|
|
title) echo "Drip 卸载脚本" ;;
|
|
select_target) echo "选择卸载对象" ;;
|
|
opt_client) echo "客户端" ;;
|
|
opt_server) echo "服务器端" ;;
|
|
opt_both) echo "客户端 + 服务器端" ;;
|
|
confirm) echo "确认卸载?" ;;
|
|
removing_client) echo "卸载客户端..." ;;
|
|
removing_server) echo "卸载服务器端..." ;;
|
|
done) echo "卸载完成" ;;
|
|
remove_config) echo "删除配置目录?" ;;
|
|
remove_data) echo "删除数据/日志目录?" ;;
|
|
no_binary) echo "未发现可执行文件,跳过二进制删除" ;;
|
|
stop_service) echo "停止并禁用 systemd 服务..." ;;
|
|
remove_service) echo "删除 systemd 服务文件..." ;;
|
|
clean_path) echo "清理 PATH 配置..." ;;
|
|
skip) echo "跳过" ;;
|
|
select_lang) echo "选择语言" ;;
|
|
lang_en) echo "English" ;;
|
|
lang_zh) echo "中文" ;;
|
|
target_label) echo "目标" ;;
|
|
need_root) echo "卸载服务器需要 root 权限(请用 sudo 运行)" ;;
|
|
still_delete_shared) echo "检测到与服务器共用同一二进制,仍然删除?" ;;
|
|
*) echo "$key" ;;
|
|
esac
|
|
else
|
|
case "$key" in
|
|
title) echo "Drip Uninstaller" ;;
|
|
select_target) echo "Select what to uninstall" ;;
|
|
opt_client) echo "Client" ;;
|
|
opt_server) echo "Server" ;;
|
|
opt_both) echo "Client + Server" ;;
|
|
confirm) echo "Proceed with uninstall?" ;;
|
|
removing_client) echo "Removing client..." ;;
|
|
removing_server) echo "Removing server..." ;;
|
|
done) echo "Uninstall completed" ;;
|
|
remove_config) echo "Remove config directory as well?" ;;
|
|
remove_data) echo "Remove data/log directory as well?" ;;
|
|
no_binary) echo "No binary found, skipping binary removal" ;;
|
|
stop_service) echo "Stopping and disabling systemd service..." ;;
|
|
remove_service) echo "Removing systemd service file..." ;;
|
|
clean_path) echo "Cleaning PATH entries..." ;;
|
|
skip) echo "Skip" ;;
|
|
select_lang) echo "Select language" ;;
|
|
lang_en) echo "English" ;;
|
|
lang_zh) echo "中文" ;;
|
|
target_label) echo "Target" ;;
|
|
need_root) echo "Server uninstall requires root (run with sudo)" ;;
|
|
still_delete_shared) echo "Server uses the same binary. Delete anyway?" ;;
|
|
*) echo "$key" ;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
repeat_char() { printf "%*s" "$2" "" | tr ' ' "$1"; }
|
|
|
|
print_panel() {
|
|
local title="$1"; shift || true
|
|
local width=58
|
|
local bar; bar="$(repeat_char "=" "$width")"
|
|
say ""
|
|
say "${CYAN}${bar}${NC}"
|
|
say "${CYAN}${title}${NC}"
|
|
say "${CYAN}${bar}${NC}"
|
|
for line in "$@"; do say " $line"; done
|
|
say "${CYAN}${bar}${NC}"
|
|
say ""
|
|
}
|
|
|
|
print_subheader() {
|
|
local title="$1"
|
|
local width=58
|
|
local bar; bar="$(repeat_char "-" "$width")"
|
|
say ""
|
|
say "${CYAN}${title}${NC}"
|
|
say "${CYAN}${bar}${NC}"
|
|
}
|
|
|
|
print_menu() {
|
|
local title="$1"; shift
|
|
say ""
|
|
say "${CYAN}------------------------------${NC}"
|
|
say "${CYAN}${title}${NC}"
|
|
say "${CYAN}------------------------------${NC}"
|
|
for line in "$@"; do say " $line"; done
|
|
say ""
|
|
}
|
|
|
|
# Ask an interactive y/n question (always visible even when piped)
|
|
prompt_yes() {
|
|
local prompt="$1"
|
|
local default_no="${2:-false}" # true => default No
|
|
|
|
if [[ "$AUTO_YES" == true ]]; then
|
|
echo "y"; return
|
|
fi
|
|
|
|
local suffix="[Y/n]"
|
|
local default_ans="y"
|
|
if [[ "$default_no" == "true" ]]; then
|
|
suffix="[y/N]"
|
|
default_ans="n"
|
|
fi
|
|
|
|
while true; do
|
|
tty_put "${prompt} ${suffix} "
|
|
local ans; tty_read ans
|
|
ans="$(echo "${ans}" | tr '[:upper:]' '[:lower:]' | tr -d ' \t')"
|
|
|
|
if [[ -z "$ans" ]]; then
|
|
echo "$default_ans"; return
|
|
fi
|
|
case "$ans" in
|
|
y|yes) echo "y"; return ;;
|
|
n|no) echo "n"; return ;;
|
|
*) say_warn "Please enter y or n." ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Privilege helpers
|
|
# -----------------------------------------------------------------------------
|
|
need_root() { [[ "${EUID:-0}" -eq 0 ]]; }
|
|
|
|
run_root() {
|
|
if need_root; then
|
|
"$@"
|
|
else
|
|
command -v sudo >/dev/null 2>&1 || return 1
|
|
sudo "$@"
|
|
fi
|
|
}
|
|
|
|
# Remove a file/dir, using sudo when needed
|
|
remove_path() {
|
|
local path="$1"
|
|
[[ -z "$path" ]] && return 0
|
|
[[ ! -e "$path" ]] && return 0
|
|
|
|
if [[ -w "$path" ]]; then
|
|
rm -rf "$path" || true
|
|
else
|
|
run_root rm -rf "$path" || true
|
|
fi
|
|
}
|
|
|
|
# Clean PATH entries by removing a dedicated marked block (safer than grep -v dir)
|
|
cleanup_shell_rc() {
|
|
local candidates=("$HOME/.zshrc" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.config/fish/config.fish")
|
|
for file in "${candidates[@]}"; do
|
|
[[ -f "$file" ]] || continue
|
|
if grep -q ">>> Drip client" "$file" 2>/dev/null; then
|
|
local tmp; tmp="$(mktemp)"
|
|
awk '
|
|
/>>> Drip client/ {inblock=1; next}
|
|
/<<< Drip client/ {inblock=0; next}
|
|
!inblock {print}
|
|
' "$file" > "$tmp" || true
|
|
mv "$tmp" "$file" || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Locate client binary "drip" (best-effort)
|
|
find_client_binary() {
|
|
local candidate=""
|
|
candidate="$(command -v drip 2>/dev/null || true)"
|
|
if [[ -n "$candidate" ]] && [[ -x "$candidate" ]]; then
|
|
echo "$candidate"; return
|
|
fi
|
|
for candidate in "/usr/local/bin/drip" "/usr/bin/drip" "/opt/drip/drip" "$HOME/.local/bin/drip"; do
|
|
[[ -x "$candidate" ]] && { echo "$candidate"; return; }
|
|
done
|
|
}
|
|
|
|
# Extract actual binary path from systemd ExecStart (server install truth source)
|
|
get_systemd_exec_binary() {
|
|
local unit="drip-server.service"
|
|
command -v systemctl >/dev/null 2>&1 || return 0
|
|
local val; val="$(systemctl show -p ExecStart "$unit" --value 2>/dev/null || true)"
|
|
[[ -z "$val" ]] && return 0
|
|
|
|
# Typical format includes "path=/path/to/bin"
|
|
local bin
|
|
bin="$(echo "$val" | sed -n 's/.*path=\([^ ;]*\).*/\1/p' | head -n1)"
|
|
[[ -n "$bin" ]] && echo "$bin"
|
|
}
|
|
|
|
# Get real unit file path, if present
|
|
get_systemd_unit_path() {
|
|
local unit="drip-server.service"
|
|
command -v systemctl >/dev/null 2>&1 || return 0
|
|
systemctl show -p FragmentPath "$unit" --value 2>/dev/null || true
|
|
}
|
|
|
|
select_language() {
|
|
# Avoid blocking when no TTY (e.g., non-interactive CI)
|
|
if ! has_tty; then return; fi
|
|
|
|
print_panel "$(msg select_lang)" \
|
|
"${GREEN}1)${NC} $(msg lang_en)" \
|
|
"${GREEN}2)${NC} $(msg lang_zh)"
|
|
tty_put "Select [1]: "
|
|
local choice; tty_read choice
|
|
case "$choice" in
|
|
2) LANG_CODE="zh" ;;
|
|
*) LANG_CODE="en" ;;
|
|
esac
|
|
}
|
|
|
|
select_target() {
|
|
# If no TTY, default to client to avoid hang
|
|
if ! has_tty; then
|
|
TARGET="client"
|
|
return
|
|
fi
|
|
|
|
print_menu "$(msg select_target)" \
|
|
"${GREEN}1)${NC} $(msg opt_client)" \
|
|
"${GREEN}2)${NC} $(msg opt_server)" \
|
|
"${GREEN}3)${NC} $(msg opt_both)"
|
|
tty_put "Select [1]: "
|
|
local choice; tty_read choice
|
|
case "$choice" in
|
|
2) TARGET="server" ;;
|
|
3) TARGET="both" ;;
|
|
*) TARGET="client" ;;
|
|
esac
|
|
}
|
|
|
|
remove_client() {
|
|
print_subheader "$(msg removing_client)"
|
|
|
|
local binary_path=""
|
|
binary_path="$(find_client_binary || true)"
|
|
|
|
# If server uses the same binary, avoid accidental removal unless user confirms
|
|
local server_bin=""
|
|
server_bin="$(get_systemd_exec_binary || true)"
|
|
|
|
if [[ -n "$binary_path" ]]; then
|
|
if [[ -n "$server_bin" ]] && [[ "$server_bin" == "$binary_path" ]]; then
|
|
say_warn "Detected shared binary with server: $binary_path"
|
|
if [[ "$(prompt_yes "$(msg still_delete_shared)" true)" == "y" ]]; then
|
|
remove_path "$binary_path"
|
|
say_ok "Removed binary: $binary_path"
|
|
else
|
|
say_info "$(msg skip)"
|
|
fi
|
|
else
|
|
remove_path "$binary_path"
|
|
say_ok "Removed binary: $binary_path"
|
|
fi
|
|
|
|
cleanup_shell_rc
|
|
say_ok "$(msg clean_path)"
|
|
else
|
|
say_warn "$(msg no_binary)"
|
|
fi
|
|
|
|
if [[ "$(prompt_yes "$(msg remove_config)" true)" == "y" ]]; then
|
|
remove_path "$HOME/.drip"
|
|
remove_path "$HOME/.config/drip"
|
|
say_ok "Client config removed"
|
|
else
|
|
say_info "$(msg skip)"
|
|
fi
|
|
}
|
|
|
|
remove_server() {
|
|
print_subheader "$(msg removing_server)"
|
|
|
|
if ! need_root; then
|
|
say_warn "$(msg need_root)"
|
|
fi
|
|
|
|
local unit="drip-server.service"
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
say_info "$(msg stop_service)"
|
|
run_root systemctl stop drip-server 2>/dev/null || true
|
|
run_root systemctl disable drip-server 2>/dev/null || true
|
|
fi
|
|
|
|
say_info "$(msg remove_service)"
|
|
local unit_path=""
|
|
unit_path="$(get_systemd_unit_path || true)"
|
|
|
|
# Remove unit file from common locations
|
|
if [[ -n "$unit_path" ]] && [[ -e "$unit_path" ]]; then
|
|
remove_path "$unit_path"
|
|
fi
|
|
remove_path "/etc/systemd/system/${unit}"
|
|
remove_path "/lib/systemd/system/${unit}"
|
|
remove_path "/usr/lib/systemd/system/${unit}"
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
run_root systemctl daemon-reload 2>/dev/null || true
|
|
run_root systemctl reset-failed 2>/dev/null || true
|
|
fi
|
|
|
|
# Remove server binary using the real ExecStart path first
|
|
local server_bin=""
|
|
server_bin="$(get_systemd_exec_binary || true)"
|
|
if [[ -n "$server_bin" ]]; then
|
|
remove_path "$server_bin"
|
|
say_ok "Removed server binary: $server_bin"
|
|
else
|
|
# Fallback guesses
|
|
remove_path "/usr/local/bin/drip-server"
|
|
remove_path "/usr/bin/drip-server"
|
|
remove_path "/usr/local/bin/drip"
|
|
fi
|
|
|
|
if [[ "$(prompt_yes "$(msg remove_config)" true)" == "y" ]]; then
|
|
remove_path "/etc/drip"
|
|
say_ok "Server config removed"
|
|
else
|
|
say_info "$(msg skip)"
|
|
fi
|
|
|
|
if [[ "$(prompt_yes "$(msg remove_data)" true)" == "y" ]]; then
|
|
remove_path "/var/lib/drip"
|
|
remove_path "/var/log/drip"
|
|
say_ok "Server data/log removed"
|
|
else
|
|
say_info "$(msg skip)"
|
|
fi
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--yes|-y) AUTO_YES=true ;;
|
|
--client) TARGET="client" ;;
|
|
--server) TARGET="server" ;;
|
|
--all|--both) TARGET="both" ;;
|
|
--lang=*) LANG_CODE="${1#*=}" ;;
|
|
--lang) shift; LANG_CODE="${1:-$LANG_CODE}" ;;
|
|
*) ;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
# Note: If you run via `curl ... | bash`, TTY prompts still work.
|
|
select_language
|
|
print_panel "$(msg title)"
|
|
|
|
if [[ "$TARGET" == "prompt" ]]; then
|
|
select_target
|
|
fi
|
|
|
|
local target_desc=""
|
|
case "$TARGET" in
|
|
client) target_desc="$(msg opt_client)" ;;
|
|
server) target_desc="$(msg opt_server)" ;;
|
|
both) target_desc="$(msg opt_both)" ;;
|
|
esac
|
|
print_subheader "$(msg target_label): ${target_desc}"
|
|
|
|
# This is where older versions "looked stuck": it was waiting for y/n input.
|
|
if [[ "$(prompt_yes "$(msg confirm)" false)" != "y" ]]; then
|
|
say_info "$(msg skip)"
|
|
exit 0
|
|
fi
|
|
|
|
case "$TARGET" in
|
|
client) remove_client ;;
|
|
server) remove_server ;;
|
|
both)
|
|
remove_client
|
|
remove_server
|
|
;;
|
|
esac
|
|
|
|
say_ok "$(msg done)"
|
|
}
|
|
|
|
main "$@"
|