跳转到内容

命令行

本页是两个二进制程序的参考:独立代理 etemenanki-app,以及面板节点 agent katana。本页列出每个参数及其默认值、--test 试运行检查什么和检查不了什么、会改变程序行为的环境变量、退出码与信号处理,以及进程在协议代码之外打印的每一条消息的原文。

本页描述的是 etemenanki 2.0.2 版本(其中的 etemenanki-app 报告的版本仍为 2.0.0,构建时使用 etemenanki-protocols 2.0.2)和 katana 3.0.1(构建时使用 etemenanki-protocols 2.0.1)。如何把这两个程序作为服务运行,包括 systemd 单元和升级流程,请参阅生产环境运行。

终端窗口
etemenanki-app [-c <path>] [--test]
etemenanki-app -h | --help
etemenanki-app -V | --version
katana [-c <path>] [--test]
katana -h | --help
katana -V | --version

两个程序都在前台运行,直到收到 SIGINT 或 SIGTERM。它们都没有子命令、位置参数、守护进程模式或 PID 文件。

两个程序接受相同的四个选项,除此之外不接受其他任何选项。

选项 参数 默认值 作用
-c, --config 路径 config.toml 要加载的 TOML 配置文件。相对路径按进程的工作目录解析。
--test 无 关闭 构建完整配置,打印结果后退出,不绑定任何 socket,也不连接任何服务器。参见检查配置。
-h, --help 无 把帮助文本打印到标准输出,并以状态 0 退出。
-V, --version 无 打印程序名和版本,例如 etemenanki-app 2.0.0 或 katana 3.0.1,并以状态 0 退出。

路径可以写成参数解析器接受的任意一种形式:-c /etc/etemenanki/config.toml、-c/etc/etemenanki/config.toml、--config /etc/etemenanki/config.toml 或 --config=/etc/etemenanki/config.toml。选项顺序不限。

用法错误时,程序会向标准错误打印一条错误、用法行和一条提示,并以状态 2 退出,不会加载任何内容。

错误用法 消息
未知选项或多余的参数 error: unexpected argument '--bogus' found
-c 后没有路径 error: a value is required for '--config <CONFIG>' but none was supplied
同一选项给了两次 error: the argument '--test' cannot be used multiple times

帮助文本很短。以下是两个程序各自打印的内容,供参考:

$ etemenanki-app --help
A minimal Xray-core-style proxy runtime
Usage: etemenanki-app [OPTIONS]
Options:
-c, --config <CONFIG> Path to the TOML configuration file [default: config.toml]
--test Validate the configuration and exit without binding any listener
-h, --help Print help
-V, --version Print version

默认值 config.toml 的含义是“当前工作目录下名为 config.toml 的文件”。在服务管理器下,工作目录通常是 /,因此请给 -c 传入绝对路径。

文件内部的每个相对路径同样按工作目录解析,而不是按配置文件所在的目录解析。这包括 cert_file、key_file、ca_file、geoip、geosite、rule_list_path 以及 Unix socket 的 listen 路径。

文件无法打开时,错误只有操作系统的原始消息,例如 No such file or directory (os error 2)、Permission denied (os error 13) 或 Is a directory (os error 21)。消息中不会重复路径;配置中引用的证书、CA 证书包或 geodata 文件缺失时,出现的也是同样的消息。请先检查 -c,再逐一检查文件中的每个路径。

运行期间,每个程序都会监视配置文件所在的目录,并在文件变化时重载。如果只给了文件名(例如默认值),该目录就是工作目录。参见 etemenanki-app 热重载和 katana 热重载。

对 etemenanki-app 而言,--test 执行与正常启动相同的构建过程,并在程序即将打开 socket 之前停止。对 katana 而言,它执行启动过程中不需要面板的那部分,外加对每个 Hysteria 2 节点本地设置的检查。两者都会读取构建所需的每个文件,因此请以服务运行时的用户身份、在服务的工作目录下运行它:这样一来,无法读取的私钥或错误的相对路径会在检查时失败,而不是在启动时才失败。

检查在遇到第一个错误时停止。修复后再次运行检查,才能发现下一个错误。

flowchart LR
  P["读取并解析文件"] --> A{"有出站吗?"}
  A -- "否" --> F["错误"]
  A -- "是" --> D["DNS 解析器"]
  D --> O["出站"]
  O --> B["负载均衡器"]
  B --> R["路由表"]
  R --> I["入站"]
  I --> OK["Configuration OK."]

etemenanki-app --test 依次执行:

步骤 检查内容 读取的文件
解析 文件是 UTF-8 编码且是合法的 TOML,每个键都是已知的,每个值的类型都正确。入站或出站 settings 表内的键要等到该入站或出站构建时才检查。 配置文件
存在出站 文件中至少有一个 [[outbound]]。除非 [route].default 指定了其他出站,否则第一个出站就是默认路由。
DNS [dns] 指定了已知的后端,并提供了该后端所需的设置。 设置了 [dns].ca_file 时读取它
出站 每个 [[outbound]] 都能构建:协议、settings、密钥、stream 设置、TLS 选项。tag 不重复。 每个出站 TLS 的 ca_file,以及 Hysteria 2 出站的 ca_file
负载均衡器 每个负载均衡器的 tag 都是新的,每个成员都指向一个出站,每个成员都有健康探测可以到达的上游,且策略是已知的。
路由 [route].default 和每条规则都指向已存在的出站或负载均衡器,且每个匹配条件都合法。 仅当有规则使用 geoip 匹配条件时读取 geoip 文件,仅当有规则使用 geosite 匹配条件时读取 geosite 文件
入站 每个 [[inbound]] 都能构建:协议、用户、监听地址、stream 设置。tag 不重复。 TLS 和 Hysteria 2 的证书与私钥

--test 不检查:

  • 端口是否空闲,或进程是否有权绑定该端口;
  • 进程能否创建 TUN 设备或安装其路由;
  • 上游服务器或 DNS 服务器是否可达,其证书能否通过验证;
  • 系统信任库能否加载(TLS 客户端和 Hysteria 2 客户端在连接时才读取它);
  • Hysteria 2 出站的 ca_file 中是否有可用的证书:--test 会读取该文件,但其内容要到出站连接时才解析;
  • [log].level,它从不经过校验(参见日志)。

正常启动执行相同的构建,然后绑定监听器。因此凡是 --test 会失败的情况,启动同样会失败;此外启动还可能因绑定错误和 TUN 设备错误而失败。

两个程序报告结果的方式不同。etemenanki-app 通过日志器输出错误,katana 则直接写到标准错误。

etemenanki-app katana
成功 标准输出打印 Configuration OK.,退出码 0 标准输出打印 Configuration OK(没有句点),退出码 0
失败 标准输出打印一条 ERROR 日志行 configuration invalid: <error>,退出码 1 标准错误打印 configuration error: <error>,退出码 1
解析错误前缀 无 config parse error:
非法 UTF-8 invalid utf-8 sequence of 1 bytes from index 0 config not utf-8: invalid utf-8 sequence of 1 bytes from index 0
$ etemenanki-app --test -c config.toml
Configuration OK.
$ etemenanki-app --test -c config.toml
2026-09-24T20:33:57.764733Z ERROR etemenanki_app: configuration invalid: TOML parse error at line 6, column 1
|
6 | bogus=1
| ^^^^^
unknown field `bogus`, expected one of `tag`, `protocol`, `listen`, `port`, `stream`, `address_family`, `sniffing`, `settings`

其他常见的失败:

configuration invalid: 之后的消息 原因
No such file or directory (os error 2) 配置文件或其中引用的某个文件在该路径下不存在。
config defines no outbounds 文件中没有 [[outbound]]。空文件也会以这种方式失败。
duplicate outbound tag: <tag> 两个 [[outbound]] 段使用了相同的 tag。
duplicate inbound tag: <tag> 两个 [[inbound]] 段使用了相同的 tag。
outbound <tag>: unknown protocol "<name>" protocol 不是任何出站协议。
balancer <tag> references unknown outbound tag: <member> 负载均衡器的某个成员不是出站 tag。

错误信息列出了每个配置段的消息。

在新文件替换正在使用的文件之前先检查它。退出状态是可靠的信号:0 表示配置能构建成功,其他任何值都表示不能。

check-and-install.sh
#!/bin/sh
set -eu
new=/etc/etemenanki/config.toml.new
live=/etc/etemenanki/config.toml
if ! out=$(cd /etc/etemenanki && etemenanki-app --test -c "$new" 2>&1); then
printf 'config rejected:\n%s\n' "$out" >&2
exit 1
fi
mv "$new" "$live" # the running process notices the change and reloads

用于 katana 时,替换路径和二进制名即可。请同时捕获两个输出流(2>&1),这样脚本既能看到 katana 写到标准错误的消息,也能看到 etemenanki-app 的日志行。

状态 etemenanki-app katana
0 --test 通过;打印了 --help 或 --version;或在 SIGINT、SIGTERM 后正常停止。 同左。
1 --test 失败;或启动失败,原因是文件无法读取或解析、无法构建、某个入站无法绑定,或某个 TUN 入站无法创建设备。 --test 失败;或启动失败,原因是文件无法读取或解析、出站池无法构建、文件中没有 [[node]],或没有任何一个节点的面板客户端和路由表能构建成功。
2 命令行用法错误。 同左。
101 panic,例如 TOKIO_WORKER_THREADS 取值非法。 同左。
shell 中的 128 + n 被信号 n 杀死,例如 SIGHUP 对应 129,SIGKILL 对应 137。 同左。

进程一旦运行起来,就只有信号能让它结束。此后发生的失败只会记录到日志,进程继续运行:

  • 重载时文件无法读取、解析或构建,会保留旧配置,进程继续运行。在 etemenanki-app 中,重载期间无法绑定的入站会被略过,其他入站照常启动。
  • katana 节点启动后,如果面板不再响应,会记录一条警告,并在下次轮询时重试。
  • katana 节点首次启动失败(例如因为无法连接面板或无法绑定端口)时,会不断重试,直到启动成功。每次失败都会记录 node <id>: <reason>; retrying in <N>s,其中 <reason> 为 node_info failed: …、panel returned no node info、panel returned port 0、user_list failed: …、panel returned no user list 或 initial start failed: …。第一次等待 1 秒,此后每失败一次翻倍,最长 60 秒;如果该节点的 update_periodic 更短,则以它为上限。修改该节点的 [[node]] 段会让它立即再试一次。节点重试期间 katana 不会退出,即使这是唯一的节点也是如此,因此服务管理器看到的是一个正在运行的进程。节点列出了这些错误。

两个程序都没有用于重载的信号。配置文件在磁盘上发生变化时即会重载。

SIGINT 和 SIGTERM 的处理器要在启动完成后才安装:etemenanki-app 在所有监听器绑定之后,katana 在所有节点派生之后。在此之前或在 --test 期间到达的信号,会按内核的默认动作立即终止进程。

信号 etemenanki-app katana
SIGINT(Ctrl-C) 正常停止,退出码 0 正常停止,退出码 0
SIGTERM 正常停止,退出码 0 正常停止,退出码 0
SIGHUP 不处理:进程立即被终止,不打印 shutting down 行 同左
SIGKILL 立即终止 立即终止;尚未上报给面板的流量会丢失

正常停止时先记录 shutting down,然后:

  • etemenanki-app 取消正在运行的配置:立即关闭所有监听器和所有打开的连接,不等待连接结束,然后退出。
  • katana 取消所有节点。每个运行中的节点关闭其监听器和连接,使流量计数器定格,然后把计数器刷新到面板:如果有用户存在未上报的流量且 disable_upload_traffic 未开启,就发送最后一次流量上报;如果有审计规则命中,就发送一次审计上报。每个请求最多耗时该节点的 api.timeout,未设置或为 0 时为 5 秒。如果最后这次上报失败,katana 会记录 node <id>: report traffic: <error>,这部分流量随之丢失,因为计数器只保存在内存中。所有节点都结束后,进程退出。

收到第一个 SIGINT 或 SIGTERM 后,程序会保留其处理器,因此之后的 SIGINT 和 SIGTERM 不会打断停止过程。停止过程卡住时,只有 SIGKILL 能结束它。

两个程序都不读取属于自己的环境变量。下列变量由它们所依赖的库读取。

变量 etemenanki-app katana 作用
RUST_LOG 是 是 日志过滤器。启动时如果能解析,就取代 [log].level。参见日志。
NO_COLOR 是 是 任何非空值(即使是 0)都会去掉日志行中的 ANSI 颜色代码。
TOKIO_WORKER_THREADS 是 是 异步运行时的工作线程数,默认等于 CPU 数。取值不是正整数时,进程会立即 panic 并以状态 101 退出,即使带了 --version 也是如此。
SSL_CERT_FILE, SSL_CERT_DIR 是 是 TLS 客户端查找受信任根证书的位置。参见受信任的根证书。
HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXY(或小写形式) 否 是 katana 会像 curl 一样,通过这些变量指定的代理发送面板请求。因此服务环境中残留的代理变量也会承载面板流量。用户的代理流量不受影响。

每个 TLS 客户端查找系统受信任根证书的方式各不相同,SSL_CERT_FILE 和 SSL_CERT_DIR 对它们的影响也各不相同。无论哪种情况,这些变量都是取代默认位置,而不是在其基础上追加。如果只想为某一个连接额外信任一个 CA,优先使用该出站或 [dns] 的 ca_file 键:它会把该 CA 添加到系统根证书之外。

客户端 程序 未设置变量时的根证书 设置了 SSL_CERT_FILE 或 SSL_CERT_DIR 时
出站 TLS([outbound.stream] 中 security = "tls")以及 [dns] 的 tls 和 https 后端 etemenanki-app 系统 OpenSSL 库的默认文件和目录。在 Debian 和 Ubuntu 上,其中就是系统证书包。 OpenSSL 读取 SSL_CERT_FILE 指定的文件以代替其默认文件,读取 SSL_CERT_DIR 指定的目录以代替其默认目录。
Hysteria 2 出站 etemenanki-app 系统证书包,通过探测 /etc/ssl/certs 等常见位置找到。 只读取变量指定的文件和目录,跳过系统位置。SSL_CERT_DIR 可以列出多个目录,以 : 分隔。如果一个证书都加载不到,连接会以 hysteria2: no system root certificates could be loaded 失败,即使该出站设置了 ca_file 也是如此。
面板客户端(通过 HTTPS 访问 api.host) katana 系统证书包,通过探测常见位置找到。 变量指定的文件或目录如果存在,就用来代替探测到的文件,并与探测到的目录一起使用。
[dns] 的 tls 和 https 后端 katana OpenSSL 内置的默认位置 /usr/local/ssl 与 etemenanki-app 相同:变量取代 OpenSSL 的默认位置。

两个程序都把日志写到标准输出,每个事件一行,包含 UTC 时间戳、级别、记录该行的模块和消息。即使标准输出不是终端,日志行中也带有 ANSI 颜色代码;设置 NO_COLOR=1 可以去掉它们。

2026-09-24T20:35:11.971456Z INFO etemenanki_app::instance: inbound socks-in listening on 127.0.0.1:1080
2026-09-24T20:35:13.967835Z INFO etemenanki_app: shutting down

过滤器取自下列来源中第一个适用的:

  1. RUST_LOG,前提是它已设置且能解析。无法解析的 RUST_LOG 会被整体忽略。已设置但为空的 RUST_LOG 会被解析为“没有任何指令”,从而屏蔽所有日志行。
  2. 配置文件中的 [log].level。如果文件无法读取或解析,就跳过这一步,因此解析错误仍会被打印出来。
  3. info。

变量和键使用相同的指令语法:以逗号分隔的列表,其中单独的级别设置默认值,target=level 针对某个模块覆盖默认值,例如 info,etemenanki_protocols=debug。target 是把 - 换成 _ 后的 crate 名:etemenanki_app、katana、etemenanki_protocols、etemenanki_environment 和 etemenanki_concepts。[log].level 中无法解析的单条指令(例如 =[)会在启动时被丢弃,并在标准错误打印 ignoring `=[`: invalid filter directive,其余指令照常生效。如果没有剩下任何指令,或 [log].level 是空字符串,就只记录 ERROR 行。

katana 还会在重载时应用变化后的 [log].level。它会取代来自 RUST_LOG 的过滤器;无法解析的值会被拒绝,并记录 invalid log level "<value>": <error>。etemenanki-app 只在启动时读取 [log].level。

键类型必填默认值说明
levelstring否"info"一条 tracing 的 EnvFilter 指令,语法与 RUST_LOG 相同:一个级别(off、error、warn、info、debug、trace,或 0 到 5 的数字,不区分大小写),后面可以跟按 target 覆盖的级别,例如 "warn,etemenanki_protocols=debug"。环境变量 RUST_LOG 只要能解析为过滤器,就会取代这个值;设为空字符串也算合法,结果是什么都不输出。这个值不做校验:不是级别的词(例如 "warning")会被当成 target 名,结果是所有日志(包括错误)都不再输出。值为空,或者没有一条指令能解析时,只输出错误日志。只在启动时读取一次,热重载时不生效。

生产环境运行中有一张常用过滤器表,并说明了每个程序在运行时如何处理变化后的 [log].level。

以下是进程自身在启动、重载和关闭前后打印的日志行。协议、面板客户端和节点的消息在对应功能的页面以及错误信息中说明。表中的 <error> 代表底层错误文本,<tag> 代表入站或出站的 tag。

级别 消息 含义
INFO inbound <tag> listening on <bind> 某个入站已就绪。<bind> 对 TCP 是 <host>:<port>,对 Hysteria 2 是 udp <host>:<port>,对 Unix socket 是 unix:<path>,对 TUN 是 tun <name>(或 tun auto)。
INFO inbound <tag> owns tun device <name> 某个 TUN 入站创建了其设备。在 listening on 行之前打印。
ERROR failed to start: <error> 启动失败,进程以 1 退出。绑定错误显示为 failed to start: inbound <tag> bind <bind> failed: <error>。
ERROR config hot-reload disabled: <error> 无法监视配置目录。代理继续运行,但对文件的修改要到重启后才会生效。
INFO config reload: <changes> 正在应用一次重载;<changes> 概括了变化的内容。
ERROR reload: cannot read <path>: <error> 重载时无法读取文件,保留当前配置。
ERROR reload: parse failed, keeping current config: <error> 新文件无法解析,保留当前配置。
ERROR reload: build failed, keeping current config: <error> 新文件能解析但无法构建,保留当前配置。
ERROR inbound <tag> bind <bind> failed: <error> 重载时某个入站无法绑定。其他入站在没有它的情况下启动。
INFO shutting down 收到了 SIGINT 或 SIGTERM。

etemenanki-app 热重载详细解释了这些重载日志行。

现象 原因 解决方法
配置文件明明存在,却报 No such file or directory (os error 2) 文件中的相对路径是按工作目录解析的,或者缺少某个 geodata、证书或 CA 文件。 在相对路径所基于的目录下运行,或改用绝对路径。
--test 以 1 退出且不打印任何内容(etemenanki-app) RUST_LOG 或 [log].level 隐藏了 ERROR 行。 运行 env -u RUST_LOG etemenanki-app --test -c …,并修正 [log].level。
用 SIGHUP 重载时服务停止了 两个程序都不处理 SIGHUP。 改为保存配置文件;程序会自动重载。
katana 通过了 --test,但某个节点始终起不来 --test 不联系面板。该节点的首次面板请求或首次启动一直失败,节点在不断重试。 查看日志中该节点的 retrying in 行并排除故障。节点会在下一次尝试时启动,无需重启;参见故障排查。
面板请求发往了意料之外的主机(katana) katana 的环境中设置了 HTTPS_PROXY、HTTP_PROXY 或 ALL_PROXY 变量。 删除该变量,或把面板主机加入 NO_PROXY。