spike(dify): WU3 形态A 真建成跑通——game 专属 dify 实例 + workflow 导入发布返 202
原计划建进共享 muse-dify 遇两阻塞:①muse 锁单工作区+create-tenant 抛错 ②HTTP 节点经 ssrf_proxy 打内网 worker 被 deny to_private_networks 拦(muse→:9501 实测403)。 创始人拍板给 game 单起隔离实例:独立 compose(game-dify)+错开端口(28080/28443/25003) +独立卷+新 SECRET_KEY,自有 ssrf 模板放行 gen worker,muse 零受扰(仍403)。 导入 DSL(0.1.5→0.6.0迁移完好)+发布+真跑:HTTP节点succeeded/worker返202/ worker日志'受理(202) userToken=<none>全局key回落'。凭据档回写 game-dify 端点+登录+ headless token 签发法(RSA/CSRF 绕过)。spike README/DSL 更新为已验证态。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87b0c085b7
commit
b9d73ae3c7
@ -154,4 +154,23 @@ canonical: true
|
||||
|
||||
---
|
||||
|
||||
## game 专属 dify 实例(mini-infra `100.64.0.8` · Docker · 2026-07-08 落档 · WU3 形态A)
|
||||
|
||||
mini-infra 上那套 dify 是 muse 与 game 两个项目共用的。muse 的实例(`muse-dify-*`、宿主端口 18080/18443/15003、owner `muse-admin@local.muse`)被配成单工作区、不许注册与建工作区,不能塞 game 的东西进去。所以给 game **单起了一套隔离 dify**——独立 compose 项目 `game-dify`、独立卷、错开宿主端口,与 muse 零共享数据,muse 侧全程未受扰。WU3「游戏开发节点」形态 A 就在这套里跑通:dify workflow → HTTP 节点 → 它自己的 ssrf_proxy(已放行生成 worker)→ cheap-worker `:9501/generate` 受理 202。
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| **Console / API** | `http://100.64.0.8:28080`(http;ssl `28443`、plugin-debug `25003`) |
|
||||
| **owner 登录** | `game-admin@local.game` / `GameDifyMuse2026` |
|
||||
| `INIT_PASSWORD`(setup 前置闸,随 muse .env 克隆来) | `37e442085c5d74ba5cbf93fe` |
|
||||
| compose 部署位 | `mini-infra:/root/dify-game/docker`(`COMPOSE_PROJECT_NAME=game-dify`;起停 `cd /root/dify-game/docker && docker compose -p game-dify up -d / down`) |
|
||||
| workflow 应用 | 「游戏开发节点-形态A」`app_id=5ba490f2-e0be-4c84-98a4-8ab5873a17c4`(已发布;跑一次 brief→worker 返 202) |
|
||||
| 与生成栈关系 | a-min 形态不带 userToken,worker 回落进程级全局 `NEWAPI_KEY`,与真实创作者 per-user 计费天然隔离 |
|
||||
|
||||
- **与 muse 隔离**:容器全 `game-dify-` 前缀,内部端口(api 5001/db 5432/redis 6379)不暴露宿主、由 compose 网络命名空间隔离,只错开了三个 EXPOSE 宿主端口。`SECRET_KEY` 已换新(不与 muse 共签)。ssrf_proxy 只在 game 自己这套的 squid 模板里放行了生成 worker(`acl gen_worker dst 100.64.0.7`,在 `deny to_private_networks` 前),muse 的 ssrf 防护一字未动(实测 muse→:9501 仍 403、game→:9501 已 404 通)。
|
||||
- **console token 签发法(headless)**:这套 dify 登录要 RSA 加密密码、且所有 console 请求走 CSRF double-submit(header `X-CSRF-Token` 须等于 cookie `csrf_token`、且是 `PassportService` 签的合法 JWT、`sub`=登录用户)。脚本里绕开浏览器取 token 的办法:`docker exec -i game-dify-api-1 flask shell` 里 `AccountService.login(account=<game-admin>, session=db.session, ip_address=...)` 拿 `tp.access_token` 与 `tp.csrf_token`,请求带 `Authorization: Bearer <access>` + `X-CSRF-Token: <csrf>` + `Cookie: csrf_token=<csrf>` 即过。
|
||||
- **搭建/操作留痕**:`spikes/dify-game-dev-node/`(配方 + 已验证可导入的 DSL)。
|
||||
|
||||
---
|
||||
|
||||
> 其余基建密钥(Gitea 等)按需补入本档,**别散落 env var 或只留在对话**。决策同理——拍板进对应 plan / 本档,不留口头。
|
||||
|
||||
@ -1,55 +1,28 @@
|
||||
# dify 游戏开发节点(形态 A / a-min)—— 搭建配方与可复现留痕
|
||||
# dify 游戏开发节点(形态 A / a-min)—— 已建成并跑通
|
||||
|
||||
本目录是内测 WU3「dify workflow 驱动 agentscope 生成」形态 A 的落地留痕。设计与边界见 [`docs/agent-specs/2026-07-07-内测-WU3-dify游戏开发节点-设计.md`](../../docs/agent-specs/2026-07-07-内测-WU3-dify游戏开发节点-设计.md);这里只放「照着做就能搭起来」的配方、一份可导入的 workflow 定义骨架,以及搭建时踩到的一个真配置阻塞。
|
||||
内测 WU3「dify workflow 驱动 agentscope 生成」形态 A 的落地留痕。设计与边界见 [`docs/agent-specs/2026-07-07-内测-WU3-dify游戏开发节点-设计.md`](../../docs/agent-specs/2026-07-07-内测-WU3-dify游戏开发节点-设计.md);实例端点与登录见 canonical [`docs/内网凭据与端点.md`](../../docs/内网凭据与端点.md)「game 专属 dify 实例」段。这里放怎么搭起来的、HTTP 节点怎么配、以及搭建时踩到的真配置阻塞与解法。
|
||||
|
||||
## 一句话
|
||||
## 一句话与现状
|
||||
|
||||
在 mini-infra 那套已部署的 dify(`:18080`)里搭一条最小 workflow:开始节点收一句话创意 `brief` → 一个 HTTP Request「游戏开发节点」把它组成后端早已定义的 §6.1 job、POST 给便宜档 cheap-worker `:9501/generate` → 结束节点回显那次投递的响应。worker 入队即返 202,agentscope 在后台真跑一局,产物落在 worker 的 `game_dir`。后端一行不改。
|
||||
在 game 专属的 dify 里搭一条最小 workflow:开始节点收一句话创意 `brief` → 一个 HTTP Request「游戏开发节点」把它组成后端早已定义的 §6.1 job、POST 给便宜档 cheap-worker `:9501/generate` → 结束节点回显那次投递的响应。worker 入队即返 202,agentscope 在后台真跑一局。后端一行不改。
|
||||
|
||||
## 现状:能力已证,但有一个必须先解的真阻塞
|
||||
**2026-07-08 已建成并端到端跑通(LIVE)**:workflow 应用「游戏开发节点-形态A」建在 game 专属 dify(`http://100.64.0.8:28080`,app_id `5ba490f2-e0be-4c84-98a4-8ab5873a17c4`),发布后真跑一次——HTTP 节点 `succeeded`、worker 返 **202**、workflow `succeeded`,出参 `accepted:true / job_id dify-… / queued:1`;worker 侧日志同刻 `收到 job … → 受理(202)`、`凭据来源=全局 env key(回落) userToken=<none>`。本目录的 `workflow-游戏开发节点-formA.yml` 就是这次真导入进去的定义,已验证可导入可跑(dify 导入时从 DSL 版 0.1.5 迁到现版 0.6.0,三节点与 HTTP 节点的 URL/body/超时全部完好)。
|
||||
|
||||
**能力这一层是通的。** 从 dify 宿主 mini-infra(`100.64.0.8`)直接 `curl --noproxy` 打一份最小 a-min job 到 mini-desktop(`100.64.0.7`)的 `:9501/generate`,worker 收下返 202、agentscope 真跑、worker 收口日志报 `status=succeeded`——这条端到端此前已实测走通。job 契约、生成核心、落盘全是现货,dify 只是又一个「组 job 的调用方」。
|
||||
## 为什么是「专属实例」——搭建时揭出的两个真阻塞
|
||||
|
||||
**但 dify 的 HTTP Request 节点不是直连,它经 dify 自带的 SSRF 代理出网,而那个代理默认把内网 worker 拦死。** dify 部署自带一个 squid 反 SSRF 代理(容器 `muse-dify-ssrf_proxy-1`,`ssrf_proxy:3128`),所有 HTTP 节点的出站请求都从它走。它的访问规则里有这么一条,且排在放行规则之前:
|
||||
原计划想把 game 的 workflow 建进 mini-infra 上那套现成 dify,搭的过程揭出两条只有真配置核查才暴露的阻塞,最终导致改走「给 game 单起一套」:
|
||||
|
||||
```
|
||||
http_access deny to_private_networks # to_private_networks 含 100.64.0.0/10(Tailscale CGN 段)
|
||||
http_access allow allowed_domains # 仅 .marketplace.dify.ai
|
||||
http_access allow client_localnet
|
||||
...
|
||||
```
|
||||
其一,**那套 dify 是 muse 与 game 两个项目共用的,且被 muse 锁成单工作区**(`is_allow_register:false`、`is_allow_create_workspace:false`),`flask create-tenant` 在这套部署里还会在注册流程里抛错。要给 game 干净地开一个隔离工作区,绕不开动 muse 共用的实例。
|
||||
|
||||
squid 首个匹配即生效,`100.64.0.7` 落在 `100.64.0.0/10` 里,先撞上 `deny to_private_networks` 就被拒,后面的放行规则根本到不了。实测坐实:从 dify-api 容器经 `ssrf_proxy:3128` 打 `100.64.0.7:9501` 返 **403**,而同样经它打允许域 `marketplace.dify.ai` 返 200。所以**直接在 dify UI 里配好 HTTP 节点点运行,会得到 403、连不上 worker**——这不是节点配错,是 dify 的 SSRF 防护按设计拦掉了所有私网目标(Tailscale 的 `100.64/10` 也算私网)。设计 §3.2/§3.5 预警过「HTTP 节点经代理出网需确认目标 IP 放行」,这里把它坐实了。
|
||||
其二,**dify 的 HTTP Request 节点经它自带的 SSRF 代理出网,而那个代理默认把内网 worker 拦死**。dify 部署自带一个 squid 反 SSRF 代理(`*-ssrf_proxy` 容器),所有 HTTP 节点出站都从它走,规则里 `http_access deny to_private_networks` 排在放行之前、且含 `100.64.0.0/10`(Tailscale CGN 段)。实测:经 muse 的 ssrf_proxy 打 `100.64.0.7:9501` 返 **403**。所以直接在共享实例的 UI 里配好节点点运行会得到 403,不是节点配错,是 SSRF 防护按设计拦掉了所有私网目标。
|
||||
|
||||
## 两个只有创始人能拍/能给的门
|
||||
创始人据此拍板:**给 game 单起一套专属 dify 实例**,天生隔离、零 muse 风险、我们全控。这套 game 实例自己的 ssrf_proxy 模板在开机前就放行了生成 worker(`acl gen_worker dst 100.64.0.7` 插在 `deny to_private_networks` 前),实测 game→:9501 已 404 通、muse→:9501 仍 403(muse 一字未动)。
|
||||
|
||||
1. **放行 ssrf_proxy 到 worker 端点(共享基建 + 安全姿态变更,须创始人拍)。** 最小侵入的修法是在 squid 的 `deny to_private_networks` 之前插一条只放行这一个 host:port 的例外,而不是关掉整个 SSRF 防护:
|
||||
## HTTP「游戏开发节点」配置(手搭/核对照填)
|
||||
|
||||
```
|
||||
# 在 muse-dify-ssrf_proxy 的 squid.conf 里,deny to_private_networks 之前加:
|
||||
acl gen_worker dst 100.64.0.7
|
||||
http_access allow gen_worker
|
||||
```
|
||||
|
||||
然后 `docker exec muse-dify-ssrf_proxy-1 squid -k reconfigure`(或重启该容器)。这只对生成 worker 那一个内网地址开一道窄缝,其余私网仍全拦,回滚就是删掉这两行再 reconfigure。但它毕竟是在改一套别人今天刚建起来、且承担 SSRF 防护职责的共享 dify 实例,又是把一个零鉴权、会真花 new-api 钱的内网入口对 dify 放开(设计 §3.5 已指出 `:9501` 绑 `0.0.0.0`、无鉴权、成本型 DoS 面靠主机网络隔离兜底),所以属安全相关决定,交创始人拍,不擅动。
|
||||
|
||||
2. **dify console 登录(凭据档里没有,须创始人给)。** dify 已 setup 完成(owner 建于 2026-07-07),但 [`docs/内网凭据与端点.md`](../../docs/内网凭据与端点.md) 里没有 dify 的 console 账号密码。没有它无法登进 dify 控制台去搭/导入 workflow,也无法用 console API 导入下面这份 DSL。setup 已完成故不能再走 `/console/api/setup` 重建 owner。拿到登录后,导入或手搭都是几分钟的活。
|
||||
|
||||
## 搭建步骤(拿到上面两个门之后)
|
||||
|
||||
**第一步 · worker 就位(已满足)。** dev 生成机 mini-desktop 上 cheap-worker(`:9501`)与 cheap Service(`:8300`)在跑,`:9501` 从 mini-infra TCP 可达、`GET /generate` 返 404(POST-only,证明在跑)。a-min 形态不带 `userToken`、不走回调,driver 用进程级全局 `NEWAPI_KEY`——dev 单一生成栈天然就是 dify 专用栈,与真实 create 路的 per-user 计费(WU2)在同栈上互斥,内测按「dev 单栈天然独立」成立(设计 §3.4)。
|
||||
|
||||
**第二步 · 放行 ssrf_proxy(门①)。** 见上。验证 = 从 dify-api 容器经 `ssrf_proxy:3128` 打 `:9501/generate` 不再 403。
|
||||
|
||||
**第三步 · dify 里建 workflow(门②)。** 登进 dify 控制台,导入 `workflow-游戏开发节点-formA.yml`(或按下节手搭三节点)。
|
||||
|
||||
**第四步 · 手动跑一次验收。** 在 dify 里给 `brief` 填一句创意点运行。成功判据 = HTTP 节点拿到 **202**(不是等生成完成——生成结果不从这个响应回来),随后到 mini-desktop 看 worker 收口日志出现 `status=succeeded`、`games/amgen-<gameId>/` 落定 `src/` 产物。注意别把「`ls src/` 非空」当成功证据:scaffold 会在真生成前先克隆 `_template/src`(7 文件),一次 scaffold 成功但生成失败的 run 同样 src/ 非空。
|
||||
|
||||
## HTTP「游戏开发节点」配置(手搭时照填)
|
||||
|
||||
- **方法 / URL**:`POST http://100.64.0.7:9501/generate`(dev worker 在 mini-desktop;若 worker 迁机改这个 IP)。
|
||||
- **超时**:握手级,约 **10 秒**。这是异步投递面,worker 入队立即返 202,真生成在后台跑几十秒到几分钟。**绝不能**把超时设成等生成完成的长值——结果经落盘/回调回流,不从这个 HTTP 响应回来。
|
||||
- **Body(application/json)**:一份 §6.1 job,a-min 形态省略 `callback`。便宜档生成核心真正只读 `brief` 与 `gameId`,其余字段给合理默认即可,缺了 worker 也不会失败:
|
||||
- **方法 / URL**:`POST http://100.64.0.7:9501/generate`(dev worker 在 mini-desktop;worker 迁机改这个 IP,并同步 game ssrf 模板的 `gen_worker` 放行 IP)。
|
||||
- **超时**:握手级,约 **10 秒**。这是异步投递面,worker 入队立即返 202,真生成在后台跑几十秒到几分钟。**绝不能**设成等生成完成的长超时——结果经落盘/回调回流,不从这个 HTTP 响应回来。
|
||||
- **Body(application/json)**:一份 §6.1 job,a-min 省略 `callback`。便宜档生成核心真正只读 `brief` 与 `gameId`,其余给合理默认即可:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -68,12 +41,14 @@ squid 首个匹配即生效,`100.64.0.7` 落在 `100.64.0.0/10` 里,先撞上 `d
|
||||
```
|
||||
|
||||
几条写死的红线(读码坐实,别踩):
|
||||
- **`traceId` 必带 `dify-` 前缀**做命名空间隔离,且令 `job_id`=`traceId`=`idempotency_key`=同一个 `dify-<workflow_run_id>`。worker 按 `job_id`(缺则 `traceId`)在进程级 `_seen` 去重:同键重投直接返 202、不重入、不重复烧 token——所以 dify 侧对同一次运行配 HTTP 重试是**安全**的(重试复用同 `workflow_run_id`)。反过来,一次**真正的重新生成必须是一次新的 dify 运行**(新 `workflow_run_id` = 新 job_id);别在节点里每次重试新造 uuid(那才会重复生成),也别以为同 job_id 再 POST 能拿到新产物(会被静默去重照返 202)。`_seen` 是进程级、永不淘汰、worker 重启即清空。
|
||||
- **`budget.maxYuan` 是死字段,不承载封顶**,别据它以为「单局最多 ¥0.15」。单局真实成本上界由服务端 `cheap_budget` 固定的 ¥10 软停 / ¥15 硬地板决定(调用方 override 被直接 pop 掉)。dify 路真正的额度兜底只有一条:给这个 dify 专用 new-api token 在 new-api 侧配用量配额上限(按 ¥15/局估,配 ¥150 日额约 10 局/日触顶)。
|
||||
- **不要试图从 dify 节点透传 token 做 per-user 计费**——driver 的 key 只从进程级 env `NEWAPI_KEY` 取,job 里没有任何 token 位,透传做不到。隔离靠「这台 dev 栈就是 dify 专用栈」,不靠 job 自带凭据。
|
||||
- **`traceId` 必带 `dify-` 前缀**做命名空间隔离,且令 `job_id`=`traceId`=`idempotency_key`=同一个 `dify-<workflow_run_id>`。worker 按 `job_id`(缺则 `traceId`)在进程级 `_seen` 去重:同键重投直接返 202、不重入、不重复烧 token——所以 dify 侧对同一次运行配 HTTP 重试是**安全**的。反过来,一次**真正的重新生成必须是一次新的 dify 运行**(新 `workflow_run_id` = 新 job_id);别在节点里每次重试新造 uuid,也别以为同 job_id 再 POST 能拿到新产物(会被静默去重照返 202)。`_seen` 进程级、永不淘汰、worker 重启即清空。
|
||||
- **`budget.maxYuan` 是死字段,不承载封顶**。单局真实成本上界由服务端 `cheap_budget` 固定的 ¥10 软停 / ¥15 硬地板决定(调用方 override 被 pop 掉)。dify 路真正的额度兜底 = 给 dify 专用 new-api token 在 new-api 侧配用量配额上限。
|
||||
- **不要试图从 dify 节点透传 token 做 per-user 计费**——driver 的 key 只从进程级 env `NEWAPI_KEY` 取,job 里没有 token 位。隔离靠「这台 dev 栈就是 dify 专用栈」,不靠 job 自带凭据(本次实测正是 `userToken=<none>` → 全局 key 回落)。
|
||||
|
||||
- **鉴权**:`/generate` 当前对入站不鉴权,信任边界靠内网不可外达。别为形态 A 单独给 worker 加鉴权(重复造设计已论证不该碰的服务间信任面);风险面(零鉴权 + 会花钱的内网入口)靠 `:9501` 所在主机 Tailscale-only/NAT/防火墙收口。
|
||||
## 复现:导入 + 发布 + 跑
|
||||
|
||||
前提 = game 专属 dify 已起、生成 worker 在 `:9501` 且 game ssrf 已放行(见凭据档「game 专属 dify 实例」段)。这套 dify 登录要 RSA、console 走 CSRF double-submit,headless 取 token 与导入的完整办法见凭据档同段。要点:`AccountService.login` 在 `flask shell` 里签发 `access_token`+`csrf_token` → `POST /console/api/apps/imports {"mode":"yaml-content","yaml_content":<本目录 yml>}` 导入 → `POST /apps/<id>/workflows/publish` 发布 → `POST /apps/<id>/workflows/draft/run {"inputs":{"brief":"…"}}` 真跑,成功判据 = HTTP 节点出参 `status_code=202`、worker 日志 `受理(202)`。
|
||||
|
||||
## 文件
|
||||
|
||||
- `workflow-游戏开发节点-formA.yml` —— dify 1.15.0 workflow 定义骨架(start → http-request → end)。**未经实测导入**(缺 console 登录无法验证),导入后须在 dify UI 内核对节点变量引用与超时,别当成拿来即跑。它是给「拿到 dify 登录后省手搭」的起点,手搭配方以本文上一节为准。
|
||||
- `workflow-游戏开发节点-formA.yml` —— dify workflow 定义,**已验证可导入可跑**(2026-07-08 真导入进 game 实例并跑出 202)。
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
# dify 1.15.0 workflow DSL 骨架 —— 内测「游戏开发节点」形态 A / a-min
|
||||
# 用途:拿到 dify console 登录后,经「导入 DSL」省去手搭三节点的起点。
|
||||
# ⚠️ 未经实测导入(缺 console 登录无法验证)。导入后必须在 dify UI 内核对:
|
||||
# ① HTTP 节点变量引用 {{#start.brief#}} / {{#sys.workflow_run_id#}} 是否解析;
|
||||
# ② 超时设为握手级(约 10s,只等 202/503),绝不设成等生成完成的长超时;
|
||||
# ③ 目标 IP 100.64.0.7 与 dev worker 实际所在机一致;
|
||||
# ④ ssrf_proxy 已放行该内网端点(见 README「门①」),否则节点跑出来是 403。
|
||||
# 手搭配方以 README「HTTP 游戏开发节点配置」一节为准,本文件从属于它。
|
||||
# dify workflow DSL —— 内测「游戏开发节点」形态 A / a-min
|
||||
# ✅ 已验证:2026-07-08 真导入进 game 专属 dify(:28080)并发布跑通,HTTP 节点返 202。
|
||||
# 导入时 dify 从本 DSL 版 0.1.5 迁到现版 0.6.0,三节点与 HTTP 节点 URL/body/超时全部完好。
|
||||
# 前提(见 docs/内网凭据与端点.md「game 专属 dify 实例」段):
|
||||
# ① 目标 IP 100.64.0.7 需与 dev worker 实际所在机一致(迁机同步改此处 + game ssrf 模板 gen_worker 放行);
|
||||
# ② game ssrf_proxy 已放行该内网端点,否则节点跑出来是 403。
|
||||
# 手搭/核对照 README「HTTP 游戏开发节点配置」一节。
|
||||
app:
|
||||
name: 游戏开发节点-形态A
|
||||
description: 内测·把一句话创意组 §6.1 job 直连 cheap-worker :9501/generate,由 agentscope 生成小游戏(a-min:无 userToken、无回调,产物落 worker game_dir)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user