mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-03-21 22:06:45 +08:00
v1.19.21
This commit is contained in:
@ -1,16 +1,36 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
trim_value() {
|
||||
local value="$1"
|
||||
echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 安全写入:避免重复块
|
||||
# =========================
|
||||
remove_block_if_exists() {
|
||||
local file="$1"
|
||||
local marker="$2"
|
||||
|
||||
[ -f "$file" ] || return 0
|
||||
|
||||
# 删除已有 block(从 marker 到文件结束)
|
||||
if grep -q "$marker" "$file"; then
|
||||
sed -i "/$marker/,\$d" "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================
|
||||
# TUN 配置
|
||||
# =========================
|
||||
apply_tun_config() {
|
||||
local config_path="$1"
|
||||
|
||||
local enable="${CLASH_TUN_ENABLE:-false}"
|
||||
if [ "$enable" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
[ "$enable" = "true" ] || return 0
|
||||
|
||||
remove_block_if_exists "$config_path" "# ==== TUN CONFIG START ===="
|
||||
|
||||
local stack="${CLASH_TUN_STACK:-system}"
|
||||
local auto_route="${CLASH_TUN_AUTO_ROUTE:-true}"
|
||||
@ -22,65 +42,91 @@ apply_tun_config() {
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "# ==== TUN CONFIG START ===="
|
||||
echo "tun:"
|
||||
echo " enable: true"
|
||||
echo " stack: ${stack}"
|
||||
echo " auto-route: ${auto_route}"
|
||||
echo " auto-redirect: ${auto_redirect}"
|
||||
echo " strict-route: ${strict_route}"
|
||||
if [ -n "$device" ]; then
|
||||
echo " device: ${device}"
|
||||
fi
|
||||
if [ -n "$mtu" ]; then
|
||||
echo " mtu: ${mtu}"
|
||||
fi
|
||||
|
||||
[ -n "$device" ] && echo " device: ${device}"
|
||||
[ -n "$mtu" ] && echo " mtu: ${mtu}"
|
||||
|
||||
if [ -n "$dns_hijack" ]; then
|
||||
echo " dns-hijack:"
|
||||
IFS=',' read -r -a hijacks <<< "$dns_hijack"
|
||||
for item in "${hijacks[@]}"; do
|
||||
local trimmed
|
||||
trimmed=$(trim_value "$item")
|
||||
if [ -n "$trimmed" ]; then
|
||||
echo " - ${trimmed}"
|
||||
fi
|
||||
item="$(trim_value "$item")"
|
||||
[ -n "$item" ] && echo " - ${item}"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "# ==== TUN CONFIG END ===="
|
||||
} >> "$config_path"
|
||||
}
|
||||
|
||||
# =========================
|
||||
# MIXIN 配置
|
||||
# =========================
|
||||
apply_mixin_config() {
|
||||
local config_path="$1"
|
||||
local base_dir="${2:-$Server_Dir}"
|
||||
local mixin_dir="${CLASH_MIXIN_DIR:-$base_dir/conf/mixin.d}"
|
||||
local base_dir="$2"
|
||||
|
||||
local mixin_dir="${CLASH_MIXIN_DIR:-$base_dir/config/mixin.d}"
|
||||
local mixin_paths=()
|
||||
|
||||
remove_block_if_exists "$config_path" "# ==== MIXIN CONFIG START ===="
|
||||
|
||||
# 用户手动指定优先
|
||||
if [ -n "${CLASH_MIXIN_PATHS:-}" ]; then
|
||||
IFS=',' read -r -a mixin_paths <<< "$CLASH_MIXIN_PATHS"
|
||||
fi
|
||||
|
||||
# 自动扫描目录(补充)
|
||||
if [ -d "$mixin_dir" ]; then
|
||||
while IFS= read -r -d '' file; do
|
||||
mixin_paths+=("$file")
|
||||
done < <(find "$mixin_dir" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0 | sort -z)
|
||||
done < <(
|
||||
find "$mixin_dir" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) \
|
||||
-print0 | sort -z
|
||||
)
|
||||
fi
|
||||
|
||||
# 去重
|
||||
local uniq_paths=()
|
||||
local seen=""
|
||||
|
||||
for path in "${mixin_paths[@]}"; do
|
||||
local trimmed
|
||||
trimmed=$(trim_value "$path")
|
||||
if [ -z "$trimmed" ]; then
|
||||
continue
|
||||
path="$(trim_value "$path")"
|
||||
[ -z "$path" ] && continue
|
||||
|
||||
# 相对路径转绝对
|
||||
if [ "${path:0:1}" != "/" ]; then
|
||||
path="$base_dir/$path"
|
||||
fi
|
||||
if [ "${trimmed:0:1}" != "/" ]; then
|
||||
trimmed="$base_dir/$trimmed"
|
||||
fi
|
||||
if [ -f "$trimmed" ]; then
|
||||
{
|
||||
echo ""
|
||||
echo "# ---- mixin: ${trimmed} ----"
|
||||
cat "$trimmed"
|
||||
} >> "$config_path"
|
||||
else
|
||||
echo "[WARN] Mixin file not found: $trimmed" >&2
|
||||
|
||||
if [[ "$seen" != *"|$path|"* ]]; then
|
||||
uniq_paths+=("$path")
|
||||
seen="${seen}|$path|"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 写入
|
||||
{
|
||||
echo ""
|
||||
echo "# ==== MIXIN CONFIG START ===="
|
||||
|
||||
for path in "${uniq_paths[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
echo ""
|
||||
echo "# ---- mixin: ${path} ----"
|
||||
cat "$path"
|
||||
else
|
||||
echo "[WARN] Mixin not found: $path" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
echo "# ==== MIXIN CONFIG END ===="
|
||||
} >> "$config_path"
|
||||
}
|
||||
@ -1,21 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
RUNTIME_DIR="$PROJECT_DIR/runtime"
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
echo "=== Clash Doctor ==="
|
||||
|
||||
if [ -f "$RUNTIME_DIR/config.yaml" ]; then
|
||||
echo "[OK] config exists"
|
||||
else
|
||||
echo "[ERROR] config missing"
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl is-active --quiet clash-for-linux.service; then
|
||||
echo "[OK] service running"
|
||||
else
|
||||
echo "[WARN] service not running"
|
||||
fi
|
||||
fi
|
||||
exec "$PROJECT_DIR/clashctl" doctor "$@"
|
||||
@ -12,6 +12,7 @@ STATE_FILE="$RUNTIME_DIR/state.env"
|
||||
TMP_DOWNLOAD="$RUNTIME_DIR/subscription.raw.yaml"
|
||||
TMP_NORMALIZED="$RUNTIME_DIR/subscription.normalized.yaml"
|
||||
TMP_PROXY_FRAGMENT="$RUNTIME_DIR/proxy.fragment.yaml"
|
||||
TMP_CONFIG="$RUNTIME_DIR/config.yaml.tmp"
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$CONFIG_DIR" "$LOG_DIR"
|
||||
|
||||
@ -64,6 +65,15 @@ generate_secret() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -s "$RUNTIME_CONFIG" ]; then
|
||||
local old_secret
|
||||
old_secret="$(sed -nE 's/^[[:space:]]*secret:[[:space:]]*"?([^"#]+)"?.*$/\1/p' "$RUNTIME_CONFIG" | head -n 1)"
|
||||
if [ -n "${old_secret:-}" ]; then
|
||||
echo "$old_secret"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -hex 16
|
||||
else
|
||||
@ -103,9 +113,11 @@ apply_controller_to_config() {
|
||||
|
||||
rm -rf "$ui_dir"
|
||||
mkdir -p "$ui_dir"
|
||||
cp -a "$PROJECT_DIR/dashboard/public/." "$ui_dir/"
|
||||
|
||||
upsert_yaml_kv_local "$file" "external-ui" "$ui_dir"
|
||||
if [ -d "$PROJECT_DIR/dashboard/public" ]; then
|
||||
cp -a "$PROJECT_DIR/dashboard/public/." "$ui_dir/"
|
||||
upsert_yaml_kv_local "$file" "external-ui" "$ui_dir"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@ -121,11 +133,32 @@ download_subscription() {
|
||||
|
||||
is_complete_clash_config() {
|
||||
local file="$1"
|
||||
grep -qE '^(proxies:|proxy-providers:|mixed-port:|port:)' "$file"
|
||||
grep -qE '^[[:space:]]*(proxies:|proxy-providers:|mixed-port:|port:)' "$file"
|
||||
}
|
||||
|
||||
cleanup_tmp_files() {
|
||||
rm -f "$TMP_NORMALIZED" "$TMP_PROXY_FRAGMENT"
|
||||
rm -f "$TMP_PROXY_FRAGMENT" "$TMP_CONFIG"
|
||||
}
|
||||
|
||||
build_fragment_config() {
|
||||
local template_file="$1"
|
||||
local target_file="$2"
|
||||
|
||||
sed -n '/^proxies:/,$p' "$TMP_NORMALIZED" > "$TMP_PROXY_FRAGMENT"
|
||||
|
||||
cat "$template_file" > "$target_file"
|
||||
cat "$TMP_PROXY_FRAGMENT" >> "$target_file"
|
||||
|
||||
sed -i "s/CLASH_HTTP_PORT_PLACEHOLDER/${CLASH_HTTP_PORT}/g" "$target_file"
|
||||
sed -i "s/CLASH_SOCKS_PORT_PLACEHOLDER/${CLASH_SOCKS_PORT}/g" "$target_file"
|
||||
sed -i "s/CLASH_REDIR_PORT_PLACEHOLDER/${CLASH_REDIR_PORT}/g" "$target_file"
|
||||
sed -i "s/CLASH_LISTEN_IP_PLACEHOLDER/${CLASH_LISTEN_IP}/g" "$target_file"
|
||||
sed -i "s/CLASH_ALLOW_LAN_PLACEHOLDER/${CLASH_ALLOW_LAN}/g" "$target_file"
|
||||
}
|
||||
|
||||
finalize_config() {
|
||||
local file="$1"
|
||||
mv -f "$file" "$RUNTIME_CONFIG"
|
||||
}
|
||||
|
||||
main() {
|
||||
@ -156,9 +189,10 @@ main() {
|
||||
cp -f "$TMP_DOWNLOAD" "$TMP_NORMALIZED"
|
||||
|
||||
if is_complete_clash_config "$TMP_NORMALIZED"; then
|
||||
cp -f "$TMP_NORMALIZED" "$RUNTIME_CONFIG"
|
||||
apply_controller_to_config "$RUNTIME_CONFIG"
|
||||
apply_secret_to_config "$RUNTIME_CONFIG"
|
||||
cp -f "$TMP_NORMALIZED" "$TMP_CONFIG"
|
||||
apply_controller_to_config "$TMP_CONFIG"
|
||||
apply_secret_to_config "$TMP_CONFIG"
|
||||
finalize_config "$TMP_CONFIG"
|
||||
write_state "success" "subscription_full" "subscription_full"
|
||||
cleanup_tmp_files
|
||||
exit 0
|
||||
@ -171,22 +205,14 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -n '/^proxies:/,$p' "$TMP_NORMALIZED" > "$TMP_PROXY_FRAGMENT"
|
||||
|
||||
cat "$template_file" > "$RUNTIME_CONFIG"
|
||||
cat "$TMP_PROXY_FRAGMENT" >> "$RUNTIME_CONFIG"
|
||||
|
||||
sed -i "s/CLASH_HTTP_PORT_PLACEHOLDER/${CLASH_HTTP_PORT}/g" "$RUNTIME_CONFIG"
|
||||
sed -i "s/CLASH_SOCKS_PORT_PLACEHOLDER/${CLASH_SOCKS_PORT}/g" "$RUNTIME_CONFIG"
|
||||
sed -i "s/CLASH_REDIR_PORT_PLACEHOLDER/${CLASH_REDIR_PORT}/g" "$RUNTIME_CONFIG"
|
||||
sed -i "s/CLASH_LISTEN_IP_PLACEHOLDER/${CLASH_LISTEN_IP}/g" "$RUNTIME_CONFIG"
|
||||
sed -i "s/CLASH_ALLOW_LAN_PLACEHOLDER/${CLASH_ALLOW_LAN}/g" "$RUNTIME_CONFIG"
|
||||
|
||||
apply_controller_to_config "$RUNTIME_CONFIG"
|
||||
apply_secret_to_config "$RUNTIME_CONFIG"
|
||||
build_fragment_config "$template_file" "$TMP_CONFIG"
|
||||
apply_controller_to_config "$TMP_CONFIG"
|
||||
apply_secret_to_config "$TMP_CONFIG"
|
||||
|
||||
finalize_config "$TMP_CONFIG"
|
||||
write_state "success" "subscription_fragment_merged" "subscription_fragment"
|
||||
cleanup_tmp_files
|
||||
}
|
||||
|
||||
trap cleanup_tmp_files EXIT
|
||||
main "$@"
|
||||
@ -10,14 +10,14 @@ SERVICE_GROUP="${CLASH_SERVICE_GROUP:-root}"
|
||||
|
||||
RUNTIME_DIR="$PROJECT_DIR/runtime"
|
||||
LOG_DIR="$PROJECT_DIR/logs"
|
||||
CONF_DIR="$PROJECT_DIR/conf"
|
||||
CONFIG_DIR="$PROJECT_DIR/config"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "[ERROR] root required to install systemd unit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -d -m 0755 "$RUNTIME_DIR" "$LOG_DIR" "$CONF_DIR"
|
||||
install -d -m 0755 "$RUNTIME_DIR" "$LOG_DIR" "$CONFIG_DIR" "$CONFIG_DIR/mixin.d"
|
||||
|
||||
cat >"$UNIT_PATH" <<EOF
|
||||
[Unit]
|
||||
@ -35,8 +35,11 @@ Group=${SERVICE_GROUP}
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
Environment=HOME=/root
|
||||
|
||||
ExecStart=/bin/bash ${PROJECT_DIR}/scripts/run_clash.sh --foreground
|
||||
ExecStop=/bin/bash ${PROJECT_DIR}/clashctl --from-systemd stop
|
||||
ExecStart=${PROJECT_DIR}/clashctl start
|
||||
ExecStop=${PROJECT_DIR}/clashctl --from-systemd stop
|
||||
ExecReload=${PROJECT_DIR}/clashctl restart
|
||||
|
||||
PIDFile=${PROJECT_DIR}/runtime/clash.pid
|
||||
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
@ -62,4 +65,5 @@ echo "[OK] systemd unit installed: ${UNIT_PATH}"
|
||||
echo "start : systemctl start ${SERVICE_NAME}.service"
|
||||
echo "stop : systemctl stop ${SERVICE_NAME}.service"
|
||||
echo "restart : systemctl restart ${SERVICE_NAME}.service"
|
||||
echo "reload : systemctl reload ${SERVICE_NAME}.service"
|
||||
echo "status : systemctl status ${SERVICE_NAME}.service -l --no-pager"
|
||||
@ -1,20 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LOG_FILE="$PROJECT_DIR/logs/clash.log"
|
||||
SERVICE_NAME="clash-for-linux.service"
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
if [ "${1:-}" = "-f" ]; then
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
journalctl -u "$SERVICE_NAME" -f
|
||||
else
|
||||
tail -f "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
journalctl -u "$SERVICE_NAME" -n 50 --no-pager
|
||||
else
|
||||
tail -n 50 "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
exec "$PROJECT_DIR/clashctl" logs "$@"
|
||||
@ -1,36 +1,49 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PORT_CHECK_WARNED=${PORT_CHECK_WARNED:-0}
|
||||
|
||||
# =========================
|
||||
# 判断端口是否被占用(更稳)
|
||||
# =========================
|
||||
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
|
||||
ss -lnt 2>/dev/null | awk '{print $4}' | grep -E "[:.]${port}$" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v netstat >/dev/null 2>&1; then
|
||||
netstat -lnt | awk '{print $4}' | grep -E "(:|\.)${port}$" >/dev/null 2>&1
|
||||
netstat -lnt 2>/dev/null | awk '{print $4}' | grep -E "[:.]${port}$" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
lsof -iTCP -sTCP:LISTEN -P -n | awk '{print $9}' | grep -E "(:|\.)${port}$" >/dev/null 2>&1
|
||||
lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | awk '{print $9}' | grep -E "[:.]${port}$" >/dev/null 2>&1
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [ "$PORT_CHECK_WARNED" -eq 0 ]; then
|
||||
echo -e "\033[33m[WARN] 未找到端口检测工具,端口冲突检测可能不准确\033[0m" >&2
|
||||
echo "[WARN] no port check tool found (ss/netstat/lsof)" >&2
|
||||
PORT_CHECK_WARNED=1
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 找可用端口(优化版)
|
||||
# =========================
|
||||
find_available_port() {
|
||||
local start_port=${1:-20000}
|
||||
local end_port=${2:-65000}
|
||||
local start="${1:-20000}"
|
||||
local end="${2:-65000}"
|
||||
local port
|
||||
|
||||
# 优先随机尝试
|
||||
if command -v shuf >/dev/null 2>&1; then
|
||||
for _ in {1..50}; do
|
||||
port=$(shuf -i "${start_port}-${end_port}" -n 1)
|
||||
for _ in {1..30}; do
|
||||
port=$(shuf -i "${start}-${end}" -n 1)
|
||||
if ! is_port_in_use "$port"; then
|
||||
echo "$port"
|
||||
return 0
|
||||
@ -38,7 +51,8 @@ find_available_port() {
|
||||
done
|
||||
fi
|
||||
|
||||
for port in $(seq "$start_port" "$end_port"); do
|
||||
# fallback 顺序扫描(限制范围避免慢)
|
||||
for port in $(seq "$start" "$((start + 2000))"); do
|
||||
if ! is_port_in_use "$port"; then
|
||||
echo "$port"
|
||||
return 0
|
||||
@ -48,43 +62,56 @@ find_available_port() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 解析端口值(核心函数)
|
||||
# =========================
|
||||
resolve_port_value() {
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
local resolved
|
||||
|
||||
# auto / 空
|
||||
if [ -z "$value" ] || [ "$value" = "auto" ]; then
|
||||
resolved=$(find_available_port)
|
||||
if [ -z "$resolved" ]; then
|
||||
resolved=$(find_available_port) || {
|
||||
echo "[ERROR] ${name} failed to allocate port" >&2
|
||||
return 1
|
||||
fi
|
||||
echo -e "\033[33m[WARN] ${name} 端口已自动分配为 ${resolved}\033[0m" >&2
|
||||
}
|
||||
echo "[WARN] ${name} auto assigned: ${resolved}" >&2
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
||||
if is_port_in_use "$value"; then
|
||||
resolved=$(find_available_port)
|
||||
if [ -n "$resolved" ]; then
|
||||
echo -e "\033[33m[WARN] ${name} 端口 ${value} 已被占用,已自动切换为 ${resolved}\033[0m" >&2
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
# 非数字
|
||||
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
|
||||
echo "[ERROR] invalid port: $value" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 被占用 → 自动替换
|
||||
if is_port_in_use "$value"; then
|
||||
resolved=$(find_available_port)
|
||||
if [ -n "$resolved" ]; then
|
||||
echo "[WARN] ${name} port ${value} in use, switched to ${resolved}" >&2
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 解析 host:port
|
||||
# =========================
|
||||
resolve_host_port() {
|
||||
local name="$1"
|
||||
local raw="$2"
|
||||
local default_host="$3"
|
||||
|
||||
local host
|
||||
local port
|
||||
|
||||
if [ "$raw" = "auto" ] || [ -z "$raw" ]; then
|
||||
if [ -z "$raw" ] || [ "$raw" = "auto" ]; then
|
||||
host="$default_host"
|
||||
port="auto"
|
||||
else
|
||||
@ -97,6 +124,10 @@ resolve_host_port() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# host 兜底
|
||||
[ -z "$host" ] && host="$default_host"
|
||||
|
||||
port=$(resolve_port_value "$name" "$port") || return 1
|
||||
|
||||
echo "${host}:${port}"
|
||||
}
|
||||
}
|
||||
@ -1,119 +1,214 @@
|
||||
#!/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}"
|
||||
;;
|
||||
x86_64|amd64) echo "linux-amd64" ;;
|
||||
aarch64|arm64) echo "linux-arm64" ;;
|
||||
armv7*|armv7l) echo "linux-armv7" ;;
|
||||
*) echo "linux-${raw_arch}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_latest_mihomo_version() {
|
||||
local url="https://api.github.com/repos/MetaCubeX/mihomo/releases/latest"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$url" \
|
||||
| grep '"tag_name"' \
|
||||
| sed -E 's/.*"([^"]+)".*/\1/' \
|
||||
| head -n 1
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- "$url" \
|
||||
| grep '"tag_name"' \
|
||||
| sed -E 's/.*"([^"]+)".*/\1/' \
|
||||
| head -n 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_clash_bin() {
|
||||
local server_dir="$1"
|
||||
local detected_arch="$2"
|
||||
|
||||
local resolved_arch
|
||||
local version
|
||||
local download_url
|
||||
|
||||
local download_target
|
||||
local archive_file
|
||||
local tmp_bin
|
||||
|
||||
resolved_arch="$(resolve_clash_arch "$detected_arch")"
|
||||
|
||||
resolved_arch=$(resolve_clash_arch "$detected_arch")
|
||||
if [ -z "$resolved_arch" ]; then
|
||||
echo -e "\033[33m[WARN] 无法识别 CPU 架构,跳过 Clash 内核自动下载\033[0m"
|
||||
echo "[WARN] 无法识别 CPU 架构" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${CLASH_AUTO_DOWNLOAD:-auto}" = "false" ]; then
|
||||
version="${MIHOMO_VERSION:-}"
|
||||
if [ -z "$version" ]; then
|
||||
version="$(get_latest_mihomo_version || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "[ERROR] 无法获取 Mihomo 版本" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local _default_url="https://github.com/Dreamacro/clash/releases/latest/download/clash-{arch}.gz"
|
||||
download_url="${CLASH_DOWNLOAD_URL_TEMPLATE:-$_default_url}"
|
||||
if [ -z "$download_url" ]; then
|
||||
echo -e "\033[33m[WARN] 未设置 CLASH_DOWNLOAD_URL_TEMPLATE,跳过 Clash 内核自动下载\033[0m"
|
||||
if [ -z "${CLASH_DOWNLOAD_URL_TEMPLATE:-}" ]; then
|
||||
echo "[ERROR] CLASH_DOWNLOAD_URL_TEMPLATE 未设置" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
download_url="${download_url//\{arch\}/${resolved_arch}}"
|
||||
download_url="${CLASH_DOWNLOAD_URL_TEMPLATE//\{arch\}/${resolved_arch}}"
|
||||
download_url="${download_url//\{version\}/${version}}"
|
||||
|
||||
download_target="${server_dir}/bin/clash-${resolved_arch}"
|
||||
archive_file="${server_dir}/temp/clash-${resolved_arch}.download"
|
||||
archive_file="${server_dir}/runtime/.clash_download.tmp"
|
||||
tmp_bin="${server_dir}/runtime/.clash_bin.tmp"
|
||||
|
||||
mkdir -p "${server_dir}/bin" "${server_dir}/temp"
|
||||
mkdir -p "${server_dir}/bin" "${server_dir}/runtime"
|
||||
|
||||
rm -f "$archive_file" "$tmp_bin"
|
||||
|
||||
echo "[INFO] downloading: $download_url"
|
||||
|
||||
# =========================
|
||||
# 下载
|
||||
# =========================
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -L -sS -o "${archive_file}" "${download_url}"
|
||||
if ! curl -fL -sS -o "$archive_file" "$download_url"; then
|
||||
echo "[ERROR] 下载失败: $download_url" >&2
|
||||
return 1
|
||||
fi
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O "${archive_file}" "${download_url}"
|
||||
if ! wget -q -O "$archive_file" "$download_url"; then
|
||||
echo "[ERROR] 下载失败: $download_url" >&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "\033[33m[WARN] 未找到 curl 或 wget,无法自动下载 Clash 内核\033[0m"
|
||||
echo "[ERROR] 未找到 curl 或 wget" >&2
|
||||
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
|
||||
# =========================
|
||||
# 基础校验(防 404 / HTML)
|
||||
# =========================
|
||||
if [ ! -s "$archive_file" ]; then
|
||||
echo "[ERROR] 下载文件为空" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "\033[33m[WARN] Clash 内核自动下载失败\033[0m"
|
||||
return 1
|
||||
if head -c 200 "$archive_file" | grep -qiE "not found|html"; then
|
||||
echo "[ERROR] 下载内容疑似错误页面(404/HTML)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 解压 / 直写
|
||||
# =========================
|
||||
if gzip -t "$archive_file" >/dev/null 2>&1; then
|
||||
if ! gzip -dc "$archive_file" > "$tmp_bin"; then
|
||||
echo "[ERROR] gzip 解压失败" >&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
cp "$archive_file" "$tmp_bin"
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# ELF 校验(关键)
|
||||
# =========================
|
||||
if ! file "$tmp_bin" | grep -q "ELF"; then
|
||||
echo "[ERROR] 非有效 ELF 二进制" >&2
|
||||
echo "[DEBUG] file result: $(file "$tmp_bin")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
chmod +x "$tmp_bin"
|
||||
mv "$tmp_bin" "$download_target"
|
||||
|
||||
rm -f "$archive_file"
|
||||
|
||||
echo "[OK] downloaded: $download_target"
|
||||
echo "$download_target"
|
||||
}
|
||||
|
||||
resolve_clash_bin() {
|
||||
local server_dir="$1"
|
||||
local detected_arch="$2"
|
||||
|
||||
local resolved_arch
|
||||
local candidates=()
|
||||
local candidate
|
||||
local downloaded_bin
|
||||
local mode
|
||||
|
||||
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"
|
||||
echo "[ERROR] CLASH_BIN 不可执行: $CLASH_BIN" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
resolved_arch=$(resolve_clash_arch "$detected_arch")
|
||||
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
|
||||
mode="${CLASH_AUTO_DOWNLOAD:-auto}"
|
||||
|
||||
if downloaded_bin=$(download_clash_bin "$server_dir" "$detected_arch"); then
|
||||
echo "$downloaded_bin"
|
||||
return 0
|
||||
fi
|
||||
case "$mode" in
|
||||
false)
|
||||
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 指定自定义路径。"
|
||||
auto)
|
||||
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
|
||||
;;
|
||||
|
||||
true)
|
||||
if downloaded_bin="$(download_clash_bin "$server_dir" "$detected_arch")"; then
|
||||
echo "$downloaded_bin"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [ -x "$candidate" ]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "[ERROR] CLASH_AUTO_DOWNLOAD 非法值: $mode" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[ERROR] 未找到可用 Mihomo 内核" >&2
|
||||
echo "请放入: ${server_dir}/bin/" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,9 @@ mkdir -p "$RUNTIME_DIR" "$LOG_DIR"
|
||||
FOREGROUND=false
|
||||
DAEMON=false
|
||||
|
||||
# 解析参数
|
||||
# =========================
|
||||
# 参数解析
|
||||
# =========================
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--foreground) FOREGROUND=true ;;
|
||||
@ -29,6 +31,14 @@ if [ "$FOREGROUND" = true ] && [ "$DAEMON" = true ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ "$FOREGROUND" = false ] && [ "$DAEMON" = false ]; then
|
||||
echo "[ERROR] Must specify --foreground or --daemon" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 基础校验
|
||||
# =========================
|
||||
if [ ! -s "$CONFIG_FILE" ]; then
|
||||
echo "[ERROR] runtime config not found: $CONFIG_FILE" >&2
|
||||
exit 2
|
||||
@ -39,6 +49,9 @@ if grep -q '\${' "$CONFIG_FILE"; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# =========================
|
||||
# 加载依赖
|
||||
# =========================
|
||||
# shellcheck disable=SC1091
|
||||
source "$PROJECT_DIR/scripts/get_cpu_arch.sh"
|
||||
# shellcheck disable=SC1091
|
||||
@ -46,40 +59,49 @@ source "$PROJECT_DIR/scripts/resolve_clash.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "$PROJECT_DIR/scripts/service_lib.sh"
|
||||
|
||||
# =========================
|
||||
# 获取二进制
|
||||
# =========================
|
||||
CLASH_BIN="$(resolve_clash_bin "$PROJECT_DIR" "${CpuArch:-}")"
|
||||
|
||||
if [ ! -x "$CLASH_BIN" ]; then
|
||||
echo "[ERROR] clash binary not found or not executable: $CLASH_BIN" >&2
|
||||
echo "[ERROR] clash binary not executable: $CLASH_BIN" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
test_config() {
|
||||
local bin="$1"
|
||||
local config="$2"
|
||||
local runtime_dir="$3"
|
||||
"$bin" -d "$runtime_dir" -t -f "$config" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
if ! test_config "$CLASH_BIN" "$CONFIG_FILE" "$RUNTIME_DIR"; then
|
||||
echo "[ERROR] config test failed: $CONFIG_FILE" >&2
|
||||
# =========================
|
||||
# config 测试(唯一一次)
|
||||
# =========================
|
||||
if ! "$CLASH_BIN" -t -f "$CONFIG_FILE" -d "$RUNTIME_DIR" >/dev/null 2>&1; then
|
||||
echo "[ERROR] clash config test failed: $CONFIG_FILE" >&2
|
||||
write_run_state "failed" "config-test"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# systemd 模式
|
||||
# =========================
|
||||
# 前台模式(systemd)
|
||||
# =========================
|
||||
if [ "$FOREGROUND" = true ]; then
|
||||
write_run_state "running" "systemd"
|
||||
exec "$CLASH_BIN" -f "$CONFIG_FILE" -d "$RUNTIME_DIR"
|
||||
fi
|
||||
|
||||
# script / daemon 模式
|
||||
if [ "$DAEMON" = true ]; then
|
||||
nohup "$CLASH_BIN" -f "$CONFIG_FILE" -d "$RUNTIME_DIR" >>"$LOG_DIR/clash.log" 2>&1 &
|
||||
pid=$!
|
||||
echo "$pid" > "$PID_FILE"
|
||||
write_run_state "running" "script" "$pid"
|
||||
echo "[OK] Clash started in script mode, pid=$pid"
|
||||
# =========================
|
||||
# 后台模式(script)
|
||||
# =========================
|
||||
cleanup_dead_pid
|
||||
|
||||
if is_script_running; then
|
||||
pid="$(read_pid 2>/dev/null || true)"
|
||||
echo "[INFO] clash already running, pid=${pid:-unknown}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[ERROR] Must specify --foreground or --daemon" >&2
|
||||
exit 2
|
||||
nohup "$CLASH_BIN" -f "$CONFIG_FILE" -d "$RUNTIME_DIR" >>"$LOG_DIR/clash.log" 2>&1 &
|
||||
|
||||
pid=$!
|
||||
echo "$pid" > "$PID_FILE"
|
||||
|
||||
write_run_state "running" "script" "$pid"
|
||||
|
||||
echo "[OK] Clash started in script mode, pid=$pid"
|
||||
@ -9,6 +9,9 @@ SERVICE_NAME="clash-for-linux.service"
|
||||
|
||||
mkdir -p "$RUNTIME_DIR"
|
||||
|
||||
# =========================
|
||||
# 基础能力
|
||||
# =========================
|
||||
has_systemd() {
|
||||
command -v systemctl >/dev/null 2>&1
|
||||
}
|
||||
@ -19,17 +22,39 @@ service_unit_exists() {
|
||||
}
|
||||
|
||||
read_pid() {
|
||||
[ -f "$PID_FILE" ] || return 1
|
||||
cat "$PID_FILE"
|
||||
[ -s "$PID_FILE" ] || return 1
|
||||
tr -d '[:space:]' < "$PID_FILE"
|
||||
}
|
||||
|
||||
is_pid_running() {
|
||||
local pid="$1"
|
||||
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
|
||||
}
|
||||
|
||||
is_script_running() {
|
||||
local pid
|
||||
pid="$(read_pid 2>/dev/null || true)"
|
||||
[ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null
|
||||
is_pid_running "$pid"
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 清理僵尸 PID(关键)
|
||||
# =========================
|
||||
cleanup_dead_pid() {
|
||||
local pid
|
||||
pid="$(read_pid 2>/dev/null || true)"
|
||||
|
||||
if [ -n "${pid:-}" ] && ! is_pid_running "$pid"; then
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================
|
||||
# 模式检测(统一)
|
||||
# =========================
|
||||
detect_mode() {
|
||||
cleanup_dead_pid
|
||||
|
||||
if service_unit_exists && systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
echo "systemd"
|
||||
elif is_script_running; then
|
||||
@ -41,59 +66,65 @@ detect_mode() {
|
||||
fi
|
||||
}
|
||||
|
||||
write_run_state() {
|
||||
local status="$1"
|
||||
local mode="${2:-unknown}"
|
||||
local pid="${3:-}"
|
||||
# =========================
|
||||
# state 写入(唯一实现)
|
||||
# =========================
|
||||
write_state_kv() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
|
||||
mkdir -p "$RUNTIME_DIR"
|
||||
touch "$STATE_FILE"
|
||||
|
||||
if grep -q '^LAST_RUN_STATUS=' "$STATE_FILE" 2>/dev/null; then
|
||||
sed -i -E "s/^LAST_RUN_STATUS=.*/LAST_RUN_STATUS=${status}/" "$STATE_FILE"
|
||||
if grep -q "^${key}=" "$STATE_FILE" 2>/dev/null; then
|
||||
sed -i "s|^${key}=.*|${key}=${value}|" "$STATE_FILE"
|
||||
else
|
||||
echo "LAST_RUN_STATUS=${status}" >> "$STATE_FILE"
|
||||
fi
|
||||
|
||||
if grep -q '^LAST_RUN_MODE=' "$STATE_FILE" 2>/dev/null; then
|
||||
sed -i -E "s/^LAST_RUN_MODE=.*/LAST_RUN_MODE=${mode}/" "$STATE_FILE"
|
||||
else
|
||||
echo "LAST_RUN_MODE=${mode}" >> "$STATE_FILE"
|
||||
fi
|
||||
|
||||
if grep -q '^LAST_RUN_AT=' "$STATE_FILE" 2>/dev/null; then
|
||||
sed -i -E "s/^LAST_RUN_AT=.*/LAST_RUN_AT=$(date -Iseconds)/" "$STATE_FILE"
|
||||
else
|
||||
echo "LAST_RUN_AT=$(date -Iseconds)" >> "$STATE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$pid" ]; then
|
||||
if grep -q '^LAST_RUN_PID=' "$STATE_FILE" 2>/dev/null; then
|
||||
sed -i -E "s/^LAST_RUN_PID=.*/LAST_RUN_PID=${pid}/" "$STATE_FILE"
|
||||
else
|
||||
echo "LAST_RUN_PID=${pid}" >> "$STATE_FILE"
|
||||
fi
|
||||
echo "${key}=${value}" >> "$STATE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
write_run_state() {
|
||||
local status="${1:-unknown}"
|
||||
local mode="${2:-unknown}"
|
||||
local pid="${3:-}"
|
||||
|
||||
write_state_kv "LAST_RUN_STATUS" "$status"
|
||||
write_state_kv "LAST_RUN_MODE" "$mode"
|
||||
write_state_kv "LAST_RUN_AT" "$(date -Iseconds)"
|
||||
|
||||
if [ -n "$pid" ]; then
|
||||
write_state_kv "LAST_RUN_PID" "$pid"
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================
|
||||
# systemd 模式
|
||||
# =========================
|
||||
start_via_systemd() {
|
||||
systemctl start "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
stop_via_systemd() {
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
systemctl stop "$SERVICE_NAME" || true
|
||||
cleanup_dead_pid
|
||||
write_run_state "stopped" "systemd"
|
||||
rm -f "$PID_FILE"
|
||||
}
|
||||
|
||||
restart_via_systemd() {
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
# =========================
|
||||
# script 模式
|
||||
# =========================
|
||||
start_via_script() {
|
||||
cleanup_dead_pid
|
||||
|
||||
if is_script_running; then
|
||||
echo "[INFO] clash already running (script mode)"
|
||||
echo "[INFO] clash already running (script)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
"$PROJECT_DIR/scripts/run_clash.sh" --daemon
|
||||
}
|
||||
|
||||
@ -101,11 +132,19 @@ stop_via_script() {
|
||||
local pid
|
||||
pid="$(read_pid 2>/dev/null || true)"
|
||||
|
||||
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
if [ -n "${pid:-}" ] && is_pid_running "$pid"; then
|
||||
echo "[INFO] stopping clash pid=$pid"
|
||||
kill "$pid"
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
|
||||
kill "$pid" 2>/dev/null || true
|
||||
|
||||
for _ in 1 2 3 4 5; do
|
||||
if ! is_pid_running "$pid"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if is_pid_running "$pid"; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -1,28 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$PROJECT_DIR/scripts/service_lib.sh"
|
||||
|
||||
mode="$(detect_mode)"
|
||||
|
||||
echo "=== Clash Status ==="
|
||||
echo "Project : $PROJECT_DIR"
|
||||
echo "Mode : $mode"
|
||||
|
||||
case "$mode" in
|
||||
systemd)
|
||||
echo "Running : yes (systemd)"
|
||||
;;
|
||||
script)
|
||||
echo "Running : yes (script)"
|
||||
;;
|
||||
systemd-installed)
|
||||
echo "Running : no (installed but not started)"
|
||||
;;
|
||||
*)
|
||||
echo "Running : no"
|
||||
;;
|
||||
esac
|
||||
exec "$PROJECT_DIR/clashctl" status "$@"
|
||||
@ -1,23 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$PROJECT_DIR/scripts/service_lib.sh"
|
||||
|
||||
mode="$(detect_mode)"
|
||||
|
||||
case "$mode" in
|
||||
systemd)
|
||||
stop_via_systemd
|
||||
echo "[OK] stopped via systemd"
|
||||
;;
|
||||
script)
|
||||
stop_via_script
|
||||
echo "[OK] stopped via script"
|
||||
;;
|
||||
*)
|
||||
echo "[WARN] nothing is running"
|
||||
;;
|
||||
esac
|
||||
exec "$PROJECT_DIR/clashctl" stop "$@"
|
||||
Reference in New Issue
Block a user