mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-03-21 22:06:45 +08:00
2
.env
2
.env
@ -42,7 +42,7 @@ CLASH_SHOW_SECRET_MASKED=true
|
||||
# - 默认仅监听本机:127.0.0.1:9090 (推荐)
|
||||
# - 如需局域网访问再改成:0.0.0.0:9090,并确保 CLASH_SECRET 足够复杂
|
||||
export EXTERNAL_CONTROLLER_ENABLED=true
|
||||
export EXTERNAL_CONTROLLER='127.0.0.1:9090'
|
||||
export EXTERNAL_CONTROLLER='0.0.0.0:9090'
|
||||
|
||||
# -------------------------
|
||||
# 3) 代理端口与监听(常用)
|
||||
|
||||
127
install.sh
127
install.sh
@ -5,7 +5,7 @@ set -euo pipefail
|
||||
# 基础参数
|
||||
# =========================
|
||||
Server_Dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
Install_Dir="${CLASH_INSTALL_DIR:-/opt/clash-for-linux}"
|
||||
Install_Dir="${CLASH_INSTALL_DIR:-$Server_Dir}"
|
||||
Service_Name="clash-for-linux"
|
||||
Service_User="root"
|
||||
Service_Group="root"
|
||||
@ -136,7 +136,6 @@ if [[ -z "${CpuArch:-}" ]]; then
|
||||
err "无法识别 CPU 架构"
|
||||
exit 1
|
||||
fi
|
||||
info "CPU architecture: ${CpuArch}"
|
||||
|
||||
# =========================
|
||||
# .env 写入工具:write_env_kv(必须在 prompt 之前定义)
|
||||
@ -351,57 +350,35 @@ read_secret_from_config() {
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# 判断 systemd 是否可用(仅有 systemctl 命令但 PID 1 不是 systemd 时视为不可用)
|
||||
systemd_ready() {
|
||||
command -v systemctl >/dev/null 2>&1 || return 1
|
||||
systemctl show --property=Version --value >/dev/null 2>&1 || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# =========================
|
||||
# systemd 安装与启动
|
||||
# =========================
|
||||
Service_Enabled="unknown"
|
||||
Service_Started="unknown"
|
||||
Systemd_Usable="false"
|
||||
|
||||
if systemd_ready; then
|
||||
Systemd_Usable="true"
|
||||
fi
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
CLASH_SERVICE_USER="$Service_User" CLASH_SERVICE_GROUP="$Service_Group" "$Install_Dir/scripts/install_systemd.sh"
|
||||
|
||||
if [ "$Systemd_Usable" = "true" ]; then
|
||||
if [ "${CLASH_ENABLE_SERVICE:-true}" = "true" ] || [ "${CLASH_START_SERVICE:-true}" = "true" ]; then
|
||||
CLASH_SERVICE_USER="$Service_User" CLASH_SERVICE_GROUP="$Service_Group" "$Install_Dir/scripts/install_systemd.sh"
|
||||
if [ "${CLASH_ENABLE_SERVICE:-true}" = "true" ]; then
|
||||
systemctl start "${Service_Name}.service" || true
|
||||
fi
|
||||
if [ "${CLASH_START_SERVICE:-true}" = "true" ]; then
|
||||
systemctl start "${Service_Name}.service" || true
|
||||
fi
|
||||
|
||||
if [ "${CLASH_ENABLE_SERVICE:-true}" = "true" ]; then
|
||||
systemctl enable "${Service_Name}.service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "${CLASH_START_SERVICE:-true}" = "true" ]; then
|
||||
systemctl start "${Service_Name}.service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if systemctl is-enabled --quiet "${Service_Name}.service" 2>/dev/null; then
|
||||
Service_Enabled="enabled"
|
||||
else
|
||||
Service_Enabled="disabled"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet "${Service_Name}.service" 2>/dev/null; then
|
||||
Service_Started="active"
|
||||
else
|
||||
Service_Started="inactive"
|
||||
fi
|
||||
if systemctl is-enabled --quiet "${Service_Name}.service" 2>/dev/null; then
|
||||
Service_Enabled="enabled"
|
||||
else
|
||||
info "已按配置跳过 systemd 服务安装与启动(CLASH_ENABLE_SERVICE=false 且 CLASH_START_SERVICE=false)"
|
||||
Service_Enabled="disabled"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet "${Service_Name}.service" 2>/dev/null; then
|
||||
Service_Started="active"
|
||||
else
|
||||
Service_Started="inactive"
|
||||
fi
|
||||
else
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
warn "检测到 systemctl 命令,但当前环境不可用 systemd(常见于 Docker 容器),已跳过服务单元生成"
|
||||
else
|
||||
warn "未检测到 systemd,已跳过服务单元生成"
|
||||
fi
|
||||
warn "未检测到 systemd,已跳过服务单元生成"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
@ -416,7 +393,7 @@ install_profiled() {
|
||||
[ "$http_port" = "auto" ] && http_port="7890"
|
||||
|
||||
# 只写 IPv4 loopback,避免某些环境 ::1 解析问题
|
||||
tee "$PROFILED_FILE" >/dev/null <<EOF
|
||||
sudo tee "$PROFILED_FILE" >/dev/null <<EOF
|
||||
# Clash for Linux proxy helpers
|
||||
# Auto-generated by clash-for-linux installer.
|
||||
|
||||
@ -447,7 +424,7 @@ proxy_status() {
|
||||
}
|
||||
EOF
|
||||
|
||||
chmod 644 "$PROFILED_FILE"
|
||||
sudo chmod 644 "$PROFILED_FILE"
|
||||
}
|
||||
|
||||
install_profiled || true
|
||||
@ -470,7 +447,7 @@ log "📦 安装目录:$(path "${Install_Dir}")"
|
||||
log "👤 运行用户:${Service_User}:${Service_Group}"
|
||||
log "🔧 服务名称:${Service_Name}.service"
|
||||
|
||||
if [ "$Systemd_Usable" = "true" ]; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
section "服务状态"
|
||||
|
||||
se="${Service_Enabled:-unknown}"
|
||||
@ -486,11 +463,6 @@ if [ "$Systemd_Usable" = "true" ]; then
|
||||
log "${C_BOLD}常用命令:${C_NC}"
|
||||
log " $(cmd "sudo systemctl status ${Service_Name}.service")"
|
||||
log " $(cmd "sudo systemctl restart ${Service_Name}.service")"
|
||||
else
|
||||
section "服务状态"
|
||||
warn "当前环境未启用 systemd(如 Docker 容器),请使用 clashctl 管理进程"
|
||||
log " $(cmd "sudo clashctl start")"
|
||||
log " $(cmd "sudo clashctl restart")"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
@ -501,28 +473,65 @@ section "控制面板"
|
||||
api_port="$(parse_port "${EXTERNAL_CONTROLLER}")"
|
||||
api_host="${EXTERNAL_CONTROLLER%:*}"
|
||||
|
||||
get_public_ip() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -4 -fsS --max-time 3 https://api.ipify.org 2>/dev/null \
|
||||
|| curl -4 -fsS --max-time 3 https://ifconfig.me 2>/dev/null \
|
||||
|| curl -4 -fsS --max-time 3 https://ipv4.icanhazip.com 2>/dev/null \
|
||||
|| true
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- --timeout=3 https://api.ipify.org 2>/dev/null \
|
||||
|| wget -qO- --timeout=3 https://ifconfig.me 2>/dev/null \
|
||||
|| wget -qO- --timeout=3 https://ipv4.icanhazip.com 2>/dev/null \
|
||||
|| true
|
||||
else
|
||||
true
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -z "$api_host" ]] || [[ "$api_host" == "$EXTERNAL_CONTROLLER" ]]; then
|
||||
api_host="127.0.0.1"
|
||||
fi
|
||||
|
||||
if [[ "$api_host" == "0.0.0.0" ]] || [[ "$api_host" == "::" ]] || [[ "$api_host" == "localhost" ]]; then
|
||||
api_host="$(get_public_ip | tr -d '\r\n')"
|
||||
[[ -z "$api_host" ]] && api_host="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
[[ -z "$api_host" ]] && api_host="127.0.0.1"
|
||||
fi
|
||||
|
||||
CONF_DIR="$Install_Dir/conf"
|
||||
CONF_FILE="$CONF_DIR/config.yaml"
|
||||
TEMP_DIR="$Install_Dir/temp"
|
||||
|
||||
SECRET_VAL=""
|
||||
if wait_secret_ready "$CONF_FILE" 6; then
|
||||
SECRET_VAL="$(read_secret_from_config "$CONF_FILE" || true)"
|
||||
fi
|
||||
SECRET_FILE=""
|
||||
|
||||
for _ in {1..15}; do
|
||||
for f in \
|
||||
"$TEMP_DIR/config.yaml" \
|
||||
"$CONF_DIR/config.yaml"
|
||||
do
|
||||
SECRET_VAL="$(read_secret_from_config "$f" 2>/dev/null || true)"
|
||||
if [[ -n "$SECRET_VAL" ]]; then
|
||||
SECRET_FILE="$f"
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
dash="http://${api_host}:${api_port}/ui"
|
||||
log "🌐 Dashboard:$(url "$dash")"
|
||||
|
||||
SHOW_FILE="${SECRET_FILE:-$CONF_DIR/config.yaml}"
|
||||
|
||||
if [[ -n "$SECRET_VAL" ]]; then
|
||||
MASKED="${SECRET_VAL:0:4}****${SECRET_VAL: -4}"
|
||||
MASKED="${SECRET_VAL}"
|
||||
log "🔐 Secret:${C_YELLOW}${MASKED}${C_NC}"
|
||||
log " 查看完整 Secret:$(cmd "sudo sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$CONF_FILE\" | head -n 1")"
|
||||
# log " 查看完整 Secret:$(cmd "sudo sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$SHOW_FILE\" | head -n 1")"
|
||||
else
|
||||
log "🔐 Secret:${C_YELLOW}启动中暂未读到(稍后再试)${C_NC}"
|
||||
log " 稍后查看:$(cmd "sudo sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$CONF_FILE\" | head -n 1")"
|
||||
log " 稍后查看:$(cmd "sudo sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$CONF_DIR/config.yaml\" | head -n 1")"
|
||||
log " 也可检查运行态:$(cmd "sudo sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$TEMP_DIR/config.yaml\" | head -n 1")"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
@ -541,11 +550,7 @@ else
|
||||
log " $(cmd "sudo bash -c 'echo \"CLASH_URL=<订阅地址>\" > ${ENV_FILE}'")"
|
||||
log ""
|
||||
log "配置完成后重启服务:"
|
||||
if [ "$Systemd_Usable" = "true" ]; then
|
||||
log " $(cmd "sudo systemctl restart ${Service_Name}.service")"
|
||||
else
|
||||
log " $(cmd "sudo clashctl restart")"
|
||||
fi
|
||||
log " $(cmd "sudo systemctl restart ${Service_Name}.service")"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
@ -566,7 +571,7 @@ fi
|
||||
# 启动后快速诊断
|
||||
# =========================
|
||||
sleep 1
|
||||
if [ "$Systemd_Usable" = "true" ] && command -v journalctl >/dev/null 2>&1; then
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
if journalctl -u "${Service_Name}.service" -n 50 --no-pager 2>/dev/null \
|
||||
| grep -q "Clash订阅地址不可访问"; then
|
||||
warn "服务启动异常:订阅不可用,请检查 CLASH_URL(可能过期 / 404 / 被墙)。"
|
||||
|
||||
@ -47,4 +47,5 @@ else
|
||||
exitWithError "Unsupported Linux distribution"
|
||||
fi
|
||||
|
||||
echo "CPU architecture: $CpuArch"
|
||||
log_info() { echo "[INFO] $*"; }
|
||||
log_info "CPU architecture: $CpuArch"
|
||||
@ -10,47 +10,64 @@ Service_User="root"
|
||||
Service_Group="root"
|
||||
|
||||
Unit_Path="/etc/systemd/system/${Service_Name}.service"
|
||||
PID_FILE="$Server_Dir/temp/clash.pid"
|
||||
Env_File="$Server_Dir/temp/clash-for-linux.sh"
|
||||
|
||||
#################### 权限检查 ####################
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e "\033[31m[ERROR] 需要 root 权限来安装 systemd 单元\033[0m"
|
||||
echo -e "[31m[ERROR] 需要 root 权限来安装 systemd 单元[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#################### 目录初始化 ####################
|
||||
|
||||
install -d -m 0755 \
|
||||
"$Server_Dir/conf" \
|
||||
"$Server_Dir/logs" \
|
||||
"$Server_Dir/temp"
|
||||
install -d -m 0755 "$Server_Dir/conf" "$Server_Dir/logs" "$Server_Dir/temp"
|
||||
|
||||
# 预创建 env 文件,避免 systemd 因路径不存在报错
|
||||
: > "$Env_File"
|
||||
chmod 0644 "$Env_File"
|
||||
|
||||
#################### 生成 systemd Unit ####################
|
||||
|
||||
cat >"$Unit_Path"<<EOF
|
||||
cat >"$Unit_Path" <<EOF
|
||||
[Unit]
|
||||
Description=Clash for Linux
|
||||
After=network-online.target
|
||||
Description=Clash for Linux (Mihomo)
|
||||
Documentation=https://github.com/wnlen/clash-for-linux
|
||||
After=network-online.target nss-lookup.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=0
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$Service_User
|
||||
Group=$Service_Group
|
||||
WorkingDirectory=$Server_Dir
|
||||
|
||||
# 启动 / 停止
|
||||
# 启动环境
|
||||
Environment=SYSTEMD_MODE=true
|
||||
Environment=CLASH_ENV_FILE=$Env_File
|
||||
Environment=HOME=/root
|
||||
|
||||
# 主进程必须由 start.sh 最后一跳 exec 成 mihomo/clash
|
||||
ExecStart=/bin/bash $Server_Dir/start.sh
|
||||
ExecStop=/bin/bash $Server_Dir/shutdown.sh
|
||||
ExecReload=/bin/kill -HUP \$MAINPID
|
||||
|
||||
# 失败策略
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
# 常驻策略:即使上层脚本正常退出,也要由 systemd 拉回
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
# 停止与日志
|
||||
KillMode=mixed
|
||||
TimeoutStartSec=120
|
||||
TimeoutStopSec=30
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# 环境变量
|
||||
Environment=SYSTEMD_MODE=true
|
||||
Environment=CLASH_ENV_FILE=$Server_Dir/temp/clash-for-linux.sh
|
||||
# 安全与文件权限
|
||||
UMask=0022
|
||||
NoNewPrivileges=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -59,7 +76,10 @@ EOF
|
||||
#################### 刷新 systemd ####################
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$Service_Name".service >/dev/null 2>&1 || true
|
||||
|
||||
echo -e "\033[32m[OK] 已生成 systemd 单元: ${Unit_Path}\033[0m"
|
||||
echo -e "可执行以下命令启动服务:"
|
||||
echo -e " sudo systemctl enable --now ${Service_Name}.service"
|
||||
echo -e "[32m[OK] 已生成 systemd 单元: ${Unit_Path}[0m"
|
||||
echo -e "已启用开机自启,可执行以下命令启动服务:"
|
||||
echo -e " systemctl restart ${Service_Name}.service"
|
||||
echo -e "查看状态:"
|
||||
echo -e " systemctl status ${Service_Name}.service -l --no-pager"
|
||||
|
||||
112
start.sh
112
start.sh
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 严格模式
|
||||
set -eo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
# --- DEBUG: 打印具体失败的行号和命令(systemd 下非常关键) ---
|
||||
trap 'rc=$?; echo "[ERR] rc=$rc line=$LINENO cmd=$BASH_COMMAND" >&2' ERR
|
||||
@ -90,14 +90,37 @@ URL="${CLASH_URL:-}"
|
||||
URL="$(printf '%s' "$URL" | tr -d '\r')"
|
||||
URL="$(printf '%s' "$URL" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
||||
|
||||
# 允许手动启动时交互填写;直接回车则切到本地兜底配置
|
||||
MANUAL_EMPTY_URL_FALLBACK=false
|
||||
if [ -z "$URL" ] && [ "${SYSTEMD_MODE:-false}" != "true" ]; then
|
||||
if [ -t 0 ]; then
|
||||
echo
|
||||
echo "[WARN] 未检测到订阅地址(CLASH_URL 为空)"
|
||||
echo "请粘贴你的 Clash 订阅地址(直接回车将使用本地兜底配置启动):"
|
||||
read -r -p "Clash URL: " input_url
|
||||
input_url="$(printf '%s' "$input_url" | tr -d '\r')"
|
||||
input_url="$(printf '%s' "$input_url" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
||||
|
||||
if [ -n "$input_url" ]; then
|
||||
if ! printf '%s' "$input_url" | grep -Eq '^https?://'; then
|
||||
echo "[ERR] CLASH_URL 格式无效:必须以 http:// 或 https:// 开头" >&2
|
||||
exit 2
|
||||
fi
|
||||
URL="$input_url"
|
||||
export CLASH_URL="$URL"
|
||||
else
|
||||
echo "[WARN] 未填写订阅地址,切换为本地兜底配置启动"
|
||||
MANUAL_EMPTY_URL_FALLBACK=true
|
||||
fi
|
||||
else
|
||||
echo "[ERR] CLASH_URL 为空(未配置订阅地址)" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
#让 bash 子进程能拿到
|
||||
export CLASH_URL="$URL"
|
||||
|
||||
# 只有在“需要在线更新订阅”的模式下才强制要求 URL
|
||||
if [ -z "$URL" ] && [ "${SYSTEMD_MODE:-false}" != "true" ]; then
|
||||
echo "[ERR] CLASH_URL 为空(未配置订阅地址)"
|
||||
exit 2
|
||||
fi
|
||||
if [ -n "$URL" ] && ! printf '%s' "$URL" | grep -Eq '^https?://'; then
|
||||
echo "[ERR] CLASH_URL 格式无效:必须以 http:// 或 https:// 开头" >&2
|
||||
exit 2
|
||||
@ -119,10 +142,10 @@ fi
|
||||
# 兜底生成随机 secret
|
||||
if [ -z "$Secret" ]; then
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
Secret="$(openssl rand -hex 32)"
|
||||
Secret="$(openssl rand -hex 16)"
|
||||
else
|
||||
# 32 bytes -> 64 hex chars
|
||||
Secret="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
||||
Secret="$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -179,7 +202,7 @@ ensure_ui_links() {
|
||||
|
||||
force_write_controller_and_ui() {
|
||||
local file="$1"
|
||||
local controller="${EXTERNAL_CONTROLLER:-127.0.0.1:9090}"
|
||||
local controller="${EXTERNAL_CONTROLLER:-0.0.0.0:9090}"
|
||||
|
||||
[ -n "$file" ] || return 1
|
||||
|
||||
@ -249,11 +272,11 @@ fix_external_ui_by_safe_paths() {
|
||||
CLASH_HTTP_PORT="${CLASH_HTTP_PORT:-7890}"
|
||||
CLASH_SOCKS_PORT="${CLASH_SOCKS_PORT:-7891}"
|
||||
CLASH_REDIR_PORT="${CLASH_REDIR_PORT:-7892}"
|
||||
CLASH_LISTEN_IP="${CLASH_LISTEN_IP:-127.0.0.1}"
|
||||
CLASH_LISTEN_IP="${CLASH_LISTEN_IP:-0.0.0.0}"
|
||||
CLASH_ALLOW_LAN="${CLASH_ALLOW_LAN:-false}"
|
||||
|
||||
EXTERNAL_CONTROLLER_ENABLED="${EXTERNAL_CONTROLLER_ENABLED:-true}"
|
||||
EXTERNAL_CONTROLLER="${EXTERNAL_CONTROLLER:-127.0.0.1:9090}"
|
||||
EXTERNAL_CONTROLLER="${EXTERNAL_CONTROLLER:-0.0.0.0:9090}"
|
||||
|
||||
ALLOW_INSECURE_TLS="${ALLOW_INSECURE_TLS:-false}"
|
||||
|
||||
@ -326,6 +349,10 @@ ensure_subconverter() {
|
||||
local bin="${Server_Dir}/tools/subconverter/subconverter"
|
||||
local port="25500"
|
||||
|
||||
# 自动获取服务器IP
|
||||
local host_ip
|
||||
host_ip="$(hostname -I | awk '{print $1}')"
|
||||
|
||||
# 没有二进制直接跳过
|
||||
if [ ! -x "$bin" ]; then
|
||||
echo "[WARN] subconverter bin not found: $bin"
|
||||
@ -335,20 +362,20 @@ ensure_subconverter() {
|
||||
|
||||
# 已在监听则认为就绪
|
||||
if ss -lntp 2>/dev/null | grep -qE ":${port}[[:space:]]"; then
|
||||
export SUBCONVERTER_URL="${SUBCONVERTER_URL:-http://127.0.0.1:${port}}"
|
||||
export SUBCONVERTER_URL="${SUBCONVERTER_URL:-http://${host_ip}:${port}}"
|
||||
export SUBCONVERTER_READY="true"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 启动(后台)
|
||||
# 启动(监听所有IP)
|
||||
echo "[INFO] starting subconverter..."
|
||||
(cd "${Server_Dir}/tools/subconverter" && nohup "./subconverter" >/dev/null 2>&1 &)
|
||||
(cd "${Server_Dir}/tools/subconverter" && nohup "./subconverter" -listen 0.0.0.0:${port} >/dev/null 2>&1 &)
|
||||
|
||||
# 等待端口起来
|
||||
for _ in 1 2 3 4 5; do
|
||||
sleep 1
|
||||
if ss -lntp 2>/dev/null | grep -qE ":${port}[[:space:]]"; then
|
||||
export SUBCONVERTER_URL="${SUBCONVERTER_URL:-http://127.0.0.1:${port}}"
|
||||
export SUBCONVERTER_URL="${SUBCONVERTER_URL:-http://${host_ip}:${port}}"
|
||||
export SUBCONVERTER_READY="true"
|
||||
echo "[OK] subconverter ready at ${SUBCONVERTER_URL}"
|
||||
return 0
|
||||
@ -410,9 +437,13 @@ ensure_fallback_config() {
|
||||
}
|
||||
SKIP_CONFIG_REBUILD=false
|
||||
|
||||
# systemd 模式下若 URL 为空:直接兜底启动
|
||||
if [ "${SYSTEMD_MODE}" = "true" ] && [ -z "${URL:-}" ]; then
|
||||
echo -e "\033[33m[WARN]\033[0m SYSTEMD_MODE=true 且 CLASH_URL 为空,跳过订阅更新,使用本地兜底配置启动"
|
||||
# systemd 模式下 URL 为空,或手动模式下用户回车跳过:直接兜底启动
|
||||
if { [ "${SYSTEMD_MODE}" = "true" ] && [ -z "${URL:-}" ]; } || [ "${MANUAL_EMPTY_URL_FALLBACK:-false}" = "true" ]; then
|
||||
if [ "${SYSTEMD_MODE}" = "true" ]; then
|
||||
echo -e "\033[33m[WARN]\033[0m SYSTEMD_MODE=true 且 CLASH_URL 为空,跳过订阅更新,使用本地兜底配置启动"
|
||||
else
|
||||
echo -e "\033[33m[WARN]\033[0m 手动模式未填写订阅地址,跳过订阅更新,使用本地兜底配置启动"
|
||||
fi
|
||||
ensure_fallback_config || true
|
||||
SKIP_CONFIG_REBUILD=true
|
||||
fi
|
||||
@ -797,12 +828,30 @@ Clash_Bin="$(resolve_clash_bin "$Server_Dir" "$CpuArch")"
|
||||
ReturnStatus=$?
|
||||
|
||||
if [ "$ReturnStatus" -eq 0 ]; then
|
||||
echo ''
|
||||
if [ "$EXTERNAL_CONTROLLER_ENABLED" = "true" ]; then
|
||||
echo -e "Clash Dashboard 访问地址: http://${EXTERNAL_CONTROLLER}/ui"
|
||||
|
||||
SHOW_SECRET="${CLASH_SHOW_SECRET:-false}"
|
||||
SHOW_SECRET_MASKED="${CLASH_SHOW_SECRET_MASKED:-true}"
|
||||
|
||||
if [ "$SHOW_SECRET" = "true" ]; then
|
||||
echo -e "Secret: ${Secret}"
|
||||
elif [ "$SHOW_SECRET_MASKED" = "true" ]; then
|
||||
masked="${Secret:0:4}****${Secret: -4}"
|
||||
echo -e "Secret: ${masked} (set CLASH_SHOW_SECRET=true to show full)"
|
||||
else
|
||||
echo -e "Secret: 已生成(未显示)。查看:${CONFIG_FILE} 或 .env"
|
||||
fi
|
||||
else
|
||||
echo -e "External Controller (Dashboard) 已禁用"
|
||||
fi
|
||||
echo ''
|
||||
|
||||
if [ "${SYSTEMD_MODE:-false}" = "true" ]; then
|
||||
echo "[INFO] SYSTEMD_MODE=true,前台启动交给 systemd 监管"
|
||||
echo "[INFO] Using config: $CONFIG_FILE"
|
||||
echo "[INFO] Using runtime dir: $RUNTIME_DIR"
|
||||
|
||||
# systemd 前台:只用 -f 指定配置文件,-d 作为工作目录
|
||||
exec "$Clash_Bin" -f "$CONFIG_FILE" -d "$RUNTIME_DIR"
|
||||
else
|
||||
echo "[INFO] 后台启动 (nohup)"
|
||||
@ -825,29 +874,6 @@ else
|
||||
if_success "$Text5" "$Text6" "$ReturnStatus"
|
||||
fi
|
||||
|
||||
#################### 输出信息 ####################
|
||||
|
||||
echo ''
|
||||
if [ "$EXTERNAL_CONTROLLER_ENABLED" = "true" ]; then
|
||||
echo -e "Clash Dashboard 访问地址: http://${EXTERNAL_CONTROLLER}/ui"
|
||||
|
||||
SHOW_SECRET="${CLASH_SHOW_SECRET:-false}"
|
||||
SHOW_SECRET_MASKED="${CLASH_SHOW_SECRET_MASKED:-true}"
|
||||
|
||||
if [ "$SHOW_SECRET" = "true" ]; then
|
||||
echo -e "Secret: ${Secret}"
|
||||
elif [ "$SHOW_SECRET_MASKED" = "true" ]; then
|
||||
# 脱敏:前4后4
|
||||
masked="${Secret:0:4}****${Secret: -4}"
|
||||
echo -e "Secret: ${masked} (set CLASH_SHOW_SECRET=true to show full)"
|
||||
else
|
||||
echo -e "Secret: 已生成(未显示)。查看:/opt/clash-for-linux/conf/config.yaml 或 .env"
|
||||
fi
|
||||
else
|
||||
echo -e "External Controller (Dashboard) 已禁用"
|
||||
fi
|
||||
echo ''
|
||||
|
||||
#################### 写入代理环境变量文件 ####################
|
||||
|
||||
Env_File="${CLASH_ENV_FILE:-}"
|
||||
|
||||
204
uninstall.sh
204
uninstall.sh
@ -1,142 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# =========================
|
||||
# 参数(对标 install.sh + install_systemd.sh)
|
||||
# =========================
|
||||
Install_Dir="${CLASH_INSTALL_DIR:-/opt/clash-for-linux}"
|
||||
Service_Name="clash-for-linux"
|
||||
Service_User="root"
|
||||
Service_Group="root"
|
||||
Unit_Path="/etc/systemd/system/${Service_Name}.service"
|
||||
# More accurate uninstall for clash-for-linux
|
||||
SERVICE_NAME="clash-for-linux"
|
||||
UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
|
||||
# =========================
|
||||
# 彩色输出
|
||||
# =========================
|
||||
RED='\033[31m'
|
||||
GREEN='\033[32m'
|
||||
YELLOW='\033[33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
err() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
|
||||
# =========================
|
||||
# 前置校验
|
||||
# =========================
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
err "需要 root 权限执行卸载脚本(请使用 sudo bash uninstall.sh)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "开始卸载 ${Service_Name} ..."
|
||||
info "Install_Dir=${Install_Dir}"
|
||||
# Candidate install dirs:
|
||||
# 1) explicit CLASH_INSTALL_DIR
|
||||
# 2) working directory if it looks like clash-for-linux
|
||||
# 3) service WorkingDirectory / ExecStart path inferred from unit
|
||||
# 4) common defaults
|
||||
candidates=()
|
||||
[ -n "${CLASH_INSTALL_DIR:-}" ] && candidates+=("${CLASH_INSTALL_DIR}")
|
||||
PWD_BASENAME="$(basename "${PWD}")"
|
||||
if [ "$PWD_BASENAME" = "clash-for-linux" ] && [ -f "${PWD}/start.sh" ]; then
|
||||
candidates+=("${PWD}")
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 1) 优雅停止(优先 shutdown.sh,再 systemd)
|
||||
# =========================
|
||||
if [ -f "${Install_Dir}/shutdown.sh" ]; then
|
||||
if [ -f "$UNIT_PATH" ]; then
|
||||
wd="$(sed -nE 's#^WorkingDirectory=(.*)#\1#p' "$UNIT_PATH" | head -n1 || true)"
|
||||
[ -n "$wd" ] && candidates+=("$wd")
|
||||
|
||||
exec_path="$(sed -nE 's#^ExecStart=/bin/bash[[:space:]]+([^[:space:]]+/start\.sh).*#\1#p' "$UNIT_PATH" | head -n1 || true)"
|
||||
if [ -n "$exec_path" ]; then
|
||||
candidates+=("$(dirname "$exec_path")")
|
||||
fi
|
||||
fi
|
||||
|
||||
candidates+=("/root/clash-for-linux" "/opt/clash-for-linux")
|
||||
|
||||
# normalize + uniq + choose first existing dir containing start.sh or shutdown.sh
|
||||
INSTALL_DIR=""
|
||||
declare -A seen
|
||||
for d in "${candidates[@]}"; do
|
||||
[ -n "$d" ] || continue
|
||||
d="${d%/}"
|
||||
[ -n "$d" ] || continue
|
||||
if [ -z "${seen[$d]:-}" ]; then
|
||||
seen[$d]=1
|
||||
if [ -d "$d" ] && { [ -f "$d/start.sh" ] || [ -f "$d/shutdown.sh" ] || [ -d "$d/conf" ]; }; then
|
||||
INSTALL_DIR="$d"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$INSTALL_DIR" ]; then
|
||||
warn "未能自动识别安装目录,将按候选路径继续清理 systemd / 环境文件。"
|
||||
else
|
||||
info "识别到安装目录: $INSTALL_DIR"
|
||||
fi
|
||||
|
||||
info "开始卸载 ${SERVICE_NAME} ..."
|
||||
|
||||
# 1) graceful stop
|
||||
if [ -n "$INSTALL_DIR" ] && [ -f "${INSTALL_DIR}/shutdown.sh" ]; then
|
||||
info "执行 shutdown.sh(优雅停止)..."
|
||||
bash "${Install_Dir}/shutdown.sh" >/dev/null 2>&1 || true
|
||||
bash "${INSTALL_DIR}/shutdown.sh" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
info "停止并禁用 systemd 服务..."
|
||||
systemctl stop "${Service_Name}.service" >/dev/null 2>&1 || true
|
||||
systemctl disable "${Service_Name}.service" >/dev/null 2>&1 || true
|
||||
systemctl stop "${SERVICE_NAME}.service" >/dev/null 2>&1 || true
|
||||
systemctl disable "${SERVICE_NAME}.service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 2) 兜底:按 PID 文件杀进程(对标 unit 的 PIDFile)
|
||||
# =========================
|
||||
PID_FILE="${Install_Dir}/temp/clash.pid"
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID="$(cat "$PID_FILE" 2>/dev/null || true)"
|
||||
if [ -n "${PID:-}" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
info "检测到 PID=${PID},尝试停止..."
|
||||
kill "$PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
if kill -0 "$PID" 2>/dev/null; then
|
||||
warn "进程仍在运行,强制 kill -9 ${PID}"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
# 2) stop process by pid file from all likely dirs
|
||||
for d in "/root/clash-for-linux" "/opt/clash-for-linux" "${INSTALL_DIR:-}"; do
|
||||
[ -n "$d" ] || continue
|
||||
PID_FILE="$d/temp/clash.pid"
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID="$(cat "$PID_FILE" 2>/dev/null || true)"
|
||||
if [ -n "${PID:-}" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
info "检测到 PID=${PID}(来自 $PID_FILE),尝试停止..."
|
||||
kill "$PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
if kill -0 "$PID" 2>/dev/null; then
|
||||
warn "进程仍在运行,强制 kill -9 ${PID}"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
ok "已停止 clash 进程(PIDFile)"
|
||||
rm -f "$PID_FILE" || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 再兜底:按进程名(系统可能有多个 clash,不建议无脑 pkill -9;先提示再杀)
|
||||
if pgrep -x clash >/dev/null 2>&1; then
|
||||
warn "检测到仍有 clash 进程存在(可能非本项目),尝试温和结束..."
|
||||
pkill -x clash >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
fi
|
||||
if pgrep -x clash >/dev/null 2>&1; then
|
||||
warn "仍残留 clash 进程,执行 pkill -9(可能影响其它 clash 实例)..."
|
||||
pkill -9 -x clash >/dev/null 2>&1 || true
|
||||
fi
|
||||
# 兜底:按完整路径匹配,避免误杀其他 clash
|
||||
pkill -f '/clash-for-linux/.*/clash' >/dev/null 2>&1 || true
|
||||
pkill -f '/clash-for-linux/.*/mihomo' >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
pkill -9 -f '/clash-for-linux/.*/clash' >/dev/null 2>&1 || true
|
||||
pkill -9 -f '/clash-for-linux/.*/mihomo' >/dev/null 2>&1 || true
|
||||
|
||||
# =========================
|
||||
# 3) 删除 systemd unit(对标 install_systemd.sh)
|
||||
# =========================
|
||||
if [ -f "$Unit_Path" ]; then
|
||||
rm -f "$Unit_Path"
|
||||
ok "已移除 systemd 单元: ${Unit_Path}"
|
||||
# 3) remove unit and related files
|
||||
if [ -f "$UNIT_PATH" ]; then
|
||||
rm -f "$UNIT_PATH"
|
||||
ok "已移除 systemd 单元: ${UNIT_PATH}"
|
||||
fi
|
||||
|
||||
# drop-in(万一用户自定义过)
|
||||
if [ -d "/etc/systemd/system/${Service_Name}.service.d" ]; then
|
||||
rm -rf "/etc/systemd/system/${Service_Name}.service.d"
|
||||
ok "已移除 drop-in: /etc/systemd/system/${Service_Name}.service.d"
|
||||
if [ -d "/etc/systemd/system/${SERVICE_NAME}.service.d" ]; then
|
||||
rm -rf "/etc/systemd/system/${SERVICE_NAME}.service.d"
|
||||
ok "已移除 drop-in: /etc/systemd/system/${SERVICE_NAME}.service.d"
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
systemctl reset-failed >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 4) 清理默认配置/环境脚本/命令入口
|
||||
# =========================
|
||||
if [ -f "/etc/default/${Service_Name}" ]; then
|
||||
rm -f "/etc/default/${Service_Name}"
|
||||
ok "已移除: /etc/default/${Service_Name}"
|
||||
# 4) cleanup env / command entry
|
||||
rm -f "/etc/default/${SERVICE_NAME}" >/dev/null 2>&1 || true
|
||||
rm -f "/etc/profile.d/clash-for-linux.sh" >/dev/null 2>&1 || true
|
||||
rm -f "/usr/local/bin/clashctl" >/dev/null 2>&1 || true
|
||||
for d in "/root/clash-for-linux" "/opt/clash-for-linux" "${INSTALL_DIR:-}"; do
|
||||
[ -n "$d" ] || continue
|
||||
rm -f "$d/temp/clash-for-linux.sh" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
# 5) remove install dirs
|
||||
removed_any=false
|
||||
for d in "${INSTALL_DIR:-}" "/root/clash-for-linux" "/opt/clash-for-linux"; do
|
||||
[ -n "$d" ] || continue
|
||||
if [ -d "$d" ] && { [ -f "$d/start.sh" ] || [ -d "$d/conf" ] || [ "$d" = "$INSTALL_DIR" ]; }; then
|
||||
rm -rf "$d"
|
||||
ok "已移除安装目录: $d"
|
||||
removed_any=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$removed_any" = false ]; then
|
||||
warn "未发现可删除的安装目录"
|
||||
fi
|
||||
|
||||
# 运行时 Env_File 可能写到 /etc/profile.d 或 temp,这里都清
|
||||
if [ -f "/etc/profile.d/clash-for-linux.sh" ]; then
|
||||
rm -f "/etc/profile.d/clash-for-linux.sh"
|
||||
ok "已移除: /etc/profile.d/clash-for-linux.sh"
|
||||
fi
|
||||
|
||||
if [ -f "${Install_Dir}/temp/clash-for-linux.sh" ]; then
|
||||
rm -f "${Install_Dir}/temp/clash-for-linux.sh" || true
|
||||
ok "已移除: ${Install_Dir}/temp/clash-for-linux.sh"
|
||||
fi
|
||||
|
||||
if [ -f "/usr/local/bin/clashctl" ]; then
|
||||
rm -f "/usr/local/bin/clashctl"
|
||||
ok "已移除: /usr/local/bin/clashctl"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 5) 删除安装目录
|
||||
# =========================
|
||||
if [ -d "$Install_Dir" ]; then
|
||||
rm -rf "$Install_Dir"
|
||||
ok "已移除安装目录: ${Install_Dir}"
|
||||
else
|
||||
warn "未找到安装目录: ${Install_Dir}"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 7) 提示:当前终端代理变量需要手动清
|
||||
# =========================
|
||||
echo
|
||||
warn "如果你曾执行 proxy_on,当前终端可能仍保留代理环境变量。可执行:"
|
||||
echo " unset http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY"
|
||||
echo " # 或关闭终端重新打开"
|
||||
|
||||
echo
|
||||
ok "卸载完成(root-only 模式)✅"
|
||||
ok "卸载完成 ✅"
|
||||
|
||||
Reference in New Issue
Block a user