跳过正文

从零搭建 Agent Devbox:Ubuntu + Hermes Agent + Claude Code 完整指南

·3490 字·7 分钟·
Hariketsu
作者
Hariketsu
ハロー・ワールド
目录

在 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 urlmimo-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_audio content 类型传入,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    # 查状态

改完 .envconfig.yaml 后记得重启 gateway。

目前feishu.py 直接改动了源码,如果后续用 hermes update 更新版本,feishu.py 的修改会被覆盖。

笔者打算有空提个issue/PR


写在最后
#

这套 devbox 现在已经稳定运行:飞书和微信双平台在线,中文语音自动转写,Claude Code 随时待命帮忙debug。整个过程踩了不少坑,但方向是对的——Hermes 开源确实给了一定的扩展空间,不过体验最深的是 Claude Code 在运维场景无比高效。

如果你也在搭类似的环境,希望这篇能帮你少走弯路。