Files
photo-live/infra/deploy/README-1Panel.md
T
浅念 250d4355b9 build(deploy): production images, compose stacks and 1panel guide
P5-prep Task 1(docs/plans/2026-09-21-P5-准备件.md):

- docker/server.Dockerfile:单镜像 photo-live-server 多角色(api-cn/api-ov/
  worker/sync/migrate-deploy/seed-admin,entrypoint.sh 分发;migrate-deploy
  支持 cn|ov|all 区域参数)。三阶段:全量 install(锁文件冻结)→ prisma
  generate 双库 + 逐包构建;--prod 依赖层;runtime=node:20-alpine+openssl/curl
  + 全局 prisma@5.22.0(对齐锁文件)+ 双库 client 镜像内重建(db-cn 落 pnpm
  store .prisma、db-ov 落包内 generated/)。sharp 走 musl 预编译验证通过。
- docker/web.Dockerfile + infra/deploy/nginx.conf.template:photo-live-web
  (nginx:alpine + envsubst 模板)。viewer(/,history 回退) + admin(/admin/,
  hash 路由) + /v1 反代 API_UPSTREAM(含 /v1/realtime WS 升级)+
  /assets/、/admin/assets/、/embed/v1/ immutable 长缓存(admin assets 的
  alias 前缀剥除坑已实测修复)。VITE_* 为构建期参数(VIEWER_BASE/
  EMBED_ORIGIN 随域名构建传入)。
- infra/deploy/docker-compose.cn.yml / -ov.yml:cn=api-cn+worker+sync+web,
  ov=api-ov+web-ov(核实架构:sync 仅 cn 侧,inbox/emergency 内嵌 api-ov,
  无 ov 侧 sync 进程);健康检查 /v1/health、unless-stopped、json-file
  10m×3、外部 1panel-network 复用 1Panel MySQL/Redis、SERVER_IMAGE/
  WEB_IMAGE 环境变量化镜像 tag(回滚杠杆)。
- .env.production.example-cn/-ov:生产变量全量清单(cn 25 键/ov 24 键,
  DB/Redis/OSS/SYNC/AUTH/限速/域名分组注释,源自 services+packages
  process.env 全量审计);.gitignore 放行 example,真实 .env.production-*
  永不入库。
- infra/deploy/README-1Panel.md:镜像导入双路径(save tar 导入 / 仓库 pull)、
  MySQL 建库授权 SQL(utf8mb4_0900_bin,<8.0.30 回退 utf8mb4_bin——D12)、
  Redis 连接、1Panel 编排、OpenResty 反代+证书、migrate-deploy 一次性容器、
  首次部署顺序(DB→迁移→ov 栈→cn 栈→seed→冒烟)。
- 根 package.json pnpm.onlyBuiltDependencies 增补 sharp/prisma/@prisma/
  engines/@prisma/client(镜像构建所需 install 脚本白名单)。

本地验证:两镜像构建成功(server 988MB / web 97.7MB);compose config 校验
通过;server 镜像内逐服务 require(sharp/argon2/双 Prisma client/bullmq/
ioredis)与双引擎 so.node、migrate-deploy 角色、无 .env 泄漏均实测;web 镜像
容器实测路由(viewer 回退/admin 子路径/assets 与 embed immutable/WS 代理路径
502 语义)全通过。未起真实栈(无真实 DB,符合准备件边界)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 01:39:55 +08:00

156 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# README-1Panel —— photo-live 生产部署指南(1Panel 单机双地域)
> P5-prep 产物(无凭证前置批):本指南只描述操作步骤与占位值,不含任何真实密钥。
> 目标形态:**cn 服务器**(1Panel:MySQL/Redis/OpenResty + photo-live-cn 编排)
> 与 **ov 服务器**(1Panel:MySQL/Redis/OpenResty + photo-live-ov 编排)。
> 同步架构(已从代码核实):sync 独立进程**只跑 cn 侧**,双库直连 + 双 API 调用;
> ov 侧 inbox(内容接收)与 emergency(紧急停用)内嵌在 api-ov 进程内。
| 产物 | 说明 |
| --- | --- |
| `docker/server.Dockerfile` | 单镜像 `photo-live-server` 多角色:api-cn / api-ov / worker / sync / migrate-deploy / seed-admin |
| `docker/web.Dockerfile` | `photo-live-web`:nginx 承载 viewer(/)+admin(/admin/)+/v1 反代+静态长缓存 |
| `docker-compose.cn.yml` / `docker-compose-ov.yml` | 两地域编排(本目录) |
| `.env.production.example-cn` / `-ov` | 变量全量清单(仓库根,复制后填值) |
| `RUNBOOK.md` | 发布/迁移/回滚/紧急停用运行手册 |
---
## 1. 镜像导入(两条路径任选)
**版本约定**:tag = `git短sha-日期`(如 `12059d9-20260921`),同一 tag 双镜像配套,
回滚即换 tag(见 RUNBOOK.md)。
### 路径 A:本地构建 → tar 上传 → 1Panel 导入(无镜像仓库时)
```bash
# 本地(仓库根)构建;tag 按上方约定替换
docker build -f docker/server.Dockerfile -t photo-live-server:12059d9-20260921 .
docker build -f docker/web.Dockerfile \
--build-arg VITE_VIEWER_BASE=https://你的viewer域名 \
--build-arg VITE_EMBED_ORIGIN=https://你的viewer域名 \
-t photo-live-web:12059d9-20260921 .
docker save photo-live-server:12059d9-20260921 | gzip > photo-live-server.tar.gz
docker save photo-live-web:12059d9-20260921 | gzip > photo-live-web.tar.gz
# 上传两台服务器后,1Panel → 容器 → 镜像 → 导入(选择 tar.gz)
```
> web 镜像的 `VITE_VIEWER_BASE`/`VITE_EMBED_ORIGIN` 是**构建期**变量(admin 分享卡
> 与嵌入代码拼 URL 用);cn/ov 两地域各用各自域名构建一次。漏传只影响分享卡外链,
> 不影响 API 路由(API 基址=同源 /v1,由运行时 `API_UPSTREAM` 决定)。
### 路径 B:推镜像仓库 → 服务器 pull(有仓库时)
1Panel → 容器 → 仓库 → 创建(阿里云 ACR 个人版即可)→ 之后编排文件里 image 直接写
`registry.cn-hangzhou.aliyuncs.com/你的命名空间/photo-live-server:tag`,或在服务器
`docker pull` 后 `docker tag` 成编排里用的名字。
## 2. MySQL 建库 / 授权(两台各执行对应段)
1Panel → 数据库 → MySQL(8.0+)→ 创建数据库。**库名/字符集**:
```sql
-- cn 服务器(库名 photo_cn)
CREATE DATABASE photo_cn CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin;
CREATE USER 'photo'@'%' IDENTIFIED BY '强密码';
GRANT ALL PRIVILEGES ON photo_cn.* TO 'photo'@'%';
FLUSH PRIVILEGES;
-- ov 服务器(库名 photo_ov)
CREATE DATABASE photo_ov CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin;
CREATE USER 'photo_ov'@'%' IDENTIFIED BY '强密码';
GRANT ALL PRIVILEGES ON photo_ov.* TO 'photo_ov'@'%';
FLUSH PRIVILEGES;
```
- `utf8mb4_0900_bin`(大小写敏感)是账号/token/publicToken 语义的前提,需
**MySQL ≥ 8.0.30**(决策 D12:先在两台 ECS 上 `SELECT VERSION();` 登记精确版本;
若低于 8.0.30,改用 `utf8mb4_bin`(同样大小写敏感),并重跑语义相关验收)。
- 授权 host:`'%'` 便于容器网段接入;生产更稳妥做法是限定网段
(`'172.%'` 容器网 + cn 服务器出口 IP)。**ov 库必须额外放行 cn 服务器出口 IP
的 3306**(sync 进程直连 ov 库),建议叠加 SSL 或隧道(P5 实测定案)。
## 3. Redis 连接
1Panel → 数据库 → Redis → 创建(默认库即可)。连接串二选一:
- **容器网络互连(推荐)**:photo-live 编排与 1Panel 的 Redis 同在
`1panel-network`,host 直接写 Redis **容器名**(`docker ps` 查名,如
`1Panel-redis-xxxx`),端口写容器内端口 6379。
- **宿主机 IP 直连**:host 写宿主机内网 IP;1Panel 应用端口默认只绑 127.0.0.1,
需在 1Panel 端口设置放开监听地址或改绑 0.0.0.0(注意防火墙)。
填入 env:cn `REDIS_URL_CN`;ov `REDIS_URL_OV`。
## 4. Compose 编排(1Panel → 容器 → 编排 → 创建编排)
1. 文件管理器在 1Panel 数据目录旁新建目录(示例 `/opt/photo-live/`),上传:
- `docker-compose.cn.yml`(cn)/ `docker-compose-ov.yml`(ov)
- 由仓库根 `.env.production.example-cn`/`-ov` 复制改名的
`.env.production-cn`/`.env.production-ov`(**与 compose 同目录**,已填值)
2. 1Panel → 容器 → 编排 → 创建编排 → 路径选择该目录(或直接粘贴 compose 内容,
再把 env 文件放到 1Panel 显示的编排目录内)。
3. 默认编排挂**外部网络 `1panel-network`**(1Panel 应用网络,容器名互通 MySQL/
Redis)。若你的 1Panel 网络名不同(`docker network ls` 核对),改 compose 末段
`name:`;若用宿主机 IP 连接,把 networks 整段换成 `photo-net: {}` 并在 env 里
用 IP 形态的连接串。
4. 启动前确认镜像 tag:默认 `photo-live-server:latest`/`photo-live-web:latest`;
建议在 1Panel 编排的环境变量里固定 `SERVER_IMAGE`/`WEB_IMAGE` 为具体版本 tag。
## 5. OpenResty 网站 + 证书反代到 web 容器
1Panel → 网站 → 创建网站 → 反向代理:
- cn:主域名 `photo.example.cn`(viewer)→ 代理 `http://127.0.0.1:8088`;
管理端 `admin.photo.example.cn` → 同一后端 `http://127.0.0.1:8088`(admin 走
hash 路由 `/admin/` 路径,也可与 viewer 同域直接用 `photo.example.cn/admin/`)。
- ov:`photo.example.com` → `http://127.0.0.1:8089`。
- 证书:网站设置 → HTTPS → 申请/绑定证书,开启「强制 HTTPS」。
- OpenResty 侧注意:
- **不要重复缓存** `/v1/`(API 动态;图片 URL 本身是带签名的 OSS 直链,不经过
这层);静态资源缓存已在 photo-live-web 容器内配置好。
- WebSocket(`/v1/realtime`)透传:1Panel 反代默认透传 Upgrade 头;若自建
OpenResty 配置,需保留 `proxy_set_header Upgrade/Connection`(参考
`nginx.conf.template` 的 /v1/ 段)。
- 若 web 端口改绑了非回环地址,注意防火墙只放行 OpenResty 出口。
## 6. 迁移执行(migrate-deploy 角色,一次性容器)
```bash
# cn 服务器:迁移 cn 库(env 文件与 compose 同目录;--rm 用完即删)
docker run --rm --env-file /opt/photo-live/.env.production-cn \
--network 1panel-network \
photo-live-server:12059d9-20260921 migrate-deploy cn
# ov 服务器:迁移 ov 库
docker run --rm --env-file /opt/photo-live/.env.production-ov \
--network 1panel-network \
photo-live-server:12059d9-20260921 migrate-deploy ov
# 首次引导 admin 账号(cn;幂等,已存在则跳过)
docker run --rm --env-file /opt/photo-live/.env.production-cn \
--network 1panel-network \
photo-live-server:12059d9-20260921 seed-admin
```
`migrate-deploy` 支持区域参数 `cn | ov | all`(缺省 all——单机双库演练用;生产两台
各跑各的)。Prisma migrate deploy 只向前应用未执行过的迁移,已在
RUNBOOK.md 固化为发布步骤与回滚原则。
## 7. 首次部署顺序(cn 与 ov 并行各自的 1-2,汇合于 3-6)
1. **DB 就绪**:两台各建库/授权(§2)+ Redis(§3)。
2. **镜像导入**:两台各自导入 server+web 镜像(§1)。
3. **迁移**:cn `migrate-deploy cn` → ov `migrate-deploy ov`(§6)。
4. **起栈**:ov 先 `docker-compose-ov.yml` up(api-ov + web-ov)→ cn
`docker-compose.cn.yml` up(api-cn + worker + sync + web)。sync 依赖 ov API
与 ov 库可达,故 cn 栈最后起。
5. **域名/证书**:OpenResty 反代 + HTTPS(§5),DNS 解析两地域各自接入。
6. **引导与演练**:cn `seed-admin`(§6)→ 管理端登录 → 建测试相册 →
`scripts/prod-smoke.sh <cnBase> <ovBase> <账号> <密码>` 四点冒烟
(健康/登录/上传/公开读,ov 未部署时 `--ov` 跳过 ov 段)→ 观察同步水位
(sync 日志 `outbox batch pushed`)→ 清理测试数据(软删照片即可)。
日常发布/回滚见 `RUNBOOK.md`。