Files
aaaamoooo f4ef3d96a6
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s
chore: migrate self-distribution links from GitHub to self-hosted Gitea
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.
2026-10-20 11:05:55 +00:00

213 lines
9.2 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.
#!/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