# Beaver Project 本机部署指南

这份文档用于在一台 Linux 或 WSL2 Ubuntu 机器上跑完整链路：

- `auth-portal`
- `authz-service`
- `deploy-control`
- `router-proxy`
- 自动创建出来的 `app-instance`

目标结果：

- 浏览器能打开 `http://127.0.0.1:3081/register`
- 注册账号后自动创建专属实例
- 浏览器跳转到 `http://<slug>.127.0.0.1.nip.io:8088`

如果你只单独启动某个前端页面，页面可以打开，但注册、登录、创建实例这些动作不一定能通。

## 0. 前提

推荐环境：

- Linux
- WSL2 Ubuntu

需要工具：

- `docker`
- `git`
- `curl`
- `openssl`
- `python3`

检查：

```bash
docker --version
docker ps
python3 --version
openssl version
curl --version
```

如果 `docker ps` 报错，先启动 Docker。

## 1. 进入项目根目录

```bash
cd /home/ivan/xuan/beaver_project
pwd
```

预期目录：

```text
/home/ivan/xuan/beaver_project
```

## 2. 准备本机测试变量

本机测试推荐用 `127.0.0.1.nip.io`。例如：

```text
alice.127.0.0.1.nip.io -> 127.0.0.1
```

这样 `router-proxy` 可以按子域名区分不同实例。

直接执行：

```bash
export PROJECT_ROOT=/home/ivan/xuan/beaver_project
export BEAVER_NET=beaver-instance-edge
export BEAVER_PROXY_CONTAINER_NAME=beaver-router-proxy

export BEAVER_DEPLOY_TOKEN="$(openssl rand -hex 32)"
export BEAVER_AUTHZ_INTERNAL_TOKEN="$(openssl rand -hex 32)"

export BEAVER_BASE_DOMAIN=127.0.0.1.nip.io

export BEAVER_AUTHZ_URL='http://beaver-authz-service:19090'
export BEAVER_DEPLOY_URL='http://beaver-deploy-control:8090'

export BEAVER_OUTLOOK_MCP_URL=''
export BEAVER_OUTLOOK_MCP_SERVER_ID='outlook_mcp'
```

变量说明：

| 变量 | 作用 |
| --- | --- |
| `PROJECT_ROOT` | 仓库根目录 |
| `BEAVER_NET` | 所有容器共用的 Docker network |
| `BEAVER_DEPLOY_TOKEN` | `auth-portal` / `authz-service` 调 `deploy-control` 的 token |
| `BEAVER_AUTHZ_INTERNAL_TOKEN` | AuthZ 内部接口 token |
| `BEAVER_BASE_DOMAIN` | 新实例的基域名 |
| `BEAVER_AUTHZ_URL` | 容器网络内访问 AuthZ 的地址 |
| `BEAVER_DEPLOY_URL` | 容器网络内访问 deploy-control 的地址 |
| `BEAVER_OUTLOOK_MCP_URL` | 可选 Outlook MCP HTTP 地址 |
| `BEAVER_OUTLOOK_MCP_SERVER_ID` | Outlook MCP server id，默认 `outlook_mcp` |

`BEAVER_AUTHZ_URL` 和 `BEAVER_DEPLOY_URL` 必须带协议头。正确写法：

```text
http://beaver-authz-service:19090
http://beaver-deploy-control:8090
```

错误写法：

```text
beaver-authz-service:19090
beaver-deploy-control:8090
127.0.0.1:19090
127.0.0.1:8090
```

如果漏了 `http://`，注册页可能报：

```text
502: Request URL is missing an 'http://' or 'https://' protocol.
```

如果你改了 shell 里的变量，已经运行的容器不会自动更新。改完这些变量后，至少要重建：

- `beaver-authz-service`
- `beaver-auth-portal`

## 3. 创建运行目录

```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"
```

这些目录保存：

- AuthZ 数据
- 实例注册表
- 每个用户实例的配置和数据
- `router-proxy` 生成的路由文件

## 4. 构建镜像

```bash
cd "$PROJECT_ROOT"

docker build -t beaver/app-instance:latest app-instance
docker build -t beaver/authz-service:latest authz-service
docker build -t beaver/deploy-control:latest deploy-control
docker build -t beaver/auth-portal:latest auth-portal/src
```

如果某个镜像构建失败，先修构建错误，不要继续往下跑。

## 5. 创建共享 Docker 网络

```bash
docker network inspect "$BEAVER_NET" >/dev/null 2>&1 || docker network create "$BEAVER_NET"
docker network ls | grep "$BEAVER_NET"
```

预期能看到：

```text
beaver-instance-edge
```

## 6. 启动 router-proxy

```bash
cd "$PROJECT_ROOT"

PROXY_NETWORK_NAME="$BEAVER_NET" \
PROXY_CONTAINER_NAME="$BEAVER_PROXY_CONTAINER_NAME" \
PROXY_HTTP_PORT=8088 \
./router-proxy/start-proxy.sh --replace
```

实例统一入口：

```text
http://<slug>.127.0.0.1.nip.io:8088
```

示例：

```text
http://alice.127.0.0.1.nip.io:8088
```

## 7. 启动 authz-service

```bash
docker rm -f beaver-authz-service >/dev/null 2>&1 || true

docker run -d \
  --name beaver-authz-service \
  --restart unless-stopped \
  --network "$BEAVER_NET" \
  -p 19090:19090 \
  -v "$PROJECT_ROOT/authz-service/runtime/data:/var/lib/authz-service/data" \
  -e AUTHZ_ISSUER="$BEAVER_AUTHZ_URL" \
  -e AUTHZ_INTERNAL_TOKEN="$BEAVER_AUTHZ_INTERNAL_TOKEN" \
  -e DEPLOY_API_BASE_URL="$BEAVER_DEPLOY_URL" \
  -e DEPLOY_API_TOKEN="$BEAVER_DEPLOY_TOKEN" \
  beaver/authz-service:latest
```

重点：

- `AUTHZ_ISSUER` 在当前部署里要写 `http://beaver-authz-service:19090`
- 不要写 `http://127.0.0.1:19090`
- 新创建的 `app-instance` 容器要通过 Docker network 访问 AuthZ

检查关键环境变量：

```bash
docker inspect beaver-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' \
  | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)='
```

## 8. 启动 deploy-control

`deploy-control` 会挂载 Docker socket，再创建新的 `app-instance` 容器。这里最容易错的是路径挂载：

- 要把宿主机真实路径按原路径挂进容器。
- 不要把 `app-instance` 挂到容器里的 `/app-instance` 这种短路径。
- `APP_INSTANCE_DIR` 和 `ROUTER_PROXY_DIR` 要和挂载路径一致。

直接执行：

```bash
docker rm -f beaver-deploy-control >/dev/null 2>&1 || true

docker run -d \
  --name beaver-deploy-control \
  --restart unless-stopped \
  --network "$BEAVER_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 PROXY_CONTAINER_NAME="$BEAVER_PROXY_CONTAINER_NAME" \
  -e PROXY_NETWORK_NAME="$BEAVER_NET" \
  -e DEPLOY_CONTROL_API_TOKEN="$BEAVER_DEPLOY_TOKEN" \
  -e APP_INSTANCE_IMAGE="beaver/app-instance:latest" \
  -e APP_INSTANCE_NETWORK_NAME="$BEAVER_NET" \
  -e DEFAULT_AUTHZ_BASE_URL="$BEAVER_AUTHZ_URL" \
  -e DEFAULT_AUTHZ_OUTLOOK_MCP_URL="$BEAVER_OUTLOOK_MCP_URL" \
  -e DEFAULT_OUTLOOK_MCP_SERVER_ID="$BEAVER_OUTLOOK_MCP_SERVER_ID" \
  -e DEPLOY_PUBLIC_SCHEME="http" \
  -e DEPLOY_PUBLIC_BASE_DOMAIN="$BEAVER_BASE_DOMAIN" \
  -e DEPLOY_PUBLIC_PORT="8088" \
  -e DEPLOY_AUTO_START_PROXY="1" \
  beaver/deploy-control:latest
```

当前版本创建实例时会传 `--skip-provider-config`，也就是先不写 provider、model 或 API key。注册成功后，`auth-portal` 会进入模型配置引导页，再调用 `deploy-control /api/instances/configure-provider` 写入该实例的 `config.json` 并重启容器。

## 9. 启动 auth-portal

```bash
docker rm -f beaver-auth-portal >/dev/null 2>&1 || true

docker run -d \
  --name beaver-auth-portal \
  --restart unless-stopped \
  --network "$BEAVER_NET" \
  -p 3081:3081 \
  -e AUTHZ_API_BASE_URL="$BEAVER_AUTHZ_URL" \
  -e DEPLOY_API_BASE_URL="$BEAVER_DEPLOY_URL" \
  -e DEPLOY_API_TOKEN="$BEAVER_DEPLOY_TOKEN" \
  beaver/auth-portal:latest
```

检查关键环境变量：

```bash
docker inspect beaver-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' \
  | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)='
```

## 10. 健康检查

```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}}'
docker logs --tail=50 beaver-router-proxy
```

至少应该看到这些容器：

- `beaver-authz-service`
- `beaver-deploy-control`
- `beaver-auth-portal`
- `beaver-router-proxy`

## 11. 浏览器首次测试

打开：

```text
http://127.0.0.1:3081/register
```

预期流程：

1. 注册一个新账号。
2. Portal 创建不含模型凭证的实例。
3. 页面进入模型配置引导。
4. 填 provider、model、API key 后确认，或暂时跳过。
5. 浏览器跳到你的实例地址。

跳转目标示例：

```text
http://alice.127.0.0.1.nip.io:8088
```

## 12. 确认实例已创建

```bash
cd "$PROJECT_ROOT/app-instance"
./list-instances.sh
./list-instances.sh --json

docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance
```

注册表里应包含：

- `instance_id`
- `instance_slug`
- `container_name`
- `public_url`
- `instance_host`

## 13. 只看 auth-portal 页面

如果只想看 Portal 页面，不跑全链路：

```bash
cd /home/ivan/xuan/beaver_project/auth-portal/src
npm install
npm run dev
```

打开：

```text
http://127.0.0.1:3081
```

注意：这只能看页面。注册、登录、创建实例仍依赖 `authz-service` 和 `deploy-control`。

## 14. 常用排错命令

```bash
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

docker logs --tail=100 beaver-authz-service
docker logs --tail=100 beaver-deploy-control
docker logs --tail=100 beaver-auth-portal
docker logs --tail=100 beaver-router-proxy

curl http://127.0.0.1:19090/healthz
curl http://127.0.0.1:8090/healthz
curl -I http://127.0.0.1:3081
```

实例创建失败时再看：

```bash
cd "$PROJECT_ROOT/app-instance"
./list-instances.sh --json
docker ps --format 'table {{.Names}}\t{{.Status}}' | grep app-instance
```

排查 URL 变量：

```bash
docker inspect beaver-authz-service --format '{{range .Config.Env}}{{println .}}{{end}}' \
  | egrep '^(AUTHZ_ISSUER|DEPLOY_API_BASE_URL)='

docker inspect beaver-auth-portal --format '{{range .Config.Env}}{{println .}}{{end}}' \
  | egrep '^(AUTHZ_API_BASE_URL|DEPLOY_API_BASE_URL)='
```

它们都必须是完整 URL，不能是空字符串，也不能是裸 `host:port`。

## 15. 常见问题

### 注册页报 URL 缺少协议

现象：

```text
502: Request URL is missing an 'http://' or 'https://' protocol.
```

优先检查：

- `beaver-authz-service` 里的 `DEPLOY_API_BASE_URL`
- `beaver-auth-portal` 里的 `AUTHZ_API_BASE_URL`
- `beaver-auth-portal` 里的 `DEPLOY_API_BASE_URL`

如果你只是改了当前 shell 变量，但没有重建容器，旧值还会继续生效。

### `AUTHZ_ISSUER` 写成了 `127.0.0.1`

错误：

```text
http://127.0.0.1:19090
```

正确：

```text
http://beaver-authz-service:19090
```

原因是 `app-instance` 容器里的 `127.0.0.1` 指向它自己。

### deploy-control 路径挂载写错

错误思路：

```text
宿主机 app-instance -> 容器 /app-instance
```

正确思路：

```text
$PROJECT_ROOT/app-instance -> $PROJECT_ROOT/app-instance
$PROJECT_ROOT/router-proxy -> $PROJECT_ROOT/router-proxy
```

因为 `deploy-control` 会通过宿主机 Docker socket 再创建新容器，传给 Docker 的 bind mount 源路径必须是宿主机真实路径。

### `nip.io` 解析失败

检查：

```bash
ping 127.0.0.1.nip.io
```

如果本地网络屏蔽了 `nip.io`，子域名测试会失败。可以临时换成本机 hosts 或正式域名。

### 端口被占用

默认端口：

- `3081`
- `8088`
- `8090`
- `19090`

检查：

```bash
ss -ltnp | grep -E '3081|8088|8090|19090'
```

## 16. 重新部署基础容器

只重建基础四个容器：

```bash
docker rm -f \
  beaver-auth-portal \
  beaver-authz-service \
  beaver-deploy-control \
  beaver-router-proxy 2>/dev/null || true
```

这不会自动删除实例数据。如果你还需要旧账号、旧实例或模型配置，不要删除 `runtime/` 目录。
