1607 lines
62 KiB
Markdown
1607 lines
62 KiB
Markdown
# FlashOps 项目开发手册 (Project Manual)
|
||
|
||
本文档用于 FlashOps / STORAGE LABOS 项目的开发、测试、部署、运维与交接。内容以
|
||
2026-07-27 的实际源码和生产环境为准;目标架构与尚未实现的能力会明确标注,避免把原型能力
|
||
误认为真实硬件能力。
|
||
|
||
> **文档状态**:V1.2 · 已与当前代码、Git 仓库和云端部署边界核对
|
||
>
|
||
> **生产地址**:<https://flashops.imagebrewing.com/>
|
||
>
|
||
> **团队 Git**:<https://git.imagebrewing.com/yuanshuai/storage-labos>
|
||
>
|
||
> **后端代号**:FlashOps
|
||
>
|
||
> **控制台品牌**:STORAGE LABOS
|
||
>
|
||
> **安全提醒**:本文不记录 SSH 密码、Gitea 管理员密码、私钥或其他明文凭据。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [项目概述](#1-项目概述)
|
||
2. [快速开始](#2-快速开始)
|
||
3. [系统架构](#3-系统架构)
|
||
4. [核心业务流程与状态机](#4-核心业务流程与状态机)
|
||
5. [安全门禁与危险动作](#5-安全门禁与危险动作)
|
||
6. [数据库与领域模型](#6-数据库与领域模型)
|
||
7. [API 接口设计](#7-api-接口设计)
|
||
8. [前端控制台](#8-前端控制台)
|
||
9. [工作流、Agent 与适配器](#9-工作流agent-与适配器)
|
||
10. [证据、失败分析与指标](#10-证据失败分析与指标)
|
||
11. [配置与环境变量](#11-配置与环境变量)
|
||
12. [腾讯云生产部署与运维](#12-腾讯云生产部署与运维)
|
||
13. [测试、质量门与开发规范](#13-测试质量门与开发规范)
|
||
14. [已知边界与演进路线](#14-已知边界与演进路线)
|
||
15. [故障排查](#15-故障排查)
|
||
16. [术语表与 FAQ](#16-术语表与-faq)
|
||
17. [变更记录](#17-变更记录)
|
||
|
||
---
|
||
|
||
## 1. 项目概述
|
||
|
||
### 1.1 背景
|
||
|
||
固件回归测试通常跨越数小时甚至数天,期间可能发生 Agent 崩溃、操作系统蓝屏、测试盘掉盘、
|
||
网络中断或整机失联。传统做法依赖工程师通宵值守、手工重启、记录截图和拼装报告,成本高,
|
||
而且很难证明“当时到底发生了什么”。
|
||
|
||
FlashOps 的目标是把一条真实实验室 SOP 变成可执行、可恢复、可审计、可取证的状态机:
|
||
|
||
- 自动执行固件 A/B 回归与循环测试;
|
||
- 在破坏性操作前阻止错盘、系统盘和未授权 DUT;
|
||
- 主机失联后通过独立带外通道逐级恢复;
|
||
- 从安全检查点继续,而不是盲目重跑非幂等步骤;
|
||
- 统一记录控制平面、Agent、OOB 和人工操作事件;
|
||
- 生成可回放的 Evidence Bundle;
|
||
- 用无人值守完成率和人工触碰次数量化节省的值守成本。
|
||
|
||
### 1.2 主要用户
|
||
|
||
| 用户 | 主要任务 |
|
||
|---|---|
|
||
| 固件 / SSD 测试工程师 | 创建回归任务、查看实时进度、分析失败证据 |
|
||
| 实验室管理员 | 管理 DUT、测试主机、带外控制器和危险动作授权 |
|
||
| 研发工程师 | 对比固件版本、复现失败、读取时间线与环境指纹 |
|
||
| 测试负责人 | 查看 UCR、证据完整率、阻断问题和工位利用情况 |
|
||
| 运维人员 | 维护控制平面、数据库、证书、日志和备份 |
|
||
|
||
### 1.3 当前可运行基线
|
||
|
||
当前版本已经具备一条无需真实硬件即可端到端运行的纵向链路:
|
||
|
||
```text
|
||
创建任务
|
||
→ 六项安全预检
|
||
→ DUT / 测试主机资源加锁
|
||
→ N 次模拟回归循环
|
||
→ 注入 Agent 心跳丢失
|
||
→ L1 Agent 软恢复失败
|
||
→ L2 OS 重启失败
|
||
→ L3 带外 Reset 恢复
|
||
→ 校验环境指纹并从检查点续跑
|
||
→ 生成 Evidence Bundle
|
||
→ 控制台展示状态与事件时间线
|
||
```
|
||
|
||
### 1.4 能力状态
|
||
|
||
| 能力 | 状态 | 说明 |
|
||
|---|---|---|
|
||
| FastAPI 控制平面 | 已实现 | 任务、预检、状态、人工操作、看板接口 |
|
||
| 14 张持久化表 | 已实现 | SQLite 可直接建库和灌种子数据 |
|
||
| Run 事件溯源 | 已实现 | 状态变化统一经 `events/recorder.py` 写入 |
|
||
| Run 状态转移约束 | 已实现 | 非法转移抛出异常,终态不可继续 |
|
||
| 六项安全门禁 | 已实现 | 序列号、授权、系统盘、固件、Host、OOB |
|
||
| L1–L5 恢复决策 | 已实现 | 演示链路实际执行到 L3;L4/L5 为策略能力 |
|
||
| 检查点续跑 | 模拟实现 | 当前保存循环边界检查点 |
|
||
| Evidence Bundle | 最小实现 | `manifest.json` + `events.ndjson` |
|
||
| 控制台 | 部分动态 | 任务中心和实时运行接 API,其余主要为产品原型 |
|
||
| 生产部署 | 已实现 | 腾讯云 CVM + systemd + Nginx + HTTPS |
|
||
| 团队 Git 服务 | 已实现 | Gitea 1.27 + HTTPS + PR/Issue + COS 定时备份 |
|
||
| 独立 Host Agent | 部分实现 | 注册、token 摘要、主机探针、心跳及状态老化已实现;Step 租约待实现 |
|
||
| 真实 NVMe / OOB 接入 | 未实现 | 尚未连接可牺牲 DUT、PDU 或 KVM |
|
||
| 失败指纹聚类执行逻辑 | 未实现 | 数据模型和界面已定义,服务逻辑待接入 |
|
||
| 应用级账号与 RBAC | 未实现 | FlashOps 预览站当前匿名开放;接真实硬件前必须恢复认证与审批 |
|
||
|
||
### 1.5 系统边界
|
||
|
||
当前版本是“可演示、可开发、可部署的控制平面基线”,不是已经可以对真实盘执行刷写和
|
||
Sanitize 的生产硬件平台。以下行为不得通过修改模拟数据来伪装完成:
|
||
|
||
- 不得宣称 Step 租约、真实命令执行和真实 OOB 已经交付;
|
||
- 不得把 `sim://` 的固件、OOB 和证据 URI 当作真实硬件结果;
|
||
- 匿名预览环境不得接入真实硬件、客户数据或有效危险命令;接入前必须启用应用级认证、
|
||
审批和命令模板白名单;
|
||
- 不得直接把生产部署的 SQLite 单机形态扩展为多 worker;
|
||
- 不得绕过安全门禁向真实设备下发自由文本命令。
|
||
|
||
### 1.6 技术栈
|
||
|
||
| 层 | 技术 | 当前用途 |
|
||
|---|---|---|
|
||
| 后端 | Python 3.9+ / FastAPI | HTTP API 与应用生命周期 |
|
||
| ORM | SQLAlchemy 2.x Async | 领域模型、事务和查询 |
|
||
| 开发数据库 | SQLite + aiosqlite | 零外部依赖启动 |
|
||
| 目标数据库 | PostgreSQL 16 + asyncpg | 多工位生产形态,尚未切换 |
|
||
| 服务进程 | Uvicorn | ASGI 运行时 |
|
||
| 校验 | Pydantic 2.x | API 请求体校验 |
|
||
| 工作流配置 | YAML / JSON | SOP 与步骤定义 |
|
||
| 测试 | pytest / pytest-asyncio / httpx | 纯函数与 API 端到端测试 |
|
||
| 前端 | 静态 HTML + Design System JS/CSS | 产品控制台原型和部分 API 接入 |
|
||
| 生产守护 | systemd | 单 worker 服务、开机自启、故障重启 |
|
||
| 入口 | 宝塔 Nginx | HTTPS、反代和安全响应头;FlashOps 预览站匿名开放 |
|
||
| Git 协作 | Gitea 1.27 | 仓库、提交历史、分支、Issue、Pull Request 与成员管理 |
|
||
| 证书 | Let's Encrypt / Certbot | HTTPS 与自动续期 |
|
||
|
||
### 1.7 目录结构
|
||
|
||
```text
|
||
STORAGE LABOS/
|
||
├── .github/ PR 模板与 CI 工作流模板
|
||
├── .gitignore / .gitattributes 忽略规则与跨平台文本规则
|
||
├── README.md Git 仓库首页
|
||
├── CONTRIBUTING.md 团队 Git 协作入口
|
||
├── ProjectManual.md 本手册
|
||
├── flashops/ 后端与技术文档
|
||
│ ├── Makefile 本地开发统一入口
|
||
│ ├── README.md 项目短说明和能力状态
|
||
│ ├── docker-compose.yml 目标生产拓扑草案
|
||
│ ├── deploy/ 当前腾讯云生产配置
|
||
│ │ ├── README.md
|
||
│ │ ├── flashops.service
|
||
│ │ ├── nginx-http.conf
|
||
│ │ ├── nginx-https.conf
|
||
│ │ └── certbot-reload-nginx.sh
|
||
│ ├── docs/ 架构、协议、状态机、ADR
|
||
│ │ ├── architecture.md
|
||
│ │ ├── domain-model.md
|
||
│ │ ├── state-machine.md
|
||
│ │ ├── workflow-spec.md
|
||
│ │ ├── agent-protocol.md
|
||
│ │ ├── adapter-sdk.md
|
||
│ │ └── decisions/
|
||
│ ├── services/control-plane/
|
||
│ │ ├── requirements.txt
|
||
│ │ ├── flashops_control/
|
||
│ │ │ ├── api/ HTTP 路由
|
||
│ │ │ ├── engine/ 状态转移与恢复决策
|
||
│ │ │ ├── events/ 事件写入口
|
||
│ │ │ ├── evidence/ 证据包生成
|
||
│ │ │ ├── models/ 14 张表
|
||
│ │ │ ├── safety/ 六项安全门禁
|
||
│ │ │ ├── services/ Run 编排和模拟执行器
|
||
│ │ │ ├── cli.py 建库、种子、演示命令
|
||
│ │ │ ├── config.py 配置入口
|
||
│ │ │ ├── db.py 异步会话与 SQLite 并发保护
|
||
│ │ │ └── main.py FastAPI 应用入口
|
||
│ │ └── tests/
|
||
│ ├── services/host-agent/
|
||
│ │ ├── README.md 独立 Agent 运行与配置
|
||
│ │ ├── flashops_agent/ 注册、指纹、凭据和心跳客户端
|
||
│ │ └── tests/
|
||
│ └── var/ 本地数据库、对象和证据(不入库)
|
||
└── 前端UI八页面完成/ 控制台静态页面与设计系统
|
||
├── Dashboard.dc.html
|
||
├── Tasks.dc.html
|
||
├── LiveRun.dc.html
|
||
├── Evidence.dc.html
|
||
├── Failures.dc.html
|
||
├── Assets.dc.html
|
||
├── Compare.dc.html
|
||
├── Workflows.dc.html
|
||
├── ConsoleNav.dc.html
|
||
├── ds-base.js / support.js
|
||
└── _ds/ 设计系统资源
|
||
```
|
||
|
||
### 1.8 服务地址与端口
|
||
|
||
| 环境 | 地址 / 端口 | 用途 |
|
||
|---|---|---|
|
||
| 本地开发 | `http://localhost:8000` | API 与控制台同源服务 |
|
||
| 本地控制台 | `/console/Dashboard.dc.html` | 控制台入口 |
|
||
| 本地 API 文档 | `/docs` | FastAPI OpenAPI UI |
|
||
| 生产内部监听 | `127.0.0.1:18080` | Uvicorn,仅服务器本机可访问 |
|
||
| 生产公网 | `https://flashops.imagebrewing.com` | Nginx HTTPS 入口 |
|
||
| 团队 Git | `https://git.imagebrewing.com` | Gitea 页面、HTTPS clone、PR 与成员管理 |
|
||
| 生产 HTTP | `80` | ACME challenge;其他请求跳转 HTTPS |
|
||
| 生产 HTTPS | `443` | TLS + 反向代理;FlashOps 预览站无需登录 |
|
||
|
||
---
|
||
|
||
## 2. 快速开始
|
||
|
||
### 2.1 环境准备
|
||
|
||
- macOS、Linux 或 WSL;
|
||
- Python 3.9 或更高版本;
|
||
- GNU Make;
|
||
- 本地运行不需要 Node、Docker、PostgreSQL 或 Redis。
|
||
|
||
### 2.2 首次初始化
|
||
|
||
```bash
|
||
cd "/Users/yuanshuai/Desktop/STORAGE LABOS/flashops"
|
||
make setup
|
||
```
|
||
|
||
`make setup` 会执行以下动作:
|
||
|
||
1. 创建 `flashops/.venv`;
|
||
2. 安装 `services/control-plane/requirements.txt`;
|
||
3. 重建本地 `var/flashops.db`;
|
||
4. 创建 `var/objects/`;
|
||
5. 灌入模拟工作流、DUT、Host、OOB 和固件数据。
|
||
|
||
> `make setup` 内含 `db-reset`,会删除本地 `var/` 中的开发数据。不要在生产目录执行。
|
||
|
||
### 2.3 启动开发服务
|
||
|
||
```bash
|
||
make dev
|
||
```
|
||
|
||
打开:
|
||
|
||
- 总览:<http://localhost:8000/console/Dashboard.dc.html>
|
||
- 任务中心:<http://localhost:8000/console/Tasks.dc.html>
|
||
- 实时运行:<http://localhost:8000/console/LiveRun.dc.html>
|
||
- API 文档:<http://localhost:8000/docs>
|
||
|
||
### 2.4 跑一条完整演示
|
||
|
||
```bash
|
||
make demo
|
||
```
|
||
|
||
演示默认运行 8 个循环,并注入一次心跳丢失。完成后会输出 Run ID 和 Evidence Bundle 路径。
|
||
|
||
也可以在控制台“任务中心”先执行安全预检,再创建任务,然后进入“实时运行”观察事件时间线。
|
||
|
||
### 2.5 运行测试
|
||
|
||
```bash
|
||
make test
|
||
```
|
||
|
||
当前基线共有 25 项测试,覆盖:
|
||
|
||
- Run 状态转移;
|
||
- L1–L5 恢复阶梯选择;
|
||
- 系统盘 / 分区保护;
|
||
- 六项安全预检;
|
||
- 完整模拟 Run、故障恢复、Evidence Bundle;
|
||
- 暂停、继续、紧急停止与人工触碰审计;
|
||
- Agent 注册、token 轮换、Bearer 鉴权和本地凭据权限;
|
||
- 心跳状态老化:15 秒降级、30 秒离线。
|
||
|
||
### 2.6 常用 Make 命令
|
||
|
||
| 命令 | 作用 |
|
||
|---|---|
|
||
| `make setup` | 创建虚拟环境、安装依赖、重建开发库 |
|
||
| `make setup-py` | 只准备 Python 依赖 |
|
||
| `make db-reset` | 删除本地数据库和对象目录后重建 |
|
||
| `make seed` | 向空库灌入种子数据;已有工作流时不重复写 |
|
||
| `make demo` | CLI 跑完整模拟链路 |
|
||
| `make dev` | 启动 API 和同源控制台 |
|
||
| `make dev-agent` | 注册 Agent 并发送一次真实进程心跳 |
|
||
| `make dev-agent-loop` | 持续运行 Agent 心跳循环 |
|
||
| `make test` | 执行完整测试集 |
|
||
| `make e2e` | 只跑端到端纵向测试 |
|
||
| `make clean` | 清理本地 `var/`、缓存和 `__pycache__` |
|
||
|
||
---
|
||
|
||
## 3. 系统架构
|
||
|
||
### 3.1 五层架构
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ 交互与集成层 静态控制台 · CLI · OpenAPI │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ 控制平面 FastAPI · Safety · Run Service · Event Store │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ 执行平面 当前:内置模拟器 + Agent 心跳;目标:Step 执行 │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ 带外平面 当前:模拟 OOB;目标:KVM / PDU / ATX / AC │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ 证据与智能层 统一事件流 · Evidence Bundle · Failure Signature│
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 依赖方向
|
||
|
||
```text
|
||
Browser / CLI ──HTTP──▶ Control Plane ◀──HTTP pull── Host Agent(部分落地)
|
||
│ │
|
||
├──▶ Database ├──▶ Adapters
|
||
├──▶ Object Store └──▶ OOB
|
||
└──▶ Event Timeline
|
||
```
|
||
|
||
硬规则:
|
||
|
||
1. Agent 不导入控制平面代码,只按 `docs/agent-protocol.md` 通信;
|
||
2. 控制平面不主动反连客户主机,Agent 采用拉模式;
|
||
3. 状态机纯逻辑不依赖 FastAPI、SQLAlchemy、文件系统或真实时钟;
|
||
4. API 路由只做校验与服务调用,不承载复杂业务决策;
|
||
5. 危险动作必须先过控制平面安全门禁;
|
||
6. Run 状态不得绕过 `events/recorder.py` 直接修改;
|
||
7. 日志正文和大文件进入对象存储,事件表只保存索引、摘要和 URI。
|
||
|
||
### 3.3 当前运行架构
|
||
|
||
```text
|
||
Internet
|
||
│
|
||
▼
|
||
DNSPod: flashops.imagebrewing.com → 62.234.39.66
|
||
│
|
||
▼
|
||
Nginx :443
|
||
├── TLS 1.2 / 1.3
|
||
├── 安全响应头
|
||
└── proxy_pass http://127.0.0.1:18080
|
||
│
|
||
▼
|
||
systemd: flashops.service
|
||
│
|
||
▼
|
||
Uvicorn 单 worker / FastAPI
|
||
├── SQLite: var/flashops.db
|
||
├── Evidence: var/objects/
|
||
└── Console: ../前端UI八页面完成/
|
||
```
|
||
|
||
### 3.4 开发形态与目标形态
|
||
|
||
| 组件 | 当前开发 / 线上基线 | 目标多工位形态 |
|
||
|---|---|---|
|
||
| 数据库 | SQLite | PostgreSQL 16 |
|
||
| 事务并发 | 应用级串行 SQLite session | 数据库行锁与队列 |
|
||
| 进程数 | 1 worker | 多 worker / 多实例 |
|
||
| 事件总线 | 进程内任务 | Redis / NATS |
|
||
| 对象存储 | 本地目录 | MinIO / S3 / COS |
|
||
| 执行器 | Run 仍由内置模拟器执行;独立 Agent 已能注册和心跳 | Agent 领取并执行 Step |
|
||
| OOB | 模拟 | JetKVM / PDU / 主板控制器 |
|
||
| 认证 | 匿名预览,无应用账号 | OIDC / SSO + RBAC + 审批 |
|
||
|
||
### 3.5 关键架构决策
|
||
|
||
`flashops/docs/decisions/` 记录已经接受的 ADR:
|
||
|
||
- ADR-0001:Monorepo 与技术栈;
|
||
- ADR-0002:Run 采用事件溯源;
|
||
- ADR-0003:首版自研状态机,不依赖 Temporal / Airflow;
|
||
- ADR-0004:安全门禁放在控制平面,Agent 保持简单;
|
||
- ADR-0005:模拟器是一等产品能力,不是临时测试脚手架。
|
||
|
||
修改这些边界前,应新增 ADR,而不是只改代码。
|
||
|
||
---
|
||
|
||
## 4. 核心业务流程与状态机
|
||
|
||
### 4.1 创建任务
|
||
|
||
任务创建时会冻结以下信息:
|
||
|
||
- 工作流 key、version 和 `spec_hash`;
|
||
- DUT、Host、固件 A、固件 B;
|
||
- 循环次数和是否注入故障;
|
||
- OS、Kernel、BIOS、Agent、工具链、DUT 序列号和型号;
|
||
- 环境指纹哈希;
|
||
- 创建人和创建时间。
|
||
|
||
创建后依次写入 `RUN_CREATED` 和 `RUN_QUEUED` 事件,再启动模拟执行任务。
|
||
|
||
### 4.2 Run 状态机
|
||
|
||
```text
|
||
QUEUED ──▶ PREFLIGHT ──▶ RUNNING ──▶ COMPLETED
|
||
│ │ │
|
||
│ └──▶ REJECTED │
|
||
│ ├──▶ PAUSED ──▶ RUNNING
|
||
│ ├──▶ RECOVERING ──▶ RUNNING
|
||
│ │ └──▶ FROZEN
|
||
└──────────────────────────┴──────────────▶ ABORTED
|
||
```
|
||
|
||
| 当前状态 | 允许转移到 |
|
||
|---|---|
|
||
| `QUEUED` | `PREFLIGHT`, `ABORTED` |
|
||
| `PREFLIGHT` | `RUNNING`, `REJECTED`, `ABORTED` |
|
||
| `RUNNING` | `RECOVERING`, `PAUSED`, `FROZEN`, `COMPLETED`, `ABORTED` |
|
||
| `RECOVERING` | `RUNNING`, `FROZEN`, `ABORTED` |
|
||
| `PAUSED` | `RUNNING`, `ABORTED` |
|
||
| `FROZEN` | 无,终态 |
|
||
| `COMPLETED` | 无,终态 |
|
||
| `ABORTED` | 无,终态 |
|
||
| `REJECTED` | 无,终态 |
|
||
|
||
非法转移由 `engine/states.py::assert_run_transition()` 拒绝。
|
||
|
||
### 4.3 Step 状态机(目标契约)
|
||
|
||
```text
|
||
PENDING → DISPATCHED → RUNNING → SUCCEEDED
|
||
├──→ FAILED
|
||
├──→ TIMED_OUT
|
||
├──→ SKIPPED
|
||
└──→ CANCELLED
|
||
```
|
||
|
||
`run_steps` 表已经定义状态、attempt、租约、输入输出和错误字段,但当前内置模拟器主要在 Run
|
||
和循环层执行,完整 Step runtime / Agent 租约尚未接入。
|
||
|
||
### 4.4 恢复阶梯
|
||
|
||
| 级别 | 名称 | 条件与动作 |
|
||
|---|---|---|
|
||
| L1 | `L1_AGENT_SOFT` | Agent 在线时优雅停止或重启执行进程 |
|
||
| L2 | `L2_OS_REBOOT` | Host 网络仍在线时请求操作系统重启 |
|
||
| L3 | `L3_OOB_RESET` | OOB 在线时触发主板 Reset |
|
||
| L4 | `L4_OOB_ATX_POWER` | 模拟 ATX 长按关机并重新开机 |
|
||
| L5 | `L5_OOB_AC_CYCLE` | 整机 AC 断电,等待安全间隔后上电 |
|
||
| Freeze | `FREEZE` | 停止自动动作,保留现场并等待人工 |
|
||
|
||
数据完整性失败不走逐级恢复,直接 `FREEZE`,防止自动动作覆盖关键证据。
|
||
|
||
### 4.5 检查点语义
|
||
|
||
当前模拟执行器在循环结束时保存:
|
||
|
||
```json
|
||
{
|
||
"loop_index": 3,
|
||
"next_step_key": "regression-loop",
|
||
"dut_fw": "B"
|
||
}
|
||
```
|
||
|
||
恢复成功后必须先确认环境指纹和物理状态仍符合预期,再写 `CHECKPOINT_RESUMED`。
|
||
刷写、Format、Sanitize 等非幂等步骤默认不得因为进程重启自动重复执行。
|
||
|
||
### 4.6 暂停、继续与紧急停止
|
||
|
||
- 暂停:仅 `RUNNING → PAUSED`;
|
||
- 继续:仅 `PAUSED → RUNNING`;
|
||
- 紧急停止:任何非终态可进入 `ABORTED`;
|
||
- 每次人工操作先写 `HUMAN_TOUCH`,递增 `human_touches`,并把
|
||
`unattended_completion` 设为 `false`;
|
||
- 紧急停止会释放资源锁并取消内置模拟任务。
|
||
|
||
### 4.7 SQLite 并发保护
|
||
|
||
模拟器与人工暂停可能同时写 `run_events`。SQLite 只允许一个 writer;如果两个会话同时读取
|
||
`event_seq` 再写入,会发生唯一键冲突。当前 `db.py` 对 SQLite 的完整 `session_scope` 事务按事件
|
||
循环串行化,保证事件序号和投影一致。
|
||
|
||
这项保护只适合单进程 / 单 worker 基线。切换 PostgreSQL 后不使用应用级锁,并需要增加
|
||
PostgreSQL 并发集成测试。
|
||
|
||
---
|
||
|
||
## 5. 安全门禁与危险动作
|
||
|
||
### 5.1 默认拒绝原则
|
||
|
||
安全判断发生在控制平面,并且必须早于命令下发。Agent 只能执行已批准模板和经过 schema
|
||
校验的参数,不能接收来自界面的自由文本 shell 命令。
|
||
|
||
### 5.2 六项预检
|
||
|
||
| key | 门禁 | 通过条件 |
|
||
|---|---|---|
|
||
| `serial` | DUT 序列号白名单 | 期望序列号非空且等于现场发现序列号 |
|
||
| `destructive` | 破坏性写入授权 | `duts.allow_destructive = true` |
|
||
| `system_disk` | 系统盘 / 分区保护 | DUT 路径不命中系统盘及其任一分区 |
|
||
| `firmware` | 固件适配型号 | DUT 型号在固件适用列表中,或列表为空 |
|
||
| `host` | 测试主机与 Agent | Host 状态为 `ONLINE` |
|
||
| `oob` | 带外控制器 | 绑定 OOB 存在且状态为 `ONLINE` |
|
||
|
||
六项全部通过,`passed` 才为 `true`。
|
||
|
||
### 5.3 系统盘保护
|
||
|
||
`safety/gates.py::_same_device()` 同时比较完整设备名和分区归属。例如:
|
||
|
||
- 目标 `/dev/nvme0n1`,系统盘 `/dev/nvme0n1`:拒绝;
|
||
- 目标 `/dev/nvme0n1p2`,系统盘 `/dev/nvme0n1`:拒绝;
|
||
- 目标 `/dev/nvme1n1`,系统盘 `/dev/nvme0n1`:可继续检查其他门禁。
|
||
|
||
真实 Agent 接入时还应补充:挂载点、root filesystem、启动标志、Windows Disk Number、BDF
|
||
和序列号的现场交叉校验。
|
||
|
||
### 5.4 危险级别
|
||
|
||
| 级别 | 含义 |
|
||
|---|---|
|
||
| `none` | 只读、报告或普通控制动作 |
|
||
| `low` | 有状态变化但可逆 |
|
||
| `high` | 刷写、Format、Sanitize、断电等,需要审批与审计 |
|
||
|
||
### 5.5 进入真实实验室前必须补齐
|
||
|
||
1. 命令模板注册表与 JSON Schema 参数校验;
|
||
2. 不可绕过的系统盘识别;
|
||
3. 应用账号、RBAC、危险动作审批和双人复核;
|
||
4. Agent 身份、短期令牌、请求签名与重放保护;
|
||
5. 审批人、模板版本、参数、执行结果和撤销动作审计;
|
||
6. 真实 OOB 的安全间隔、最大恢复次数和硬件熔断;
|
||
7. 真实 DUT 的可牺牲测试环境,禁止先在业务盘验证。
|
||
|
||
---
|
||
|
||
## 6. 数据库与领域模型
|
||
|
||
### 6.1 当前数据库
|
||
|
||
- 本地:`flashops/var/flashops.db`;
|
||
- 生产:`/data/wangzhan/app/storage-labos/flashops/var/flashops.db`;
|
||
- 驱动:`sqlite+aiosqlite`;
|
||
- 表由 SQLAlchemy `Base.metadata.create_all()` 创建;
|
||
- 应用启动时会确保表和种子数据存在;
|
||
- 当前无 Alembic 迁移,修改表结构时需要先补迁移方案。
|
||
|
||
### 6.2 14 张表
|
||
|
||
| 领域 | 表 | 作用 |
|
||
|---|---|---|
|
||
| 资产 | `oob_controllers` | 带外控制器、能力、状态、电源和温度 |
|
||
| 资产 | `test_hosts` | 操作系统、Agent、工具链、心跳和 OOB 绑定 |
|
||
| 资产 | `duts` | 序列号、型号、BDF、设备路径、危险授权和健康信息 |
|
||
| 资产 | `firmware_artifacts` | 版本、哈希、签名、适用型号和文件 URI |
|
||
| 工作流 | `workflows` | SOP spec、版本、哈希、危险级别和审批人 |
|
||
| 运行 | `runs` | 当前状态投影、环境指纹、进度、检查点和结论 |
|
||
| 运行 | `run_steps` | 步骤状态、attempt、租约、输入输出和错误 |
|
||
| 事件 | `run_events` | 每个 Run 内单调递增的 append-only 时间线 |
|
||
| 恢复 | `recovery_actions` | 触发原因、恢复级别、结果、详情和证据 URI |
|
||
| 调度 | `resource_locks` | DUT / Host 独占锁,防止并发踩踏 |
|
||
| 分析 | `failure_signatures` | 失败聚类签名和统计 |
|
||
| 分析 | `run_failures` | 单次失败实例及其分类 |
|
||
| 证据 | `evidence_bundles` | 证据包 URI、manifest、大小和完整率 |
|
||
| 审计 | `audit_log` | 操作者、动作、命令模板、参数、审批和结果 |
|
||
|
||
### 6.3 事件溯源
|
||
|
||
```text
|
||
run_events(顺序真相)
|
||
│
|
||
├──投影──▶ runs
|
||
├──投影──▶ run_steps
|
||
└──关联──▶ recovery_actions / evidence_bundles / failures
|
||
```
|
||
|
||
`runs` 是为了查询效率保存的当前投影,`run_events` 才是完整顺序真相。事件的 `seq` 由控制平面
|
||
分配,不能用不同机器的时间戳代替排序。
|
||
|
||
### 6.4 关键字段
|
||
|
||
- `runs.spec_hash`:任务使用的工作流不可变版本;
|
||
- `runs.env_fingerprint` / `env_fingerprint_hash`:A/B 可比性和失败聚类输入;
|
||
- `runs.event_seq`:每个 Run 的下一事件序号来源;
|
||
- `runs.checkpoint`:安全续跑位置;
|
||
- `runs.unattended_completion` / `human_touches`:UCR 与人工介入统计;
|
||
- `duts.serial` / `device_path` / `allow_destructive`:危险动作三信号;
|
||
- `firmware_artifacts.sha256` / `signature`:固件完整性与来源;
|
||
- `evidence_bundles.completeness` / `missing_fields`:证据质量;
|
||
- `resource_locks` 的资源类型和资源 ID 唯一约束:防止同一资产被重复占用。
|
||
|
||
### 6.5 数据文件与 Git
|
||
|
||
以下内容属于运行产物,不进入 Git:
|
||
|
||
```text
|
||
flashops/var/
|
||
flashops/.venv/
|
||
flashops/.pytest_cache/
|
||
*.db
|
||
*.db-journal
|
||
*.log
|
||
.env
|
||
.env.local
|
||
```
|
||
|
||
---
|
||
|
||
## 7. API 接口设计
|
||
|
||
### 7.1 基础约定
|
||
|
||
- 前缀:`/api/v1`;
|
||
- 数据格式:JSON;
|
||
- 时间:API 输出 ISO 8601 UTC 字符串;
|
||
- 开发文档:`/docs`;
|
||
- 当前应用内部没有用户认证中间件;
|
||
- 当前公网预览站未启用 Nginx Basic Auth,页面和普通 API 可匿名访问;
|
||
- 匿名预览只能使用模拟资产。进入真实硬件或多用户阶段前必须增加应用级认证、RBAC、
|
||
审批和审计上下文。
|
||
|
||
### 7.2 接口清单
|
||
|
||
| 方法 | 路径 | 作用 |
|
||
|---|---|---|
|
||
| GET | `/api/v1/health` | 服务健康检查 |
|
||
| POST | `/api/v1/preflight` | 对指定或默认资产执行六项预检 |
|
||
| POST | `/api/v1/runs` | 创建 Run 并启动模拟执行 |
|
||
| POST | `/api/v1/runs/demo` | 用默认参数创建演示 Run |
|
||
| GET | `/api/v1/runs?limit=30` | 查询最近 Run,limit 限制 1–100 |
|
||
| GET | `/api/v1/runs/{run_id}` | 查询 Run 当前投影和 Evidence Bundle |
|
||
| GET | `/api/v1/runs/{run_id}/events?after_seq=0` | 增量查询事件时间线 |
|
||
| POST | `/api/v1/runs/{run_id}/pause` | 人工暂停 RUNNING 任务 |
|
||
| POST | `/api/v1/runs/{run_id}/resume` | 人工继续 PAUSED 任务 |
|
||
| POST | `/api/v1/runs/{run_id}/emergency-stop` | 紧急停止非终态任务 |
|
||
| GET | `/api/v1/dashboard/summary` | 汇总任务数、活动数、完成数和 UCR |
|
||
| POST | `/api/v1/agent/register` | 首次注册或轮换 Agent token |
|
||
| POST | `/api/v1/agent/{agent_id}/heartbeat` | Bearer 心跳、主机状态和探针上报 |
|
||
| GET | `/api/v1/agent/hosts` | 查询 Host / Agent 在线投影,不返回 token |
|
||
|
||
### 7.3 请求模型
|
||
|
||
#### PreflightBody
|
||
|
||
```json
|
||
{
|
||
"dut_id": null,
|
||
"host_id": null,
|
||
"firmware_id": null,
|
||
"discovered_serial": null
|
||
}
|
||
```
|
||
|
||
全部为空时使用第一组种子资产。`discovered_serial` 可用于模拟现场序列号不一致。
|
||
|
||
#### CreateRunBody
|
||
|
||
```json
|
||
{
|
||
"name": "固件 A/B 无人值守回归",
|
||
"workflow_id": null,
|
||
"dut_id": null,
|
||
"host_id": null,
|
||
"firmware_a_id": null,
|
||
"firmware_b_id": null,
|
||
"loops": 8,
|
||
"inject_failure": true,
|
||
"created_by": "console-demo"
|
||
}
|
||
```
|
||
|
||
API 当前允许 `loops` 为 1–500;工作流种子 spec 的产品目标上限为 10000,两者尚未统一。
|
||
|
||
#### HumanActionBody
|
||
|
||
```json
|
||
{
|
||
"actor": "engineer-name",
|
||
"reason": "检查暂停"
|
||
}
|
||
```
|
||
|
||
### 7.4 调用示例
|
||
|
||
本地:
|
||
|
||
```bash
|
||
curl -sS http://localhost:8000/api/v1/health
|
||
|
||
curl -sS -X POST http://localhost:8000/api/v1/preflight \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{}'
|
||
|
||
curl -sS -X POST http://localhost:8000/api/v1/runs \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"loops":8,"inject_failure":true,"created_by":"manual"}'
|
||
```
|
||
|
||
公网预览环境可直接访问:
|
||
|
||
```bash
|
||
curl https://flashops.imagebrewing.com/api/v1/health
|
||
```
|
||
|
||
### 7.5 常见状态码
|
||
|
||
| 状态码 | 场景 |
|
||
|---|---|
|
||
| 200 | 查询或动作成功 |
|
||
| 201 | Run 或 Agent 注册成功 |
|
||
| 400 | 资产或工作流数据无法解析 |
|
||
| 401 | Agent 注册口令或 Agent bearer token 无效 |
|
||
| 404 | Run / DUT / Host 等资源不存在 |
|
||
| 409 | 当前状态不允许暂停、继续或停止 |
|
||
| 422 | Pydantic 请求参数校验失败 |
|
||
| 503 | 生产环境尚未配置 Agent 注册口令 |
|
||
| 500 | 应用未处理异常或数据约束冲突 |
|
||
|
||
---
|
||
|
||
## 8. 前端控制台
|
||
|
||
### 8.1 挂载方式
|
||
|
||
`main.py` 把后端仓库的同级目录 `前端UI八页面完成/` 挂载到 `/console`:
|
||
|
||
```text
|
||
STORAGE LABOS/
|
||
├── flashops/
|
||
└── 前端UI八页面完成/
|
||
```
|
||
|
||
生产服务器必须保持同样的兄弟目录关系,否则根路径会跳到 `/docs`,控制台路径返回 404。
|
||
|
||
### 8.2 核心页面
|
||
|
||
| 页面 | 文件 | 当前状态 |
|
||
|---|---|---|
|
||
| 总览 | `Dashboard.dc.html` | 产品级静态样例,看板 API 尚未全面接入 |
|
||
| 任务中心 | `Tasks.dc.html` | 已接预检与创建 Run API |
|
||
| 实时运行 | `LiveRun.dc.html` | 已接 Run、事件、演示、暂停、继续、急停 API |
|
||
| 证据浏览器 | `Evidence.dc.html` | 主要为静态样例,真实 Evidence 下载待接 |
|
||
| 失败中心 | `Failures.dc.html` | 静态产品原型,聚类服务待实现 |
|
||
| 资产中心 | `Assets.dc.html` | 静态产品原型,资产 CRUD 待实现 |
|
||
| 版本比较 | `Compare.dc.html` | 静态产品原型,真实 A/B 查询待实现 |
|
||
| 工作流模板 | `Workflows.dc.html` | 静态产品原型,编辑与发布 API 待实现 |
|
||
|
||
`ConsoleNav.dc.html` 提供导航与全局停止交互原型;`Deck.dc.html` 和 `_template/` 用于展示稿,
|
||
不属于正式业务路由。
|
||
|
||
### 8.3 前端调用约定
|
||
|
||
- 页面与 API 同源,不配置独立 API Base URL;
|
||
- 所有动态请求使用相对路径 `/api/v1/...`;
|
||
- 前端代码不得保存任何生产密码或 Agent token;
|
||
- Run ID 应通过 URL 或浏览器状态传递;
|
||
- 事件增量读取使用 `after_seq`,不要每次重复拉取全部事件;
|
||
- 页面中静态样例必须用“演示 / 示例”标识,避免与真实数据混淆。
|
||
|
||
### 8.4 下一步前端接入优先级
|
||
|
||
1. 总览接 `/dashboard/summary` 与最近 Run;
|
||
2. 证据页接 Run Evidence Bundle 和真实事件;
|
||
3. 资产页增加只读 API,再增加受审批保护的写接口;
|
||
4. 工作流页面接版本、spec hash、发布和审批;
|
||
5. 失败中心接失败签名和人工分类;
|
||
6. 版本比较接真实环境指纹与指标;
|
||
7. 引入统一的 loading、empty、error、401、409 状态。
|
||
|
||
---
|
||
|
||
## 9. 工作流、Agent 与适配器
|
||
|
||
### 9.1 种子工作流
|
||
|
||
默认工作流:
|
||
|
||
```text
|
||
key: fw-ab-regression
|
||
name: 固件 A/B 电源状态回归
|
||
version: 3
|
||
danger_level: high
|
||
```
|
||
|
||
步骤:
|
||
|
||
1. `preflight`:安全预检;
|
||
2. `flash-fw-b`:高危、非幂等固件刷写,完成后检查点;
|
||
3. `regression-loop`:fio、设备枚举、完整性校验,循环边界检查点;
|
||
4. `report`:汇总报告。
|
||
|
||
每个发布版本保存完整 spec 和稳定 `spec_hash`。同 key 的修改应创建新版本,不覆盖历史版本。
|
||
|
||
### 9.2 工作流字段原则
|
||
|
||
- `key`:稳定机器标识;
|
||
- `version`:不可变整数版本;
|
||
- `danger_level`:工作流整体风险;
|
||
- `params`:类型、默认值、范围;
|
||
- `steps[].adapter`:调用的能力边界;
|
||
- `steps[].template`:命令模板,而不是任意命令文本;
|
||
- `idempotent`:是否允许安全重试;
|
||
- `checkpoint`:是否允许从此边界恢复;
|
||
- `on_fail`:retry / fail_run / continue / freeze / recover。
|
||
|
||
详见 `flashops/docs/workflow-spec.md`。
|
||
|
||
### 9.3 Host Agent 当前实现与目标模型
|
||
|
||
独立 Agent 已作为 `services/host-agent/flashops_agent` 实现注册、主机指纹发现、
|
||
本地 `0600` 凭据文件、Bearer token 和心跳。控制平面只保存 token 的 SHA-256 摘要;
|
||
生产注册口令缺失时默认拒绝注册。心跳迟到 15 秒时 Host 自动进入 `DEGRADED`,
|
||
丢失 30 秒时进入 `OFFLINE`;安全预检会刷新状态后再判断 Host 是否可用。
|
||
当前 Run 执行仍由内置模拟器完成。
|
||
|
||
下一阶段继续按拉模式实现:
|
||
|
||
```text
|
||
Agent 注册 / 心跳
|
||
→ 拉取可执行 Step 租约
|
||
→ ACK 接手
|
||
→ 按模板执行
|
||
→ 上报输出摘要与对象 URI
|
||
→ 完成 / 失败 / 超时
|
||
→ 租约续期或到期回收
|
||
```
|
||
|
||
拉模式用于适配客户内网、NAT 和不允许控制平面反向连接的测试主机。
|
||
|
||
### 9.4 Adapter 边界
|
||
|
||
目标适配器包括:
|
||
|
||
- `nvme_cli`:识别设备、固件下载 / commit、SMART、Format / Sanitize;
|
||
- `fio`:负载执行、指标和错误提取;
|
||
- `shell`:只允许登记模板;
|
||
- `oob`:Reset、ATX、AC、画面 / 电源状态;
|
||
- `builtin`:预检、等待、报告和证据处理。
|
||
|
||
模拟适配器与真实适配器必须通过同一套契约测试。真实实现不得在接口外增加隐藏行为。
|
||
|
||
### 9.5 幂等、租约和重放
|
||
|
||
- 每次 Step dispatch 带唯一 idempotency key;
|
||
- Agent 重复上报不能重复产生危险动作;
|
||
- 非幂等步骤中断后默认 `INCONCLUSIVE` 或人工确认;
|
||
- 租约到期只代表执行权回收,不代表命令一定未执行;
|
||
- 重派前必须根据模板类型和现场状态决定是否安全;
|
||
- 任何重放都必须在事件流中留下原因和原始 dispatch 引用。
|
||
|
||
---
|
||
|
||
## 10. 证据、失败分析与指标
|
||
|
||
### 10.1 Evidence Bundle 当前内容
|
||
|
||
每个完成 Run 的对象目录:
|
||
|
||
```text
|
||
var/objects/runs/<run_id>/
|
||
├── manifest.json
|
||
└── events.ndjson
|
||
```
|
||
|
||
`manifest.json` 当前包含:
|
||
|
||
- schema version;
|
||
- Run ID;
|
||
- workflow key / version / spec hash;
|
||
- environment fingerprint;
|
||
- checkpoint;
|
||
- event count;
|
||
- 文件路径和字节数。
|
||
|
||
`events.ndjson` 每行一条按 `seq` 排序的事件,包含时间、来源、kind、严重度、消息和 payload。
|
||
|
||
### 10.2 目标证据包
|
||
|
||
真实硬件阶段应补充:
|
||
|
||
- Agent stdout / stderr 和工具原始输出;
|
||
- SMART、identify、PCIe、OS、驱动、BIOS 快照;
|
||
- fio JSON、校验 hash、失败 LBA 范围;
|
||
- 蓝屏 / panic dump、系统日志和屏幕证据;
|
||
- OOB 电源状态与画面指纹;
|
||
- 固件文件 hash 与签名验证结果;
|
||
- 审批、命令模板和参数;
|
||
- 文件级 SHA-256、缺失字段和生成器版本。
|
||
|
||
### 10.3 失败分类
|
||
|
||
| 分类 | 含义 |
|
||
|---|---|
|
||
| `DUT_DEFECT` | DUT / 固件自身缺陷 |
|
||
| `INFRA_FAILURE` | 主机、网络、供电、OOB 等基础设施问题 |
|
||
| `SCRIPT_FAILURE` | 测试脚本、适配器或参数问题 |
|
||
| `DATA_INTEGRITY` | 数据不一致,默认冻结现场 |
|
||
| `UNKNOWN` | 证据不足或尚未分类 |
|
||
|
||
基础设施故障必须与 DUT 缺陷分开统计,否则会污染固件质量结论。
|
||
|
||
### 10.4 失败签名目标
|
||
|
||
建议由以下稳定要素归一化后计算 hash:
|
||
|
||
```text
|
||
workflow step
|
||
+ normalized error codes
|
||
+ normalized log templates
|
||
+ host state
|
||
+ DUT enumeration state
|
||
+ data integrity state
|
||
+ recovery outcome
|
||
+ environment fingerprint
|
||
```
|
||
|
||
原始时间、随机 ID、绝对路径等高基数字段不应直接进入签名。
|
||
|
||
### 10.5 核心产品指标
|
||
|
||
| 指标 | 定义 |
|
||
|---|---|
|
||
| UCR | 无人值守完成 Run / 已完成 Run |
|
||
| Human Touches | Run 执行期间人工操作次数 |
|
||
| Evidence Completeness | 已收集必需证据字段 / 应收集字段 |
|
||
| Recovery Success Rate | 自动恢复成功次数 / 自动恢复尝试次数 |
|
||
| Infra False Positive Rate | 被误判为 DUT 缺陷的基础设施故障比例 |
|
||
| Reproduction Rate | 可由证据与最小步骤成功复现的失败比例 |
|
||
|
||
当前 `/dashboard/summary` 已提供总 Run、活动 Run、完成 Run 和 UCR 的最小聚合。
|
||
|
||
---
|
||
|
||
## 11. 配置与环境变量
|
||
|
||
所有应用变量使用 `FLASHOPS_` 前缀。
|
||
|
||
| 变量 | 开发默认值 | 说明 |
|
||
|---|---|---|
|
||
| `FLASHOPS_ENV` | `development` | 环境标识 |
|
||
| `FLASHOPS_DATABASE_URL` | `sqlite+aiosqlite:///.../var/flashops.db` | SQLAlchemy DSN |
|
||
| `FLASHOPS_OBJECT_STORE_URL` | `.../var/objects` | 当前必须为本地路径 |
|
||
| `FLASHOPS_BUS_URL` | 空 | 目标 Redis / NATS 地址,当前未启用 |
|
||
| `FLASHOPS_AGENT_ENROLLMENT_TOKEN` | 空 | 控制面与 Agent 共享的首次注册口令;生产控制面缺失时拒绝注册 |
|
||
| `FLASHOPS_API_HOST` | `0.0.0.0` | 监听地址 |
|
||
| `FLASHOPS_API_PORT` | `8000` | 监听端口 |
|
||
| `FLASHOPS_CORS_ORIGINS` | `http://localhost:3000` | 逗号分隔 CORS 来源 |
|
||
| `FLASHOPS_ENGINE_TICK_S` | `0.5` | 引擎 tick 间隔 |
|
||
| `FLASHOPS_TIME_SCALE` | `1.0` | 模拟时间倍率;测试使用更小值 |
|
||
|
||
### 11.1 时间策略默认值
|
||
|
||
| 配置 | 默认值 |
|
||
|---|---:|
|
||
| 心跳间隔 | 5s |
|
||
| 心跳降级 | 15s |
|
||
| 心跳丢失 | 30s |
|
||
| Step 租约 TTL | 60s |
|
||
| 租约拉取超时 | 20s |
|
||
| Step 默认超时 | 600s |
|
||
| 恢复后稳定等待 | 20s |
|
||
| AC 断电安全间隔 | 10s |
|
||
| 单循环最大恢复 | 3 |
|
||
| 单 Run 最大恢复 | 20 |
|
||
|
||
当前这些时间策略在 `config.py::TimingPolicy` 中定义,还未全部开放为环境变量。
|
||
|
||
### 11.2 Host Agent 运行变量
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `FLASHOPS_AGENT_SERVER` | `http://127.0.0.1:8000` | 控制面基础地址 |
|
||
| `FLASHOPS_AGENT_HOST_ID` | 空 | 绑定已有 Test Host ID |
|
||
| `FLASHOPS_AGENT_HOST_NAME` | 本机 hostname | 按名称绑定或新建 Test Host |
|
||
| `FLASHOPS_AGENT_STATE_PATH` | `~/.flashops/agent-state.json` | 本地 bearer 凭据文件,权限固定为 `0600` |
|
||
| `FLASHOPS_AGENT_ENROLLMENT_TOKEN` | 空 | 与控制面一致的首次注册口令 |
|
||
| `FLASHOPS_AGENT_BASIC_USER` | 空 | 可选上游认证网关用户名;当前预览站未使用 |
|
||
| `FLASHOPS_AGENT_BASIC_PASSWORD` | 空 | 可选上游认证网关密码;当前预览站未使用 |
|
||
|
||
用户名和密码必须同时配置。Agent 不会把 bearer token 输出到终端;首次注册响应中的原始 token
|
||
只写入本机状态文件。重新注册会轮换 token,使旧 token 立即失效。
|
||
|
||
### 11.3 配置原则
|
||
|
||
- 凭据不得写进仓库、本文档、systemd unit 或 Nginx 注释;
|
||
- 生产密码仅保存在受控凭据文件、密码管理器或服务器哈希文件;
|
||
- 修改生产环境变量后必须重启 `flashops.service`;
|
||
- 数据库和对象目录必须可写,其余应用目录应只读;
|
||
- SQLite URL 使用四个斜杠表示绝对路径;
|
||
- 多 worker 前必须先切换 PostgreSQL 和外部事件总线。
|
||
|
||
---
|
||
|
||
## 12. 腾讯云生产部署与运维
|
||
|
||
### 12.1 生产资源
|
||
|
||
| 项目 | 当前值 |
|
||
|---|---|
|
||
| 云厂商 | 腾讯云 CVM |
|
||
| 操作系统 | Ubuntu 24.04 |
|
||
| 公网 IP | `62.234.39.66` |
|
||
| 域名 | `flashops.imagebrewing.com`、`git.imagebrewing.com` |
|
||
| DNS | DNSPod A 记录,TTL 600 |
|
||
| 反向代理 | 宝塔 Nginx |
|
||
| 应用服务 | `flashops.service` |
|
||
| 应用用户 | `ubuntu` |
|
||
| 内部端口 | `127.0.0.1:18080` |
|
||
| 应用根目录 | `/data/wangzhan/app/storage-labos` |
|
||
| 持久化目录 | `/data/wangzhan/app/storage-labos/flashops/var` |
|
||
| Nginx vhost | `/www/server/panel/vhost/nginx/flashops.imagebrewing.com.conf` |
|
||
| 证书 | `/etc/letsencrypt/live/flashops.imagebrewing.com/` |
|
||
| 发布包备份 | `/data/wangzhan/backups/flashops-release-20260727.tar.gz` |
|
||
| Git 服务 | Gitea 1.27.0 Docker 容器,回环端口 `127.0.0.1:13000` |
|
||
| Git 实时数据 | `/data/gitea/data`,本地文件系统 |
|
||
| Git 云端备份 | `/chucun/wangzhan-production/backups/gitea/`,每日定时 dump + SHA-256 |
|
||
|
||
初次证书签发于 2026-07-27,初始到期日为 2026-10-25。Certbot 已配置自动续期,并通过
|
||
`--dry-run` 验证;续期成功后会执行 `/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh`。
|
||
|
||
> Agent 注册 / 心跳能力已在本地代码与进程联调中通过,但本节所述线上版本尚未发布该增量。
|
||
> 发布前需创建 root-only `/etc/flashops/flashops.env` 并配置
|
||
> `FLASHOPS_AGENT_ENROLLMENT_TOKEN`;不得把注册口令写进 systemd unit 或仓库。
|
||
|
||
### 12.2 生产安全边界
|
||
|
||
- Uvicorn 仅监听回环地址,不直接暴露 18080;
|
||
- Nginx 统一处理 TLS;FlashOps 当前作为匿名半成品预览开放;
|
||
- ACME challenge 路径不要求认证,以支持自动续期;
|
||
- systemd 启用 `NoNewPrivileges`、`PrivateTmp`、`ProtectSystem=full` 和 `ProtectHome`;
|
||
- 仅 `flashops/var` 允许应用写入;
|
||
- 当前 SQLite 生产基线固定一个 worker;
|
||
- 公开预览不得绑定真实 DUT、客户数据、真实 OOB 或有效危险命令;接入前必须先启用应用级
|
||
账号、RBAC、审批与命令模板白名单。
|
||
|
||
### 12.3 服务管理
|
||
|
||
```bash
|
||
sudo systemctl status flashops --no-pager -l
|
||
sudo systemctl restart flashops
|
||
sudo systemctl stop flashops
|
||
sudo systemctl start flashops
|
||
sudo systemctl enable flashops
|
||
```
|
||
|
||
健康检查:
|
||
|
||
```bash
|
||
curl -fsS http://127.0.0.1:18080/api/v1/health
|
||
curl https://flashops.imagebrewing.com/api/v1/health
|
||
```
|
||
|
||
### 12.4 日志
|
||
|
||
```bash
|
||
# 应用实时日志
|
||
sudo journalctl -u flashops -f
|
||
|
||
# 最近 200 行
|
||
sudo journalctl -u flashops -n 200 --no-pager
|
||
|
||
# Nginx 访问 / 错误日志
|
||
sudo tail -f /www/wwwlogs/flashops.imagebrewing.com.log
|
||
sudo tail -f /www/wwwlogs/flashops.imagebrewing.com.error.log
|
||
```
|
||
|
||
### 12.5 Nginx 与证书
|
||
|
||
```bash
|
||
sudo nginx -t
|
||
sudo systemctl reload nginx
|
||
sudo certbot certificates
|
||
sudo certbot renew --cert-name flashops.imagebrewing.com \
|
||
--dry-run --non-interactive --no-random-sleep-on-renew
|
||
```
|
||
|
||
任何 Nginx 修改都必须先 `nginx -t`,只有成功后才能 reload。
|
||
|
||
### 12.6 发布流程
|
||
|
||
标准流程:
|
||
|
||
1. 本地 `make test`;
|
||
2. 确认前端和后端兄弟目录结构;
|
||
3. 打包时排除 `.venv`、`var`、缓存和 `__pycache__`;
|
||
4. 上传到服务器临时目录;
|
||
5. 解压到独立 incoming 目录;
|
||
6. 安装服务器虚拟环境和依赖;
|
||
7. 保留生产 `var/`,不要用本地空目录覆盖;
|
||
8. 安装 / 校验 systemd unit;
|
||
9. 重启服务并轮询内部 health;
|
||
10. `nginx -t` 后 reload;
|
||
11. 从服务器和外部网络各验证一次匿名 HTTPS 200 和 API health;
|
||
12. 保存带 SHA-256 的发布包到 `/data/wangzhan/backups/`。
|
||
|
||
### 12.7 SQLite 备份
|
||
|
||
推荐使用 SQLite 在线备份 API,避免直接复制正在写入的数据库:
|
||
|
||
```bash
|
||
sudo /data/wangzhan/app/storage-labos/flashops/.venv/bin/python - <<'PY'
|
||
import datetime
|
||
import sqlite3
|
||
from pathlib import Path
|
||
|
||
source = Path('/data/wangzhan/app/storage-labos/flashops/var/flashops.db')
|
||
stamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
||
target = Path('/data/wangzhan/backups') / f'flashops-db-{stamp}.db'
|
||
target.parent.mkdir(parents=True, exist_ok=True)
|
||
with sqlite3.connect(source) as src, sqlite3.connect(target) as dst:
|
||
src.backup(dst)
|
||
print(target)
|
||
PY
|
||
```
|
||
|
||
恢复数据库是高影响操作:先停止服务,核对目标备份并额外备份当前数据库,再替换文件、修复
|
||
`ubuntu:ubuntu` 所有权、启动服务并检查事件序号连续性。不得在未确认备份内容时直接覆盖。
|
||
|
||
### 12.8 公网预览访问策略
|
||
|
||
2026-07-27 起,`flashops.imagebrewing.com` 已取消 Nginx Basic Auth 和 IP 访问限制,匿名用户
|
||
可以查看控制台并调用模拟 API。取消前的 Nginx 配置保留在:
|
||
|
||
```text
|
||
/www/server/panel/vhost/nginx/flashops.imagebrewing.com.conf.before-public-20260727
|
||
```
|
||
|
||
这是临时产品演示策略,不是最终安全方案。接入真实硬件或非公开数据前,不能只恢复 Basic Auth;
|
||
必须优先实现应用账号、RBAC、危险动作审批和审计上下文。
|
||
|
||
### 12.9 发布验收清单
|
||
|
||
- [ ] `flashops.service` 为 active;
|
||
- [ ] Nginx 为 active 且 `nginx -t` 成功;
|
||
- [ ] `getent ahostsv4 flashops.imagebrewing.com` 指向 `62.234.39.66`;
|
||
- [ ] HTTP 返回 301 到 HTTPS;
|
||
- [ ] HTTPS 证书域名正确且未过期;
|
||
- [ ] 未登录访问控制台最终返回 200;
|
||
- [ ] `/api/v1/health` 返回 `status=ok`;
|
||
- [ ] `/api/v1/dashboard/summary` 返回合法 JSON;
|
||
- [ ] 生产 `var/flashops.db` 和 `var/objects` 可写;
|
||
- [ ] 应用最近日志无 error;
|
||
- [ ] Certbot 模拟续期成功;
|
||
- [ ] 发布包和数据库备份已记录。
|
||
|
||
### 12.10 Gitea 团队 Git 服务
|
||
|
||
| 项目 | 当前值 |
|
||
|---|---|
|
||
| 页面与 clone 地址 | `https://git.imagebrewing.com` |
|
||
| 项目仓库 | `https://git.imagebrewing.com/yuanshuai/storage-labos` |
|
||
| 容器 | `gitea` / `gitea/gitea:1.27.0` |
|
||
| HTTP 监听 | `127.0.0.1:13000`,不直接暴露公网 |
|
||
| Git 传输 | HTTPS;当前不开放独立 SSH 端口 |
|
||
| 匿名访问 | 可查看 public 仓库与最近提交 |
|
||
| 注册 | 开放申请,但必须由管理员人工批准后才能登录 |
|
||
| 实时数据 | `/data/gitea/data`,不得放在 COSFS 上 |
|
||
| 备份 | 每日约 03:20 dump 到 `/chucun/wangzhan-production/backups/gitea/` |
|
||
|
||
Gitea 管理员凭据只保存在本机被 `.gitignore` 排除的 `gitea-admin.env.local`,不写进手册、
|
||
仓库或服务器部署文件。团队成员先在网页注册,再由管理员在“站点管理 → 用户账户”中批准,
|
||
随后把成员加入仓库协作者。
|
||
|
||
```bash
|
||
# 服务与健康
|
||
sudo docker ps --filter name=gitea
|
||
curl -fsS http://127.0.0.1:13000/api/healthz
|
||
|
||
# 日志
|
||
sudo docker logs --tail 200 gitea
|
||
|
||
# 备份定时器与手工备份
|
||
sudo systemctl status flashops-gitea-backup.timer --no-pager
|
||
sudo systemctl start flashops-gitea-backup.service
|
||
```
|
||
|
||
生产服务器没有启用 Gitea Actions Runner。原因是 Runner 需要执行仓库中的任意代码,直接挂在
|
||
同一台生产服务器会放大供应链风险;当前 PR 必须附本地 `make test` 结果。未来 CI 应部署到
|
||
隔离机器或一次性 Runner,而不是把 Docker socket 交给生产仓库任务。
|
||
|
||
---
|
||
|
||
## 13. 测试、质量门与开发规范
|
||
|
||
### 13.1 当前测试基线
|
||
|
||
截至 2026-07-27:
|
||
|
||
- 测试总数:25;
|
||
- 含 Agent 增量的完整测试集本地连续 20 轮全部通过;
|
||
- 腾讯云生产环境依赖下连续 20 轮全部通过;
|
||
- 服务器单轮约 1–2 秒;
|
||
- 证书续期 dry-run 通过;
|
||
- systemd 重启后 health 恢复通过;
|
||
- FlashOps 匿名 HTTPS 200、Gitea HTTPS 200 与两个证书域名验证通过。
|
||
|
||
### 13.2 测试分层
|
||
|
||
| 层 | 文件 | 重点 |
|
||
|---|---|---|
|
||
| 状态机 | `test_state_machine.py` | 合法 / 非法转移与终态 |
|
||
| 恢复策略 | `test_recovery.py` | Agent、网络、OOB、完整性触发 |
|
||
| 安全 | `test_safety.py` | 序列号、系统盘、分区和门禁组合 |
|
||
| 端到端 | `test_e2e_vertical_slice.py` | 创建、恢复、证据、暂停、继续、急停 |
|
||
| Agent 安全 | `test_agent_enrollment.py` | 生产缺少注册口令时默认拒绝 |
|
||
| Agent 状态 | `test_agent_status.py` | 新鲜、迟到、失联和主动降级心跳 |
|
||
| Agent 客户端 | `services/host-agent/tests/test_client.py` | HTTP 契约、401、凭据 `0600` |
|
||
|
||
### 13.3 提交前检查
|
||
|
||
```bash
|
||
cd "/Users/yuanshuai/Desktop/STORAGE LABOS/flashops"
|
||
make test
|
||
make demo
|
||
```
|
||
|
||
如果改了部署配置,再检查:
|
||
|
||
```bash
|
||
systemd-analyze verify deploy/flashops.service # 在 Linux 上执行
|
||
sudo nginx -t # 在目标服务器执行
|
||
```
|
||
|
||
### 13.4 开发规则
|
||
|
||
1. 状态变化必须追加事件;
|
||
2. 新状态必须更新枚举、转移表、文档和测试;
|
||
3. 新事件 kind 只增不改,历史事件已经落库;
|
||
4. 安全规则保持纯函数,拒绝发生在下发之前;
|
||
5. 危险步骤默认不可自动重试;
|
||
6. API 路由不堆业务逻辑;
|
||
7. 真实适配器与模拟适配器使用同一契约;
|
||
8. 大日志写对象存储,不塞进数据库 JSON;
|
||
9. 生产数据和凭据不进 Git;
|
||
10. 修改数据结构必须同时提供迁移和回滚说明;
|
||
11. 文档中的“已实现”必须能由代码或验收记录证明;
|
||
12. 生产发布必须保留可核验的归档包和数据库备份。
|
||
|
||
### 13.5 Git 仓库与团队协作
|
||
|
||
当前 Git 基线:
|
||
|
||
| 项目 | 当前值 |
|
||
|---|---|
|
||
| 本地仓库根 | `STORAGE LABOS/` |
|
||
| 默认分支 | `main` |
|
||
| 远程 | `https://git.imagebrewing.com/yuanshuai/storage-labos.git` |
|
||
| 网页 | `https://git.imagebrewing.com/yuanshuai/storage-labos` |
|
||
| 可见性 | public:匿名可读;提交、Issue 和 PR 写操作必须登录 |
|
||
| 管理员 | `yuanshuai`;密码只在本机忽略文件中保存 |
|
||
| 团队规则入口 | 根目录 `CONTRIBUTING.md` |
|
||
|
||
不要通过聊天工具反复传 ZIP 继续开发。团队以远程仓库为唯一代码与文档版本源,新成员执行:
|
||
|
||
```bash
|
||
git clone https://git.imagebrewing.com/yuanshuai/storage-labos.git
|
||
cd storage-labos/flashops
|
||
make setup
|
||
make test
|
||
```
|
||
|
||
分支规则:
|
||
|
||
- `main`:始终保持可测试、可发布,禁止直接开发和强制推送;
|
||
- `feat/<short-name>`:功能;
|
||
- `fix/<short-name>`:缺陷;
|
||
- `docs/<short-name>`:文档;
|
||
- `chore/<short-name>`:仓库、依赖、CI 和部署。
|
||
|
||
日常开发:
|
||
|
||
```bash
|
||
git switch main
|
||
git pull --ff-only
|
||
git switch -c feat/agent-step-lease
|
||
|
||
# 修改后先验证
|
||
cd flashops
|
||
make test
|
||
make demo
|
||
cd ..
|
||
|
||
# 只暂存本次相关文件;混合工作区不要直接 git add -A
|
||
git status -sb
|
||
git add <明确的文件或目录>
|
||
git diff --cached --stat
|
||
git diff --cached --check
|
||
git commit -m "feat(agent): add step lease polling"
|
||
git push -u origin "$(git branch --show-current)"
|
||
```
|
||
|
||
采用 Conventional Commits:`type(scope): subject`。
|
||
|
||
| 类型 | 用途 | 示例 |
|
||
|---|---|---|
|
||
| `feat` | 新功能 | `feat(agent): add step lease polling` |
|
||
| `fix` | 修复 | `fix(safety): reject stale host heartbeat` |
|
||
| `docs` | 文档 | `docs(manual): add gitea operations` |
|
||
| `test` | 测试 | `test(agent): cover token rotation` |
|
||
| `refactor` | 不改变行为的重构 | `refactor(events): isolate projection writer` |
|
||
| `perf` | 性能优化 | `perf(api): reduce timeline query cost` |
|
||
| `chore` | 工具、依赖、部署 | `chore(repo): initialize collaboration repository` |
|
||
|
||
每项工作使用独立分支和 Pull Request。PR 必须说明改动、原因、影响、验证结果、风险和回滚;
|
||
至少一名团队成员评审后再合并。优先 Squash merge,合并后删除功能分支。只允许对自己的未合并
|
||
分支使用 `--force-with-lease`,严禁对 `main` 强推;已共享的错误提交使用 `git revert` 撤销。
|
||
|
||
团队成员在 Gitea 注册后需要管理员人工批准。批准后在仓库“设置 → 协作者”中授予写权限;
|
||
不要共用管理员账号。离职或设备丢失时立即移除成员、撤销 token 并检查审计记录。
|
||
|
||
以下内容永不入库:`.env`、密码、token、私钥、证书私钥、Agent state、SQLite、Evidence、日志、
|
||
生产备份、`.venv`、`node_modules` 和未脱敏客户数据。若凭据误入提交,先轮换凭据,再清理历史;
|
||
仅删除当前文件不能消除 Git 历史中的泄露。
|
||
|
||
### 13.6 CI 与 Pull Request 质量门
|
||
|
||
根目录 `.github/workflows/ci.yml` 已提供 Python 3.9 / 3.12 测试矩阵模板,执行控制平面与
|
||
Host Agent 测试并编译全部 Python 模块。它可用于后续 GitHub 镜像或启用 Gitea Actions。
|
||
|
||
当前生产 Gitea 未配置 Actions Runner,因此这个工作流不会自动执行。这样做是刻意的:把
|
||
Runner 和 Docker socket 放在同一台生产服务器,会允许仓库代码获得过大的服务器权限。
|
||
现阶段 PR 必须附上本地 `make test` 与相关演示结果;下一阶段把 Runner 放到隔离机器或一次性
|
||
执行环境,再把检查设为 `main` 的必需状态。
|
||
|
||
仍待补充的质量门:PostgreSQL 集成、Ruff / mypy、工作流 schema、依赖漏洞与 secret scan、
|
||
Nginx / systemd 静态检查、模拟器长跑、发布包 SBOM 和签名。
|
||
|
||
---
|
||
|
||
## 14. 已知边界与演进路线
|
||
|
||
### 14.1 当前已知边界
|
||
|
||
| 边界 | 风险 | 处理方向 |
|
||
|---|---|---|
|
||
| SQLite 单 writer | 多并发会阻塞 | 当前单 worker;后续 PostgreSQL |
|
||
| 无 Alembic | 表结构难升级 | 引入版本化 migration |
|
||
| 无应用账号 / RBAC | 无细粒度审计与授权 | OIDC/SSO、角色、审批 |
|
||
| 匿名公网预览 | 页面和模拟 API 可被任何人调用 | 不接真实资产;真实阶段启用 OIDC/RBAC/审批 |
|
||
| Agent 无 Step 租约 | 只能注册和心跳,不能领取真实任务 | 实现 lease、ACK、续租和完成上报 |
|
||
| OOB 为模拟 | 无法验证真实断电恢复 | 接可牺牲硬件和独立网络 OOB |
|
||
| Step runtime 不完整 | 工作流只是部分执行 | planner/runtime/lease 落地 |
|
||
| Evidence 最小 | 不足以支撑真实缺陷结论 | 接工具原始输出、hash、dump 和画面 |
|
||
| 失败聚类未实现 | 失败中心为静态原型 | 归一化、签名、合并 / 拆分和复现任务 |
|
||
| 多数页面静态 | UI 与真实数据可能不一致 | 按 8.4 顺序接 API |
|
||
| 依赖范围较宽 | 重新安装可能拿到不同版本 | 生成并维护 production lock file |
|
||
| 无定时 DB 备份 | 数据恢复点不稳定 | systemd timer + 备份校验 + 保留策略 |
|
||
|
||
### 14.2 建议演进顺序
|
||
|
||
#### Phase 1:独立 Agent 契约
|
||
|
||
- 已完成:注册、token 轮换、心跳、状态老化、主机探针和本地凭据;
|
||
- 待完成:租约、ACK、续租、结果上报;
|
||
- idempotency key 和重放保护;
|
||
- 模拟 Agent 与控制平面完全分进程;
|
||
- Agent 崩溃、网络断开、控制平面重启测试。
|
||
|
||
#### Phase 2:真实只读硬件接入
|
||
|
||
- NVMe identify / SMART / BDF / device path;
|
||
- Host 环境探针;
|
||
- OOB 只读电源和画面状态;
|
||
- 不执行刷写、Format、Sanitize 或断电。
|
||
|
||
#### Phase 3:受控危险动作
|
||
|
||
- 命令模板白名单;
|
||
- 审批、双人复核、短期授权;
|
||
- 可牺牲 DUT;
|
||
- 真实刷写、Reset、ATX、AC 阶梯;
|
||
- 完整 Evidence Bundle。
|
||
|
||
#### Phase 4:多工位与生产化
|
||
|
||
- PostgreSQL、外部事件总线、对象存储;
|
||
- 多工位调度与资源配额;
|
||
- SSO / RBAC / 审计;
|
||
- HA、监控、告警、备份和灾备;
|
||
- 评估 Temporal 是否只替换 runtime。
|
||
|
||
#### Phase 5:失败智能与版本决策
|
||
|
||
- 失败签名聚类;
|
||
- 环境可比性检查;
|
||
- 自动裁剪最小复现工作流;
|
||
- A/B 指标和证据门禁;
|
||
- AI 只生成带证据引用的摘要,不替代工程师结论。
|
||
|
||
---
|
||
|
||
## 15. 故障排查
|
||
|
||
### 15.1 公网返回 502
|
||
|
||
```bash
|
||
sudo systemctl status flashops --no-pager -l
|
||
sudo journalctl -u flashops -n 200 --no-pager
|
||
curl -v http://127.0.0.1:18080/api/v1/health
|
||
sudo nginx -t
|
||
sudo tail -n 100 /www/wwwlogs/flashops.imagebrewing.com.error.log
|
||
```
|
||
|
||
常见原因:服务未启动、虚拟环境缺依赖、端口不一致、数据库目录不可写、systemd 安全策略阻止写入。
|
||
|
||
### 15.2 公网返回 401 / 403
|
||
|
||
当前预览站允许匿名访问,首页和普通查询 API 不应由 Nginx 返回 401 / 403。遇到问题时:
|
||
|
||
- 用无痕窗口访问,排除浏览器缓存的旧认证状态;
|
||
- 检查 Nginx 生效配置中是否残留 `auth_basic`、`allow` 或 `deny`;
|
||
- 区分普通页面与 Agent API:Agent 注册口令或 bearer token 无效仍会返回 401;
|
||
- 执行 `nginx -t`,确认无误后 reload;
|
||
- 查看站点 access/error 日志,确认状态码来自 Nginx、应用还是上游安全产品。
|
||
|
||
### 15.3 HTTPS 证书错误
|
||
|
||
```bash
|
||
getent ahostsv4 flashops.imagebrewing.com
|
||
sudo certbot certificates
|
||
sudo nginx -T | grep -A8 -B3 flashops.imagebrewing.com
|
||
openssl s_client -connect 62.234.39.66:443 \
|
||
-servername flashops.imagebrewing.com </dev/null 2>/dev/null \
|
||
| openssl x509 -noout -subject -issuer -dates
|
||
```
|
||
|
||
检查 DNS 是否指向正确 IP、证书路径是否存在、Nginx 是否已经 reload。
|
||
|
||
### 15.4 控制台 404
|
||
|
||
确认目录关系:
|
||
|
||
```text
|
||
/data/wangzhan/app/storage-labos/
|
||
├── flashops/
|
||
└── 前端UI八页面完成/
|
||
```
|
||
|
||
`main.py` 通过 `REPO_ROOT.parent / "前端UI八页面完成"` 查找控制台。
|
||
|
||
### 15.5 SQLite locked / 事件序号冲突
|
||
|
||
- 确认 Uvicorn 仍为一个 worker;
|
||
- 不要从多个进程同时直接操作同一个 SQLite 文件;
|
||
- 所有应用事务必须经 `session_scope()`;
|
||
- 所有 Run 状态写入必须经 `append_event()`;
|
||
- 检查是否有脚本绕过服务直接写库;
|
||
- 若需要多进程并发,迁移 PostgreSQL,不要继续堆 SQLite 锁。
|
||
|
||
### 15.6 服务不断重启
|
||
|
||
```bash
|
||
sudo systemctl show flashops -p NRestarts -p ExecMainStatus
|
||
sudo journalctl -u flashops --since '30 minutes ago' --no-pager
|
||
```
|
||
|
||
重点检查:`PYTHONPATH`、依赖安装、数据库 URL、目录权限、配置拼写和 18080 端口占用。
|
||
|
||
### 15.7 测试本机通过、服务器失败
|
||
|
||
比较:
|
||
|
||
```bash
|
||
.venv/bin/python --version
|
||
.venv/bin/pip freeze
|
||
```
|
||
|
||
当前 requirements 使用范围约束而非完整 lock,同一天不同环境可能安装不同 FastAPI、Starlette、
|
||
httpx 或 SQLAlchemy 版本。应先复现服务器依赖,再决定修代码或锁版本,不能只在本机降级掩盖并发缺陷。
|
||
|
||
### 15.8 Gitea 无法访问、注册或推送
|
||
|
||
```bash
|
||
sudo docker ps --filter name=^gitea$
|
||
sudo docker logs --tail 100 gitea
|
||
curl -fsS http://127.0.0.1:13000/api/healthz
|
||
getent ahostsv4 git.imagebrewing.com
|
||
sudo nginx -t
|
||
```
|
||
|
||
- 公开仓库页面应允许匿名查看和克隆;
|
||
- 新成员注册后需要管理员在 Gitea 后台批准,未批准时不能登录;
|
||
- 推送必须使用已批准账号,不要把密码或访问令牌写入远程地址;
|
||
- 若备份失败,检查 `gitea-backup.service` 日志和 `/chucun` 挂载状态;
|
||
- Gitea 实时数据必须保留在 `/data/gitea/data`,不得迁移到 COSFS 挂载目录。
|
||
|
||
---
|
||
|
||
## 16. 术语表与 FAQ
|
||
|
||
### 16.1 术语表
|
||
|
||
| 术语 | 含义 |
|
||
|---|---|
|
||
| DUT | Device Under Test,被测 SSD / NVMe 设备 |
|
||
| Host | 连接 DUT 并运行测试工具的主机 |
|
||
| Agent | 部署在 Host 上,拉取并执行已批准步骤的服务 |
|
||
| OOB | Out-of-Band,主机失联后仍可观测 / 控制的带外通道 |
|
||
| Run | 一次完整工作流执行实例 |
|
||
| Step | Run 中最小可调度执行单元 |
|
||
| Workflow | 版本化 SOP 和步骤定义 |
|
||
| Checkpoint | 可安全恢复的执行边界 |
|
||
| Evidence Bundle | 一次 Run 的证据清单与文件集合 |
|
||
| Event Sourcing | 用 append-only 事件作为状态变化真相 |
|
||
| Projection | 从事件派生的当前状态读模型 |
|
||
| UCR | Unattended Completion Rate,无人值守完成率 |
|
||
| Failure Signature | 归一化失败要素生成的聚类标识 |
|
||
| Idempotency | 同一请求重复执行不会产生额外副作用 |
|
||
| Lease | 控制平面临时授予 Agent 的步骤执行权 |
|
||
| BDF | PCIe Bus:Device.Function 地址 |
|
||
|
||
### 16.2 FAQ
|
||
|
||
#### 为什么生产还用 SQLite?
|
||
|
||
当前是单工位、单 worker、内部演示基线,SQLite 能降低部署摩擦。它不是多工位最终方案;增加并发前
|
||
必须切换 PostgreSQL。
|
||
|
||
#### 为什么不用 Airflow / Temporal?
|
||
|
||
恢复语义跨越 Agent、OS、OOB 和物理供电,且非幂等步骤默认不重跑。首版用纯函数状态机更容易
|
||
精确控制和测试。多工位跨节点调度成为瓶颈时,再评估用 Temporal 替换 runtime。
|
||
|
||
#### 为什么事件不能按时间戳排序?
|
||
|
||
Host、OOB 和控制平面时钟可能不同,主机崩溃时还会丢失最后日志。控制平面分配的 `seq` 才能提供
|
||
稳定、可重放的总顺序。
|
||
|
||
#### 为什么数据完整性失败直接冻结?
|
||
|
||
自动重启、重跑或写盘可能覆盖最关键的现场。宁可暂停等待工程师,也不能为了完成率破坏证据。
|
||
|
||
#### 为什么 Agent 要用拉模式?
|
||
|
||
客户测试主机通常位于 NAT、内网或防火墙后面。由 Agent 主动向控制平面发起连接更容易部署,
|
||
也减少暴露入站端口。
|
||
|
||
#### 为什么控制台和后端放在一起?
|
||
|
||
当前静态控制台由 FastAPI 同源挂载,避免单独的前端构建和 CORS 配置,适合原型与演示。后续如果
|
||
切换 Next.js 或独立 SPA,可以保持 `/api/v1` 契约不变。
|
||
|
||
#### 管理凭据在哪里?
|
||
|
||
本文档不保存明文密码。Gitea 管理员、SSH 与云平台凭据通过本机受控文件、系统钥匙串或密码管理器
|
||
交接;这些文件必须命中 `.gitignore`,不得进入提交历史。
|
||
|
||
#### 可以直接把 18080 暴露公网吗?
|
||
|
||
不可以。该端口没有应用级认证和 TLS,必须只绑定 `127.0.0.1`,公网只走 Nginx 443。
|
||
|
||
---
|
||
|
||
## 17. 变更记录
|
||
|
||
### V1.2 — 2026-07-27
|
||
|
||
- 上线 `git.imagebrewing.com` Gitea 团队 Git 服务和公开项目仓库;
|
||
- 增加分支、提交、评审、密钥和本地验证规范;
|
||
- 使用 `/chucun/wangzhan-production/backups/gitea/` 保存每日 Gitea 校验备份;
|
||
- FlashOps 预览站取消 Nginx Basic Auth 与 IP 限制,普通页面和查询 API 可匿名访问;
|
||
- 明确匿名预览不得连接真实硬件、危险动作或非公开数据,正式生产前必须完成 SSO / RBAC。
|
||
|
||
### V1.1 — 2026-07-27
|
||
|
||
- 新增独立拉模式 Host Agent;
|
||
- 新增 Agent 注册、token 轮换、Bearer 心跳和 Host 状态查询 API;
|
||
- 控制平面只保存 token SHA-256 摘要,Agent 凭据文件权限固定为 `0600`;
|
||
- 生产环境缺少 Agent 注册口令时默认拒绝注册;
|
||
- Agent 保留可选网关 Basic Auth 能力,当前匿名预览环境不启用;
|
||
- 新增 Agent 客户端、注册安全、状态老化和进程联调测试;全量测试增至 22 项;
|
||
- Step 租约、ACK、事件、完成与产物上传仍为下一阶段。
|
||
|
||
### V1.0 — 2026-07-27
|
||
|
||
- 根据实际源码建立首版完整项目手册;
|
||
- 记录控制平面、14 张表、状态机、六项门禁和 Evidence Bundle;
|
||
- 记录 8 个核心控制台页面及真实 / 静态边界;
|
||
- 记录腾讯云、DNSPod、systemd、Nginx 和 Let's Encrypt 部署;
|
||
- 记录 SQLite 并发写入修复和单 worker 约束;
|
||
- 记录本机与服务器连续 20 轮测试结果;
|
||
- 给出生产发布、验收、备份、密码轮换和故障排查流程;
|
||
- 明确独立 Agent、真实硬件、RBAC、PostgreSQL 和失败聚类为后续工作。
|
||
|
||
---
|
||
|
||
## 文档维护要求
|
||
|
||
以下变化发生时,必须同步更新本文:
|
||
|
||
- 新增 / 删除 API;
|
||
- 新增状态、事件种类或恢复级别;
|
||
- 修改表结构或迁移策略;
|
||
- 修改环境变量;
|
||
- 调整域名、端口、服务器路径或服务名;
|
||
- 新增真实 Agent、Adapter 或 OOB;
|
||
- 安全门禁、审批或认证方式变化;
|
||
- Evidence Bundle schema 变化;
|
||
- 生产发布和回滚流程变化;
|
||
- 能力从“模拟 / 原型”变为“真实已验收”。
|
||
|
||
文档里的完成状态必须由代码、测试或生产验收记录支撑。
|