Files
ai-video-fullstack/deploy/README.md
2026-08-05 14:05:22 +08:00

347 lines
9.7 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.
# 开发 HTTPS 调试部署
语音预览要求麦克风可用,浏览器只在 **安全上下文**(localhost 或 https)下放行
`getUserMedia`。本机用 localhost 就够;**局域网或远程机器用 IP/域名给别的设备测,
就要走 https**。开发环境推荐 nginx 统一做 TLS 入口,前端页面、HTTP API 和 WS 信令都同源。
## 结构
```
浏览器 ──https/wss──> nginx :443 (唯一 TLS 入口, mkcert 证书)
├── /ws/ → 后端 :8000 (/ws/voice 信令、/ws/stream 裸流)
├── /api/ → 后端 :8000 (assistants/model-resources/...)
├── /health → 后端 :8000
└── / → 前端 :3000 (Next dev + HMR)
```
前端页面和信令 ws 同源(同 host 同端口),没有混合内容 / 跨源问题。
## 路径 A:本机 / 局域网,nginx 直接跑在宿主机
适合 Mac 本机调试,或同一局域网里手机访问电脑 IP。
```bash
# 1) 装 mkcert(只需一次)
brew install mkcert nss
# 2) 生成证书(本机 CA + localhost/LAN IP/ai-video.local 的证书)
./deploy/setup-certs.sh
# 3) 起前后端(任选其一)
docker compose up -d # api:8000 + ui:3030 都发布到 localhost
# 或本地分别 npm run dev / uvicorn app:app --port 8000
# 4) 起 nginx(装一下:brew install nginx)
nginx -c "$(pwd)/deploy/nginx/ai-video.dev.conf" -g 'daemon off;'
# 5) 访问
# 本机: https://localhost 或 https://ai-video.local
# 局域网:https://<本机IP> (脚本结尾会打印)
```
`deploy/nginx/ai-video.dev.conf` 里的证书路径是本机绝对路径。换目录或换机器时,
`ssl_certificate``ssl_certificate_key` 改到当前仓库的 `deploy/certs/`
## 路径 B:远程开发机,Docker nginx + 宿主机 npm/uvicorn
适合在云主机/远程 Linux 上开发:数据库可以用 Docker,但前端和后端单独跑,
便于热更新和看日志。nginx 跑在 Docker 容器里,通过 `host.docker.internal`
反代到宿主机上的 `3000/8000`
### 1. 准备证书
有域名时推荐使用正式证书,把证书文件放到:
```text
deploy/certs/ai-video.pem
deploy/certs/ai-video-key.pem
```
只做开发调试也可以用 mkcert。远程机器上把访问用的 IP/域名传给脚本:
```bash
./deploy/setup-certs.sh <远程机器公网IP或内网IP> <可选域名>
```
mkcert 是本地 CA。浏览器所在设备要免警告,需要信任这台远程机器生成的
`rootCA.pem`;否则可以临时接受浏览器的自签证书警告。
### 2. 启动数据库并初始化
```bash
docker compose up -d postgres
make db-seed
```
`make db-seed` 会建表、同步接口定义、写入模型资源和助手示例。模型密钥不放
`.env`,启动后到前端「组件 / 模型」里把 `replace-me` 换成真实密钥。
### 3. 单独启动后端
后端必须监听 `0.0.0.0`,否则 nginx 容器连不到宿主机端口:
```bash
cd backend
cp .env.example .env
# 如果 postgres 用 docker compose,宿主机连库可用 localhost:5432
# backend/.env:
# DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/postgres
uv run --with-requirements requirements.txt \
uvicorn app:app --host 0.0.0.0 --port 8000 --reload
```
### 4. 单独启动前端
前端也监听 `0.0.0.0``NEXT_PUBLIC_API_BASE_URL` 只有一个规则:
**写浏览器地址栏里访问 nginx 的同源地址**。如果地址栏带端口,这里也必须带端口;
如果地址栏不带端口,这里也不带端口。不要写后端直连端口 `:8000`
Next dev 还会校验开发资源的来源。远程访问时,把浏览器地址栏里的 host 加到
`frontend/next.config.ts``allowedDevOrigins`,并重启前端 dev server:
```ts
// frontend/next.config.ts
const nextConfig = {
allowedDevOrigins: ["127.0.0.1", "<远程机器IP或域名>"],
};
```
如果漏掉这一步,页面 HTML 可能能打开,但客户端 JS 无法接管:按钮无响应,
列表一直停在 `正在加载`,同时 Next dev 内部资源可能返回 `403 Unauthorized`
```bash
cd frontend
npm install
# nginx 使用默认 443 时:
NEXT_PUBLIC_API_BASE_URL=https://<远程机器IP或域名> \
npm run dev -- --hostname 0.0.0.0
```
### 5. 启动 Docker nginx
推荐用 compose profile:
```bash
docker compose --profile remote-dev up -d nginx-remote-dev
```
如果远程机器希望用非标准 HTTPS 端口,例如 `18189`,把宿主机端口映射到
容器的 `443` 即可:
```bash
NGINX_HTTPS_PORT=18189 docker compose --profile remote-dev up -d nginx-remote-dev
```
此时前端也要使用带端口的同源地址:
```bash
NEXT_PUBLIC_API_BASE_URL=https://<远程机器IP或域名>:18189 \
npm run dev -- --hostname 0.0.0.0
```
浏览器访问:
```text
https://<远程机器IP或域名>:18189
```
也可以直接 `docker run`:
```bash
docker run --rm --name ai-video-nginx \
--add-host=host.docker.internal:host-gateway \
-p 80:80 -p 18189:443 \
-v "$PWD/deploy/nginx/ai-video.remote-dev.conf:/etc/nginx/nginx.conf:ro" \
-v "$PWD/deploy/certs:/etc/nginx/certs:ro" \
nginx:alpine
```
访问:
```text
https://<远程机器IP或域名>:18189
```
### 6. 部署 coturn(TURN 中继)
如果浏览器和后端不在同一内网,WebRTC 媒体链路通常需要 TURN。项目里的
`docker-compose.yaml` 已经带了 coturn,用 `remote` profile 启动即可。
#### 6.1 确认公网/私网 IP
查询云主机默认路由使用的私网 IP:
```bash
ip -4 route get 1.1.1.1
```
输出中 `src` 后的地址是 `PRIVATE_IP`。例如:
```text
1.1.1.1 via 10.0.0.1 dev eth0 src 10.0.0.8
```
云控制台中绑定到该主机、用于 SSH 的公网地址是 `PUBLIC_IP`
#### 6.2 配项目根目录 `.env`
Docker Compose 会把项目根 `.env` 中的 ICE 配置同时传给 API 和 coturn:
```text
PUBLIC_IP=<远程机器公网IP>
PRIVATE_IP=<远程机器私网IP>
TURN_SECRET=<一串足够长的随机密钥>
STUN_URL=stun:<远程机器公网IP>:3478
TURN_URLS=turn:<远程机器公网IP>:3478?transport=udp,turn:<远程机器公网IP>:3478?transport=tcp
```
示例:
```text
PUBLIC_IP=118.89.89.89
PRIVATE_IP=10.0.0.8
TURN_SECRET=change-me-to-a-long-random-string
STUN_URL=stun:118.89.89.89:3478
TURN_URLS=turn:118.89.89.89:3478?transport=udp,turn:118.89.89.89:3478?transport=tcp
```
`STUN_URL` 指向同机 coturn可避免后端等待不可达的公共 STUN。
Compose 的 `environment` 会覆盖 `backend/.env` 中的同名值,因此 Docker
部署必须在项目根 `.env` 设置上述变量。
#### 6.3 启动 coturn
```bash
docker compose config --quiet
docker compose --profile remote up -d --build --force-recreate api coturn
```
查看状态和日志:
```bash
docker compose ps coturn
docker compose logs -f coturn
```
#### 6.4 放行端口
云安全组至少放行:
```text
80/tcp
443/tcp
3478/tcp
3478/udp
49152-49200/udp
```
如果 HTTPS 映射到 `18189`,还要放行:
```text
18189/tcp
```
如果远程机器启用了 `ufw`:
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 18189/tcp
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 49152:49200/udp
```
#### 6.5 验证后端已下发 TURN
后端和 nginx 都跑起来后:
```bash
curl -k https://<远程机器IP或域名>:18189/api/webrtc/ice-servers
```
应该能看到 `stun:``turn:`:
```json
{
"iceServers": [
{"urls": "stun:118.89.89.89:3478"},
{
"urls": "turn:<远程机器公网IP>:3478?transport=udp",
"username": "...",
"credential": "..."
}
]
}
```
## 前端怎么连后端
前端读环境变量 `NEXT_PUBLIC_API_BASE_URL`。走 nginx 后,让它等于浏览器访问
页面的 **origin** 即可。origin 包含协议、host 和可选端口:
```
默认 443: NEXT_PUBLIC_API_BASE_URL=https://<访问用的host>
自定义端口: NEXT_PUBLIC_API_BASE_URL=https://<访问用的host>:18189
wsUrl = NEXT_PUBLIC_API_BASE_URL.replace(/^http/, 'ws') + '/ws/voice'
```
> 没有反代、直连后端时则是 `https://<host>:8000`,但那样要给后端单独配证书、
> 还有跨源,不推荐。统一走 nginx 最干净。
## 给别的设备免警告
证书是 mkcert 本地 CA 签的,只有装了该 CA 的设备才信任:
```bash
mkcert -CAROOT # 打印 CA 目录,里面的 rootCA.pem 拷到设备并信任
```
LAN IP 不在证书 SAN 里会报名称不匹配——`setup-certs.sh` 已自动把探测到的
en0/en1 IP 加进 SAN;换网络换了 IP,重跑脚本即可。
## 证书不入库
`deploy/.gitignore` 已忽略 `certs/`。私钥不要提交。
## 常见问题
### nginx 容器里为什么不用 127.0.0.1?
Docker 容器里的 `127.0.0.1` 是容器自己,不是宿主机。远程开发路径使用
`host.docker.internal`,并通过 `host-gateway` 映射到宿主机。
### 页面能打开,但 HMR 或语音 WS 断开?
检查 nginx 配置里是否保留了 `Upgrade``Connection` 头,以及 `/ws/`
`proxy_read_timeout 3600s`。本仓库的两份开发 nginx 配置都已经包含。
### 页面一直正在加载,但 API 直接访问正常?
先确认前端 dev server 已用浏览器访问同源地址启动:
```bash
NEXT_PUBLIC_API_BASE_URL=https://<远程机器IP或域名>:18189 \
npm run dev -- --hostname 0.0.0.0
```
再确认 `frontend/next.config.ts``allowedDevOrigins` 包含同一个 host:
```ts
allowedDevOrigins: ["127.0.0.1", "<远程机器IP或域名>"],
```
修改 `NEXT_PUBLIC_API_BASE_URL``allowedDevOrigins` 后都必须重启
`npm run dev`。若 API 直接返回 `200`,但页面按钮无响应、弹窗打不开、列表
仍停在 `正在加载`,通常是 Next dev 的 `_next/*` 客户端资源被跨源保护拦截,
不是后端接口没有数据。
### API 请求跨域报错?
走 nginx 时前端和后端同源,通常不需要跨源。若你绕过 nginx 直接访问后端,
需要在 `backend/.env``CORS_ORIGINS` 加上前端实际 origin。