第一次跑 XERJ 的翻车现场:索引、端口、密钥五个坑
【免费下载链接】xerjXERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.项目地址: https://gitcode.com/GitHub_Trending/xe/xerj
XERJ 是一款宣称"一条命令即可为代码、文档、日志和 PDF 建索引"的本地搜索引擎,同时承诺与 Elasticsearch 8.x 线协议兼容,现有 ES 客户端零改动就能接入。但正是这种"开箱即用"的承诺,让不少第一次上手的人栽了跟头:索引跑着跑着 401、服务明明起来了另一台机器却连不上、kill -9之后重跑居然把整个语料写进了别人的数据目录。本文基于仓库源码与真实故障复现记录(benchmarks/autoindex-resilience/下的 stderr 抓取与engine/crates/xerj-server/tests/中的进程级回归测试),逐坑拆解五个高频翻车现场,并给出对应的规避清单。
一、端口与地址:回环绑定的第一道坎
坑 1:你以为的 9200,可能根本不是你的 9200
XERJ 服务端默认同时起三个 TCP 监听器(见 入口模块 与 默认配置):
| 监听器 | 默认端口 | 用途 |
|---|---|---|
| Native REST | 8080 | 原生 API(向量检索、日志摄取等) |
| gRPC | 8081 | 预留,明文 h2c |
| ES-compat | 9200 | ES 8.x 线协议,给已有 ES 客户端/Kibana/Logstash 用 |
对熟悉 Elasticsearch 的人来说,9200 是最顺手的一个端口,但也是最容易撞车的一个:本机若已跑着真实 ES、另一个 XERJ 节点或 Docker 端口映射,9200 就被别人占用了。
旧版本在这里有个非常隐蔽的坑:三个监听器是在 banner 打印之后才在tokio::spawn里绑定的,绑定失败只会error!记日志、进程照常存活。于是你会看到完整的成功横幅——"Started in 120ms"——但 ES-compat 端口其实一个请求都服务不了。更致命的是,文档推荐的健康检查是GET /_cluster/health返回green,而真正占用 9200 的那个进程恰好能答出green,于是xerj autoindex照常推进,把整个语料流进了你根本不知道的数据目录。仓库的进程级回归测试 taken_port_fails_startup.rs 记录了这个事故:一次现场实测中,三位 agent 独立踩中此坑,其中一位把 905 个文件——合同、发票、银行导出——写进了陌生人的数据目录,还拿到了ok=true exit=0。
对应修复(issue #465)现在是fail-closed:任何一个监听器绑定失败,进程直接非零退出,且 stdout 上绝不出现任何读起来像成功的文字,stderr 必须点名具体是bind 127.0.0.1:<port>失败、原因Address already in use。所以今天第一次跑如果看到进程秒退、stderr 里带Address already in use,别怀疑是自己命令写错了——先去把占用 9200 的进程找出来。
坑 2:回环绑定,局域网内"永远连不上"
另一个第一道坎是地址。默认bind_address = "127.0.0.1",只绑回环。设计文档在 xerj.default.toml 里解释得很直白:默认 TLS 是关的,一个"默认绑所有网卡"的节点会把 API 密钥和文档原文以明文撒到网络上,而一条成功的curl不会给你任何提示。所以默认回环是刻意的安全默认值,不是忘了配置。
后果就是你换了台机器、或同一个局域网内想从别的终端访问时,怎么curl都是 connection refused。正确姿势是二选一:
xerj --config xerj.toml --bind 0.0.0.0 # 命令行覆盖 XERJ_BIND_ADDRESS=0.0.0.0 xerj --config xerj.toml注意一个联动约束:非回环地址 + 关闭 TLS 的组合,默认会被拒绝启动。必须显式把allow_insecure_network_bind = true打开(见 cleartext_bind_fail_closed.rs,对应 issue #228)。原因同样是 fail-closed:Authorization: ApiKey头会随每个请求明文过网,这个组合只有在"前面有反代/边车/服务网格终结 TLS"或"链路本身可信"时才允许。容器镜像默认开它,是因为容器自己的网络命名空间就是边界,TLS 在发布端口上补。
换句话说:"绑 0.0.0.0 却连不上"通常不是网络问题,而是进程根本没起来。看到进程消失,先看 stderr 里有没有cleartext或allow_insecure_network_bind字样。
二、密钥管理:自动生成之后的交接问题
坑 3:401 三连——密钥在哪?为什么总是 401?
默认配置[auth] enabled = true,所有端点都要求Authorization: ApiKey <key>。密钥不是写在配置里的,而是首次启动时自动生成并写入<data_dir>/admin.key。而data_dir默认是相对路径./data——意味着密钥落在"你启动服务的那一时刻所在的工作目录"下面。
于是第一次上手最常见的翻车路径是:cd /tmp && xerj --config xerj.toml启动服务,然后回到项目目录跑xerj autoindex ./docs,得到 401。密钥确实生成了,只是生成在了/tmp/data/admin.key,跟你以为的地方毫无关系。服务端的 401 响应文本里其实写得很清楚(见 auth.rs 的 unauthorized_response):
Send `Authorization: ApiKey <key>`. This server writes its admin key to <data_dir>/admin.key on first start, so `export XERJ_API_KEY="$(cat <data_dir>/admin.key)"` ...正确做法是让密钥位置显式化:配置里写死data_dir = "/绝对/路径/xerj-data",然后:
export XERJ_API_KEY="$(cat /绝对/路径/xerj-data/admin.key)" xerj autoindex ./docsXERJ 也内置了一个"回环兜底":当autoindex的--url指向本机(loopback)且没有显式传--api-key/XERJ_API_KEY时,会自动尝试读取本地密钥文件并打印用的是哪个文件(见 cli.rs 的密钥兜底逻辑 与 localauth.rs)。候选路径按优先级是:data/admin.key、xerj-data/admin.key、/var/lib/xerj/admin.key、$HOME/.xerj/brain/admin.key、$HOME/.xerj/admin.key。注意前两个是相对路径,相对于当前工作目录解析——所以同样一条命令,在 A 目录跑通了、在 B 目录跑就是 401,这种"它在这行、在那不行"的谜题正是兜底逻辑要显式打印文件路径的原因。兜底只对 loopback 生效:把密钥发给非本机地址等于把凭据泄漏给不属于它的主机,所以远程部署必须显式传参。
交接场景同理:把数据目录拷到新机器、或换了启动目录,旧admin.key就不再自动可见,该显式--api-key就显式传。
三、索引中断与重复文档:恢复机制救不了的场景
坑 4:kill -9 之后能续跑,但"新增文件"和"删除文件"会卡死
先讲好消息。XERJ 的 autoindex 在客户端侧维护一个resume journal(默认~/.xerj/autoindex/<hash>/,可用--state-dir指定),配合幂等文档 ID实现断点续跑:_id由(dataset_slug, file_key, locator)经 xxh3_128 派生,其中file_key是"文件内容前 64KB 的 xxh3_64 哈希 + 文件大小",与 mtime 无关(见 ids.rs)。同一语料重跑会产出byte-identical 的_id,批量index动作天然覆盖而不是重复插入——kill -9 + 重跑最终收敛到完全一致的结果。benchmarks/autoindex-resilience/after-955.full-corpus-resume.stderr.txt里能亲眼看到恢复过程:重启后先phase=replay回放 journal,再进入index、finalize-verify(对 47,444 个条目做核对)。
但恢复机制有两个明确救不了的场景,都写在 cli.rs 的 RESUME POLICY 里,且都集中在默认的 graph(关系检测)路径:
- 新增文件被跳过:graph 路径重跑是"恢复一份冻结的计划",计划冻结之后才出现的文件会被报告为
appeared after the resume plan was frozen,不索引,直到用--fresh重建计划。注意--fresh不是万能药:一旦存在 durable corpus generation,--fresh会被直接拒绝(要独立重建得换新的--state-dir+ 新--prefix+ 新--brain)。 - 删除文件会让重跑中止:因为被删文件的文档还活在目标索引里,graph 路径上没有任何机制去删除它们,所以"删除一个已索引文件"会让本次运行 ABORT(exit 1),并且之后每一次重跑都会继续中止。源码注释原话是"a file deleted from the folder ABORTS the run — and every re-run after it"。
这就是"恢复机制救不了的场景":journal 保的是"断点续跑、不重复",但 graph 路径的冻结计划无法对"增/删"收敛。出路是--no-graph:generated 路径的 journal 支持增、删、改、改名与 no-op 的增量对账(incremental reconcile),代价是放弃 wikilink、局部链接、目录链等关系边。这也解释了为什么--watch强制要求--no-graph——没有增量对账能力的路径上,一个 watcher 根本没法保持最新。
坑 5:重复文档的来源——不是 id,而是"你以为的两个文件"
就算 id 幂等,仍可能出现"重复"。小语料实测(after-fix.small-repo.tty.txt)里能看到 autoindex 的 hash 阶段输出:
autoindex: 2 byte-identical duplicate path(s) will reuse canonical content duplicate: client/examples/typed_then_raw.rs → client/examples/raw.rs duplicate: server/tests/common/logging.rs → core/tests/common/logging.rs内容相同的文件会被识别为 duplicate 并复用 canonical content,只索引一份。但注意file_key是"前 64KB哈希 + size"——超过 64KB 且前 64KB 相同的两个大文件会被误判为同一内容。绝大多数场景够用,但追求极致准确性时要知道这个边界。
四、五个坑的规避清单
汇总成一张可直接照做的清单:
- 端口:先
ss -lntp | grep 9200确认端口归属再启动;服务秒退且 stderr 带Address already in use,就是端口被占,不是命令写错。 - 地址:默认只绑 127.0.0.1,跨机访问必须
--bind 0.0.0.0;非回环 + 无 TLS 必须显式allow_insecure_network_bind = true,否则进程拒绝启动。 - 密钥:
data_dir写绝对路径,让admin.key位置可预期;export XERJ_API_KEY="$(cat <data_dir>/admin.key)"再跑xerj autoindex;换机器/换目录后密钥不会自动跟随,要显式传--api-key。 - 中断恢复:kill -9 后直接重跑即可收敛,journal + 幂等 id 保证不重复;但默认 graph 路径救不了"新增/删除文件",需要增量对账就用
--no-graph,需要常驻监听就用--watch --no-graph。 - 大文件边界:内容去重依据"前 64KB + size",前 64KB 相同的超 64KB 文件可能被合并为一份,对准确性敏感的场景需自查。
最后说一句公道话:这五个坑里,至少三个(回环绑定、明文拒绝启动、端口占用失败即退)是 XERJ 刻意选择的 fail-closed 安全默认值——翻车的瞬间会痛,但比起"默认裸奔到公网、密钥明文过网"的隐患,痛得值。真正需要记住的是它的设计哲学:它宁可拒绝启动,也不愿在错误的假设下运行。理解了这一点,大部分"翻车"其实都能从 stderr 的第一行里找到答案。
【免费下载链接】xerjXERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.项目地址: https://gitcode.com/GitHub_Trending/xe/xerj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考