# EchoChat 会议 MVP 部署指南 > **适用阶段**:Phase 2e-2(会议 MVP) > **覆盖形态**:本机 Demo(零配置)+ 公网部署(含 TURN) > **最后更新**:2026-04-23(Task 14 双态部署脚本 + docker-compose 扩展完成) ## 一、双态部署总览 EchoChat 会议 MVP 按 Phase 2e-2 设计决策 D01 采用**"一套代码 + 环境变量切换"** 的双态策略,不做代码分支。 | 对比项 | 本机 Demo(local) | 公网部署(public) | |---|---|---| | 适用场景 | 开发调试、同局域网演示 | 上线、跨公网用户 | | `MEDIASOUP_ANNOUNCED_IP` | 留空(自动探测) | **必填**服务器公网 IP 或域名解析 A 记录 IP | | `TURN_ENABLED` | `false`(不起 coturn) | `true`(coturn 走 `profiles: [public]`) | | 防火墙/安全组 | 无特殊要求 | 需放通 UDP:40000-40199 + TURN 端口 | | 启动方式 | `./scripts/start.sh full` | `./scripts/deploy-public.sh` | | 关键文件 | `deploy/.env.local.example` | `deploy/.env.public.example` | 服务拓扑对比见设计文档 [docs/plans/2026-04-21-phase2e-2-design.md §4.3 双态部署拓扑](../plans/2026-04-21-phase2e-2-design.md)。 --- ## 二、本机 Demo 部署 **前置要求**:Docker Desktop(含 Compose V2)、Node 20+、Go 1.22+ ### Step 1:复制环境变量文件 ```bash cp deploy/.env.local.example deploy/.env ``` 本机模式下无需修改任何字段即可跑通(`MEDIASOUP_ANNOUNCED_IP` 留空,`TURN_ENABLED=false`)。 ### Step 2:一键启动全栈 ```bash ./scripts/start.sh full ``` 这会做以下事情: 1. `docker compose up -d --build` 拉起 5 个容器:`postgres / redis / minio / go-service / media-server` 2. 等待 `media-server` healthcheck 通过(最多 180 秒) 3. 用本地进程启动 `frontend`(5173)+ `admin`(3100)—— 热更体验更好 启动完成后会打印访问地址: ``` 前台用户端 (H5): http://localhost:5173 后台管理端: http://localhost:3100 Go 后端 API: http://localhost:8085 媒体服务器 (SFU): http://localhost:3300 (healthz: /healthz) MinIO 控制台: http://localhost:9001 (echochat / echochat123456) ``` ### Step 3:验证 ```bash ./scripts/status.sh # 预期全部 ● RUNNING curl http://localhost:8085/healthz # Go backend curl http://localhost:3300/healthz # media-server curl http://localhost:3300/readyz # mediasoup worker 已就绪 ``` ### 停止 ```bash ./scripts/stop.sh full # 停止 docker compose 全栈(保留数据卷) ./scripts/stop.sh # 仅停应用层(保留所有容器) ./scripts/stop.sh --all # 含数据库中间件 ``` --- ## 三、公网部署 **前置要求**: - 服务器有**公网 IP** 或已解析 A 记录的域名 - Docker 已安装 - 云安全组/iptables 可由你控制(不能仅有 80/443 的 PaaS) ### Step 1:复制模板并替换所有占位符 ```bash cp deploy/.env.public.example deploy/.env vim deploy/.env ``` **必须替换**:所有 `_REPLACE_WITH_*_` 占位符(否则 `deploy-public.sh` 会拒绝启动)。 生成强密码的示例: ```bash openssl rand -hex 32 # JWT_SECRET / MEDIA_INTERNAL_TOKEN openssl rand -base64 24 # 数据库密码 / TURN 密码 ``` **核心字段**: | 字段 | 示例 | 说明 | |---|---|---| | `MEDIASOUP_ANNOUNCED_IP` | `203.0.113.42` | **必填**服务器公网 IP;不能写 `127.0.0.1` | | `TURN_ENABLED` | `true` | 对称 NAT fallback 必备 | | `TURN_USERNAME / TURN_PASSWORD` | 自设 | 避免 TURN 账号被互联网滥用 | | `JWT_SECRET` | 64 hex | 生产必改 | | `MEDIA_INTERNAL_TOKEN` | 32 hex | Go ↔ Node 内部鉴权,必须与 `media-server/.env` 一致 | ### Step 2:放通云安全组 / iptables 本脚本**不会**自动改防火墙(不同云厂商命令不同),请你提前在云控制台/iptables 放通: | 协议 | 端口 | 用途 | |---|---|---| | TCP | 8085 | Go backend API | | TCP | 5173 | 前台用户端 H5(如单独暴露) | | UDP | 40000-40199 | mediasoup RTC 流量 | | TCP | 40000-40199 | mediasoup ICE-TCP fallback | | UDP | 3478 | coturn STUN/TURN | | TCP | 3478 | coturn | | TCP | 5349 | coturn TLS | | UDP | 49160-49200 | coturn 中继流量 | `iptables` 示例(仅参考,请结合实际 zone 调整): ```bash # UDP 范围 sudo iptables -I INPUT -p udp --dport 40000:40199 -j ACCEPT sudo iptables -I INPUT -p udp --dport 49160:49200 -j ACCEPT # STUN/TURN sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT sudo iptables -I INPUT -p tcp --dport 3478 -j ACCEPT sudo iptables -I INPUT -p tcp --dport 5349 -j ACCEPT # API sudo iptables -I INPUT -p tcp --dport 8085 -j ACCEPT ``` ### Step 3:运行部署脚本 ```bash ./scripts/deploy-public.sh ``` 脚本会按 4 步依次执行: 1. **校验 `.env`**:占位符、长度、`ANNOUNCED_IP` 必填、`DEPLOY_MODE=public` 等 2. **本机端口检查**:TCP:8085 / TCP:3300 / 潜在冲突 3. **Docker 环境**:docker daemon + compose v2 可访问 4. **启动**:`TURN_ENABLED=true` 时用 `--profile public`(含 coturn),否则跳过 启动完成后会打印访问地址。此时用浏览器访问 `http://:5173`(或你放通的端口)即可。 ### 验证 ```bash ./scripts/status.sh # 容器级检查 docker compose -f deploy/docker-compose.dev.yml ps docker logs echochat-media-server | tail -40 docker logs echochat-coturn | tail -20 ``` 公网验证要点: - 用**两台不同网络**的设备(比如手机 4G + 家里 WiFi)分别入会,双向能看到对端画面 - Chrome 调 `chrome://webrtc-internals` 看 `Remote candidate type` —— 对称 NAT 场景能看到 `relay` 类型,说明 TURN 生效 - `curl http://:8085/healthz` 返回 200 ### 停止 ```bash ./scripts/stop.sh full # 等价于:docker compose -f deploy/docker-compose.dev.yml --profile public stop ``` --- ## 四、常见问题 FAQ ### Q1:`docker compose up` 卡在 media-server 构建,报 `python3 not found` 或 `g++ not found`? **原因**:mediasoup 的 C++ worker 需要在构建阶段编译原生代码。`media-server/Dockerfile` 的 builder 阶段已装 python3 + build-essential;若你绕过 Dockerfile 本地构建,需要手动装这些。 ```bash # Debian/Ubuntu sudo apt install python3 build-essential pkg-config # macOS brew install python3 xcode-select --install ``` ### Q2:本机 Demo 下两个浏览器 Tab 都在 127.0.0.1:5173,但视频流对端一片黑? **检查 1**:`MEDIASOUP_ANNOUNCED_IP` 一定不要写 `127.0.0.1`,留空让 mediasoup 自动探测到局域网 IP 即可。 **检查 2**:浏览器摄像头权限是否授予(Chrome 左上角锁图标)。 **检查 3**:`docker logs echochat-media-server | grep announcedIp`,确认探测到的 IP 在你本机 `ifconfig` 能看到。 ### Q3:公网部署后用手机(4G 网络)加入会议,能看到自己但看不到别人? **典型症状**:单向 ICE 候选协商不成功。通常是以下之一: 1. **TURN 没打开** —— 确认 `TURN_ENABLED=true` 且 `docker ps` 里有 `echochat-coturn`。对称 NAT 用户必走 TURN。 2. **UDP:40000-40199 没放通** —— mediasoup 要在这个范围分配动态 RTC 端口。很多云默认只放 80/443/22。 3. **`MEDIASOUP_ANNOUNCED_IP` 写错** —— 如果是 NAT 机器且写了内网 IP,公网用户拿到的候选地址不可达。 ### Q4:为什么 coturn 用 `network_mode: host`? TURN 服务器为每个会话动态分配中继端口(`--min-port=49160 --max-port=49200`);Docker 默认 bridge 网络的端口映射会把回包的源端口改写,导致客户端收不到流量。host 网络能绕过这个问题,是 coturn 官方推荐做法。 代价:coturn 会占用宿主机端口命名空间,请确保 `3478 / 5349 / 49160-49200` 宿主机无占用。 ### Q5:我有 HTTPS 证书,想把前端端口改成 443? MVP 阶段脚本没封装 HTTPS 反代,建议在 compose 外层加 Nginx(或 Caddy)作为 TLS 终结。mediasoup 的 RTC 流量本身就走 DTLS 加密,不需要你额外处理。 Caddy 最小示例(独立容器或宿主进程): ``` yourdomain.com { reverse_proxy /api/* localhost:8085 reverse_proxy /ws localhost:8085 reverse_proxy * localhost:5173 } ``` ### Q6:public profile 下 `stop.sh full` 能停掉 coturn 吗? 能。`scripts/stop.sh full` 里写了 `docker compose --profile public stop`,profile 参数确保 coturn 这种默认不暴露的服务也被覆盖。 ### Q7:数据库要备份怎么操作? ```bash # 快速备份当前数据库 docker exec echochat-postgres pg_dump -U echochat echochat | gzip > backup-$(date +%F).sql.gz # 从备份恢复(容器停机时) gunzip -c backup-2026-04-23.sql.gz | docker exec -i echochat-postgres psql -U echochat -d echochat ``` `pgdata / redisdata / minio_data` 三个 volume 的物理位置由 Docker 管理: ```bash docker volume inspect deploy_pgdata ``` --- ## 五、相关文档 - 设计文档:[docs/plans/2026-04-21-phase2e-2-design.md](../plans/2026-04-21-phase2e-2-design.md) - 实施计划:[docs/plans/2026-04-21-phase2e-2-implementation.plan.md](../plans/2026-04-21-phase2e-2-implementation.plan.md) - 会议 API:[docs/api/frontend/meeting.md](../api/frontend/meeting.md) - WebSocket 信令:[docs/api/frontend/meeting.md#websocket-信令协议](../api/frontend/meeting.md) - media-server 子项目:[media-server/README.md](../../media-server/README.md)(如有)