feat: ship blog platform admin and deploy stack
This commit is contained in:
62
deploy/docker/.env.example
Normal file
62
deploy/docker/.env.example
Normal file
@@ -0,0 +1,62 @@
|
||||
# Compose runtime variables (package image deployment)
|
||||
BACKEND_PORT=5150
|
||||
FRONTEND_PORT=4321
|
||||
ADMIN_PORT=4322
|
||||
|
||||
# frontend SSR 服务端访问 backend 用这个内部地址(compose 默认可直接使用)
|
||||
INTERNAL_API_BASE_URL=http://backend:5150/api
|
||||
|
||||
# 浏览器里评论 / AI 问答 / 搜索等请求优先读取这个公开 API 地址。
|
||||
# 如果留空,frontend 会在生产环境按“当前访问主机 + :5150/api”回退。
|
||||
# 走反向代理时建议显式设置,例如:
|
||||
# PUBLIC_API_BASE_URL=https://your-frontend.example.com/api
|
||||
PUBLIC_API_BASE_URL=
|
||||
|
||||
# 前台 /_img 图片优化端点默认只放行“当前站点同域”图片。
|
||||
# 如果你的文章封面或对象存储图片来自额外 CDN / R2 公网域名,
|
||||
# 可以在这里填逗号分隔的 host 列表,例如:
|
||||
# PUBLIC_IMAGE_ALLOWED_HOSTS=cdn.example.com,pub-xxxx.r2.dev
|
||||
PUBLIC_IMAGE_ALLOWED_HOSTS=
|
||||
|
||||
# admin 浏览器请求 backend API 优先读取这个公开地址。
|
||||
# 如果留空,admin 会在生产环境按“当前访问主机 + :5150”回退。
|
||||
# 如果你采用推荐方案(admin 域名同域转发 /api 到 backend),
|
||||
# 建议直接填后台域名 origin,例如:
|
||||
# ADMIN_API_BASE_URL=https://admin.example.com
|
||||
ADMIN_API_BASE_URL=
|
||||
|
||||
# admin 页面里的“打开前台 / AI 问答 / 文章预览”链接优先读取这个运行时变量。
|
||||
# 如果你不是直接把前台暴露在 http://<host>:4321,而是走独立域名 / HTTPS / 反向代理,
|
||||
# 建议设置为正式前台地址,例如:
|
||||
# ADMIN_FRONTEND_BASE_URL=https://your-frontend.example.com
|
||||
ADMIN_FRONTEND_BASE_URL=
|
||||
|
||||
APP_BASE_URL=http://localhost:5150
|
||||
DATABASE_URL=postgres://<user>:<password>@<external-db-host>:5432/termi_api
|
||||
REDIS_URL=redis://<external-redis-host>:6379
|
||||
JWT_SECRET=change-me-before-production
|
||||
JWT_EXPIRATION_SECONDS=604800
|
||||
RUST_LOG=info
|
||||
|
||||
# 邮件确认 / 通知投递需要 SMTP
|
||||
SMTP_ENABLE=false
|
||||
SMTP_HOST=localhost
|
||||
SMTP_PORT=1025
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_HELLO_NAME=
|
||||
|
||||
# 启用 TinyAuth / Pocket ID / Caddy forward_auth 时建议:
|
||||
# - TERMI_ADMIN_TRUST_PROXY_AUTH=true
|
||||
# - TERMI_ADMIN_LOCAL_LOGIN_ENABLED=false
|
||||
# - 额外配置一个共享密钥,并在 Caddy 转发 /api 到 backend 时附带:
|
||||
# X-Termi-Proxy-Secret: {$TERMI_ADMIN_PROXY_SHARED_SECRET}
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH=false
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED=true
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET=
|
||||
|
||||
# Optional: override package tags if needed
|
||||
BACKEND_IMAGE=git.init.cool/cool/termi-astro-backend:latest
|
||||
FRONTEND_IMAGE=git.init.cool/cool/termi-astro-frontend:latest
|
||||
ADMIN_IMAGE=git.init.cool/cool/termi-astro-admin:latest
|
||||
224
deploy/docker/ARCHITECTURE.md
Normal file
224
deploy/docker/ARCHITECTURE.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 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`,通知会入队但没人消费。
|
||||
|
||||
## 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-markdown.sh`
|
||||
- `deploy/scripts/backup/backup-media.sh`
|
||||
- `deploy/scripts/backup/restore-postgres.sh`
|
||||
- `deploy/scripts/backup/restore-markdown.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
|
||||
148
deploy/docker/BACKUP_AND_RECOVERY.md
Normal file
148
deploy/docker/BACKUP_AND_RECOVERY.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 备份与恢复说明
|
||||
|
||||
这套博客现在已经有:
|
||||
|
||||
- PostgreSQL 数据库
|
||||
- Markdown 原文内容
|
||||
- 媒体文件 / 对象存储
|
||||
- 版本历史 / 审计日志 / 订阅数据
|
||||
|
||||
所以生产上最重要的不是再多一两个功能,而是**出事后能不能快速恢复**。
|
||||
|
||||
## 1. 建议的最小备份策略
|
||||
|
||||
### PostgreSQL
|
||||
- **频率**:每天至少 1 次;高频站点建议每 6~12 小时 1 次
|
||||
- **工具**:`pg_dump --format=custom`
|
||||
- **脚本**:`deploy/scripts/backup/backup-postgres.sh`
|
||||
|
||||
### Markdown 原文
|
||||
- **频率**:每次发布后 + 每天定时 1 次
|
||||
- **脚本**:`deploy/scripts/backup/backup-markdown.sh`
|
||||
- **原因**:Markdown 是内容源,恢复速度最快
|
||||
|
||||
### 媒体文件
|
||||
- 如果是本地目录:打包归档
|
||||
- 如果是 R2 / S3 / MinIO:定时 `aws s3 sync`
|
||||
- **脚本**:`deploy/scripts/backup/backup-media.sh`
|
||||
|
||||
## 2. 一键脚本
|
||||
|
||||
```bash
|
||||
# 全量备份
|
||||
./deploy/scripts/backup/backup-all.sh
|
||||
|
||||
# 单独备份数据库
|
||||
DATABASE_URL=postgres://... ./deploy/scripts/backup/backup-postgres.sh
|
||||
|
||||
# 单独备份 Markdown
|
||||
MARKDOWN_SOURCE_DIR=./backend/content/posts ./deploy/scripts/backup/backup-markdown.sh
|
||||
|
||||
# 单独备份媒体(本地目录)
|
||||
MEDIA_SOURCE_DIR=./uploads ./deploy/scripts/backup/backup-media.sh
|
||||
|
||||
# 单独备份媒体(R2 / S3)
|
||||
MEDIA_S3_SOURCE=s3://bucket-name ./deploy/scripts/backup/backup-media.sh
|
||||
```
|
||||
|
||||
## 3. 恢复步骤
|
||||
|
||||
### 恢复 PostgreSQL
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgres://... ./deploy/scripts/backup/restore-postgres.sh ./backups/postgres/latest.dump
|
||||
```
|
||||
|
||||
### 恢复 Markdown
|
||||
|
||||
```bash
|
||||
MARKDOWN_TARGET_DIR=./backend/content/posts ./deploy/scripts/backup/restore-markdown.sh ./backups/markdown/latest.tar.gz
|
||||
```
|
||||
|
||||
### 恢复媒体
|
||||
|
||||
```bash
|
||||
# 本地目录方式
|
||||
MEDIA_TARGET_DIR=./uploads ./deploy/scripts/backup/restore-media.sh ./backups/media/latest.tar.gz
|
||||
|
||||
# R2 / S3 方式
|
||||
MEDIA_S3_TARGET=s3://bucket-name ./deploy/scripts/backup/restore-media.sh ./backups/media/media-20260331T120000Z
|
||||
```
|
||||
|
||||
## 4. 推荐的生产 Cron 示例
|
||||
|
||||
```cron
|
||||
# 每天 03:10 备份 PostgreSQL
|
||||
10 3 * * * cd /opt/termi-astro && DATABASE_URL=postgres://... ./deploy/scripts/backup/backup-postgres.sh >> /var/log/termi-backup.log 2>&1
|
||||
|
||||
# 每天 03:25 备份 Markdown
|
||||
25 3 * * * cd /opt/termi-astro && MARKDOWN_SOURCE_DIR=./backend/content/posts ./deploy/scripts/backup/backup-markdown.sh >> /var/log/termi-backup.log 2>&1
|
||||
|
||||
# 每天 03:40 备份媒体
|
||||
40 3 * * * cd /opt/termi-astro && MEDIA_S3_SOURCE=s3://bucket-name ./deploy/scripts/backup/backup-media.sh >> /var/log/termi-backup.log 2>&1
|
||||
|
||||
# 每天 04:15 清理过期备份
|
||||
15 4 * * * cd /opt/termi-astro && ./deploy/scripts/backup/prune-backups.sh >> /var/log/termi-backup.log 2>&1
|
||||
|
||||
# 每天 04:40 异地同步
|
||||
40 4 * * * cd /opt/termi-astro && OFFSITE_TARGET=/mnt/offsite/termi-astro-backups ./deploy/scripts/backup/sync-backups-offsite.sh >> /var/log/termi-backup.log 2>&1
|
||||
```
|
||||
|
||||
## 5. 建议你们再加一层异地备份
|
||||
|
||||
仅仅把备份留在同一台服务器上不够。
|
||||
|
||||
至少保证:
|
||||
- 主机本地保留最近 7~14 天
|
||||
- 再同步一份到另一块存储 / 另一台主机 / 对象存储冷备桶
|
||||
|
||||
## 6. 恢复演练建议
|
||||
|
||||
建议每个月至少做 1 次演练:
|
||||
|
||||
1. 用最新数据库备份恢复到临时环境
|
||||
2. 用 Markdown 备份恢复内容目录
|
||||
3. 用媒体备份恢复对象
|
||||
4. 校验:
|
||||
- 首页可打开
|
||||
- 文章详情可打开
|
||||
- 图片可访问
|
||||
- 后台可登录
|
||||
- 审计 / 版本 / 订阅表存在数据
|
||||
|
||||
也可以直接用恢复演练脚本:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgres://... \
|
||||
POSTGRES_BACKUP=./backups/postgres/latest.dump \
|
||||
MARKDOWN_BACKUP=./backups/markdown/latest.tar.gz \
|
||||
MEDIA_BACKUP=./backups/media/latest.tar.gz \
|
||||
./deploy/scripts/backup/verify-restore.sh
|
||||
```
|
||||
|
||||
## 7. 当前架构下的恢复优先级
|
||||
|
||||
发生事故时建议按这个顺序:
|
||||
|
||||
1. 恢复数据库
|
||||
2. 恢复 Markdown 原文
|
||||
3. 恢复媒体资源
|
||||
4. 启动 backend / frontend / admin
|
||||
5. 进入后台检查:
|
||||
- 审计日志
|
||||
- 文章版本历史
|
||||
- 订阅目标与最近投递
|
||||
|
||||
## 8. 说明
|
||||
|
||||
这些脚本是**仓库内参考实现**,没有在你们生产机上自动执行。
|
||||
正式上线前请按你们实际目录、R2/S3 桶、数据库连接串、cron 规范再过一遍。
|
||||
|
||||
另外仓库里已经提供:
|
||||
|
||||
- `deploy/systemd/README.md`
|
||||
- `deploy/systemd/termi-backup-all.timer`
|
||||
- `deploy/systemd/termi-backup-prune.timer`
|
||||
- `deploy/systemd/termi-backup-offsite-sync.timer`
|
||||
|
||||
如果宿主机使用 systemd,建议优先启用 timer,而不是只靠手工执行。
|
||||
173
deploy/docker/README.md
Normal file
173
deploy/docker/README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Docker 部署(Package 镜像)
|
||||
|
||||
补充架构说明见:
|
||||
|
||||
- `deploy/docker/ARCHITECTURE.md`
|
||||
- `deploy/caddy/Caddyfile.tohka.example`
|
||||
- `deploy/caddy/Caddyfile.tohka.production.example`
|
||||
- `deploy/docker/TOHKA_POCKET_ID.md`
|
||||
- `deploy/docker/TOHKA_DEPLOY_RUNBOOK.md`
|
||||
- `deploy/docker/config.yaml.example`
|
||||
- `deploy/docker/BACKUP_AND_RECOVERY.md`
|
||||
|
||||
## 1) 准备主配置文件(config.yaml)
|
||||
|
||||
现在推荐把:
|
||||
|
||||
- `deploy/docker/config.yaml`
|
||||
|
||||
作为部署主配置源;`deploy/docker/.env` 改为脚本生成产物。
|
||||
|
||||
先复制模板:
|
||||
|
||||
```bash
|
||||
cp deploy/docker/config.yaml.example deploy/docker/config.yaml
|
||||
```
|
||||
|
||||
然后填写至少这些核心项:
|
||||
|
||||
- `compose_env.DATABASE_URL`
|
||||
- `compose_env.REDIS_URL`
|
||||
- `compose_env.JWT_SECRET`
|
||||
- 邮件确认 / 邮件通知上线前,请同时补齐 SMTP 配置
|
||||
|
||||
填完后执行:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
```
|
||||
|
||||
如果你们内部喜欢先审 YAML,再部署,这就是现在的推荐流程。
|
||||
|
||||
建议在 `config.yaml -> compose_env` 下同时检查这些运行时变量:
|
||||
|
||||
- `INTERNAL_API_BASE_URL`:frontend SSR 容器访问 backend 用,compose 默认推荐 `http://backend:5150/api`
|
||||
- `PUBLIC_API_BASE_URL`:浏览器访问 backend API 用;留空时前台会回退到“当前主机 + `:5150/api`”
|
||||
- `PUBLIC_IMAGE_ALLOWED_HOSTS`:前台 `/_img` 图片优化端点允许的额外图片 host(逗号分隔)
|
||||
- `ADMIN_API_BASE_URL`:admin 浏览器访问 backend API 用;留空时后台会回退到“当前主机 + `:5150`”
|
||||
- `ADMIN_FRONTEND_BASE_URL`:admin 里“打开前台 / 问答页 / 文章页预览”跳转用
|
||||
- `TERMI_ADMIN_TRUST_PROXY_AUTH`:是否信任前置代理(如 Caddy + TinyAuth)注入的后台认证头
|
||||
- `TERMI_ADMIN_LOCAL_LOGIN_ENABLED`:是否保留本地账号密码登录兜底
|
||||
- `TERMI_ADMIN_PROXY_SHARED_SECRET`:代理 SSO 共享密钥;建议和 Caddy 的 `X-Termi-Proxy-Secret` 配套使用
|
||||
- `SMTP_ENABLE / SMTP_HOST / SMTP_PORT / SMTP_SECURE / SMTP_USER / SMTP_PASSWORD / SMTP_HELLO_NAME`:订阅确认和邮件通知需要
|
||||
|
||||
例如:
|
||||
|
||||
```yaml
|
||||
compose_env:
|
||||
PUBLIC_API_BASE_URL: https://api.blog.init.cool
|
||||
ADMIN_API_BASE_URL: https://admin.blog.init.cool
|
||||
ADMIN_FRONTEND_BASE_URL: https://blog.init.cool
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: true
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: false
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: replace-with-a-long-random-secret
|
||||
```
|
||||
|
||||
> 这些值最终会被渲染成 `deploy/docker/.env`,再由 `compose.package.yml` 读取。
|
||||
> 如果镜像构建期也注入了 `FRONTEND_PUBLIC_API_BASE_URL` / `ADMIN_VITE_API_BASE` / `ADMIN_VITE_FRONTEND_BASE_URL`,则运行时变量优先级更高。
|
||||
|
||||
## 2) 启动
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
|
||||
docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
如果是在 `tohka` 上并且前面接宿主机 Caddy,推荐改用:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
|
||||
docker compose \
|
||||
-f deploy/docker/compose.package.yml \
|
||||
-f deploy/docker/compose.tohka.override.yml \
|
||||
--env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
当前 compose 推荐一起启动 4 个容器:
|
||||
|
||||
- `backend`:API / migration / `/healthz`
|
||||
- `backend-worker`:Redis 队列消费者(通知异步投递、失败重试)
|
||||
- `frontend`:Astro SSR(Node)
|
||||
- `admin`:静态 SPA + Nginx
|
||||
|
||||
`compose.tohka.override.yml` 额外会把三个对外端口绑到 `127.0.0.1`,避免把 backend / admin 直接暴露到公网。
|
||||
|
||||
## 3) 更新镜像后重拉
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env pull
|
||||
docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
## 4) 问答记录(2026-03-31)
|
||||
|
||||
### Q1: 为什么前台容器是 `4321` 端口?
|
||||
A: 前台是 **Astro SSR(Node)**(`output: 'server'`),容器内运行 Node 服务(`dist/server/entry.mjs`),所以需要监听内部端口 `4321`。
|
||||
|
||||
### Q2: 两个前端都能做成纯静态 + 反代吗?
|
||||
A:
|
||||
- `admin` 可以,当前就是静态资源由 Nginx 提供。
|
||||
- `frontend` 当前不行(未做纯静态改造):项目里使用了 `prerender = false`、`Astro.request`、`Astro.cookies` 等请求期逻辑。
|
||||
|
||||
### Q3: SSR 和 CSR 怎么选?
|
||||
A: 当前站点对外内容页优先 SEO 与首屏可见性,保留 SSR 更稳;后台管理台继续 CSR/静态站即可。
|
||||
|
||||
### Q4: 生产推荐端口设计是什么?
|
||||
A: 推荐前置 Caddy/Nginx 统一暴露 `80/443`,`frontend:4321` / `backend:5150` / `admin:80` 仅走内网。
|
||||
当前 `compose.package.yml` 属于直连端口版,便于快速部署与联调。
|
||||
另外因为通知已经走异步队列,生产务必同时启动 `backend-worker`。
|
||||
|
||||
### Q5: 为什么 compose 里没看到 `ADMIN_VITE_FRONTEND_BASE_URL`?
|
||||
A:
|
||||
- 现在 compose 运行时可以直接设置 `ADMIN_FRONTEND_BASE_URL`,用于覆盖 admin 里所有前台跳转链接。
|
||||
- `ADMIN_VITE_FRONTEND_BASE_URL` 仍然可以作为 **镜像构建期默认值** 保留在 CI / workflow 里。
|
||||
- 如果两者都存在,**运行时 `ADMIN_FRONTEND_BASE_URL` 优先**。
|
||||
|
||||
### Q6: 前台 / 后台为什么拆成 public/internal 两种 API 地址?
|
||||
A:
|
||||
- `frontend` 是 Astro SSR,服务端渲染请求 backend 时更适合走内网地址(如 `http://backend:5150/api`)。
|
||||
- 但浏览器里的评论、问答、搜索请求必须走用户可访问的公开地址。
|
||||
- `admin` 是纯静态 SPA,也只能使用浏览器可访问的公开 API 地址。
|
||||
- 所以现在区分为:
|
||||
- `INTERNAL_API_BASE_URL`:frontend SSR 内部访问
|
||||
- `PUBLIC_API_BASE_URL`:前台浏览器访问
|
||||
- `ADMIN_API_BASE_URL`:admin 浏览器访问
|
||||
|
||||
### Q7: 现在后台 OIDC / Pocket ID 怎么接?
|
||||
A:
|
||||
- 推荐直接复用 tohka 上现成的 **TinyAuth + Pocket ID**。
|
||||
- Caddy 在 `admin` 域名入口加 `import tinyauth`,并把 `/api/*` 同域转发到 backend。
|
||||
- backend 开启:
|
||||
- `TERMI_ADMIN_TRUST_PROXY_AUTH=true`
|
||||
- `TERMI_ADMIN_LOCAL_LOGIN_ENABLED=false`
|
||||
- `TERMI_ADMIN_PROXY_SHARED_SECRET=<随机长字符串>`
|
||||
- Caddy 在 `/api/*` -> backend 时补:
|
||||
- `X-Termi-Proxy-Secret: {$TERMI_ADMIN_PROXY_SHARED_SECRET}`
|
||||
- backend 会信任 TinyAuth 转发的:
|
||||
- `Remote-User`
|
||||
- `Remote-Email`
|
||||
- `Remote-Groups`
|
||||
- 本地开发仍可保留内置账号密码登录。
|
||||
|
||||
### Q8: 备份现在放在哪看?
|
||||
A:
|
||||
- 参考脚本:`deploy/scripts/backup/`
|
||||
- 恢复文档:`deploy/docker/BACKUP_AND_RECOVERY.md`
|
||||
|
||||
### Q9: 现在健康检查和 migration 怎么处理?
|
||||
A:
|
||||
- `backend` 镜像启动时会先执行 `db migrate`
|
||||
- `backend` 提供 `/healthz`
|
||||
- `frontend` 提供 `/healthz`
|
||||
- `admin` 继续由 Nginx 提供 `/healthz`
|
||||
- compose 现在使用 `depends_on.condition: service_healthy`
|
||||
124
deploy/docker/TOHKA_DEPLOY_RUNBOOK.md
Normal file
124
deploy/docker/TOHKA_DEPLOY_RUNBOOK.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# tohka 最终部署操作手册(config.yaml 版)
|
||||
|
||||
这份手册按“准备 -> 渲染配置 -> 启动 -> 接 Caddy -> 启 timers -> 验证”执行。
|
||||
|
||||
## 1. 准备文件
|
||||
|
||||
先复制配置模板:
|
||||
|
||||
```bash
|
||||
cp deploy/docker/config.yaml.example deploy/docker/config.yaml
|
||||
```
|
||||
|
||||
再按生产实际填写:
|
||||
|
||||
- 域名
|
||||
- Postgres / Redis 地址
|
||||
- JWT secret
|
||||
- SMTP
|
||||
- TinyAuth / Pocket ID 共享密钥
|
||||
- 镜像 tag
|
||||
|
||||
主配置源是:
|
||||
|
||||
- `deploy/docker/config.yaml`
|
||||
|
||||
## 2. 渲染 `.env`
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
```
|
||||
|
||||
如果只是想预览,不落盘:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--stdout
|
||||
```
|
||||
|
||||
## 3. 启动容器
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f deploy/docker/compose.package.yml \
|
||||
-f deploy/docker/compose.tohka.override.yml \
|
||||
--env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
查看状态:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f deploy/docker/compose.package.yml \
|
||||
-f deploy/docker/compose.tohka.override.yml \
|
||||
--env-file deploy/docker/.env ps
|
||||
```
|
||||
|
||||
## 4. 接宿主机 Caddy
|
||||
|
||||
直接参考:
|
||||
|
||||
- `deploy/caddy/Caddyfile.tohka.production.example`
|
||||
|
||||
建议域名:
|
||||
|
||||
- `blog.init.cool`
|
||||
- `admin.blog.init.cool`
|
||||
- `api.blog.init.cool`
|
||||
|
||||
关键点:
|
||||
|
||||
- `admin.blog.init.cool` 整体挂 `import tinyauth`
|
||||
- `admin.blog.init.cool/api/*` 转 backend 时带:
|
||||
- `X-Termi-Proxy-Secret {$TERMI_ADMIN_PROXY_SHARED_SECRET}`
|
||||
|
||||
## 5. 启用 systemd timers
|
||||
|
||||
```bash
|
||||
sudo cp deploy/systemd/*.service /etc/systemd/system/
|
||||
sudo cp deploy/systemd/*.timer /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now termi-retry-deliveries.timer
|
||||
sudo systemctl enable --now termi-weekly-digest.timer
|
||||
sudo systemctl enable --now termi-monthly-digest.timer
|
||||
sudo systemctl enable --now termi-backup-all.timer
|
||||
sudo systemctl enable --now termi-backup-prune.timer
|
||||
sudo systemctl enable --now termi-backup-offsite-sync.timer
|
||||
```
|
||||
|
||||
## 6. 做首轮验证
|
||||
|
||||
至少检查:
|
||||
|
||||
- `http://127.0.0.1:5150/healthz`
|
||||
- `http://127.0.0.1:4321/healthz`
|
||||
- `http://127.0.0.1:4322/healthz`
|
||||
- `https://admin.blog.init.cool` 能正常走 Pocket ID / TinyAuth 登录
|
||||
- 订阅确认邮件能正常送达
|
||||
- 测试通知 / 周报 / 月报能正常入队并送达
|
||||
|
||||
## 7. 上线后维护动作
|
||||
|
||||
每次改 `deploy/docker/config.yaml` 后,记得重新:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
|
||||
docker compose \
|
||||
-f deploy/docker/compose.package.yml \
|
||||
-f deploy/docker/compose.tohka.override.yml \
|
||||
--env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
## 8. 配套文档
|
||||
|
||||
- `deploy/docker/README.md`
|
||||
- `deploy/docker/ARCHITECTURE.md`
|
||||
- `deploy/docker/TOHKA_POCKET_ID.md`
|
||||
- `deploy/systemd/GO_LIVE_CHECKLIST.md`
|
||||
- `deploy/docker/BACKUP_AND_RECOVERY.md`
|
||||
154
deploy/docker/TOHKA_POCKET_ID.md
Normal file
154
deploy/docker/TOHKA_POCKET_ID.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# tohka 上接入 Pocket ID / TinyAuth / Caddy 的推荐做法
|
||||
|
||||
这份文档记录当前项目在 `tohka` 上最推荐的后台保护方式:
|
||||
|
||||
```text
|
||||
Browser
|
||||
-> Host Caddy
|
||||
-> TinyAuth
|
||||
-> Pocket ID (OIDC)
|
||||
-> admin nginx
|
||||
-> backend /api
|
||||
```
|
||||
|
||||
## 1. 目标
|
||||
|
||||
实现这些效果:
|
||||
|
||||
- `blog.init.cool` 对外公开,走 frontend SSR
|
||||
- `admin.blog.init.cool` 整体受保护
|
||||
- admin 页面和它访问的 `/api/*` 同域
|
||||
- backend 信任 TinyAuth 注入的登录身份
|
||||
- 即使 backend 端口误暴露,也额外要求一个共享密钥头,降低伪造 `Remote-User` 风险
|
||||
|
||||
## 2. 推荐使用的 compose 方式
|
||||
|
||||
基础 compose:
|
||||
|
||||
- `deploy/docker/compose.package.yml`
|
||||
|
||||
在 tohka 上再叠加这个 override:
|
||||
|
||||
- `deploy/docker/compose.tohka.override.yml`
|
||||
|
||||
部署主配置源建议使用:
|
||||
|
||||
- `deploy/docker/config.yaml`
|
||||
|
||||
启动方式:
|
||||
|
||||
```bash
|
||||
python deploy/scripts/render_compose_env.py \
|
||||
--input deploy/docker/config.yaml \
|
||||
--output deploy/docker/.env
|
||||
|
||||
docker compose \
|
||||
-f deploy/docker/compose.package.yml \
|
||||
-f deploy/docker/compose.tohka.override.yml \
|
||||
--env-file deploy/docker/.env up -d
|
||||
```
|
||||
|
||||
这个 override 会做三件事:
|
||||
|
||||
1. `frontend / admin / backend` 只绑定到 `127.0.0.1`
|
||||
2. 默认打开 `TERMI_ADMIN_TRUST_PROXY_AUTH=true`
|
||||
3. 默认关闭 `TERMI_ADMIN_LOCAL_LOGIN_ENABLED`
|
||||
|
||||
## 3. `config.yaml -> compose_env` 里最关键的变量
|
||||
|
||||
至少补这些:
|
||||
|
||||
现在推荐直接从下面这个模板开始:
|
||||
|
||||
- `deploy/docker/config.yaml.example`
|
||||
|
||||
然后复制成:
|
||||
|
||||
- `deploy/docker/config.yaml`
|
||||
|
||||
至少补这些:
|
||||
|
||||
```yaml
|
||||
compose_env:
|
||||
APP_BASE_URL: https://admin.blog.init.cool
|
||||
PUBLIC_API_BASE_URL: https://api.blog.init.cool
|
||||
ADMIN_API_BASE_URL: https://admin.blog.init.cool
|
||||
ADMIN_FRONTEND_BASE_URL: https://blog.init.cool
|
||||
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: true
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: false
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: replace-with-a-long-random-secret
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `APP_BASE_URL` 建议填后台正式地址,便于后端生成后台相关链接
|
||||
- `TERMI_ADMIN_PROXY_SHARED_SECRET` 是 backend 和 Caddy 之间约定的共享密钥
|
||||
- backend 现在会在代理 SSO 模式下检查 `X-Termi-Proxy-Secret`
|
||||
|
||||
## 4. Caddy 侧应该怎么配
|
||||
|
||||
直接参考:
|
||||
|
||||
- `deploy/caddy/Caddyfile.tohka.example`
|
||||
- `deploy/caddy/Caddyfile.tohka.production.example`
|
||||
|
||||
关键点是:admin 域名下 `/api/*` 反代到 backend 时要带上:
|
||||
|
||||
```caddy
|
||||
header_up X-Termi-Proxy-Secret {$TERMI_ADMIN_PROXY_SHARED_SECRET}
|
||||
```
|
||||
|
||||
这样 backend 才会接受代理注入的:
|
||||
|
||||
- `Remote-User`
|
||||
- `Remote-Email`
|
||||
- `Remote-Groups`
|
||||
|
||||
## 5. 为什么要加共享密钥头
|
||||
|
||||
如果只看 `Remote-User` 这类头,而 backend 又被公网直接访问,
|
||||
理论上别人可以手工伪造请求头来冒充后台用户。
|
||||
|
||||
现在的建议闭环是:
|
||||
|
||||
- backend 端口只监听 `127.0.0.1`
|
||||
- admin 域名入口必须经过 TinyAuth / Pocket ID
|
||||
- backend 额外校验 `X-Termi-Proxy-Secret`
|
||||
|
||||
这样会稳很多。
|
||||
|
||||
## 6. 后台登录页现在的行为
|
||||
|
||||
当前逻辑:
|
||||
|
||||
- 如果 `TERMI_ADMIN_LOCAL_LOGIN_ENABLED=true`,仍可用本地账号密码兜底
|
||||
- 如果关闭本地登录,admin 登录页会提示“请从受保护入口进入,并重新检查会话”
|
||||
- backend 会优先读取代理身份头
|
||||
|
||||
## 7. 建议的 tohka 落地顺序
|
||||
|
||||
1. 在 tohka 的大 Caddyfile 里接入 `admin.blog.init.cool`
|
||||
2. 给 admin 域名启用 `import tinyauth`
|
||||
3. `/api/*` 同域转发到 `localhost:5150`
|
||||
4. 加上 `X-Termi-Proxy-Secret`
|
||||
5. 用 `compose.tohka.override.yml` 启动容器
|
||||
6. 打开 `https://admin.blog.init.cool`
|
||||
7. 在后台里确认当前 session 的 `auth_source=proxy`
|
||||
|
||||
完整部署步骤可再配合:
|
||||
|
||||
- `deploy/docker/TOHKA_DEPLOY_RUNBOOK.md`
|
||||
|
||||
## 8. digest / retry / 备份建议一起启用
|
||||
|
||||
上线时建议连同这些一起启用:
|
||||
|
||||
- `deploy/systemd/termi-retry-deliveries.timer`
|
||||
- `deploy/systemd/termi-weekly-digest.timer`
|
||||
- `deploy/systemd/termi-monthly-digest.timer`
|
||||
- `deploy/systemd/termi-backup-all.timer`
|
||||
- `deploy/systemd/termi-backup-prune.timer`
|
||||
- `deploy/systemd/termi-backup-offsite-sync.timer`
|
||||
|
||||
这样后台保护、通知投递、备份恢复三件事才算闭环。
|
||||
80
deploy/docker/compose.package.yml
Normal file
80
deploy/docker/compose.package.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
services:
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE:-git.init.cool/cool/termi-astro-backend:latest}
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 5150
|
||||
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:5150}
|
||||
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required}
|
||||
REDIS_URL: ${REDIS_URL:?REDIS_URL is required}
|
||||
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
|
||||
# 当前推荐把 admin 放在受保护的后台域名下(同域转发 /api 到 backend),
|
||||
# 然后让 backend 信任 TinyAuth / Pocket ID 通过 Caddy 注入的认证头。
|
||||
# 如启用代理 SSO,建议同时配置 TERMI_ADMIN_PROXY_SHARED_SECRET,
|
||||
# 并让 Caddy 在转发 /api 到 backend 时附带 X-Termi-Proxy-Secret。
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: ${TERMI_ADMIN_TRUST_PROXY_AUTH:-false}
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: ${TERMI_ADMIN_LOCAL_LOGIN_ENABLED:-true}
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: ${TERMI_ADMIN_PROXY_SHARED_SECRET:-}
|
||||
RUST_LOG: ${RUST_LOG:-info}
|
||||
ports:
|
||||
# 这是“直连端口”示例;如果前面接 tohka 宿主机 Caddy,
|
||||
# 推荐叠加 compose.tohka.override.yml,把 backend 只绑定到 127.0.0.1。
|
||||
- '${BACKEND_PORT:-5150}:5150'
|
||||
|
||||
backend-worker:
|
||||
image: ${BACKEND_IMAGE:-git.init.cool/cool/termi-astro-backend:latest}
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
command: ['termi_api-cli', '-e', 'production', 'start', '--worker']
|
||||
environment:
|
||||
PORT: 5150
|
||||
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:5150}
|
||||
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required}
|
||||
REDIS_URL: ${REDIS_URL:?REDIS_URL is required}
|
||||
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: ${TERMI_ADMIN_TRUST_PROXY_AUTH:-false}
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: ${TERMI_ADMIN_LOCAL_LOGIN_ENABLED:-true}
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: ${TERMI_ADMIN_PROXY_SHARED_SECRET:-}
|
||||
RUST_LOG: ${RUST_LOG:-info}
|
||||
TERMI_SKIP_MIGRATIONS: 'true'
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE:-git.init.cool/cool/termi-astro-frontend:latest}
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# frontend 是 Astro SSR(Node):
|
||||
# - INTERNAL_API_BASE_URL 给服务端渲染访问 backend 用
|
||||
# - PUBLIC_API_BASE_URL 给浏览器里的评论 / AI 问答等请求用
|
||||
# - PUBLIC_IMAGE_ALLOWED_HOSTS 给前台图片优化端点 /_img 放行额外图片域名
|
||||
INTERNAL_API_BASE_URL: ${INTERNAL_API_BASE_URL:-http://backend:5150/api}
|
||||
PUBLIC_API_BASE_URL: ${PUBLIC_API_BASE_URL:-}
|
||||
PUBLIC_IMAGE_ALLOWED_HOSTS: ${PUBLIC_IMAGE_ALLOWED_HOSTS:-}
|
||||
# frontend 是 Astro SSR(Node) 服务,容器内部监听 4321
|
||||
# 生产建议由网关统一反代,仅对外开放 80/443
|
||||
ports:
|
||||
- '${FRONTEND_PORT:-4321}:4321'
|
||||
|
||||
admin:
|
||||
image: ${ADMIN_IMAGE:-git.init.cool/cool/termi-astro-admin:latest}
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ADMIN_API_BASE_URL: ${ADMIN_API_BASE_URL:-}
|
||||
ADMIN_FRONTEND_BASE_URL: ${ADMIN_FRONTEND_BASE_URL:-}
|
||||
# admin 是静态 SPA,由 Nginx 在容器内监听 80
|
||||
# API 与“打开前台 / AI 问答 / 文章预览”这类地址都优先读取运行时环境变量
|
||||
# ADMIN_API_BASE_URL / ADMIN_FRONTEND_BASE_URL;未设置时再回退到构建期值 / 同主机默认端口
|
||||
ports:
|
||||
# 如果 admin 域名由宿主机 Caddy 统一反代,推荐改成 127.0.0.1 绑定。
|
||||
- '${ADMIN_PORT:-4322}:80'
|
||||
28
deploy/docker/compose.tohka.override.yml
Normal file
28
deploy/docker/compose.tohka.override.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
services:
|
||||
# 这个 override 专门给 tohka 这种“宿主机 Caddy -> localhost 端口 -> Docker 容器”模式使用。
|
||||
# 使用方式:
|
||||
# docker compose \
|
||||
# -f deploy/docker/compose.package.yml \
|
||||
# -f deploy/docker/compose.tohka.override.yml \
|
||||
# --env-file deploy/docker/.env up -d
|
||||
backend:
|
||||
environment:
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: ${TERMI_ADMIN_TRUST_PROXY_AUTH:-true}
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: ${TERMI_ADMIN_LOCAL_LOGIN_ENABLED:-false}
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: ${TERMI_ADMIN_PROXY_SHARED_SECRET:?TERMI_ADMIN_PROXY_SHARED_SECRET is required for tohka proxy mode}
|
||||
ports:
|
||||
- '127.0.0.1:${BACKEND_PORT:-5150}:5150'
|
||||
|
||||
backend-worker:
|
||||
environment:
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: ${TERMI_ADMIN_TRUST_PROXY_AUTH:-true}
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: ${TERMI_ADMIN_LOCAL_LOGIN_ENABLED:-false}
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: ${TERMI_ADMIN_PROXY_SHARED_SECRET:?TERMI_ADMIN_PROXY_SHARED_SECRET is required for tohka proxy mode}
|
||||
|
||||
frontend:
|
||||
ports:
|
||||
- '127.0.0.1:${FRONTEND_PORT:-4321}:4321'
|
||||
|
||||
admin:
|
||||
ports:
|
||||
- '127.0.0.1:${ADMIN_PORT:-4322}:80'
|
||||
88
deploy/docker/config.yaml.example
Normal file
88
deploy/docker/config.yaml.example
Normal file
@@ -0,0 +1,88 @@
|
||||
# tohka 生产部署主配置源(config.yaml)
|
||||
# 使用方式:
|
||||
# 1. cp deploy/docker/config.yaml.example deploy/docker/config.yaml
|
||||
# 2. 按实际环境填写下面参数
|
||||
# 3. python deploy/scripts/render_compose_env.py --input deploy/docker/config.yaml --output deploy/docker/.env
|
||||
# 4. docker compose -f deploy/docker/compose.package.yml -f deploy/docker/compose.tohka.override.yml --env-file deploy/docker/.env up -d
|
||||
|
||||
project:
|
||||
name: termi-astro
|
||||
host: tohka
|
||||
compose_files:
|
||||
- deploy/docker/compose.package.yml
|
||||
- deploy/docker/compose.tohka.override.yml
|
||||
env_output: deploy/docker/.env
|
||||
|
||||
# 仅做文档/运维留档;docker compose 实际读取 compose_env
|
||||
meta:
|
||||
blog_origin: https://blog.init.cool
|
||||
admin_origin: https://admin.blog.init.cool
|
||||
api_origin: https://api.blog.init.cool
|
||||
pocket_id_issuer: https://id.example.com
|
||||
pocket_id_client: admin.blog.init.cool
|
||||
|
||||
compose_env:
|
||||
BACKEND_PORT: 5150
|
||||
FRONTEND_PORT: 4321
|
||||
ADMIN_PORT: 4322
|
||||
|
||||
APP_BASE_URL: https://admin.blog.init.cool
|
||||
INTERNAL_API_BASE_URL: http://backend:5150/api
|
||||
PUBLIC_API_BASE_URL: https://api.blog.init.cool
|
||||
ADMIN_API_BASE_URL: https://admin.blog.init.cool
|
||||
ADMIN_FRONTEND_BASE_URL: https://blog.init.cool
|
||||
PUBLIC_IMAGE_ALLOWED_HOSTS: cdn.example.com,pub-xxxx.r2.dev
|
||||
|
||||
DATABASE_URL: postgres://termi:replace-me@postgres.internal:5432/termi_api
|
||||
REDIS_URL: redis://redis.internal:6379
|
||||
JWT_SECRET: replace-with-a-long-random-secret
|
||||
JWT_EXPIRATION_SECONDS: 604800
|
||||
RUST_LOG: info
|
||||
|
||||
SMTP_ENABLE: true
|
||||
SMTP_HOST: smtp.resend.com
|
||||
SMTP_PORT: 587
|
||||
SMTP_SECURE: false
|
||||
SMTP_USER: resend
|
||||
SMTP_PASSWORD: replace-with-smtp-password
|
||||
SMTP_HELLO_NAME: admin.blog.init.cool
|
||||
|
||||
TERMI_ADMIN_TRUST_PROXY_AUTH: true
|
||||
TERMI_ADMIN_LOCAL_LOGIN_ENABLED: false
|
||||
TERMI_ADMIN_PROXY_SHARED_SECRET: replace-with-another-long-random-secret
|
||||
|
||||
BACKEND_IMAGE: git.init.cool/cool/termi-astro-backend:latest
|
||||
FRONTEND_IMAGE: git.init.cool/cool/termi-astro-frontend:latest
|
||||
ADMIN_IMAGE: git.init.cool/cool/termi-astro-admin:latest
|
||||
|
||||
notifications:
|
||||
ntfy:
|
||||
enabled: true
|
||||
base_url: https://ntfy.sh
|
||||
example_topic: your-team-topic
|
||||
timers:
|
||||
retry_deliveries: termi-retry-deliveries.timer
|
||||
weekly_digest: termi-weekly-digest.timer
|
||||
monthly_digest: termi-monthly-digest.timer
|
||||
|
||||
backups:
|
||||
offsite_target: /mnt/offsite/termi-astro-backups
|
||||
local_retention_days:
|
||||
postgres: 14
|
||||
markdown: 14
|
||||
media: 14
|
||||
timers:
|
||||
backup_all: termi-backup-all.timer
|
||||
backup_prune: termi-backup-prune.timer
|
||||
backup_offsite_sync: termi-backup-offsite-sync.timer
|
||||
|
||||
systemd:
|
||||
repo_path: /opt/termi-astro
|
||||
install_path: /etc/systemd/system
|
||||
enable_timers:
|
||||
- termi-retry-deliveries.timer
|
||||
- termi-weekly-digest.timer
|
||||
- termi-monthly-digest.timer
|
||||
- termi-backup-all.timer
|
||||
- termi-backup-prune.timer
|
||||
- termi-backup-offsite-sync.timer
|
||||
Reference in New Issue
Block a user