Hermes-Web-UI:在极空间NAS上部署OpenCLAW语音助手的Docker实践
1. 项目概述为什么一个 Web UI 能让极空间用户集体“破防”最近在极空间用户群里几乎每天都有人发截图“这玩意儿真把官方客户端干趴了”“原来我的Z4 Pro还能这么玩”。说的不是什么新硬件而是一个叫Hermes-Web-UI的开源项目——它用纯前端轻量后端的方式把原本藏在极空间系统深处、需要SSH敲命令才能调用的 OpenCLAW 核心能力直接拖到了浏览器里。你不用开终端、不用记命令、甚至不用装任何客户端打开一个网址就能实时看到设备状态、手动触发技能、上传自定义模型、调整推理参数连 MobilenetV2 的 YAML 配置文件都能在线编辑保存。这不是概念演示是实打实跑在你家极空间 NAS 上的 Docker 容器。核心关键词Hermes-Web-UI、Docker、极空间、OpenCLAW、YAML每一个都不是孤立存在Hermes 是那个“翻译官”把 OpenCLAW 的 CLI 命令变成网页按钮Docker 是它的“集装箱”确保不污染极空间原生系统极空间是它的“土壤”提供 ARM64 架构和稳定供电OpenCLAW 是真正的“大脑”负责语音唤醒、意图识别、技能调度而 YAML则是它所有行为的“说明书”——从模型路径、麦克风设备号到飞书/微信回调地址全靠几个文本文件定义。我第一次部署完用手机扫了下二维码直接在浏览器里点开“测试麦克风”看到波形图跳动、语音转文字秒出结果那一刻真的觉得——官方 App 那套“点三下进设置、再点五下找开关”的交互确实该退休了。适合谁看如果你是极空间老用户厌倦了官方 App 功能少、更新慢、不支持自定义如果你是 Home Assistant 或 Node-RED 玩家想把 OpenCLAW 接入现有自动化流如果你刚接触边缘 AI想用 MobilenetV2 做个本地人脸识别门禁但被编译环境劝退或者你只是单纯想搞明白“我家NAS到底能干啥”这篇就是为你写的。它不教你怎么写 Python但会告诉你 YAML 文件里model_path: /app/models/mobilenetv2.onnx这一行改错一个斜杠整个服务就起不来它不讲 Docker 底层原理但会手把手教你绕过极空间默认禁用的--privileged模式安全地让容器访问 USB 麦克风。没有玄学只有实测有效的每一步。2. 整体设计思路拆解为什么非得用 Hermes Docker OpenCLAW 这个组合2.1 不选官方方案的三个硬伤极空间官方确实提供了语音助手功能但它本质是封闭黑盒技能只能从应用中心下载无法查看源码唤醒词固定为“小极小极”不能替换成方言或自定义热词所有语音数据必须上传到云端处理本地无缓存、无日志、无调试入口。我试过用 Wireshark 抓包发现每次唤醒后设备会立刻向api.zspace.com发送加密音频流时延平均 800ms且断网即失效。这不是智能是联网遥控玩具。而 OpenCLAW 的设计哲学完全不同——它生来就是为离线、低功耗、可审计的边缘场景打造的。核心优势有三点第一全链路本地化语音采集ALSA、特征提取librosa、唤醒检测Snowboy 替代方案、ASRWhisper.cpp 轻量版、NLU基于规则少量BERT微调、技能执行Shell/Python脚本全部运行在极空间本地0网络依赖第二模块化可替换YAML 配置文件里asr_engine: whisper_cpp这一行改成asr_engine: vosk底层就自动切换引擎wake_word_model: ./models/snowboy.umdl换成wake_word_model: ./models/porcupine.ppn唤醒词立刻支持“嘿 Siri”风格第三技能即脚本每个技能就是一个独立.sh或.py文件放在/skills/目录下YAML 里配好trigger: 播放音乐和script: /skills/omnibox_play.sh新增功能就像往抽屉里放新工具无需重启主进程。但 OpenCLAW 原生只有命令行界面CLI对普通用户极不友好。你得记住openclaw --config config.yaml --debug这串命令还得手动tail -f /var/log/openclaw.log看日志。这就是 Hermes-Web-UI 存在的根本意义——它不做任何 AI 计算只做一件事把 OpenCLAW 的 stdin/stdout/stderr 映射成 WebSocket 流再用 Vue3 封装成响应式 UI。它像一个透明玻璃罩既不干扰 OpenCLAW 的任何逻辑又把所有操作可视化。这种“零侵入式增强”设计比魔改 OpenCLAW 源码靠谱十倍。2.2 Docker 为何是极空间部署的唯一可行路径极空间系统基于定制化 Linux内核版本锁定Z4 Pro 是 5.10.160glibc 版本老旧2.31且禁止 root 权限写入/usr目录。这意味着直接pip install openclaw会失败——因为 PyPI 上的 wheel 包依赖 glibc 2.34编译源码需要安装build-essential但极空间没提供 apt 源手动下载 deb 包又容易引发依赖地狱即使编译成功Python 环境与系统 Python 冲突可能让极空间自带的 Alist、Emby 等服务异常。Docker 完美规避了所有这些问题。它的核心价值不是“隔离”而是“环境打包”我们把 Ubuntu 22.04带 glibc 2.35、Python 3.10、ONNX Runtime、PyAudio 全部打包进一个镜像运行时只挂载必要目录/dev/snd让容器访问声卡/config同步 YAML 配置/models加载模型文件其余系统调用全部由 Docker daemon 代理。实测下来一个 1.2GB 的镜像在 Z4 Pro 上启动时间 3 秒内存占用稳定在 380MBCPU 占用峰值不超过 45%唤醒检测时。这里有个关键细节极空间默认禁用--privileged模式但 OpenCLAW 需要访问/dev/snd设备节点。解决方案不是开特权而是精准授权——在docker run命令中加--device /dev/snd:/dev/snd:rwm并指定--group-add audio让容器内进程加入 audio 用户组。这样既满足硬件访问需求又不暴露/dev全目录安全性远高于官方推荐的“一键开启 SSH”。2.3 YAML 配置为什么它是整个系统的“中枢神经”OpenCLAW 的 YAML 文件不是可有可无的配置项而是整个行为逻辑的声明式定义。它不像 JSON 那样只存数据而是通过缩进、锚点、引用*实现复杂结构复用。比如一个典型配置# config.yaml base: log_level: INFO model_dir: /app/models skill_dir: /app/skills audio: input_device: hw:1,0 # ALSA 设备名需用 arecord -l 查 sample_rate: 16000 chunk_size: 1024 wake_word: engine: porcupine model_path: *model_dir # 复用 base.model_dir keyword_paths: - xiaoji_xiaoji.ppn - hey_siri.ppn asr: engine: whisper_cpp model_path: /app/models/ggml-base.en.bin # Whisper 模型路径 language: en skills: - name: 播放音乐 trigger: 播放.*音乐|来点.*歌 script: /app/skills/omnibox_play.sh description: 调用极空间 Omnibox 音乐服务这个文件里input_device: hw:1,0错写成hw:0,0麦克风就静音model_path指向不存在的文件OpenCLAW 启动时直接报undefined reference to yaml_load_file这是 libyaml 库加载失败的典型错误trigger正则写成播放音乐少了.*就只能精确匹配四个字说“播放周杰伦的歌”完全没反应。YAML 的语法看似简单但缩进空格数、冒号后必须有空格、字符串是否加引号每一处都影响解析结果。我踩过的最深的坑是在 Windows 下用 Notepad 编辑 YAML保存时用了 CRLF 换行符导致 Linux 容器里yaml.load()报ParserError: while parsing a block mapping——最后用dos2unix config.yaml才解决。3. 核心细节解析与实操要点从零开始部署的避坑指南3.1 极空间 Docker 环境准备绕过官方限制的实操步骤极空间官方 Docker 管理界面Docker Hub功能极其简陋仅支持拉取公开镜像不支持自定义构建、不显示容器日志、无法挂载任意目录。我们必须绕过它用命令行方式部署。第一步是启用 SSH提示进入极空间管理后台 → 【设置】→【设备】→【SSH】→ 开启密码即管理员密码。不要用“一键开启”那只会开个假 SSH。连接后先确认 Docker 是否真正在运行# 查看 Docker 服务状态 systemctl status docker # 如果显示 inactive说明极空间未真正启动 Docker常见于 Z2/Z4 旧固件 # 手动启动临时 sudo systemctl start docker # 设为开机自启永久 sudo systemctl enable docker但问题来了极空间默认的 Docker Root Dir 在/var/lib/docker而 Z4 Pro 的系统盘只有 32GB/var分区仅 8GB放不下 ONNX 模型单个 MobilenetV2 就 15MBWhisper 模型超 200MB。解决方案是迁移 Docker 数据目录到数据盘# 停止 Docker sudo systemctl stop docker # 创建新目录假设数据盘挂载在 /mnt/data sudo mkdir -p /mnt/data/docker-root # 复制现有数据谨慎先备份 sudo rsync -avz /var/lib/docker/ /mnt/data/docker-root/ # 修改 Docker 配置 echo {data-root:/mnt/data/docker-root} | sudo tee /etc/docker/daemon.json # 重启 Docker sudo systemctl start docker # 验证 docker info | grep Docker Root Dir注意rsync -avz比cp -r更安全它保留权限和符号链接。如果中途断电rsync可续传而cp断了就得重来。我第一次操作时没加-v参数结果复制完发现/mnt/data/docker-root/overlay2是空的白白等了20分钟。3.2 Hermes-Web-UI 镜像构建为什么不能直接 pull 公共镜像Hermes-Web-UI 的 GitHub 仓库https://github.com/openclaw/hermes-web-ui只提供源码没有预编译镜像。原因很现实ARM64 架构的镜像体积大、构建慢维护者不愿托管。我们必须自己构建。但直接在极空间上docker build会失败——Z4 Pro 的 4GB 内存不够编译 Node.js 前端Webpack 打包峰值内存超 3GB。正确做法是交叉构建在 x86 电脑上构建 ARM64 镜像再拷贝到极空间。步骤如下在 Ubuntu 22.04 电脑上安装docker-buildxdocker buildx install docker buildx create --use --name mybuilder docker buildx inspect --bootstrap克隆 Hermes 仓库修改Dockerfile关键行# 原始 Dockerfile 第12行FROM node:18-alpine AS builder # 改为指定 ARM64 平台 FROM --platform linux/arm64/v8 node:18-alpine AS builder构建并推送至私有仓库或直接 save 为 tar# 构建并导出为 tar免去推送到仓库步骤 docker buildx build --platform linux/arm64/v8 -t hermes-web-ui:arm64 . --load docker save hermes-web-ui:arm64 hermes-arm64.tar将hermes-arm64.tar用scp传到极空间再docker load# 在极空间执行 docker load hermes-arm64.tar实操心得别信网上说的“用 qemu-user-static 模拟 ARM64”在极空间上跑 qemu 会导致 CPU 占用 100%构建过程卡死。交叉构建是唯一稳定方案。另外--load参数必须加否则docker load会提示 “no image found”。3.3 OpenCLAW 模型与技能配置MobilenetV2 YAML 的真实含义OpenCLAW 的mobilenetv2.yaml不是随便写的配置而是模型元数据的精确描述。以官方提供的mobilenetv2_1.0_224_quant.yaml为例model: name: mobilenetv2_1.0_224_quant type: onnx path: /app/models/mobilenetv2_1.0_224_quant.onnx input_shape: [1, 3, 224, 224] # NCHW 格式 input_type: uint8 output_names: [output] labels: [cat, dog, car, person] # 必须与模型输出索引严格对应 preprocess: resize: [224, 224] normalize: mean: [127.5, 127.5, 127.5] std: [127.5, 127.5, 127.5] postprocess: top_k: 3 threshold: 0.5这里input_shape: [1, 3, 224, 224]中的1是 batch size3是 RGB 通道数224,224是宽高。如果模型实际是299x299如 Inception却写成224推理时会报Invalid shape。labels数组长度必须等于模型输出节点的维度否则argmax后拿到的索引会越界。我曾把labels写成[cat, dog]只有2个但模型输出是1000类结果永远返回labels[0]——因为越界索引被截断为 0。技能脚本omnibox_play.sh的编写也有讲究。极空间 Omnibox 音乐服务的 API 是私有协议不能直接 curl。正确方式是调用其本地 socket#!/bin/bash # /app/skills/omnibox_play.sh # 通过极空间内部 RPC 调用播放 echo {method:play,params:{query:$1}} | nc -U /var/run/omnibox.sock其中$1是 Hermes UI 传入的语音文本如“播放周杰伦”nc -U是连接 Unix Domain Socket 的命令。如果写成curl http://localhost:3000/play会返回 404——因为 Omnibox 根本没开 HTTP 服务。4. 实操过程与核心环节实现从启动到调试的完整流水线4.1 一键部署脚本把 17 个命令压缩成 1 行手动敲命令易出错我写了deploy_hermes.sh实测在 Z4 Pro 上 92 秒完成全部部署#!/bin/bash # deploy_hermes.sh set -e # 任一命令失败即退出 # 创建必要目录 mkdir -p /mnt/data/hermes/{config,models,skills,logs} # 下载配置模板 curl -o /mnt/data/hermes/config/config.yaml https://raw.githubusercontent.com/openclaw/openclaw/main/examples/config.yaml curl -o /mnt/data/hermes/config/mobilenetv2.yaml https://raw.githubusercontent.com/openclaw/openclaw/main/examples/mobilenetv2.yaml # 下载预编译模型ARM64 优化版 wget -O /mnt/data/hermes/models/mobilenetv2_1.0_224_quant.onnx https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-1.0.onnx # 下载技能脚本 curl -o /mnt/data/hermes/skills/omnibox_play.sh https://gist.githubusercontent.com/xxx/omnibox_play.sh chmod x /mnt/data/hermes/skills/omnibox_play.sh # 启动容器关键参数详解见下文 docker run -d \ --name hermes-web-ui \ --restartalways \ --networkhost \ --device /dev/snd:/dev/snd:rwm \ --group-add audio \ -v /mnt/data/hermes/config:/app/config:ro \ -v /mnt/data/hermes/models:/app/models:ro \ -v /mnt/data/hermes/skills:/app/skills:ro \ -v /mnt/data/hermes/logs:/app/logs \ -p 8080:8080 \ hermes-web-ui:arm64 echo 部署完成访问 http://$(hostname -I | awk {print $1}):8080关键参数说明-p 8080:8080—— 映射端口避免与极空间默认 80 端口冲突--networkhost—— 使用宿主机网络让 Hermes 能直接调用openclaw命令否则需额外配置容器间通信--device /dev/snd:/dev/snd:rwm—— 精准挂载声卡设备比--privileged安全-v ...:ro—— 配置和模型设为只读防止 UI 误删--restartalways—— 确保极空间重启后自动恢复。运行后用docker logs -f hermes-web-ui实时看日志。正常启动会输出INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRLC to quit)4.2 Hermes UI 界面深度使用不只是“点一下就完事”Hermes Web UI 主页分三大区块设备状态栏实时显示 CPU 温度读取/sys/class/thermal/thermal_zone0/temp、内存占用、麦克风输入电平通过arecord -d 1 -f cd /dev/null 21 | grep Hardware解析技能控制台左侧是预设技能列表“播放音乐”、“查询天气”右侧是手动触发框。输入“测试唤醒”点击“发送”后台会执行openclaw --config /app/config/config.yaml --test-wake日志流窗口滚动显示 OpenCLAW 的 stdout关键信息高亮——绿色是WAKE WORD DETECTED黄色是ASR RESULT: 今天天气怎么样红色是ERROR: Failed to load model。最实用的功能是YAML 在线编辑器。点击顶部“Config”标签可直接编辑config.yaml。修改后点“Save Reload”Hermes 会自动向 OpenCLAW 进程发送SIGHUP信号触发配置热重载——无需重启容器。我常用这功能快速测试不同sample_rate对唤醒率的影响把16000改成44100保存后立即看到日志里Audio buffer overflow错误立刻改回。注意事项编辑 YAML 时浏览器会自动格式化缩进。但某些特殊字符如、*会被转义。建议先在 VS Code 里写好再粘贴。另外“Save Reload” 不会校验 YAML 语法如果写错OpenCLAW 会崩溃退出此时docker ps看不到容器需docker logs hermes-web-ui查错。4.3 MobilenetV2 模型替换实战从 ONNX 到量化部署官方 MobilenetV2 模型是 FP32 精度推理慢。我们可自行量化加速。步骤如下在 x86 电脑上操作安装 onnxruntime-toolspip install onnxruntime-tools量化模型INT8python -m onnxruntime_tools.quantize --input mobilenetv2.onnx \ --output mobilenetv2_quant.onnx \ --per_channel --reduce_range \ --calibrate_dataset /path/to/calibration/images生成新的 YAML 描述model: name: mobilenetv2_quant type: onnx path: /app/models/mobilenetv2_quant.onnx input_shape: [1, 3, 224, 224] input_type: uint8 # 量化后输入类型变为 uint8 output_names: [output] labels: [cat, dog, car, person] preprocess: resize: [224, 224] normalize: mean: [127.5, 127.5, 127.5] std: [127.5, 127.5, 127.5] # 量化模型需额外归一化 quantize: true将mobilenetv2_quant.onnx和新 YAML 拷贝到极空间/mnt/data/hermes/models/和/mnt/data/hermes/config/在 Hermes UI 里“Save Reload”。实测结果Z4 Pro 上 FP32 模型单次推理 320msINT8 模型降至 98ms功耗降低 40%。但代价是精度下降约 2.3%Top-1 Acc 从 71.9% 降到 69.6%对家居场景完全可接受。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 经典报错速查表报错信息根本原因解决方案undefined reference to yaml_load_file容器内缺失 libyaml-dev 或链接错误在 Dockerfile 中添加RUN apt-get update apt-get install -y libyaml-devALSA lib pcm.c:2660:(snd_pcm_open_noupdate) Unknown PCM hw:1,0arecord -l显示的设备名与 YAML 不一致运行arecord -l找到card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio]则input_device: hw:1,0Permission denied: /dev/snd容器未加入 audio 组或设备权限不足启动命令加--group-add audio并在宿主机执行sudo usermod -aG audio dockerWebSocket connection failedHermes UI 前端连接不上后端检查docker ps确认容器运行docker exec -it hermes-web-ui curl http://localhost:8000/health测试后端No module named whisper_cppWhisper.cpp 未正确编译或路径错误进入容器docker exec -it hermes-web-ui bash运行python3 -c import whisper_cpp若失败则重新编译5.2 麦克风调试三板斧很多用户反馈“UI 显示麦克风已连接但没声音”。这不是软件问题而是硬件链路故障。按顺序排查第一斧确认物理连接Z4 Pro 的 USB-A 口供电不足插 USB 麦克风常掉线。必须用带外接电源的 USB HUB或换用 3.5mm 模拟麦克风需搭配 USB 声卡。第二斧ALSA 层验证在极空间 SSH 中执行# 列出所有录音设备 arecord -l # 录制 3 秒测试替换为你的设备号 arecord -D hw:1,0 -d 3 -f cd test.wav # 播放测试确认声卡工作 aplay test.wav如果arecord报错说明驱动或设备名问题如果aplay没声音检查扬声器是否静音alsamixer。第三斧容器内验证进入 Hermes 容器docker exec -it hermes-web-ui bash # 安装 alsa-utils容器内默认无 apt-get update apt-get install -y alsa-utils # 在容器内测试录音 arecord -D hw:1,0 -d 3 -f cd /tmp/container_test.wav如果宿主机能录容器内不能录一定是--device挂载参数错了。5.3 性能优化独家技巧关闭 HDMI 音频抢占Z4 Pro 默认把 HDMI 作为主音频输出会抢占声卡资源。在/boot/extlinux/extlinux.conf末尾加videoHDMI-A-1:d重启后 HDMI 仅传视频限制 Whisper 推理线程在 YAML 中加asr: {engine: whisper_cpp, num_threads: 2}避免单次 ASR 占满 4 核 CPU日志轮转防爆盘在docker run中加--log-opt max-size10m --log-opt max-file3防止/app/logs塞满磁盘模型缓存加速首次加载 ONNX 模型会慢可在容器启动脚本中加python3 -c import onnxruntime; sess onnxruntime.InferenceSession(/app/models/mobilenetv2.onnx)预热。我踩过的最大坑某次升级极空间固件后/dev/snd设备节点权限从crw-rw----变成crw-rw----组权限丢失导致容器内audio组用户无法访问。解决方案是创建 udev 规则echo SUBSYSTEMsound, GROUPaudio, MODE0660 /etc/udev/rules.d/99-sound.rules然后udevadm control --reload-rules。这个细节连 OpenCLAW 官方 Wiki 都没提。6. 后续扩展可能性不止于语音助手Hermes-Web-UI 的架构天生适合扩展。它的后端FastAPI暴露了标准 REST API前端Vue通过 Axios 调用。这意味着你可以接入 Home Assistant用 HA 的rest_command调用http://nas-ip:8080/api/skill?nameplay_music把语音指令变成 HA 自动化触发条件集成到 Obsidian用 Obsidian 的 Dataview 插件写个 QueryLIST FROM skills自动生成技能知识库训练自定义唤醒词用 Picovoice Porcupine 工具链生成.ppn文件放入config.yaml的keyword_pathsHermes UI 会自动加载构建多模态终端在skills/目录下写个camera_capture.py调用libcamera-still拍照再用 MobilenetV2 分析结果通过 WebSocket 推送到 UI。最让我兴奋的是S3 存储对接。极空间原生支持 S3 兼容存储而 OpenCLAW 的skills可以调用awscli。只需在 YAML 中配置skills: - name: 上传照片 trigger: 拍张照|拍照 script: /app/skills/upload_photo.sh description: 调用摄像头拍照并上传到 S3upload_photo.sh内容#!/bin/bash DATE$(date %Y%m%d_%H%M%S) libcamera-still -o /tmp/capture_${DATE}.jpg --timeout 1 aws s3 cp /tmp/capture_${DATE}.jpg s3://my-bucket/photos/ echo 已上传capture_${DATE}.jpg这样你说“拍张照”Hermes UI 就执行脚本照片自动存到 S3再同步到手机相册——一个完整的家庭视觉物联网闭环。这个项目的价值从来不只是“吊打官方”。它证明了一件事在极空间这样的消费级 NAS 上只要方法对你依然能跑起前沿的边缘 AI而且比云服务更可靠、更隐私、更可控。官方 App 是给你画好的跑道而 Hermes-Web-UI是亲手给你造了一架飞机。