@@ -10,7 +10,13 @@ Provider Gateway 是计算节点侧容器。它只主动连出到主 server 暴
当前主 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,并复用 `config.json` / `.state/docker-compose.env` 中的 provider token、心跳间隔和重连参数。
计算节点部署 provider-gateway 的最小方法是:准备可运行 `unidesk_provider-gateway` 镜像的 Docker 环境,为节点分配唯一 `PROVIDER_ID` 与可读 `PROVIDER_NAME` ,设置 `PROVIDER_SERVER_URL=ws://74.48.78.17:18082/ws/provider` 、`PROVIDER_TOKEN` 、`PROVIDER_LABELS_JSON` 、`HEARTBEAT_INTERVAL_MS` 、`RECONNECT_BASE_MS` 和 `RECONNECT_MAX_MS` ,并挂载 `/var/run/docker.sock:/var/run/docker.sock` 作为 Docker 状态采集、任务执行和远程升级的唯一自动化通道。所有长期接入节点都必须配置 `PROVIDER_UPGRADE_*` 环境变量,把节点上的 UniDesk 仓库只读挂载到 `PROVIDER_UPGRADE_WORKSPACE_PATH` ,并确保升级命令只重建 `provider-gateway` service,不影响 database、backend-core、frontend。需要维护桥连接 WSL 的 节点必须额外 设置 `HOST_SSH_HOST=host.docker.internal` 、`HOST_SSH_PORT=22` 、`HOST_SSH_USER=<WSL 用户>` 、`HOST_SSH_KEY=/run/host-ssh/id_ed25519` 、`HOST_REMOTE_CWD=/home/<WSL 用户>` ,并把只含维护私钥的宿主目录只读挂载到 `/run/host-ssh` 。
计算节点部署 provider-gateway 的最小方法是:准备可运行 `unidesk_provider-gateway` 镜像的 Docker 环境,为节点分配唯一 `PROVIDER_ID` 与可读 `PROVIDER_NAME` ,设置 `PROVIDER_SERVER_URL=ws://74.48.78.17:18082/ws/provider` 、`PROVIDER_TOKEN` 、`PROVIDER_LABELS_JSON` 、`HEARTBEAT_INTERVAL_MS` 、`RECONNECT_BASE_MS` 和 `RECONNECT_MAX_MS` ,并挂载 `/var/run/docker.sock:/var/run/docker.sock` 作为 Docker 状态采集、任务执行和远程升级的唯一自动化通道。所有长期接入节点都必须配置 `PROVIDER_UPGRADE_*` 环境变量,把节点上的 UniDesk 仓库只读挂载到 `PROVIDER_UPGRADE_WORKSPACE_PATH` ,并确保升级命令只重建 `provider-gateway` service,不影响 database、backend-core、frontend。provider-gateway 部署必须同时交付 Host SSH / WSL SSH 透传维护桥; WSL 节点应 设置 `HOST_SSH_HOST=host.docker.internal` 、`HOST_SSH_PORT=22` 、`HOST_SSH_USER=<WSL 用户>` 、`HOST_SSH_KEY=/run/host-ssh/id_ed25519` 、`HOST_REMOTE_CWD=/home/<WSL 用户>` ,并把只含维护私钥的宿主目录只读挂载到 `/run/host-ssh` 。
## Mandatory SSH Passthrough Bundle
新增或重建 provider-gateway 时,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 透传只作为维护桥,不作为普通计算任务和自动升级的执行通道。普通任务、Docker 状态采集和 `provider.upgrade` 仍必须走本地 Docker socket;SSH 透传用于节点诊断、人工修复和验证 WSL/宿主维护入口是否可达。
## WSL Compute Node Deployment
@@ -20,7 +26,7 @@ WSL 节点应优先使用 WSL 内部原生 Docker Engine 和 `/var/run/docker.so
WSL provider 的最小环境文件应放在节点本地私有路径,例如 `/home/ubuntu/unidesk/.state/provider-<ID>.env` ,并由 `docker run --env-file` 读取。`PROVIDER_LABELS_JSON` 在 Docker env-file 中可以写成单行 JSON;如果临时用 shell `source` 方式调试,必须对整段 JSON 加引号,否则 shell 会按 `{}` 和逗号拆分导致 JSON 解析失败。WSL 节点建议至少包含这些 labels:`host` 、`role=wsl-provider` 、`wsl=true` 、`distro` 、`docker=true` ;运行时 provider-gateway 会自动追加 `runtime` 、`dockerSocketPresent` 和 `gatewayUptimeSeconds` 。`.state/provider-<ID>.env` 、`logs/provider-<ID>/` 和容器日志属于节点本地运行态,必须保持在 `.gitignore` 覆盖范围内,不能提交 provider token、登录态或运行日志。
长期运行推荐用 systemd 管理 provider-gateway 容器,而不是只在交互 shell 中运行 Bun 进程。systemd unit 的稳定形态是:`ExecStartPre=-docker rm -f unidesk-provider-gateway-<ID>` 清理同名旧容器,`ExecStart=docker run --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 unidesk_provider-gateway:<id>` , `ExecStop=docker stop unidesk-provider-gateway-<ID>` ,并设置 `Restart=always` 。临时部署可以直接使用 `docker run -d --restart unless-stopped` ,但仍要保证容器名、env 文件、日志目录和镜像 tag 都带上节点 ID,便于 frontend、Docker 状态和本地排障互相对应。`provider.upgrade` 是长期接入节点的必备能力,provider-gateway 不提供 `PROVIDER_UPGRADE_ENABLED` 或等价禁用开关;如果节点缺少升级环境变量,必须修正节点部署,而不是在服务端接受只能预检不能升级的半成品状态。
长期运行推荐用 systemd 管理 provider-gateway 容器,而不是只在交互 shell 中运行 Bun 进程。systemd unit 的稳定形态是:`ExecStartPre=-docker rm -f unidesk-provider-gateway-<ID>` 清理同名旧容器,`ExecStart=docker run --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 unless-stopped` ,但仍要保证容器名、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 层面的常驻守护。
@@ -36,6 +42,8 @@ provider-gateway 部署是否成功必须以 UniDesk frontend 中可见的 Provi
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` 的状态都只能算未完成部署。
自动化验证必须使用 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
@@ -74,14 +82,14 @@ backend-core 可以通过真实 WebSocket 调度向在线 provider 下发 `provi
如果节点已有专用 Compose,优先用节点本地 Compose 手动重建一次:`docker compose --env-file .state/provider-<ID>.env -f <compose-file> -p <compose-project> up -d --no-deps --build provider-gateway` 。老版 `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> .` ,再以固定容器名重建:挂载 `/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 在线;随后在任意装有本仓库和主 server SSH key 的计算节点上执行 `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` ,确认节点重连和 指标恢复。需要 Host SSH / WSL SSH 的节点,还必须执行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh <PROVIDER_ID> hostname` 验证维护桥。
手动升级完成后的判定标准固定为主 server 可观测结果,而不是节点容器 `running` :访问公网 frontend `http://74.48.78.17:18081/` ,确认该 Provider 在线;随后在任意装有本仓库和主 server SSH key 的计算节点上执行 `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` , 验证维护桥没有在升级后丢失 。
## Host SSH Maintenance Bridge
宿主 SSH / WSL SSH 转发只作为应急维护辅助路径,不用于自动计算任务调度。实现参考 `../web-terminal` 的经验:容器内使用只读挂载的私钥,主动连接宿主或 WSL sshd,并设置 `BatchMode=yes` 、`StrictHostKeyChecking=accept-new` 、`ServerAliveInterval=20` 和 `ServerAliveCountMax=3` 。主 server Compose 会把 `config.json` 的 `sshForwarding.keyDir` 只读挂载为 `/run/host-ssh` , provider 标签会上报 `hostSshConfigured` 、`hostSshKeyPresent` 和 `hostSshTarget` ,便于在前端节点清单确认维护桥是否具备条件。
宿主 SSH / WSL SSH 转发是 provider-gateway 部署的必备维护能力,但 只作为应急维护辅助路径,不用于自动计算任务调度。实现参考 `../web-terminal` 的经验:容器内使用只读挂载的私钥,主动连接宿主或 WSL sshd,并设置 `BatchMode=yes` 、`StrictHostKeyChecking=accept-new` 、`ServerAliveInterval=20` 和 `ServerAliveCountMax=3` 。主 server Compose 会把 `config.json` 的 `sshForwarding.keyDir` 只读挂载为 `/run/host-ssh` , provider 标签会上报 `hostSshConfigured` 、`hostSshKeyPresent` 和 `hostSshTarget` ,便于在前端节点清单确认维护桥是否具备条件。
维护桥通过真实 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 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 的远端命令体验。如果 D518 这类 WSL 节点没有公网 SSH 入口,也必须通过这个 provider-gateway 自连维护桥完成验证,而不是要求主 server 直接连节点公网 22 端口;旧版 provider 未声明 `host.ssh` 时必须先升级 provider-gateway,否则 core 会拒绝 SSH 透传。
验证 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 dispatch <PROVIDER_ID> host.ssh --wait-ms 15000` 和 `bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh <PROVIDER_ID> hostname` 。 如果 D518 这类 WSL 节点没有公网 SSH 入口,也必须通过这个 provider-gateway 自连维护桥完成验证,而不是要求主 server 直接连节点公网 22 端口;旧版 provider 未声明 `host.ssh` 时必须先升级 provider-gateway,否则 core 会拒绝 SSH 透传。