feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s

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.
This commit is contained in:
aaaamoooo
2026-10-20 08:21:54 +00:00
parent abb1a4344b
commit 3951134940
37 changed files with 12183 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
# 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(调试用)。