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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0d4b8cc4601018ae7e2f2838e59c57720cff0ca3bfda47cbbfb423c67f4620b
4
- data.tar.gz: 6b8527cee1080118ff83574a80a9d71cedb01d47427b7e5c8d35621e2f206187
3
+ metadata.gz: b6ba09a377ed95488c9ecd62788732557564339638dec3ae596bbce0226369fa
4
+ data.tar.gz: '0876aa9703bdbb2e46db010fcf263a2e1f1e7549e0f63d1bcf60f8529193e3ed'
5
5
  SHA512:
6
- metadata.gz: c8070ed62134a1eac35ed652183fc608d276e12a35aef4bfefda3d3cb39c31d354386fe8ebdb49dec2cf9721f41caca047e671927b8f456e97de72ac0acfc897
7
- data.tar.gz: b04fd3c47d8d09c68961db1c3eb320bca4b76c94f501b9fe107410b13169b747413cbb2c8fdc4d60dbe88f59f963319c4f0313bcb95571c7bb915eb76870e64f
6
+ metadata.gz: 6636cacf21a401c68a6b6dd596e3c4ab11eb153879e6e6e9f74339cf72bea7f8e71e6937745300fc9e513c7b4521b1eecdb249a1cf3abe7b6c3a15fc253b34a4
7
+ data.tar.gz: f97ff17b82418d78a1c959f420d4c17a53efc7ce4101694719e394e7d50d42f1ae2e86167a3b470850e598eba9becc42cb6364133fe2e6888eca426342534129
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.10.0 - 2026-10-11
4
+
5
+ - 新增 `sync_interface_descriptions`:一次调用、每台设备一个连接,完成邻居发现、描述修改、读回与保存;无变化时不写入或保存,后端仍支持定制格式和分阶段审批。
6
+ - 行为变更:默认接口描述改为 `To_<设备名>_<接口简称>`。H3C、Cisco、Hillstone 的聚合口及 10/25/40/100G 接口别名统一为简称,保留端口、拆分通道和子接口编号。
7
+ - 批量配置与备份共用有限并发调度,`NC_CONCURRENCY` 设置默认并发数;单台设备和回调的普通故障独立记录,报告提供清单总数、成功、失败及细分类别,中断回收本批资源,不自动重放命令。
8
+ - Netdisco 清单默认缓存三天,支持 `ND_CACHE_TTL` 和 YAML 调整、强制刷新、过期刷新失败时回退历史。缓存按来源和身份隔离,只保存白名单字段,缓存写入失败仍交付新清单。
9
+ - 修复设备连接、后台线程、PostgreSQL 连接和传输超时恢复的收尾边界:取消时释放已取得资源,后续清理故障不覆盖原始失败或已完成结果。
10
+ - TFTP 核验在目标锁内采集上传前基线;未变化的旧文件不能凭未来时间戳冒充本次上传。回调与报告使用同一核验结果,后续故障保留上传回执。
11
+ - 拓扑计划按规范接口判重,在连接或新增 I/O 前拒绝重复变更。全量清单规划减少采样副本和索引,保持设备选择顺序及 TFTP 目标仲裁。
12
+
13
+ ## 0.9.0 - 2026-10-11
14
+
15
+ - 破坏性变更:Netdisco 环境变量统一改用 `ND_`,net-connector 使用 `NC_`,仅保留清单地址、身份凭据及 `NC_CONFIG`;其他策略移到 YAML/CLI,数据库策略使用 `netdisco.db_*`,TFTP 根目录使用 `tftp.verification_root`,进度开关使用 `--no-progress`,不兼容旧环境变量。
16
+ - README 改为简洁英文入口并保留中文入口,设备操作和 Netdisco 批量备份拆为双语指南,随 gem 一起分发。
17
+ - 批次在创建目录和读取凭据前固定并校验策略,补充日志目录等覆盖时保留原策略快照,数据库 CA 路径包含 NUL 时提前拒绝,动态凭据仍可逐台轮换。
18
+ - 修复 TFTP 命令、文件名和 VRF 中的诊断词被误报为上传失败,以及终端颜色或重绘掩盖设备错误的问题,上传失败和拓扑保存失败均保留完整证据。
19
+ - SSH、主机密钥文件、会话和日志收尾保留原始故障或中断,一个资源关闭失败仍尝试释放其余资源,外层已处理的异常不再掩盖独立关闭故障。
20
+ - 修复 Ruby 3.2 值对象的 `with` 更新绕过校验和冻结的问题,设备、计划、文件回执与诊断对象更新后继续保护外部数据和敏感字段。
21
+ - 清单只在存在地址覆盖规则时再次解析 IP,重复地址检查减少临时对象,降低大清单的处理时间和内存分配。
22
+
3
23
  ## 0.8.0 - 2026-10-09
4
24
 
5
25
  - 破坏性变更:命令入口改为 `bin/net-backup`,gem 安装后使用 `net-backup`;移除 `TftpHistory`、`TftpArchive#path_for/#capture` 和 `ResultStore::Text` 兼容接口,调用方需更新路径与接口名称。
data/CONTRIBUTING.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  请先阅读 [README](README.md) 和[架构文档](docs/architecture.md),在源码检出目录中开发。问题和 PR 可使用中文或英文;涉及凭据泄露或可利用漏洞时,使用 [SECURITY.md](SECURITY.md) 中的私密渠道。
4
4
 
5
+ `README.md` 是简洁的英文入口,`README.zh-CN.md` 提供对应中文入口。设备操作与 Netdisco 指南分别放在 `docs/usage*.md`、`docs/netdisco*.md`;修改用法时同步两种语言的契约与示例。详细参考和源码注释继续使用中文。新增公开文档须同步 gem 文件清单与打包校验,根目录文件还须进入无 Git 场景的源码扫描清单。
6
+
5
7
  ## 本地检查
6
8
 
7
9
  需要 Ruby 3.2 及以上版本、POSIX 环境和 Git。安装依赖后运行完整预检:
@@ -30,6 +32,15 @@ bundle exec rake lint
30
32
  - 使用虚构凭据和 `192.0.2.0/24` 等文档地址。不要提交设备配置、备份、私有地址或日志;扫描规则同样适用于测试和示例。
31
33
  - 行为或公开契约变更写入 `CHANGELOG.md` 的“未发布”(兼容 `Unreleased`),同步修改相应文档。保留与本次任务无关的工作区改动。
32
34
 
35
+ ### 注释约定
36
+
37
+ - 库、脚本、测试、工作流和配置示例的解释性注释统一使用中文;标识符、协议原文、配置键及第三方 API 名称保留原样。双语文档中的示例说明随文档语言编写。
38
+ - Ruby 使用独立的 `# ` 行,缩进与所说明的代码一致。模块、业务类和加载入口说明职责;公开业务操作及扩展点说明副作用、必要前提和失败边界。简单访问器、命名空间和显而易见的委托不逐项复述。
39
+ - 复杂流程在相关代码之前说明原因:资源由谁持有、锁与截止时间覆盖什么、何时固定策略或读取凭据、失败后哪些结果仍有效、为何不能自动重试。避免只把方法名翻译一遍,也不要复制整段架构文档。
40
+ - 注释以当前实现和测试为依据,区分设备回显、服务器核验、文件提交和持久化;未实现或未经现场验证的能力必须写明边界。改动契约时同步检查调用者、厂商策略、测试和配置示例中的说明。
41
+ - shebang、`frozen_string_literal`、编码声明、RuboCop 指令,以及注释形式的配置样例属于机器可读内容,保持语法有效;RuboCop 指令后的豁免理由仍使用中文。不能把字符串、设备输出、模板规则或测试夹具当作源码注释翻译。
42
+ - 仅修改注释时核对 Ruby 可执行 token 和 YAML/环境示例的有效配置保持一致,并运行 lint 与打包校验;不以注释行数或覆盖率替代语义审阅。
43
+
33
44
  ### Ruby 范式与语法边界
34
45
 
35
46
  - 语法保持 Ruby 3.2 兼容:双引号、两空格缩进、冻结字符串;不强制 80 列或统一尾随逗号,不以 10 行/3 参数为拆分目标。
@@ -40,6 +51,7 @@ bundle exec rake lint
40
51
  - 外部输入先校验再转换,不用 `to_i`、`Array()` 等掩盖非法输入。保留缺失、显式 nil、false 的区别及必要哨兵;不扩大配置来源或改变优先级。
41
52
  - 模式匹配只用于稳定结构,并明确未知结构行为;不批量替换分支。Guard clause 保持返回值及预检顺序。完整透传可用匿名块参数,调整参数时显式传递,保留 `super` 的继承语义。
42
53
  - Data 用于稳定值对象,Hash 用于配置和外部协议;不为缩短参数列表增加对象。freeze 不会递归冻结,复制规则按对象所有权与现有契约决定。
54
+ - Data 的自定义初始化涉及校验、复制冻结或诊断白名单时,`with` 更新也须执行同一构造边界;Ruby 3.2 的原生 `Data#with` 不调用自定义初始化,回归必须在最低 Ruby 版本验证。
43
55
  - 普通失败通过已有错误边界归一化;保留 `cause: nil` 和脱敏先于截断的顺序。资源清理处的 `rescue Exception` 是有理由的例外,清理后保留原中断,Worker 可抑制次级清理异常;不能转换成业务成功。
44
56
  - 不自动重放设备命令;只有现有连接恢复允许重试。路径锁在会话锁外,完成步骤和持久化回执不能因后续失败丢失。ensure 中不能新增覆盖原异常或返回值的控制流。
45
57
 
data/README.md CHANGED
@@ -1,463 +1,64 @@
1
1
  # net-connector
2
2
 
3
- `net-connector` 通过 SSH 或 Telnet 操作网络设备的命令行。它可以采集运行配置、保存私有备份、执行命令脚本、回答设备提示、记录脱敏会话日志,并在失败时返回结构化错误和已完成的步骤。
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
4
 
5
- 代码按职责组织:`engine/` 管理会话、传输、脚本、结果和日志;`device/` 集中设备入口、档案、配置采集/保存、备份和拓扑能力;`vendor/<厂商>/` 保存厂商差异;`storage/` 负责私有文件、路径锁和离线配置;`textfsm.rb` 提供唯一的 TextFSM 适配入口;`netdisco/` 负责清单和批量编排。每项设备能力的公开方法与实现放在一起,由 `Base` 组合,设计说明见[架构文档](docs/architecture.md)。用 `require "net/connector"` 加载设备 API,用 `require "net/connector/netdisco"` 加载 Netdisco 集成;厂商规则和 TextFSM 依赖按需加载。
5
+ Ruby sessions for network device CLIs over SSH or Telnet. Collect configurations, save private backups, run interactive scripts, parse output with TextFSM, and back up fleets from Netdisco inventories.
6
6
 
7
- 支持 Ruby 3.2 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。主要依赖为 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.5.0 及以上和 [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.0 及以上。
7
+ Requires Ruby 3.2+ and a POSIX system. SSH uses local OpenSSH; Telnet requires a local `telnet` executable and explicit configuration.
8
8
 
9
- 脱敏直接复用 expect-pty 从 0.5.0 起公开的 `Expect::Redactor` 接口;连接器只管理秘密作用域和配置输出的隐私策略。
10
-
11
- ## 安装
9
+ ## Install
12
10
 
13
11
  ```sh
14
12
  gem install net-connector
15
13
  ```
16
14
 
17
- ```ruby
18
- require "net/connector"
19
- ```
20
-
21
- 运行时依赖只声明所需最低版本,不设置缺少兼容性依据的上限;宿主应用通过自己的 Gemfile/锁文件选择版本。正式发布依赖的安装验证使用 JSON 2。RubyGems 上 textfsm 0.2.0 仍约束 json ~> 2.0;JSON 3 使用固定 TextFSM 源码和显式依赖声明补丁单独验证,不代表公开依赖已经支持 JSON 3。开发工具及兼容测试矩阵的版本约束不影响 gem 使用者。
22
-
23
- 示例脚本通过开发依赖 `dotenv` 自动读取项目根目录 `.env`(先执行 `bundle install`),已有进程环境变量优先;库和 CLI 本身不会隐式读取 `.env`。
24
-
25
- ## 支持的设备
26
-
27
- `device.supports?(:tftp_backup)` 等能力查询不会连接设备,只表示该连接器实现了对应操作,不代表设备权限或固件一定支持。完整能力矩阵和扩展示例见[厂商能力](docs/architecture.md#厂商能力)。
28
-
29
- | 厂商标识 | 设备系列 | 运行配置命令 | 保存配置命令 |
30
- | --- | --- | --- | --- |
31
- | `:h3c` | H3C Comware | `dis cur` | `save force` |
32
- | `:h3c_wireless` | H3C 无线控制器 | `dis cur` | `save force` |
33
- | `:cisco_ios` | Cisco IOS / IOS XE | `show running-config` | `copy running-config startup-config` |
34
- | `:cisco_nxos` | Cisco NX-OS | `show running-config` | `copy run start` |
35
- | `:radware` | Radware Alteon | `/cfg/dump` | `/cfg/save` |
36
- | `:palo_alto` | Palo Alto PAN-OS | 候选视图中的 set 格式导出 | 不支持 |
37
- | `:huawei` | 华为 | `dis cur` | `save force` |
38
- | `:hillstone` | 山石 StoneOS | `show configuration running` | `save all` |
39
-
40
- 厂商标识仅使用上表的规范名称。厂商连接器位于 `Net::Connector` 下,例如 `Net::Connector::H3cWireless::Connector`。
41
-
42
- ## 登录与本地备份
43
-
44
- ```ruby
45
- Net::Connector.open(:cisco_ios,
46
- host: "192.0.2.10", username: "admin", password: ENV.fetch("DEVICE_PASSWORD"),
47
- known_hosts: "/etc/net-connector/known_hosts", host_key_policy: :strict,
48
- log_file: "/var/log/net-connector/router.log") do |device|
49
- backup = device.backup(path: "/var/backups/router-running.cfg")
50
- puts "#{backup.bytes} 字节,SHA-256 #{backup.sha256}"
51
- end
52
- ```
53
-
54
- `open` 会在代码块结束或出错时关闭会话。`backup` 在采集前取得目标路径锁,持有到比较、保存和生成原有 `Backup` 元数据对象完成;采集失败不会覆盖旧备份。同路径争用默认立即抛出 `BackupBusy`(`code: :backup_busy`),需要有限等待可传 `lock_timeout: 2`,单位为秒。直接调用 `backup`;不要在已有 `with_operation` 会话租约内调用,否则会因锁顺序返回 `SessionBusy`。
55
-
56
- 写入使用 `0600` 临时文件、文件同步、原子替换及父目录同步。替换后同步或收尾失败会抛出 `BackupPersistenceError`,`error.backup` 保留已写文件的元数据,`error.receipt` 区分 `:committed`(持久性未确认)和 `:durable`(同步已完成)。目录同步不受支持对应 `:backup_durability_unsupported`,不会静默报告持久化成功,也不会自动重做设备采集。
57
-
58
- 调用方应创建并保护备份目录,运行配置可能含有设备凭据。同目录下的 `.net-connector-<摘要>.lock` 是私有的长期锁文件,正常释放不删除它。所有写入者须使用本库的锁协议;`NOFOLLOW` 不保护被外部替换的祖先目录。锁名保守合并大小写及 Unicode 等价写法,实际备份文件名不变;在区分大小写的文件系统上,这些名称也会串行。
59
-
60
- ## 设备发起的 TFTP 备份
61
-
62
- `tftp_backup` 让设备把原生配置直接上传到 TFTP 服务器,命令和提示交互由厂商策略提供。H3C 和华为从设备文件读取源配置,例如:
63
-
64
- ```ruby
65
- Net::Connector.open(:huawei, host: "192.0.2.20", username: ENV.fetch("DEVICE_USERNAME"),
66
- password: ENV.fetch("DEVICE_PASSWORD")) do |device|
67
- transfer = device.tftp_backup(host: "192.0.2.30", source_file: "flash:/startup.cfg")
68
- puts "已上传 #{transfer.path} 到 #{transfer.server}"
69
- end
70
- ```
71
-
72
- 示例会下发 `tftp 192.0.2.30 put flash:/startup.cfg`;可传入 `path: "site/switch.cfg"` 指定目标文件名。H3C 默认通过 `display startup` 查找保存配置;华为因型号差异需要显式指定 `source_file:`。Cisco IOS 使用交互式 `copy running-config tftp:`;Nexus 9000 默认使用 `vrf management`,可用 `vrf:` 覆盖。山石导出已保存的启动配置,Radware Alteon 生成 `.tgz` 并处理私钥及 `mansync` 提示,PAN-OS 使用固定的 `running-config.xml` 文件名。其他厂商默认使用 `<管理地址>.cfg`。
73
-
74
- 只有设备回显确认传输完成,`tftp_backup` 才返回不可变的 `TftpReceipt`。回执直接包含 `server`、实际目标 `path`、`completed_at`、`configuration_kind`、`source_file`、`format`、`requested_path`、`verification` 和 `server_sha256`。明确失败对应 `:transfer_failed`,缺少成功证据对应 `:transfer_unconfirmed`。本方法不读取服务器上的文件。TFTP 不加密配置数据,应限制在合适的管理网络中使用。
75
-
76
- 回执的当前验证等级为 `:device_reported`,服务器摘要为 `nil`。H3C 自动探测结果标为 `:startup`;H3C/华为显式文件标为 `:saved_file`,格式为 `:unknown`,不凭扩展名推断内容。山石未指定文件名时 `requested_path` 为 `nil`,`path` 保留设备生成名称。
77
-
78
- 策略先校验参数组合,再在同一次会话租约中完成源探测、上传、证据检查和收尾。上传已确认但日志或清理失败时抛出 `TftpCompletionError`,通过 `error.receipt` 保留完成事实。显式请求与实际路径不同返回 `:transfer_path_mismatch`;实际路径无法安全确认返回 `:transfer_path_unconfirmed`,此时回执的 `path` 为 `nil`。这些错误不会自动重传;Fleet 保留上传事实并标为 `reported_with_error`。
79
-
80
- 可运行[单设备示例](examples/device_tftp.rb)。它从环境变量读取 `DEVICE_VENDOR`、`DEVICE_HOST`、`DEVICE_USERNAME`、`DEVICE_PASSWORD` 和 `TFTP_HOST`;`TFTP_SOURCE_FILE`、`TFTP_PATH`、`TFTP_VRF` 分别指定源文件、目标文件和设备 VRF。
81
-
82
- 只需在内存中采集配置时,调用 `device.running_config`;它返回 `Result`,`result.value!` 返回清理后的文本,失败时抛出对应错误。
83
-
84
- 配置采集统一由 `device/running_config` 提供,厂商差异位于 `vendor/<厂商>/running_config`。
85
- 旧 `operations/running_config`、TFTP / 拓扑厂商转发路径及 `engine/base` 等设备转发入口已移除;
86
- 自定义扩展请按[加载入口与迁移表](docs/architecture.md#加载入口与厂商策略)使用当前路径和常量。
87
-
88
- 子类可覆盖 protected 的 `login_interactions`、`confirmation_interactions`,通过 `super` 取得档案中的交互数组再追加规则。原 `login_dialogues`、`confirmation_dialogues` 已改名,不保留别名;这两个钩子仍不属于应用层公开调用入口。
89
-
90
- 配置采集默认屏蔽日志和错误诊断中的配置正文,包括 debug/raw 日志和外部 logger;返回的配置、步骤输出和备份内容保持完整。调用方应按敏感数据保管这些业务结果。
91
-
92
- ## TextFSM 解析
93
-
94
- `parse_command` 执行一条命令,再按厂商和命令选择 TextFSM 模板。内置索引覆盖 Cisco IOS 的 `show ip interface brief`,以及拓扑发现使用的 CDP/LLDP 命令:
95
-
96
- ```ruby
97
- Net::Connector.open(:cisco_ios, host: "192.0.2.10", username: "admin",
98
- password: ENV.fetch("DEVICE_PASSWORD")) do |device|
99
- interfaces = device.parse_command("show ip interface brief")
100
- puts interfaces.first.fetch("INTERFACE")
101
- end
102
- ```
103
-
104
- `parse_config` 采集运行配置,要求显式指定模板。内置 Cisco IOS 模板只提取接口名称和描述,并非完整配置模型:
105
-
106
- ```ruby
107
- interfaces = device.parse_config(template: "cisco_ios_running_config_interfaces.textfsm")
108
- ```
109
-
110
- 两种方法都返回由模板字段名组成的哈希数组;匹配不到记录时返回 `[]`。模板缺失或无效会抛出 `ParsingError`,设备命令失败则保留原始连接器错误。可用 `template:` 指定外部模板,或用 `template_dir:` 指定含 `index` 的模板目录。每次解析使用独立解析器,批量任务之间不共享状态。已有本地备份也可离线解析:
111
-
112
- ```ruby
113
- saved = Net::Connector::Storage::SavedConfig.new(directory: "/var/backups")
114
- rows = saved.parse(host: "192.0.2.10", template: "/path/to/template.textfsm")
115
- ```
116
-
117
- 解析默认严格检查 UTF-8 字节,包括终端控制符处理前的原文和处理后的文本;非法字节会抛出 `ParsingError`(`code: :invalid_output_encoding`),不会转成替换文本继续生成记录或拓扑计划。库不猜测或自动转换其他编码,原始结果、备份和离线导出保持原字节。Netdisco 入口及纯文件导出不加载 TextFSM,实际解析时才加载。
118
-
119
- ## 邻居发现与接口描述
120
-
121
- `neighbors` 在 Cisco IOS/NX-OS 上查询 CDP,在 H3C、H3C 无线、山石和 PAN-OS 上查询 LLDP。记录包含 `local_interface`、`neighbor_name`、`neighbor_interface`、`chassis_id` 和 `protocol`。H3C 会按表头选择不同列顺序的模板;未知输出会抛出 `ParsingError`,不会误判为空表。`interface_descriptions` 从运行配置读取当前描述,Radware 则从配置转储读取端口名称。
122
-
123
- ```ruby
124
- Net::Connector.open(:h3c, host: "192.0.2.10", username: ENV.fetch("DEVICE_USERNAME"),
125
- password: ENV.fetch("DEVICE_PASSWORD")) do |device|
126
- plan = device.plan_interface_descriptions
127
- plan.changes.each { |change| puts "#{change.interface}: #{change.old_description.inspect} -> #{change.new_description.inspect}" }
128
- puts plan.commands.join("\n")
129
- # 核对将要下发的命令,并完成操作审批。
130
- result = device.apply_interface_descriptions(plan, confirmed: true)
131
- result.value!
132
- end
133
- ```
134
-
135
- 默认描述为 `To <邻居名称> <邻居接口简称>`。接口缩写默认开启并保留大小写:`ethernet1/1` 变为 `eth1/1`,`Ethernet1/1` 变为 `Eth1/1`,`GigabitEthernet1/0/1` 变为 `Gi1/0/1`;未知形式保持原样。原始邻居记录、计划证据和本机下发接口名不会被缩写。`abbreviate: false` 关闭缩写,`lowercase: true` 才会转为小写;也可传入代码块自行生成完整描述。公共纯函数是 `Net::Connector::InterfaceDescription.format`。
136
-
137
- 规划会拒绝歧义邻居、缺少对端身份、不安全文本和无法识别的输出。下发必须传 `confirmed: true`,操作会再次读取邻居和旧描述;证据变化在写入前抛出 `DeviceError`,其 `code` 为 `:stale_plan`。IOS/NX-OS、H3C 和山石采用“修改 → 退出配置视图 → 读回 → 保存”的顺序;读回不匹配返回 `:description_unconfirmed`,解析不完整或查询命令不符合审批时也不会保存。
138
-
139
- `plan.commands` 现在包括读回命令,旧版计划必须重新生成、审核;执行仍返回 `Result`,保留修改、读回及保存阶段已经完成的步骤。只有设备提供明确保存完成行才成功,保存超时、失败或只有提示符会返回 `:persistence_unconfirmed`,此时描述可能已生效,不能自动重放或回滚。现场下发前仍应按设备固件核对命令与回显。
140
-
141
- PAN-OS 的自动描述计划及改写暂不提供,`supports?(:interface_description_changes)` 为 false,入口在设备 I/O 前抛出 `:candidate_isolation_unavailable`。候选配置的归属、验证及提交需要经目标固件实验确认的独立流程;目前可继续只读查询邻居和描述。Radware 支持读取端口名称,但不提供邻居发现或自动改写,其 `neighbors` 返回 `:neighbor_discovery_unsupported`。
142
-
143
- PAN-OS 在导出前后检查候选配置差异,并拒绝 XML 或非 set 格式输出,避免将未提交配置误作运行配置。
144
-
145
- ## 命令脚本与自动交互
146
-
147
- ```ruby
148
- script = Net::Connector::Script.parse(<<~CLI, name: "change-123")
149
- # 独立成行的注释会被忽略。
150
- configure terminal
151
- interface GigabitEthernet1/0/1
152
- description uplink
153
- end
154
- CLI
155
-
156
- result = device.execute_script(script) do |step|
157
- puts "#{step.command.text}: #{step.duration.round(2)} 秒"
158
- end
159
-
160
- if result.failure?
161
- warn "#{result.error.code},阶段 #{result.error.phase}"
162
- warn "失败前已完成 #{result.steps.size} 条命令"
163
- end
164
- ```
165
-
166
- `execute_command` 执行一条命令;`execute_script` 接收 `Script` 或命令数组;`Script.load(path)` 读取脚本文件。所有命令在设备 I/O 前校验,后续步骤失败时仍保留已完成结果,库不会自动重放命令。`save_config` 显式执行厂商保存命令,普通脚本不会自动保存。
167
-
168
- 创建连接器时可设置 `max_script_output_bytes: 8 * 1024 * 1024`,限制每个脚本及其追加查询累计收到的原始响应字节;默认 `nil` 保持原有完整输出行为。达到上限后不再发送下一命令,当前响应超过上限时保留刚完成的步骤并返回 `ScriptOutputLimitExceeded`(`code: :script_output_limit_exceeded`)。命令可能已经执行,不会自动重试。原有 `max_output_bytes` 继续限制单次读取响应;累计预算不是进程内存上限,详细计数规则见架构文档。
169
-
170
- 厂商档案处理分页和常见确认提示。特定命令可给 `execute_command` 传入 `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]`。敏感命令及交互会暂停回显日志并在错误中脱敏;若普通命令文本包含秘密,必须显式标记 `sensitive: true`。
171
-
172
- 命令文本安全、输出可能包含秘密时,使用 `device.execute_command("show running-config", output_sensitive: true)`。该标记保护响应及其准备、后处理、回调异常,保留安全命令文字;不会改写 `Result` 的业务输出。`running_config` 自动启用此保护,手写脚本的默认值仍为 `false`。
173
-
174
- ## 连接与日志设置
175
-
176
- 连接参数 `on_event: ->(event) { ... }` 接收不可变、已脱敏的 `Log::Event`,可与日志文件或应用 logger 共用;按 `log_level` 过滤,独立于应用 logger 的阈值。回调同步执行,应保持简短;异常按日志故障处理,不会静默吞掉。它不提供未脱敏的配置正文。
177
-
178
- `examples/backup.rb` 和 `examples/backup_tftp.rb` 默认全量备份符合筛选条件的设备,并在终端原地刷新两行进度(登录、采集、排队、成功和未完成数,以及耗时、平均吞吐、预计剩余时间);失败单独输出,结束后显示汇总和报告路径。重定向时每 25 台输出一次进度,避免刷屏。`--verbose` 显示逐条登录和命令事件,`--json` 才在 STDOUT 输出 JSON;完整计划和结果始终保存在批次目录。`NC_PROGRESS=0` 关闭人类进度。百分比表示任务完成比例,不是成功率。
179
-
180
- ```sh
181
- ruby examples/backup.rb # 全量,默认并发 4
182
- ruby examples/backup.rb --sample 3 # 每厂商最多 3 台
183
- ruby examples/backup.rb --verbose # 详细命令过程
184
- ruby examples/backup.rb --json > result.jsonl
185
- ruby examples/backup.rb --concurrency 10 --username backup-user --ask-password
186
- ```
187
-
188
- 批量示例支持 `--concurrency`(1 至 50)、`--username`、`--password`、`--enable-password`、`--netdisco-url`、`--netdisco-username`、`--netdisco-password`、`--directory` 和 `--config`。命令行优先于环境变量与 YAML;显式设备凭据逐字段覆盖对应厂商凭据。指定 Netdisco 用户名或密码时不再沿用环境中的 API key。密码可使用 `--ask-password` / `--ask-netdisco-password` 隐藏输入,避免命令行密码进入 shell 历史或进程参数;自动化仍可用环境变量或原有 `--stdin-credentials`。`--config` 与 `--directory` 的相对路径基于项目根目录。
15
+ ## Quick start
189
16
 
190
-
191
- `Configuration` 支持 `protocol: :ssh`(默认)或 `:telnet`,以及端口、超时、输出大小、`log_file`、`logger`、`log_format`、`log_level` 和 `known_hosts` 等参数。文本日志使用 Ruby 标准库 `Logger`,记录毫秒时间、级别、设备、中文说明和完整事件字段。`:info` 包括连接、登录、命令响应、脚本处理和 TFTP 结果;`:debug` 增加逐行脱敏回显;`:warn`、`:error` 只保留相应级别。`:raw` 文件只写经过现有敏感保护的设备字节,不添加事件字段。
192
-
193
- `log_file` 只接受普通文件,拒绝符号链接、FIFO 和设备文件;应用管理的输出流可通过 `logger` 注入。命令已经完成后若日志或事件回调失败,失败结果仍保留完成步骤,TFTP 错误仍携带已确认的设备回执;调用方不能据此自动重放命令。
194
-
195
- 每次连接生成 `session_id`,每条实际发送的命令分配 `command_id`;日志还包含 `operation`、`phase`、脚本 `source` / `line`、`duration_ms`、`response_bytes` 和失败 `code`。`command_complete` 的 `response_received` 只表示收到了提示符;`operation_complete` 覆盖脚本准备、执行及后处理,不替代 TFTP 服务端核验或设备持久化证据。普通自定义事件使用 `device.log_event("audit", level: :info, count: 2)`。
196
-
197
- 可注入 `logger: Rails.logger` 或普通 `Logger`。有效级别取 `log_level` 与调用方**当前**级别中较严格的一项;连接器不修改它的级别、formatter 或 progname,也不关闭它。消息是已脱敏且冻结的 `Net::Connector::Log::Event`,`to_s` 供文本显示,`to_h` 供应用 formatter 输出 JSON:
17
+ Create and protect the backup directory, register the device's SSH host key, and provide credentials through your environment.
198
18
 
199
19
  ```ruby
200
20
  require "net/connector"
201
- require "json"
202
- require "logger"
203
- require "time"
204
21
 
205
- logger = Logger.new($stdout)
206
- logger.formatter = lambda do |severity, time, program, message|
207
- fields = message.is_a?(Net::Connector::Log::Event) ? message.to_h : { message: message.to_s }
208
- "#{JSON.generate(time: time.iso8601(3), severity: severity, program: program, **fields)}\n"
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}"
209
27
  end
210
- # 将 logger: logger 传给 Net::Connector.build / open。
211
28
  ```
212
29
 
213
- 事件字段接受字符串、符号、整数、有限浮点数、布尔和 nil;复杂对象统一隐藏,不展开对象内容。事件名、字段名和值经过校验/脱敏,会话与命令标识不可由自定义字段覆盖。敏感命令或配置处理期间,自定义事件的名称和载荷整体隐藏,避免钩子把未登记的配置秘密写进日志。更多边界见[架构文档](docs/architecture.md)。
30
+ The block closes the session on exit. Backups use private files and atomic replacement; a failed collection preserves the previous backup. Commands are never automatically replayed. Returned configurations and command output remain sensitive data.
214
31
 
215
- 主机密钥策略默认为 `:strict`;`:accept_new` 接受首次连接的密钥;`:replace` 需要显式 `known_hosts` 文件。`telnet_fallback` 和 `legacy_ssh` 默认关闭,只在已识别的连接失败时使用。外部命令以参数数组执行,不经 shell。Telnet 不提供 SSH 加密,只应在可信管理网络启用。设备授权和变更审批由调用方负责。
32
+ ## Supported devices
216
33
 
217
- ## Netdisco 清单与批量备份
34
+ H3C Comware and wireless controllers, Cisco IOS/IOS XE and NX-OS, Huawei, Hillstone StoneOS, Radware Alteon, and Palo Alto PAN-OS. Capabilities vary by vendor; see the [device guide](docs/usage.md#supported-devices). Synthetic tests do not establish support for every firmware version.
218
35
 
219
- `Net::Connector::Netdisco` 读取并验证完整清单,再将支持的记录映射到连接器,使用有上限的工作线程执行备份。Netdisco 只提供清单字段;设备凭据来自环境变量或调用方提供的解析器。清单会在连接任何设备前完成校验;不支持、被过滤、重复、缺少凭据、失败,以及保存成功但关闭失败的结果分别保留。
220
-
221
- 每次 `Client#devices` 默认限制单响应 16 MiB、累计响应 128 MiB、去重前 100,000 条记录、10,000 页和 300 秒总期限。认证、分页及兼容查询共用这些预算;默认 HTTP 客户端逐块计数,超限会关闭连接并抛出带稳定 `code` 的 `Client::Error`,Fleet 不会执行半份清单。这些默认值是可调整的设计起点,不是实测容量。可注入 `requester: ->(uri, request)`;该回调只能在回调返回后检查正文和期限,回调自身的阻塞及内存用量由注入方控制。
222
-
223
- 推荐使用 HTTPS,标准证书验证保持开启。`allow_insecure_http` 默认为 `true`;设为 `false` 可在发请求前拒绝 HTTP,YAML 中对应 `netdisco.allow_insecure_http: false`。HTTP 会明文传输登录凭据和 API key,迁移时应先提供可验证的 HTTPS 端点。
224
-
225
- `Fleet#plan_backup` 和 `Fleet#plan_tftp_backup` 从同一份清单生成计划。把计划传给 `backup_all(plan:)` 或 `tftp_backup_all(plan:)`,可使预览与执行选择同一批设备;计划与清单不符时会拒绝执行。单台设备异常或结果回调失败不会阻止其他设备。`batch.summary` 包含总数、成功、失败、部分成功、跳过、具体状态和逐台结果。部分成功包括已保存但关闭失败,以及本地文件已替换但目录同步或收尾失败;后者保留 backup 并标为 `saved_with_error`。TFTP 的 `reported_uploaded` 仅代表设备报告上传,不代表服务器文件已核验。
226
-
227
- 本地 `backup(path:)` 用 SHA-256 比较新旧配置,`backup.change` 返回 `:created`、`:changed` 或 `:unchanged`;内容未变且权限、文件身份正常时保留修改时间。这不补验历史写入的断电持久性。`backup_all` 的 `on_change:` 仅在新建或更改文件保存后触发;`on_start:` 和 `on_result:` 观察每台已尝试设备。回调异常记录在 `batch.callback_errors`,不丢弃设备结果。每项结果包含开始、结束和耗时。TFTP 无法比较服务器文件,因此没有 `change`,也不触发变更通知。
228
-
229
- 每批默认由 `ResultStore::Json` 写入私有 JSON 报告,路径见 `batch.report_location`。调用方如有数据库仓储,可传 `ResultStore::Database.new(repository: YourModel)`;仓储需实现 `create!(attributes)`。`result_store: nil` 表示由调用方自行持久化。报告失败保留在 `batch.report_error`,同时使 `batch.success?` 为假。若报告已替换但目录同步失败,仍保留位置;离线 `--export --output` 遇到同类错误返回 2,并说明文件已经提交。
230
-
231
- Fleet 统一返回 `Netdisco::Report`,JSON 的 `schema_version` 固定为 `2`。报告包含 `policy`、`policy_success`、清单覆盖和受控诊断;`success?` / `status` 表示严格完成情况,`policy_success?` 表示所选成功策略。任务耗时使用单调时钟,UTC 开始/结束时间独立保留。`report.batch` 是原始执行快照;手工构造的 Batch 可用 `batch.build_report(policy: :selected)` 生成报告,不会再次执行设备或重写文件。
232
-
233
- `success_policy: :selected` 要求至少一台设备成功,其他记录只因 `filtered` 或 `sample_limit` 跳过,而且没有部分成功、回调或报告错误。缺少凭据、重复地址、无效地址、未知厂商、目标冲突和未知状态均会阻止成功。`coverage.complete` 只表示每条清单记录都已尝试任务;失败任务也计入尝试,不能据此判断配置已保存。自定义 `ResultStore#write(report, directory:)` 始终接收 Report,并通过 `summary` 获取统一 JSON 结构。
234
-
235
- 外部构造的计划也必须把同一管理地址的全部条目标记为 `duplicate_host` 并跳过,不能通过采样恢复其中一条的执行资格;IPv6 的等价写法视为同一地址。只需汇总时使用 `report.statistics`,它返回 `summary` 除 `devices` 外的字段,不生成设备明细,也不缓存调用方持有的批次容器。
236
-
237
- ```sh
238
- export NETDISCO_URL=https://netdisco.example/netdisco
239
- export NETDISCO_USERNAME=inventory-reader
240
- export NETDISCO_PASSWORD='replace-me'
241
- export NC_DEVICE_USERNAME=backup-user
242
- export NC_DEVICE_PASSWORD='replace-me'
243
- export NC_BACKUP_DIRECTORY=/var/backups/network
244
- export NC_CONCURRENCY=4
245
- ```
246
-
247
- ```ruby
248
- require "net/connector/netdisco"
249
-
250
- fleet = Net::Connector::Netdisco::Fleet.new
251
- plan = fleet.plan_backup
252
- puts plan.selected.map(&:host)
253
- batch = fleet.backup_all(plan: plan, on_change: ->(outcome) {
254
- puts "#{outcome.device.host}: #{outcome.backup.change}"
255
- })
256
- puts batch.counts
257
- puts batch.summary.slice(:succeeded, :failed, :partial, :skipped)
258
- puts batch.report_location
259
- batch.outcomes.each do |outcome|
260
- puts "#{outcome.device.source_ip}: #{outcome.status} #{outcome.backup&.path}"
261
- end
262
- exit 1 unless batch.success?
263
- ```
264
-
265
- gem 安装后的命令为 `net-backup`;源码入口为 `bin/net-backup`,可在项目目录运行 `bundle exec bin/net-backup --help`。原命令 `net-connector-backup` 已改名。
266
-
267
- 命令行程序 `net-backup` 的 YAML 文件只允许非敏感设置;Netdisco 和设备凭据留在环境变量中。常用环境变量优先于 YAML,复杂参数只通过 YAML 或 CLI 设置。批量示例也支持 `NC_CONFIG`,可从 [完整配置示例](examples/backup.yml) 开始。只有传入 `--config FILE` 或设置 `NC_CONFIG` 时才加载文件:
268
-
269
- ```yaml
270
- netdisco:
271
- url: https://netdisco.example/netdisco
272
- page_size: 500
273
- max_response_bytes: 16777216
274
- max_inventory_bytes: 134217728
275
- max_devices: 100000
276
- max_pages: 10000
277
- inventory_timeout: 300
278
- allow_insecure_http: false
279
- backup:
280
- directory: /var/backups/network
281
- concurrency: 4
282
- inventory:
283
- include_vendors: [h3c, huawei, cisco_ios]
284
- host_overrides:
285
- 192.0.2.7: h3c_wireless
286
- ssh:
287
- host_key_policy: strict
288
- max_script_output_bytes: null # 可选正整数字节数;null 保持不限制累计值。
289
- tftp:
290
- server: 192.0.2.10
291
- vrfs:
292
- cisco_nxos: management
293
- ```
36
+ ## Fleet backups
294
37
 
295
38
  ```sh
296
39
  net-backup --config config.yml --show-config
297
- net-backup --config config.yml --plan --host 192.0.2.7
298
- net-backup --config config.yml --host 192.0.2.7
299
- net-backup --config config.yml --tftp --plan
300
- net-backup --config config.yml --tftp --all
301
- net-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
302
- ```
303
-
304
- ### PostgreSQL 联机查询
305
-
306
- 设置 `netdisco.source: postgres` 可以直接从数据库查询清单,继续使用同一套 Fleet、规则、计划和备份流程。库不内置表名、SQL 或业务筛选条件;必须提供查询,可直接修改 [YAML 示例](examples/inventory_sql.yml):
307
-
308
- ```yaml
309
- netdisco:
310
- source: postgres
311
- query: |
312
- SELECT host(ip) AS ip, name, dns, vendor, os, model, os_ver, serial
313
- FROM device
314
- WHERE vendor = $1
315
- ORDER BY ip
316
- query_params: [H3C]
317
- ```
318
-
319
- 查询必须返回唯一命名的 `ip` 列;可选列为 `name`、`dns`、`vendor`、`os`、`model`、`os_ver`、`serial`,其他列会丢弃。自定义表、视图、JOIN 和只读 CTE 均可用列别名适配这一契约。PostgreSQL 的 `inet` 字段建议用 `host(ip) AS ip` 去除前缀长度;筛选及排序由 SQL 决定。参数按数组顺序绑定 `$1`、`$2`,支持字符串、数字、布尔和 null,不做字符串插值或环境变量展开。
320
-
321
- 数据库连接信息只从进程环境读取,沿用 [Netdisco 官方环境变量命名](https://github.com/netdisco/netdisco/wiki/Environment-Variables)。`HOST`、`NAME`、`USER`、`PASS` 必填;HTTP 地址、API key 和设备登录凭据不是清单查询的前置条件:
322
-
323
- ```sh
324
- export NETDISCO_DB_HOST=database.example
325
- export NETDISCO_DB_NAME=netdisco
326
- export NETDISCO_DB_USER=inventory-reader
327
- export NETDISCO_DB_PASS='replace-me'
328
- export NETDISCO_DB_SSLMODE=verify-full
329
- export NETDISCO_DB_SSLROOTCERT=/etc/net-connector/database-ca.crt
330
-
331
- net-backup --config examples/inventory_sql.yml --show-config
332
- net-backup --config examples/inventory_sql.yml --plan
333
- # 覆盖查询参数;实际备份仍需设置 NC_DEVICE_* 凭据。
334
- net-backup --config examples/inventory_sql.yml --plan --query-params '["Cisco"]'
40
+ net-backup --config config.yml --plan
41
+ net-backup --config config.yml
335
42
  ```
336
43
 
337
- SQL、参数和来源也可分别通过 `--query SQL`、`--query-params JSON`、`--source postgres` 覆盖 YAML,不再从环境变量读取。SQL 和参数属于可公开配置,会出现在 `--show-config` 中;数据库密码只放在连接环境变量中。连接信息不进入策略快照、计划、报告或 `inspect`。已有 Fleet 每次重新查询时读取最新连接凭据;传入已有 `plan:` 执行时不会重新查询。
338
-
339
- 客户端通过 `pg` 驱动执行只读事务,使用参数化游标分批取数,并在每批启用单行读取。PostgreSQL 原生解析拒绝多条语句,写入和锁定查询会失败;查询账户应仅授予所需表/视图的 SELECT 权限,只读事务不能替代账户权限隔离。驱动只在实际查询时加载,HTTP 和离线导出路径不加载它。
340
-
341
- 数据库查询复用现有清单预算:`page_size` 控制每次 FETCH 的行数,`max_pages` 限制 FETCH 次数,`max_devices` 在去重前计数;字节预算统计返回列名与文本值,包含最终丢弃的列。字节检查发生在 libpq 解码一行之后,不能限制单个超大字段在驱动内部的瞬时内存。连接、查询和全部 FETCH 共用总期限,同时设置数据库 statement_timeout。任一查询错误、超时或超限均关闭连接并丢弃整份清单,不连接设备;错误消息不输出原始 SQL、参数或数据库响应。建议只选所需字段,并提供明确的 ORDER BY 保持采样顺序稳定。
342
-
343
- ### 计划与执行
344
-
345
- `--plan` 只拉取并验证清单;`--host` 选择一个管理地址;`--tftp` 默认每厂商最多选择五台,`--all` 选择所有就绪设备。本地备份默认选择所有就绪设备,可用 `--limit-per-vendor` 限制。`--show-config` 只输出有效的非敏感设置,不访问 Netdisco。CLI 会拒绝未知 YAML 字段、Ruby 对象标签及配置中的凭据。`--export IP` 只离线读取规范化管理地址对应的 `<IP>.txt`;默认原样写到标准输出,指定 `--output` 后以 `0600` 权限原子写文件。导出的配置仍是敏感数据。
346
-
347
- CLI 的计划与批次摘要使用 JSON。默认 `--success-policy strict` 使用严格规则:非空清单且全部成功、回调及报告正常时为 `0`;空清单或有跳过、部分成功、失败时为 `1`;清单或配置错误为 `2`。`--host` 未在清单中找到也返回 `2`。由于其他清单记录会标记为过滤,默认单主机备份成功时批次退出码仍可能是 `1`;应查看 JSON 中的 `succeeded`、`skipped` 和逐台 `status`。
348
-
349
- 显式使用 `--success-policy selected` 后,CLI 按上述 selected 规则决定退出码,保留严格的 `status: incomplete` 与跳过计数,另列 `policy_success`。例如 `net-backup --config config.yml --host 192.0.2.7 --success-policy selected`。成功策略由本次 CLI/API 参数指定,不改变已批准的清单选择,也不触发重试。
350
-
351
- 本地批量示例和 TFTP 批量示例默认全量;显式 `--sample N`、`NC_SAMPLE_PER_VENDOR` 或 YAML `backup.limit_per_vendor` 才限制每厂商数量(示例允许 1 至 5)。优先级为命令行抽样 > 环境变量 > YAML。并发默认 4,可用 `NC_CONCURRENCY` 覆盖。两类示例在备份目录下创建唯一批次目录。TFTP 批量示例在可访问服务器目录时使用 hostname-ip 远端文件名并逐批归档;无法访问服务器目录时使用带批次标识的远端文件名,避免覆盖旧文件;上传完成与服务器文件验证是不同状态。
44
+ Start with [backup.yml](examples/backup.yml) or [inventory_sql.yml](examples/inventory_sql.yml). Configure inventory and device credentials in the environment; YAML contains non-sensitive settings. See the [Netdisco guide](docs/netdisco.md) for HTTP/PostgreSQL inventories, success policies, TFTP verification, and batch examples.
352
45
 
353
- 全量 TFTP 计划保存为 `plan.json`。目标文件名通常为 `<设备名>-<IP>.cfg`,Radware 用 `.tgz`,山石用 `.dat`。可访问本机 TFTP 服务器目录时,批量示例直接使用 `<设备名>-<IP>.<扩展名>` 上传,并在服务器的 `archive/<批次>/` 与本地 `<批次目录>/tftp/` 各保留一份;上传前已有的同名文件先保存到新归档目录的 `previous/`。无法访问服务器目录时,远端文件名增加东八区批次标识及同秒序号,避免覆盖旧文件;实际文件名见 `summary.json` 的 `remote_path`。计划仍检查同批目标名冲突。PAN-OS 固定使用 `running-config.xml`,还要求 `Sent ... bytes` 完成行;为避免下次上传覆盖它,运行批量示例时必须能访问本机 TFTP 服务器目录,否则该设备会明确失败且不会发起上传。H3C 从 `display startup` 发现源文件,可用 YAML `tftp.h3c_source_file` 覆盖;华为默认 `flash:/startup.cfg`。Nexus 9000 默认 VRF 为 `management`,山石为 `mgt-vr`;YAML `tftp.vrfs` 接受按厂商键配置的映射。山石命令在 `vrouter` 参数后追加唯一的 `.dat` 文件名;直接调用山石连接器且不指定 `path:` 时,由设备生成文件名并在结果中返回。通过 `--tftp-root DIR` 或 `TFTP_ROOT` 指定本机或已挂载的服务器目录时,批量示例在每台设备完成后核验实际回执路径、非空文件、修改时间和 SHA-256,并立即保存两份历史文件;报告的 `local_file` 和 `server_archive_file` 分别指向本地批次副本和服务器归档副本。核验或归档失败不会报告成功。未提供本机目录时,可命名设备仍使用不覆盖旧文件的远端文件名,回执保留为 `device_reported`,本地批次不包含配置副本。默认示例使用 `selected` 策略;要求服务器文件核验时使用 `--success-policy verified`,并提供本机服务器目录。可运行 `ruby examples/review_tftp.rb <批次目录>`,根据会话日志复核剩余失败,而不改写原始结果。日志、`events.jsonl` 和逐台结果均以私有权限保存。
46
+ ## Documentation
354
47
 
355
- 批量 TFTP 源文件通过 YAML `tftp.h3c_source_file`、`tftp.h3c_wireless_source_file`、`tftp.huawei_source_file` 设置。源文件是设备上的路径,需符合连接器校验规则。
356
-
357
- 本地配置备份写到 `<目录>/<IP>.txt`,IPv6 的 `:` 转成 `_`。设备改名不改变文件名或比较基线;只读取该规范路径,缺失时创建新备份。其他名称的文件不参与查找或哈希比较。Fleet 和离线读取拒绝符号链接及非普通文件。文件原子替换为 `0600`,新目录权限为 `0700`。批次在当前进程执行,需要定时任务或持久队列时由调用方安排;失败命令不会自动重试。
358
-
359
-
360
- | 环境变量 | 默认值 | 用途 |
48
+ | Topic | English | 简体中文 |
361
49
  | --- | --- | --- |
362
- | `NETDISCO_URL` | HTTP 模式必填 | Netdisco 服务根地址,可包含租户路径 |
363
- | `NC_CONFIG` | 未设置 | CLI / 批量示例的非敏感 YAML 配置文件 |
364
- | `NETDISCO_USERNAME`, `NETDISCO_PASSWORD` | 未提供 API 密钥时必填 | 清单 API 登录 |
365
- | `NETDISCO_API_KEY` | 未设置 | 直接使用已有 API 密钥 |
366
- | `NETDISCO_DB_HOST`, `NETDISCO_DB_NAME` | PostgreSQL 模式必填 | 数据库主机或 Unix socket 目录、数据库名 |
367
- | `NETDISCO_DB_USER`, `NETDISCO_DB_PASS` | PostgreSQL 模式必填 | 仅从环境注入的数据库用户名、密码 |
368
- | `NETDISCO_DB_PORT` | libpq 默认 `5432` | PostgreSQL 端口 |
369
- | `NETDISCO_DB_SSLMODE`, `NETDISCO_DB_SSLROOTCERT` | libpq 默认 | TLS 模式、CA 文件;远程连接建议 `verify-full` |
370
- | `NETDISCO_DB_CONNECT_TIMEOUT` | 未单独设置 | 可选正整数秒;连接始终受清单总期限限制 |
371
- | `NC_DEVICE_USERNAME`, `NC_DEVICE_PASSWORD` | 未设置 | 设备登录默认凭据 |
372
- | `NC_<VENDOR>_USERNAME`, `NC_<VENDOR>_PASSWORD` | 未设置 | 单厂商凭据,例如 `CISCO_IOS` |
373
- | `NC_BACKUP_DIRECTORY` | `./backups` | 备份及默认报告目录 |
374
- | `NC_CONCURRENCY` | `4` | 并发设备数,范围 1 至 50 |
375
- | `NC_PROTOCOL` | `ssh` | 默认连接协议,可按厂商覆盖 |
376
- | `NC_KNOWN_HOSTS`, `NC_HOST_KEY_POLICY` | 系统主机记录、`strict` | SSH 主机密钥设置 |
377
- | `NC_LOG_DIRECTORY` | 未设置 | 逐台会话日志目录 |
378
- | `NC_LOG_LEVEL` | `info` | `debug`、`info`、`warn`、`error` 日志级别 |
379
-
380
- 环境变量前缀统一为 `NC_`;`NETDISCO_*` 仅保留服务地址、认证和数据库连接设置。旧 `NET_CONNECTOR_*` 及已移除的复杂环境变量会报迁移错误,不会静默忽略。凭据保持在环境变量中,YAML 不接受密码。
381
-
382
- | 其他常用变量 | 用途 |
383
- | --- | --- |
384
- | `NC_SAMPLE_PER_VENDOR` | 可选抽样上限;示例不设置时全量,设置时允许 1 至 5 |
385
- | `NC_PROGRESS` | 批量示例实时进度,默认 1;0 关闭 |
386
- | `NC_ENABLE_PASSWORD`, `NC_<VENDOR>_ENABLE_PASSWORD` | 可选提权密码 |
387
- | `TFTP_HOST`, `TFTP_ROOT` | TFTP 地址、示例验证用的本地服务器目录 |
388
-
389
- 将原来的环境配置移到 YAML:清单预算放在 `netdisco`;过滤、厂商/主机映射及规则放在 `inventory`;脚本输出预算与厂商协议放在 `ssh`;VRF 和源文件放在 `tftp`。例如:
390
-
391
- ```yaml
392
- inventory:
393
- include_vendors: [h3c, cisco_nxos]
394
- device_rules:
395
- - vendor: Cisco
396
- model_prefix: N9K
397
- connector: cisco_nxos
398
- ssh:
399
- vendor_protocols:
400
- h3c: ssh
401
- max_script_output_bytes: 16777216
402
- ```
403
-
50
+ | Device operations, parsing, topology, logging | [Device guide](docs/usage.md) | [设备操作](docs/usage.zh-CN.md) |
51
+ | Inventory, configuration, CLI, fleet backups | [Netdisco guide](docs/netdisco.md) | [清单与批量备份](docs/netdisco.zh-CN.md) |
404
52
 
405
- `Fleet` 每次规划或执行前通过 `Settings#snapshot(mode:)` 固定非敏感设置,包括筛选规则、目录、并发、协议、主机密钥、日志、TFTP 参数和清单预算。执行中修改 ENV 不改变当批策略;再次调用会读取新值。CLI 一次调用的规划与执行共用策略,优先级为 CLI > 常用环境变量 > YAML > 默认值。`Settings#validate!(mode:)` 复用连接配置及 Planner 的枚举和范围规则;`--show-config` 会拒绝非法设置,`--export` 只验证离线目录,不要求清单地址或认证。
53
+ Detailed references: [architecture](docs/architecture.md), [verification](docs/VERIFICATION.md), [releasing](docs/RELEASING.md), and [examples](examples/README.md) (Chinese).
406
54
 
407
- 每台任务开始时仍读取设备凭据,`Settings.from_file` 也保留轮换能力。纯策略快照不保存密码、API key 或凭据解析器。需要让多次 API 调用共用策略时,可传 `Settings.new.for_run(mode: :backup)`。自定义 `credentials:` 解析器仍可逐设备返回连接设置,它显式给出的选项优先于批次默认值,由调用方负责一致性;注入的 `client:` 生命周期也由调用方管理。传入 `plan:` 的执行不会重新拉取或筛选已批准清单。`fleet.devices` 可在不连接设备时检查映射。
408
-
409
- ## 开发与验证
55
+ ## Development
410
56
 
411
57
  ```sh
412
58
  bundle install
413
59
  script/ci
414
60
  ```
415
61
 
416
- CI 在 Linux 和 macOS 上覆盖 Ruby 3.2、3.3、3.4、4.0。`script/ci` 扫描源码与可用 Git 历史中的敏感数据,执行 Ruby 与工作流 lint、完整测试,并验证已构建 gem 的隔离安装和本地 PTY 烟测;不连接真实网络设备。首次运行需下载固定校验和的 Gitleaks 与 actionlint。
417
-
418
- `bundle exec rake test` 报告当前测试进程的行、分支覆盖率和未加载文件清单。核心引擎、脱敏与错误处理(含 `Redactor`)、批量工作线程分别要求行和分支覆盖率均达到 80%,关键文件没有覆盖率数据也会失败;该门槛同时阻止 CI 和发布预检通过。`bundle exec rake lint` 检查 Ruby 代码,并对 `engine/`、`netdisco/` 限制方法长度(40)和 ABC 复杂度(60)。`bundle exec rake security:check` 扫描敏感数据,`bundle exec rake release:check` 执行完整预检。构建产物和脱敏扫描报告保存在被忽略的 `tmp/` 下。
419
-
420
- 参与开发见 [CONTRIBUTING.md](CONTRIBUTING.md),漏洞报告见 [SECURITY.md](SECURITY.md)。实现注释和主要文档使用中文,欢迎中文或英文的问题与 PR。
421
-
422
- 真实凭据应放在环境变量和版本库外的本地配置中。检查范围、依赖政策和忽略规则见[验证文档](docs/VERIFICATION.md),发布流程见[发布文档](docs/RELEASING.md)。
423
-
424
- 备份示例通过 `examples/boot.rb` 读取项目根目录 `.env`,不会自动切换依赖或注入源码路径。直接 `ruby examples/backup.rb`(或在 `examples/` 中执行 `ruby backup.rb`)使用已安装的 gem;修改库后必须重新构建并安装,开发源码验证则使用 `bundle exec ruby examples/backup.rb`。示例中的相对备份、日志路径统一基于项目根目录;进程中显式传入的 `NC_CONFIG` 路径按启动目录解析。
425
-
426
- 清单自动识别保留显式主机映射、设备规则和厂商覆盖的优先级。若厂商标签陈旧,而 `os: Comware` 与 `model: H3C ...` 同时确认 H3C,则自动使用 H3C 连接器;`H3C WX...` / `H3C AC...` 使用无线连接器。仅型号片段或操作系统单项不会覆盖其他厂商。
427
-
428
- 备份部署示例 `.env.example` 使用 `NC_HOST_KEY_POLICY=accept_new`:首次连接自动登记到 OpenSSH known_hosts,已有密钥变化仍拒绝连接。`--host-key-policy strict|accept_new` 可覆盖本次策略,`--known-hosts FILE` 指定持久保存位置;不应删除密钥文件,否则会丢失历史身份记录。库本身默认仍为 strict。
429
-
430
- 设备密钥变化也需自动更新时,使用 `--host-key-policy replace --known-hosts FILE`,或设置 `NC_HOST_KEY_POLICY=replace` 与 `NC_KNOWN_HOSTS`。文件必须显式指定;只在主机密钥变化错误时删除该设备旧记录并重连一次,不重放设备命令。建议使用备份任务专用的持久密钥文件。
431
-
432
- 进度每秒更新一次心跳;设备无新输出时仍显示耗时,超过 10 秒的最久任务显示地址、阶段与任务耗时。预计剩余时间按已完成任务的平均吞吐计算,前五台完成前显示“估算中”,异构设备和末尾慢任务会使估算波动。终端事件刷新最多约每秒五次,退出或异常时回收显示线程;结束按错误代码汇总未完成任务。
433
-
434
- 本地批量示例将配置写为 `hostname-ip.txt`(名称取 Netdisco name/dns),空名称使用 `unnamed`,特殊字符清理,IPv6 冒号替换为下划线。批次目录固定使用 UTC+8 的 `YYYY-MM-DD_HH-mm-ss`,同秒冲突追加 `_01` 等序号。每批包含 `plan.json`、`summary.json` 和人类可读的 `summary.txt`(时间、计数、失败分类及失败明细),报告权限为 0600。历史目录不改名;通用 Fleet/CLI 仍默认 IP 文件名,API 可显式传入 `filename_style: :hostname_ip`,该模式设备改名会改变文件路径与比较基线。
435
-
436
- 公共备份辅助接口随 gem 分发,不依赖 examples 文件:
437
-
438
- ```ruby
439
- require "net/connector/netdisco"
440
- netdisco = Net::Connector::Netdisco
441
- options = netdisco::CLI::Options.parse(argv: ["--concurrency", "10"])
442
- settings = netdisco::CLI::Options.settings(options)
443
- client, credentials = netdisco::Connection.build(settings)
444
- directory = Net::Connector::Storage::BatchDirectory.create("./backups")
445
- # 获得 report 和 plan 后:
446
- # report = netdisco::Report::Files.write(report, directory: directory, plan: plan, concurrency: 10)
447
- ```
448
-
449
- `CLI::Options.parse` 接受 `argv/input/output/error/program`,不修改传入参数数组;帮助通过返回值 `:help` 通知调用方,非法输入抛出 `ArgumentError`。`Connection.build` 接受 `input:`,标准输入凭据仅消费一行,RUN 确认由脚本编排负责。模块不加载 `.env` 或切换目录。
450
-
451
-
452
- 批量示例由 `Netdisco::BackupRun` 统一编排,`CLI::Options.settings` 为示例和正式 CLI 共用配置覆盖入口。
453
- 示例默认 `selected`,正式 CLI 保留 `strict` 默认;两者均支持显式 `--success-policy`,退出码与报告的 `policy_success` 一致。
454
- 新示例 `summary.json` 使用标准 schema 2 的 `devices`,不再另造 `outcomes`;旧报告可继续用 `review_tftp.rb` 读取。
455
- TFTP 的 `verification` 汇总与设备条目的 `server_file_verified` 区分上传回执和服务器文件核验,核验成功还包含 `local_file`、`server_archive_file`、`bytes`、`sha256`。
456
- 报告通过私有原子写入保存;报告保存失败会阻止成功退出,终端最终结果在报告保存之后输出。
457
-
458
- `--stdin-credentials` 的一行 JSON 保留 `netdisco_username`、`netdisco_password`、`device_username`、`device_password` 四个字段。
459
- 清单来源为 PostgreSQL 时,前两个字段覆盖数据库用户名和密码,主机、数据库名及 TLS 参数仍由 NETDISCO_DB_* 提供;第二行 `RUN` 仍是执行设备任务的确认。
62
+ The gate scans secrets, lints Ruby and workflows, runs tests and local PTY benchmarks, and verifies isolated gem installs. See [CONTRIBUTING](CONTRIBUTING.md), [security reporting](SECURITY.md), and the [changelog](CHANGELOG.md). Issues and pull requests may be written in English or Chinese.
460
63
 
461
- 使用 `accept_new` / `replace` 时,SSH 在每个会话的临时 known_hosts 中协商,认证后通过独立锁文件合并到共享文件。
462
- 网络登录不占用共享锁;失败登录不删除原信任记录,`replace` 只替换当前主机。该同步协调本库进程,外部手工工具应避免同时改写同一文件。
463
- 认证后的合并锁另有最多 `login_timeout` 秒的等待预算,超时返回 `ConnectionError`(`code: :known_hosts_busy`),关闭会话且保留共享信任记录;此预算只约束锁等待,不是整批任务的期限。
64
+ Licensed under [MIT](LICENSE).