vdi-cloud/PROJECT_CONTEXT.md
songyiming 66f0cacb32 修复:QProcess::finished 重载消歧;文档:串流失败根因(双 IP ARP 混乱)与延迟 A/B 结论
- moonlight_session.cpp:QOverload 显式指定 finished 重载,修复 ARM64 编译
- moonlight.conf:vsync/frame-pacing 默认改回开启(A/B 实测关闭无改善)
- docs:补充 10-29 登录流程验收、重启验收、Monitor 恢复、双 IP 根因与 A/B 数据
- tools:新增 deploy_kiosk.sh 一键部署脚本与语法自检
2026-10-29 11:58:32 +08:00

410 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数字员工 VDI 云终端项目上下文
> 这是本项目后续继续开发时的必读上下文。所有实现必须优先以项目经理提供的正式文档和会议纪要为准;文档未明确的地方只能标记为待确认/待调研,不能擅自定版。
>
> 本文件整理自截至 2026-10-23 的完整开发对话。
## 1. 项目目标
项目是“云-管-端” VDI 云桌面方案:
```text
RK3528 云终端
-> 终端接入程序
-> 自研管理平台 API
-> Proxmox VE 管理的虚拟机
-> Windows / Ubuntu / 麒麟 / 后续安卓桌面
```
终端侧最终职责:
- 开机进入 Kiosk/首配登录页;
- WiFi/有线联网;
- 终端注册、心跳、配置下发、日志上报;
- 连接远程桌面;
- 显示远程画面并传递键盘、鼠标、U 盘等外设;
- 断线重连;
- 独立 Monitor 看门狗;
- A/B OTA;
- 后续使用 VPU/DRM 硬件解码与零拷贝显示。
项目经理新增方向:RDP 可能不适合 GPU/CAD/3D 高质量低延迟串流,周一需要调研 RDP 与 Moonlight。Moonlight 只作为待调研/待验证的协议方向,不能在未验证前替换 RDP 或擅自确定端口、认证字段、嵌入方式。
## 2. 权威需求来源
1. `E:\Desktop\数字员工\VDI云终端产品方案-定版摘要(1).docx`
- 产品定版 v2.1。
- 规定 RK3528 终端、2K 默认/4K 上限、FreeRDP 3、GStreamer + mppvideodec、Proxmox/KVM/VFIO、GPU 算力池、管理平台等。
2. `E:\Desktop\数字员工\VDI云桌面软件开发方案(1).docx`
- 软件开发 v1.0。
- 规定 monorepo、终端/云端/控制台技术栈、20 项需求追踪矩阵、测试层次、M1-M4 里程碑。
3. `E:\Documents\xwechat_files\wxid_q6y4us6kip0g22_aa63\msg\file\2026-10\export_1792654201375.docx`
- 会议纪要。
- 规定当前优先:先拿盒子、终端基础连接、Proxmox 逻辑/API、2K/4K/发热/网络验证、Monitor、OTA 后置;张帅协助 P100 GPU 验证。
4. 会议纪要图片:项目技术要点思维导图。
核心纪律:如果代码实现与上述文档不一致,先指出偏差并等待确认;不能以“更快”或“更方便”为理由改用未定版技术栈。
## 3. 正式技术选型与边界
正式开发方案 2.1/2.2 节:
```text
vdi-cloud/
├─ terminal/
│ ├─ meta-vdi/ # Yocto:BSP/内核/设备树/U-Boot/Kiosk/Agent 配方
│ ├─ kiosk-shell/ # Weston + 全屏 Qt 壳
│ ├─ freerdp-rk/ # FreeRDP 3 fork + MPP/VPU 硬解补丁
│ ├─ vdt-agent/ # Rust:注册/心跳/命令/日志
│ ├─ ota-updater/ # Rust:manifest/签名/A-B/回滚
│ └─ usb-monitor/ # Rust:udev/挂载/重定向策略
├─ platform/ # Go 云端服务
├─ console/ # Vue3 + TS + Element Plus
├─ deploy/ # compose/Helm/离线包
├─ docs/ # OpenAPI/MQTT/DB schema
└─ tools/ # 烧录/压测/CI
```
终端技术栈定版:
- Kiosk 壳:C/C++ + Qt,目标显示环境 Weston/Wayland;
- FreeRDP 插件:C/C++;
- Agent/OTA/USB:Rust;
- 协议主线:FreeRDP 3;SPICE 备用;自研 H.265 over QUIC 只作为后续预留;
- 云端:Go;
- 前端:Vue3 + TypeScript + Element Plus;
- 数据库:PostgreSQL + VictoriaMetrics;
- 通信:HTTPS + MQTT;
- 云端底座:Proxmox VE + KVM/QEMU + VFIO;
- GPU:NVIDIA CUDA、华为 CANN 等按卡型池化;终端不安装 GPU 计算组件。
开发方案 3.2 对 kiosk-shell 的明确要求:
- 技术:Weston(Wayland)+ 全屏 Qt 壳进程;
- 状态机:`BOOT -> CONFIG -> CONNECTING -> SESSION -> RECONNECT -> ERROR`;
- 首配页:SN 绑定、WiFi 扫码、服务器地址;
- 状态条:网络、会话、OTA 进度;
- FreeRDP:以 `libfreerdp3` API 内嵌,不拉起外部进程;
- 全局热键:`Ctrl+Alt+Shift+M` 呼出管理菜单。
当前实现是在现有 Armbian 的 LightDM + Xorg 环境中先完成 Qt/Kiosk POC;这不是对正式 Weston 选型的替代。Weston/DRM 必须继续验证。
## 4. 盒子与联调环境
### 盒子
- 型号:RK3528 PRO DDR4;
- OS:Armbian v25.05 rolling,Ubuntu Noble 用户空间;
- Kernel:`6.1.115-vendor-rk35xx`;
- 架构:`aarch64`;
- 内存:约 3.83 GB;
- 存储:约 29 GB;
- IP:`192.168.1.188`;
- SSH:`ssh armbian@192.168.1.188`;
- 图形:LightDM + Xorg,当前不是 Wayland;
- 显示器:`HDMI-1 1920x1440 @ 75Hz`,没有 2560x1440 EDID 模式;
- DRM:`/dev/dri/card0`、`card1`、`renderD128`、`renderD129`;
- 权限:用户已在 `video`、`render` 组;
- 温度:空载约 53°C;
- WiFi:`wlan0`,SSID `YXTech`,信号约 -47 dBm,协商约 200 Mbit/s,实测无丢包/重传;
- 设备节点:`/dev/mpp_service`、`/dev/rga` 存在;
- OpenGL:Mali450,Mesa 25.0.7;Xorg 日志显示 modesetting + glamor initialized。
### 已安装环境
- gcc/g++ 13;
- make、cmake 3.28、ninja、pkg-config、git;
- Qt5 5.15.13(Core/Gui/Widgets/Wayland 包);
- Weston 13.0.0;
- `freerdp3-dev` 3.32.1;
- `freerdp3-x11`,实际命令:`xfreerdp3`;
- `freerdp3-wayland`,实际命令:`wlfreerdp3`,但当前没有 Wayland 会话;
- `mesa-utils`;
- `xdotool`(测试用);
- 当前未确认 `mppvideodec`,Ubuntu 官方源没有 Rockchip MPP GStreamer 插件。
### apt 源
旧 `/etc/apt/sources.list` 中普通 Ubuntu 源会造成 ARM64 404,已备份为 `sources.list.bak` 并注释。有效配置在 `/etc/apt/sources.list.d/ubuntu.sources`,使用 TUNA 的 `ubuntu-ports`。
不要执行:
- `sudo apt upgrade`:曾显示约 298 个包待升级,当前不要整体升级;
- `do-release-upgrade`:不要升级系统版本;
- 不要在盒子上安装 Proxmox,盒子是 ARM64 终端,不是 Proxmox 主机。
本机已经给盒子安装了 SSH 公钥(注释 `zcode-dev`),可以免密远程执行:
```bash
ssh armbian@192.168.1.188
```
SSH 中执行图形命令要带:
```bash
export DISPLAY=:0
export XAUTHORITY=/home/armbian/.Xauthority
```
## 5. Proxmox/Windows 联调环境
- Proxmox VE:9.2.2;
- 节点:`starhe`;
- VM:101,名称 `win10-gongchang01`;
- Windows:Windows 10 22H2 专业版;
- VM IP:`192.168.1.70`;
- RDP:TCP 3389;
- 用户:`test1`;
- 当前 POC 允许空密码登录,正式环境不能使用空密码。
边界:终端最终不应直接调用 Proxmox API。最终关系是:
```text
终端 -> 自研管理平台 API -> Proxmox API -> 虚拟机
终端 -> 管理平台返回的桌面连接地址/网关 -> RDP 或其他协议
```
## 6. 已完成工作
### 6.1 网络与远程桌面基础验证
已完成:
- 盒子 WiFi 联通;
- Windows 10 VM 可达;
- 手工命令 `xfreerdp3` 成功显示 Windows 10 桌面;
- 键盘鼠标可操作;
- 退出全屏最可靠方式:从 SSH 执行 `pkill -f xfreerdp3`;
- 当前 X11 环境下不要直接在 SSH 无 DISPLAY 会话中启动 GUI。
### 6.2 monorepo
项目已在:
```text
D:\DAIMA\vdi-cloud
```
已初始化 git。最近已提交:
```text
589cde4 feat(kiosk-shell): FreeRDP 3 内嵌客户端跑通,登录页可进入远程桌面
419630f feat(kiosk-shell): 首版登录页与状态机骨架
```
当前 git 在日报暂停开发时有未提交改动,主要是 Monitor、心跳、网络显示修复等。不要丢弃这些改动。
### 6.3 kiosk-shell
位置:`D:\DAIMA\vdi-cloud\terminal\kiosk-shell`
已实现:
- Qt5 全屏登录页;
- 深色 UI;
- SN 显示;
- 服务器地址、账号、密码、显示分辨率;
- 记住连接参数;
- 本地配置文件:`~/.config/vdi-terminal/client.conf`;
- 状态机:`BOOT/CONFIG/CONNECTING/SESSION/RECONNECT/ERROR`;
- `Ctrl+Alt+Shift+M` 管理菜单;
- RDP 会话状态;
- 连接断开后重连状态;
- 主进程心跳文件设计:`/tmp/vdi-kiosk.heartbeat`;
- CMake + Ninja 原生 ARM64 编译通过过多次。
当前盒子上的实际运行路径:
```text
~/vdi-terminal/bin/kiosk-shell
```
配置示例:
```text
version=1
sn=...
deviceKey=
server=192.168.1.70
username=test1
password=
wifiSsid=
wifiPassword=
resolution=1920x1440
remember=1
```
### 6.4 libfreerdp3 内嵌
已实现 `rdp_session.cpp/h`,不拉起外部 `xfreerdp3` 进程:
```text
freerdp_new()
freerdp_context_new_ex()
freerdp_connect()
FreeRDP 3 AuthenticateEx
GDI primary buffer
Qt QImage/QLabel 显示
```
已实测修复的坑:
1. `gdi_init` 必须放在 `PostConnect` 中;连接前调用会在 `gdi_init_ex` 段错误;
2. 不能依赖扩展 `ContextSize` 保存自定义指针,`gdi_init_ex` 可能重新分配 context 导致段错误;当前使用按 `freerdp*` 的静态注册表和 `RdpgfxClientContext.custom`;
3. NLA 认证回调空密码必须传 `strdup("")`,不能传 `nullptr`,否则出现 `SEC_E_NO_CREDENTIALS`;
4. FreeRDP 3 设置键使用 `FreeRDP_ServerHostname`、`FreeRDP_Username`、`FreeRDP_DesktopWidth` 等,不要照抄 FreeRDP 2.x 键名;
5. `PIXEL_FORMAT_BGRX32` 是 B/G/R/X 四字节布局,Qt 显示前需要 BGR -> RGB 转换;
6. `/gfx` 命令行是能力组合语义,不能把 `AVC420`/`AVC444` 当作单个值使用。实测可解析形式包括 `progressive|RFX|AVC420|AVC444`。
重要:当前 POC 已成功显示 Windows 10 的真实远程桌面画面,颜色正确,进程保持运行。这是目前最重要的实测成果。
详细说明在:
```text
D:\DAIMA\vdi-cloud\docs\freerdp3-embedding-notes.md
```
### 6.5 显示问题
曾出现“从上到下卷帘式渲染”。目前确认:
- WiFi 信号/速率/丢包不是主要原因;
- 显示器原生是 1920x1440,而曾请求 2560x1440,存在缩放;
- X11/MATE/FreeRDP CPU 侧渲染路径性能有限;
- 正式文档要求 Weston + DRM plane + GStreamer mppvideodec 零拷贝,这正是后续方向;
- 该问题按用户要求暂时不阻断当前 GUI/登录页主线,后续处理。
注意:此前对“X11 无 glamor 硬件合成”的判断不准确,Xorg 日志实际显示 `glamor X acceleration enabled on Mali450`;后续不要继续引用这个错误结论。正确表述是:当前路径仍未达到目标流畅度,具体瓶颈需要后续基准测试确认。
### 6.6 Moonlight 云桌面开机直达(2026-10-28)
RDP/Moonlight 实测对比见 `docs/rdp-vs-moonlight-comparison-20261028.md`,
落地记录见 `docs/moonlight-kiosk-20261028.md`。
盒子当前开机默认走 Moonlight 直连云桌面,链路:
```text
~/.config/autostart/vdi-moonlight.desktop
-> vdi-kiosk-monitor --heartbeat-file /tmp/vdi-moonlight.heartbeat --max-restart 200
-- /home/armbian/vdi-terminal/bin/vdi-moonlight
-> moonlight-qt stream 192.168.1.70 Desktop (fullscreen, 1920x1080, H.264, 软解)
```
仓库新增:`terminal/kiosk-shell/scripts/vdi-moonlight`、`etc/moonlight.conf`、
`vdi-moonlight.desktop`;`src/monitor.cpp` 改为按进程组结束整棵进程树。
实测:满屏云桌面 1:1 无缩放;杀客户端 5 秒内重连;杀启动器 3 秒内被看门狗拉起;
性能叠图 1080p60 实际 21.59 FPS、网络丢包 0%、延迟 3 ms、解码 15.04 ms、抖动丢帧 13.64%。
关键坑位(后续维护必须知道):
1. Windows 防火墙屏蔽 ICMP,盒子 ping 不到云桌面主机,但 TCP/UDP 正常;
可达性判断必须用 TCP 探测,ping 会误判;
2. `moonlight-qt` 的应用 id 随服务端重装/升级失效,旧 id 会报“找不到应用程序 <id>”;
命令行要用应用名 `Desktop`;
3. RK3528 上 `--video-decoder auto` 会选到不可用的 `h264_v4l2m2m`,
必须显式 `--video-decoder software`;
4. RDP 路径的自启项已禁用但保留,可随时切回。
## 7. 当前未完成/未验证
1. 服务端 GPU 直通未做:宿主机 `starhe` 有 GTX 965M(`0000:01:00.0`,IOMMU Group 2 独占),
主机同时有 Intel 核显 `0000:00:02.0` 可继续驱动本机显示,具备直通条件;
直通后 Sunshine 才能用 NVENC 硬编码,把 1080p 帧率从 ~21.6 FPS 提到 60 FPS;
2. RK3528 VPU 硬解不可用:`h264_v4l2m2m` / `hevc_v4l2m2m` / `hevc_v4l2request` 均报
`Could not find a valid device`,当前只能用软件解码,4K 目标必须先解决;
3. 2K 分辨率链路未验证:盒子屏幕现为 1920x1080@60Hz,Sunshine 侧虚拟显示器也是 1920x1080@60Hz;
4. 当前实现仍使用 X11 POC,正式方案要求 Weston/Wayland;
5. 当前 RDP 会话使用 CPU GDI -> Qt 复制,尚未接 GStreamer/mppvideodec/VPU;
6. `mppvideodec` 来源和可用性未确认;
7. 终端注册、MQTT 心跳、配置下发、日志上报、OTA、USB 尚未接后端 API;
8. 前端/后端管理平台尚未接入;Moonlight 路径目前读本地静态配置文件;
9. 终端音频出音未现场确认(`--audio-on-host` 已开);
10. 协议最终选型未定版:实测已证明 Moonlight 画面质量与 CPU 代价优于 RDP,
但“办公用 RDP、GPU/CAD/3D 用 Moonlight”的双协议结论仍需产品方案确认。
## 8. 下一步建议(按优先级)
### A. 服务端 GPU 直通(当前最高价值项)
Sunshine 在 VM 内用 libx264 软件编码,4 核同时承担 1080p 捕获与编码,
是 21.6 FPS 与 13.64% 抖动丢帧的唯一原因;客户端侧链路完全健康。
```text
宿主机 starhe(Xeon E3-1200 v3 + Intel 核显 + GTX 965M)
-> 确认内核 IOMMU 已启用(PCI 设备已有独立 IOMMU group)
-> vfio-pci 绑定 0000:01:00.0,提取 vBIOS ROM
-> VM 100 增加 hostpci,Windows 内装 GeForce 驱动
-> nvidia-smi 正常后 Sunshine 切 NVENC 硬编码
-> 复测 1080p60 帧率、抖动丢帧、服务端 CPU
```
主机 root 密码已知(`yx66668888`),可用 `SSH_ASKPASS` 非交互登录。
### B. 收尾当前终端 POC
1. 保留当前 Moonlight 开机直达版本作为可回退基线(`docs/moonlight-kiosk-20261028.md`);
2. 确认开机自启在真实重启后生效(当前只验证了手动拉起与被守护重启);
3. 网络状态显示修复(此前显示错误的 `600`)尚未确认最新二进制效果;
4. RDP 路径代码保留但自启已禁用,按需切回。
### C. 协议策略与代码结构
实测结论已经支持双协议方向,但仍未定版:
```text
普通办公 -> RDP
GPU/CAD/3D -> Moonlight
```
如果确认需要双协议,代码结构应演进为:
```text
DesktopSession 接口
├─ RdpSession # 保留当前 libfreerdp3
└─ MoonlightSession # 当前是 shell 启动器 + moonlght-qt 子进程
SessionManager # 根据 API 下发 protocol 选择实现
```
不要在定版前擅自确定 Moonlight 的嵌入方式、认证字段或删除 RDP 代码。
### C. 后续 API 对接
与后端确认:
- `/api/v1/terminal/register`;
- 配置拉取接口;
- MQTT 心跳和命令格式;
- 桌面连接信息字段;
- 返回 VM 地址还是 RDP/Moonlight 网关地址;
- 是否使用一次性票据;
- GPU 桌面如何标记协议/资源池;
- 终端不直接调用 Proxmox API。
## 9. 安全与工程注意事项
- `/cert:ignore` 只能用于当前局域网 POC,正式版本必须证书校验;
- 空密码只能用于当前测试 VM,正式环境必须禁用;
- 不要把密码提交到 git;当前本地配置仅用于 POC;
- 终端正式版本不要长期保存 Windows 密码,应使用管理平台/网关/短期凭据方案;
- 不要执行 `git reset --hard`、删除用户已有改动或覆盖未审阅文件;
- 不要把推测写成“已实现”;日报和项目状态要区分“已验证/已完成/待验证/待调研”。
## 10. 今日日报关联
日报模板:`E:\Desktop\日报.txt`。
已按 10 月 23 日实际情况填入并保存:
- 终端硬件与系统基线;
- 编译环境;
- monorepo/kiosk-shell;
- libfreerdp3 内嵌 RDP;
- Windows 10 远程桌面实机验证;
- 2K 显示异常初步定位;
- 明日 Monitor/Weston/mppvideodec/API 待办;
- AVC444、mppvideodec、Weston、API 契约风险。
如果继续写日报,必须以实际工具执行结果为准,不能把未编译或未实测项目写成完成。