Routr 2.25.1 常用 CLI 命令与工具详解

面向用命令行与脚本管理 Routr 的运维与后端工程师,实测 rctl 对 2.25.1 服务端的全部资源组命令(7 组增删改查 + describe/get/extended/分页)、Node.js SDK 的类/方法/签名矩阵与三大传参坑、2.25 新增 trace 工具族(validate/lint/replay/send/sanitize/compat)对消息录制 JSONL 的完整消费,全部输出为本地实测回显。

RoutrRoutrSIPVoIP2026/10/26

Routr 2.25.1 常用 CLI 命令与工具详解

目标:面向用命令行与脚本管理 Routr 的运维与后端工程师,实测 rctl 对 2.25.1 服务端的全部资源组命令(7 组 × create/delete/describe/get/update,含单资源、extended、分页)、Node.js SDK 的类/方法/签名矩阵与三大传参坑、2.25 新增 trace 工具族(validate/lint/replay/send/sanitize/compat)对消息录制 JSONL 的完整消费。全部命令与回显均为本地实测。

一、实验环境与取证说明

项目取值
服务端fonoster/routr-one:2.25.1(部署篇同一栈,API 经宿主 8128 → 51908)
CLIrctl @routr/ctl/2.25.0 linux-x64 node-v24.22.1(npm 当前最新)
SDK@routr/sdk(npm 最新,Node.js 24 实测)
版本错位说明CLI 2.25.0 ↔ 服务端 2.25.1,小版本错位不影响资源管理(全流程实测通过)
初始资源Domain sip.local、Credentials admin、Agent 1001(部署篇所建;鉴权 admin/q1w2e3r4)
取证日期2026-10-26

二、rctl 命令地图

2.1 命令结构

rctl <资源组> <动作> [REF] [全局flags]
  • 资源组 7 个:acl / agents / credentials / domains / numbers / peers / trunks
  • 动作 5 个:create / delete / describe / get / update
  • 另有 plugins(oclif 插件管理)与 trace(2.25 新增录制工具族,第六节)

2.2 全局 flags(必须放在资源命令之后)

flag说明
-e, --endpointAPI 地址,默认 localhost:51908;本实验为 localhost:8128(compose 映射)
-i, --insecure明文连接 API(API 口默认无 TLS)
-c, --cacertCA 证书路径;TLS_ON=true 时替代 -i

部署篇已踩过的坑再强调一次:全局 flags 写在资源命令前面会被 oclif 误解析(rctl -e localhost:8128 domains get 报 command -e not found)。正确姿势:rctl domains get -e localhost:8128 -i。

2.3 动作语义(rctl agents get --help 实测)

动作参数行为
get [REF]-s 页大小(默认 50)、-x 扩展输出有 REF 出单资源详情表,无 REF 出分页表格
describe [REF]—键值式详情(含 Credentials/Extended 嵌套)
create无字段参数全交互 prompt,结束打印新资源 ref
update REF无字段参数全交互 prompt,按已有值回填
delete [REF]—静默执行,成功仅退出码 0

create/update 只走交互意味着脚本化场景必须用 SDK(第五节)。

三、资源组 get:七张实测表

以下为同一时刻七组资源的 rctl <组> get -e localhost:8128 -i 输出(构造过程见第五节校验实录):

$ rctl agents get ...
Name       Username Domain    Max Contacts Privacy Enabled
Agent 1003 1003     sip.local 1            None    Yes
Agent 1002 1002     sip.local              None    Yes
Agent 1001 1001     sip.local              Private Yes
$ rctl credentials get ...
Name              Username
Cred 2002         2002
Cred 2001         2001
Agent credentials admin
$ rctl domains get ...
Name         URI
Local domain sip.local
$ rctl peers get ...
Name            Username      AOR                         Max Contacts Balancing Algorithm Enabled
FreeSWITCH Demo freeswitchpbx sip:freeswitchpbx@sip.local              Least Connections   Yes
Asterisk Demo   asteriskpbx   sip:asteriskpbx@sip.local                Least Connections   Yes
$ rctl trunks get ...
Name       Inbound SIP URI      Send Register
Trunk Demo sip.provider.example false
$ rctl numbers get ...
Name        Telephony URL    AOR Link           Geo
Number Demo tel:+15551234567 sip:1001@sip.local Hangzhou, China (CN)
$ rctl acl get ...
Name     Deny CIDR Networks Allow CIDR Networks
ACL Demo 0.0.0.0/0          10.89.57.0/24

要点:Agent 的 Privacy 支持 Private/None;Peer 表自带负载均衡算法列(默认 Least Connections);Number 的 Geo 来自创建时的 city/country 字段;ACL 必须同时有 allow 与 deny 两列(校验规则见 5.2)。

四、describe、单资源与时间戳缺陷

$ rctl agents describe 8a2e3a2e-f796-479d-a0bd-5c5a583a6fb7 -e localhost:8128 -i
Ref:          8a2e3a2e-f796-479d-a0bd-5c5a583a6fb7
APIVersion:   v2
Name:         Agent 1002
Username:     1002
Domain:       sip.local
Max Contacts:
Privacy:      None
Credentials:
  Ref:      336cbbb3-d129-4d17-ad98-1ce3f32cd84f
  Username: 2001
Enabled:      Yes
Extended:     null
Created:      1970-01-21T18:03:43.286Z
Updated:      1970-01-21T18:03:43.286Z
  • Created/Updated 显示为 1970 年:实测资源创建于 2026-10-26,rctl 2.25.0 把毫秒时间戳按秒解释了(1.79e12 ms → 1970-01-21)。判断真实创建时间以 SDK 返回值或数据库为准。
  • 单资源查询:rctl agents get 7055df5a-5a2e-4be6-b303-2b9ae7005da5 -e localhost:8128 -i,输出与列表同款单行表。
  • delete 静默成功:rctl agents delete <ref> -e ... -i 无任何输出、退出码 0,需再 get 复核。
  • update 为交互 prompt(逐字段回填旧值),改完用 describe 复核。

五、创建与校验报错实录(SDK 构造 + API 校验)

5.1 正确 payload 实测

create 交互式不便脚本化,以下用 SDK 构造(脚本见 5.3),全部创建成功:

const acl = await acls.createAcl({
  name: "ACL Demo", allow: ["10.89.57.0/24"], deny: ["0.0.0.0/0"] })
const peer = await peers.createPeer({
  name: "Asterisk Demo", username: "asteriskpbx",
  aor: "sip:asteriskpbx@sip.local", contactAddr: "10.89.57.20:5060",
  enabled: true, maxContacts: -1 })
const trunk = await trunks.createTrunk({
  name: "Trunk Demo", sendRegister: false, inboundUri: "sip.provider.example",
  uris: [{ user: "trunkuser", host: "sip.provider.example", port: 5060,
           transport: "UDP", weight: 1, priority: 1 }] })
const num = await numbers.createNumber({
  name: "Number Demo", telUrl: "tel:+15551234567",
  aorLink: "sip:1001@sip.local", city: "Hangzhou", country: "China",
  countryIsoCode: "CN", trunkRef: trunk.ref })

5.2 校验报错对照(实测原话,缺什么补什么)

报错触发处置
3 INVALID_ARGUMENT: acl rules are requiredACL 只给 allowallow 与 deny 都必须非空,且每项为合法 IPv4 CIDR/IP(源码 hasACLRules:任一为空即拒);无 deny 需求时填 ["0.0.0.0/0"]
3 INVALID_ARGUMENT: the username is requiredPeer 未给 usernamePeer 必填:username/aor/contactAddr/enabled/maxContacts
3 INVALID_ARGUMENT: the priority must be a valid integerTrunkURI 缺 priorityuris[] 每项需 user/host/port/transport/weight/priority
3 INVALID_ARGUMENT: the inboundUri is requiredTrunk 缺 inboundUriTrunk 必填:sendRegister + inboundUri(出向 URI 列表为可选)
3 INVALID_ARGUMENT: the city is requiredNumber 缺地理信息Number 必填:telUrl/aorLink/city/country/countryIsoCode;trunkRef 可选

5.3 分页实测(pageSize/pageToken)

let token
let page = 0
do {
  const r = await agents.listAgents({ pageSize: 2, pageToken: token })
  page++
  console.log(page, r.items.map(i => i.name), r.nextPageToken)
  token = r.nextPageToken
} while (token && page < 5)
page1 {"n":2,"names":["Agent temp","Agent 1001"],"next":"7055df5a-5a2e-4be6-b303-2b9ae7005da5"}
page2 {"n":0,"names":[],"next":null}

实测结论:pageSize 生效(2→2 条);结果按创建时间新→旧排序;nextPageToken 为本页末条的 ref 游标、向更旧方向翻页——尾页之后仍会返回一个"空页 + null",遍历循环按 nextPageToken 判停即可(会多一次空查询)。rctl 的 -s 即该页大小参数(默认 50)。

六、SDK 脚本化:签名矩阵与三大传参坑

6.1 类与方法(@routr/sdk 实测导出)

7 个客户端类:Acls / Agents / Domains / Numbers / Peers / Trunks / Credentials;每类 5 个方法,命名规整:

类方法
AclscreateAcl / updateAcl / getAcl / deleteAcl / listAcls
AgentscreateAgent / updateAgent / getAgent / deleteAgent / listAgents
DomainscreateDomain / updateDomain / getDomain / deleteDomain / listDomains
NumberscreateNumber / updateNumber / getNumber / deleteNumber / listNumbers
PeerscreatePeer / updatePeer / getPeer / deletePeer / listPeers
TrunkscreateTrunk / updateTrunk / getTrunk / deleteTrunk / listTrunks
CredentialscreateCredentials / updateCredentials / getCredentials / deleteCredentials / listCredentials

构造参数 ClientOptions:{ endpoint, insecure?, cacert?, username?, password? }——明文用 insecure: true,TLS 用 cacert(配置篇 6.2 实测)。

6.2 三大传参坑(全部实测踩中)

  1. getX(ref) / deleteX(ref) 收位置字符串,不收对象。传 {ref: "..."} 时:getAgent 报 5 NOT_FOUND: Resource not found: [object Object](对象被字符串化成字面量 [object Object]);deleteAgent 更隐蔽——静默返回成功但什么都没删(实测 before/after 列表完全一致)。正确:await agents.deleteAgent(ref)。
  2. updateX 才收对象,且 ref 写在对象里:await agents.updateAgent({ ref, name: "...", maxContacts: 1 }),改后 getAgent(ref) 复核 name/maxContacts 均已生效(实测输出 "Agent temp (updated)", 1)。
  3. 列表参数是 pageSize/pageToken,不是 size。传 {size: 2} 键名不匹配被忽略,一次返回全部 50 条默认页——看起来像"分页失效",实为传参错误。

6.3 端到端示例(创建 → 查 → 改 → 删,实测通过)

const SDK = require("@routr/sdk").default
const O = { endpoint: "localhost:8128", insecure: true }
const credentials = new SDK.Credentials(O)
const agents = new SDK.Agents(O)

async function main() {
  const cred = await credentials.createCredentials({
    name: "Cred temp", username: "temp2", password: "temppass" })
  const agent = await agents.createAgent({
    name: "Agent temp", username: "temp2",
    domainRef: "7117e0cd-4459-4af4-931a-d406d65d57e6",
    credentialsRef: cred.ref, maxContacts: -1, enabled: true })

  const got = await agents.getAgent(agent.ref)          // 位置字符串!
  console.log(got.name, got.username)                   // Agent temp temp2

  await agents.updateAgent({ ref: agent.ref, name: "Agent temp (updated)", maxContacts: 1 })
  console.log((await agents.getAgent(agent.ref)).name)  // Agent temp (updated)

  await agents.deleteAgent(agent.ref)                   // 位置字符串!
  await credentials.deleteCredentials(cred.ref)
}
main().catch(console.error)

七、trace 工具族(2.25 新增):录制消息的消费端

rctl 2.25 新增 trace 六个子命令,消费 dispatcher 的消息录制产物(JSONL,每行一条消息记录)。录制开启方法见《Routr 2.25.1 配置参数详解》第六节(RECORD_MESSAGES 家族)。

子命令用途
trace validate FILE校验 JSONL 可读性:JSON 合法、traceVersion 支持、记录 schema 正确
trace lint FILE...对每条记录跑 SIP 规则,--sip-connect 追加 SIPconnect 1.1 规则
trace replay FILE重放整通呼叫:消息阶梯、组件改动、规则结论;`--view summary
trace send FILE把单条录制消息发给运行中的 dispatcher/processor,比对线上应答与录制差异
trace sanitize FILE生成语料:用语料测试凭据重算 Digest 响应、对号码/主机做字面量改写
trace compat语料回归:整批语料过 dispatcher,报告每条漂移

实测(录制文件为配置篇 6.3 的一次 401→200 REGISTER 全流程):

$ rctl trace validate 20261026T135939229Z-1911051453_10.89.57.3.jsonl
20261026T135939229Z-1911051453_10.89.57.3.jsonl: 2 records, trace version 1, Call-ID 1911051453@10.89.57.3

$ rctl trace lint 20261026T135939229Z-1911051453_10.89.57.3.jsonl
20261026T135939229Z-1911051453_10.89.57.3.jsonl  2 records  ok no findings
rules: sip

$ rctl trace replay 20261026T135939229Z-1911051453_10.89.57.3.jsonl --static
routr. trace  20261026T135939229Z-1911051453_10.89.57.3.jsonl  1911051453@10.89.57.3    ANSWERED 200
8e6607060fc0 2.25.1 recorded 2026-10-26 13:59:39 UTC 2 messages
ANSWERED 200  REGISTER c2  2 messages

FACTS
Call        1001@sip.local → 1001
Call-ID     1911051453@10.89.57.3
EdgePort    8e6607060fc0 2.25.1
Dispatcher  dispatcher 2.25.1
Recorded    2026-10-26 13:59:39 UTC

CHECKS  no INVITE was forwarded
 -  sip rules (no INVITE was forwarded)

LADDER
                    1001                                    8e6607060fc0
          |                                                       |
 ?    +0  |-REGISTER c1--------------------------------------->
          <........................................401 Unauthorized.|
 -  +112  |-REGISTER c2---------------------------------------|

价值:一次 docker logs 看不出来的"这通呼叫每个组件改了什么、规则怎么说",replay 一条命令给出结论;--fail-on rejected 可直接挂进 CI。录制文件中 401 响应的 nonce/response 摘要凭据字段已被红摘(redact)。

八、排障速查

现象根因与处置
Cannot connect to service [service address = ..., tlsOn = false]API 口不通或 TLS 状态不匹配:明文口加 -i;TLS_ON=true 后改 -c <ca.crt>(SDK 用 cacert)
command -e not found全局 flags 写到了资源命令前面
SDK 删了"成功"但资源还在deleteX(ref) 传了 {ref} 对象——静默无操作。改传字符串
NOT_FOUND: Resource not found: [object Object]getX(ref) 传了对象。改传字符串 ref
"分页不生效,一次返回全部"列表参数名写成了 size,应为 pageSize/pageToken
describe 时间是 1970 年rctl 2.25.0 毫秒/秒换算缺陷,展示层问题
3 INVALID_ARGUMENT: ...按 5.2 对照表补必填字段
create/update 想自动化rctl 无字段级参数,改用 SDK(第六节)

九、参考