docs: add detailed agent and production operations guide
This commit is contained in:
@@ -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、静默蓝牙或绕过用户权限。
|
||||
|
||||
新增功能、生产化改造或安全修复应优先解决这些明确限制,并同步更新本文件。
|
||||
Reference in New Issue
Block a user