pvectl 0.2.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 (558) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/rules/branch-before-changes.md +52 -0
  3. data/.claude/rules/documentation-updates.md +104 -0
  4. data/.claude/rules/git-workflow.md +84 -0
  5. data/.claude/rules/proxmox-api-docs.md +58 -0
  6. data/.claude/rules/rbs-signatures.md +80 -0
  7. data/.claude/rules/refactoring-as-design-option.md +35 -0
  8. data/.claude/scheduled_tasks.lock +1 -0
  9. data/.claude/settings.json +51 -0
  10. data/.mcp.json +8 -0
  11. data/.ruby-gemset +1 -0
  12. data/.ruby-version +1 -0
  13. data/CHANGELOG.md +138 -0
  14. data/CLAUDE.md +211 -0
  15. data/CODE_OF_CONDUCT.md +132 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +143 -0
  18. data/Rakefile +8 -0
  19. data/docs/proxmox-api-update.sh +96 -0
  20. data/exe/pvectl +5 -0
  21. data/lib/pvectl/argv_preprocessor.rb +334 -0
  22. data/lib/pvectl/cli.rb +102 -0
  23. data/lib/pvectl/commands/apt.rb +389 -0
  24. data/lib/pvectl/commands/clone_container.rb +230 -0
  25. data/lib/pvectl/commands/clone_vm.rb +331 -0
  26. data/lib/pvectl/commands/cloudinit/command.rb +122 -0
  27. data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
  28. data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
  29. data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
  30. data/lib/pvectl/commands/config/command.rb +65 -0
  31. data/lib/pvectl/commands/config/get_contexts.rb +68 -0
  32. data/lib/pvectl/commands/config/set_cluster.rb +103 -0
  33. data/lib/pvectl/commands/config/set_context.rb +136 -0
  34. data/lib/pvectl/commands/config/set_credentials.rb +181 -0
  35. data/lib/pvectl/commands/config/use_context.rb +69 -0
  36. data/lib/pvectl/commands/config/view.rb +67 -0
  37. data/lib/pvectl/commands/console.rb +93 -0
  38. data/lib/pvectl/commands/console_ct.rb +187 -0
  39. data/lib/pvectl/commands/console_vm.rb +187 -0
  40. data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
  41. data/lib/pvectl/commands/create_backup.rb +173 -0
  42. data/lib/pvectl/commands/create_container.rb +141 -0
  43. data/lib/pvectl/commands/create_resource_command.rb +244 -0
  44. data/lib/pvectl/commands/create_snapshot.rb +242 -0
  45. data/lib/pvectl/commands/create_vm.rb +267 -0
  46. data/lib/pvectl/commands/delete_backup.rb +139 -0
  47. data/lib/pvectl/commands/delete_command.rb +119 -0
  48. data/lib/pvectl/commands/delete_container.rb +30 -0
  49. data/lib/pvectl/commands/delete_snapshot.rb +248 -0
  50. data/lib/pvectl/commands/delete_vm.rb +127 -0
  51. data/lib/pvectl/commands/describe/command.rb +251 -0
  52. data/lib/pvectl/commands/edit_container.rb +56 -0
  53. data/lib/pvectl/commands/edit_dns.rb +149 -0
  54. data/lib/pvectl/commands/edit_hosts.rb +135 -0
  55. data/lib/pvectl/commands/edit_node.rb +54 -0
  56. data/lib/pvectl/commands/edit_resource_command.rb +180 -0
  57. data/lib/pvectl/commands/edit_vm.rb +154 -0
  58. data/lib/pvectl/commands/edit_volume.rb +189 -0
  59. data/lib/pvectl/commands/feature_command.rb +230 -0
  60. data/lib/pvectl/commands/feature_container.rb +21 -0
  61. data/lib/pvectl/commands/feature_vm.rb +94 -0
  62. data/lib/pvectl/commands/get/command.rb +360 -0
  63. data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
  64. data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
  65. data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
  66. data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
  67. data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
  68. data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
  69. data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
  70. data/lib/pvectl/commands/get/handlers/services.rb +81 -0
  71. data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
  72. data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
  73. data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
  74. data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
  75. data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
  76. data/lib/pvectl/commands/get/handlers/time.rb +118 -0
  77. data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
  78. data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
  79. data/lib/pvectl/commands/get/resource_handler.rb +63 -0
  80. data/lib/pvectl/commands/get/resource_registry.rb +18 -0
  81. data/lib/pvectl/commands/get/watch_loop.rb +129 -0
  82. data/lib/pvectl/commands/irreversible_command.rb +265 -0
  83. data/lib/pvectl/commands/logs/command.rb +275 -0
  84. data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
  85. data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
  86. data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
  87. data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
  88. data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
  89. data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
  90. data/lib/pvectl/commands/migrate_command.rb +282 -0
  91. data/lib/pvectl/commands/migrate_container.rb +23 -0
  92. data/lib/pvectl/commands/migrate_vm.rb +122 -0
  93. data/lib/pvectl/commands/move_disk_command.rb +239 -0
  94. data/lib/pvectl/commands/move_disk_container.rb +21 -0
  95. data/lib/pvectl/commands/move_disk_vm.rb +127 -0
  96. data/lib/pvectl/commands/ping.rb +249 -0
  97. data/lib/pvectl/commands/pull.rb +342 -0
  98. data/lib/pvectl/commands/push.rb +352 -0
  99. data/lib/pvectl/commands/reset.rb +64 -0
  100. data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
  101. data/lib/pvectl/commands/resource_registry.rb +73 -0
  102. data/lib/pvectl/commands/restart.rb +70 -0
  103. data/lib/pvectl/commands/restart_container.rb +18 -0
  104. data/lib/pvectl/commands/restore_backup.rb +236 -0
  105. data/lib/pvectl/commands/resume.rb +57 -0
  106. data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
  107. data/lib/pvectl/commands/sendkey_vm.rb +205 -0
  108. data/lib/pvectl/commands/service.rb +293 -0
  109. data/lib/pvectl/commands/set_container.rb +50 -0
  110. data/lib/pvectl/commands/set_node.rb +52 -0
  111. data/lib/pvectl/commands/set_resource_command.rb +185 -0
  112. data/lib/pvectl/commands/set_vm.rb +136 -0
  113. data/lib/pvectl/commands/set_volume.rb +212 -0
  114. data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
  115. data/lib/pvectl/commands/shared_flags.rb +155 -0
  116. data/lib/pvectl/commands/shutdown.rb +73 -0
  117. data/lib/pvectl/commands/shutdown_container.rb +18 -0
  118. data/lib/pvectl/commands/start.rb +79 -0
  119. data/lib/pvectl/commands/start_container.rb +18 -0
  120. data/lib/pvectl/commands/stop.rb +75 -0
  121. data/lib/pvectl/commands/stop_container.rb +18 -0
  122. data/lib/pvectl/commands/suspend.rb +64 -0
  123. data/lib/pvectl/commands/template_command.rb +205 -0
  124. data/lib/pvectl/commands/template_container.rb +27 -0
  125. data/lib/pvectl/commands/template_vm.rb +106 -0
  126. data/lib/pvectl/commands/top/command.rb +206 -0
  127. data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
  128. data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
  129. data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
  130. data/lib/pvectl/commands/top/resource_handler.rb +46 -0
  131. data/lib/pvectl/commands/top/resource_registry.rb +22 -0
  132. data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
  133. data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
  134. data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
  135. data/lib/pvectl/config/errors.rb +62 -0
  136. data/lib/pvectl/config/models/cluster.rb +180 -0
  137. data/lib/pvectl/config/models/context.rb +100 -0
  138. data/lib/pvectl/config/models/resolved_config.rb +171 -0
  139. data/lib/pvectl/config/models/user.rb +133 -0
  140. data/lib/pvectl/config/provider.rb +297 -0
  141. data/lib/pvectl/config/service.rb +300 -0
  142. data/lib/pvectl/config/store.rb +161 -0
  143. data/lib/pvectl/config/wizard.rb +309 -0
  144. data/lib/pvectl/config_serializer.rb +1034 -0
  145. data/lib/pvectl/connection/retry_handler.rb +161 -0
  146. data/lib/pvectl/connection.rb +157 -0
  147. data/lib/pvectl/console/terminal_session.rb +449 -0
  148. data/lib/pvectl/editor_session.rb +157 -0
  149. data/lib/pvectl/exit_codes.rb +43 -0
  150. data/lib/pvectl/formatters/base.rb +55 -0
  151. data/lib/pvectl/formatters/color_support.rb +90 -0
  152. data/lib/pvectl/formatters/json.rb +45 -0
  153. data/lib/pvectl/formatters/output_helper.rb +77 -0
  154. data/lib/pvectl/formatters/registry.rb +72 -0
  155. data/lib/pvectl/formatters/table.rb +235 -0
  156. data/lib/pvectl/formatters/wide.rb +93 -0
  157. data/lib/pvectl/formatters/yaml.rb +49 -0
  158. data/lib/pvectl/manifest_serializer.rb +142 -0
  159. data/lib/pvectl/models/apt_package.rb +107 -0
  160. data/lib/pvectl/models/backup.rb +173 -0
  161. data/lib/pvectl/models/base.rb +49 -0
  162. data/lib/pvectl/models/capability.rb +62 -0
  163. data/lib/pvectl/models/container.rb +205 -0
  164. data/lib/pvectl/models/container_operation_result.rb +27 -0
  165. data/lib/pvectl/models/dns_config.rb +54 -0
  166. data/lib/pvectl/models/hosts_file.rb +47 -0
  167. data/lib/pvectl/models/journal_entry.rb +16 -0
  168. data/lib/pvectl/models/network_interface.rb +85 -0
  169. data/lib/pvectl/models/node.rb +195 -0
  170. data/lib/pvectl/models/node_operation_result.rb +45 -0
  171. data/lib/pvectl/models/operation_result.rb +110 -0
  172. data/lib/pvectl/models/physical_disk.rb +193 -0
  173. data/lib/pvectl/models/service.rb +80 -0
  174. data/lib/pvectl/models/snapshot.rb +101 -0
  175. data/lib/pvectl/models/snapshot_description.rb +39 -0
  176. data/lib/pvectl/models/storage.rb +180 -0
  177. data/lib/pvectl/models/subscription.rb +87 -0
  178. data/lib/pvectl/models/syslog_entry.rb +17 -0
  179. data/lib/pvectl/models/task.rb +95 -0
  180. data/lib/pvectl/models/task_entry.rb +52 -0
  181. data/lib/pvectl/models/task_log_line.rb +17 -0
  182. data/lib/pvectl/models/time_config.rb +47 -0
  183. data/lib/pvectl/models/vm.rb +137 -0
  184. data/lib/pvectl/models/vm_operation_result.rb +27 -0
  185. data/lib/pvectl/models/volume.rb +133 -0
  186. data/lib/pvectl/models/volume_operation_result.rb +26 -0
  187. data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
  188. data/lib/pvectl/parsers/disk_config.rb +97 -0
  189. data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
  190. data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
  191. data/lib/pvectl/parsers/net_config.rb +95 -0
  192. data/lib/pvectl/parsers/smart_text.rb +42 -0
  193. data/lib/pvectl/plugin_loader.rb +157 -0
  194. data/lib/pvectl/presenters/apt_package.rb +99 -0
  195. data/lib/pvectl/presenters/backup.rb +128 -0
  196. data/lib/pvectl/presenters/base.rb +283 -0
  197. data/lib/pvectl/presenters/capability.rb +104 -0
  198. data/lib/pvectl/presenters/config/context.rb +80 -0
  199. data/lib/pvectl/presenters/container.rb +574 -0
  200. data/lib/pvectl/presenters/container_operation_result.rb +109 -0
  201. data/lib/pvectl/presenters/disk.rb +184 -0
  202. data/lib/pvectl/presenters/dns_config.rb +68 -0
  203. data/lib/pvectl/presenters/hosts_file.rb +61 -0
  204. data/lib/pvectl/presenters/journal_entry.rb +20 -0
  205. data/lib/pvectl/presenters/node.rb +762 -0
  206. data/lib/pvectl/presenters/node_operation_result.rb +50 -0
  207. data/lib/pvectl/presenters/operation_result.rb +61 -0
  208. data/lib/pvectl/presenters/service.rb +76 -0
  209. data/lib/pvectl/presenters/snapshot.rb +239 -0
  210. data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
  211. data/lib/pvectl/presenters/storage.rb +329 -0
  212. data/lib/pvectl/presenters/subscription.rb +189 -0
  213. data/lib/pvectl/presenters/syslog_entry.rb +20 -0
  214. data/lib/pvectl/presenters/task_entry.rb +69 -0
  215. data/lib/pvectl/presenters/task_log_line.rb +20 -0
  216. data/lib/pvectl/presenters/template.rb +76 -0
  217. data/lib/pvectl/presenters/time_config.rb +86 -0
  218. data/lib/pvectl/presenters/top_container.rb +112 -0
  219. data/lib/pvectl/presenters/top_node.rb +115 -0
  220. data/lib/pvectl/presenters/top_presenter.rb +59 -0
  221. data/lib/pvectl/presenters/top_vm.rb +105 -0
  222. data/lib/pvectl/presenters/vm.rb +853 -0
  223. data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
  224. data/lib/pvectl/presenters/volume.rb +136 -0
  225. data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
  226. data/lib/pvectl/repositories/apt.rb +93 -0
  227. data/lib/pvectl/repositories/backup.rb +186 -0
  228. data/lib/pvectl/repositories/base.rb +110 -0
  229. data/lib/pvectl/repositories/capabilities.rb +96 -0
  230. data/lib/pvectl/repositories/container.rb +503 -0
  231. data/lib/pvectl/repositories/disk.rb +87 -0
  232. data/lib/pvectl/repositories/dns.rb +54 -0
  233. data/lib/pvectl/repositories/hosts.rb +63 -0
  234. data/lib/pvectl/repositories/journal.rb +23 -0
  235. data/lib/pvectl/repositories/node.rb +537 -0
  236. data/lib/pvectl/repositories/service.rb +139 -0
  237. data/lib/pvectl/repositories/snapshot.rb +133 -0
  238. data/lib/pvectl/repositories/storage.rb +302 -0
  239. data/lib/pvectl/repositories/subscription.rb +77 -0
  240. data/lib/pvectl/repositories/syslog.rb +25 -0
  241. data/lib/pvectl/repositories/task.rb +82 -0
  242. data/lib/pvectl/repositories/task_list.rb +30 -0
  243. data/lib/pvectl/repositories/task_log.rb +31 -0
  244. data/lib/pvectl/repositories/time_config.rb +53 -0
  245. data/lib/pvectl/repositories/vm.rb +616 -0
  246. data/lib/pvectl/repositories/volume.rb +306 -0
  247. data/lib/pvectl/selectors/base.rb +201 -0
  248. data/lib/pvectl/selectors/container.rb +116 -0
  249. data/lib/pvectl/selectors/disk.rb +59 -0
  250. data/lib/pvectl/selectors/vm.rb +116 -0
  251. data/lib/pvectl/selectors/volume.rb +59 -0
  252. data/lib/pvectl/services/backup.rb +209 -0
  253. data/lib/pvectl/services/clone_container.rb +260 -0
  254. data/lib/pvectl/services/clone_vm.rb +265 -0
  255. data/lib/pvectl/services/cloudinit.rb +96 -0
  256. data/lib/pvectl/services/console.rb +152 -0
  257. data/lib/pvectl/services/container_lifecycle.rb +124 -0
  258. data/lib/pvectl/services/create_container.rb +179 -0
  259. data/lib/pvectl/services/create_vm.rb +191 -0
  260. data/lib/pvectl/services/edit_container.rb +125 -0
  261. data/lib/pvectl/services/edit_dns.rb +159 -0
  262. data/lib/pvectl/services/edit_hosts.rb +78 -0
  263. data/lib/pvectl/services/edit_node.rb +147 -0
  264. data/lib/pvectl/services/edit_vm.rb +125 -0
  265. data/lib/pvectl/services/edit_volume.rb +224 -0
  266. data/lib/pvectl/services/get/resource_service.rb +98 -0
  267. data/lib/pvectl/services/move_disk.rb +132 -0
  268. data/lib/pvectl/services/pull_config.rb +94 -0
  269. data/lib/pvectl/services/push_config.rb +524 -0
  270. data/lib/pvectl/services/resize_volume.rb +253 -0
  271. data/lib/pvectl/services/resource_delete.rb +169 -0
  272. data/lib/pvectl/services/resource_migration.rb +170 -0
  273. data/lib/pvectl/services/sendkey.rb +108 -0
  274. data/lib/pvectl/services/service_lifecycle.rb +89 -0
  275. data/lib/pvectl/services/set_container.rb +128 -0
  276. data/lib/pvectl/services/set_node.rb +236 -0
  277. data/lib/pvectl/services/set_vm.rb +128 -0
  278. data/lib/pvectl/services/set_volume.rb +126 -0
  279. data/lib/pvectl/services/snapshot.rb +261 -0
  280. data/lib/pvectl/services/task_listing.rb +75 -0
  281. data/lib/pvectl/services/unlink_disk.rb +86 -0
  282. data/lib/pvectl/services/vm_lifecycle.rb +124 -0
  283. data/lib/pvectl/services/wakeonlan.rb +79 -0
  284. data/lib/pvectl/utils/resource_resolver.rb +80 -0
  285. data/lib/pvectl/version.rb +13 -0
  286. data/lib/pvectl/wizards/create_container.rb +105 -0
  287. data/lib/pvectl/wizards/create_vm.rb +98 -0
  288. data/lib/pvectl.rb +439 -0
  289. data/sig/external/gli.rbs +16 -0
  290. data/sig/external/proxmox_api.rbs +10 -0
  291. data/sig/pvectl/argv_preprocessor.rbs +53 -0
  292. data/sig/pvectl/cli.rbs +26 -0
  293. data/sig/pvectl/commands/apt.rbs +47 -0
  294. data/sig/pvectl/commands/clone_container.rbs +31 -0
  295. data/sig/pvectl/commands/clone_vm.rbs +33 -0
  296. data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
  297. data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
  298. data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
  299. data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
  300. data/sig/pvectl/commands/config/command.rbs +9 -0
  301. data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
  302. data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
  303. data/sig/pvectl/commands/config/set_context.rbs +15 -0
  304. data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
  305. data/sig/pvectl/commands/config/use_context.rbs +11 -0
  306. data/sig/pvectl/commands/config/view.rbs +11 -0
  307. data/sig/pvectl/commands/console.rbs +9 -0
  308. data/sig/pvectl/commands/console_ct.rbs +27 -0
  309. data/sig/pvectl/commands/console_vm.rbs +27 -0
  310. data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
  311. data/sig/pvectl/commands/create_backup.rbs +29 -0
  312. data/sig/pvectl/commands/create_container.rbs +30 -0
  313. data/sig/pvectl/commands/create_resource_command.rbs +53 -0
  314. data/sig/pvectl/commands/create_snapshot.rbs +35 -0
  315. data/sig/pvectl/commands/create_vm.rbs +30 -0
  316. data/sig/pvectl/commands/delete_backup.rbs +25 -0
  317. data/sig/pvectl/commands/delete_command.rbs +45 -0
  318. data/sig/pvectl/commands/delete_container.rbs +11 -0
  319. data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
  320. data/sig/pvectl/commands/delete_vm.rbs +13 -0
  321. data/sig/pvectl/commands/describe/command.rbs +27 -0
  322. data/sig/pvectl/commands/edit_container.rbs +17 -0
  323. data/sig/pvectl/commands/edit_dns.rbs +25 -0
  324. data/sig/pvectl/commands/edit_hosts.rbs +23 -0
  325. data/sig/pvectl/commands/edit_node.rbs +17 -0
  326. data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
  327. data/sig/pvectl/commands/edit_vm.rbs +19 -0
  328. data/sig/pvectl/commands/edit_volume.rbs +24 -0
  329. data/sig/pvectl/commands/feature_command.rbs +43 -0
  330. data/sig/pvectl/commands/feature_container.rbs +10 -0
  331. data/sig/pvectl/commands/feature_vm.rbs +12 -0
  332. data/sig/pvectl/commands/get/command.rbs +42 -0
  333. data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
  334. data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
  335. data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
  336. data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
  337. data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
  338. data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
  339. data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
  340. data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
  341. data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
  342. data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
  343. data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
  344. data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
  345. data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
  346. data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
  347. data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
  348. data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
  349. data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
  350. data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
  351. data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
  352. data/sig/pvectl/commands/irreversible_command.rbs +32 -0
  353. data/sig/pvectl/commands/logs/command.rbs +35 -0
  354. data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
  355. data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
  356. data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
  357. data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
  358. data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
  359. data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
  360. data/sig/pvectl/commands/migrate_command.rbs +45 -0
  361. data/sig/pvectl/commands/migrate_container.rbs +11 -0
  362. data/sig/pvectl/commands/migrate_vm.rbs +13 -0
  363. data/sig/pvectl/commands/move_disk_command.rbs +43 -0
  364. data/sig/pvectl/commands/move_disk_container.rbs +11 -0
  365. data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
  366. data/sig/pvectl/commands/ping.rbs +39 -0
  367. data/sig/pvectl/commands/pull.rbs +33 -0
  368. data/sig/pvectl/commands/push.rbs +32 -0
  369. data/sig/pvectl/commands/reset.rbs +11 -0
  370. data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
  371. data/sig/pvectl/commands/resource_registry.rbs +19 -0
  372. data/sig/pvectl/commands/restart.rbs +11 -0
  373. data/sig/pvectl/commands/restart_container.rbs +9 -0
  374. data/sig/pvectl/commands/restore_backup.rbs +27 -0
  375. data/sig/pvectl/commands/resume.rbs +11 -0
  376. data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
  377. data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
  378. data/sig/pvectl/commands/service.rbs +38 -0
  379. data/sig/pvectl/commands/set_container.rbs +13 -0
  380. data/sig/pvectl/commands/set_node.rbs +13 -0
  381. data/sig/pvectl/commands/set_resource_command.rbs +25 -0
  382. data/sig/pvectl/commands/set_vm.rbs +15 -0
  383. data/sig/pvectl/commands/set_volume.rbs +24 -0
  384. data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
  385. data/sig/pvectl/commands/shared_flags.rbs +10 -0
  386. data/sig/pvectl/commands/shutdown.rbs +11 -0
  387. data/sig/pvectl/commands/shutdown_container.rbs +9 -0
  388. data/sig/pvectl/commands/start.rbs +11 -0
  389. data/sig/pvectl/commands/start_container.rbs +9 -0
  390. data/sig/pvectl/commands/stop.rbs +11 -0
  391. data/sig/pvectl/commands/stop_container.rbs +9 -0
  392. data/sig/pvectl/commands/suspend.rbs +11 -0
  393. data/sig/pvectl/commands/template_command.rbs +21 -0
  394. data/sig/pvectl/commands/template_container.rbs +10 -0
  395. data/sig/pvectl/commands/template_vm.rbs +12 -0
  396. data/sig/pvectl/commands/top/command.rbs +31 -0
  397. data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
  398. data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
  399. data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
  400. data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
  401. data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
  402. data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
  403. data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
  404. data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
  405. data/sig/pvectl/config/errors.rbs +24 -0
  406. data/sig/pvectl/config/models/cluster.rbs +39 -0
  407. data/sig/pvectl/config/models/context.rbs +23 -0
  408. data/sig/pvectl/config/models/resolved_config.rbs +51 -0
  409. data/sig/pvectl/config/models/user.rbs +31 -0
  410. data/sig/pvectl/config/provider.rbs +40 -0
  411. data/sig/pvectl/config/service.rbs +65 -0
  412. data/sig/pvectl/config/store.rbs +14 -0
  413. data/sig/pvectl/config/wizard.rbs +48 -0
  414. data/sig/pvectl/config_serializer.rbs +121 -0
  415. data/sig/pvectl/connection/retry_handler.rbs +31 -0
  416. data/sig/pvectl/connection.rbs +35 -0
  417. data/sig/pvectl/console/terminal_session.rbs +63 -0
  418. data/sig/pvectl/editor_session.rbs +33 -0
  419. data/sig/pvectl/exit_codes.rbs +19 -0
  420. data/sig/pvectl/formatters/base.rbs +13 -0
  421. data/sig/pvectl/formatters/color_support.rbs +13 -0
  422. data/sig/pvectl/formatters/json.rbs +7 -0
  423. data/sig/pvectl/formatters/output_helper.rbs +9 -0
  424. data/sig/pvectl/formatters/registry.rbs +13 -0
  425. data/sig/pvectl/formatters/table.rbs +25 -0
  426. data/sig/pvectl/formatters/wide.rbs +15 -0
  427. data/sig/pvectl/formatters/yaml.rbs +7 -0
  428. data/sig/pvectl/manifest_serializer.rbs +18 -0
  429. data/sig/pvectl/models/apt_package.rbs +26 -0
  430. data/sig/pvectl/models/backup.rbs +31 -0
  431. data/sig/pvectl/models/base.rbs +11 -0
  432. data/sig/pvectl/models/capability.rbs +16 -0
  433. data/sig/pvectl/models/container.rbs +44 -0
  434. data/sig/pvectl/models/container_operation_result.rbs +9 -0
  435. data/sig/pvectl/models/dns_config.rbs +15 -0
  436. data/sig/pvectl/models/hosts_file.rbs +13 -0
  437. data/sig/pvectl/models/journal_entry.rbs +10 -0
  438. data/sig/pvectl/models/network_interface.rbs +20 -0
  439. data/sig/pvectl/models/node.rbs +47 -0
  440. data/sig/pvectl/models/node_operation_result.rbs +12 -0
  441. data/sig/pvectl/models/operation_result.rbs +21 -0
  442. data/sig/pvectl/models/physical_disk.rbs +35 -0
  443. data/sig/pvectl/models/service.rbs +18 -0
  444. data/sig/pvectl/models/snapshot.rbs +21 -0
  445. data/sig/pvectl/models/snapshot_description.rbs +18 -0
  446. data/sig/pvectl/models/storage.rbs +39 -0
  447. data/sig/pvectl/models/subscription.rbs +24 -0
  448. data/sig/pvectl/models/syslog_entry.rbs +10 -0
  449. data/sig/pvectl/models/task.rbs +22 -0
  450. data/sig/pvectl/models/task_entry.rbs +24 -0
  451. data/sig/pvectl/models/task_log_line.rbs +10 -0
  452. data/sig/pvectl/models/time_config.rbs +12 -0
  453. data/sig/pvectl/models/vm.rbs +32 -0
  454. data/sig/pvectl/models/vm_operation_result.rbs +9 -0
  455. data/sig/pvectl/models/volume.rbs +29 -0
  456. data/sig/pvectl/models/volume_operation_result.rbs +9 -0
  457. data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
  458. data/sig/pvectl/parsers/disk_config.rbs +19 -0
  459. data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
  460. data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
  461. data/sig/pvectl/parsers/net_config.rbs +19 -0
  462. data/sig/pvectl/parsers/smart_text.rbs +7 -0
  463. data/sig/pvectl/plugin_loader.rbs +25 -0
  464. data/sig/pvectl/presenters/apt_package.rbs +19 -0
  465. data/sig/pvectl/presenters/backup.rbs +25 -0
  466. data/sig/pvectl/presenters/base.rbs +41 -0
  467. data/sig/pvectl/presenters/capability.rbs +19 -0
  468. data/sig/pvectl/presenters/config/context.rbs +17 -0
  469. data/sig/pvectl/presenters/container.rbs +78 -0
  470. data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
  471. data/sig/pvectl/presenters/disk.rbs +31 -0
  472. data/sig/pvectl/presenters/dns_config.rbs +13 -0
  473. data/sig/pvectl/presenters/hosts_file.rbs +13 -0
  474. data/sig/pvectl/presenters/journal_entry.rbs +11 -0
  475. data/sig/pvectl/presenters/node.rbs +118 -0
  476. data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
  477. data/sig/pvectl/presenters/operation_result.rbs +17 -0
  478. data/sig/pvectl/presenters/service.rbs +15 -0
  479. data/sig/pvectl/presenters/snapshot.rbs +35 -0
  480. data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
  481. data/sig/pvectl/presenters/storage.rbs +59 -0
  482. data/sig/pvectl/presenters/subscription.rbs +36 -0
  483. data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
  484. data/sig/pvectl/presenters/task_entry.rbs +21 -0
  485. data/sig/pvectl/presenters/task_log_line.rbs +11 -0
  486. data/sig/pvectl/presenters/template.rbs +15 -0
  487. data/sig/pvectl/presenters/time_config.rbs +19 -0
  488. data/sig/pvectl/presenters/top_container.rbs +17 -0
  489. data/sig/pvectl/presenters/top_node.rbs +17 -0
  490. data/sig/pvectl/presenters/top_presenter.rbs +13 -0
  491. data/sig/pvectl/presenters/top_vm.rbs +17 -0
  492. data/sig/pvectl/presenters/vm.rbs +91 -0
  493. data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
  494. data/sig/pvectl/presenters/volume.rbs +23 -0
  495. data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
  496. data/sig/pvectl/repositories/apt.rbs +17 -0
  497. data/sig/pvectl/repositories/backup.rbs +27 -0
  498. data/sig/pvectl/repositories/base.rbs +23 -0
  499. data/sig/pvectl/repositories/capabilities.rbs +20 -0
  500. data/sig/pvectl/repositories/container.rbs +63 -0
  501. data/sig/pvectl/repositories/disk.rbs +17 -0
  502. data/sig/pvectl/repositories/dns.rbs +13 -0
  503. data/sig/pvectl/repositories/hosts.rbs +13 -0
  504. data/sig/pvectl/repositories/journal.rbs +7 -0
  505. data/sig/pvectl/repositories/node.rbs +68 -0
  506. data/sig/pvectl/repositories/service.rbs +27 -0
  507. data/sig/pvectl/repositories/snapshot.rbs +19 -0
  508. data/sig/pvectl/repositories/storage.rbs +37 -0
  509. data/sig/pvectl/repositories/subscription.rbs +17 -0
  510. data/sig/pvectl/repositories/syslog.rbs +7 -0
  511. data/sig/pvectl/repositories/task.rbs +19 -0
  512. data/sig/pvectl/repositories/task_list.rbs +7 -0
  513. data/sig/pvectl/repositories/task_log.rbs +11 -0
  514. data/sig/pvectl/repositories/time_config.rbs +13 -0
  515. data/sig/pvectl/repositories/vm.rbs +85 -0
  516. data/sig/pvectl/repositories/volume.rbs +43 -0
  517. data/sig/pvectl/selectors/base.rbs +37 -0
  518. data/sig/pvectl/selectors/container.rbs +19 -0
  519. data/sig/pvectl/selectors/disk.rbs +13 -0
  520. data/sig/pvectl/selectors/vm.rbs +19 -0
  521. data/sig/pvectl/selectors/volume.rbs +13 -0
  522. data/sig/pvectl/services/backup.rbs +27 -0
  523. data/sig/pvectl/services/clone_container.rbs +35 -0
  524. data/sig/pvectl/services/clone_vm.rbs +35 -0
  525. data/sig/pvectl/services/cloudinit.rbs +19 -0
  526. data/sig/pvectl/services/console.rbs +23 -0
  527. data/sig/pvectl/services/container_lifecycle.rbs +26 -0
  528. data/sig/pvectl/services/create_container.rbs +64 -0
  529. data/sig/pvectl/services/create_vm.rbs +72 -0
  530. data/sig/pvectl/services/edit_container.rbs +17 -0
  531. data/sig/pvectl/services/edit_dns.rbs +23 -0
  532. data/sig/pvectl/services/edit_hosts.rbs +13 -0
  533. data/sig/pvectl/services/edit_node.rbs +21 -0
  534. data/sig/pvectl/services/edit_vm.rbs +17 -0
  535. data/sig/pvectl/services/edit_volume.rbs +18 -0
  536. data/sig/pvectl/services/get/resource_service.rbs +23 -0
  537. data/sig/pvectl/services/move_disk.rbs +21 -0
  538. data/sig/pvectl/services/pull_config.rbs +18 -0
  539. data/sig/pvectl/services/push_config.rbs +37 -0
  540. data/sig/pvectl/services/resize_volume.rbs +47 -0
  541. data/sig/pvectl/services/resource_delete.rbs +27 -0
  542. data/sig/pvectl/services/resource_migration.rbs +29 -0
  543. data/sig/pvectl/services/sendkey.rbs +19 -0
  544. data/sig/pvectl/services/service_lifecycle.rbs +17 -0
  545. data/sig/pvectl/services/set_container.rbs +14 -0
  546. data/sig/pvectl/services/set_node.rbs +23 -0
  547. data/sig/pvectl/services/set_vm.rbs +14 -0
  548. data/sig/pvectl/services/set_volume.rbs +12 -0
  549. data/sig/pvectl/services/snapshot.rbs +35 -0
  550. data/sig/pvectl/services/task_listing.rbs +13 -0
  551. data/sig/pvectl/services/unlink_disk.rbs +17 -0
  552. data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
  553. data/sig/pvectl/services/wakeonlan.rbs +17 -0
  554. data/sig/pvectl/utils/resource_resolver.rbs +17 -0
  555. data/sig/pvectl/wizards/create_container.rbs +21 -0
  556. data/sig/pvectl/wizards/create_vm.rbs +21 -0
  557. data/sig/pvectl.rbs +9 -0
  558. metadata +675 -0
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for per-node DNS resolver settings.
6
+ #
7
+ # Uses the `/nodes/{node}/dns` endpoint to fetch and update
8
+ # the DNS configuration of a single node.
9
+ #
10
+ # @example Fetching DNS config
11
+ # repo = Dns.new(connection)
12
+ # dns = repo.fetch("pve1")
13
+ # dns.servers #=> ["8.8.8.8"]
14
+ #
15
+ # @example Updating DNS config
16
+ # repo.update("pve1", search: "example.com", dns1: "8.8.8.8")
17
+ #
18
+ # @see Pvectl::Models::DnsConfig DNS configuration model
19
+ #
20
+ class Dns < Base
21
+ # Fetches the DNS configuration for a node.
22
+ #
23
+ # @param node_name [String] node name
24
+ # @return [Models::DnsConfig] DNS configuration model
25
+ def fetch(node_name)
26
+ response = connection.client["nodes/#{node_name}/dns"].get
27
+ data = extract_data(response) || {}
28
+ build_model(data.merge(node: node_name))
29
+ end
30
+
31
+ # Updates the DNS configuration for a node.
32
+ #
33
+ # The Proxmox PUT endpoint requires `search` and accepts optional
34
+ # `dns1`, `dns2`, `dns3`. Only provided keys are sent.
35
+ #
36
+ # @param node_name [String] node name
37
+ # @param params [Hash] update parameters (:search, :dns1, :dns2, :dns3)
38
+ # @return [void]
39
+ def update(node_name, params = {})
40
+ connection.client["nodes/#{node_name}/dns"].put(params)
41
+ end
42
+
43
+ protected
44
+
45
+ # Builds DnsConfig model from API data.
46
+ #
47
+ # @param data [Hash] API response hash (must include :node key for context)
48
+ # @return [Models::DnsConfig] DNS configuration model
49
+ def build_model(data)
50
+ Models::DnsConfig.new(data)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for per-node /etc/hosts file contents.
6
+ #
7
+ # Uses the `/nodes/{node}/hosts` endpoint to fetch and update
8
+ # the raw contents of /etc/hosts on a single node.
9
+ #
10
+ # The Proxmox API returns a {data, digest} pair. The digest is used
11
+ # for optimistic concurrency control — the same digest must be sent
12
+ # back with the POST update.
13
+ #
14
+ # @example Fetching hosts file
15
+ # repo = Hosts.new(connection)
16
+ # hosts = repo.fetch("pve1")
17
+ # hosts.data #=> "127.0.0.1 localhost\n..."
18
+ # hosts.digest #=> "abc123def..."
19
+ #
20
+ # @example Updating hosts file with digest
21
+ # repo.update("pve1", "127.0.0.1 localhost\n", "abc123def...")
22
+ #
23
+ # @see Pvectl::Models::HostsFile HostsFile model
24
+ #
25
+ class Hosts < Base
26
+ # Fetches the /etc/hosts contents for a node.
27
+ #
28
+ # @param node_name [String] node name
29
+ # @return [Models::HostsFile] hosts file model with data and digest
30
+ def fetch(node_name)
31
+ response = connection.client["nodes/#{node_name}/hosts"].get
32
+ data = extract_data(response) || {}
33
+ build_model(data.merge(node: node_name))
34
+ end
35
+
36
+ # Updates the /etc/hosts contents for a node.
37
+ #
38
+ # The Proxmox POST endpoint requires `data` and optionally accepts
39
+ # `digest` for optimistic locking. If the on-server digest no longer
40
+ # matches, Proxmox returns an error which is propagated verbatim.
41
+ #
42
+ # @param node_name [String] node name
43
+ # @param data [String] new /etc/hosts content
44
+ # @param digest [String, nil] digest from prior fetch (optional but recommended)
45
+ # @return [void]
46
+ def update(node_name, data, digest = nil)
47
+ params = { data: data }
48
+ params[:digest] = digest if digest && !digest.empty?
49
+ connection.client["nodes/#{node_name}/hosts"].post(params)
50
+ end
51
+
52
+ protected
53
+
54
+ # Builds HostsFile model from API data.
55
+ #
56
+ # @param data [Hash] API response hash (must include :node key for context)
57
+ # @return [Models::HostsFile] hosts file model
58
+ def build_model(data)
59
+ Models::HostsFile.new(data)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for reading node systemd journal.
6
+ # Uses GET /nodes/{node}/journal endpoint.
7
+ class Journal < Base
8
+ # @param node [String] node name (required)
9
+ # @param last_entries [Integer] number of recent entries (default 50)
10
+ # @param since [Integer, nil] start time (epoch)
11
+ # @param until_time [Integer, nil] end time (epoch)
12
+ # @return [Array<Models::JournalEntry>]
13
+ def list(node:, last_entries: 50, since: nil, until_time: nil)
14
+ params = { lastentries: last_entries }
15
+ params[:since] = since if since
16
+ params[:until] = until_time if until_time
17
+
18
+ response = connection.client["nodes/#{node}/journal"].get(params: params)
19
+ models_from(response, Models::JournalEntry)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,537 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_list"
4
+
5
+ module Pvectl
6
+ module Repositories
7
+ # Repository for Proxmox cluster nodes.
8
+ #
9
+ # Uses the `/nodes` API endpoint to list nodes.
10
+ # Optionally fetches additional details from per-node endpoints.
11
+ #
12
+ # @example Listing all nodes
13
+ # repo = Node.new(connection)
14
+ # nodes = repo.list
15
+ # nodes.each { |n| puts "#{n.name}: #{n.status}" }
16
+ #
17
+ # @example Getting node with extended details
18
+ # node = repo.get("pve-node1", include_details: true)
19
+ #
20
+ # @see Pvectl::Models::Node Node model
21
+ # @see Pvectl::Connection API connection
22
+ #
23
+ class Node < Base
24
+ # Creates a new Node repository.
25
+ #
26
+ # @param connection [Connection] API connection
27
+ # @param storage_repository [Repositories::Storage, nil] optional storage repository for DI
28
+ def initialize(connection, storage_repository: nil)
29
+ super(connection)
30
+ @storage_repository = storage_repository
31
+ end
32
+
33
+ # Lists all nodes in the cluster.
34
+ #
35
+ # @param include_details [Boolean] fetch version/status details (extra API calls)
36
+ # @return [Array<Models::Node>] collection of Node models
37
+ def list(include_details: false)
38
+ response = connection.client["nodes"].get
39
+ nodes_data = unwrap(response)
40
+
41
+ # Get guest counts from cluster/resources
42
+ guest_counts = guest_counts_for_cluster
43
+
44
+ nodes_data.map do |data|
45
+ node_name = data[:node] || data[:name]
46
+
47
+ # Merge guest counts
48
+ data = data.merge(
49
+ guests_vms: guest_counts.dig(node_name, :vms) || 0,
50
+ guests_cts: guest_counts.dig(node_name, :cts) || 0
51
+ )
52
+
53
+ # Fetch extended details if requested
54
+ if include_details && (data[:status] == "online")
55
+ data = data.merge(details_for(node_name))
56
+ end
57
+
58
+ build_model(data)
59
+ end
60
+ end
61
+
62
+ # Gets a single node by name.
63
+ #
64
+ # @param name [String] node name
65
+ # @param include_details [Boolean] fetch version/status details
66
+ # @return [Models::Node, nil] Node model or nil if not found
67
+ def get(name, include_details: false)
68
+ list(include_details: include_details).find { |n| n.name == name }
69
+ end
70
+
71
+ # Describes a node with comprehensive details from multiple API endpoints.
72
+ #
73
+ # @param name [String] node name
74
+ # @return [Models::Node, nil] Node model with full details, or nil if not found
75
+ def describe(name)
76
+ # First check if node exists in cluster
77
+ nodes_data = unwrap(connection.client["nodes"].get)
78
+ basic_data = nodes_data.find { |n| (n[:node] || n[:name]) == name }
79
+ return nil if basic_data.nil?
80
+
81
+ # Merge guest counts
82
+ guest_counts = guest_counts_for_cluster
83
+ data = basic_data.merge(
84
+ guests_vms: guest_counts.dig(name, :vms) || 0,
85
+ guests_cts: guest_counts.dig(name, :cts) || 0
86
+ )
87
+
88
+ # For offline nodes, return basic data with offline note
89
+ unless basic_data[:status] == "online"
90
+ data[:offline_note] = "Node offline - detailed metrics unavailable"
91
+ return build_describe_model(data)
92
+ end
93
+
94
+ # Fetch comprehensive details
95
+ data = data.merge(describe_details_for(name))
96
+
97
+ build_describe_model(data)
98
+ end
99
+
100
+
101
+ # Fetches configuration for a node.
102
+ #
103
+ # @param node_name [String] node name
104
+ # @return [Hash] node configuration
105
+ def fetch_config(node_name)
106
+ resp = connection.client["nodes/#{node_name}/config"].get
107
+ extract_data(resp)
108
+ rescue StandardError
109
+ {}
110
+ end
111
+
112
+ # Updates node configuration.
113
+ #
114
+ # @param node_name [String] node name
115
+ # @param params [Hash] configuration parameters to update
116
+ # @return [void]
117
+ def update(node_name, params = {})
118
+ connection.client["nodes/#{node_name}/config"].put(params)
119
+ end
120
+
121
+ # Sends a Wake-on-LAN packet to a node.
122
+ #
123
+ # Calls `POST /nodes/{node}/wakeonlan`. The target node must have its
124
+ # MAC address registered in the cluster configuration beforehand
125
+ # (via `pvecm` or the web UI). The API returns the MAC address used
126
+ # for the magic packet.
127
+ #
128
+ # @param node_name [String] cluster node name
129
+ # @return [String, nil] MAC address used to assemble the WoL packet
130
+ # @raise [StandardError] propagates any API error (e.g., missing MAC)
131
+ def wakeonlan(node_name)
132
+ response = connection.client["nodes/#{node_name}/wakeonlan"].post
133
+ data = extract_data(response)
134
+ data.is_a?(String) ? data : nil
135
+ end
136
+
137
+ protected
138
+
139
+ # Builds Node model from API response data.
140
+ #
141
+ # @param data [Hash] API response hash
142
+ # @return [Models::Node] Node model instance
143
+ def build_model(data)
144
+ Models::Node.new(
145
+ name: data[:node] || data[:name],
146
+ status: data[:status],
147
+ cpu: data[:cpu],
148
+ maxcpu: data[:maxcpu],
149
+ mem: data[:mem],
150
+ maxmem: data[:maxmem],
151
+ disk: data[:disk],
152
+ maxdisk: data[:maxdisk],
153
+ uptime: data[:uptime],
154
+ level: data[:level],
155
+ version: data[:version],
156
+ kernel: data[:kernel],
157
+ loadavg: data[:loadavg],
158
+ swap_used: data[:swap_used],
159
+ swap_total: data[:swap_total],
160
+ guests_vms: data[:guests_vms],
161
+ guests_cts: data[:guests_cts],
162
+ ip: data[:ip]
163
+ )
164
+ end
165
+
166
+ private
167
+
168
+ # Fetches guest counts per node from cluster/resources.
169
+ #
170
+ # @return [Hash] { "node_name" => { vms: N, cts: M } }
171
+ def guest_counts_for_cluster
172
+ response = connection.client["cluster/resources"].get(params: { type: "vm" })
173
+ resources = unwrap(response)
174
+
175
+ counts = Hash.new { |h, k| h[k] = { vms: 0, cts: 0 } }
176
+ resources.each do |r|
177
+ node = r[:node]
178
+ next if node.nil?
179
+
180
+ if r[:type] == "qemu"
181
+ counts[node][:vms] += 1
182
+ elsif r[:type] == "lxc"
183
+ counts[node][:cts] += 1
184
+ end
185
+ end
186
+ counts
187
+ end
188
+
189
+ # Fetches extended details for a node (version, status).
190
+ #
191
+ # @param node_name [String] node name
192
+ # @return [Hash] merged version and status data
193
+ def details_for(node_name)
194
+ result = {}
195
+
196
+ # Fetch version
197
+ begin
198
+ version_resp = connection.client["nodes/#{node_name}/version"].get
199
+ version_data = extract_data(version_resp)
200
+ result[:version] = version_data[:version]
201
+ result[:kernel] = version_data[:kernel]
202
+ rescue StandardError
203
+ # Ignore errors fetching version
204
+ end
205
+
206
+ # Fetch status (for load, swap)
207
+ begin
208
+ status_resp = connection.client["nodes/#{node_name}/status"].get
209
+ status_data = extract_data(status_resp)
210
+ result[:loadavg] = status_data[:loadavg]&.map(&:to_f)
211
+ result[:kernel] ||= extract_kernel_version(status_data[:kversion])
212
+ if status_data[:swap]
213
+ result[:swap_used] = status_data[:swap][:used]
214
+ result[:swap_total] = status_data[:swap][:total]
215
+ end
216
+ rescue StandardError
217
+ # Ignore errors fetching status
218
+ end
219
+
220
+ # Fetch network (for IP)
221
+ result[:ip] = ip_for(node_name)
222
+
223
+ result
224
+ end
225
+
226
+ # Fetches IP address from node network configuration.
227
+ #
228
+ # Finds the first interface with a gateway configured (default route)
229
+ # and extracts its IP address.
230
+ #
231
+ # @param node_name [String] node name
232
+ # @return [String, nil] IP address or nil if unavailable
233
+ def ip_for(node_name)
234
+ network_resp = connection.client["nodes/#{node_name}/network"].get
235
+ interfaces = unwrap(network_resp)
236
+ extract_ip_from_network(interfaces)
237
+ rescue StandardError
238
+ nil
239
+ end
240
+
241
+ # Extracts IP from network interfaces.
242
+ #
243
+ # Algorithm (KISS):
244
+ # 1. Find first interface with non-empty `gateway` field
245
+ # 2. Extract IP from `address` field
246
+ # 3. Remove CIDR suffix if present (e.g., "192.168.1.10/24" -> "192.168.1.10")
247
+ #
248
+ # @param interfaces [Array<Hash>] network interfaces from API
249
+ # @return [String, nil] IP address or nil
250
+ def extract_ip_from_network(interfaces)
251
+ return nil if interfaces.nil? || interfaces.empty?
252
+
253
+ # Find first interface with gateway (default route interface)
254
+ iface = interfaces.find { |i| i[:gateway] && !i[:gateway].to_s.empty? }
255
+ return nil unless iface
256
+
257
+ # Extract IP, removing CIDR suffix if present
258
+ address = iface[:address] || iface[:cidr]
259
+ return nil if address.nil? || address.to_s.empty?
260
+
261
+ address.to_s.split("/").first
262
+ end
263
+
264
+ # Extracts kernel version from kversion string.
265
+ #
266
+ # The kversion string from Proxmox API looks like:
267
+ # "Linux 6.8.12-1-pve #1 SMP PREEMPT_DYNAMIC..."
268
+ # This method extracts just the version: "6.8.12-1-pve"
269
+ #
270
+ # @param kversion [String, nil] full kernel version string
271
+ # @return [String, nil] extracted kernel version or nil
272
+ def extract_kernel_version(kversion)
273
+ return nil if kversion.nil? || kversion.empty?
274
+
275
+ # Match pattern: "Linux X.Y.Z-something ..."
276
+ match = kversion.match(/Linux\s+([\d.]+-[\w.-]+)/)
277
+ match ? match[1] : kversion
278
+ end
279
+
280
+ # Fetches comprehensive details for describe command.
281
+ # Reuses existing helper methods where possible.
282
+ #
283
+ # @param node_name [String] node name
284
+ # @return [Hash] aggregated data from multiple endpoints
285
+ def describe_details_for(node_name)
286
+ result = {}
287
+
288
+ # Reuse existing details_for for version/status/network
289
+ result.merge!(details_for(node_name))
290
+
291
+ # Additional describe-specific endpoints
292
+ result.merge!(subscription_for(node_name))
293
+ result.merge!(dns_for(node_name))
294
+ result.merge!(time_for(node_name))
295
+ result.merge!(services_for(node_name))
296
+ result.merge!(storage_pools_for(node_name))
297
+ result.merge!(disks_for(node_name))
298
+ result.merge!(qemu_cpu_for(node_name))
299
+ result.merge!(qemu_machines_for(node_name))
300
+ result.merge!(updates_for(node_name))
301
+ result.merge!(extended_status_for(node_name))
302
+ result.merge!(firewall_for(node_name))
303
+ result.merge!(tasks_for(node_name))
304
+
305
+ result
306
+ end
307
+
308
+ # Fetches subscription info for a node.
309
+ #
310
+ # Filters sensitive fields (license key) to prevent accidental exposure.
311
+ # Only safe display data is returned.
312
+ #
313
+ # @param node_name [String] node name
314
+ # @return [Hash] subscription data (filtered for safety)
315
+ def subscription_for(node_name)
316
+ resp = connection.client["nodes/#{node_name}/subscription"].get
317
+ data = extract_data(resp)
318
+ # Filter sensitive fields - keep only safe display data
319
+ safe_data = {
320
+ status: data[:status],
321
+ level: data[:level],
322
+ productname: data[:productname],
323
+ regdate: data[:regdate],
324
+ checktime: data[:checktime]
325
+ }
326
+ { subscription: safe_data }
327
+ rescue StandardError
328
+ { subscription: nil }
329
+ end
330
+
331
+ # Fetches DNS configuration for a node.
332
+ #
333
+ # @param node_name [String] node name
334
+ # @return [Hash] DNS data
335
+ def dns_for(node_name)
336
+ resp = connection.client["nodes/#{node_name}/dns"].get
337
+ data = extract_data(resp)
338
+ { dns: data }
339
+ rescue StandardError
340
+ { dns: nil }
341
+ end
342
+
343
+ # Fetches time configuration for a node.
344
+ #
345
+ # @param node_name [String] node name
346
+ # @return [Hash] time data
347
+ def time_for(node_name)
348
+ resp = connection.client["nodes/#{node_name}/time"].get
349
+ data = extract_data(resp)
350
+ { time_info: data }
351
+ rescue StandardError
352
+ { time_info: nil }
353
+ end
354
+
355
+ # Fetches services for a node.
356
+ #
357
+ # @param node_name [String] node name
358
+ # @return [Hash] services data (raw hashes, not models - used in describe)
359
+ def services_for(node_name)
360
+ resp = connection.client["nodes/#{node_name}/services"].get
361
+ { services: unwrap(resp) }
362
+ rescue StandardError
363
+ { services: [] }
364
+ end
365
+
366
+ # Fetches storage pools for a node.
367
+ #
368
+ # Delegates to Repositories::Storage#list_for_node for consistent
369
+ # model creation and DRY compliance.
370
+ #
371
+ # @param node_name [String] node name
372
+ # @return [Hash] storage pools data with Array<Models::Storage>
373
+ def storage_pools_for(node_name)
374
+ { storage_pools: storage_repository.list_for_node(node_name) }
375
+ rescue StandardError
376
+ { storage_pools: [] }
377
+ end
378
+
379
+ # Returns storage repository instance.
380
+ # Uses injected repository if provided, otherwise creates new one.
381
+ #
382
+ # @return [Repositories::Storage] storage repository
383
+ def storage_repository
384
+ @storage_repository ||= Repositories::Storage.new(connection)
385
+ end
386
+
387
+ # Fetches physical disks for a node.
388
+ #
389
+ # @param node_name [String] node name
390
+ # @return [Hash] disks data (raw hashes, not models - used in describe)
391
+ def disks_for(node_name)
392
+ resp = connection.client["nodes/#{node_name}/disks/list"].get
393
+ { physical_disks: unwrap(resp) }
394
+ rescue StandardError
395
+ { physical_disks: [] }
396
+ end
397
+
398
+ # Fetches QEMU CPU models for a node.
399
+ #
400
+ # @param node_name [String] node name
401
+ # @return [Hash] QEMU CPU models
402
+ def qemu_cpu_for(node_name)
403
+ resp = connection.client["nodes/#{node_name}/capabilities/qemu/cpu"].get
404
+ { qemu_cpu_models: unwrap(resp) }
405
+ rescue StandardError
406
+ { qemu_cpu_models: [] }
407
+ end
408
+
409
+ # Fetches QEMU machine types for a node.
410
+ #
411
+ # @param node_name [String] node name
412
+ # @return [Hash] QEMU machine types
413
+ def qemu_machines_for(node_name)
414
+ resp = connection.client["nodes/#{node_name}/capabilities/qemu/machines"].get
415
+ { qemu_machines: unwrap(resp) }
416
+ rescue StandardError
417
+ { qemu_machines: [] }
418
+ end
419
+
420
+ # Fetches available updates for a node.
421
+ #
422
+ # @param node_name [String] node name
423
+ # @return [Hash] updates data
424
+ def updates_for(node_name)
425
+ resp = connection.client["nodes/#{node_name}/apt/versions"].get
426
+ packages = unwrap(resp)
427
+ upgradable = packages.select do |p|
428
+ p[:AvailableVersion] && p[:AvailableVersion] != p[:CurrentVersion]
429
+ end
430
+ { updates_available: upgradable.size, updates: upgradable }
431
+ rescue StandardError
432
+ { updates_available: 0, updates: [] }
433
+ end
434
+
435
+ # Fetches extended status (cpuinfo, boot_info, rootfs, network_interfaces).
436
+ #
437
+ # @param node_name [String] node name
438
+ # @return [Hash] extended status data
439
+ def extended_status_for(node_name)
440
+ resp = connection.client["nodes/#{node_name}/status"].get
441
+ data = extract_data(resp)
442
+ {
443
+ cpuinfo: data[:cpuinfo],
444
+ boot_info: data[:"boot-info"],
445
+ rootfs: data[:rootfs],
446
+ network_interfaces: network_interfaces_for(node_name)
447
+ }
448
+ rescue StandardError
449
+ {}
450
+ end
451
+
452
+ # Fetches network interfaces for a node.
453
+ #
454
+ # @param node_name [String] node name
455
+ # @return [Array<Hash>] network interfaces (raw hashes for describe output)
456
+ def network_interfaces_for(node_name)
457
+ resp = connection.client["nodes/#{node_name}/network"].get
458
+ unwrap(resp)
459
+ rescue StandardError
460
+ []
461
+ end
462
+
463
+ # Fetches firewall configuration (options, rules, aliases, IP sets).
464
+ #
465
+ # @param node_name [String] node name
466
+ # @return [Hash] firewall data under :firewall key
467
+ def firewall_for(node_name)
468
+ base = "nodes/#{node_name}/firewall"
469
+ options = (extract_data(connection.client["#{base}/options"].get) rescue {})
470
+ rules = (unwrap(connection.client["#{base}/rules"].get) rescue [])
471
+ aliases_data = (unwrap(connection.client["#{base}/aliases"].get) rescue [])
472
+ ipset = (unwrap(connection.client["#{base}/ipset"].get) rescue [])
473
+ { firewall: { options: options, rules: rules, aliases: aliases_data, ipset: ipset } }
474
+ rescue StandardError
475
+ {}
476
+ end
477
+
478
+ # Fetches recent task history for the node.
479
+ #
480
+ # @param node_name [String] node name
481
+ # @param limit [Integer] max entries (default 10)
482
+ # @return [Hash] tasks data under :tasks key
483
+ def tasks_for(node_name, limit: 10)
484
+ task_list_repo = TaskList.new(connection)
485
+ { tasks: task_list_repo.list(node: node_name, limit: limit) }
486
+ rescue StandardError
487
+ { tasks: [] }
488
+ end
489
+
490
+ # Builds Node model with describe-specific attributes.
491
+ #
492
+ # @param data [Hash] aggregated data from API
493
+ # @return [Models::Node] Node model
494
+ def build_describe_model(data)
495
+ Models::Node.new(
496
+ # Basic fields (existing)
497
+ name: data[:node] || data[:name],
498
+ status: data[:status],
499
+ cpu: data[:cpu],
500
+ maxcpu: data[:maxcpu],
501
+ mem: data[:mem],
502
+ maxmem: data[:maxmem],
503
+ disk: data[:disk],
504
+ maxdisk: data[:maxdisk],
505
+ uptime: data[:uptime],
506
+ level: data[:level],
507
+ version: data[:version],
508
+ kernel: data[:kernel],
509
+ loadavg: data[:loadavg],
510
+ swap_used: data[:swap_used],
511
+ swap_total: data[:swap_total],
512
+ guests_vms: data[:guests_vms],
513
+ guests_cts: data[:guests_cts],
514
+ ip: data[:ip],
515
+ # Extended fields for describe
516
+ cpuinfo: data[:cpuinfo],
517
+ boot_info: data[:boot_info],
518
+ rootfs: data[:rootfs],
519
+ subscription: data[:subscription],
520
+ dns: data[:dns],
521
+ time_info: data[:time_info],
522
+ network_interfaces: data[:network_interfaces],
523
+ services: data[:services],
524
+ storage_pools: data[:storage_pools],
525
+ physical_disks: data[:physical_disks],
526
+ qemu_cpu_models: data[:qemu_cpu_models],
527
+ qemu_machines: data[:qemu_machines],
528
+ updates_available: data[:updates_available],
529
+ updates: data[:updates],
530
+ offline_note: data[:offline_note],
531
+ firewall: data[:firewall],
532
+ tasks: data[:tasks]
533
+ )
534
+ end
535
+ end
536
+ end
537
+ end