Ver código fonte

补充 Docker Web 的 Cursor 桌面可选部署配置

Samien 1 mês atrás
pai
commit
2bb5ec9aa7

+ 2 - 0
README.md

@@ -72,6 +72,8 @@ docker compose --env-file docker.env up -d --build
 
 宿主机连接、局域网访问、验证及升级步骤见 [Docker 部署说明](docs/docker-install.md)。
 
+需要随 Docker Web 部署电脑环境,可使用 `compose.desktops.yaml`(两台标准桌面)或 `compose.desktops.cursor.yaml`(一台 Cursor Universal 开发桌面)。Cursor 方案基于 `public.ecr.aws/k0i0n2g5/cursorenvironments/universal:sand-box-latest` 加入夭夭桌面桥接层,配置与启动命令见 [Cursor 桌面部署](docs/docker-install.md#cursor-universal-可选配置)。
+
 ## iOS
 
 iOS 统一连接 Web 地址,通过设置中的手机登录二维码或账号密码登录。不能再直接填写 9119 地址;旧 15300 Web 账号和本机聊天缓存继续保留。

+ 81 - 0
compose.desktops.cursor.yaml

@@ -0,0 +1,81 @@
+# Generated from compose.yaml and deploy/compose-desktops.cursor.json.
+name: hermes-yaoyao
+
+services:
+  web:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: "${HERMES_YAOYAO_IMAGE:-yaoyao:local}"
+    restart: unless-stopped
+    init: true
+    read_only: true
+    security_opt:
+      - no-new-privileges:true
+    cap_drop:
+      - ALL
+    ports:
+      - "${HERMES_YAOYAO_BIND_ADDRESS:-127.0.0.1}:${HERMES_YAOYAO_PUBLISHED_PORT:-15300}:15300"
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    environment:
+      NODE_ENV: production
+      HERMES_YAOYAO_HOST: 0.0.0.0
+      HERMES_YAOYAO_PORT: 15300
+      HERMES_YAOYAO_UPSTREAM: "${HERMES_YAOYAO_UPSTREAM:-http://host.docker.internal:9119}"
+      # Permit localhost access when the published host port differs from the
+      # container's fixed 15300 port; configured public host names remain opt-in.
+      HERMES_YAOYAO_ALLOWED_HOSTS: "localhost,127.0.0.1,${HERMES_YAOYAO_ALLOWED_HOSTS:-}"
+      HERMES_YAOYAO_ALLOW_INSECURE_LAN: "1"
+      HERMES_YAOYAO_SUPERVISE_DASHBOARD: "0"
+      HERMES_YAOYAO_LOCAL_VM_HOST: "runner"
+      HERMES_YAOYAO_COMPOSE_DESKTOPS: "[{\"id\":\"647fbb98-0c40-4d96-91e0-3290eb9020eb\",\"name\":\"Cursor 开发桌面\",\"socketPath\":\"/run/yaoyao-desktops/desktop-cursor/desktop.sock\",\"imageKey\":\"cursor\"}]"
+      HERMES_YAOYAO_CHAT_CACHE_MODE: "${HERMES_YAOYAO_CHAT_CACHE_MODE:-prefer-local}"
+      HERMES_YAOYAO_RELEASE_SOURCE: "${HERMES_YAOYAO_RELEASE_SOURCE:-https://github.com/Lsamien/yaoyao.git}"
+      HERMES_YAOYAO_HOME: /home/node/.yaoyao
+      HERMES_YAOYAO_APNS_KEY_FILE: "${HERMES_YAOYAO_APNS_KEY_FILE:-}"
+      HERMES_YAOYAO_APNS_KEY_ID: "${HERMES_YAOYAO_APNS_KEY_ID:-}"
+      HERMES_YAOYAO_APNS_TEAM_ID: "${HERMES_YAOYAO_APNS_TEAM_ID:-}"
+      HERMES_YAOYAO_APNS_TOPIC: "${HERMES_YAOYAO_APNS_TOPIC:-}"
+    volumes:
+      - yaoyao-data:/home/node/.yaoyao
+      - desktop-cursor-ipc:/run/yaoyao-desktops/desktop-cursor:ro
+    tmpfs:
+      - /tmp:mode=1777
+
+  desktop-cursor:
+    image: "${YAOYAO_CURSOR_DESKTOP_IMAGE:-yaoyao-desktop:cursor}"
+    platform: linux/amd64
+    build:
+      context: ./deploy/computer
+      dockerfile: Dockerfile.cursor
+    hostname: desktop-cursor
+    extra_hosts:
+      - "desktop-cursor:127.0.0.1"
+    init: true
+    restart: unless-stopped
+    network_mode: none
+    shm_size: 512m
+    mem_limit: 4g
+    cpus: 2
+    pids_limit: 512
+    security_opt:
+      - no-new-privileges:true
+    cap_drop:
+      - ALL
+    cap_add:
+      - SETUID
+      - SETGID
+      - CHOWN
+      - KILL
+    environment:
+      YAOYAO_COMPOSE_DESKTOP_ID: "647fbb98-0c40-4d96-91e0-3290eb9020eb"
+    volumes:
+      - desktop-cursor-workspace:/home/cua/workspace
+      - desktop-cursor-ipc:/run/yaoyao-private/bridge
+
+volumes:
+  yaoyao-data:
+
+  desktop-cursor-workspace:
+  desktop-cursor-ipc:

+ 3 - 0
deploy/compose-desktops.cursor.json

@@ -0,0 +1,3 @@
+[
+  {"id":"647fbb98-0c40-4d96-91e0-3290eb9020eb","service":"desktop-cursor","name":"Cursor 开发桌面","imageKey":"cursor"}
+]

+ 4 - 0
docker.env.example

@@ -6,6 +6,10 @@ HERMES_YAOYAO_PUBLISHED_PORT=15300
 # 固定版本:samienluo/yaoyao:v0.4.11;仅 AMD64:samienluo/yaoyao:v0.4.11-amd。
 # 留空时使用本地构建名称 yaoyao:local。
 # YAOYAO_DESKTOP_IMAGE=hermes-yaoyao-desktop:0.4.0
+# Cursor Universal 兼容桌面的本地构建名称;基础镜像在 deploy/computer/Dockerfile.cursor 中固定。
+# 使用 compose.desktops.cursor.yaml 部署 Web + 一台 Cursor 桌面(linux/amd64)。
+# 不要填写上游原始镜像地址,此变量指向已加入夭夭桥接层的运行镜像。
+# YAOYAO_CURSOR_DESKTOP_IMAGE=yaoyao-desktop:cursor
 
 # 部署步骤见 docs/docker-install.md;使用 docker compose --env-file docker.env 启动。
 # 填写容器可访问的 Hermes Dashboard/Gateway 地址。

+ 66 - 1
docs/docker-install.md

@@ -105,6 +105,71 @@ docker compose --env-file docker.env -f compose.desktops.yaml up -d --build
 
 生成器会把 Web 连接清单、桌面服务和数据卷同步写入同一个 Compose 文件。运行中不能通过 Web API 扩容。不要用 `down --volumes` 更新部署,否则会删除工作文件。
 
+### Cursor Universal 可选配置
+
+新部署可直接选择仓库中的 **`compose.desktops.cursor.yaml`**,同时启动 Web 和一台“Cursor 开发桌面”。这是完整部署文件,单独使用;与 `compose.yaml`、`compose.desktops.yaml` 三选一,不要叠加启动。电脑环境是共享宿主内核的 Linux 桌面容器。
+
+| 项目 | 配置 |
+| --- | --- |
+| 上游基础镜像 | `public.ecr.aws/k0i0n2g5/cursorenvironments/universal:sand-box-latest` |
+| 构建固定版本 | `sha256:858b6df5d0aeffd5db0e20c81553e98f84b07e9c700a13fcd1febac0e8c54830` |
+| 兼容镜像 | `yaoyao-desktop:cursor`,通过 `deploy/computer/Dockerfile.cursor` 在本地构建 |
+| 架构 | `linux/amd64`;ARM64 宿主需要 Docker 的 amd64 模拟支持 |
+| 桌面资源 | 2 CPU、4 GB 内存、512 MB 共享内存、512 个进程 |
+| 持久工作目录 | `desktop-cursor-workspace` 卷 → `/home/cua/workspace`,含浏览器资料 |
+| Web 连接 | `desktop-cursor-ipc` 卷中的私有 Unix socket,Web 只读挂载 |
+| 桌面网络 | 默认 `network_mode: none`,不发布任何桌面端口 |
+
+上游标签会变化,Dockerfile 固定已适配的 digest,加入 CUA 驱动、浏览器启动器和夭夭私有桥接协议。因此不能把上游原始镜像直接填到 `YAOYAO_CURSOR_DESKTOP_IMAGE`;该变量只用于指定构建后的兼容镜像名称。
+
+首次配置(已有 `docker.env` 时直接编辑):
+
+```sh
+cp -n docker.env.example docker.env
+```
+
+`docker.env` 的相关配置示例:
+
+```dotenv
+HERMES_YAOYAO_IMAGE=yaoyao:local
+HERMES_YAOYAO_UPSTREAM=http://host.docker.internal:9119
+HERMES_YAOYAO_BIND_ADDRESS=127.0.0.1
+HERMES_YAOYAO_PUBLISHED_PORT=15300
+YAOYAO_CURSOR_DESKTOP_IMAGE=yaoyao-desktop:cursor
+```
+
+使用本次源码构建 Web 与桌面镜像,然后启动:
+
+```sh
+docker compose --env-file docker.env -f compose.desktops.cursor.yaml config --quiet
+docker compose --env-file docker.env -f compose.desktops.cursor.yaml build web desktop-cursor
+docker compose --env-file docker.env -f compose.desktops.cursor.yaml up -d --no-build
+docker compose --env-file docker.env -f compose.desktops.cursor.yaml ps
+curl --fail http://127.0.0.1:15300/healthz
+```
+
+如果使用已包含 Cursor 桌面支持的预构建 Web 镜像,将 `HERMES_YAOYAO_IMAGE` 设为相应版本,先 `pull web`,再单独 `build desktop-cursor`,最后执行上述 `up -d --no-build`。兼容桌面镜像需自行构建,本文不假定它已发布到远程仓库。
+
+随后按下方“[Hermes 执行连接](#hermes-执行连接)”注册或连接执行节点,再在 Agent 聊天的 **电脑 → 此 Agent 使用的电脑** 中选择“Cursor 开发桌面”。
+
+需要浏览外网或下载依赖时,创建 `compose.desktops.network.yaml`:
+
+```yaml
+services:
+  desktop-cursor:
+    network_mode: bridge
+```
+
+之后启动、查看和停止均带上两个文件:
+
+```sh
+docker compose --env-file docker.env -f compose.desktops.cursor.yaml -f compose.desktops.network.yaml up -d --no-build
+```
+
+`bridge` 允许桌面通过宿主 Docker 网络出站,也可能访问宿主及局域网;它不提供本机动态桌面的受控外网代理策略。仅在部署网络允许时启用,并通过宿主防火墙管理访问范围。桌面仍不映射 VNC 或控制端口。网络模式语义见 [Docker Compose 服务配置](https://docs.docker.com/reference/compose-file/services/#network_mode)。
+
+调整这套配置的桌面数量或名称时,编辑 `deploy/compose-desktops.cursor.json`,然后运行 `node scripts/generate-desktop-compose.mjs`;生成器同时更新两套部署文件,`--check` 同时检查它们。已有标准桌面的部署若只想切换一台镜像,应按下一节保留原有 ID、service 和工作卷。
+
 ### 同时部署两种镜像
 
 在 `deploy/compose-desktops.json` 的每个桌面条目增加 `imageKey`:`standard` 为现有标准桌面(不填写时的默认值),`cursor` 为 Cursor Universal 兼容桌面。例如保留已有 ID 和 service,将第二台配置为:
@@ -132,7 +197,7 @@ node yaoyao-runner/runner.mjs --config /完整路径/runner.json
 
 整份程序目录须保留。节点配置包含私有凭据,不要提交到仓库。运行中的任务或接管操作仍有独占授权;交还后其他成员才能操作。取消无法确认结束的命令时,会重置对应桌面服务来终止旧程序,数量与数据卷保持不变。
 
-只需要 Web 服务、继续使用既有外部动态桌面的部署仍可使用 `compose.yaml`;两种部署文件任选其一,不要让两套 Web 同时写同一数据卷。
+只需要 Web 服务、继续使用既有外部动态桌面的部署仍可使用 `compose.yaml`;三种部署文件任选其一,不要让两套 Web 同时写同一数据卷。
 
 验收截图:[Docker Web 的 Agent 桌面](screenshots/compose-desktops/agent-desktop.png)。验收使用独立数据和测试模型服务,桌面与容器连接是真实运行。
 

+ 12 - 8
scripts/generate-desktop-compose.mjs

@@ -6,7 +6,7 @@ const recipes={
   standard:{image:'${YAOYAO_DESKTOP_IMAGE:-hermes-yaoyao-desktop:0.4.0}',dockerfile:'Dockerfile'},
   cursor:{image:'${YAOYAO_CURSOR_DESKTOP_IMAGE:-yaoyao-desktop:cursor}',dockerfile:'Dockerfile.cursor',platform:'linux/amd64'},
 }
-export function generateDesktopCompose(base,entries){
+export function generateDesktopCompose(base,entries,source='deploy/compose-desktops.json'){
   if(!Array.isArray(entries)||entries.length<1||entries.length>32)throw new Error('Compose 桌面数量须为 1–32')
   for(const d of entries){
     if(!d||!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(d.id)||!/^desktop-[a-z0-9-]+$/.test(d.service)||typeof d.name!=='string'||!d.name.trim())throw new Error('Compose 桌面定义无效')
@@ -19,7 +19,7 @@ export function generateDesktopCompose(base,entries){
     .replace('      - yaoyao-data:/home/node/.yaoyao','      - yaoyao-data:/home/node/.yaoyao\n'+entries.map(d=>`      - ${d.service}-ipc:/run/yaoyao-desktops/${d.service}:ro`).join('\n'))
   const volumesAt=base.indexOf('\nvolumes:')
   if(volumesAt<0||!base.includes('HERMES_YAOYAO_COMPOSE_DESKTOPS'))throw new Error('compose.yaml 模板结构已变化')
-  let yaml='# Generated from compose.yaml and deploy/compose-desktops.json.\n'+base.slice(0,volumesAt)+'\n'
+  let yaml=`# Generated from compose.yaml and ${source}.\n`+base.slice(0,volumesAt)+'\n'
   for(const d of entries){
     const recipe=recipes[d.imageKey??'standard']
     yaml+=`  ${d.service}:
@@ -58,10 +58,14 @@ ${recipe.platform?`    platform: ${recipe.platform}\n`:''}    build:
   return yaml
 }
 if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){
-  const entries=JSON.parse(readFileSync(resolve(root,'deploy/compose-desktops.json'),'utf8'))
-  const yaml=generateDesktopCompose(readFileSync(resolve(root,'compose.yaml'),'utf8'),entries)
-  const output=resolve(root,'compose.desktops.yaml')
-  if(process.argv.includes('--check')){if(readFileSync(output,'utf8')!==yaml)throw new Error('compose.desktops.yaml 与桌面定义不同步,请运行 node scripts/generate-desktop-compose.mjs')}
-  else writeFileSync(output,yaml)
-  console.log(`Compose 共享桌面:${entries.length} 台`)
+  const base=readFileSync(resolve(root,'compose.yaml'),'utf8')
+  for(const suffix of ['', '.cursor']){
+    const source=`deploy/compose-desktops${suffix}.json`
+    const entries=JSON.parse(readFileSync(resolve(root,source),'utf8'))
+    const yaml=generateDesktopCompose(base,entries,source)
+    const name=`compose.desktops${suffix}.yaml`,output=resolve(root,name)
+    if(process.argv.includes('--check')){if(readFileSync(output,'utf8')!==yaml)throw new Error(`${name} 与桌面定义不同步,请运行 node scripts/generate-desktop-compose.mjs`)}
+    else writeFileSync(output,yaml)
+    console.log(`${name}:${entries.length} 台共享桌面`)
+  }
 }