net-connector 0.8.0 → 0.10.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/CONTRIBUTING.md +12 -0
  4. data/README.md +26 -425
  5. data/README.zh-CN.md +64 -0
  6. data/bin/net-backup +1 -0
  7. data/docs/VERIFICATION.md +3 -3
  8. data/docs/architecture.md +25 -9
  9. data/docs/netdisco.md +229 -0
  10. data/docs/netdisco.zh-CN.md +291 -0
  11. data/docs/usage.md +141 -0
  12. data/docs/usage.zh-CN.md +204 -0
  13. data/examples/backup.yml +5 -4
  14. data/examples/inventory_sql.yml +5 -1
  15. data/lib/net/connector/background_task.rb +69 -0
  16. data/lib/net/connector/device/base.rb +5 -4
  17. data/lib/net/connector/device/local_backup.rb +3 -0
  18. data/lib/net/connector/device/running_config/strategy.rb +2 -1
  19. data/lib/net/connector/device/running_config.rb +2 -0
  20. data/lib/net/connector/device/tftp/receipt.rb +1 -0
  21. data/lib/net/connector/device/tftp/strategy.rb +28 -9
  22. data/lib/net/connector/device/tftp.rb +9 -8
  23. data/lib/net/connector/device/topology/immediate_strategy.rb +2 -1
  24. data/lib/net/connector/device/topology/interface_description.rb +3 -3
  25. data/lib/net/connector/device/topology/interface_name.rb +10 -19
  26. data/lib/net/connector/device/topology/stages.rb +50 -0
  27. data/lib/net/connector/device/topology.rb +65 -24
  28. data/lib/net/connector/device_session.rb +38 -0
  29. data/lib/net/connector/engine/core.rb +1 -0
  30. data/lib/net/connector/engine/dialogue.rb +5 -2
  31. data/lib/net/connector/engine/known_hosts.rb +20 -4
  32. data/lib/net/connector/engine/log.rb +14 -9
  33. data/lib/net/connector/engine/recovery.rb +1 -1
  34. data/lib/net/connector/engine/session.rb +14 -5
  35. data/lib/net/connector/engine/transport.rb +36 -15
  36. data/lib/net/connector/execution_pool.rb +108 -0
  37. data/lib/net/connector/netdisco/backup_run.rb +27 -7
  38. data/lib/net/connector/netdisco/cli/options.rb +18 -8
  39. data/lib/net/connector/netdisco/cli.rb +20 -9
  40. data/lib/net/connector/netdisco/client.rb +5 -6
  41. data/lib/net/connector/netdisco/config_file.rb +22 -14
  42. data/lib/net/connector/netdisco/database_client.rb +73 -39
  43. data/lib/net/connector/netdisco/device.rb +3 -0
  44. data/lib/net/connector/netdisco/diagnostic.rb +4 -0
  45. data/lib/net/connector/netdisco/fleet.rb +70 -38
  46. data/lib/net/connector/netdisco/inventory_cache.rb +218 -0
  47. data/lib/net/connector/netdisco/operation_batch.rb +154 -0
  48. data/lib/net/connector/netdisco/operations.rb +268 -0
  49. data/lib/net/connector/netdisco/plan.rb +12 -3
  50. data/lib/net/connector/netdisco/planner.rb +26 -17
  51. data/lib/net/connector/netdisco/progress.rb +31 -10
  52. data/lib/net/connector/netdisco/report/files.rb +1 -0
  53. data/lib/net/connector/netdisco/result_store.rb +1 -0
  54. data/lib/net/connector/netdisco/rules.rb +7 -5
  55. data/lib/net/connector/netdisco/settings.rb +111 -23
  56. data/lib/net/connector/netdisco/tftp_archive.rb +6 -1
  57. data/lib/net/connector/netdisco/tftp_verification.rb +36 -11
  58. data/lib/net/connector/netdisco/worker.rb +7 -46
  59. data/lib/net/connector/storage/private_file.rb +6 -0
  60. data/lib/net/connector/vendor/cisco_ios/running_config.rb +1 -0
  61. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +2 -0
  62. data/lib/net/connector/vendor/cisco_ios/topology.rb +2 -0
  63. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +2 -0
  64. data/lib/net/connector/vendor/h3c/tftp_backup.rb +1 -0
  65. data/lib/net/connector/vendor/h3c/topology.rb +3 -1
  66. data/lib/net/connector/vendor/hillstone/running_config.rb +1 -0
  67. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +2 -0
  68. data/lib/net/connector/vendor/hillstone/topology.rb +3 -1
  69. data/lib/net/connector/vendor/huawei/tftp_backup.rb +2 -1
  70. data/lib/net/connector/vendor/palo_alto/running_config.rb +1 -0
  71. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +2 -0
  72. data/lib/net/connector/vendor/palo_alto/topology.rb +16 -16
  73. data/lib/net/connector/vendor/radware/running_config.rb +1 -0
  74. data/lib/net/connector/vendor/radware/tftp_backup.rb +2 -0
  75. data/lib/net/connector/vendor/radware/topology.rb +1 -0
  76. data/lib/net/connector/version.rb +2 -1
  77. data/lib/net/connector.rb +1 -0
  78. data/lib/net-connector.rb +1 -0
  79. metadata +14 -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) 许可。
data/bin/net-backup CHANGED
@@ -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
@@ -97,7 +97,7 @@ debug Logger 计数目标样本;不关闭协议检查、丢弃步骤或共享
97
97
  | --- | --- | --- |
98
98
  | 核心引擎 | `lib/net/connector/engine/**/*.rb` | 80% / 80% |
99
99
  | 脱敏与错误处理 | `lib/net/connector/engine/errors.rb`,包含 `Redactor` | 80% / 80% |
100
- | 批量并发 | `lib/net/connector/netdisco/worker.rb` | 80% / 80% |
100
+ | 批量并发 | `lib/net/connector/background_task.rb`、`execution_pool.rb`、`device_session.rb`、`netdisco/worker.rb` | 80% / 80% |
101
101
 
102
102
  关键组为空、任一关键文件未测量或组内没有可执行行时均失败。没有分支的组
103
103
  不需要分支命中,但仍须通过行覆盖率及文件加载检查。
@@ -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
@@ -1,6 +1,6 @@
1
1
  # 设备操作架构
2
2
 
3
- 一个连接器对应一台设备的一段会话。`Base` 组合设备能力,提供统一的命令执行和会话入口;各项能力的公开方法、流程和策略放在同一功能目录。公共层不反向依赖厂商,也不保留另一套 Operations 入口。
3
+ 一个连接器对应一台设备的一段会话。`Base` 组合设备能力,提供统一的命令执行和会话入口;各项能力的公开方法、流程和策略放在同一功能目录。设备公共层不直接加载厂商实现,也不额外包装同一套单设备业务;批次编排由 Netdisco 层调用这些公开能力。
4
4
 
5
5
  | 层次 | 职责 | 位置 |
6
6
  | --- | --- | --- |
@@ -38,6 +38,7 @@
38
38
  | `tftp_backup` | `TftpReceipt`,直接包含 server、path、时间、来源、格式及核验等级 | 参数先校验;失败/未确认直接抛出。完成后故障为 `TftpCompletionError`,保留回执 |
39
39
  | `parse_command`、`parse_config` | TextFSM 记录数组 | 命令失败、模板错误、严格 UTF-8/解析错误直接抛出,不用空数组代替未知输出 |
40
40
  | `neighbors`、`interface_descriptions` | `Neighbor` 数组、接口到描述的 Hash | 不支持、未知/部分输出或读取失败直接抛出;明确空表才允许空结果 |
41
+ | `sync_interface_descriptions` | `Result` | 显式同步请求在同一租约内完成发现、重验、修改、读回、保存;无变化不写入或保存 |
41
42
  | `plan_interface_descriptions` | 冻结的 `Topology::Plan` | 只读证据和能力检查;PAN-OS 当前拒绝自动改写计划,无隐式 commit |
42
43
  | `apply_interface_descriptions(plan, confirmed:)` | `Result`,包括变更后已完成的读回/保存步骤 | 未确认、错误设备/旧命令序列、能力不足及重验失败在写入前直接抛出;`stale_plan` 是 `DeviceError`。开始变更后的领域失败保留步骤 |
43
44
  | `SavedConfig#read/#parse/#export` | 原始字节、记录数组;export 返回目标路径或 stdout 模式的 nil | 纯本地,只读取规范地址文件;缺失、不安全文件、解析或写入错误直接抛出 |
@@ -48,7 +49,8 @@
48
49
  1. 命令响应完成:匹配本次响应的结束提示,或收到厂商明确完成行;不等于业务状态正确。
49
50
  2. 业务状态读回:目标描述与批准计划一致;不排除其他设备会话此后改写,也不等于已持久化。
50
51
  3. 持久化/服务端证据:本地文件完成 fsync/rename/目录 fsync,或设备明确报告保存完成。
51
- TFTP 目前只有设备报告,不能据此证明服务器文件、SHA-256 或本批版本归属;设备保存
52
+ 单设备 TFTP 入口只有设备报告;批量入口可通过本机服务器文件核验和归档补充
53
+ SHA-256 与本批版本证据。未经核验的设备报告不能证明服务器文件;设备保存
52
54
  完成行也不是断电测试。读取、核验、持久化任何阶段失败都不触发自动回放/回滚。
53
55
 
54
56
  ## 业务约束
@@ -75,13 +77,15 @@ TFTP 归档分别保留本地批次副本与服务器归档副本的提交位置
75
77
 
76
78
  TFTP 只确认设备报告的上传结果:策略去掉命令、应答和提示符回显后,检查明确的完成行;文件名或表示“即将上传”的进度文字不算成功。原始输出或终端渲染文本中的失败证据优先于成功文字。
77
79
 
80
+ 失败证据核对与完成核对共用精确回显过滤。静态应答从已完成步骤的 `Command#interactions` 读取,`Interaction#static_response` 对动态回调返回 nil,核对不会重新执行挑战回调。失败核对逐步读取原文、回车历史和终端画面,不累积全部画面副本;颜色样式只影响显示,不能拆开诊断词或借光标擦除隐藏已经收到的失败。命令诊断及拓扑保存确认同样保留这类失败历史。
81
+
78
82
  TFTP 内置策略的 `validate_options!` 是纯参数校验,执行于 H3C 源文件探测之前。整个探测、脚本、证据核对、回执和事件记录由原有 `with_operation(:tftp_backup)` 独占;其他线程、Fiber 及回调重入均不能插入设备命令。租约只保护当前会话,不能隔离其他设备连接或服务器上的同名文件。
79
83
 
80
84
  `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,不能据扩展名或字节数推断服务器内容。
81
85
 
82
86
  明确完成证据先生成最小回执,再执行路径提取、元数据钩子及事件记录。已完成步骤之后的清理错误同样保留回执;路径不可用时保留 nil,不把请求文件名冒充实际目标。`TftpCompletionError` 只含受控码、类型和回执,不带原始消息、输出或 cause。Fleet 仅信任库定义的精确错误类及匹配产物;第三方异常上的 receipt 字段和错误子类不能供应完成事实。已上传但收尾失败使用 reported_with_error/partial 分类。
83
87
 
84
- `Topology` 读取邻居和旧描述,冻结计划,要求显式确认,并在写入前重验全部证据和重建的命令。`ImmediateStrategy` 将即时生效设备的修改/退出视图、运行配置读回、保存拆开:读回未确认或有未识别接口块时停止,不发送保存。重验、下发、读回、保存及阶段间隙都持有原有会话租约;只有所属线程和 Fiber 能顺序执行脚本,其他调用、关闭请求和脚本回调重入均返回 `SessionBusy`。租约不隔离其他设备会话或管理员。
88
+ `Topology` 默认通过显式 `sync_interface_descriptions` 调用在同一连接与租约内完成发现到保存,调用方无须传递计划。独立规划/应用流程仍冻结计划并要求显式确认;两条路径共用写入前证据重验和命令核对。`ImmediateStrategy` 将即时生效设备的修改/退出视图、运行配置读回、保存拆开:读回未确认或有未识别接口块时停止,不发送保存。重验、下发、读回、保存及阶段间隙都持有原有会话租约;只有所属线程和 Fiber 能顺序执行脚本,其他调用、关闭请求和脚本回调重入均返回 `SessionBusy`。租约不隔离其他设备会话或管理员。
85
89
 
86
90
  ```mermaid
87
91
  flowchart LR
@@ -119,11 +123,13 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
119
123
 
120
124
  `Settings` 保留动态秘密来源,`snapshot(mode:)` 只复制允许公开的非敏感字段,并在副作用前复用 `Configuration`、`Planner` 和 `Client.options` 校验。Fleet 的一次规划/执行只使用该份冻结策略;CLI 通过 `for_run` 将规划与执行绑定到同一策略。每台设备的凭据读取与策略解析分开,自定义凭据解析器给出的显式连接选项仍保留原优先级。带 `plan:` 的执行不再查询清单;以后新建的批次可以读取新策略和新的 API 凭据。
121
125
 
126
+ `BackupRun` 在创建批次目录和读取凭据前固定并验证策略。已固定的 `Settings#with_overrides` 只复制非敏感快照上的覆盖值,不重新读取环境中的策略字段;后续 `snapshot(mode:)` 继续按当前业务模式校验,离线导出不会隐式转成网络备份校验。原凭据来源仍保持动态。
127
+
122
128
  `Client#devices` 每次持有独立 `InventoryBudget`,认证、分页、QueryRequired 查询共同累计正文字节和去重前记录数,并共享单调时钟 deadline。默认 HTTP 使用 `read_body`,先检查实际接收字节再追加,不信任 Content-Length。每阶段缩短原生连接/读/写超时;总期限还覆盖慢速响应头。期限观察线程只关闭本次拥有的 HTTP 连接,防止 chunked 的收尾读取拖延返回,调用结束即唤醒并 join。默认 HTTP 不做隐式 GET 重试;这一机制不进入设备命令路径。
123
129
 
124
130
  注入的 `requester` 接收 URI 和 request。它返回后才接受长度和期限检查,不在任意用户 Ruby 回调中注入异步异常。解析和集合操作完成后也检查期限,但这不构成任意 CPU 回调的抢占保证。预算失败仅报告错误码与安全类型,没有部分清单、响应正文或底层 cause。HTTPS 的标准证书检查不变;默认允许明文 HTTP,可通过显式策略禁止。
125
131
 
126
- `Settings#inventory_source` 选择 HTTP `Client` 或 PostgreSQL `DatabaseClient`,两者只向 Fleet 提供 `devices`,不改变设备映射、计划和执行接口。数据库 SQL 与标量参数由用户配置,不内置业务表名或筛选;结果用列别名适配 `Client::FIELDS`,只保留这份现有字段白名单。连接参数由 `DatabaseClient::CONNECTION_ENV` 集中映射到 Netdisco 的 `NETDISCO_DB_*` 环境变量,全部排除在纯策略快照外;每次新建清单客户端时读取最新值。
132
+ `Settings#inventory_source` 选择 HTTP `Client` 或 PostgreSQL `DatabaseClient`,两者只向 Fleet 提供 `devices`,不改变设备映射、计划和执行接口。数据库 SQL 与标量参数由用户配置,不内置业务表名或筛选;结果用列别名适配 `Client::FIELDS`,只保留这份现有字段白名单。数据库身份使用 `ND_DB_HOST/NAME/USER/PASS`,每次新建客户端时读取;端口、TLS 和超时来自 YAML 并进入不可变策略快照。
127
133
 
128
134
  `DatabaseClient` 每次调用独占 PostgreSQL 连接,在只读事务中用扩展查询协议声明游标,原生解析器拒绝多语句和非查询输入。FETCH 大小及次数复用清单预算,单行模式使 libpq 不缓存整页;每行在追加前计数,额外列也消耗字节预算。驱动必须先解码单行,所以单个超大字段仍可超过 Ruby 预算的瞬时内存。总 deadline 覆盖连接和所有语句,每次 FETCH 前缩短 statement_timeout;正常完成回滚只读事务,异常关闭连接,任何失败均不交付部分清单。NOTICE、原始数据库异常及 cause 不进入日志或 CLI;稳定错误码区分连接失败、查询失败、无效清单和预算超限。SQL/参数可在 show-config 中查看,不能用于传递凭据。
129
135
 
@@ -177,7 +183,7 @@ Fleet 可把回执与本地普通文件的时间、稳定性、摘要及目录
177
183
 
178
184
  累计等于上限时当前脚本可正常结束,但下一条查询在发送前失败;超过上限的完整主响应先进入 steps,再返回 `ScriptOutputLimitExceeded`,后处理和下一命令不会继续。追加查询不进入主脚本的公开 steps,但计入预算并在错误中保留实际查询命令。吞掉查询预算异常的钩子不能让超额脚本报告成功。已执行命令不重放;确认的 TFTP 完成行仍可构造带收尾错误的回执。
179
185
 
180
- 累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS 或整批内存硬上限。合成基准不足以确定适合所有设备的默认阈值,因此默认不限制累计值。设置通过 YAML `ssh.max_script_output_bytes`、`NC_MAX_SCRIPT_OUTPUT_BYTES` 和 CLI 同名选项进入批次策略快照,优先级为 CLI > ENV > YAML;未配置时不传递该可选键,显式凭据 resolver 可覆盖连接参数。
186
+ 累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS 或整批内存硬上限。合成基准不足以确定适合所有设备的默认阈值,因此默认不限制累计值。设置通过 YAML `ssh.max_script_output_bytes` 和 CLI 同名选项进入批次策略快照,优先级为 CLI > YAML;未配置时不传递该可选键,显式凭据 resolver 可覆盖连接参数。
181
187
 
182
188
  Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限声明入口,厂商策略负责差异行为。公开方法、厂商钩子、结果对象和 CLI JSON 字段以当前文档为准;内部不做运行时方法注入,也没有工作流 DSL。
183
189
 
@@ -284,7 +290,7 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
284
290
  | Juniper、Arista、F5、Fortinet | 每次增加一个设备系列的登录、配置采集与本地备份,再按需扩展 TFTP / 拓扑 | 指定型号和固件的脱敏会话样本、分页和失败回显、现场验证条件;不能仅靠厂商名称声明支持 |
285
291
  | TextFSM 模板 | 按业务需要增加接口、路由等具体命令及字段 | 样本来源和许可、正常 / 空表 / 部分输出测试、模板随 gem 安装的证据;`parse_config` 仍是指定模板提取,不是完整配置模型 |
286
292
  | 持久调度与重试 | 先由调用应用在持久队列中运行单设备任务 | 设备互斥、任务标识、幂等策略、重试上限及取消语义;连接器不自动重放可能已执行的配置命令 |
287
- | 英文文档 | 优先增加英文 README,提供安装、主要能力、限制和验证入口 | 确定与中文文档同步更新的维护方式;CLI 消息国际化另行评估,避免静默改变现有输出 |
293
+ | 文档翻译 | README 与设备、Netdisco 指南已提供双语;架构、验证和发布参考仍使用中文 | 按 CONTRIBUTING 同步双语使用文档;参考文档翻译与 CLI 消息国际化另行评估 |
288
294
 
289
295
  几千台设备的调度应由应用限制总并发及同设备并发,并持久保存逐台结果。
290
296
  只读采集可在重新建连后按应用策略重试;配置下发中断后应先回读设备状态,
@@ -350,9 +356,9 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
350
356
 
351
357
  原始 `Neighbor` 字段和计划证据完整保留发现值。`Topology::InterfaceName.key` 用于匹配本机接口别名与运行配置名称;`Topology::InterfaceName.configuration` 保留配置命令的接口展开规则。`Topology::InterfaceName.short` 只决定描述里对端接口的显示形式,绝不替换本机下发命令中的接口名。
352
358
 
353
- `Topology::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商默认计划共用的纯函数,生成 `To <名称> <接口>`。已知接口族会缩写,并保留原有大小写:Ethernet/Eth 为 Eth,GigabitEthernet/GE/Gi 为 Gi,Ten-GigabitEthernet/TenGigabitEthernet/XGE/Te 为 Te,FastEthernet/Fa 为 Fa,port-channel/Po 为 Po。端口编号与子接口后缀保留。`ge-0/0/1`、`100GE1/0/1`、`Port 12` 等未知形式默认不变;这是有限映射,不声称识别全部厂商命名。
359
+ `Topology::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商同步与计划共用的纯函数,生成 `To_<名称>_<接口简称>`。接口族的全称、简称和大小写统一,端口、拆分通道与子接口后缀保持不变;H3C 二层和三层聚合身份不合并。完整规则及厂商命名依据见[设备指南](usage.zh-CN.md#邻居发现与接口描述)。
354
360
 
355
- 默认建议因此从 `To peer Ethernet1/2` 变为 `To peer Eth1/2`。`abbreviate: false` 保留原拼写,`lowercase: true` 仅将对端接口改为小写。自定义代码块拿到原始邻居,可生成完整描述。输出仍必须经过 80 字节及字符校验、证据复核、确认和回读。
361
+ `abbreviate: false` 保留接口原拼写,`lowercase: true` 仅将对端接口改为小写。自定义代码块拿到原始邻居,可生成完整描述。输出仍须经过 80 字节及字符校验、证据复核和回读;同步调用表达写入意图,独立计划应用仍要求确认。
356
362
 
357
363
  `Topology::InterfaceDescription.commands(interface:, description:, leave: "exit")` 为 IOS/NX-OS 和山石分别生成 `interface`、`description`、退出命令;H3C 使用 `leave: "quit"`。进入、退出全局配置视图和保存完成证据由厂商负责。各阶段命令分别由策略声明,PAN-OS 的命令构造/超时辅助方法不会启用未经验证的候选提交。命令构造器本身不连接设备,也不能绕开已审核的拓扑计划。
358
364
 
@@ -396,7 +402,7 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
396
402
 
397
403
  注入的 logger 由调用方持有;连接器不修改级别、formatter、progname,也不关闭它。每次写入同时检查配置与 logger 当前级别。消息对象支持 to_s/inspect 和安全 to_h,应用可自行输出 JSON。自有文本日志使用毫秒时间及逐行事件;raw 文件只写经过敏感保护的字节。上下文退出前先收尾渲染与脱敏缓冲,避免上一命令尾部被标成下一条命令。
398
404
 
399
- 人类进度显示由 Progress 单独管理:终端 write/flush 的 I/O 故障会停用后续显示,并只保留固定词表内的错误类型,不携带原始异常。心跳照常回收,设备任务、events.jsonl 与报告持久化继续进行;BackupRun 将显示故障合并为一次批次 callback_errors。若首次故障发生在报告保存后的最终显示阶段,仅补写一次报告诊断,最终 JSON 与退出码采用更新后的策略结果;已有报告错误保持优先,不重放设备备份或事件。
405
+ 人类进度显示由 Progress 单独管理:终端 write/flush 的普通故障会停用后续显示,并只保留固定词表内的错误类型,不携带原始异常。心跳照常回收,设备任务、events.jsonl 与报告持久化继续进行;BackupRun 将显示故障合并为一次批次 callback_errors。若首次故障发生在报告保存后的最终显示阶段,仅补写一次报告诊断,最终 JSON 与退出码采用更新后的策略结果;已有报告错误保持优先,不重放设备备份或事件。
400
406
 
401
407
  `log_file` 使用 NOFOLLOW/NONBLOCK 打开,再对同一 FD 检查普通文件类型,随后才修改权限或写入。FIFO、设备文件和目录不能用作日志文件;应用拥有的输出流通过 `logger` 接入。日志打开先于登录,不能依赖登录超时为文件打开提供期限。
402
408
 
@@ -417,3 +423,13 @@ end
417
423
  ```
418
424
 
419
425
  这里的 `DeviceCredential` 是应用自己的模型。涉及 Rails 的工厂、回调或自定义连接器方法也遵循同一规则;Rails 的 Executor 负责应用代码的执行边界及数据库连接回收。不要在工作线程中使用 Reloader 包住整批任务。需要每台设备独立重试和调度时,可在应用的 Active Job 中调用单设备 API。具体规则见 [Rails 线程与代码执行指南](https://guides.rubyonrails.org/threading_and_code_execution.html)。
426
+
427
+ ## 公共并发、配置批次与清单缓存
428
+
429
+ `ExecutionPool` 只持有线程、启动闸门和完成事件,按输入顺序交付块结果。普通设备失败如何转成结果由业务适配负责;创建并登记线程时延迟异步中断,登记完成后恢复,启动失败不执行任何设备块。工作线程捕获异常并由调用线程同步传播,避免宿主全局线程策略影响无关线程。原 `Netdisco::Worker` 保留备份计时、结果槽位和通知适配,不再自行管理线程。
430
+
431
+ `DeviceSession` 持有单台任务连接器,分别交付业务值与普通故障,收尾不覆盖先前错误或中断。`Netdisco::Operations` 为配置脚本与接口描述同步、规划/应用提供全批预检、固定策略、动态凭据和安全摘要;它保留领域 `Result`、计划和完成步骤,使用独立 `OperationBatch`,不借用备份路径或上传状态。应用审批计划必须走原拓扑重验、变更、回读、保存流程;有限并发不代表跨设备事务或自动重试安全。
432
+
433
+ `Settings#client` 为原始清单适配器包裹 `InventoryCache`,默认三天有效。缓存仅保存完整白名单 rows,Rules 和重复地址判定每次重做。身份、SQL 及绑定参数只参与摘要分区,不写入文件;凭据缺失仍在缓存读取前拒绝。过期刷新在有界、按身份的跨进程文件锁内完成,成功后原子替换;失败使用原时间的已验证历史,成功空清单替换旧值。最新查询成功与本地缓存持久化成功是两个事实,不能因写盘失败回退旧数据或重复查询。强刷跳过有效期,不跳过身份、限额、文件校验和失败回退边界。
434
+
435
+ 后台心跳和 HTTP 超时观察使用内部 `BackgroundTask` 作用域,前台保留主业务值和原始故障;观察线程的异常同步交付所有者,不受宿主全局线程异常策略影响。创建至登记期间延后中断,停止和等待期间保证线程回收;心跳普通故障转换为显示诊断,正常 HTTP 请求的未知观察故障仍传播。
data/docs/netdisco.md ADDED
@@ -0,0 +1,229 @@
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
+ `NC_CONCURRENCY` sets the default worker count for backups, configuration scripts and interface changes (1–50, default 4). An explicit API argument or CLI option takes precedence over the environment, then YAML.
22
+
23
+ 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):
24
+
25
+ ```yaml
26
+ netdisco:
27
+ url: https://netdisco.example/netdisco
28
+ allow_insecure_http: false
29
+ backup:
30
+ directory: /var/backups/network
31
+ concurrency: 4
32
+ inventory:
33
+ include_vendors: [h3c, huawei, cisco_ios]
34
+ ssh:
35
+ host_key_policy: strict
36
+ max_script_output_bytes: null
37
+ tftp:
38
+ server: 192.0.2.30
39
+ vrfs:
40
+ cisco_nxos: management
41
+ ```
42
+
43
+ ## Inventory cache
44
+
45
+ HTTP and PostgreSQL inventory use a local cache by default, valid for three days (259,200 seconds). The default directory is `.cache/net-connector/inventory`, relative to the current working directory. Configure it through YAML:
46
+
47
+ ```yaml
48
+ netdisco:
49
+ cache_directory: /var/cache/network/inventory
50
+ cache_ttl: 259200
51
+ cache_enabled: true
52
+ ```
53
+
54
+ `ND_CACHE_TTL=86400` sets a one-day lifetime. Explicit settings overrides take precedence over the environment, then YAML, then the three-day default. TTL must be finite and positive; each batch fixes its policy snapshot before scheduling.
55
+
56
+ The directory must belong to the current user with mode `0700`; cache and lock files use `0600`. Source, account, database and SQL parameters partition the cache. Only inventory fields are stored; passwords, API keys, SQL and extra response fields are excluded. Each read applies current filtering and vendor rules. A successful empty inventory replaces history.
57
+
58
+ Expired inventory refreshes automatically. A remote inventory failure falls back to validated history without changing its original timestamp. Configuration errors, missing credentials, interruption and unsafe files remain errors. Failed cache writes preserve newly fetched inventory. `fleet.inventory_status` reports `:fresh_cache`, `:refreshed`, `:stale_cache` or `:uncached`, the original `fetched_at`, and safe failure metadata; the CLI warns on history fallback or cache write failure.
59
+
60
+ Use `net-backup --refresh-inventory`, `fleet.devices(force_refresh: true)`, or `fleet.plan_backup(force_refresh: true)` to bypass freshness. TFTP planning accepts the same keyword. A forced refresh also falls back to valid history if the remote request fails. `cache_enabled: false` disables caching. Injected clients stay uncached unless explicitly wrapped in `Netdisco::InventoryCache`; shared wrapper status describes the latest call, so synchronize concurrent access to the same instance. Cache locks bound waiting and coalesce ordinary expired-cache refreshes across processes; independently requested forced refreshes still query the source.
61
+
62
+ ## CLI planning and execution
63
+
64
+ ```sh
65
+ net-backup --config config.yml --show-config
66
+ net-backup --config config.yml --plan
67
+ net-backup --config config.yml --host 192.0.2.7 --success-policy selected
68
+ net-backup --config config.yml --tftp --plan
69
+ net-backup --config config.yml --tftp --all
70
+ net-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
71
+ ```
72
+
73
+ 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.
74
+
75
+ `--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.
76
+
77
+ Plans and results are JSON. Exit codes:
78
+
79
+ | Code | Meaning |
80
+ | --- | --- |
81
+ | `0` | The requested success policy passed |
82
+ | `1` | Empty or incomplete batch under that policy |
83
+ | `2` | Configuration, inventory, or other CLI error |
84
+
85
+ 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.
86
+
87
+ `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.
88
+
89
+ ## Fleet API and reports
90
+
91
+ ```ruby
92
+ require "net/connector/netdisco"
93
+
94
+ fleet = Net::Connector::Netdisco::Fleet.new
95
+ plan = fleet.plan_backup
96
+ puts plan.selected.map(&:host)
97
+ report = fleet.backup_all(plan: plan, success_policy: :selected, on_change: ->(outcome) {
98
+ puts "#{outcome.device.host}: #{outcome.backup.change}"
99
+ })
100
+ puts report.counts
101
+ puts report.report_location
102
+ exit 1 unless report.policy_success?
103
+ ```
104
+
105
+ `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.
106
+
107
+ 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.
108
+
109
+ 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.
110
+
111
+ `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.
112
+
113
+ ## Configuration batches and interface changes
114
+
115
+ ```ruby
116
+ fleet = Net::Connector::Netdisco::Fleet.new
117
+ batch = fleet.execute_scripts(
118
+ scripts: { "192.0.2.1" => ["system-view", "interface GigabitEthernet1/0/1", "description uplink", "quit", "quit"] },
119
+ confirmed: true,
120
+ concurrency: 4
121
+ )
122
+ puts JSON.pretty_generate(batch.to_h)
123
+ puts "Total=#{batch.summary[:total]} succeeded=#{batch.summary[:succeeded]} failed=#{batch.summary[:failed]}"
124
+
125
+ batch = fleet.sync_interface_descriptions(hosts: ["192.0.2.1"])
126
+ puts "Total=#{batch.summary[:total]} succeeded=#{batch.summary[:succeeded]} failed=#{batch.summary[:failed]}"
127
+ ```
128
+
129
+ Scripts accept `Script`, command arrays or multiline text. The full batch is parsed and target addresses are normalized before credentials or device connections; duplicate, unknown, filtered and unsupported targets are rejected. Scripts contain the vendor's complete commands, including mode transitions and save commands where required. An empty batch reports `:no_devices` and is unsuccessful.
130
+
131
+ Interface application calls the existing topology operation, retaining evidence revalidation, configuration readback and persistence confirmation. It does not execute an approved plan as an ordinary script. Both write operations require `confirmed: true`, and never automatically retry commands. Each task owns and closes its connector. Ordinary failures remain per-device outcomes; `Interrupt` stops and joins all owned workers. Confirmed work is not rolled back across devices.
132
+
133
+ `OperationBatch` is separate from backup reports. `outcomes` retain the original `Result` or plan, completed steps and the first failure; `:partial` preserves work followed by an error, and missing credentials prevent success. `plans` selects only fully successful planning results. `to_h` includes safe diagnostics and counts, excluding commands, outputs, credentials and evidence. `on_start` and `on_result` run on workers and must be thread safe; ordinary callback faults are recorded without repeating device operations. Logging uses the configured `ssh.log_directory`.
134
+
135
+ `sync_interface_descriptions` is the direct update entry: one request and one connection per device, from discovery through readback and save. Matching descriptions cause no writes or saves; a no-op does not confirm an earlier save. The default format is `To_<peer name>_<interface abbreviation>`; formatting options and blocks remain backend choices. Separate preview/approval workflows remain available through `plan_interface_descriptions` and `apply_interface_descriptions(plans: planned.plans, confirmed: true)`.
136
+
137
+ Each device publishes one immutable outcome after execution and connection cleanup. Ordinary credential, connection, execution and cleanup failures affect that device while active and queued siblings continue. Once scheduling completes, `summary` and `to_h[:summary]` report `total`, `succeeded` and `failed`, with total = succeeded + failed. Total counts the devices requested and scheduled in this batch; partial completion and missing credentials count as failures and also appear in `partial` and `missing_credentials`. `callback_errors` counts notification faults separately without changing device outcomes. Empty batches report zero counts and `no_devices`. The library returns the report; callers choose terminal output or JSON export. Whole-batch approval validation and interruption retain their existing boundaries, and completed device changes cannot be rolled back.
138
+
139
+ For other independent tasks, load `net/connector/execution_pool` and use `Net::Connector::ExecutionPool.new(concurrency: 4).map(items) { |item| ... }`. Results preserve input order, all threads must start before any block runs, and uncaught exceptions stop the pool and propagate to its caller. Business code decides which ordinary errors become per-task results.
140
+
141
+ ## PostgreSQL inventory
142
+
143
+ 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):
144
+
145
+ ```yaml
146
+ netdisco:
147
+ source: postgres
148
+ db_port: 5432
149
+ db_sslmode: verify-full
150
+ db_sslrootcert: /etc/net-connector/database-ca.crt
151
+ db_connect_timeout: 10
152
+ query: |
153
+ SELECT host(ip) AS ip, name, dns, vendor, os, model, os_ver, serial
154
+ FROM device
155
+ WHERE vendor = $1
156
+ ORDER BY ip
157
+ query_params: [H3C]
158
+ ```
159
+
160
+ 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.
161
+
162
+ ```sh
163
+ export ND_DB_HOST=database.example
164
+ export ND_DB_NAME=netdisco
165
+ export ND_DB_USER=inventory-reader
166
+ export ND_DB_PASS='replace-me'
167
+ net-backup --config examples/inventory_sql.yml --plan
168
+ ```
169
+
170
+ 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.
171
+
172
+ 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.
173
+
174
+ ## Inventory budgets and policy snapshots
175
+
176
+ 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.
177
+
178
+ 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.
179
+
180
+ `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.
181
+
182
+ 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.
183
+
184
+ ## TFTP history and verification
185
+
186
+ 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. A mismatched receipt path is retained as device evidence, but cannot be verified or archived without a baseline for that locked target.
187
+
188
+ Verification holds a target lock across the pre-upload file-state/time baseline, upload and fingerprint check, including batches without history preservation. An unchanged old file cannot qualify through a future timestamp. The server directory must be writable for lock files and the filesystem-time probe. Result callbacks receive the verified outcome, and file evidence never substitutes for a failed device operation or cleanup.
189
+
190
+ 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.
191
+
192
+ 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.
193
+
194
+ Reports distinguish `verification`, `server_file_verified`, `local_file`, `server_archive_file`, `bytes`, and `sha256`. TFTP has no configuration `change` notification.
195
+
196
+ ## Batch examples and deployment
197
+
198
+ ```sh
199
+ bundle exec ruby examples/backup.rb --json
200
+ bundle exec ruby examples/backup.rb --sample 3 --concurrency 10
201
+ bundle exec ruby examples/backup_tftp.rb --tftp-root /srv/tftp --success-policy verified
202
+ ```
203
+
204
+ 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.
205
+
206
+ 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.
207
+
208
+ 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.
209
+
210
+ 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).
211
+
212
+ ## Environment reference
213
+
214
+ | Variables | Purpose / default |
215
+ | --- | --- |
216
+ | `ND_URL` | HTTP inventory base URL |
217
+ | `ND_CACHE_TTL` | Inventory cache lifetime in seconds; finite positive number, default 259200 (three days) |
218
+ | `ND_USERNAME`, `ND_PASSWORD` | HTTP login without API key |
219
+ | `ND_API_KEY` | Existing API key |
220
+ | `ND_DB_HOST`, `ND_DB_NAME`, `ND_DB_USER`, `ND_DB_PASS` | Required database connection fields |
221
+ | `NC_CONFIG` | Non-sensitive YAML file |
222
+ | `NC_CONCURRENCY` | Default batch worker count; integer 1–50, default 4 |
223
+ | `NC_DEVICE_USERNAME`, `NC_DEVICE_PASSWORD` | Default device credentials |
224
+ | `NC_<VENDOR>_USERNAME`, `NC_<VENDOR>_PASSWORD` | Vendor credentials, e.g. `CISCO_IOS` |
225
+ | `NC_ENABLE_PASSWORD`, `NC_<VENDOR>_ENABLE_PASSWORD` | Optional privilege credentials |
226
+
227
+ 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`.
228
+
229
+ 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).