远程 GPU 后端¶
在一台机器(GPU 机器、家庭服务器)上运行 VoiceStudio 后端,然后从另一台机器的桌面应用或浏览器驱动它——通过你的 tailnet,推理仍留在性能强大的机器上。
你是通过自己的脚本调用 API,而不是使用桌面应用?请参阅 docs/api-auth.md,这是一份面向使用者的参考文档,涵盖每个认证门禁(共享 PIN、API 密钥、听写 WebSocket、受信网络),并说明确切的请求头、参数以及
401/403/429的含义。
本页介绍保留的远程后端模式:整个后端及其项目在另一台机器上运行。它与网络共享(Network sharing)不同,后者将当前后端的 UI 提供给局域网内的其他设备。
这是可选功能,默认关闭:未设置 API key 时,后端仍然像以前一样仅允许回环访问。
形态¶
┌──────────────┐ tailnet (WireGuard) ┌─────────────────────┐
│ laptop │ ws/https to MagicDNS URL │ gpu-box │
│ VoiceStudio UI │ ──────────────────────────▶ │ VoiceStudio backend │
│ (thin client) │ short-lived session/ticket │ OMNIVOICE_API_KEY set │
└──────────────┘ └─────────────────────┘
桌面应用就是瘦客户端——没有单独的二进制文件。你在设置中输入后端 URL 和 API key。该密钥只兑换一次,获得一个短期会话;普通 HTTP 请求使用该会话,WebSocket 使用绑定路径的一次性票据。主密钥永远不会被存储或放入 URL。
1. 在 GPU 机器上:使用密钥运行后端¶
生成一个密钥,并以后端已设置该密钥的方式启动:
export OMNIVOICE_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(24))')"
export OMNIVOICE_SERVER_MODE=1 # headless: relaxes the loopback admin gate
uv run uvicorn backend.main:app --host 0.0.0.0 --port 3900
Docker 镜像也是同样的思路——传入 -e OMNIVOICE_API_KEY=…。
如果浏览器要从与后端不同的源加载 UI(例如通过该机器的局域网 IP 打开 :3901 上的 Vite 开发服务器),你还需要后端 CORS 允许列表包含该源——参见 来自其他源的浏览器(CORS);服务器模式和受信网络都不涵盖 CORS。
当设置了 OMNIVOICE_API_KEY 时,每个非回环请求都需要被接受的凭据。脚本应使用 Authorization: Bearer <key>。为兼容起见,旧的 ?api_key= 和 ov_key 传递方式仍被接受,但后端不再创建主密钥 cookie,且内置 UI 仅使用短期会话。机器本身上的回环流量仍然不受门禁限制。
2. 通过 Tailscale 访问¶
在两台机器上都安装 Tailscale(其客户端是 BSD-3 开源;如果你想要完全开源的方案,可以用 headscale 自托管控制平面)。然后,该机器可通过其 MagicDNS 名称访问:
对于 TLS(推荐——见下文警告),请将该端口置于机器上的 Tailscale Serve 之后:
Serve 在节点上终止连接并从 127.0.0.1 转发,因此对后端来说,该请求看起来像是回环请求——这正是该路径仍然需要 API key 的原因(bearer 门禁在非本地暴露场景下不依赖来源地址;设置密钥后,它始终适用于受密钥保护的客户端)。Uvicorn 默认信任来自回环的代理头,因此 Serve 转发的 HTTPS scheme 会成为权威的 ASGI scheme,浏览器会话 cookie 会收到 Secure。对于非回环反向代理,请显式配置 Uvicorn 的 --forwarded-allow-ips=<proxy-ip>;应用本身从不信任任意的 X-Forwarded-Proto 头。
不要为此使用
tailscale funnel(公共互联网暴露)。即使有密钥,语音克隆后端也不应出现在开放的互联网上。
3. 在应用中指向远程¶
设置 → 共享 → 远程后端:
- 后端 URL:第 2 步中的 MagicDNS URL(如果你没有使用 Serve,则带
:3900;如果使用了,则不带端口)。 - API key:第 1 步中
OMNIVOICE_API_KEY的值。 - 测试连接:不使用凭据请求免认证的
{url}/health,如果健康检查成功,则将输入的密钥兑换为会话。 - 保存并重新加载:仅存储 URL,并针对远程后端重启 UI。密钥输入在一次兑换后被清空。URL 必须是完整的
http://或https://URL(单独的gpu-box:3900会被拒绝);保存未通过测试连接的 URL 时会先要求确认——错误的基地址会让应用无法访问任何后端,直到你在此处改回来。
将 URL 留空即可回到本地后端。
从浏览器使用(无需桌面应用)¶
你也可以用普通浏览器驱动远程后端——在 fragment 中带密钥打开一次 URL:
刻意使用 fragment(#,而不是 ?):fragment 永远不会发送到服务器,因此密钥不会出现在 GPU 机器或任何反向代理的请求日志中。fragment 会被同步清除,然后密钥兑换一次,获得最长八小时的会话;主密钥不会被存储。如果你的密钥包含 +、&、# 或 =,请进行 URL 编码(例如 #api_key=a%2Bb);来自上述 secrets.token_urlsafe 的密钥无需编码。此后,UI 会正常加载并使用短期会话。跨源的 bearer 会话以标签页为作用域;关闭标签页后需要重新输入。如果某个请求再次返回 401(密钥过期/错误/轮换),系统会提示你重新输入。当访问门禁是网络共享(而非远程密钥)时,同一个门禁会改为显示局域网共享 PIN 提示。
安全说明¶
- 明文 HTTP 可被嗅探。 在敌对网络上通过
http://传输的 bearer 密钥可能被从线路上读取。对于完全可信局域网以外的任何场景,请使用 Tailscale(WireGuard 加密)或 Tailscale Serve(TLS)。 - 第一方 UI 从不持久化
OMNIVOICE_API_KEY,从不创建包含它的 URL,也从不将其管理员会话放入 WebSocket URL。WebSocket 票据在 30 秒后过期,并且只能在一个路径上使用一次。 - API key 和局域网共享 PIN 相互独立:PIN 保护临时共享会话,而密钥是持久化的远程凭据。两者任一都可启用;设置后都会被检查。
- 除非在机器上设置
OMNIVOICE_SERVER_MODE=1,否则管理路由(/system/*、/api/settings/*)仍受回环门禁限制;在服务器模式下,API key 也是这些路由的访问控制(短共享 PIN 仅用于消费,不门禁管理)——参见下方的凭据规则。 - 使用
OMNIVOICE_TRUSTED_NETWORKS信任局域网或反向代理。 如果你在反向代理(nginx、Caddy、NPM)后面运行 VoiceStudio,或者只把它暴露在受信任的局域网/Tailnet 上,请将OMNIVOICE_TRUSTED_NETWORKS设置为逗号分隔的 CIDR 列表(例如192.168.1.0/24,10.0.0.0/8);来自这些网络的客户端随后会被消费门禁(共享 PIN、API key、听写 WebSocket)视为受信任,无需密钥/PIN。转发原始Host头的代理(Caddy 和 Nginx Proxy Manager 会这样做)还需要将其主机名加入OMNIVOICE_ALLOWED_HOSTS,否则没有 API key 的请求会因主机名无法识别而被拒绝;IP 地址、localhost和*.ts.net名称无需条目(参见 主机名)。管理路由(/system/*、/api/settings/*)仍然严格仅限回环——如需无头管理,请使用OMNIVOICE_SERVER_MODE=1。这是OMNIVOICE_SERVER_MODE=1(信任所有非回环来源)的细粒度替代方案,也能绕过会剥离Authorization头的代理。默认为空——不改变严格的回环默认值。受信任网络成员资格只是消费豁免——它本身永远不会解锁管理路由,即使在服务器模式下也是如此(#1213)。 与OMNIVOICE_SERVER_MODE=1结合使用时,未提供任何凭据的受信任网络客户端在管理路由上仍会收到403(除非完全没有配置凭据,即裸 Docker #261 流程,此时管理路由开放);如果设置了凭据,则只有 API key——而不是共享 PIN——能访问管理路由。完整的双层模型参见api-auth.md。 - 密钥以恒定时间比较,且永远不会被记录。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio