Files
drip/scripts/uninstall.sh
Gouryella 522c092d8d feat(install-scripts): Enhanced installation script functionality and added uninstallation support
- 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.
2025-12-06 13:27:34 +08:00

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 "$@"