docs: add detailed agent and production operations guide
CI / Frontend test and build (push) Successful in 1m21s
CI / Rust test (push) Failing after 2m14s
CI / Docker build (push) Skipped

This commit is contained in:
Test User
2026-09-14 23:57:47 +08:00
parent 1b12ad8451
commit 9854b1a61c
+754
View File
@@ -0,0 +1,754 @@
# AGENTS.md
本文件是本仓库的开发代理规范、系统事实说明和生产环境运维手册。任何自动化代理、维护人员或新加入的开发者在修改代码、发布版本或处理故障前,都应先阅读本文件。
## 1. 项目定位
项目名称:浏览器暴露画像(Browser Fingerprint)
仓库:`https://git.aiot.ml/kanshan/browser-fingerprint`
生产目标主机:`92.119.167.24`
项目目标是让用户直观看到普通网页、后端请求、浏览器授权和网络诊断分别能够观察到哪些信息,并把用户明确选择共享的脱敏结果用于个人报告和聚合统计。项目不是隐蔽追踪器,也不应被改造成绕过浏览器安全模型、静默扫描局域网设备或收集可直接识别个人的数据库。
当前技术栈:
- 前端:Vite + React 19 + TanStack Router/Query,构建结果为静态文件。
- 后端:Rust + Tokio + Axum + SQLx SQLite。
- 运行方式:一个 Rust 进程同时提供 API 和前端静态文件。
- 数据库:SQLite,启动时执行 `backend/schema.sql` 中的幂等建表语句。
- 网络画像:服务端请求头、连接信息、WebRTC 候选分类摘要、Network Information API 和本地 MMDB 查询。
- 部署:Docker 多阶段构建 + Docker Compose,当前 VPS 目标为 `92.119.167.24`。
- CI/CD:Gitea Actions,配置在 `.gitea/workflows/`。
## 2. 最高优先级规则
### 2.1 修改原则
- 先阅读相关模块、测试和现有命令,再修改。
- 用最简单、直接、改动最少的实现,优先复用现有函数、类型和数据结构。
- 不为未来需求提前增加抽象,不保留已经替换掉的旧路径。
- 不覆盖或回退用户已有的未提交改动;发现同一文件存在外部改动时,先理解后再合并。
- 手工编辑使用 `apply_patch`;不要用 `cat >`、重定向或 Python 临时脚本写源码文件。
- 默认使用 ASCII;项目已有中文文案时,中文是合理的业务内容,可以继续使用。
- 不提交构建产物、数据库文件、私钥、访问令牌、`.env` 或本机配置。
- 任何会影响数据结构、公开接口、部署方式或隐私边界的改动,都必须同时更新本文件、README 或相应测试。
### 2.2 数据与隐私边界
这些边界是产品功能的一部分,不是可随意删除的说明文案:
- 第一阶段只在浏览器本地生成初步画像,不调用后端,不发送检测数据。
- 查询权限状态本身不应触发授权弹窗;定位、摄像头、麦克风等权限只能在明确的用户操作后请求。
- 普通网页不能静默扫描周边 Wi-Fi,也不能读取 SSID、BSSID、信号强度或 AP 列表。
- 普通网页不能后台静默扫描全部蓝牙广播;Web Bluetooth 只能在浏览器支持且用户触发设备选择器的授权模型内使用。
- WebRTC 只记录候选数量、类型和地址类别等脱敏摘要,不向后端提交精确候选地址。
- `/api/network/probe` 返回当前请求的服务端可见信息给当前访客,但该接口本身不写入数据库。
- 用户开启“共享匿名画像”后,前端才会把脱敏画像摘要、网络摘要、透明返回钥匙和指纹组件哈希发送到 `/api/scan`。
- 不把原始 User-Agent、精确 WebRTC 地址、定位坐标、摄像头/麦克风内容或原始高熵指纹值写入统计数据。
- 指纹 ID 是用于用户自查和比较的透明哈希标识,不是账号、实名身份或跨站跟踪凭证。
- 统计页面只展示聚合数据。新增统计维度前必须确认不会反推出单个访客。
- 不增加绕过权限、欺骗授权、隐藏采集、设备探测、局域网扫描或第三方个人信息反查功能。
如果需求与上述边界冲突,应暂停实现并把冲突写清楚,而不是用隐蔽实现满足需求。
## 3. 仓库结构
```text
.
├── AGENTS.md # 本文件:开发规范与生产运维手册
├── README.md # 面向使用者的项目说明
├── compose.yml # 生产 Docker Compose
├── backend/
│ ├── Cargo.toml # Rust 包定义
│ ├── Cargo.lock # Rust 锁定依赖
│ ├── Dockerfile # 前端构建、Rust 编译和运行镜像
│ ├── schema.sql # SQLite 初始化结构
│ ├── data/ # 本地 GeoIP/ASN MMDB 与许可说明
│ ├── src/
│ │ ├── main.rs # 启动、配置、监听、优雅退出
│ │ ├── lib.rs # 模块导出
│ │ ├── app.rs # Axum 路由、请求头和输入校验
│ │ ├── db.rs # SQLite 连接、写入和统计查询
│ │ ├── geoip.rs # 本地 MMDB 查询
│ │ └── models.rs # API 数据模型
│ └── tests/api.rs # 后端 API 集成测试
├── frontend/
│ ├── package.json # pnpm 脚本和依赖
│ ├── pnpm-lock.yaml # 前端锁定依赖
│ ├── vite.config.js # 开发服务器和 API 代理
│ ├── src/main.jsx # 页面入口和交互编排
│ ├── src/i18n.mjs # 中英文文案
│ ├── src/superFingerprint.mjs # 本地/高级/授权画像采集
│ ├── src/network.mjs # WebRTC、网络诊断和脱敏
│ ├── src/tracking.mjs # 透明指纹 ID 和复访比较
│ ├── src/analytics.mjs # 统计数据整形
│ └── test/ # Node 原生测试
├── scripts/
│ ├── deploy-vps.sh # 上传代码并滚动切换 VPS 版本
│ ├── download-geoip-mmdb.sh # 下载本地 GeoIP/ASN 数据
│ ├── package-release.sh # 构建运行包和可选 Docker 镜像
│ └── publish-gitea-release.sh # 上传 Gitea Release 资产
├── docs/release.md # CI/Release 简要说明
└── .gitea/workflows/
├── ci.yml # 前端、后端、Docker 构建检查
└── release.yml # v* tag 发布
```
## 4. 本地开发
### 4.1 前置环境
- Node.js 24 或兼容版本。
- Corepack 和 pnpm 11.7.0。
- Rust stable;Docker 构建使用 Rust 1.93 和 Node 24。
- 本地运行后端不需要外部 IP 查询服务;MMDB 文件位于 `backend/data/`。
依赖安装:
```bash
cd frontend
corepack enable
corepack prepare [email protected] --activate
pnpm install --frozen-lockfile
```
### 4.2 开发模式
终端一:
```bash
cd backend
cargo run
```
终端二:
```bash
cd frontend
pnpm dev
```
访问 `http://127.0.0.1:5173/`。Vite 会将 `/api` 和 `/health` 代理到 `http://127.0.0.1:8787`。
### 4.3 单进程预览
先构建前端,再启动 Rust:
```bash
cd frontend
pnpm install --frozen-lockfile
pnpm test
pnpm build
cd ../backend
cargo run
```
访问 `http://127.0.0.1:8787/`。
### 4.4 环境变量
| 变量 | 默认值 | 作用 |
| --- | --- | --- |
| `BIND_ADDR` | `0.0.0.0:8787` | Rust 监听地址和端口 |
| `DATABASE_URL` | `sqlite://fingerprints.db` | SQLite 连接地址;生产 Compose 为 `sqlite:///data/fingerprints.db` |
| `ASSETS_DIR` | 自动查找 `frontend/dist`、`../frontend/dist`、`dist` | 前端静态文件目录 |
| `GEOIP_CITY_DB` | 自动查找 `backend/data/dbip-city-ipv4.mmdb` 和 IPv6 文件 | 城市级 IP 数据库,可指定单个文件或由程序按默认路径查找 |
| `GEOIP_ASN_DB` | `backend/data/origin-asn.mmdb` | ASN/ISP 数据库 |
| `RUST_LOG` | `info` | `tracing_subscriber` 日志过滤器 |
部署脚本变量:
| 变量 | 默认值 | 作用 |
| --- | --- | --- |
| `DEPLOY_HOST` | `92.119.167.24` | VPS 地址 |
| `DEPLOY_USER` | `root` | SSH 用户;应优先使用受限部署用户 |
| `DEPLOY_PORT` | `22` | SSH 端口 |
| `DEPLOY_DIR` | `/opt/browser-fingerprint` | 远端发布目录 |
| `DEPLOY_KEY` | 自动尝试 `~/.ssh/termius_deploy_ed25519` | SSH 私钥路径,仅本机使用 |
| `APP_PORT` | `80` | VPS 对外端口,映射到容器 8787 |
| `APP_DATA` | `/opt/browser-fingerprint/data` | SQLite 数据目录 |
不要把令牌放在 shell 历史、仓库文件或日志中。 `GITEA_TOKEN` 只作为临时环境变量传入发布命令或由 Gitea Actions Secret 提供。
## 5. 测试、构建与变更验证
### 5.1 必跑检查
前端:
```bash
cd frontend
pnpm install --frozen-lockfile
pnpm test
pnpm build
```
后端:
```bash
cd backend
cargo fmt --check
cargo test --locked
cargo build --release --locked
```
仓库级差异检查:
```bash
git diff --check
git status --short --branch --ignore-submodules=dirty
```
涉及 Dockerfile、Compose 或运行时依赖时,再执行:
```bash
docker build -f backend/Dockerfile -t browser-fingerprint:local .
```
### 5.2 手工验收
启动后检查:
```bash
curl -fsS http://127.0.0.1:8787/health
curl -fsS http://127.0.0.1:8787/api/stats
curl -fsS http://127.0.0.1:8787/api/analytics
curl -fsS http://127.0.0.1:8787/api/network/probe
```
浏览器验收至少覆盖:
1. 首次打开页面能自动生成本地阶段画像,网络面板没有发生后端检测请求。
2. 取消共享选项后,检测结果仍能显示,但不会调用写入接口。
3. 开启共享后,`POST /api/scan` 成功时显示后端确认和复访比较结果。
4. 第二次使用同一透明 ID 时,复访次数、指纹变化和组件相似度显示合理。
5. 定位、摄像头和麦克风只在对应用户操作后进入授权流程。
6. 中文和英文切换后,阶段标题、统计标签、错误状态、授权状态和网络限制说明都已翻译。
7. WebRTC 不把候选原始地址放入发送到后端的 payload。
### 5.3 修改数据结构时
当前后端启动时把 `backend/schema.sql` 按分号拆分并执行,只有 `CREATE TABLE IF NOT EXISTS` 和索引创建这类幂等初始化。当前没有版本化 migration 表,也不会自动为已有表添加新列。
因此,涉及 SQLite 表结构时必须:
- 先备份生产数据库。
- 更新 `backend/schema.sql` 和后端模型/查询。
- 为新行为增加 `backend/tests/api.rs` 测试。
- 在干净数据库和包含旧数据的数据库上分别启动验证。
- 如需修改已有表,提供明确的人工 SQL 迁移和回滚步骤,不要假设重启会自动完成迁移。
## 6. 数据流与 API 事实
### 6.1 阶段流
| 阶段 | 浏览器行为 | 网络行为 | 是否落库 |
| --- | --- | --- | --- |
| 本地初步画像 | 语言、时区、平台、UA 摘要、屏幕、硬件档位、字体包、Cookie/存储能力、显示偏好、权限状态等 | 不请求后端 | 否 |
| 网络诊断 | 请求服务端可见连接与请求头,执行 WebRTC 摘要和连接 API 读取 | `GET /api/network/probe` | 该接口不落库 |
| 共享匿名画像 | 将已生成的阶段摘要、脱敏网络信号和哈希指纹发送 | `POST /api/scan` | 是,写入 SQLite |
| 用户授权画像 | 用户触发后请求定位或媒体权限,并显示结果 | 由浏览器授权模型决定 | 只有用户开启共享时才随扫描摘要发送 |
### 6.2 路由
#### `GET /health`
返回:
```json
{"ok":true}
```
该接口只表示进程可响应,不代表数据库可写、GeoIP 文件完整、磁盘空间充足或 Cloudflare 配置正确。生产探活应同时检查日志和数据库写入能力。
#### `GET /api/network/probe`
返回服务端当前看到的连接地址、代理头、Cloudflare 头、主机/协议、`geoIp` 本地 MMDB 查询结果,并明确返回 `stored: false`。该接口不把这些值插入 `scan_events`。
注意:当前实现按 `cf-connecting-ip`、`x-real-ip`、`x-forwarded-for`、`forwarded` 优先取来源地址。如果应用端口直接暴露给公网,任意客户端可以伪造这些头,影响本次页面显示的 GeoIP 结果。上生产时必须限制源站入口为可信反向代理,或在代码中增加可信代理来源校验。
#### `POST /api/scan`
接收检测结果并写入:
- `scan_events`:分数、风险带、语言、时区、UA 家族和信号 JSON。
- `signal_hits`:信号 ID、贡献度、风险判断和已经脱敏的 `raw` 摘要。
- `fingerprint_visits`:透明访客钥匙、指纹哈希、组件哈希 JSON 和组件数量。
后端校验分数、风险带、文本长度、信号数量、信号 ID、权重、数值范围、指纹哈希和组件数量。客户端输入不可视为可信身份数据;统计接口应假设请求可以被伪造。
#### `GET /api/stats`
返回扫描总次数、平均分、风险带分布和信号排行。
#### `GET /api/analytics`
返回公开统计看板使用的聚合数据,包括分数区间、语言、时区、时区偏移、UA 家族、阶段、类别、敏感度、网络暴露和连接类型分布。
当前 `/api/stats` 和 `/api/analytics` 没有登录鉴权。它们只应返回聚合数据;如未来加入单访客明细、原始地址或导出功能,必须先增加鉴权、权限分级、审计和脱敏。
### 6.3 数据库表
`backend/schema.sql` 当前包含:
- `scan_events`:一次画像扫描的主记录。
- `signal_hits`:扫描命中的信号明细,通过外键级联删除。
- `fingerprint_visits`:透明指纹复访比较记录,通过外键关联扫描事件。
数据库文件默认是 `fingerprints.db`;Docker 生产路径是 `/data/fingerprints.db`。仓库忽略数据库文件,不能把生产库复制进 Git。
## 7. GeoIP/ASN 数据库
`backend/data/` 的 MMDB 来自 `sapics/ip-location-db` Releases:
- `dbip-city-ipv4.mmdb`、`dbip-city-ipv6.mmdb`:DB-IP Lite 城市数据,CC BY 4.0。
- `origin-asn.mmdb`:origin ASN 数据,PDDL。
更新命令:
```bash
./scripts/download-geoip-mmdb.sh
```
更新后检查:
```bash
ls -lh backend/data/*.mmdb
git diff --stat
git diff --check
```
更新 MMDB 属于可重复的二进制数据更新,应记录来源和时间,并确认许可证没有变化。应用启动时如果文件缺失,服务仍可启动,但相关 GeoIP 字段会显示不可用;发布前必须确认镜像内包含这些文件。
## 8. Docker 与发布资产
### 8.1 Dockerfile 事实
`backend/Dockerfile` 是三阶段构建:
1. Node 24 构建前端 `frontend/dist`。
2. Rust 1.93 编译 `fingerprint_receiver`。
3. Debian Trixie slim 运行单个 Rust 二进制,并复制前端文件和本地 MMDB。
运行时默认值:
```text
BIND_ADDR=0.0.0.0:8787
DATABASE_URL=sqlite:///data/fingerprints.db
ASSETS_DIR=/app/frontend/dist
```
### 8.2 Compose
`compose.yml` 的服务名为 `app`,项目名约定为 `browser-fingerprint`:
- 容器端口:`8787`。
- 默认宿主端口:`80`,可用 `APP_PORT` 覆盖。
- 默认数据目录:`/opt/browser-fingerprint/data`,可用 `APP_DATA` 覆盖。
- 重启策略:`unless-stopped`。
- SQLite 不放在容器可写层,必须使用数据卷。
本地 Docker 启动:
```bash
APP_PORT=8787 APP_DATA="$PWD/data" docker compose -p browser-fingerprint up -d --build
docker compose -p browser-fingerprint ps
curl -fsS http://127.0.0.1:8787/health
```
### 8.3 本地打包
```bash
VERSION=v0.1.0 BUILD_DOCKER=1 ./scripts/package-release.sh
```
资产写入 `release/`,包括运行包、源码包和可选的 Docker 镜像包。脚本会清理并重建仓库内的 `release/` 目录;不要在该目录存放未备份的手工文件。
### 8.4 Gitea Release
推送 `v*` tag 会触发 `.gitea/workflows/release.yml`:
1. 安装并测试前端。
2. 格式检查、测试并编译 Rust。
3. 打包运行时、源码、schema、静态文件和本地 MMDB。
4. 创建或复用同名 Gitea Release。
5. 上传 `release/*` 资产。
手动发布:
```bash
GITEA_TOKEN=... \
GITEA_API_URL=https://git.aiot.ml/api/v1 \
GITEA_REPOSITORY=kanshan/browser-fingerprint \
RELEASE_TAG=v0.1.0 \
./scripts/publish-gitea-release.sh release/*
```
正式发布前先确认 `git status` 干净、CI 通过、tag 指向目标提交、Release 资产能够下载,并且没有把令牌写入命令日志或提交历史。
## 9. 生产部署手册:VPS
### 9.1 生产目录布局
部署脚本使用以下结构:
```text
/opt/browser-fingerprint/
├── current -> releases/YYYYMMDDHHMMSS/
├── releases/
│ ├── 20260704120000/
│ └── 20260705100000/
└── data/
└── fingerprints.db
```
发布目录保存代码和构建上下文,`data/` 独立保存数据库。部署脚本不会上传本地 `.git`、`target`、`node_modules`、前端 `dist` 和本地数据库。
### 9.2 上线前检查
在本机执行:
```bash
git status --short --branch --ignore-submodules=dirty
git log -1 --oneline
./scripts/download-geoip-mmdb.sh # 只有需要刷新数据库时执行
cd frontend && pnpm install --frozen-lockfile && pnpm test && pnpm build
cd ../backend && cargo fmt --check && cargo test --locked
cd .. && docker build -f backend/Dockerfile -t browser-fingerprint:preflight .
```
在 VPS 执行或通过 SSH 检查:
```bash
ssh -p 22 [email protected] 'docker --version && docker compose version && df -h && free -h'
```
上线前必须确认:
- SSH 私钥权限正确,且没有把私钥复制到仓库。
- Docker Engine 和 Compose 插件可用。
- `/opt/browser-fingerprint/data` 有足够空间,且数据库备份策略已建立。
- 防火墙只开放必要的 SSH、HTTP/HTTPS 端口。
- 如果使用 Cloudflare,源站端口不应允许任意来源伪造代理请求头。
### 9.3 标准部署
从仓库根目录执行:
```bash
./scripts/deploy-vps.sh
```
自定义 SSH 参数:
```bash
DEPLOY_USER=deploy \
DEPLOY_PORT=22 \
DEPLOY_KEY="$HOME/.ssh/termius_deploy_ed25519" \
./scripts/deploy-vps.sh
```
脚本步骤:
1. 通过 SSH 检查 Docker 和 Compose。
2. 创建新的时间戳发布目录和数据目录。
3. 使用 tar 上传源代码和部署文件。
4. 将 `current` 原子切换到新发布目录。
5. 用新目录执行 `docker compose -p browser-fingerprint up -d --build`。
6. 输出容器状态和 HTTP 地址。
### 9.4 发布后验收
本机或 VPS 执行:
```bash
curl -fsS http://92.119.167.24/health
curl -fsS http://92.119.167.24/api/stats
curl -fsS http://92.119.167.24/api/analytics
```
远端检查:
```bash
ssh [email protected] 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint ps'
ssh [email protected] 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint logs --tail=200 app'
ssh [email protected] 'ls -lh /opt/browser-fingerprint/data/fingerprints.db'
```
浏览器验收首页、静态资源、中文文案、网络诊断、共享开关和公开统计看板。只看到 `health` 成功不能替代完整验收。
### 9.5 查看状态、日志和资源
```bash
cd /opt/browser-fingerprint/current
docker compose -p browser-fingerprint ps
docker compose -p browser-fingerprint logs --tail=200 app
docker compose -p browser-fingerprint logs -f app
docker stats --no-stream
df -h /opt/browser-fingerprint
du -sh /opt/browser-fingerprint/data /opt/browser-fingerprint/releases/*
```
默认日志级别为 `info`。临时增加日志可以在 Compose 环境中设置 `RUST_LOG` 后重建容器;不要在日志中打印完整请求头、指纹原文、定位数据或访问令牌。
### 9.6 备份 SQLite
当前仓库没有自动备份任务,生产环境必须由运维系统补充。最低限度的人工备份流程:
```bash
DEPLOY_DIR=/opt/browser-fingerprint
STAMP=$(date +%Y%m%d%H%M%S)
ssh [email protected] "cd ${DEPLOY_DIR}/current && docker compose -p browser-fingerprint stop app && cp ${DEPLOY_DIR}/data/fingerprints.db ${DEPLOY_DIR}/data/fingerprints.db.${STAMP}.bak && docker compose -p browser-fingerprint start app"
```
更稳妥的生产策略:
- 每日备份到 VPS 之外的受控存储。
- 保留最近 7 个日备份和最近 4 个周备份,按组织的合规要求调整。
- 对备份文件设置严格权限,不把数据库备份暴露在 Web 根目录。
- 定期在临时目录验证备份可读,不能只看文件存在。
- 记录备份时间、文件大小、校验和和恢复演练结果。
如果主机安装了 `sqlite3`,可在停服后验证:
```bash
sqlite3 /opt/browser-fingerprint/data/fingerprints.db 'PRAGMA integrity_check;'
```
期望返回 `ok`。备份完成后再启动服务,确认 `/health` 和一次测试写入均成功。
### 9.7 恢复数据库
恢复是有影响的操作,先确认备份文件路径并保留当前库副本:
```bash
DEPLOY_DIR=/opt/browser-fingerprint
BACKUP=/opt/browser-fingerprint/data/fingerprints.db.YYYYMMDDHHMMSS.bak
STAMP=$(date +%Y%m%d%H%M%S)
ssh [email protected] "cd ${DEPLOY_DIR}/current && docker compose -p browser-fingerprint stop app && cp ${DEPLOY_DIR}/data/fingerprints.db ${DEPLOY_DIR}/data/fingerprints.db.before-restore.${STAMP}.bak && cp ${BACKUP} ${DEPLOY_DIR}/data/fingerprints.db && docker compose -p browser-fingerprint start app"
```
恢复后检查完整性、表数量、统计接口和浏览器写入流程。不要删除恢复前的数据库,至少保留到恢复验证完成。
### 9.8 回滚应用版本
先列出已有发布:
```bash
ssh [email protected] 'ls -1dt /opt/browser-fingerprint/releases/*'
```
将 `current` 指向上一个已验证发布,然后重建容器:
```bash
ssh [email protected] 'set -eu
DEPLOY_DIR=/opt/browser-fingerprint
ROLLBACK_RELEASE=$(ls -1dt "$DEPLOY_DIR"/releases/* | sed -n "2p")
test -n "$ROLLBACK_RELEASE"
ln -sfn "$ROLLBACK_RELEASE" "$DEPLOY_DIR/current"
cd "$DEPLOY_DIR/current"
APP_PORT=80 APP_DATA="$DEPLOY_DIR/data" docker compose -p browser-fingerprint up -d --build
docker compose -p browser-fingerprint ps
curl -fsS http://127.0.0.1/health'
```
如果新版本修改了数据库结构,应用回滚不一定能回滚数据库。必须先按发布记录决定是否恢复数据库备份;不要在没有备份的情况下尝试破坏性 SQL。
### 9.9 清理旧发布
发布目录会持续增长。确认当前版本和至少一个可回滚版本后,才清理更旧的目录。清理前应确认目录不再被 `current` 指向,并保留与数据库备份关联的发布记录。不要使用指向未核实路径的递归删除命令。
## 10. Cloudflare 接入
当前应用不是 Cloudflare Pages 架构,而是 VPS 上的 Rust 单进程服务。Cloudflare 应作为 DNS/反向代理层:
1. DNS 添加域名 `A` 记录,指向 `92.119.167.24`。
2. 首先使用 `DNS only` 验证源站 HTTP 80 正常。
3. 确认域名访问、静态资源、`/health`、API 和页面交互正常后,再切换代理模式。
4. 代理开启后确认应用收到并正确显示 `CF-Connecting-IP`、`CF-IPCountry` 和 `CF-Ray`。
5. 生产 HTTPS 使用 Cloudflare 的 `Full (strict)`;只有在源站证书配置完成后才启用。不要把长期生产环境设为 `Flexible`。
重要安全事项:
- 代理模式下应在 VPS 防火墙限制源站入口,避免绕过 Cloudflare 直接访问。
- 当前代码优先信任代理请求头,不会验证连接来源是否属于 Cloudflare;在启用代理前应限制源站网络入口,或实现可信代理 IP 校验。
- 如果需要保留直连 HTTP 作为故障入口,必须接受请求头可伪造,并不得把这些值用于身份判断、风控决策或持久化身份关联。
- Cloudflare 缓存策略不能缓存动态 `/api/*`,尤其不能缓存 `/api/network/probe` 和 `/api/scan`。
- 部署后用真实域名检查 CORS、缓存头、WebSocket/Fetch 行为和客户端看到的协议。
## 11. 故障处理手册
### 11.1 `/health` 失败或连接拒绝
```bash
ssh [email protected] 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint ps && docker compose -p browser-fingerprint logs --tail=200 app'
ssh [email protected] 'ss -ltnp | grep -E ":80|:8787" || true'
```
处理顺序:确认容器是否退出、读取最后一段日志、检查端口冲突和磁盘空间、确认 `/data` 可写,再执行:
```bash
cd /opt/browser-fingerprint/current
docker compose -p browser-fingerprint up -d
```
不要在没有读取日志的情况下反复重启。
### 11.2 页面 200 但 API 失败
检查 API 是否被 Cloudflare 缓存、容器日志是否有 JSON 解析或 SQLite 错误、浏览器请求是否发往正确域名:
```bash
curl -i http://92.119.167.24/api/network/probe
curl -i http://92.119.167.24/api/stats
docker compose -p browser-fingerprint logs --tail=300 app
```
如果只有共享写入失败,重点检查 `/opt/browser-fingerprint/data` 权限、磁盘空间和 SQLite 完整性。不要为了绕过错误把数据库改到容器临时层。
### 11.3 数据库锁定、损坏或写入变慢
- 确认只有一个应用容器使用该数据库。
- 检查磁盘空间和 inode。
- 检查是否有备份、导出或人工 SQL 长时间占用数据库。
- 先停止应用,再做备份和完整性检查。
- 必要时从最近一次已验证备份恢复。
当前连接池最多 5 个连接;不要通过无限增大连接数掩盖 SQLite 写锁问题。数据量明显增长后,应评估归档、保留期限或迁移到适合并发写入的数据库。
### 11.4 GeoIP 显示不可用
先用 `docker compose ps -q app` 找到容器 ID:
```bash
cd /opt/browser-fingerprint/current
CONTAINER=$(docker compose -p browser-fingerprint ps -q app)
docker exec "$CONTAINER" ls -lh /app/data
ls -lh /opt/browser-fingerprint/current/backend/data
```
确认 MMDB 已进入镜像、环境变量没有指向错误路径、数据文件没有被下载脚本留下的 `.tmp` 文件替代。更新数据后重新构建镜像。
### 11.5 部署后 502、循环重定向或 HTTPS 异常
先用源站 IP 和 `DNS only` 访问,区分应用故障与 Cloudflare 配置故障。检查 Cloudflare SSL/TLS 模式、源站端口、防火墙和缓存规则。应用自身只监听 HTTP 端口 80 映射,不提供独立 TLS 终止。
### 11.6 磁盘空间不足
```bash
df -h
docker system df
du -sh /opt/browser-fingerprint/data /opt/browser-fingerprint/releases/*
```
先保留数据库和最近可回滚版本,再清理已确认无用的旧镜像、旧发布包和日志。任何数据库清理都要先备份。
## 12. 数据保留与删除
当前代码没有自动保留期限或自动删除任务。生产环境在确定业务保留期后,应由运维定期执行经过审核的清理流程,并先备份。
示例:删除 90 天以前的扫描事件。由于外键级联依赖 SQLite 的外键开关,执行时显式打开:
```sql
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
DELETE FROM scan_events
WHERE julianday(created_at) < julianday('now', '-90 days');
COMMIT;
```
执行后检查 `scan_events`、`signal_hits`、`fingerprint_visits` 数量、数据库大小和统计结果。生产执行前应在备份副本上演练,确认没有把仍需保留的运营数据删除。
## 13. 安全检查清单
发布前:
- [ ] 没有新增明文 token、密码、私钥或 `.env`。
- [ ] `git diff --check` 通过。
- [ ] 前端测试、前端构建、Rust 格式检查、Rust 测试通过。
- [ ] Docker 镜像能构建并启动。
- [ ] 数据库文件没有进入 Git 或 Docker build context。
- [ ] `/api/network/probe` 没有被缓存。
- [ ] 没有把原始 WebRTC 候选、摄像头/麦克风内容或精确定位数据写入统计。
- [ ] 新增权限能力有明确用户操作和失败状态。
- [ ] 中文、英文和限制说明同步更新。
生产环境:
- [ ] SSH 使用密钥,禁止把私钥放入仓库。
- [ ] Docker、操作系统和 Cloudflare 配置由专人维护并有变更记录。
- [ ] 数据目录在独立卷,权限最小化。
- [ ] 数据库有异机备份,且做过恢复演练。
- [ ] 防火墙限制源站访问,代理请求头不会被任意公网客户端伪造。
- [ ] `/api/stats` 和 `/api/analytics` 的公开范围经过确认,不包含单访客数据。
- [ ] 有最近一个可回滚发布和对应数据库备份。
- [ ] 有磁盘空间、容器状态、健康检查和错误日志监控。
- [ ] 发生故障时先保留证据和备份,再重启、回滚或清理。
## 14. Git、CI 和交付规范
### 14.1 提交前
```bash
git status --short --branch --ignore-submodules=dirty
git diff --stat
git diff --check
```
只暂存当前需求相关文件。提交信息使用简短、可检索的英文或中文动词描述,例如:
```text
docs: add production operations guide
fix: validate proxy network headers
feat: add aggregated analytics field
```
### 14.2 CI
`.gitea/workflows/ci.yml` 在 `main` push 和 Pull Request 运行:
- 前端依赖安装、测试和构建。
- Rust 格式检查和测试。
- Docker 镜像构建。
修改依赖、构建脚本、Dockerfile 或 workflow 后,本地先运行对应步骤,再推送。不要用跳过测试的提交作为生产发布依据。
### 14.3 Release
发布前:
```bash
git tag -a v0.1.0 -m 'release v0.1.0'
git push origin v0.1.0
```
Tag 应指向已经合并、测试通过的提交。推送后在 Gitea Actions 和 Release 页面确认任务、资产和提交 SHA 一致。
## 15. 代理完成任务时的交付格式
完成代码或文档修改后,最终说明至少包含:
- 修改了哪些文件、解决了什么问题。
- 执行了哪些测试或构建命令及结果。
- 是否涉及数据库、部署、权限、数据格式或兼容性变化。
- 未执行的验证和剩余风险。
- 如已推送,给出提交 SHA 和仓库链接;如未推送,明确说明原因。
不要声称“已部署”“已备份”“CI 已通过”或“数据已写入”,除非有实际命令或平台结果作为依据。
## 16. 当前已知限制
这些限制属于当前系统事实,后续代理不能在文案中把它们描述成已解决:
- 当前数据库是 SQLite,连接池为最多 5 个连接,没有自动归档和自动备份。
- `/api/stats` 和 `/api/analytics` 当前公开且无鉴权。
- `/api/network/probe` 当前优先读取代理请求头,未在应用层验证请求是否来自可信代理。
- 当前只提供 `/health`,没有 Prometheus 指标、告警规则或后台管理员界面。
- 当前 Compose 只把 HTTP 80 映射到容器 8787,TLS 由外部 Cloudflare/反向代理负责。
- 当前没有自动化数据库 migration 版本管理。
- 普通网页能力受浏览器安全模型限制,不能承诺读取周边 Wi-Fi、静默蓝牙或绕过用户权限。
新增功能、生产化改造或安全修复应优先解决这些明确限制,并同步更新本文件。