家里十几台服务器的命令历史,被我塞进了一个 hishtory:顺便接上自建 AI 网关

random-pic-api

前言

家里这些年陆陆续续堆起来的服务器,现在打开 ~/.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
2
3
4
5
6
7
8
9
10
11
12
13
# docker-compose.yml
services:
hishtory:
image: ghcr.io/ddworken/hishtory:latest
container_name: hishtory-server
restart: unless-stopped
ports:
- "4000:8080"
environment:
- HISHTORY_SQLITE_DB=/data/hishtory.db
- HISHTORY_MAX_NUM_USERS=1 # 只给自己用,限制注册
volumes:
- ./data:/data

起来之后 curl http://192.168.31.110:4000/api/v1/ping 能返回就算成了。

💡 我没有把这个 server 暴露到公网。出门在外的机器(比如我那台 MacBook)是走 WireGuard 回家里局域网的,所以所有客户端填的 HISHTORY_SERVER 都是局域网地址,省掉了一层 Cloudflare 代理和 HTTPS 证书的麻烦。

客户端安装与多机绑定

第一台机器上跑官方的一键安装脚本:

1
2
3
# 先指定 server,再装
export HISHTORY_SERVER="http://192.168.31.110:4000"
curl https://hishtory.dev/install.py | python3 -

装完之后看一下状态:

1
hishtory status

输出里会有一个 secret key(形如 xxxx-xxxx-xxxx-xxxx),这个就是”我的账号凭证”,后面所有机器都靠它加入到同一个数据池。

然后在其他每一台机器上,都用这段:

1
2
3
export HISHTORY_SERVER="http://192.168.31.110:4000"
curl https://hishtory.dev/install.py | python3 -
hishtory init $YOUR_HISHTORY_SECRET

装完之后,它会往你 shell 的 rc 文件里(.bashrc / .zshrc / fish config)加一段 hook,后续每一条命令都会被它捕获并异步发回 server。

我的 .zshrc 末尾现在长这样:

1
2
3
4
# Hishtory Config: https://github.com/ddworken/hishtory
export PATH="$PATH:$HOME/.hishtory"
export HISHTORY_SERVER="http://192.168.31.110:4000"
source $HOME/.hishtory/config.zsh

验证一下跨机器同步

随便在 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-013 月之后 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
2
3
hishtory config-set ai-completion-endpoint https://oneapi.dong4j.site
export OPENAI_API_KEY="sk-xxxxx"
export OPENAI_API_MODEL="xxx-model"

然后 TUI 里打 ? list all files larger than 1MB,返回 404

1
Error: 404 page not found

一开始我以为是 OneAPI 那边路由问题,跑去看 OneAPI 的访问日志,发现压根没收到请求,或者收到了但路径是 /,不是 /v1/chat/completions

搞清楚之后:必须填完整路径

翻了几个 issue 之后才看明白 (#304#186PR #231):

ai-completion-endpoint 的值是整条 URL,hishtory 发请求的时候**不会再拼 /v1/chat/completions**,而是直接 POST 这个 URL。

这一点和大多数 OpenAI SDK / LangChain 之类库的设计是反过来的 —— 那些通常让你填 base_url,然后内部拼上 /v1/chat/completions。hishtory 是”填啥就打啥”。

所以正确的配法是:

1
2
3
4
5
# 公网走 Cloudflare 的入口
hishtory config-set ai-completion-endpoint https://oneapi.dong4j.site/v1/chat/completions

# 或者出门在外走 WireGuard 回家,直接打局域网
hishtory config-set ai-completion-endpoint http://192.168.31.104:3000/v1/chat/completions

配完之后把 API Key 和模型也设置上:

1
2
export OPENAI_API_KEY="sk-xxxxxxxx"         # OneAPI 里签出来的令牌
export OPENAI_API_MODEL="gpt-4o-mini" # 或者你 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_SERVEROPENAI_API_* 改成你自己的:

1
2
3
4
5
6
# ----- hishtory -----
export PATH="$PATH:$HOME/.hishtory"
export HISHTORY_SERVER="http://192.168.31.110:4000"
export OPENAI_API_MODEL="gpt-4o-mini"
export OPENAI_API_KEY="sk-xxxxxxxx"
source $HOME/.hishtory/config.zsh

ai-completion-endpoint 因为是存在 hishtory 配置里的(~/.hishtory/config.json),所以只需要首次配置一次,不用写到 rc 文件里。

一些小 Tips

几个用一段时间后才摸清楚的细节:

  1. 临时不想被记录:跑敏感命令(比如带密码的 mysql 登录)前先 hishtory disable,跑完再 hishtory enable。或者命令前加个空格(HISTCONTROL=ignorespace 默认行为)某些 shell 里也会被 hishtory 忽略。

  2. 误记录了怎么办hishtory redact <keyword> 把含关键字的历史批量删除,TUI 里选中某条后 Ctrl+K 也能删单条。

  3. 自己升级客户端hishtory update 就行,不用重新跑 install.py。

  4. 按自定义列排查:可以加 custom-columns,比如我给每条命令都附上当时 git 仓库的 remote:

    1
    2
    3
    hishtory 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 了,那就没有坑了。


参考