配置文件
katana 读取一个 TOML 文件,里面描述的是面板管不到的那部分:要对接哪些面板、提供哪些证书、流量可以去往哪里,以及域名如何解析。本页从整体上介绍这个文件:四个顶层段、katana 解析它们时遵循的规则、进程级的 [log] 和 [dns] 段,以及多个 [[node]] 块如何共用一个进程。
在编写第一份配置、添加第二个节点,或者 --test 拒绝了某个文件而你想知道原因时,都可以参考本页。节点内部的配置、出站和路由规则各有专门的页面,下文的表格中给出了链接。
最小的配置文件
Section titled “最小的配置文件”最小的可用文件只需指定一个面板节点,其余一切都有默认值:
[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"不启动任何服务,直接检查它:
katana --test -c /etc/katana/config.toml文件有效时打印 Configuration OK,退出状态为 0;无效时打印 configuration error: 加上原因,退出状态为 1。不带 -c 时,katana 读取工作目录下的 config.toml。
这个文件足以通过 --test,但实际的节点通常还需要更多配置。如果面板把节点标记为 TLS,katana 还需要在 [node.controller.cert] 中配置证书,而 --test 不会联系面板,因此无从得知这一点。各类节点分别需要什么,见节点。
完整的文件有四个顶层段。[log] 和 [dns] 作用于整个进程;每个 [[node]] 块是一个面板节点,带有自己的子表;所有 [[outbound]] 块组成一个出站池,每个节点都路由到这个池中。
[log] # 进程级:日志过滤器[dns] # 进程级:所有出站背后的解析器
[[node]] # 每个面板节点一个块[node.api] # 如何访问面板,以及节点是什么[node.controller] # 绑定地址、轮询间隔、功能开关[node.controller.cert] # TLS 和 Hysteria2 节点使用的证书[node.hysteria] # Hysteria2 监听器设置[node.route] # 该节点的默认出站和 geodata 文件[[node.route.rule]] # 该节点的路由规则,首个匹配生效
[[outbound]] # 每个具名上游一个块,所有节点共用TOML 会把每个子表归到它上方最近的 [[node]],所以节点的子表要紧跟在它的 [[node]] 行之后、下一个 [[node]] 之前书写。
| 段 | 作用范围 | 文档位置 |
|---|---|---|
[log] |
进程 | 本页 |
[dns] |
进程 | 本页 |
[[node]]、[node.api]、[node.controller]、[node.controller.cert] |
单个节点 | 节点 |
[node.hysteria] |
单个 Hysteria2 节点 | Hysteria 2 节点 |
[node.route]、[[node.route.rule]] |
单个节点 | 路由 |
[[outbound]] |
所有节点 | 出站 |
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
log | table | 否 | {} | 日志设置,写作 [log],只有 level 一个键。与 etemenanki-app 不同,katana 在热重载时会应用改动后的 level。 |
dns | table | 否 | {} | 整个出站池共用的域名解析,写作 [dns]。不写时使用系统解析器,前面始终有一层缓存。访问面板的 HTTP 请求不经过它。 |
node | array of tables | 是 | — | 要服务的面板节点,每个 [[node]] 块一个,下面有 [node.api]、[node.controller]、[node.controller.cert]、[node.hysteria]、[node.route] 和 [[node.route.rule]] 几个子表。键名是单数:[[nodes]] 会被当作未知字段,只写一个 [node] 表会报 invalid type: map, expected a sequence。一个都不写时文件能解析,但 --test 和正常启动都会报 config defines no [[node]] entries。 |
outbound | array of tables | 否 | [] | 具名的上游代理,每个 [[outbound]] 块一个,所有节点共用。键名是单数。direct、freedom、block、blackhole 这几个 tag 始终存在,不能重新定义;使用保留的或重复的 tag 会报 duplicate/reserved outbound tag <tag>。 |
下图展示了运行时各段之间的关系。每个节点有自己的面板和自己的路由表,但所有路由表都从同一个出站池中选择出站,而每个出站都通过同一个 [dns] 解析器解析域名。
flowchart LR P1["面板 A"] --> N1["节点 1"] P2["面板 B"] --> N2["节点 2"] N1 --> R1["节点 1 路由表"] N2 --> R2["节点 2 路由表"] R1 --> POOL["共享出站池"] R2 --> POOL POOL --> BUILTIN["direct, freedom, block, blackhole"] POOL --> OUT["outbound 条目"] DNS["dns 解析器"] -.-> POOL
没有顶层的 [route]。路由配置位于每个节点之下,写作 [node.route];顶层的 [route] 表会被当作未知字段,导致解析失败。
katana 如何读取文件
Section titled “katana 如何读取文件”文件中的每个表都会拒绝它不认识的键,而且没有任何键有别名。拼错的键会让 katana 停下,而不是被跳过,因此笔误不会悄无声息地让某项设置停留在默认值。报错会指出行号、键名以及该表接受的键:
configuration error: config parse error: TOML parse error at line 3, column 1 |3 | paneltype = "x" | ^^^^^^^^^unknown field `paneltype`, expected one of `panel_type`, `api`, `controller`, `route`, `hysteria`值的类型同样严格检查。node_id = "1" 会报 invalid type: string "1", expected u32,node_id = -1 会报 invalid value: integer `-1`, expected u32,level = 3 会报 invalid type: integer `3`, expected a string。文件必须是 UTF-8 编码,否则报 config not utf-8。
键名采用 snake_case,与参考表中列出的写法完全一致。XrayR 的 YAML 使用 ApiHost、NodeID 这样的名称,在 katana 中对应的是 [node.api] 下的 host 和 node_id。两者的差异见从 XrayR 迁移。
一部分表示选项的值在匹配时不区分大小写,因此 panel_type = "NewV2board" 与 panel_type = "newv2board" 等价;另一部分则必须严格写成小写。下表列出了文件中所有此类值:
| 值 | 大小写 | 可接受写法示例 |
|---|---|---|
[[node]].panel_type |
任意 | "SSpanel"、"NewV2board"、"V2board" |
[node.api].node_type |
任意 | "V2ray"、"Trojan"、"Shadowsocks"、"Hysteria2" |
[[outbound]].protocol |
任意 | "socks"、"Shadowsocks"、"WireGuard" |
[[outbound]].security |
任意 | "auto"、"AES-128-GCM" |
[[outbound]].address_family |
任意,且 - 视同 _ |
"ipv4_only"、"IPv4-Only" |
[[outbound]].method,Shadowsocks 2022 名称 |
精确匹配 | "2022-blake3-aes-256-gcm" |
[[outbound]].method,其他加密方式 |
任意 | "aes-128-gcm"、"CHACHA20-POLY1305" |
[dns].backend |
精确匹配 | "system"、"udp"、"tls"、"https" |
[node.controller.cert].mode |
精确匹配:只有 "file" 会加载证书 |
"none"、"file" |
[node.hysteria].credential、obfs |
精确匹配 | "uuid"、"user_pass"、"salamander" |
katana 报告未知的 panel_type 时,消息中的值会显示为小写:panel_type = "XBoard" 报的是 unknown panel_type "xboard"。
文件中的路径,例如 cert_file、key_file、geoip、geosite、rule_list_path 和 ca_file,会按原样打开。相对路径以 katana 的工作目录为基准,而不是配置文件所在的目录。systemd 服务的工作目录是 /,除非 unit 中设置了 WorkingDirectory=,所以请使用 /etc/katana/geoip.dat 这样的绝对路径。
--test 检查什么
Section titled “--test 检查什么”--test 在不绑定端口、不联系面板的前提下,尽可能多地构建配置。它按以下顺序检查,遇到第一个错误即停止:
- 读取并解析文件,发现未知键、类型错误和非 UTF-8 编码。
- 如果设置了
[dns].ca_file,读取该文件,然后构建[dns]解析器。 - 构建每个
[[outbound]],并检查 tag 没有重复,也没有占用内置名称。 - 检查至少有一个
[[node]]。 - 依次对每个节点检查
panel_type和node_type,然后基于出站池编译[node.route],并读取规则用到的 geodata 文件。对于 Hysteria2 节点,还会检查[node.hysteria]并读取证书和私钥。
凡是由面板决定的事情,它都无法检查。--test 不检查面板 URL 和密钥是否可用、面板分配的端口是否空闲,也不检查 TLS 节点的证书文件是否存在。它也不检查 [log].level,不读取 rule_list_path。这些问题要等节点启动时才会出现在日志中。
[log] 段
Section titled “[log] 段”[log] 设置 katana 向标准输出写入哪些日志行。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
level | string | 否 | "info" | 一条 tracing 的 EnvFilter 指令,语法与 RUST_LOG 相同:一个级别(off、error、warn、info、debug、trace,或 0 到 5 的数字,不区分大小写),后面可以跟按 target 覆盖的级别,例如 "info,katana=debug"。启动时,环境变量 RUST_LOG 只要是合法的过滤器就优先生效。--test 不校验这个值:启动时无法解析的指令会被跳过,并在 stderr 打印一行 ignoring ...;不是级别的词(例如 "warning")会被当成 target 名,结果是所有日志(包括错误)都不再输出。热重载时如果这个值变了,katana 会应用新值,并取代 RUST_LOG 设置的过滤器;新值无法解析时记录 invalid log level,保留当前的过滤器。 |
[log]level = "info,katana=debug"启动时,如果环境变量 RUST_LOG 是合法的过滤器,katana 就使用它,否则使用 [log].level。想在不修改文件的情况下让某一次运行输出更多细节,可以用 RUST_LOG=debug katana -c /etc/katana/config.toml。
与 etemenanki-app 不同,katana 会在热重载时应用改动后的 level。重载发现 level 变了时,katana 会安装新的过滤器,以 info 级别记录 reload: log level → <level>,此后以文件为准,不再使用 RUST_LOG。如果新值无法解析,katana 记录 invalid log level 并保留当前过滤器。删除这个键会把级别恢复为 info。没有改动 level 的重载不会触碰当前过滤器,即使它来自 RUST_LOG。
[dns] 段
Section titled “[dns] 段”[dns] 决定出站池如何把主机名解析为地址。它与 etemenanki-app 使用的是同一个解析器,键和规则也完全相同;关于解析器本身的更多细节,见 DNS。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
backend | string (enum) | 否 | "system" | 解析结果的来源:"system"(系统解析器,遵循 /etc/hosts 和 nsswitch.conf)、"udp"(普通 DNS)、"tls"(DNS over TLS,RFC 7858)或 "https"(DNS over HTTPS,RFC 8484)。与 katana 大多数枚举值不同,这里区分大小写:"UDP" 会报 dns: unknown backend "UDP" (expected "system", "udp", "tls" or "https")。 |
server | string | 视情况 | — | 解析服务器地址,写成带端口的 IP 字面量,例如 "192.0.2.53:53" 或 "[2001:db8::53]:853"。udp、tls、https 必填;缺少时构建失败,报 dns: the <backend> backend needs a server address。写成主机名或漏掉端口会报 dns: invalid server address: invalid socket address syntax。对 https 来说,katana 连接的就是这个地址,不会去解析 url 里的主机名。system 忽略这个键。 |
server_name | string | 视情况 | — | 校验解析服务器证书时使用的名称。tls 必填;缺少时构建失败,报 dns: the tls backend needs a server name to verify against。其他后端忽略这个键。 |
url | string | 视情况 | — | DNS over HTTPS 的地址,例如 "https://dns.example.com/dns-query"。https 必填;缺少时构建失败,报 dns: the https backend needs the resolver's url。必须以 https:// 开头,否则报 dns: "<url>" is not an https:// url。其中的主机名用来校验证书,也作为 Host 头发送;URL 里的端口会被忽略,没有路径时使用 /dns-query。带用户信息的 URL 会报 has no usable host。其他后端忽略这个键。 |
ca_file | path | 否 | — | 一个 PEM 证书包,里面是校验 tls 或 https 解析服务器时额外信任的 CA 证书。katana 把它们加到系统根证书之上,不会取代系统根证书。只要设置了这个键,katana 就会读取文件,即使后端是用不到它的 system 或 udp。相对路径以工作目录为基准。文件不存在时只报 No such file or directory (os error 2),不带路径。后端是 tls 或 https 时,文件里没有证书会报 no certificate in CA PEM bundle。 |
# 默认值;与不写 [dns] 相同。[dns]backend = "system"通过 getaddrinfo 使用系统解析器。只有这个后端会遵循 /etc/hosts、nsswitch.conf 和搜索域。
[dns]backend = "udp"server = "192.0.2.53:53"通过 UDP 向单个服务器发送普通 DNS 查询。
[dns]backend = "tls"server = "192.0.2.53:853"server_name = "dns.example.com"DNS over TLS。katana 连接 server,并用 server_name 校验其证书。
[dns]backend = "https"server = "192.0.2.53:443"url = "https://dns.example.com/dns-query"DNS over HTTPS。katana 连接 server,并用 url 中的主机名校验证书,因此永远不需要先解析解析服务器自己的名称。
如果解析服务器的证书由私有 CA 签发,可以在 tls 或 https 后端中加上 ca_file 来信任它。katana 会把文件中的证书加到系统根证书之上,而不是取代系统根证书,因此持有公开受信任证书的解析服务器仍能通过校验:
[dns]backend = "tls"server = "192.0.2.53:853"server_name = "dns.example.com"ca_file = "/etc/katana/dns-ca.pem"所选后端用不到的键不会被检查。在 backend = "system" 下,残留的 server = "garbage" 也能通过 --test。例外是 ca_file:只要设置了,katana 就会读取它。
谁在使用解析器
Section titled “谁在使用解析器”katana 只构建一个解析器,带一个缓存,并把它交给池中的每个出站,包括内置的 direct 和 freedom。它负责解析:
- 经由
direct、freedom或protocol = "direct"出站离开的流量的目标地址; - 上游代理出站的
server名称; - 经由 WireGuard 出站发送的流量的目标地址。
WireGuard 出站自己的 server,即对端 endpoint,是个例外:katana 使用系统解析器来解析这个名称。
由于所有节点都路由到同一个池,所有节点也共享这个缓存。面板请求不经过它:无论 [dns] 怎么写,katana 访问面板 API 的 HTTP 客户端都使用系统解析器。
缓存最多保存 8192 个名称的解析结果。系统解析器的结果不带 TTL,katana 将其保留 60 秒;其他后端的结果按 TTL 保留,并限制在 5 秒到 1 小时之间。使用 udp、tls 和 https 时,每次查询 5 秒超时。
一个文件中的多个节点
Section titled “一个文件中的多个节点”一个 katana 进程可以服务任意数量的面板节点,这些节点可以来自同一个面板,也可以来自不同面板。每个节点写一个 [[node]] 块,后面跟上它自己的子表。各节点独立运行:每个节点都有自己的面板客户端、轮询定时器、监听器、用户表、流量计数器和路由表,一个节点的面板不可达不会影响其他节点。
首次无法启动的节点会重试。原因可能是针对该节点或其用户的面板请求失败,也可能是监听器无法绑定。此时它记录 node <id>: <reason>; retrying in <N>s,然后再次尝试。第一次等待 1 秒,此后每失败一次等待时间翻倍,上限为 60 秒与该节点 update_periodic 中的较小者。在此期间其他节点照常运行。如果某次重载改动了正在等待的节点的 [[node]] 块,它会立即重试,因为这次编辑可能正好修复了问题。
| 所有节点共享 | 每个节点独立 |
|---|---|
[log] |
panel_type 以及 [node.api] 中的全部内容 |
[dns] 及其缓存 |
[node.controller] 及其证书 |
[[outbound]] 池,包括 direct 和 block |
[node.hysteria] |
| 进程本身、它的文件监视器和信号 | [node.route] 及其规则 |
下面的示例在一个进程中同时服务一个 Xboard VMess 节点和一个 SSPanel Trojan 节点。VMess 节点把私有地址发往 block,把 example.org 及其子域名发往 relay 出站,其余流量发往 direct。Trojan 节点除 RFC 1918 私有地址段和 25 端口(SMTP)发往 block 外,其余流量全部发往 relay。两个节点使用同一个只定义了一次的 relay 出站。
# Two panel nodes in one katana process: an Xboard (newV2board) VMess node and# an SSPanel Trojan node. Both share the [dns] resolver and the [[outbound]]# pool; each has its own [node.route] table.## Validate with: katana --test -c /etc/katana/config.toml
[log]level = "info"
# One resolver and cache for every outbound in the pool.[dns]backend = "tls"server = "192.0.2.53:853"server_name = "dns.example.com"
# ---------------------------------------------------------------------------# Node 1: VMess from an Xboard panel. The panel supplies the port, transport# and TLS flag; the certificate is always local.# ---------------------------------------------------------------------------[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"timeout = 10
[node.controller]listen_ip = "0.0.0.0"update_periodic = 60
[node.controller.cert]mode = "file"cert_file = "/etc/katana/proxy.example.com.crt"key_file = "/etc/katana/proxy.example.com.key"
[node.route]default = "direct"geoip = "/etc/katana/geoip.dat"
[[node.route.rule]]outbound = "block"geoip = ["private"]
[[node.route.rule]]outbound = "relay"domain_suffix = ["example.org"]
# ---------------------------------------------------------------------------# Node 2: Trojan from an SSPanel panel. Trojan always runs over TLS.# ---------------------------------------------------------------------------[[node]]panel_type = "SSpanel"
[node.api]host = "https://sspanel.example.com"node_id = 2key = "replace-with-the-panel-key"node_type = "Trojan"
[node.controller]update_periodic = 60
[node.controller.cert]mode = "file"cert_file = "/etc/katana/proxy.example.com.crt"key_file = "/etc/katana/proxy.example.com.key"
[node.route]default = "relay"
[[node.route.rule]]outbound = "block"cidr = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
[[node.route.rule]]outbound = "block"port = ["25"]
# ---------------------------------------------------------------------------# The shared outbound pool. `direct`, `freedom`, `block` and `blackhole` are# built in; these entries add to them.# ---------------------------------------------------------------------------[[outbound]]tag = "relay"protocol = "shadowsocks"server = "198.51.100.20"port = 8388method = "2022-blake3-aes-256-gcm"# Generate a real key with: openssl rand -base64 32password = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="relay 出站中的 Shadowsocks 2022 密钥是占位值。请用 openssl rand -base64 32 生成一个真实的密钥。
每个节点在自己的 listen_ip 上绑定一个端口。这个端口由面板分配;唯一的例外是设置了 [node.hysteria].port 的 Hysteria2 节点,它使用该端口,不向面板索取端口。两个节点在同一地址上绑定同一个 TCP 端口时无法同时启动:后绑定的那个会记录 node <id>: initial start failed: …; retrying in <N>s 并持续重试,因此端口空出来后它就会启动。Hysteria2 节点监听的是 UDP,因此可以与 TCP 节点使用相同的端口号。--test 不检查端口。
katana 如何区分节点
Section titled “katana 如何区分节点”热重载时,katana 按节点的身份把新文件中的每个 [[node]] 与正在运行的节点对应起来。身份由以下各项共同组成:
panel_type,比较时不区分大小写;[node.api].host,按原样比较;[node.api].node_id;[node.api].key;- 在 NewV2board 和 V2board 上,还包括 katana 向面板请求的节点类型:
enable_vless = true的V2ray、Vmess或Vless节点为vless,其他情况为小写的node_type。SSPanel 仅凭 ID 查找节点,因此 SSPanel 节点的身份不含节点类型。
节点类型之所以属于身份,是因为 Xboard 和 V2board 按节点 ID 和所请求的类型来查找节点。同一个 node_id 分别以 vless 和 vmess 请求,就是两个面板节点,各有自己的用户和流量,katana 也同样这样对待。
条目的身份不变时,katana 就地重新配置正在运行的节点。这也包括 [node.api] 中的其他键,例如 timeout 和 speed_limit:katana 会用改动后的块构建新的面板客户端,并立即向面板请求节点及其用户。身份中任何一项发生变化时,katana 会把这次编辑视为删除一个节点再添加另一个:停止旧节点(旧节点会最后一次上报它统计到的流量),并启动一个从头联系面板的新节点。[[node]] 块在文件中的顺序无关紧要。各类改动对运行中节点的影响,见热重载。
host 按字符串比较,所以 https://panel.example.com 和 https://panel.example.com/ 算作不同的身份,尽管两者访问的是同一个面板。
重载如何处理各段
Section titled “重载如何处理各段”katana 监视配置文件所在的目录。该目录中的任何变化都会在约半秒后触发重载,随后 katana 逐段比较新文件与正在运行的配置。无法解析的文件会被整体忽略,日志中记录 config reload failed, keeping current: …。对于能解析的文件,各个顶层段的处理方式不同:
| 段 | 重载时 |
|---|---|
[log] |
改动后的 level 立即生效,除非这次重载因出站或节点问题被拒绝。 |
[dns] |
只有同一次重载也改动了某个 [[outbound]] 时才会应用。 |
[[outbound]] |
重建出站池。构建失败时,整个重载被拒绝,记录 reload: bad outbounds, keeping current config: …,其他改动也都不会应用。构建成功时,每个节点重新编译路由表并重启监听器,这会断开所有节点上的所有连接。如果某个节点的规则无法针对新出站池编译,它会记录 node <id>: route rebuild failed, keeping current: … 并保留旧路由表,但仍会重启监听器。 |
[[node]] |
在应用任何改动之前,katana 先构建文件新增的每个节点(包括其面板客户端和路由表),以及每个块有改动的节点的面板客户端。其中任何一个构建失败,整个重载都会被拒绝,记录 reload: node <name>: <error>; keeping current config,其他改动也都不会应用。否则启动新出现的身份,停止消失的身份,并通过新的面板客户端在线重新配置有改动的身份,[node.api] 中的键也包括在内。改动后的 [node.route] 如果无法编译,只由该节点自己拒绝:它记录 node <id>: config edit refused, keeping the running one: … 并保留全部旧设置,重载的其余部分照常应用。 |
如果重载后文件中一个 [[node]] 条目都没有,所有节点都会被停止,katana 会在没有任何节点的状态下继续运行。空文件可以解析,所以清空文件的效果相同。“至少一个节点”的规则只在启动时和 --test 中生效。
| 消息 | 原因 | 解决方法 |
|---|---|---|
unknown field `route`, expected one of `log`, `dns`, `node`, `outbound` |
写了顶层的 [route],或其他多余的段。 |
把路由移到每个节点之下,写作 [node.route]。 |
unknown field `nodes` |
写成了复数的 [[nodes]]。 |
[[node]] 和 [[outbound]] 都用单数。 |
invalid type: map, expected a sequence |
节点写成了 [node] 而不是 [[node]]。 |
即使只有一个节点,也要用双方括号。 |
config defines no [[node]] entries |
没有任何 [[node]] 块。 |
至少添加一个节点。 |
unknown panel_type "" |
[[node]] 中缺少 panel_type。 |
把 panel_type 设为 SSpanel、NewV2board 或 V2board。 |
unknown panel_type "xboard" |
写的是面板的产品名,而不是它的 API 类型。 | Xboard 和 V2board 使用 newV2board API:请用 NewV2board。 |
unknown node_type "vmess2" |
katana 不认识的 node_type。 |
使用 V2ray、Trojan、Shadowsocks 或 Hysteria2。 |
duplicate/reserved outbound tag direct |
某个出站 tag 用了两次,或者用了 direct、freedom、block、blackhole 之一。 |
给出站改名。如果想为直连流量单独设置参数,在一个新 tag 下添加 protocol = "direct"。 |
route references unknown outbound tag: x |
路由的 default 或规则的 outbound 指向了不存在的出站。这条消息不会指明是哪个节点。 |
修正 tag,或补上缺失的 [[outbound]]。 |
dns: unknown backend "UDP" (expected …) |
后端名称大小写不对,或者写了 "doh" 之类的名称。 |
使用小写的 system、udp、tls 或 https。 |
dns: invalid server address: invalid socket address syntax |
[dns].server 写成了主机名,或者地址没有端口。 |
写成 IP 地址加端口,例如 "192.0.2.53:53"。 |
No such file or directory (os error 2) |
配置文件本身,或其中引用的某个文件不存在:[dns].ca_file、geodata 文件或 Hysteria2 证书。消息不会指明是哪一个。 |
逐一检查路径,并使用绝对路径。 |
只有在节点运行后才会出现的错误,见故障排查。