net-connector 0.4.0 → 0.4.2

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -68
  3. data/CONTRIBUTING.md +40 -0
  4. data/README.md +140 -95
  5. data/SECURITY.md +11 -0
  6. data/docs/VERIFICATION.md +129 -5
  7. data/docs/architecture.md +321 -248
  8. data/lib/net/connector/device/base.rb +12 -4
  9. data/lib/net/connector/device/interface_description.rb +3 -0
  10. data/lib/net/connector/device/profile/builder.rb +236 -0
  11. data/lib/net/connector/device/profile.rb +2 -227
  12. data/lib/net/connector/device/running_config/strategy.rb +5 -0
  13. data/lib/net/connector/device/running_config.rb +7 -0
  14. data/lib/net/connector/engine/command.rb +14 -2
  15. data/lib/net/connector/engine/configuration.rb +10 -7
  16. data/lib/net/connector/engine/dialogue.rb +53 -28
  17. data/lib/net/connector/engine/errors.rb +41 -54
  18. data/lib/net/connector/engine/execution.rb +32 -5
  19. data/lib/net/connector/engine/log.rb +23 -17
  20. data/lib/net/connector/engine/session.rb +39 -7
  21. data/lib/net/connector/engine/terminal_renderer.rb +7 -4
  22. data/lib/net/connector/netdisco/batch.rb +31 -3
  23. data/lib/net/connector/netdisco/cli.rb +92 -50
  24. data/lib/net/connector/netdisco/client.rb +206 -73
  25. data/lib/net/connector/netdisco/config_file.rb +26 -4
  26. data/lib/net/connector/netdisco/device.rb +4 -8
  27. data/lib/net/connector/netdisco/diagnostic.rb +91 -0
  28. data/lib/net/connector/netdisco/fleet.rb +105 -108
  29. data/lib/net/connector/netdisco/inventory_budget.rb +49 -0
  30. data/lib/net/connector/netdisco/plan.rb +77 -0
  31. data/lib/net/connector/netdisco/planner.rb +12 -23
  32. data/lib/net/connector/netdisco/report.rb +94 -0
  33. data/lib/net/connector/netdisco/rules.rb +28 -5
  34. data/lib/net/connector/netdisco/settings.rb +194 -85
  35. data/lib/net/connector/netdisco/worker.rb +43 -27
  36. data/lib/net/connector/operations/backup_lock.rb +116 -0
  37. data/lib/net/connector/operations/local_backup.rb +49 -9
  38. data/lib/net/connector/operations/parse_output.rb +20 -3
  39. data/lib/net/connector/operations/private_file.rb +94 -5
  40. data/lib/net/connector/operations/safe_file.rb +62 -0
  41. data/lib/net/connector/operations/saved_config/legacy_index.rb +109 -0
  42. data/lib/net/connector/operations/saved_config.rb +40 -10
  43. data/lib/net/connector/operations/tftp/file_upload.rb +55 -0
  44. data/lib/net/connector/operations/tftp/strategy.rb +9 -11
  45. data/lib/net/connector/operations/tftp_backup.rb +78 -15
  46. data/lib/net/connector/operations/tftp_receipt.rb +73 -0
  47. data/lib/net/connector/operations/topology/immediate_strategy.rb +45 -0
  48. data/lib/net/connector/operations/topology/strategy.rb +34 -0
  49. data/lib/net/connector/operations/topology.rb +98 -29
  50. data/lib/net/connector/operations.rb +6 -0
  51. data/lib/net/connector/vendor/cisco_ios/running_config.rb +1 -0
  52. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +9 -2
  53. data/lib/net/connector/vendor/cisco_ios/topology.rb +18 -3
  54. data/lib/net/connector/vendor/cisco_nxos/running_config.rb +1 -0
  55. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +8 -1
  56. data/lib/net/connector/vendor/h3c/tftp_backup.rb +7 -18
  57. data/lib/net/connector/vendor/h3c/topology.rb +17 -3
  58. data/lib/net/connector/vendor/hillstone/running_config.rb +1 -0
  59. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +16 -3
  60. data/lib/net/connector/vendor/hillstone/topology.rb +17 -3
  61. data/lib/net/connector/vendor/huawei/tftp_backup.rb +8 -18
  62. data/lib/net/connector/vendor/palo_alto/running_config.rb +4 -0
  63. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +19 -8
  64. data/lib/net/connector/vendor/palo_alto/topology.rb +16 -1
  65. data/lib/net/connector/vendor/radware/tftp_backup.rb +11 -4
  66. data/lib/net/connector/vendor/radware/topology.rb +3 -0
  67. data/lib/net/connector/version.rb +1 -1
  68. metadata +60 -28
data/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # net-connector
2
2
 
3
- `net-connector` runs network device CLI sessions over SSH or Telnet. It collects running configuration, writes private backup files, executes command scripts, answers device prompts, records redacted session logs, and returns structured errors and partial results.
3
+ `net-connector` 通过 SSH 或 Telnet 操作网络设备的命令行。它可以采集运行配置、保存私有备份、执行命令脚本、回答设备提示、记录脱敏会话日志,并在失败时返回结构化错误和已完成的步骤。
4
4
 
5
- The code is organized by responsibility: `lib/net/connector/engine/` owns sessions, transport, scripts, results, and logging; `device/` owns the device facade, profile, running configuration and interface text helpers; `vendor/<name>.rb` assembles that vendor's rules from `vendor/<name>/running_config.rb`, `tftp_backup.rb` and `topology.rb` where needed; `operations/` owns shared backup, parsing and topology workflows; `netdisco/` owns inventory and batch orchestration. Identical vendor rules are shared. See [docs/architecture.md](docs/architecture.md) for the model. Load the public API with `require "net/connector"` or the Netdisco integration with `require "net/connector/netdisco"`. The public API loads vendor rules and parsing on demand; `require "net/connector/engine/core"` loads only the session execution layer.
5
+ 代码按职责组织:`engine/` 管理会话、传输、脚本、结果和日志;`device/` 提供设备入口、档案、运行配置和接口名称处理;`vendor/<厂商>/` 保存各厂商的采集、TFTP 和拓扑规则;`operations/` 实现公共业务流程;`netdisco/` 负责清单和批量编排。相同规则直接复用,设计说明见[架构文档](docs/architecture.md)。用 `require "net/connector"` 加载设备 API,用 `require "net/connector/netdisco"` 加载 Netdisco 集成;厂商规则和 TextFSM 解析器按需加载。
6
6
 
7
- Ruby 3.2+ and a POSIX system are required. SSH uses the local OpenSSH client. Telnet requires the local `telnet` program and must be selected explicitly. The gem depends on [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.3.x (at least 0.3.1) and [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.x.
7
+ 支持 Ruby 3.2 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。主要依赖为 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.5.x 和 [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.x。
8
8
 
9
- ## Install
9
+ 脱敏直接复用 expect-pty 从 0.5.0 起公开的 `Expect::Redactor` 接口;连接器只管理秘密作用域和配置输出的隐私策略。
10
+
11
+ ## 安装
10
12
 
11
13
  ```sh
12
14
  gem install net-connector
@@ -16,28 +18,24 @@ gem install net-connector
16
18
  require "net/connector"
17
19
  ```
18
20
 
19
- ## Supported devices
21
+ ## 支持的设备
20
22
 
21
- Use `device.supports?(:tftp_backup)` or another capability to check whether a
22
- connector implements an operation without contacting the device. The complete
23
- [capability matrix and extension example](docs/architecture.md#vendor-capabilities)
24
- cover collection, saving, TFTP, neighbor discovery, and description changes.
25
- This check does not test device permissions or firmware behavior.
23
+ `device.supports?(:tftp_backup)` 等能力查询不会连接设备,只表示该连接器实现了对应操作,不代表设备权限或固件一定支持。完整能力矩阵和扩展示例见[厂商能力](docs/architecture.md#厂商能力)。
26
24
 
27
- | Vendor key | Device family | Running configuration | Save configuration |
25
+ | 厂商标识 | 设备系列 | 运行配置命令 | 保存配置命令 |
28
26
  | --- | --- | --- | --- |
29
27
  | `:h3c` | H3C Comware | `dis cur` | `save force` |
30
- | `:h3c_wireless` | H3C wireless controller, Comware CLI | `dis cur` | `save force` |
28
+ | `:h3c_wireless` | H3C 无线控制器 | `dis cur` | `save force` |
31
29
  | `:cisco_ios` | Cisco IOS / IOS XE | `show running-config` | `copy running-config startup-config` |
32
30
  | `:cisco_nxos` | Cisco NX-OS | `show running-config` | `copy run start` |
33
- | `:radware` | Radware Alteon CLI | `/cfg/dump` | `/cfg/save` |
34
- | `:palo_alto` | Palo Alto PAN-OS CLI | set format candidate export | unsupported |
35
- | `:huawei` | Huawei CLI | `dis cur` | `save force` |
36
- | `:hillstone` | Hillstone StoneOS | `show configuration running` | `save all` |
31
+ | `:radware` | Radware Alteon | `/cfg/dump` | `/cfg/save` |
32
+ | `:palo_alto` | Palo Alto PAN-OS | 候选视图中的 set 格式导出 | 不支持 |
33
+ | `:huawei` | 华为 | `dis cur` | `save force` |
34
+ | `:hillstone` | 山石 StoneOS | `show configuration running` | `save all` |
37
35
 
38
- Aliases `:cisco_n9k` and `:paloalto` are accepted. Vendor classes live directly under `Net::Connector`, such as `Net::Connector::H3cWireless::Connector`.
36
+ 兼容标识 `:cisco_n9k` 和 `:paloalto`。厂商连接器位于 `Net::Connector` 下,例如 `Net::Connector::H3cWireless::Connector`。
39
37
 
40
- ## Login and backup
38
+ ## 登录与本地备份
41
39
 
42
40
  ```ruby
43
41
  Net::Connector.open(:cisco_ios,
@@ -45,33 +43,45 @@ Net::Connector.open(:cisco_ios,
45
43
  known_hosts: "/etc/net-connector/known_hosts", host_key_policy: :strict,
46
44
  log_file: "/var/log/net-connector/router.log") do |device|
47
45
  backup = device.backup(path: "/var/backups/router-running.cfg")
48
- puts "#{backup.bytes} bytes, SHA-256 #{backup.sha256}"
46
+ puts "#{backup.bytes} 字节,SHA-256 #{backup.sha256}"
49
47
  end
50
48
  ```
51
49
 
52
- `open` closes the session even when the block fails. `backup` collects first, then atomically replaces the requested file with mode `0600`. Collection failure leaves an existing file unchanged. It returns `Backup(path:, bytes:, sha256:, collected_at:)`. The caller must create the destination directory and protect backups because running configurations can contain device secrets.
50
+ `open` 会在代码块结束或出错时关闭会话。`backup` 在采集前取得目标路径锁,持有到比较、保存和生成原有 `Backup` 元数据对象完成;采集失败不会覆盖旧备份。同路径争用默认立即抛出 `BackupBusy`(`code: :backup_busy`),需要有限等待可传 `lock_timeout: 2`,单位为秒。直接调用 `backup`;不要在已有 `with_operation` 会话租约内调用,否则会因锁顺序返回 `SessionBusy`。
51
+
52
+ 写入使用 `0600` 临时文件、文件同步、原子替换及父目录同步。替换后同步或收尾失败会抛出 `BackupPersistenceError`,`error.backup` 保留已写文件的元数据,`error.receipt` 区分 `:committed`(持久性未确认)和 `:durable`(同步已完成)。目录同步不受支持对应 `:backup_durability_unsupported`,不会静默报告持久化成功,也不会自动重做设备采集。
53
+
54
+ 调用方应创建并保护备份目录,运行配置可能含有设备凭据。同目录下的 `.net-connector-<摘要>.lock` 是私有的长期锁文件,正常释放不删除它。所有写入者须使用本库的锁协议;`NOFOLLOW` 不保护被外部替换的祖先目录。锁名保守合并大小写及 Unicode 等价写法,实际备份文件名不变;在区分大小写的文件系统上,这些名称也会串行。
53
55
 
54
- ## Native TFTP backup
56
+ ## 设备发起的 TFTP 备份
55
57
 
56
- `tftp_backup` asks the device to send its native configuration directly to a TFTP server. Each vendor supplies its own command and prompt handling. For example, Huawei and H3C use a device file as the source:
58
+ `tftp_backup` 让设备把原生配置直接上传到 TFTP 服务器,命令和提示交互由厂商策略提供。H3C 和华为从设备文件读取源配置,例如:
57
59
 
58
60
  ```ruby
59
61
  Net::Connector.open(:huawei, host: "192.0.2.20", username: ENV.fetch("DEVICE_USERNAME"),
60
62
  password: ENV.fetch("DEVICE_PASSWORD")) do |device|
61
63
  transfer = device.tftp_backup(host: "192.0.2.30", source_file: "flash:/startup.cfg")
62
- puts "Uploaded #{transfer.path} to #{transfer.server}"
64
+ puts "已上传 #{transfer.path} 到 #{transfer.server}"
63
65
  end
64
66
  ```
65
67
 
66
- This emits `tftp 192.0.2.30 put flash:/startup.cfg`; pass `path: "site/switch.cfg"` to choose a different remote filename. H3C discovers its saved startup file with `display startup` unless `source_file:` is given. Huawei requires `source_file:` because its saved file location varies by model. Cisco IOS exports `running-config` using its interactive `copy running-config tftp:` flow. Cisco Nexus 9000 exports `running-config` with `vrf management` by default; pass `vrf: "other-vrf"` to override it. Hillstone exports the saved startup configuration with `export configuration startup to tftp server <server> <filename>`; its separate running-configuration query uses `show configuration running`. Radware Alteon uses `/cfg/ptcfg <server> -tftp`, produces a `.tgz` file, declines private-key export, and answers `mansync` for the internal-index prompt. Palo Alto exports `running-config.xml` and requires its fixed remote filename. All other vendors default the remote filename to `<management IP>.cfg`. The method returns `TftpBackup(server:, path:, completed_at:)` only when the device output confirms a transfer. Explicit device-side transfer failures use `:transfer_failed`; a missing success confirmation uses `:transfer_unconfirmed`. It does not read back the file from the TFTP server. TFTP carries configuration data without encryption; use it only on an appropriate management network.
68
+ 示例会下发 `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`。
69
+
70
+ 只有设备回显确认传输完成,方法才返回 `TftpBackup(server:, path:, completed_at:)`。明确失败对应 `:transfer_failed`,缺少成功证据对应 `:transfer_unconfirmed`。本方法不读取服务器上的文件。TFTP 不加密配置数据,应限制在合适的管理网络中使用。
71
+
72
+ 需要来源和格式时,调用 `device.tftp_backup_receipt(...)`,参数与旧入口相同。返回的不可变 `TftpReceipt` 包含原 `transfer`,以及 `configuration_kind`、`source_file`、`format`、`requested_path`、`actual_path`、`verification` 和 `server_sha256`。当前验证等级为 `:device_reported`,摘要为 `nil`。H3C 自动探测结果标为 `:startup`;H3C/华为显式文件标为 `:saved_file`,格式为 `:unknown`,不凭扩展名推断内容。山石未指定文件名时 `requested_path` 为 `nil`,`actual_path` 保留设备生成名称。
73
+
74
+ 内置策略先校验参数组合,再在同一次会话租约中完成源探测、上传、证据检查和收尾。上传已确认但日志或清理失败时抛出 `TftpCompletionError`,通过 `error.receipt` 和 `error.transfer` 保留完成事实。显式请求与实际路径不同返回 `:transfer_path_mismatch`;实际路径无法安全确认返回 `:transfer_path_unconfirmed`,此时回执的 `actual_path` 和 `transfer.path` 为 `nil`。这些错误不会自动重传;Fleet 保留上传事实并标为 `reported_with_error`。原 `TftpBackup` 的三个成员和构造方式不变。
67
75
 
68
- The runnable [examples/tftp_backup.rb](examples/tftp_backup.rb) reads `DEVICE_VENDOR`, `DEVICE_HOST`, `DEVICE_USERNAME`, `DEVICE_PASSWORD`, and `TFTP_HOST` from the environment. Set `TFTP_SOURCE_FILE` for H3C or Huawei, `TFTP_PATH` for a remote filename, or `TFTP_VRF` to override the NX-OS VRF or Hillstone vrouter.
76
+ 可运行[单设备示例](examples/tftp_backup.rb)。它从环境变量读取 `DEVICE_VENDOR`、`DEVICE_HOST`、`DEVICE_USERNAME`、`DEVICE_PASSWORD` 和 `TFTP_HOST`;`TFTP_SOURCE_FILE`、`TFTP_PATH`、`TFTP_VRF` 分别指定源文件、目标文件和设备 VRF。
69
77
 
70
- For in-memory collection, call `device.running_config`. It returns a `Result`; `result.value!` yields the cleaned configuration or raises its typed error.
78
+ 只需在内存中采集配置时,调用 `device.running_config`;它返回 `Result`,`result.value!` 返回清理后的文本,失败时抛出对应错误。
71
79
 
72
- ## TextFSM parsing
80
+ 配置采集默认屏蔽日志和错误诊断中的配置正文,包括 debug/raw 日志和外部 logger;返回的配置、步骤输出和备份内容保持完整。调用方应按敏感数据保管这些业务结果。
73
81
 
74
- `parse_command` executes one CLI command, then selects a TextFSM template by vendor and command. The bundled index covers Cisco IOS `show ip interface brief` and the CDP/LLDP commands used by topology discovery:
82
+ ## TextFSM 解析
83
+
84
+ `parse_command` 执行一条命令,再按厂商和命令选择 TextFSM 模板。内置索引覆盖 Cisco IOS 的 `show ip interface brief`,以及拓扑发现使用的 CDP/LLDP 命令:
75
85
 
76
86
  ```ruby
77
87
  Net::Connector.open(:cisco_ios, host: "192.0.2.10", username: "admin",
@@ -81,22 +91,24 @@ Net::Connector.open(:cisco_ios, host: "192.0.2.10", username: "admin",
81
91
  end
82
92
  ```
83
93
 
84
- `parse_config` collects the cleaned running configuration and requires an explicit template. The bundled Cisco IOS template extracts interface names and descriptions; it is not a complete configuration model:
94
+ `parse_config` 采集运行配置,要求显式指定模板。内置 Cisco IOS 模板只提取接口名称和描述,并非完整配置模型:
85
95
 
86
96
  ```ruby
87
97
  interfaces = device.parse_config(template: "cisco_ios_running_config_interfaces.textfsm")
88
98
  ```
89
99
 
90
- Both methods return an Array of Hash records using the template's field names. A valid template with no matching records returns `[]`. A missing or invalid template raises `Net::Connector::ParsingError`; a failed device command retains its original connector error. To use other vendors or commands, provide `template: "/path/to/template.textfsm"`, or `template_dir: "/path/to/templates"` with a TextFSM `index` containing `Template, Vendor, Command` columns. Each parse creates a fresh parser, so concurrent device tasks do not share parsing state. Existing local backups can be parsed without connecting to a device:
100
+ 两种方法都返回由模板字段名组成的哈希数组;匹配不到记录时返回 `[]`。模板缺失或无效会抛出 `ParsingError`,设备命令失败则保留原始连接器错误。可用 `template:` 指定外部模板,或用 `template_dir:` 指定含 `index` 的模板目录。每次解析使用独立解析器,批量任务之间不共享状态。已有本地备份也可离线解析:
91
101
 
92
102
  ```ruby
93
103
  saved = Net::Connector::Operations::SavedConfig.new(directory: "/var/backups")
94
104
  rows = saved.parse(host: "192.0.2.10", template: "/path/to/template.textfsm")
95
105
  ```
96
106
 
97
- ## Neighbors and interface descriptions
107
+ 解析默认严格检查 UTF-8 字节,包括终端控制符处理前的原文和处理后的文本;非法字节会抛出 `ParsingError`(`code: :invalid_output_encoding`),不会转成替换文本继续生成记录或拓扑计划。库不猜测或自动转换其他编码,原始结果、备份和离线导出保持原字节。Netdisco 入口及纯文件导出不加载 TextFSM,实际解析时才加载。
108
+
109
+ ## 邻居发现与接口描述
98
110
 
99
- `neighbors` queries CDP on Cisco IOS/NX-OS and LLDP on H3C, H3C wireless, Hillstone, and PAN-OS. It returns records with `local_interface`, `neighbor_name`, `neighbor_interface`, `chassis_id`, and `protocol`. H3C LLDP list output is selected by its column header, covering releases that put the system name first or last. Unknown output raises `ParsingError`; it is not treated as an empty neighbor list. `interface_descriptions` reads the current running configuration, including Alteon port names in a Radware configuration dump.
111
+ `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 则从配置转储读取端口名称。
100
112
 
101
113
  ```ruby
102
114
  Net::Connector.open(:h3c, host: "192.0.2.10", username: ENV.fetch("DEVICE_USERNAME"),
@@ -104,21 +116,27 @@ Net::Connector.open(:h3c, host: "192.0.2.10", username: ENV.fetch("DEVICE_USERNA
104
116
  plan = device.plan_interface_descriptions
105
117
  plan.changes.each { |change| puts "#{change.interface}: #{change.old_description.inspect} -> #{change.new_description.inspect}" }
106
118
  puts plan.commands.join("\n")
107
- # Review the exact commands and obtain operator approval before applying.
119
+ # 核对将要下发的命令,并完成操作审批。
108
120
  result = device.apply_interface_descriptions(plan, confirmed: true)
109
121
  result.value!
110
122
  end
111
123
  ```
112
124
 
113
- The default proposal is `To <neighbor name> <abbreviated neighbor port>`. Abbreviation is enabled and preserves case: `ethernet1/1` becomes `eth1/1`, `Ethernet1/1` becomes `Eth1/1`, and `GigabitEthernet1/0/1` becomes `Gi1/0/1`. Unknown interface forms remain unchanged. Raw neighbors and plan evidence retain their original values; local command interface names are not abbreviated. Use `plan_interface_descriptions(abbreviate: false)` for the previous default, or `lowercase: true` to lowercase the remote port. Pass a block to control the complete description using the original neighbor. The pure common formatter is also available as `Net::Connector::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)`. Planning rejects ambiguous neighbors, missing peer identities, unsafe text, and unrecognized output. Applying requires `confirmed: true` and reads neighbors and old descriptions again; changed evidence raises `:stale_plan`. The plan includes the vendor's save command; PAN-OS uses `commit`. After the script, the operation reads the configuration back and reports `:description_unconfirmed` if the new text is absent. A failed script returns a `Result` with completed steps and its error. Radware Alteon can advertise LLDP on documented versions, but its documented CLI does not provide neighbor discovery; `neighbors` therefore raises `:neighbor_discovery_unsupported`. Its port names remain readable through `interface_descriptions`. Validate commands and output against the specific firmware before using an approved plan on a live device.
125
+ 默认描述为 `To <邻居名称> <邻居接口简称>`。接口缩写默认开启并保留大小写:`ethernet1/1` 变为 `eth1/1`,`Ethernet1/1` 变为 `Eth1/1`,`GigabitEthernet1/0/1` 变为 `Gi1/0/1`;未知形式保持原样。原始邻居记录、计划证据和本机下发接口名不会被缩写。`abbreviate: false` 关闭缩写,`lowercase: true` 才会转为小写;也可传入代码块自行生成完整描述。公共纯函数是 `Net::Connector::InterfaceDescription.format`。
126
+
127
+ 规划会拒绝歧义邻居、缺少对端身份、不安全文本和无法识别的输出。下发必须传 `confirmed: true`,操作会再次读取邻居和旧描述;证据变化在写入前抛出 `DeviceError`,其 `code` 为 `:stale_plan`。IOS/NX-OS、H3C 和山石采用“修改 → 退出配置视图 → 读回 → 保存”的顺序;读回不匹配返回 `:description_unconfirmed`,解析不完整或查询命令不符合审批时也不会保存。
114
128
 
115
- PAN-OS collection checks for pending candidate changes before and after export, and rejects XML or non-set output. This avoids reporting an ambiguous candidate configuration as a running configuration backup. Device-specific CLI and firmware differences still need validation against the target device.
129
+ `plan.commands` 现在包括读回命令,旧版计划必须重新生成、审核;执行仍返回 `Result`,保留修改、读回及保存阶段已经完成的步骤。只有设备提供明确保存完成行才成功,保存超时、失败或只有提示符会返回 `:persistence_unconfirmed`,此时描述可能已生效,不能自动重放或回滚。现场下发前仍应按设备固件核对命令与回显。
116
130
 
117
- ## Scripts and automatic interaction
131
+ PAN-OS 的自动描述计划及改写暂不提供,`supports?(:interface_description_changes)` 为 false,入口在设备 I/O 前抛出 `:candidate_isolation_unavailable`。候选配置的归属、验证及提交需要经目标固件实验确认的独立流程;目前可继续只读查询邻居和描述。Radware 支持读取端口名称,但不提供邻居发现或自动改写,其 `neighbors` 返回 `:neighbor_discovery_unsupported`。
132
+
133
+ PAN-OS 在导出前后检查候选配置差异,并拒绝 XML 或非 set 格式输出,避免将未提交配置误作运行配置。
134
+
135
+ ## 命令脚本与自动交互
118
136
 
119
137
  ```ruby
120
138
  script = Net::Connector::Script.parse(<<~CLI, name: "change-123")
121
- # Comments on their own line are ignored.
139
+ # 独立成行的注释会被忽略。
122
140
  configure terminal
123
141
  interface GigabitEthernet1/0/1
124
142
  description uplink
@@ -126,34 +144,46 @@ script = Net::Connector::Script.parse(<<~CLI, name: "change-123")
126
144
  CLI
127
145
 
128
146
  result = device.execute_script(script) do |step|
129
- puts "#{step.command.text}: #{step.duration.round(2)}s"
147
+ puts "#{step.command.text}: #{step.duration.round(2)} 秒"
130
148
  end
131
149
 
132
150
  if result.failure?
133
- warn "#{result.error.code} at #{result.error.phase}"
134
- warn "#{result.steps.size} commands completed before failure"
151
+ warn "#{result.error.code},阶段 #{result.error.phase}"
152
+ warn "失败前已完成 #{result.steps.size} 条命令"
135
153
  end
136
154
  ```
137
155
 
138
- `execute` sends one command; `execute_script` accepts a `Script` or an array of commands. `Script.load(path)` reads a file. Scripts are validated before any device I/O. A result retains completed steps when a later command fails. The library does not replay commands after failure. `save_config` explicitly sends the vendor's save command when supported; script execution does not save automatically.
156
+ `execute` 执行一条命令;`execute_script` 接收 `Script` 或命令数组;`Script.load(path)` 读取脚本文件。所有命令在设备 I/O 前校验,后续步骤失败时仍保留已完成结果,库不会自动重放命令。`save_config` 显式执行厂商保存命令,普通脚本不会自动保存。
157
+
158
+ 创建连接器时可设置 `max_script_output_bytes: 8 * 1024 * 1024`,限制每个脚本及其追加查询累计收到的原始响应字节;默认 `nil` 保持原有完整输出行为。达到上限后不再发送下一命令,当前响应超过上限时保留刚完成的步骤并返回 `ScriptOutputLimitExceeded`(`code: :script_output_limit_exceeded`)。命令可能已经执行,不会自动重试。原有 `max_output_bytes` 继续限制单次读取响应;累计预算不是进程内存上限,详细计数规则见架构文档。
139
159
 
140
- Vendor profiles cover pager prompts and common confirmation prompts. For a command-specific dialogue, pass `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]` to `execute`. Sensitive commands and responses pause session logging and are redacted in errors. Do not place secrets in ordinary command text unless `sensitive: true` is set.
160
+ 厂商档案处理分页和常见确认提示。特定命令可给 `execute` 传入 `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]`。敏感命令及交互会暂停回显日志并在错误中脱敏;若普通命令文本包含秘密,必须显式标记 `sensitive: true`。
141
161
 
142
- ## Connection settings
162
+ 命令文本安全、输出可能包含秘密时,使用 `device.execute("show running-config", output_sensitive: true)`。该标记保护响应及其准备、后处理、回调异常,保留安全命令文字;不会改写 `Result` 的业务输出。`running_config` 和 `collect_config` 自动启用此保护,手写脚本的默认值仍为 `false`。
143
163
 
144
- `Configuration` accepts `protocol: :ssh` (default) or `:telnet`, `port`, `login_timeout`, `command_timeout`, `write_timeout`, `max_output_bytes`, `log_file`, `logger`, `log_format: :text` or `:raw`, `log_level: :info` (default), and `known_hosts`. Text logs use `ActiveSupport::Logger` and `TaggedLogging`: each line has a local timestamp, severity, device tag, and readable Chinese action. At `:info`, one `log_file` contains connection, login, command, and TFTP outcomes. At `:debug`, that same file also contains sanitized login and device output plus command timing. No separate transcript file is created. `:warn` and `:error` retain only events at those levels or higher. The TFTP example defaults to `:debug`; set `NET_CONNECTOR_LOG_LEVEL` to change it. `log_format: :raw` writes only device bytes to `log_file` without event metadata. Configured credentials and sensitive command interactions are excluded from device output. Inject a Rails logger with `logger: Rails.logger` to send tagged events to the host application; the connector does not close or change its level. Host key policy defaults to `:strict`; `:accept_new` allows first-contact keys, while `:replace` requires an explicit known-hosts file. `telnet_fallback` and `legacy_ssh` are disabled by default and only apply to known connection failures. Commands are passed as argv, without a shell.
164
+ ## 连接与日志设置
145
165
 
146
- Telnet sends credentials without SSH encryption. Enable it only on a trusted management network. The gem does not check device authorization or review change plans; callers must enforce their own operational approval flow.
166
+ `Configuration` 支持 `protocol: :ssh`(默认)或 `:telnet`,以及端口、超时、输出大小、`log_file`、`logger`、`log_format`、`log_level` 和 `known_hosts` 等参数。文本日志使用 Ruby 标准库 `Logger`,记录本地时间、级别、设备标记和中文事件。`:info` 记录连接、登录、命令和 TFTP 结果;`:debug` 还记录脱敏回显与耗时;`:warn`、`:error` 只保留相应级别。`:raw` 文件只写设备字节,不写事件元数据。可注入 `logger: Rails.logger`,连接器不会关闭或修改调用方的日志器。
147
167
 
148
- ## Netdisco inventory and batch backup
168
+ 主机密钥策略默认为 `:strict`;`:accept_new` 接受首次连接的密钥;`:replace` 需要显式 `known_hosts` 文件。`telnet_fallback` 和 `legacy_ssh` 默认关闭,只在已识别的连接失败时使用。外部命令以参数数组执行,不经 shell。Telnet 不提供 SSH 加密,只应在可信管理网络启用。设备授权和变更审批由调用方负责。
149
169
 
150
- `Net::Connector::Netdisco` reads the full Netdisco device inventory, maps supported rows to connector instances, then runs backups with a bounded number of worker threads. Netdisco supplies only inventory fields; device login credentials come from your environment or a resolver you provide. Inventory is fetched and validated before any device session starts. Unsupported, filtered, duplicate, missing-credential, failed, and saved-with-close-error outcomes stay distinct.
170
+ ## Netdisco 清单与批量备份
151
171
 
152
- `Fleet#plan_backup` and `Fleet#plan_tftp_backup` select devices from one validated Netdisco snapshot. Pass the returned plan to `backup_all(plan:)` or `tftp_backup_all(plan:)` so the preview and execution use exactly the same devices. Execution rejects a plan whose tasks or skip results no longer match its inventory. `Netdisco::Worker` runs each selected device independently; an exception or result-callback failure on one device does not stop the others. `batch.summary` reports total, succeeded, failed, partial, skipped, exact status counts, per-device outcomes, and an overall `status` of `succeeded`, `incomplete`, or `no_devices`. A partial result means the device reported a completed backup but closing its session failed. TFTP `reported_uploaded` only means the device reported an upload; it does not verify a file on the server.
172
+ `Net::Connector::Netdisco` 读取并验证完整清单,再将支持的记录映射到连接器,使用有上限的工作线程执行备份。Netdisco 只提供清单字段;设备凭据来自环境变量或调用方提供的解析器。清单会在连接任何设备前完成校验;不支持、被过滤、重复、缺少凭据、失败,以及保存成功但关闭失败的结果分别保留。
153
173
 
154
- Local `backup(path:)` compares the new configuration with the existing file by SHA-256. It reports `:created`, `:changed`, or `:unchanged` through `backup.change`; unchanged files keep their modification time. The `on_change:` callback on `backup_all` runs only after a newly created or changed file is saved, including a saved file whose session later fails to close. `on_start:` and `on_result:` observe each attempted device for either batch method. Callback exceptions are recorded in `batch.callback_errors` without stopping other devices. Each outcome records its start, finish, and duration. TFTP uploads have no `change` value because this library cannot compare the server file; a device-reported upload must not trigger a change notification.
174
+ 每次 `Client#devices` 默认限制单响应 16 MiB、累计响应 128 MiB、去重前 100,000 条记录、10,000 页和 300 秒总期限。认证、分页及兼容查询共用这些预算;默认 HTTP 客户端逐块计数,超限会关闭连接并抛出带稳定 `code` 的 `Client::Error`,Fleet 不会执行半份清单。这些默认值是可调整的设计起点,不是实测容量。旧 `requester: ->(uri, request)` 仍可使用,但只能在回调返回后检查正文和期限,回调自身的阻塞及内存用量由注入方控制。
155
175
 
156
- Each batch writes a private JSON report by default and returns its path in `batch.report_location`. Set `result_store: Net::Connector::Netdisco::ResultStore::Database.new(repository: YourModel)` when the caller owns a database table; the repository must implement `create!(attributes)` for `batch.summary`. Pass `result_store: nil` to handle persistence elsewhere. Report write failures remain visible as `batch.report_error`, and `batch.success?` becomes false without losing device outcomes.
176
+ 推荐使用 HTTPS,标准证书验证保持开启。为兼容既有部署,`allow_insecure_http` 默认仍为 `true`;设为 `false` 可在发请求前拒绝 HTTP,ENV 中对应 `NETDISCO_ALLOW_INSECURE_HTTP=false`。HTTP 会明文传输登录凭据和 API key,迁移时应先提供可验证的 HTTPS 端点。
177
+
178
+ `Fleet#plan_backup` 和 `Fleet#plan_tftp_backup` 从同一份清单生成计划。把计划传给 `backup_all(plan:)` 或 `tftp_backup_all(plan:)`,可使预览与执行选择同一批设备;计划与清单不符时会拒绝执行。单台设备异常或结果回调失败不会阻止其他设备。`batch.summary` 包含总数、成功、失败、部分成功、跳过、具体状态和逐台结果。部分成功包括已保存但关闭失败,以及本地文件已替换但目录同步或收尾失败;后者保留 backup 并标为 `saved_with_error`。TFTP 的 `reported_uploaded` 仅代表设备报告上传,不代表服务器文件已核验。
179
+
180
+ 本地 `backup(path:)` 用 SHA-256 比较新旧配置,`backup.change` 返回 `:created`、`:changed` 或 `:unchanged`;内容未变且权限、文件身份正常时保留修改时间。这不补验历史写入的断电持久性。`backup_all` 的 `on_change:` 仅在新建或更改文件保存后触发;`on_start:` 和 `on_result:` 观察每台已尝试设备。回调异常记录在 `batch.callback_errors`,不丢弃设备结果。每项结果包含开始、结束和耗时。TFTP 无法比较服务器文件,因此没有 `change`,也不触发变更通知。
181
+
182
+ 每批默认写入私有 JSON 报告,路径见 `batch.report_location`。调用方如有数据库仓储,可传 `ResultStore::Database.new(repository: YourModel)`;仓储需实现 `create!(attributes)`。`result_store: nil` 表示由调用方自行持久化。报告失败保留在 `batch.report_error`,同时使 `batch.success?` 为假。若报告已替换但目录同步失败,仍保留位置;离线 `--export --output` 遇到同类错误返回 2,并说明文件已经提交。
183
+
184
+ 默认仍返回原 `Batch` 和原 JSON 字段。显式传 `report_schema: 2` 可获得 `Netdisco::Report`,通过 `report.batch` 访问原批次;`success?` 和 `status` 仍保留严格语义。新报告增加 `policy`、`policy_success`、清单覆盖和受控诊断。任务耗时使用单调时钟,UTC 开始/结束时间独立保留;墙钟调整不会产生负的设备耗时。已有批次可用 `batch.report(policy: :selected)` 创建 v2 视图,不会再次执行设备或重写已有报告。
185
+
186
+ `success_policy: :selected` 显式使用 v2:至少一台设备成功,其他记录只因 `filtered` 或 `sample_limit` 跳过,而且没有部分成功、回调或报告错误时,`report.policy_success?` 才为真。缺少凭据、重复地址、无效地址、未知厂商、目标冲突和未知状态均会阻止成功。`coverage.complete` 只表示每条清单记录都已尝试任务;失败任务也计入尝试,不能据此判断配置已保存。所有设备成功与否仍单独查看 `policy_success` 和逐台结果。自定义 `ResultStore#write(result, directory:)` 签名保持;选择 v2 时传入 Report,其 `summary` 为新格式。
157
187
 
158
188
  ```sh
159
189
  export NETDISCO_URL=https://netdisco.example/netdisco
@@ -183,12 +213,18 @@ end
183
213
  exit 1 unless batch.success?
184
214
  ```
185
215
 
186
- The gem also installs `net-connector-backup`. Its YAML file contains non-secret settings; keep Netdisco and device credentials in environment variables. Environment variables override YAML values. The file is loaded only when `--config FILE` or `NET_CONNECTOR_CONFIG` is set. For example:
216
+ 命令行程序 `net-connector-backup` 的 YAML 文件只允许非敏感设置;Netdisco 和设备凭据留在环境变量中。环境变量优先于 YAML。只有传入 `--config FILE` 或设置 `NET_CONNECTOR_CONFIG` 时才加载文件:
187
217
 
188
218
  ```yaml
189
219
  netdisco:
190
220
  url: https://netdisco.example/netdisco
191
221
  page_size: 500
222
+ max_response_bytes: 16777216
223
+ max_inventory_bytes: 134217728
224
+ max_devices: 100000
225
+ max_pages: 10000
226
+ inventory_timeout: 300
227
+ allow_insecure_http: false
192
228
  backup:
193
229
  directory: /var/backups/network
194
230
  concurrency: 4
@@ -198,6 +234,7 @@ inventory:
198
234
  192.0.2.7: h3c_wireless
199
235
  ssh:
200
236
  host_key_policy: strict
237
+ max_script_output_bytes: null # 可选正整数字节数;null 保持不限制累计值。
201
238
  tftp:
202
239
  server: 192.0.2.10
203
240
  vrfs:
@@ -213,65 +250,73 @@ net-connector-backup --config config.yml --tftp --all
213
250
  net-connector-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
214
251
  ```
215
252
 
216
- `--plan` fetches and validates inventory without logging into devices. `--host` selects one management IP from that inventory. `--tftp` selects up to five devices per vendor by default; `--all` selects every ready device. Local backups select all ready devices unless `--limit-per-vendor` or `backup.limit_per_vendor` is set. `--show-config` prints effective non-secret settings and does not contact Netdisco. The CLI rejects unknown YAML keys, Ruby object tags, and secrets in the YAML schema. `--export IP` reads an existing local `<IP>.txt` backup (or a unique legacy `<device name>-<IP>.txt` file) without contacting Netdisco or the device; it writes the exact contents to stdout, or atomically creates a mode `0600` file when `--output` is set. Treat exported configurations as sensitive.
253
+ `--plan` 只拉取并验证清单;`--host` 选择一个管理地址;`--tftp` 默认每厂商最多选择五台,`--all` 选择所有就绪设备。本地备份默认选择所有就绪设备,可用 `--limit-per-vendor` 限制。`--show-config` 只输出有效的非敏感设置,不访问 Netdisco。CLI 会拒绝未知 YAML 字段、Ruby 对象标签及配置中的凭据。`--export IP` 离线读取已有 `<IP>.txt`,或唯一匹配的旧版 `<设备名>-<IP>.txt`;默认原样写到标准输出,指定 `--output` 后以 `0600` 权限原子写文件。导出的配置仍是敏感数据。
254
+
255
+ CLI 的计划与批次摘要使用 JSON。默认 `--success-policy strict` 保留原规则:非空清单且全部成功、回调及报告正常时为 `0`;空清单或有跳过、部分成功、失败时为 `1`;清单或配置错误为 `2`。`--host` 未在清单中找到也返回 `2`。由于其他清单记录会标记为过滤,默认单主机备份成功时批次退出码仍可能是 `1`;应查看 JSON 中的 `succeeded`、`skipped` 和逐台 `status`。
217
256
 
218
- The CLI prints JSON for plans and batch summaries. Exit status is `0` when a nonempty inventory has only successful outcomes, `1` when the inventory is empty or any outcome was skipped, partial, or failed, and `2` for an inventory or configuration error. A `--host` address absent from the inventory is an error with status `2`. A `--host` run can return `1` when its selected backup succeeds because other inventory rows are reported as filtered; inspect `succeeded`, `skipped`, and per-device `status` in the JSON summary.
257
+ 显式使用 `--success-policy selected` 后,CLI 按上述 selected 规则决定退出码,并自动输出/存储 v2 报告,保留 `status: incomplete` 与跳过计数,另列 `policy_success`。例如 `net-connector-backup --config config.yml --host 192.0.2.7 --success-policy selected`。仅想增加诊断时使用 `--report-schema 2`,成功策略仍为 strict;selected 不能配合 schema 1。策略和报告版本由本次 CLI/API 参数指定,不改变已批准的清单选择,也不触发重试。
219
258
 
220
- For a small live trial, run [examples/netdisco_backup.rb](examples/netdisco_backup.rb). It fetches a validated inventory snapshot and backs up at most three ready devices per mapped vendor by default. Set `NET_CONNECTOR_SAMPLE_PER_VENDOR` to an integer from 1 to 5 to change the sample size. Results and a per-device `summary.json` are written under a unique `examples/backups/<UTC timestamp>-<suffix>/` directory; that directory is Git-ignored. The example uses the same environment variables listed below.
259
+ 小范围现场试运行可用[本地批量示例](examples/netdisco_backup.rb),默认每厂商最多三台;`NET_CONNECTOR_SAMPLE_PER_VENDOR` 可设为 1 至 5。设备发起 TFTP 上传可用[批量 TFTP 示例](examples/netdisco_tftp_backup.rb),默认每厂商最多五台;`NET_CONNECTOR_ALL=1` 才选择全部就绪设备,全量任务默认并发 50。`NET_CONNECTOR_CONCURRENCY` 可覆盖并发数。两类示例将结果和日志写入唯一的 `examples/backups/<UTC 时间戳>-<后缀>/` 目录,该目录不纳入 Git。
221
260
 
222
- For device-initiated TFTP uploads, run [examples/netdisco_tftp_backup.rb](examples/netdisco_tftp_backup.rb) with `NETDISCO_URL`, `TFTP_HOST`, and the credential environment variables below. It samples at most five devices per vendor by default; set `NET_CONNECTOR_ALL=1` to select every ready inventory device. Full runs use 50 simultaneous tasks by default; `NET_CONNECTOR_CONCURRENCY` can override this. The full-run plan is saved as `plan.json`. Remote filenames are unique `<device name>-<IP>.cfg` (`.tgz` for Radware, `.dat` for Hillstone). PAN-OS uses its fixed `running-config.xml` filename: additional Palo Alto devices are reported as `remote_filename_collision` rather than overwriting another backup. PAN-OS requires a positive `Sent ... bytes` response to report upload success. H3C and H3C wireless read `display startup` to find the saved startup file; override the source with `NET_CONNECTOR_H3C_TFTP_SOURCE_FILE` or `NET_CONNECTOR_H3C_WIRELESS_TFTP_SOURCE_FILE`. Huawei defaults to `flash:/startup.cfg` and accepts `NET_CONNECTOR_HUAWEI_TFTP_SOURCE_FILE`. Nexus 9000 uses `management` by default and Hillstone uses `mgt-vr`; set `NET_CONNECTOR_TFTP_VRFS` to a JSON map such as `{"cisco_nxos":"backup","hillstone":"mgt-vr"}` to override either one. Hillstone appends its unique `.dat` filename after the device `vrouter` argument. When calling a Hillstone connector directly without `path:`, the device generates its own filename and the return value reports that filename. `reported_uploaded` means the device reported success. Small runs attempt a server readback; full runs skip per-file readback and mark these files unverified. Run `ruby examples/review_tftp_backup.rb <batch directory>` to keep the original summary and review remaining failures from the session logs. Each `logs/<IP>.log` contains the session events and a final backup result; at debug level it also includes the sanitized login and device output in that same file. Per-device outcomes, incremental `events.jsonl`, and logs are written privately under a unique `examples/backups/<UTC timestamp>-<suffix>/` directory.
261
+ 全量 TFTP 计划保存为 `plan.json`。目标文件名通常为 `<设备名>-<IP>.cfg`,Radware 用 `.tgz`,山石用 `.dat`。计划按实际文件名检查所有厂商的覆盖冲突,包括地址规范化后的重名;保留首个入选目标,其余标记为 `remote_filename_collision`。PAN-OS 固定使用 `running-config.xml`,还要求 `Sent ... bytes` 完成行。H3C 从 `display startup` 发现源文件,可用厂商环境变量覆盖;华为默认 `flash:/startup.cfg`。Nexus 9000 默认 VRF 为 `management`,山石为 `mgt-vr`;`NET_CONNECTOR_TFTP_VRFS` 接受按厂商键配置的 JSON,例如 `{"cisco_nxos":"backup","hillstone":"mgt-vr"}`。山石命令在 `vrouter` 参数后追加唯一的 `.dat` 文件名;直接调用山石连接器且不指定 `path:` 时,由设备生成文件名并在结果中返回。小批次会尝试回读服务器文件;全量任务跳过逐文件回读并标为未验证。可运行 `ruby examples/review_tftp_backup.rb <批次目录>`,根据会话日志复核剩余失败,而不改写原始结果。日志、`events.jsonl` 和逐台结果均以私有权限保存。
223
262
 
224
- Local backups are written to `<backup directory>/<IP>.txt`, using the normalized management IP; `:` in IPv6 addresses becomes `_`. Device renames preserve the filename and comparison baseline. If the canonical file is absent, a unique legacy `<device name>-<IP>.txt` file supplies the comparison baseline and remains untouched. Multiple legacy matches fail explicitly; review them and place the verified current configuration at the canonical path. Canonical files take precedence, and symlinks or non-regular files are rejected. Inventory names remain in result metadata and TFTP filenames. Files are atomically replaced with mode `0600`. The directory is created with mode `0700` if absent. A batch runs in the current process; schedule it with your job runner or cron if you need recurring or durable work. It does not retry a failed device command.
263
+ 批量 TFTP 可用 `NET_CONNECTOR_<VENDOR>_TFTP_SOURCE_FILE` 指定单厂商源文件。`NET_CONNECTOR_H3C_TFTP_SOURCE_FILE` 与 `NET_CONNECTOR_H3C_WIRELESS_TFTP_SOURCE_FILE` 可分别覆盖 H3C 设备的自动发现结果;华为使用 `NET_CONNECTOR_HUAWEI_TFTP_SOURCE_FILE`。源文件是设备上的路径,需符合连接器校验规则。
225
264
 
226
- | Environment variable | Default | Purpose |
265
+ 本地配置备份写到 `<目录>/<IP>.txt`,IPv6 的 `:` 转成 `_`。设备改名不改变文件名或比较基线。若规范文件不存在,唯一匹配的旧版 `<设备名>-<IP>.txt` 可作比较基线但不会被改写;匹配多个旧文件时会明确失败。规范文件优先,符号链接和非普通文件会被拒绝。文件原子替换为 `0600`,新目录权限为 `0700`。批次在当前进程执行,需要定时任务或持久队列时由调用方安排;失败命令不会自动重试。
266
+
267
+ 同批次的旧文件目录最多扫描一次,规范文件全命中时不扫描;新批次重建索引。索引只保留文件名和身份元数据,读取时再次核验;旧文件在快照后消失、替换或修改会返回 `SavedConfigChanged`(`code: :saved_config_changed`),该设备不继续采集。批次中新出现的旧名称文件到下一批才可见,规范文件始终优先。
268
+
269
+ | 环境变量 | 默认值 | 用途 |
227
270
  | --- | --- | --- |
228
- | `NETDISCO_URL` | required | Netdisco base URL, including any tenant path |
229
- | `NET_CONNECTOR_CONFIG` | unset | Explicit non-secret YAML settings file for the CLI |
230
- | `NETDISCO_USERNAME`, `NETDISCO_PASSWORD` | required unless API key is supplied | Inventory API login |
231
- | `NETDISCO_API_KEY` | unset | Use an existing API key instead of login |
232
- | `NETDISCO_PAGE_SIZE` | `500` | Device inventory page size |
233
- | `NET_CONNECTOR_DEVICE_USERNAME`, `NET_CONNECTOR_DEVICE_PASSWORD` | unset | Device login defaults |
234
- | `NET_CONNECTOR_<VENDOR>_USERNAME`, `NET_CONNECTOR_<VENDOR>_PASSWORD` | unset | Override credentials for one connector key, such as `CISCO_IOS` |
235
- | `NET_CONNECTOR_BACKUP_DIRECTORY` | `./backups` | Backup destination and default batch-report directory |
236
- | `NET_CONNECTOR_CONCURRENCY` | `4` | Maximum simultaneous device backups, from 1 to 50 |
237
- | `NET_CONNECTOR_INCLUDE_HOSTS`, `NET_CONNECTOR_EXCLUDE_HOSTS` | unset | Comma-separated management IP filters |
238
- | `NET_CONNECTOR_INCLUDE_VENDORS` | unset | Comma-separated connector keys to include |
239
- | `NET_CONNECTOR_VENDOR_OVERRIDES` | `{}` | JSON object mapping a Netdisco vendor label to a connector key |
240
- | `NET_CONNECTOR_HOST_OVERRIDES` | `{}` | JSON object mapping a management IP to a connector key |
241
- | `NET_CONNECTOR_DEVICE_RULES` | `[]` | JSON array of mapping rules with `vendor`, optional `os` or `model_prefix`, and `connector` |
242
- | `NET_CONNECTOR_PROTOCOL` | `ssh` | Default device protocol; per-vendor override is available |
243
- | `NET_CONNECTOR_KNOWN_HOSTS`, `NET_CONNECTOR_HOST_KEY_POLICY` | system hosts, `strict` | SSH host-key settings |
244
- | `NET_CONNECTOR_LOG_DIRECTORY` | unset | Optional per-device session log directory |
245
- | `NET_CONNECTOR_LOG_LEVEL` | `info` (`debug` in TFTP example) | `debug`, `info`, `warn`, or `error` event detail |
246
- | `NET_CONNECTOR_TFTP_VRFS` | `{}` | JSON map of NX-OS and Hillstone VRF names for the TFTP example |
247
-
248
- Mapping rules are evaluated before vendor-label overrides and built-in rules. A specific rule can distinguish models that share a vendor label:
271
+ | `NETDISCO_URL` | 必填 | Netdisco 服务根地址,可包含租户路径 |
272
+ | `NET_CONNECTOR_CONFIG` | 未设置 | CLI 的非敏感 YAML 配置文件 |
273
+ | `NETDISCO_USERNAME`, `NETDISCO_PASSWORD` | 未提供 API 密钥时必填 | 清单 API 登录 |
274
+ | `NETDISCO_API_KEY` | 未设置 | 直接使用已有 API 密钥 |
275
+ | `NETDISCO_PAGE_SIZE` | `500` | 清单分页大小 |
276
+ | `NETDISCO_MAX_PAGES` | `10000` | 最大分页次数 |
277
+ | `NETDISCO_MAX_RESPONSE_BYTES` | `16777216` | 单次响应正文上限,认证和错误正文也计数 |
278
+ | `NETDISCO_MAX_INVENTORY_BYTES` | `134217728` | 一次清单调用的累计正文上限 |
279
+ | `NETDISCO_MAX_DEVICES` | `100000` | 去重前累计记录上限,兼容查询共用 |
280
+ | `NETDISCO_INVENTORY_TIMEOUT` | `300` | 整次清单调用的有限正数秒数 |
281
+ | `NETDISCO_ALLOW_INSECURE_HTTP` | `true` | 显式设为 `false` 拒绝明文 HTTP |
282
+ | `NET_CONNECTOR_DEVICE_USERNAME`, `NET_CONNECTOR_DEVICE_PASSWORD` | 未设置 | 设备登录默认凭据 |
283
+ | `NET_CONNECTOR_<VENDOR>_USERNAME`, `NET_CONNECTOR_<VENDOR>_PASSWORD` | 未设置 | 单厂商凭据,例如 `CISCO_IOS` |
284
+ | `NET_CONNECTOR_BACKUP_DIRECTORY` | `./backups` | 备份及默认报告目录 |
285
+ | `NET_CONNECTOR_CONCURRENCY` | `4` | 并发设备数,范围 1 至 50 |
286
+ | `NET_CONNECTOR_MAX_SCRIPT_OUTPUT_BYTES` | 未设置 | 每个脚本的累计响应上限;CLI `--max-script-output-bytes N` 优先 |
287
+ | `NET_CONNECTOR_INCLUDE_HOSTS`, `NET_CONNECTOR_EXCLUDE_HOSTS` | 未设置 | 逗号分隔的管理地址过滤器 |
288
+ | `NET_CONNECTOR_INCLUDE_VENDORS` | 未设置 | 逗号分隔的厂商标识过滤器 |
289
+ | `NET_CONNECTOR_VENDOR_OVERRIDES` | `{}` | Netdisco 厂商标签到连接器标识的 JSON 映射 |
290
+ | `NET_CONNECTOR_HOST_OVERRIDES` | `{}` | 管理地址到连接器标识的 JSON 映射 |
291
+ | `NET_CONNECTOR_DEVICE_RULES` | `[]` | 含 `vendor`、可选 `os` 或 `model_prefix` 及 `connector` 的映射规则 |
292
+ | `NET_CONNECTOR_PROTOCOL` | `ssh` | 默认连接协议,可按厂商覆盖 |
293
+ | `NET_CONNECTOR_KNOWN_HOSTS`, `NET_CONNECTOR_HOST_KEY_POLICY` | 系统主机记录、`strict` | SSH 主机密钥设置 |
294
+ | `NET_CONNECTOR_LOG_DIRECTORY` | 未设置 | 逐台会话日志目录 |
295
+ | `NET_CONNECTOR_LOG_LEVEL` | `info`(TFTP 示例为 `debug`) | `debug`、`info`、`warn`、`error` 日志级别 |
296
+ | `NET_CONNECTOR_TFTP_VRFS` | `{}` | NX-OS 和山石的 TFTP VRF 映射 |
297
+ | `NET_CONNECTOR_<VENDOR>_TFTP_SOURCE_FILE` | 按厂商决定 | 批量 TFTP 使用的设备源文件,H3C 可覆盖自动发现结果 |
298
+
299
+ 设备映射规则先于厂商标签覆盖和内置规则执行,可用 `model_prefix` 区分同厂商型号:
249
300
 
250
301
  ```sh
251
302
  export NET_CONNECTOR_DEVICE_RULES='[{"vendor":"Cisco","model_prefix":"N9K","connector":"cisco_nxos"}]'
252
303
  ```
253
304
 
254
- The environment is read when a `Settings` object builds its client or rules, and device credentials are read for each backup task. A new batch can therefore pick up rotated credentials. For per-device secrets managed outside the environment, inject a resolver: `Fleet.new(credentials: ->(device) { { username: "...", password: "..." } })`. To inspect association without connecting, call `fleet.devices`; a ready item can instantiate its connector with `device.connector(username: "...", password: "...")`.
305
+ `Fleet` 每次规划或执行前通过 `Settings#snapshot(mode:)` 固定非敏感设置,包括筛选规则、目录、并发、协议、主机密钥、日志、TFTP 参数和清单预算。执行中修改 ENV 不改变当批策略;再次调用会读取新值。CLI 一次调用的规划与执行共用策略,优先级为 CLI > ENV > YAML > 默认值。`Settings#validate!(mode:)` 复用连接配置及 Planner 的枚举和范围规则;`--show-config` 会拒绝非法设置,`--export` 只验证离线目录,不要求清单地址或认证。
306
+
307
+ 每台任务开始时仍读取设备凭据,`Settings.from_file` 也保留轮换能力。纯策略快照不保存密码、API key 或凭据解析器。需要让多次 API 调用共用策略时,可传 `Settings.new.for_run(mode: :backup)`。自定义 `credentials:` 解析器仍可逐设备返回连接设置,它显式给出的选项优先于批次默认值,由调用方负责一致性;注入的 `client:` 生命周期也由调用方管理。传入 `plan:` 的执行不会重新拉取或筛选已批准清单。`fleet.devices` 可在不连接设备时检查映射。
255
308
 
256
- ## Development
309
+ ## 开发与验证
257
310
 
258
311
  ```sh
259
312
  bundle install
260
313
  script/ci
261
314
  ```
262
315
 
263
- The same command runs in CI on Linux and macOS with Ruby 3.2, 3.3, 3.4 and 4.0.
264
- It scans source and available Git history for sensitive data, runs Ruby and
265
- workflow lint plus the complete test suite, and verifies the built gem through
266
- isolated installation and a real local PTY session. No network device is needed.
267
- The first run downloads checksum-pinned Gitleaks and actionlint binaries.
268
-
269
- `bundle exec rake test` runs tests, `bundle exec rake lint` checks Ruby code,
270
- and `bundle exec rake security:check` scans source and history. The full
271
- pre-release check is also available as `bundle exec rake release:check`.
272
- Builds and redacted scan reports go under ignored `tmp/` directories.
273
-
274
- Keep real credentials in environment variables and local configuration outside
275
- version control. See [verification and sensitive-data policy](docs/VERIFICATION.md)
276
- for scan coverage, dependency policy and `.gitignore`, and
277
- [release instructions](docs/RELEASING.md) for local and GitHub Actions publishing.
316
+ CI 在 Linux 和 macOS 上覆盖 Ruby 3.2、3.3、3.4、4.0。`script/ci` 扫描源码与可用 Git 历史中的敏感数据,执行 Ruby 与工作流 lint、完整测试,并验证已构建 gem 的隔离安装和本地 PTY 烟测;不连接真实网络设备。首次运行需下载固定校验和的 Gitleaks 与 actionlint。
317
+
318
+ `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/` 下。
319
+
320
+ 参与开发见 [CONTRIBUTING.md](CONTRIBUTING.md),漏洞报告见 [SECURITY.md](SECURITY.md)。实现注释和主要文档使用中文,欢迎中文或英文的问题与 PR。
321
+
322
+ 真实凭据应放在环境变量和版本库外的本地配置中。检查范围、依赖政策和忽略规则见[验证文档](docs/VERIFICATION.md),发布流程见[发布文档](docs/RELEASING.md)。
data/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # 安全问题上报
2
+
3
+ 请通过 GitHub 的[私密漏洞报告入口](https://github.com/gatework/net-connector/security/advisories/new)联系维护者。仓库已启用 Private vulnerability reporting;报告内容在协作披露前不会作为公开 Issue 发布。普通功能问题和使用疑问请提交 Issue。
4
+
5
+ 报告中请提供受影响的 gem / Ruby / 操作系统版本、涉及的厂商与固件、最小复现步骤、预期和实际结果,以及可能影响的凭据、文件或设备操作。使用文档地址和虚构凭据重现;不要附上真实密码、令牌、私钥、完整生产配置或未经脱敏的会话日志。维护者会在私密报告中沟通复现、修复和披露安排。
6
+
7
+ 凭据泄露、脱敏绕过、命令注入、主机密钥校验绕过、路径越界,以及中断后遗留设备操作等问题均适合私密报告。若发现已公开的真实凭据,请立即在对应系统撤销或轮换,并在私密报告中提供泄露位置;删除当前文件不能撤销已经分发的秘密。
8
+
9
+ 修复优先面向最新发布版本。请尽可能确认问题是否仍存在于最新版本,并注明首次发现问题的版本;旧版本是否回补在具体报告中评估,不承诺每个历史版本都有维护分支。
10
+
11
+ Please [report vulnerabilities privately](https://github.com/gatework/net-connector/security/advisories/new). Include affected versions and a minimal reproduction with synthetic credentials. Reports in English or Chinese are welcome.