From 9854b1a61c451939a6af0753372a3939f2026f74 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 14 Sep 2026 23:57:47 +0800 Subject: [PATCH] docs: add detailed agent and production operations guide --- AGENTS.md | 754 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..93f0267 --- /dev/null +++ b/AGENTS.md @@ -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 pnpm@11.7.0 --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 root@92.119.167.24 '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 root@92.119.167.24 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint ps' +ssh root@92.119.167.24 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint logs --tail=200 app' +ssh root@92.119.167.24 '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 root@92.119.167.24 "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 root@92.119.167.24 "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 root@92.119.167.24 'ls -1dt /opt/browser-fingerprint/releases/*' +``` + +将 `current` 指向上一个已验证发布,然后重建容器: + +```bash +ssh root@92.119.167.24 '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 root@92.119.167.24 'cd /opt/browser-fingerprint/current && docker compose -p browser-fingerprint ps && docker compose -p browser-fingerprint logs --tail=200 app' +ssh root@92.119.167.24 '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、静默蓝牙或绕过用户权限。 + +新增功能、生产化改造或安全修复应优先解决这些明确限制,并同步更新本文件。