net-connector 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/CONTRIBUTING.md +12 -0
  4. data/README.md +27 -424
  5. data/README.zh-CN.md +64 -0
  6. data/{exe/net-connector-backup → bin/net-backup} +1 -0
  7. data/docs/VERIFICATION.md +2 -2
  8. data/docs/architecture.md +27 -5
  9. data/docs/netdisco.md +176 -0
  10. data/docs/netdisco.zh-CN.md +247 -0
  11. data/docs/usage.md +124 -0
  12. data/docs/usage.zh-CN.md +190 -0
  13. data/examples/backup.yml +5 -4
  14. data/examples/inventory_sql.yml +5 -1
  15. data/lib/net/connector/device/base.rb +12 -5
  16. data/lib/net/connector/device/local_backup.rb +3 -0
  17. data/lib/net/connector/device/running_config/strategy.rb +2 -1
  18. data/lib/net/connector/device/running_config.rb +2 -0
  19. data/lib/net/connector/device/tftp/receipt.rb +1 -0
  20. data/lib/net/connector/device/tftp/strategy.rb +28 -9
  21. data/lib/net/connector/device/tftp.rb +23 -16
  22. data/lib/net/connector/device/topology/immediate_strategy.rb +2 -1
  23. data/lib/net/connector/engine/authentication.rb +1 -0
  24. data/lib/net/connector/engine/core.rb +1 -0
  25. data/lib/net/connector/engine/dialogue.rb +5 -134
  26. data/lib/net/connector/engine/errors.rb +0 -92
  27. data/lib/net/connector/engine/known_hosts.rb +20 -4
  28. data/lib/net/connector/engine/log.rb +27 -6
  29. data/lib/net/connector/engine/recovery.rb +1 -1
  30. data/lib/net/connector/engine/redactor.rb +98 -0
  31. data/lib/net/connector/engine/response_reader.rb +139 -0
  32. data/lib/net/connector/engine/session.rb +20 -7
  33. data/lib/net/connector/engine/transport.rb +21 -10
  34. data/lib/net/connector/netdisco/backup_run.rb +35 -8
  35. data/lib/net/connector/netdisco/cli/options.rb +20 -8
  36. data/lib/net/connector/netdisco/cli.rb +14 -11
  37. data/lib/net/connector/netdisco/config_file.rb +19 -14
  38. data/lib/net/connector/netdisco/connection.rb +0 -1
  39. data/lib/net/connector/netdisco/database_client.rb +44 -26
  40. data/lib/net/connector/netdisco/device.rb +5 -0
  41. data/lib/net/connector/netdisco/diagnostic.rb +8 -0
  42. data/lib/net/connector/netdisco/fleet.rb +12 -3
  43. data/lib/net/connector/netdisco/plan.rb +16 -3
  44. data/lib/net/connector/netdisco/planner.rb +2 -1
  45. data/lib/net/connector/netdisco/progress.rb +31 -7
  46. data/lib/net/connector/netdisco/report/files.rb +4 -2
  47. data/lib/net/connector/netdisco/result_store.rb +6 -7
  48. data/lib/net/connector/netdisco/rules.rb +8 -5
  49. data/lib/net/connector/netdisco/settings.rb +83 -19
  50. data/lib/net/connector/netdisco/tftp_archive.rb +6 -3
  51. data/lib/net/connector/netdisco/tftp_verification.rb +1 -0
  52. data/lib/net/connector/netdisco/worker.rb +3 -1
  53. data/lib/net/connector/netdisco.rb +1 -21
  54. data/lib/net/connector/storage/private_file.rb +6 -0
  55. data/lib/net/connector/vendor/cisco_ios/running_config.rb +1 -0
  56. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +2 -0
  57. data/lib/net/connector/vendor/cisco_ios/topology.rb +2 -0
  58. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +2 -0
  59. data/lib/net/connector/vendor/h3c/tftp_backup.rb +1 -0
  60. data/lib/net/connector/vendor/h3c/topology.rb +2 -0
  61. data/lib/net/connector/vendor/hillstone/running_config.rb +1 -0
  62. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +2 -0
  63. data/lib/net/connector/vendor/hillstone/topology.rb +2 -0
  64. data/lib/net/connector/vendor/huawei/tftp_backup.rb +2 -1
  65. data/lib/net/connector/vendor/palo_alto/running_config.rb +27 -6
  66. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +2 -0
  67. data/lib/net/connector/vendor/palo_alto/topology.rb +16 -16
  68. data/lib/net/connector/vendor/radware/running_config.rb +1 -0
  69. data/lib/net/connector/vendor/radware/tftp_backup.rb +2 -0
  70. data/lib/net/connector/vendor/radware/topology.rb +1 -0
  71. data/lib/net/connector/version.rb +2 -1
  72. data/lib/net/connector.rb +1 -0
  73. data/lib/net-connector.rb +1 -0
  74. metadata +12 -20
  75. data/lib/net/connector/netdisco/tftp_history.rb +0 -16
data/README.zh-CN.md ADDED
@@ -0,0 +1,64 @@
1
+ # net-connector
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ 通过 SSH 或 Telnet 操作网络设备的 Ruby 库。支持配置采集、私有备份、交互脚本、TextFSM 解析,以及基于 Netdisco 清单的批量备份。
6
+
7
+ 需要 Ruby 3.2+ 和 POSIX 系统。SSH 使用本机 OpenSSH;Telnet 需要本机安装 `telnet` 并显式配置。
8
+
9
+ ## 安装
10
+
11
+ ```sh
12
+ gem install net-connector
13
+ ```
14
+
15
+ ## 快速开始
16
+
17
+ 先创建并保护备份目录、登记设备 SSH 主机密钥,再通过环境变量提供凭据。
18
+
19
+ ```ruby
20
+ require "net/connector"
21
+
22
+ Net::Connector.open(:cisco_ios,
23
+ host: "192.0.2.10", username: ENV.fetch("NC_DEVICE_USERNAME"),
24
+ password: ENV.fetch("NC_DEVICE_PASSWORD")) do |device|
25
+ backup = device.backup(path: "/var/backups/network/router.cfg")
26
+ puts "#{backup.bytes} bytes, SHA-256 #{backup.sha256}"
27
+ end
28
+ ```
29
+
30
+ 代码块退出时关闭会话。备份通过私有文件原子替换,采集失败保留旧备份,设备命令不会自动重放。返回的配置和命令输出仍是敏感数据。
31
+
32
+ ## 支持的设备
33
+
34
+ H3C Comware 及无线控制器、Cisco IOS/IOS XE 与 NX-OS、华为、山石 StoneOS、Radware Alteon 和 Palo Alto PAN-OS。各厂商能力不同,详见[设备操作指南](docs/usage.zh-CN.md#支持的设备)。合成测试不代表所有固件均已现场验证。
35
+
36
+ ## 批量备份
37
+
38
+ ```sh
39
+ net-backup --config config.yml --show-config
40
+ net-backup --config config.yml --plan
41
+ net-backup --config config.yml
42
+ ```
43
+
44
+ 从 [backup.yml](examples/backup.yml) 或 [inventory_sql.yml](examples/inventory_sql.yml) 开始配置。清单与设备凭据放在环境变量中,YAML 只保存非敏感设置。HTTP/PostgreSQL 清单、成功策略、TFTP 核验和批量示例见 [Netdisco 指南](docs/netdisco.zh-CN.md)。
45
+
46
+ ## 文档
47
+
48
+ | 主题 | English | 简体中文 |
49
+ | --- | --- | --- |
50
+ | 设备操作、解析、拓扑与日志 | [Device guide](docs/usage.md) | [设备操作](docs/usage.zh-CN.md) |
51
+ | 清单、配置、CLI 与批量备份 | [Netdisco guide](docs/netdisco.md) | [清单与批量备份](docs/netdisco.zh-CN.md) |
52
+
53
+ 详细参考:[架构](docs/architecture.md)、[验证](docs/VERIFICATION.md)、[发布](docs/RELEASING.md)和[示例](examples/README.md)。
54
+
55
+ ## 开发
56
+
57
+ ```sh
58
+ bundle install
59
+ script/ci
60
+ ```
61
+
62
+ 门禁执行敏感数据扫描、Ruby 与工作流检查、测试、本地 PTY 基准及隔离安装验证。参与开发见 [CONTRIBUTING](CONTRIBUTING.md),漏洞报告见 [SECURITY](SECURITY.md),版本变化见[更新记录](CHANGELOG.md)。问题和 PR 可使用中文或英文。
63
+
64
+ 采用 [MIT](LICENSE) 许可。
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ # 安装后的命令行入口;退出码由批量 CLI 决定,加载时不直接连接设备。
4
5
  require "net/connector/netdisco"
5
6
 
6
7
  exit Net::Connector::Netdisco::CLI.new.run
data/docs/VERIFICATION.md CHANGED
@@ -176,8 +176,8 @@ BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle exec rake test package:verify
176
176
  当前这两个关键依赖的正常解析与下限恰好相同,报告仍记录实际版本,供以后比较。
177
177
  开发锁文件均忽略;gemspec 保持兼容范围,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
178
178
 
179
- gem 只收录库代码、TextFSM 模板、CLI、架构/验证/发布文档、README、LICENSE、
180
- CHANGELOG、SECURITY 和 CONTRIBUTING。测试、示例、发布工具、工作流、本地评审快照、配置和备份不进入包。
179
+ gem 只收录库代码、TextFSM 模板、CLI、双语设备/Netdisco 指南、架构/验证/发布文档、双语 README、LICENSE、
180
+ CHANGELOG、SECURITY 和 CONTRIBUTING。仅两个非敏感 YAML 示例随包分发;测试、示例脚本、发布工具、工作流、本地评审快照、本地配置和备份不进入包。
181
181
  更改 gemspec 后,实际归档仍须通过独立的文件白名单检查。
182
182
 
183
183
  ## 敏感数据
data/docs/architecture.md CHANGED
@@ -5,6 +5,8 @@
5
5
  | 层次 | 职责 | 位置 |
6
6
  | --- | --- | --- |
7
7
  | 会话引擎 | 登录、传输、命令、脚本及资源所有权 | `engine/` |
8
+ | 对话协议与响应读取 | 交互规则和响应值独立于有状态的读取、截止时间与输出预算 | `engine/dialogue.rb`、`engine/response_reader.rb` |
9
+ | 错误与脱敏 | 错误类型和安全诊断;秘密作用域及 Expect 脱敏适配独立实现 | `engine/errors.rb`、`engine/redactor.rb` |
8
10
  | 日志 | 事件上下文与生命周期;安全事件、格式和字节流分别实现 | `engine/log.rb`、`engine/log/` |
9
11
  | 设备入口 | 能力组合、会话代理、通用脚本钩子 | `device/base.rb` |
10
12
  | 设备档案 | 不可变规则与策略接口校验;Builder 负责声明 DSL | `device/profile.rb`、`device/profile/` |
@@ -17,6 +19,7 @@
17
19
  | 解析 | TextFSM 模板选择、记录转换及异常映射 | `textfsm.rb`、`templates/` |
18
20
  | 终端文本 | 严格 UTF-8 字节验证和终端渲染 | `engine/terminal_text.rb`、`engine/terminal_renderer.rb` |
19
21
  | 清单与批量编排 | 清单预算、策略快照、计划校验、工作线程和报告 | `netdisco/` |
22
+ | 命令行入口 | 转交给 CLI,参数处理与业务流程由库负责 | `bin/net-backup` |
20
23
 
21
24
  `Base` 引入 `RunningConfig::Capability`、`LocalBackup::Capability`、`Tftp::Capability`、`Topology::Capability`、`TextFSM::Capability` 及 `SaveConfig`。能力入口与实现共置,Base 不再重复业务包装。配置采集的临时策略绑定也由 RunningConfig 管理;备份与拓扑通过 `device.running_config` 复用同一流程。
22
25
 
@@ -45,7 +48,8 @@
45
48
  1. 命令响应完成:匹配本次响应的结束提示,或收到厂商明确完成行;不等于业务状态正确。
46
49
  2. 业务状态读回:目标描述与批准计划一致;不排除其他设备会话此后改写,也不等于已持久化。
47
50
  3. 持久化/服务端证据:本地文件完成 fsync/rename/目录 fsync,或设备明确报告保存完成。
48
- TFTP 目前只有设备报告,不能据此证明服务器文件、SHA-256 或本批版本归属;设备保存
51
+ 单设备 TFTP 入口只有设备报告;批量入口可通过本机服务器文件核验和归档补充
52
+ SHA-256 与本批版本证据。未经核验的设备报告不能证明服务器文件;设备保存
49
53
  完成行也不是断电测试。读取、核验、持久化任何阶段失败都不触发自动回放/回滚。
50
54
 
51
55
  ## 业务约束
@@ -54,6 +58,8 @@
54
58
 
55
59
  采集命令匹配当前会话的完整提示符行,不以末尾单个 `#`、`>` 或 `]` 判断完成。PAN-OS 切换视图时保留已认证的设备身份。缺少最终提示符会使采集失败,旧备份保持不变;只有提示符或命令回显的响应属于 `:incomplete_configuration`,不是成功的空配置。PAN-OS 在 `show` 前后都检查候选配置差异。
56
60
 
61
+ PAN-OS 的 set 文本逐行增量跟踪单引号、双引号和转义状态,整条逻辑命令闭合后才由 Shellwords 校验;未闭合的多行内容仍拒绝。证书等长值不再每追加一行就重新解析完整前缀,校验仍在原会话租约内完成。
62
+
57
63
  IOS/NX-OS 清理器处理单条配置命令的响应,不把正文当成多命令会话记录。banner 内的命令、进度及完成提示均属于配置内容;易变时间注释仅从首个配置语句之前的响应头删除。终端渲染器成段处理普通文本,控制序列仍逐字节解释;光标覆盖、行尾空白、UTF-8 校验和逐行字节限额保持一致。
58
64
 
59
65
  `LocalBackup` 在采集前取得 `BackupLock`,持有到摘要比较、替换与结果构造结束。锁名由 realpath 父目录与归一化文件名决定;Unicode NFC、大小写折叠后的摘要同时覆盖尚未创建的目标。区分大小写的文件系统也保守合并这些锁,目标名称本身不变。锁文件用 NOFOLLOW/0600 打开,再验证普通文件、所有者、单硬链接和 inode;释放仅关闭 FD,不删除锁文件。默认 `flock(LOCK_EX | LOCK_NB)`,可选等待共用有限单调期限,超时均为 `BackupBusy`。
@@ -70,6 +76,8 @@ TFTP 归档分别保留本地批次副本与服务器归档副本的提交位置
70
76
 
71
77
  TFTP 只确认设备报告的上传结果:策略去掉命令、应答和提示符回显后,检查明确的完成行;文件名或表示“即将上传”的进度文字不算成功。原始输出或终端渲染文本中的失败证据优先于成功文字。
72
78
 
79
+ 失败证据核对与完成核对共用精确回显过滤。静态应答从已完成步骤的 `Command#interactions` 读取,`Interaction#static_response` 对动态回调返回 nil,核对不会重新执行挑战回调。失败核对逐步读取原文、回车历史和终端画面,不累积全部画面副本;颜色样式只影响显示,不能拆开诊断词或借光标擦除隐藏已经收到的失败。命令诊断及拓扑保存确认同样保留这类失败历史。
80
+
73
81
  TFTP 内置策略的 `validate_options!` 是纯参数校验,执行于 H3C 源文件探测之前。整个探测、脚本、证据核对、回执和事件记录由原有 `with_operation(:tftp_backup)` 独占;其他线程、Fiber 及回调重入均不能插入设备命令。租约只保护当前会话,不能隔离其他设备连接或服务器上的同名文件。
74
82
 
75
83
  `tftp_backup` 只返回不可变 `TftpReceipt`,直接包含来源、格式、请求路径和实际 `path`、完成时间及核验等级。只有设备完成证据时为 device_reported,没有服务器 SHA-256。H3C 自动源探测为 startup,显式文件为 saved_file/unknown;山石为 startup/dat,Cisco 为 running/cfg,PAN-OS 为 running/xml,Radware 为 native_archive/tgz。公共策略的默认来源为 unknown,不能据扩展名或字节数推断服务器内容。
@@ -114,11 +122,13 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
114
122
 
115
123
  `Settings` 保留动态秘密来源,`snapshot(mode:)` 只复制允许公开的非敏感字段,并在副作用前复用 `Configuration`、`Planner` 和 `Client.options` 校验。Fleet 的一次规划/执行只使用该份冻结策略;CLI 通过 `for_run` 将规划与执行绑定到同一策略。每台设备的凭据读取与策略解析分开,自定义凭据解析器给出的显式连接选项仍保留原优先级。带 `plan:` 的执行不再查询清单;以后新建的批次可以读取新策略和新的 API 凭据。
116
124
 
125
+ `BackupRun` 在创建批次目录和读取凭据前固定并验证策略。已固定的 `Settings#with_overrides` 只复制非敏感快照上的覆盖值,不重新读取环境中的策略字段;后续 `snapshot(mode:)` 继续按当前业务模式校验,离线导出不会隐式转成网络备份校验。原凭据来源仍保持动态。
126
+
117
127
  `Client#devices` 每次持有独立 `InventoryBudget`,认证、分页、QueryRequired 查询共同累计正文字节和去重前记录数,并共享单调时钟 deadline。默认 HTTP 使用 `read_body`,先检查实际接收字节再追加,不信任 Content-Length。每阶段缩短原生连接/读/写超时;总期限还覆盖慢速响应头。期限观察线程只关闭本次拥有的 HTTP 连接,防止 chunked 的收尾读取拖延返回,调用结束即唤醒并 join。默认 HTTP 不做隐式 GET 重试;这一机制不进入设备命令路径。
118
128
 
119
129
  注入的 `requester` 接收 URI 和 request。它返回后才接受长度和期限检查,不在任意用户 Ruby 回调中注入异步异常。解析和集合操作完成后也检查期限,但这不构成任意 CPU 回调的抢占保证。预算失败仅报告错误码与安全类型,没有部分清单、响应正文或底层 cause。HTTPS 的标准证书检查不变;默认允许明文 HTTP,可通过显式策略禁止。
120
130
 
121
- `Settings#inventory_source` 选择 HTTP `Client` 或 PostgreSQL `DatabaseClient`,两者只向 Fleet 提供 `devices`,不改变设备映射、计划和执行接口。数据库 SQL 与标量参数由用户配置,不内置业务表名或筛选;结果用列别名适配 `Client::FIELDS`,只保留这份现有字段白名单。连接参数由 `DatabaseClient::CONNECTION_ENV` 集中映射到 Netdisco 的 `NETDISCO_DB_*` 环境变量,全部排除在纯策略快照外;每次新建清单客户端时读取最新值。
131
+ `Settings#inventory_source` 选择 HTTP `Client` 或 PostgreSQL `DatabaseClient`,两者只向 Fleet 提供 `devices`,不改变设备映射、计划和执行接口。数据库 SQL 与标量参数由用户配置,不内置业务表名或筛选;结果用列别名适配 `Client::FIELDS`,只保留这份现有字段白名单。数据库身份使用 `ND_DB_HOST/NAME/USER/PASS`,每次新建客户端时读取;端口、TLS 和超时来自 YAML 并进入不可变策略快照。
122
132
 
123
133
  `DatabaseClient` 每次调用独占 PostgreSQL 连接,在只读事务中用扩展查询协议声明游标,原生解析器拒绝多语句和非查询输入。FETCH 大小及次数复用清单预算,单行模式使 libpq 不缓存整页;每行在追加前计数,额外列也消耗字节预算。驱动必须先解码单行,所以单个超大字段仍可超过 Ruby 预算的瞬时内存。总 deadline 覆盖连接和所有语句,每次 FETCH 前缩短 statement_timeout;正常完成回滚只读事务,异常关闭连接,任何失败均不交付部分清单。NOTICE、原始数据库异常及 cause 不进入日志或 CLI;稳定错误码区分连接失败、查询失败、无效清单和预算超限。SQL/参数可在 show-config 中查看,不能用于传递凭据。
124
134
 
@@ -172,7 +182,7 @@ Fleet 可把回执与本地普通文件的时间、稳定性、摘要及目录
172
182
 
173
183
  累计等于上限时当前脚本可正常结束,但下一条查询在发送前失败;超过上限的完整主响应先进入 steps,再返回 `ScriptOutputLimitExceeded`,后处理和下一命令不会继续。追加查询不进入主脚本的公开 steps,但计入预算并在错误中保留实际查询命令。吞掉查询预算异常的钩子不能让超额脚本报告成功。已执行命令不重放;确认的 TFTP 完成行仍可构造带收尾错误的回执。
174
184
 
175
- 累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS 或整批内存硬上限。合成基准不足以确定适合所有设备的默认阈值,因此默认不限制累计值。设置通过 YAML `ssh.max_script_output_bytes`、`NC_MAX_SCRIPT_OUTPUT_BYTES` 和 CLI 同名选项进入批次策略快照,优先级为 CLI > ENV > YAML;未配置时不传递该可选键,显式凭据 resolver 可覆盖连接参数。
185
+ 累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS 或整批内存硬上限。合成基准不足以确定适合所有设备的默认阈值,因此默认不限制累计值。设置通过 YAML `ssh.max_script_output_bytes` 和 CLI 同名选项进入批次策略快照,优先级为 CLI > YAML;未配置时不传递该可选键,显式凭据 resolver 可覆盖连接参数。
176
186
 
177
187
  Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限声明入口,厂商策略负责差异行为。公开方法、厂商钩子、结果对象和 CLI JSON 字段以当前文档为准;内部不做运行时方法注入,也没有工作流 DSL。
178
188
 
@@ -279,7 +289,7 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
279
289
  | Juniper、Arista、F5、Fortinet | 每次增加一个设备系列的登录、配置采集与本地备份,再按需扩展 TFTP / 拓扑 | 指定型号和固件的脱敏会话样本、分页和失败回显、现场验证条件;不能仅靠厂商名称声明支持 |
280
290
  | TextFSM 模板 | 按业务需要增加接口、路由等具体命令及字段 | 样本来源和许可、正常 / 空表 / 部分输出测试、模板随 gem 安装的证据;`parse_config` 仍是指定模板提取,不是完整配置模型 |
281
291
  | 持久调度与重试 | 先由调用应用在持久队列中运行单设备任务 | 设备互斥、任务标识、幂等策略、重试上限及取消语义;连接器不自动重放可能已执行的配置命令 |
282
- | 英文文档 | 优先增加英文 README,提供安装、主要能力、限制和验证入口 | 确定与中文文档同步更新的维护方式;CLI 消息国际化另行评估,避免静默改变现有输出 |
292
+ | 文档翻译 | README 与设备、Netdisco 指南已提供双语;架构、验证和发布参考仍使用中文 | 按 CONTRIBUTING 同步双语使用文档;参考文档翻译与 CLI 消息国际化另行评估 |
283
293
 
284
294
  几千台设备的调度应由应用限制总并发及同设备并发,并持久保存逐台结果。
285
295
  只读采集可在重新建连后按应用策略重试;配置下发中断后应先回读设备状态,
@@ -288,7 +298,11 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
288
298
 
289
299
  ## 加载入口与厂商策略
290
300
 
291
- `require "net/connector"` 加载设备 API 和引擎,厂商通过 autoload 按需加载。TextFSM 能力模块可预先组合,但外部 `textfsm` gem 只在实际解析时加载。仅需会话引擎时使用 `require "net/connector/engine/core"`;离线文件操作使用 `net/connector/storage`,不加载厂商。
301
+ `require "net/connector"` 加载设备 API 和引擎,厂商由 `vendor_class` 按需加载。TextFSM 能力模块可预先组合,但外部 `textfsm` gem 只在实际解析时加载。仅需会话引擎时使用 `require "net/connector/engine/core"`;离线文件操作使用 `net/connector/storage`,不加载厂商。
302
+
303
+ `engine/dialogue` 只定义 Interaction、Dialogue 与 Response;读取循环位于 `engine/response_reader`。`engine/errors` 不加载脱敏运行时,`engine/redactor` 负责秘密作用域。Session 明确加载并持有读取器与脱敏器。
304
+
305
+ Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb` 的预设加载顺序。总入口只组合 CLI 和 BackupRun;Batch 通过 autoload 创建 Report,避免两者循环加载。私有 InventoryBudget 由 Client 加载。Progress 的终端显示、Fleet 的设备执行、Report 的结果解释和 Storage 的文件持久化保持独立职责。
292
306
 
293
307
  公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Topology::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
294
308
 
@@ -311,6 +325,10 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
311
325
 
312
326
  | 原名称或入口 | 当前入口 |
313
327
  | --- | --- |
328
+ | `exe/net-connector-backup` | `bin/net-backup`;安装后命令为 `net-backup` |
329
+ | TFTP 策略 `interaction` | private `build_interaction`,构造交互规则;`device.interact` 仍表示立即进入人工终端交互 |
330
+ | `TftpHistory`、`TftpArchive#path_for/#capture` | `TftpArchive#upload_filename/#upload_and_archive` |
331
+ | `ResultStore::Text` | `ResultStore::Json`,与 `Report::Text` 的文本报告职责区分 |
314
332
  | `Base#login_dialogues` / `confirmation_dialogues` | protected `login_interactions` / `confirmation_interactions`;子类覆盖同步改名,继续支持 `super` |
315
333
  | `Fleet#backup_one` / `tftp_backup_one` / `run_one` | private `backup_device` / `tftp_backup_device` / `run_device`;公开批次入口不变 |
316
334
  | `engine`、`engine/base`、`engine/profile` | `net/connector`;底层分别为 `engine/core`、`device/base`、`device/profile` |
@@ -371,6 +389,8 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
371
389
 
372
390
  操作后处理无论抛出异常还是返回失败 Result,都在敏感作用域退出前归一化错误,再记录完成事件;Result 的已完成步骤及配置内容保持原样。最终处理继承实际执行的命令及交互敏感性,包括准备钩子、替换命令和追加查询;只累计标记,不长期保留秘密,也不影响后续普通命令的日志。
373
391
 
392
+ 业务执行与完成事件的异常范围分开:完成事件只尝试一次,观察者失败不能触发第二次相同通知。已有登录、命令或业务错误优先于失败日志的错误;脚本最终处理已生成的 Result.config 与 steps 不因日志收尾丢失。日志上下文刷新和传输接线恢复仍执行清理,但保留正在传播的主错误及 Interrupt。TFTP 的失败证据同样先决定错误码,再尝试记录失败事件。
393
+
374
394
  错误归一化通过 `Error#with_diagnostics` 保留内置错误的业务回执,并新建异常的原生状态,避免复制原 cause 或调用栈。自定义错误子类使用脱敏后的参数重新构造,不能复制可能参与消息呈现的原始私有字段。会话租约退出时若日志收尾失败,已返回的 Result 仍保留步骤、配置和原业务错误;仅在原操作成功时附加日志错误。
375
395
 
376
396
  单条命令的完整响应在输出日志和完成事件之前保存;即使这些日志处理失败,Session 仍在脱敏作用域退出前把响应交给 Execution,保留已完成步骤与输出预算。TFTP 可从失败结果的完成步骤构造设备回执,再报告日志错误;不会据此重放命令。
@@ -381,6 +401,8 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
381
401
 
382
402
  注入的 logger 由调用方持有;连接器不修改级别、formatter、progname,也不关闭它。每次写入同时检查配置与 logger 当前级别。消息对象支持 to_s/inspect 和安全 to_h,应用可自行输出 JSON。自有文本日志使用毫秒时间及逐行事件;raw 文件只写经过敏感保护的字节。上下文退出前先收尾渲染与脱敏缓冲,避免上一命令尾部被标成下一条命令。
383
403
 
404
+ 人类进度显示由 Progress 单独管理:终端 write/flush 的 I/O 故障会停用后续显示,并只保留固定词表内的错误类型,不携带原始异常。心跳照常回收,设备任务、events.jsonl 与报告持久化继续进行;BackupRun 将显示故障合并为一次批次 callback_errors。若首次故障发生在报告保存后的最终显示阶段,仅补写一次报告诊断,最终 JSON 与退出码采用更新后的策略结果;已有报告错误保持优先,不重放设备备份或事件。
405
+
384
406
  `log_file` 使用 NOFOLLOW/NONBLOCK 打开,再对同一 FD 检查普通文件类型,随后才修改权限或写入。FIFO、设备文件和目录不能用作日志文件;应用拥有的输出流通过 `logger` 接入。日志打开先于登录,不能依赖登录超时为文件打开提供期限。
385
407
 
386
408
  ## 公开契约与 Rails 接入
data/docs/netdisco.md ADDED
@@ -0,0 +1,176 @@
1
+ # Netdisco and fleet backups
2
+
3
+ [简体中文](netdisco.zh-CN.md) | [Home](../README.md) | [Device operations](usage.md)
4
+
5
+ ## Configure inventory and credentials
6
+
7
+ Load `require "net/connector/netdisco"` for inventory and batch APIs. Netdisco supplies metadata; device credentials come from the environment or a caller-provided resolver. The full inventory is validated before device tasks start. Invalid addresses, unsupported vendors, filters, duplicates, missing credentials, failures, and partial results remain distinguishable.
8
+
9
+ For HTTP inventory:
10
+
11
+ ```sh
12
+ export ND_URL=https://netdisco.example/netdisco
13
+ export ND_USERNAME=inventory-reader
14
+ export ND_PASSWORD='replace-me'
15
+ export NC_DEVICE_USERNAME=backup-user
16
+ export NC_DEVICE_PASSWORD='replace-me'
17
+ ```
18
+
19
+ Use `ND_API_KEY` instead of the HTTP username/password pair when available. HTTPS retains standard certificate verification. HTTP is allowed by default for compatibility; set `netdisco.allow_insecure_http: false` to reject it before sending credentials.
20
+
21
+ YAML contains only non-sensitive settings. Unknown fields, Ruby object tags, and credentials in YAML are rejected. Settings precedence is CLI, supported environment variables, YAML, then defaults. Files load only through `--config FILE` or `NC_CONFIG`. Start with [backup.yml](../examples/backup.yml):
22
+
23
+ ```yaml
24
+ netdisco:
25
+ url: https://netdisco.example/netdisco
26
+ allow_insecure_http: false
27
+ backup:
28
+ directory: /var/backups/network
29
+ concurrency: 4
30
+ inventory:
31
+ include_vendors: [h3c, huawei, cisco_ios]
32
+ ssh:
33
+ host_key_policy: strict
34
+ max_script_output_bytes: null
35
+ tftp:
36
+ server: 192.0.2.30
37
+ vrfs:
38
+ cisco_nxos: management
39
+ ```
40
+
41
+ ## CLI planning and execution
42
+
43
+ ```sh
44
+ net-backup --config config.yml --show-config
45
+ net-backup --config config.yml --plan
46
+ net-backup --config config.yml --host 192.0.2.7 --success-policy selected
47
+ net-backup --config config.yml --tftp --plan
48
+ net-backup --config config.yml --tftp --all
49
+ net-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
50
+ ```
51
+
52
+ The installed command is `net-backup`; source checkouts use `bundle exec bin/net-backup`. `--show-config` validates public settings without querying inventory. `--plan` queries and validates inventory without connecting devices. `--host` selects one address and fails if absent. Local CLI backups select all ready devices by default; TFTP selects at most five per vendor unless `--all` is given. `--limit-per-vendor` accepts 1 through 5.
53
+
54
+ `--export IP` reads an existing canonical `<IP>.txt` file offline. It writes original bytes to stdout, or atomically writes a private file with `--output`. It does not require inventory or device credentials. Hostname-prefixed batch-example files are not searched by this command.
55
+
56
+ Plans and results are JSON. Exit codes:
57
+
58
+ | Code | Meaning |
59
+ | --- | --- |
60
+ | `0` | The requested success policy passed |
61
+ | `1` | Empty or incomplete batch under that policy |
62
+ | `2` | Configuration, inventory, or other CLI error |
63
+
64
+ The default `strict` policy requires a nonempty inventory, every outcome successful, and no callback/report errors. Filtered records still prevent strict success, so use `selected` when intentionally selecting one host or sampling. `selected` requires at least one success; remaining outcomes may only be `filtered` or `sample_limit`. Missing credentials, duplicates, invalid addresses, unsupported vendors, collisions, partial results, callback errors, and report errors all prevent it from passing.
65
+
66
+ `verified` adds server-file verification to selected success and requires TFTP with a local server directory. Strict `status` remains separate from `policy_success`; `coverage.complete` means every record was attempted, including failed tasks.
67
+
68
+ ## Fleet API and reports
69
+
70
+ ```ruby
71
+ require "net/connector/netdisco"
72
+
73
+ fleet = Net::Connector::Netdisco::Fleet.new
74
+ plan = fleet.plan_backup
75
+ puts plan.selected.map(&:host)
76
+ report = fleet.backup_all(plan: plan, success_policy: :selected, on_change: ->(outcome) {
77
+ puts "#{outcome.device.host}: #{outcome.backup.change}"
78
+ })
79
+ puts report.counts
80
+ puts report.report_location
81
+ exit 1 unless report.policy_success?
82
+ ```
83
+
84
+ `plan_backup` and `plan_tftp_backup` build inventory snapshots. Passing `plan:` preserves the selection and avoids another inventory query; malformed external plans are rejected before credentials, directories, or device I/O. Tasks run with bounded concurrency and retain inventory order. Ordinary device/callback failures do not discard other results; interruption stops workers and cleans resources.
85
+
86
+ Fleet returns `Netdisco::Report`, delegating raw execution fields to `report.batch`. `success?` and `status` remain strict; `policy_success?` evaluates the chosen policy. `summary` uses schema 2, with public settings, coverage, per-device diagnostics, timestamps, monotonic durations, and verification. `statistics` omits device detail. `Batch#build_report` only wraps existing execution data.
87
+
88
+ Local backups compare SHA-256. `on_change:` fires for created or changed artifacts; `on_start:` and `on_result:` observe attempted tasks. Callback faults appear in `callback_errors`. Saved artifacts with later errors remain `saved_with_error`; confirmed uploads with later errors remain `reported_with_error`. Neither result permits automatic replay.
89
+
90
+ `ResultStore::Json` writes private JSON by default. Use `ResultStore::Database.new(repository: YourModel)` with a `create!(attributes)` repository, or `result_store: nil` to persist yourself. Custom stores receive `write(report, directory:)`. Report errors retain known committed locations and prevent success.
91
+
92
+ ## PostgreSQL inventory
93
+
94
+ Set `netdisco.source: postgres` and provide your own SELECT query. Table names and business filters are not built into the library. See [inventory_sql.yml](../examples/inventory_sql.yml):
95
+
96
+ ```yaml
97
+ netdisco:
98
+ source: postgres
99
+ db_port: 5432
100
+ db_sslmode: verify-full
101
+ db_sslrootcert: /etc/net-connector/database-ca.crt
102
+ db_connect_timeout: 10
103
+ query: |
104
+ SELECT host(ip) AS ip, name, dns, vendor, os, model, os_ver, serial
105
+ FROM device
106
+ WHERE vendor = $1
107
+ ORDER BY ip
108
+ query_params: [H3C]
109
+ ```
110
+
111
+ The query must return unique column names including `ip`. Optional retained fields are `name`, `dns`, `vendor`, `os`, `model`, `os_ver`, and `serial`; other fields are discarded. Use `host(ip)` for PostgreSQL `inet` fields to omit prefixes. Parameters bind in array order, without interpolation or environment expansion. Use explicit ordering for repeatable selection.
112
+
113
+ ```sh
114
+ export ND_DB_HOST=database.example
115
+ export ND_DB_NAME=netdisco
116
+ export ND_DB_USER=inventory-reader
117
+ export ND_DB_PASS='replace-me'
118
+ net-backup --config examples/inventory_sql.yml --plan
119
+ ```
120
+
121
+ Host, database, user, and password are required. Source, query, and parameters may be overridden by `--source`, `--query`, and `--query-params`. They are public configuration and may appear in `--show-config`; passwords belong only in connection variables. HTTP credentials and device credentials are not prerequisites for a database-only plan.
122
+
123
+ The lazy-loaded `pg` driver owns one connection per query, uses a read-only transaction and parameterized cursor, and reads rows in single-row mode. Multiple statements, writes, and locking queries fail. Still grant the account only necessary SELECT privileges. Failure or timeout closes the connection and discards the entire inventory; raw SQL, parameters, and responses are excluded from diagnostics.
124
+
125
+ ## Inventory budgets and policy snapshots
126
+
127
+ Default budgets cover authentication, pagination, fallback queries, or database FETCH operations: 500 rows per page, 10,000 pages, 100,000 records before deduplication, 16 MiB per response, 128 MiB total, and 300 seconds total. Configure them under `netdisco`. These are adjustable design defaults, not measured deployment capacity.
128
+
129
+ HTTP counts actual chunks and closes on excess. Injected `requester:` callbacks are checked only after returning; their blocking and memory belong to the caller. PostgreSQL counts column names and values, including discarded fields, after libpq decodes each row; a huge field may still consume driver memory before the check. Connection, query, and FETCH share the deadline and use database statement timeouts.
130
+
131
+ `Settings#snapshot(mode:)` freezes public policy before side effects. CLI planning and execution share it; later calls can read new settings. Credentials remain dynamic per task. `Settings.new.for_run(mode: :backup)` shares policy across API calls; non-sensitive overrides preserve an existing snapshot. A custom `credentials:` resolver can supply per-device connection options, taking precedence over defaults. Injected clients remain caller-owned.
132
+
133
+ Explicit host mappings, device rules, and vendor overrides take precedence over automatic detection. Comware plus a branded H3C model can correct stale vendor labels; H3C WX/AC models select the wireless connector.
134
+
135
+ ## TFTP history and verification
136
+
137
+ Direct upload receipts only establish `device_reported`. Fleet can verify a local/mounted TFTP directory via `verification_root:`; the CLI accepts `--tftp-root DIR` or YAML `tftp.verification_root`. A verified receipt requires a current, nonempty regular file matching the actual receipt path, with SHA-256 evidence. Missing evidence prevents `verified` success.
138
+
139
+ Batch examples enable history preservation. With an accessible server directory, uploads use hostname/IP names and are retained in both `<batch>/tftp/` and server `archive/<batch>/`; pre-existing same-name files are archived under `previous/` before upload. File-state and filesystem-time baselines are captured under the upload lock. Verification or archival failure preserves available receipts and committed paths without reporting success.
140
+
141
+ Without a server directory, batch examples add a batch identifier to names, retain `device_reported`, and cannot save local configuration copies. PAN-OS uses the fixed `running-config.xml` name, so the history-preserving example refuses its upload without local server access. Plans still prevent same-batch filename collisions.
142
+
143
+ Reports distinguish `verification`, `server_file_verified`, `local_file`, `server_archive_file`, `bytes`, and `sha256`. TFTP has no configuration `change` notification.
144
+
145
+ ## Batch examples and deployment
146
+
147
+ ```sh
148
+ bundle exec ruby examples/backup.rb --json
149
+ bundle exec ruby examples/backup.rb --sample 3 --concurrency 10
150
+ bundle exec ruby examples/backup_tftp.rb --tftp-root /srv/tftp --success-policy verified
151
+ ```
152
+
153
+ Examples default to all filtered devices and `selected` success, unlike the CLI's strict default and TFTP sampling. Sampling comes from `--sample` or YAML `backup.limit_per_vendor`. Examples create unique UTC+8 batch directories named `YYYY-MM-DD_HH-mm-ss`, adding a sequence on collision. Local files use hostname/IP names; generic Fleet/CLI backups default to IP names, replacing IPv6 colons with underscores. `filename_style: :hostname_ip` changes the path and comparison baseline when a device is renamed.
154
+
155
+ Each batch retains `plan.json`, `events.jsonl`, `summary.json`, and `summary.txt`, with private permissions. Human progress goes to stderr; `--json` controls JSON stdout. `--verbose` shows device events; `--no-progress` disables progress. TTY progress has a heartbeat and throughput/ETA, while redirected output reports every 25 devices. Terminal faults are retained as callback diagnostics without skipping tasks or reports.
156
+
157
+ Examples support hidden `--ask-password` and `--ask-netdisco-password` prompts, plus environment credentials. `--stdin-credentials` reads one JSON line with `netdisco_username`, `netdisco_password`, `device_username`, and `device_password`; a second `RUN` line confirms device execution. For PostgreSQL, the first pair overrides database user/password.
158
+
159
+ Examples load the project `.env` through `dotenv` without overriding existing environment values. Direct `ruby examples/backup.rb` uses the installed gem; `bundle exec ruby` tests checkout code. Example directory paths are based on the project root; an environment-provided `NC_CONFIG` resolves from the startup directory. Shared library helpers do not load `.env` or change directories. See [examples](../examples/README.md) (Chinese).
160
+
161
+ ## Environment reference
162
+
163
+ | Variables | Purpose / default |
164
+ | --- | --- |
165
+ | `ND_URL` | HTTP inventory base URL |
166
+ | `ND_USERNAME`, `ND_PASSWORD` | HTTP login without API key |
167
+ | `ND_API_KEY` | Existing API key |
168
+ | `ND_DB_HOST`, `ND_DB_NAME`, `ND_DB_USER`, `ND_DB_PASS` | Required database connection fields |
169
+ | `NC_CONFIG` | Non-sensitive YAML file |
170
+ | `NC_DEVICE_USERNAME`, `NC_DEVICE_PASSWORD` | Default device credentials |
171
+ | `NC_<VENDOR>_USERNAME`, `NC_<VENDOR>_PASSWORD` | Vendor credentials, e.g. `CISCO_IOS` |
172
+ | `NC_ENABLE_PASSWORD`, `NC_<VENDOR>_ENABLE_PASSWORD` | Optional privilege credentials |
173
+
174
+ Only the variables listed above are supported. There are no legacy aliases. Public settings belong in YAML: budgets under `netdisco`, filters and mappings under `inventory`, script output budget and vendor protocols under `ssh`, and source files/VRFs under `tftp`. Database port, TLS mode, CA path, and connection timeout use `netdisco.db_port`, `db_sslmode`, `db_sslrootcert`, and `db_connect_timeout`. TFTP uses `tftp.server` and `tftp.verification_root`; progress uses `--no-progress`. Removed settings and old prefixes are rejected without echoing their values. Vendor source fields are `h3c_source_file`, `h3c_wireless_source_file`, and `huawei_source_file` under `tftp`.
175
+
176
+ Scheduling and durable queues belong to the caller. No device command retries are added by batch orchestration. For development gates and evidence limits, see [verification](VERIFICATION.md); for protected extension interfaces and report helpers, see [architecture](architecture.md) (Chinese).