# Beaver Project

`Beaver Project` 是一套单机 Docker 部署的多实例运行环境：

- 用户先进入独立的 `auth-portal` 完成注册或登录。
- 注册会触发 `authz-service` 调用 `deploy-control`。
- `deploy-control` 在同一台机器上创建一个独立的 `app-instance` 容器。
- `router-proxy` 按实例域名把流量转发到对应容器。

当前推荐的最小部署方式是一台 Linux / WSL2 Ubuntu 机器加 Docker。生产域名和 HTTPS 可以放在项目外层的 Nginx、Caddy、Traefik 或云负载均衡上。

## 组件

| 目录 | 职责 | 默认端口 |
| --- | --- | --- |
| `auth-portal/` | 用户登录、注册、模型配置引导入口 | `3081` |
| `authz-service/` | AuthZ 服务，负责账号和 backend 身份编排 | `19090` |
| `deploy-control/` | 部署控制面，调用 Docker 创建和管理实例 | `8090` |
| `router-proxy/` | 统一实例入口代理，按 Host 分发到实例容器 | `8088` |
| `app-instance/` | 单用户运行实例，容器内包含前端、后端和 Nginx | 容器内 `8080` |

公网环境通常只暴露：

- `auth-portal`: `3081`，或外层代理后的 `https://portal.example.com`
- `router-proxy`: `8088`，或外层代理后的 `https://<slug>.apps.example.com`

不要直接把 `deploy-control:8090` 和 `authz-service:19090` 暴露到公网。

## 请求链路

注册：

```text
Browser
  -> auth-portal
  -> authz-service POST /portal/register
  -> deploy-control POST /api/instances/register
  -> app-instance/create-instance.sh
  -> app-instance POST /api/auth/register
  -> authz-service /oauth/register or /backends/register
  -> auth-portal provider onboarding
  -> deploy-control POST /api/instances/configure-provider
```

登录：

```text
Browser
  -> auth-portal
  -> deploy-control POST /api/instances/resolve
  -> app-instance POST /api/auth/login
  -> app-instance frontend URL
```

## 快速开始

本机完整流程见：

- [部署指南.md](./部署指南.md)

域名、HTTPS、公网反向代理说明见：

- [域名配置指引.md](./域名配置指引.md)

最小配置变量：

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

启动顺序：

1. 创建运行目录。
2. 构建四个镜像。
3. 创建共享 Docker network。
4. 启动 `router-proxy`。
5. 启动 `authz-service`。
6. 启动 `deploy-control`。
7. 启动 `auth-portal`。
8. 打开 `http://127.0.0.1:3081/register` 测试注册。

## 关键配置关系

`DEPLOY_API_TOKEN` 和 `DEPLOY_CONTROL_API_TOKEN` 必须相等：

- `auth-portal` / `authz-service` 用 `DEPLOY_API_TOKEN` 请求 `deploy-control`。
- `deploy-control` 用 `DEPLOY_CONTROL_API_TOKEN` 校验请求。

`AUTHZ_ISSUER` 在这套单机部署里要写容器网络地址：

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

不要写成 `http://127.0.0.1:19090`，因为新创建的 `app-instance` 容器里的 `127.0.0.1` 指向它自己，不是 AuthZ 容器。

`DEPLOY_PUBLIC_*` 决定新实例展示给用户的 URL：

```bash
DEPLOY_PUBLIC_SCHEME=http
DEPLOY_PUBLIC_BASE_DOMAIN=127.0.0.1.nip.io
DEPLOY_PUBLIC_PORT=8088
```

本机测试时实例 URL 形如：

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

正式 HTTPS 域名通常改成：

```bash
DEPLOY_PUBLIC_SCHEME=https
DEPLOY_PUBLIC_BASE_DOMAIN=apps.example.com
DEPLOY_PUBLIC_PORT=443
```

实例 URL 形如：

```text
https://alice.apps.example.com
```

前提是你已经在项目外层把 `*.apps.example.com` 的 `80/443` 流量转发到 `router-proxy:8088`。

## 模型配置方式

当前版本不会在注册创建实例时写入模型 provider、model 或 API key。

流程是：

1. 注册先创建一个不含模型凭证的实例。
2. `auth-portal` 进入模型配置引导页。
3. 用户确认后，Portal 调用 `deploy-control /api/instances/configure-provider`。
4. `deploy-control` 写入该实例的 `config.json` 并重启对应容器。

如果用户跳过引导，实例仍会创建成功，但后续需要在实例内补齐 provider 配置后才能正常调用模型。

## 持久化目录

至少保留：

```text
authz-service/runtime/data
app-instance/runtime/instances
app-instance/runtime/registry
router-proxy/runtime/conf.d
```

不要在需要保留账号、实例或配置时删除这些目录。

## 模板文件

可参考这些环境变量模板：

- [`.env.example`](./.env.example)
- [`auth-portal/src/.env.example`](./auth-portal/src/.env.example)
- [`authz-service/.env.example`](./authz-service/.env.example)
- [`deploy-control/.env.example`](./deploy-control/.env.example)
- [`router-proxy/.env.example`](./router-proxy/.env.example)

这些模板不会被脚本自动加载。你可以手动 `export`，也可以在 `docker run` 时使用 `--env-file`。

## 子项目文档

- [`app-instance/README.md`](./app-instance/README.md)
- [`auth-portal/src/README.md`](./auth-portal/src/README.md)
- [`authz-service/README.md`](./authz-service/README.md)
- [`deploy-control/README.md`](./deploy-control/README.md)
- [`router-proxy/README.md`](./router-proxy/README.md)
