# ReleaseDock **Repository Path**: ica520/release-dock ## Basic Information - **Project Name**: ReleaseDock - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-08 - **Last Updated**: 2026-11-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ReleaseDock · 软件分发台 内网自建、面向科室的软件分发台:管理员把安装包放到**中科院数据胶囊**,开启分发后拿到链接与二维码; 访客扫码填写**这份应用自己的核实信息**,校验通过后进入下载。 - 后端:Python 3.10+(开发机 3.13)+ FastAPI + SQLite,项目内 `.venv`,免安装 - 前端:React 19 + HeroUI v3(官方组件)+ Tailwind v4 + Vite,构建产物由 FastAPI 托管; 更新内容用 Markdown(`marked` 解析 + `DOMPurify` 消毒后渲染,防存储型 XSS) - 云存储:**内置纯 Python S3 客户端**(`backend/s3.py`,标准库 SigV4 签名,读写 / 列举 / 校验 / 分段传输 / 流式转发), 预签名直链用同一套签名(`backend/presign.py`)——**不需要 rclone,也不需要任何外部可执行文件** - 配置:除监听地址与端口外,全部存数据库、在网页上改,保存即生效 - 备份:`data/releasedock.db` 一个文件(含配置、名单令牌、访问日志) > 状态(2026-11-08):`scripts/selftest.py` **155 项全通过**(全程离线,假胶囊也是 Python 写的);界面截图见 [docs/shots](docs/shots)。 > 真实数据胶囊已接通(列举 / HEAD / 上传 / 流式下载均实测通过),但该网关**拒绝预签名直链**,下载自动走服务端流式转发,详见 §5.3、§5.4。 --- ## 1. 快速开始(Windows) ```bat python -m venv .venv .venv\Scripts\python.exe -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt cd frontend && npm install && npm run build && cd .. .venv\Scripts\python.exe run.py ``` 也可以双击 `start.bat`(会检查 venv、必要时构建前端并打开浏览器)。之后打开 。 Linux 部署见 §10.1,只有三步:拷代码、建 venv 装依赖、`python run.py`(没有别的组件要装)。 `run.py` 的常用参数:`--port`、`--host`、`--dev`(后端热重载)、`--init`(只生成配置)、`--selftest`。 ## 2. 首次初始化 数据库为空时打开网页会进入初始化向导,**不需要改任何文件**: 1. **管理员账号**:设账号口令,确认「分发链接的对外地址」(默认取本机局域网 IP); 2. **数据胶囊**(可跳过):填 Endpoint、Bucket、AccessKey ID、Secret 四项,其余用默认; 3. **恢复密钥**:生成一次性的 `XXXX-XXXX-XXXX-XXXX-XXXX`,抄下来或下载 txt。忘口令全靠它。 完成后自动登录。演示数据(虚构,可清理): ```bat .venv\Scripts\python.exe scripts\seed_demo.py --fake-capsule :: 起本地假胶囊 + 演示应用 .venv\Scripts\python.exe scripts\seed_demo.py --stop / --clean ``` 自动化部署可用环境变量 `RELEASEDOCK_ADMIN_PASSWORD` 预建管理员;此时口令与恢复密钥会写入 `data/初始管理员与恢复密钥.txt`。 ## 3. 配置放在哪 | 配置 | 位置 | 生效 | |---|---|---| | 数据胶囊(Endpoint / Bucket / AK / SK / 区域 / 前缀 / Path-Style) | 网页「设置 → 数据胶囊」 | 保存即生效 | | 下载方式、预签名有效期、目录缓存 | 网页「设置 → 运行时开关」 | 立即 | | 核实限流、登录保持、日志保留天数、对外地址 | 网页「设置 → 运行时开关」 | 立即 | | 信任反向代理头、仅 HTTPS(Secure Cookie) | 网页「设置 → 运行时开关」 | 立即 | | 管理员 / 口令 / 恢复密钥 | 网页「设置 → 管理员账号 / 我的口令」 | 立即 | | 监听地址 `server.host`、端口 `server.port`、单次请求超时 `s3.timeout_seconds` | `config/设置.json` | 需重启 | `config/设置.json` 首次启动自动生成,只有两块: ```json { "server": { "host": "0.0.0.0", "port": 8730 }, "s3": { "timeout_seconds": 600 } } ``` > 从旧版升级:首次启动会把旧 `设置.json` 里已填的 capsule / security / 对外地址一次性迁入数据库,之后以数据库为准。 ## 4. 目录结构 ``` ReleaseDock/ ├─ run.py / start.bat 启动入口(Windows 双击 start.bat) ├─ backend/ FastAPI、SQLite、S3 客户端与 SigV4 签名、名单加密、配置存储、日志保留 ├─ frontend/ React 19 + HeroUI v3(构建到 frontend/dist) ├─ config/设置.json 只放监听地址/端口与请求超时(自动生成) ├─ data/releasedock.db 全部业务数据与运行时配置(含胶囊密钥) ├─ deploy/ systemd 单元(另附可选 Nginx 示例,本项目不需要) ├─ docs/shots/ 真机截图(无头 Chrome 实拍) ├─ docs/shots-jobs.example.json 截图任务示例(node scripts/shoot.mjs 用) ├─ requirements.txt 运行依赖;requirements-dev.txt 额外带 httpx(自检) └─ scripts/ selftest / fake_s3(假胶囊)/ reset_admin / seed_demo / shoot ``` ## 5. 数据胶囊接入 ### 5.1 填什么 在数据胶囊网页「设置 → 开放接口 / S3 密钥」生成密钥,然后到「设置 → 数据胶囊」: | 页面字段 | 填什么 | |---|---| | 接入点 / Endpoint | `s3.cstcloud.cn`(带不带 `https://` 都行) | | 桶名 / Bucket | 网页上显示的数据集 id | | AccessKey ID / Secret | 生成的密钥;保存后只显示打码,留空表示不修改 | | 区域 / Region | `us-east-1`(默认,必须) | | 路径寻址 Path-Style | 开启(默认,必须) | | 签名版本 | v4(内置签名,无需填) | | 客户端标识 User-Agent | 留空即可;数据胶囊只放行 UA 含 `rclone` 的请求,内置默认值已带上(见 §5.4) | | 目录前缀 | `releases` 或留空(`/<前缀>/<应用slug>/`) | 点「保存并生效」→「连通性自检」。常见报错: | 现象 | 原因 | 处理 | |---|---|---| | `InvalidAccessKeyId` | AK 错/失效 | 重新生成,别带空格 | | `SignatureDoesNotMatch` | Secret 或 region 不对 | region 用 `us-east-1` | | `NoSuchBucket` | 桶名错 | 用网页上那串 id | | `403 Forbidden` | 密钥对该数据集无权限 | 换密钥/数据集 | | 中文名下载 500 | 旧版签名编码 bug | 已修(`_encode` 只放行 ASCII) | 协议只支持 S3:数据胶囊就是 S3 服务,内置客户端也只实现 S3(原来靠 rclone 兜着的 WebDAV 备选已去掉)。 ### 5.2 列举的两个网关怪癖(实测) - **列目录的前缀要带结尾斜杠**:`prefix=releases` 返回空,`prefix=releases/` 才列出对象; 单文件用 HEAD 直查正常。代码统一走 `s3.as_dir()` + 先 HEAD 再列父目录,否则上传校验会误判 0 字节、 云端选文件会报"找不到"。 - **桶根本身不能带结尾斜杠**:请求 `/数据集id/` 一律 401,`/数据集id?prefix=...` 才正常(rclone 也是这么发的)。 - **不支持 ListObjectsV2**:带 `list-type=2` 的请求直接 401(空错误体),所以内置客户端用的是老版 ListObjects (`marker` 分页),任何 S3 服务都支持。 ### 5.3 预签名被拒 → 自动回退流式(实测) 拿同一把 AK/SK 生成的预签名 URL 一律 `401`(空包体),换任何 UA、换 boto3 生成都一样; 而头部签名的请求正常。因此程序内置**预签名探针**: - 启动时后台探一次,结果缓存 10 分钟; - 探测对象:`//.releasedock-presign-probe.txt`(不到 40 字节,可删,下次自检重建); - **探不通就自动改为"服务器流式转发"**(`s3.cat_stream()` 边读边发给访客):功能正常, 代价是占本机带宽、浏览器不能断点续传; - 设置页会显示「预签名不可用 · 走流式」及原因;服务商哪天支持了 query 签名,会自动切回直链。 ### 5.4 网关按 User-Agent 放行(实测,重要) 数据胶囊的 S3 网关只放行 **User-Agent 里含 `rclone`** 的请求:空 UA、`Python-urllib`、`boto3`、`s3cmd`、 `minio-go`、`curl` 全部返回 `401`(错误体为空,光看状态码很难定位)。内置客户端因此默认发送: ``` User-Agent: ReleaseDock/0.1 (S3; rclone-compatible) ``` - 该值在「系统设置 → 数据胶囊 → 客户端标识」可改,留空表示沿用默认值; - 只影响请求头,签名本身与它无关(UA 不进 SignedHeaders); - 自检里的假胶囊也照这条规则设了 UA 白名单,改坏了会被 `scripts/selftest.py` 拦住。 ## 6. 分发流程 ### 管理员 1. **新建应用**:名称、平台、核实方式(授权名单 / 仅登记 / 口令 / 免核实)。版本号与安装包在「版本」页管理。 2. **核实信息**:每应用最多 8 个字段,可设类型、必填、最长字数与**归一化规则**(全角半角、中英文括号、 去 `-心内科` 后缀、只留数字……只影响比对)。每个字段下方有「名单标签与校验」两个开关: - **标签**:这个字段照常出现在访客页、照常参与比对,导入名单时它的值同时写进名单「标签」列(明文),方便认人; - **仅标签**:不参与核实、不出现在访客表单,只作为名单标签(此时「必填」自动置灰、归一化规则隐藏); - 两个都不开就是普通核实字段。 **一个应用只能开启一个标签**:在新字段上开启标签时,会自动关掉其他字段的标签开关并提示「标签已换到这个字段」; 只剩一个核实字段时不允许把它改成「仅标签」(否则没人可核实);口令字段两个开关都是灰的(标签是明文,口令只存哈希)。 3. **授权名单**:模板下载 / 手工录入 / CSV·XLSX 导入(GBK 也认),支持搜索、状态筛选、翻页(50/100/200 条)。 详见 §7。 4. **版本 / 安装包**:在「版本」页上传新版本(两步上传:请求体先落暂存,再由内置客户端推到胶囊,超过 64 MiB 自动分段,带进度), 或直接引用云端已有文件;每个版本填版本号 + Markdown 更新内容,并可勾选「设为最新版」。 5. **开分发**:有效期、下载上限、每份核实信息可用次数、会话时长、设备绑定、下载方式。 **三个 0 都是不限**:有效期 0 = 长期(会清旧到期时间)、下载上限 0 = 不限、每份核实信息可用 0 = 不限。 保存后给出链接与二维码(可下载 PNG、打印海报)。 ### 多版本与更新内容 - 一个应用可以有**多个版本**,每条版本记录 = 版本号 + 安装包(云端路径/大小/校验值)+ Markdown 更新内容 + 登记时间。 - **最新版**:标「最新」的那一条就是访客默认下载的版本;把任意旧版本「设为最新」会同步切换访客下载内容。 启用中的最新版不允许删除;**先停用、再强制删除**即可,删除时可选择是否**连带删除数据胶囊里的安装文件** (不勾则只删版本记录、保留云端文件)。删掉最新版后会自动把最近一条启用版本提为最新,没有则清空应用快照。 - **更新内容**:Markdown 格式,管理台可编辑 + 实时预览;访客页在核实通过后展示**最新版**的更新内容。 渲染前经 DOMPurify 消毒,只保留安全 HTML。 - **历史版本**:访客页下载卡片下方列出所有**启用中**的版本(最新优先、登记时间倒序),每条可点开看该版本的更新内容; 核实通过后每条都可直接下载——下载请求带 `version_id` 精确指向那个版本的安装对象(不传则默认启用中的最新版)。 停用(is_active=0)的版本不列入、也不可下载。**配置了核实时,整块历史版本要核实通过后才展示**(免核实模式则直接展示)。 - 存量应用首次启动会自动把原有的版本号 + 安装包转成第一条「最新」版本,不丢数据。 ### 访客 打开 `/d/<分享码>` → 填写该应用要求的核实项 → 通过后拿到**会话**(默认 30 分钟、绑定设备、额度不限/有限) → 点下载(直链或服务器转发)→ 刷新页面在会话有效期内无需再填;额度用尽或超时后自动回到核实表单。 下载卡片下方还列出**历史版本**:核实通过前每条「下载」是灰的,核实通过后即可逐条下载任意启用中的版本; 每条还能展开看该版本的更新内容。 ### 兑换码分发(第二种分发方式) 面向苹果 App 商务等场景:分发的不是安装包,而是一次性的**兑换码 / 兑换链接**。 - **应用级模式**:新建应用时选「分发方式 = 兑换码」(`distribute_mode=codes`),与「安装包 / 版本」二选一。 兑换码应用的「版本 / 安装包」那套不适用,改由**兑换码池**管理。 - **码池**:`兑换码 / 链接 / 备注` 三列,可**逐条手工添加**,或**批量导入**(CSV / XLSX / 文本,列自动映射), 也能**下载模板**;重复码值自动跳过,「覆盖 / 追加」两种导入方式;已用码可**重置回可用**、可删除。 - **一次性**:访客**核实身份通过即自动领一个未使用码**,领过立即标记「已用、不可再用」,发完即止; 码池清空后该分发会被判定「未就绪」。核实机制与安装包应用完全一致(授权名单 / 仅登记 / 口令 / 免核实)。 - 详情页的标签页会随之变成「兑换码」;访客页核实通过后展示领取到的码值 / 链接 / 备注(可一键复制)。 ## 7. 名单:加密、模板、手工录入、查询 - **只存令牌**:每行是 `HMAC-SHA256(主密钥, 应用盐 | 字段=归一化值…)`,导入后原文即焚; 每应用独立 24 位盐,跨应用不可比对。界面只显示令牌前 8 位与可选标签。 - **文件导入**:表头自动映射;解析结果只在服务进程内存(30 分钟、一次性);预览是脱敏的(中间打星)。 - **模板**:按当前字段标签生成 CSV(带 BOM,Excel 直接打开);表头里若有「备注/标签」列会自动认作标签。 - **手工录入**:逐条填写、加入列表、追加/覆盖,与文件导入走同一套归一化与 HMAC。 - **同标签自动覆盖**:导入(文件或手工)时若某行标签与已有名额相同,自动覆盖那条旧名额——替换核实值、 清零已用次数、解除吊销,不会留下重复标签;没有标签的行仍按核实值指纹去重。导入结果会报告覆盖了几条。 - **查询**:按**标签**或**令牌指纹**搜索(工号等只存哈希,**无法反查**),按状态筛选,分页。 - **标签是明文**:仅用于管理员辨认;参与核实的列仍然只存 HMAC 令牌。 - **什么时候要重导名单**:只拨「标签」开关(参与核实 ↔ 核实+标签)**不影响**已导入名单(参与比对的字段集合没变); 拨「仅标签」开关、调整归一化规则或字段标识才会改变令牌,需要重新导入。 - **名额操作**:吊销 / 恢复、单独设可用次数(0 = 跟随分发设置);**已吊销的可「删除」彻底抹掉这条令牌** (未吊销的行没有删除按钮,防止手滑;删除不可撤销,历史使用次数一并消失,之后重新导入同一个人会当成新名额重新计数)。 删除只动名单表,已经发出去的会话仍按自己的额度用完为止;操作会写审计(`roster_delete`)。 - ⚠️ 修改字段角色或归一化规则会改变令牌,**已有名单需重新导入**(页面有红色提示)。 ## 8. 口令与恢复密钥 - 忘记口令:登录页「用恢复密钥找回」→ 账号 + 恢复密钥 + 新口令;旧密钥立即作废并返回新密钥。 - 登录状态下可在「我的口令」改口令、重新生成恢复密钥。 - 恢复密钥只存 scrypt 散列,服务器无法还原;命令行兜底:`scripts/reset_admin.py --username admin`。 ## 9. 安全设计 - **名单**:只存 HMAC 令牌 + 可选标签;原文只在处理瞬间存在于内存;不支持按核实值反查。 - **口令**:scrypt(16MB)散列;恢复密钥同样散列、一次性轮换。 - **会话**:管理员 Cookie `HttpOnly + SameSite=Lax`,默认 8 小时;访客会话按 `/api/portal/` 路径隔离; **改口令 / 停用 / 删除账号会立即作废该用户全部旧会话**(防「改了口令旧会话还活着」)。 - **下载票据**:核实成功另发 `ticket`,下载必须带票据 + Cookie,防跨站 GET 触发下载。 - **限速**:同一 IP 对同一分发连续填错会锁定(默认 15 分钟 / 5 次);登录、找回口令、首次初始化同样限速。 - **响应头**:全站 `nosniff / X-Frame-Options: DENY / Referrer-Policy / COOP+CORP` + 严格 CSP (内联主题脚本用 sha256 白名单,不用 `unsafe-inline`);`/api/` 一律 `no-store`。 - **接口文档**:`/api/docs` 与 `/api/openapi.json` 只对已登录管理员可见,匿名访问一律 404(不给外部递 API 清单)。 - **请求体**:JSON 接口统一 2MB 上限;名单导入 20MB、安装包上传 3GB 各自走流式限额,chunked 请求也拦得住(413)。 - **表格公式注入**:访问日志 CSV 导出与名单模板里,以 `= + - @ \t \r` 开头的单元格统一加前导单引号。 - **路径与注入**:云端目录清洗丢掉 `.`/`..` 段;静态托管有解析后白名单兜底;SQL 全参数化;前端无 innerHTML。 - **信息收敛**:`/api/health` 不回本机绝对路径;登录错误对「账号不存在 / 口令错误」一视同仁。 - **首启文件**:环境变量建号时把口令与恢复密钥写进 `data/初始管理员与恢复密钥.txt`,POSIX 上自动 `chmod 600`。 - **日志**:只记字段名、令牌前 8 位、脱敏 IP、结果;**不记录访客填写内容**;可导出带 BOM 的 CSV; 默认保留 90 天,每天首次访问时后台清理(0 = 永不清理,可手动「立即清理」)。 - **密钥**:胶囊 AK/SK 存本机 SQLite,界面只显示打码;备份文件与 `config/设置.json` 建议 `chmod 600` 保管。 ## 10. 运维 ### 10.1 部署到 Linux 适用于 Ubuntu 22.04+/Debian 12+/Rocky 9,要求只有 Python ≥3.10(构建前端另需 Node 20.19+)。 **没有别的组件要装**:不装 rclone、不装 nginx,Python 依赖只有 fastapi / uvicorn / openpyxl。 ```bash # 1) 拷代码 sudo useradd -r -s /usr/sbin/nologin -d /opt/releasedock releasedock sudo mkdir -p /opt/releasedock && sudo chown releasedock: /opt/releasedock rsync -a --exclude .venv --exclude frontend/node_modules --exclude data ./ user@server:/opt/releasedock/ # 2) 建 venv、装依赖、构建前端 cd /opt/releasedock sudo -u releasedock python3 -m venv .venv sudo -u releasedock .venv/bin/pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt cd frontend && npm install && npm run build && cd .. # 3) 启动:先手工跑一次完成初始化(浏览器开 127.0.0.1:8730,或 ssh -L 8730:127.0.0.1:8730) sudo -u releasedock .venv/bin/python run.py --host 127.0.0.1 # 想开机自启再装 systemd 单元(可选,不装就一直开着这个终端) sudo cp deploy/releasedock.service /etc/systemd/system/ sudo systemctl daemon-reload && sudo systemctl enable --now releasedock sudo journalctl -u releasedock -f ``` 要点: - **只跑单进程**:监听套接字、传输任务、目录缓存都在进程内,不要 `--workers`、不要双实例。 - systemd 单元已设 `TZ=Asia/Shanghai`(日志与每日清理按本地日期)、非 root、只允许写 `data/` 与 `config/`。 - 权限:`chmod 600 config/设置.json data/releasedock.db`。 - Rocky/CentOS 写不了 `data/`:`chcon -R -t httpd_sys_rw_content_t /opt/releasedock/data`。 - 需要挂在域名/HTTPS 后面时,再自行配反向代理,并在设置里打开 **信任反向代理头**、 **仅 HTTPS(Secure Cookie)**,对外地址填 `https://域名`;`deploy/nginx.releasedock.conf` 只是个可选样例, 直接 `python run.py` 对外服务不需要它。 ### 10.2 备份与升级 ```bash sqlite3 data/releasedock.db ".backup '/var/backups/rd-$(date +%F).db'" ``` 换机器:`data/releasedock.db` + `config/设置.json` 一起拷(代码里有内置客户端,不再带工具目录)。 升级:替换代码 → `pip install -r requirements.txt` → `npm run build` → 重启;数据库自动补列,不丢数据。 ### 10.3 自检 ```bash .venv/bin/python scripts/selftest.py # 用内置假胶囊(scripts/fake_s3.py),离线跑全链路 .venv/bin/python scripts/selftest.py --real # 用当前真实胶囊(只读为主) ``` 自检覆盖:S3 客户端读写/校验/分段/列举、SigV4 预签名、Range、UA 白名单、名单加密(GBK/列映射/标签字段)、核实与限流、设备绑定、 次数与不限、流式下载、审计与导出、配置保存、日志保留、名单查询分页、 多版本(登记/设最新/编辑 Markdown/门户展示/删除保护)、名单同标签自动覆盖、 安全头与 CSP / 文档收敛 / 公式注入防护 / 路径清洗 / 改口令后会话作废 / 请求体上限等。 ### 10.4 常见问题 | 现象 | 处理 | |---|---| | 启动报端口占用 | Windows 用独占绑定,第二个实例会直接失败;先停旧进程 | | 页面能开但没样式 | 没构建前端:`cd frontend && npm run build` | | 胶囊自检失败 | 看设置页每一步的 detail,对照 §5.1 报错表 | | 下载 401 | 预签名被网关拒绝;确认探针结果为"走流式",或看 §5.3 | | 所有云端操作都 401(错误体为空) | 多半是 User-Agent 被网关拦了,见 §5.4;把设置里的「客户端标识」改回含 `rclone` 的值 | | 访客页提示"未就绪" | 应用缺安装包 / 名单为空 / 分发已停用或过期 | | 忘记管理员口令 | 登录页用恢复密钥;或 `scripts/reset_admin.py` | ## 11. 已验证 / 未验证 **已验证(Windows 11 + Python 3.13 + Node 24,真机点击 + 截图)** - 首启向导(建号 → 填胶囊 → 恢复密钥 → 自动登录) - 恢复密钥:生成 / 重置口令 / 旧密钥作废 / 轮换 - 真实数据胶囊(改用内置 Python 客户端后重测):列举 / HEAD / 185MB 安装包流式下载 / MD5 取 ETag / 中文文件名 / 目录斜杠与桶根路径两个坑 / UA 白名单(§5.4) - 名单:GBK CSV、表头映射、脱敏预览、标签字段唯一、导入后不含原文、一次性重放被拒、删除仅限已吊销名额且删后不再命中 - 核实:归一化命中、限流、会话额度、设备绑定、不限次数、次数用尽回到表单 - 两步上传 → 任务完成 → 登记大小/校验 → 从云端取回一致 - 审计:不含填写值、CSV 导出、90 天保留与手动清理 - 名单搜索/状态筛选/翻页(官方分页样式) - 界面:亮/暗、1440 与 390 宽、二维码可扫 **未验证** - Linux 实际运行(部署文件与命令已按真实路径写好,但本机是 Windows) - 预签名直链(网关不支持 query 签名,已自动回退流式) ## 12. 已知限制 - **单进程**:不支持 `--workers` 多进程。 - 流式下载不支持 Range 断点续传;要恢复需服务商支持预签名。 - 单文件上传上限 3 GB(`routes_admin.MAX_UPLOAD_BYTES`)。 - S3 只提供 MD5(ETag);从云端挑的旧文件只在 ≤64MB 时自动算,自己上传的在暂存阶段算好 MD5+SHA1。 - 纯中文应用名会生成 `app-xxxxxx` 作为云端目录名(可在基本信息改成英文短名)。 - SQLite 删除日志后文件不会立刻变小(空间复用),需要时停机 `VACUUM`。 - 访客端按 Chrome 107+ 构建(覆盖 Win7 上的 Chrome/Edge 109);服务端需 Win10+/Linux。 ## 13. 截图 | 文件 | 内容 | |---|---| | `01-setup-account.png` / `02-setup-capsule.png` / `03-setup-recovery.png` | 首次初始化三步 | | `04-login.png` / `05-login-dark.png` | 登录(口令框带显示/隐藏图标) | | `06-login-recovery.png` | 忘记口令 · 恢复密钥找回 | | `07-dashboard.png` / `08-dashboard-light.png` | 概览 | | `09-apps.png` / `24-apps-mobile.png` | 应用列表(桌面/手机) | | `10-app-basic.png` | 应用详情 · 基本信息 | | `11-app-verify.png` | 核实字段设计器(两个普通核实字段) | | `25-label-field.png` | 「标签」开关开启:照常核实 + 写进名单标签 | | `25b-label-only.png` | 「仅标签」开关开启:不参与核实、必填置灰 | | `25c-label-light.png` | 同上(亮色) | | `25d-label-swap.png` | 标签唯一:在新字段开启标签,其他字段自动关掉 | | `25e-label-guard.png` | 只剩一个核实字段时不允许改成「仅标签」 | | `25f-label-password.png` | 口令字段两个标签开关都置灰 | | `26-label-manual.png` | 手工录入(标签单独一栏) | | `12-app-roster.png` | 授权名单 | | `27-roster-paging.png` / `28-roster-search.png` | 名单翻页 / 搜索 | | `29-roster-delete.png` | 已吊销的行才有「删除」按钮(可用行只有吊销/次数) | | `29b-roster-delete-confirm.png` | 删除确认(令牌指纹 + 标签 + 后果说明) | | `29c-roster-deleted.png` | 删除后:名额与吊销计数同步减少 | | `30-versions.png` | 版本管理:多版本列表、最新标记、设为最新 / 删除 | | `30b-notes-preview.png` / `30c-notes-edit.png` | 更新内容 Markdown 预览 / 编辑 | | `31-portal-notes.png` | 访客页展示最新版更新内容 | | `p3-portal-verified.png` / `p4-portal-history-download.png`(`gui-test-screenshots/`,黑盒实拍) | 访客页历史版本列表:核实前「下载」置灰、核实后逐版本下载,每条可展开看该版本更新内容 | | `13-app-share.png` / `14-app-share-light.png` | 分发 · 二维码(暗/亮) | | `15-capsule.png` | 数据胶囊文件浏览(内置 Python 客户端,无外部程序) | | `16-logs.png` | 访问记录 | | `17-settings-capsule.png` | 系统设置 · 数据胶囊(含客户端标识 User-Agent) | | `17c-capsule-selftest.png` | 连通性自检六个步骤(配置完整 / 访问方式 / 接入点格式 / 网络可达 / 列目录 / 预签名) | | `18-settings-runtime.png` / `19-settings-account.png` | 系统设置 · 运行时开关 / 管理员账号 | | `20-portal.png` / `21-portal-dark.png` / `22-portal-mobile.png` / `23-portal-verified.png` | 访客页与核实通过 | 截图由 `node scripts/shoot.mjs --base http://127.0.0.1:8730 --cookie "rd_admin=…" --out docs/shots --jobs docs/shots-jobs.example.json` 生成 (示例里的分享码/路径按你的环境改);`docs/shots-jobs-label.json` 是「标签/仅标签开关」那几张的作业, 用 `--jobs docs/shots-jobs-label.json` 复现,里面的 `/apps/1…4` 按你的应用 id 调整。