From 9e4eb7f76ce2c68c475168d64867c9c638f92567 Mon Sep 17 00:00:00 2001 From: wnlen <62139570+wnlen@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:25:38 +0800 Subject: [PATCH] Add install scripts and Clash core auto-download --- README.md | 25 +++++++++ install.sh | 112 +++++++++++++++++++++++++++++++++++++ scripts/resolve_clash.sh | 118 +++++++++++++++++++++++++++++++++++++++ start.sh | 58 +------------------ uninstall.sh | 36 ++++++++++++ 5 files changed, 294 insertions(+), 55 deletions(-) create mode 100755 install.sh create mode 100755 scripts/resolve_clash.sh create mode 100755 uninstall.sh diff --git a/README.md b/README.md index 3172c68..23fce5e 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,31 @@ $ source /etc/profile.d/clash-for-linux.sh $ proxy_on ``` +
+ +## 一键安装/卸载 + +脚本会自动识别安装路径、创建低权限用户、检测端口冲突,并根据架构自动下载 Clash 内核(可通过 `CLASH_DOWNLOAD_URL_TEMPLATE` 自定义下载地址)。 + +```bash +$ sudo bash install.sh +``` + +如需调整安装路径或服务行为,可使用以下环境变量: + +- `CLASH_INSTALL_DIR`:默认 `/opt/clash-for-linux` +- `CLASH_SERVICE_USER` / `CLASH_SERVICE_GROUP`:systemd 运行用户/组 +- `CLASH_ENABLE_SERVICE`:是否 `systemctl enable`(默认 `true`) +- `CLASH_START_SERVICE`:是否 `systemctl start`(默认 `true`) +- `CLASH_AUTO_DOWNLOAD`:是否自动下载 Clash 内核(默认 `auto`) +- `CLASH_DOWNLOAD_URL_TEMPLATE`:自定义下载模板(默认 `https://github.com/Dreamacro/clash/releases/latest/download/clash-{arch}.gz`) + +卸载: + +```bash +$ sudo bash uninstall.sh +``` + - 检查服务端口 ```bash diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..ec1818f --- /dev/null +++ b/install.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +set -euo pipefail + +Server_Dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +Install_Dir="${CLASH_INSTALL_DIR:-/opt/clash-for-linux}" +Service_Name="clash-for-linux" +Service_User="${CLASH_SERVICE_USER:-clash}" +Service_Group="${CLASH_SERVICE_GROUP:-$Service_User}" + +if [ "$(id -u)" -ne 0 ]; then + echo -e "\033[31m[ERROR] 需要 root 权限执行安装脚本\033[0m" + exit 1 +fi + +if [ ! -f "${Server_Dir}/.env" ]; then + echo -e "\033[31m[ERROR] 未找到 .env 文件,请确认脚本所在目录\033[0m" + exit 1 +fi + +mkdir -p "$Install_Dir" +if [ "$Server_Dir" != "$Install_Dir" ]; then + if command -v rsync >/dev/null 2>&1; then + rsync -a --delete --exclude '.git' "$Server_Dir/" "$Install_Dir/" + else + cp -a "$Server_Dir/." "$Install_Dir/" + fi +fi + +chmod +x "$Install_Dir"/*.sh 2>/dev/null || true +chmod +x "$Install_Dir"/scripts/* 2>/dev/null || true +chmod +x "$Install_Dir"/bin/* 2>/dev/null || true + +source "$Install_Dir/.env" +source "$Install_Dir/scripts/get_cpu_arch.sh" +source "$Install_Dir/scripts/resolve_clash.sh" + +if [[ -z "${CpuArch:-}" ]]; then + echo -e "\033[31m[ERROR] 无法识别 CPU 架构\033[0m" + exit 1 +fi + +CLASH_HTTP_PORT=${CLASH_HTTP_PORT:-7890} +CLASH_SOCKS_PORT=${CLASH_SOCKS_PORT:-7891} +CLASH_REDIR_PORT=${CLASH_REDIR_PORT:-7892} +EXTERNAL_CONTROLLER=${EXTERNAL_CONTROLLER:-127.0.0.1:9090} + +parse_port() { + local raw="$1" + raw="${raw##*:}" + echo "$raw" +} + +is_port_in_use() { + local port="$1" + if command -v ss >/dev/null 2>&1; then + ss -lnt | awk '{print $4}' | grep -E "(:|\.)${port}$" >/dev/null 2>&1 + elif command -v netstat >/dev/null 2>&1; then + netstat -lnt | awk '{print $4}' | grep -E "(:|\.)${port}$" >/dev/null 2>&1 + elif command -v lsof >/dev/null 2>&1; then + lsof -iTCP -sTCP:LISTEN -P -n | awk '{print $9}' | grep -E "(:|\.)${port}$" >/dev/null 2>&1 + else + echo -e "\033[33m[WARN] 未找到端口检测工具,跳过端口冲突检测\033[0m" + return 1 + fi +} + +Port_Conflicts=() +for port in "$CLASH_HTTP_PORT" "$CLASH_SOCKS_PORT" "$CLASH_REDIR_PORT" "$(parse_port "$EXTERNAL_CONTROLLER")"; do + if [[ "$port" =~ ^[0-9]+$ ]]; then + if is_port_in_use "$port"; then + Port_Conflicts+=("$port") + fi + fi +done + +if [ "${#Port_Conflicts[@]}" -ne 0 ]; then + echo -e "\033[31m[ERROR] 检测到端口冲突: ${Port_Conflicts[*]}\033[0m" + echo -e "请修改 .env 中的端口设置后重新安装。" + exit 1 +fi + +if ! getent group "$Service_Group" >/dev/null 2>&1; then + groupadd --system "$Service_Group" +fi + +if ! id "$Service_User" >/dev/null 2>&1; then + useradd --system --no-create-home --shell /usr/sbin/nologin --gid "$Service_Group" "$Service_User" +fi + +install -d -m 0755 "$Install_Dir/conf" "$Install_Dir/logs" "$Install_Dir/temp" +chown -R "$Service_User:$Service_Group" "$Install_Dir/conf" "$Install_Dir/logs" "$Install_Dir/temp" + +if ! resolve_clash_bin "$Install_Dir" "$CpuArch" >/dev/null 2>&1; then + echo -e "\033[31m[ERROR] Clash 内核未就绪,请检查下载配置或手动放置二进制\033[0m" + exit 1 +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 [ "${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 +else + echo -e "\033[33m[WARN] 未检测到 systemd,已跳过服务单元生成\033[0m" +fi + +echo -e "\033[32m[OK] Clash for Linux 已安装至: ${Install_Dir}\033[0m" +echo -e "请编辑 ${Install_Dir}/.env 配置订阅地址后启动服务。" diff --git a/scripts/resolve_clash.sh b/scripts/resolve_clash.sh new file mode 100755 index 0000000..06a7357 --- /dev/null +++ b/scripts/resolve_clash.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +resolve_clash_arch() { + local raw_arch="$1" + case "$raw_arch" in + x86_64|amd64) + echo "linux-amd64" + ;; + aarch64|arm64) + echo "linux-arm64" + ;; + armv7*|armv7l) + echo "linux-armv7" + ;; + *) + echo "linux-${raw_arch}" + ;; + esac +} + +download_clash_bin() { + local server_dir="$1" + local detected_arch="$2" + local resolved_arch + local download_url + local download_target + local archive_file + + resolved_arch=$(resolve_clash_arch "$detected_arch") + if [ -z "$resolved_arch" ]; then + echo -e "\033[33m[WARN] 无法识别 CPU 架构,跳过 Clash 内核自动下载\033[0m" + return 1 + fi + + if [ "${CLASH_AUTO_DOWNLOAD:-auto}" = "false" ]; then + return 1 + fi + + download_url="${CLASH_DOWNLOAD_URL_TEMPLATE:-https://github.com/Dreamacro/clash/releases/latest/download/clash-{arch}.gz}" + if [ -z "$download_url" ]; then + echo -e "\033[33m[WARN] 未设置 CLASH_DOWNLOAD_URL_TEMPLATE,跳过 Clash 内核自动下载\033[0m" + return 1 + fi + + download_url="${download_url//\{arch\}/${resolved_arch}}" + download_target="${server_dir}/bin/clash-${resolved_arch}" + archive_file="${server_dir}/temp/clash-${resolved_arch}.download" + + mkdir -p "${server_dir}/bin" "${server_dir}/temp" + + if command -v curl >/dev/null 2>&1; then + curl -L -sS -o "${archive_file}" "${download_url}" + elif command -v wget >/dev/null 2>&1; then + wget -q -O "${archive_file}" "${download_url}" + else + echo -e "\033[33m[WARN] 未找到 curl 或 wget,无法自动下载 Clash 内核\033[0m" + return 1 + fi + + if [ -f "${archive_file}" ]; then + if gzip -t "${archive_file}" >/dev/null 2>&1; then + gzip -dc "${archive_file}" >"${download_target}" + else + mv "${archive_file}" "${download_target}" + fi + chmod +x "${download_target}" + echo "${download_target}" + return 0 + fi + + echo -e "\033[33m[WARN] Clash 内核自动下载失败\033[0m" + return 1 +} + +resolve_clash_bin() { + local server_dir="$1" + local detected_arch="$2" + local resolved_arch + local candidates=() + local candidate + local downloaded_bin + + if [ -n "${CLASH_BIN:-}" ]; then + if [ -x "$CLASH_BIN" ]; then + echo "$CLASH_BIN" + return 0 + fi + echo -e "\033[31m[ERROR] CLASH_BIN 指定的文件不可执行: $CLASH_BIN\033[0m" + return 1 + fi + + resolved_arch=$(resolve_clash_arch "$detected_arch") + if [ -n "$resolved_arch" ]; then + candidates+=("${server_dir}/bin/clash-${resolved_arch}") + fi + candidates+=( + "${server_dir}/bin/clash-${detected_arch}" + "${server_dir}/bin/clash" + ) + + for candidate in "${candidates[@]}"; do + if [ -x "$candidate" ]; then + echo "$candidate" + return 0 + fi + done + + if downloaded_bin=$(download_clash_bin "$server_dir" "$detected_arch"); then + echo "$downloaded_bin" + return 0 + fi + + echo -e "\033[31m\n[ERROR] 未找到可用的 Clash 二进制。\033[0m" + echo -e "请将对应架构的二进制放入: $server_dir/bin/" + echo -e "可用命名示例: clash-${resolved_arch} 或 clash-${detected_arch}" + echo -e "或通过 CLASH_BIN 指定自定义路径。" + return 1 +} diff --git a/start.sh b/start.sh index e8f6999..cb817b2 100644 --- a/start.sh +++ b/start.sh @@ -97,6 +97,8 @@ if [[ -z "$CpuArch" ]]; then exit 1 fi +source "$Server_Dir/scripts/resolve_clash.sh" + ## 临时取消环境变量 unset http_proxy @@ -223,66 +225,12 @@ fi sed -r -i '/^secret: /s@(secret: ).*@\1'${Secret}'@g' $Conf_Dir/config.yaml -resolve_clash_arch() { - local raw_arch="$1" - case "$raw_arch" in - x86_64|amd64) - echo "linux-amd64" - ;; - aarch64|arm64) - echo "linux-arm64" - ;; - armv7*|armv7l) - echo "linux-armv7" - ;; - *) - echo "linux-${raw_arch}" - ;; - esac -} - -resolve_clash_bin() { - local detected_arch="${CpuArch:-$(uname -m 2>/dev/null)}" - local resolved_arch - local candidates=() - - if [ -n "$CLASH_BIN" ]; then - if [ -x "$CLASH_BIN" ]; then - echo "$CLASH_BIN" - return 0 - fi - echo -e "\033[31m[ERROR] CLASH_BIN 指定的文件不可执行: $CLASH_BIN\033[0m" - return 1 - fi - - resolved_arch=$(resolve_clash_arch "$detected_arch") - if [ -n "$resolved_arch" ]; then - candidates+=("$Server_Dir/bin/clash-${resolved_arch}") - fi - candidates+=( - "$Server_Dir/bin/clash-${detected_arch}" - "$Server_Dir/bin/clash" - ) - - for candidate in "${candidates[@]}"; do - if [ -x "$candidate" ]; then - echo "$candidate" - return 0 - fi - done - - echo -e "\033[31m\n[ERROR] 未找到可用的 Clash 二进制。\033[0m" - echo -e "请将对应架构的二进制放入: $Server_Dir/bin/" - echo -e "可用命名示例: clash-${resolved_arch} 或 clash-${detected_arch}" - echo -e "或通过 CLASH_BIN 指定自定义路径。" - return 1 -} ## 启动Clash服务 echo -e '\n正在启动Clash服务...' Text5="服务启动成功!" Text6="服务启动失败!" -Clash_Bin=$(resolve_clash_bin) +Clash_Bin=$(resolve_clash_bin "$Server_Dir" "$CpuArch") ReturnStatus=$? if [ $ReturnStatus -eq 0 ]; then nohup "$Clash_Bin" -d "$Conf_Dir" &> "$Log_Dir/clash.log" & diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..107165c --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -euo pipefail + +Install_Dir="${CLASH_INSTALL_DIR:-/opt/clash-for-linux}" +Service_Name="clash-for-linux" + +if [ "$(id -u)" -ne 0 ]; then + echo -e "\033[31m[ERROR] 需要 root 权限执行卸载脚本\033[0m" + exit 1 +fi + +if command -v systemctl >/dev/null 2>&1; then + systemctl stop "${Service_Name}.service" >/dev/null 2>&1 || true + systemctl disable "${Service_Name}.service" >/dev/null 2>&1 || true + if [ -f "/etc/systemd/system/${Service_Name}.service" ]; then + rm -f "/etc/systemd/system/${Service_Name}.service" + systemctl daemon-reload + fi +fi + +if [ -f "/etc/default/${Service_Name}" ]; then + rm -f "/etc/default/${Service_Name}" +fi + +if [ -f "/etc/profile.d/clash-for-linux.sh" ]; then + rm -f "/etc/profile.d/clash-for-linux.sh" +fi + +if [ -d "$Install_Dir" ]; then + rm -rf "$Install_Dir" + echo -e "\033[32m[OK] 已移除安装目录: ${Install_Dir}\033[0m" +else + echo -e "\033[33m[WARN] 未找到安装目录: ${Install_Dir}\033[0m" +fi +