docs: add agent operations manual
This commit is contained in:
@@ -0,0 +1,649 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
本文件是 NetStable 项目的维护入口。任何自动化代理、Codex 会话或人工维护者在改动本仓库前都应先读这里。
|
||||||
|
|
||||||
|
## 基本原则
|
||||||
|
|
||||||
|
- 用最简单、直接、改动最少的实现,优先复用现有文件、函数和类型。
|
||||||
|
- 仅为当前实际需求或明显重复逻辑增加抽象,不为未来扩展提前设计。
|
||||||
|
- 替换实现时移除旧路径,避免新旧双轨。
|
||||||
|
- 不做顺手重构。除非它直接服务当前问题,否则不要改命名、目录结构、样式或部署流程。
|
||||||
|
- 不回滚用户或其他维护者的改动。遇到不相关的脏工作区,忽略;遇到相关冲突,先读懂再处理。
|
||||||
|
- 所有影响生产行为的改动都必须有验证证据:至少跑相关测试;改到公共路径时跑 `go test ./... -count=1`。
|
||||||
|
- 完成代码改动后,提交并对齐 Gitea,除非用户明确要求不要提交或不要推送。
|
||||||
|
|
||||||
|
## 项目概况
|
||||||
|
|
||||||
|
NetStable 是一个独立 Go Web/CLI 测速项目,用于测试用户浏览器或 CLI 到部署服务器的网络质量。
|
||||||
|
|
||||||
|
核心能力:
|
||||||
|
|
||||||
|
- 浏览器测速:用户打开网页后测试下载、上传、延迟和稳定性。
|
||||||
|
- CLI 测速:同一个二进制可作为客户端运行,支持 HTTP 和 `iperf3` 模式。
|
||||||
|
- 全局锁:同一时间只允许一个用户真正测速,其它用户进入 FIFO 队列。
|
||||||
|
- 半截记录:已经产生样本的中断/停止测试也必须保存为历史记录;还没轮到且没有样本的排队取消不生成空记录。
|
||||||
|
- 历史记录:完整样本曲线、摘要、脱敏 IP、地区、运营商写入 JSONL。
|
||||||
|
- 域名访问:生产主入口是 `https://speedtest.hzct.obscura.work/`。
|
||||||
|
|
||||||
|
## 仓库信息
|
||||||
|
|
||||||
|
- 本地路径:`/Users/kanshan/Documents/speedtest`
|
||||||
|
- Gitea 仓库:`https://git.aiot.ml/kanshan/speedtest`
|
||||||
|
- 远端名:`origin`
|
||||||
|
- 远端主分支:`main`
|
||||||
|
- 当前本地工作分支通常是:`codex/go-network-stability-tester`
|
||||||
|
- 推送当前 HEAD 到 Gitea 主分支:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin HEAD:main
|
||||||
|
```
|
||||||
|
|
||||||
|
提交前后检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short --branch
|
||||||
|
git log --oneline --decorate -5
|
||||||
|
git remote -v
|
||||||
|
git ls-remote origin refs/heads/main
|
||||||
|
```
|
||||||
|
|
||||||
|
不要提交 `dist/`、本地 JSONL 记录、日志或临时文件;这些已在 `.gitignore` 中排除。
|
||||||
|
|
||||||
|
## 代码结构
|
||||||
|
|
||||||
|
- `cmd/netstable/main.go`
|
||||||
|
- CLI 入口。
|
||||||
|
- 默认不带子命令时启动服务端。
|
||||||
|
- `serve` 启动 Web 服务。
|
||||||
|
- `client` 运行 HTTP/iperf3 客户端测速。
|
||||||
|
|
||||||
|
- `internal/web/server.go`
|
||||||
|
- HTTP API、网页静态资源、队列、全局锁、记录完成、下载/上传接口、iperf3 会话。
|
||||||
|
- 这是生产行为最关键的文件,修改前必须读相关测试。
|
||||||
|
|
||||||
|
- `internal/web/runner.go`
|
||||||
|
- 服务端 SSE/探测 runner。
|
||||||
|
|
||||||
|
- `internal/client/client.go`
|
||||||
|
- HTTP CLI 客户端。
|
||||||
|
- 负责创建会话、排队等待、下载/上传阶段、complete/cancel。
|
||||||
|
- 已产生样本后中断,必须走 partial complete,不能只 cancel。
|
||||||
|
|
||||||
|
- `internal/client/iperf3.go`
|
||||||
|
- CLI iperf3 模式。
|
||||||
|
- 服务端创建一次性高端口,客户端执行本机 `iperf3`,完成后回写记录。
|
||||||
|
|
||||||
|
- `internal/probe`
|
||||||
|
- TCP/HTTP 探测样本和摘要计算。
|
||||||
|
|
||||||
|
- `internal/store`
|
||||||
|
- JSONL 历史记录读写。
|
||||||
|
- 单条记录可能很大,读取端已提高到 64 MiB 单行上限。
|
||||||
|
|
||||||
|
- `internal/geo`
|
||||||
|
- IP 地区/运营商解析。
|
||||||
|
- 优先内置 `ip2region_v4.xdb`,再走在线 fallback。
|
||||||
|
|
||||||
|
- `internal/limit`
|
||||||
|
- 程序级带宽限速。
|
||||||
|
- 生产当前默认不限速。
|
||||||
|
|
||||||
|
- `web/static`
|
||||||
|
- 前端 HTML/CSS/JS。
|
||||||
|
- 一键 CLI 命令由 `web/static/app.js` 根据当前 `window.location.origin` 生成。
|
||||||
|
|
||||||
|
- `data/ip2region_v4.xdb`
|
||||||
|
- 内置 IPv4 数据库。
|
||||||
|
|
||||||
|
- `deploy/netstable.service`
|
||||||
|
- systemd 服务模板。
|
||||||
|
|
||||||
|
- `.gitea/workflows/ci.yml`
|
||||||
|
- Gitea Actions:测试、vet、release artifact。
|
||||||
|
|
||||||
|
## 关键业务约束
|
||||||
|
|
||||||
|
这些约束比局部代码风格更重要,不能破坏。
|
||||||
|
|
||||||
|
1. 同一时间只能有一个真正测速会话。
|
||||||
|
- `Server.activeID` 是全局锁。
|
||||||
|
- 普通 Web/CLI HTTP 测试和 iperf3 测试必须共用同一个锁。
|
||||||
|
- 其它请求必须排队,而不是并发跑测速。
|
||||||
|
|
||||||
|
2. 队列必须 FIFO。
|
||||||
|
- 新请求在有 active 会话时进入 `Server.queue`。
|
||||||
|
- active 完成、取消或过期后只提升队列头。
|
||||||
|
- 手动停止或 CLI 中断必须释放队列。
|
||||||
|
|
||||||
|
3. 已产生样本的半截测试必须保存。
|
||||||
|
- CLI HTTP 中断:如果 `samples` 非空,先 complete partial,再返回原始中断错误。
|
||||||
|
- 浏览器停止/刷新:如果 `state.samples.length > 0`,优先 complete partial。
|
||||||
|
- 还没轮到、没有样本的取消只释放队列,不写空记录。
|
||||||
|
|
||||||
|
4. complete 成功写入记录后才释放会话。
|
||||||
|
- 如果 `Store.Append` 失败,complete 应返回 500,并保留会话以便重试。
|
||||||
|
- 不能先 `takeSession` 再写盘,否则会丢记录。
|
||||||
|
|
||||||
|
5. 历史记录必须保护隐私。
|
||||||
|
- API 输出的 IP 必须脱敏。
|
||||||
|
- ISP 缺失时显示 `未知运营商`。
|
||||||
|
- 不要在前端、日志或 API 输出完整用户 IP,除非用户明确要求调试且范围受控。
|
||||||
|
|
||||||
|
6. 大样本记录必须可保存和读取。
|
||||||
|
- complete body 上限:64 MiB。
|
||||||
|
- JSONL 单行读取上限:64 MiB。
|
||||||
|
- 不要把限制改回 2 MiB 或默认 scanner 64 KiB。
|
||||||
|
|
||||||
|
7. 峰值口径不能用单次极短请求直接当最大值。
|
||||||
|
- 现有逻辑使用持续窗口计算 max,避免计时误差导致几百 Mbps 的假峰值。
|
||||||
|
|
||||||
|
8. 生产默认不限速。
|
||||||
|
- `-bandwidth-limit-mbps 0` 表示不限速。
|
||||||
|
- 如果用户要求限速,必须同时更新页面显示和服务启动参数。
|
||||||
|
|
||||||
|
## 常用开发命令
|
||||||
|
|
||||||
|
本地测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... -count=1
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
运行服务端:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/netstable serve -listen 127.0.0.1:18080 -data /tmp/netstable-records.jsonl -geo-base ''
|
||||||
|
```
|
||||||
|
|
||||||
|
运行 CLI HTTP 测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/netstable client -server http://127.0.0.1:18080 -duration 15
|
||||||
|
```
|
||||||
|
|
||||||
|
构建本机二进制:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
构建 Linux release 包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make release VERSION=v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
生成文件:
|
||||||
|
|
||||||
|
- `dist/netstable-v0.1.0-linux-amd64.tar.gz`
|
||||||
|
- `dist/netstable-v0.1.0-linux-arm64.tar.gz`
|
||||||
|
- `dist/checksums.txt`
|
||||||
|
|
||||||
|
更新 IP 数据库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make update-ip-db
|
||||||
|
```
|
||||||
|
|
||||||
|
更新数据库后必须检查 `THIRD_PARTY.md` 和 `LICENSES/ip2region-LICENSE.md` 是否仍然准确。
|
||||||
|
|
||||||
|
## 测试要求
|
||||||
|
|
||||||
|
最小规则:
|
||||||
|
|
||||||
|
- 修改 Go 行为:跑对应 package 测试,再跑 `go test ./... -count=1`。
|
||||||
|
- 修改公共服务端路径:同时跑 `go vet ./...`。
|
||||||
|
- 修改 release/deploy/CI:跑 `make release VERSION=v0.1.0`。
|
||||||
|
- 修改前端 JS:跑 `node --check web/static/app.js` 和 `go test ./web -count=1`。
|
||||||
|
- 修改生产相关配置:先本地校验配置,再线上验证接口。
|
||||||
|
|
||||||
|
重点回归测试名称:
|
||||||
|
|
||||||
|
- `TestCompleteEndpointAcceptsLargeSamplePayload`
|
||||||
|
- `TestCompleteEndpointCanRetryWhenPersistFails`
|
||||||
|
- `TestRunPersistsPartialRecordWhenCanceledAfterSamples`
|
||||||
|
- `TestRunCompletesBrowserCompatibleSpeedTest`
|
||||||
|
- `TestRunIperf3CompletesOneOffServerSession`
|
||||||
|
- `TestCancelActiveTestPromotesQueuedSession`
|
||||||
|
- `TestCancelQueuedTestRemovesItAndUpdatesPositions`
|
||||||
|
- `TestBrowserSummaryDoesNotTreatShortBurstsAsSustainedMax`
|
||||||
|
- `TestStoreListsLargeRecordsWithFullSampleCurves`
|
||||||
|
|
||||||
|
## API 速查
|
||||||
|
|
||||||
|
- `GET /`
|
||||||
|
- Web UI。
|
||||||
|
|
||||||
|
- `GET /api/config`
|
||||||
|
- 返回带宽限制标签。
|
||||||
|
|
||||||
|
- `GET /api/records?limit=100`
|
||||||
|
- 返回历史记录,输出前会脱敏 IP。
|
||||||
|
- `limit` 范围:1 到 1000。
|
||||||
|
|
||||||
|
- `GET /api/health/events?limit=1`
|
||||||
|
- SSE 心跳,用于页面连接状态。
|
||||||
|
|
||||||
|
- `POST /api/tests`
|
||||||
|
- 创建 HTTP/Web 测速会话。
|
||||||
|
- 返回 queue/download/upload/complete/cancel URL。
|
||||||
|
|
||||||
|
- `GET /api/tests/{id}/queue`
|
||||||
|
- 查询排队位置。
|
||||||
|
|
||||||
|
- `POST /api/tests/{id}/complete`
|
||||||
|
- 提交浏览器或 HTTP CLI 样本并写历史。
|
||||||
|
|
||||||
|
- `POST /api/tests/{id}/cancel`
|
||||||
|
- 取消还未产生样本的会话或释放队列。
|
||||||
|
|
||||||
|
- `GET /api/download?testId={id}&bytes={n}`
|
||||||
|
- 下载测速数据。
|
||||||
|
|
||||||
|
- `POST /api/upload?testId={id}`
|
||||||
|
- 上传测速数据。
|
||||||
|
|
||||||
|
- `POST /api/iperf3/sessions`
|
||||||
|
- 创建一次性 iperf3 服务端会话。
|
||||||
|
|
||||||
|
- `POST /api/iperf3/sessions/{id}/complete`
|
||||||
|
- 提交 iperf3 结果并写历史。
|
||||||
|
|
||||||
|
- `GET /downloads/netstable-linux-amd64.tar.gz`
|
||||||
|
- CLI 一键下载包。
|
||||||
|
|
||||||
|
- `GET /downloads/netstable-linux-arm64.tar.gz`
|
||||||
|
- CLI 一键下载包。
|
||||||
|
|
||||||
|
## 生产环境概览
|
||||||
|
|
||||||
|
最后核对时间:2026-09-15 Asia/Shanghai。
|
||||||
|
|
||||||
|
### 主入口
|
||||||
|
|
||||||
|
- URL:`https://speedtest.hzct.obscura.work/`
|
||||||
|
- HTTPS:Caddy 自动申请 Let's Encrypt 证书。
|
||||||
|
- 证书核对结果:
|
||||||
|
- Subject:`CN = speedtest.hzct.obscura.work`
|
||||||
|
- Issuer:`Let's Encrypt`
|
||||||
|
- 当前证书有效期:`2026-08-09` 到 `2026-11-07`
|
||||||
|
- HTTP 会 308 跳转 HTTPS。
|
||||||
|
|
||||||
|
### 生产节点
|
||||||
|
|
||||||
|
| 节点 | 用途 | 状态备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `103.46.93.244` | 主域名节点,Caddy 80/443 反代到本机 NetStable 18080 | HTTPS/API 正常;2026-09-15 检查时 SSH 连接被远端关闭,部署前先恢复 SSH |
|
||||||
|
| `110.42.44.35` | 直连备用节点,`http://110.42.44.35:18080/` | 2026-09-15 HTTP/API 正常,SSH 可用 |
|
||||||
|
| `103.46.93.38` | 历史直连节点,`http://103.46.93.38:18080/` | 2026-09-15 检查时 HTTP 空响应,SSH host key 变化;不要盲目信任或部署,必须先人工确认主机身份 |
|
||||||
|
|
||||||
|
### 主节点端口
|
||||||
|
|
||||||
|
`103.46.93.244` 预期监听:
|
||||||
|
|
||||||
|
- `:80` Caddy HTTP,自动跳转 HTTPS。
|
||||||
|
- `:443` Caddy HTTPS。
|
||||||
|
- `:18080` NetStable 服务。
|
||||||
|
- `127.0.0.1:2019` Caddy admin。
|
||||||
|
|
||||||
|
### 主节点 Caddy 配置
|
||||||
|
|
||||||
|
路径:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/etc/caddy/Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
关键站点块:
|
||||||
|
|
||||||
|
```caddyfile
|
||||||
|
speedtest.hzct.obscura.work {
|
||||||
|
reverse_proxy 127.0.0.1:18080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
最近已知备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/etc/caddy/Caddyfile.bak-20260610110050
|
||||||
|
```
|
||||||
|
|
||||||
|
修改 Caddy 后必须校验:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||||
|
caddy validate --config /etc/caddy/Caddyfile
|
||||||
|
systemctl reload caddy
|
||||||
|
systemctl is-active caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
验证 HTTPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS -I https://speedtest.hzct.obscura.work/
|
||||||
|
curl -fsS https://speedtest.hzct.obscura.work/api/config | jq .
|
||||||
|
echo | openssl s_client -servername speedtest.hzct.obscura.work -connect speedtest.hzct.obscura.work:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates
|
||||||
|
```
|
||||||
|
|
||||||
|
查看 Caddy 证书日志:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u caddy --no-pager -n 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd 服务
|
||||||
|
|
||||||
|
模板文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deploy/netstable.service
|
||||||
|
```
|
||||||
|
|
||||||
|
线上预期服务名:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
netstable.service
|
||||||
|
```
|
||||||
|
|
||||||
|
预期服务配置:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
ExecStart=/usr/local/bin/netstable -listen 0.0.0.0:18080 -data /var/lib/netstable/records.jsonl -downloads-dir /var/lib/netstable/downloads
|
||||||
|
```
|
||||||
|
|
||||||
|
常用命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl status netstable --no-pager -l
|
||||||
|
systemctl restart netstable
|
||||||
|
systemctl is-active netstable
|
||||||
|
journalctl -u netstable --no-pager -n 100
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 当前模板没有启用 `-trust-proxy-headers`。
|
||||||
|
- 如果要通过 Caddy 获取真实客户端 IP,必须同时满足:
|
||||||
|
- NetStable 只接受可信代理流量,或公网 `18080` 被防火墙挡住。
|
||||||
|
- 服务启动参数显式加 `-trust-proxy-headers`。
|
||||||
|
- 不要在 `18080` 仍公网直连时直接开启 `-trust-proxy-headers`,否则用户可伪造 `X-Forwarded-For`。
|
||||||
|
|
||||||
|
### 生产路径
|
||||||
|
|
||||||
|
- 二进制:`/usr/local/bin/netstable`
|
||||||
|
- 历史记录:`/var/lib/netstable/records.jsonl`
|
||||||
|
- CLI 下载包目录:`/var/lib/netstable/downloads`
|
||||||
|
- amd64 下载包:`/var/lib/netstable/downloads/netstable-linux-amd64.tar.gz`
|
||||||
|
- arm64 下载包:`/var/lib/netstable/downloads/netstable-linux-arm64.tar.gz`
|
||||||
|
|
||||||
|
## 发布和部署手册
|
||||||
|
|
||||||
|
### 1. 发布前检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short --branch
|
||||||
|
go test ./... -count=1
|
||||||
|
go vet ./...
|
||||||
|
make release VERSION=v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
确认生成包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh dist
|
||||||
|
cat dist/checksums.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 部署到可 SSH 节点
|
||||||
|
|
||||||
|
优先用 SSH 标准输入流上传。不要直接 `scp` 覆盖 `/usr/local/bin/netstable`,运行中的 Linux 二进制可能导致写入失败。
|
||||||
|
|
||||||
|
单节点部署模板:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
host=110.42.44.35
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 root@$host 'mkdir -p /var/lib/netstable/downloads'
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 root@$host 'cat > /tmp/netstable.new' < dist/netstable-v0.1.0-linux-amd64/netstable
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 root@$host 'cat > /var/lib/netstable/downloads/netstable-linux-amd64.tar.gz' < dist/netstable-v0.1.0-linux-amd64.tar.gz
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 root@$host 'cat > /var/lib/netstable/downloads/netstable-linux-arm64.tar.gz' < dist/netstable-v0.1.0-linux-arm64.tar.gz
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 root@$host 'install -m 0755 /tmp/netstable.new /usr/local/bin/netstable && rm -f /tmp/netstable.new && systemctl restart netstable && systemctl is-active netstable'
|
||||||
|
```
|
||||||
|
|
||||||
|
多节点部署前先确认每台 SSH 身份和服务状态,不要无脑批量推。
|
||||||
|
|
||||||
|
### 3. 部署后验证
|
||||||
|
|
||||||
|
本地哈希:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 dist/netstable-v0.1.0-linux-amd64/netstable
|
||||||
|
```
|
||||||
|
|
||||||
|
远端哈希:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@SERVER 'sha256sum /usr/local/bin/netstable'
|
||||||
|
```
|
||||||
|
|
||||||
|
直连节点检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS "http://SERVER:18080/api/config" | jq .
|
||||||
|
curl -fsS "http://SERVER:18080/api/records?limit=1" | jq .
|
||||||
|
curl -fsS -r 0-127 "http://SERVER:18080/downloads/netstable-linux-amd64.tar.gz" >/tmp/netstable-range.bin
|
||||||
|
wc -c /tmp/netstable-range.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
域名检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS -I https://speedtest.hzct.obscura.work/
|
||||||
|
curl -fsS https://speedtest.hzct.obscura.work/api/config | jq .
|
||||||
|
curl -fsS "https://speedtest.hzct.obscura.work/api/records?limit=1" | jq .
|
||||||
|
curl -fsS "https://speedtest.hzct.obscura.work/api/health/events?limit=1"
|
||||||
|
curl -fsS -r 0-127 "https://speedtest.hzct.obscura.work/downloads/netstable-linux-amd64.tar.gz" >/tmp/netstable-https-range.bin
|
||||||
|
wc -c /tmp/netstable-https-range.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 推送 Gitea
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short --branch
|
||||||
|
git add <changed-files>
|
||||||
|
git commit -m "<message>"
|
||||||
|
git push origin HEAD:main
|
||||||
|
git ls-remote origin refs/heads/main
|
||||||
|
```
|
||||||
|
|
||||||
|
如果用户只要求文档变更,也要提交并推送,除非用户明确不要。
|
||||||
|
|
||||||
|
## 回滚手册
|
||||||
|
|
||||||
|
### 代码回滚
|
||||||
|
|
||||||
|
优先回滚到上一个已知好提交,重新构建并部署。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log --oneline --decorate -10
|
||||||
|
git checkout <known-good-commit>
|
||||||
|
go test ./... -count=1
|
||||||
|
make release VERSION=v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
然后按“部署到可 SSH 节点”重新上传。
|
||||||
|
|
||||||
|
不要对用户工作区执行 `git reset --hard`,除非用户明确要求。
|
||||||
|
|
||||||
|
### Caddy 回滚
|
||||||
|
|
||||||
|
先找备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh /etc/caddy/Caddyfile.bak-*
|
||||||
|
```
|
||||||
|
|
||||||
|
恢复:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /etc/caddy/Caddyfile.bak-YYYYMMDDHHMMSS /etc/caddy/Caddyfile
|
||||||
|
caddy validate --config /etc/caddy/Caddyfile
|
||||||
|
systemctl reload caddy
|
||||||
|
systemctl is-active caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据回滚
|
||||||
|
|
||||||
|
历史记录是 append-only JSONL。修复坏记录时优先备份再处理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /var/lib/netstable/records.jsonl /var/lib/netstable/records.jsonl.bak-$(date +%Y%m%d%H%M%S)
|
||||||
|
```
|
||||||
|
|
||||||
|
不要直接清空生产记录,除非用户明确要求。
|
||||||
|
|
||||||
|
## 排障手册
|
||||||
|
|
||||||
|
### 记录接口 500:`failed to load records`
|
||||||
|
|
||||||
|
常见原因:
|
||||||
|
|
||||||
|
- JSONL 单行超过读取限制。
|
||||||
|
- 某行 JSON 损坏。
|
||||||
|
- 文件权限或磁盘问题。
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u netstable --no-pager -n 100
|
||||||
|
ls -lh /var/lib/netstable/records.jsonl
|
||||||
|
tail -n 3 /var/lib/netstable/records.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
本项目已经把单行读取上限提高到 64 MiB;不要改回默认 scanner 行为。
|
||||||
|
|
||||||
|
### complete 保存失败
|
||||||
|
|
||||||
|
原则:
|
||||||
|
|
||||||
|
- 写记录失败时,会话必须保留,客户端可以重试。
|
||||||
|
- 如果重试变成 404,说明 complete 路径又被改坏了。
|
||||||
|
|
||||||
|
优先跑:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/web -run TestCompleteEndpointCanRetryWhenPersistFails -count=1 -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户停止后队列不释放
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
- 浏览器停止时有没有调用 partial complete 或 cancel。
|
||||||
|
- CLI 中断时有没有调用 partial complete 或 cancel。
|
||||||
|
- `takeSession`、`finishSession`、`promoteNextLocked` 是否被绕过。
|
||||||
|
|
||||||
|
优先跑:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/client -run TestRunCancelsQueuedSessionWhenContextStops -count=1 -v
|
||||||
|
go test ./internal/web -run 'TestCancelActiveTestPromotesQueuedSession|TestCancelQueuedTestRemovesItAndUpdatesPositions' -count=1 -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 峰值速度明显虚高
|
||||||
|
|
||||||
|
不要直接相信单个样本的 `mbps`。检查持续吞吐统计:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/web -run 'TestBrowserSummaryUsesSustainedTransferRatesForMax|TestBrowserSummaryDoesNotTreatShortBurstsAsSustainedMax' -count=1 -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 域名 HTTPS 失败
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -I https://speedtest.hzct.obscura.work/
|
||||||
|
systemctl status caddy --no-pager -l
|
||||||
|
journalctl -u caddy --no-pager -n 100
|
||||||
|
caddy validate --config /etc/caddy/Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
如果证书过期或申请失败,先看 Caddy ACME 日志。不要手动复制不明来源证书覆盖 Caddy 管理目录。
|
||||||
|
|
||||||
|
### 直连 18080 正常但域名记录 IP 不准
|
||||||
|
|
||||||
|
原因通常是反向代理后没有启用可信代理头,或启用了但公网 18080 仍可被用户直连伪造。
|
||||||
|
|
||||||
|
正确处理顺序:
|
||||||
|
|
||||||
|
1. 先决定是否只允许通过 Caddy 访问主节点。
|
||||||
|
2. 用防火墙限制公网直接访问 18080,或让 NetStable 只监听 `127.0.0.1:18080`。
|
||||||
|
3. 再给服务加 `-trust-proxy-headers`。
|
||||||
|
4. 测试 IP 脱敏、ISP 和地区显示。
|
||||||
|
|
||||||
|
不要只加 `-trust-proxy-headers`。
|
||||||
|
|
||||||
|
### SSH host key 变化
|
||||||
|
|
||||||
|
`103.46.93.38` 在 2026-09-15 检查时出现 host key changed。处理方式:
|
||||||
|
|
||||||
|
1. 不要直接 `ssh-keygen -R` 后继续部署。
|
||||||
|
2. 先通过控制台或可信渠道确认服务器是否重装、迁移或 IP 复用。
|
||||||
|
3. 确认无误后再更新 `~/.ssh/known_hosts`。
|
||||||
|
4. 更新后先只跑只读检查,再部署。
|
||||||
|
|
||||||
|
## 安全和隐私
|
||||||
|
|
||||||
|
- 不要把完整用户 IP 写到公开 API 响应。
|
||||||
|
- 不要把 Gitea token、密码、SSH 私钥写入仓库、日志或最终回复。
|
||||||
|
- Gitea 凭据由本机 Git credential helper 管理;不要打印明文。
|
||||||
|
- 生产记录包含用户网络信息,下载或复制前先确认用途。
|
||||||
|
- 对外开放的下载文件名必须限制在已知二进制包名,不能做任意文件下载。
|
||||||
|
|
||||||
|
## CI 和 Release
|
||||||
|
|
||||||
|
Gitea Actions 文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.gitea/workflows/ci.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
当前行为:
|
||||||
|
|
||||||
|
- push 到 `main`、`master`、`codex/**`:运行测试和 vet。
|
||||||
|
- pull request:运行测试和 vet。
|
||||||
|
- tag `v*` 或手动触发:构建 release tar.gz 和 checksums,并上传 artifact。
|
||||||
|
|
||||||
|
如果改 Makefile、release 包内容或运行时依赖,必须同步检查 CI。
|
||||||
|
|
||||||
|
## 最终交付检查清单
|
||||||
|
|
||||||
|
代码或文档改动完成后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short --branch
|
||||||
|
go test ./... -count=1
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
按改动类型补充:
|
||||||
|
|
||||||
|
- 前端 JS:`node --check web/static/app.js`
|
||||||
|
- release:`make release VERSION=v0.1.0`
|
||||||
|
- 域名/证书:`curl -I https://speedtest.hzct.obscura.work/`
|
||||||
|
|
||||||
|
然后提交并推送:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add <changed-files>
|
||||||
|
git commit -m "<message>"
|
||||||
|
git push origin HEAD:main
|
||||||
|
```
|
||||||
|
|
||||||
|
最终回复必须说明:
|
||||||
|
|
||||||
|
- 改了什么。
|
||||||
|
- 跑了哪些验证。
|
||||||
|
- 是否已提交和推送 Gitea。
|
||||||
|
- 如涉及生产,列出线上验证结果或明确说明未部署。
|
||||||
Reference in New Issue
Block a user