功能:完善通用云管工作台、审计与诊断能力

This commit is contained in:
songyiming committed 2026-11-11 18:46:16 +08:00
1 parent 5ac692724d
commit 9f05aa20ea
225 files changed
+14266 -141

No files matched your search

+14
View File
@@ -41,6 +41,20 @@ gcc -shared -nostdlib -fPIC -O2 -Wall -Wextra -Werror -Wl,-z,defs \
每台主机单独的ed25519私钥和known_hosts留在盒子配置目录(目录700、私钥600),不进仓库。known_hosts是在工作站用用户提供的SSH凭据成功认证后取得的真实主机公钥,没有在运行时自动接受新主机密钥。JSON和密钥应在已验证0005的客户端部署后才启用。上例SHA为2026-11-05现场构建;重新构建后须先停用profile、完成对应验证,再更新SHA并启用,不能把旧值机械替换为任意新文件的SHA。
## NVIDIA 硬件采集(2026-11-11)
当前两台 Linux 的现场部署已切换为 GTX 965M 渲染、NvFBC/CUDA 采集与 NVENC H.264 编码;不再沿用上文历史 X11 软件编码基线。使用兼容 Maxwell 的 Sunshine v2025.924.154138;不得仅更新主二进制而不复测 CUDA 架构支持和 pinned manifest。
Ubuntu 的 NVIDIA→modesetting PRIME 路径会把服务端软件光标提前画进画面,仅设 NvFBC `bWithCursor=false` 仍会出现本地光标拖着视频光标。该主机在 `host.py` 同目录启用空标记文件 `hide-server-cursor`:捕获 guard 用独立 X11 连接持有 `XFixesHideCursor`,原生光标形状仍由另一个连接通过 `XFixesGetCursorImage` 读取。结束或异常退出时先停 Sunshine,再显式恢复并关闭隐藏连接;X server 也会在连接断开时释放该客户端的隐藏状态。标记仅在 Ubuntu 现场启用,麒麟保持原路径。撤销须先退出串流、停 guard,再移除标记后重连,不在同一串流中途切换。
适配器编译增加 `-DVDI_NVFBC -Ivendor`。麒麟缺少 libc 开发启动对象时另加 `-ffreestanding -nostdlib`,并显式链接系统版本化 libc/libdl。`vendor/NvFBC.h` 保留上游 MIT 许可。拦截 NvFBC 函数表时检查 SDK 参数版本,仅在租约内关闭视频内光标,不修改调用方参数;`nvfbc-ready-v1` 是加载就绪凭据,`nvfbc-v1` 才表示实际捕获会话创建成功。
麒麟的 Optimus 卡没有可用物理输出,旧内核 PRIME 显存导入也失败,因此显式启用 `VDI_NVFBC_HEADLESS=1`。适配器只在驱动报告支持采集、没有 RandR 输出且真实 X screen 为 2560×1440 时枚举整屏,标记 RandR 不可用,使 Sunshine 使用 `NVFBC_TRACKING_SCREEN`。它不生成真实显示输出,也不把不可采集屏幕报告为可用。UKUI 已目视验证桌面正常;Ubuntu GNOME 在相同无输出条件下黑屏,已恢复 NVIDIA + modesetting PRIME 输出,不能直接照搬麒麟配置。
麒麟使用 `/opt/vdi-sunshine-maxwell/runtime/` 的私有动态加载器和依赖库运行该 Sunshine,不替换系统 glibc;可执行文件、适配器和新增脚本按文件进行 KYSEC verified 认证。GPU 路径及测量边界见 [本轮实施证据](../../docs/evidence/linux-gpu-20261111/README.md)。
重启显示管理器可能使临时 guard 单元消失而遗留旧 ready/receipt;新租约在获得独占锁并确认旧 guard 停止后清理这两个标记,再等待新会话凭据。`test_native_cursor_host.py` 在实际 Ubuntu Linux 上覆盖该恢复情形及不完整 manifest 拒绝;`run_nvfbc_adapter.py` 配合 `nvfbc_adapter_stub.c` 在麒麟实际 glibc 上覆盖函数表 ABI、光标与整屏枚举边界。
## 停用与回退
退出当前Linux串流,等guard inactive且原vdi-sunshine active。将对应 `<IP>.json` 改名为 `.disabled`,下次从同一正常列表连接即使用原视频光标。临时诊断也可设置 `VDI_NATIVE_CURSOR=0`,但必须在当前租约结束、原服务恢复之后重连。
+82 -5
View File
@@ -5,6 +5,9 @@
*/
#define _GNU_SOURCE
#include <stddef.h>
#ifdef VDI_NVFBC
#include "NvFBC.h"
#endif
extern void *dlvsym(void *, const char *, const char *);
extern char *getenv(const char *);
extern int strcmp(const char *, const char *);
@@ -24,6 +27,77 @@ typedef void *(*Lookup)(void *, const char *);
typedef CursorImage *(*GetImage)(void *);
static GetImage real_image;
static void receipt(const char *value, unsigned long size) {
const char *path = getenv("VDI_CURSOR_RECEIPT");
if (path) {
int fd = open(path, 0101 | 01000 | 02000000, 0600);
if (fd >= 0) { write(fd, value, size); close(fd); }
}
}
#ifdef VDI_NVFBC
static PNVFBCCREATEINSTANCE real_instance;
static PNVFBCCREATECAPTURESESSION real_capture;
static PNVFBCGETSTATUS real_status;
/* Some Optimus cards expose a real NVIDIA X screen without RandR outputs.
* Sunshine's enumeration rejects that otherwise capturable screen. Explicit
* single-seat mode enumerates the whole 2K X screen, with RandR unavailable,
* so Sunshine selects NVFBC_TRACKING_SCREEN instead of an invented output ID.
* Never report capture support or dimensions that the driver did not return.
*/
static NVFBCSTATUS NVFBCAPI screen_status(const NVFBC_SESSION_HANDLE session,
NVFBC_GET_STATUS_PARAMS *params) {
if (!params || params->dwVersion != NVFBC_GET_STATUS_PARAMS_VER) abort();
PNVFBCGETSTATUS fn = __atomic_load_n(&real_status, __ATOMIC_ACQUIRE);
if (!fn) abort();
NVFBCSTATUS status = fn(session, params);
if (status == NVFBC_SUCCESS && params->bIsCapturePossible &&
params->dwOutputNum == 0 && params->screenSize.w == 2560 &&
params->screenSize.h == 1440) {
params->bXRandRAvailable = NVFBC_FALSE;
params->dwOutputNum = 1;
params->outputs[0] = (NVFBC_RANDR_OUTPUT_INFO){
.dwId = 0, .name = "VDI-X-screen",
.trackedBox = {0, 0, 2560, 1440}
};
}
return status;
}
static NVFBCSTATUS NVFBCAPI capture_session(const NVFBC_SESSION_HANDLE session,
NVFBC_CREATE_CAPTURE_SESSION_PARAMS *params) {
if (!params || params->dwVersion != NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER)
abort(); /* A new SDK ABI must be reviewed before suppressing its cursor. */
NVFBC_CREATE_CAPTURE_SESSION_PARAMS local = *params;
if (getenv("VDI_CURSOR_LEASE")) local.bWithCursor = NVFBC_FALSE;
PNVFBCCREATECAPTURESESSION fn = __atomic_load_n(&real_capture, __ATOMIC_ACQUIRE);
if (!fn) abort();
NVFBCSTATUS status = fn(session, &local);
if (status == NVFBC_SUCCESS) receipt("nvfbc-v1\n", 9);
return status;
}
static NVFBCSTATUS NVFBCAPI create_instance(NVFBC_API_FUNCTION_LIST *functions) {
PNVFBCCREATEINSTANCE fn = __atomic_load_n(&real_instance, __ATOMIC_ACQUIRE);
if (!fn) abort();
NVFBCSTATUS status = fn(functions);
if (status == NVFBC_SUCCESS) {
if (!functions->nvFBCCreateCaptureSession) abort();
__atomic_store_n(&real_capture, functions->nvFBCCreateCaptureSession, __ATOMIC_RELEASE);
functions->nvFBCCreateCaptureSession = capture_session;
if (getenv("VDI_NVFBC_HEADLESS")) {
if (!functions->nvFBCGetStatus) abort();
__atomic_store_n(&real_status, functions->nvFBCGetStatus, __ATOMIC_RELEASE);
functions->nvFBCGetStatus = screen_status;
}
/* Startup probes do not create a capture session until Moonlight connects. */
receipt("nvfbc-ready-v1\n", 15);
}
return status;
}
#endif
static CursorImage *capture_image(void *display) {
GetImage fn = __atomic_load_n(&real_image, __ATOMIC_ACQUIRE);
if (!fn) abort();
@@ -43,12 +117,15 @@ void *dlsym(void *handle, const char *name) {
if (result && getenv("VDI_CURSOR_LEASE") &&
strcmp(name, "XFixesGetCursorImage") == 0) {
__atomic_store_n(&real_image, (GetImage)result, __ATOMIC_RELEASE);
const char *receipt = getenv("VDI_CURSOR_RECEIPT");
if (receipt) {
int fd = open(receipt, 0101 | 01000 | 02000000, 0600);
if (fd >= 0) { write(fd, "xfixes-v1\n", 10); close(fd); }
}
receipt("xfixes-v1\n", 10);
return (void *)capture_image;
}
#ifdef VDI_NVFBC
if (result && (getenv("VDI_CURSOR_LEASE") || getenv("VDI_NVFBC_HEADLESS")) &&
strcmp(name, "NvFBCCreateInstance") == 0) {
__atomic_store_n(&real_instance, (PNVFBCCREATEINSTANCE)result, __ATOMIC_RELEASE);
return (void *)create_instance;
}
#endif
return result;
}
+53
View File
@@ -49,10 +49,29 @@ def guard():
if len(lease) != 32 or any(c not in '0123456789abcdef' for c in lease):
raise ValueError('invalid lease')
manifest = json.loads((ROOT / 'manifest.json').read_text())
if set(manifest) != {'sunshine', 'capture-no-cursor.so'}:
raise RuntimeError('incomplete pinned capture manifest')
for name, expected in manifest.items():
if name not in ('sunshine', 'capture-no-cursor.so') or hashlib.sha256(
(ROOT / name).read_bytes()).hexdigest() != expected:
raise RuntimeError('pinned capture build changed')
if (ROOT / 'hide-server-cursor').exists():
# PRIME can flatten Xorg's software sprite into the framebuffer before
# NvFBC sees it. Keep a separate XFixes hide owned by the capture guard;
# GetCursorImage still supplies the unmodified native shape to stream().
with XCursor() as cursor:
cursor.hide_server_cursor()
try:
guard_capture(lease)
finally:
# Stop the video producer before restoring its visible sprite.
# ExecStopPost then restores normal, non-leased capture.
systemctl('stop', SERVICE)
else:
guard_capture(lease)
def guard_capture(lease):
kms_config = ROOT / 'kms.json'
scanout = None
def checked_scanout(timeout=3):
@@ -145,8 +164,13 @@ class XCursor:
for name in ('XDisplayWidth', 'XDisplayHeight'):
getattr(self.x, name).argtypes = [C.c_void_p, C.c_int]
self.x.XFree.argtypes = [C.c_void_p]
self.x.XSync.argtypes = [C.c_void_p, C.c_int]
self.x.XCloseDisplay.argtypes = [C.c_void_p]
self.f.XFixesGetCursorImage.argtypes = [C.c_void_p]
self.f.XFixesGetCursorImage.restype = C.POINTER(Cursor)
self.f.XFixesHideCursor.argtypes = [C.c_void_p, C.c_ulong]
self.f.XFixesShowCursor.argtypes = [C.c_void_p, C.c_ulong]
self.hidden = False
self.display = self.x.XOpenDisplay(None)
if not self.display:
raise RuntimeError('no authenticated X11 display')
@@ -154,6 +178,28 @@ class XCursor:
self.root = self.x.XDefaultRootWindow(self.display)
self.size = self.dimensions()
def __enter__(self):
return self
def __exit__(self, *_):
self.close()
def hide_server_cursor(self):
if not self.hidden:
self.f.XFixesHideCursor(self.display, self.root)
self.x.XSync(self.display, 0)
self.hidden = True
def close(self):
if self.display:
try:
if self.hidden:
self.f.XFixesShowCursor(self.display, self.root)
self.x.XSync(self.display, 0)
finally:
self.x.XCloseDisplay(self.display)
self.display = None
def dimensions(self):
root = C.c_ulong()
x, y = C.c_int(), C.c_int()
@@ -214,6 +260,11 @@ def stream():
# Fail before touching the service if the native shape is unsupported.
x.read()
systemctl('stop', GUARD)
# A display-manager restart can remove the transient unit before its
# ExecStopPost runs. The exclusive stream lock and confirmed stop make
# it safe to discard the old producer's readiness/receipt here.
for name in ('ready', 'receipt'):
(RUN / name).unlink(missing_ok=True)
(RUN / 'lease').write_text(lease)
systemctl('start', GUARD)
deadline = time.monotonic() + 40
@@ -221,6 +272,8 @@ def stream():
while not (RUN / 'ready').exists():
if time.monotonic() > deadline:
raise RuntimeError('guard not ready')
if not (RUN / 'lease').exists():
raise RuntimeError('capture guard ended before ready; inspect guard journal')
os.utime(RUN / 'lease', None)
time.sleep(.05)
if (RUN / 'ready').read_text() != lease:
File diff suppressed because it is too large. Load diff