Files
3x-ui-agent/install-agent.sh
Muse 58cdfe73b0 feat(agent): P12 改名 xui-agent——独立二进制文件+改名覆盖+存量迁移清理
install-agent.sh: AGENT_BIN 改 /usr/local/x-ui/xui-agent,下载走 .new 改名
覆盖(agent 自升级不撞 Text file busy),全程不写主控 x-ui 文件;新增
stop_legacy_agent 只精确匹配旧形态 'x-ui agent' 进程(主控裸 x-ui 无参数、
天然不误杀);生成的 systemd/OpenRC 服务改指 xui-agent。仓库 systemd/
x-ui-agent.service 与 openrc/x-ui-agent( procd ) 同步改指;README/docs 的
agent 手动部署段落位改 xui-agent。Go 代码无硬引用、零改动。
fake-root 模拟(systemd/OpenRC/存量重跑)23/23 PASS,见 P12-REPORT.md。
2026-10-22 09:46:46 +00:00

268 lines
10 KiB
Bash
Executable File
Raw Permalink 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.
#!/bin/bash
# 3x-ui-agent 节点一键安装脚本
# 用法: bash install-agent.sh --master http://MASTER_IP:2053 --token YOUR_AGENT_TOKEN [--name my-node]
# 要求: Debian/Ubuntu/Alpine/CentOS (amd64/arm64), root 运行
#
# 测试用环境变量(一般无需设置):
# XUI_ROOT 安装路径前缀(假根目录验证用),默认空 = 真实路径
# XUI_INIT_SYSTEM 强制 init 系统: systemd | openrc | none,默认 auto 探测
# XUI_URL agent 二进制下载地址覆盖
#
# 二进制命名(P12 改名):agent 独立装为 /usr/local/x-ui/xui-agent,不再与主控共用
# x-ui 文件——①ps 里进程名即 xui-agent,与主控一眼可辨;②同机部署时 agent 的
# 安装/升级永不写主控文件,不再撞 Text file busy;③进程清理只按 "xui-agent agent"
# 精确匹配 agent 自身。存量旧 agent 的进程形态是 "x-ui agent"(带 agent 子命令),
# 与主控的裸 "x-ui"(无参数)天然可区分,迁移清理只杀前者、绝不误杀主控。
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
[[ $EUID -ne 0 ]] && error "请用 root 运行"
MASTER=""; TOKEN=""; NAME="agent-node"
while [[ $# -gt 0 ]]; do
case $1 in
--master) MASTER="$2"; shift 2;;
--token) TOKEN="$2"; shift 2;;
--name) NAME="$2"; shift 2;;
*) error "未知参数: $1";;
esac
done
[[ -z "$MASTER" ]] && error "缺少 --master 参数"
[[ -z "$TOKEN" ]] && error "缺少 --token 参数 (从主控面板 API 获取)"
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64) BIN_ARCH="amd64";;
aarch64|arm64) BIN_ARCH="arm64";;
*) error "不支持的架构: $ARCH (仅支持 amd64/arm64)";;
esac
# 二进制下载地址(本仓 Gitea Releases,tag 沿用 v3.7.0-agent.1)
XUI_URL="${XUI_URL:-https://git.881218.xyz/rogue/3x-ui-agent/releases/download/v3.7.0-agent.1/x-ui-linux-${BIN_ARCH}}"
XRAY_VER="26.6.27"
ROOT="${XUI_ROOT:-}"
XUI_HOME="${ROOT}/usr/local/x-ui"
CONF_DIR="${ROOT}/etc/x-ui"
AGENT_BIN="${XUI_HOME}/xui-agent"
info "创建目录..."
mkdir -p "${XUI_HOME}/bin" "${CONF_DIR}"
info "下载 agent 二进制..."
# 先下到 .new 再改名覆盖:改名是原子替换,正在运行的旧 agent 进程不受影响,
# 也不会因目标文件正被执行而报 Text file busy;全程只写 xui-agent,不碰主控的 x-ui。
curl -fL -o "${AGENT_BIN}.new" "$XUI_URL" || error "agent 下载失败"
chmod +x "${AGENT_BIN}.new"
mv -f "${AGENT_BIN}.new" "${AGENT_BIN}"
info "下载 xray v${XRAY_VER}..."
cd "${XUI_HOME}/bin"
case "$BIN_ARCH" in
amd64) XRAY_ZIP="Xray-linux-64.zip";;
arm64) XRAY_ZIP="Xray-linux-arm64-v8a.zip";;
esac
curl -fL -o "$XRAY_ZIP" "https://github.com/XTLS/Xray-core/releases/download/v${XRAY_VER}/${XRAY_ZIP}" || error "xray 下载失败"
unzip -o -q "$XRAY_ZIP" xray && mv xray "xray-linux-${BIN_ARCH}" && chmod +x "xray-linux-${BIN_ARCH}"
rm -f "$XRAY_ZIP"
info "下载 geo 文件..."
curl -fL -o geoip.dat "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" || error "geoip 下载失败"
curl -fL -o geosite.dat "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" || error "geosite 下载失败"
cd - >/dev/null
info "写入 agent 配置..."
cat > "${CONF_DIR}/agent.json" <<EOF
{
"master_url": "$MASTER",
"token": "$TOKEN",
"node_name": "$NAME"
}
EOF
chmod 600 "${CONF_DIR}/agent.json"
# 探测主控可达性:拿假 token 打一次注册接口,在启动前把地址问题拦下来,
# 别让 agent 带着必死的配置装完再无声无息地死掉(主控上一直显示"未知")。
# 404 → 注册路由不存在,主控地址大概率少了面板基础路径
# 连不上 → 网络/域名/TLS 问题
# 其他(401等) → 路由存在(假 token 被拒正是预期),路径正确
probe_master() {
local url="${MASTER%/}/panel/api/agent/register"
info "探测主控注册接口: $url"
local code
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 -X POST "$url" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer install-probe-invalid-token' \
-d '{"token":"install-probe-invalid-token","name":"install-probe"}' 2>/dev/null) || code="000"
case "$code" in
404)
echo ""
echo -e "${RED}============================================================${NC}"
echo -e "${RED} 主控返回 404:注册接口不存在,安装中止。${NC}"
echo -e "${RED} 主控地址大概率少了面板基础路径(如 /rogue)。${NC}"
echo -e "${RED} 当前地址: ${MASTER}${NC}"
echo -e "${RED} 若面板实际地址带基础路径,请改用 --master ${MASTER%/}/rogue${NC}"
echo -e "${RED} 之类的完整地址(以面板真实基础路径为准)改对后重跑本脚本。${NC}"
echo -e "${RED}============================================================${NC}"
exit 1
;;
000)
echo ""
echo -e "${RED}============================================================${NC}"
echo -e "${RED} 连不上主控,安装中止。${NC}"
echo -e "${RED} 请检查:网络是否通、域名解析是否正常、主控地址与端口是否写对、${NC}"
echo -e "${RED} 以及主控 HTTPS 证书是否有效(agent 不接受自签证书)。${NC}"
echo -e "${RED} 目标: $url${NC}"
echo -e "${RED}============================================================${NC}"
exit 1
;;
*)
info "主控探测通过 (HTTP $code,注册路由存在)"
;;
esac
}
probe_master
# 存量迁移清理:旧版脚本把 agent 装在主控文件 x-ui 上(进程形态 "x-ui agent"),
# 重跑本脚本升级时先停掉这类旧进程,服务定义随后会被新单元覆盖。
# 安全边界:只精确匹配带 agent 子命令的旧路径进程;主控是裸 x-ui(无 agent 参数),
# 同机部署时绝不误杀。本脚本任何地方都不写、不删主控文件 x-ui 本身——agent-only
# 节点上它在迁移后成为无用残留,确认该机不跑主控后可人工删除。
stop_legacy_agent() {
pkill -f "${XUI_HOME}/x-ui agent" 2>/dev/null || true
}
detect_init_system() {
case "${XUI_INIT_SYSTEM:-auto}" in
systemd|openrc|none) echo "${XUI_INIT_SYSTEM}"; return;;
esac
if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then
echo systemd; return
fi
if command -v rc-update >/dev/null 2>&1 && command -v rc-service >/dev/null 2>&1; then
echo openrc; return
fi
echo none
}
# 以 systemd 服务方式安装并启动(开机自启)
start_via_systemd() {
local unit_dir="${ROOT}/etc/systemd/system"
mkdir -p "$unit_dir"
cat > "${unit_dir}/x-ui-agent.service" <<'EOF'
[Unit]
Description=3x-ui agent
After=network.target
[Service]
ExecStart=/usr/local/x-ui/xui-agent agent
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
info "已生成 systemd 服务文件: ${unit_dir}/x-ui-agent.service"
if [ -n "$ROOT" ]; then
info "(XUI_ROOT 模式:跳过 systemctl 实际操作)"
return 0
fi
systemctl stop x-ui-agent 2>/dev/null || true
stop_legacy_agent
pkill -f "${AGENT_BIN} agent" 2>/dev/null || true; sleep 1
systemctl daemon-reload
systemctl enable x-ui-agent
systemctl restart x-ui-agent
sleep 2
if systemctl is-active --quiet x-ui-agent; then
info "agent 已由 systemd 托管启动(开机自启)"
echo ""
echo " 主控: $MASTER"
echo " 节点名: $NAME"
echo " 日志: journalctl -u x-ui-agent -f"
echo " agent 会在 30 秒内从主控拉取配置"
else
error "agent 服务启动失败,查看: systemctl status x-ui-agent"
fi
}
# 以 OpenRC 服务方式安装并启动(开机自启,Alpine 等)
start_via_openrc() {
local init_dir="${ROOT}/etc/init.d"
mkdir -p "$init_dir"
cat > "${init_dir}/x-ui-agent" <<'EOF'
#!/sbin/openrc-run
name="x-ui-agent"
description="3x-ui lightweight node agent"
command="/usr/local/x-ui/xui-agent"
command_args="agent"
command_background="yes"
pidfile="/run/x-ui-agent.pid"
output_log="/var/log/x-ui-agent.log"
error_log="/var/log/x-ui-agent.log"
depend() {
need net
}
EOF
chmod +x "${init_dir}/x-ui-agent"
info "已生成 OpenRC 服务文件: ${init_dir}/x-ui-agent"
if [ -n "$ROOT" ]; then
info "(XUI_ROOT 模式:跳过 rc-update/rc-service 实际操作)"
return 0
fi
rc-service x-ui-agent stop 2>/dev/null || true
stop_legacy_agent
pkill -f "${AGENT_BIN} agent" 2>/dev/null || true; sleep 1
rc-update add x-ui-agent default
rc-service x-ui-agent restart
sleep 2
if rc-service x-ui-agent status >/dev/null 2>&1; then
info "agent 已由 OpenRC 托管启动(开机自启)"
echo ""
echo " 主控: $MASTER"
echo " 节点名: $NAME"
echo " 日志: tail -f /var/log/x-ui-agent.log"
echo " agent 会在 30 秒内从主控拉取配置"
else
error "agent 服务启动失败,查看: rc-service x-ui-agent status"
fi
}
# 无服务管理器时回落 nohup(无开机自启,明确警告)
start_via_nohup() {
warn "未检测到 systemd / OpenRC,退回 nohup 方式启动"
warn "注意:nohup 方式没有开机自启,机器重启后 agent 不会自动恢复,需手动重跑本脚本!"
stop_legacy_agent
pkill -f "${AGENT_BIN} agent" 2>/dev/null || true; sleep 1
# -config 显式给出(生产环境即其默认路径 /etc/x-ui/agent.json,行为不变),
# 这样 XUI_ROOT 假根验证时 agent 也能找到刚写下的配置。
nohup "${AGENT_BIN}" agent -config "${CONF_DIR}/agent.json" > /tmp/agent.log 2>&1 &
sleep 8
if grep -q "Starting 3x-ui agent" /tmp/agent.log 2>/dev/null; then
info "agent 启动成功!"
echo ""
echo " 主控: $MASTER"
echo " 节点名: $NAME"
echo " 日志: tail -f /tmp/agent.log"
echo " agent 会在 30 秒内从主控拉取配置"
else
error "启动失败,查看日志: cat /tmp/agent.log"
fi
}
info "启动 agent..."
case "$(detect_init_system)" in
systemd) start_via_systemd;;
openrc) start_via_openrc;;
*) start_via_nohup;;
esac