Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 67 additions & 21 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ An agent only needs a BaseURL and a Secret Key to discover capabilities, read th

[![npm: cli](https://img.shields.io/npm/v/@tool-bridge/cli?label=%40tool-bridge%2Fcli)](https://www.npmjs.com/package/@tool-bridge/cli)
[![npm: sdk](https://img.shields.io/npm/v/@tool-bridge/sdk?label=%40tool-bridge%2Fsdk)](https://www.npmjs.com/package/@tool-bridge/sdk)
[![Railway](https://img.shields.io/badge/Railway-Quick_Deploy-0B0D0E?logo=railway)](#railway)
[![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)


Expand Down Expand Up @@ -49,25 +50,20 @@ The tree brings four concerns together:

## Quick start: run a gateway locally

This example uses the Node/Docker host. It stores state in SQLite and objects under `/data`, making it the shortest path to a local end-to-end setup.
The default Docker Compose stack includes the app, PostgreSQL, and S3-compatible object storage. Docker with Compose is required; credentials are generated by the installer.

### 1. Generate trust roots and start the server

Node.js 22+ is used below to generate two random values. Save the Admin SK: the gateway cannot reveal it later.
### 1. Start and pair the instance

```sh
export TB_ADMIN_SK="$(node -e "console.log('tbk_'+require('crypto').randomBytes(32).toString('base64url'))")"
export TB_ENCRYPTION_KEY="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))")"

docker run -d --name tool-bridge \
-p 127.0.0.1:8787:8787 \
-v tool-bridge-data:/data \
-e TB_BOOTSTRAP_ADMIN_SK="$TB_ADMIN_SK" \
-e TB_SECRET_ENCRYPTION_KEY="$TB_ENCRYPTION_KEY" \
ghcr.io/tokenrollai/tool-bridge:latest
git clone https://github.com/TokenRollAI/tool-bridge.git
cd tool-bridge
docker compose up -d --build
docker compose exec -T app node /app/dist/admin.js pair
```

In production, inject both values through your platform's secret mechanism. Do not bake them into an image, repository, or shared script.
Open [http://127.0.0.1:8787/ui/setup](http://127.0.0.1:8787/ui/setup), enter the one-time pairing credential, and complete installation using the built-in database and object storage. Save the Admin SK shown after installation in a password manager; use it to log in below. PostgreSQL, object storage, and bootstrap identity/keys each persist in Docker volumes.

Prefer a hosted deployment? Follow the [Railway quick start](#railway).

### 2. Log in, discover, and invoke with the CLI

Expand All @@ -82,9 +78,11 @@ tb call system/status/get # invoke its get command

The deployment includes the Dashboard at [http://127.0.0.1:8787/ui](http://127.0.0.1:8787/ui). It uses the same public API, and the SK stays in local browser storage.

You can also use plain fetch without the CLI:
You can also use plain HTTP without the CLI. First read the saved Admin SK into a temporary shell variable (Bash):

```bash
read -rsp "Admin SK: " TB_ADMIN_SK; echo

```sh
curl -H "Authorization: Bearer $TB_ADMIN_SK" \
http://127.0.0.1:8787/~help

Expand Down Expand Up @@ -239,14 +237,62 @@ Federation fails closed by default: an empty host allowlist permits no remote, H

## Deploy or embed

| Shape | State / objects / devices | Replicas | Best fit |
| Shape | State / objects | Replicas | Best fit |
|---|---|---|---|
| **Docker (single container)** | SQLite + local filesystem + in-process WebSocket | 1 | Self-hosting, private networks, quick evaluation |
| **Docker Compose** | PostgreSQL (+ optional S3, Redis) | 1–2 (one machine) | Single-machine production, incl. an HA reference stack — see [`deploy/compose/`](deploy/compose/docker-compose.yml) |
| **Kubernetes (Helm)** | PostgreSQL + S3 + Redis → stateless multi-replica; or SQLite + PVC single replica | 1–N | Multi-replica production with rolling updates — see [`deploy/helm/tool-bridge/`](deploy/helm/tool-bridge) |
| **Docker Compose** | PostgreSQL + S3 + persistent bootstrap volume | 1 by default | Local evaluation and self-hosting; see [`docker-compose.yml`](docker-compose.yml) |
| **Railway** | PostgreSQL + S3 + `/data` volume | 1 | Hosted deployment; follow the [quick start below](#railway) |
| **Kubernetes (Helm)** | PostgreSQL + S3 + persistent bootstrap; Redis for multiple replicas | 1–N | Cluster deployments; see [`deploy/helm/tool-bridge/`](deploy/helm/tool-bridge) |
| **Embedded SDK** | Caller-injected stores and providers | — | Register local functions inside your own Node application |

Horizontal scaling formula for the Node host: **PostgreSQL (`TB_DATABASE_URL`) + S3 (`TB_OBJECT_STORE_*`) + Redis (`TB_REDIS_URL`) together make it a stateless multi-replica deployment**; with only the first two it is a single-replica stateless shape (containers can be recreated freely, but do not scale out). The Helm chart rejects dangerous combinations (such as `replicas>1 + SQLite`) at render time. Health probes: `/livez` (liveness), `/readyz` (readiness: backend connectivity plus early traffic removal during graceful shutdown), `/healthz` (version and catalog digest).
PostgreSQL is the authoritative state backend; S3 stores object bytes. Preserve the bootstrap identity and keys as well as database and object data. Product settings are managed through setup and the API/CLI/Dashboard, not legacy `TB_DATABASE_URL` / `TB_OBJECT_STORE_*` variables. Multiple replicas additionally require shared initialized bootstrap storage and Redis.

<a id="railway"></a>

### Quick deploy on Railway

This path uses the existing [`Dockerfile.railway`](Dockerfile.railway) and finishes with browser-based setup. Prepare a PostgreSQL service and an **existing S3 bucket with a public HTTPS endpoint and path-style URL support**. Redis is not required for this single-replica deployment. The S3 endpoint must be an origin without a bucket/path/query; its credentials must allow reading, writing, deleting, and conditional writes so the setup probe can pass. Replace `your-domain.up.railway.app` below with your actual domain.

1. Open [Railway New Project](https://railway.com/new), add PostgreSQL, then add a GitHub service from `TokenRollAI/tool-bridge` (fork it first if needed). Keep the app's root directory at the repository root.
2. Before deploying the app, attach a persistent volume at `/data` and configure:

| Setting | Value |
|---|---|
| Variable `RAILWAY_DOCKERFILE_PATH` | `Dockerfile.railway` |
| Variable `RAILWAY_RUN_UID` | `0` (the start command below drops privileges) |
| Variable `TB_BOOTSTRAP_DIR` | `/data/bootstrap` |
| Healthcheck Path | `/healthz` |
| Replicas | `1` |

Set **Start Command** to:

```sh
/bin/sh -c 'install -d -m 700 -o 1000 -g 1000 /data /data/bootstrap && exec setpriv --reuid=node --regid=node --init-groups node /app/dist/main.js'
```

This gives the application user ownership of the mounted directories before starting Node as `node`. Both directories must be writable so bootstrap locking works. The app reads Railway's `PORT` automatically; leave Build Command unset.
3. Deploy, then use **Settings → Networking → Generate Domain** to get the app's public HTTPS origin. Use the app's `PORT` as the target port (default `8787` when unset).
4. Open the app container through Railway's **Copy SSH Command** ([Railway CLI](https://docs.railway.com/cli/ssh)), then run:

```sh
if [ "$(id -u)" = 0 ]; then
setpriv --reuid=node --regid=node --init-groups node /app/dist/admin.js pair
else
node /app/dist/admin.js pair
fi
```

Visit `https://your-domain.up.railway.app/ui/setup` and paste the one-time pairing credential. Enter the PostgreSQL service's private `DATABASE_URL`, the S3 endpoint/bucket/region/access key/secret key, and your public HTTPS origin in the public-address field. Leave Redis empty. These credentials go into protected setup, not application environment variables.
5. Complete installation, save the Admin SK, and verify `https://your-domain.up.railway.app/readyz` returns HTTP 200. Then connect locally:

```sh
npm install -g @tool-bridge/cli
tb login --base-url https://your-domain.up.railway.app
tb call system/status/get
```

`/healthz` can succeed before installation is complete; `/readyz` confirms business readiness. Keep `/data`, PostgreSQL, and S3 data when redeploying. The Railway volume setup above is for one replica.

The current S3 client uses path-style URLs. New [Railway Storage Buckets](https://docs.railway.com/storage-buckets) use virtual-hosted URLs, so check compatibility before choosing one. Private S3 endpoints require a protected `install-defaults.json`; see the [deployment guide](llmdoc/hosts-deploy/node-docker-and-helm.mdx). Platform settings: [variables](https://docs.railway.com/variables/reference), [volumes](https://docs.railway.com/volumes/reference), [start commands](https://docs.railway.com/deployments/start-command).

### Embed in your own application

Expand Down
90 changes: 68 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Agent 只需要一个 BaseURL 和一个 Secret Key,就能发现能力、阅读

[![npm: cli](https://img.shields.io/npm/v/@tool-bridge/cli?label=%40tool-bridge%2Fcli)](https://www.npmjs.com/package/@tool-bridge/cli)
[![npm: sdk](https://img.shields.io/npm/v/@tool-bridge/sdk?label=%40tool-bridge%2Fsdk)](https://www.npmjs.com/package/@tool-bridge/sdk)
[![Railway](https://img.shields.io/badge/Railway-Quick_Deploy-0B0D0E?logo=railway)](#railway)
[![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)


Expand Down Expand Up @@ -49,25 +50,20 @@ Agent / CLI / Dashboard / MCP client

## 快速开始:本地运行一个网关

下面使用 Node/Docker 宿主。它把状态存到 SQLite,把对象存到 `/data`,适合先完成一个本地闭环
默认 Docker Compose 栈包含应用、PostgreSQL 和 S3 兼容对象存储。需要安装 Docker(含 Compose);安装器会自动生成基础设施凭证

### 1. 生成信任根并启动

需要 Node.js 22+ 来生成两个随机值;请保存 Admin SK,丢失后无法从网关中读回。
### 1. 启动并配对实例

```sh
export TB_ADMIN_SK="$(node -e "console.log('tbk_'+require('crypto').randomBytes(32).toString('base64url'))")"
export TB_ENCRYPTION_KEY="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))")"

docker run -d --name tool-bridge \
-p 127.0.0.1:8787:8787 \
-v tool-bridge-data:/data \
-e TB_BOOTSTRAP_ADMIN_SK="$TB_ADMIN_SK" \
-e TB_SECRET_ENCRYPTION_KEY="$TB_ENCRYPTION_KEY" \
ghcr.io/tokenrollai/tool-bridge:latest
git clone https://github.com/TokenRollAI/tool-bridge.git
cd tool-bridge
docker compose up -d --build
docker compose exec -T app node /app/dist/admin.js pair
```

生产部署应通过平台的 Secret 机制注入这两个值,不要把它们写进镜像、仓库或共享脚本。
打开 [http://127.0.0.1:8787/ui/setup](http://127.0.0.1:8787/ui/setup),输入一次性配对凭证,使用内置数据库和对象存储完成安装。将安装成功后显示的 Admin SK 保存到密码管理器,后续登录时使用。PostgreSQL、对象存储和 bootstrap 身份/密钥分别保存在 Docker 持久卷中。

希望直接托管到云上?跳到 [Railway 快速部署](#railway)。

### 2. 用 CLI 登录、发现和调用

Expand All @@ -82,9 +78,11 @@ tb call system/status/get # 调用节点上的 get 命令

部署包含 Dashboard,可直接打开 [http://127.0.0.1:8787/ui](http://127.0.0.1:8787/ui)。Dashboard 使用同一套公开 API,SK 只保存在浏览器本地。

不使用 CLI 也可以直接 fetch:
不使用 CLI 也可以直接 fetch:先把保存的 Admin SK 读入临时变量(Bash):

```bash
read -rsp "Admin SK: " TB_ADMIN_SK; echo

```sh
curl -H "Authorization: Bearer $TB_ADMIN_SK" \
http://127.0.0.1:8787/~help

Expand Down Expand Up @@ -240,7 +238,7 @@ tb device op cancel build-01 <operation-id>
Mailbox 是 pull-only 的持久化执行账本:网关先落库,设备用
`@tool-bridge/sdk/device` 主动 claim / renew / complete;它不会用 APNs、FCM 唤醒设备。fallback
只在网关确认 realtime 尚未 dispatch 时入队;发送后的断线/超时属于执行歧义,不会再次入队。
`TB_SECRET_ENCRYPTION_KEY` 是启用 Mailbox 的部署前提;参数与结果使用
Mailbox 使用安装时生成并持久保存在 bootstrap 中的加密根;参数与结果使用
独立 HKDF 子密钥做静态加密,但不是端到端加密,网关在授权后的 claim/complete 路径仍会处理明文。

`result_unknown` 表示设备确认已经开始执行但结果无法恢复;claimed 操作过期则为 `expired` 且标记
Expand Down Expand Up @@ -288,14 +286,62 @@ tb help teams/team-b/tools/search

## 部署与嵌入

| 形态 | 状态 / 对象 / 设备 | 副本 | 适合场景 |
| 形态 | 状态 / 对象 | 副本 | 适合场景 |
|---|---|---|---|
| **Docker 单容器** | SQLite + 本地文件系统 + 进程内 WebSocket | 1 | 自托管、内网、快速验证 |
| **Docker Compose** | PostgreSQL(+ 可选 S3、Redis) | 1–2(单机) | 单机生产、含 HA 参考栈,见 [`deploy/compose/`](deploy/compose/docker-compose.yml) |
| **Kubernetes(Helm)** | PostgreSQL + S3 + Redis → 无状态多副本;或 SQLite + PVC 单副本 | 1–N | 多副本生产、滚动更新,见 [`deploy/helm/tool-bridge/`](deploy/helm/tool-bridge) |
| **Docker Compose** | PostgreSQL + S3 + bootstrap 持久卷 | 默认 1 | 本地试用、自托管,见 [`docker-compose.yml`](docker-compose.yml) |
| **Railway** | PostgreSQL + S3 + `/data` 持久卷 | 1 | 云上托管,见[下方快速部署](#railway) |
| **KubernetesHelm** | PostgreSQL + S3 + bootstrap 持久化;多副本另配 Redis | 1–N | 集群部署,见 [`deploy/helm/tool-bridge/`](deploy/helm/tool-bridge) |
| **嵌入式 SDK** | 由调用方注入 store/provider | — | 在自己的 Node 应用里注册本地函数 |

Node 宿主的横向扩容公式:**PG(`TB_DATABASE_URL`)+ S3(`TB_OBJECT_STORE_*`)+ Redis(`TB_REDIS_URL`)三件配齐即无状态多副本**;只配前两件是"容器可随意重建、但别扩副本"的单副本无状态形态。Helm chart 会在渲染期直接拒绝危险组合(如 `replicas>1 + SQLite`)。健康探针:`/livez`(liveness)、`/readyz`(readiness,探后端连通 + 优雅关停时提前摘流量)、`/healthz`(版本与 catalog 对拍)。
PostgreSQL 保存权威状态,S3 保存对象字节;bootstrap 中的实例身份和密钥也必须持久保存。业务配置通过安装流程及 API/CLI/Dashboard 管理,不再使用旧的 `TB_DATABASE_URL` / `TB_OBJECT_STORE_*` 环境变量。多副本还需要共享已初始化的 bootstrap 存储并配置 Redis。

<a id="railway"></a>

### 快速部署到 Railway

使用仓库现有的 [`Dockerfile.railway`](Dockerfile.railway) 构建,最后在浏览器中完成安装。需要一个 PostgreSQL 服务,以及一个**已创建、支持 path-style URL、可通过公网 HTTPS endpoint 访问的 S3 桶**。这套单副本部署不需要 Redis。S3 endpoint 只填 origin,不带 bucket、路径或 query;凭证需允许读写、删除及条件写入,以通过安装探测。下文的 `your-domain.up.railway.app` 请替换为实际域名。

1. 打开 [Railway 新建项目](https://railway.com/new),添加 PostgreSQL,再从 `TokenRollAI/tool-bridge` 添加 GitHub 应用服务(必要时先 Fork)。应用的 Root Directory 保持仓库根目录。
2. 部署应用前,为它添加挂载到 `/data` 的持久卷,并配置:

| 设置 | 值 |
|---|---|
| 环境变量 `RAILWAY_DOCKERFILE_PATH` | `Dockerfile.railway` |
| 环境变量 `RAILWAY_RUN_UID` | `0`(下面的启动命令会降权) |
| 环境变量 `TB_BOOTSTRAP_DIR` | `/data/bootstrap` |
| Healthcheck Path | `/healthz` |
| Replicas | `1` |

将 **Start Command** 设为:

```sh
/bin/sh -c 'install -d -m 700 -o 1000 -g 1000 /data /data/bootstrap && exec setpriv --reuid=node --regid=node --init-groups node /app/dist/main.js'
```

这会先设置挂载目录的所有者,再以 `node` 用户运行应用;两个目录都需要可写,bootstrap 锁才能正常创建。应用自动读取 Railway 的 `PORT`,Build Command 留空。
3. 部署后,通过 **Settings → Networking → Generate Domain** 获取应用的公网 HTTPS 地址。目标端口使用应用的 `PORT`(未设置时默认 `8787`)。
4. 使用应用服务的 **Copy SSH Command** 进入部署容器(需安装 [Railway CLI](https://docs.railway.com/cli/ssh)),执行:

```sh
if [ "$(id -u)" = 0 ]; then
setpriv --reuid=node --regid=node --init-groups node /app/dist/admin.js pair
else
node /app/dist/admin.js pair
fi
```

打开 `https://your-domain.up.railway.app/ui/setup`,粘贴一次性配对凭证;填写 PostgreSQL 服务的私网 `DATABASE_URL`、S3 endpoint/bucket/region/access key/secret key,并在“公开访问地址”中填入应用的公网 HTTPS origin。Redis 留空。这些凭证通过受保护的安装页提交,不作为应用环境变量配置。
5. 完成安装并保存 Admin SK,确认 `https://your-domain.up.railway.app/readyz` 返回 HTTP 200,然后在本机连接:

```sh
npm install -g @tool-bridge/cli
tb login --base-url https://your-domain.up.railway.app
tb call system/status/get
```

`/healthz` 在安装前也可能成功,业务就绪以 `/readyz` 为准。重新部署时保留 `/data`、PostgreSQL 和 S3 数据;以上挂卷方案保持单副本。

当前 S3 客户端使用 path-style URL,新建 [Railway Storage Bucket](https://docs.railway.com/storage-buckets) 使用 virtual-hosted URL,选用前需确认兼容性。私网 S3 endpoint 需要受保护的 `install-defaults.json`,见[部署指南](llmdoc/hosts-deploy/node-docker-and-helm.mdx)。平台配置参考:[环境变量](https://docs.railway.com/variables/reference)、[持久卷](https://docs.railway.com/volumes/reference)、[启动命令](https://docs.railway.com/deployments/start-command)。

### 嵌入自己的应用

Expand Down
Loading