内容
<p align="center">
<img src="octobuslogo.jpg" alt="OctoBus" width="240">
</p>
<p align="center">
<a href="https://github.com/chaitin/OctoBus/actions/workflows/ci.yml">
<img src="https://github.com/chaitin/OctoBus/actions/workflows/ci.yml/badge.svg?branch=main" alt="ci">
</a>
<a href="./CONTRIBUTING.md">
<img src="https://img.shields.io/badge/Contributing-Guide-blue" alt="contributing">
</a>
<a href="./SECURITY.md">
<img src="https://img.shields.io/badge/Security-Feedback-blue" alt="security">
</a>
</p>
---
[中文版 README](README.zh-CN.md)
OctoBus 是一个本地运行的单二进制网关,用于管理可插拔的 Node.js 服务包,并通过 capset 将这些包中的 gRPC 功能暴露给客户端或代理。
当前的实现提供了一个用 Go 构建的 `octobus` 二进制文件,负责:
- 守护进程:启动本地控制平面和公共数据平面,并根据每个服务运行时模式管理 Node.js 子进程
- CLI:通过本地管理 API 管理服务、实例和能力集
- 网关:暴露选定的方法为 gRPC,并将单向方法暴露为 Connect RPC 和 MCP 流式 HTTP
- 存储:使用 SQLite 记录服务、实例、能力集、方法绑定、描述符和运行时状态
- 运行时管理:导入服务包、准备运行时目录,并管理长期运行或按需 Node.js 实例
## 项目概述
OctoBus 围绕以下核心模型构建:
- **服务**:可导入的 Node.js 包中的服务根目录。它包含 `service.json`、proto 文件和 gRPC 实现。单个分发包可以通过 `//service-dir` 暴露多个服务根目录。
- **实例**:服务的运行时实例,具有独立的配置和工作目录。长期运行的实例还具有日志和本地监听端口。
- **能力集**:代理或用例的确定性能力集,由 `capset -> service -> instance -> method` 绑定组成。
- **方法绑定**:能力集中实际选择和暴露的 gRPC 方法。单向方法可以通过 gRPC、Connect RPC 和 MCP 调用。流式方法仅支持 gRPC 调用长期运行的服务,不适用于 Connect RPC、MCP 或按需调用路径。
默认情况下,守护进程监听单个端口 `127.0.0.1:9000`。管理 API、gRPC、Connect RPC、MCP 和反射都通过该端口调度。您可以显式绑定到另一个地址,例如 `0.0.0.0:9000`;在远程暴露 OctoBus 时,您负责网络访问控制。CLI 默认通过管理 API 进行管理操作,不直接写入 SQLite。
服务包默认使用 `long-running` 运行时模式:在创建或启动实例后,OctoBus 启动一个常驻的 Node.js gRPC 子进程。包也可以在 `service.json` 中声明 `"runtime":{"mode":"on-demand"}`:此类实例不会预先启动,不会存储 PID 或监听地址。对于每个传入请求,OctoBus 启动一个短生命周期的 `invoke` 子进程。
## 启动守护进程
### 从 npm 安装
OctoBus 作为 `@chaitin-ai/octobus` npm 包发布。主包安装一个小型 Node.js 启动器,并通过特定平台的可选依赖项(例如 `@chaitin-ai/octobus-linux-x64`)拉取匹配的原生 Go 二进制文件。
```bash
npm install -g @chaitin-ai/octobus
octobus serve
```
您也可以在不进行全局安装的情况下运行它:
```bash
npx @chaitin-ai/octobus serve
```
npm 包仅安装 `octobus` 二进制文件。正常的服务导入和运行时流程仍然需要 `node`、`npm`、`protoc` 和 `git`,如下所述。
### 使用 Docker 运行
Docker 镜像包括 `octobus` 二进制文件和用于正常服务导入和实例启动流程的运行时依赖项。
```bash
docker run --rm \
-p 9000:9000 \
-v octobus-data:/var/lib/octobus \
ghcr.io/chaitin/octobus:latest
```
容器默认监听 `0.0.0.0:9000`,并将守护进程状态存储在 `/var/lib/octobus` 下。
### 从检出版本构建
首次检出后,构建二进制文件:
```bash
task build
```
使用默认配置启动:
```bash
./bin/octobus serve
```
常见选项:
```bash
./bin/octobus serve \
--data-dir .octobus \
--addr 127.0.0.1:9000
```
您也可以通过环境变量覆盖默认值:
```bash
export OCTOBUS_DATA_DIR="./.octobus"
export OCTOBUS_ADDR="127.0.0.1:9000"
```
数据目录存储 SQLite 数据库、服务工件和运行时、实例配置和日志。默认数据目录是 `.octobus`,位于启动守护进程命令的当前目录下。
### 依赖项
要在本地运行守护进程并执行正常的服务导入/启动工作流程,请安装以下命令,并确保它们在 `PATH` 中可用:
- `node`:运行导入的 Node.js 服务包;版本必须满足包自己的要求
- `npm`:在服务导入期间获取 npm 包,并在运行时目录中安装生产依赖项
- `protoc`:在服务导入期间编译 proto 描述符
- `git`:从 HTTPS Git 源获取和存档包
如果 `go build` 或 `task build` 在下载 Go 模块时出现超时(例如 `dial tcp ... i/o timeout`),您可能需要配置 Go 模块代理:
```bash
go env -w GOPROXY=https://goproxy.cn,direct
```
## 基本工作流程
以下示例使用内置的计算器服务来完成整个工作流程。在启动之前,构建二进制文件,启动守护进程,并验证 CLI 可以连接:
```bash
./bin/octobus status
```
如果守护进程不在默认地址运行,请通过全局选项或环境变量指定它。本地守护进程默认使用 HTTP/h2c,地址可以是裸 `host:port` 或 `http://host:port`:
```bash
./bin/octobus --addr 127.0.0.1:19001 status
OCTOBUS_ADDR=http://127.0.0.1:19001 ./bin/octobus service list
```
仅当 OctoBus 远程暴露并由外部代理提供 TLS 时,才使用 `https://host:port` 形式。
> 计算器示例通过此存储库的本地 SDK 中的构建工件安装依赖项。在从干净的检出运行示例之前,请准备示例依赖项;此任务会自动构建本地 SDK 并安装示例依赖项:
>
> ```bash
> task example:calculator:dev-deps
> ```
>
> 存储库还提供 `examples/calculator-on-demand-js` 中的按需运行时计算器示例。要在本地运行该示例,请首先准备其依赖项;此任务还会自动构建本地 SDK:
>
> ```bash
> task example:calculator-on-demand:dev-deps
> ```
您还可以运行本地计算器的干净检出烟雾测试脚本。此任务会清理生成的工件,重新构建二进制文件和本地 SDK,安装计算器示例依赖项,启动临时守护进程,导入服务,创建实例和能力集,并调用 Connect RPC 以断言响应为 `result: 42`:
```bash
task example:clean-checkout-smoke
```
导入示例服务包:
```bash
./bin/octobus service import calculator ./examples/calculator-js
```
第一个位置参数 `calculator` 是本地 OctoBus 服务 ID,必填。 `--name` 是可选的,覆盖显示名称。如果省略 `--name`,则第一次导入使用 `service.json` 中的 `displayName`,或如果 `displayName` 不存在,则使用 `name`。重新导入相同的服务 ID 而不带 `--name` 会保留现有的显示名称。
默认情况下,`service import` 使用 `--source-mode auto`:如果 `SOURCE` 是客户端本地目录、客户端本地 `.tgz` / `.tar.gz` / `.zip` 存档或 `npm:` 后跟客户端本地路径,CLI 会将该源上传到守护进程。否则,CLI 保留现有的守护进程端 JSON 导入行为,适用于来自 npm 注册表包、HTTPS Git 源、远程 HTTP(S) 存档或仅在守护进程主机上存在的路径的源。此规则不依赖于 `--addr` 是 `127.0.0.1`、`localhost` 还是远程地址;Docker 端口映射、SSH 隧道和端口转发都可以使用环回地址而无需共享文件系统。
使用 `--source-mode remote` 时,应由守护进程文件系统解析 `SOURCE` 路径,即使同一路径存在于 CLI 机器上。使用 `--source-mode upload` 强制客户端上传,如果源不是现有支持的本地源,则在发出管理请求之前失败。
创建并启动实例:
```bash
./bin/octobus instance create \
calculator-test \
--service calculator \
--config-json '{"label":"primary"}' \
--secret-json '{"apiToken":"dev-token"}'
```
创建能力集并暴露实例中的方法:
```bash
./bin/octobus capset create dev --name DevAgent
./bin/octobus capset add-instance \
dev \
calculator-test
```
查看能力集目录并确认方法已暴露:
```bash
./bin/octobus catalog dev --all --json
```
通过 Connect RPC 调用计算器:
```bash
curl -X POST \
http://127.0.0.1:9000/capsets/dev/connect/calculator-test/calculator.v1.CalculatorService/Add \
-H 'Content-Type: application/json' \
-d '{"left":20,"right":22}'
```
额外说明:
- 除了客户端本地目录外,`service import` 还支持客户端本地和远程 HTTP(S) `.tgz` / `.tar.gz` / `.zip` 存档、`npm:` 源和 HTTPS Git 源。除了远程 HTTP(S) 存档 URL 外,包源可以附加 `//service-dir` 以选择分发包内的服务根目录,例如 `npm:@scope/tentacle@1.0.0//Hanqing_Ticket` 或 `https://github.com/acme/tentacle.git//Hanqing_Ticket@v1.0.0`。远程存档 URL 使用包根目录作为服务根目录;使用递归导入多服务存档。有关源传输模式、离线导入、强制依赖项重新安装和其他选项,请参见 `./bin/octobus service import --help`。
- 使用 `service import --recursive SOURCE` 导入多服务分发包中发现的每个服务根目录,例如 `./bin/octobus service import --recursive npm:@chaitin-ai/octobus-tentacles`。在递归模式下,`SOURCE//some-dir` 限制发现到该扫描根目录,同时仍然使用其 `service.json.name` 导入每个发现的服务。
- `instance` 支持 `list/get/update/delete/update-config/update-secret/start/stop/restart`。对于 `long-running` 服务,`create` 默认启动实例。配置可以来自 `--config`、`--config-json` 或 stdin;秘密可以来自 `--secret`、`--secret-json` 或 stdin。
- `on-demand` 实例保持逻辑 `enabled/running` 状态,但 `start/stop/restart` 和配置更新返回错误,因为运行时模式不支持持久运行时控制。
- `capset` 支持 `list/get/update/delete/add-instance/remove-instance`。您还可以使用 `select-method` / `unselect-method` 进行精确的方法暴露控制。`add-token/list-tokens/remove-token` 管理访问令牌。
- `capset add-instance` 接受两个位置参数:能力集 ID 和实例 ID。从实例记录中查找服务。默认情况下,此命令选择所有方法,并在执行时静态扩展当前服务的所有方法。使用 `--no-all-methods` 稍后使用 `select-method` 选择方法。gRPC 目录包括选定的单向和流式方法;Connect RPC、MCP 和 OpenAPI 只包含单向方法。通过后续服务更新添加的方法不会自动暴露给现有能力集。
有关更多调用方法,请参见下一节。命令详细信息可通过每个子命令的 `--help` 获取。
## 调用暴露的能力
获取能力集目录:
```bash
curl 'http://127.0.0.1:9000/admin/v1/catalog/dev?all=true'
```
目录通过协议返回每个方法,包括运行时模式、后端状态、gRPC 元数据、Connect RPC 端点、MCP 工具名称、描述符哈希/版本和请求/响应消息名称。默认情况下,仅返回 gRPC 目录。使用 `grpc=true`、`connect=true`、`mcp=true` 或 `all=true` 查询参数选择协议,或运行 `./bin/octobus catalog --help` 查看 CLI 选项。
默认情况下,能力集不需要访问令牌。当没有添加令牌时,能力集下的公共资源保持公开可访问。添加一个或多个令牌后,这些公共资源需要有效凭证:HTTP/Connect/MCP/OpenAPI 使用 `Authorization: Bearer <token>`,而 gRPC 和反射使用相同名称的元数据。令牌机密仅在创建时提交。OctoBus 保留验证哈希,不存储明文令牌。
```bash
printf '%s' 'dev-secret' | ./bin/octobus capset add-token dev local --token-stdin
./bin/octobus capset list-tokens dev
./bin/octobus capset remove-token dev local
```
### gRPC
gRPC 调用保留原始方法路径,并通过元数据指定路由目标:
```bash
grpcurl -plaintext \
-H 'x-octobus-capset: dev' \
-H 'x-octobus-instance: gitlab-test' \
-d '{"projectId":"p1"}' \
127.0.0.1:9000 \
gitlab.MergeRequestService/List
```
在转发到后端 Node 实例之前,OctoBus 删除 `x-octobus-*` 控制元数据,除了 `x-octobus-ext-*`,它会传递。业务扩展元数据应使用 `x-octobus-ext-*` 命名模式,例如 `x-octobus-ext-business-request-id` 和 `x-octobus-ext-username`,并转发到服务包。计算器示例读取 `x-octobus-ext-business-request-id` 并与旧的 `x-business-request-id` 保持兼容。长期运行服务的 gRPC 网关支持 unary、server streaming、client streaming 和 bidirectional streaming。按需服务仅支持 unary 调用。
### Connect RPC
Connect RPC 端点是:
```text
POST /capsets/{capset_id}/connect/{instance_id}/{full_service}/{method}
```
示例:
```bash
curl -X POST \
http://127.0.0.1:9000/capsets/dev/connect/gitlab-test/gitlab.MergeRequestService/List \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer dev-secret' \
-H 'x-octobus-ext-business-request-id: req-1' \
-d '{"projectId":"p1"}'
```
Connect RPC 使用 protobuf JSON 映射,拒绝未知字段,并默认省略响应中的零值。字段级模式可通过能力集 OpenAPI 端点获取:
```bash
curl http://127.0.0.1:9000/capsets/dev/openapi.json
curl http://127.0.0.1:9000/capsets/dev/openapi.yaml
curl http://127.0.0.1:9000/admin/v1/catalog/dev/openapi.json
curl http://127.0.0.1:9000/admin/v1/catalog/dev/openapi.yaml
```
### MCP
MCP 流式 HTTP 端点是:
```text
POST /capsets/{capset_id}/mcp
```
列出工具:
```bash
curl -X POST http://127.0.0.1:9000/capsets/dev/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
调用工具:
```bash
curl -X POST http://127.0.0.1:9000/capsets/dev/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"gitlab__gitlab-test__list","arguments":{"projectId":"p1"}}}'
```
默认工具名称由 `{service}__{instance}__{method}` 生成。如果发生冲突,请使用 `--mcp-tool` 运行 `capset select-method` 时显式指定。
### gRPC 反射
OctoBus 自身提供 gRPC 反射,而不是将反射代理到 Node 实例。反射请求必须包含 `x-octobus-capset`,响应仅限于该能力集中的描述符闭包。
```bash
grpcurl -plaintext \
-H 'x-octobus-capset: dev' \
127.0.0.1:9000 \
list
```
## 查看访问日志
能力集的公共协议访问日志写入数据目录下的 `access.log`。文件权限为 `0600`。它记录协议、能力集、服务、实例、方法/工具、路由、状态码、持续时间、远程地址和用户代理。它不记录请求体、响应体、Authorization、令牌、秘密或业务元数据。
通过 CLI 视图日志:
```bash
./bin/octobus logs
./bin/octobus logs --capset dev --instance calculator-test
./bin/octobus logs --service calculator --limit 1000
./bin/octobus logs --capset dev --tail 0 --follow
```
`--limit 0` 返回所有匹配记录。 `--tail N` 返回最后 N 个匹配记录。 `--follow` 连续输出新的匹配记录。过滤器组合为精确匹配。
## 开发服务包
服务包必须至少包含以下文件:
```text
my-service/
package.json
service.json
proto/
service.proto
dist/
index.js
```
单个npm分发包也可以包含多个服务根目录。在这种情况下,根目录下的`package.json`是依赖项安装、发布和运行时入口的单一来源,而每个服务根目录子目录提供自己的`service.json`、proto和schema。在导入单个服务时,在源路径后追加`//service-dir`以选择目标服务根目录。如果没有该后缀,根目录本身就是服务根目录。使用`octobus service import --recursive SOURCE`命令来发现和导入所有服务根目录;递归模式下,`SOURCE//some-dir`是发现的扫描根目录。
示例`service.json`文件:
```json
{
"schema": "chaitin.octobus.service.v1",
"name": "gitlab-wrapper",
"displayName": "GitLab 封装",
"description": "GitLab API 封装服务",
"runtime": {
"mode": "长期运行"
},
"proto": {
"roots": ["proto"],
"files": ["proto/gitlab.proto"]
},
"configSchema": "config.schema.json",
"secretSchema": "secret.schema.json"
}
```
必填字段:
- `schema`
- `name`
- `proto.roots`
- `proto.files`
`name`是包内声明的名称,而不是OctoBus服务ID。`service.json`不能声明顶级`id`或`entry`字段。运行时入口必须由分发包根目录的`package.json bin`提供;单入口包可以使用字符串或单入口对象,而多服务包必须使`service.json.name`与根目录`bin`对象中的键匹配。`runtime.mode`是可选的,支持`长期运行`和`按需`;省略时,等同于`长期运行`。如果提供了`configSchema`,则在创建或更新实例配置时执行JSON Schema验证。如果提供了`secretSchema`,则在创建或更新实例密钥时执行JSON Schema验证。
当`长期运行`实例启动时,OctoBus执行运行时目录中解析的`node_entry`,并传递固定参数:
```text
--runtime serve --host 127.0.0.1 --port <port> --config <config.json> --secret <secret.json> --workdir <instance_workdir> --service <service_id> --instance <instance_id>
```
服务进程必须启动gRPC服务器并实现标准gRPC健康检查。
`按需`服务还必须支持一次性调用:
```text
--runtime invoke --method <package.Service/Method> --config <config.json> --secret <secret.json> --metadata <metadata.json> --workdir <instance_workdir> --service <service_id> --instance <instance_id>
```
OctoBus将protobuf wire-format请求写入stdin,并期望stdout仅包含protobuf wire-format响应。OctoBus还会设置`OCTOBUS_PACKAGE_DIR=<runtime>/<service_root>`,因此SDK从服务根目录读取`service.json`、proto和schema,而完整的运行时目录仍然保留分发包根目录的依赖项布局。`@chaitin-ai/octobus-sdk`的`runServiceMain`在未提供`--runtime`时进入业务CLI;在提供`--runtime`时,它进入运行时解析器,并支持`serve`、`invoke`、`dev`、`inspect`、`client-stub`和`client-package`等命令。
在本地运行服务入口时,使用`OCTOBUS_SERVICE_CONTEXT`将默认配置/密钥注入业务CLI,并使用`--runtime dev`:
```bash
OCTOBUS_SERVICE_CONTEXT='{"config":{"baseUrl":"https://example.com"},"secret":{"token":"dev-token"}}' \
node bin/service.js call --data-json '{"id":"123"}'
```
SDK还会从当前工作目录的`.env`文件中读取相同的变量。它只读取该键,不会注入其他`.env`变量。此变量不会影响守护进程的`--runtime serve`或`--runtime invoke`协议。当守护进程管理实例时,它继续通过文件和文件描述符传递配置/密钥。
## 开发
### 架构
```text
客户端 / 代理
-> OctoBus Go 二进制文件
-> 公共 HTTP/2 h2c 服务器
-> gRPC 网关
-> Connect RPC 适配器
-> MCP 适配器
-> 反射服务器
-> 本地管理员 API
-> SQLite 存储
-> 描述符加载器
-> Node 监督器
-> Node.js gRPC 实例进程
-> 按需调用子进程
```
主要代码目录:
- `cmd/octobus`:程序入口点,根命令,`serve`命令和守护进程组装
- `internal/cli`:Cobra CLI;所有管理命令调用本地管理员API
- `internal/admin`:本地管理员HTTP API
- `internal/packageimport`:服务包获取,解包,运行时准备和描述符编译
- `internal/supervisor`:实例配置写入,Node子进程启动/停止/恢复,健康检查和日志
- `internal/store`:SQLite模式,迁移和领域对象读取/写入
- `internal/protocol`:gRPC代理,Connect RPC,MCP,目录,OpenAPI和反射
- `internal/descriptors`:proto描述符编译,加载和元数据解析
- `sdk`:TypeScript源代码,测试和`@chaitin-ai/octobus-sdk`的构建产物
- `examples/calculator-js`:长期运行的JavaScript计算器服务示例
- `examples/calculator-on-demand-js`:按需JavaScript计算器服务示例
- `tests/e2e`:端到端测试
- `docs/design`:设计文档和目标
运行时数据大致布局如下:
```text
{data_dir}/
octobus.db
artifacts/services/{service_id}/
<package-artifact>.tgz 或 package.zip
package/
runtime/
descriptor.protoset
instances/{instance_id}/
config.json
secret.json
stdout.log
stderr.log
tmp/
```
当守护进程重新启动时,它从SQLite恢复`enabled=true`和`runtime_mode=long-running`的实例,并重新启动相应的Node.js子进程。`按需`实例不会预先启动;后续请求通过`invoke`调用它们。
### 要求
- Go:项目`go.mod`声明`go 1.26.1`
- Task:`Taskfile.yml`用于构建、检查和测试入口点
- Node.js / npm:导入和运行Node.js服务包所需
- `protoc`:编译proto描述符时服务导入和运行e2e测试所需
- `git`:从HTTPS Git源和某些测试导入服务所需
### 构建和测试
项目使用`Taskfile.yml`管理lint、测试和构建阶段。运行所有阶段:
```bash
task all
```
您也可以运行单个阶段:
```bash
task # 列出可用任务
task lint
task test
task build
```
`task test`首先构建本地SDK并为长期运行和按需计算器示例安装依赖项,然后运行Go测试,涵盖跨包覆盖率,包括`tests/e2e`。`task build`生成`bin/octobus`并为`version`子命令注入构建元数据。如果当前提交恰好位于与OctoBus发布标签`v[0-9]*`匹配的提交上,则使用该标签作为显示版本。否则,版本来自最近可达的匹配标签加上提交距离和短提交哈希,例如`v1.2.0-12-gabc1234`;如果没有可达的匹配标签,则回退到短Git提交哈希。没有Git元数据的构建环境可以通过`OCTOBUS_VERSION`、`OCTOBUS_COMMIT`和`OCTOBUS_BUILD_DATE`覆盖注入的值。您可以使用以下命令检查结果:
```bash
./bin/octobus version
```
端到端测试也可以单独运行:
```bash
go test ./tests/e2e -count=1
```
端到端测试构建真正的`octobus`二进制文件,启动真正的守护进程,通过CLI调用管理员API,然后验证gRPC、Connect RPC、MCP、OpenAPI和反射端点。
默认的GitHub Actions CI是轻量级验证:它检查公共跟踪、Go格式和vet,运行`go test ./cmd/... ./internal/...`,构建二进制文件,检查OctoBus npm二进制包,并运行`sdk`下的npm测试/构建/打包干运行。完整的`task test`和端到端测试仍然是本地关卡。只有`v<version>`标签推送构建才会触发OctoBus二进制包发布,且标签版本必须与`npm/octobus/package.json.version`匹配。SDK发布仅由`sdk-v<version>`标签推送构建触发,且标签版本必须与`sdk/package.json.version`匹配。两个npm发布路径都需要仓库密钥`NPM_TOKEN`。
## 安全和合规
如果您认为此仓库包含安全漏洞、侵权内容或其他合规风险,请私下向`octobus-opensource@chaitin.com`报告。
有关完整的报告流程和所需详细信息,请参见[SECURITY.md](SECURITY.md)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器