@homeflare/alchemy 0.1.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 (417) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/cloudflare/client.d.ts +45 -0
  4. package/dist/cloudflare/client.d.ts.map +1 -0
  5. package/dist/cloudflare/index.d.ts +11 -0
  6. package/dist/cloudflare/index.d.ts.map +1 -0
  7. package/dist/cloudflare/index.js +129 -0
  8. package/dist/cloudflare/index.js.map +13 -0
  9. package/dist/cloudflare/lock-rules.d.ts +50 -0
  10. package/dist/cloudflare/lock-rules.d.ts.map +1 -0
  11. package/dist/cloudflare/providers.d.ts +19 -0
  12. package/dist/cloudflare/providers.d.ts.map +1 -0
  13. package/dist/cloudflare/r2-bucket-lock.d.ts +100 -0
  14. package/dist/cloudflare/r2-bucket-lock.d.ts.map +1 -0
  15. package/dist/forgejo/branch-protection-form.d.ts +19 -0
  16. package/dist/forgejo/branch-protection-form.d.ts.map +1 -0
  17. package/dist/forgejo/branch-protection.d.ts +47 -0
  18. package/dist/forgejo/branch-protection.d.ts.map +1 -0
  19. package/dist/forgejo/client.d.ts +37 -0
  20. package/dist/forgejo/client.d.ts.map +1 -0
  21. package/dist/forgejo/index.d.ts +18 -0
  22. package/dist/forgejo/index.d.ts.map +1 -0
  23. package/dist/forgejo/index.js +479 -0
  24. package/dist/forgejo/index.js.map +21 -0
  25. package/dist/forgejo/org-actions-secrets.d.ts +45 -0
  26. package/dist/forgejo/org-actions-secrets.d.ts.map +1 -0
  27. package/dist/forgejo/org-label.d.ts +46 -0
  28. package/dist/forgejo/org-label.d.ts.map +1 -0
  29. package/dist/forgejo/org-team.d.ts +42 -0
  30. package/dist/forgejo/org-team.d.ts.map +1 -0
  31. package/dist/forgejo/repo-webhook-form.d.ts +27 -0
  32. package/dist/forgejo/repo-webhook-form.d.ts.map +1 -0
  33. package/dist/forgejo/repo-webhook.d.ts +46 -0
  34. package/dist/forgejo/repo-webhook.d.ts.map +1 -0
  35. package/dist/forgejo/repository.d.ts +45 -0
  36. package/dist/forgejo/repository.d.ts.map +1 -0
  37. package/dist/forgejo/resource.d.ts +83 -0
  38. package/dist/forgejo/resource.d.ts.map +1 -0
  39. package/dist/forgejo/team-member.d.ts +39 -0
  40. package/dist/forgejo/team-member.d.ts.map +1 -0
  41. package/dist/forgejo/values.d.ts +24 -0
  42. package/dist/forgejo/values.d.ts.map +1 -0
  43. package/dist/index.d.ts +18 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +9 -0
  47. package/dist/openbao/auth-role-form.d.ts +38 -0
  48. package/dist/openbao/auth-role-form.d.ts.map +1 -0
  49. package/dist/openbao/auth-role.d.ts +20 -0
  50. package/dist/openbao/auth-role.d.ts.map +1 -0
  51. package/dist/openbao/bao-address.d.ts +72 -0
  52. package/dist/openbao/bao-address.d.ts.map +1 -0
  53. package/dist/openbao/bao-http.d.ts +71 -0
  54. package/dist/openbao/bao-http.d.ts.map +1 -0
  55. package/dist/openbao/bao-status.d.ts +74 -0
  56. package/dist/openbao/bao-status.d.ts.map +1 -0
  57. package/dist/openbao/cloudflare-group-scope.d.ts +73 -0
  58. package/dist/openbao/cloudflare-group-scope.d.ts.map +1 -0
  59. package/dist/openbao/cloudflare-parity-catalog.d.ts +28 -0
  60. package/dist/openbao/cloudflare-parity-catalog.d.ts.map +1 -0
  61. package/dist/openbao/cloudflare-parity-snapshot.d.ts +31 -0
  62. package/dist/openbao/cloudflare-parity-snapshot.d.ts.map +1 -0
  63. package/dist/openbao/cloudflare-parity.d.ts +14 -0
  64. package/dist/openbao/cloudflare-parity.d.ts.map +1 -0
  65. package/dist/openbao/cloudflare-permission-groups-solve.d.ts +85 -0
  66. package/dist/openbao/cloudflare-permission-groups-solve.d.ts.map +1 -0
  67. package/dist/openbao/cloudflare-permission-groups.d.ts +87 -0
  68. package/dist/openbao/cloudflare-permission-groups.d.ts.map +1 -0
  69. package/dist/openbao/cloudflare-policy.d.ts +65 -0
  70. package/dist/openbao/cloudflare-policy.d.ts.map +1 -0
  71. package/dist/openbao/cloudflare-role-form.d.ts +110 -0
  72. package/dist/openbao/cloudflare-role-form.d.ts.map +1 -0
  73. package/dist/openbao/cloudflare-role-wire.d.ts +27 -0
  74. package/dist/openbao/cloudflare-role-wire.d.ts.map +1 -0
  75. package/dist/openbao/cloudflare-role.d.ts +35 -0
  76. package/dist/openbao/cloudflare-role.d.ts.map +1 -0
  77. package/dist/openbao/cloudflare-roles-config.d.ts +67 -0
  78. package/dist/openbao/cloudflare-roles-config.d.ts.map +1 -0
  79. package/dist/openbao/cloudflare-roles-expand.d.ts +76 -0
  80. package/dist/openbao/cloudflare-roles-expand.d.ts.map +1 -0
  81. package/dist/openbao/digest.d.ts +24 -0
  82. package/dist/openbao/digest.d.ts.map +1 -0
  83. package/dist/openbao/fake-bao.d.ts +40 -0
  84. package/dist/openbao/fake-bao.d.ts.map +1 -0
  85. package/dist/openbao/forgejo-bootstrap-parse.d.ts +46 -0
  86. package/dist/openbao/forgejo-bootstrap-parse.d.ts.map +1 -0
  87. package/dist/openbao/forgejo-bootstrap.d.ts +29 -0
  88. package/dist/openbao/forgejo-bootstrap.d.ts.map +1 -0
  89. package/dist/openbao/index.d.ts +18 -0
  90. package/dist/openbao/index.d.ts.map +1 -0
  91. package/dist/openbao/index.js +1158 -0
  92. package/dist/openbao/index.js.map +33 -0
  93. package/dist/openbao/mount-form.d.ts +60 -0
  94. package/dist/openbao/mount-form.d.ts.map +1 -0
  95. package/dist/openbao/mount-wire.d.ts +24 -0
  96. package/dist/openbao/mount-wire.d.ts.map +1 -0
  97. package/dist/openbao/mount.d.ts +20 -0
  98. package/dist/openbao/mount.d.ts.map +1 -0
  99. package/dist/openbao/pki-role-form.d.ts +118 -0
  100. package/dist/openbao/pki-role-form.d.ts.map +1 -0
  101. package/dist/openbao/pki-role.d.ts +50 -0
  102. package/dist/openbao/pki-role.d.ts.map +1 -0
  103. package/dist/openbao/policy-assembly.d.ts +13 -0
  104. package/dist/openbao/policy-assembly.d.ts.map +1 -0
  105. package/dist/openbao/policy-wire.d.ts +41 -0
  106. package/dist/openbao/policy-wire.d.ts.map +1 -0
  107. package/dist/openbao/policy.d.ts +83 -0
  108. package/dist/openbao/policy.d.ts.map +1 -0
  109. package/dist/openbao/proxmox-role-form.d.ts +92 -0
  110. package/dist/openbao/proxmox-role-form.d.ts.map +1 -0
  111. package/dist/openbao/proxmox-role.d.ts +47 -0
  112. package/dist/openbao/proxmox-role.d.ts.map +1 -0
  113. package/dist/openbao/ssh-role-form.d.ts +99 -0
  114. package/dist/openbao/ssh-role-form.d.ts.map +1 -0
  115. package/dist/openbao/ssh-role.d.ts +43 -0
  116. package/dist/openbao/ssh-role.d.ts.map +1 -0
  117. package/dist/proxmox/acl.d.ts +72 -0
  118. package/dist/proxmox/acl.d.ts.map +1 -0
  119. package/dist/proxmox/api-token-form.d.ts +59 -0
  120. package/dist/proxmox/api-token-form.d.ts.map +1 -0
  121. package/dist/proxmox/api-token.d.ts +159 -0
  122. package/dist/proxmox/api-token.d.ts.map +1 -0
  123. package/dist/proxmox/backup-job.d.ts +112 -0
  124. package/dist/proxmox/backup-job.d.ts.map +1 -0
  125. package/dist/proxmox/ceph-daemon-form.d.ts +70 -0
  126. package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -0
  127. package/dist/proxmox/ceph-daemon.d.ts +162 -0
  128. package/dist/proxmox/ceph-daemon.d.ts.map +1 -0
  129. package/dist/proxmox/ceph-flag.d.ts +131 -0
  130. package/dist/proxmox/ceph-flag.d.ts.map +1 -0
  131. package/dist/proxmox/ceph-fs-wire.d.ts +106 -0
  132. package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -0
  133. package/dist/proxmox/ceph-fs.d.ts +124 -0
  134. package/dist/proxmox/ceph-fs.d.ts.map +1 -0
  135. package/dist/proxmox/ceph-osd-tree.d.ts +154 -0
  136. package/dist/proxmox/ceph-osd-tree.d.ts.map +1 -0
  137. package/dist/proxmox/ceph-osd-write.d.ts +36 -0
  138. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -0
  139. package/dist/proxmox/ceph-osd.d.ts +102 -0
  140. package/dist/proxmox/ceph-osd.d.ts.map +1 -0
  141. package/dist/proxmox/ceph-pool-form.d.ts +217 -0
  142. package/dist/proxmox/ceph-pool-form.d.ts.map +1 -0
  143. package/dist/proxmox/ceph-pool-settle.d.ts +39 -0
  144. package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -0
  145. package/dist/proxmox/ceph-pool.d.ts +48 -0
  146. package/dist/proxmox/ceph-pool.d.ts.map +1 -0
  147. package/dist/proxmox/client.d.ts +64 -0
  148. package/dist/proxmox/client.d.ts.map +1 -0
  149. package/dist/proxmox/credentials.d.ts +153 -0
  150. package/dist/proxmox/credentials.d.ts.map +1 -0
  151. package/dist/proxmox/firewall-alias-form.d.ts +98 -0
  152. package/dist/proxmox/firewall-alias-form.d.ts.map +1 -0
  153. package/dist/proxmox/firewall-alias.d.ts +141 -0
  154. package/dist/proxmox/firewall-alias.d.ts.map +1 -0
  155. package/dist/proxmox/generated/pbs.d.ts +388 -0
  156. package/dist/proxmox/generated/pbs.d.ts.map +1 -0
  157. package/dist/proxmox/generated/pve.d.ts +4713 -0
  158. package/dist/proxmox/generated/pve.d.ts.map +1 -0
  159. package/dist/proxmox/group.d.ts +104 -0
  160. package/dist/proxmox/group.d.ts.map +1 -0
  161. package/dist/proxmox/ha-resource.d.ts +104 -0
  162. package/dist/proxmox/ha-resource.d.ts.map +1 -0
  163. package/dist/proxmox/ha-rule-form.d.ts +103 -0
  164. package/dist/proxmox/ha-rule-form.d.ts.map +1 -0
  165. package/dist/proxmox/ha-rule.d.ts +156 -0
  166. package/dist/proxmox/ha-rule.d.ts.map +1 -0
  167. package/dist/proxmox/index.d.ts +43 -0
  168. package/dist/proxmox/index.d.ts.map +1 -0
  169. package/dist/proxmox/index.js +2322 -0
  170. package/dist/proxmox/index.js.map +70 -0
  171. package/dist/proxmox/lease-cache.d.ts +98 -0
  172. package/dist/proxmox/lease-cache.d.ts.map +1 -0
  173. package/dist/proxmox/lxc.d.ts +59 -0
  174. package/dist/proxmox/lxc.d.ts.map +1 -0
  175. package/dist/proxmox/members.d.ts +56 -0
  176. package/dist/proxmox/members.d.ts.map +1 -0
  177. package/dist/proxmox/metric-server-form.d.ts +53 -0
  178. package/dist/proxmox/metric-server-form.d.ts.map +1 -0
  179. package/dist/proxmox/metric-server-otel.d.ts +49 -0
  180. package/dist/proxmox/metric-server-otel.d.ts.map +1 -0
  181. package/dist/proxmox/metric-server.d.ts +123 -0
  182. package/dist/proxmox/metric-server.d.ts.map +1 -0
  183. package/dist/proxmox/network-apply-read.d.ts +93 -0
  184. package/dist/proxmox/network-apply-read.d.ts.map +1 -0
  185. package/dist/proxmox/network-apply.d.ts +102 -0
  186. package/dist/proxmox/network-apply.d.ts.map +1 -0
  187. package/dist/proxmox/node-network-form.d.ts +128 -0
  188. package/dist/proxmox/node-network-form.d.ts.map +1 -0
  189. package/dist/proxmox/node-network.d.ts +173 -0
  190. package/dist/proxmox/node-network.d.ts.map +1 -0
  191. package/dist/proxmox/notification-target-form.d.ts +44 -0
  192. package/dist/proxmox/notification-target-form.d.ts.map +1 -0
  193. package/dist/proxmox/notification-target.d.ts +146 -0
  194. package/dist/proxmox/notification-target.d.ts.map +1 -0
  195. package/dist/proxmox/pbs-datastore-form.d.ts +147 -0
  196. package/dist/proxmox/pbs-datastore-form.d.ts.map +1 -0
  197. package/dist/proxmox/pbs-datastore-guard.d.ts +55 -0
  198. package/dist/proxmox/pbs-datastore-guard.d.ts.map +1 -0
  199. package/dist/proxmox/pbs-datastore.d.ts +140 -0
  200. package/dist/proxmox/pbs-datastore.d.ts.map +1 -0
  201. package/dist/proxmox/pbs-prune-job-form.d.ts +97 -0
  202. package/dist/proxmox/pbs-prune-job-form.d.ts.map +1 -0
  203. package/dist/proxmox/pbs-prune-job.d.ts +168 -0
  204. package/dist/proxmox/pbs-prune-job.d.ts.map +1 -0
  205. package/dist/proxmox/pbs-sync-job-form.d.ts +83 -0
  206. package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -0
  207. package/dist/proxmox/pbs-sync-job.d.ts +246 -0
  208. package/dist/proxmox/pbs-sync-job.d.ts.map +1 -0
  209. package/dist/proxmox/pbs-verify-job-form.d.ts +114 -0
  210. package/dist/proxmox/pbs-verify-job-form.d.ts.map +1 -0
  211. package/dist/proxmox/pbs-verify-job.d.ts +145 -0
  212. package/dist/proxmox/pbs-verify-job.d.ts.map +1 -0
  213. package/dist/proxmox/pool.d.ts +42 -0
  214. package/dist/proxmox/pool.d.ts.map +1 -0
  215. package/dist/proxmox/qemu.d.ts +46 -0
  216. package/dist/proxmox/qemu.d.ts.map +1 -0
  217. package/dist/proxmox/replication-job-form.d.ts +71 -0
  218. package/dist/proxmox/replication-job-form.d.ts.map +1 -0
  219. package/dist/proxmox/replication-job.d.ts +162 -0
  220. package/dist/proxmox/replication-job.d.ts.map +1 -0
  221. package/dist/proxmox/resource.d.ts +157 -0
  222. package/dist/proxmox/resource.d.ts.map +1 -0
  223. package/dist/proxmox/role.d.ts +97 -0
  224. package/dist/proxmox/role.d.ts.map +1 -0
  225. package/dist/proxmox/sdn-apply-read.d.ts +80 -0
  226. package/dist/proxmox/sdn-apply-read.d.ts.map +1 -0
  227. package/dist/proxmox/sdn-apply.d.ts +63 -0
  228. package/dist/proxmox/sdn-apply.d.ts.map +1 -0
  229. package/dist/proxmox/sdn-subnet-form.d.ts +111 -0
  230. package/dist/proxmox/sdn-subnet-form.d.ts.map +1 -0
  231. package/dist/proxmox/sdn-subnet.d.ts +161 -0
  232. package/dist/proxmox/sdn-subnet.d.ts.map +1 -0
  233. package/dist/proxmox/sdn-vnet.d.ts +113 -0
  234. package/dist/proxmox/sdn-vnet.d.ts.map +1 -0
  235. package/dist/proxmox/sdn-zone.d.ts +126 -0
  236. package/dist/proxmox/sdn-zone.d.ts.map +1 -0
  237. package/dist/proxmox/storage.d.ts +120 -0
  238. package/dist/proxmox/storage.d.ts.map +1 -0
  239. package/dist/proxmox/user.d.ts +107 -0
  240. package/dist/proxmox/user.d.ts.map +1 -0
  241. package/dist/proxmox/values.d.ts +134 -0
  242. package/dist/proxmox/values.d.ts.map +1 -0
  243. package/dist/proxmox/zfs-pool-write.d.ts +76 -0
  244. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -0
  245. package/dist/proxmox/zfs-pool.d.ts +146 -0
  246. package/dist/proxmox/zfs-pool.d.ts.map +1 -0
  247. package/dist/talos/credentials.d.ts +30 -0
  248. package/dist/talos/credentials.d.ts.map +1 -0
  249. package/dist/talos/index.d.ts +15 -0
  250. package/dist/talos/index.d.ts.map +1 -0
  251. package/dist/talos/index.js +392 -0
  252. package/dist/talos/index.js.map +16 -0
  253. package/dist/talos/kubeconfig.d.ts +53 -0
  254. package/dist/talos/kubeconfig.d.ts.map +1 -0
  255. package/dist/talos/resource.d.ts +32 -0
  256. package/dist/talos/resource.d.ts.map +1 -0
  257. package/dist/talos/talos-bootstrap.d.ts +27 -0
  258. package/dist/talos/talos-bootstrap.d.ts.map +1 -0
  259. package/dist/talos/talos-cluster-health.d.ts +36 -0
  260. package/dist/talos/talos-cluster-health.d.ts.map +1 -0
  261. package/dist/talos/talos-machine-config.d.ts +41 -0
  262. package/dist/talos/talos-machine-config.d.ts.map +1 -0
  263. package/dist/talos/talosctl.d.ts +29 -0
  264. package/dist/talos/talosctl.d.ts.map +1 -0
  265. package/dist/talos/values.d.ts +20 -0
  266. package/dist/talos/values.d.ts.map +1 -0
  267. package/package.json +57 -0
  268. package/src/cloudflare/client.ts +76 -0
  269. package/src/cloudflare/index.ts +10 -0
  270. package/src/cloudflare/lock-rules.ts +60 -0
  271. package/src/cloudflare/providers.ts +23 -0
  272. package/src/cloudflare/r2-bucket-lock.ts +220 -0
  273. package/src/forgejo/branch-protection-form.ts +29 -0
  274. package/src/forgejo/branch-protection.ts +101 -0
  275. package/src/forgejo/client.ts +136 -0
  276. package/src/forgejo/index.ts +17 -0
  277. package/src/forgejo/org-actions-secrets.ts +99 -0
  278. package/src/forgejo/org-label.ts +100 -0
  279. package/src/forgejo/org-team.ts +105 -0
  280. package/src/forgejo/repo-webhook-form.ts +47 -0
  281. package/src/forgejo/repo-webhook.ts +118 -0
  282. package/src/forgejo/repository.ts +126 -0
  283. package/src/forgejo/resource.ts +171 -0
  284. package/src/forgejo/team-member.ts +97 -0
  285. package/src/forgejo/values.test.ts +48 -0
  286. package/src/forgejo/values.ts +61 -0
  287. package/src/index.ts +18 -0
  288. package/src/openbao/auth-role-form.ts +112 -0
  289. package/src/openbao/auth-role.ts +101 -0
  290. package/src/openbao/bao-address.test.ts +89 -0
  291. package/src/openbao/bao-address.ts +97 -0
  292. package/src/openbao/bao-http-gate.test.ts +88 -0
  293. package/src/openbao/bao-http-unix.test.ts +70 -0
  294. package/src/openbao/bao-http.test.ts +161 -0
  295. package/src/openbao/bao-http.ts +185 -0
  296. package/src/openbao/bao-status.test.ts +114 -0
  297. package/src/openbao/bao-status.ts +137 -0
  298. package/src/openbao/cloudflare-group-scope.test.ts +103 -0
  299. package/src/openbao/cloudflare-group-scope.ts +83 -0
  300. package/src/openbao/cloudflare-parity-catalog.ts +104 -0
  301. package/src/openbao/cloudflare-parity-snapshot.ts +84 -0
  302. package/src/openbao/cloudflare-parity.ts +152 -0
  303. package/src/openbao/cloudflare-permission-groups-solve.test.ts +171 -0
  304. package/src/openbao/cloudflare-permission-groups-solve.ts +197 -0
  305. package/src/openbao/cloudflare-permission-groups.test.ts +369 -0
  306. package/src/openbao/cloudflare-permission-groups.ts +299 -0
  307. package/src/openbao/cloudflare-policy.ts +180 -0
  308. package/src/openbao/cloudflare-role-form.test.ts +177 -0
  309. package/src/openbao/cloudflare-role-form.ts +183 -0
  310. package/src/openbao/cloudflare-role-wire.test.ts +132 -0
  311. package/src/openbao/cloudflare-role-wire.ts +41 -0
  312. package/src/openbao/cloudflare-role.ts +189 -0
  313. package/src/openbao/cloudflare-roles-config.ts +230 -0
  314. package/src/openbao/cloudflare-roles-expand-bucket.test.ts +134 -0
  315. package/src/openbao/cloudflare-roles-expand.ts +231 -0
  316. package/src/openbao/digest.ts +26 -0
  317. package/src/openbao/fake-bao.ts +84 -0
  318. package/src/openbao/forgejo-bootstrap-parse.test.ts +76 -0
  319. package/src/openbao/forgejo-bootstrap-parse.ts +90 -0
  320. package/src/openbao/forgejo-bootstrap.ts +181 -0
  321. package/src/openbao/index.ts +17 -0
  322. package/src/openbao/mount-form.ts +147 -0
  323. package/src/openbao/mount-wire.test.ts +108 -0
  324. package/src/openbao/mount-wire.ts +79 -0
  325. package/src/openbao/mount.ts +132 -0
  326. package/src/openbao/pki-role-form.ts +248 -0
  327. package/src/openbao/pki-role.ts +202 -0
  328. package/src/openbao/policy-assembly.test.ts +28 -0
  329. package/src/openbao/policy-assembly.ts +13 -0
  330. package/src/openbao/policy-wire.test.ts +90 -0
  331. package/src/openbao/policy-wire.ts +53 -0
  332. package/src/openbao/policy.ts +212 -0
  333. package/src/openbao/proxmox-role-form.ts +174 -0
  334. package/src/openbao/proxmox-role.ts +214 -0
  335. package/src/openbao/ssh-role-form.ts +248 -0
  336. package/src/openbao/ssh-role.ts +240 -0
  337. package/src/proxmox/acl.ts +267 -0
  338. package/src/proxmox/api-token-form.ts +122 -0
  339. package/src/proxmox/api-token.ts +222 -0
  340. package/src/proxmox/backup-job.ts +213 -0
  341. package/src/proxmox/ceph-daemon-form.ts +84 -0
  342. package/src/proxmox/ceph-daemon.ts +243 -0
  343. package/src/proxmox/ceph-flag.ts +249 -0
  344. package/src/proxmox/ceph-fs-wire.ts +246 -0
  345. package/src/proxmox/ceph-fs.ts +249 -0
  346. package/src/proxmox/ceph-osd-tree.ts +193 -0
  347. package/src/proxmox/ceph-osd-write.ts +76 -0
  348. package/src/proxmox/ceph-osd.ts +244 -0
  349. package/src/proxmox/ceph-pool-form.ts +249 -0
  350. package/src/proxmox/ceph-pool-settle.ts +70 -0
  351. package/src/proxmox/ceph-pool.ts +212 -0
  352. package/src/proxmox/client.test.ts +185 -0
  353. package/src/proxmox/client.ts +240 -0
  354. package/src/proxmox/credentials.test.ts +144 -0
  355. package/src/proxmox/credentials.ts +254 -0
  356. package/src/proxmox/firewall-alias-form.ts +112 -0
  357. package/src/proxmox/firewall-alias.ts +208 -0
  358. package/src/proxmox/generated/pbs.ts +461 -0
  359. package/src/proxmox/generated/pve.ts +7344 -0
  360. package/src/proxmox/group.ts +199 -0
  361. package/src/proxmox/ha-resource.ts +169 -0
  362. package/src/proxmox/ha-rule-form.ts +185 -0
  363. package/src/proxmox/ha-rule.ts +240 -0
  364. package/src/proxmox/index.ts +42 -0
  365. package/src/proxmox/lease-cache.test.ts +163 -0
  366. package/src/proxmox/lease-cache.ts +131 -0
  367. package/src/proxmox/lxc.ts +117 -0
  368. package/src/proxmox/members.test.ts +54 -0
  369. package/src/proxmox/members.ts +174 -0
  370. package/src/proxmox/metric-server-form.ts +128 -0
  371. package/src/proxmox/metric-server-otel.ts +99 -0
  372. package/src/proxmox/metric-server.ts +214 -0
  373. package/src/proxmox/network-apply-read.ts +228 -0
  374. package/src/proxmox/network-apply.ts +244 -0
  375. package/src/proxmox/node-network-form.ts +213 -0
  376. package/src/proxmox/node-network.ts +243 -0
  377. package/src/proxmox/notification-target-form.ts +104 -0
  378. package/src/proxmox/notification-target.ts +222 -0
  379. package/src/proxmox/pbs-datastore-form.ts +248 -0
  380. package/src/proxmox/pbs-datastore-guard.ts +105 -0
  381. package/src/proxmox/pbs-datastore.ts +271 -0
  382. package/src/proxmox/pbs-prune-job-form.ts +208 -0
  383. package/src/proxmox/pbs-prune-job.ts +227 -0
  384. package/src/proxmox/pbs-sync-job-form.ts +234 -0
  385. package/src/proxmox/pbs-sync-job.ts +258 -0
  386. package/src/proxmox/pbs-verify-job-form.ts +131 -0
  387. package/src/proxmox/pbs-verify-job.ts +227 -0
  388. package/src/proxmox/pool.test.ts +42 -0
  389. package/src/proxmox/pool.ts +80 -0
  390. package/src/proxmox/qemu.ts +89 -0
  391. package/src/proxmox/replication-job-form.ts +89 -0
  392. package/src/proxmox/replication-job.ts +248 -0
  393. package/src/proxmox/resource.ts +257 -0
  394. package/src/proxmox/role.ts +161 -0
  395. package/src/proxmox/sdn-apply-read.test.ts +152 -0
  396. package/src/proxmox/sdn-apply-read.ts +189 -0
  397. package/src/proxmox/sdn-apply.ts +154 -0
  398. package/src/proxmox/sdn-subnet-form.ts +156 -0
  399. package/src/proxmox/sdn-subnet.ts +238 -0
  400. package/src/proxmox/sdn-vnet.ts +228 -0
  401. package/src/proxmox/sdn-zone.ts +246 -0
  402. package/src/proxmox/storage.ts +245 -0
  403. package/src/proxmox/user.ts +214 -0
  404. package/src/proxmox/values.test.ts +193 -0
  405. package/src/proxmox/values.ts +203 -0
  406. package/src/proxmox/zfs-pool-write.ts +130 -0
  407. package/src/proxmox/zfs-pool.ts +253 -0
  408. package/src/talos/credentials.ts +136 -0
  409. package/src/talos/index.ts +14 -0
  410. package/src/talos/kubeconfig.ts +170 -0
  411. package/src/talos/resource.ts +31 -0
  412. package/src/talos/talos-bootstrap.ts +109 -0
  413. package/src/talos/talos-cluster-health.ts +119 -0
  414. package/src/talos/talos-machine-config.ts +146 -0
  415. package/src/talos/talosctl.ts +94 -0
  416. package/src/talos/values.test.ts +80 -0
  417. package/src/talos/values.ts +70 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * `Pbs.VerifyJob` — one section of PBS's `verification.cfg`, declared.
3
+ *
4
+ * ★ THIS IS THE FAMILY THAT ANSWERS "ARE THE BACKUPS ACTUALLY GOOD", AND NOTHING ELSE IN THIS
5
+ * PACKAGE DOES. A datastore reports free space, a prune job reports that it ran, and every
6
+ * snapshot in the store can still be unreadable: PBS learns a chunk has rotted only when
7
+ * something reads it back and checks its digest, and verification is the only thing that does
8
+ * that on a schedule. An undeclared verify job is the reason nobody notices for months — and a
9
+ * green `Proxmox.BackupJob` makes it worse rather than better, because a vzdump job that says
10
+ * "OK" every night is exactly the evidence people stop at.
11
+ *
12
+ * ⛔ THIS FILE CANNOT AUTHENTICATE UNTIL TWO THINGS OUTSIDE IT CHANGE, AND IT IS SHIPPED SAYING SO
13
+ * RATHER THAN SHIPPED LOOKING FINISHED. Both are recorded on `PbsTarget` below:
14
+ * 1. `credentials.ts` has no PBS mint. MEASURED 2026-09-13 from this Mac: the `claude-code`
15
+ * approle is refused `sys/mounts`, `kv/infra/proxmox` and `proxmox-pbs/creds/read` alike —
16
+ * 403 permission denied on all three — so there is no mount to name and none is invented.
17
+ * 2. `client.ts`'s `authorization()` builds a PVE header, and PBS will not accept it. See the
18
+ * ⛔ on `PbsTarget`.
19
+ * Until both land, every call 401s, `pveOperations.read` folds that into "absent", and the plan
20
+ * reports `create` for a job that is plainly there. The deploy then FAILS at the POST rather
21
+ * than clobbering anything — an unauthenticated POST 401s, and an authenticated one is refused
22
+ * because PBS will not take a duplicate section id — so the cost is a LYING PLAN and a broken
23
+ * run, not a lost job. That is still reason enough not to wire it into a stack yet: a plan
24
+ * nobody can trust is its own outage.
25
+ *
26
+ * ⚠️ THE SHAPES BELOW ARE READ FROM PUBLISHED SOURCE, NOT MEASURED ON THIS ESTATE. Everything is
27
+ * taken from `pbs-api-types/src/jobs.rs`, `src/api2/config/verify.rs`, `src/server/verify_job.rs`
28
+ * and `src/backup/verify.rs` at HEAD of the Proxmox git mirrors on 2026-09-13. What WAS measured
29
+ * here: `pbs.example.com:8007` and `pbs.mgmt.example.com:8007` both answer HTTP 401 under
30
+ * STRICT TLS (curl `ssl_verify_result=0`), so no `-k` is needed and `client.ts`'s plain `fetch`
31
+ * reaches PBS unchanged. The running host's own schema was NOT read — no credential — so a PBS
32
+ * older than HEAD may differ.
33
+ */
34
+ import { Resource } from 'alchemy';
35
+ import * as Provider from 'alchemy/Provider';
36
+ import * as Effect from 'effect/Effect';
37
+ import type { PbsTarget } from './credentials.ts';
38
+ import { rechecks, shape } from './pbs-verify-job-form.ts';
39
+ import { type PveRequirements, pveHandlers } from './resource.ts';
40
+ import { bool, int, text } from './values.ts';
41
+
42
+ export interface PbsVerifyJobProps {
43
+ /** ⛔ A PBS host, never a PVE cluster. See `PbsTarget`. */
44
+ target: PbsTarget;
45
+ /**
46
+ * ⛔ The primary key, REQUIRED, and never left to PBS. 3–32 chars of
47
+ * `[A-Za-z0-9_][A-Za-z0-9._-]*`. PBS refuses a duplicate section id outright, which is the loud
48
+ * failure `Proxmox.BackupJob` had to engineer for itself — but only because the id is sent.
49
+ */
50
+ id: string;
51
+ /** Datastore name, 3–32 safe-id chars. ⚠️ MUTABLE — see the ⚠️ on `matches`. */
52
+ store: string;
53
+ /**
54
+ * ⛔ THE KEY IS REQUIRED AND THE VALUE MAY BE `null`. PBS makes the field optional; this family
55
+ * refuses to let you OMIT it, which is not the same thing. A verification job with no schedule
56
+ * never runs on its own and PBS reports it as perfectly healthy — and an absent schedule is also
57
+ * the only way to turn one off, because there is NO `disable` field on a verification job.
58
+ * ⛔ THAT LAST CLAIM IS NOW MEASURED, not read from source. `proxmox-backup-manager verify-job
59
+ * update --help` on the live 4.2 host lists `--delete
60
+ * ignore-verified|comment|schedule|outdated-after|ns|max-depth|read-threads|verify-threads`
61
+ * and no `--disable` anywhere. So `schedule` is deletable — "parked" is a state PBS supports —
62
+ * and `disable` genuinely does not exist. pbs-verify-job-form.ts said this was read off the
63
+ * source and NOT verified against a running host; it is now verified, on this one.
64
+ * ★ SO `null` MEANS "DELIBERATELY PARKED" AND OMITTING IT IS STILL AN ERROR. The original
65
+ * required-string kept you from declaring verification that never happens, which is right, but
66
+ * it also made a genuinely parked job UNDECLARABLE — and the estate has one: `v-datacenter` is
67
+ * parked because syno1 is powered off, comment and runbook and all. A resource that cannot
68
+ * describe what is really there pushes the operator back to the CLI, which is the failure this
69
+ * package exists to end. Writing `schedule: null` costs a word and says the quiet part out
70
+ * loud; leaving the key out still will not compile.
71
+ * ⚠️ ADOPTING A SCHEDULELESS JOB WITH A SCHEDULE DECLARED GIVES IT ONE ON THE FIRST DEPLOY, which
72
+ * is the intended direction and is still a write nobody asked for in words. `matches` compares
73
+ * `schedule`, the live value reads `''`, so the first reconcile PUTs the declared one and
74
+ * verification starts running that night. Read the first plan against an adopted job.
75
+ * ⚠️ A systemd calendar event. Spelled `daily`, `sat 02:00`, `mon..fri 23:00`. A step expression
76
+ * is legal too and is not written out here: those two characters would close this comment.
77
+ */
78
+ schedule: string | null;
79
+ /**
80
+ * The namespace to verify, e.g. `tier1/prod`. Omitted means the datastore root.
81
+ * ⛔ NEVER SENT AS THE EMPTY STRING. MEASURED in the update handler: `if !ns.is_root()` — a root
82
+ * namespace on a PUT is accepted by the schema, reaches the handler and is then SILENTLY
83
+ * DISCARDED. A job cannot be moved back to the root namespace by this resource at all; that
84
+ * needs `--delete ns` by hand. So an undeclared `ns` is neither sent nor compared.
85
+ */
86
+ ns?: string;
87
+ /**
88
+ * How many namespace levels below `ns` to descend. `0` is that level only; PBS's maximum is 7.
89
+ * ⚠️ PBS's own default is FULL recursion (7), not 0. Undeclared means undeclared here: not sent,
90
+ * not compared, because "unset" and "7" are stored differently and only one is reachable.
91
+ */
92
+ 'max-depth'?: number;
93
+ /**
94
+ * ⛔ THE HALF OF THE PAIR THAT DECIDES WHETHER THIS JOB EVER RE-CHECKS ANYTHING. Defaults to
95
+ * TRUE. Read `rechecks` and the ⛔ in pbs-verify-job-form.ts before setting either of these.
96
+ */
97
+ 'ignore-verified'?: boolean;
98
+ /** Days after which a past verification is stale. ⛔ See the pair's ⛔ in the form file. */
99
+ 'outdated-after'?: number;
100
+ /** ⚠️ PBS TRIMS IT AND STORES AN EMPTY ONE AS ABSENT, so `' '` reads back as `''`. */
101
+ comment?: string;
102
+ }
103
+
104
+ export interface PbsVerifyJobAttributes {
105
+ id: string;
106
+ store: string;
107
+ schedule: string;
108
+ ns: string;
109
+ /** ⚠️ `-1` means "no max-depth in the section". `0` is a real setting meaning no recursion. */
110
+ 'max-depth': number;
111
+ 'ignore-verified': boolean;
112
+ /** ⚠️ `-1` means "unset", which is NOT `0`. See the ⛔ in the form file. */
113
+ 'outdated-after': number;
114
+ comment: string;
115
+ /**
116
+ * ★ WHAT THIS JOB ACTUALLY RE-CHECKS, IN ENGLISH, COMPUTED FROM THE LIVE PAIR. REPORTED, NEVER
117
+ * COMPARED — it is derived from two fields `matches` already compares, so diffing it would
118
+ * double-count them. It exists because the failure this family guards against is a job that
119
+ * looks healthy in every column and verifies each snapshot exactly once, ever; that job reads
120
+ * `never` here. A plan line is the only place anyone will see it.
121
+ */
122
+ rechecks: string;
123
+ }
124
+
125
+ export interface PbsVerifyJob extends Resource<
126
+ 'Pbs.VerifyJob',
127
+ PbsVerifyJobProps,
128
+ PbsVerifyJobAttributes,
129
+ never,
130
+ PveRequirements
131
+ > {}
132
+
133
+ /**
134
+ * ⚠️ NO `defaultRemovalPolicy: 'retain'`, DELIBERATELY, AND IT IS THE CLOSER CALL THAN IT LOOKS.
135
+ * The section holds no data — deleting it destroys nothing, the way `Proxmox.BackupJob`'s delete
136
+ * destroys no archives. What it destroys is the CHECKING, silently and permanently, and PBS will
137
+ * not refuse it the way it refuses to remove a datastore in use. `retain` would hide that behind
138
+ * a state-row drop instead of showing it as a plan line, and the plan line is the only warning
139
+ * anyone gets. Pipe `RemovalPolicy.retain()` per declaration where the job must outlive the code.
140
+ */
141
+ export const PbsVerifyJob = Resource<PbsVerifyJob>('Pbs.VerifyJob');
142
+
143
+ /**
144
+ * ⚠️ EVERY FIELD IS READ THROUGH `values.ts` THOUGH PBS RETURNS REAL JSON TYPES. Unlike PVE's
145
+ * SectionConfig round-trip, `GET /config/verify/{id}` serialises a Rust struct, so
146
+ * `ignore-verified` arrives as a JSON boolean and `max-depth` as a JSON number. `bool` and `int`
147
+ * accept both spellings, and using them costs nothing while covering the version that does not.
148
+ * ⚠️ `digest` IS NOT AN ATTRIBUTE AND CANNOT BECOME ONE BY ACCIDENT. PBS puts it on the rpcenv, and
149
+ * the JSON formatter adds rpcenv attributes as SIBLINGS of `data` — `{"data":{…},"digest":"…"}` —
150
+ * so `client.ts`, which returns `body.data`, never sees it. That is lucky rather than designed:
151
+ * the digest covers verification.cfg as a FILE, so keeping it would churn this resource's state
152
+ * whenever an unrelated verify job was edited.
153
+ */
154
+ const handlers = pveHandlers<PbsVerifyJobProps, PbsVerifyJobAttributes>({
155
+ attributes: (live, props) => {
156
+ const ignoreVerified = bool(live['ignore-verified'], true);
157
+ const outdatedAfter = int(live['outdated-after'], -1);
158
+ return {
159
+ comment: text(live['comment'], ''),
160
+ id: props.id,
161
+ 'ignore-verified': ignoreVerified,
162
+ 'max-depth': int(live['max-depth'], -1),
163
+ ns: text(live['ns'], ''),
164
+ 'outdated-after': outdatedAfter,
165
+ rechecks: rechecks(ignoreVerified, outdatedAfter),
166
+ schedule: text(live['schedule'], ''),
167
+ store: text(live['store'], props.store),
168
+ };
169
+ },
170
+ /**
171
+ * 🔴 THIS WAS `config/verification` AND PBS ANSWERS 404 FOR IT. MEASURED on the live 4.2 server:
172
+ * `GET /api2/json/config/verification` -> 404 "Path not found"; `GET /api2/json/config/verify`
173
+ * -> 200 with both jobs. The struct is `VerificationJobConfig` and the CLI subcommand is
174
+ * `verify-job`, so the long spelling reads right and is simply not the route.
175
+ * ⛔ THE FAILURE MODE IS THE ONE THIS PACKAGE KEEPS FINDING: `read` folds every failure into
176
+ * `undefined`, so a 404 from a WRONG PATH is indistinguishable from an object that is not
177
+ * there. The first plan against the live estate said `create` for two verification jobs that
178
+ * have existed for weeks — and a create would then have POSTed to a 404 as well, so it fails
179
+ * loudly rather than duplicating anything. Nothing caught it earlier because no verification
180
+ * job had ever been declared; registering the provider exercises no path at all.
181
+ */
182
+ collection: () => 'config/verify',
183
+ /** ⛔ `id` IS SENT. PBS refuses a duplicate id, and that refusal is this family's safety net. */
184
+ createForm: (props) => ({ ...shape(props), id: props.id }),
185
+ /**
186
+ * ⚠️ AN UNDECLARED FIELD IS NEITHER SENT NOR COMPARED — the `Proxmox.BackupJob` trade, for the
187
+ * same reason and with one extra: this form carries NO `delete` list, so there is nothing it
188
+ * could clear even if it wanted to. See the ⛔ on `withClears` in pbs-verify-job-form.ts.
189
+ * ⚠️ `store` IS COMPARED AND IS MUTABLE, which is unusual and worth reading twice. PBS's update
190
+ * handler assigns it and re-checks privileges on both the old and the new path, so changing
191
+ * `store` MOVES the job — and the datastore it left is then verified by nothing. That is a
192
+ * one-word edit with the same effect as deleting the job, and it plans as a quiet `update`.
193
+ * ⚠️ `rechecks` IS ABSENT HERE ON PURPOSE. It is a rendering of the two fields on the lines
194
+ * above; comparing it too would report the same drift twice.
195
+ */
196
+ matches: (attributes, props) =>
197
+ attributes.store === props.store &&
198
+ // ⚠️ `null` IS COMPARED AGAINST `''`, NOT SKIPPED. A parked job is a declaration like any
199
+ // other, so declaring `null` against a job that HAS a schedule must plan an update — the
200
+ // form then omits `schedule`, which is a set-only form, so see the ⛔ on clearing below.
201
+ attributes.schedule === (props.schedule ?? '') &&
202
+ attributes['ignore-verified'] === (props['ignore-verified'] !== false) &&
203
+ (props['outdated-after'] === undefined ||
204
+ attributes['outdated-after'] === props['outdated-after']) &&
205
+ (props['max-depth'] === undefined || attributes['max-depth'] === props['max-depth']) &&
206
+ (props.ns === undefined || attributes.ns === props.ns) &&
207
+ (props.comment === undefined || attributes.comment === props.comment),
208
+ path: (props) => `config/verify/${props.id}`,
209
+ updateForm: shape,
210
+ });
211
+
212
+ /**
213
+ * ⛔ `list` IS EMPTY, INHERITED FROM `pveHandlers`, AND THE REASON IS SHARPER HERE THAN ELSEWHERE.
214
+ * `GET /config/verify` returns every verification job the caller can see, including the
215
+ * ones the PBS installer and a human made. Adopting those would put Alchemy one dropped line
216
+ * away from DELETING a datastore's only verification schedule — and deleting it destroys nothing
217
+ * visible, so the loss is discovered the day a restore fails and there is no verification history
218
+ * to say when the rot started. Adoption stays explicit: declare the existing id.
219
+ *
220
+ * ⚠️ NO `digest` IS SENT ON UPDATE OR DELETE, SO THIS IS LAST-WRITER-WINS. PBS accepts an optional
221
+ * `digest` on both and would refuse a write made against a stale read. Sending one would need a
222
+ * read-then-write window this provider does not have — `pveOperations` reads and writes in two
223
+ * separate calls with two separate mints — so a concurrent edit from the PBS UI is overwritten
224
+ * rather than refused. Same behaviour as every PVE family here; stated because PBS offers better.
225
+ */
226
+ export const PbsVerifyJobProvider = () =>
227
+ Provider.effect(PbsVerifyJob, Effect.succeed(PbsVerifyJob.Provider.of(handlers)));
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pool family — compile-time and runtime checks against generated `/pools` schema types.
3
+ *
4
+ * ⚠️ THE DRIFT TEST IS THE POINT: if codegen renames a parameter or marks `poolid` optional on
5
+ * POST, this file fails `tsc` before anything reaches a cluster.
6
+ */
7
+ import assert from 'node:assert/strict';
8
+ import { describe, it } from 'node:test';
9
+ import type { PoolsPoolidPutParams, PoolsPostParams } from './generated/pve.ts';
10
+ import { type PoolProps, poolCreateForm, poolUpdateForm } from './pool.ts';
11
+
12
+ const target: PoolProps['target'] = {
13
+ members: ['example.test'],
14
+ mount: 'proxmox-test',
15
+ scheme: 'pve',
16
+ };
17
+
18
+ const props = (over: Partial<PoolProps> = {}): PoolProps => ({
19
+ target,
20
+ poolid: 'house',
21
+ ...over,
22
+ });
23
+
24
+ describe('pool forms match generated POST/PUT parameters', () => {
25
+ it('createForm sends exactly poolid and comment', () => {
26
+ const form: PoolsPostParams = poolCreateForm(props({ comment: 'estate' }));
27
+ assert.deepEqual(form, { poolid: 'house', comment: 'estate' });
28
+ assert.deepEqual(Object.keys(form).sort(), ['comment', 'poolid']);
29
+ });
30
+
31
+ it('updateForm sends only comment on the deprecated item path', () => {
32
+ const form: Pick<PoolsPoolidPutParams, 'comment'> = poolUpdateForm(props());
33
+ assert.deepEqual(form, { comment: '' });
34
+ assert.deepEqual(Object.keys(form), ['comment']);
35
+ });
36
+
37
+ it('would fail tsc if poolid were dropped from createForm', () => {
38
+ // @ts-expect-error poolid is required on POST /pools
39
+ const bad: PoolsPostParams = { comment: '' };
40
+ assert.equal('poolid' in bad, false);
41
+ });
42
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `Proxmox.Pool` — a PVE resource pool. Guests grouped for permissions and quota.
3
+ *
4
+ * ★ PROVEN END TO END AGAINST THE LIVE CLUSTER, 2026-09-13. create -> verified with `pvesh get
5
+ * /pools` on the node rather than by believing Alchemy; second plan -> `no changes / noop`; then
6
+ * the comment was edited ON THE CLUSTER and plan said `1 to update`, and a deploy repaired it.
7
+ * That drift case is the one that matters: `diff` reads the LIVE pool, so a provider trusting
8
+ * its own state would have reported noop straight past a hand-edit in the UI.
9
+ *
10
+ * ⚠️ IT 403'd UNTIL THE ROLE WAS WIDENED, AND THE HISTORY IS WORTH KEEPING. `hf-provision@pve`
11
+ * held `LXCProvisioner` on `/` with no `Pool.Allocate`, so reconcile answered
12
+ * "Permission check failed (/pool/house, Pool.Allocate)". The credential could not widen itself
13
+ * — `PUT /access/roles` is 403 for it too — so the role was extended over SSH with
14
+ * `pveum role modify LXCProvisioner --privs "<existing>,Pool.Allocate,Pool.Audit"`, preserving
15
+ * every existing privilege. Each new resource in this package should state the privileges its
16
+ * reconcile needs, so widening stays a deliberate act rather than a reaction to a 403.
17
+ */
18
+ import { Resource } from 'alchemy';
19
+ import * as Provider from 'alchemy/Provider';
20
+ import * as Effect from 'effect/Effect';
21
+ import type {
22
+ PoolsPoolidGetReturn,
23
+ PoolsPoolidPutParams,
24
+ PoolsPostParams,
25
+ } from './generated/pve.ts';
26
+ import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
27
+
28
+ export interface PoolProps extends WithTarget {
29
+ /** PVE's primary key for a pool. Changing it is a replace, not an update. */
30
+ poolid: string;
31
+ /** Free text shown in the UI — the only mutable field a pool has. */
32
+ comment?: string;
33
+ }
34
+
35
+ export interface PoolAttributes {
36
+ poolid: string;
37
+ comment: string;
38
+ /** Guests currently in the pool, so a plan can say what a delete would refuse to orphan. */
39
+ members: number;
40
+ }
41
+
42
+ export interface ProxmoxPool extends Resource<
43
+ 'Proxmox.Pool',
44
+ PoolProps,
45
+ PoolAttributes,
46
+ never,
47
+ PveRequirements
48
+ > {}
49
+
50
+ export const ProxmoxPool = Resource<ProxmoxPool>('Proxmox.Pool');
51
+
52
+ /** Form bodies this family sends — exported for the drift test in pool.test.ts. */
53
+ export const poolCreateForm = (props: PoolProps): PoolsPostParams => ({
54
+ comment: props.comment ?? '',
55
+ poolid: props.poolid,
56
+ });
57
+
58
+ /** PUT body — only `comment`; path carries `poolid`. Matches deprecated `/pools/{poolid}`. */
59
+ export const poolUpdateForm = (props: PoolProps): Pick<PoolsPoolidPutParams, 'comment'> => ({
60
+ comment: props.comment ?? '',
61
+ });
62
+
63
+ const handlers = pveHandlers<PoolProps, PoolAttributes>({
64
+ attributes: (live, props) => {
65
+ const row = live as PoolsPoolidGetReturn;
66
+ return {
67
+ comment: typeof row.comment === 'string' ? row.comment : '',
68
+ members: Array.isArray(row.members) ? row.members.length : 0,
69
+ poolid: props.poolid,
70
+ };
71
+ },
72
+ collection: () => 'pools',
73
+ createForm: poolCreateForm,
74
+ matches: (attributes, props) => attributes.comment === (props.comment ?? ''),
75
+ path: (props) => `pools/${props.poolid}`,
76
+ updateForm: poolUpdateForm,
77
+ });
78
+
79
+ export const ProxmoxPoolProvider = () =>
80
+ Provider.effect(ProxmoxPool, Effect.succeed(ProxmoxPool.Provider.of(handlers)));
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `Proxmox.Vm` — a QEMU virtual machine, declared.
3
+ *
4
+ * ⚠️ THE SAME FOUR OPERATIONS AS AN LXC OVER A DIFFERENT PATH, which is the point of the factory:
5
+ * `nodes/{node}/qemu` to create, `nodes/{node}/qemu/{vmid}/config` to read and update. A VM and a
6
+ * container differ in what they carry, not in how they are reconciled.
7
+ *
8
+ * ⛔ `vmid` IS CLUSTER-WIDE AND SHARED WITH CONTAINERS. A VM and an LXC cannot hold the same id, so
9
+ * these two resources compete for one number space. Declaring both with vmid 101 is not a
10
+ * collision Alchemy can see -- they are different resource types -- and PVE refuses the second
11
+ * with "already exists". Pick ids per cluster, not per kind.
12
+ *
13
+ * ⚠️ POWER STATE IS REPORTED, NEVER DECLARED. `status` is an attribute so a plan can show it; there
14
+ * is no `running` prop. Starting and stopping a VM from a plan would make a deploy a maintenance
15
+ * window, and the estate has one of those already.
16
+ */
17
+ import { Resource } from 'alchemy';
18
+ import * as Provider from 'alchemy/Provider';
19
+ import * as Effect from 'effect/Effect';
20
+ import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
21
+ import { num } from './values.ts';
22
+
23
+ export interface VmProps extends WithTarget {
24
+ node: string;
25
+ /** ⛔ Cluster-wide, and shared with LXC. See the ⛔ above. */
26
+ vmid: number;
27
+ name?: string;
28
+ /** MiB. */
29
+ memory?: number;
30
+ cores?: number;
31
+ sockets?: number;
32
+ /** e.g. `virtio=<mac>,bridge=vmbr0`. */
33
+ net0?: string;
34
+ onboot?: boolean;
35
+ }
36
+
37
+ export interface VmAttributes {
38
+ vmid: number;
39
+ node: string;
40
+ name: string;
41
+ memory: number;
42
+ cores: number;
43
+ sockets: number;
44
+ onboot: boolean;
45
+ }
46
+
47
+ export interface ProxmoxVm extends Resource<
48
+ 'Proxmox.Vm',
49
+ VmProps,
50
+ VmAttributes,
51
+ never,
52
+ PveRequirements
53
+ > {}
54
+
55
+ export const ProxmoxVm = Resource<ProxmoxVm>('Proxmox.Vm');
56
+
57
+ const shape = (props: VmProps) => ({
58
+ cores: String(props.cores ?? 1),
59
+ memory: String(props.memory ?? 512),
60
+ name: props.name ?? `vm${String(props.vmid)}`,
61
+ onboot: props.onboot === true ? '1' : '0',
62
+ sockets: String(props.sockets ?? 1),
63
+ ...(props.net0 === undefined ? {} : { net0: props.net0 }),
64
+ });
65
+
66
+ const handlers = pveHandlers<VmProps, VmAttributes>({
67
+ attributes: (live, props) => ({
68
+ cores: num(live['cores'], 1),
69
+ memory: num(live['memory'], 512),
70
+ name: typeof live['name'] === 'string' ? live['name'] : '',
71
+ node: props.node,
72
+ onboot: live['onboot'] === 1 || live['onboot'] === true,
73
+ sockets: num(live['sockets'], 1),
74
+ vmid: props.vmid,
75
+ }),
76
+ collection: (props) => `nodes/${props.node}/qemu`,
77
+ createForm: (props) => ({ ...shape(props), vmid: String(props.vmid) }),
78
+ matches: (attributes, props) =>
79
+ attributes.name === (props.name ?? `vm${String(props.vmid)}`) &&
80
+ attributes.memory === (props.memory ?? 512) &&
81
+ attributes.cores === (props.cores ?? 1) &&
82
+ attributes.sockets === (props.sockets ?? 1) &&
83
+ attributes.onboot === (props.onboot === true),
84
+ path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
85
+ updateForm: shape,
86
+ });
87
+
88
+ export const ProxmoxVmProvider = () =>
89
+ Provider.effect(ProxmoxVm, Effect.succeed(ProxmoxVm.Provider.of(handlers)));
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A replication job's props, as the id and the forms PVE wants.
3
+ *
4
+ * ★ SPLIT OUT OF replication-job.ts TO KEEP BOTH FILES UNDER THE 250-LINE CAP, on the same seam
5
+ * metric-server-form.ts uses and for the same reason: this file answers "how does a declaration
6
+ * become a PVE write", and replication-job.ts answers "what is a replication job and when has it
7
+ * changed". Nothing here reads the cluster and nothing here decides a diff.
8
+ *
9
+ * ⚠️ THE `import type` BACK TO replication-job.ts IS A CYCLE ON PAPER ONLY. It is type-only, so it
10
+ * is erased before anything runs, and `ReplicationJobProps` stays the resource's public shape in
11
+ * the file that declares the resource rather than being moved somewhere odd to dodge the arrow.
12
+ */
13
+ import type { ReplicationJobProps } from './replication-job.ts';
14
+
15
+ /**
16
+ * PVE's own documented default for `schedule`, applied on BOTH sides of the comparison.
17
+ *
18
+ * ⚠️ IT LIVES HERE BECAUSE BOTH HALVES NEED THE SAME STRING. `shape` sends it and `attributes`
19
+ * falls back to it; two copies of that literal in two files is exactly the drift that makes a
20
+ * plan report an update over a value nobody set.
21
+ * ⚠️ AND IT IS NEVER SPELLED OUT IN A COMMENT, because the string PVE chose ENDS A BLOCK COMMENT:
22
+ * the two characters before the `15` are the comment terminator, and tsc says so loudly. It is
23
+ * referred to by name everywhere else in these two files for that reason, not for style.
24
+ */
25
+ export const DEFAULT_SCHEDULE = '*/15';
26
+
27
+ /**
28
+ * `{guest, jobnum}` -> `100-0`, the id in the only spelling PVE ever answers with.
29
+ *
30
+ * ⚠️ NUMBERS IN, SO CANONICAL OUT. PVE runs both halves of a job id through `int()` on parse — see
31
+ * the ⛔ in replication-job.ts — and a JavaScript number has no leading-zero spelling to lose.
32
+ * That is the whole reason this resource is keyed on two numbers instead of one string.
33
+ */
34
+ export const jobId = (props: ReplicationJobProps) =>
35
+ `${String(props.guest)}-${String(props.jobnum)}`;
36
+
37
+ /**
38
+ * The mutable half, shared by create and update.
39
+ *
40
+ * ⚠️ `schedule`, `disable` AND `comment` ARE ALWAYS SENT, EVEN AT THEIR DEFAULTS, because they are
41
+ * always compared — and a field compared against a default but not SENT as that default is an
42
+ * update reported on every plan that the PUT never performs (backup-job.ts records the same
43
+ * trade). The empty string is how PVE is told to clear a comment. `rate` is the opposite case:
44
+ * not declared, not sent, not compared, so a rate limit set by hand survives adoption untouched.
45
+ * ⛔ `source` IS NEVER SENT, AND SENDING IT WOULD BE WORSE THAN REDUNDANT. MEASURED in
46
+ * API2/ReplicationConfig.pm: create does `$param->{source} //= $guest_info->{node}` — PVE fills
47
+ * it from wherever the guest actually is — and DIES with "Source '<x>' does not match current
48
+ * node of guest" when a declaration disagrees. There is no correct value to hold here.
49
+ */
50
+ const shape = (props: ReplicationJobProps) => ({
51
+ comment: props.comment ?? '',
52
+ disable: props.disable === true ? '1' : '0',
53
+ schedule: props.schedule ?? DEFAULT_SCHEDULE,
54
+ ...(props.rate === undefined ? {} : { rate: String(props.rate) }),
55
+ });
56
+
57
+ /**
58
+ * ⚠️ `type` IS A CONSTANT RATHER THAN A PROP: `local` is the only section type PVE registers, and
59
+ * an enum of one value is a prop that can only ever be wrong. `id` and `target` are both required
60
+ * by the create schema and neither belongs in `shape` — `target` because PUT will not take it.
61
+ * ⛔ THIS LINE IS THE ONLY PLACE PVE'S KEY `target` IS SPELLED, and the value beside it comes from
62
+ * `targetNode`. The two words are different objects — a node name here, the whole cluster in
63
+ * `WithTarget.target` — and the ⛔ in replication-job.ts explains why the rename was forced.
64
+ */
65
+ export const createBody = (props: ReplicationJobProps) => ({
66
+ ...shape(props),
67
+ id: jobId(props),
68
+ target: props.targetNode,
69
+ type: 'local',
70
+ });
71
+
72
+ /**
73
+ * ⛔ `delete=remove_job` ON EVERY UPDATE IS THE DELIBERATE PART. PVE's PUT merges the form into the
74
+ * section and cannot unset anything, so without this the removal marker a DELETE wrote would
75
+ * survive every reconcile and `matches` would report an update that never lands. MEASURED as
76
+ * legal: the update handler refuses `delete` only for required or `fixed` options, and
77
+ * `remove_job => { optional => 1 }` is neither; deleting a key that is not set is a no-op, so
78
+ * this is safe to send unconditionally.
79
+ * ⛔ AND THE DELETE LIST IS THIS ONE CONSTANT KEY, NEVER A COMPUTED "everything you did not
80
+ * declare". backup-job.ts spells out why the computed form is dangerous — it strips settings the
81
+ * resource does not even model off a job adopted from the UI. `remove_job` cannot be declared
82
+ * here at all, so clearing it can only ever undo a removal this stack did not ask for.
83
+ * ⚠️ IT CANCELS A REMOVAL SOMEBODY STARTED, WHICH IS THE POINT AND IS STILL WORTH KNOWING. If the
84
+ * removal pass already ran its local-snapshot half, the revived job re-sends in full.
85
+ */
86
+ export const updateBody = (props: ReplicationJobProps) => ({
87
+ ...shape(props),
88
+ delete: 'remove_job',
89
+ });