@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,42 @@
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 type { PoolsPoolidPutParams, PoolsPostParams } from './generated/pve.ts';
21
+ import { type PveRequirements, type WithTarget } from './resource.ts';
22
+ export interface PoolProps extends WithTarget {
23
+ /** PVE's primary key for a pool. Changing it is a replace, not an update. */
24
+ poolid: string;
25
+ /** Free text shown in the UI — the only mutable field a pool has. */
26
+ comment?: string;
27
+ }
28
+ export interface PoolAttributes {
29
+ poolid: string;
30
+ comment: string;
31
+ /** Guests currently in the pool, so a plan can say what a delete would refuse to orphan. */
32
+ members: number;
33
+ }
34
+ export interface ProxmoxPool extends Resource<'Proxmox.Pool', PoolProps, PoolAttributes, never, PveRequirements> {
35
+ }
36
+ export declare const ProxmoxPool: import("alchemy").ResourceClass<ProxmoxPool>;
37
+ /** Form bodies this family sends — exported for the drift test in pool.test.ts. */
38
+ export declare const poolCreateForm: (props: PoolProps) => PoolsPostParams;
39
+ /** PUT body — only `comment`; path carries `poolid`. Matches deprecated `/pools/{poolid}`. */
40
+ export declare const poolUpdateForm: (props: PoolProps) => Pick<PoolsPoolidPutParams, 'comment'>;
41
+ export declare const ProxmoxPoolProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxPool>, never, import("effect/unstable/http/HttpClient").HttpClient>;
42
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,cAAc,UAAW,SAAS,KAAG,eAGhD,CAAC;AAEH,8FAA8F;AAC9F,eAAO,MAAM,cAAc,UAAW,SAAS,KAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAEpF,CAAC;AAkBH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}
@@ -0,0 +1,46 @@
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 { type PveRequirements, type WithTarget } from './resource.ts';
20
+ export interface VmProps extends WithTarget {
21
+ node: string;
22
+ /** ⛔ Cluster-wide, and shared with LXC. See the ⛔ above. */
23
+ vmid: number;
24
+ name?: string;
25
+ /** MiB. */
26
+ memory?: number;
27
+ cores?: number;
28
+ sockets?: number;
29
+ /** e.g. `virtio=<mac>,bridge=vmbr0`. */
30
+ net0?: string;
31
+ onboot?: boolean;
32
+ }
33
+ export interface VmAttributes {
34
+ vmid: number;
35
+ node: string;
36
+ name: string;
37
+ memory: number;
38
+ cores: number;
39
+ sockets: number;
40
+ onboot: boolean;
41
+ }
42
+ export interface ProxmoxVm extends Resource<'Proxmox.Vm', VmProps, VmAttributes, never, PveRequirements> {
43
+ }
44
+ export declare const ProxmoxVm: import("alchemy").ResourceClass<ProxmoxVm>;
45
+ export declare const ProxmoxVmProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxVm>, never, import("effect/unstable/http/HttpClient").HttpClient>;
46
+ //# sourceMappingURL=qemu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qemu.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,OAAQ,SAAQ,UAAU;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ,CACzC,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,SAAS,4CAAoC,CAAC;AAiC3D,eAAO,MAAM,iBAAiB,+HAC+C,CAAC"}
@@ -0,0 +1,71 @@
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
+ * PVE's own documented default for `schedule`, applied on BOTH sides of the comparison.
16
+ *
17
+ * ⚠️ IT LIVES HERE BECAUSE BOTH HALVES NEED THE SAME STRING. `shape` sends it and `attributes`
18
+ * falls back to it; two copies of that literal in two files is exactly the drift that makes a
19
+ * plan report an update over a value nobody set.
20
+ * ⚠️ AND IT IS NEVER SPELLED OUT IN A COMMENT, because the string PVE chose ENDS A BLOCK COMMENT:
21
+ * the two characters before the `15` are the comment terminator, and tsc says so loudly. It is
22
+ * referred to by name everywhere else in these two files for that reason, not for style.
23
+ */
24
+ export declare const DEFAULT_SCHEDULE = "*/15";
25
+ /**
26
+ * `{guest, jobnum}` -> `100-0`, the id in the only spelling PVE ever answers with.
27
+ *
28
+ * ⚠️ NUMBERS IN, SO CANONICAL OUT. PVE runs both halves of a job id through `int()` on parse — see
29
+ * the ⛔ in replication-job.ts — and a JavaScript number has no leading-zero spelling to lose.
30
+ * That is the whole reason this resource is keyed on two numbers instead of one string.
31
+ */
32
+ export declare const jobId: (props: ReplicationJobProps) => string;
33
+ /**
34
+ * ⚠️ `type` IS A CONSTANT RATHER THAN A PROP: `local` is the only section type PVE registers, and
35
+ * an enum of one value is a prop that can only ever be wrong. `id` and `target` are both required
36
+ * by the create schema and neither belongs in `shape` — `target` because PUT will not take it.
37
+ * ⛔ THIS LINE IS THE ONLY PLACE PVE'S KEY `target` IS SPELLED, and the value beside it comes from
38
+ * `targetNode`. The two words are different objects — a node name here, the whole cluster in
39
+ * `WithTarget.target` — and the ⛔ in replication-job.ts explains why the rename was forced.
40
+ */
41
+ export declare const createBody: (props: ReplicationJobProps) => {
42
+ comment: string;
43
+ disable: string;
44
+ schedule: string;
45
+ rate?: string;
46
+ id: string;
47
+ target: string;
48
+ type: string;
49
+ };
50
+ /**
51
+ * ⛔ `delete=remove_job` ON EVERY UPDATE IS THE DELIBERATE PART. PVE's PUT merges the form into the
52
+ * section and cannot unset anything, so without this the removal marker a DELETE wrote would
53
+ * survive every reconcile and `matches` would report an update that never lands. MEASURED as
54
+ * legal: the update handler refuses `delete` only for required or `fixed` options, and
55
+ * `remove_job => { optional => 1 }` is neither; deleting a key that is not set is a no-op, so
56
+ * this is safe to send unconditionally.
57
+ * ⛔ AND THE DELETE LIST IS THIS ONE CONSTANT KEY, NEVER A COMPUTED "everything you did not
58
+ * declare". backup-job.ts spells out why the computed form is dangerous — it strips settings the
59
+ * resource does not even model off a job adopted from the UI. `remove_job` cannot be declared
60
+ * here at all, so clearing it can only ever undo a removal this stack did not ask for.
61
+ * ⚠️ IT CANCELS A REMOVAL SOMEBODY STARTED, WHICH IS THE POINT AND IS STILL WORTH KNOWING. If the
62
+ * removal pass already ran its local-snapshot half, the revived job re-sends in full.
63
+ */
64
+ export declare const updateBody: (props: ReplicationJobProps) => {
65
+ comment: string;
66
+ disable: string;
67
+ schedule: string;
68
+ rate?: string;
69
+ delete: string;
70
+ };
71
+ //# sourceMappingURL=replication-job-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-job-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/replication-job-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,UAAW,mBAAmB,WACE,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,UAAW,mBAAmB;;;;;;;;CAKnD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,UAAW,mBAAmB;;;;;;CAGnD,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `Proxmox.ReplicationJob` — a guest's ZFS storage replication to a second node, declared.
3
+ *
4
+ * ⛔ NOTHING ON TB4 CAN USE THIS FAMILY, AND THAT IS SAID HERE RATHER THAN FOUND OUT LATER. PVE
5
+ * storage replication is ZFS-only: `POST /cluster/replication` calls `get_replicatable_volumes`
6
+ * and refuses a guest whose disks are anywhere else with "No replicatable volumes found". TB4 is
7
+ * Ceph/RBD-backed — shared storage, where replication means nothing because every node already
8
+ * sees the volume — so `GET /cluster/replication` answers `[]` (MEASURED on n2, 2026-09-13) and
9
+ * on this cluster always will. The file is written for a ZFS cluster; against TB4 a declaration
10
+ * is a create that fails with PVE's own sentence, not a plan that quietly does nothing.
11
+ *
12
+ * ★ WHAT IS MEASURED HERE AND WHAT IS REASONED. Measured on n2 (pve-manager 9.2.11), read-only:
13
+ * the published schema for all five methods; `PVE/ReplicationConfig.pm` and
14
+ * `PVE/API2/ReplicationConfig.pm` line by line; `GET /cluster/replication` -> `[]`; and the ACL
15
+ * row `{"path":"/","propagate":1,"roleid":"PVEAuditor","ugid":"hf-read@pve"}`. NOT measured: any
16
+ * round trip. There is no live job to adopt and none can be created on this cluster, so the
17
+ * argument that a matching declaration plans as `noop` rests on the schema and on PVE's own
18
+ * source — not on a plan anybody ran. Nobody should claim otherwise until a ZFS cluster exists.
19
+ *
20
+ * ⛔ THE ID IS STRUCTURED AND PVE RE-SPELLS IT, so this resource is keyed on the two numbers rather
21
+ * than on the string — a deliberate break from `backup-job.ts`'s "props are PVE's own key names".
22
+ * MEASURED: `parse_replication_job_id` matches `^(\d+)-(\d+)$`, runs BOTH halves through `int()`
23
+ * and returns "$guest-$jobnum", and `parse_section_header` does the same when reading
24
+ * replication.cfg. So a declared `100-007` is read back as `100-7`, and a provider holding the
25
+ * raw string would compare two spellings of one id forever. Two numbers cannot be spelled two
26
+ * ways. PVE also refuses `guest < 100` ("guest IDs < 100 are reserved") and the API pattern
27
+ * `[1-9][0-9]{2,8}-\d{1,9}` caps both halves at nine digits.
28
+ * ⛔ BOTH HALVES ARE REQUIRED AND NEITHER IS DEFAULTED, for `backup-job.ts`'s reason: a provider
29
+ * that picks a job number picks a DIFFERENT job from the one a person meant, and then adopts or
30
+ * creates beside the real one. Declare the numbers of the job you mean.
31
+ *
32
+ * ⛔ PVE'S `target` AND THIS PACKAGE'S `target` ARE DIFFERENT THINGS, AND THE COMPILER SAID SO
33
+ * BEFORE A REVIEWER DID. Every resource here extends `WithTarget`, whose `target` is the CLUSTER
34
+ * a call goes to (`PveTarget`, carrying the API base and the credential mount). PVE's replication
35
+ * `target` is a NODE NAME. They cannot both be called `target` in one props interface — tsc:
36
+ * "Interface 'ReplicationJobProps' incorrectly extends interface 'WithTarget'" — so the prop and
37
+ * the attribute are `targetNode`, and PVE's spelling appears exactly once, in `createBody`, where
38
+ * it is the wire key. That is the only safe shape: had the collision been resolvable by silence,
39
+ * `createForm` would have serialised a whole `PveTarget` object into a form field called `target`
40
+ * and PVE would have answered that the node does not exist.
41
+ *
42
+ * ⛔ ONE JOB PER (GUEST, TARGET), ENFORCED BY PVE AND NOT BY THIS FILE. MEASURED: `write_config`
43
+ * dies "replication job for guest '$vmid' to target '$tid' already exists" where `$tid` is
44
+ * `local/<target>`, and `parse_config` DELETES the loser on read if two ever reach the file. Two
45
+ * declarations differing only in `jobnum` but naming the same target are not two jobs; the second
46
+ * is a create that fails.
47
+ *
48
+ * ⚠️ RECONCILE NEEDS `VM.Replicate` ON `/vms/<guest>`, WHICH `LXCProvisioner` DOES NOT HOLD. All
49
+ * three writes check it (MEASURED in API2/ReplicationConfig.pm: create, update and delete each
50
+ * call `$rpcenv->check($authuser, "/vms/$vmid", ['VM.Replicate'])`), and the role's 27 privileges
51
+ * include `VM.Allocate`, `VM.Audit`, `VM.Backup`, `VM.Config.*` and `VM.PowerMgmt` but not
52
+ * `VM.Replicate`. Widen it deliberately over SSH, preserving what is there, as `pool.ts` records:
53
+ * `pveum role modify LXCProvisioner --privs "<existing>,VM.Replicate"`.
54
+ * ★ THE READ LANE NEEDS NOTHING EXTRA, which is why there is no `readRole` below. The ITEM read
55
+ * checks `VM.Audit` on `/vms/<guest>`, `hf-read@pve` holds the built-in `PVEAuditor` at `/`
56
+ * with propagate — both MEASURED — and `PVEAuditor` carries `VM.Audit`. This family is not a
57
+ * fourth one that must borrow the provision lease.
58
+ * ⚠️ A MISSING JOB IS A 500, NOT A 404 — "no such replication job '100-0'" — and the factory
59
+ * folds any failed read into "absent", so a 403 reads as absent too. A plan insisting on
60
+ * creating a job that plainly exists is a credential problem; the honest error arrives one step
61
+ * later, from the create.
62
+ *
63
+ * ⛔ DELETE DOES NOT DELETE, AND A DESTROY IS NOT FREE. MEASURED: with neither `force` nor `keep` —
64
+ * and the factory's `destroy` sends no form at all — the handler only sets `remove_job = 'full'`
65
+ * on the job and writes the config back. The job stays in replication.cfg until the SOURCE node's
66
+ * `pvesr` timer next runs it, at which point it removes the local replication snapshots, removes
67
+ * the replicated volumes on the target, and finally removes itself. So a destroy costs the
68
+ * standby copy and the incremental base: re-declaring the job afterwards is a FULL send, not a
69
+ * resume. The guest's own disks are untouched, which is exactly why this family does NOT carry
70
+ * `defaultRemovalPolicy: 'retain'` the way `ZfsPool` and `Storage` do — what it destroys is a
71
+ * copy a re-sync rebuilds, expensively, rather than something irreplaceable.
72
+ * ⚠️ AND IF THE SOURCE NODE IS DOWN THE REMOVAL NEVER RUNS. Alchemy drops the state row on a
73
+ * DELETE that returned no error, so a destroy against a dead source leaves a marked job on the
74
+ * cluster that nothing declares any more. `GET /cluster/replication` is where it shows up.
75
+ *
76
+ * ⚠️ THE RUNTIME STATUS IS NOT HERE, AND NOT BECAUSE IT WAS FORGOTTEN. `last_sync`, `last_try`,
77
+ * `fail_count`, `error`, `duration` and `pid` come from the per-node replication STATE file and
78
+ * are merged in by `GET /nodes/{node}/replication` (MEASURED: API2/Replication.pm line 179,
79
+ * `foreach my $k (qw(last_sync last_try fail_count error duration))`). `GET
80
+ * /cluster/replication/{id}` — the only endpoint this resource reads — returns not one of them.
81
+ * Reporting them would mean a second, per-node call on the read path answering a different value
82
+ * every fifteen minutes, and a plan whose output changes when nothing changed is a plan people
83
+ * stop reading. Ask the node for status; ask the cluster for configuration.
84
+ */
85
+ import { Resource } from 'alchemy';
86
+ import * as Provider from 'alchemy/Provider';
87
+ import { type PveRequirements, type WithTarget } from './resource.ts';
88
+ export interface ReplicationJobProps extends WithTarget {
89
+ /** ⛔ The guest being replicated. 100 or above — PVE reserves everything below. Identity. */
90
+ guest: number;
91
+ /** ⛔ The job number within that guest, 0 upwards. Identity. Never generated — see the header. */
92
+ jobnum: number;
93
+ /**
94
+ * ⛔ THE NODE THE COPY LANDS ON — PVE's `target`, RENAMED; see the ⛔ in the header — AND IT IS
95
+ * CREATE-ONLY. MEASURED: the plugin declares `target => { fixed => 1, optional => 0 }`, so
96
+ * `updateSchema()` omits it entirely — `PUT` has no `target` parameter at all, and
97
+ * `delete=target` answers "unable to delete fixed option". It is therefore NOT compared; see
98
+ * the ⚠️ on `matches`. Changing it in a declaration plans as NOOP. Retire the declaration and
99
+ * add another, exactly as `user.ts` says for `userid`.
100
+ * ⚠️ PVE refuses a target equal to the guest's current node ("Source and target must not be
101
+ * identical") and one that is not a cluster member. Neither is checkable from here.
102
+ */
103
+ targetNode: string;
104
+ /**
105
+ * A subset of systemd calendar events. Unset means PVE's own default — `DEFAULT_SCHEDULE`,
106
+ * every fifteen minutes — and that default is what gets SENT and compared, not silence.
107
+ * ⚠️ THIS COMPARES STRINGS, NOT MEANINGS. A calendar event has many equivalent spellings and
108
+ * PVE stores the one it was handed, verbatim, so declare the spelling the cluster echoes.
109
+ */
110
+ schedule?: string;
111
+ /** Rate limit in mbps as a float, 1 or above. Unset sends nothing and compares nothing. */
112
+ rate?: number;
113
+ /** Keep the job but stop replicating. Unset is an ACTIVE job, and that is what is compared. */
114
+ disable?: boolean;
115
+ /** Free text shown in the UI. Round-trips through PVE's `encode_text`/`decode_text`. */
116
+ comment?: string;
117
+ }
118
+ export interface ReplicationJobAttributes {
119
+ /** `100-0`, in PVE's own int-parsed spelling. */
120
+ id: string;
121
+ /**
122
+ * ⚠️ REPORTED AS INTEGERS, NEVER COMPARED, AND THIS IS THE "id written as a name" TRAP. MEASURED:
123
+ * `parse_config` sets both from the section header via `int()`, so they are the id re-spelled
124
+ * as numbers and no write accepts either. Compared against anything they would be an update
125
+ * forever; reported, they let a plan say which guest is about to lose a replica.
126
+ */
127
+ guest: number;
128
+ jobnum: number;
129
+ /** Always `local` — the only section type PVE registers. Create-only, reported, not compared. */
130
+ type: string;
131
+ /**
132
+ * ⚠️ REPORTED, NEVER COMPARED. Create-only AND rewritten by PVE — see `matches`. Named for the
133
+ * prop rather than for PVE's wire key `target`, so that one file never means two things by one
134
+ * word; the header says why the wire name could not be kept.
135
+ */
136
+ targetNode: string;
137
+ /** ⚠️ REPORTED, NEVER SENT. PVE's own bookkeeping — see the ⛔ in replication-job-form.ts. */
138
+ source: string;
139
+ schedule: string;
140
+ /** 0 when unset. See `rateOf`. */
141
+ rate: number;
142
+ disable: boolean;
143
+ comment: string;
144
+ /**
145
+ * `''`, `local` or `full`. ⛔ NOT COSMETIC: a non-empty value is a job that is deleting itself,
146
+ * so `matches` refuses it and `updateBody` clears it. See both.
147
+ */
148
+ remove_job: string;
149
+ }
150
+ export interface ProxmoxReplicationJob extends Resource<'Proxmox.ReplicationJob', ReplicationJobProps, ReplicationJobAttributes, never, PveRequirements> {
151
+ }
152
+ export declare const ProxmoxReplicationJob: import("alchemy").ResourceClass<ProxmoxReplicationJob>;
153
+ /**
154
+ * ⛔ Empty `list` like every other resource here: `GET /cluster/replication` hands back every job an
155
+ * operator ever made, and adopting one is how a later `alchemy destroy` takes away a standby copy
156
+ * nobody declared. Adoption is an explicit act — declare the `guest` and `jobnum` you mean.
157
+ * ⚠️ NOTHING BRAKES THIS DESTROY. PVE refuses to delete a pool that still holds guests; it accepts
158
+ * this without a word, because nothing depends on a replication job. Re-read the DELETE ⛔ at the
159
+ * top of this file before approving a plan line that removes one.
160
+ */
161
+ export declare const ProxmoxReplicationJobProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxReplicationJob>, never, import("effect/unstable/http/HttpClient").HttpClient>;
162
+ //# sourceMappingURL=replication-job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-job.d.ts","sourceRoot":"","sources":["../../src/proxmox/replication-job.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,mBAAoB,SAAQ,UAAU;IACrD,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;IACd,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAA4D,CAAC;AAwE/F;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,2IAIvC,CAAC"}
@@ -0,0 +1,157 @@
1
+ import type { Input } from 'alchemy/Input';
2
+ import * as Effect from 'effect/Effect';
3
+ import type * as HttpClient from 'effect/unstable/http/HttpClient';
4
+ import type { ApiTarget, PbsTarget, PveRole, PveTarget } from './credentials.ts';
5
+ /**
6
+ * ★ WHY SEVERAL FAMILIES HERE DECLARE `defaultRemovalPolicy: 'retain'`, WRITTEN ONCE.
7
+ *
8
+ * Alchemy's own convention: a few resource types default to retain "because their contents are
9
+ * irreplaceable" — `GitHub.Repository` and `Cloudflare.Zone` do — and those opt into deletion
10
+ * with `destroy()`. `retain` means the engine SKIPS `provider.delete` entirely when a resource
11
+ * is orphaned or destroyed: the state row is dropped, the cluster object lives on.
12
+ *
13
+ * So the PVE families whose contents cannot be rebuilt from a line of TypeScript — CephOsd,
14
+ * CephPool, CephFs, CephDaemon, ZfsPool, Storage, NodeNetwork — carry that default. Their
15
+ * `delete` is FULLY IMPLEMENTED and runs the moment a caller opts in with
16
+ * `.pipe(RemovalPolicy.destroy())`. This is deliberately the Terraform `prevent_destroy` shape
17
+ * rather than a stubbed-out operation: a `delete` that silently does nothing lies to whoever is
18
+ * reading the plan, and the lie is discovered at the worst possible time.
19
+ *
20
+ * ⚠️ THE POLICY IS A DECORATION, NOT A PROP, so changing it produces NO DIFF — the resource plans
21
+ * as a noop and the deploy re-commits the row's policy in place. A policy change therefore takes
22
+ * effect from the very next deploy even though the plan shows nothing.
23
+ */
24
+ /**
25
+ * ⛔ A RESOURCE IS PINNED TO ITS PRODUCT; ONLY THE FACTORY TAKES EITHER. The two hosts are the same
26
+ * two strings, so without a required discriminant TypeScript would accept a PBS host wherever a
27
+ * PVE one belongs — silently, with every call 401ing and `read` folding that into "absent". The
28
+ * whole argument is on `scheme` in credentials.ts.
29
+ */
30
+ /**
31
+ * ★ WHAT EVERY OPERATION IN THIS PACKAGE NEEDS FROM THE RUNTIME, NAMED ONCE.
32
+ *
33
+ * `HttpClient`, for both halves of every call: the credential is minted from OpenBao's HTTP API
34
+ * and the PVE call goes through the same client. That is what Alchemy's own providers do
35
+ * (src/Hetzner/Providers.ts builds on `FetchHttpClient.layer`, and there is no bare `fetch` in
36
+ * its Docker, Kubernetes or GitHub providers).
37
+ * ★ `ChildProcessSpawner` WAS HERE UNTIL 2026-09-14, while the mint shelled out to `bao`. The HTTP
38
+ * client replaced that (credentials.ts), so the requirement went with it.
39
+ *
40
+ * ⚠️ SPELLED OUT AT EACH OF THE FORTY-ODD RESOURCES this would be a union nobody keeps in step —
41
+ * one file left on the old shape is a type error at the stack, far from the cause. Named here,
42
+ * adding a third service later is one edit.
43
+ */
44
+ export type PveRequirements = HttpClient.HttpClient;
45
+ export type WithTarget = {
46
+ target: PveTarget;
47
+ };
48
+ export type WithPbsTarget = {
49
+ target: PbsTarget;
50
+ };
51
+ export type WithApiTarget = {
52
+ target: ApiTarget;
53
+ };
54
+ export type PveSpec<Props extends WithApiTarget, Attributes> = {
55
+ /** `pools/house`, `nodes/n2/lxc/101` — where ONE object is read, updated and deleted. */
56
+ readonly path: (props: Props) => string;
57
+ /** `pools`, `nodes/n2/lxc` — where a NEW one is POSTed. */
58
+ readonly collection: (props: Props) => string;
59
+ /** Live JSON to attributes. Returning undefined means "this is not really there". */
60
+ readonly attributes: (live: Record<string, unknown>, props: Props) => Attributes | undefined;
61
+ /** The form PVE wants on create. ⚠️ PVE takes form encoding, not JSON. */
62
+ readonly createForm: (props: Props) => Record<string, string>;
63
+ /**
64
+ * The form for an update, or undefined when the object has no mutable fields.
65
+ *
66
+ * ⚠️ SOME PVE OBJECTS CANNOT BE UPDATED AT ALL. Returning undefined makes a changed prop a
67
+ * REPLACE rather than a silent no-op, which is the honest answer for an immutable object.
68
+ */
69
+ readonly updateForm?: (props: Props) => Record<string, string>;
70
+ /** True when live already matches props. Decides noop vs update. */
71
+ readonly matches: (attributes: Attributes, props: Props) => boolean;
72
+ /**
73
+ * Which lease reads this family. Defaults to `read`, the 3600s auditor-shaped one.
74
+ *
75
+ * ⛔ THREE FAMILIES SET THIS TO `provision`, AND THE FAILURE IT AVOIDS IS SILENT. PVE gates some
76
+ * SINGLE-OBJECT reads on the allocate privilege rather than the audit one — `/storage/{id}`,
77
+ * `/cluster/sdn/zones/{zone}`, `/cluster/sdn/vnets/{vnet}` — while their COLLECTION reads
78
+ * accept audit, so nothing looks wrong until a resource reads one object. `read` below folds
79
+ * every failure into `undefined`, right for a 404 and wrong for a 403, so the plan says create
80
+ * and PVE answers that the object already exists. Measured both ways on `local`, and written
81
+ * up with the alternative that was rejected, in docs/privileges.md.
82
+ */
83
+ readonly readRole?: PveRole;
84
+ };
85
+ export declare const pveOperations: <Props extends WithApiTarget, Attributes>(spec: PveSpec<Props, Attributes>) => {
86
+ read: (props: Props) => Effect.Effect<Attributes | undefined, never, HttpClient.HttpClient>;
87
+ diff: (news: Input<Props>, output: Attributes | undefined) => Effect.Effect<{
88
+ readonly action: 'update';
89
+ } | {
90
+ readonly action: 'noop';
91
+ } | {
92
+ readonly action: 'replace';
93
+ } | undefined, never, HttpClient.HttpClient>;
94
+ reconcile: (news: Props) => Effect.Effect<Attributes & ({} | null), Error, HttpClient.HttpClient>;
95
+ /**
96
+ * ⚠️ PVE REFUSES TO DELETE THINGS THAT ARE STILL IN USE — a pool holding guests, a storage with
97
+ * volumes. That refusal is the cluster declining to orphan something because a line left a
98
+ * file, so it is surfaced as-is rather than retried with a force flag.
99
+ */
100
+ destroy: (olds: Props) => Effect.Effect<unknown, Error, HttpClient.HttpClient>;
101
+ };
102
+ /**
103
+ * The five provider handlers for a spec'd PVE object, wired once.
104
+ *
105
+ * ⛔ THIS BLOCK WAS WRITTEN TEN TIMES BEFORE IT WAS EXTRACTED, and every copy was character for
106
+ * character identical apart from its comments: `list` answering empty, and four handlers whose
107
+ * entire body was `yield* ops.<same name>(<same argument>)`. Ten copies of a delegation is ten
108
+ * chances to delegate to the wrong one — `ops.read(olds)` inside `reconcile` typechecks, returns
109
+ * a plausible value, and turns every deploy into a no-op that reports success.
110
+ *
111
+ * ★ IT TAKES THE SPEC, NOT THE OPERATIONS, so a resource declares its four spec functions and
112
+ * stops. `pveOperations` stays exported for an object that genuinely needs a handler of its own;
113
+ * nothing in this package does yet, and the day one does it should be visibly different from the
114
+ * ten that are not.
115
+ *
116
+ * ⛔ IT STOPS AT THE HANDLERS AND DOES NOT RETURN THE LAYER, AND THAT IS NOT AN OVERSIGHT — it was
117
+ * written the other way first and reverted. Wrapping `Provider.effect(cls, …)` here means naming
118
+ * the resource class generically, and Alchemy's `Props<R>` and `R["Attributes"]` are not the
119
+ * plain `Props` and `Attributes` this spec is written against: `Attributes` comes back as
120
+ * `Attributes & AttrOutput<Attributes["Attributes"]>`. Against a free type variable TypeScript
121
+ * cannot prove those line up — "could be instantiated with a different subtype" — so the only
122
+ * way to compile a Layer-returning version is a cast, and a cast is a LIE ABOUT WHETHER THE
123
+ * HANDLERS MATCH THE RESOURCE, which is the one thing the call site exists to check. Leaving
124
+ * `Cls.Provider.of(...)` in each file keeps that check where the types are concrete and real.
125
+ * One line per resource is the price of it, and it is worth paying.
126
+ *
127
+ * ⚠️ `list` IS OPTIONAL IN `ProviderServiceInput` AND IS PASSED ANYWAY. An empty list is a claim —
128
+ * this provider adopts nothing — and it is made here for every PVE object at once, because the
129
+ * reasoning is identical for all of them: every PVE index endpoint answers with the whole
130
+ * cluster's objects, PVE's own built-ins included. `GET /pools` returns pools a human made years
131
+ * ago; `GET /access/roles` returns `Administrator`. Returning any of them would invite Alchemy to
132
+ * adopt an object it never created, and therefore one day to narrow or delete it. Adoption stays
133
+ * an explicit act. A resource with a reason to differ passes its own `list` and says why.
134
+ */
135
+ export declare const pveHandlers: <Props extends WithApiTarget, Attributes extends object>(spec: PveSpec<Props, Attributes>) => {
136
+ list: () => Effect.Effect<never[], never, never>;
137
+ read: ({ olds }: {
138
+ olds: Props;
139
+ }) => Effect.Effect<Attributes | undefined, never, HttpClient.HttpClient>;
140
+ diff: ({ news, output }: {
141
+ news: Input<Props>;
142
+ output: Attributes | undefined;
143
+ }) => Effect.Effect<{
144
+ readonly action: 'update';
145
+ } | {
146
+ readonly action: 'noop';
147
+ } | {
148
+ readonly action: 'replace';
149
+ } | undefined, never, HttpClient.HttpClient>;
150
+ reconcile: ({ news }: {
151
+ news: Props;
152
+ }) => Effect.Effect<Attributes, Error, HttpClient.HttpClient>;
153
+ delete: ({ olds }: {
154
+ olds: Props;
155
+ }) => Effect.Effect<unknown, Error, HttpClient.HttpClient>;
156
+ };
157
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/proxmox/resource.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,UAAU,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAC/C,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,OAAO,CAAC,KAAK,SAAS,aAAa,EAAE,UAAU,IAAI;IAC7D,yFAAyF;IACzF,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IACxC,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC9C,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,UAAU,GAAG,SAAS,CAAC;IAC7F,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9D;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/D,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACpE;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,SAAS,aAAa,EAAE,UAAU,QAC7D,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC;kBAGX,KAAK;iBAiBX,KAAK,CAAC,KAAK,CAAC,UAAU,UAAU,GAAG,SAAS;yBASZ,QAAQ;;yBACF,MAAM;;yBAGtC,SAAS;;sBAIV,KAAK;IA6DvB;;;;OAIG;oBACa,KAAK;CAExB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,SAAS,aAAa,EAAE,UAAU,SAAS,MAAM,QAC1E,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC;IAI9B,IAAI;IACJ,IAAI,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IAChC,IAAI,qBAAqB;QAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;KAAE;yBAtHlC,QAAQ;;yBACF,MAAM;;yBAGtC,SAAS;;IAoH5B,SAAS,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IACrC,MAAM,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;CAErC,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `Proxmox.Role` — a PVE role: one name, one set of privileges. The object every other resource
3
+ * in this package stands on, because a role is what the provision credential actually holds.
4
+ *
5
+ * ★ THIS IS WHERE pool.ts's ⚠️ STOPS BEING A MEMORY. That file records, in prose, that the
6
+ * provision role lacked `Pool.Allocate`, that reconcile answered "Permission check failed", and
7
+ * that a human widened the role over SSH with `pveum role modify`. Prose cannot be planned
8
+ * against: it goes stale the first time somebody edits the role in the UI and tells nobody.
9
+ * Declared here, the same fact is a diff — the privileges the role is SUPPOSED to hold are read
10
+ * off the cluster on every plan, and a hand edit shows up as `1 to update` rather than as a 403
11
+ * six weeks later in the middle of something else.
12
+ *
13
+ * ⛔ THE INDEX AND THE ITEM DISAGREE ABOUT WHAT `privs` IS, AND MISSING THAT COSTS A DIFF THAT
14
+ * NEVER CONVERGES. `GET /access/roles` (the index) reports each role's privileges as a COMMA
15
+ * STRING. `GET /access/roles/{roleid}` — the path this resource reads — returns a privilege MAP
16
+ * instead, `{"VM.Allocate":1,"Sys.Audit":1,…}`, and its key order is a Perl hash's order, so it
17
+ * is not stable between two calls to the same endpoint. Compare the raw string, or the raw
18
+ * object, or the keys in the order they arrived, and every plan reports an update forever.
19
+ * `canonical` below is the whole answer: both sides become a sorted, de-duplicated set before
20
+ * anything is compared, and `attributes` STORES the sorted form so Alchemy's state does not
21
+ * churn either.
22
+ *
23
+ * ⚠️ A DECLARATION REPLACES THE PRIVILEGE SET; IT DOES NOT ADD TO IT. `PUT /access/roles/{roleid}`
24
+ * accepts an `append` flag and this resource deliberately never sends it — with append a role
25
+ * could only ever grow, so `matches` would report an update forever whenever props were a subset
26
+ * of live, which is the same perpetual diff by a different road. The consequence is the one that
27
+ * bites: anything a human added by hand is REVOKED on the next deploy unless it is in `privs`.
28
+ * Copy the live set out of `pveum role list` before declaring a role that already exists; do not
29
+ * type it from memory.
30
+ *
31
+ * ⛔ A ROLE CAN LOCK ITS OWN PROVIDER OUT, AND NOTHING IN THIS PACKAGE CAN UNDO IT. Reconcile runs
32
+ * as the provision credential, and that credential holds a role. Declare THAT role without the
33
+ * privileges the provider needs — `Sys.Modify` on `/access` above all — and the write succeeds,
34
+ * after which every later plan reads 403 and the repair has to happen out of band as `root@pam`
35
+ * over SSH. It is a one-way door, which is why the privileges a reconcile needs are written
36
+ * down at the bottom of this comment rather than left to be rediscovered from an error message.
37
+ *
38
+ * ⚠️ PVE REFUSES TO EDIT ITS OWN BUILT-IN ROLES (`Administrator`, `NoAccess`, the `PVE*` set), and
39
+ * the item endpoint gives no sign of which those are — only the index carries the `special`
40
+ * flag. A declaration aimed at a built-in role therefore READS BACK CLEANLY and fails at write
41
+ * time, which reads like a broken provider rather than like a refusal. Declare roles you own.
42
+ *
43
+ * ★ PRIVILEGES A RECONCILE NEEDS: `Sys.Audit` on `/access` to read and diff, `Sys.Modify` on
44
+ * `/access` to create, update or delete. Not `Permissions.Modify` — binding a role to a user is
45
+ * `/access/acl`, a different object, and nothing here writes it. An auditor-shaped credential
46
+ * can already PLAN this resource and cannot DEPLOY it, which is the honest failure: the plan is
47
+ * true and the deploy says exactly which privilege is missing.
48
+ */
49
+ import { Resource } from 'alchemy';
50
+ import * as Provider from 'alchemy/Provider';
51
+ import { type PveRequirements, type WithTarget } from './resource.ts';
52
+ export interface RoleProps extends WithTarget {
53
+ /**
54
+ * PVE's primary key for a role.
55
+ *
56
+ * ⚠️ THERE IS NO RENAME. `path` is built from this, so changing it makes the factory read
57
+ * nothing live, create a role under the new name, and leave the old one on the cluster with
58
+ * nobody managing it. Delete the resource and declare a new one rather than editing this.
59
+ */
60
+ roleid: string;
61
+ /**
62
+ * The COMPLETE privilege set, e.g. `['VM.Allocate', 'VM.Audit', 'Sys.Audit']`. Order and
63
+ * duplicates do not matter — see the ⛔ in the header — but completeness does: this list is the
64
+ * role, not an addition to it.
65
+ *
66
+ * ⚠️ DECLARE AT LEAST ONE. A role with no privileges grants nothing and reads back as an empty
67
+ * document, which is the one shape the factory cannot tell apart from an object that is not
68
+ * there — reconcile would then refuse with its "the write returned no error but the object is
69
+ * still absent" message, which is true of the read and misleading about the cause.
70
+ */
71
+ privs: string[];
72
+ }
73
+ export interface RoleAttributes {
74
+ roleid: string;
75
+ /** Sorted and de-duplicated on the way in, so persisted state is identical across plans. */
76
+ privs: string[];
77
+ }
78
+ export interface ProxmoxRole extends Resource<'Proxmox.Role', RoleProps, RoleAttributes, never, PveRequirements> {
79
+ }
80
+ export declare const ProxmoxRole: import("alchemy").ResourceClass<ProxmoxRole>;
81
+ /**
82
+ * ⛔ AN EMPTY LIST, AND NOWHERE DOES IT MATTER MORE. `GET /access/roles` returns every
83
+ * role on the cluster, PVE's own built-ins included. Returning them would invite Alchemy
84
+ * to adopt `Administrator` — and therefore one day to narrow or delete it. Adoption is
85
+ * an explicit act, here as everywhere else in this package.
86
+
87
+ *
88
+ * ⛔ DO NOT CARRY THE FACTORY'S REASSURANCE OVER TO THIS ONE. `destroy` there notes that
89
+ * PVE refuses to delete things still in use — a pool holding guests, a storage with
90
+ * volumes — and a role is NOT protected that way. Whoever is bound to the role loses
91
+ * those privileges the moment it goes, and the credential this provider runs with is
92
+ * bound through exactly such a binding. Checking who holds a role before removing it is
93
+ * an operator's job, and it is not done here on their behalf while they read a diff.
94
+
95
+ */
96
+ export declare const ProxmoxRoleProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxRole>, never, import("effect/unstable/http/HttpClient").HttpClient>;
97
+ //# sourceMappingURL=role.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAuDjE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}