diff --git a/.env.example b/.env.example index b44a3c9..e8a21d2 100644 --- a/.env.example +++ b/.env.example @@ -1,69 +1,33 @@ -# 字节火山引擎 TTS v3 API 配置示例 -# 将此文件复制为 .env 并填入实际配置 +# 字节火山引擎 TTS v3 → OpenAI 兼容接口 +# 配置文件示例 · 复制为 .env 后填入 # ========================================== -# 必需的环境变量 +# 启动引导环境变量(4 个,启动前必看) # ========================================== +# 业务配置(api_key / resource_id / speaker / 格式 / 采样率 ...)通过 WebUI 设置, +# 不再需要在这里配。M3 起所有 BYTEDANCE_TTS_* 已搬入数据库。 -# 火山引擎新版控制台获取的 API Key -BYTEDANCE_TTS_API_KEY=your_api_key_here +# 1. 安装 token(必设,首次 /setup 时校验) +# 留空 = 不设 token,任何人可调 /api/setup(适合本地开发) +# 正式部署务必设复杂字符串 +TTS_ADMIN_KEY= -# 资源信息ID(决定使用1.0还是2.0模型) -# 复刻 2.0 音色(seed-icl-2.0) -BYTEDANCE_TTS_RESOURCE_ID=seed-icl-2.0 +# 2. 数据库路径(可选, 默认 ./tts.db) +# Docker 部署务必改成挂载路径,例: /data/tts.db +# TTS_DB_PATH=/data/tts.db -# 发音人(音色)ID -BYTEDANCE_TTS_SPEAKER=your_speaker_id_here - -# ========================================== -# 可选的环境变量 -# ========================================== - -# 单次合成超时,默认30s -BYTEDANCE_TTS_TIMEOUT=30s - -# 上游实际请求的音频格式:mp3 / pcm / ogg_opus -# 客户端要求 wav 时,内部自动转 pcm 上游 + 本地拼 WAV 头 -BYTEDANCE_TTS_FORMAT=mp3 - -# 上游采样率:8000/16000/22050/24000/32000/44100/48000 -BYTEDANCE_TTS_SAMPLE_RATE=24000 - -# MP3 比特率(可选),仅 MP3 生效 -# BYTEDANCE_TTS_BIT_RATE=128000 - -# 复刻 2.0 子模型(可选),留空则使用控制台默认值 -# seed-tts-2.0-standard:标准版,延时更优 -# seed-tts-2.0-expressive:表现力增强版,支持 QA / Cot -# BYTEDANCE_TTS_MODEL=seed-tts-2.0-standard - -# 复刻 2.0 模型类型(可选,推荐显式指定) -# 4 = ICL V2,5 = ICL V3 -# BYTEDANCE_TTS_MODEL_TYPE=4 - -# 非中文/英文合成时指定语种(可选) -# zh-cn / en / ja / es-mx / id / pt-br / ko -# BYTEDANCE_TTS_EXPLICIT_LANGUAGE=zh-cn - -# 复刻 2.0 启用字级时间戳(可选) -# BYTEDANCE_TTS_ENABLE_SUBTITLE=false - -# OpenAI兼容接口的API密钥(可选,多个用逗号分隔) -OPENAI_TTS_API_KEY=your_openai_compatible_key_here - -# 反代拓扑配置(0-10)。控制 X-Forwarded-For 解析方式,影响 IP 限流的 key。 -# 不设置 / 0:启发式模式(默认)—— 从 XFF 链尾扫描,跳过私有 IP,返回第一个公网 IP -# 适合 90% 部署(单跳/多跳/直出),无需了解精确跳数 -# N (N>0) :精确模式 —— 精准到真实 client IP,需要正确配置跳数 -# N=1:单跳反代(client → nginx → 本服务) -# N=2:双跳反代(client → CDN → nginx → 本服务,如 Cloudflare + nginx) -# N=3:三跳,以此类推 -# 直出部署(无反代):无需配置,XFF 分支不会执行 -# 详见 README "反代拓扑与 X-Forwarded-For 解析"章节 -# TRUSTED_PROXY_HOPS= - -# CORS 跨域白名单(逗号分隔;开发环境可设 *;空则拒绝所有跨域) -# ALLOWED_ORIGINS=https://example.com,https://app.example.com - -# 服务监听端口,默认8080 +# 3. 服务监听端口(可选, 默认 8080) PORT=8080 + +# 4. OpenAI 兼容接口的鉴权 key(可选) +# 不设 = 不鉴权,任何人能调 /v1/audio/speech(适合内网/反代后) +# 多个 key 用逗号分隔 +# 公网部署必设,见 README "公网部署安全清单" +OPENAI_TTS_API_KEY= + +# ========================================== +# 装完后可改(也支持环境变量作为 DB 缺失时的 fallback) +# ========================================== +# X-Forwarded-For 解析跳数(可选, 0=启发式默认, N>0=精确 N 跳反代) +# 装好后推荐到 /admin 改 trusted_proxy_hops +# TRUSTED_PROXY_HOPS=2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..563c7cd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +本项目的所有重要变更都记录在本文档。版本号遵循 [SemVer](https://semver.org/)。 + +## [0.2.0] - 2026-08-30 + +### 新增 + +- **M0 · SQLite + store 包**: 引入 `modernc.org/sqlite` (无 CGO), 所有运行时配置 (settings + voices) 持久化到 SQLite +- **M1 · 安装流程**: 首次启动进入 `/setup` 向导, 通过 4 步表单收集凭证 + 默认音色 + 音色列表, 完成后写 `installed.lock` 锁定 + - 引导页无外部依赖: Vue 3.4 + axios 1.6 via bootcdn, `//go:embed` 进 binary + - **自愈回退**: 检测到 DB 损坏自动备份 + 转 setup 模式 (不丢数据) +- **M2 · WebUI 后台**: `/admin` 单页 SPA, 含登录 / 仪表盘 / 音色管理 / 设置 / CORS 配置 + - 默认鉴权基于 `auth_key` (DB), 失败计数 + 限流 + - 浏览器安装修复: 同源请求跳过 CORS 校验, install 模式完全跳过 CORS +- **M3 · 全局设置 + 声音路由**: + - `default_speaker` 是 voice **名字** (如 `chun`), 路由时查 voice 表拿到真 speaker ID (如 `S_G8tEKnaJ1`) + - `/v1/audio/speech` 支持 `voice=` 动态路由, 命中但 enabled=0 返回 403 + - 未知 voice 返回 400 `unknown_voice: ''` + - 禁用 voice 返回 403 `voice '' is disabled` +- **OpenAI 端 key 走 DB**: `auth_key` 设置项, 不再依赖 `OPENAI_TTS_API_KEY` env +- **CORS 全 DB 化**: `cors_origins` / `cors_allow_all` 走 `/admin` 设置, install 模式跳过 +- **CORS 修复**: 同源请求跳过 CORS 校验 (避免浏览器自家人拦自家人) +- **运营工具**: `cmd/dumpdb` — 离线 dump tts.db 的 settings + voices +- **fail-fast**: normal 模式下 TTS 配置损坏 → `log.Fatalf` 退出, 触发 K8s / Docker 重启 + - 配套 metric `tts_config_load_failures_total{mode="normal"}` 便于告警 + - `/health` body 加 `error` 字段直接展示失败原因 +- **speaker ID 隐私保护**: 日志里 `telemetry.MaskSpeaker` (`S_G8****naJ1`); `/metrics` 标签用 `telemetry.SpeakerLabel` (sha1[:8]) + +### 修复 + +- **resource_id 覆盖**: `LoadRuntimeConfig` 之前用 voice 行的 `resource_id` 覆盖 settings 里的, 导致用户设的 `default_resource_id` 永远没机会生效。现在 settings 优先, voice 行的 resource_id 仅在 `voice=` 显式传时使用 +- **默认值修正**: 把过时的 `volc.megatts.icl` / `volc.megatts.default` 全部改成 v3 API 2.0 复刻项目唯一合法的 `seed-icl-2.0`; 模型名统一 `seed-tts-2.0-standard` +- **log 格式一致**: voice 命中 log 跟合成 log 同样打码 +- **setup UX 改进**: voice 行 `resource_id` 留空时, 自动用 settings 里的 `default_resource_id` 兜底, 避免 settings / voice 资源 ID 不一致导致 500 +- **/admin dashboard banner 误报**: `reloadAll()` 加 `loadSettings()`, 避免 "CORS 未配置" 黄条永远显示 + +### 变更 + +- **.env.example 收敛**: 移除 `BYTEDANCE_TTS_*` 业务 env, 只留 4 个引导 env (`TTS_ADMIN_KEY` / `TTS_DB_PATH` / `PORT` / `OPENAI_TTS_API_KEY`); 业务配置走 WebUI +- **docker-compose**: 加 `tts-data` named volume 持久化 tts.db +- **Dockerfile**: 准备 `/data` 目录, appuser 可写, 解决 tts.db 落盘权限 + +## [0.1.0] - 初版 + +- 火山 TTS v3 → OpenAI 兼容 `/v1/audio/speech` 单二进制 +- 配置全 env 驱动 (`BYTEDANCE_TTS_*` 11 个) +- 限流 / 鉴权 / Prometheus metrics / CORS / 反代 XFF 解析 diff --git a/Dockerfile b/Dockerfile index 18a4b54..a7b965c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,13 +27,16 @@ WORKDIR /app COPY --from=builder /app/tts-api . # health.html 已通过 //go:embed 嵌入 binary,无需单独复制 -RUN chown -R appuser:appgroup /app +# 准备 /data 目录存 SQLite (tts.db + installed.lock) +# appuser 必须可写,否则启动后无法创建 DB +RUN mkdir -p /data \ + && chown -R appuser:appgroup /app /data USER appuser EXPOSE 8080 -HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:8080/health || exit 1 ENTRYPOINT ["./tts-api"] diff --git a/docker-compose.yml b/docker-compose.yml index 87e7105..0355220 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,32 @@ -version: '3.8' - services: tts-api: build: . + image: volcano-tts-aggregator:latest container_name: tts-api ports: - "${PORT:-8080}:8080" environment: - - BYTEDANCE_TTS_API_KEY=${BYTEDANCE_TTS_API_KEY} - - BYTEDANCE_TTS_RESOURCE_ID=${BYTEDANCE_TTS_RESOURCE_ID} - - BYTEDANCE_TTS_SPEAKER=${BYTEDANCE_TTS_SPEAKER} - - BYTEDANCE_TTS_TIMEOUT=${BYTEDANCE_TTS_TIMEOUT:-30s} - - OPENAI_TTS_API_KEY=${OPENAI_TTS_API_KEY:-} - - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} + # 4 个启动引导 env(仅这 4 个必看) + # 业务配置(api_key/resource_id/speaker/格式...)通过 WebUI 设置, + # 存到 /data/tts.db 持久化 + - TTS_ADMIN_KEY=${TTS_ADMIN_KEY:-} + - TTS_DB_PATH=/data/tts.db - PORT=8080 + - OPENAI_TTS_API_KEY=${OPENAI_TTS_API_KEY:-} + # 可选,装好后也可在 /admin 改 + - TRUSTED_PROXY_HOPS=${TRUSTED_PROXY_HOPS:-} + volumes: + # DB + lock 文件必须挂载,否则容器重启后配置全丢 + - tts-data:/data restart: unless-stopped healthcheck: + # 用 wget 检查,alpine 镜像有 test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3 - start_period: 5s + start_period: 10s + +volumes: + tts-data: + name: tts-api-data