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:
Muse
2026-10-19 22:23:40 +08:00
parent 1678f766ac
commit 084afeb8f4
35 changed files with 2142 additions and 1749 deletions
-4
View File
@@ -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
View File
@@ -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
View File
@@ -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 检查原因'
+4 -7
View File
@@ -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
View File
@@ -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
-71
View File
@@ -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
View File
@@ -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
View File
@@ -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 ;;