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