140 lines
37 KiB
Markdown
140 lines
37 KiB
Markdown
# Provider Gateway Reference
|
||
|
||
Provider Gateway 是计算节点侧容器。它只主动连出到主 server 暴露的 provider ingress WebSocket,不要求计算节点有公网 IP,适合 NAT、内网和防火墙后的机器。
|
||
|
||
## Main Server Self Provider
|
||
|
||
当前主 server 也运行一个 provider-gateway,`providerId` 固定来自 `config.json` 的 `providerGateway.id`。这让单机环境也能验证完整的分布式调度闭环:frontend 发起任务,core 写数据库并通过 provider ingress WebSocket 下发,provider gateway 执行后回传状态。
|
||
|
||
## Upgrade Safety Gate
|
||
|
||
计算节点 `provider-gateway` 容器的重建和升级权威路径是 `provider.upgrade` 的 `mode: "schedule"`,或 frontend 中等价的显式升级调度。该路径由在线 provider 通过本地 Docker socket 启动 detached updater 容器,让升级动作脱离当前 WebSocket 与 SSH 透传会话的生命周期;重建目标只能是 `provider-gateway` service,并且必须带 `--no-deps` 与 `--force-recreate`,不得牵连 database、backend-core、frontend 或业务用户服务,也不得因为镜像 tag 未变而 no-op。
|
||
|
||
远程升级必须采用 sleep-and-validate 回滚保护:旧 gateway 在成功调度 updater 后关闭当前 WebSocket 并进入最长 5 分钟的助眠期;updater 先构建新镜像,再用旧容器的环境变量、挂载、网络和 `extra_hosts` 拉起候选 gateway;候选 gateway 必须在日志中出现 `connect_open` 和 register ack 成功,才允许把候选容器 restart policy 改为 `always`、删除旧 gateway、并把候选容器改名为原容器名。候选验证失败时 updater 必须删除候选容器并退出失败,旧 gateway 到达助眠上限后自动重连主 server,形成自动回滚。backend-core 必须在同一 Provider ID 被新 WebSocket 替换后忽略旧 WebSocket 的 close 事件,避免候选已上线后又被旧连接关闭标记为 offline。
|
||
|
||
禁止通过 UniDesk 自己的 Host SSH / WSL SSH 透传同步执行 `docker compose up -d --build provider-gateway`、`docker compose restart provider-gateway`、`docker rm -f <provider-gateway>` 后再启动等自重建命令。原因是这条 SSH 透传连接正由被重建的旧 `provider-gateway` 容器承载;旧容器停止后会切断控制通道,可能把节点留在旧容器已停、新容器未起的不可达状态。SSH 透传只允许用于诊断、修复升级前置条件、查看本地状态和升级后验证,不允许作为计算节点 `provider-gateway` 正式重建/升级通道。
|
||
|
||
只有旧节点尚不支持 `provider.upgrade mode=schedule` 时,才允许使用节点本地终端、节点自有 Web terminal、systemd、计划任务或 detached shell 做一次 bootstrap;bootstrap 完成后必须立刻回到 `provider.upgrade mode=schedule` 做真实升级验证,并通过公网 frontend 或 remote CLI 确认节点重新上线、远程更新可用性和 SSH 透传可用性。
|
||
|
||
## Deployment Method
|
||
|
||
当前主 server 公网 IP 是 `74.48.78.17`,`config.json` 中的 `network.publicHost` 必须保持为该地址;公网 frontend 入口是 `http://74.48.78.17:18081/`,provider gateway 对外接入入口是 `ws://74.48.78.17:18082/ws/provider`,provider ingress 健康检查是 `http://74.48.78.17:18082/health`。主 server 本机 provider 由根目录 `docker-compose.yml` 的 `provider-gateway` 服务启动,容器内使用 Docker 内网地址 `ws://backend-core:8081/ws/provider` 自接入;外部计算节点部署 provider-gateway 时必须改用公网 provider ingress URL。
|
||
|
||
新增计算节点推荐使用两项配置的简化挂载流程:在目标节点的 UniDesk 仓库根目录运行 `bun scripts/cli.ts provider attach <PROVIDER_ID> --master-server http://74.48.78.17/ --up`;如果主 server 仍是默认地址,`--master-server` 可省略。该命令生成 `.state/provider-<ID>.env` 和 `provider-<ID>.yml`,env 文件默认只保留 `UNIDESK_MASTER_SERVER` 与 `PROVIDER_ID` 两项,Compose 固定 `restart: always`、`pid: "host"`、Docker socket、只读 `/workspace` 仓库挂载、日志目录和 `/run/host-ssh` 维护私钥目录。provider-gateway 会从 `UNIDESK_MASTER_SERVER=http://74.48.78.17/` 自动派生 `ws://74.48.78.17:18082/ws/provider`,并自动补齐 `PROVIDER_NAME`、默认 labels、心跳/重连参数、`DOCKER_SOCKET_PATH`、`MONITOR_DISK_PATH`、`PROVIDER_UPGRADE_*`、runner image 和日志路径;远程升级所需的宿主仓库路径会优先通过 Docker inspect 反查当前容器 `/workspace` 挂载源,避免手写 `/home/ubuntu/unidesk`、Compose project、env file 或 runner image 时出错。显式环境变量仍可覆盖这些默认值;如果主 server 的 provider token 已改成非默认值,挂载时只额外传一次 `--provider-token <token>` 或在 env 文件中加入 `PROVIDER_TOKEN`。
|
||
|
||
手写 Compose 仍必须满足同一部署约束:挂载 `/var/run/docker.sock:/var/run/docker.sock` 作为 Docker 状态采集、任务执行和远程升级的唯一自动化通道,并让 provider-gateway 容器运行在宿主 PID namespace;Compose 写法是 `pid: "host"`,`docker run` 写法是 `--pid host`。缺少该配置时只能看到 provider 容器命名空间内的进程,不能视为完整节点资源监控。provider-gateway 容器必须使用 Docker restart policy `always`;`unless-stopped`、空 restart policy、手动 `docker stop` 后期待 Docker daemon 重启自动拉起,都不符合长期接入要求。长期接入节点必须保留只读 `/workspace` 仓库挂载,使 `provider.upgrade mode=schedule` 能构建候选 gateway 且只重建 `provider-gateway` service,不影响 database、backend-core、frontend 或业务用户服务。provider-gateway 部署必须同时交付 Host SSH / WSL SSH 透传维护桥;WSL 节点默认把私钥目录挂载到 `/run/host-ssh` 后,gateway 会在发现 `/run/host-ssh/id_ed25519` 时自动使用 `host.docker.internal:22`、从仓库路径推断 WSL 用户与默认工作目录,必要时仍可用 `HOST_SSH_HOST`、`HOST_SSH_PORT`、`HOST_SSH_USER`、`HOST_SSH_KEY`、`HOST_REMOTE_CWD` 显式覆盖。
|
||
|
||
## Mandatory SSH Passthrough Bundle
|
||
|
||
新增 provider-gateway 或 legacy bootstrap 后,Docker socket、远程升级和 SSH 透传必须作为同一个部署包验收,不能只让 provider 在线就认为完成。节点侧应先启动目标宿主或 WSL 的 sshd,把维护公钥写入目标用户 `authorized_keys`,再用只读目录挂载私钥到 provider-gateway 容器内 `/run/host-ssh`;provider-gateway 环境变量必须包含 `HOST_SSH_HOST`、`HOST_SSH_PORT`、`HOST_SSH_USER`、`HOST_SSH_KEY` 和 `HOST_REMOTE_CWD`。注册成功后,主 server 看到的 labels 必须同时满足 `hostSshConfigured=true`、`hostSshKeyPresent=true`、`hostSshTarget` 指向目标 sshd,且 `unideskCapabilities` 包含 `host.ssh`。
|
||
|
||
计算节点镜像必须包含 `ssh` 客户端,否则 provider 虽然可以在线并执行 Docker 任务,但 `host.ssh` 调度会在节点侧失败。标准 `src/components/provider-gateway/Dockerfile` 已安装 `openssh-client`;如果节点使用本地兜底镜像或复制 Bun/Docker CLI 的自定义镜像,也必须同时安装或复制可用的 OpenSSH client,并在容器内通过 `ssh -V`、`test -r /run/host-ssh/id_ed25519`、`ssh -i /run/host-ssh/id_ed25519 ... <target> hostname` 做本地烟测后再接入主 server。
|
||
|
||
SSH 透传只作为维护桥,不作为普通计算任务和自动升级的执行通道。普通任务、Docker 状态采集和 `provider.upgrade` 仍必须走本地 Docker socket;SSH 透传用于节点诊断、人工修复和验证 WSL/宿主维护入口是否可达。
|
||
|
||
## WSL Compute Node Deployment
|
||
|
||
WSL 计算节点和普通外部计算节点使用同一套 provider ingress 协议:节点不暴露入站端口,只需要从 WSL 主动连出到 `ws://74.48.78.17:18082/ws/provider`。已验证的 WSL 节点命名口径是短设备编号,例如 `PROVIDER_ID=D601` 与 `PROVIDER_NAME=D601` 保持一致;新增 WSL 节点必须替换为唯一 ID,避免把旧节点状态覆盖到同一条 `unidesk_nodes.provider_id` 记录。
|
||
|
||
WSL 节点应优先使用 WSL 内部原生 Docker Engine 和 `/var/run/docker.sock`,让资源采样、Docker 状态和任务执行都反映 WSL 计算环境本身,而不是 Windows 或 Docker Desktop 的代理上下文。如果当前 Docker CLI 实际连接 Docker Desktop daemon,也可以先作为可用计算节点接入,但必须在 `PROVIDER_LABELS_JSON` 中显式标注 `dockerContext=docker-desktop` 或等价标签,避免在 frontend 中把 Docker Desktop 资源误判为 WSL 原生资源。UniDesk 仓库建议放在 WSL 原生文件系统,例如 `/home/ubuntu/unidesk`,再按需从 Windows 工作区同步源码;长期运行和升级挂载应使用 WSL 原生路径只读挂到容器内 `/workspace`,减少 `/mnt/c` 权限、性能和路径转换问题。
|
||
|
||
WSL provider 的最小环境文件应放在节点本地私有路径,例如 `/home/ubuntu/unidesk/.state/provider-<ID>.env`,并由生成的 `provider-<ID>.yml` 或 `docker run --env-file` 读取。新挂载默认只写 `UNIDESK_MASTER_SERVER` 与 `PROVIDER_ID`;如果需要覆盖 labels,`PROVIDER_LABELS_JSON` 在 Docker env-file 中可以写成单行 JSON,临时用 shell `source` 调试时必须对整段 JSON 加引号,否则 shell 会按 `{}` 和逗号拆分导致 JSON 解析失败。不写 labels 时 provider-gateway 会根据 Provider ID、Docker、WSL 内核和 `/etc/os-release` 自动生成 `host`、`role`、`docker`、`wsl`、`distro` 与 `attachMode=simple`,并在运行时追加 `runtime`、`dockerSocketPresent`、gateway 版本和 `gatewayUptimeSeconds`。`.state/provider-<ID>.env`、`provider-<ID>.yml`、`logs/provider-<ID>/` 和容器日志属于节点本地运行态,必须保持在 `.gitignore` 覆盖范围内,不能提交 provider token、登录态或运行日志。
|
||
|
||
长期运行推荐用 systemd 管理 provider-gateway 容器,而不是只在交互 shell 中运行 Bun 进程。systemd unit 的稳定形态是:`ExecStartPre=-docker rm -f unidesk-provider-gateway-<ID>` 清理同名旧容器,`ExecStart=docker run --restart always --pid host --name unidesk-provider-gateway-<ID> --env-file ... -v /var/run/docker.sock:/var/run/docker.sock -v /home/ubuntu/unidesk:/workspace:ro -v /home/ubuntu/unidesk/logs/provider-<ID>:/var/log/unidesk -v <ssh-key-dir>:/run/host-ssh:ro unidesk_provider-gateway:<id>`,`ExecStop=docker stop unidesk-provider-gateway-<ID>`,并设置 `Restart=always`。临时部署也必须使用 `docker run -d --restart always --pid host`,并保证容器名、env 文件、日志目录、SSH 私钥只读挂载和镜像 tag 都带上节点 ID,便于 frontend、Docker 状态、SSH 透传、进程资源表和本地排障互相对应。`provider.upgrade` 是长期接入节点的必备能力,provider-gateway 不提供 `PROVIDER_UPGRADE_ENABLED` 或等价禁用开关;如果节点缺少升级环境变量或 SSH 透传环境变量,必须修正节点部署,而不是在服务端接受只能预检、不能升级或不能维护透传的半成品状态。
|
||
|
||
WSL 本身会在没有前台进程时被 Windows 回收;如果该节点要作为长期在线算力,必须通过 Windows 启动项、计划任务或后台 `wsl.exe -d <distro> -u root -- bash -lc "systemctl start docker unidesk-provider-gateway-<ID>.service; exec sleep infinity"` 这类 keepalive 进程保持发行版运行。仅启用 WSL 内 systemd service 不等价于 Windows 层面的常驻守护。
|
||
|
||
Docker daemon 重启后的恢复验收必须看两个层级。第一层是 Docker 自身:`docker inspect --format '{{.HostConfig.RestartPolicy.Name}} {{.HostConfig.PidMode}} {{.State.Status}}' unidesk-provider-gateway-<ID>` 必须返回 `always host running`;D601/D518 这类 Docker Desktop daemon 上还要记录 `docker info --format '{{.Name}} {{.LiveRestoreEnabled}}'`,当前 `LiveRestore=false` 意味着 daemon 重启会停止容器,恢复完全依赖 restart policy,因此不能把容器曾经是 running 当作已具备重启恢复能力。第二层是宿主/WSL 常驻:节点应有 systemd、Windows 计划任务、Docker Desktop 自启动或等价 keepalive 证明 Docker daemon 会随机器/WSL 启动;如果 `systemctl list-unit-files '*unidesk*'` 为空,必须在验收记录中明确该节点暂时只依赖 Docker Desktop daemon 自身,不能声称有 WSL 内 systemd watchdog。provider-gateway 新版本会在启动与 heartbeat 中自检当前容器 restart policy,发现不是 `always` 时通过 Docker socket 尝试 `docker update --restart always <self>` 并在 labels 中上报 `providerGatewayRestartPolicyOk`、`providerGatewayPidModeOk` 与 `providerGatewayRuntimeGuardOk`;这只是最后一道自愈,不能替代 Compose/systemd 的正确配置。
|
||
|
||
## WSL Network And Proxy Bootstrap
|
||
|
||
WSL 节点出网异常时,先把代理设置固化到目标 WSL 用户的 `~/.bashrc`,而不是只在当前 shell 临时 `export`。长期可复用的写法是在每次交互 shell 启动时从 `/etc/resolv.conf` 读取 Windows 宿主在 WSL NAT 中的 nameserver IP,再导出 `http_proxy`、`https_proxy`、`HTTP_PROXY`、`HTTPS_PROXY`、`all_proxy` 和 `ALL_PROXY` 指向 `http://<windows-host-ip>:7890` / `socks5://<windows-host-ip>:7890`,并保留 `no_proxy=localhost,127.0.0.1,::1,host.docker.internal`。不要把某次启动看到的宿主 IP 当成永远不变的常量;动态读取可以避免 WSL 网络重建后代理失效。
|
||
|
||
Windows 代理程序常见默认只监听 `127.0.0.1:7890`,此时 WSL 访问 `<windows-host-ip>:7890` 会失败。可选修复是让代理程序监听 WSL 可达网卡,或在 Windows 用户态启动一个幂等 TCP relay,把 `<windows-host-ip>:7890` 转发到 `127.0.0.1:7890`;`netsh interface portproxy` 通常需要管理员权限,不应作为普通节点 bootstrap 的唯一方案。`.bashrc` 可以在发现 `<windows-host-ip>:7890` 不可达时调用 Windows 侧脚本启动该 relay,但 relay 脚本、临时 apt source、日志和节点私钥都属于本地运行态,必须留在 `.state` 或用户目录,不能提交到仓库。
|
||
|
||
apt 或镜像构建遇到 `archive.ubuntu.com` 超时、代理 `503` 或 registry metadata 拉取失败时,优先使用节点侧临时镜像源或临时 apt sources 完成 bootstrap,例如把 Ubuntu 源临时替换到可达镜像后安装 `openssh-server`。这类网络兜底只解决节点初始化,不改变 `PROVIDER_SERVER_URL`、provider token、Docker socket、Host SSH 透传和 remote CLI 验收标准。
|
||
|
||
## WSL Image Build Fallback
|
||
|
||
标准镜像构建路径使用 `src/components/provider-gateway/Dockerfile`,基础镜像为 `oven/bun:1-alpine`,并在镜像内安装 Bun、Docker CLI、Compose plugin、`df` 和 provider-gateway 源码。WSL 或 Docker Desktop 环境中的 registry mirror、DNS 或代理配置可能导致 `oven/bun` 元数据拉取失败;此时不要修改 provider ingress 协议或服务端配置,应改用节点侧镜像交付兜底。
|
||
|
||
可复用的兜底方式是使用本机已存在的 Debian/Node 基础镜像,复制 WSL 本地 `bun`、`docker` 和 `docker-compose` plugin 到镜像内,再复制 `src/components/provider-gateway/src` 与 `src/components/shared/src`。该镜像必须能在容器内执行 `bun --version`、`docker version`、`docker compose version`、`ssh -V`,并通过挂载的 `/var/run/docker.sock` 执行 `docker info` 和 `docker ps`;只有这些命令通过后才能作为 `unidesk_provider-gateway:<provider-id>` 运行。该兜底镜像只改变节点侧交付方式,不改变 `PROVIDER_SERVER_URL`、token、heartbeat、labels、Docker socket、SSH 私钥只读挂载和服务端验收标准。
|
||
|
||
如果兜底镜像还要承载 WSL SSH 透传,必须同时复制 `/usr/bin/ssh`、`/etc/ssh/ssh_config`、`/etc/ssh/ssh_config.d` 以及 `ldd /usr/bin/ssh` 输出的全部动态库,尤其不能漏掉 `/lib64/ld-linux-x86-64.so.2` 这类 ELF loader。只在 rootfs 中看到 `/usr/bin/ssh` 文件不代表可执行;验收必须运行 `docker run --rm <image> /bin/sh -lc '/usr/bin/ssh -V && /usr/bin/docker compose version'`,再以实际 provider 容器执行 `ssh -T -i /run/host-ssh/id_ed25519 -p 22 ubuntu@host.docker.internal hostname`。若 Compose 使用 Docker bridge 网络,WSL 节点必须在 compose 文件中加入 `extra_hosts: ["host.docker.internal:host-gateway"]`,否则容器内可能无法把 `host.docker.internal` 解析到 WSL 宿主。
|
||
|
||
## Deployment Verification
|
||
|
||
provider-gateway 部署是否成功必须以 UniDesk frontend 中可见的 Provider 信息为准,不能只看节点容器 `running`。验证时访问 `http://74.48.78.17:18081/`,使用配置中的账号密码登录,进入 `资源节点 / 节点清单`,确认目标 `PROVIDER_ID`、`PROVIDER_NAME`、`online` 状态、`lastHeartbeat` 和 labels 可见;点击该节点的 `查看原始JSON`,确认 raw payload 中的 `providerId`、`name`、`status`、`labels` 与部署环境变量一致。随后进入 `资源节点 / 资源监控`,确认该 Provider 有 CPU、实际内存和硬盘采样曲线;进入 `资源节点 / Docker 状态`,确认 Docker daemon、containers、images、volumes、networks 已渲染出来,且 `dockerSocketPresent` 或 Docker ready 状态与预期一致。只有这些前端信息都能通过 UniDesk 正常读取,才说明 provider-gateway 已经真正挂载到主 server。
|
||
|
||
WSL 节点还应补充一次真实调度验证:向该 `PROVIDER_ID` 下发 `docker.ps`,任务必须从 `dispatched` 进入 `succeeded`,并在结果中看到 WSL Docker daemon 返回的容器列表;对于容器化运行的 provider-gateway,列表中通常应包含 `unidesk-provider-gateway-<PROVIDER_ID>`。这一步可以同时证明 provider WebSocket、服务端任务路由、节点侧 Docker socket 和结果回传链路都已贯通。
|
||
|
||
SSH 透传自测是 provider-gateway 部署验收的一部分。目标 Provider 在线后,先确认 frontend 节点清单或 `debug health` 中该节点 labels 显示 `hostSshConfigured=true`、`hostSshKeyPresent=true` 且能力包含 `host.ssh`;再运行 `bun scripts/cli.ts debug dispatch <PROVIDER_ID> host.ssh --wait-ms 15000`,任务必须 `succeeded`,result 中 `probeLine` 必须包含 `UNIDESK_SSH_TEST` 且 `exitCode=0`;最后运行 `bun scripts/cli.ts ssh <PROVIDER_ID> hostname`,输出必须是目标宿主或 WSL 的 hostname,进程退出码必须为 0。任何 provider 在线但不声明 `host.ssh` 的状态都只能算未完成部署。
|
||
|
||
如果该节点承载用户服务,还必须声明 `microservice.http` capability,并通过 `bun scripts/cli.ts microservice health <id>` 或 remote CLI 等价命令验证 backend-core 能经 provider-gateway 访问节点本机后端。用户服务后端端口不得映射到公网;provider-gateway 只允许代理节点本地 HTTP 地址或主 server 显式 Compose 服务名,业务 API 路径和 HTTP 方法还要受 backend-core `allowedPathPrefixes` 与 `allowedMethods` 限制。
|
||
|
||
自动化验证必须使用 Playwright 访问公网 frontend,而不是在容器内直接调 core API 代替浏览器验收。标准命令是 `bun scripts/cli.ts e2e run`;该命令会让 Playwright 打开公网 `http://74.48.78.17:18081/`、登录、抓取页面中的 Provider 信息和 `查看原始JSON` 内容,并检查 Provider 自接入、资源指标、Docker 状态和 `provider.upgrade` 预检。外部新增节点的人工验收应复用同一套前端路径:先确认 Provider 信息出现在节点清单,再确认资源监控和 Docker 状态页面有该节点的数据,最后通过任务调度向该 Provider 下发 `echo`、`docker.ps` 或维护专用 `host.ssh` probe,并在任务历史中查看耗时、状态、stdout/stderr 摘要和失败原因。
|
||
|
||
## Provider Ingress
|
||
|
||
provider ingress 是唯一允许公网暴露的 provider 连接接口,当前由 backend-core 容器的独立端口提供 `/ws/provider` 和 `/health`。backend-core REST API 仍只在 Docker 内网开放,外部计算节点只应连接 provider ingress。
|
||
|
||
## Docker Socket Path
|
||
|
||
自动任务执行只允许走本地 Docker socket。Compose 将 `/var/run/docker.sock` 挂入 provider-gateway,provider 标签会报告 `dockerSocketPresent`,`docker.ps` 调试任务会通过该 socket 查询宿主 Docker 容器。
|
||
|
||
## User Service HTTP Proxy
|
||
|
||
`microservice.http` 是 provider-gateway 给 UniDesk 用户服务使用的私有后端访问能力。backend-core 通过真实 WebSocket dispatch 下发目标 service id、节点本机 `targetBaseUrl`、path、query、method、request body、timeout 和可选 JSON 数组裁剪参数;provider-gateway 支持 `GET`、`HEAD`、`POST`、`PUT`、`PATCH`、`DELETE`,但最终允许方法必须由每个用户服务的 `backend.allowedMethods` 显式配置。provider-gateway 只允许访问 `http://127.0.0.1`、`http://localhost`、`http://host.docker.internal` 这些节点本地地址;主 server 内置 Todo Note 与 Code Queue 后端可分别使用 Compose 服务名 `http://todo-note:4211` 与 `http://code-queue:4222`。该能力不打开 provider-gateway 入站端口,也不替代业务仓库自身 Dockerfile/docker-compose。
|
||
|
||
超大 JSON 响应可以使用 `jsonArrayLimits` 在 provider-gateway 返回前裁剪指定数组,并在响应体中写入 `_unidesk.arrayLimits` 元数据,便于 UniDesk frontend 预览列表而不展示裸 JSON。长期应优先推动业务后端提供分页 API;裁剪只是 UniDesk 集成层的展示保护。
|
||
|
||
## Gateway Version Metadata
|
||
|
||
provider-gateway 必须从自身 `package.json` 读取版本号,并在 register 与 heartbeat labels 中上报 `providerGatewayName`、`providerGatewayVersion`、`providerGatewayStartedAt` 和 `providerGatewayUpgradePolicy`。backend-core 将这些 labels 合并到 `unidesk_nodes.labels`,frontend 在节点清单、资源监控和 `资源节点 / 网关版本` 中展示;旧节点缺少这些字段时只能显示版本未知,不能用猜测值替代。`unideskCapabilities`、`hostSshConfigured`、`hostSshKeyPresent` 和 `hostSshTarget` 也是 WebUI 运维可用性徽标的数据源,用于直接显示每个计算节点的 SSH 透传可用性与远程更新可用性。
|
||
|
||
`src/components/provider-gateway` 下任何代码或行为变更都必须在同一变更集中递增 `src/components/provider-gateway/package.json` 的 `version`,不得只改实现而沿用旧版本号。验收时必须通过公网 frontend 的 `资源节点 / 网关版本` 或 `bun scripts/cli.ts debug health` 确认目标 provider 的 `providerGatewayVersion` 已上报新版本;如果线上节点仍显示旧版本,该 provider-gateway 变更不能视为交付完成。
|
||
|
||
任何远程升级预检、执行升级和远程更新记录都必须显式显示指定 Provider 的 gateway 版本号。`provider.upgrade` result 的 plan 中必须包含 `providerId`、`providerName`、当前运行中的 `providerGatewayVersion` 和从升级 workspace 的 `src/components/provider-gateway/package.json` 读取到的 `targetProviderGatewayVersion`;frontend 的升级控件与 `资源节点 / 网关版本` 远程更新记录必须把该版本渲染为结构化字段,不能只把版本埋在原始 JSON 中。
|
||
|
||
## Docker Status Telemetry
|
||
|
||
provider-gateway 连接成功后必须周期性上报 Docker daemon 状态,数据来源是本地 Docker socket 上的 `docker info`、`docker ps -a`、`docker images`、`docker volume ls` 和 `docker network ls`。backend-core 将最新快照保存到 `unidesk_node_docker_status`,frontend 的资源节点 `Docker 状态` 子标签用该快照渲染 Docker Desktop 风格视图;该能力仍然只通过 provider 主动上报,不要求主 server 反向连接计算节点。
|
||
|
||
## System Status Telemetry
|
||
|
||
provider-gateway 连接成功后必须周期性上报节点 CPU、内存、硬盘和进程资源占用。整体采集来源是节点本地 `/proc/stat`、`/proc/loadavg`、`/proc/meminfo` 与 `df -PB1`,进程表来源是 `/proc/[pid]/stat`、`/proc/[pid]/status`、`/proc/[pid]/cmdline` 和 `/proc/[pid]/io`;backend-core 将最新快照保存到 `unidesk_node_system_status`,并将历史采样保存到 `unidesk_node_metric_samples` 供 frontend 绘制任务管理器风格曲线。内存使用量采用实际占用口径:`MemTotal - MemFree - Buffers - Cached - SReclaimable + Shmem`,也就是不把 Linux page cache / buffer 计入占用;上报中同时保留 `cacheBytes` 便于排查。进程表的 `rssBytes` 是实际常驻内存,默认按 `rssBytes` 降序截取前 120 个进程;`cpuPercent` 使用相邻采样 CPU tick 差值,首个采样用进程生命周期平均值兜底;磁盘 I/O 速率使用相邻 `/proc/[pid]/io` 的 `read_bytes/write_bytes` 差值。该链路仍然由 provider 主动上报,主 server 不反向探测计算节点。
|
||
|
||
## Remote Provider Upgrade
|
||
|
||
backend-core 可以通过真实 WebSocket 调度向在线 provider 下发 `provider.upgrade`。`mode: "plan"` 只返回升级计划,用于 E2E 和人工预检;`mode: "schedule"` 会要求 provider-gateway 通过本地 Docker socket 启动一个 detached updater 容器。updater 的固定策略是先执行 `docker compose build provider-gateway`,构建成功后只按 `com.docker.compose.project` 与 `com.docker.compose.service=provider-gateway` label 删除旧 provider-gateway 容器,最后执行 `docker compose up -d --no-deps --force-recreate provider-gateway`。`--no-deps` 是强制要求,`--force-recreate` 用于保证 provider 重新注册能力标签并避免 compose no-op;升级 provider-gateway 时不得重建或停止 database、backend-core、frontend。对 D518、D601 这类计算节点,`mode: "schedule"` 是正式重建/升级 `provider-gateway` 容器的唯一标准路径;升级执行路径使用 Docker socket 和只读仓库挂载,不使用 Host SSH 维护桥作为自动调度通道。
|
||
|
||
远程升级策略固定为 always-enabled:只要 provider-gateway 在线并声明 `provider.upgrade`,`mode: "schedule"` 就必须真正调度升级容器,不允许被 `PROVIDER_UPGRADE_ENABLED=false`、前端隐藏按钮或服务端特殊名单禁用。升级能力的安全边界不是开关,而是显式 `PROVIDER_UPGRADE_*` 配置、Docker socket 权限、只读仓库挂载、固定 Compose service 和 `--no-deps` 约束。升级计划中必须展示 `policy: "always-enabled"`、updater 容器名、runner image、workspace、Compose project/service、env file、compose file 和实际 `docker run` 命令,方便前端任务历史与 CLI debug 直接诊断。
|
||
|
||
`mode: "schedule"` 的成功返回只代表 updater 已被调度,最终升级成败由候选 gateway 自验证决定。updater 必须先按 Compose 构建新镜像,再用旧容器的 `Config.Env` 生成候选 env-file,并复用旧容器的 Docker socket、日志目录、SSH 私钥只读挂载、Compose 网络和 `extra_hosts`;候选容器启动时 restart policy 必须先是 `no`,并显式使用 `--pid host` 保持节点级进程资源采集,验证通过后才能改成 `always` 并删除旧容器。升级计划的 `replacementStrategy` 必须包含 `oldGatewaySleepMs`、`validationTimeoutMs`、`promoteOnlyAfterCandidateValidation`、`candidateRestartPolicyAfterPromotion: "always"`、`candidateUsesOldContainerEnvironment`、`candidateUsesOldContainerMounts`、`candidateUsesOldContainerNetworks`、`candidateUsesOldContainerExtraHosts` 和 `candidateUsesHostPidNamespace`,并且必须在 plan 中显示指定 Provider 的当前/目标 gateway 版本号,便于前端和 CLI 判断这不是旧的先删旧容器再 up 的危险流程。
|
||
|
||
远程更新记录的权威来源是 backend-core 保存的 `provider.upgrade` 任务历史,而不是 provider-gateway 容器日志文件。frontend 必须按 Provider 聚合这些任务,并把状态、模式、task id、来源、耗时、策略、updater 容器摘要、失败原因和更新时间渲染为表格或卡片;完整 task/result JSON 只能由操作员点击 `查看原始JSON` 后查看。
|
||
|
||
旧版 provider-gateway 如果只能返回 plan 或因为旧环境中的 `PROVIDER_UPGRADE_ENABLED=false` 拒绝 schedule,需要先通过任意现有维护通道手动 bootstrap 一次。bootstrap 的目标不是长期流程,而是把节点更新到支持 always-enabled 远程升级和 Host SSH / WSL SSH 维护桥的版本;完成后必须立刻用 `bun scripts/cli.ts debug dispatch <PROVIDER_ID> provider.upgrade --mode schedule --wait-ms 15000` 做一次真实一键升级验证,再用 `bun scripts/cli.ts debug health` 或公网 frontend 确认该节点仍在线、`unideskCapabilities` 包含 `provider.upgrade`,需要 SSH 维护的 WSL 节点还必须包含 `host.ssh`。
|
||
|
||
## Manual Upgrade Maintenance
|
||
|
||
手动升级只用于把旧节点 bootstrap 到支持 always-enabled 远程升级的版本;bootstrap 完成后,常规重建/升级必须回到 `provider.upgrade mode=schedule`,不得再用 SSH 透传同步重建 `provider-gateway`。节点侧维护步骤是:进入节点本地 UniDesk 仓库,执行 `git pull --ff-only` 获取主 server 已推送版本;确认 `.state/provider-<ID>.env` 中存在 `PROVIDER_SERVER_URL=ws://74.48.78.17:18082/ws/provider`、`PROVIDER_ID=<ID>`、`PROVIDER_NAME=<ID>`、`PROVIDER_TOKEN`、`PROVIDER_LABELS_JSON`、`PROVIDER_UPGRADE_HOST_PROJECT_ROOT=/home/ubuntu/unidesk`、`PROVIDER_UPGRADE_WORKSPACE_PATH=/workspace`、`PROVIDER_UPGRADE_COMPOSE_FILE`、`PROVIDER_UPGRADE_ENV_FILE`、`PROVIDER_UPGRADE_COMPOSE_PROJECT`、`PROVIDER_UPGRADE_SERVICE=provider-gateway`、`PROVIDER_UPGRADE_RUNNER_IMAGE=unidesk_provider-gateway:<id>`、`DOCKER_SOCKET_PATH=/var/run/docker.sock`、`MONITOR_DISK_PATH=/`、心跳和重连参数。旧 env 文件中如果还残留 `PROVIDER_UPGRADE_ENABLED`,新版 provider-gateway 会忽略它;长期文档和新部署不得再依赖这个键。
|
||
|
||
如果节点已有专用 Compose,优先用节点本地 Compose 手动重建一次:`docker compose --env-file .state/provider-<ID>.env -f <compose-file> -p <compose-project> up -d --no-deps --build --force-recreate provider-gateway`。这条命令必须在节点本地终端、节点自有 Web terminal、系统计划任务或 detached shell 中执行;不得通过正在被重建的 UniDesk provider-gateway 自己提供的 SSH 透传同步执行,否则旧 provider 容器停止时会切断 SSH client,可能导致重建中断在旧容器已停、新容器未起的状态。若只能通过 UniDesk 触达该节点,必须使用 `provider.upgrade mode=schedule` 的 detached updater,或先用节点本地 `nohup`/systemd 启动一个不依赖当前 provider 容器生命周期的重建脚本。老版 `docker-compose` 可能在重建已存在容器时因为 `ContainerConfig` 兼容问题失败;此时只能移除目标 provider-gateway 容器后重新 `up -d --no-deps provider-gateway`,不得执行 `down -v`、`docker volume rm` 或任何会影响 database 命名卷的命令。如果节点当前只有 `docker run` 部署,则先构建镜像 `docker build -f src/components/provider-gateway/Dockerfile -t unidesk_provider-gateway:<id> .`,再以固定容器名重建:使用 `--restart always --pid host`,挂载 `/var/run/docker.sock:/var/run/docker.sock`、`/home/ubuntu/unidesk:/workspace:ro`、节点日志目录到 `/var/log/unidesk`,如需 WSL SSH 维护桥还要把只读私钥目录挂载到 `/run/host-ssh`,并使用同一个 `.state/provider-<ID>.env` 启动。无论 Compose 还是 `docker run`,容器名和镜像 tag 都必须带 Provider ID,便于 Docker 状态页、进程资源表、任务历史和节点本地排障互相对应。
|
||
|
||
手动升级完成后的判定标准固定为主 server 可观测结果,而不是节点容器 `running`:访问公网 frontend `http://74.48.78.17:18081/`,确认该 Provider 在线;随后在任意装有本仓库且 `config.json` 含正确 frontend 登录凭据的计算节点上执行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch <PROVIDER_ID> provider.upgrade --mode schedule --wait-ms 15000`,确认任务 `succeeded` 且 result 包含 updater 容器信息;最后再次查看 frontend 或执行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug health`,确认节点重连、指标恢复、labels 中 `host.ssh` 能力存在。每个 provider-gateway 手动升级后都必须用 remote CLI 再执行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch <PROVIDER_ID> host.ssh --wait-ms 15000` 和 `bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh <PROVIDER_ID> hostname`,验证维护桥没有在升级后丢失;该 remote CLI 默认走公网 frontend,不需要指定 `--main-server-key`。
|
||
|
||
## Host SSH Maintenance Bridge
|
||
|
||
宿主 SSH / WSL SSH 转发是 provider-gateway 部署的必备维护能力,但只作为应急维护辅助路径,不用于自动计算任务调度。实现参考 `../web-terminal` 的经验:容器内使用只读挂载的私钥,主动连接宿主或 WSL sshd,并设置 `BatchMode=yes`、`StrictHostKeyChecking=accept-new`、`ServerAliveInterval=20` 和 `ServerAliveCountMax=3`。TTY 策略必须按会话类型区分:无远端命令的交互登录 shell 使用 `ssh -tt`,带远端命令的会话使用 `ssh -T`,避免脚本 stdin 被伪终端回显或把 `Ctrl-D` 当成业务输入。主 server Compose 会把 `config.json` 的 `sshForwarding.keyDir` 只读挂载为 `/run/host-ssh`,provider 标签会上报 `hostSshConfigured`、`hostSshKeyPresent` 和 `hostSshTarget`,便于在前端节点清单确认维护桥是否具备条件。
|
||
|
||
WSL 计算节点使用 Docker Desktop daemon 时,provider-gateway 容器通常应连接 `host.docker.internal:22`,目标是当前 WSL 发行版里的 sshd,而不是给节点开放公网 SSH。节点侧必须确认 sshd 监听 `22`、目标用户可用维护公钥免密登录、`authorized_keys` 与挂载到 `/run/host-ssh/id_ed25519` 的私钥匹配;如果容器内直连 `host.docker.internal` 都失败,先修复 WSL sshd、Docker Desktop host gateway 或密钥权限,再排查 UniDesk WebSocket 透传。
|
||
|
||
WSL provider 需要调用 Windows-only 工具链时,优先在 WSL 用户的 `~/.local/bin` 放轻量 shim,而不是维护一套完全分叉的 Windows skill。稳定形态是:`win-powershell` 负责 Windows 发现和诊断,`win-cmd` 通过临时 `.cmd` 文件执行命令以规避 UNC 当前目录和带空格路径的 cmd 引号问题,`win-py` 将 `/mnt/<drive>/...` 参数转换为 Windows 路径后调用 `py.exe`,`win-npm` 通过 `npm.cmd --prefix <skill_dir>` 运行需要访问 COM 口的 TypeScript skill。Keil 这类依赖 Windows GUI/驱动/注册表的 skill 应由 WSL wrapper 调用 Windows 侧 skill 脚本;board-comm 这类纯 TCP JSON-RPC skill 可以直接用 WSL Python 运行;serial-monitor 需要访问 Windows COM 口时应走 Windows Node/npm。不要用宽泛的 `find /mnt/*` 扫 Windows 盘,skill 位置先用 `bun scripts/cli.ts ssh <PROVIDER_ID> skills`,项目位置优先用 PowerShell 或结构化 `glob`/`find` 缩小范围。Windows 透传 wrapper 属于节点 bootstrap,不等于修改 WSL skill 业务代码;完整规则见 `docs/reference/windows-passthrough.md`。
|
||
|
||
维护桥通过真实 WebSocket dispatch 暴露为 `host.ssh` 命令。默认 payload 使用 `mode: "probe"`,远端只执行一个短命令并返回 `UNIDESK_SSH_TEST user=... host=... bridge=host.ssh cwd=...`;需要人工诊断时可以显式使用 `mode: "exec"` 与 `command` 字段执行有界命令。所有 `host.ssh` 执行都必须有超时,stdout/stderr 在 task result 中截断展示;自动升级和普通任务仍必须使用 Docker socket 与 `provider.upgrade`,不得把 WSL SSH 维护桥当成调度通道。
|
||
|
||
面向人的终端入口是 `bun scripts/cli.ts ssh <PROVIDER_ID> [ssh-like args...]`。无后续参数时打开远端登录 shell,有后续参数时执行远端命令并返回远端 exit code;该入口走 backend-core 内网 `/ws/ssh` broker 和 provider 既有 WebSocket,不新增公网 core 端口。传统 ssh 传输参数由 provider-gateway 环境变量统一控制,CLI 只负责把 Provider ID 后的远端命令和终端 stdin/stdout/stderr 透传过去。WSL 节点需要同时看清 Linux/WSL 与 Windows 两套 skill 时,使用 `bun scripts/cli.ts ssh <PROVIDER_ID> skills`,该命令只通过已建立的维护桥读取 `SKILL.md` 元数据,不要求 provider-gateway 新增业务 API。
|
||
|
||
验证 WSL SSH 桥时,先在目标 WSL 中启动 sshd 并确保维护公钥写入目标用户的 `authorized_keys`,再确认目标 provider 注册 labels 中 `unideskCapabilities` 包含 `host.ssh`。运行 `bun scripts/cli.ts debug dispatch <PROVIDER_ID> host.ssh --wait-ms 15000` 后,结果应在 `debug task latest` 或前端任务历史中显示 `status: succeeded`、`probeLine` 含 `UNIDESK_SSH_TEST`、`exitCode: 0`,并且目标节点 labels 中 `hostSshKeyPresent` 为 true;随后运行 `bun scripts/cli.ts ssh <PROVIDER_ID> hostname` 验证近似原生 ssh 的远端命令体验。在计算节点本机自测时,使用 remote CLI 透传同一组命令:`bun scripts/cli.ts --main-server-ip 74.48.78.17 debug health`、`bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch <PROVIDER_ID> host.ssh --wait-ms 15000` 和 `bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh <PROVIDER_ID> hostname`;默认 remote CLI 走公网 frontend 登录态,不需要主 server SSH key。健康检查必须能看到该 Provider 在线、`hostSshConfigured=true`、`hostSshKeyPresent=true`、`hostSshTarget` 正确、`unideskCapabilities` 包含 `host.ssh`,probe 必须返回 `UNIDESK_SSH_TEST`,`ssh <PROVIDER_ID> hostname` 必须输出目标 WSL/宿主 hostname 且 exit code 为 0。如果 D518 这类 WSL 节点没有公网 SSH 入口,也必须通过这个 provider-gateway 自连维护桥完成验证,而不是要求主 server 直接连节点公网 22 端口;旧版 provider 未声明 `host.ssh` 时必须先升级 provider-gateway,否则 core 会拒绝 SSH 透传。
|