3x-ui 式共享节点改造:nodes 状态机 + nodeClients 各自凭据
- 移除 VMess 协议 - VLESS/SS2022 按用户流量统计(AnyTLS 日志解析,SS 节点级) - sing-box v2ray_api stats 支持 - 内存优化:面板 128M,agent 64M,sing-box 64M
This commit is contained in:
@@ -46,10 +46,8 @@ validate() {
|
||||
activate() {
|
||||
flush_usage
|
||||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
systemctl try-restart nexusgate-xray.service || printf 'Xray 重启失败,请运行 ng-agent doctor\n' >&2
|
||||
systemctl try-restart nexusgate-sing-box.service || printf 'sing-box 重启失败,请运行 ng-agent doctor\n' >&2
|
||||
elif command -v rc-service >/dev/null; then
|
||||
rc-service nexusgate-xray status >/dev/null 2>&1 && rc-service nexusgate-xray restart || true
|
||||
rc-service nexusgate-sing-box status >/dev/null 2>&1 && rc-service nexusgate-sing-box restart || true
|
||||
fi
|
||||
printf '证书已安装:%s;有效期:' "$target"
|
||||
@@ -104,10 +102,8 @@ if [[ -r /etc/nexusgate/agent.env && -f /opt/nexusgate-agent/agent.js ]]; then
|
||||
fi
|
||||
fi
|
||||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
systemctl try-restart nexusgate-xray.service
|
||||
systemctl try-restart nexusgate-sing-box.service
|
||||
elif command -v rc-service >/dev/null; then
|
||||
rc-service nexusgate-xray status >/dev/null 2>&1 && rc-service nexusgate-xray restart || true
|
||||
rc-service nexusgate-sing-box status >/dev/null 2>&1 && rc-service nexusgate-sing-box restart || true
|
||||
fi
|
||||
EOF
|
||||
|
||||
+9
-44
@@ -10,19 +10,13 @@ printf '已验证节点证书:'
|
||||
node -e 'const domains=require("/opt/nexusgate-agent/agent.js").installedCertificates();console.log(domains.length?domains.join(", "):"暂无;运行 ng-agent cert")' 2>&1 || true
|
||||
printf 'Agent 版本:'
|
||||
node -p "require('fs').readFileSync('/opt/nexusgate-agent/agent.js','utf8').match(/const VERSION = '([^']+)'/)[1]" 2>/dev/null || printf '无法读取\n'
|
||||
has_xray_resources=1
|
||||
if ! node -e 'const fs=require("fs");const dir="/etc/nexusgate/xray/resources";process.exit(fs.readdirSync(dir).some(name=>name.endsWith(".json")&&JSON.parse(fs.readFileSync(`${dir}/${name}`)).engine!=="sing-box")?0:1)' 2>/dev/null; then
|
||||
has_xray_resources=0
|
||||
printf '这台机器没有 Xray 资源,Xray 空闲服务停止属于正常(AnyTLS 由 sing-box 运行)。\n'
|
||||
fi
|
||||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
for name in nexusgate-agent nexusgate-xray nexusgate-sing-box; do
|
||||
for name in nexusgate-agent nexusgate-sing-box; do
|
||||
printf '%s:' "$name"
|
||||
systemctl is-active "$name" 2>&1 || true
|
||||
done
|
||||
else
|
||||
rc-service nexusgate-agent status || true
|
||||
rc-service nexusgate-xray status || true
|
||||
rc-service nexusgate-sing-box status || true
|
||||
fi
|
||||
printf '\nsing-box 构建与配置:\n'
|
||||
@@ -32,66 +26,37 @@ elif [[ -x /usr/local/bin/nexusgate-sing-box ]]; then
|
||||
/usr/local/bin/nexusgate-sing-box version | head -n 4
|
||||
if [[ -f /etc/nexusgate/sing-box/config.json ]]; then
|
||||
/usr/local/bin/nexusgate-sing-box check -c /etc/nexusgate/sing-box/config.json 2>&1 | tail -n 12
|
||||
printf 'AnyTLS 入站流量统计(累计):\n'
|
||||
printf '入站流量统计(累计):\n'
|
||||
/usr/local/bin/nexusgate-sing-box-stats --server="127.0.0.1:${NG_SINGBOX_API_PORT:-10086}" 2>&1 | head -c 900
|
||||
printf '\n'
|
||||
else
|
||||
printf '已安装,当前没有 AnyTLS 资源\n'
|
||||
printf '已安装,当前没有节点资源\n'
|
||||
fi
|
||||
else
|
||||
printf '未安装;运行 ng-agent engine install\n'
|
||||
fi
|
||||
printf '控制面连通性:'
|
||||
curl --max-time 8 -fsS "${NG_CONTROLLER%/}/healthz" 2>&1 | head -c 350 || true
|
||||
printf '\nXray 配置校验:\n'
|
||||
if [[ -f /etc/nexusgate/xray/config.json ]]; then
|
||||
/usr/local/bin/xray run -test -config /etc/nexusgate/xray/config.json 2>&1 | tail -n 12
|
||||
else
|
||||
printf '配置文件不存在\n'
|
||||
fi
|
||||
printf '\nHysteria 2 UDP 监听:\n'
|
||||
node <<'NODE' 2>&1 || true
|
||||
const fs = require('node:fs');
|
||||
const agent = require('/opt/nexusgate-agent/agent.js');
|
||||
const dir = '/etc/nexusgate/xray/resources';
|
||||
const dir = '/etc/nexusgate/agent/resources';
|
||||
const resources = fs.readdirSync(dir).filter((name) => name.endsWith('.json'))
|
||||
.map((name) => JSON.parse(fs.readFileSync(`${dir}/${name}`, 'utf8')));
|
||||
const expected = resources.flatMap((resource) => resource.inbounds || [])
|
||||
.filter((inbound) => inbound.protocol === 'hysteria').map((inbound) => inbound.port);
|
||||
.filter((inbound) => inbound.type === 'hysteria2').map((inbound) => inbound.listen_port);
|
||||
if (!expected.length) console.log('没有 Hysteria 2 入站');
|
||||
else {
|
||||
const config = JSON.parse(fs.readFileSync('/etc/nexusgate/xray/config.json', 'utf8'));
|
||||
for (const port of expected) {
|
||||
const inbound = config.inbounds.find((item) => item.port === port && item.protocol === 'hysteria');
|
||||
console.log(`UDP ${port}: 传输 ${inbound?.streamSettings?.network || '未配置'},账户 ${(inbound?.settings?.clients || inbound?.settings?.users || []).length}`);
|
||||
}
|
||||
try {
|
||||
const missing = agent.missingHysteriaListeners(resources);
|
||||
console.log(missing.length ? `未监听 UDP: ${missing.join(', ')}` : '所有 Hysteria 2 UDP 端口均由 Xray 监听');
|
||||
console.log(missing.length ? `未监听 UDP: ${missing.join(', ')}` : '所有 Hysteria 2 UDP 端口均由 sing-box 监听');
|
||||
} catch (error) { console.log(`监听检查失败: ${error.message}`); }
|
||||
}
|
||||
NODE
|
||||
printf '\nXray 入站流量统计(累计,查询不会清零):\n'
|
||||
if [[ "$has_xray_resources" == 0 ]]; then
|
||||
printf '无 Xray 入口,跳过统计查询。\n'
|
||||
else
|
||||
stats_output="$(/usr/local/bin/xray api statsquery --server="127.0.0.1:${NG_XRAY_API_PORT:-10085}" -pattern 'inbound>>>' 2>&1)"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
printf '查询失败:%.250s\n' "$stats_output"
|
||||
else
|
||||
STATS_OUTPUT="$stats_output" node -e '
|
||||
try {
|
||||
const stats = JSON.parse(process.env.STATS_OUTPUT).stat || [];
|
||||
const entries = stats.filter(item => /^inbound>>>ng-.*>>>traffic>>>(uplink|downlink)$/.test(item.name));
|
||||
if (!entries.length) console.log("尚无入口流量计数;请确认客户端确实连接本机节点后再测试");
|
||||
else for (const item of entries.slice(0, 24)) console.log(`${item.name}: ${item.value === undefined ? 0 : item.value} B`);
|
||||
} catch { console.log("统计返回格式无效,请升级 Xray 和 Agent"); }
|
||||
'
|
||||
fi
|
||||
fi
|
||||
printf '\n最近 Agent 与 Xray 日志:\n'
|
||||
printf '\n最近 Agent 与 sing-box 日志:\n'
|
||||
if command -v journalctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
journalctl -u nexusgate-agent -u nexusgate-xray -u nexusgate-sing-box -n 45 --no-pager -o short-iso 2>&1 | sed -E 's/(Bearer |NG_AGENT_KEY=)[^[:space:]]+/\1[REDACTED]/g; s/[Pp]rivate[[:space:]]*[Kk]ey:[[:space:]]*[^[:space:]]+/PrivateKey: [REDACTED]/g'
|
||||
journalctl -u nexusgate-agent -u nexusgate-sing-box -n 45 --no-pager -o short-iso 2>&1 | sed -E 's/(Bearer |NG_AGENT_KEY=)[^[:space:]]+/\1[REDACTED]/g; s/[Pp]rivate[[:space:]]*[Kk]ey:[[:space:]]*[^[:space:]]+/PrivateKey: [REDACTED]/g'
|
||||
else
|
||||
tail -n 45 /var/log/nexusgate/xray-error.log 2>/dev/null || true
|
||||
tail -n 45 /var/log/nexusgate/sing-box-access.log 2>/dev/null || true
|
||||
fi
|
||||
|
||||
+33
-45
@@ -9,6 +9,16 @@ TOKEN=""
|
||||
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '\033[1;36m[NexusGate Agent]\033[0m %s\n' "$*"; }
|
||||
|
||||
# NG_LOCAL_SRC 指向本地源码目录时跳过 GitHub 下载(本地改动尚未 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
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/$1" -o "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--server) CONTROLLER="${2:-}"; shift 2 ;;
|
||||
@@ -35,31 +45,19 @@ fi
|
||||
node_major="$(node -p 'process.versions.node.split(`.`)[0]')"
|
||||
[[ "$node_major" -ge 18 ]] || die "需要 Node.js 18 或更高版本"
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) xray_arch="64" ;;
|
||||
aarch64|arm64) xray_arch="arm64-v8a" ;;
|
||||
armv7l) xray_arch="arm32-v7a" ;;
|
||||
*) die "暂不支持的架构:$(uname -m)" ;;
|
||||
esac
|
||||
|
||||
tmp_dir="$(mktemp -d /tmp/nexusgate-agent.XXXXXX)"
|
||||
trap 'rm -rf -- "$tmp_dir"' EXIT
|
||||
info "下载 Xray-core"
|
||||
xray_asset="Xray-linux-${xray_arch}.zip"
|
||||
curl -fL --retry 3 "https://github.com/XTLS/Xray-core/releases/latest/download/${xray_asset}" -o "$tmp_dir/xray.zip"
|
||||
unzip -q "$tmp_dir/xray.zip" xray -d "$tmp_dir/xray"
|
||||
install -m 0755 "$tmp_dir/xray/xray" /usr/local/bin/xray
|
||||
|
||||
install -d -m 0755 /opt/nexusgate-agent
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/agent/agent.js" -o /opt/nexusgate-agent/agent.js
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/agent/run.sh" -o /opt/nexusgate-agent/run.sh
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-update.sh" -o /usr/local/sbin/ng-agent-update
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-uninstall.sh" -o /usr/local/sbin/ng-agent-uninstall
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-doctor.sh" -o /usr/local/sbin/ng-agent-doctor
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-cert.sh" -o /usr/local/sbin/ng-agent-cert
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-singbox.sh" -o /usr/local/sbin/ng-agent-singbox
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-logrotate.conf" -o "$tmp_dir/logrotate.conf"
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-logrotate-setup.sh" -o "$tmp_dir/logrotate-setup.sh"
|
||||
fetch agent/agent.js /opt/nexusgate-agent/agent.js
|
||||
fetch agent/run.sh /opt/nexusgate-agent/run.sh
|
||||
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
|
||||
@@ -74,19 +72,12 @@ esac
|
||||
EOF
|
||||
chmod 0644 /opt/nexusgate-agent/agent.js
|
||||
chmod 0755 /opt/nexusgate-agent/run.sh /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/xray /etc/nexusgate/xray/resources /etc/nexusgate/sing-box
|
||||
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
|
||||
if [[ ! -f /etc/nexusgate/xray/config.json ]]; then
|
||||
printf '{"log":{"loglevel":"warning"},"inbounds":[],"outbounds":[]}\n' > /etc/nexusgate/xray/config.json
|
||||
fi
|
||||
chmod 0600 /etc/nexusgate/xray/config.json
|
||||
if [[ "${NG_INSTALL_ANYTLS:-0}" == 1 ]]; then
|
||||
NG_REPO="$REPO" NG_BRANCH="$BRANCH" ng-agent-singbox install || die 'AnyTLS 引擎安装失败;修复后运行 ng-agent engine install'
|
||||
else
|
||||
info '基础节点只安装 Xray;需要 AnyTLS 时在入口机运行 ng-agent engine install。'
|
||||
fi
|
||||
# sing-box 是唯一的入口引擎,安装时直接构建。
|
||||
NG_REPO="$REPO" NG_BRANCH="$BRANCH" ng-agent-singbox install || die 'sing-box 引擎安装失败;修复后运行 ng-agent engine install'
|
||||
|
||||
info "向控制面注册"
|
||||
enroll_json="$(TOKEN_VALUE="$TOKEN" node -e 'process.stdout.write(JSON.stringify({token:process.env.TOKEN_VALUE,hostname:require("node:os").hostname(),version:"0.6.8",system:{platform:process.platform,arch:process.arch}}))')"
|
||||
@@ -96,36 +87,33 @@ agent_key="$(RESPONSE_VALUE="$response" node -e 'const r=JSON.parse(process.env.
|
||||
{
|
||||
printf 'NG_CONTROLLER=%q\n' "${CONTROLLER%/}"
|
||||
printf 'NG_AGENT_KEY=%q\n' "$agent_key"
|
||||
printf 'NG_XRAY_BIN=/usr/local/bin/xray\n'
|
||||
printf 'NG_CONFIG_DIR=/etc/nexusgate/xray\n'
|
||||
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
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-agent.service" -o /etc/systemd/system/nexusgate-agent.service
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-xray.service" -o /etc/systemd/system/nexusgate-xray.service
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-sing-box.service" -o /etc/systemd/system/nexusgate-sing-box.service
|
||||
fetch systemd/nexusgate-agent.service /etc/systemd/system/nexusgate-agent.service
|
||||
fetch systemd/nexusgate-sing-box.service /etc/systemd/system/nexusgate-sing-box.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable nexusgate-xray.service nexusgate-agent.service
|
||||
systemctl restart nexusgate-xray.service
|
||||
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
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-agent" -o /etc/init.d/nexusgate-agent
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-xray" -o /etc/init.d/nexusgate-xray
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-sing-box" -o /etc/init.d/nexusgate-sing-box
|
||||
chmod 0755 /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-xray /etc/init.d/nexusgate-sing-box
|
||||
rc-update add nexusgate-xray default >/dev/null
|
||||
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
|
||||
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-xray restart
|
||||
rc-service nexusgate-sing-box restart
|
||||
rm -f -- /etc/nexusgate/last-heartbeat.json
|
||||
rc-service nexusgate-agent restart
|
||||
service_manager="OpenRC"
|
||||
@@ -144,7 +132,7 @@ 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 与 Xray:ng-agent doctor\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 检查原因'
|
||||
|
||||
@@ -3,7 +3,7 @@ set -Eeuo pipefail
|
||||
|
||||
[[ "${EUID}" -eq 0 ]] || { printf '请使用 root 运行\n' >&2; exit 1; }
|
||||
if [[ "${1:-}" != "--yes" ]]; then
|
||||
printf '将停止并删除此机器上的 NexusGate Agent、专属 Xray 服务、节点资源和密钥。\n'
|
||||
printf '将停止并删除此机器上的 NexusGate Agent、sing-box 服务、节点资源和密钥。\n'
|
||||
printf '请先在控制台停用/删除关联线路;如设备已离线,之后在控制台执行“强制遗忘”。\n'
|
||||
printf '输入 UNINSTALL 确认:' > /dev/tty
|
||||
read -r confirmation < /dev/tty
|
||||
@@ -12,20 +12,17 @@ fi
|
||||
|
||||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
systemctl disable --now nexusgate-agent.service 2>/dev/null || true
|
||||
systemctl disable --now nexusgate-xray.service 2>/dev/null || true
|
||||
systemctl disable --now nexusgate-sing-box.service 2>/dev/null || true
|
||||
systemctl disable --now nexusgate-cert-renew.timer 2>/dev/null || true
|
||||
systemctl disable --now nexusgate-agent-logrotate.timer 2>/dev/null || true
|
||||
rm -f -- /etc/systemd/system/nexusgate-agent.service /etc/systemd/system/nexusgate-xray.service /etc/systemd/system/nexusgate-sing-box.service /etc/systemd/system/nexusgate-cert-renew.service /etc/systemd/system/nexusgate-cert-renew.timer /etc/systemd/system/nexusgate-agent-logrotate.service /etc/systemd/system/nexusgate-agent-logrotate.timer
|
||||
rm -f -- /etc/systemd/system/nexusgate-agent.service /etc/systemd/system/nexusgate-sing-box.service /etc/systemd/system/nexusgate-cert-renew.service /etc/systemd/system/nexusgate-cert-renew.timer /etc/systemd/system/nexusgate-agent-logrotate.service /etc/systemd/system/nexusgate-agent-logrotate.timer
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
if command -v rc-service >/dev/null; then
|
||||
rc-service nexusgate-agent stop 2>/dev/null || true
|
||||
rc-service nexusgate-xray stop 2>/dev/null || true
|
||||
rc-service nexusgate-sing-box stop 2>/dev/null || true
|
||||
rc-update del nexusgate-agent default 2>/dev/null || true
|
||||
rc-update del nexusgate-xray default 2>/dev/null || true
|
||||
rm -f -- /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-xray /etc/init.d/nexusgate-sing-box
|
||||
rm -f -- /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-sing-box
|
||||
rm -f -- /etc/periodic/daily/nexusgate-cert-renew
|
||||
rm -f -- /etc/periodic/hourly/nexusgate-agent-logrotate
|
||||
fi
|
||||
@@ -34,6 +31,6 @@ rm -rf -- /opt/nexusgate-agent /etc/nexusgate /var/log/nexusgate
|
||||
rm -f -- /etc/logrotate.d/nexusgate-agent
|
||||
rm -f -- /etc/letsencrypt/renewal-hooks/deploy/nexusgate-reload.sh
|
||||
rm -f -- /usr/local/sbin/ng-agent /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-logrotate-setup /usr/local/bin/nexusgate-sing-box /usr/local/bin/nexusgate-sing-box-stats
|
||||
printf 'NexusGate Agent 与其资源已删除。系统共用的 Node.js、Xray 可执行文件和其他服务未删除。\n'
|
||||
printf 'NexusGate Agent 与其资源已删除。系统共用的 Node.js 和其他服务未删除。\n'
|
||||
printf 'Certbot 管理的证书仍保留;若曾使用 CF DNS 验证,其令牌已删除,保留的续签任务需另行配置或在确认没有其他服务使用后用 certbot delete 清理。\n'
|
||||
printf '请在控制台删除/遗忘此设备,以撤销其 Agent 密钥和登记。\n'
|
||||
|
||||
+7
-11
@@ -21,8 +21,8 @@ curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/agent-logrotate-setup.sh" -o "$tmp_dir/logrotate-setup.sh"
|
||||
[[ -s "$tmp_dir/update.sh" ]] || die 'Agent 升级脚本下载不完整'
|
||||
node --check "$tmp_dir/agent.js"
|
||||
# Updating can rewrite an older HY2 configuration and restart Xray. Persist
|
||||
# its current bidirectional counters before the running process is replaced.
|
||||
# Updating restarts sing-box. Persist its current bidirectional counters
|
||||
# before the running process is replaced.
|
||||
if [[ -f /opt/nexusgate-agent/agent.js ]]; then
|
||||
( set -a; source /etc/nexusgate/agent.env; set +a; node /opt/nexusgate-agent/agent.js flush-usage ) \
|
||||
|| die '更新前流量统计上报失败,保留原 Agent;检查控制面连接和 ng-agent doctor 后重试'
|
||||
@@ -50,16 +50,13 @@ case "${1:-}" in
|
||||
esac
|
||||
EOF
|
||||
chmod 0755 /usr/local/sbin/ng-agent
|
||||
if [[ "${NG_INSTALL_ANYTLS:-0}" == 1 ]]; then
|
||||
if ! NG_REPO="$REPO" NG_BRANCH="$BRANCH" ng-agent-singbox install; then
|
||||
info 'sing-box 构建未完成;现有 Xray 节点正常。修复后运行 ng-agent engine install 重试。'
|
||||
fi
|
||||
elif [[ -x /usr/local/bin/nexusgate-sing-box && ! -x /usr/local/bin/nexusgate-sing-box-stats ]]; then
|
||||
info '发现未完成的 sing-box 安装;更新 Agent 不会再次编译,请在需要 AnyTLS 的入口机运行 ng-agent engine install。'
|
||||
# sing-box 是唯一的入口引擎;缺失时直接安装。
|
||||
if [[ ! -x /usr/local/bin/nexusgate-sing-box || ! -x /usr/local/bin/nexusgate-sing-box-stats ]]; then
|
||||
NG_REPO="$REPO" NG_BRANCH="$BRANCH" ng-agent-singbox install \
|
||||
|| die 'sing-box 引擎安装失败;修复后运行 ng-agent engine install 重试'
|
||||
fi
|
||||
if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-agent.service" -o /etc/systemd/system/nexusgate-agent.service
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-xray.service" -o /etc/systemd/system/nexusgate-xray.service
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/systemd/nexusgate-sing-box.service" -o /etc/systemd/system/nexusgate-sing-box.service
|
||||
systemctl daemon-reload
|
||||
rm -f -- /etc/nexusgate/last-heartbeat.json
|
||||
@@ -67,9 +64,8 @@ if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then
|
||||
systemctl is-active --quiet nexusgate-agent.service || die "Agent 重启失败"
|
||||
elif command -v rc-service >/dev/null; then
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-agent" -o /etc/init.d/nexusgate-agent
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-xray" -o /etc/init.d/nexusgate-xray
|
||||
curl -fL --retry 3 "https://raw.githubusercontent.com/${REPO}/${BRANCH}/openrc/nexusgate-sing-box" -o /etc/init.d/nexusgate-sing-box
|
||||
chmod 0755 /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-xray /etc/init.d/nexusgate-sing-box
|
||||
chmod 0755 /etc/init.d/nexusgate-agent /etc/init.d/nexusgate-sing-box
|
||||
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
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
xray="${NG_XRAY_BIN:-/usr/local/bin/xray}"
|
||||
singbox="${NG_SINGBOX_BIN:-/usr/local/bin/nexusgate-sing-box}"
|
||||
command -v "$xray" >/dev/null || { printf 'Xray binary missing\n' >&2; exit 1; }
|
||||
command -v "$singbox" >/dev/null || { printf 'sing-box binary missing\n' >&2; exit 1; }
|
||||
tmp="$(mktemp -d /tmp/nexusgate-hy2-smoke.XXXXXX)"
|
||||
server_pid='' client_pid='' http_pid=''
|
||||
cleanup() {
|
||||
for pid in "$client_pid" "$server_pid" "$http_pid"; do
|
||||
[[ -n "$pid" ]] && kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
rm -rf -- "$tmp"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -keyout "$tmp/key.pem" -out "$tmp/cert.pem" \
|
||||
-subj '/CN=node.test' -addext 'subjectAltName=DNS:node.test' >/dev/null 2>&1
|
||||
NG_SMOKE_DIR="$tmp" node <<'NODE'
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const root = process.env.NG_SMOKE_DIR;
|
||||
const { newCredentialSet, buildDirectResource } = require('./lib/protocols');
|
||||
const { combinedConfig } = require('./agent/agent');
|
||||
const credentials = newCredentialSet('hysteria2');
|
||||
const resource = buildDirectResource({ resourceId:'res_hy2_smoke', tagPrefix:'smoke', port:23460,
|
||||
protocol:'hysteria2', credentials, customer:{ id:'smoke-customer' }, tlsDomain:'node.test' });
|
||||
resource.inbounds[0].listen = '127.0.0.1';
|
||||
const certificate = resource.inbounds[0].streamSettings.tlsSettings.certificates[0];
|
||||
certificate.certificateFile = path.join(root, 'cert.pem');
|
||||
certificate.keyFile = path.join(root, 'key.pem');
|
||||
const server = combinedConfig([resource]);
|
||||
server.inbounds[0].port = 23461;
|
||||
server.log.access = path.join(root, 'access.log');
|
||||
server.log.error = path.join(root, 'error.log');
|
||||
fs.writeFileSync(path.join(root, 'server.json'), JSON.stringify(server));
|
||||
const client = { log:{ level:'warn' },
|
||||
inbounds:[{ type:'mixed', listen:'127.0.0.1', listen_port:23462, tag:'local' }],
|
||||
outbounds:[{ type:'hysteria2', tag:'server', server:'127.0.0.1', server_port:23460,
|
||||
password:credentials.relayPassword, tls:{ enabled:true, server_name:'node.test', insecure:true, alpn:['h3'] } }],
|
||||
route:{ final:'server' } };
|
||||
fs.writeFileSync(path.join(root, 'client.json'), JSON.stringify(client));
|
||||
NODE
|
||||
"$xray" run -test -config "$tmp/server.json"
|
||||
"$singbox" check -c "$tmp/client.json"
|
||||
node -e 'require("http").createServer((_,res)=>res.end("NexusGate HY2 smoke OK")).listen(23463,"127.0.0.1")' & http_pid=$!
|
||||
"$xray" run -config "$tmp/server.json" > "$tmp/server.out" 2>&1 & server_pid=$!
|
||||
"$singbox" run -c "$tmp/client.json" > "$tmp/client.out" 2>&1 & client_pid=$!
|
||||
for _ in {1..40}; do
|
||||
if curl -fsS --max-time 2 --noproxy '' --socks5-hostname 127.0.0.1:23462 http://127.0.0.1:23463/ > "$tmp/response" 2>/dev/null; then break; fi
|
||||
sleep 0.25
|
||||
done
|
||||
if [[ "$(cat "$tmp/response" 2>/dev/null || true)" != 'NexusGate HY2 smoke OK' ]]; then
|
||||
cat "$tmp/server.out" "$tmp/client.out" "$tmp/error.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
NG_SMOKE_PID="$server_pid" node <<'NODE'
|
||||
const { udpPortsForPid } = require('./agent/agent');
|
||||
if (!udpPortsForPid(Number(process.env.NG_SMOKE_PID)).has(23460)) throw new Error('Xray has no UDP listener on the HY2 port');
|
||||
NODE
|
||||
"$xray" api statsquery --server=127.0.0.1:23461 -pattern 'inbound>>>' > "$tmp/stats.json"
|
||||
NG_SMOKE_STATS="$tmp/stats.json" node <<'NODE'
|
||||
const stats = JSON.parse(require('node:fs').readFileSync(process.env.NG_SMOKE_STATS));
|
||||
const counters = new Map((stats.stat || []).map((item) => [item.name, Number(item.value)]));
|
||||
for (const direction of ['uplink','downlink']) {
|
||||
const value = counters.get(`inbound>>>smoke-in>>>traffic>>>${direction}`);
|
||||
if (!(value > 0)) throw new Error(`missing HY2 ${direction} counter`);
|
||||
}
|
||||
console.log('HY2 UDP listener, sing-box handshake, round trip, and dual direction stats OK');
|
||||
NODE
|
||||
+109
-29
@@ -25,52 +25,107 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
[[ "${EUID}" -eq 0 ]] || die "请使用 root 运行"
|
||||
[[ -f /etc/debian_version ]] || die "控制面一键安装器当前支持 Debian 12 / Ubuntu 22.04+"
|
||||
[[ ! -e /opt/nexusgate ]] || die "检测到已有安装,请运行 nexusgate update"
|
||||
IS_OPENWRT=false
|
||||
if [[ -f /etc/openwrt_release ]]; then
|
||||
IS_OPENWRT=true
|
||||
elif [[ ! -f /etc/debian_version ]]; then
|
||||
die "控制面一键安装器当前支持 Debian 12 / Ubuntu 22.04+ 和 OpenWrt"
|
||||
fi
|
||||
|
||||
if [[ -z "$DOMAIN" ]]; then DOMAIN="$(ask '请输入面板域名(如 gate.example.com):')"; fi
|
||||
[[ "$DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]] || die "域名格式不正确"
|
||||
if [[ -z "$DOMAIN" ]]; then
|
||||
if $IS_OPENWRT; then
|
||||
DOMAIN="$(ask '面板域名(OpenWrt 上可留空,留空则仅内网访问、不配置 Caddy):')"
|
||||
else
|
||||
DOMAIN="$(ask '请输入面板域名(如 gate.example.com):')"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$DOMAIN" ]]; then
|
||||
[[ "$DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]] || die "域名格式不正确"
|
||||
elif ! $IS_OPENWRT; then
|
||||
die "域名不能为空"
|
||||
fi
|
||||
if [[ -z "$EMAIL" ]]; then EMAIL="$(ask '证书邮箱(可留空):')"; fi
|
||||
|
||||
info "安装系统依赖"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq ca-certificates curl tar openssl nodejs caddy
|
||||
if $IS_OPENWRT; then
|
||||
# 注意:OpenWrt 上需预先装好 bash(本脚本依赖 bash):opkg update && opkg install bash
|
||||
opkg update
|
||||
# OpenWrt 上 openssl 命令行工具在 openssl-util 包里;部分精简固件没有 install 命令,需 coreutils-install
|
||||
opkg install node caddy curl ca-certificates openssl-util tar coreutils-install
|
||||
else
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq ca-certificates curl tar openssl nodejs caddy
|
||||
fi
|
||||
node_major="$(node -p 'process.versions.node.split(`.`)[0]')"
|
||||
[[ "$node_major" -ge 18 ]] || die "需要 Node.js 18 或更高版本"
|
||||
|
||||
tmp_dir="$(mktemp -d /tmp/nexusgate-install.XXXXXX)"
|
||||
trap 'rm -rf -- "$tmp_dir"' EXIT
|
||||
info "下载 ${REPO}@${BRANCH}"
|
||||
curl -fL --retry 3 "https://github.com/${REPO}/archive/refs/heads/${BRANCH}.tar.gz" -o "$tmp_dir/source.tgz"
|
||||
mkdir -p "$tmp_dir/source"
|
||||
tar -xzf "$tmp_dir/source.tgz" -C "$tmp_dir/source" --strip-components=1
|
||||
[[ -f "$tmp_dir/source/server.js" ]] || die "安装包不完整"
|
||||
# NG_LOCAL_SRC 指向本地源码目录时跳过 GitHub 下载(本地改动尚未 push 时的全新安装用这个)
|
||||
src_dir=""
|
||||
if [[ -n "${NG_LOCAL_SRC:-}" ]]; then
|
||||
[[ -f "${NG_LOCAL_SRC}/server.js" ]] || die "NG_LOCAL_SRC 目录不完整(缺少 server.js)"
|
||||
info "使用本地源码 ${NG_LOCAL_SRC}"
|
||||
src_dir="${NG_LOCAL_SRC}"
|
||||
else
|
||||
tmp_dir="$(mktemp -d /tmp/nexusgate-install.XXXXXX)"
|
||||
trap 'rm -rf -- "$tmp_dir"' EXIT
|
||||
info "下载 ${REPO}@${BRANCH}"
|
||||
curl -fL --retry 3 "https://github.com/${REPO}/archive/refs/heads/${BRANCH}.tar.gz" -o "$tmp_dir/source.tgz"
|
||||
mkdir -p "$tmp_dir/source"
|
||||
tar -xzf "$tmp_dir/source.tgz" -C "$tmp_dir/source"
|
||||
# GitHub tarball 解包后只有一层顶层目录;不用 --strip-components(BusyBox tar 不支持)
|
||||
src_dir="$(echo "$tmp_dir/source"/*/)"
|
||||
[[ -f "${src_dir}server.js" ]] || die "安装包不完整"
|
||||
fi
|
||||
|
||||
getent group nexusgate >/dev/null || groupadd --system nexusgate
|
||||
id nexusgate >/dev/null 2>&1 || useradd --system --gid nexusgate --home-dir /var/lib/nexusgate --shell /usr/sbin/nologin nexusgate
|
||||
install -d -o root -g root -m 0755 /opt/nexusgate
|
||||
cp -a "$tmp_dir/source/." /opt/nexusgate/
|
||||
install -d -o nexusgate -g nexusgate -m 0700 /var/lib/nexusgate
|
||||
if $IS_OPENWRT; then
|
||||
# OpenWrt 是单用户系统,不建专用账号;/var 是内存盘,数据必须放闪存
|
||||
DATA_DIR=/etc/nexusgate/data
|
||||
install -d -m 0755 /opt/nexusgate
|
||||
cp -a "${src_dir}/." /opt/nexusgate/
|
||||
install -d -m 0700 "$DATA_DIR"
|
||||
else
|
||||
DATA_DIR=/var/lib/nexusgate
|
||||
getent group nexusgate >/dev/null || groupadd --system nexusgate
|
||||
id nexusgate >/dev/null 2>&1 || useradd --system --gid nexusgate --home-dir /var/lib/nexusgate --shell /usr/sbin/nologin nexusgate
|
||||
install -d -o root -g root -m 0755 /opt/nexusgate
|
||||
cp -a "${src_dir}/." /opt/nexusgate/
|
||||
install -d -o nexusgate -g nexusgate -m 0700 "$DATA_DIR"
|
||||
fi
|
||||
|
||||
if $IS_OPENWRT && [[ -z "$DOMAIN" ]]; then
|
||||
# 无域名:直接监听内网(OpenWrt 防火墙默认拦截 WAN 入站),纯 HTTP
|
||||
NG_HOST=0.0.0.0
|
||||
NG_COOKIE_SECURE=false
|
||||
else
|
||||
NG_HOST=127.0.0.1
|
||||
NG_COOKIE_SECURE=true
|
||||
fi
|
||||
admin_password="$(openssl rand -base64 24 | tr -d '\n')"
|
||||
session_secret="$(openssl rand -hex 32)"
|
||||
cat > /etc/nexusgate.env <<EOF
|
||||
NODE_ENV=production
|
||||
NG_HOST=127.0.0.1
|
||||
NG_HOST=${NG_HOST}
|
||||
NG_PORT=8787
|
||||
NG_DATA_FILE=/var/lib/nexusgate/nexusgate.json
|
||||
NG_COOKIE_SECURE=true
|
||||
NG_DATA_FILE=${DATA_DIR}/nexusgate.json
|
||||
NG_COOKIE_SECURE=${NG_COOKIE_SECURE}
|
||||
NG_ADMIN_USERNAME=admin
|
||||
NG_ADMIN_PASSWORD=${admin_password}
|
||||
NG_SESSION_SECRET=${session_secret}
|
||||
EOF
|
||||
chmod 0600 /etc/nexusgate.env
|
||||
|
||||
install -m 0644 /opt/nexusgate/systemd/nexusgate.service /etc/systemd/system/nexusgate.service
|
||||
install -d -m 0755 /usr/local/sbin
|
||||
install -m 0755 /opt/nexusgate/scripts/nexusgate.sh /usr/local/sbin/nexusgate
|
||||
ln -sf /usr/local/sbin/nexusgate /usr/local/sbin/ng
|
||||
if $IS_OPENWRT; then
|
||||
install -m 0755 /opt/nexusgate/openrc/nexusgate /etc/init.d/nexusgate
|
||||
else
|
||||
install -m 0644 /opt/nexusgate/systemd/nexusgate.service /etc/systemd/system/nexusgate.service
|
||||
fi
|
||||
|
||||
if [[ -n "$DOMAIN" ]]; then
|
||||
install -d -m 0755 /etc/caddy/Caddyfile.d
|
||||
cat > /etc/caddy/Caddyfile.d/nexusgate.caddy <<EOF
|
||||
${DOMAIN} {
|
||||
@@ -96,23 +151,48 @@ fi
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile.d/nexusgate.caddy
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now nexusgate.service
|
||||
if $IS_OPENWRT; then
|
||||
/etc/init.d/nexusgate enable
|
||||
/etc/init.d/nexusgate start
|
||||
else
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now nexusgate.service
|
||||
fi
|
||||
healthy=false
|
||||
for _ in {1..30}; do
|
||||
if curl -fsS http://127.0.0.1:8787/healthz >/dev/null 2>&1; then healthy=true; break; fi
|
||||
sleep 1
|
||||
done
|
||||
if [[ "$healthy" != true ]]; then
|
||||
systemctl status nexusgate --no-pager || true
|
||||
journalctl -u nexusgate -n 100 --no-pager || true
|
||||
if $IS_OPENWRT; then
|
||||
logread 2>/dev/null | grep -a nexusgate | tail -n 100 || true
|
||||
else
|
||||
systemctl status nexusgate --no-pager || true
|
||||
journalctl -u nexusgate -n 100 --no-pager || true
|
||||
fi
|
||||
die "控制面启动失败,诊断信息已输出"
|
||||
fi
|
||||
if $IS_OPENWRT && [[ "$NG_HOST" == "0.0.0.0" ]] && ! netstat -tln 2>/dev/null | grep -q "0.0.0.0:8787"; then
|
||||
die "控制面未监听 0.0.0.0:8787(环境变量未正确传递),请检查 /etc/init.d/nexusgate"
|
||||
fi
|
||||
sed -i '/^NG_ADMIN_PASSWORD=/d' /etc/nexusgate.env
|
||||
systemctl enable --now caddy
|
||||
systemctl reload caddy
|
||||
if [[ -n "$DOMAIN" ]]; then
|
||||
if $IS_OPENWRT; then
|
||||
/etc/init.d/caddy enable
|
||||
/etc/init.d/caddy restart
|
||||
else
|
||||
systemctl enable --now caddy
|
||||
systemctl reload caddy
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\n\033[1;32m安装完成\033[0m\n'
|
||||
printf '地址:https://%s\n账号:admin\n密码:%s\n' "$DOMAIN" "$admin_password"
|
||||
if [[ -n "$DOMAIN" ]]; then
|
||||
printf '地址:https://%s\n' "$DOMAIN"
|
||||
else
|
||||
printf '地址:http://<路由器 LAN IP>:8787(内网访问)\n'
|
||||
fi
|
||||
printf '账号:admin\n密码:%s\n' "$admin_password"
|
||||
printf '管理命令:nexusgate 或 ng\n请立即登录并妥善保存密码。\n'
|
||||
|
||||
+58
-37
@@ -8,13 +8,25 @@ die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '\033[1;36m[NexusGate]\033[0m %s\n' "$*"; }
|
||||
need_root() { [[ "${EUID}" -eq 0 ]] || die "请使用 root 运行"; }
|
||||
|
||||
IS_OPENWRT=false
|
||||
[[ -f /etc/openwrt_release ]] && IS_OPENWRT=true
|
||||
if $IS_OPENWRT; then DATA_DIR=/etc/nexusgate/data; else DATA_DIR=/var/lib/nexusgate; fi
|
||||
|
||||
panel_stop() { if $IS_OPENWRT; then /etc/init.d/nexusgate stop; else systemctl stop nexusgate; fi }
|
||||
panel_start() { if $IS_OPENWRT; then /etc/init.d/nexusgate start; else systemctl start nexusgate; fi }
|
||||
panel_restart() { if $IS_OPENWRT; then /etc/init.d/nexusgate restart; else systemctl restart nexusgate; fi }
|
||||
panel_status() { if $IS_OPENWRT; then /etc/init.d/nexusgate status; else systemctl status nexusgate --no-pager; fi }
|
||||
panel_logs() { local n="${1:-120}"; if $IS_OPENWRT; then logread 2>/dev/null | grep -a nexusgate | tail -n "$n"; else journalctl -u nexusgate -n "$n" --no-pager; fi }
|
||||
caddy_reload() { if $IS_OPENWRT; then /etc/init.d/caddy reload; else systemctl reload caddy; fi }
|
||||
own_data() { if ! $IS_OPENWRT; then chown -R nexusgate:nexusgate "$DATA_DIR"; chmod 0700 "$DATA_DIR"; fi }
|
||||
|
||||
backup() {
|
||||
need_root
|
||||
local output="${1:-/root/nexusgate-backup-$(date +%Y%m%d-%H%M%S).tar.gz}"
|
||||
local stage
|
||||
stage="$(mktemp -d /tmp/nexusgate-backup.XXXXXX)"
|
||||
trap 'rm -rf -- "$stage"' RETURN
|
||||
cp -a /var/lib/nexusgate "$stage/data"
|
||||
cp -a "$DATA_DIR" "$stage/data"
|
||||
cp -a /etc/nexusgate.env "$stage/nexusgate.env"
|
||||
[[ -f /etc/caddy/Caddyfile.d/nexusgate.caddy ]] && cp -a /etc/caddy/Caddyfile.d/nexusgate.caddy "$stage/nexusgate.caddy"
|
||||
tar -czf "$output" -C "$stage" .
|
||||
@@ -35,15 +47,14 @@ restore() {
|
||||
[[ -f "$stage/data/nexusgate.json" && -f "$stage/nexusgate.env" ]] || die "备份包不完整"
|
||||
safety="/root/nexusgate-before-restore-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
backup "$safety"
|
||||
systemctl stop nexusgate
|
||||
find /var/lib/nexusgate -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
cp -a "$stage/data/." /var/lib/nexusgate/
|
||||
chown -R nexusgate:nexusgate /var/lib/nexusgate
|
||||
chmod 0700 /var/lib/nexusgate
|
||||
panel_stop
|
||||
find "$DATA_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
cp -a "$stage/data/." "$DATA_DIR/"
|
||||
own_data
|
||||
cp -a "$stage/nexusgate.env" /etc/nexusgate.env && chmod 0600 /etc/nexusgate.env
|
||||
if [[ -f "$stage/nexusgate.caddy" ]]; then cp -a "$stage/nexusgate.caddy" /etc/caddy/Caddyfile.d/nexusgate.caddy; fi
|
||||
systemctl start nexusgate
|
||||
systemctl reload caddy || true
|
||||
panel_start
|
||||
caddy_reload || true
|
||||
info "恢复完成;恢复前快照:$safety"
|
||||
}
|
||||
|
||||
@@ -58,22 +69,25 @@ update_panel() {
|
||||
curl -fL --retry 3 "https://github.com/${REPO}/archive/refs/heads/${BRANCH}.tar.gz" -o "$stage/source.tgz"
|
||||
mkdir -p "$stage/source" && tar -xzf "$stage/source.tgz" -C "$stage/source" --strip-components=1
|
||||
[[ -f "$stage/source/server.js" ]] || die "更新包不完整"
|
||||
systemctl stop nexusgate
|
||||
panel_stop
|
||||
cp -a "$stage/source/." /opt/nexusgate/
|
||||
install -m 0644 /opt/nexusgate/systemd/nexusgate.service /etc/systemd/system/nexusgate.service
|
||||
if $IS_OPENWRT; then
|
||||
install -m 0755 /opt/nexusgate/openrc/nexusgate /etc/init.d/nexusgate
|
||||
else
|
||||
install -m 0644 /opt/nexusgate/systemd/nexusgate.service /etc/systemd/system/nexusgate.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
install -m 0755 /opt/nexusgate/scripts/nexusgate.sh /usr/local/sbin/nexusgate
|
||||
chown -R nexusgate:nexusgate /var/lib/nexusgate
|
||||
chmod 0700 /var/lib/nexusgate
|
||||
[[ ! -f /var/lib/nexusgate/nexusgate.json ]] || chmod 0600 /var/lib/nexusgate/nexusgate.json
|
||||
systemctl daemon-reload && systemctl start nexusgate
|
||||
own_data
|
||||
[[ ! -f "$DATA_DIR/nexusgate.json" ]] || chmod 0600 "$DATA_DIR/nexusgate.json"
|
||||
panel_start
|
||||
local healthy=false
|
||||
for _ in {1..30}; do
|
||||
if curl -fsS http://127.0.0.1:8787/healthz >/dev/null 2>&1; then healthy=true; break; fi
|
||||
sleep 1
|
||||
done
|
||||
if [[ "$healthy" != true ]]; then
|
||||
systemctl status nexusgate --no-pager || true
|
||||
journalctl -u nexusgate -n 100 --no-pager || true
|
||||
panel_logs 35 || true
|
||||
die "更新后控制面启动失败;可使用上方备份恢复"
|
||||
fi
|
||||
if [[ -f /etc/nexusgate/agent.env ]]; then
|
||||
@@ -94,16 +108,16 @@ change_domain() {
|
||||
[[ "$domain" =~ ^[A-Za-z0-9.-]+$ ]] || die "域名格式不正确"
|
||||
sed -i -E "1s/^[^ ]+/${domain}/" /etc/caddy/Caddyfile.d/nexusgate.caddy
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile.d/nexusgate.caddy
|
||||
caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy
|
||||
caddy validate --config /etc/caddy/Caddyfile && caddy_reload
|
||||
info "域名已更新为 https://${domain}"
|
||||
}
|
||||
|
||||
certificate_status() {
|
||||
need_root
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
systemctl reload caddy
|
||||
caddy_reload
|
||||
info "Caddy 已校验并重新加载;证书会在到期前自动续签"
|
||||
journalctl -u caddy -n 30 --no-pager
|
||||
if $IS_OPENWRT; then logread 2>/dev/null | grep -a caddy | tail -n 30; else journalctl -u caddy -n 30 --no-pager; fi
|
||||
}
|
||||
|
||||
change_account() {
|
||||
@@ -119,18 +133,18 @@ change_account() {
|
||||
[[ "$first" == "$second" ]] || die "两次输入不一致"
|
||||
[[ -n "$next" || -n "$first" ]] || die "账号与密码都没有更改"
|
||||
[[ -z "$first" || ${#first} -ge 10 ]] || die "密码至少需要 10 个字符"
|
||||
systemctl stop nexusgate
|
||||
panel_stop
|
||||
set +e
|
||||
NG_DATA_FILE=/var/lib/nexusgate/nexusgate.json NG_NEW_USERNAME="$next" NG_NEW_PASSWORD="$first" node /opt/nexusgate/scripts/reset-password.js "$current"
|
||||
NG_DATA_FILE="$DATA_DIR/nexusgate.json" NG_NEW_USERNAME="$next" NG_NEW_PASSWORD="$first" node /opt/nexusgate/scripts/reset-password.js "$current"
|
||||
local result=$?
|
||||
unset first second
|
||||
set -e
|
||||
if [[ $result -eq 0 ]]; then
|
||||
chown nexusgate:nexusgate /var/lib/nexusgate/nexusgate.json
|
||||
chmod 0600 /var/lib/nexusgate/nexusgate.json
|
||||
if ! $IS_OPENWRT; then chown nexusgate:nexusgate "$DATA_DIR/nexusgate.json"; fi
|
||||
chmod 0600 "$DATA_DIR/nexusgate.json"
|
||||
sed -i '/^NG_ADMIN_PASSWORD=/d' /etc/nexusgate.env
|
||||
fi
|
||||
systemctl start nexusgate
|
||||
panel_start
|
||||
[[ $result -eq 0 ]] || die "管理员账号更新失败"
|
||||
info "管理员账号已更新,现有登录会话将在服务重启后失效"
|
||||
}
|
||||
@@ -142,13 +156,20 @@ uninstall_panel() {
|
||||
[[ "$confirm" == "UNINSTALL" ]] || die "已取消卸载"
|
||||
archive="/root/nexusgate-before-uninstall-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
backup "$archive"
|
||||
systemctl disable --now nexusgate.service >/dev/null 2>&1 || true
|
||||
rm -f -- /etc/systemd/system/nexusgate.service /etc/nexusgate.env /etc/caddy/Caddyfile.d/nexusgate.caddy /usr/local/sbin/ng /usr/local/sbin/nexusgate
|
||||
rm -rf -- /opt/nexusgate /var/lib/nexusgate
|
||||
systemctl daemon-reload
|
||||
systemctl reload caddy >/dev/null 2>&1 || true
|
||||
userdel nexusgate >/dev/null 2>&1 || true
|
||||
groupdel nexusgate >/dev/null 2>&1 || true
|
||||
if $IS_OPENWRT; then
|
||||
/etc/init.d/nexusgate stop >/dev/null 2>&1 || true
|
||||
/etc/init.d/nexusgate disable >/dev/null 2>&1 || true
|
||||
rm -f -- /etc/init.d/nexusgate
|
||||
else
|
||||
systemctl disable --now nexusgate.service >/dev/null 2>&1 || true
|
||||
rm -f -- /etc/systemd/system/nexusgate.service
|
||||
systemctl daemon-reload
|
||||
userdel nexusgate >/dev/null 2>&1 || true
|
||||
groupdel nexusgate >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f -- /etc/nexusgate.env /etc/caddy/Caddyfile.d/nexusgate.caddy /usr/local/sbin/ng /usr/local/sbin/nexusgate
|
||||
rm -rf -- /opt/nexusgate "$DATA_DIR"
|
||||
caddy_reload >/dev/null 2>&1 || true
|
||||
printf '\nNexusGate 已卸载。可恢复备份:%s\n' "$archive"
|
||||
}
|
||||
|
||||
@@ -164,9 +185,9 @@ menu() {
|
||||
4) backup ;;
|
||||
5) restore ;;
|
||||
6) change_account ;;
|
||||
7) systemctl status nexusgate --no-pager ;;
|
||||
8) need_root; systemctl restart nexusgate caddy; info '已重启' ;;
|
||||
9) journalctl -u nexusgate -n 120 --no-pager ;;
|
||||
7) panel_status ;;
|
||||
8) need_root; panel_restart; caddy_reload; info '已重启' ;;
|
||||
9) panel_logs 120 ;;
|
||||
10) uninstall_panel ;;
|
||||
0) exit 0 ;;
|
||||
*) die '无效选择' ;;
|
||||
@@ -174,9 +195,9 @@ menu() {
|
||||
}
|
||||
|
||||
case "${1:-menu}" in
|
||||
status) systemctl status nexusgate --no-pager ;;
|
||||
restart) need_root; systemctl restart nexusgate caddy ;;
|
||||
logs) journalctl -u nexusgate -n "${2:-120}" --no-pager ;;
|
||||
status) panel_status ;;
|
||||
restart) need_root; panel_restart; caddy_reload ;;
|
||||
logs) panel_logs "${2:-120}" ;;
|
||||
backup) backup "${2:-}" ;;
|
||||
restore) restore "${2:-}" ;;
|
||||
update) update_panel ;;
|
||||
|
||||
Reference in New Issue
Block a user