Files
beaver_project/README.md
steven_li 4e45f8b717 feat: 重命名项目为Boardware Genius并添加运行时环境同步功能
- 将项目品牌从nanobot重命名为Boardware Genius,更新所有相关文档、注释和日志输出
- 在web服务器中添加运行时环境变量同步功能,支持授权和后端身份配置
- 更新create-instance脚本以生成运行时环境文件
- 添加实例后端绑定功能到部署控制服务
- 修改入口脚本以加载运行时环境变量
- 更新前端和认证门户的相关描述文本
2026-03-18 15:45:42 +08:00

416 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# nano_project
单机部署版运行结构:
- `auth-portal`
- 用户入口页,提供登录、注册、跳转。
- `authz-service`
- AuthZ 服务。
- 现在负责注册编排:`auth-portal -> authz-service -> deploy-control -> app-instance -> authz-service`
- `deploy-control`
- 部署机控制面。
- 负责创建实例、解析实例、刷新反向代理。
- `router-proxy`
- 统一入口代理。
- 按 Host 把 `<slug>.<base_domain>` 转发到对应实例容器。
- `app-instance`
- 真正的单用户实例。
- 一个容器里同时包含前端、后端和 Nginx。
## 请求链路
注册:
```text
Browser
-> auth-portal
-> authz-service POST /portal/register
-> deploy-control POST /api/instances/register
-> create-instance.sh
-> app-instance POST /api/auth/register
-> authz-service /oauth/register or /backends/register
```
登录:
```text
Browser
-> auth-portal
-> deploy-control POST /api/instances/resolve
-> app-instance POST /api/auth/login
```
## 这份部署指南的前提
这份 README 只覆盖一套基准方案:
- 一台 Linux 服务器
- 用 Docker 运行 `auth-portal``authz-service``deploy-control``router-proxy`
- `deploy-control` 通过 Docker socket 在同一台机器上创建 `app-instance`
- 所有容器共用一个 Docker network`nano-instance-edge`
- 测试域名先用 `nip.io`
如果你后面要接 HTTPS、外部 LB、Kubernetes、真实 DNS这份流程仍然适合作为最小可运行基线。
可直接参考这些模板文件:
- [`.env.example`](/home/ivan/xuan/nano_project/.env.example)
- [`auth-portal/src/.env.example`](/home/ivan/xuan/nano_project/auth-portal/src/.env.example)
- [`authz-service/.env.example`](/home/ivan/xuan/nano_project/authz-service/.env.example)
- [`deploy-control/.env.example`](/home/ivan/xuan/nano_project/deploy-control/.env.example)
- [`router-proxy/.env.example`](/home/ivan/xuan/nano_project/router-proxy/.env.example)
注意:
- 这些文件是模板,不会被现有脚本自动加载
- 你可以手动 `export`,或者在 `docker run` 时使用 `--env-file`
## 部署前必须先定好的值
先准备这些值:
- `PROJECT_ROOT`
- 仓库根目录。
- `NANO_NET`
- Docker network 名。
- 推荐固定成 `nano-instance-edge`
- `NANO_DEPLOY_TOKEN`
- `auth-portal` / `authz-service``deploy-control` 时用的 Bearer token。
- `NANO_AUTHZ_INTERNAL_TOKEN`
- AuthZ 内部接口 token。
- `NANO_SERVER_IP`
- 服务器公网 IP`nip.io` 测试使用。
- `NANO_BASE_DOMAIN`
- 实例基域名。
- 测试环境推荐 `<SERVER_IP>.nip.io`
- `NANO_PROVIDER`
- 默认 provider例如 `openai`
- `NANO_MODEL`
- 默认模型,例如 `openai/gpt-5`
- `NANO_API_KEY`
- 默认分发给新实例的 provider API key
- `NANO_API_BASE`
- 可空,自定义 provider base URL
- `NANO_AUTHZ_URL`
- 这个值必须是 `app-instance` 容器能访问到的 AuthZ 地址
- `NANO_OUTLOOK_MCP_URL`
- 可空。
- 如果配置了,所有新创建的 `app-instance` 都会默认带一个 Outlook MCP HTTP 工具配置。
- `NANO_OUTLOOK_MCP_SERVER_ID`
- Outlook MCP 默认 server id。
- 推荐固定成 `outlook_mcp`
- `NANO_DEPLOY_URL`
- `auth-portal``authz-service` 在容器网络里访问 deploy-control 的地址
直接导出一套最小配置:
```bash
export PROJECT_ROOT=/home/ivan/xuan/nano_project
export NANO_NET=nano-instance-edge
export NANO_DEPLOY_TOKEN="$(openssl rand -hex 32)"
export NANO_AUTHZ_INTERNAL_TOKEN="$(openssl rand -hex 32)"
export NANO_SERVER_IP=203.0.113.10
export NANO_BASE_DOMAIN="${NANO_SERVER_IP}.nip.io"
export NANO_PROVIDER=openai
export NANO_MODEL=openai/gpt-5
export NANO_API_KEY='sk-xxxxxxxx'
export NANO_API_BASE=''
export NANO_AUTHZ_URL='http://nano-authz-service:19090'
export NANO_OUTLOOK_MCP_URL=''
export NANO_OUTLOOK_MCP_SERVER_ID='outlook_mcp'
export NANO_DEPLOY_URL='http://nano-deploy-control:8090'
```
## 变量到底是什么
最容易混淆的是下面这几组:
- `DEPLOY_API_TOKEN`
- 调用方带出去的 token。
- `auth-portal``authz-service` 会用它请求 `deploy-control`
- `DEPLOY_CONTROL_API_TOKEN`
- `deploy-control` 服务端校验的 token。
- 它必须和 `DEPLOY_API_TOKEN` 相等。
- `AUTHZ_ISSUER`
- 当前实现里它既是 AuthZ 的 issuer也是新实例要访问的 AuthZ base URL。
- 所以不要乱写 `127.0.0.1`,要写成实例容器能访问到的地址。
- `APP_INSTANCE_PROVIDER`
- 新实例默认 provider。
- `APP_INSTANCE_MODEL`
- 新实例默认模型。
- `APP_INSTANCE_API_KEY`
- 新实例默认 API key。
- `APP_INSTANCE_API_BASE`
- 新实例默认 API base。
- `DEFAULT_AUTHZ_BASE_URL`
- `deploy-control` 在没收到显式 `authz_base_url` 时,给新实例写入的兜底 AuthZ 地址。
当前版本里provider 配置不是从 AuthZ setting 下发的。
它是在创建实例时由 `deploy-control` 写入 `app-instance``config.json`
## 目录持久化
至少保留这几个目录:
- `authz-service/runtime/data`
- `app-instance/runtime`
- `router-proxy/runtime`
建议先创建:
```bash
mkdir -p \
"$PROJECT_ROOT/authz-service/runtime/data" \
"$PROJECT_ROOT/app-instance/runtime/instances" \
"$PROJECT_ROOT/app-instance/runtime/registry" \
"$PROJECT_ROOT/router-proxy/runtime/conf.d"
```
## 1. 构建镜像
先把四个镜像都构建好。虽然 `deploy-control` 在镜像缺失时也能触发构建,但上线前先显式构建更容易排错。
```bash
cd "$PROJECT_ROOT"
docker build -t nano/app-instance:latest app-instance
docker build -t nano/authz-service:latest authz-service
docker build -t nano/deploy-control:latest deploy-control
docker build -t nano/auth-portal:latest auth-portal/src
```
## 2. 创建共享网络
```bash
docker network inspect "$NANO_NET" >/dev/null 2>&1 || docker network create "$NANO_NET"
```
## 3. 启动 router-proxy
```bash
cd "$PROJECT_ROOT"
PROXY_NETWORK_NAME="$NANO_NET" \
PROXY_HTTP_PORT=8088 \
./router-proxy/start-proxy.sh --replace
```
默认对外入口:
- `router-proxy`: `http://<slug>.<base_domain>:8088`
## 4. 启动 authz-service
```bash
docker rm -f nano-authz-service >/dev/null 2>&1 || true
docker run -d \
--name nano-authz-service \
--restart unless-stopped \
--network "$NANO_NET" \
-p 19090:19090 \
-v "$PROJECT_ROOT/authz-service/runtime/data:/var/lib/authz-service/data" \
-e AUTHZ_ISSUER="$NANO_AUTHZ_URL" \
-e AUTHZ_INTERNAL_TOKEN="$NANO_AUTHZ_INTERNAL_TOKEN" \
-e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \
-e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \
nano/authz-service:latest
```
这里最重要的是:
- `AUTHZ_ISSUER` 现在不能只按“外部访问地址”理解
- 它必须是后续 `app-instance` 容器也能访问到的地址
- 这套单机 Docker 方案里直接用 `http://nano-authz-service:19090`
## 5. 启动 deploy-control
`deploy-control` 需要高权限:
- 读写 Docker socket
- 访问 `app-instance/`
- 访问 `router-proxy/`
```bash
docker rm -f nano-deploy-control >/dev/null 2>&1 || true
docker run -d \
--name nano-deploy-control \
--restart unless-stopped \
--network "$NANO_NET" \
-p 8090:8090 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PROJECT_ROOT/app-instance:$PROJECT_ROOT/app-instance" \
-v "$PROJECT_ROOT/router-proxy:$PROJECT_ROOT/router-proxy" \
-e APP_INSTANCE_DIR="$PROJECT_ROOT/app-instance" \
-e ROUTER_PROXY_DIR="$PROJECT_ROOT/router-proxy" \
-e DEPLOY_CONTROL_API_TOKEN="$NANO_DEPLOY_TOKEN" \
-e APP_INSTANCE_IMAGE="nano/app-instance:latest" \
-e APP_INSTANCE_NETWORK_NAME="$NANO_NET" \
-e APP_INSTANCE_PROVIDER="$NANO_PROVIDER" \
-e APP_INSTANCE_MODEL="$NANO_MODEL" \
-e APP_INSTANCE_API_KEY="$NANO_API_KEY" \
-e APP_INSTANCE_API_BASE="$NANO_API_BASE" \
-e DEFAULT_AUTHZ_BASE_URL="$NANO_AUTHZ_URL" \
-e DEFAULT_AUTHZ_OUTLOOK_MCP_URL="$NANO_OUTLOOK_MCP_URL" \
-e DEFAULT_OUTLOOK_MCP_SERVER_ID="$NANO_OUTLOOK_MCP_SERVER_ID" \
-e DEPLOY_PUBLIC_SCHEME="http" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="$NANO_BASE_DOMAIN" \
-e DEPLOY_PUBLIC_PORT="8088" \
-e DEPLOY_AUTO_START_PROXY="1" \
nano/deploy-control:latest
```
这里不要把宿主机目录挂到容器内的另一个短路径,比如 `/app-instance`
原因是 `deploy-control` 会通过挂载进来的 Docker socket 再去创建 `app-instance` 容器;这时传给 Docker 的 bind mount 源路径必须是宿主机真实路径。如果你把宿主机目录映射成容器内短路径,`create-instance.sh` 生成的挂载源就会变成错误路径,最终表现为:
- 注册接口超时
- `app-instance` 容器反复重启
- 日志里出现 `Missing Boardware Genius config: /root/.nanobot/config.json`
当前版本里,新实例的默认大模型配置就是从这里分发的:
- `APP_INSTANCE_PROVIDER`
- `APP_INSTANCE_MODEL`
- `APP_INSTANCE_API_KEY`
- `APP_INSTANCE_API_BASE`
如果 `APP_INSTANCE_API_KEY` 没配,新用户注册时创建实例会直接失败。
## 6. 启动 auth-portal
```bash
docker rm -f nano-auth-portal >/dev/null 2>&1 || true
docker run -d \
--name nano-auth-portal \
--restart unless-stopped \
--network "$NANO_NET" \
-p 3081:3081 \
-e AUTHZ_API_BASE_URL="$NANO_AUTHZ_URL" \
-e DEPLOY_API_BASE_URL="$NANO_DEPLOY_URL" \
-e DEPLOY_API_TOKEN="$NANO_DEPLOY_TOKEN" \
nano/auth-portal:latest
```
当前页面入口:
- `http://<server-ip>:3081`
## 7. 上线前健康检查
先确认四个基础组件都起来了:
```bash
curl http://127.0.0.1:19090/healthz
curl http://127.0.0.1:8090/healthz
curl -I http://127.0.0.1:3081
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
```
再确认 `router-proxy` 在跑:
```bash
docker logs --tail=50 nano-router-proxy
```
## 8. 首次注册验收
打开:
```text
http://<server-ip>:3081/register
```
注册一个新用户后,预期结果是:
1. `auth-portal``authz-service /portal/register`
2. `authz-service``deploy-control /api/instances/register`
3. `deploy-control` 创建一个新的 `app-instance`
4. `app-instance` 回调 AuthZ 完成 backend 身份初始化
5. 浏览器被跳转到该实例自己的 URL
同时你应该能看到:
```bash
cd "$PROJECT_ROOT"
./app-instance/list-instances.sh --json
```
新实例 URL 形如:
```text
http://<instance-slug>.<base-domain>:8088
```
如果你前面用的是:
```text
NANO_BASE_DOMAIN=<server-ip>.nip.io
```
那么实例地址会像:
```text
http://alice.203.0.113.10.nip.io:8088
```
## 9. 常见问题
### 1. 为什么不要把 `AUTHZ_ISSUER` 写成 `http://127.0.0.1:19090`
因为 `app-instance` 容器里访问 `127.0.0.1` 只会打到它自己,不会打到 AuthZ 容器。
在当前实现里,`AUTHZ_ISSUER` 会被直接传给新实例当作 `authz_base_url`
### 2. `DEPLOY_API_TOKEN` 和 `DEPLOY_CONTROL_API_TOKEN` 为什么要一样
因为一个是客户端发出去的 token一个是服务端拿来校验的 token。
### 3. 这些 provider 配置是写到哪里
写到每个实例自己的:
```text
app-instance/runtime/instances/<slug>/nanobot-home/config.json
```
不是写在 AuthZ 的某个 setting 里。
### 4. 现在支持每个用户注册时填自己的 API key 吗
后端请求模型已经支持 `provider/model/api_key/api_base` 字段,但当前 `auth-portal` 页面没有把这些字段暴露出来。
当前上线流程默认是:所有新实例先继承 `deploy-control` 上配置的默认 provider 凭证。
### 5. 现在内置 HTTPS 吗
没有。
当前内置 `router-proxy` 是 HTTP 入口。如果你要公网 HTTPS
- 在外面再放一层 Nginx / Caddy / LB 做 TLS 终止
- 再把流量转给 `router-proxy:8088``auth-portal:3081`
## 10. 仓库结构
```text
/home/ivan/xuan/nano_project
├── README.md
├── app-instance/
├── auth-portal/
├── authz-service/
├── deploy-control/
└── router-proxy/
```
各子目录更细的实现说明见:
- [`app-instance/README.md`](/home/ivan/xuan/nano_project/app-instance/README.md)
- [`auth-portal/src/README.md`](/home/ivan/xuan/nano_project/auth-portal/src/README.md)
- [`authz-service/README.md`](/home/ivan/xuan/nano_project/authz-service/README.md)
- [`deploy-control/README.md`](/home/ivan/xuan/nano_project/deploy-control/README.md)
- [`router-proxy/README.md`](/home/ivan/xuan/nano_project/router-proxy/README.md)