Runbook · 2026-08 实测

小智全栈装机手册

把小智服务端和四个配套服务,远程部署到一台国内网络环境下的 Windows 机器。 这份文档写给执行部署的 agent——每一步都给到可直接执行的命令、验证方式,以及上一次实际踩到的坑。

目标环境
Windows 10/11 + 国内网络
服务数量
5 个
远程方式
Tailscale + OpenSSH
上次实测
2026-08-18
00

开工前:给 agent 的执行须知

你要操作的是别人的电脑。机主已经授权,但这不代表可以随便动——下面三条是硬规矩,上一次部署时每一条都付出过代价。

规矩一 · 不要把委托人的账号发过去

音乐服务的 API key、小智服务端的 LLM/TTS 凭据、B站和小宇宙的登录态,一律让机主自己注册、自己扫码。 你的角色是把服务跑起来并留好填凭据的入口,不是把自己这边的账号复制一份过去。

规矩二 · 打包前必须读 .gitignore,不要凭记忆列排除项

三个私有服务要从委托人的机器打包传过去。上次我凭记忆写 --exclude, 漏掉了 app/allow.json(设备 MAC 白名单),把它一起打进包传到了对方机器。 正确做法:tar 前先 cat .gitignore,按文件里写的来;打完包先列一遍包内容确认没有 state/cookies/凭据,再传。

规矩三 · 私有仓库 clone 不了,只能打包传

下面五个服务里有三个是私有仓库(git@github.com:tankxu/…),对方机器没有权限。 唯一的路径是在委托人的机器上 tarscp → 对方机器解压。 公开的那两个(小智服务端、CLIProxyAPI)可以直接在对方机器上拉。

开工前要跟机主要到的东西

要什么用途拿不到的后果
Tailscale 上的 IP建立 SSH 通道只能靠 ToDesk 手动点,效率差一个数量级
Windows 用户名配置计划任务的 -UserIdDocker 拉镜像那一步会卡死
局域网 IP(建议固定)设备要连的地址DHCP 一换设备就失联
是否装了代理软件排查网络问题的前提会把代理造成的现象错怪到别的地方
大模型 / TTS 的 key小智服务端服务能起但不出声

机器硬指标

上次那台是 Win10 专业版 22H2 / 16 核 / 32G / C 盘剩 125G,跑满五个服务无压力。 家庭版装不了 Hyper-V,但 WSL2 可以,所以家庭版也能跑——只是 Docker Desktop 只能走 WSL2 后端。 磁盘至少留 40G。

01

建立远程通道

先把 SSH 打通,后面所有操作都在 SSH 里做。ToDesk 只在两个场景用:装 Tailscale 之前的引导,和后面需要人在桌面上点确认的时刻(Docker Desktop 首次启动、B站扫码登录)。

验证

从你这边连过去,应当不问密码直接进,并且 IsAdminTrue

ssh w@100.x.x.x "powershell -c \"([Security.Principal.WindowsPrincipal] \
  [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators')\""

这条通道重启后依然可用

sshdtailscaled 都是系统服务,开机自启,不需要有人登录 Windows 桌面。 但下一节的 Docker Desktop 是用户级程序——机器重启后必须有人登录桌面,容器才会起来。这个区别后面会反复咬人。

02

三个 Windows 专有坑

这一节放在装东西之前,因为它们是整个部署里最耗时间的部分。上次五个服务里,每一个都在其中某一条上卡过。 先读完再动手,能省掉大半天。

坑一:SSH 会话里 Docker 拿不到 registry 凭据

任何碰 registry 的命令(docker pulldocker compose build)在 SSH 会话里都会报:

error getting credentials - err: exit status 1,
out: `A specified logon session does not exist.`

根因是 Docker Desktop 的凭据助手依赖 Windows 的交互式登录会话, 而 SSH 是非交互式会话,拿不到那个 token。这是设计限制,不是配置问题—— DOCKER_CONFIG--config 指向干净目录、删掉 credsStore 字段,这三种办法我都试了,全部无效

唯一可行的解法:注册一个 LogonType Interactive 的计划任务,把命令推回用户会话执行。

通用模板 —— 凡是要拉镜像 / build 的命令都套这个
$dir  = 'C:\xiaozhi-music-mcp'
$log  = "$dir\build.log"
$act  = New-ScheduledTaskAction -Execute 'cmd.exe' `
        -Argument "/c cd /d $dir && docker compose up -d --build > $log 2>&1"
$prin = New-ScheduledTaskPrincipal -UserId 'DESKTOP-XXXX\w' -LogonType Interactive

Register-ScheduledTask -TaskName 'dc-build' -Action $act -Principal $prin -Force
Start-ScheduledTask -TaskName 'dc-build'

计划任务是异步的,你拿不到退出码

Start-ScheduledTask 立刻返回,命令在后台跑。判断进度要靠轮询副作用docker images 里镜像有没有出现、build.log 的尾部、docker ps 的状态。

写轮询脚本时注意不要用宽泛的 grep 判成功——上次我用 grep ota 结果匹配到了日志里的 Total, 用 grep TTS 匹配到的其实是一条 TTS 失败日志,两次都误报了成功。 匹配要够具体(POST /xiaozhi/ota/ 这种),并且收集一个时间窗口的完整日志再判断,不要一命中就 break。

坑二:Windows 防火墙的 Block 规则优先于 Allow

Docker Desktop 安装时会自己写入一对规则:Allow / PrivateBlock / Public。 你后加的「Profile=Any 放行 8000」会被那条 Block 整个压住——因为 Windows 防火墙里 Block 永远赢 Allow,与添加顺序无关。

现象极其反直觉

你从 Tailscale 连得上,同一个局域网里的设备反而连不上。 因为 Tailscale 虚拟网卡被识别成 Private(走 Allow),而 WLAN 被识别成 Public(撞上 Block)。 排查时很容易怀疑到端口、容器、路由上去,实际上是网络配置文件的问题。

解法:把 WLAN 改成专用网络
Get-NetConnectionProfile          # 先看当前是 Public 还是 Private
Set-NetConnectionProfile -InterfaceAlias 'WLAN' -NetworkCategory Private

这比删掉 Block 规则更安全:笔记本带出门连公共 WiFi 时,那条 Public 的 Block 会继续保护。 另外仍然要为服务端口加放行规则:

New-NetFirewallRule -DisplayName 'xiaozhi-stack' -Direction Inbound -Action Allow `
  -Protocol TCP -LocalPort 8000,8002,8003,2233,2234,8080,8317,23020,23021

坑三:PowerShell 5.1 的编码

操作默认行为后果
Get-Content -Raw 按 ANSI 读 UTF-8 中文注释读成乱码,并且吃掉换行——compose 文件 88 行变 85 行,YAML 直接塌掉
> 重定向 写 UTF-16LE JSON 文件首字节是 0xFF,任何解析器都读不了
数组里拼引号 [char]34 被当独立元素 生成的字符串结构错乱
一律改用 .NET 方法读写
$enc = New-Object System.Text.UTF8Encoding $false   # $false = 不带 BOM
$txt = [System.IO.File]::ReadAllText($path, $enc)
[System.IO.File]::WriteAllText($path, $txt, $enc)

# 拼引号用反引号转义,不要用 [char]34
$s = "key=`"value`""
03

装 WSL2 与 Docker Desktop

验证

ssh w@100.x.x.x "docker version && docker run --rm docker.m.daocloud.io/library/hello-world"

hello-world 那条报 logon session 错误,说明必须走计划任务(坑一),这是预期内的,不是装错了。

04

国内网络镜像总表

每一个服务都在这张表里的某一行卡过。建议在 build 之前就把 Dockerfile 全部改好, 而不是等它失败再改——一次 build 失败在国内网络下可能要等好几分钟才超时。

依赖症状解法
Docker Hub 解析不了 production.cloudfront.docker.com 基础镜像全改 docker.m.daocloud.io/library/xxx;deno 用 docker.m.daocloud.io/denoland/deno:bin
Debian apt Unable to connect to deb.debian.org 见下方 sed。时通时不通——上次 B站服务侥幸过了,音乐服务就卡死,所以不要因为一个服务通了就跳过这步
PyPI 极慢或超时 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple
Go 模块 proxy.golang.org 不通 Dockerfile 里加 ENV GOPROXY=https://goproxy.cn,direct
npm npm config set registry https://registry.npmmirror.com
Debian 换源 —— 注意新版是 debian.sources 不是 sources.list
RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g; \
            s|security.debian.org|mirrors.tuna.tsinghua.edu.cn|g' \
    /etc/apt/sources.list.d/debian.sources

如果机器上装了代理软件

上次那台装了 0dcloud(Meta Tunnel,mihomo 内核)。TUN 模式下走 fake-ip, 所有域名解析到 198.18.x.x,HTTP 代理口在 127.0.0.1:17891。 容器出站也被它接管(不配代理也能连上 YouTube),但显式配 http://host.docker.internal:17891 更快更稳。

它的路由表是干净的(192.168.1.0/24 正常走 WLAN)——排查局域网连不通时不要错怪代理, 先按坑二查防火墙。

05

五个服务

目录统一放 C 盘根下,凭据各自存在同目录的 KEYS.txt 里,方便机主自己查改。 下面按建议的部署顺序排列——小智服务端是核心,其余四个是它的能力扩展,可按需取舍。

先建好目录结构

C:\xiaozhi-server       # 小智服务端(全模块)
C:\xiaozhi-music-mcp    # 音乐
C:\stackchan-bili       # B站转码
C:\xiaoyuzhou-server    # 小宇宙播客
C:\cli-proxy-api        # CLIProxyAPI

私有仓库的传输方式

在委托人的机器上执行
# 1. 先看清楚要排除什么 —— 不要凭记忆写
cat .gitignore

# 2. 打包(--exclude 按 .gitignore 来,state/data/cookies 一律不带)
tar --exclude='.git' --exclude='state' --exclude='data' \
    --exclude='__pycache__' --exclude='.env' \
    -czf /tmp/svc.tgz -C /path/to/repo .

# 3. 传之前先列一遍包内容,确认没有凭据混进去
tar -tzf /tmp/svc.tgz | grep -iE 'cookie|allow|token|secret|\.env|state/'

# 4. 传过去解压
scp /tmp/svc.tgz w@100.x.x.x:/C:/tmp/
ssh w@100.x.x.x "tar -xzf C:/tmp/svc.tgz -C C:/xiaozhi-music-mcp"

小智服务端(全模块)

8000 ws 8002 智控台 8003 视觉
来源
xinnan-tech/xiaozhi-esp32-server(公开,v0.9.6 全模块)
目录
C:\xiaozhi-server
组成
Python 主服务 + Java manager-api + Vue manager-web + MySQL + Redis
传输
公开仓库,对方机器直接拉
要机主自备
LLM key、ASR key、TTS key

启动顺序有依赖,不能一把 up:

  1. 先起 MySQL + Redis + manager-api + manager-web
  2. 浏览器开 http://<局域网IP>:8002注册的第一个账号自动是管理员
  3. 在智控台「参数管理」里取出 server.secret
  4. 把 secret 填进主服务的 data/.config.yaml,再起主服务

改了数据库要清 Redis 缓存

模型配置、音色(model:data:* / timbre:* / server:config)都在 Redis 里有缓存, 直接改库不清缓存不生效。但 agent 的 system_prompt 没有缓存键,改完设备重连即可。

选 ASR / TTS 时的实测经验

音色下拉里如果找不到某个音色(比如讯飞的 x6_pro),那是数据库预置数据里没有,需要自己插—— 不是配置写错了。讯飞 ASR/TTS + DeepSeek 这条链路已实测跑通。

音乐服务 xiaozhi-music-mcp

2234 8080 备用
来源
git@github.com:tankxu/xiaozhi-music-mcp私有,需打包传)
目录
C:\xiaozhi-music-mcp
音源
网易云 / YouTube / 哔哩哔哩,YTMusic 为主、B站兜底
关键端点
/stream_pcm(固件层)、/mcp(MCP 端点)、/manage?key=(管理页)、/admin?key=(设备白名单)
要机主自备
四个必填令牌(见下)+ 可选的 YouTube cookies

四个 .env 键缺一不可

ADMIN_USER / ADMIN_PASS / MCP_TOKEN / MCP_AUDIO_TOKEN 在 compose 里写的是 :? 语法——没设置就直接报错退出,不会用默认值。 这是故意的(防止把默认凭据带上线),不是 bug。

C:\xiaozhi-music-mcp\.env
ADMIN_USER=让机主自己定
ADMIN_PASS=让机主自己定
MCP_TOKEN=随机串
MCP_AUDIO_TOKEN=随机串
ALT_PORT=8080
LIVE_STREAM=0

data/ 是 bind mount(音乐缓存 + allow.json 设备白名单),重建容器不丢。 这个目录不要从委托人那边打包过去,让它在对方机器上重新生成。

YouTube 报「Sign in to confirm you're not a bot」

这是出口 IP 信誉问题,需要 cookies。但注意: yt-dlp --cookies-from-browser chrome 在 2026 年已彻底失效—— Chrome 127+ 的 App-Bound Encryption 把 cookies 绑定到应用身份,报 Failed to decrypt with DPAPI (yt-dlp #10927),这是设计上挡死的,改配置救不了。

可行路径只有两条:用 Firefox(cookies 是明文 sqlite),或用浏览器扩展导出 Netscape 格式。 拿到后放 data/cookies.txt 重启容器,代码会自动从 android_vr 客户端切回 web 客户端。

B站转码 stackchan-bili

2233 host 网络
来源
git@github.com:tankxu/stackchan-bili私有
目录
C:\stackchan-bili
网络
network_mode: host —— 不写也不能写 ports 映射
要机主自备
B站账号(扫码登录)

state/ 必须挂载

B站登录凭据 cookies.json 和播放历史都在 ./state不挂的话容器一重建登录态就没了,每次都要重新扫码。这个目录也不要从委托人那边带过来。

登录要在对方的桌面上完成:用 ToDesk 弹出扫码页,让机主用自己的B站 App 扫。 服务会轮询扫码状态(最长 3 分钟),成功后 state/cookies.json 落盘。

默认参数是给 CoreS3 屏调的

OUT_W=320 OUT_H=240 OUT_FPS=12 JPEG_Q=7、音频 16kHz 单声道。 BILI_QN=32 是 480P(实测免登录上限),要更低画质用 16。

小宇宙 xiaoyuzhou-server

23020 23021
来源
git@github.com:tankxu/xiaoyuzhou-server私有
目录
C:\xiaoyuzhou-server
技术栈
Go(build 需要 GOPROXY=https://goproxy.cn,direct
要机主自备
自己的小宇宙账号——不要用委托人的

Go 编译约 70 秒。上游 token 很短命,Mac 侧原本有自动 refresh 的机制; 如果对方机器上要长期用,需要确认 refresh 逻辑在 Windows 上也跑得起来——这一条上次没有验证

CLIProxyAPI

8317
来源
router-for-me/CLIProxyAPI(公开)
目录
C:\cli-proxy-api
要机主自备
上游账号的 OAuth 授权

最后一步只能由人来做

添加上游账号走的是 OAuth 浏览器授权,agent 代劳不了。 把服务跑起来、端口通了之后,剩下的登录动作留给机主,并把操作步骤写清楚交给他。

06

收尾:让它能被找到、且一直活着

交付清单

07

症状对照表

症状最可能的根因去哪一节
A specified logon session does not existSSH 非交互会话拿不到 Docker 凭据坑一
Tailscale 能连,局域网设备连不上防火墙 Block/Public 压住了 Allow坑二
compose 文件被改后 YAML 报错Get-Content -Raw 吃掉了换行坑三
JSON 文件读不了,首字节 0xFF> 重定向写成了 UTF-16LE坑三
Docker 引擎一直转圈起不来装了 16MB 的内核包而不是 250MB 完整包03-3
sshd 服务不存在但装的时候说成功了Add-WindowsCapability 拉不到组件01-2
密钥认证失败、继续问密码公钥放了 ~/.ssh/ 而不是 administrators_authorized_keys01-3
icacls 报找不到组中文版 Windows,要用 SID 不能用组名01-3
build 卡在 deb.debian.orgDebian 源没换04
音乐容器起来就退出四个必填 .env 键缺了服务卡 2
B站每次都要重新扫码state/ 没挂载服务卡 3
YouTube 报 bot 检测需要 cookies,且不能用 Chrome 导服务卡 2
改了模型配置不生效Redis 缓存没清服务卡 1
重启后所有服务都没了没人登录桌面,Docker Desktop 没启动06-4
08

这份文档没覆盖到的

诚实标注,避免下一个 agent 把没验证的当成已验证的:

上次部署的耗时分布,供估算

远程通道(Tailscale + OpenSSH)约 1 小时,WSL/Docker 底座约 2 小时(大半耗在那个 16MB 的错误安装包上), 五个服务约 4 小时(大半耗在 Docker 凭据和各种换源上)。 照着这份文档走,底座和换源的时间应该能压掉大半。

基于 2026-08-18 一次真实远程部署整理。目标机 Windows 10 专业版 22H2 / 16 核 / 32G。
文档里所有「上次」指的都是那一次,标注为「没有验证」的条目请自行确认后再依赖。