Files
aaaamoooo 3951134940
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s
feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
Pure-stdlib Go implementation of the whole control plane: same
nexusgate.json schema (unknown fields preserved verbatim, explicit
null/false tri-states modeled), same HTTP API and error strings,
embedded frontend (NG_PUBLIC_DIR override), self-implemented scrypt
auth, X25519 Reality keys, panel-hosted keyStore and heartbeat
reconciliation included. Version stays 0.6.8.

Verified: Go tests 33/33 (scrypt + Reality vectors generated from
the Node implementation, subscription goldens byte-identical),
Node suite 52/52 untouched, differential E2E against the Node
panel 50/50 including two-way DB swap. See panel-go/PARITY.md
and panel-go/INSTALL-GO.md.
2026-10-20 08:21:54 +00:00

5.1 KiB
Raw Permalink Blame History

NexusGate 面板 Go 移植 — 行为对齐说明(PARITY)

Go 版(本目录)是 Node 版(../server.js + ../lib/)的 1:1 移植:同一 nexusgate.json、同一 HTTP API、同一前端(public/ 零改动,embed 进二进制)。 版本号保持 0.6.8(agent 的 AnyTLS 版本门禁按数字解析面板下发的版本串, 且 overview/healthz 的 version 字段与 Node 一致)。

已验证等价(2026-10-20,沙箱内)

  • Go 单元测试 33/33:含 scrypt 测试向量 6 组(由 Node 实现生成, testdata/scrypt_vectors.json,逐字节一致)、Reality 密钥向量 4 组 (固定私钥→公钥推导与 Node JWK 导出一致)、资源构建黄金深比较、 订阅 8 种格式黄金逐字节一致、store 事务/原子写/未知字段透传。
  • Node 测试 52/52:未改动任何 Node 文件,原样全绿。
  • 差分 E2E 正式模式 50/50(difftest/run.mjs):同一种子 DB(由 Node 面板自身业务流生成)分别起 Node/Go 面板,跑登录/CSRF、建服务器+注册 令牌、agent enroll→带 resources 心跳→对账重排→poll→complete、建节点+ 客户+分配→订阅全格式逐字比对、用量上报、节点删除/服务器遗忘、备份 比对、错误用例;随后交换 DB 文件互启对方实现,列表/订阅/写入均 正常,keyStore/jobs/用量不丢。
  • 三态字段已精确建模(差分首轮抓到并修复):nodes.autoRemoving (缺失 vs 显式 false)、servers.engine(缺失 vs 显式 null)、 customers.suspendReason(缺失 vs 显式 null)在 Go 版用 指针/presence 标志区分,加载、响应、写回均与 Node 一致。
  • 未知字段(其他版本写入的键)在记录级与顶层均透传保留,Node 写的 DB 由 Go 加载再写回不丢字段(store 测试 + 互通步骤双重覆盖)。

已知微偏差(均为边缘路径,不影响线上业务流)

  1. Cookie 解析:值含畸形 % 转义时 JS decodeURIComponent 抛错 (请求 500);Go 跳过该对。Go 的 PathUnescape 也不拒绝非法 UTF-8 字节序列(JS 会抛错)。
  2. VerifySecret 的 hex 解码:JS Buffer.from(hex,'hex') 对非法 hex 是截断容忍,Go 严格解码失败即 false;最终判定结果一致。
  3. 字符串截断口径:CleanText、错误截断(1800/240/80)、access 的 128/200 上限,Go 按 rune、JS 按 UTF-16 码元;仅星平面字符(emoji 等)恰在边界时可能差 1 字符。新密码 10 位长度校验同此口径。
  4. settings 缺失判定:Go 用值结构体,以「五字段全零且无 Extra」 近似 JS 的 !data.settings;真实写路径不会产生全零 settings, 不可达。
  5. 订阅信息页日期:Go 按存储值(UTC)以 2006/01/02 15:04:05 格式化;JS toLocaleString('zh-CN') 按服务器本地时区渲染且随 ICU 版本可能有差异(旧 ICU 带上午/下午标记)。百分比 %.1f 与 JS toFixed(1) 在 .x5 边界可能差 0.1。
  6. NG_DATA_FILE 默认值:Go 取可执行文件同目录 data/nexusgate.json, JS 取源码目录 __dirname/data;正式部署都显式设置该变量,无影响。 NG_PORT 不可解析时 Go 回退 8787,JS 为 NaN 监听失败。
  7. ReadJSON 边缘:body 为 null 时 Go 视为空对象,JS 返回 null 后在个别 handler 会 500;非对象 JSON 体 Go 返回 400,JS 各 handler 自行退化。路由参数解码失败时 Go 保留原始段,JS 抛错 500。
  8. JSString 近似:对象/数组入参 Go 返回 "",JS 为 "[object Object]"/逗号拼接;现有 handler 不可达。 OptionalISO 只接受 ParseISO 覆盖的格式,JS Date 还接受 2026/10/20 等写法(Go 返回 400)。
  9. DNS 失败文案:resolveBindAddress 报错括号内 Go 用解析器简述 (如 no such host),JS 用 getaddrinfo 的 code(如 ENOTFOUND); 仅 DNS 解析失败路径的文案细节不同。
  10. keyMaterial 回填遍历顺序:多 keyId 时 Go map 迭代序与 JS 对象序不同,audit 先后可能不同;各 key 相互独立,结果等价。
  11. JSON 键序:Go 响应/落盘的键序为结构体字段序(合并未知字段 时为字母序),JS 为插入序;JSON 语义等价,双方互读无碍(互通 步骤已验证)。
  12. HTTP 超时:Go ReadHeaderTimeout 15s + ReadTimeout 30s 近似 Node 的 headersTimeout/requestTimeout 语义。
  13. TLS 域名校验:RE2 不支持 lookahead,总长度 253 改在代码中 校验,接受/拒绝结果一致。
  14. 畸形请求建记录:节点创建缺 name 时 Go 存 "",JS 该键缺失; 服务器 labels 缺省 Go 为 [],JS 为 undefined;仅手工畸形请求 可见,前端正常请求不触发。

构建与产物

  • build.sh:从 ../public 刷新 embed_public 后 CGO_ENABLED=0 构建。
  • 交叉编译(发布):CGO_ENABLED=0 GOOS=linux GOARCH=amd64|arm64 go build -trimpath -ldflags="-s -w", 产物 nexusgate-panel-linux-{amd64,arm64},纯静态、无外部依赖。
  • 运行时可用 NG_PUBLIC_DIR 指定外部静态目录覆盖 embed(调试用)。