# app-instance

单实例应用单元：

- 一个 Docker 容器里同时运行前端、后端和 Nginx 反代
- 前端走 `/`
- 后端 API 走 `/api`
- WebSocket 走 `/ws`

## 关键文件

- `Dockerfile`
  - 统一镜像构建入口
- `entrypoint.sh`
  - 容器内启动前端、后端、Nginx
- `create-instance.sh`
  - 创建实例目录、生成配置、启动容器、写注册表
- `remove-instance.sh`
  - 删除容器、移除注册表、可选清理实例目录
- `list-instances.sh`
  - 查看当前注册实例
- `instance-registry.py`
  - 维护 `runtime/registry/instances.json`

## 注册表

默认注册表路径：

```text
runtime/registry/instances.json
```

每条记录至少包含：

- `instance_id`
- `instance_slug`
- `container_name`
- `host_port`
- `public_url`
- `instance_root`
- `image_name`

## 常用命令

### 1. 构建镜像

```bash
docker build -t beaver/app-instance:latest .
```

### 2. 创建实例

```bash
./create-instance.sh \
  --image beaver/app-instance:latest \
  --instance-id demo-001 \
  --auth-username admin \
  --auth-password 123456 \
  --api-key 'your-api-key'
```

可选参数：

- `--host-port`
- `--public-url`
- `--username`
- `--email`
- `--instance-host`
- `--authz-base-url`
- `--backend-id`
- `--client-id`
- `--client-secret`
- `--network`
- `--host-bind-ip`
- `--initial-skills-dir`
- `--skip-initial-skills`
- `--build`
- `--replace`

### 3. 查看实例

```bash
./list-instances.sh
./list-instances.sh --json
```

### 4. 删除实例

```bash
./remove-instance.sh --instance-id demo-001
```

如果要把实例目录也一并清掉：

```bash
./remove-instance.sh --instance-id demo-001 --purge-data
```

## 目录约定

默认实例数据目录：

```text
runtime/instances/<instance-slug>/
```

其中会生成：

```text
runtime/instances/<instance-slug>/
└── beaver-home
    ├── config.json
    ├── web_auth_users.json
    └── workspace/
```

这个目录是单用户 sandbox 的配置与数据边界。容器内会把它挂到：

```text
/root/.beaver/
```

并设置：

```text
BEAVER_CONFIG_PATH=/root/.beaver/config.json
BEAVER_WORKSPACE=/root/.beaver/workspace
```

所以模型 `provider/api_key/api_base/model` 配一次即可，Web / channel 请求不需要、也不应该携带 API Key。

`create-instance.sh` 默认会把仓库根目录的 `skills/` 非覆盖式复制到实例 workspace，并把同一个目录只读挂载到实例容器的 `/opt/app/initial-skills`。`entrypoint.sh` 每次启动都会用该目录补齐缺失的 published 初始 skills；已有 skill 目录不会被覆盖，index 只做并集追加。

## 当前状态

这层已经支持：

- 统一镜像构建
- 镜像内安装并启动新的 `beaver` 后端
- 实例创建
- 实例删除
- 实例列表
- 基于注册表的端口分配
- 为 deploy-control / router-proxy 记录用户名和实例 host

## 生产注意

- 实例容器的宿主机端口默认只绑定 `127.0.0.1`
- 外部访问应统一走 `router-proxy`
- 如果你确实要把单个实例端口直接暴露到公网，再显式传 `--host-bind-ip 0.0.0.0`
- 使用共享 `external-connector` sidecar 时，每个实例容器都必须带自己的内部回调地址：
  `EXTERNAL_CONNECTOR_CALLBACK_BASE_URL=http://<app-instance-container-name>:8080`
- 通过 `create-instance.sh --network <docker-network>` 创建实例时，脚本会默认使用
  `http://<container-name>:8080` 作为回调地址；生产部署也可以用
  `--external-connector-callback-base-url <url>` 显式覆盖
- `BEAVER_BRIDGE_BASE_URL` 只作为 sidecar 的旧连接或兜底地址；多实例部署不能依赖它路由所有入站事件

下一步可以继续接：

- portal 调用创建实例
- URL 分配和反向代理
- 实例续期 / 停用 / 启用
