GitHub accounts suspended; all first-party distribution now served from git.881218.xyz/rogue/NexusGate (Gitea): - raw: /rogue/NexusGate/raw/branch/<branch>/<path> - release assets and source archives under the same repo path - scripts gain a GIT_BASE variable built from NG_REPO (default rogue/NexusGate); NG_REPO/NG_BRANCH/--repo/--branch semantics unchanged - panel enrollment/uninstall commands in public/app.js point at Gitea raw - install.sh/nexusgate.sh/agent-singbox.sh stats source previously defaulted to the upstream repo (a2899882); defaults now this repo Upstream dependency references (sagernet/sing-box Go module) untouched.
213 lines
9.2 KiB
Bash
Executable File
213 lines
9.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
REPO="${NG_REPO:-rogue/NexusGate}"
|
||
BRANCH="${NG_BRANCH:-main}"
|
||
# 自建 Gitea 分发基址(raw / release / archive 均由此拼装)
|
||
GIT_BASE="https://git.881218.xyz/${REPO}"
|
||
# Agent 实现:默认 go(静态二进制,节点机不再需要 Node.js)。
|
||
# NG_AGENT_IMPL=node 可回退到旧版 Node agent(agent/agent.js + nodejs 包)。
|
||
AGENT_IMPL="${NG_AGENT_IMPL:-go}"
|
||
# Go agent 二进制所在的 Gitea Release tag
|
||
AGENT_RELEASE="${NG_AGENT_RELEASE:-agent-v0.6.8}"
|
||
CONTROLLER=""
|
||
TOKEN=""
|
||
|
||
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||
info() { printf '\033[1;36m[NexusGate Agent]\033[0m %s\n' "$*"; }
|
||
|
||
# NG_LOCAL_SRC 指向本地源码目录时跳过远程下载(本地改动尚未 push 时用这个)
|
||
fetch() {
|
||
if [[ -n "${NG_LOCAL_SRC:-}" ]]; then
|
||
[[ -f "${NG_LOCAL_SRC}/$1" ]] || die "NG_LOCAL_SRC 目录缺少 $1"
|
||
cp -a "${NG_LOCAL_SRC}/$1" "$2"
|
||
else
|
||
# 加时间戳破坏 raw 缓存,避免拉到旧版
|
||
curl -fL --retry 3 "${GIT_BASE}/raw/branch/${BRANCH}/$1?t=$(date +%s)" -o "$2"
|
||
fi
|
||
}
|
||
|
||
# uname -m → release 资产架构名(amd64/arm64)
|
||
agent_arch() {
|
||
case "$(uname -m)" in
|
||
x86_64|amd64) printf 'amd64' ;;
|
||
aarch64|arm64) printf 'arm64' ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
# uname -m → Node 风格架构名(注册信息用,与旧版 process.arch 输出一致)
|
||
node_arch() {
|
||
case "$(uname -m)" in
|
||
x86_64|amd64) printf 'x64' ;;
|
||
aarch64|arm64) printf 'arm64' ;;
|
||
*) uname -m ;;
|
||
esac
|
||
}
|
||
|
||
# 下载 Go agent 二进制到 $1(NG_LOCAL_SRC 时从 agent-go/ 取已构建产物)
|
||
fetch_agent_binary() {
|
||
local arch out="$1"
|
||
arch="$(agent_arch)" || die "不支持的架构:$(uname -m)"
|
||
if [[ -n "${NG_LOCAL_SRC:-}" && -f "${NG_LOCAL_SRC}/agent-go/nexusgate-agent-linux-${arch}" ]]; then
|
||
cp -a "${NG_LOCAL_SRC}/agent-go/nexusgate-agent-linux-${arch}" "$out"
|
||
else
|
||
curl -fL --retry 3 "${GIT_BASE}/releases/download/${AGENT_RELEASE}/nexusgate-agent-linux-${arch}" -o "$out"
|
||
fi
|
||
[[ -s "$out" ]] || die 'Agent 二进制下载不完整'
|
||
}
|
||
|
||
# 极简 JSON 字符串转义(注册请求拼 JSON 用)
|
||
json_escape() {
|
||
local s="$1"
|
||
s="${s//\\/\\\\}"
|
||
s="${s//\"/\\\"}"
|
||
printf '"%s"' "$s"
|
||
}
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--server) CONTROLLER="${2:-}"; shift 2 ;;
|
||
--token) TOKEN="${2:-}"; shift 2 ;;
|
||
--repo) REPO="${2:-}"; shift 2 ;;
|
||
--branch) BRANCH="${2:-}"; shift 2 ;;
|
||
*) die "未知参数:$1" ;;
|
||
esac
|
||
done
|
||
|
||
[[ "${EUID}" -eq 0 ]] || die "请使用 root 运行"
|
||
[[ "$CONTROLLER" =~ ^https?:// ]] || die "--server 必须是完整的 HTTP(S) 地址"
|
||
[[ -n "$TOKEN" ]] || die "缺少 --token"
|
||
[[ "$AGENT_IMPL" == "go" || "$AGENT_IMPL" == "node" ]] || die "NG_AGENT_IMPL 只能是 go 或 node"
|
||
extra_pkgs=()
|
||
if [[ "$AGENT_IMPL" == "node" ]]; then
|
||
# 仅回退到 Node 实现时才需要 nodejs
|
||
extra_pkgs=(nodejs)
|
||
fi
|
||
if command -v apt-get >/dev/null; then
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq && apt-get install -y -qq ca-certificates curl unzip openssl logrotate "${extra_pkgs[@]}"
|
||
elif command -v dnf >/dev/null; then
|
||
dnf install -y ca-certificates curl unzip openssl logrotate "${extra_pkgs[@]}"
|
||
elif command -v apk >/dev/null; then
|
||
apk add --no-cache bash ca-certificates curl unzip openrc openssl logrotate "${extra_pkgs[@]}"
|
||
else
|
||
die "当前安装器支持 Debian/Ubuntu、RHEL 系和 Alpine"
|
||
fi
|
||
if [[ "$AGENT_IMPL" == "node" ]]; then
|
||
node_major="$(node -p 'process.versions.node.split(`.`)[0]')"
|
||
[[ "$node_major" -ge 18 ]] || die "需要 Node.js 18 或更高版本"
|
||
fi
|
||
|
||
tmp_dir="$(mktemp -d /tmp/nexusgate-agent.XXXXXX)"
|
||
trap 'rm -rf -- "$tmp_dir"' EXIT
|
||
|
||
install -d -m 0755 /opt/nexusgate-agent /usr/local/sbin
|
||
if [[ "$AGENT_IMPL" == "node" ]]; then
|
||
fetch agent/agent.js /opt/nexusgate-agent/agent.js
|
||
fetch agent/run.sh /opt/nexusgate-agent/run.sh
|
||
chmod 0644 /opt/nexusgate-agent/agent.js
|
||
chmod 0755 /opt/nexusgate-agent/run.sh
|
||
rm -f -- /opt/nexusgate-agent/nexusgate-agent
|
||
else
|
||
fetch_agent_binary "$tmp_dir/nexusgate-agent"
|
||
install -m 0755 "$tmp_dir/nexusgate-agent" /opt/nexusgate-agent/nexusgate-agent
|
||
# 从 Node 实现迁移过来的节点:清掉旧文件,避免新旧实现混淆
|
||
rm -f -- /opt/nexusgate-agent/agent.js /opt/nexusgate-agent/run.sh
|
||
fi
|
||
fetch scripts/agent-update.sh /usr/local/sbin/ng-agent-update
|
||
fetch scripts/agent-uninstall.sh /usr/local/sbin/ng-agent-uninstall
|
||
fetch scripts/agent-doctor.sh /usr/local/sbin/ng-agent-doctor
|
||
fetch scripts/agent-cert.sh /usr/local/sbin/ng-agent-cert
|
||
fetch scripts/agent-singbox.sh /usr/local/sbin/ng-agent-singbox
|
||
fetch scripts/agent-logrotate.conf "$tmp_dir/logrotate.conf"
|
||
fetch scripts/agent-logrotate-setup.sh "$tmp_dir/logrotate-setup.sh"
|
||
cat > /usr/local/sbin/ng-agent <<'EOF'
|
||
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
case "${1:-}" in
|
||
update) exec ng-agent-update ;;
|
||
uninstall) shift; exec ng-agent-uninstall "$@" ;;
|
||
doctor|status) exec ng-agent-doctor ;;
|
||
cert) shift; exec ng-agent-cert "$@" ;;
|
||
engine) shift; exec ng-agent-singbox "$@" ;;
|
||
*) printf 'NexusGate Agent: ng-agent doctor | ng-agent cert | ng-agent engine install | ng-agent update | ng-agent uninstall\n' ;;
|
||
esac
|
||
EOF
|
||
chmod 0755 /usr/local/sbin/ng-agent-update /usr/local/sbin/ng-agent-uninstall /usr/local/sbin/ng-agent-doctor /usr/local/sbin/ng-agent-cert /usr/local/sbin/ng-agent-singbox /usr/local/sbin/ng-agent
|
||
install -d -m 0700 /etc/nexusgate /etc/nexusgate/agent /etc/nexusgate/agent/resources /etc/nexusgate/sing-box
|
||
install -d -m 0750 /var/log/nexusgate
|
||
install -m 0644 "$tmp_dir/logrotate.conf" /etc/nexusgate/agent-logrotate.conf
|
||
install -m 0755 "$tmp_dir/logrotate-setup.sh" /usr/local/sbin/ng-agent-logrotate-setup
|
||
# sing-box 是唯一的入口引擎,安装时直接构建。
|
||
NG_REPO="$REPO" NG_BRANCH="$BRANCH" ng-agent-singbox install || die 'sing-box 引擎安装失败;修复后运行 ng-agent engine install'
|
||
|
||
info "向控制面注册"
|
||
enroll_json="$(printf '{"token":%s,"hostname":%s,"version":"0.6.8","system":{"platform":"linux","arch":%s}}' \
|
||
"$(json_escape "$TOKEN")" "$(json_escape "$(hostname)")" "$(json_escape "$(node_arch)")")"
|
||
response="$(curl -fsS -H 'content-type: application/json' --data "$enroll_json" "${CONTROLLER%/}/api/agent/enroll")" || die "注册失败,请检查地址和令牌"
|
||
agent_key="$(printf '%s' "$response" | grep -o '"agentKey"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n 1 | sed -E 's/^"agentKey"[[:space:]]*:[[:space:]]*"//; s/"$//')"
|
||
[[ -n "$agent_key" ]] || die "控制面返回无效"
|
||
|
||
{
|
||
printf 'NG_CONTROLLER=%q\n' "${CONTROLLER%/}"
|
||
printf 'NG_AGENT_KEY=%q\n' "$agent_key"
|
||
printf 'NG_CONFIG_DIR=/etc/nexusgate/agent\n'
|
||
printf 'NG_KEY_FILE=/etc/nexusgate/keys.json\n'
|
||
} > /etc/nexusgate/agent.env
|
||
chmod 0600 /etc/nexusgate/agent.env
|
||
|
||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||
fetch systemd/nexusgate-agent.service /etc/systemd/system/nexusgate-agent.service
|
||
fetch systemd/nexusgate-sing-box.service /etc/systemd/system/nexusgate-sing-box.service
|
||
if [[ "$AGENT_IMPL" == "node" ]]; then
|
||
# 仓库里的 unit 默认指向 Go 二进制;Node 回退改为经 run.sh 启动
|
||
sed -i 's|^ExecStart=.*|ExecStart=/opt/nexusgate-agent/run.sh|' /etc/systemd/system/nexusgate-agent.service
|
||
fi
|
||
systemctl daemon-reload
|
||
systemctl enable nexusgate-sing-box.service nexusgate-agent.service
|
||
rm -f -- /etc/nexusgate/last-heartbeat.json
|
||
systemctl restart nexusgate-sing-box.service
|
||
systemctl restart nexusgate-agent.service
|
||
service_manager="systemd"
|
||
elif command -v rc-service >/dev/null; then
|
||
install -d -m 0755 /etc/init.d
|
||
fetch openrc/nexusgate-agent /etc/init.d/nexusgate-agent
|
||
fetch openrc/nexusgate-sing-box /etc/init.d/nexusgate-sing-box
|
||
chmod 0755 /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-sing-box
|
||
if [[ "$AGENT_IMPL" == "node" ]]; then
|
||
# 仓库里的 OpenRC 服务默认指向 Go 二进制;Node 回退改为经 run.sh 启动
|
||
sed -i 's|^command=.*|command="/opt/nexusgate-agent/run.sh"|' /etc/init.d/nexusgate-agent
|
||
fi
|
||
rc-update add nexusgate-sing-box default >/dev/null
|
||
rc-update add nexusgate-agent default >/dev/null
|
||
# Alpine runs the dedicated hourly task through BusyBox crond.
|
||
if [[ -e /etc/init.d/crond ]]; then
|
||
rc-update add crond default >/dev/null 2>&1 || true
|
||
rc-service crond start >/dev/null 2>&1 || true
|
||
fi
|
||
rc-service nexusgate-sing-box restart
|
||
rm -f -- /etc/nexusgate/last-heartbeat.json
|
||
rc-service nexusgate-agent restart
|
||
service_manager="OpenRC"
|
||
else
|
||
die "未检测到 systemd 或 OpenRC"
|
||
fi
|
||
ng-agent-logrotate-setup
|
||
for _ in {1..35}; do
|
||
[[ -s /etc/nexusgate/last-heartbeat.json ]] && break
|
||
sleep 1
|
||
done
|
||
if [[ ! -s /etc/nexusgate/last-heartbeat.json ]]; then
|
||
if [[ "$service_manager" == systemd ]]; then journalctl -u nexusgate-agent.service -n 35 --no-pager || true; fi
|
||
die 'Agent 注册成功但没有向控制面上报心跳;运行 ng-agent doctor 排查后再重试'
|
||
fi
|
||
printf '\n\033[1;32mAgent 安装、注册和首次心跳完成(%s)。\033[0m\n' "$service_manager"
|
||
printf '后续更新 Agent:ng-agent-update\n'
|
||
printf '卸载 Agent:ng-agent uninstall\n'
|
||
printf '检查 Agent 与 sing-box:ng-agent doctor\n'
|
||
if [[ "$service_manager" == systemd ]] && ! systemctl is-active --quiet nexusgate-agent.service; then
|
||
journalctl -u nexusgate-agent.service -n 35 --no-pager || true
|
||
die 'Agent 注册已完成,但服务没有运行;请执行 ng-agent doctor 检查原因'
|
||
fi
|