家里十几台服务器的命令历史,被我塞进了一个 hishtory:顺便接上自建 AI 网关
家里十几台服务器的命令历史,被我塞进了一个 hishtory:顺便接上自建 AI 网关
dong4j前言
家里这些年陆陆续续堆起来的服务器,现在打开 ~/.ssh/config 数了一下,大大小小十几个 entry:PVE 主节点、PVE 子节点、NAS、软路由、一堆 LXC、几台跑服务的虚拟机、再加上两台 Mac mini。
多了之后就有个很典型的痛苦场景:
“我之前写过一条
ffmpeg命令把 MKV 转 MP4 的,在哪台机器上来着?”
然后就开始 ssh 每台机器跑一遍 history | grep ffmpeg,还翻不到。因为 zsh 默认的 history 是按机器本地存的,一旦换了机器就没了。更烦的是有时候想在一批新机器上统一跑几条命令(比如同步 docker compose 配置、修 sshd_config),还得一台一台粘贴,粘着粘着就怀疑人生。
我之前也试过 Atuin,功能更花哨,但它的 TUI 相对重一些,而且我只想要一个”跨机器搜索 + 执行时的完整上下文“,并不需要太多仪式感。最后选定了 hishtory,理由很简单:
- 记录的上下文足够多:命令、cwd、退出码、运行时长、主机名、时间,一条都不少;
- 自带端到端加密 + 自建后端,数据全在家里机器上,不出内网;
Ctrl+R弹 TUI,搜全家机器,模糊匹配、按主机名过滤都行;- 附赠一个 AI 自然语言补全(
? list all files larger than 1MB这种),我本来就有自建的 OneAPI 网关,对接起来不亏。
这篇就记录一下完整的折腾过程,以及那个折腾我最久的坑:AI endpoint 必须填完整 URL。
先把自建 server 架起来
hishtory 所有数据默认会发到官方托管的服务器,虽然是端到端加密的,但我还是习惯自建。官方提供了 Go 写的后端 binary,也有 docker-compose 模板,我直接用 Docker 起一个,数据库用 SQLite(我就自己用,完全没必要上 Postgres)。
我的自建 server 跑在家里一台常年在线的小 x86 机器上(192.168.31.110),对外暴露 4000 端口:
1 | # docker-compose.yml |
起来之后 curl http://192.168.31.110:4000/api/v1/ping 能返回就算成了。
💡 我没有把这个 server 暴露到公网。出门在外的机器(比如我那台 MacBook)是走 WireGuard 回家里局域网的,所以所有客户端填的
HISHTORY_SERVER都是局域网地址,省掉了一层 Cloudflare 代理和 HTTPS 证书的麻烦。
客户端安装与多机绑定
在第一台机器上跑官方的一键安装脚本:
1 | # 先指定 server,再装 |
装完之后看一下状态:
1 | hishtory status |
输出里会有一个 secret key(形如 xxxx-xxxx-xxxx-xxxx),这个就是”我的账号凭证”,后面所有机器都靠它加入到同一个数据池。
然后在其他每一台机器上,都用这段:
1 | export HISHTORY_SERVER="http://192.168.31.110:4000" |
装完之后,它会往你 shell 的 rc 文件里(.bashrc / .zshrc / fish config)加一段 hook,后续每一条命令都会被它捕获并异步发回 server。
我的 .zshrc 末尾现在长这样:
1 | # Hishtory Config: https://github.com/ddworken/hishtory |
验证一下跨机器同步
随便在 A 机器上跑一条命令:
1 | echo "hello from A" && date |
立刻切到 B 机器,按 Ctrl+R,输入 hello from A,能看到那条记录,并且右边显示主机名是 A —— OK,全家互通了。
真正解决痛点的查询语法
跨机搜索只是第一步,hishtory 的查询语法才是解决”我之前在哪里跑过 XXX”这类问题的关键:
| 查询 | 含义 |
|---|---|
ffmpeg | 所有包含 ffmpeg 的命令 |
ffmpeg hostname:nas | 只看 nas 这台机器上跑过的 ffmpeg |
exit_code:0 docker | 所有成功跑完的 docker 命令(过滤掉自己打错的) |
"docker compose up" after:2025-03-01 | 3 月之后 docker compose up 的命令 |
user:root | 只看以 root 身份跑过的命令 |
我最常用的是 hostname:xxx,因为有时候就是很清楚地记得”这条命令我肯定是在 pve1 上跑的”,但具体是啥忘了,过滤一下立刻就出来。
还有个隐藏技能:这些 query 直接在 TUI(Ctrl+R)里就能用,不用退出来 hishtory query。
顺手改了下显示列
默认那堆列信息量太大,一屏放不下。我把它精简成三列:
1 | hishtory config-set displayed-columns Hostname CWD Command |
这样 TUI 里就只剩主机名、目录、命令,看着清爽多了。退出码和时间那些不是每次都关心,真要看的时候按 Ctrl+H 能展开完整信息。
接入自建的 OneAPI —— 这篇的重点坑
前面是常规操作,下面才是我专门想记下来的部分。
hishtory 支持在 Ctrl+R 的 TUI 里,用 ? 开头让 AI 给你生成命令,比如:
1 | ? 列出所有大于 1MB 的文件 |
它会把这段描述发给 OpenAI 的 /v1/chat/completions,拿到建议命令直接填进你 TUI 的输入框。默认指向的是官方 endpoint:
1 | DefaultOpenAiEndpoint = "https://api.openai.com/v1/chat/completions" |
但我不想直连 OpenAI(家里网络直连 OpenAI 本来也不稳),我已经有一个 OneAPI 在家里跑着,聚合了一堆渠道的 API,对外暴露 OpenAI 兼容的接口。照理说直接把 endpoint 换过去就行。
第一次配置:我以为只要填 base URL
一开始我是这么配的(大错特错):
1 | hishtory config-set ai-completion-endpoint https://oneapi.dong4j.site |
然后 TUI 里打 ? list all files larger than 1MB,返回 404。
1 | Error: 404 page not found |
一开始我以为是 OneAPI 那边路由问题,跑去看 OneAPI 的访问日志,发现压根没收到请求,或者收到了但路径是 /,不是 /v1/chat/completions。
搞清楚之后:必须填完整路径
翻了几个 issue 之后才看明白 (#304、#186、PR #231):
ai-completion-endpoint的值是整条 URL,hishtory 发请求的时候**不会再拼/v1/chat/completions**,而是直接 POST 这个 URL。
这一点和大多数 OpenAI SDK / LangChain 之类库的设计是反过来的 —— 那些通常让你填 base_url,然后内部拼上 /v1/chat/completions。hishtory 是”填啥就打啥”。
所以正确的配法是:
1 | # 公网走 Cloudflare 的入口 |
配完之后把 API Key 和模型也设置上:
1 | export OPENAI_API_KEY="sk-xxxxxxxx" # OneAPI 里签出来的令牌 |
回到 TUI,再来一次 ? list all files larger than 1MB,瞬间就有了:
1 | find . -type f -size +1M |
顺手再来一个常用的:
1 | ? 杀掉所有 node 进程但别动 npm |
它会给我 pkill -f '^node' | grep -v npm,细节上有点小问题但大方向对,改两下就能用。
我的完整配置(zshrc 片段)
这是我现在每台机器上通用的配置段,抄的时候把 HISHTORY_SERVER 和 OPENAI_API_* 改成你自己的:
1 | # ----- hishtory ----- |
ai-completion-endpoint 因为是存在 hishtory 配置里的(~/.hishtory/config.json),所以只需要首次配置一次,不用写到 rc 文件里。
一些小 Tips
几个用一段时间后才摸清楚的细节:
临时不想被记录:跑敏感命令(比如带密码的 mysql 登录)前先
hishtory disable,跑完再hishtory enable。或者命令前加个空格(HISTCONTROL=ignorespace默认行为)某些 shell 里也会被 hishtory 忽略。误记录了怎么办:
hishtory redact <keyword>把含关键字的历史批量删除,TUI 里选中某条后Ctrl+K也能删单条。自己升级客户端:
hishtory update就行,不用重新跑 install.py。按自定义列排查:可以加
custom-columns,比如我给每条命令都附上当时 git 仓库的 remote:1
2
3hishtory config-add custom-columns git_remote \
'(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true'
hishtory config-add displayed-columns git_remote这样在 TUI 里能直接看出”这条命令当时是在哪个仓库里跑的”,找陈年代码命令的时候很有用。
最后聊两句
用下来最直观的感受是:我终于不用在 ssh 窗口之间来回翻 history 了。
所有机器的命令像被摊在同一张桌子上:
- 在 MacBook 上按一下
Ctrl+R,能看到自己上周在 PVE 主节点上敲的那条qm set; - 把它选中回车,命令就填到当前 MacBook 的终端里 —— 如果是在 MacBook 上跑就直接跑,如果要去另一台机器上跑,直接复制粘贴过去就行;
- AI 补全顺便能把”我只会描述不会写”的命令一把生成出来,当一个 “shell 版 Copilot” 也挺香。
如果你也在搞 HomeLab、也被多机器 history 搞得有点抓狂,强烈推荐花半小时把 hishtory 自建一下。真正的时间成本就是那个 AI endpoint 的坑 —— 现在你知道要填完整 URL 了,那就没有坑了。
参考:

















