usb-device-farm 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usb_device_farm-0.5.0/.gitignore +13 -0
- usb_device_farm-0.5.0/.python-version +1 -0
- usb_device_farm-0.5.0/.workbuddy/memory/2026-11-05.md +50 -0
- usb_device_farm-0.5.0/.workbuddy/memory/2026-11-06.md +35 -0
- usb_device_farm-0.5.0/PKG-INFO +187 -0
- usb_device_farm-0.5.0/README.md +163 -0
- usb_device_farm-0.5.0/pyproject.toml +43 -0
- usb_device_farm-0.5.0/tests/__init__.py +1 -0
- usb_device_farm-0.5.0/tests/test_attach.py +33 -0
- usb_device_farm-0.5.0/tests/test_lease.py +207 -0
- usb_device_farm-0.5.0/tests/test_platform_gaps.py +98 -0
- usb_device_farm-0.5.0/tests/test_protocol.py +90 -0
- usb_device_farm-0.5.0/tests/test_relay_integration.py +162 -0
- usb_device_farm-0.5.0/tests/test_webui.py +102 -0
- usb_device_farm-0.5.0/usbipd/__init__.py +3 -0
- usb_device_farm-0.5.0/usbipd/__main__.py +45 -0
- usb_device_farm-0.5.0/usbipd/agent/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/agent/cli.py +66 -0
- usb_device_farm-0.5.0/usbipd/agent/client.py +27 -0
- usb_device_farm-0.5.0/usbipd/agent/collector.py +118 -0
- usb_device_farm-0.5.0/usbipd/agent/policy.py +75 -0
- usb_device_farm-0.5.0/usbipd/client/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/client/api.py +50 -0
- usb_device_farm-0.5.0/usbipd/client/attach.py +104 -0
- usb_device_farm-0.5.0/usbipd/client/cli.py +312 -0
- usb_device_farm-0.5.0/usbipd/common/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/common/config.py +83 -0
- usb_device_farm-0.5.0/usbipd/common/errors.py +57 -0
- usb_device_farm-0.5.0/usbipd/common/http.py +54 -0
- usb_device_farm-0.5.0/usbipd/common/security.py +68 -0
- usb_device_farm-0.5.0/usbipd/common/util.py +45 -0
- usb_device_farm-0.5.0/usbipd/core/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/core/dataplane.py +102 -0
- usb_device_farm-0.5.0/usbipd/core/lease.py +353 -0
- usb_device_farm-0.5.0/usbipd/core/models.py +155 -0
- usb_device_farm-0.5.0/usbipd/core/store.py +317 -0
- usb_device_farm-0.5.0/usbipd/hub/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/hub/api.py +360 -0
- usb_device_farm-0.5.0/usbipd/hub/app.py +158 -0
- usb_device_farm-0.5.0/usbipd/hub/cli.py +63 -0
- usb_device_farm-0.5.0/usbipd/hub/server.py +119 -0
- usb_device_farm-0.5.0/usbipd/hub/service.py +147 -0
- usb_device_farm-0.5.0/usbipd/hub/static/index.html +395 -0
- usb_device_farm-0.5.0/usbipd/relay/__init__.py +1 -0
- usb_device_farm-0.5.0/usbipd/relay/protocol.py +150 -0
- usb_device_farm-0.5.0/usbipd/relay/registry.py +87 -0
- usb_device_farm-0.5.0/usbipd/relay/server.py +225 -0
- usb_device_farm-0.5.0/usbipd/selftest.py +210 -0
- usb_device_farm-0.5.0/uv.lock +13 -0
- usb_device_farm-0.5.0//347/233/256/346/240/207/344/270/216/350/247/204/346/240/274/344/271/246.md +517 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 2026-11-05
|
|
2
|
+
|
|
3
|
+
- 建立 `目标与规格书.md`(工作区根目录,v0.1 骨架)。项目名暂定 usbipd,工作区此前为空。
|
|
4
|
+
- 文档结构:目标(背景/目标/非目标/成功判据/约束)+规格(场景/功能总表/流程/接口/非功能/异常/验收/里程碑)+需求登记表+待确认问题+修订记录。
|
|
5
|
+
- 约定编号体系 G-xx / S-xx / A-xx / Q-xx / R-xx;状态标记 🟡待确认 / 🟢已确认 / 🔵已实现 / ⛔不做;量化指标须带数值+单位+测量条件+来源,规格项必须能映射到验收判据。
|
|
6
|
+
- 用户后续会逐条口述需求:先原样记入「需求登记表」,确认后再上升到正式章节,避免把讨论中想法当成已定规格。
|
|
7
|
+
|
|
8
|
+
## 第二轮(22:35)需求落地,`目标与规格书.md` 升到 v0.2
|
|
9
|
+
- 需求:集群 USB 设备信息集中管理 → 中心服务收集/刷新 → 用户查询可用设备 → 租借并映射到本机;走 USB/IP;支持 Linux+Windows;管理员/用户两类账号(管理员管账号与设备,用户租用)。登记为 R-01~R-09,拆出 G-01~G-09、S-01~S-14、A-01~A-12、E-01~E-09、Q-01~Q-12。
|
|
10
|
+
- 架构推导(待用户确认):三组件 hub(中心控制服务)/ node-agent(宿主代理)/ client(客户端);**控制面与数据面分离**,hub 不转发 USB 流量。
|
|
11
|
+
- 核实到的技术事实 F-01~F-05(检索 2026-11-05,来源写在文档第六部分):
|
|
12
|
+
① Linux 内核原生 USB/IP 双向成熟;② `usbipd-win`(dorssel,2025-10 v5.3.0)只是 **Windows 导出端**,TCP 3240,bind 持久,**不含 Windows 客户端驱动**;③ USB/IP **无认证无加密、明文**,任何可达 IP 可 attach;④ Windows 客户端需 usbip-win/usbip-win2 的 VHCI 驱动,常未签名要 `testsigning on` + 管理员权限,HASP/Sentinel 加密狗类不兼容;⑤ attach 非持久,重启/复位/插拔后要重连。
|
|
13
|
+
- **最关键风险**:hub 只发令牌无法执行租约,用户可绕过平台直连 3240。候选 A 宿主动态防火墙白名单 / B WireGuard·Tailscale·SSH 隧道 / C 中心中继(与"不做流量代理"的非目标冲突)。Q-03 待用户决策。
|
|
14
|
+
- 另两个待确认分叉:Q-02 平台矩阵(节点×客户端,Windows↔Windows 是否必需、能否装测试签名驱动);Q-04「本地服务器」vs「本地电脑」措辞冲突(工作站 vs 无人值守服务器、是否跨网段/NAT)。
|
|
15
|
+
- 里程碑提案:M0 先手工验证 Linux→Linux 与 Windows 客户端驱动可行性,再进 M1 控制面、M2 租借闭环、M3 双平台健壮性、M4 规模验收。
|
|
16
|
+
|
|
17
|
+
## 第三轮(22:40)R-10 补充,`目标与规格书.md` 升到 v0.3
|
|
18
|
+
- 需求:「USB/IP 无法直连宿主,需要走中心服务器」。**推翻 v0.2 的非目标 N-01(中心不转发 USB 流量)**,数据面改为双路径:可达 direct 直连宿主 3240 / 不可达 relay 由 hub 中继。新增 G-10、S-15~S-19、E-10~E-14、A-13~A-17、Q-13~Q-17、M2.5 里程碑。
|
|
19
|
+
- 术语澄清:数据面直连的是宿主的 USB/IP 导出服务(TCP 3240),不是 node-agent 控制进程。
|
|
20
|
+
- 核实 Linux 内核官方协议文档(docs.kernel.org/usb/usbip_protocol.html,2026-11-05)得 F-06/F-07:v1.1.1(0x0111)、大端;`OP_REQ_DEVLIST`=0x8005 / `OP_REP_DEVLIST`=0x0005 **返回宿主全部导出设备**后关连接;`OP_REQ_IMPORT`=0x8003,offset 8 起 32 字节 busid;import 成功后**同一条 TCP 长连接**承载 CMD_SUBMIT/RET_SUBMIT/CMD_UNLINK/RET_UNLINK 直到 detach。
|
|
21
|
+
- 由此推出三条中继必需设计:① 中继必须**协议感知**校验 IMPORT 的 busid 属于该租约 + 过滤 DEVLIST,否则纯 TCP 透传会变成「枚举并越权 attach 宿主所有设备」的后门(写成安全红线 N-06);② busid 是宿主机本地值(如 `3-2`),多宿主必然冲突,需全局 `virtual_busid` 映射并改写协议字段(S-18);③ 中继是长连接流转发,租约到期要主动断链;relay 重启会断开全部活跃 attach。
|
|
22
|
+
- hub 下发 `connect_plan`(mode direct/relay、attach 目标、virtual_busid、一次性 session_ticket、expires_at)比让客户端自行探测更可控。Q-13 是最高优先待确认:中继是自动回退 / 按拓扑预先指定 / 一律强制走 hub。
|
|
23
|
+
- 架构权衡:中继路径租约鉴权天然在链路上,但 hub 成为数据面单点与带宽瓶颈;A-17 要求直连路径不被中继劣化。
|
|
24
|
+
|
|
25
|
+
## 第四轮(22:44)用户答复 Q-01~Q-04,`目标与规格书.md` 升到 v0.4
|
|
26
|
+
- **Q-01 用户定调**:直接用现有工具链,不要重新开发。我采纳并把边界精确化:不碰协议栈/内核驱动(N-07),复用 Linux usbip/usbipd、Windows usbipd-win 导出、usbip-win2 客户端驱动、nftables/Windows Firewall/WireGuard/SSH;**但「集中目录+账号+租约+抢占+审计+中继鉴权」无现成工具,编排层(hub/agent/client/relay 控制逻辑)必须自建**。唯一要写协议代码的是 relay 薄握手解析层;备选 Q-19:纯 SSH 端口转发零代码但放弃 DEVLIST 过滤与 busid 隔离。
|
|
27
|
+
- **Q-02 确认**:节点与客户端都要 Windows/Linux,任一服务器都可能是两者之一 → 2×2 全矩阵 + 集群混布。后果:采集 sysfs/udev vs SetupAPI/WMI、导出 `usbip bind` vs `usbipd bind --busid`、防火墙 nftables vs Windows Firewall 各两套。**Windows 客户端(usbip-win2 未签名驱动 + testsigning)是最大风险**,M0 必须先实机验证。追问:能否接受开启测试签名。
|
|
28
|
+
- **Q-03 用户没明白**,已在文档 2.3.4 用「前台登记但柜子没锁」比喻解释:USB/IP 协议不认令牌,没租的人敲 `usbip attach -r 宿主IP -b 1-1` 就能抢设备。建议直连路径用 A 动态白名单(现成防火墙工具),不可达走中继则天然拦得住,Q-03 降为确认题。
|
|
29
|
+
- **Q-04 确认**:「本地服务器」=「本地电脑」=使用者实际操作的机器,客户端三动作=查询/映射/断开(ADR-03、G-14、S-20、A-22)。同时新增三条需求 R-11 到期强制断开、R-12 高优先级用户抢占、R-13 管理员强制收回。
|
|
30
|
+
- **新增维度:用户优先级**(ADR-05:角色仍两类,优先级是账号附加属性)。新增 G-11~G-14、S-20~S-25、E-15~E-18、A-18~A-23、Q-18~Q-22、N-07、M2.6 里程碑;租约终止状态扩为 expired / revoked_preempt / revoked_admin / revoked_fault。
|
|
31
|
+
- 关键约束:`active` 租约与数据面 TCP 长连接必须同生共死(F-07),强制断开=主动断链+撤销准入+detach+设备复位,且抢占要在单事务内原子转交避免空窗期被第三人抢走(E-16)。待决策风险:设备正在读写时强拆可能损坏数据(Q-21)、是否给宽限期(Q-20)。
|
|
32
|
+
- 文档新增 `0.1 已确认决策(ADR 简表)` 与 `0.2 复用边界表`,后续答复用 ADR 固化,避免反复追问。
|
|
33
|
+
|
|
34
|
+
## 第五轮(23:08)Q-02 追问确认,`目标与规格书.md` 升到 v0.4.1
|
|
35
|
+
- 用户确认:Windows 客户端可以接受开启测试签名装 `usbip-win2` 驱动。Q-02 追问关闭,新增 ADR-06、F-08、F-09,修订 F-04,2×2 矩阵 #2/#4 风险从「高/最高」降为「中/中高」。
|
|
36
|
+
- **核实到的重要纠正(2026-11-05 检索)**:`usbip-win2` **v0.9.7.3 起已有 WHLK 认证/微软签名驱动**(Open Source Codesigning Initiative),当前 v0.9.8.2(2026-10-18),分「仅驱动签名」与「全二进制签名」安装包 → **正常部署根本不用开 testsigning**。只有 ≤v0.9.7.1 的旧版才未签名(早期还自称 not production ready、可能 BSOD)。所以路线改为:优先 ≥v0.9.7.3 签名版,测试签名仅作用户已授权的降级备选。
|
|
37
|
+
- 两个必须写进规格的硬约束:① 服务端(Linux 宿主或 relay)**必须实现 USB/IP 协议 v1.1.1** 报文,否则 Windows 客户端连不上(Windows 要求 Win10 x64 1903+ / Win11 ARM64,兼容 Linux 内核 4.19–7.0);② 运维红线:装了测试签名驱动后**不得关闭 testsigning**,否则该机所有 USB 设备失效。
|
|
38
|
+
- testsigning 官方前置(Microsoft Learn):管理员权限+重启;报 Secure Boot policy 保护须进 BIOS 关 Secure Boot,BitLocker 亦可能阻挡;桌面「测试模式」水印;HVCI 开启时未签名二进制不加载;安装会重启 USB Hub 3.0 短暂中断全部 USB,需停机窗口。
|
|
39
|
+
- M0 前置依赖去掉 Q-02 追问,改为先实测签名版驱动。文档头部 R 编号修正为 R-01~R-14(v0.4 曾误写 R-13)。
|
|
40
|
+
|
|
41
|
+
## 第六轮(23:12–23:40)用户「帮我实现」→ 交付可运行编排层,文档升 v0.5
|
|
42
|
+
- **技术栈决策(ADR-07)**:Python ≥3.9 **标准库零第三方依赖**(sqlite3 WAL / http.server / asyncio / urllib),HTTP+JSON 控制面、SQLite 单文件。理由:任意 Win/Linux 服务器 `python -m usbipd` 直接跑,不装包;接口层隔离,后续可换 FastAPI/PostgreSQL。四组件 hub / agent / client / relay + common / core 全部落地,README、pyproject.toml、.gitignore、`python -m usbipd selftest` 配套。
|
|
43
|
+
- **中继最终形态(ADR-12)**:单监听端口(默认 3240)+ 全局 `virtual_busid` + 客户端源 IP 定位会话 + ticket 主动断链;薄协议感知层只解析 DEVLIST/IMPORT 握手头(v1.1.1、大端、DEVLIST 回复 12 字节头 + 每条 312 字节 + 4×接口数、IMPORT 请求 40 字节 offset 8 起 32 字节 busid),URB 透明转发;DEVLIST 只返回该 IP 已租设备并改写 busid,越权 IMPORT 直接拒。hub 与 relay 同进程共享 `RelayRegistry`。
|
|
44
|
+
- **F-10 重要纠正**:实现前误判「标准 usbip 客户端固定 3240 不能指定端口」,核实后 **Linux `usbip` 有全局 `--tcp-port`、`usbipd -D --tcp-port`;Windows usbip-win2 `usbip.exe` 有 `-t, --tcp-port`**(ArchWiki USB/IP、deepwiki usbip-win2 CLI,检索 2026-11-05)。保留单端口设计(少开端口/兼容旧工具链),但 `connect_plan.attach_port != 3240` 时客户端自动追加 `--tcp-port`,并加 `tests/test_attach.py` 回归。
|
|
45
|
+
- **踩坑(本轮实测)**:① `_terminate_locked` 断链失败时抛异常会 **ROLLBACK 掉 fault 状态**,反而留下「连接还在、租约 active」→ 改为异常在事务提交后抛,租约落 `revoked_fault`;② `argparse.REMAINDER` 会把子命令后的 `--host` 当顶层参数报错 → 改手工 `argv[0]` 分发;③ hub CLI 参数默认值会覆盖 `USBIPD_DB`/`USBIPD_RELAY_PORT` → 改为仅显式传入才覆盖;④ Windows 上 `0.0.0.0:3240` 绑定报 `WinError 10013`(端口保留/Hyper-V 排除段)→ relay 默认 bind 改 `127.0.0.1`;⑤ CLI `--json` 被 stdout 提示污染 → 提示全部改 stderr,map 补 `preempted`、admin 补 `ok`。
|
|
46
|
+
- **安全/部署前置**:默认 `USBIPD_DRY_RUN=1`(hub 仍写 agent_commands 供演示,agent 只打印不改防火墙/不复位设备);生产需宿主 3240 预置默认 DROP(iptables/netsh),设 `USBIPD_AGENT_TOKEN`、`USBIPD_SECRET`,控制面挂 HTTPS 反代。首个管理员口令由 `USBIPD_BOOTSTRAP_PASSWORD` 或随机生成只打印一次,不硬编码。
|
|
47
|
+
- **默认值(ADR-08~15,均 🟡 待用户确认、配置化可改)**:PBKDF2+HMAC 令牌;优先级 0–99、管理员恒 100、严格大于才可抢;抢占立即断链无宽限;存储/音视频类禁抢;直连动态白名单 + 不可达自动 relay;租期 3600s/单次 28800s/累计 86400s;仅 CLI 无 Web UI。
|
|
48
|
+
- **验证**:`python -m unittest discover -s tests -q` **27/27 通过**(含真实 TCP 假宿主 + relay 集成测试:DEVLIST 过滤、越权 IMPORT 拒绝、URB 转发、主动断链);`python -m usbipd selftest` **11/11 通过**;另用后台 hub + 独立 CLI 子进程跑通 query/map/status/unmap、抢占 `revoked_preempt`、管理员收回 `revoked_admin`、设备回 available、审计完整。M0(2×2 实机矩阵)、M3(自愈/HA/Web UI)、M4(压测)仍未做。
|
|
49
|
+
- 文档新增「第七部分 · 实现状态与验证结果」、ADR-07~15、F-10、里程碑 🔵 标记;实现状态以第七部分为准,第二部分功能表未逐行改标记。
|
|
50
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 2026-11-06 工作日志
|
|
2
|
+
|
|
3
|
+
## 00:02 需求逐条核对(规格书 v0.5 → v0.5.1)
|
|
4
|
+
- 用户问「当前需求都实现了吗」。核对结论:R-01~R-14 **全部有可运行代码落点,但没有一条可宣称真机验收通过**。判据分成「代码/自动化/真机」三列:控制面(账号、目录、租约、抢占、收回、审计、三动作)可自动化验收;数据面(真实 attach、防火墙准入、2×2 实机矩阵、设备类兼容)未验收。仓库内所有验证都在 dry-run 下,从未发生真实 `usbip attach`/防火墙增删/设备复位。
|
|
5
|
+
- 规格书新增 `7.2.1 R-01~R-14 逐条核对` 表;7.5 补 dry-run 边界、S-03(周期全量对账,无 udev/WMI 事件驱动)、S-12(审计表只追加但无哈希链)、direct 断链校验把 pending 命令视为「已下发待确认」。
|
|
6
|
+
- 核对中发现并修复 3 处真实缺口(均已留回归):
|
|
7
|
+
1. `core/dataplane.py` 直连 `allow_ip` 硬编码 3240,与 `connect_plan.attach_port`/`terminate()` 的 `revoke_ip` 所用 `node.usbip_port` 不一致 → 非 3240 宿主会放行错端口、撤销不存在的规则(E-09)。统一端口,`agent/cli.py` 增加 `--usbip-port` 注册参数。
|
|
8
|
+
2. `agent/collector.py` Windows STATE 用 `split()[-1]` 会把 "Not shared" 误读成 "shared";设备类一律伪填 `vendor_specific`,使 U 盘等绕过 Q-21 设备类禁抢保护。抽出 `parse_windows_list()` 纯函数,整段短语解析 STATE,如实标 `unknown` + `class_unknown=1`。
|
|
9
|
+
3. `hub/service.py::sync_devices` 对 Windows 新上报设备默认 `preemptible=0`;管理员 `PATCH /devices/{id}` 显式放开后,心跳全量对账不覆盖(`upsert_device` 更新时保留管理字段)。
|
|
10
|
+
- 新增 `tests/test_platform_gaps.py`(4 项回归);全量 **31/31 单测 + 11/11 冒烟**通过;README 同步 31 项与 Windows 采集/dry-run 已知限制。
|
|
11
|
+
- 仍待用户拍板:ADR-07~15 共 9 项实现期默认值(尤其 Q-13 是否一律走 hub、Q-18 优先级规则、Q-20/Q-21 宽限期与强拆数据风险)。**M0 的 2×2 实机矩阵是 R-06「支持 Win/Linux」能否宣称完成的唯一门槛**。
|
|
12
|
+
|
|
13
|
+
## 07:30 可选 Web 控制台(v0.5.1 → v0.5.2)
|
|
14
|
+
- 用户先问「有界面吗?」,随后明确「提供一个可选的 web 界面, 使用对应参数时启动」。已实现:`python -m usbipd hub --web`(或 `USBIPD_WEB=1`)才启用,**默认关闭纯 API**。未启用时 `GET /` 与 `/ui` 返回 `404 WEB_UI_DISABLED` 并提示加 `--web`。
|
|
15
|
+
- 页面:`usbipd/hub/static/index.html` 单文件、原生 JS、无 CDN/构建链、暗色主题。用户视图=设备目录(状态/关键词筛选、租用/抢占)+我的租约(激活/续租/断开、剩余时间倒计时、toast 显示 attach 命令);管理员视图=新建/停用账号、改优先级、节点、停/开共享、复位 fault、强制收回(原因入审计)、审计日志。token 存 localStorage,页面 5s 轮询,登出清除。
|
|
16
|
+
- 服务端:`Config.web_ui`(默认 False)+ `hub/app.py` 静态路由分支+`hub/cli.py --web`+启动日志打印 `web=on → URL`。
|
|
17
|
+
- 修掉一个真实交互缺陷:`switchTab` 原来只切显示、依赖 5s 定时器拉数据,管理页会空白数秒;改为切 tab 立即 `loadX()`。
|
|
18
|
+
- 验证:新增 `tests/test_webui.py`(6 项:启用时页面 200/未登录 API 401/登录后可用/未启用 404 WEB_UI_DISABLED 等)→ 全量 **37/37 单测 + 11/11 冒烟**;真实 Chrome(puppeteer)跑通 admin→alice 登录、设备租用、管理页 2 用户/1 节点/9 审计、租约页 2 条,**console/pageerror 全空**。
|
|
19
|
+
- 注意:Web UI 与 API 同端口同 Bearer 令牌,控制面仍是 HTTP,生产必须前置 HTTPS 反代并只在受控网络开 `--web`(已写入 README 已知限制)。
|
|
20
|
+
|
|
21
|
+
## 07:41 迁移 uv 管理(v0.5.2 → v0.5.3)
|
|
22
|
+
- 用户「使用 python uv 管理项目」。本机 uv 0.9.28(装在系统 Python312 Scripts)。
|
|
23
|
+
- 变更:`pyproject.toml` 从 setuptools 迁到 **hatchling** 后端(uv 官方推荐),新增 `.python-version`(3.12)、`uv.lock`(零第三方依赖,仅锁本项目);`.gitignore` 加 `.venv/`(**uv.lock 保留入库**,应用类项目应提交锁文件)。
|
|
24
|
+
- 运行约定:开发/演示统一 `uv sync` + `uv run python -m usbipd ...`(或 `uv run usbipd ...`);README 快速开始全部加 `uv run` 前缀并注明「无 uv 机器去掉前缀直跑」,零依赖特性不变(ADR-16)。
|
|
25
|
+
- 验证:`uv sync`(Python 3.12.11、可编辑安装)、`uv run usbipd --help`、`uv build`(sdist+wheel 构建成功后清理 dist)、`uv run` 下 **37/37 单测 + 11/11 冒烟**全绿。
|
|
26
|
+
- 规格书 Q-09 部分关闭(工程管理用 uv 🟢,运行时栈仍 ADR-07 零依赖);新增 ADR-16。
|
|
27
|
+
|
|
28
|
+
## 08:20 PyPI 发布准备(v0.5.3 → v0.5.4)
|
|
29
|
+
- 用户要求发布 PyPI,定名 **`usb-device-farm`**。核对:`usbipd` 已被 usbipd-python 0.0.7(abrinkman,GPLv3,2025-12-09)占用;`usb-device-farm` 与 `usbipd-orchestrator` 均 404 可注册。
|
|
30
|
+
- 关键改名决策:**命令入口 `usbipd` → `usb-device-farm`**,避免与 Windows `usbipd-win` 的 `usbipd.exe` 安装冲突;**导入名/`python -m usbipd` 不变**(改动最小)。
|
|
31
|
+
- pyproject 补齐发布元数据:authors(暂填 Laoding 占位待确认)、keywords、classifiers(Beta/双OS/3.9-3.13);`uv sync` 后旧 `usbipd` 入口自动移除。
|
|
32
|
+
- `uv build` 产物核验:wheel 38 个文件、entry_points 正确、`hub/static/index.html` 打包在内、tests 未泄漏;37/37 单测 + 11/11 冒烟复验通过。dist/ 已留(含 .gitignore 防 dist 泄漏)。
|
|
33
|
+
- **发布被凭据阻塞**:`uv publish` 需要 PyPI 账号 API token;已建议先 TestPyPI 预演。README/规格书(v0.5.4)同步包名。
|
|
34
|
+
|
|
35
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: usb-device-farm
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: 集群 USB 设备集中管理与租借平台(复用现有 USB/IP 工具链的编排层)
|
|
5
|
+
Author: h.t.x
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: device-sharing,lease,orchestration,usb,usbip
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: System :: Hardware
|
|
21
|
+
Classifier: Topic :: System :: Networking
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# usb-device-farm — 集群 USB 设备集中管理与租借平台
|
|
26
|
+
|
|
27
|
+
> 编排层实现(v0.5.0 / M1 + M2 + M2.5 + M2.6)。**不重写 USB/IP 协议栈与内核驱动**(ADR-01),
|
|
28
|
+
> 只复用 Linux `usbip` / Windows `usbipd-win` + `usbip-win2` 工具链,自建账号、目录、租约、抢占、审计、路径决策与中继鉴权。
|
|
29
|
+
> PyPI 包名 `usb-device-farm`(导入名仍为 `usbipd`;命令入口 `usb-device-farm`,避免与 usbipd-win 的 `usbipd.exe` 冲突)。
|
|
30
|
+
|
|
31
|
+
## 三点结论
|
|
32
|
+
|
|
33
|
+
1. **零第三方依赖**:Python 3.9+ 标准库(`sqlite3` / `http.server` / `asyncio` / `urllib`)。项目用 [uv](https://docs.astral.sh/uv/) 管理:`uv run` 自动创建虚拟环境并执行;无 uv 的机器仍可 `python -m usbipd` 直接跑,无需 pip install。
|
|
34
|
+
2. **默认 `dry-run`**:整条链路(查询→租用→attach→抢占→收回→到期断链)都能在无真机环境跑通并验收,但不会改系统防火墙、不会复位真实 USB 设备;`--live` 才真正调用现成工具。
|
|
35
|
+
3. **强制断开是真断数据面**:撤准入白名单 + relay 主动关闭长连接 + detach + 设备复位 + 审计,且抢占在单事务内原子转交,不留可被第三人抢走的空窗(E-16);断链校验失败设备置 `fault`(E-18)。
|
|
36
|
+
|
|
37
|
+
## 环境准备(uv)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 需 uv ≥ 0.4(项目已含 .python-version=3.12 与 uv.lock)
|
|
41
|
+
uv sync # 创建 .venv 并以可编辑方式安装本项目(零第三方依赖)
|
|
42
|
+
uv run usb-device-farm --help
|
|
43
|
+
|
|
44
|
+
# 发布后可从 PyPI 安装:uv tool install usb-device-farm 或 pip install usb-device-farm
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| 命令 | 作用 |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `uv sync` | 按 `uv.lock` 同步虚拟环境与本项目 |
|
|
50
|
+
| `uv run python -m usbipd ...` | 在 `.venv` 中运行(下文示例均可加 `uv run` 前缀) |
|
|
51
|
+
| `uv run usb-device-farm ...` | 同上,等价的安装后命令入口(名称避开 usbipd-win 的 `usbipd`) |
|
|
52
|
+
| `uv run python -m unittest discover -s tests -q` | 运行 37 项单测 |
|
|
53
|
+
| `uv lock --upgrade` | 升级锁文件(当前无第三方依赖,仅锁定本项目与 Python 版本) |
|
|
54
|
+
| `uv python install 3.12` | 无系统 Python 时由 uv 托管解释器 |
|
|
55
|
+
| `uv build` / `uv publish` | 构建并发布 sdist+wheel 到 PyPI |
|
|
56
|
+
|
|
57
|
+
## 架构
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
flowchart LR
|
|
61
|
+
C[client CLI<br/>query / map / unmap] -- 控制面 HTTPS/JSON --> H[hub<br/>目录·账号·优先级·租约·审计]
|
|
62
|
+
A[node-agent<br/>采集·心跳·策略] -- 控制面 --> H
|
|
63
|
+
H -- connect_plan --> C
|
|
64
|
+
H -- 下发命令/复位 --> A
|
|
65
|
+
C -. 直连 direct: USB/IP 3240 .-> N[宿主 usbipd / usbipd-win]
|
|
66
|
+
C -. 不可达 relay: USB/IP 3240 .-> R[relay<br/>v1.1.1 协议感知]
|
|
67
|
+
R -. 长连接转发 .-> N
|
|
68
|
+
H -. 会话注册/主动断链 .-> R
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`hub` 与 `relay` 目前同进程共享会话注册表(`RelayRegistry`);独立 relay 节点池是 Q-15 的后续工作。
|
|
72
|
+
|
|
73
|
+
## 快速开始(三终端,dry-run 演示)
|
|
74
|
+
|
|
75
|
+
以下命令均在 `uv sync` 之后执行(无 uv 时去掉 `uv run` 前缀即可):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 1) 中心服务:首次启动自动生成管理员并打印一次性口令
|
|
79
|
+
# 加 --web 才启用浏览器控制台(默认关闭,纯 API)
|
|
80
|
+
USBIPD_BOOTSTRAP_PASSWORD=admin123 USBIPD_AGENT_TOKEN=secret \
|
|
81
|
+
uv run python -m usbipd hub --host 0.0.0.0 --port 8420 --relay-host <hub对外IP> --web
|
|
82
|
+
|
|
83
|
+
# 2) 宿主代理(无真机时 --mock 上报 2 台模拟设备)
|
|
84
|
+
USBIPD_AGENT_TOKEN=secret uv run python -m usbipd agent \
|
|
85
|
+
--hub http://<hub>:8420 --node-id node-1 --host <宿主IP> --mock --interval 5
|
|
86
|
+
# 跨网段不可直连时加 --unreachable,hub 会自动对该节点走 relay
|
|
87
|
+
|
|
88
|
+
# 3) 使用者电脑
|
|
89
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user admin --password admin123 \
|
|
90
|
+
admin user-add alice pass123 --priority 10
|
|
91
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 query
|
|
92
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 map <device_id>
|
|
93
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 status
|
|
94
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 unmap
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`map` 默认只打印将要执行的 `usbip attach -r <host> -b <busid>`;加 `--live` 才真正执行,且直连失败会自动请求 hub 切换到 relay(Q-13 自动回退)。
|
|
98
|
+
|
|
99
|
+
## Web 控制台(可选,`--web` 启用)
|
|
100
|
+
|
|
101
|
+
默认 **不启用**,`hub` 只提供 JSON API。加 `--web`(或 `USBIPD_WEB=1`)后访问 `http://<hub>:<port>/`:
|
|
102
|
+
|
|
103
|
+
| 角色 | 能做什么 |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| 用户 | 设备目录(按状态/关键词筛选)、租用与抢占、查看 connect_plan 与 attach 命令、我的租约(激活/续租/断开,剩余时间倒计时) |
|
|
106
|
+
| 管理员 | 上述全部 + 新建/停用账号、改优先级、节点列表、停/开共享、复位 fault、强制收回(原因写审计)、审计日志 |
|
|
107
|
+
|
|
108
|
+
单文件 `usbipd/hub/static/index.html`,原生 JS、无 CDN、无构建步骤;token 存 localStorage,页面每 5s 刷新,登出即清除。未启用时 `GET /` 与 `/ui` 返回 `404 WEB_UI_DISABLED` 并提示加 `--web`。
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
python -m usbipd hub --port 8420 --web # 浏览器打开 http://127.0.0.1:8420/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 命令速查
|
|
115
|
+
|
|
116
|
+
| 组件 | 命令 | 说明 |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| hub | `python -m usbipd hub --help` | 中心服务;`--web` 开控制台、`--no-relay` / `--force-relay` / `--live` |
|
|
119
|
+
| agent | `python -m usbipd agent --help` | `--os auto/linux/windows`、`--mock`、`--once`、`--unreachable` |
|
|
120
|
+
| client | `query` / `map` / `unmap` / `renew` / `activate` / `switch-relay` / `status` / `login` | 三动作 + 续租/回退 |
|
|
121
|
+
| client | `admin users|user-add|user-disable|user-set|device-set|force-release|reset-fault|audit|nodes|sessions` | 管理员账号与设备管理 |
|
|
122
|
+
| 自检 | `uv run python -m usbipd selftest` | 端到端 11 项冒烟,无需真机 |
|
|
123
|
+
| 单测 | `uv run python -m unittest discover -s tests -v` | 37 项,含真实 TCP 中继越权、非默认端口一致性、Windows 未知设备类禁抢、Web UI 启用/禁用 |
|
|
124
|
+
|
|
125
|
+
全局参数 `--hub` / `--user` / `--password` / `--token` / `--live` / `--json` 放在**动作之前**;也可用环境变量 `USBIPD_HUB` / `USBIPD_USER` / `USBIPD_PASSWORD`。`--json` 输出纯 JSON 供脚本消费(提示信息走 stderr)。
|
|
126
|
+
|
|
127
|
+
## 主要配置(环境变量,CLI 显式参数优先)
|
|
128
|
+
|
|
129
|
+
| 变量 | 默认值 | 用途 |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| `USBIPD_SECRET` / `USBIPD_SECRET_FILE` | 首次自动生成并落盘 | HMAC 令牌签名密钥,不硬编码 |
|
|
132
|
+
| `USBIPD_DB` | `usbipd.db` | SQLite 单文件(WAL) |
|
|
133
|
+
| `USBIPD_AGENT_TOKEN` | 空(不校验) | node-agent 注册令牌,**生产必填** |
|
|
134
|
+
| `USBIPD_BOOTSTRAP_PASSWORD` | 随机生成并打印一次 | 首次启动管理员口令 |
|
|
135
|
+
| `USBIPD_RELAY_HOST` / `USBIPD_RELAY_PORT` / `USBIPD_RELAY_BIND` | `127.0.0.1` / `3240` / `127.0.0.1` | relay 客户端连接地址、监听端口与绑定地址 |
|
|
136
|
+
| `USBIPD_DRY_RUN` | `1` | `0` 时 agent 才真正执行防火墙与复位命令 |
|
|
137
|
+
| `USBIPD_LEASE_TTL` / `USBIPD_LEASE_MAX_TTL` / `USBIPD_LEASE_ABSOLUTE_MAX` | 3600 / 28800 / 86400 秒 | 默认租期、单次上限、累计绝对上限 |
|
|
138
|
+
| `USBIPD_FORCE_RELAY` / `USBIPD_ALLOW_SAME_PRIORITY_PREEMPT` / `USBIPD_STORAGE_PREEMPTIBLE` | `0` | Q-13 / Q-18 / Q-21 策略开关 |
|
|
139
|
+
| `USBIPD_HEARTBEAT_TIMEOUT` | 90 秒 | 节点掉线收敛 |
|
|
140
|
+
| `USBIPD_WEB` | `0` | `1` 启用 Web 控制台(等价 `hub --web`) |
|
|
141
|
+
|
|
142
|
+
## 实现范围与规格映射
|
|
143
|
+
|
|
144
|
+
| 状态 | 规格项 |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| 🔵 已实现 | S-01~S-12(注册/采集/刷新/查询/账号/设备/租约/直连准入/客户端三动作/自愈骨架/审计)、S-13 Web 控制台(可选 `--web`)、S-15~S-18(路径决策、中继转发、协议感知校验、虚拟 busid)、S-20~S-25(三动作、到期强断、优先级、抢占、管理员收回) |
|
|
147
|
+
| 🟡 简化实现 | S-11 自愈(客户端需手工重连;无自动重连守护进程)、S-19 中继高可用(单 relay)、S-14 预约排队(未做) |
|
|
148
|
+
| ⛔ 未做 | 重写 USB/IP 协议栈(N-07)、计费/审批(N-02)、2×2 平台矩阵实机验证(M0) |
|
|
149
|
+
|
|
150
|
+
验收映射:`A-07/A-18/A-19/A-20/A-21/A-22` 由 `tests/test_lease.py` + `selftest.py` 覆盖;`A-15`(DEVLIST 过滤、越权 IMPORT、主动断链)由 `tests/test_relay_integration.py` 用真实 TCP + 假宿主验证;`A-23`(2×2 平台矩阵)需 M0 实机验证,代码侧已具备 Linux/Windows 双采集与双策略实现。
|
|
151
|
+
|
|
152
|
+
## 生产部署前置(务必阅读)
|
|
153
|
+
|
|
154
|
+
1. **直连准入必须预置默认 DROP**(Q-03 方案 A),否则白名单形同虚设:
|
|
155
|
+
- Linux:`iptables -A INPUT -p tcp --dport 3240 -j DROP`
|
|
156
|
+
- Windows:`netsh advfirewall firewall add rule name=usbipd-default-block dir=in action=block protocol=TCP localport=3240`
|
|
157
|
+
agent 只增删 ACCEPT 规则;relay 路径下宿主只放行 relay 主机 IP。
|
|
158
|
+
2. **relay 端口与客户端端口必须一致**:默认 TCP 3240。Linux `usbip` 与 Windows `usbip-win2` 的 `usbip.exe` 都支持全局 `--tcp-port` / `-t` 指定端口(ArchWiki《USB/IP》、usbip-win2 CLI 文档;检索 2026-11-05),所以 `USBIPD_RELAY_PORT` 改成非 3240 时,客户端 `map` 会自动在 `usbip attach` 前追加 `--tcp-port <port>`。之所以仍选**单中继端口 + 全局 `virtual_busid` + 源 IP 路由**(而非每租约一个端口),是为了少开防火墙端口、兼容不带该选项的旧工具链、简化会话管理;`connect_plan.attach_port` 已保留,未来可切动态端口。跨网段时把 `--relay-host` 设为 hub 对外 IP、`--relay-bind 0.0.0.0`;Windows 上 3240 若被保留,先查 `netsh interface ipv4 show excludedportrange protocol=tcp`。宿主导出服务端口若改成非 3240,用 `agent --usbip-port <port>` 注册,`connect_plan` 与白名单增删会同步该端口。
|
|
159
|
+
3. **必须实现 USB/IP v1.1.1 报文**(F-08):Linux 内核宿主与本项目 relay 均满足;否则 Windows 客户端连不上。Windows 客户端优先用 `usbip-win2 ≥ v0.9.7.3` 微软签名驱动(ADR-06),测试签名仅作降级且**装后不得关闭 testsigning**(F-09)。
|
|
160
|
+
4. **安全**:设置 `USBIPD_AGENT_TOKEN`、`USBIPD_SECRET`,控制面前面挂 HTTPS 反向代理(当前为 HTTP + Bearer 令牌),审计表只追加不提供删除接口;`.usbipd_client.json` / `.usbipd_sessions.json` 含令牌,勿提交版本库。
|
|
161
|
+
5. **强拆数据风险(Q-21)**:存储/音视频类设备默认禁止抢占;强制断开前是否已优雅 detach 取决于客户端实现,U 盘等正在读写时仍可能损坏数据。
|
|
162
|
+
|
|
163
|
+
## 目录结构
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
usbipd/
|
|
167
|
+
common/ 配置、错误码、PBKDF2/HMAC 鉴权、JSON HTTP 客户端
|
|
168
|
+
core/ 模型、SQLite 事务、租约状态机、数据面执行器
|
|
169
|
+
hub/ REST 路由、后台到期扫描、服务启动、CLI
|
|
170
|
+
hub/static/index.html Web 控制台(可选 --web,单文件原生 JS)
|
|
171
|
+
agent/ Linux sysfs / Windows usbipd list 采集、防火墙与复位策略、心跳
|
|
172
|
+
client/ 三动作 CLI、usbip attach/detach 封装
|
|
173
|
+
relay/ v1.1.1 报文解析、会话注册表、asyncio 长连接中继
|
|
174
|
+
selftest.py
|
|
175
|
+
tests/ 37 项 unittest(含真实 TCP 中继集成测试、Web UI 启用/禁用)
|
|
176
|
+
pyproject.toml / uv.lock / .python-version uv 项目管理(Python 3.12)
|
|
177
|
+
目标与规格书.md
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 已知限制
|
|
181
|
+
|
|
182
|
+
- hub 为单实例 SQLite,未做多副本/高可用;relay 重启会断开全部活跃 attach(F-07,规格已标注)。
|
|
183
|
+
- 数据面连接与租约状态的强一致依赖 node-agent 在线回报;`dry-run` 下「断链校验」只验证会话已关闭,真机需配合定期对账把残留升级为 `fault`。
|
|
184
|
+
- Windows 采集依赖 `usbipd list` 文本输出,只能拿到 busid/VID:PID/STATE;设备类、序列号、速度、`driver_bound` 无法准确判定(如实标 `unknown`,Linux 侧由 sysfs 判定)。因此 Windows 节点新上报的设备**默认禁止抢占**,管理员确认设备类型后可用 `PATCH /devices/{id} {"preemptible": true}` 显式放开;心跳对账不会覆盖该设置。
|
|
185
|
+
- 非 3240 宿主端口需用 `agent --usbip-port PORT` 注册,`connect_plan` 与防火墙准入/撤销命令会同步使用该端口。
|
|
186
|
+
- Web 控制台默认关闭;启用后与 JSON API 共用同一端口与 Bearer 令牌(token 存 localStorage),控制面仍是 HTTP,生产环境必须在前面挂 HTTPS 反向代理,并只对受控网络开放 `--web`。
|
|
187
|
+
- 尚未做 M0 实机 2×2 矩阵验证,Windows 客户端驱动与设备类兼容性需实测(F-04/F-08);本仓所有自动化验证均在 `dry-run` 下完成,**从未发生过真实 `usbip attach`**。
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# usb-device-farm — 集群 USB 设备集中管理与租借平台
|
|
2
|
+
|
|
3
|
+
> 编排层实现(v0.5.0 / M1 + M2 + M2.5 + M2.6)。**不重写 USB/IP 协议栈与内核驱动**(ADR-01),
|
|
4
|
+
> 只复用 Linux `usbip` / Windows `usbipd-win` + `usbip-win2` 工具链,自建账号、目录、租约、抢占、审计、路径决策与中继鉴权。
|
|
5
|
+
> PyPI 包名 `usb-device-farm`(导入名仍为 `usbipd`;命令入口 `usb-device-farm`,避免与 usbipd-win 的 `usbipd.exe` 冲突)。
|
|
6
|
+
|
|
7
|
+
## 三点结论
|
|
8
|
+
|
|
9
|
+
1. **零第三方依赖**:Python 3.9+ 标准库(`sqlite3` / `http.server` / `asyncio` / `urllib`)。项目用 [uv](https://docs.astral.sh/uv/) 管理:`uv run` 自动创建虚拟环境并执行;无 uv 的机器仍可 `python -m usbipd` 直接跑,无需 pip install。
|
|
10
|
+
2. **默认 `dry-run`**:整条链路(查询→租用→attach→抢占→收回→到期断链)都能在无真机环境跑通并验收,但不会改系统防火墙、不会复位真实 USB 设备;`--live` 才真正调用现成工具。
|
|
11
|
+
3. **强制断开是真断数据面**:撤准入白名单 + relay 主动关闭长连接 + detach + 设备复位 + 审计,且抢占在单事务内原子转交,不留可被第三人抢走的空窗(E-16);断链校验失败设备置 `fault`(E-18)。
|
|
12
|
+
|
|
13
|
+
## 环境准备(uv)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 需 uv ≥ 0.4(项目已含 .python-version=3.12 与 uv.lock)
|
|
17
|
+
uv sync # 创建 .venv 并以可编辑方式安装本项目(零第三方依赖)
|
|
18
|
+
uv run usb-device-farm --help
|
|
19
|
+
|
|
20
|
+
# 发布后可从 PyPI 安装:uv tool install usb-device-farm 或 pip install usb-device-farm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| 命令 | 作用 |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `uv sync` | 按 `uv.lock` 同步虚拟环境与本项目 |
|
|
26
|
+
| `uv run python -m usbipd ...` | 在 `.venv` 中运行(下文示例均可加 `uv run` 前缀) |
|
|
27
|
+
| `uv run usb-device-farm ...` | 同上,等价的安装后命令入口(名称避开 usbipd-win 的 `usbipd`) |
|
|
28
|
+
| `uv run python -m unittest discover -s tests -q` | 运行 37 项单测 |
|
|
29
|
+
| `uv lock --upgrade` | 升级锁文件(当前无第三方依赖,仅锁定本项目与 Python 版本) |
|
|
30
|
+
| `uv python install 3.12` | 无系统 Python 时由 uv 托管解释器 |
|
|
31
|
+
| `uv build` / `uv publish` | 构建并发布 sdist+wheel 到 PyPI |
|
|
32
|
+
|
|
33
|
+
## 架构
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart LR
|
|
37
|
+
C[client CLI<br/>query / map / unmap] -- 控制面 HTTPS/JSON --> H[hub<br/>目录·账号·优先级·租约·审计]
|
|
38
|
+
A[node-agent<br/>采集·心跳·策略] -- 控制面 --> H
|
|
39
|
+
H -- connect_plan --> C
|
|
40
|
+
H -- 下发命令/复位 --> A
|
|
41
|
+
C -. 直连 direct: USB/IP 3240 .-> N[宿主 usbipd / usbipd-win]
|
|
42
|
+
C -. 不可达 relay: USB/IP 3240 .-> R[relay<br/>v1.1.1 协议感知]
|
|
43
|
+
R -. 长连接转发 .-> N
|
|
44
|
+
H -. 会话注册/主动断链 .-> R
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`hub` 与 `relay` 目前同进程共享会话注册表(`RelayRegistry`);独立 relay 节点池是 Q-15 的后续工作。
|
|
48
|
+
|
|
49
|
+
## 快速开始(三终端,dry-run 演示)
|
|
50
|
+
|
|
51
|
+
以下命令均在 `uv sync` 之后执行(无 uv 时去掉 `uv run` 前缀即可):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1) 中心服务:首次启动自动生成管理员并打印一次性口令
|
|
55
|
+
# 加 --web 才启用浏览器控制台(默认关闭,纯 API)
|
|
56
|
+
USBIPD_BOOTSTRAP_PASSWORD=admin123 USBIPD_AGENT_TOKEN=secret \
|
|
57
|
+
uv run python -m usbipd hub --host 0.0.0.0 --port 8420 --relay-host <hub对外IP> --web
|
|
58
|
+
|
|
59
|
+
# 2) 宿主代理(无真机时 --mock 上报 2 台模拟设备)
|
|
60
|
+
USBIPD_AGENT_TOKEN=secret uv run python -m usbipd agent \
|
|
61
|
+
--hub http://<hub>:8420 --node-id node-1 --host <宿主IP> --mock --interval 5
|
|
62
|
+
# 跨网段不可直连时加 --unreachable,hub 会自动对该节点走 relay
|
|
63
|
+
|
|
64
|
+
# 3) 使用者电脑
|
|
65
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user admin --password admin123 \
|
|
66
|
+
admin user-add alice pass123 --priority 10
|
|
67
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 query
|
|
68
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 map <device_id>
|
|
69
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 status
|
|
70
|
+
uv run python -m usbipd client --hub http://<hub>:8420 --user alice --password pass123 unmap
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`map` 默认只打印将要执行的 `usbip attach -r <host> -b <busid>`;加 `--live` 才真正执行,且直连失败会自动请求 hub 切换到 relay(Q-13 自动回退)。
|
|
74
|
+
|
|
75
|
+
## Web 控制台(可选,`--web` 启用)
|
|
76
|
+
|
|
77
|
+
默认 **不启用**,`hub` 只提供 JSON API。加 `--web`(或 `USBIPD_WEB=1`)后访问 `http://<hub>:<port>/`:
|
|
78
|
+
|
|
79
|
+
| 角色 | 能做什么 |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| 用户 | 设备目录(按状态/关键词筛选)、租用与抢占、查看 connect_plan 与 attach 命令、我的租约(激活/续租/断开,剩余时间倒计时) |
|
|
82
|
+
| 管理员 | 上述全部 + 新建/停用账号、改优先级、节点列表、停/开共享、复位 fault、强制收回(原因写审计)、审计日志 |
|
|
83
|
+
|
|
84
|
+
单文件 `usbipd/hub/static/index.html`,原生 JS、无 CDN、无构建步骤;token 存 localStorage,页面每 5s 刷新,登出即清除。未启用时 `GET /` 与 `/ui` 返回 `404 WEB_UI_DISABLED` 并提示加 `--web`。
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m usbipd hub --port 8420 --web # 浏览器打开 http://127.0.0.1:8420/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 命令速查
|
|
91
|
+
|
|
92
|
+
| 组件 | 命令 | 说明 |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| hub | `python -m usbipd hub --help` | 中心服务;`--web` 开控制台、`--no-relay` / `--force-relay` / `--live` |
|
|
95
|
+
| agent | `python -m usbipd agent --help` | `--os auto/linux/windows`、`--mock`、`--once`、`--unreachable` |
|
|
96
|
+
| client | `query` / `map` / `unmap` / `renew` / `activate` / `switch-relay` / `status` / `login` | 三动作 + 续租/回退 |
|
|
97
|
+
| client | `admin users|user-add|user-disable|user-set|device-set|force-release|reset-fault|audit|nodes|sessions` | 管理员账号与设备管理 |
|
|
98
|
+
| 自检 | `uv run python -m usbipd selftest` | 端到端 11 项冒烟,无需真机 |
|
|
99
|
+
| 单测 | `uv run python -m unittest discover -s tests -v` | 37 项,含真实 TCP 中继越权、非默认端口一致性、Windows 未知设备类禁抢、Web UI 启用/禁用 |
|
|
100
|
+
|
|
101
|
+
全局参数 `--hub` / `--user` / `--password` / `--token` / `--live` / `--json` 放在**动作之前**;也可用环境变量 `USBIPD_HUB` / `USBIPD_USER` / `USBIPD_PASSWORD`。`--json` 输出纯 JSON 供脚本消费(提示信息走 stderr)。
|
|
102
|
+
|
|
103
|
+
## 主要配置(环境变量,CLI 显式参数优先)
|
|
104
|
+
|
|
105
|
+
| 变量 | 默认值 | 用途 |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `USBIPD_SECRET` / `USBIPD_SECRET_FILE` | 首次自动生成并落盘 | HMAC 令牌签名密钥,不硬编码 |
|
|
108
|
+
| `USBIPD_DB` | `usbipd.db` | SQLite 单文件(WAL) |
|
|
109
|
+
| `USBIPD_AGENT_TOKEN` | 空(不校验) | node-agent 注册令牌,**生产必填** |
|
|
110
|
+
| `USBIPD_BOOTSTRAP_PASSWORD` | 随机生成并打印一次 | 首次启动管理员口令 |
|
|
111
|
+
| `USBIPD_RELAY_HOST` / `USBIPD_RELAY_PORT` / `USBIPD_RELAY_BIND` | `127.0.0.1` / `3240` / `127.0.0.1` | relay 客户端连接地址、监听端口与绑定地址 |
|
|
112
|
+
| `USBIPD_DRY_RUN` | `1` | `0` 时 agent 才真正执行防火墙与复位命令 |
|
|
113
|
+
| `USBIPD_LEASE_TTL` / `USBIPD_LEASE_MAX_TTL` / `USBIPD_LEASE_ABSOLUTE_MAX` | 3600 / 28800 / 86400 秒 | 默认租期、单次上限、累计绝对上限 |
|
|
114
|
+
| `USBIPD_FORCE_RELAY` / `USBIPD_ALLOW_SAME_PRIORITY_PREEMPT` / `USBIPD_STORAGE_PREEMPTIBLE` | `0` | Q-13 / Q-18 / Q-21 策略开关 |
|
|
115
|
+
| `USBIPD_HEARTBEAT_TIMEOUT` | 90 秒 | 节点掉线收敛 |
|
|
116
|
+
| `USBIPD_WEB` | `0` | `1` 启用 Web 控制台(等价 `hub --web`) |
|
|
117
|
+
|
|
118
|
+
## 实现范围与规格映射
|
|
119
|
+
|
|
120
|
+
| 状态 | 规格项 |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| 🔵 已实现 | S-01~S-12(注册/采集/刷新/查询/账号/设备/租约/直连准入/客户端三动作/自愈骨架/审计)、S-13 Web 控制台(可选 `--web`)、S-15~S-18(路径决策、中继转发、协议感知校验、虚拟 busid)、S-20~S-25(三动作、到期强断、优先级、抢占、管理员收回) |
|
|
123
|
+
| 🟡 简化实现 | S-11 自愈(客户端需手工重连;无自动重连守护进程)、S-19 中继高可用(单 relay)、S-14 预约排队(未做) |
|
|
124
|
+
| ⛔ 未做 | 重写 USB/IP 协议栈(N-07)、计费/审批(N-02)、2×2 平台矩阵实机验证(M0) |
|
|
125
|
+
|
|
126
|
+
验收映射:`A-07/A-18/A-19/A-20/A-21/A-22` 由 `tests/test_lease.py` + `selftest.py` 覆盖;`A-15`(DEVLIST 过滤、越权 IMPORT、主动断链)由 `tests/test_relay_integration.py` 用真实 TCP + 假宿主验证;`A-23`(2×2 平台矩阵)需 M0 实机验证,代码侧已具备 Linux/Windows 双采集与双策略实现。
|
|
127
|
+
|
|
128
|
+
## 生产部署前置(务必阅读)
|
|
129
|
+
|
|
130
|
+
1. **直连准入必须预置默认 DROP**(Q-03 方案 A),否则白名单形同虚设:
|
|
131
|
+
- Linux:`iptables -A INPUT -p tcp --dport 3240 -j DROP`
|
|
132
|
+
- Windows:`netsh advfirewall firewall add rule name=usbipd-default-block dir=in action=block protocol=TCP localport=3240`
|
|
133
|
+
agent 只增删 ACCEPT 规则;relay 路径下宿主只放行 relay 主机 IP。
|
|
134
|
+
2. **relay 端口与客户端端口必须一致**:默认 TCP 3240。Linux `usbip` 与 Windows `usbip-win2` 的 `usbip.exe` 都支持全局 `--tcp-port` / `-t` 指定端口(ArchWiki《USB/IP》、usbip-win2 CLI 文档;检索 2026-11-05),所以 `USBIPD_RELAY_PORT` 改成非 3240 时,客户端 `map` 会自动在 `usbip attach` 前追加 `--tcp-port <port>`。之所以仍选**单中继端口 + 全局 `virtual_busid` + 源 IP 路由**(而非每租约一个端口),是为了少开防火墙端口、兼容不带该选项的旧工具链、简化会话管理;`connect_plan.attach_port` 已保留,未来可切动态端口。跨网段时把 `--relay-host` 设为 hub 对外 IP、`--relay-bind 0.0.0.0`;Windows 上 3240 若被保留,先查 `netsh interface ipv4 show excludedportrange protocol=tcp`。宿主导出服务端口若改成非 3240,用 `agent --usbip-port <port>` 注册,`connect_plan` 与白名单增删会同步该端口。
|
|
135
|
+
3. **必须实现 USB/IP v1.1.1 报文**(F-08):Linux 内核宿主与本项目 relay 均满足;否则 Windows 客户端连不上。Windows 客户端优先用 `usbip-win2 ≥ v0.9.7.3` 微软签名驱动(ADR-06),测试签名仅作降级且**装后不得关闭 testsigning**(F-09)。
|
|
136
|
+
4. **安全**:设置 `USBIPD_AGENT_TOKEN`、`USBIPD_SECRET`,控制面前面挂 HTTPS 反向代理(当前为 HTTP + Bearer 令牌),审计表只追加不提供删除接口;`.usbipd_client.json` / `.usbipd_sessions.json` 含令牌,勿提交版本库。
|
|
137
|
+
5. **强拆数据风险(Q-21)**:存储/音视频类设备默认禁止抢占;强制断开前是否已优雅 detach 取决于客户端实现,U 盘等正在读写时仍可能损坏数据。
|
|
138
|
+
|
|
139
|
+
## 目录结构
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
usbipd/
|
|
143
|
+
common/ 配置、错误码、PBKDF2/HMAC 鉴权、JSON HTTP 客户端
|
|
144
|
+
core/ 模型、SQLite 事务、租约状态机、数据面执行器
|
|
145
|
+
hub/ REST 路由、后台到期扫描、服务启动、CLI
|
|
146
|
+
hub/static/index.html Web 控制台(可选 --web,单文件原生 JS)
|
|
147
|
+
agent/ Linux sysfs / Windows usbipd list 采集、防火墙与复位策略、心跳
|
|
148
|
+
client/ 三动作 CLI、usbip attach/detach 封装
|
|
149
|
+
relay/ v1.1.1 报文解析、会话注册表、asyncio 长连接中继
|
|
150
|
+
selftest.py
|
|
151
|
+
tests/ 37 项 unittest(含真实 TCP 中继集成测试、Web UI 启用/禁用)
|
|
152
|
+
pyproject.toml / uv.lock / .python-version uv 项目管理(Python 3.12)
|
|
153
|
+
目标与规格书.md
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 已知限制
|
|
157
|
+
|
|
158
|
+
- hub 为单实例 SQLite,未做多副本/高可用;relay 重启会断开全部活跃 attach(F-07,规格已标注)。
|
|
159
|
+
- 数据面连接与租约状态的强一致依赖 node-agent 在线回报;`dry-run` 下「断链校验」只验证会话已关闭,真机需配合定期对账把残留升级为 `fault`。
|
|
160
|
+
- Windows 采集依赖 `usbipd list` 文本输出,只能拿到 busid/VID:PID/STATE;设备类、序列号、速度、`driver_bound` 无法准确判定(如实标 `unknown`,Linux 侧由 sysfs 判定)。因此 Windows 节点新上报的设备**默认禁止抢占**,管理员确认设备类型后可用 `PATCH /devices/{id} {"preemptible": true}` 显式放开;心跳对账不会覆盖该设置。
|
|
161
|
+
- 非 3240 宿主端口需用 `agent --usbip-port PORT` 注册,`connect_plan` 与防火墙准入/撤销命令会同步使用该端口。
|
|
162
|
+
- Web 控制台默认关闭;启用后与 JSON API 共用同一端口与 Bearer 令牌(token 存 localStorage),控制面仍是 HTTP,生产环境必须在前面挂 HTTPS 反向代理,并只对受控网络开放 `--web`。
|
|
163
|
+
- 尚未做 M0 实机 2×2 矩阵验证,Windows 客户端驱动与设备类兼容性需实测(F-04/F-08);本仓所有自动化验证均在 `dry-run` 下完成,**从未发生过真实 `usbip attach`**。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "usb-device-farm"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "集群 USB 设备集中管理与租借平台(复用现有 USB/IP 工具链的编排层)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = []
|
|
12
|
+
license = "MIT"
|
|
13
|
+
authors = [{ name = "h.t.x" }]
|
|
14
|
+
keywords = ["usbip", "usb", "device-sharing", "lease", "orchestration"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: System :: Networking",
|
|
29
|
+
"Topic :: System :: Hardware",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
# 不用 usbipd 作为命令名,避免与 Windows usbipd-win 的 usbipd.exe 冲突
|
|
34
|
+
usb-device-farm = "usbipd.__main__:main"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = []
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["usbipd"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""usbipd 单元测试:python -m unittest discover -s tests -v"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""客户端 attach 命令封装:默认端口兼容旧工具链,非默认端口自动带 --tcp-port。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from usbipd.client.attach import build_attach_command, build_detach_command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AttachCommandTestCase(unittest.TestCase):
|
|
10
|
+
def test_default_port_omits_tcp_port(self):
|
|
11
|
+
plan = {"attach_host": "10.0.0.2", "attach_port": 3240, "busid": "1-1"}
|
|
12
|
+
self.assertEqual(build_attach_command(plan),
|
|
13
|
+
["usbip", "attach", "-r", "10.0.0.2", "-b", "1-1"])
|
|
14
|
+
|
|
15
|
+
def test_non_default_port_is_passed_as_global_option(self):
|
|
16
|
+
plan = {"attach_host": "10.0.0.1", "attach_port": 13240, "busid": "9-3"}
|
|
17
|
+
self.assertEqual(build_attach_command(plan),
|
|
18
|
+
["usbip", "--tcp-port", "13240", "attach", "-r", "10.0.0.1", "-b", "9-3"])
|
|
19
|
+
|
|
20
|
+
def test_missing_or_invalid_port_falls_back_to_default(self):
|
|
21
|
+
self.assertEqual(build_attach_command({"attach_host": "h", "busid": "1-1"})[:2],
|
|
22
|
+
["usbip", "attach"])
|
|
23
|
+
self.assertEqual(build_attach_command({"attach_host": "h", "busid": "1-1",
|
|
24
|
+
"attach_port": "bad"})[:2],
|
|
25
|
+
["usbip", "attach"])
|
|
26
|
+
|
|
27
|
+
def test_detach_uses_dry_run_port_zero(self):
|
|
28
|
+
self.assertEqual(build_detach_command("9-3", live=False),
|
|
29
|
+
["usbip", "detach", "-p", "0"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
unittest.main()
|