# Docker / 反代架构说明

本文记录当前项目在 `tohka` 这类宿主机上的推荐部署结构，以及为什么会同时出现：

- 宿主机层的 **Caddy**
- `admin` 容器内的 **Nginx**

## 1. 总体分层

推荐生产结构：

```text
Internet
  -> Host Caddy (:80 / :443)
    -> frontend container (Astro SSR / Node :4321)
    -> admin container (Nginx :80, 静态 SPA)
    -> backend container (Loco.rs API :5150)
    -> backend-worker container (Loco.rs worker / Redis queue)
```

职责划分：

- **Host Caddy**
  - 统一接收公网流量
  - 处理域名、HTTPS、证书续签
  - 反向代理到各个内部容器
- **frontend**
  - Astro SSR(Node) 应用
  - 不是纯静态站，所以容器内直接运行 Node 服务即可
- **admin**
  - React/Vite 打包后的纯静态 SPA
  - 容器内使用 Nginx 提供静态文件
  - 生产推荐前面接 TinyAuth / Pocket ID 做 SSO
- **backend**
  - API、后台鉴权、审计、版本历史、订阅投递
- **backend-worker**
  - 消费 Redis 队列
  - 负责通知异步投递、失败重试、digest 任务触发后的实际发送

## 2. 为什么上层已经有 Caddy，admin 容器里还要 Nginx？

这两层并不冲突，职责不同：

- **Caddy** 是入口网关
- **Nginx** 是 admin 容器内部的静态文件服务器

也就是说：

```text
Browser
  -> Caddy
    -> admin nginx
      -> /usr/share/nginx/html
```

这样做的好处：

- admin 镜像本身自带可用的静态文件服务能力
- 宿主机层仍然保留统一的域名 / HTTPS / 路由管理
- admin 作为独立前端，可以单独构建、单独发布

## 2.2 为什么现在多了 `backend-worker`？

因为当前通知系统已经改成：

```text
backend (web)
  -> 写入 notification_deliveries
  -> enqueue 到 Redis
backend-worker
  -> 消费队列
  -> 发送 email / webhook / discord / telegram / ntfy
```

如果只启动 `backend` 而没有 `backend-worker`，通知会入队但没人消费。

补充说明：

- `backend-worker` 目前主要消费 Redis 队列里的通知相关任务。
- AI 索引重建会直接在 `backend` 进程本地启动，这样创建任务后会立即进入执行，不再依赖独立 worker 消费。

## 2.1 推荐的后台认证链路

当前最推荐：

```text
Browser
  -> Caddy (import tinyauth)
    -> TinyAuth
      -> Pocket ID (OIDC)
        -> admin nginx / backend API
```

关键点：

- admin 页面和它调用的 `/api/*` 建议走 **同一个受保护后台域名**
- Caddy 使用 `forward_auth`
- backend 开启 `TERMI_ADMIN_TRUST_PROXY_AUTH=true`
- 生产推荐再配 `TERMI_ADMIN_PROXY_SHARED_SECRET=<随机长字符串>`
- Caddy 在 `/api/*` 反代到 backend 时补 `X-Termi-Proxy-Secret`
- backend 读取 TinyAuth 转发的 `Remote-User / Remote-Email / Remote-Groups`
- 本地开发可以保留 `TERMI_ADMIN_LOCAL_LOGIN_ENABLED=true`

## 3. 为什么 frontend 不使用同样的 Nginx 模式？

因为当前 `frontend` 是 **Astro SSR**：

- `output: 'server'`
- `@astrojs/node` standalone

它需要在请求期执行服务端逻辑，因此更适合：

```text
Caddy -> frontend Node server
```

而不是先打成纯静态文件再由 Nginx 托管。

## 4. admin 容器内 Nginx 当前负责什么？

当前 `admin/nginx.conf` 主要负责：

- SPA fallback：`try_files ... /index.html`
- `assets/` 长缓存（hash 资源可 `immutable`）
- `index.html` / `runtime-config.js` 禁缓存，避免配置或入口文件陈旧
- `gzip` 压缩
- 基础安全响应头
- `/healthz` 健康检查入口

### 为什么 `runtime-config.js` 要禁缓存？

因为 admin 现在支持运行时环境变量注入，例如：

- `ADMIN_API_BASE_URL`
- `ADMIN_FRONTEND_BASE_URL`

容器启动时会生成 `runtime-config.js`。  
如果它被强缓存，改完环境变量重启容器后，浏览器可能仍然读到旧地址。

## 5. 为什么没有在 admin 容器里启用 Brotli？

当前基础镜像是官方 `nginx:alpine`。  
这个镜像默认不一定带 Brotli 模块，所以这里先启用通用的 `gzip`。

如果后续确实需要 Brotli，有两个常见做法：

- 让宿主机层的 Caddy 统一负责压缩
- 改用带 Brotli 模块的自定义 Nginx 镜像

对当前项目而言，优先让 **宿主机 Caddy 做统一公网入口**，admin 容器内部只负责稳妥地提供静态文件，是更简单的方案。

## 6. 推荐的 tohka 思路

如果 `tohka` 上已经有一个统一的大 Caddyfile，推荐继续保持：

- Caddy 统一暴露 `80/443`
- `frontend/admin/backend` 只走内网端口
- 不把数据库 / Redis 直接暴露到公网
- backend 如果启用了代理 SSO，不要再把 `:5150` 直接开放给公网

仓库里已经额外提供：

- `deploy/docker/compose.tohka.override.yml`

用它叠加 `compose.package.yml` 后，会把：

- `frontend:4321`
- `admin:4322`
- `backend:5150`

都只绑定到 `127.0.0.1`

## 7. 当前和配置相关的关键文件

- 宿主机入口反代：`tohka` 上的大 Caddyfile
- admin 静态服务：`admin/nginx.conf`
- admin 镜像：`admin/Dockerfile`
- Caddy 参考模板：`deploy/caddy/Caddyfile.tohka.example`
- compose 示例：`deploy/docker/compose.package.yml`
- tohka override：`deploy/docker/compose.tohka.override.yml`
- OIDC / Pocket ID 落地：`deploy/docker/TOHKA_POCKET_ID.md`
- 运行时环境示例：`deploy/docker/.env.example`

## 8. Caddy 路由推荐

默认更推荐：

- `blog.init.cool` -> frontend
- `admin.blog.init.cool` -> admin + backend(`/api/*`)
- `api.blog.init.cool` -> backend

这样最省心，也最不容易碰到路径前缀、资源基路径、Cookie Path 等问题。

如果一定要用：

- `init.cool/admin`
- `init.cool/api`

也可以，但 `admin` 需要在构建时设置：

- `VITE_ADMIN_BASENAME=/admin`

对应模板见：

- `deploy/caddy/Caddyfile.tohka.example`

## 9. 备份 / 恢复入口

当前仓库内已经补了：

- `deploy/scripts/backup/backup-postgres.sh`
- `deploy/scripts/backup/backup-media.sh`
- `deploy/scripts/backup/restore-postgres.sh`
- `deploy/scripts/backup/restore-media.sh`
- `deploy/docker/BACKUP_AND_RECOVERY.md`

建议把这些接进生产上的 cron / systemd timer，而不是只停留在仓库里。

## 10. 健康检查与启动顺序

当前推荐闭环：

- backend 启动时先自动跑 migration
- backend 提供 `/healthz`
- frontend 提供 `/healthz`
- admin 由 Nginx 提供 `/healthz`
- compose 中 frontend / admin / backend-worker 都依赖 backend healthy
