← Back to Plugins
Tools

Wonton Plugin Openclaw

geraldpeng6 By geraldpeng6 👁 16 views ▲ 0 votes

OpenClaw plugin from geraldpeng6/wonton-plugin-openclaw

GitHub

Configuration Example

x-wonton:
  spec: "0.4"
  id: app.example.hello
  version: 0.1.0
  kind: app
  title: { zh: 你好, en: Hello }
  description: { zh: 向登录的人问好的示例网页, en: An example page that greets whoever is signed in }
  permissions: []

README

# 云吞插件模板

云吞(Wonton)的插件就是一个 `docker-compose.yml`,顶层多一个 `x-wonton` 字段,写明插件在云吞里的身份、网页入口和要的
权限。一个插件一个 Git 仓库,仓库根目录就是插件包,发布就是打 tag。主人在 App 里装上插件,核心按清单起容器、接好登录,
插件的网页在 App 里作为小程序打开。

这个仓库是模板:一个能装上的示例插件(nginx 显示一个向主人问好的网页),加上检查、开发环境和发布三个脚本。从这个模板
新建仓库,照着下面把示例换成你的应用。

## 准备

- Docker Engine 28 或更新的版本,带 Compose 插件(`docker compose`)。Linux 上的 Docker Engine、macOS 上的 Docker Desktop 和
  OrbStack 都行。
- `git`、`curl`(7.76 或更新)、`python3`。
- Docker 要在本机:开发环境的数据目录是这个仓库下的路径,网页也开在本机的端口上。在远程主机上开发见后面的
  “在远程主机上开发”。

不用装云吞,也不要 root:脚本用发布的核心镜像(`ghcr.io/geraldpeng6/wonton-core`)检查插件、起开发环境。

## 先跑起来

```bash
scripts/dev.sh up
```

```bash
scripts/dev.sh install
```

`up` 在本机起一套云吞,打印它的地址。示例(实例名取自目录名加 4 位随机数,端口随机)是
`http://template3f2a.wonton.localhost:21234/`。浏览器打开,用 `scripts/dev.sh token` 输出的主人令牌登录。`install` 先列出
插件要的权限,确认后装上,打印插件网页的地址。

示例网页经 `sso: proxy` 拿到用户名,打开就是“你好,<用户名>”。换成真实应用时,把示例的 `description`、`about`、`tags`
换成你的,`web/`、compose 里的两个 `configs` 和 `sso: proxy` 及其 `proxy` 删掉(默认让应用自己登录),`webui.service`、
`port` 换成你的,按下面的规则重写。Chrome、Firefox 和 curl 把 `*.localhost` 直接解析到本机,不用改 hosts。

## 仓库里有什么

| 路径 | 内容 |
| --- | --- |
| `docker-compose.yml` | 插件本身:compose 加上 `x-wonton` 清单 |
| `icon.svg`、`web/` | 清单和 compose 引用的文件:图标、交给容器的配置和网页 |
| `screenshots/` | 商店详情页里的截图 |
| `scripts/check.sh` | 检查插件包,规则和安装时一样 |
| `scripts/dev.sh` | 开发环境:起一套云吞,装、重装、卸载插件,看日志 |
| `scripts/smoke-probes.sh` | `scripts/dev.sh smoke` 对这个插件逐项的探测 |
| `scripts/publish.sh` | 发布一个版本:打 tag、推送;`images/` 里有自己构建的镜像时先构建、推送并按摘要写回(示例没有) |
| `AGENTS.md` | 给人和 AI 编程代理的规则 |
| `LICENSE` | 插件包自己的许可证;从模板新建时换成作者自己的名字和年份 |
| `NOTICE` | 图标等第三方素材的许可和声明;图标是自己画的就写原创和插件许可证,见第 10 步 |
| `.wonton/` | 开发环境的设置、密钥和数据,不提交 |

插件包是 `docker-compose.yml` 加上它引用的文件:`icon`、`settings`、`bot.avatar`、`notices` 和顶层 `configs` 的 `file`。README、
截图、Dockerfile、源码都不在插件包里,核心不取。引用的路径相对仓库根目录,不能是绝对路径、不能含 `..`、不能用变量,
也不能经过软链接;单个文件不超过 1 MiB,整个插件包不超过 2 MiB。安装时插件包以 tar 归档上传,归档里只能有普通文件和
目录,最多 256 项。

## 写插件

按顺序改 `docker-compose.yml`,每改一步运行一次 `scripts/check.sh`,再 `scripts/dev.sh reinstall` 装上看效果。

### 1. 身份

```yaml
x-wonton:
  spec: "0.4"
  id: app.example.hello
  version: 0.1.0
  kind: app
  title: { zh: 你好, en: Hello }
  description: { zh: 向登录的人问好的示例网页, en: An example page that greets whoever is signed in }
  permissions: []
```

- `spec` 是清单按哪一版插件规范写的,要加引号(不然 YAML 读成数字)。用到 v0.4 的 `notices` 就写 `"0.4"`;只用到
  v0.3 的能力(如 `owner.identity`)时写 `"0.3"`;没写时按 `"0.1"`。比核心支持的版本新时装不上,提示先升级核心。
- `id` 是反向域名,全局唯一,装上以后不能改:至少两段,每段由小写字母、数字和单个连字符组成,总长不超过 63。用你自己的
  域名或 GitHub 用户名开头,如 `app.alice.photos`。把 `.` 换成 `-` 就是插件的 slug(`app-alice-photos`),网页地址和
  Docker 里的资源名都用它。
- `version` 是插件包的版本(语义化版本),和应用、镜像的版本无关。
- `kind`:`app`(应用)、`agent`(Agent)、`channel`(第三方 IM 渠道)、`model`(模型服务)。`system` 只给随核心预装的
  官方插件。
- `title`、`description` 至少写 `zh` 和 `en`。
- `permissions` 必填,不要权限时写空列表。
- `requirements` 可选:`arch`(如 `[amd64, arm64]`,和主人的服务器架构不符时装不上)、`memory`(如 `2G`)、
  `gpu`(`required`(默认)或 `optional`,还要申请 `devices.gpu` 并写 `gpu.services`,见第 6 步)。
- 插件的设置做在插件自己的网页里。清单的 `settings`(给 App 渲染原生表单的 JSON Schema)是预留的。
- 清单里有未知字段时装不上:拼错的字段不会被悄悄忽略。

### 2. 服务

`services` 照常写 compose,核心只认下面这些字段,其余的一律报错:

- 服务:`image`、`command`、`entrypoint`、`environment`、`volumes`、`configs`、`restart`(没写时是 `unless-stopped`)、
  `healthcheck`、`depends_on`(含 `service_started`、`service_healthy`、`service_completed_successfully`)、`user`、
  `working_dir`、`labels`(`wonton.` 开头的留给核心)、`hostname`、`expose`、`init`、`read_only`、`shm_size`、`tmpfs`、
  `stop_signal`、`stop_grace_period`、`pull_policy`(默认 `missing`)、`network_mode`(只能是 `none`),以及 `x-` 开头的
  扩展字段。
- 顶层:`name`、`services`、`volumes`(不带参数的本地卷)、`configs`、`x-wonton` 和 `x-` 开头的扩展字段;`version` 会被
  忽略。
- 不能用:`ports`、`networks`、`privileged`、`devices`(显卡由核心按 `gpu.services` 交给服务,见第 6 步)、
  `cap_add`、`security_opt`、`pid`、`ipc`、`container_name`、`build`、
  `env_file`、`extends`、`links`、`external_links`、`volumes_from`,顶层的 `networks`、`secrets`,以及 `environment` 里只写
  键、不写值(取宿主的环境变量)的写法。网页入口经核心网关访问,不写 compose 的 `ports`;要局域网直连,在第 5 步
  申请 `network.lan` 并写 `lan.ports`。

服务之间用服务名互访。镜像用上游的官方镜像,不改它、不另发修改版;按摘要固定,写成 `名字:标签@sha256:…`,多架构镜像写
清单列表(index)的摘要。`image` 既没写标签也没写摘要时按 `latest` 拉取。

```bash
docker buildx imagetools inspect nginx:1.30.6-alpine
```

输出开头的 `Digest:` 就是。没按摘要固定也能装,但检查会提醒,官方商店不收。

### 3. 网页入口和登录

```yaml
  webui:
    service: web
    port: 80
    sso: proxy
    proxy:
      headers:
        Remote-User: "{user}"
```

`webui` 指向一个服务的端口(`path` 默认 `/`)。插件网页的地址是 `http://<slug>.<基础域名>[:端口]/`,经这个入口的请求先过
核心网关:没登录的请求到不了插件,网关去掉客户端伪造的 `X-Forwarded-*`,保留原始的 `Host`,支持 WebSocket。`sso` 决定
用户怎样登进应用:

- `oidc`:应用官方支持 OIDC、符合下述 SSO 接入条件时用,由核心的身份提供方登录,用户不会看到第二个登录页。写上应用登录回调的路径
  `oidc.redirect_paths`(如 `[/auth/callback]`),在 `environment` 里把注入的 `WONTON_OIDC_ISSUER`、
  `WONTON_OIDC_CLIENT_ID`、`WONTON_OIDC_CLIENT_SECRET` 交给应用;不支持发现文档的应用另有
  `WONTON_OIDC_AUTH_URL`、`WONTON_OIDC_TOKEN_URL`、`WONTON_OIDC_USERINFO_URL`。应用在令牌端点出示密钥的方式由
  `oidc.token_endpoint_auth_method` 决定:`client_secret_basic`(默认)或 `client_secret_post`。可以申请的 scope 有
  `openid`、`profile`、`email`、`groups`(主人是 `["owner"]`)、`offline_access`。
- `proxy`:应用不支持 OIDC,但认反向代理传来的用户名(Navidrome、Paperless-ngx、Grafana 的 auth proxy)或一个固定的
  密钥时用。网关认出主人后,在转发给插件的请求上加上 `proxy.headers` 或 `proxy.cookies`,两项至少写一项。值里可以用
  `{user}`、`{name}`(显示名)、`{email}` 和 `{secret:<名字>}`(插件密钥里由核心生成、至少 16 字节的一项,见第 8 步),
  其余文字原样保留;Cookie 的值只能用 `{secret:…}` 和固定文字。网关先删掉客户端发来的同名请求头和 Cookie,再注入凭据。
  用请求头认人的应用只信任 `${WONTON_CORE_ADDRESS}/32`,不能信任整个网段。请求头和 Cookie 限制、可信代理条件见规范的
  [网关代登录](https://github.com/geraldpeng6/wonton-docs/blob/main/plugin-spec.md#网关代登录sso-proxy)。
- `none`(默认):网关只校验登录,应用保留自己的登录,或者本来就不需要登录。新插件默认这样:装好后第一次打开,主人在应用里
  自己注册管理员。插件网页仍然在网关的登录后面,只有主人打得开注册页面。

SSO 的接入条件见规范的[网页入口与单点登录](https://github.com/geraldpeng6/wonton-docs/blob/main/plugin-spec.md#网页入口与单点登录):
不能依赖脚本预建账号、改应用数据或每次启动核对,做不到就用 `none`。身份变量的授权另见第 7 步。

### 4. 数据

```yaml
services:
  app:
    user: "${PUID}:${PGID}"
    volumes:
      - ${WONTON_APP_DATA}/config:/config
      - ${WONTON_SHARE_PHOTOS}:/photos:ro
```

- `${WONTON_APP_DATA}` 是插件自己的数据目录(宿主上的路径),挂它的子目录。核心安装前逐级建好这些目录(路径上有软链接时
  拒绝安装),属主看挂载它的服务的 `user`:没写或是 root 时交给 `PUID:PGID`(以 root 启动、再按 `PUID`/`PGID` 降权的镜像
  就是这样,如 LinuxServer.io 的镜像);写成数字的 `uid:gid` 时交给它(镜像固定以某个 uid 运行时写上它);写用户名或只写
  uid 时装不上,几个服务以不同的属主挂载同一个目录时也装不上。能以任意 uid 运行的镜像写 `user: "${PUID}:${PGID}"`,
  宿主上的文件就归主人的用户。核心只改这些目录本身的属主,不递归改里面已有的文件。
- 开发环境里 `${WONTON_APP_DATA}` 是 `.wonton/data/apps/<slug>/`,可以直接看插件写了什么。
- 也可以用命名卷:在顶层 `volumes` 里声明,Docker 里叫 `wonton-<slug>--<卷名>`。同名卷已经存在、属于本插件(上次卸载时
  保留的数据)时直接复用;属于别的插件或不是核心创建的时拒绝安装。
- 共享文件夹(照片、文档、音乐……)要在 `permissions` 里申请 `storage.share.<名称>:read` 或 `:write`,挂
  `${WONTON_SHARE_<名称大写>}`。只读的必须写 `:ro`,而且只能挂整个文件夹,不能挂子目录。
- 只能挂这几种宿主路径,不能有 `..`,不支持相对路径;同一个插件的绑定挂载不能一个套着另一个。
- 卸载时默认保留数据(数据目录、命名卷、插件密钥),再装时接着用;主人选了删除数据才删。共享文件夹永远不删。

### 5. 网络

- 插件默认不能出网:它的 Docker 网络是内部网络,访问不了外网和局域网,也解析不了外部域名。要出网(下载封面、同步、调
  第三方 API)就申请 `network.egress`,装的时候列在权限里。镜像由 Docker 拉取,不受这个限制。
- 要让局域网里的电脑、手机直接连上来(Samba 的 445、Syncthing 的 22000),申请 `network.lan`(高权限),在 `lan.ports`
  里逐项写服务、端口和协议:

  ```yaml
  x-wonton:
    permissions: [network.lan]
    lan:
      ports:
        - { service: syncthing, port: 22000 }
        - { service: syncthing, port: 22000, protocol: udp }
  ```

  宿主端口和容器端口相同,不做映射;`protocol` 是 `tcp`(默认)或 `udp`。`lan` 和 `network.lan` 必须同时声明。
  不能列 `mcp` 的 TCP 端口;`webui` 的端口可以列入,由主人决定是否开放。`lan_webui` 开关及风险、其他限制和地址规则见规范的
  [局域网](https://github.com/geraldpeng6/wonton-docs/blob/main/plugin-spec.md#局域网networklan)。
- 插件网络不是信任边界,不能据此信任身份头。核心服务的地址以注入变量为准,不要自己拼;网络隔离和代理可达性见规范的
  [网络](https://github.com/geraldpeng6/wonton-docs/blob/main/plugin-spec.md#网络)。

### 6. 权限

| 权限 | 含义 |
| --- | --- |
| `storage.share.<名称>:read`、`:write` | 读或写一个共享文件夹,如 `photos`、`documents`、`music`、`downloads`。设备上传的文件在 `transfers` 里 |
| `storage.home:read`、`:write` | 读或写当前用户的私人空间 |
| `models` | 经模型网关用大模型,注入 `WONTON_MODEL_*` |
| `tools` | 经核心的 MCP 总线调用核心和其他插件的工具,注入 `WONTON_MCP_URL` |
| `matrix.bot` | 以 Bot 的身份收发消息,注入 `WONTON_MATRIX_*` |
| `owner.identity` | 获取主人的用户名和邮箱,使用 `spec: "0.3"`,注入条件见第 7 步 |
| `notify` | 给主人发通知 |
| `network.egress` | 访问外网和局域网 |
| `network.lan` | 在局域网上开放 `lan.ports` 列出的端口,高权限 |
| `network.host` | 不开放(ADR-0019),局域网访问用 `network.lan`,高权限 |
| `devices.gpu` | 使用显卡,高权限 |
| `devices.<路径>` | 访问宿主设备,如 USB,高权限 |
| `docker.socket` | 访问 Docker socket,最高权限,默认拒绝,需要单独确认 |

高权限在安装确认时单独醒目展示,插件不声明的能力一律不给。核心兑现 `storage.share.*`、`network.egress`、`network.lan`、
`models`、`matrix.bot`、`tools`、`owner.identity` 和 `devices.gpu`;`storage.home`、`notify`、`devices.<路径>`、`docker.socket` 可以声明,
安装时如实报“尚未支持”。

申请 `devices.gpu` 时,在 `gpu.services` 里写明哪些服务拿到显卡(至少一项、不能重复),`requirements.gpu` 写
`required`(不写时的默认)或 `optional`;写了 `requirements.gpu` 也必须申请权限、写服务。核心只给显卡本身,不给
`privileged`、`cap_add`、`security_opt`、`ipc: host`;要大的共享内存写 `shm_size`。`required` 而本机没有可用的显卡时装不上
(`gpu_unavailable`)。

安装时主人要确认清单里的全部权限,少一项多一项都装不上。只申请真用得到的:权限越少,主人越放心。

### 7. 注入的变量

核心在安装时提供下面这些变量,只用于 compose 插值(`${VAR}`、`${VAR:-默认值}`,`$$` 是字面的 `$`),不会自动进入容器:
要用的变量在服务的 `environment` 里显式写上,密钥只给用得到它的服务。引用了没提供的变量、又没写默认值时装不上。

| 变量 | 什么时候有 |
| --- | --- |
| `WONTON_APP_ID`、`WONTON_APP_DATA`、`TZ`、`PUID`、`PGID` | 总是 |
| `WONTON_APP_URL` | 有 `webui`:插件网页的地址,不带末尾的 `/`,应用生成链接和回调地址时用 |
| `WONTON_OWNER_NAME` | 申请 `owner.identity`:固定的主人用户名;旧清单兼容条件见下文 |
| `WONTON_OWNER_EMAIL` | 同上,且用户填了邮箱;值是建容器时的快照。开发环境里 `scripts/dev.sh up` 替你填 |
| `WONTON_SHARE_<名称>` | 申请了这个共享文件夹 |
| `WONTON_OIDC_*` | `sso: oidc` |
| `WONTON_CORE_ADDRESS` | `sso: proxy`:核心在本插件网络里的固定地址,用于应用的可信代理配置 |
| `WONTON_MODEL_BASE_URL`、`WONTON_MODEL_API_KEY`、`WONTON_MODEL_DEFAULT` 等 | `models` |
| `WONTON_MCP_URL` | `tools` |
| `WONTON_MATRIX_HOMESERVER`、`WONTON_MATRIX_USER_ID`、`WONTON_MATRIX_ACCESS_TOKEN` 等 | `matrix.bot` |
| `WONTON_LAN_ADDRESSES` | `network.lan`:局域网端口开在哪些地址上,逗号分隔 |
| `WONTON_SECRET_<名字>` | `secrets` 里核心生成的项 |
| `WONTON_BOT_NAME`、`WONTON_BOT_DATA` | `bots.services` 里的服务 |

- v0.3 引用主人身份变量要申请 `owner.identity`,不能只靠 `oidc` 或 `proxy`;低于 v0.3 的旧清单保留 SSO 兼容注入。
  `sso: none` 也能申请,不要为拿变量开启 SSO。登录时从 OIDC 或代登录模板拿到的身份不受此权限控制,完整规则见规范的
  [安装时注入的变量](https://github.com/geraldpeng

... (truncated)
tools

Comments

Sign in to leave a comment

Loading comments...