Files
clash-for-linux/clashctl
2026-03-20 15:52:20 +08:00

322 lines
6.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
Server_Dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Service_Name="clash-for-linux.service"
PROFILED_FILE="/etc/profile.d/clash-for-linux.sh"
ENV_FILE="$Server_Dir/.env"
CONF_FILE="$Server_Dir/conf/config.yaml"
TEMP_CONF_FILE="$Server_Dir/temp/config.yaml"
log() { printf "%b\n" "$*"; }
info() { log "\033[36m[INFO]\033[0m $*"; }
ok() { log "\033[32m[OK]\033[0m $*"; }
warn() { log "\033[33m[WARN]\033[0m $*"; }
err() { log "\033[31m[ERROR]\033[0m $*"; }
usage() {
cat <<'EOF'
Usage:
clashctl COMMAND [OPTIONS]
Commands:
on 开启代理
off 关闭代理
status 内核状况
proxy 系统代理
ui Web 面板
secret Web 密钥
sub 订阅管理
upgrade 升级内核
tun Tun 模式
mixin Mixin 配置
Global Options:
-h, --help 显示帮助信息
Examples:
clashctl on
clashctl off
clashctl status
clashctl proxy status
clashctl ui
clashctl secret
clashctl sub show
clashctl sub update
clashctl tun status
clashctl mixin status
💡 clashon 同 clashctl onTab 补全更方便!
EOF
}
require_profiled() {
if [ ! -f "$PROFILED_FILE" ]; then
err "未安装 Shell 代理快捷命令:$PROFILED_FILE"
exit 1
fi
}
has_systemd() {
command -v systemctl >/dev/null 2>&1
}
service_exists() {
has_systemd || return 1
local load_state
load_state="$(systemctl show "$Service_Name" --property=LoadState --value 2>/dev/null || true)"
[ -n "$load_state" ] && [ "$load_state" != "not-found" ]
}
service_is_active() {
service_exists || return 1
systemctl is-active --quiet "$Service_Name"
}
read_config_value() {
local key="$1"
local f
for f in "$TEMP_CONF_FILE" "$CONF_FILE"; do
if [ -f "$f" ]; then
sed -nE "s/^[[:space:]]*${key}:[[:space:]]*//p" "$f" \
| head -n 1 \
| tr -d '\r' \
| sed -E 's/^"(.*)"$/\1/; s/^'\''(.*)'\''$/\1/'
return 0
fi
done
return 1
}
cmd_on() {
require_profiled
# shellcheck disable=SC1090
source "$PROFILED_FILE"
proxy_on
}
cmd_off() {
require_profiled
# shellcheck disable=SC1090
source "$PROFILED_FILE"
proxy_off
}
cmd_status() {
local proc_info=""
proc_info="$(ps -eo pid,ppid,cmd | grep -E '[c]lash-linux|[m]ihomo' || true)"
echo "=== Clash Status ==="
if service_exists; then
local active_state enabled_state
active_state="$(systemctl is-active "$Service_Name" 2>/dev/null || true)"
enabled_state="$(systemctl is-enabled "$Service_Name" 2>/dev/null || true)"
echo "Service: installed"
echo "Active : ${active_state:-unknown}"
echo "Enabled: ${enabled_state:-unknown}"
if [ -n "$proc_info" ]; then
echo
echo "Process:"
echo "$proc_info"
fi
return 0
fi
if [ -n "$proc_info" ]; then
warn "未检测到 systemd 服务,但发现 Clash 进程正在运行"
echo
echo "Process:"
echo "$proc_info"
return 0
fi
warn "未检测到 systemd 服务,也未发现 Clash 进程"
return 1
}
cmd_proxy() {
require_profiled
# shellcheck disable=SC1090
source "$PROFILED_FILE"
local sub="${1:-status}"
case "$sub" in
on) proxy_on ;;
off) proxy_off ;;
status) proxy_status ;;
*)
err "未知 proxy 子命令: $sub"
echo "用法: clashctl proxy [on|off|status]"
exit 1
;;
esac
}
cmd_ui() {
local controller host port
controller="$(read_config_value "external-controller" || true)"
[ -n "${controller:-}" ] || controller="127.0.0.1:9090"
host="${controller%:*}"
port="${controller##*:}"
case "$host" in
0.0.0.0|::|localhost)
host="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -n "$host" ] || host="127.0.0.1"
;;
esac
printf 'http://%s:%s/ui\n' "$host" "$port"
}
cmd_secret() {
local secret
secret="$(read_config_value "secret" || true)"
if [ -n "${secret:-}" ]; then
echo "$secret"
else
err "未读取到 secret"
exit 1
fi
}
cmd_sub() {
local subcmd="${1:-show}"
case "$subcmd" in
show)
if [ -f "$ENV_FILE" ]; then
grep -E '^[[:space:]]*(export[[:space:]]+)?CLASH_URL=' "$ENV_FILE" || true
else
err "未找到 .env"
exit 1
fi
;;
update)
if service_exists; then
info "重启服务以刷新订阅..."
systemctl restart "$Service_Name"
ok "订阅刷新完成"
elif [ -f "$Server_Dir/restart.sh" ]; then
bash "$Server_Dir/restart.sh"
ok "订阅刷新完成"
else
err "未找到 restart.sh"
exit 1
fi
;;
*)
err "未知 sub 子命令: $subcmd"
echo "用法: clashctl sub [show|update]"
exit 1
;;
esac
}
cmd_upgrade() {
if [ -f "$Server_Dir/update.sh" ]; then
bash "$Server_Dir/update.sh"
else
err "未找到 update.sh"
exit 1
fi
}
write_env_bool() {
local key="$1"
local value="$2"
if [ ! -f "$ENV_FILE" ]; then
err "未找到 .env"
exit 1
fi
if grep -qE "^[[:space:]]*(export[[:space:]]+)?${key}=" "$ENV_FILE"; then
sed -i -E "s|^[[:space:]]*(export[[:space:]]+)?${key}=.*$|export ${key}=\"${value}\"|g" "$ENV_FILE"
else
echo "export ${key}=\"${value}\"" >> "$ENV_FILE"
fi
}
cmd_tun() {
local subcmd="${1:-status}"
case "$subcmd" in
status)
if [ -f "$ENV_FILE" ]; then
grep -E '^[[:space:]]*(export[[:space:]]+)?CLASH_TUN=' "$ENV_FILE" || echo 'CLASH_TUN=未配置'
else
err "未找到 .env"
exit 1
fi
;;
on)
write_env_bool "CLASH_TUN" "true"
ok "已写入 CLASH_TUN=true"
;;
off)
write_env_bool "CLASH_TUN" "false"
ok "已写入 CLASH_TUN=false"
;;
*)
err "未知 tun 子命令: $subcmd"
echo "用法: clashctl tun [on|off|status]"
exit 1
;;
esac
}
cmd_mixin() {
local subcmd="${1:-status}"
local mixin_file="$Server_Dir/conf/mixin.yaml"
case "$subcmd" in
status)
if [ -f "$mixin_file" ]; then
ok "Mixin 已存在: $mixin_file"
else
warn "Mixin 不存在: $mixin_file"
fi
;;
edit)
${EDITOR:-vi} "$mixin_file"
;;
*)
err "未知 mixin 子命令: $subcmd"
echo "用法: clashctl mixin [status|edit]"
exit 1
;;
esac
}
main() {
local cmd="${1:-}"
shift || true
case "$cmd" in
on) cmd_on "$@" ;;
off) cmd_off "$@" ;;
status) cmd_status "$@" ;;
proxy) cmd_proxy "$@" ;;
ui) cmd_ui "$@" ;;
secret) cmd_secret "$@" ;;
sub) cmd_sub "$@" ;;
upgrade) cmd_upgrade "$@" ;;
tun) cmd_tun "$@" ;;
mixin) cmd_mixin "$@" ;;
-h|--help|"") usage ;;
*)
err "未知命令: $cmd"
echo
usage
exit 1
;;
esac
}
main "$@"