在 NAS 的 Ubuntu 虚拟机上从零部署 Hermes Agent,对接飞书和微信,踩坑语音转文字,顺便把 Claude Code 装上去当运维工具。本文记录完整过程。
前言:为什么要折腾这个 #
我想要一个常驻在服务器上的 AI 助理,能接入飞书和微信,自动处理消息、执行任务、转写语音,顺便研究一下Agent Memory机制和LLM Wiki。
近期爆火的 Hermes Agent 无疑是首选,Hermes 是由 Nous Research 开源的 AI Agent,已原生支持飞书、微信、Telegram、Discord 等十余个平台,内置 STT/TTS、记忆系统、定时任务,架构相对成熟。于是决定在 NAS 通过 Ubuntu 虚拟机进行部署。
为了方便后续运维(改配置、查日志、重启服务),我提前装上了 Claude Code 作为助手。
这篇文章会从零开始,希望对大家有帮助。
环境:Ubuntu 24.04 LTS 、局域网 HTTP 代理
一、基础环境 #
sudo apt update
sudo apt install -y curl ca-certificates git build-essential
sudo apt install -y micro # 终端编辑器,比 nano 好用
sudo apt install -y openssh-server # SSH 远程登录
sudo systemctl enable --now ssh # 用熟悉的终端连接SSH比较舒服
micro是 nano 的现代替代品,快捷键跟现代编辑器一致,对新手友好。
防火墙(UFW) #
趁 SSH 刚装好,补齐基础网络安全规则:
# 放行 SSH,否则防火墙一开你就连不上了
sudo ufw allow 22/tcp
# 预留给 Hermes Agent 的端口(后面第六节会用到)
sudo ufw allow 9119/tcp # Hermes Dashboard
sudo ufw allow 8642/tcp # Hermes Gateway
# 默认拒绝所有入站请求
sudo ufw default deny incoming
# 启用防火墙
sudo ufw enable
验证:
sudo ufw status verbose
放行 TCP 即可
二、网络代理 #
本机通过局域网代理转发访问世界。
代理地址每个人不一样,下面用 192.168.1.2:7890 示意,请替换成你自己的代理。
先把代理写进 ~/.bashrc 持久化:
cat >> ~/.bashrc <<'EOF'
# LAN proxy (replace with your own)
export http_proxy="http://192.168.1.2:7890"
export https_proxy="http://192.168.1.2:7890"
export HTTP_PROXY="$http_proxy"
export HTTPS_PROXY="$https_proxy"
export all_proxy="$http_proxy"
export ALL_PROXY="$http_proxy"
export no_proxy="localhost,127.0.0.1,::1"
export NO_PROXY="$no_proxy"
EOF
source ~/.bashrc
注意:Git 也需要单独配置代理,否则 git clone 可能有问题:
git config --global http.proxy "http://192.168.1.2:7890"
git config --global https.proxy "http://192.168.1.2:7890"
三、Node.js(nvm) #
Hermes 和 Claude Code 都依赖 Node.js 运行时。
用 nvm 管理版本,方便后续切换:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
# 加载 nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# 安装 Node.js LTS
nvm install --lts
# 验证
node -v
npm -v
如果下载脚本失败,先
curl -I https://raw.githubusercontent.com进行排查。必要时可下载脚本到本地用METHOD=script bash /tmp/install_nvm.sh执行。
四、Claude Code——运维用的 AI 终端 #
Hermes 负责处理IM平台的交互,在此之前我还需要一个能在终端里直接帮我改配置、查日志、排查问题的运维Agent。
npm install -g @anthropic-ai/claude-code@2.1.150
为什么锁版本 #
当前使用 DeepSeek API 的 Anthropic 兼容端点(api.deepseek.com/anthropic)。较新版本的 Claude Code 在兼容端点上有适配问题,所以:
- 锁版本
@2.1.150 - 禁用自动更新:
"DISABLE_AUTOUPDATER": "true"
Claude Code 配置 #
~/.claude/settings.json:
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "<your-deepseek-api-key>",
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-pro[1m]",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro[1m]",
"ANTHROPIC_MODEL": "deepseek-v4-pro[1m]",
"CLAUDE_CODE_EFFORT_LEVEL": "max",
"CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-v4-flash",
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
"DISABLE_AUTOUPDATER": "true"
},
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.local)",
"Read(./secrets/**)",
],
"defaultMode": "auto"
},
"language": "简体中文",
"theme": "dark"
}
配置说明:
ANTHROPIC_BASE_URL指向 DeepSeek 兼容端点,模型映射到 DeepSeek V4 系列CLAUDE_CODE_EFFORT_LEVEL: "max"开启最强模式permissions.deny禁止读取.env等敏感文件,避免 API key 泄漏defaultMode: "auto"允许 Claude Code 在大多数情况下自动执行,减少审批打断
重启终端后 claude 命令即可用。
五、Hermes Agent 安装 #
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
安装脚本把 hermes 放到 ~/.local/bin/,并自动追加 PATH 到 ~/.bashrc。
初始化 #
hermes setup
这是一个交互式向导,会引导配置:
- 模型提供商:我选择 custom,填入
base url和mimo-v2.5-pro。特别鸣谢 Xiaomi mimo Token Plan 的赞助 - API keys:写入
~/.hermes/.env - 平台对接:飞书、微信等
hermes memory setup # 初始化持久记忆系统
飞书平台配对 #
使用飞书扫描二维码 或 打开 setup过程 提供的link进行 创建机器人
在飞书和机器人打招呼之后会得到一条命令,在 Hermes 宿主机粘贴即可
hermes pairing approve feishu <PAIRING-CODE>
微信平台的对接流程类似,使用微信扫描二维码连接微信ClawBot
六、Gateway 注册为系统服务 #
为了让Hermes Gateway 7×24 运行且和NAS一起开机自启。
需要把它注册为 systemd 服务:
# 注意:必须用 sudo + 完整路径,root 的 PATH 不包含 ~/.local/bin
sudo /home/user/.local/bin/hermes gateway install --system
sudo systemctl start hermes-gateway.service
sudo systemctl status hermes-gateway.service
这会生成 /etc/systemd/system/hermes-gateway.service,关键配置:
[Service]
Type=simple
User=user
ExecStart=/home/user/.hermes/hermes-agent/venv/bin/python \
-m hermes_cli.main gateway run --replace
Environment="HERMES_HOME=/home/user/.hermes"
Restart=always
RestartSec=5
KillSignal=SIGTERM
后续改配置重启:
sudo systemctl restart hermes-gateway.service
仪表盘(可选) #
hermes dashboard --host 0.0.0.0 --insecure --tui
--host 0.0.0.0 允许局域网内其他设备访问;--insecure 跳过 HTTPS(内网用);--tui 同时启动终端 UI。
七、飞书语音转文字(STT)踩坑记 #
这个是整个搭建过程中最折腾的部分,值得单独写一节。
背景 #
微信和飞书都支持用户发送语音消息,Hermes 收到后需要转成文字再交给 LLM 处理。Hermes 内置了多个 STT provider(faster-whisper 本地模型、Groq/OpenAI/Mistral/xAI API),但我想用阿里百炼 DashScope 的 qwen3-asr-flash,相对来说中文识别效果更好、计费更便宜、无需额外维护网络。
第一个坑:qwen3-asr-flash 不是 Whisper API #
DashScope 的 qwen3-asr-flash 虽然可以用 OpenAI SDK 调用,但它不是 /v1/audio/transcriptions 兼容接口。它的调用方式是:
- base_url:
https://dashscope.aliyuncs.com/compatible-mode/v1 - endpoint:
/chat/completions(Chat Completions 接口) - model:
qwen3-asr-flash - 音频数据以
input_audiocontent 类型传入,base64 data URI 编码
所以不能直接把 Hermes 内置的 OpenAI STT provider 改个 base_url 就行。需要自己写 wrapper script。
好在 Hermes 已经内置了 command-type STT provider 机制,我们可以在 config.yaml 中声明一个自定义命令,Hermes 传给它 {input_path}(音频文件路径)和 {output_path}(转写结果路径),脚本负责调 API 写结果。
wrapper script 核心逻辑:
# 1. 读取音频文件
audio_bytes = Path(input_path).read_bytes()
# 2. 推断 MIME type
mime = "audio/ogg" # 根据扩展名判断
# 3. base64 编码
b64 = base64.b64encode(audio_bytes).decode("ascii")
data_uri = f"data:{mime};base64,{b64}"
# 4. 调用 DashScope Chat Completions
client = OpenAI(api_key=api_key, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
completion = client.chat.completions.create(
model="qwen3-asr-flash",
messages=[{
"role": "user",
"content": [{"type": "input_audio", "input_audio": {"data": data_uri}}]
}],
extra_body={"asr_options": {"language": "zh"}}
)
# 5. 写出结果
Path(output_path).write_text(completion.choices[0].message.content)
Hermes config.yaml 配置片段:
stt:
provider: qwen3-asr-flash
providers:
qwen3-asr-flash:
type: command
command: "python3 ~/qwen3_asr_flash.py --input {input_path} --output {output_path} --model {model} --language {language}"
model: qwen3-asr-flash
language: zh
format: txt
timeout: 120
第二个坑:飞书语音根本没走 STT 管道 #
当我配好 command provider、重启 gateway,给飞书发了一条语音:结果 Hermes 识别为我发了一个音频附件.ogg,甚至转头就准备调用faster-whisper(第三个坑会讲到)
翻日志发现,agent 收到的消息是:
[The user sent an audio file attachment: 'audio_xxx.ogg']
而不是预期的:
[The user sent a voice message~ Here's what they said: "..."]
问题出在 Hermes 的消息类型分发。gateway dispatch 代码(gateway/run.py)里有一段:
# MessageType.AUDIO = audio file attachment — never STT
# MessageType.VOICE = voice message — always STT
if event.message_type == MessageType.AUDIO:
audio_file_paths.append(path) # 当文件附件,不转录
elif event.message_type == MessageType.VOICE:
audio_paths.append(path) # 进 STT 转写管道
而飞书 adapter(gateway/platforms/feishu.py)把所有音频消息都映射为 MessageType.AUDIO(文件附件),而不是 MessageType.VOICE(语音消息)。在飞书平台上,用户发送的音频就是语音消息,不存在"音频文件"和"语音消息"的区分,所以这个映射是错的。
修复只需改一行:
# gateway/platforms/feishu.py
# 修改前
if preferred == "audio":
return MessageType.AUDIO
# 修改后
if preferred == "audio":
return MessageType.VOICE # 飞书所有音频都是用户语音
第三个坑:systemd 环境隔离导致 API key 不可见 #
改完上面两处、重启 gateway,飞书发语音:wrapper script 报错 DASHSCOPE_API_KEY is not set。
前面说过,Hermes Gateway 作为 systemd 服务运行。systemd 启动进程时使用干净的环境,只包含 service 文件中 Environment= 显式列出的变量。虽然 Hermes 源码在启动时调用了 load_hermes_dotenv() 试图加载 ~/.hermes/.env,但在 systemd 上下文中没能把变量注入进程环境。
Wrapper script 作为 child process 继承了空的 DASHSCOPE_API_KEY,自然找不到。
解决方案:让 wrapper script 自给自足,先检查环境变量,没有就从 ~/.hermes/.env 自己加载:
api_key = os.getenv("DASHSCOPE_API_KEY", "").strip()
if not api_key:
# systemd 下父进程可能没加载 .env,自己去读
from dotenv import load_dotenv
load_dotenv(os.path.expanduser("~/.hermes/.env"), override=True)
api_key = os.getenv("DASHSCOPE_API_KEY", "").strip()
if not api_key:
sys.exit("DASHSCOPE_API_KEY is not set")
三个坑踩完,飞书语音终于能正常转写了。
STT 问题总结 #
| 问题 | 根因 | 解决 |
|---|---|---|
| qwen3-asr-flash 接口不兼容 | 不是 Whisper /v1/audio/transcriptions,是 Chat Completions 接口 |
写 wrapper script + 用 Hermes command provider |
| 飞书语音不进 STT | feishu.py 把音频映射为 MessageType.AUDIO(文件附件,永不转录) |
改为 MessageType.VOICE |
| API key 不可见 | systemd 环境隔离,.env 未注入进程 |
wrapper 内置 dotenv fallback |
八、配置结构一览 #
~/.hermes/
├── config.yaml # 主配置(模型、平台、STT、session)
├── .env # API keys(DASHSCOPE_API_KEY 等)
├── bin/ # 自定义脚本(qwen3_asr_flash.py)
├── logs/ # gateway / agent / error 日志
├── sessions/ # 对话持久化
├── skills/ # 自定义 skills
└── audio_cache/ # 语音消息缓存
/etc/systemd/system/
└── hermes-gateway.service # systemd 服务定义
~/.claude/
└── settings.json # Claude Code 配置
九、日常运维速查 #
# Hermes
sudo systemctl restart hermes-gateway.service # 改配置后重启
sudo systemctl status hermes-gateway.service # 查状态
改完 .env 或 config.yaml 后记得重启 gateway。
目前feishu.py 直接改动了源码,如果后续用 hermes update 更新版本,feishu.py 的修改会被覆盖。
笔者打算有空提个issue/PR
写在最后 #
这套 devbox 现在已经稳定运行:飞书和微信双平台在线,中文语音自动转写,Claude Code 随时待命帮忙debug。整个过程踩了不少坑,但方向是对的——Hermes 开源确实给了一定的扩展空间,不过体验最深的是 Claude Code 在运维场景无比高效。
如果你也在搭类似的环境,希望这篇能帮你少走弯路。