diff --git a/DEPLOYMENT_GUIDE_LOCAL.md b/DEPLOYMENT_GUIDE_LOCAL.md new file mode 100644 index 0000000..a068fb0 --- /dev/null +++ b/DEPLOYMENT_GUIDE_LOCAL.md @@ -0,0 +1,402 @@ +# xiaozhi-esp32-server 全模块本地部署指南 + +> **目标平台:** MacBook Pro M1 Pro (ARM64, 32GB) +> **系统:** macOS Darwin 24.6.0 (arm64) +> **适用范围:** 从当前"仅 server 模块"升级为"server + manager-api + manager-web + MySQL + Redis"全模块 +> **生成时间:** 2026-04-06 + +--- + +## 0. 背景与目标 + +**当前问题:** xiaozhi-server 的 LLM 配置被 xinnan-tech 云端 manager-api 覆盖,实际使用了默认的 `glm-4-flash` 而非 MiniMax,导致 401 错误。 + +**解决思路:** 升级到全模块部署,智控台配置保存在本地 MySQL 数据库,MiniMax API Key 由智控台管理,不再被云端覆盖。 + +--- + +## 1. 环境概览 + +### 当前状态 + +``` +/Users/bigemon/WorkSpace/xiaozhi-server/ +├── docker-compose.yml ← 单模块(server only) +├── data/ +│ ├── .config.yaml ← MiniMax 配置(被云端覆盖) +│ └── .mcp_server_settings.json +└── models/ + ├── SenseVoiceSmall/model.pt/ ← 目录,模型在 nested 路径 + ├── snakers4_silero-vad/ + └── mcp-endpoint-server/ +``` + +### 部署后架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ MacBook Pro M1 Pro (32GB) │ +│ │ +│ xiaozhi-esp32-server 8000,8003/tcp (server) │ +│ xiaozhi-esp32-server-web 8002/tcp (智控台) │ +│ xiaozhi-esp32-server-db 3306/tcp (MySQL) │ +│ xiaozhi-esp32-server-redis 6379/tcp (Redis) │ +│ │ +│ docker.for.mac.host.internal ← 容器访问宿主机 │ +└─────────────────────────────────────────────────────────┘ +``` + +### ARM64 兼容性确认 + +| 镜像 | 状态 | 备注 | +|------|------|------| +| `server_latest` | ✅ ARM64 | `ghcr.nju.edu.cn/xinnan-tech` 提供 | +| `web_latest` | ✅ ARM64 | Spring Boot + Vue,无平台依赖 | +| `mysql:latest` | ⚠️ 基本支持 | 建议改用 `mysql:8.0` | +| `redis:8.0` | ✅ ARM64 | 官方多架构镜像 | +| FunASR SenseVoiceSmall | ✅ 支持 | PyTorch CPU 模式 | + +--- + +## 2. 前置检查 + +```bash +# 1. 端口检查(8002 必须空闲) +lsof -i :8002 2>/dev/null | grep LISTEN || echo "8002 空闲 ✅" + +# 2. 确认 Docker 可用 +docker --version && docker compose version + +# 3. 确认现有容器状态 +docker ps --format "{{.Names}}\t{{.Status}}" | grep xiaozhi + +# 4. 确认模型文件存在 +ls /Users/bigemon/WorkSpace/xiaozhi-server/models/SenseVoiceSmall/model.pt/ +# 应看到 SenseVoiceSmall.pt 或 nested 目录 + +# 5. 备份现有配置 +cp /Users/bigemon/WorkSpace/xiaozhi-server/data/.config.yaml \ + /Users/bigemon/WorkSpace/xiaozhi-server/data/.config.yaml.bak.$(date +%Y%m%d%H%M%S) +``` + +--- + +## 3. 详细部署步骤 + +### Step 1:建立目录结构 + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# MySQL 数据目录 +mkdir -p mysql/data + +# 最终目录结构应为: +# xiaozhi-server/ +# ├── docker-compose_all.yml ← 新增 +# ├── docker-compose.yml ← 旧文件(可保留备份) +# ├── data/ +# │ └── .config.yaml +# ├── models/ +# └── mysql/ +# └── data/ +``` + +### Step 2:下载 docker-compose_all.yml + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# 下载全模块编排文件 +wget -q https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/docker-compose_all.yml \ + -O docker-compose_all.yml + +# 验证下载 +head -20 docker-compose_all.yml +``` + +### Step 3:修复 MySQL 镜像版本(避免 latest 不稳定问题) + +```bash +# 将 mysql:latest 改为 mysql:8.0 +sed -i '' 's|image: mysql:latest|image: mysql:8.0|' docker-compose_all.yml + +# 确认修改 +grep "image:" docker-compose_all.yml +# 预期:mysql:8.0 ✅ +``` + +### Step 4:修复 FunASR 模型文件路径 + +> **问题:** `models/SenseVoiceSmall/model.pt` 是**目录**,但 docker-compose volume 挂载期望它是**文件**。 +> **实际模型路径:** `models/SenseVoiceSmall/model.pt/SenseVoiceSmall.pt` + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# 查看实际模型文件位置 +ls -la models/SenseVoiceSmall/model.pt/ +# 预期:drwxr-xr-x SenseVoiceSmall.pt(或其他 .pt 文件) + +# 移动到正确路径 +PT_FILE=$(ls models/SenseVoiceSmall/model.pt/*.pt 2>/dev/null | head -1) +if [ -n "$PT_FILE" ]; then + mv "$PT_FILE" models/SenseVoiceSmall/model.pt.file + ln -sf model.pt.file models/SenseVoiceSmall/model.pt + echo "✅ 模型文件已移动:$(basename $PT_FILE) → model.pt.file" +else + echo "⚠️ 未找到 .pt 模型文件,请手动检查 models/SenseVoiceSmall/model.pt/" +fi + +# 验证 +ls -lh models/SenseVoiceSmall/ +``` + +### Step 5:准备初始 .config.yaml + +> **关键原则:** `manager-api.secret` 先留空,等智控台启动后从页面获取再填入。 + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server/data + +cat > .config.yaml << 'EOF' +server: + ip: 0.0.0.0 + port: 8000 + http_port: 8003 + # vision_explain: http://192.168.1.194:8003/mcp/vision/explain # 全模块后由智控台管理 + +manager-api: + url: http://xiaozhi-esp32-server-web:8002/xiaozhi + secret: "" # ← 先留空,智控台启动后从这里获取并填入 + +# MCP 接入点(保持不变) +mcp_endpoint: ws://mcp-endpoint-server:8004/mcp_endpoint/mcp/?token=usxQ%2BQ7GkvPsndgGVZDZBCE1%2Bcp6w1dJKkmoj7EJCqM%3D +EOF + +echo "✅ .config.yaml 初始版本已创建" +``` + +### Step 6:停止现有单模块容器 + +```bash +# 停止并移除旧容器(约 1-3 秒服务中断) +docker stop xiaozhi-esp32-server && docker rm xiaozhi-esp32-server + +echo "✅ 旧容器已清理" +echo "⚠️ ESP32 设备将在新容器启动后自动重连(端口不变)" +``` + +### Step 7:启动全模块 Docker Compose + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# 启动全部 4 个容器 +docker compose -f docker-compose_all.yml up -d + +# 确认 4 个容器都在运行 +docker ps --format "{{.Names}}\t{{.Status}}" | grep xiaozhi +``` + +**预期输出:** +``` +xiaozhi-esp32-server Up +xiaozhi-esp32-server-web Up +xiaozhi-esp32-server-db Up (healthy) +xiaozhi-esp32-server-redis Up (healthy) +``` + +### Step 8:等待 MySQL + Redis 健康检查 + +```bash +# 等待 MySQL 健康检查(最多 60 秒) +echo "等待 MySQL 就绪..." +for i in $(seq 1 20); do + status=$(docker inspect --format='{{.State.Health.Status}}' xiaozhi-esp32-server-db 2>/dev/null) + [ "$status" = "healthy" ] && echo "✅ MySQL 已就绪 (${i}0s)" && break + echo " 尝试 ${i}/20,状态: ${status:-starting}..." + sleep 3 +done + +# 等待 Redis +echo "等待 Redis 就绪..." +for i in $(seq 1 10); do + status=$(docker inspect --format='{{.State.Health.Status}}' xiaozhi-esp32-server-redis 2>/dev/null) + [ "$status" = "healthy" ] && echo "✅ Redis 已就绪 (${i}0s)" && break + echo " 尝试 ${i}/10,状态: ${status:-starting}..." + sleep 2 +done +``` + +### Step 9:验证智控台启动成功 + +```bash +docker logs xiaozhi-esp32-server-web 2>&1 | tail -10 +``` + +**成功标志:** +``` +Started AdminApplication in 16.057 seconds (process running for 17.941) +http://localhost:8002/xiaozhi/doc.html +``` + +### Step 10:注册智控台管理员账号 + +用浏览器打开:**http://127.0.0.1:8002** + +1. 点击**注册**,创建第一个账号 +2. **第一个注册的账号自动成为超级管理员** +3. 超级管理员权限:模型管理、用户管理、参数配置 + +### Step 11:配置 server.secret + +1. 登录智控台 → **参数管理** +2. 找到 `server.secret`,复制参数值 +3. 填入 `data/.config.yaml`: + +```bash +# 将 SECRET_VALUE 替换为从智控台复制的值 +sed -i '' "s|secret: \"\"|secret: \"YOUR_SECRET_VALUE\"|" \ + /Users/bigemon/WorkSpace/xiaozhi-server/data/.config.yaml + +# 确认 +grep "secret:" /Users/bigemon/WorkSpace/xiaozhi-server/data/.config.yaml +``` + +### Step 12:配置 MiniMax LLM + +1. 登录智控台 → **模型配置** → **大语言模型** +2. 新增或修改 MiniMax 配置: + - 类型:`OpenAI 兼容` + - 模型名:`MiniMax-M2.5` 或 `MiniMax-M2.7` + - API 地址:`https://api.minimaxi.com/v1` + - API Key:MiniMax Token Plan Key +3. 保存 + +### Step 13:配置 MiniMax TTS(如需) + +1. 智控台 → **模型配置** → **语音合成** +2. 选择 `MinimaxTTS` 或继续使用 `EdgeTTS`(免费无需 Key) + +### Step 14:配置 server.websocket 和 server.ota + +> 全模块部署后,ESP32 固件需要从智控台读取这两个地址。 + +1. 登录智控台 → **参数管理** +2. 找到 `server.websocket`,填入: + ``` + ws://192.168.1.194:8000/xiaozhi/v1/ + ``` +3. 找到 `server.ota`,填入: + ``` + http://192.168.1.194:8002/xiaozhi/ota/ + ``` + +> ⚠️ OTA 端口是 **8002**(智控台),不是 8003! + +### Step 15:重启 server 容器 + +```bash +docker restart xiaozhi-esp32-server +docker logs -f xiaozhi-esp32-server 2>&1 | head -20 +``` + +**成功标志:** +``` +Websocket地址是 ws://192.168.1.194:8000/xiaozhi/v1/ +``` + +### Step 16:验证 MiniMax LLM + +```bash +# 测试玩偶对话(用修复后的 CLI) +cd /Users/bigemon/WorkSpace/child-psycho-companion +source .venv/bin/activate +python xiaozhi_cli_client.py "你好" +``` + +观察日志中是否还有 `401` 错误。如成功,玩偶应正常回复。 + +--- + +## 4. 验证清单 + +| 验证项 | 命令/方法 | 成功标准 | +|--------|---------|---------| +| MySQL 健康 | `docker inspect xiaozhi-esp32-server-db -f '{{.State.Health.Status}}'` | `healthy` | +| Redis 健康 | `docker inspect xiaozhi-esp32-server-redis -f '{{.State.Health.Status}}'` | `healthy` | +| 智控台 | 浏览器打开 `http://127.0.0.1:8002` | 能注册/登录 | +| MiniMax LLM | `python xiaozhi_cli_client.py "测试"` | 无 401 错误,玩偶回复 | +| WS 服务 | `curl -s --max-time 3 http://127.0.0.1:8003/xiaozhi/v1/` | 连接建立 | +| MCP Endpoint | `docker logs xiaozhi-esp32-server 2>&1 \| grep MCP` | 连接成功 | + +--- + +## 5. 回滚步骤 + +### 情况 A:全模块启动后 server 连接 manager-api 失败 + +```bash +# 1. 确认 secret 已正确填写 +grep "secret:" /Users/bigemon/WorkSpace/xiaozhi-server/data/.config.yaml + +# 2. 仅重启 server +docker restart xiaozhi-esp32-server +docker logs -f xiaozhi-esp32-server +``` + +### 情况 B:完全回滚到单模块(服务中断最小化) + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# 停止全模块(保留 volume) +docker compose -f docker-compose_all.yml stop + +# 恢复旧单模块容器 +docker run -d \ + --name xiaozhi-esp32-server \ + --restart always \ + --security-opt seccomp:unconfined \ + -e TZ=Asia/Shanghai \ + -p 8000:8000 -p 8003:8003 \ + -v /Users/bigemon/WorkSpace/xiaozhi-server/data:/opt/xiaozhi-esp32-server/data \ + ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest + +# 恢复网络连接 +docker network connect mcp-endpoint-server_default xiaozhi-esp32-server 2>/dev/null || true +docker network connect xiaozhi-server_default xiaozhi-esp32-server 2>/dev/null || true + +echo "✅ 已回滚到单模块" +``` + +### 情况 C:完全恢复(保留 MySQL 数据) + +```bash +cd /Users/bigemon/WorkSpace/xiaozhi-server + +# 停止全模块(保留 MySQL 数据 volume) +docker compose -f docker-compose_all.yml down + +# 启动单模块 +docker run -d \ + --name xiaozhi-esp32-server \ + --restart always \ + --security-opt seccomp:unconfined \ + -e TZ=Asia/Shanghai \ + -p 8000:8000 -p 8003:8003 \ + -v /Users/bigemon/WorkSpace/xiaozhi-server/data:/opt/xiaozhi-esp32-server/data \ + ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest +``` + +--- + +## 附录:已知问题与解决 + +| 问题 | 原因 | 解决 | +|------|------|------| +| MySQL 启动慢 | `latest` tag 构建不稳定 | 改用 `mysql:8.0` | +| FunASR 模型 404 | volume 挂载期望文件,实际是目录 | 移动 .pt 文件到正确路径 | +| LLM 401 | manager-api 云端配置覆盖本地 | 全模块后由智控台管理 | +| OTA 地址写错端口 | 单模块用 8003,全模块用 8002 | 见 Step 14 |