Hister 自托管教程:搭建您的个人搜索引擎
在 VPS 上运行 Hister v0.17.0,全文搜索访问过的网页和本地文件。本文涵盖二进制与 Docker 安装、TLS、登录配置、浏览器采集及 MCP 端点。
Hister 是什么,以及它不是什么
Hister 是一个自行托管的个人搜索引擎。它会为您访问过的页面和保存的文件建立全文索引,然后通过 Web 界面、终端客户端、HTTP API 或 AI(人工智能)助手,让您搜索这些内容。Hister 只回答一个问题:我在哪里读到过这段内容。
大多数读者会通过 SearXNG 了解这个概念,但两者不是同一种工具。SearXNG 是元搜索代理。您的查询会发送给它,由它代表您向其他搜索引擎发起查询,然后返回移除跟踪信息后的结果。索引属于这些搜索引擎。Hister 则根据您提供的内容建立自己的索引,包括浏览器扩展捕获的页面、导入的浏览器历史记录、爬取的 URL,以及您指定目录中的文件。自行托管的 SearXNG 实例让您可以私密访问公共 Web。Hister 则让您搜索自己阅读过的内容。这两项工作的用途不同,因此在同一台服务器上同时运行它们很常见。
Hister 是基于 AGPLv3(GNU Affero 通用公共许可证,第 3 版)或更高版本发布的自由软件。它不会收集遥测数据,也不需要云服务。本指南固定使用 v0.17.0;该版本在 2026-07-28 是当前版本。在复制任何内容前,请先查看 releases 页面中的当前标签,然后固定使用您在那里找到的标签。
为何在 VPS 上自行托管 Hister
索引只有在完整时才有用,而只有服务器在您阅读期间持续运行,索引才可能完整。笔记本电脑每天有半天处于睡眠状态。在此期间,您通过手机打开的页面无法发送到笔记本电脑,夜间导入任务也不会启动。VPS(虚拟专用服务器)会持续运行,因此您拥有的每台设备都可以将内容发送到同一个索引,爬虫也会在您睡眠时继续工作。
第二个原因是隔离。在 app 部分设置 user_handling: true 后,同一实例中的每个账户都拥有各自的凭据和文档集合。这样,一台服务器就可以供家庭或小型团队使用,同时不会有人搜索其他人的阅读内容。
第三个原因是网络接入。VPS 已经具备公网主机名和证书,浏览器扩展才能从您无法控制的网络访问服务器。
安装方式 1:发布二进制文件
Hister 为每个平台提供一个二进制文件。下载该文件及校验和文件,并在安装前完成验证。
cd /tmp
curl -LO https://github.com/asciimoo/hister/releases/download/v0.17.0/hister_0.17.0_linux_amd64
curl -LO https://github.com/asciimoo/hister/releases/download/v0.17.0/hister_0.17.0_checksums.txt
sha256sum --ignore-missing -c hister_0.17.0_checksums.txt正常结果是仅包含一行 hister_0.17.0_linux_amd64: OK。出现 FAILED 行表示下载文件已损坏或被篡改。请重新下载,不要安装该文件。
安装二进制文件,然后创建系统账户及其将使用的目录。
sudo install -m 755 /tmp/hister_0.17.0_linux_amd64 /usr/local/bin/hister
sudo useradd --system --home-dir /var/lib/hister --shell /usr/sbin/nologin hister
sudo install -d -o hister -g hister -m 750 /var/lib/hister
sudo install -d -m 755 /etc/hister
sudo hister create-config /etc/hister/config.ymlcreate-config 会写入默认配置文件,同时证明该二进制文件可以在此计算机上运行。架构错误的下载文件会在此处失败,并显示 cannot execute binary file: Exec format error。
编辑少数关键设置。生成文件的其余内容可以保持不变。
app:
directory: /var/lib/hister
access_token: 'paste-a-long-random-string-here'
server:
address: 127.0.0.1:4433
base_url: https://hister.example.com使用 openssl rand -hex 32 生成令牌。该文件现在包含凭据,因此必须在服务启动前限制其权限。
sudo chown root:hister /etc/hister/config.yml
sudo chmod 640 /etc/hister/config.yml在 systemd 下运行
编写 /etc/systemd/system/hister.service:
[Unit]
Description=Hister personal search engine
After=network-online.target
Wants=network-online.target
[Service]
User=hister
Group=hister
Environment=HISTER_CONFIG=/etc/hister/config.yml
ExecStart=/usr/local/bin/hister listen
Restart=on-failure
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/hister
[Install]
WantedBy=multi-user.targetHISTER_CONFIG 是文档规定的配置路径环境变量,因此该单元不依赖 hister 账户的主目录。ProtectSystem=strict 使该服务只能以只读方式访问整个文件系统,因此 ReadWritePaths 必须指定数据目录。ProtectHome=yes 会对该服务隐藏 /home,因此索引器会将 /home 下受监控的目录视为空目录。如果需要在此处索引文件,请删除这一行。
sudo systemctl daemon-reload
sudo systemctl enable --now hister
systemctl status hister --no-pager
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4433/最后一条命令输出任何 HTTP 状态码,都表示进程正在监听。curl: (7) Failed to connect 表示进程未监听,journalctl -u hister -n 50 --no-pager 会说明原因。
安装路径二:Docker Compose
该镜像发布在 GitHub 容器注册表中,每个版本对应一个标签。
services:
hister:
image: ghcr.io/asciimoo/hister:v0.17.0
container_name: hister
user: '1000:1000'
restart: unless-stopped
environment:
- HISTER__SERVER__ADDRESS=0.0.0.0:4433
- HISTER__SERVER__BASE_URL=https://hister.example.com
- HISTER__APP__ACCESS_TOKEN=${HISTER_ACCESS_TOKEN}
volumes:
- ./data:/hister/data
ports:
- 127.0.0.1:4433:4433每个配置键都有格式为 HISTER__<SECTION>__<KEY> 的环境变量覆盖项,分隔符是两个下划线,因此容器部署无需挂载配置文件。将 HISTER_ACCESS_TOKEN 保存在 compose 文件旁的 .env 文件中。如果需要编辑文件,docker run --rm ghcr.io/asciimoo/hister:v0.17.0 create-config > config.yml 可以输出默认值。
上面的两行很容易写错,且都值得理解。
容器内的地址必须是 0.0.0.0:4433。容器拥有独立的网络命名空间,因此绑定到其中 127.0.0.1 的进程只能从该容器内部访问,发布的端口也没有可转发的目标。
发布端口应写成 127.0.0.1:4433:4433,而不是 4433:4433。Docker 通过插入自己的 netfilter 规则来发布端口。这些规则的处理顺序早于 ufw 规则,因此即使在 ufw status 显示端口已关闭的主机上,普通的 4433:4433 仍可从互联网访问。将主机侧绑定到 127.0.0.1 后,反向代理就成为唯一的访问入口。同一问题适用于服务器上的每个容器,VPS 上的 Docker Compose将介绍其余相关内容。
默认镜像以 UID 1000 和 GID 1000 运行,因此 ./data 必须可由该账户写入,否则容器会在启动时因权限错误而停止。sudo chown -R 1000:1000 ./data 可以修复该问题。如果不熟悉这些数字,请先阅读容器以哪个 UID 和 GID 写入文件。
为什么个人搜索索引不应暴露
Hister 默认监听 127.0.0.1:4433,这是有意设计的。想想使用一个月后索引中包含的内容:内部 Wiki 页面、发票、您登录后打开的支持工单、密码重置页面,以及您阅读的其他所有内容的全文。项目文档对此有明确说明:“Hister 会将您的完整浏览历史记录(包括页面内容)传输到服务器和从服务器传出。”
泄露的密码数据库仍需破解。泄露的个人索引是纯文本,而且已经可以直接搜索,因此需要比类似的小型自托管应用更加谨慎地保护。
由此可以得出两点。Hister 默认不要求身份验证,因此仅配置反向代理,就会将您阅读内容的可搜索副本发布给任何知道主机名的人。MCP 端点默认也在 /mcp 提供服务;如果没有令牌,任何能访问该端点的客户端都可以对索引执行搜索。
在服务首次离开 localhost 之前配置身份验证。单个用户只需设置 app.access_token,浏览器扩展、终端客户端和任何 MCP 客户端都使用同一个共享密钥。对于多个用户,设置 user_handling: true 并创建账户:
sudo -u hister hister create-user alice --admin --config /etc/hister/config.yml该命令会提示您输入至少 8 个字符的密码。每个账户都有自己的文档和个人 API 令牌。账户所有者可以从个人资料页面重新生成令牌,也可以在 hister update-user 中使用 --regen-token 标志重新生成。生成新令牌后,旧令牌会立即失效,因此该账户使用的每台设备都必须随后更新。
除非确有需要,否则不要修改 app.public。公共模式允许未通过身份验证的用户执行搜索、查看预览、提供文件和执行 MCP 搜索,但仍会阻止写入、访问历史记录和执行管理操作。
反向代理、TLS 和防火墙
Hister 本身不提供 HTTPS,因此应在其前端终止 TLS(传输层安全)。Caddy 是最简便的方案,因为它会通过 ACME(自动证书管理环境)自行申请和续期证书。
hister.example.com {
reverse_proxy 127.0.0.1:4433
}使用 sudo systemctl reload caddy 重新加载。签发证书前必须满足两个条件:hister.example.com 的 A 记录必须指向此服务器,并且必须开放端口 80,因为 HTTP-01 challenge 会在该端口上响应。缺少任一条件时,浏览器会显示 TLS 错误,而不是页面;Caddy 日志也会反复记录 challenge 失败。
然后关闭其他所有端口。
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status端口 4433 是特意未列出的。
server.base_url 必须与您在浏览器中输入的地址完全一致,包括 scheme。地址不匹配时,界面会以无样式文本加载,并且缺少图像,因为服务器会根据 base_url 生成资源链接,而浏览器随后会从无法响应的 origin 请求这些资源。浏览器扩展也使用同一个 URL。
填充索引
浏览器扩展是主要的采集器。从 Mozilla Add-ons 或 Chrome Web Store 安装扩展,打开其选项页面,将服务器 URL 设置为 https://hister.example.com,然后粘贴访问令牌。之后,扩展会捕获您访问的每个页面的标题、全文、HTML 和 favicon,并将其发送到服务器。提取在客户端浏览器内完成。扩展不会联系任何第三方,唯一发出的外部请求是获取页面 favicon。
客户端提取是实现私有索引的关键。扩展看到的页面与您看到的完全相同,包括登录后和渲染后的内容。因此,内部 wiki 页面或付费文章都可以被正确编入索引,服务器也不需要获取凭据。这也意味着您查看的所有内容都可能进入索引,所以应先配置跳过规则,再添加更多内容。
单用户安装的跳过规则位于 rules.json 中;多用户安装时,每个用户的规则存储在数据库中。Web 界面的 Rules 选项卡是编辑规则的最简单方式。规则使用 Go 正则表达式,并针对完整 URL 进行匹配:
^https://mail\.example\.com
^https://bank\.example\.com
.*?utm_source=类似 ^mail.example.com 的模式永远不会匹配,因为待测试字符串以 https:// 开头。末尾的 $ 在带查询字符串的 URL 上也会匹配失败,因为匹配时会保留查询参数。
导入现有历史记录时,程序会读取浏览器自己的数据库。因此,该命令应在存放浏览器配置文件的计算机上运行,也就是您的笔记本电脑,而不是 VPS。在该计算机上安装相同的二进制文件,并将其指向服务器:
export HISTER_TOKEN='your-access-token'
hister import browser firefox -u https://hister.example.com -t "$HISTER_TOKEN"导入会作为名为 browser-import-YYYY-MM-DD 的可恢复任务运行,因此您可以中断任务,稍后再重新启动。书签服务也使用相同方式导入,包括 Linkwarden、Karakeep、Wallabag、Linkding、Readeck 和 Shaarli。重复导入时,只会获取上次导入后新增的内容。
通过在配置中指定目录,可以为服务器上的文件建立索引:
indexer:
directories:
- path: '/var/lib/hister/documents'
label: 'documents'
filetypes: ['pdf', 'docx', 'md', 'txt']程序会将 PDF、DOCX、Markdown、Org mode 和有效的 UTF-8 文本文件读取为全文。照片和视频不在支持列表中。因此,图像库需要使用能够按人脸、地点和日期建立索引的服务器,而不是文本索引服务器。PhotoPrism 和 Immich 通常是这类场景中比较的两个方案。使用 hister index https://example.com 可以添加单个页面。将整个网站转换为供其他工具使用的纯净文本属于另一项任务,应使用 将页面转换为纯净文本的自托管爬虫 完成。
搜索按字段进行,因此值得花十分钟阅读查询语言:
"connection reset" domain:github.com added:<30d
title:(wireguard|nftables) -tutorial sort:-visits让编码代理通过 MCP 访问您自己的索引
MCP(模型上下文协议)是助手调用服务器工具的接口。Hister 在同一基础 URL 下通过可流式 HTTP 传输,在 POST /mcp 提供 MCP,并公开 search、get_preview 和 get_history。身份验证使用与 API 其他部分相同的 bearer token。
{
"mcpServers": {
"hister": {
"url": "https://hister.example.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
}
}
}X-Access-Token header 可作为 Authorization 的替代方案。
这里的价值在于代理搜索的内容。开放式 Web 搜索返回当前排名靠前的结果,而对于更新频繁的软件,这些结果通常是您未在使用的版本对应的文档。您自己的索引返回您已经阅读并选择保留的页面,get_preview 则提供存储的副本,因此即使原始页面下线,答案仍然可用。如果您也需要公开结果,可以同时提供这两个来源:由 SearXNG 提供支持的浏览器搜索技能会将开放式 Web 作为单独的工具添加。运行多个此类端点后,建议阅读在 VPS 上托管 MCP 服务器,因为它们都存在相同的暴露问题。
磁盘、备份和维护
文档将一个已编入索引的页面(包括压缩后的预览)按约 100 KB 计算,因此 100000 个页面约占 10 GB。系统没有配额机制。以下两个设置容易被混淆:indexer.max_file_size_mb(默认 1 MiB)限制单个受监控文件的大小,server.max_batch_body_size(默认 40 MiB)限制单个 API 请求的大小。
app.directory 指定的目录包含 index.db(其中存放各语言的索引文件)、db.sqlite3(用于存放账户和任务)、data/html/(用于存放预览)以及 rules.json。备份操作包括停止服务,然后复制整个目录和配置文件。hister export backup.json 会将文档写入 JSON 文件,用于迁移,但这不是服务器备份。
有两个维护命令值得了解。hister reindex 会重建搜索索引。修改索引器设置后必须执行此命令。如果大型导入期间内存使用量持续上升,请在 indexer 部分设置 detect_languages: false,然后重新编制索引。hister cleanup 会删除因删除操作遗留的孤立预览文件和 favicon 文件。
删除操作使用查询,因此应先以试运行模式执行:
hister delete 'domain:example.com' --dry --verbose如果采集器仍在提交某个页面,该页面会再次出现。因此,请先添加跳过规则,再执行删除操作。
只有修改代码时,AGPLv3 才会产生实际影响。自行运行未修改的副本不承担任何义务。如果修改 Hister,并通过网络让其他人使用您的版本,该许可证要求您向这些用户提供修改后的源代码。
故障模式与您将看到的提示
服务器无法启动。 可能是 4433 端口已被占用,也可能是配置文件存在 YAML 语法错误。sudo ss -lntp | grep 4433 会显示占用该端口的进程,journalctl -u hister -n 50 --no-pager 会输出解析错误。
界面可以加载,但显示异常。 如果文字混乱且图片缺失,说明 server.base_url 与地址栏中的 URL 不一致。末尾斜杠不同也算不匹配。
扩展无法连接。 扩展中的服务器 URL 必须与 base_url 完全一致,服务器必须正在运行且处于最新状态,中间的防火墙也不能在没有页面提示的情况下阻止连接。Firefox 不会将扩展日志写入普通控制台:打开 about:debugging#/runtime/this-firefox,检查 Hister 扩展。
容器在启动时退出。 如果 ./data 报权限错误,说明该目录的所有者 UID 不是 1000;默认镜像中的账户使用 UID 1000。
管理路由返回 403 Forbidden。 启用用户管理后,POST /api/reindex 和 POST /api/cleanup 仅限管理员使用,因此普通账户会被拒绝访问。
导入过程中内存持续增长。 通常是对大量历史记录执行语言检测导致的。设置 detect_languages: false,然后运行 hister reindex。
FAQ
Hister 与 SearXNG 有何不同?
SearXNG 是元搜索代理:它将您的查询转发到公共搜索引擎,并返回去除跟踪信息的结果,因此索引属于这些搜索引擎。Hister 为您访问过的页面和保存的文件维护自己的全文索引,因此它回答的是“我在哪里读到过这段内容”,而 SearXNG 回答的是“网络上对此怎么说”。两者解决的问题不同,许多人会在同一台服务器上同时运行它们。
将完整浏览历史放在 VPS 上安全吗?
只有先完成暴露面防护才安全。Hister 绑定到 127.0.0.1:4433,默认不要求身份验证。设置 app.access_token 或 user_handling: true,在其前面配置带 TLS 的反向代理,并在防火墙中关闭 4433 端口。您的阅读记录全文索引以明文保存,因此任何能访问该端口的人都可以读取全部内容,无需破解任何东西。
我需要使用浏览器扩展,还是只导入历史记录即可?
导入操作只用于一次性回填。它读取浏览器自己的历史记录数据库,因此应在存有浏览器配置文件的计算机上运行,而不是在服务器上运行。之后,扩展会持续更新索引。页面加载后,扩展会在浏览器中提取内容,因此也能捕获登录后才能访问的页面。常见做法是先导入一次,然后使用扩展。
编程代理可以搜索我的 Hister 索引吗?
可以。Hister 是一个 MCP(模型上下文协议)服务器,位于基础 URL 的 POST /mcp,并提供 search、get_preview 和 get_history。将客户端指向 https://your-host/mcp,并在 Authorization: Bearer header 中提供访问令牌。这样,代理搜索的是您实际读过的文档及其对应版本,而不是今天公共搜索引擎返回的排名结果。