SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-28

在 VPS 上自行托管 Hister:个人全文搜索引擎

了解如何在 VPS 上部署 Hister v0.17.0:为浏览过的网页和本地文件建立全文索引,涵盖二进制文件与 Docker 安装、TLS、登录认证及 MCP 端点配置。

Hister 是什么,不是什么

Hister 是一个自行托管的个人搜索引擎。它会为您访问过的页面和保存的文件建立全文索引,然后通过 Web 界面、终端客户端、HTTP API 或 AI(人工智能)助手,让您搜索这些内容。Hister 只回答一个问题:我在哪里读过这段内容。

大多数读者是通过 SearXNG 了解这一概念的,但两者不是同一个工具。您熟悉的名称如果是较早的 Searx,该项目自 2023 年起就没有新的代码提交,SearXNG 延续了它的开发,因此您今天新部署的实例无论如何都是 SearXNG。SearXNG 是元搜索代理。您将查询发送给它,由它代表您请求其他搜索引擎,再移除跟踪信息后返回结果。索引属于这些搜索引擎。Hister 根据您提供的内容建立自己的索引,包括浏览器扩展捕获的页面、导入的浏览器历史记录、抓取的 URL,以及您指定目录中的文件。自行托管的 SearXNG 实例可让您私密访问公共 Web。Hister 则用于搜索您自己的阅读内容。两者用途不同,因此在同一台服务器上同时运行很常见。如果这样部署,您还应了解SearXNG 实际隐藏了多少搜索信息,因为它在搜索引擎看来会用您服务器的 IP 替换您的 IP,但不会隐藏查询内容本身。

Hister 是基于 AGPLv3(GNU Affero General Public License,第3版)或更高版本发布的自由软件。它不收集遥测数据,也不需要云服务。本指南固定使用 v0.17.0;该版本在 2026-07-28 时是当前发布版本。在复制任何内容前,请先查看 releases 页面中的当前 tag,然后固定使用您在那里找到的 tag。

为什么在 VPS 上自行托管 Hister

索引只有完整时才有用;而只有在阅读期间服务器一直运行,索引才可能完整。笔记本电脑每天有半天处于睡眠状态。在这段时间内,您通过手机打开的页面永远不会到达笔记本电脑,夜间导入任务也不会启动。VPS(虚拟专用服务器)会持续运行,因此您所有设备提交的内容都进入同一个索引,爬虫也会在您睡觉时继续工作。

第二个原因是隔离。在 app 部分设置 user_handling: true 后,单个实例中的每个帐户都有自己的凭据和文档集合。这样,一台服务器就可以供家庭或小型团队使用,同时不会有人搜索其他人的阅读内容。

第三个原因是基础配置。VPS 已经有公网主机名和证书,浏览器扩展需要依靠它们从您无法控制的网络访问服务器。同一组主机名和证书还会在服务器的其他位置发挥作用,因为 openGym 会将其第一个通行密钥注册到当时生效的主机名下。因此,必须在创建第一个账户前确定主机名和证书。

安装路径一:发布二进制文件

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.yml

create-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.target

HISTER_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 container registry 中,每个版本对应一个标签。

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 端口是有意为之。公网主机名并不是唯一的访问方式,指向同一 loopback 端口的 onion service 也可以让您在自己的设备上访问索引页,无需 DNS 记录,也无需开放任何入站端口。

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 传输协议提供该接口,暴露 search、get_preview 和 get_history,访问路径为 POST /mcp。身份验证使用与 API 其余部分相同的 bearer token。如果您不熟悉工具调用,自行编写一个小型代理循环是快速了解此类端点实际向助手提供哪些内容的方法。

{
  "mcpServers": {
    "hister": {
      "url": "https://hister.example.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_ACCESS_TOKEN"
      }
    }
  }
}

X-Access-Token 标头可作为 Authorization 的替代项。

这里的价值在于代理搜索的内容。开放网络搜索返回当前排名靠前的结果。对于快速变化的软件,这些结果通常是您尚未运行的版本的文档。您自己的索引会返回您已经阅读并选择保留的页面,而 get_preview 会提供存储的副本,因此即使原始页面下线,答案仍然可用。如果您还需要公开结果,可以同时向代理提供这两个来源:由 SearXNG 支持的浏览器搜索技能会将开放网络作为单独的工具添加。运行多个此类端点后,建议阅读在 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 以外的用户所有;默认镜像中的账户 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 标头中携带访问令牌。这样,代理搜索的是您实际阅读过的文档及其对应版本,而不是当前公共搜索引擎返回的排名结果。