@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,2322 @@
1
+ // src/proxmox/acl.ts
2
+ import { Resource } from "alchemy";
3
+ import { isResolved as isResolved2 } from "alchemy/Diff";
4
+ import * as Provider from "alchemy/Provider";
5
+ import * as Effect6 from "effect/Effect";
6
+
7
+ // src/proxmox/client.ts
8
+ import * as Effect4 from "effect/Effect";
9
+ import * as HttpClient5 from "effect/unstable/http/HttpClient";
10
+ import * as HttpClientRequest2 from "effect/unstable/http/HttpClientRequest";
11
+
12
+ // src/proxmox/credentials.ts
13
+ import * as Effect from "effect/Effect";
14
+ import * as Option from "effect/Option";
15
+ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
16
+ import * as Headers from "effect/unstable/http/Headers";
17
+ import * as HttpClient from "effect/unstable/http/HttpClient";
18
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
19
+ var baoVariable = (env, name) => env[`BAO_${name}`] ?? env[`VAULT_${name}`] ?? "";
20
+ var mint = (target, role, env = process.env) => Effect.gen(function* () {
21
+ const path = `${target.mount}/creds/${role}`;
22
+ const refuse = (status2, detail) => new Error(`OpenBao GET /v1/${path} -> ${status2 === 0 ? "no response" : status2}: ${detail}`);
23
+ const address = baoVariable(env, "AGENT_ADDR") || baoVariable(env, "ADDR") || "https://127.0.0.1:8200";
24
+ const socket = address.startsWith("unix://") ? address.slice("unix://".length) : undefined;
25
+ const base = socket === undefined ? address.replace(/\/+$/, "") : "http://localhost";
26
+ const headers = { "X-Vault-Request": "true" };
27
+ const namespace = baoVariable(env, "NAMESPACE");
28
+ if (namespace !== "")
29
+ headers["X-Vault-Namespace"] = namespace;
30
+ const token = baoVariable(env, "TOKEN");
31
+ if (token !== "")
32
+ headers["X-Vault-Token"] = token;
33
+ if (socket !== undefined && !("Bun" in globalThis)) {
34
+ return yield* Effect.fail(refuse(0, "a unix:// address needs Bun fetch; this is not Bun"));
35
+ }
36
+ const client = yield* HttpClient.HttpClient;
37
+ const redacted = yield* Headers.CurrentRedactedNames;
38
+ const defaults = Option.getOrElse(yield* Effect.serviceOption(FetchHttpClient.RequestInit), () => ({}));
39
+ const request = HttpClientRequest.get(`${base}/v1/${path}`).pipe(HttpClientRequest.setHeaders(headers));
40
+ const exchange = client.execute(request).pipe(Effect.flatMap((response) => Effect.map(response.text, (text2) => ({ status: response.status, text: text2 }))), Effect.timeout("60 seconds"), Effect.provideService(Headers.CurrentRedactedNames, [...redacted, "x-vault-token"]));
41
+ const init = { ...defaults, unix: socket };
42
+ const { status, text } = yield* (socket === undefined ? exchange : Effect.provideService(exchange, FetchHttpClient.RequestInit, init)).pipe(Effect.mapError((cause) => refuse(0, cause.message)));
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(text);
46
+ } catch {
47
+ parsed = undefined;
48
+ }
49
+ if (status < 200 || status >= 300) {
50
+ const errors = parsed?.errors;
51
+ const detail = Array.isArray(errors) ? errors.map(String).join("; ") : "not an OpenBao error body";
52
+ return yield* Effect.fail(refuse(status, detail || "(no errors given)"));
53
+ }
54
+ const tokenId = parsed?.data?.token_id;
55
+ const secret = parsed?.data?.secret;
56
+ if (typeof tokenId !== "string" || typeof secret !== "string") {
57
+ return yield* Effect.fail(new Error(`${target.mount}/creds/${role} returned no token_id/secret. Either this approle lacks ` + `the grant, or the mount is not ${target.mount}. Say which mount and role you needed ` + `than falling back to a stored credential -- every stored PVE token is read-only.`));
58
+ }
59
+ const lease = parsed?.lease_duration;
60
+ const leaseSeconds = typeof lease === "number" ? lease : 0;
61
+ return { leaseSeconds, secret, tokenId };
62
+ });
63
+ var authorization = (credential, target) => target.scheme === "pbs" ? `PBSAPIToken=${credential.tokenId}:${credential.secret}` : `PVEAPIToken=${credential.tokenId}=${credential.secret}`;
64
+
65
+ // src/proxmox/lease-cache.ts
66
+ import * as Cache from "effect/Cache";
67
+ import * as Duration from "effect/Duration";
68
+ import * as Effect2 from "effect/Effect";
69
+ import * as Exit from "effect/Exit";
70
+ var REMINT_MARGIN_SECONDS = 60;
71
+ var keyOf = (target, role) => ({
72
+ mount: target.mount,
73
+ role,
74
+ scheme: target.scheme
75
+ });
76
+ var timeToLive = (exit) => Exit.isSuccess(exit) && exit.value.leaseSeconds > REMINT_MARGIN_SECONDS ? Duration.seconds(exit.value.leaseSeconds - REMINT_MARGIN_SECONDS) : Duration.zero;
77
+ var makeLeases = (mintFor) => Cache.makeWith(mintFor, { capacity: 64, requireServicesAt: "lookup", timeToLive });
78
+ var targetForKey = (key) => key.scheme === "pbs" ? { api: "https://pbs.invalid/api2/json", mount: key.mount, scheme: "pbs" } : { members: [], mount: key.mount, scheme: "pve" };
79
+ var leases = Effect2.runSync(makeLeases((key) => mint(targetForKey(key), key.role)));
80
+ var leased = (target, role) => Cache.get(leases, keyOf(target, role));
81
+
82
+ // src/proxmox/members.ts
83
+ import * as Effect3 from "effect/Effect";
84
+ import * as Result from "effect/Result";
85
+ import * as HttpClient3 from "effect/unstable/http/HttpClient";
86
+ import * as HttpClientError from "effect/unstable/http/HttpClientError";
87
+ var PVE_API_PORT = 8006;
88
+ var pveApiBase = (member) => `https://${member}:${String(PVE_API_PORT)}/api2/json`;
89
+ var lastGoodByMount = new Map;
90
+ var noteGoodMember = (target, member) => {
91
+ lastGoodByMount.set(target.mount, member);
92
+ };
93
+ var orderedMembers = (target) => {
94
+ const preferred = lastGoodByMount.get(target.mount);
95
+ if (preferred === undefined || !target.members.includes(preferred))
96
+ return target.members;
97
+ return [preferred, ...target.members.filter((member) => member !== preferred)];
98
+ };
99
+ var errorCode = (cause) => {
100
+ if (cause !== null && typeof cause === "object") {
101
+ if ("code" in cause && typeof cause.code === "string")
102
+ return cause.code;
103
+ if ("cause" in cause)
104
+ return errorCode(cause.cause);
105
+ }
106
+ return;
107
+ };
108
+ var isTimeoutCause = (cause) => {
109
+ const code = errorCode(cause);
110
+ if (code === "AbortError" || code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") {
111
+ return true;
112
+ }
113
+ return cause instanceof Error && /timed out|timeout/i.test(cause.message);
114
+ };
115
+ var PRE_SEND_CODES = new Set(["ConnectionRefused", "ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN"]);
116
+ var isCertificateCode = (code) => /^(ERR_TLS_CERT_|CERT_|UNABLE_TO_VERIFY_|DEPTH_ZERO_SELF_SIGNED|SELF_SIGNED_CERT)/.test(code);
117
+ var isPreSendCause = (cause) => {
118
+ const code = errorCode(cause);
119
+ return code !== undefined && (PRE_SEND_CODES.has(code) || isCertificateCode(code));
120
+ };
121
+ var isTransportFailure = (cause) => {
122
+ if (!HttpClientError.isHttpClientError(cause))
123
+ return false;
124
+ const tag = cause.reason._tag;
125
+ return tag === "TransportError" || tag === "InvalidUrlError" || tag === "EncodeError";
126
+ };
127
+ var isPreSendTransport = (cause) => {
128
+ if (!HttpClientError.isHttpClientError(cause))
129
+ return false;
130
+ const { reason } = cause;
131
+ if (reason._tag === "InvalidUrlError" || reason._tag === "EncodeError")
132
+ return true;
133
+ if (reason._tag === "TransportError") {
134
+ if (isTimeoutCause(reason.cause))
135
+ return false;
136
+ return isPreSendCause(reason.cause);
137
+ }
138
+ return false;
139
+ };
140
+ var describeTransport = (cause) => {
141
+ if (HttpClientError.isHttpClientError(cause))
142
+ return cause.message;
143
+ return String(cause);
144
+ };
145
+ var executeOnCluster = (target, method, path, build) => Effect3.gen(function* () {
146
+ const client = yield* HttpClient3.HttpClient;
147
+ const isWrite = method !== "GET";
148
+ const attempts = [];
149
+ for (const member of orderedMembers(target)) {
150
+ const outcome = yield* client.execute(build(pveApiBase(member))).pipe(Effect3.result);
151
+ if (Result.isSuccess(outcome)) {
152
+ noteGoodMember(target, member);
153
+ return outcome.success;
154
+ }
155
+ const why = describeTransport(outcome.failure);
156
+ attempts.push({ member, why });
157
+ const mayRetry = isWrite ? isPreSendTransport(outcome.failure) : isTransportFailure(outcome.failure);
158
+ if (!mayRetry) {
159
+ return yield* Effect3.fail({ attempts, method, path, tag: "cluster" });
160
+ }
161
+ }
162
+ return yield* Effect3.fail({ attempts, method, path, tag: "cluster" });
163
+ });
164
+
165
+ // src/proxmox/client.ts
166
+ class PveError extends Error {
167
+ status;
168
+ method;
169
+ path;
170
+ constructor(status, method, path, detail) {
171
+ super(`PVE ${method} ${path} -> ${String(status)}: ${detail}`);
172
+ this.status = status;
173
+ this.method = method;
174
+ this.path = path;
175
+ this.name = "PveError";
176
+ }
177
+ }
178
+ var clusterExhausted = (method, path, attempts) => new PveError(0, method, path, `all members failed: ${attempts.map((a) => `${a.member} (${a.why})`).join("; ")}`);
179
+ var encode = (form) => {
180
+ const params = new URLSearchParams;
181
+ for (const [key, value] of Object.entries(form)) {
182
+ if (typeof value === "string")
183
+ params.append(key, value);
184
+ else
185
+ for (const item of value)
186
+ params.append(key, item);
187
+ }
188
+ return params.toString();
189
+ };
190
+ var buildRequest = (method, apiBase, path, credential, target, form) => {
191
+ const url = `${apiBase}/${path}`;
192
+ return HttpClientRequest2.make(method)(url).pipe(HttpClientRequest2.setHeaders({ Authorization: authorization(credential, target) }), form === undefined ? (self) => self : HttpClientRequest2.bodyText(encode(form), "application/x-www-form-urlencoded"));
193
+ };
194
+ var executeOne = (target, credential, method, path, form, apiBase) => Effect4.gen(function* () {
195
+ const client = yield* HttpClient5.HttpClient;
196
+ const request = buildRequest(method, apiBase, path, credential, target, form);
197
+ return yield* client.execute(request).pipe(Effect4.mapError((cause) => new PveError(0, method, path, String(cause))));
198
+ });
199
+ var readResponse = (response, method, path) => Effect4.gen(function* () {
200
+ if (response.status < 200 || response.status >= 300) {
201
+ const detail = yield* response.text.pipe(Effect4.orElseSucceed(() => ""));
202
+ return yield* Effect4.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
203
+ }
204
+ const body = yield* response.json.pipe(Effect4.mapError((cause) => new PveError(0, method, path, String(cause))));
205
+ return body.data;
206
+ });
207
+ var pveWith = (target, credential, method, path, form) => Effect4.gen(function* () {
208
+ const response = target.scheme === "pve" ? yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target, form)).pipe(Effect4.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure)))) : yield* executeOne(target, credential, method, path, form, target.api);
209
+ return yield* readResponse(response, method, path);
210
+ });
211
+ var pveEnvelopeWith = (target, credential, method, path) => Effect4.gen(function* () {
212
+ const response = yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target)).pipe(Effect4.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure))));
213
+ if (response.status < 200 || response.status >= 300) {
214
+ const detail = yield* response.text.pipe(Effect4.orElseSucceed(() => ""));
215
+ return yield* Effect4.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
216
+ }
217
+ return yield* response.json.pipe(Effect4.mapError((cause) => new PveError(0, method, path, String(cause))));
218
+ });
219
+ var pve = (target, role, method, path, form) => Effect4.gen(function* () {
220
+ const credential = yield* leased(target, role);
221
+ return yield* pveWith(target, credential, method, path, form);
222
+ });
223
+
224
+ // src/proxmox/resource.ts
225
+ import { isResolved } from "alchemy/Diff";
226
+ import * as Effect5 from "effect/Effect";
227
+ var pveOperations = (spec) => {
228
+ const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect5.map((data) => data === undefined ? undefined : spec.attributes(data, props)), Effect5.orElseSucceed(() => {
229
+ return;
230
+ }));
231
+ return {
232
+ read,
233
+ diff: (news, output) => Effect5.gen(function* () {
234
+ if (output === undefined || !isResolved(news))
235
+ return;
236
+ const live = yield* read(news);
237
+ if (live === undefined)
238
+ return { action: "update" };
239
+ if (spec.matches(live, news))
240
+ return { action: "noop" };
241
+ return spec.updateForm === undefined ? { action: "replace" } : { action: "update" };
242
+ }),
243
+ reconcile: (news) => Effect5.gen(function* () {
244
+ const live = yield* read(news);
245
+ if (live === undefined) {
246
+ yield* pve(news.target, "provision", "POST", spec.collection(news), spec.createForm(news));
247
+ } else if (spec.updateForm !== undefined && !spec.matches(live, news)) {
248
+ const form = spec.updateForm(news);
249
+ if (Object.keys(form).length > 0) {
250
+ yield* pve(news.target, "provision", "PUT", spec.path(news), form);
251
+ }
252
+ }
253
+ const after = yield* read(news);
254
+ if (after === undefined) {
255
+ return yield* Effect5.die(new Error(`${spec.path(news)}: the write returned no error but the object is still absent. ` + 'PVE wraps every answer in {"data":...} and can report success on a call that did ' + "nothing -- read back rather than trusting the status code."));
256
+ }
257
+ return after;
258
+ }),
259
+ destroy: (olds) => pve(olds.target, "provision", "DELETE", spec.path(olds))
260
+ };
261
+ };
262
+ var pveHandlers = (spec) => {
263
+ const ops = pveOperations(spec);
264
+ return {
265
+ list: () => Effect5.succeed([]),
266
+ read: ({ olds }) => ops.read(olds),
267
+ diff: ({ news, output }) => ops.diff(news, output),
268
+ reconcile: ({ news }) => ops.reconcile(news),
269
+ delete: ({ olds }) => ops.destroy(olds)
270
+ };
271
+ };
272
+
273
+ // src/proxmox/acl.ts
274
+ var ProxmoxAcl = Resource("Proxmox.Acl");
275
+ var normalize = (raw) => `/${raw.split("/").filter(Boolean).join("/")}`;
276
+ var SUBJECT_FIELD = { group: "groups", token: "tokens", user: "users" };
277
+ var tuple = (props) => ({
278
+ path: normalize(props.path),
279
+ roles: props.roleid,
280
+ [SUBJECT_FIELD[props.type]]: props.ugid
281
+ });
282
+ var bind = (props) => ({
283
+ propagate: props.propagate === false ? "0" : "1",
284
+ ...tuple(props)
285
+ });
286
+ var identity = (grant) => [normalize(grant.path), grant.type, grant.ugid, grant.roleid].join(" ");
287
+ var find = (live, props) => (Array.isArray(live) ? live : []).filter((row) => typeof row === "object" && row !== null).find((row) => row["path"] === normalize(props.path) && row["type"] === props.type && row["ugid"] === props.ugid && row["roleid"] === props.roleid);
288
+ var propagates = (row) => {
289
+ const value = row["propagate"];
290
+ return value === undefined || value === 1 || value === true || value === "1";
291
+ };
292
+ var ops = pveOperations({
293
+ attributes: (live, props) => {
294
+ const row = find(live, props);
295
+ return {
296
+ bound: row !== undefined,
297
+ path: normalize(props.path),
298
+ propagate: row !== undefined && propagates(row),
299
+ roleid: props.roleid,
300
+ type: props.type,
301
+ ugid: props.ugid
302
+ };
303
+ },
304
+ readRole: "provision",
305
+ collection: () => "access/acl",
306
+ createForm: bind,
307
+ matches: (attributes, props) => attributes.bound && attributes.propagate === (props.propagate !== false),
308
+ path: () => "access/acl",
309
+ updateForm: bind
310
+ });
311
+ var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect6.succeed(ProxmoxAcl.Provider.of({
312
+ list: () => Effect6.succeed([]),
313
+ read: Effect6.fn(function* ({ olds }) {
314
+ return yield* ops.read(olds);
315
+ }),
316
+ diff: Effect6.fn(function* ({ news, output }) {
317
+ if (output !== undefined && isResolved2(news) && identity(news) !== identity(output)) {
318
+ return { action: "replace" };
319
+ }
320
+ return yield* ops.diff(news, output);
321
+ }),
322
+ reconcile: Effect6.fn(function* ({ news }) {
323
+ const after = yield* ops.reconcile(news);
324
+ if (!after.bound) {
325
+ return yield* Effect6.die(new Error(`access/acl: the write returned no error but ${identity(news)} is still not ` + "bound. Either the PUT did nothing, or the read credential cannot see the " + "grant -- GET /access/acl is filtered to paths you may MODIFY permissions on, " + "so a read-only role is answered [] rather than 403."));
326
+ }
327
+ return after;
328
+ }),
329
+ delete: Effect6.fn(function* ({ olds }) {
330
+ const removal = { delete: "1", ...tuple(olds) };
331
+ yield* pve(olds.target, "provision", "PUT", "access/acl", removal);
332
+ })
333
+ })));
334
+ // src/proxmox/api-token.ts
335
+ import { Resource as Resource2 } from "alchemy";
336
+ import * as Provider2 from "alchemy/Provider";
337
+ import * as Effect7 from "effect/Effect";
338
+
339
+ // src/proxmox/values.ts
340
+ var num = (value, fallback) => typeof value === "number" ? value : fallback;
341
+ var text = (value, fallback = "") => typeof value === "string" ? value : fallback;
342
+ var bool = (value, fallback = false) => value === undefined || value === null || value === "" ? fallback : value === 1 || value === true || value === "1";
343
+ var flag = (value) => value === undefined ? undefined : value ? "1" : "0";
344
+ var csv = (value) => (typeof value === "string" ? value.split(",") : value ?? []).map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
345
+ var guestList = (value) => {
346
+ const parts = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value.map((entry) => String(entry)) : [];
347
+ return parts.map((part) => Number(part.trim())).filter((id) => Number.isFinite(id)).sort((left, right) => left - right).map((id) => String(id)).join(",");
348
+ };
349
+ var canonicalToken = (raw) => {
350
+ const lowered = raw.trim().toLowerCase();
351
+ if (["1", "yes", "true", "on"].includes(lowered))
352
+ return "1";
353
+ if (["0", "no", "false", "off"].includes(lowered))
354
+ return "0";
355
+ return lowered;
356
+ };
357
+ var propertyString = (value, defaultKey) => {
358
+ const pairs = typeof value === "string" ? value.split(",").map((part) => {
359
+ const [key, ...rest] = part.split("=");
360
+ return rest.length === 0 && defaultKey !== undefined ? [defaultKey, key ?? ""] : [key ?? "", rest.join("=")];
361
+ }) : typeof value === "object" && value !== null ? Object.entries(value).map(([key, entry]) => [key, String(entry)]) : [];
362
+ return pairs.map(([key, entry]) => [key.trim(), canonicalToken(entry)]).filter(([key]) => key !== "").map(([key, entry]) => `${key}=${entry}`).sort().join(",");
363
+ };
364
+ var int = (value, fallback) => {
365
+ if (typeof value === "number")
366
+ return value;
367
+ const parsed = Number.parseInt(text(value), 10);
368
+ return Number.isNaN(parsed) ? fallback : parsed;
369
+ };
370
+ var withClears = (fields, clear) => {
371
+ const cleared = new Set(clear);
372
+ const kept = {};
373
+ for (const [key, value] of Object.entries(fields)) {
374
+ if (value !== undefined && !cleared.has(key))
375
+ kept[key] = value;
376
+ }
377
+ return cleared.size === 0 ? kept : { ...kept, delete: [...cleared].join(",") };
378
+ };
379
+
380
+ // src/proxmox/api-token-form.ts
381
+ var fullTokenid = (props) => `${props.userid}!${props.tokenid}`;
382
+ var shape = (props) => ({
383
+ comment: props.comment ?? "",
384
+ expire: String(props.expire),
385
+ privsep: props.privsep ? "1" : "0"
386
+ });
387
+ var apiTokenSpec = {
388
+ readRole: "provision",
389
+ attributes: (live, props) => {
390
+ const expire = live["expire"];
391
+ const privsep = live["privsep"];
392
+ if (expire === undefined && privsep === undefined)
393
+ return;
394
+ return {
395
+ comment: text(live["comment"]),
396
+ expire: int(expire, 0),
397
+ fullTokenid: fullTokenid(props),
398
+ privsep: bool(privsep, true),
399
+ tokenid: props.tokenid,
400
+ userid: props.userid
401
+ };
402
+ },
403
+ collection: (props) => `access/users/${props.userid}/token/${props.tokenid}`,
404
+ createForm: shape,
405
+ matches: (attributes, props) => attributes.comment === (props.comment ?? "") && attributes.expire === props.expire && attributes.privsep === props.privsep,
406
+ path: (props) => `access/users/${props.userid}/token/${props.tokenid}`,
407
+ updateForm: shape
408
+ };
409
+
410
+ // src/proxmox/api-token.ts
411
+ var ProxmoxApiToken = Resource2("Proxmox.ApiToken", {
412
+ defaultRemovalPolicy: "retain"
413
+ });
414
+ var ops2 = pveOperations(apiTokenSpec);
415
+ var handlers = {
416
+ ...pveHandlers(apiTokenSpec),
417
+ reconcile: Effect7.fn(function* ({ news }) {
418
+ const live = yield* ops2.read(news);
419
+ if (live === undefined) {
420
+ return yield* Effect7.die(new Error(`${news.userid}!${news.tokenid}: this resource does not create API tokens. PVE returns ` + "the secret only in the create response and it cannot be stored, so a token made here " + "would be a live credential nobody holds. Create it with `pveum user token add` and " + "capture the value, or mint a short-lived one from the OpenBao proxmox mount " + "(`bao read proxmox-tb4/creds/<role>`) -- then declare it here to manage it."));
421
+ }
422
+ return yield* ops2.reconcile(news);
423
+ })
424
+ };
425
+ var ProxmoxApiTokenProvider = () => Provider2.effect(ProxmoxApiToken, Effect7.succeed(ProxmoxApiToken.Provider.of(handlers)));
426
+ // src/proxmox/backup-job.ts
427
+ import { Resource as Resource3 } from "alchemy";
428
+ import * as Provider3 from "alchemy/Provider";
429
+ import * as Effect8 from "effect/Effect";
430
+ var ProxmoxBackupJob = Resource3("Proxmox.BackupJob");
431
+ var shape2 = (props) => ({
432
+ all: props.all === true ? "1" : "0",
433
+ enabled: props.enabled === false ? "0" : "1",
434
+ mode: props.mode ?? "snapshot",
435
+ "notification-mode": props["notification-mode"] ?? "auto",
436
+ "repeat-missed": props["repeat-missed"] === true ? "1" : "0",
437
+ schedule: props.schedule,
438
+ ...props.comment === undefined ? {} : { comment: props.comment },
439
+ ...props.exclude === undefined ? {} : { exclude: guestList(props.exclude) },
440
+ ...props.fleecing === undefined ? {} : { fleecing: props.fleecing },
441
+ ...props["notes-template"] === undefined ? {} : { "notes-template": props["notes-template"] },
442
+ ...props.pool === undefined ? {} : { pool: props.pool },
443
+ ...props["prune-backups"] === undefined ? {} : { "prune-backups": props["prune-backups"] },
444
+ ...props.storage === undefined ? {} : { storage: props.storage },
445
+ ...props.vmid === undefined ? {} : { vmid: guestList(props.vmid) }
446
+ });
447
+ var handlers2 = pveHandlers({
448
+ attributes: (live, props) => ({
449
+ all: bool(live["all"], false),
450
+ comment: text(live["comment"], ""),
451
+ enabled: bool(live["enabled"], true),
452
+ exclude: guestList(live["exclude"]),
453
+ fleecing: propertyString(live["fleecing"], "enabled"),
454
+ id: props.id,
455
+ mode: text(live["mode"], "snapshot"),
456
+ "next-run": typeof live["next-run"] === "number" ? live["next-run"] : 0,
457
+ "notes-template": text(live["notes-template"], ""),
458
+ "notification-mode": text(live["notification-mode"], "auto"),
459
+ pool: text(live["pool"], ""),
460
+ "prune-backups": propertyString(live["prune-backups"]),
461
+ "repeat-missed": bool(live["repeat-missed"], false),
462
+ schedule: text(live["schedule"], ""),
463
+ storage: text(live["storage"], ""),
464
+ vmid: guestList(live["vmid"])
465
+ }),
466
+ collection: () => "cluster/backup",
467
+ createForm: (props) => ({ ...shape2(props), id: props.id }),
468
+ matches: (attributes, props) => attributes.schedule === props.schedule && attributes.enabled === (props.enabled !== false) && attributes.all === (props.all === true) && attributes.mode === (props.mode ?? "snapshot") && attributes["notification-mode"] === (props["notification-mode"] ?? "auto") && attributes["repeat-missed"] === (props["repeat-missed"] === true) && (props.storage === undefined || attributes.storage === props.storage) && (props.pool === undefined || attributes.pool === props.pool) && (props.comment === undefined || attributes.comment === props.comment) && (props.vmid === undefined || attributes.vmid === guestList(props.vmid)) && (props.exclude === undefined || attributes.exclude === guestList(props.exclude)) && (props["notes-template"] === undefined || attributes["notes-template"] === props["notes-template"]) && (props["prune-backups"] === undefined || attributes["prune-backups"] === propertyString(props["prune-backups"])) && (props.fleecing === undefined || attributes.fleecing === propertyString(props.fleecing, "enabled")),
469
+ path: (props) => `cluster/backup/${props.id}`,
470
+ updateForm: shape2
471
+ });
472
+ var ProxmoxBackupJobProvider = () => Provider3.effect(ProxmoxBackupJob, Effect8.succeed(ProxmoxBackupJob.Provider.of(handlers2)));
473
+ // src/proxmox/ceph-daemon.ts
474
+ import { Resource as Resource4 } from "alchemy";
475
+ import * as Provider4 from "alchemy/Provider";
476
+ import * as Effect9 from "effect/Effect";
477
+
478
+ // src/proxmox/ceph-daemon-form.ts
479
+ var daemonId = (props) => props.name ?? props.node;
480
+ var collectionPath = (props) => `nodes/${props.node}/ceph/${props.kind}`;
481
+ var daemonPath = (props) => `${collectionPath(props)}/${daemonId(props)}`;
482
+ var createForm = (props) => {
483
+ if (props.kind === "mon" && props["mon-address"] !== undefined) {
484
+ return { "mon-address": props["mon-address"] };
485
+ }
486
+ const hotstandby = props.kind === "mds" ? flag(props.hotstandby) : undefined;
487
+ return hotstandby === undefined ? {} : { hotstandby };
488
+ };
489
+ var findRow = (live, props) => (Array.isArray(live) ? live : []).filter((row) => typeof row === "object" && row !== null).find((row) => row["name"] === daemonId(props));
490
+
491
+ // src/proxmox/ceph-daemon.ts
492
+ var ProxmoxCephDaemon = Resource4("Proxmox.CephDaemon", {
493
+ defaultRemovalPolicy: "retain"
494
+ });
495
+ var handlers3 = pveHandlers({
496
+ attributes: (live, props) => {
497
+ const row = findRow(live, props);
498
+ if (row === undefined)
499
+ return;
500
+ return {
501
+ addr: text(row["addr"]),
502
+ fsName: text(row["fs_name"]),
503
+ host: text(row["host"]),
504
+ kind: props.kind,
505
+ name: daemonId(props),
506
+ node: props.node,
507
+ quorum: bool(row["quorum"]),
508
+ rank: num(row["rank"], -1),
509
+ service: bool(row["service"]),
510
+ standbyReplay: bool(row["standby_replay"]),
511
+ state: text(row["state"]),
512
+ version: text(row["ceph_version_short"])
513
+ };
514
+ },
515
+ collection: daemonPath,
516
+ createForm,
517
+ matches: () => true,
518
+ path: collectionPath
519
+ });
520
+ var ProxmoxCephDaemonProvider = () => Provider4.effect(ProxmoxCephDaemon, Effect9.succeed(ProxmoxCephDaemon.Provider.of({
521
+ ...handlers3,
522
+ delete: ({ olds }) => pve(olds.target, "provision", "DELETE", daemonPath(olds))
523
+ })));
524
+ // src/proxmox/ceph-flag.ts
525
+ import { Resource as Resource5 } from "alchemy";
526
+ import * as Provider5 from "alchemy/Provider";
527
+ import * as Effect10 from "effect/Effect";
528
+ var ProxmoxCephFlag = Resource5("Proxmox.CephFlag");
529
+ var handlers4 = pveHandlers({
530
+ attributes: (live, props) => ({ flag: props.flag, value: bool(live) }),
531
+ collection: () => "cluster/ceph/flags",
532
+ createForm: (props) => ({ [props.flag]: flag(props.value) ?? "0" }),
533
+ matches: (attributes, props) => attributes.value === props.value,
534
+ path: (props) => `cluster/ceph/flags/${props.flag}`,
535
+ updateForm: (props) => ({ value: flag(props.value) ?? "0" })
536
+ });
537
+ var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect10.succeed(ProxmoxCephFlag.Provider.of({
538
+ ...handlers4,
539
+ delete: () => Effect10.void,
540
+ reconcile: Effect10.fn(function* ({ news }) {
541
+ const after = yield* handlers4.reconcile({ news });
542
+ if (after.value !== news.value) {
543
+ return yield* Effect10.die(new Error(`cluster/ceph/flags/${news.flag}: the PUT returned no error but ceph still ` + `reports the flag ${after.value ? "set" : "clear"} rather than ` + `${news.value ? "set" : "clear"}. This endpoint's success is documented as a ` + "null body, so the status code proves nothing -- check `ceph osd dump | " + "head -1` and the cluster log. `pause` in particular is two ceph flags read " + "as one; see the header."));
544
+ }
545
+ return after;
546
+ })
547
+ })));
548
+ // src/proxmox/ceph-fs.ts
549
+ import { Resource as Resource6 } from "alchemy";
550
+ import { isResolved as isResolved3 } from "alchemy/Diff";
551
+ import * as Provider6 from "alchemy/Provider";
552
+ import * as Effect12 from "effect/Effect";
553
+
554
+ // src/proxmox/ceph-fs-wire.ts
555
+ import * as Effect11 from "effect/Effect";
556
+ var readRow = (live, props) => {
557
+ const row = (Array.isArray(live) ? live : []).filter((entry) => typeof entry === "object" && entry !== null).find((entry) => entry["name"] === props.name);
558
+ if (row === undefined)
559
+ return;
560
+ return {
561
+ data_pool: text(row["data_pool"]),
562
+ data_pools: csv(Array.isArray(row["data_pools"]) ? row["data_pools"].map((entry) => String(entry)) : []),
563
+ metadata_pool: text(row["metadata_pool"]),
564
+ metadata_pool_id: int(row["metadata_pool_id"], -1),
565
+ name: props.name
566
+ };
567
+ };
568
+ var POLL_SECONDS = 2;
569
+ var POLL_ATTEMPTS = 45;
570
+ var objectPath = (props) => `nodes/${props.node}/ceph/fs/${props.name}`;
571
+ var field = (name, value) => value === undefined ? {} : { [name]: value };
572
+ var createForm2 = (props) => ({
573
+ ...field("add-storage", flag(props["add-storage"])),
574
+ ...field("pg_num", props.pg_num === undefined ? undefined : String(props.pg_num))
575
+ });
576
+ var destroyPath = (props) => {
577
+ const query = [
578
+ props["remove-pools"] === true ? "remove-pools=1" : undefined,
579
+ props["remove-storages"] === true ? "remove-storages=1" : undefined
580
+ ].filter((part) => part !== undefined);
581
+ return query.length === 0 ? objectPath(props) : `${objectPath(props)}?${query.join("&")}`;
582
+ };
583
+ var settle = (target, node, upid, what) => Effect11.gen(function* () {
584
+ for (let attempt = 0;attempt < POLL_ATTEMPTS; attempt += 1) {
585
+ const status = yield* pve(target, "read", "GET", `nodes/${node}/tasks/${encodeURIComponent(upid)}/status`).pipe(Effect11.orElseSucceed(() => {
586
+ return;
587
+ }));
588
+ if (status?.status === "stopped") {
589
+ const exit = text(status.exitstatus);
590
+ if (exit === "OK")
591
+ return;
592
+ return yield* Effect11.die(new Error(`${what}: PVE task ${upid} finished with "${exit === "" ? "no exit status" : exit}". ` + "The write returned HTTP 200 because it only forked the worker -- the real error is " + `in \`pvesh get /nodes/${node}/tasks/${upid}/log\`.`));
593
+ }
594
+ yield* Effect11.sleep(POLL_SECONDS * 1000);
595
+ }
596
+ return yield* Effect11.die(new Error(`${what}: PVE task ${upid} was still running after ` + `${String(POLL_ATTEMPTS * POLL_SECONDS)}s, or its ` + "status could not be read. Check the task log, and check that the `read` role holds " + `Sys.Audit on /nodes/${node} -- each call mints a new token, so the poller is never the ` + "task owner and is answered 403 rather than told to wait."));
597
+ });
598
+ var createFs = (props) => Effect11.gen(function* () {
599
+ const upid = text(yield* pve(props.target, "provision", "POST", objectPath(props), createForm2(props)));
600
+ if (upid === "") {
601
+ return yield* Effect11.die(new Error(`${objectPath(props)}: POST returned no task id. PVE wraps every answer in ` + '{"data":...} and can report success on a call that did nothing.'));
602
+ }
603
+ yield* settle(props.target, props.node, upid, objectPath(props));
604
+ });
605
+ var destroyFs = (props) => Effect11.gen(function* () {
606
+ const upid = text(yield* pve(props.target, "provision", "DELETE", destroyPath(props)));
607
+ if (upid !== "")
608
+ yield* settle(props.target, props.node, upid, destroyPath(props));
609
+ });
610
+ var notCreated = (props) => new Error(`${objectPath(props)}: the create task finished but the filesystem is not in ` + `GET nodes/${props.node}/ceph/fs. Check the task log -- a PVE worker can exit having ` + "rolled back the pools it had just made.");
611
+ var notDestroyed = (props) => new Error(`${objectPath(props)}: the destroy task finished but the filesystem is still in ` + `GET nodes/${props.node}/ceph/fs. PVE refuses to destroy a CephFS while a PVE storage entry ` + "of type cephfs still references it and is not disabled.");
612
+
613
+ // src/proxmox/ceph-fs.ts
614
+ var ProxmoxCephFs = Resource6("Proxmox.CephFs", {
615
+ defaultRemovalPolicy: "retain"
616
+ });
617
+ var ops3 = pveOperations({
618
+ attributes: readRow,
619
+ collection: objectPath,
620
+ createForm: createForm2,
621
+ matches: () => true,
622
+ path: (props) => `nodes/${props.node}/ceph/fs`
623
+ });
624
+ var ProxmoxCephFsProvider = () => Provider6.effect(ProxmoxCephFs, Effect12.succeed(ProxmoxCephFs.Provider.of({
625
+ list: () => Effect12.succeed([]),
626
+ read: Effect12.fn(function* ({ olds }) {
627
+ return yield* ops3.read(olds);
628
+ }),
629
+ diff: Effect12.fn(function* ({ news, output }) {
630
+ if (output !== undefined && isResolved3(news) && news.name !== output.name) {
631
+ return { action: "replace" };
632
+ }
633
+ return yield* ops3.diff(news, output);
634
+ }),
635
+ reconcile: Effect12.fn(function* ({ news }) {
636
+ const live = yield* ops3.read(news);
637
+ if (live !== undefined)
638
+ return live;
639
+ yield* createFs(news);
640
+ const after = yield* ops3.read(news);
641
+ if (after === undefined)
642
+ return yield* Effect12.die(notCreated(news));
643
+ return after;
644
+ }),
645
+ delete: Effect12.fn(function* ({ olds }) {
646
+ if ((yield* ops3.read(olds)) === undefined)
647
+ return;
648
+ yield* destroyFs(olds);
649
+ const after = yield* ops3.read(olds);
650
+ if (after !== undefined)
651
+ return yield* Effect12.die(notDestroyed(olds));
652
+ })
653
+ })));
654
+ // src/proxmox/ceph-osd.ts
655
+ import { Resource as Resource7 } from "alchemy";
656
+ import { isResolved as isResolved4 } from "alchemy/Diff";
657
+ import * as Provider7 from "alchemy/Provider";
658
+ import * as Effect15 from "effect/Effect";
659
+
660
+ // src/proxmox/ceph-osd-tree.ts
661
+ import * as Effect13 from "effect/Effect";
662
+ var branches = (node) => (Array.isArray(node["children"]) ? node["children"] : []).filter((child) => typeof child === "object" && child !== null);
663
+ var osdLeaves = (tree) => {
664
+ const root = tree["root"];
665
+ const start = typeof root === "object" && root !== null ? root : {};
666
+ const walk = (node) => [
667
+ ...node["type"] === "osd" ? [node] : [],
668
+ ...branches(node).flatMap(walk)
669
+ ];
670
+ return walk(start);
671
+ };
672
+ var findOsd = (tree, osdid) => osdLeaves(tree).find((leaf) => int(leaf["id"], Number.NaN) === osdid);
673
+ var osdAttributes = (leaf, props) => ({
674
+ ceph_version: text(leaf["ceph_version_short"]),
675
+ crush_weight: num(leaf["crush_weight"], 0),
676
+ device_class: text(leaf["device_class"]),
677
+ host: text(leaf["host"]),
678
+ in: bool(leaf["in"]),
679
+ name: text(leaf["name"], `osd.${String(props.osdid)}`),
680
+ osdid: int(leaf["id"], props.osdid),
681
+ osdtype: text(leaf["osdtype"]),
682
+ pgs: num(leaf["pgs"], 0),
683
+ reweight: num(leaf["reweight"], 0),
684
+ status: text(leaf["status"]),
685
+ total_space: num(leaf["total_space"], 0)
686
+ });
687
+ var readOsd = (props) => pve(props.target, "read", "GET", `nodes/${props.node}/ceph/osd`).pipe(Effect13.map((t) => {
688
+ const leaf = t === undefined ? undefined : findOsd(t, props.osdid);
689
+ return leaf === undefined ? undefined : osdAttributes(leaf, props);
690
+ }));
691
+
692
+ // src/proxmox/ceph-osd-write.ts
693
+ import * as Effect14 from "effect/Effect";
694
+ var describe = (props) => `osd.${String(props.osdid)} on ${props.node}`;
695
+ var createOsd = Effect14.fn(function* (news) {
696
+ if (news.dev === undefined) {
697
+ return yield* Effect14.die(new Error(`${describe(news)}: no such OSD in the CRUSH tree, and no \`dev\` was declared. ` + "Without a device this resource is an ASSERTION over an OSD that must already " + "exist. Declare `dev` to let it create one -- which WIPES that device -- or fix " + "the declaration; if the disk was there yesterday, a replica is missing and Ceph " + "is degraded."));
698
+ }
699
+ yield* pve(news.target, "provision", "POST", `nodes/${news.node}/ceph/osd`, {
700
+ dev: news.dev,
701
+ ...news.device_class === undefined ? {} : { "crush-device-class": news.device_class }
702
+ });
703
+ const created = yield* readOsd(news);
704
+ if (created === undefined) {
705
+ return yield* Effect14.die(new Error(`${describe(news)}: the create returned no error but osd.${String(news.osdid)} is ` + "still absent. Ceph allocates OSD ids and cannot be told which to use, so it most " + "likely assigned a different number -- check `ceph osd tree`, and declare the id it " + "actually gave the device rather than deploying again."));
706
+ }
707
+ return created;
708
+ });
709
+ var destroyOsd = (olds) => pve(olds.target, "provision", "DELETE", `nodes/${olds.node}/ceph/osd/${String(olds.osdid)}`, olds.cleanup === true ? { cleanup: "1" } : {});
710
+
711
+ // src/proxmox/ceph-osd.ts
712
+ var ProxmoxCephOsd = Resource7("Proxmox.CephOsd", {
713
+ defaultRemovalPolicy: "retain"
714
+ });
715
+ var settled = (live, props) => (props.host === undefined || props.host === live.host) && (props.device_class === undefined || props.device_class === live.device_class);
716
+ var describe2 = (props) => `osd.${String(props.osdid)} (read via nodes/${props.node}/ceph/osd)`;
717
+ var handlers5 = {
718
+ list: () => Effect15.succeed([]),
719
+ read: Effect15.fn(function* ({ olds }) {
720
+ return yield* readOsd(olds);
721
+ }),
722
+ diff: Effect15.fn(function* ({
723
+ news,
724
+ output
725
+ }) {
726
+ if (output === undefined || !isResolved4(news))
727
+ return;
728
+ const live = yield* readOsd(news);
729
+ if (live === undefined)
730
+ return { action: "update" };
731
+ if (output.osdid !== news.osdid)
732
+ return { action: "update" };
733
+ return settled(live, news) ? { action: "noop" } : { action: "update" };
734
+ }),
735
+ reconcile: Effect15.fn(function* ({ news }) {
736
+ const live = yield* readOsd(news);
737
+ if (live === undefined) {
738
+ return yield* createOsd(news);
739
+ }
740
+ if (!settled(live, news)) {
741
+ return yield* Effect15.die(new Error(`${describe2(news)}: exists but not as declared -- live host=${live.host} ` + `device_class=${live.device_class}. Nothing here moves an OSD between hosts or ` + "reclassifies it; fix the declaration, or move it deliberately with `ceph osd crush " + "move` / `ceph osd crush set-device-class` and deploy again."));
742
+ }
743
+ return live;
744
+ }),
745
+ delete: Effect15.fn(function* ({ olds }) {
746
+ yield* destroyOsd(olds);
747
+ })
748
+ };
749
+ var ProxmoxCephOsdProvider = () => Provider7.effect(ProxmoxCephOsd, Effect15.succeed(ProxmoxCephOsd.Provider.of(handlers5)));
750
+ // src/proxmox/ceph-pool.ts
751
+ import { Resource as Resource8 } from "alchemy";
752
+ import * as Provider8 from "alchemy/Provider";
753
+ import * as Effect17 from "effect/Effect";
754
+
755
+ // src/proxmox/ceph-pool-form.ts
756
+ var object = (props) => `nodes/${props.node}/ceph/pool/${props.name}`;
757
+ var collection = (props) => `nodes/${props.node}/ceph/pool`;
758
+ var UNSET = -1;
759
+ var applications = (value) => csv(Array.isArray(value) ? value.map((entry) => String(entry)) : []);
760
+ var same = (declared, live, frozen = false) => declared === undefined || frozen || declared === live;
761
+ var hint = (value) => value === 0 ? undefined : value;
762
+ var field2 = (name, value) => value === undefined ? {} : { [name]: value };
763
+ var numeric = (value) => value === undefined ? undefined : String(value);
764
+ var updateBody = (props) => ({
765
+ ...field2("crush_rule", props.crush_rule),
766
+ ...field2("min_size", numeric(props.min_size)),
767
+ ...field2("pg_autoscale_mode", props.pg_autoscale_mode),
768
+ ...field2("pg_num_min", numeric(props.pg_num_min)),
769
+ ...field2("size", numeric(props.size)),
770
+ ...field2("target_size", numeric(props.target_size)),
771
+ ...field2("target_size_ratio", numeric(props.target_size_ratio))
772
+ });
773
+ var createBody = (props) => ({
774
+ ...updateBody(props),
775
+ ...field2("application", props.application),
776
+ ...field2("pg_num", numeric(props.pg_num)),
777
+ add_storages: "0",
778
+ name: props.name
779
+ });
780
+
781
+ // src/proxmox/ceph-pool-settle.ts
782
+ import * as Effect16 from "effect/Effect";
783
+ var confirmAbsent = (props, collection2, describe3) => pve(props.target, "read", "GET", collection2).pipe(Effect16.flatMap((rows) => (rows ?? []).some((row) => row.pool_name === props.name) ? Effect16.die(new Error(`${describe3}: the cluster lists this pool but its status could not be read, ` + "so this deploy will NOT create over it. Check that the node is up and that the " + "read role holds Sys.Audit or Datastore.Audit on / -- a create here would apply " + "pg_num to a live pool and start a PG merge.")) : Effect16.void));
784
+ var settle2 = (props, read, done) => Effect16.gen(function* () {
785
+ for (let attempt = 0;attempt < 30; attempt += 1) {
786
+ const live = yield* read(props);
787
+ if (done(live))
788
+ return live;
789
+ yield* Effect16.sleep("2 seconds");
790
+ }
791
+ return yield* read(props);
792
+ });
793
+
794
+ // src/proxmox/ceph-pool.ts
795
+ var ProxmoxCephPool = Resource8("Proxmox.CephPool", {
796
+ defaultRemovalPolicy: "retain"
797
+ });
798
+ var spec = {
799
+ attributes: (live, props) => {
800
+ if (typeof live["name"] !== "string")
801
+ return;
802
+ return {
803
+ applications: applications(live["application_list"]),
804
+ crush_rule: text(live["crush_rule"]),
805
+ id: int(live["id"], UNSET),
806
+ min_size: int(live["min_size"], UNSET),
807
+ name: props.name,
808
+ node: props.node,
809
+ nodelete: bool(live["nodelete"]),
810
+ nopgchange: bool(live["nopgchange"]),
811
+ nosizechange: bool(live["nosizechange"]),
812
+ pg_autoscale_mode: text(live["pg_autoscale_mode"]),
813
+ pg_num: int(live["pg_num"], UNSET),
814
+ pg_num_min: int(live["pg_num_min"], UNSET),
815
+ size: int(live["size"], UNSET),
816
+ target_size: int(live["target_size"], UNSET),
817
+ target_size_ratio: num(live["target_size_ratio"], UNSET)
818
+ };
819
+ },
820
+ collection,
821
+ createForm: createBody,
822
+ matches: (attributes, props) => same(props.size, attributes.size, attributes.nosizechange) && same(props.min_size, attributes.min_size, attributes.nosizechange) && same(props.pg_autoscale_mode, attributes.pg_autoscale_mode) && same(hint(props.pg_num_min), attributes.pg_num_min, attributes.nopgchange) && same(props.crush_rule, attributes.crush_rule, attributes.crush_rule === "") && same(hint(props.target_size), attributes.target_size),
823
+ path: (props) => `${object(props)}/status?verbose=1`,
824
+ updateForm: updateBody
825
+ };
826
+ var ops4 = pveOperations(spec);
827
+ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.succeed(ProxmoxCephPool.Provider.of({
828
+ list: () => Effect17.succeed([]),
829
+ read: ({ olds }) => ops4.read(olds),
830
+ diff: ({ news, output }) => ops4.diff(news, output),
831
+ reconcile: Effect17.fn(function* ({ news }) {
832
+ const live = yield* ops4.read(news);
833
+ let upid;
834
+ if (live === undefined) {
835
+ yield* confirmAbsent(news, spec.collection(news), object(news));
836
+ upid = yield* pve(news.target, "provision", "POST", collection(news), createBody(news));
837
+ } else {
838
+ const form = updateBody(news);
839
+ if (Object.keys(form).length > 0) {
840
+ upid = yield* pve(news.target, "provision", "PUT", object(news), form);
841
+ }
842
+ }
843
+ const after = yield* settle2(news, ops4.read, (row) => row !== undefined && spec.matches(row, news));
844
+ if (after === undefined || !spec.matches(after, news)) {
845
+ return yield* Effect17.die(new Error(`${object(news)}: the write returned no error but the pool still does not match ` + `the declaration after 60s. PVE answered with task ${upid ?? "(none)"} -- read ` + "its log with `pvesh get /nodes/<node>/tasks/<upid>/log` to see what Ceph " + "refused."));
846
+ }
847
+ return after;
848
+ }),
849
+ delete: Effect17.fn(function* ({ olds }) {
850
+ yield* pve(olds.target, "provision", "DELETE", object(olds));
851
+ const left = yield* settle2(olds, ops4.read, (row) => row === undefined);
852
+ if (left !== undefined) {
853
+ return yield* Effect17.die(new Error(`${object(olds)}: the DELETE returned no error but the pool is still there after ` + "60s. Either a storage on it still holds RBD images, or the mons are running " + "with mon_allow_pool_delete false."));
854
+ }
855
+ })
856
+ })));
857
+ // src/proxmox/firewall-alias.ts
858
+ import { Resource as Resource9 } from "alchemy";
859
+ import { isResolved as isResolved5 } from "alchemy/Diff";
860
+ import * as Provider9 from "alchemy/Provider";
861
+ import * as Effect18 from "effect/Effect";
862
+
863
+ // src/proxmox/firewall-alias-form.ts
864
+ var fold = (name) => name.trim().toLowerCase();
865
+ var cidr = (value) => {
866
+ const raw = text(value).trim();
867
+ const host = raw.includes(":") ? "/128" : "/32";
868
+ return raw.endsWith(host) ? raw.slice(0, -host.length) : raw;
869
+ };
870
+ var comment = (value) => {
871
+ const trimmed = text(value).trim();
872
+ return trimmed === "0" ? "" : trimmed;
873
+ };
874
+ var body = (props) => {
875
+ const note = comment(props.comment);
876
+ return { cidr: cidr(props.cidr), ...note === "" ? {} : { comment: note } };
877
+ };
878
+
879
+ // src/proxmox/firewall-alias.ts
880
+ var ProxmoxFirewallAlias = Resource9("Proxmox.FirewallAlias");
881
+ var handlers6 = pveHandlers({
882
+ attributes: (live, props) => {
883
+ const address = cidr(live["cidr"]);
884
+ return address === "" ? undefined : {
885
+ cidr: address,
886
+ comment: comment(live["comment"]),
887
+ ipversion: int(live["ipversion"], 0),
888
+ name: text(live["name"], props.name)
889
+ };
890
+ },
891
+ collection: () => "cluster/firewall/aliases",
892
+ createForm: (props) => ({ ...body(props), name: props.name }),
893
+ matches: (attributes, props) => attributes.cidr === cidr(props.cidr) && attributes.comment === comment(props.comment),
894
+ path: (props) => `cluster/firewall/aliases/${props.name}`,
895
+ updateForm: body
896
+ });
897
+ var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias, Effect18.succeed(ProxmoxFirewallAlias.Provider.of({
898
+ ...handlers6,
899
+ diff: Effect18.fn(function* ({ news, output }) {
900
+ if (output !== undefined && isResolved5(news) && fold(news.name) !== fold(output.name)) {
901
+ return { action: "replace" };
902
+ }
903
+ return yield* handlers6.diff({ news, output });
904
+ })
905
+ })));
906
+ // src/proxmox/group.ts
907
+ import { Resource as Resource10 } from "alchemy";
908
+ import * as Provider10 from "alchemy/Provider";
909
+ import * as Effect19 from "effect/Effect";
910
+ var ProxmoxGroup = Resource10("Proxmox.Group", {
911
+ defaultRemovalPolicy: "retain"
912
+ });
913
+ var storedComment = (comment2) => {
914
+ const value = comment2 ?? "";
915
+ return value === "0" ? "" : value;
916
+ };
917
+ var memberList = (live) => {
918
+ const joined = csv((Array.isArray(live) ? live : []).map((member) => text(member)));
919
+ return joined === "" ? [] : joined.split(",");
920
+ };
921
+ var handlers7 = pveHandlers({
922
+ attributes: (live, props) => ({
923
+ comment: text(live["comment"]),
924
+ groupid: props.groupid,
925
+ members: memberList(live["members"])
926
+ }),
927
+ collection: () => "access/groups",
928
+ createForm: (props) => ({ comment: storedComment(props.comment), groupid: props.groupid }),
929
+ matches: (attributes, props) => attributes.comment === storedComment(props.comment),
930
+ path: (props) => `access/groups/${props.groupid}`,
931
+ updateForm: (props) => ({ comment: storedComment(props.comment) })
932
+ });
933
+ var ProxmoxGroupProvider = () => Provider10.effect(ProxmoxGroup, Effect19.succeed(ProxmoxGroup.Provider.of(handlers7)));
934
+ // src/proxmox/ha-resource.ts
935
+ import { Resource as Resource11 } from "alchemy";
936
+ import * as Provider11 from "alchemy/Provider";
937
+ import * as Effect20 from "effect/Effect";
938
+ var ProxmoxHaResource = Resource11("Proxmox.HaResource");
939
+ var kind = (sid) => sid.split(":")[0] ?? "";
940
+ var shape3 = (props) => ({
941
+ comment: props.comment ?? "",
942
+ max_relocate: String(props.max_relocate ?? 1),
943
+ max_restart: String(props.max_restart ?? 1),
944
+ state: props.state ?? "started"
945
+ });
946
+ var handlers8 = pveHandlers({
947
+ attributes: (live, props) => ({
948
+ comment: typeof live["comment"] === "string" ? live["comment"] : "",
949
+ max_relocate: num(live["max_relocate"], 1),
950
+ max_restart: num(live["max_restart"], 1),
951
+ sid: typeof live["sid"] === "string" ? live["sid"] : props.sid,
952
+ state: typeof live["state"] === "string" ? live["state"] : "started",
953
+ type: typeof live["type"] === "string" ? live["type"] : kind(props.sid)
954
+ }),
955
+ collection: () => "cluster/ha/resources",
956
+ createForm: (props) => ({ ...shape3(props), sid: props.sid }),
957
+ matches: (attributes, props) => attributes.state === (props.state ?? "started") && attributes.comment === (props.comment ?? "") && attributes.max_restart === (props.max_restart ?? 1) && attributes.max_relocate === (props.max_relocate ?? 1),
958
+ path: (props) => `cluster/ha/resources/${props.sid}`,
959
+ updateForm: shape3
960
+ });
961
+ var ProxmoxHaResourceProvider = () => Provider11.effect(ProxmoxHaResource, Effect20.succeed(ProxmoxHaResource.Provider.of(handlers8)));
962
+ // src/proxmox/ha-rule.ts
963
+ import { Resource as Resource12 } from "alchemy";
964
+ import * as Provider12 from "alchemy/Provider";
965
+ import * as Effect21 from "effect/Effect";
966
+
967
+ // src/proxmox/ha-rule-form.ts
968
+ var nodeList = (value) => {
969
+ const ranked = new Map;
970
+ for (const entry of typeof value === "string" ? value.split(",") : value ?? []) {
971
+ const [node, priority] = entry.trim().split(":");
972
+ if (node === undefined || node === "")
973
+ continue;
974
+ const rank = Number.parseInt(priority ?? "0", 10);
975
+ ranked.set(node, Number.isNaN(rank) ? 0 : rank);
976
+ }
977
+ return [...ranked.keys()].sort().map((node) => {
978
+ const rank = ranked.get(node) ?? 0;
979
+ return rank === 0 ? node : `${node}:${String(rank)}`;
980
+ }).join(",");
981
+ };
982
+ var problems = (value) => typeof value === "object" && value !== null ? Object.entries(value).map(([option, message]) => `${option}: ${String(message)}`).sort().join("; ") : "";
983
+ var commentText = (raw) => raw.replace(/(?:%[0-9a-fA-F]{2})+/g, (escaped) => {
984
+ try {
985
+ return decodeURIComponent(escaped);
986
+ } catch {
987
+ return escaped;
988
+ }
989
+ });
990
+ var optional = (props) => ({
991
+ comment: props.comment === undefined || props.comment === "" ? undefined : props.comment,
992
+ disable: props.disable === true ? "1" : undefined,
993
+ ...props.type === "node-affinity" ? { strict: props.strict === true ? "1" : undefined } : {}
994
+ });
995
+ var required = (props) => ({
996
+ affinity: props.affinity,
997
+ resources: csv(props.resources),
998
+ type: props.type,
999
+ ...props.type === "node-affinity" ? { nodes: nodeList(props.nodes) } : {}
1000
+ });
1001
+ var body2 = (props) => {
1002
+ const fields = required(props);
1003
+ for (const [option, value] of Object.entries(optional(props))) {
1004
+ if (value !== undefined)
1005
+ fields[option] = value;
1006
+ }
1007
+ return fields;
1008
+ };
1009
+ var clearList = (props) => Object.entries(optional(props)).filter(([, value]) => value === undefined).map(([option]) => option);
1010
+
1011
+ // src/proxmox/ha-rule.ts
1012
+ var ProxmoxHaRule = Resource12("Proxmox.HaRule");
1013
+ var handlers9 = pveHandlers({
1014
+ attributes: (live, props) => {
1015
+ const liveType = text(live["type"]);
1016
+ if (liveType !== "" && liveType !== props.type)
1017
+ return;
1018
+ return {
1019
+ affinity: text(live["affinity"]),
1020
+ comment: text(live["comment"]),
1021
+ disable: bool(live["disable"], false),
1022
+ errors: problems(live["errors"]),
1023
+ nodes: nodeList(typeof live["nodes"] === "string" ? live["nodes"] : undefined),
1024
+ order: int(live["order"], 0),
1025
+ resources: csv(typeof live["resources"] === "string" ? live["resources"] : undefined),
1026
+ rule: text(live["rule"], props.rule),
1027
+ strict: bool(live["strict"], false),
1028
+ type: props.type
1029
+ };
1030
+ },
1031
+ collection: () => "cluster/ha/rules",
1032
+ createForm: (props) => ({ ...body2(props), rule: props.rule }),
1033
+ matches: (attributes, props) => attributes.resources === csv(props.resources) && attributes.affinity === props.affinity && attributes.comment === commentText(props.comment ?? "") && attributes.disable === (props.disable === true) && (props.type !== "node-affinity" || attributes.nodes === nodeList(props.nodes) && attributes.strict === (props.strict === true)),
1034
+ path: (props) => `cluster/ha/rules/${props.rule}`,
1035
+ updateForm: (props) => {
1036
+ const clear = clearList(props);
1037
+ const fields = body2(props);
1038
+ return withClears(fields, clear);
1039
+ }
1040
+ });
1041
+ var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect21.succeed(ProxmoxHaRule.Provider.of(handlers9)));
1042
+ // src/proxmox/lxc.ts
1043
+ import { Resource as Resource13 } from "alchemy";
1044
+ import * as Provider13 from "alchemy/Provider";
1045
+ import * as Effect22 from "effect/Effect";
1046
+ var ProxmoxLxc = Resource13("Proxmox.Lxc");
1047
+ var handlers10 = pveHandlers({
1048
+ attributes: (live, props) => ({
1049
+ cores: num(live["cores"], 1),
1050
+ hostname: typeof live["hostname"] === "string" ? live["hostname"] : "",
1051
+ memory: num(live["memory"], 512),
1052
+ node: props.node,
1053
+ onboot: live["onboot"] === 1 || live["onboot"] === true,
1054
+ status: typeof live["status"] === "string" ? live["status"] : "unknown",
1055
+ vmid: props.vmid
1056
+ }),
1057
+ collection: (props) => `nodes/${props.node}/lxc`,
1058
+ createForm: (props) => ({
1059
+ cores: String(props.cores ?? 1),
1060
+ hostname: props.hostname ?? `ct${String(props.vmid)}`,
1061
+ memory: String(props.memory ?? 512),
1062
+ onboot: props.onboot === true ? "1" : "0",
1063
+ ostemplate: props.ostemplate,
1064
+ storage: props.storage,
1065
+ vmid: String(props.vmid),
1066
+ ...props.net0 === undefined ? {} : { net0: props.net0 }
1067
+ }),
1068
+ matches: (attributes, props) => attributes.hostname === (props.hostname ?? `ct${String(props.vmid)}`) && attributes.memory === (props.memory ?? 512) && attributes.cores === (props.cores ?? 1) && attributes.onboot === (props.onboot === true),
1069
+ path: (props) => `nodes/${props.node}/lxc/${String(props.vmid)}/config`,
1070
+ updateForm: (props) => ({
1071
+ cores: String(props.cores ?? 1),
1072
+ hostname: props.hostname ?? `ct${String(props.vmid)}`,
1073
+ memory: String(props.memory ?? 512),
1074
+ onboot: props.onboot === true ? "1" : "0",
1075
+ ...props.net0 === undefined ? {} : { net0: props.net0 }
1076
+ })
1077
+ });
1078
+ var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect22.succeed(ProxmoxLxc.Provider.of(handlers10)));
1079
+ // src/proxmox/metric-server.ts
1080
+ import { Resource as Resource14 } from "alchemy";
1081
+ import * as Provider14 from "alchemy/Provider";
1082
+ import * as Effect23 from "effect/Effect";
1083
+
1084
+ // src/proxmox/metric-server-form.ts
1085
+ var UNSET2 = -1;
1086
+ var str = (value) => value === undefined ? undefined : String(value);
1087
+ var optional2 = (props) => {
1088
+ switch (props.type) {
1089
+ case "graphite":
1090
+ return { mtu: str(props.mtu), path: props.path, timeout: str(props.timeout) };
1091
+ case "influxdb":
1092
+ return {
1093
+ "api-path-prefix": props["api-path-prefix"],
1094
+ bucket: props.bucket,
1095
+ influxdbproto: props.influxdbproto,
1096
+ "max-body-size": str(props["max-body-size"]),
1097
+ mtu: str(props.mtu),
1098
+ organization: props.organization,
1099
+ timeout: str(props.timeout)
1100
+ };
1101
+ case "opentelemetry":
1102
+ return {
1103
+ "otel-compression": props["otel-compression"],
1104
+ "otel-max-body-size": str(props["otel-max-body-size"]),
1105
+ "otel-path": props["otel-path"],
1106
+ "otel-protocol": props["otel-protocol"],
1107
+ "otel-timeout": str(props["otel-timeout"])
1108
+ };
1109
+ }
1110
+ };
1111
+ var verification = (props) => {
1112
+ switch (props.type) {
1113
+ case "graphite":
1114
+ return {};
1115
+ case "influxdb":
1116
+ return { "verify-certificate": props["verify-certificate"] === false ? "0" : "1" };
1117
+ case "opentelemetry":
1118
+ return { "otel-verify-ssl": props["otel-verify-ssl"] === false ? "0" : "1" };
1119
+ }
1120
+ };
1121
+ var required2 = (props) => ({
1122
+ disable: props.disable === true ? "1" : "0",
1123
+ port: String(props.port),
1124
+ server: props.server,
1125
+ ...verification(props)
1126
+ });
1127
+ var body3 = (props) => {
1128
+ const fields = required2(props);
1129
+ for (const [option, value] of Object.entries(optional2(props))) {
1130
+ if (value !== undefined)
1131
+ fields[option] = value;
1132
+ }
1133
+ return fields;
1134
+ };
1135
+ var update = (props) => {
1136
+ const clear = Object.entries(optional2(props)).filter(([, value]) => value === undefined).map(([option]) => option);
1137
+ return withClears(body3(props), clear);
1138
+ };
1139
+
1140
+ // src/proxmox/metric-server-otel.ts
1141
+ var otelAttributes = (live) => ({
1142
+ "otel-compression": text(live["otel-compression"]),
1143
+ "otel-max-body-size": int(live["otel-max-body-size"], UNSET2),
1144
+ "otel-path": text(live["otel-path"]),
1145
+ "otel-protocol": text(live["otel-protocol"]),
1146
+ "otel-timeout": int(live["otel-timeout"], UNSET2),
1147
+ "otel-verify-ssl": bool(live["otel-verify-ssl"], true)
1148
+ });
1149
+ var otelMatches = (attributes, props) => attributes["otel-compression"] === (props["otel-compression"] ?? "") && attributes["otel-max-body-size"] === (props["otel-max-body-size"] ?? UNSET2) && attributes["otel-path"] === (props["otel-path"] ?? "") && attributes["otel-protocol"] === (props["otel-protocol"] ?? "") && attributes["otel-timeout"] === (props["otel-timeout"] ?? UNSET2) && attributes["otel-verify-ssl"] === (props["otel-verify-ssl"] !== false);
1150
+
1151
+ // src/proxmox/metric-server.ts
1152
+ var ProxmoxMetricServer = Resource14("Proxmox.MetricServer");
1153
+ var matchesType = (attributes, props) => {
1154
+ if (props.type === "opentelemetry")
1155
+ return otelMatches(attributes, props);
1156
+ const socket = attributes.mtu === (props.mtu ?? UNSET2) && attributes.timeout === (props.timeout ?? UNSET2);
1157
+ if (props.type === "graphite")
1158
+ return socket && attributes.path === (props.path ?? "");
1159
+ return socket && attributes.influxdbproto === (props.influxdbproto ?? "") && attributes.organization === (props.organization ?? "") && attributes.bucket === (props.bucket ?? "") && attributes["api-path-prefix"] === (props["api-path-prefix"] ?? "") && attributes["max-body-size"] === (props["max-body-size"] ?? UNSET2) && attributes["verify-certificate"] === (props["verify-certificate"] !== false);
1160
+ };
1161
+ var handlers11 = pveHandlers({
1162
+ attributes: (live, props) => {
1163
+ const liveType = text(live["type"]);
1164
+ if (liveType !== "" && liveType !== props.type)
1165
+ return;
1166
+ return {
1167
+ ...otelAttributes(live),
1168
+ "api-path-prefix": text(live["api-path-prefix"]),
1169
+ bucket: text(live["bucket"]),
1170
+ disable: bool(live["disable"], false),
1171
+ id: props.id,
1172
+ influxdbproto: text(live["influxdbproto"]),
1173
+ "max-body-size": int(live["max-body-size"], UNSET2),
1174
+ mtu: int(live["mtu"], UNSET2),
1175
+ organization: text(live["organization"]),
1176
+ path: text(live["path"]),
1177
+ port: int(live["port"], 0),
1178
+ server: text(live["server"]),
1179
+ timeout: int(live["timeout"], UNSET2),
1180
+ type: props.type,
1181
+ "verify-certificate": bool(live["verify-certificate"], true)
1182
+ };
1183
+ },
1184
+ collection: (props) => `cluster/metrics/server/${props.id}`,
1185
+ createForm: (props) => ({ ...body3(props), type: props.type }),
1186
+ matches: (attributes, props) => attributes.server === props.server && attributes.port === props.port && attributes.disable === (props.disable === true) && matchesType(attributes, props),
1187
+ path: (props) => `cluster/metrics/server/${props.id}`,
1188
+ updateForm: update
1189
+ });
1190
+ var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect23.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
1191
+ // src/proxmox/network-apply.ts
1192
+ import { Resource as Resource15 } from "alchemy";
1193
+ import { isResolved as isResolved6 } from "alchemy/Diff";
1194
+ import * as Provider15 from "alchemy/Provider";
1195
+ import * as Effect25 from "effect/Effect";
1196
+
1197
+ // src/proxmox/network-apply-read.ts
1198
+ import * as Effect24 from "effect/Effect";
1199
+ var stagedDiff = (target, node) => Effect24.gen(function* () {
1200
+ const path = `nodes/${node}/network`;
1201
+ const credential = yield* leased(target, "read");
1202
+ const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
1203
+ return text(envelope.changes);
1204
+ });
1205
+ var pendingLines = (diff) => {
1206
+ if (diff === "")
1207
+ return 0;
1208
+ const changed = diff.split(`
1209
+ `).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
1210
+ return Math.max(changed, 1);
1211
+ };
1212
+ var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect24.map(pendingLines));
1213
+ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect24.map((rows) => {
1214
+ const all = rows ?? [];
1215
+ const cluster = all.find((row) => row.type === "cluster");
1216
+ return {
1217
+ offline: all.filter((row) => row.type === "node" && !bool(row.online)).map((row) => text(row.name, "<unnamed>")),
1218
+ quorate: cluster === undefined ? true : bool(cluster.quorate)
1219
+ };
1220
+ }));
1221
+ var degradedReason = (target) => clusterHealth(target).pipe(Effect24.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
1222
+ var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
1223
+ var STILL_RUNNING = "still running when this provider stopped waiting";
1224
+ var ATTEMPTS = 30;
1225
+ var awaitTask = (target, credential, node, upid) => Effect24.gen(function* () {
1226
+ const path = `nodes/${node}/tasks/${upid}/status`;
1227
+ for (let attempt = 0;attempt < ATTEMPTS; attempt += 1) {
1228
+ const status = yield* pveWith(target, credential, "GET", path).pipe(Effect24.orElseSucceed(() => {
1229
+ return;
1230
+ }));
1231
+ if (status === undefined)
1232
+ return UNREACHABLE;
1233
+ if (status.status === "stopped")
1234
+ return text(status.exitstatus, "stopped with no exitstatus");
1235
+ yield* Effect24.sleep("1 second");
1236
+ }
1237
+ return STILL_RUNNING;
1238
+ });
1239
+
1240
+ // src/proxmox/network-apply.ts
1241
+ var ProxmoxNetworkApply = Resource15("Proxmox.NetworkApply");
1242
+ var read = (props) => pendingCount(props.target, props.node).pipe(Effect25.map((pending) => ({ node: props.node, pending })));
1243
+ var diff = (news) => Effect25.gen(function* () {
1244
+ if (!isResolved6(news))
1245
+ return;
1246
+ const { pending } = yield* read(news);
1247
+ return pending === 0 ? { action: "noop" } : { action: "update" };
1248
+ });
1249
+ var refuse = (node, why) => Effect25.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
1250
+ var apply = (props) => Effect25.gen(function* () {
1251
+ const node = props.node;
1252
+ const staged = yield* pendingCount(props.target, node);
1253
+ if (staged === 0)
1254
+ return { node, pending: 0 };
1255
+ const before = yield* degradedReason(props.target);
1256
+ if (before !== undefined) {
1257
+ return yield* refuse(node, `refusing to reload networking on a cluster that is already degraded (${before}). ` + "Ceph spans these nodes -- fix the cluster first, then re-run the deploy.");
1258
+ }
1259
+ const credential = yield* mint(props.target, "provision");
1260
+ const upid = yield* pveWith(props.target, credential, "PUT", `nodes/${node}/network`);
1261
+ if (upid === undefined) {
1262
+ return yield* refuse(node, 'the apply returned no UPID. PVE wraps every answer in {"data":...} and can report success ' + "on a call that did nothing -- the reload cannot be confirmed, so it is not claimed.");
1263
+ }
1264
+ const exitstatus = yield* awaitTask(props.target, credential, node, upid);
1265
+ if (exitstatus !== "OK") {
1266
+ return yield* refuse(node, `the reload ended "${exitstatus}" (${upid}). Read it from ANOTHER node -- ` + `\`pvesh get /nodes/${node}/tasks/${upid}/log\` -- and do not apply the next node until ` + "this one is understood.");
1267
+ }
1268
+ const after = yield* degradedReason(props.target);
1269
+ if (after !== undefined) {
1270
+ return yield* refuse(node, `the reload reported OK but the cluster is now degraded (${after}). The next ` + "node has NOT been applied. Restore this one before continuing -- Ceph runs over these " + "links and a second reload now would take pools below min_size.");
1271
+ }
1272
+ const left = yield* pendingCount(props.target, node);
1273
+ if (left !== 0) {
1274
+ return yield* refuse(node, `the reload reported OK but ${String(left)} staged line(s) remain. The running config does ` + "not match the declared one, and saying otherwise is the lie this resource exists to stop.");
1275
+ }
1276
+ return { node, pending: left };
1277
+ });
1278
+ var handlers12 = {
1279
+ delete: () => Effect25.void,
1280
+ diff: ({ news }) => diff(news),
1281
+ list: () => Effect25.succeed([]),
1282
+ read: ({ olds }) => read(olds),
1283
+ reconcile: ({ news }) => apply(news)
1284
+ };
1285
+ var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect25.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
1286
+ // src/proxmox/node-network.ts
1287
+ import { Resource as Resource16 } from "alchemy";
1288
+ import * as Provider16 from "alchemy/Provider";
1289
+ import * as Effect26 from "effect/Effect";
1290
+
1291
+ // src/proxmox/node-network-form.ts
1292
+ var UNSET3 = -1;
1293
+ var ifaceList = (value) => text(value).split(/[\s,;]+/).filter((part) => part !== "").sort().join(" ");
1294
+ var comment2 = (value) => text(value).replace(/\s+$/, "");
1295
+ var same2 = (declared, live) => declared === undefined || declared === live;
1296
+ var sameList = (declared, live) => declared === undefined || ifaceList(declared) === live;
1297
+ var sameComment = (declared, live) => declared === undefined || comment2(declared) === live;
1298
+ var field3 = (name, value) => value === undefined ? {} : { [name]: value };
1299
+ var body4 = (props) => ({
1300
+ ...field3("autostart", flag(props.autostart)),
1301
+ ...field3("bond-primary", props["bond-primary"]),
1302
+ ...field3("bond_mode", props.bond_mode),
1303
+ ...field3("bond_xmit_hash_policy", props.bond_xmit_hash_policy),
1304
+ ...field3("bridge_ports", props.bridge_ports),
1305
+ ...field3("bridge_vids", props.bridge_vids),
1306
+ ...field3("bridge_vlan_aware", props.bridge_vlan_aware === true ? "1" : undefined),
1307
+ ...field3("cidr", props.cidr),
1308
+ ...field3("comments", props.comments),
1309
+ ...field3("gateway", props.gateway),
1310
+ ...field3("mtu", props.mtu === undefined ? undefined : String(props.mtu)),
1311
+ ...field3("slaves", props.slaves),
1312
+ ...field3("vlan-id", props["vlan-id"] === undefined ? undefined : String(props["vlan-id"])),
1313
+ ...field3("vlan-raw-device", props["vlan-raw-device"]),
1314
+ type: props.type
1315
+ });
1316
+ var updateBody2 = (props) => {
1317
+ const clear = [
1318
+ ...props.cidr === undefined ? ["cidr"] : [],
1319
+ ...props.bridge_vlan_aware === false ? ["bridge_vlan_aware"] : []
1320
+ ];
1321
+ const fields = body4(props);
1322
+ return withClears(fields, clear);
1323
+ };
1324
+ var readAttributes = (live, props) => {
1325
+ const liveType = text(live["type"]);
1326
+ if (liveType !== "" && liveType !== props.type)
1327
+ return;
1328
+ return {
1329
+ active: bool(live["active"]),
1330
+ autostart: bool(live["autostart"]),
1331
+ "bond-primary": text(live["bond-primary"]),
1332
+ bond_miimon: text(live["bond_miimon"]),
1333
+ bond_mode: text(live["bond_mode"]),
1334
+ bond_xmit_hash_policy: text(live["bond_xmit_hash_policy"]),
1335
+ bridge_fd: text(live["bridge_fd"]),
1336
+ bridge_ports: ifaceList(live["bridge_ports"]),
1337
+ bridge_stp: text(live["bridge_stp"]),
1338
+ bridge_vids: ifaceList(live["bridge_vids"]),
1339
+ bridge_vlan_aware: bool(live["bridge_vlan_aware"]),
1340
+ cidr: text(live["cidr"]),
1341
+ comments: comment2(live["comments"]),
1342
+ exists: bool(live["exists"]),
1343
+ families: Array.isArray(live["families"]) ? live["families"].join(",") : "",
1344
+ gateway: text(live["gateway"]),
1345
+ iface: props.iface,
1346
+ method: text(live["method"]),
1347
+ mtu: int(live["mtu"], UNSET3),
1348
+ node: props.node,
1349
+ priority: int(live["priority"], UNSET3),
1350
+ slaves: ifaceList(live["slaves"]),
1351
+ type: liveType === "" ? props.type : liveType,
1352
+ "vlan-id": int(live["vlan-id"], UNSET3),
1353
+ "vlan-raw-device": text(live["vlan-raw-device"])
1354
+ };
1355
+ };
1356
+
1357
+ // src/proxmox/node-network.ts
1358
+ var ProxmoxNodeNetwork = Resource16("Proxmox.NodeNetwork", {
1359
+ defaultRemovalPolicy: "retain"
1360
+ });
1361
+ var handlers13 = pveHandlers({
1362
+ attributes: readAttributes,
1363
+ collection: (props) => `nodes/${props.node}/network`,
1364
+ createForm: body4,
1365
+ matches: (attributes, props) => attributes.cidr === (props.cidr ?? "") && same2(props.gateway, attributes.gateway) && same2(props.autostart, attributes.autostart) && same2(props.mtu, attributes.mtu) && sameComment(props.comments, attributes.comments) && sameList(props.bridge_ports, attributes.bridge_ports) && sameList(props.bridge_vids, attributes.bridge_vids) && same2(props.bridge_vlan_aware, attributes.bridge_vlan_aware) && sameList(props.slaves, attributes.slaves) && same2(props.bond_mode, attributes.bond_mode) && same2(props.bond_xmit_hash_policy, attributes.bond_xmit_hash_policy) && same2(props["bond-primary"], attributes["bond-primary"]) && same2(props["vlan-id"], attributes["vlan-id"]) && same2(props["vlan-raw-device"], attributes["vlan-raw-device"]),
1366
+ path: (props) => `nodes/${props.node}/network/${props.iface}`,
1367
+ updateForm: updateBody2
1368
+ });
1369
+ var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect26.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
1370
+ // src/proxmox/notification-target.ts
1371
+ import { Resource as Resource17 } from "alchemy";
1372
+ import * as Provider17 from "alchemy/Provider";
1373
+ import * as Effect27 from "effect/Effect";
1374
+
1375
+ // src/proxmox/notification-target-form.ts
1376
+ var REPEATED_KEY = "\x00";
1377
+ var portOf = (value) => {
1378
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
1379
+ return Number.isNaN(parsed) ? 0 : parsed;
1380
+ };
1381
+ var sorted = (parts) => parts.map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
1382
+ var addressList = (value) => sorted(Array.isArray(value) ? value.map((item) => String(item)) : typeof value === "string" ? value.split(/[\s,;]+/) : []);
1383
+ var headerList = (value) => sorted(Array.isArray(value) ? value.map((item) => String(item)) : [text(value)]);
1384
+ var set = (key, value) => value === undefined ? {} : { [key]: value };
1385
+ var shape4 = (props) => ({
1386
+ disable: props.disable === true ? "1" : "0",
1387
+ ...set("author", props.author),
1388
+ ...set("body", props.body),
1389
+ ...set("comment", props.comment),
1390
+ ...set("from-address", props["from-address"]),
1391
+ ...set("header", props.header === undefined ? undefined : props.header.join(REPEATED_KEY)),
1392
+ ...set("mailto", props.mailto === undefined ? undefined : addressList(props.mailto)),
1393
+ ...set("mailto-user", props["mailto-user"] === undefined ? undefined : addressList(props["mailto-user"])),
1394
+ ...set("method", props.method),
1395
+ ...set("mode", props.mode),
1396
+ ...set("port", props.port === undefined ? undefined : String(props.port)),
1397
+ ...set("server", props.server),
1398
+ ...set("url", props.url),
1399
+ ...set("username", props.username)
1400
+ });
1401
+
1402
+ // src/proxmox/notification-target.ts
1403
+ var ProxmoxNotificationTarget = Resource17("Proxmox.NotificationTarget");
1404
+ var handlers14 = pveHandlers({
1405
+ attributes: (live, props) => ({
1406
+ author: text(live["author"]),
1407
+ body: text(live["body"]),
1408
+ comment: text(live["comment"]),
1409
+ disable: live["disable"] === 1 || live["disable"] === true,
1410
+ "from-address": text(live["from-address"]),
1411
+ header: headerList(live["header"]),
1412
+ mailto: addressList(live["mailto"]),
1413
+ "mailto-user": addressList(live["mailto-user"]),
1414
+ method: text(live["method"]),
1415
+ mode: text(live["mode"]),
1416
+ name: props.name,
1417
+ port: portOf(live["port"]),
1418
+ server: text(live["server"]),
1419
+ type: props.type,
1420
+ url: text(live["url"]),
1421
+ username: text(live["username"])
1422
+ }),
1423
+ collection: (props) => `cluster/notifications/endpoints/${props.type}`,
1424
+ createForm: (props) => ({ ...shape4(props), name: props.name }),
1425
+ matches: (attributes, props) => attributes.disable === (props.disable === true) && (props.comment === undefined || attributes.comment === props.comment) && (props.server === undefined || attributes.server === props.server) && (props.port === undefined || attributes.port === props.port) && (props.mode === undefined || attributes.mode === props.mode) && (props.username === undefined || attributes.username === props.username) && (props["from-address"] === undefined || attributes["from-address"] === props["from-address"]) && (props.author === undefined || attributes.author === props.author) && (props.url === undefined || attributes.url === props.url) && (props.method === undefined || attributes.method === props.method) && (props.body === undefined || attributes.body === props.body) && (props.mailto === undefined || attributes.mailto === addressList(props.mailto)) && (props["mailto-user"] === undefined || attributes["mailto-user"] === addressList(props["mailto-user"])) && (props.header === undefined || attributes.header === headerList(props.header)),
1426
+ path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
1427
+ updateForm: shape4
1428
+ });
1429
+ var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget, Effect27.succeed(ProxmoxNotificationTarget.Provider.of(handlers14)));
1430
+ // src/proxmox/pbs-datastore.ts
1431
+ import { Resource as Resource18 } from "alchemy";
1432
+ import { isResolved as isResolved7 } from "alchemy/Diff";
1433
+ import * as Provider18 from "alchemy/Provider";
1434
+ import * as Effect30 from "effect/Effect";
1435
+
1436
+ // src/proxmox/pbs-datastore-form.ts
1437
+ import * as Effect28 from "effect/Effect";
1438
+ var attributes = (live, props) => ({
1439
+ backend: propertyString(live["backend"]),
1440
+ comment: text(live["comment"]),
1441
+ "counter-reset-schedule": text(live["counter-reset-schedule"]).trim(),
1442
+ "gc-schedule": text(live["gc-schedule"]).trim(),
1443
+ "keep-daily": int(live["keep-daily"], 0),
1444
+ "keep-hourly": int(live["keep-hourly"], 0),
1445
+ "keep-last": int(live["keep-last"], 0),
1446
+ "keep-monthly": int(live["keep-monthly"], 0),
1447
+ "keep-weekly": int(live["keep-weekly"], 0),
1448
+ "keep-yearly": int(live["keep-yearly"], 0),
1449
+ "maintenance-mode": propertyString(live["maintenance-mode"], "type"),
1450
+ name: props.name,
1451
+ "notification-thresholds": propertyString(live["notification-thresholds"]),
1452
+ notify: propertyString(live["notify"]),
1453
+ "notify-user": text(live["notify-user"]),
1454
+ path: text(live["path"]),
1455
+ "prune-schedule": text(live["prune-schedule"]).trim(),
1456
+ tuning: propertyString(live["tuning"]),
1457
+ "verify-new": bool(live["verify-new"])
1458
+ });
1459
+ var same3 = (declared, live) => declared === undefined || declared === live;
1460
+ var matches = (live, props) => same3(props.comment, live.comment) && same3(props["gc-schedule"]?.trim(), live["gc-schedule"]) && same3(props["prune-schedule"]?.trim(), live["prune-schedule"]) && same3(props["keep-last"], live["keep-last"]) && same3(props["keep-hourly"], live["keep-hourly"]) && same3(props["keep-daily"], live["keep-daily"]) && same3(props["keep-weekly"], live["keep-weekly"]) && same3(props["keep-monthly"], live["keep-monthly"]) && same3(props["keep-yearly"], live["keep-yearly"]) && same3(props["notify-user"], live["notify-user"]) && same3(props["counter-reset-schedule"]?.trim(), live["counter-reset-schedule"]) && (props["notification-thresholds"] === undefined || propertyString(props["notification-thresholds"]) === live["notification-thresholds"]) && same3(props["verify-new"], live["verify-new"]) && (props.notify === undefined || propertyString(props.notify) === live.notify) && (props.tuning === undefined || propertyString(props.tuning) === live.tuning) && (props["maintenance-mode"] === undefined || propertyString(props["maintenance-mode"], "type") === live["maintenance-mode"]);
1461
+ var set2 = (key, value) => value === undefined ? {} : { [key]: value };
1462
+ var count = (value) => value === undefined ? undefined : String(value);
1463
+ var mutable = (props) => ({
1464
+ ...set2("comment", props.comment),
1465
+ ...set2("gc-schedule", props["gc-schedule"]?.trim()),
1466
+ ...set2("keep-daily", count(props["keep-daily"])),
1467
+ ...set2("keep-hourly", count(props["keep-hourly"])),
1468
+ ...set2("keep-last", count(props["keep-last"])),
1469
+ ...set2("keep-monthly", count(props["keep-monthly"])),
1470
+ ...set2("keep-weekly", count(props["keep-weekly"])),
1471
+ ...set2("keep-yearly", count(props["keep-yearly"])),
1472
+ ...set2("counter-reset-schedule", props["counter-reset-schedule"]),
1473
+ ...set2("maintenance-mode", props["maintenance-mode"]),
1474
+ ...set2("notification-thresholds", props["notification-thresholds"]),
1475
+ ...set2("notify", props.notify),
1476
+ ...set2("notify-user", props["notify-user"]),
1477
+ ...set2("prune-schedule", props["prune-schedule"]?.trim()),
1478
+ ...set2("tuning", props.tuning),
1479
+ ...set2("verify-new", flag(props["verify-new"]))
1480
+ });
1481
+ var createForm3 = (props) => ({
1482
+ ...mutable(props),
1483
+ name: props.name,
1484
+ path: props.path,
1485
+ ...props.backend === undefined ? {} : { backend: props.backend }
1486
+ });
1487
+ var updateForm = (props) => mutable(props);
1488
+ var object2 = (props) => `config/datastore/${props.name}`;
1489
+ var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect28.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect28.orElseSucceed(() => {
1490
+ return;
1491
+ }));
1492
+
1493
+ // src/proxmox/pbs-datastore-guard.ts
1494
+ import * as Effect29 from "effect/Effect";
1495
+ var samePath = (left, right) => {
1496
+ const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
1497
+ return trim(left) === trim(right);
1498
+ };
1499
+ var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect29.void : Effect29.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
1500
+ var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect29.void : Effect29.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
1501
+ var settle3 = (read2, done) => Effect29.gen(function* () {
1502
+ for (let attempt = 0;attempt < 60; attempt += 1) {
1503
+ const live = yield* read2;
1504
+ if (done(live))
1505
+ return live;
1506
+ yield* Effect29.sleep("2 seconds");
1507
+ }
1508
+ return yield* read2;
1509
+ });
1510
+
1511
+ // src/proxmox/pbs-datastore.ts
1512
+ var PbsDatastore = Resource18("Pbs.Datastore", {
1513
+ defaultRemovalPolicy: "retain"
1514
+ });
1515
+ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect30.succeed(PbsDatastore.Provider.of({
1516
+ list: () => Effect30.succeed([]),
1517
+ read: ({ olds }) => readOne(olds),
1518
+ diff: ({ news, output }) => Effect30.gen(function* () {
1519
+ if (output === undefined || !isResolved7(news))
1520
+ return;
1521
+ const live = yield* readOne(news);
1522
+ if (live === undefined)
1523
+ return { action: "update" };
1524
+ yield* guardPath(live, news);
1525
+ yield* guardBackend(live, news);
1526
+ return matches(live, news) ? { action: "noop" } : { action: "update" };
1527
+ }),
1528
+ reconcile: Effect30.fn(function* ({ news }) {
1529
+ const live = yield* readOne(news);
1530
+ let upid;
1531
+ if (live === undefined) {
1532
+ upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm3(news));
1533
+ } else {
1534
+ yield* guardPath(live, news);
1535
+ yield* guardBackend(live, news);
1536
+ const form = updateForm(news);
1537
+ if (!matches(live, news) && Object.keys(form).length > 0) {
1538
+ yield* pve(news.target, "provision", "PUT", object2(news), form);
1539
+ }
1540
+ }
1541
+ const after = yield* settle3(readOne(news), (row) => row !== undefined && matches(row, news));
1542
+ if (after === undefined || !matches(after, news)) {
1543
+ return yield* Effect30.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
1544
+ }
1545
+ return after;
1546
+ }),
1547
+ delete: Effect30.fn(function* ({ olds }) {
1548
+ yield* pve(olds.target, "provision", "DELETE", object2(olds));
1549
+ const left = yield* settle3(readOne(olds), (row) => row === undefined);
1550
+ if (left !== undefined) {
1551
+ return yield* Effect30.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
1552
+ }
1553
+ })
1554
+ })));
1555
+ // src/proxmox/pbs-prune-job.ts
1556
+ import { Resource as Resource19 } from "alchemy";
1557
+ import * as Provider19 from "alchemy/Provider";
1558
+ import * as Effect31 from "effect/Effect";
1559
+
1560
+ // src/proxmox/pbs-prune-job-form.ts
1561
+ var KEEP_KEYS = [
1562
+ "keep-last",
1563
+ "keep-hourly",
1564
+ "keep-daily",
1565
+ "keep-weekly",
1566
+ "keep-monthly",
1567
+ "keep-yearly"
1568
+ ];
1569
+ var keepForm = (props) => {
1570
+ const form = {};
1571
+ for (const key of KEEP_KEYS) {
1572
+ const value = props[key];
1573
+ if (value !== undefined)
1574
+ form[key] = String(value);
1575
+ }
1576
+ return form;
1577
+ };
1578
+ var keepAttributes = (live) => ({
1579
+ "keep-daily": int(live["keep-daily"], 0),
1580
+ "keep-hourly": int(live["keep-hourly"], 0),
1581
+ "keep-last": int(live["keep-last"], 0),
1582
+ "keep-monthly": int(live["keep-monthly"], 0),
1583
+ "keep-weekly": int(live["keep-weekly"], 0),
1584
+ "keep-yearly": int(live["keep-yearly"], 0)
1585
+ });
1586
+ var keepsMatch = (attributes2, props) => KEEP_KEYS.every((key) => props[key] === undefined || props[key] === attributes2[key]);
1587
+ var keepsSomething = (props) => KEEP_KEYS.some((key) => props[key] !== undefined);
1588
+ var requireKeeps = (props) => {
1589
+ if (keepsSomething(props))
1590
+ return;
1591
+ throw new Error(`Pbs.PruneJob ${props.id}: declares no keep-* window, which tells PBS to keep NOTHING on ` + `datastore '${props.store}'. Every keep-* is optional on the wire, so an omission and a ` + 'deliberate "retain nothing" are indistinguishable; declare at least one window (keep-last, ' + "keep-hourly, keep-daily, keep-weekly, keep-monthly or keep-yearly) or delete the job.");
1592
+ };
1593
+ var field4 = (name, value) => value === undefined ? {} : { [name]: value };
1594
+ var integer = (value) => value === undefined ? undefined : String(value);
1595
+ var shape5 = (props) => ({
1596
+ ...keepForm(props),
1597
+ ...field4("comment", props.comment),
1598
+ ...field4("disable", flag(props.disable)),
1599
+ ...field4("max-depth", integer(props["max-depth"])),
1600
+ ...field4("ns", props.ns),
1601
+ schedule: props.schedule,
1602
+ store: props.store
1603
+ });
1604
+ var createBody2 = (props) => {
1605
+ requireKeeps(props);
1606
+ return { ...shape5(props), id: props.id };
1607
+ };
1608
+ var updateBody3 = (props) => {
1609
+ requireKeeps(props);
1610
+ return shape5(props);
1611
+ };
1612
+
1613
+ // src/proxmox/pbs-prune-job.ts
1614
+ var PbsPruneJob = Resource19("Pbs.PruneJob");
1615
+ var same4 = (declared, live) => declared === undefined || declared === live;
1616
+ var handlers15 = pveHandlers({
1617
+ attributes: (live, props) => ({
1618
+ comment: text(live["comment"], ""),
1619
+ disable: bool(live["disable"], false),
1620
+ id: props.id,
1621
+ ...keepAttributes(live),
1622
+ "max-depth": int(live["max-depth"], -1),
1623
+ ns: text(live["ns"], ""),
1624
+ schedule: text(live["schedule"], ""),
1625
+ store: text(live["store"], props.store)
1626
+ }),
1627
+ collection: () => "config/prune",
1628
+ createForm: createBody2,
1629
+ matches: (attributes2, props) => attributes2.schedule === props.schedule && attributes2.store === props.store && same4(props.ns, attributes2.ns) && same4(props["max-depth"], attributes2["max-depth"]) && same4(props.disable, attributes2.disable) && same4(props.comment, attributes2.comment) && keepsMatch(attributes2, props),
1630
+ path: (props) => `config/prune/${props.id}`,
1631
+ updateForm: updateBody3
1632
+ });
1633
+ var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob, Effect31.succeed(PbsPruneJob.Provider.of(handlers15)));
1634
+ // src/proxmox/pbs-sync-job.ts
1635
+ import { Resource as Resource20 } from "alchemy";
1636
+ import * as Provider20 from "alchemy/Provider";
1637
+ import * as Effect32 from "effect/Effect";
1638
+
1639
+ // src/proxmox/pbs-sync-job-form.ts
1640
+ var UNSET4 = -1;
1641
+ var UNITS = {
1642
+ "": 1,
1643
+ B: 1,
1644
+ GB: 1e9,
1645
+ GiB: 1073741824,
1646
+ KB: 1000,
1647
+ KiB: 1024,
1648
+ MB: 1e6,
1649
+ MiB: 1048576,
1650
+ TB: 1000000000000,
1651
+ TiB: 1099511627776
1652
+ };
1653
+ var bytes = (value) => {
1654
+ if (typeof value === "number")
1655
+ return value;
1656
+ const raw = text(value).trim();
1657
+ const match = /^([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z]*)$/.exec(raw);
1658
+ if (match === null)
1659
+ return UNSET4;
1660
+ const unit = UNITS[match[2] ?? ""];
1661
+ return unit === undefined ? UNSET4 : Math.round(Number(match[1] ?? "0") * unit);
1662
+ };
1663
+ var filters = (value) => Array.isArray(value) ? value.map((entry) => String(entry)).join(" | ") : text(value);
1664
+ var shape6 = (props) => ({
1665
+ "remote-store": props["remote-store"],
1666
+ "remove-vanished": props["remove-vanished"] === true ? "1" : "0",
1667
+ store: props.store,
1668
+ ...props.comment === undefined ? {} : { comment: props.comment },
1669
+ ...props["max-depth"] === undefined ? {} : { "max-depth": String(props["max-depth"]) },
1670
+ ...props.ns === undefined ? {} : { ns: props.ns },
1671
+ ...props.owner === undefined ? {} : { owner: props.owner },
1672
+ ...props["rate-in"] === undefined ? {} : { "rate-in": String(props["rate-in"]) },
1673
+ ...props.remote === undefined ? {} : { remote: props.remote },
1674
+ ...props["remote-ns"] === undefined ? {} : { "remote-ns": props["remote-ns"] },
1675
+ ...props.schedule === undefined ? {} : { schedule: props.schedule },
1676
+ ...props["verified-only"] === undefined ? {} : { "verified-only": props["verified-only"] ? "1" : "0" },
1677
+ ...props["transfer-last"] === undefined ? {} : { "transfer-last": String(props["transfer-last"]) }
1678
+ });
1679
+ var createBody3 = (props) => ({
1680
+ ...shape6(props),
1681
+ id: props.id,
1682
+ ...props["sync-direction"] === undefined ? {} : { "sync-direction": props["sync-direction"] }
1683
+ });
1684
+ var updateBody4 = (props) => shape6(props);
1685
+ var handlers16 = pveHandlers({
1686
+ attributes: (live, props) => ({
1687
+ comment: text(live["comment"], ""),
1688
+ "group-filter": filters(live["group-filter"]),
1689
+ id: props.id,
1690
+ "max-depth": int(live["max-depth"], UNSET4),
1691
+ ns: text(live["ns"], ""),
1692
+ owner: text(live["owner"], ""),
1693
+ "rate-in": bytes(live["rate-in"]),
1694
+ remote: text(live["remote"], ""),
1695
+ "remote-ns": text(live["remote-ns"], ""),
1696
+ "remote-store": text(live["remote-store"], ""),
1697
+ "remove-vanished": bool(live["remove-vanished"], false),
1698
+ schedule: text(live["schedule"], ""),
1699
+ "verified-only": bool(live["verified-only"], false),
1700
+ store: text(live["store"], ""),
1701
+ "sync-direction": text(live["sync-direction"], "pull"),
1702
+ "transfer-last": int(live["transfer-last"], UNSET4)
1703
+ }),
1704
+ collection: () => "config/sync",
1705
+ createForm: createBody3,
1706
+ matches: (attributes2, props) => attributes2["remove-vanished"] === (props["remove-vanished"] === true) && attributes2.store === props.store && attributes2["remote-store"] === props["remote-store"] && (props.remote === undefined || attributes2.remote === props.remote) && (props.ns === undefined || attributes2.ns === props.ns) && (props["remote-ns"] === undefined || attributes2["remote-ns"] === props["remote-ns"]) && (props.schedule === undefined || attributes2.schedule === props.schedule) && (props.comment === undefined || attributes2.comment === props.comment) && (props.owner === undefined || attributes2.owner === props.owner) && (props["verified-only"] === undefined || attributes2["verified-only"] === props["verified-only"]) && (props["rate-in"] === undefined || attributes2["rate-in"] === props["rate-in"]) && (props["max-depth"] === undefined || attributes2["max-depth"] === props["max-depth"]) && (props["transfer-last"] === undefined || attributes2["transfer-last"] === props["transfer-last"]),
1707
+ path: (props) => `config/sync/${props.id}`,
1708
+ updateForm: updateBody4
1709
+ });
1710
+
1711
+ // src/proxmox/pbs-sync-job.ts
1712
+ var PbsSyncJob = Resource20("Pbs.SyncJob");
1713
+ var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob, Effect32.succeed(PbsSyncJob.Provider.of(handlers16)));
1714
+ // src/proxmox/pbs-verify-job.ts
1715
+ import { Resource as Resource21 } from "alchemy";
1716
+ import * as Provider21 from "alchemy/Provider";
1717
+ import * as Effect33 from "effect/Effect";
1718
+
1719
+ // src/proxmox/pbs-verify-job-form.ts
1720
+ var rechecks = (ignoreVerified, outdatedAfter) => {
1721
+ if (!ignoreVerified)
1722
+ return "every snapshot on every run";
1723
+ if (outdatedAfter < 0)
1724
+ return "never: each snapshot is checked once and then skipped forever";
1725
+ return `once the last check is more than ${String(outdatedAfter)} day(s) old`;
1726
+ };
1727
+ var shape7 = (props) => ({
1728
+ "ignore-verified": props["ignore-verified"] === false ? "0" : "1",
1729
+ store: props.store,
1730
+ ...props.schedule === null ? {} : { schedule: props.schedule },
1731
+ ...props.comment === undefined ? {} : { comment: props.comment },
1732
+ ...props["max-depth"] === undefined ? {} : { "max-depth": String(props["max-depth"]) },
1733
+ ...props.ns === undefined || props.ns === "" ? {} : { ns: props.ns },
1734
+ ...props["outdated-after"] === undefined ? {} : { "outdated-after": String(props["outdated-after"]) }
1735
+ });
1736
+
1737
+ // src/proxmox/pbs-verify-job.ts
1738
+ var PbsVerifyJob = Resource21("Pbs.VerifyJob");
1739
+ var handlers17 = pveHandlers({
1740
+ attributes: (live, props) => {
1741
+ const ignoreVerified = bool(live["ignore-verified"], true);
1742
+ const outdatedAfter = int(live["outdated-after"], -1);
1743
+ return {
1744
+ comment: text(live["comment"], ""),
1745
+ id: props.id,
1746
+ "ignore-verified": ignoreVerified,
1747
+ "max-depth": int(live["max-depth"], -1),
1748
+ ns: text(live["ns"], ""),
1749
+ "outdated-after": outdatedAfter,
1750
+ rechecks: rechecks(ignoreVerified, outdatedAfter),
1751
+ schedule: text(live["schedule"], ""),
1752
+ store: text(live["store"], props.store)
1753
+ };
1754
+ },
1755
+ collection: () => "config/verify",
1756
+ createForm: (props) => ({ ...shape7(props), id: props.id }),
1757
+ matches: (attributes2, props) => attributes2.store === props.store && attributes2.schedule === (props.schedule ?? "") && attributes2["ignore-verified"] === (props["ignore-verified"] !== false) && (props["outdated-after"] === undefined || attributes2["outdated-after"] === props["outdated-after"]) && (props["max-depth"] === undefined || attributes2["max-depth"] === props["max-depth"]) && (props.ns === undefined || attributes2.ns === props.ns) && (props.comment === undefined || attributes2.comment === props.comment),
1758
+ path: (props) => `config/verify/${props.id}`,
1759
+ updateForm: shape7
1760
+ });
1761
+ var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob, Effect33.succeed(PbsVerifyJob.Provider.of(handlers17)));
1762
+ // src/proxmox/pool.ts
1763
+ import { Resource as Resource22 } from "alchemy";
1764
+ import * as Provider22 from "alchemy/Provider";
1765
+ import * as Effect34 from "effect/Effect";
1766
+ var ProxmoxPool = Resource22("Proxmox.Pool");
1767
+ var poolCreateForm = (props) => ({
1768
+ comment: props.comment ?? "",
1769
+ poolid: props.poolid
1770
+ });
1771
+ var poolUpdateForm = (props) => ({
1772
+ comment: props.comment ?? ""
1773
+ });
1774
+ var handlers18 = pveHandlers({
1775
+ attributes: (live, props) => {
1776
+ const row = live;
1777
+ return {
1778
+ comment: typeof row.comment === "string" ? row.comment : "",
1779
+ members: Array.isArray(row.members) ? row.members.length : 0,
1780
+ poolid: props.poolid
1781
+ };
1782
+ },
1783
+ collection: () => "pools",
1784
+ createForm: poolCreateForm,
1785
+ matches: (attributes2, props) => attributes2.comment === (props.comment ?? ""),
1786
+ path: (props) => `pools/${props.poolid}`,
1787
+ updateForm: poolUpdateForm
1788
+ });
1789
+ var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool, Effect34.succeed(ProxmoxPool.Provider.of(handlers18)));
1790
+ // src/proxmox/qemu.ts
1791
+ import { Resource as Resource23 } from "alchemy";
1792
+ import * as Provider23 from "alchemy/Provider";
1793
+ import * as Effect35 from "effect/Effect";
1794
+ var ProxmoxVm = Resource23("Proxmox.Vm");
1795
+ var shape8 = (props) => ({
1796
+ cores: String(props.cores ?? 1),
1797
+ memory: String(props.memory ?? 512),
1798
+ name: props.name ?? `vm${String(props.vmid)}`,
1799
+ onboot: props.onboot === true ? "1" : "0",
1800
+ sockets: String(props.sockets ?? 1),
1801
+ ...props.net0 === undefined ? {} : { net0: props.net0 }
1802
+ });
1803
+ var handlers19 = pveHandlers({
1804
+ attributes: (live, props) => ({
1805
+ cores: num(live["cores"], 1),
1806
+ memory: num(live["memory"], 512),
1807
+ name: typeof live["name"] === "string" ? live["name"] : "",
1808
+ node: props.node,
1809
+ onboot: live["onboot"] === 1 || live["onboot"] === true,
1810
+ sockets: num(live["sockets"], 1),
1811
+ vmid: props.vmid
1812
+ }),
1813
+ collection: (props) => `nodes/${props.node}/qemu`,
1814
+ createForm: (props) => ({ ...shape8(props), vmid: String(props.vmid) }),
1815
+ matches: (attributes2, props) => attributes2.name === (props.name ?? `vm${String(props.vmid)}`) && attributes2.memory === (props.memory ?? 512) && attributes2.cores === (props.cores ?? 1) && attributes2.sockets === (props.sockets ?? 1) && attributes2.onboot === (props.onboot === true),
1816
+ path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
1817
+ updateForm: shape8
1818
+ });
1819
+ var ProxmoxVmProvider = () => Provider23.effect(ProxmoxVm, Effect35.succeed(ProxmoxVm.Provider.of(handlers19)));
1820
+ // src/proxmox/replication-job.ts
1821
+ import { Resource as Resource24 } from "alchemy";
1822
+ import * as Provider24 from "alchemy/Provider";
1823
+ import * as Effect36 from "effect/Effect";
1824
+
1825
+ // src/proxmox/replication-job-form.ts
1826
+ var DEFAULT_SCHEDULE = "*/15";
1827
+ var jobId = (props) => `${String(props.guest)}-${String(props.jobnum)}`;
1828
+ var shape9 = (props) => ({
1829
+ comment: props.comment ?? "",
1830
+ disable: props.disable === true ? "1" : "0",
1831
+ schedule: props.schedule ?? DEFAULT_SCHEDULE,
1832
+ ...props.rate === undefined ? {} : { rate: String(props.rate) }
1833
+ });
1834
+ var createBody4 = (props) => ({
1835
+ ...shape9(props),
1836
+ id: jobId(props),
1837
+ target: props.targetNode,
1838
+ type: "local"
1839
+ });
1840
+ var updateBody5 = (props) => ({
1841
+ ...shape9(props),
1842
+ delete: "remove_job"
1843
+ });
1844
+
1845
+ // src/proxmox/replication-job.ts
1846
+ var ProxmoxReplicationJob = Resource24("Proxmox.ReplicationJob");
1847
+ var rateOf = (value) => {
1848
+ if (typeof value === "number")
1849
+ return value;
1850
+ const parsed = Number.parseFloat(text(value));
1851
+ return Number.isFinite(parsed) ? parsed : 0;
1852
+ };
1853
+ var handlers20 = pveHandlers({
1854
+ attributes: (live, props) => ({
1855
+ comment: text(live["comment"], ""),
1856
+ disable: bool(live["disable"], false),
1857
+ guest: int(live["guest"], props.guest),
1858
+ id: text(live["id"], jobId(props)),
1859
+ jobnum: int(live["jobnum"], props.jobnum),
1860
+ rate: rateOf(live["rate"]),
1861
+ remove_job: text(live["remove_job"], ""),
1862
+ schedule: text(live["schedule"], DEFAULT_SCHEDULE),
1863
+ source: text(live["source"], ""),
1864
+ targetNode: text(live["target"], props.targetNode),
1865
+ type: text(live["type"], "local")
1866
+ }),
1867
+ collection: () => "cluster/replication",
1868
+ createForm: createBody4,
1869
+ matches: (attributes2, props) => attributes2.remove_job === "" && attributes2.schedule === (props.schedule ?? DEFAULT_SCHEDULE) && attributes2.disable === (props.disable === true) && attributes2.comment === (props.comment ?? "") && (props.rate === undefined || attributes2.rate === props.rate),
1870
+ path: (props) => `cluster/replication/${jobId(props)}`,
1871
+ updateForm: updateBody5
1872
+ });
1873
+ var ProxmoxReplicationJobProvider = () => Provider24.effect(ProxmoxReplicationJob, Effect36.succeed(ProxmoxReplicationJob.Provider.of(handlers20)));
1874
+ // src/proxmox/role.ts
1875
+ import { Resource as Resource25 } from "alchemy";
1876
+ import * as Provider25 from "alchemy/Provider";
1877
+ import * as Effect37 from "effect/Effect";
1878
+ var ProxmoxRole = Resource25("Proxmox.Role");
1879
+ var canonical = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
1880
+ var livePrivs = (live) => {
1881
+ const listed = live["privs"];
1882
+ if (typeof listed === "string")
1883
+ return canonical(listed.split(","));
1884
+ return canonical(Object.keys(live).filter((key) => key.includes(".")));
1885
+ };
1886
+ var handlers21 = pveHandlers({
1887
+ attributes: (live, props) => ({ privs: livePrivs(live), roleid: props.roleid }),
1888
+ collection: () => "access/roles",
1889
+ createForm: (props) => ({ privs: canonical(props.privs).join(","), roleid: props.roleid }),
1890
+ matches: (attributes2, props) => canonical(attributes2.privs).join(",") === canonical(props.privs).join(","),
1891
+ path: (props) => `access/roles/${props.roleid}`,
1892
+ updateForm: (props) => ({ privs: canonical(props.privs).join(",") })
1893
+ });
1894
+ var ProxmoxRoleProvider = () => Provider25.effect(ProxmoxRole, Effect37.succeed(ProxmoxRole.Provider.of(handlers21)));
1895
+ // src/proxmox/sdn-apply.ts
1896
+ import { Resource as Resource26 } from "alchemy";
1897
+ import { isResolved as isResolved8 } from "alchemy/Diff";
1898
+ import * as Provider26 from "alchemy/Provider";
1899
+ import * as Effect39 from "effect/Effect";
1900
+
1901
+ // src/proxmox/sdn-apply-read.ts
1902
+ import * as Effect38 from "effect/Effect";
1903
+ var MARKED = [
1904
+ "cluster/sdn/zones",
1905
+ "cluster/sdn/vnets",
1906
+ "cluster/sdn/controllers",
1907
+ "cluster/sdn/prefix-lists",
1908
+ "cluster/sdn/route-maps"
1909
+ ];
1910
+ var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
1911
+ var markedPending = (target) => Effect38.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect38.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect38.catchIf(subsystemAbsent, () => Effect38.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect38.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
1912
+ var fabricStaged = (staged, running) => {
1913
+ if (staged === undefined && running === undefined)
1914
+ return 0;
1915
+ if (staged === undefined || running === undefined)
1916
+ return "inconsistent";
1917
+ return staged === running ? 0 : 1;
1918
+ };
1919
+ var fabricPending = (target) => Effect38.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect38.map(canonical2), Effect38.catchIf(subsystemAbsent, () => Effect38.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect38.flatMap(([staged, running]) => {
1920
+ const verdict = fabricStaged(staged, running);
1921
+ return verdict === "inconsistent" ? Effect38.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect38.succeed(verdict);
1922
+ }));
1923
+ var canonical2 = (value) => JSON.stringify(sortDeep(value));
1924
+ var sortDeep = (value) => {
1925
+ if (Array.isArray(value)) {
1926
+ return value.map(sortDeep).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
1927
+ }
1928
+ if (value !== null && typeof value === "object") {
1929
+ const entries = Object.entries(value).filter(([key]) => key !== "digest").sort(([left], [right]) => left.localeCompare(right));
1930
+ return Object.fromEntries(entries.map(([key, inner]) => [key, sortDeep(inner)]));
1931
+ }
1932
+ return value;
1933
+ };
1934
+ var sdnPendingCount = (target) => Effect38.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect38.map(([marked, fabric]) => marked + fabric));
1935
+
1936
+ // src/proxmox/sdn-apply.ts
1937
+ var ProxmoxSdnApply = Resource26("Proxmox.SdnApply");
1938
+ var read2 = (props) => sdnPendingCount(props.target).pipe(Effect39.map((pending) => ({ pending })));
1939
+ var ProxmoxSdnApplyProvider = () => Provider26.effect(ProxmoxSdnApply, Effect39.succeed(ProxmoxSdnApply.Provider.of({
1940
+ list: () => Effect39.succeed([]),
1941
+ read: Effect39.fn(function* ({ olds }) {
1942
+ return yield* read2(olds);
1943
+ }),
1944
+ diff: Effect39.fn(function* ({ news }) {
1945
+ if (!isResolved8(news))
1946
+ return;
1947
+ const { pending } = yield* read2(news);
1948
+ return pending === 0 ? { action: "noop" } : { action: "update" };
1949
+ }),
1950
+ reconcile: Effect39.fn(function* ({ news }) {
1951
+ const staged = yield* read2(news);
1952
+ if (staged.pending === 0)
1953
+ return staged;
1954
+ yield* pve(news.target, "provision", "PUT", "cluster/sdn");
1955
+ const after = yield* read2(news);
1956
+ if (after.pending !== 0) {
1957
+ return yield* Effect39.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
1958
+ }
1959
+ return after;
1960
+ }),
1961
+ delete: () => Effect39.void
1962
+ })));
1963
+ // src/proxmox/sdn-subnet.ts
1964
+ import { Resource as Resource27 } from "alchemy";
1965
+ import * as Provider27 from "alchemy/Provider";
1966
+ import * as Effect40 from "effect/Effect";
1967
+
1968
+ // src/proxmox/sdn-subnet-form.ts
1969
+ var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
1970
+ var rangeString = (range) => `start-address=${range.start},end-address=${range.end}`;
1971
+ var dhcpRanges = (value) => (Array.isArray(value) ? value : []).map((entry) => propertyString(entry)).filter((entry) => entry !== "").sort().join(";");
1972
+ var declaredRanges = (range) => range === undefined ? "" : dhcpRanges([rangeString(range)]);
1973
+ var unset = (value) => value === undefined || value === "" ? undefined : value;
1974
+ var optional3 = (props) => ({
1975
+ "dhcp-dns-server": unset(props.dhcpDnsServer),
1976
+ "dhcp-range": props.dhcpRange === undefined ? undefined : rangeString(props.dhcpRange),
1977
+ dnszoneprefix: unset(props.dnszoneprefix),
1978
+ gateway: unset(props.gateway)
1979
+ });
1980
+ var body5 = (props) => ({
1981
+ snat: props.snat === true ? "1" : "0",
1982
+ vnet: props.vnet,
1983
+ ...Object.fromEntries(Object.entries(optional3(props)).filter((entry) => entry[1] !== undefined))
1984
+ });
1985
+ var createForm4 = (props) => ({
1986
+ ...body5(props),
1987
+ subnet: props.cidr,
1988
+ type: "subnet"
1989
+ });
1990
+ var updateForm2 = (props) => {
1991
+ const clear = Object.entries(optional3(props)).filter(([, value]) => value === undefined).map(([key]) => key);
1992
+ return withClears(body5(props), clear);
1993
+ };
1994
+
1995
+ // src/proxmox/sdn-subnet.ts
1996
+ var ProxmoxSdnSubnet = Resource27("Proxmox.SdnSubnet");
1997
+ var handlers22 = pveHandlers({
1998
+ attributes: (live, props) => {
1999
+ const liveVnet = text(live["vnet"]);
2000
+ if (liveVnet !== "" && liveVnet !== props.vnet)
2001
+ return;
2002
+ return {
2003
+ cidr: text(live["cidr"]),
2004
+ dhcpDnsServer: text(live["dhcp-dns-server"]),
2005
+ dhcpRange: dhcpRanges(live["dhcp-range"]),
2006
+ dnszoneprefix: text(live["dnszoneprefix"]),
2007
+ gateway: text(live["gateway"]),
2008
+ mask: int(live["mask"], 0),
2009
+ snat: bool(live["snat"]),
2010
+ staged: true,
2011
+ subnetId: subnetId(props),
2012
+ vnet: props.vnet,
2013
+ zone: text(live["zone"])
2014
+ };
2015
+ },
2016
+ collection: (props) => `cluster/sdn/vnets/${props.vnet}/subnets`,
2017
+ createForm: createForm4,
2018
+ matches: (attributes2, props) => attributes2.gateway === (props.gateway ?? "") && attributes2.snat === (props.snat === true) && attributes2.dnszoneprefix === (props.dnszoneprefix ?? "") && attributes2.dhcpDnsServer === (props.dhcpDnsServer ?? "") && attributes2.dhcpRange === declaredRanges(props.dhcpRange),
2019
+ path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
2020
+ updateForm: updateForm2
2021
+ });
2022
+ var ProxmoxSdnSubnetProvider = () => Provider27.effect(ProxmoxSdnSubnet, Effect40.succeed(ProxmoxSdnSubnet.Provider.of(handlers22)));
2023
+ // src/proxmox/sdn-vnet.ts
2024
+ import { Resource as Resource28 } from "alchemy";
2025
+ import * as Provider28 from "alchemy/Provider";
2026
+ import * as Effect41 from "effect/Effect";
2027
+ var ProxmoxSdnVnet = Resource28("Proxmox.SdnVnet");
2028
+ var tagOf = (value) => typeof value === "number" ? value : undefined;
2029
+ var shape10 = (props) => {
2030
+ const alias = props.alias ?? "";
2031
+ return {
2032
+ "isolate-ports": props.isolatePorts === true ? "1" : "0",
2033
+ vlanaware: props.vlanaware === true ? "1" : "0",
2034
+ zone: props.zone,
2035
+ ...alias === "" ? {} : { alias },
2036
+ ...props.tag === undefined ? {} : { tag: String(props.tag) }
2037
+ };
2038
+ };
2039
+ var updateForm3 = (props) => {
2040
+ const unset2 = [
2041
+ ...(props.alias ?? "") === "" ? ["alias"] : [],
2042
+ ...props.tag === undefined ? ["tag"] : []
2043
+ ];
2044
+ return withClears(shape10(props), unset2);
2045
+ };
2046
+ var handlers23 = pveHandlers({
2047
+ readRole: "provision",
2048
+ attributes: (live, props) => ({
2049
+ alias: typeof live["alias"] === "string" ? live["alias"] : "",
2050
+ bridge: props.vnet,
2051
+ isolatePorts: bool(live["isolate-ports"]),
2052
+ tag: tagOf(live["tag"]),
2053
+ vlanaware: bool(live["vlanaware"]),
2054
+ vnet: props.vnet,
2055
+ zone: typeof live["zone"] === "string" ? live["zone"] : ""
2056
+ }),
2057
+ collection: () => "cluster/sdn/vnets",
2058
+ createForm: (props) => ({ ...shape10(props), vnet: props.vnet }),
2059
+ matches: (attributes2, props) => attributes2.zone === props.zone && attributes2.alias === (props.alias ?? "") && attributes2.tag === props.tag && attributes2.vlanaware === (props.vlanaware === true) && attributes2.isolatePorts === (props.isolatePorts === true),
2060
+ path: (props) => `cluster/sdn/vnets/${props.vnet}`,
2061
+ updateForm: updateForm3
2062
+ });
2063
+ var ProxmoxSdnVnetProvider = () => Provider28.effect(ProxmoxSdnVnet, Effect41.succeed(ProxmoxSdnVnet.Provider.of(handlers23)));
2064
+ // src/proxmox/sdn-zone.ts
2065
+ import { Resource as Resource29 } from "alchemy";
2066
+ import * as Provider29 from "alchemy/Provider";
2067
+ import * as Effect42 from "effect/Effect";
2068
+ var ProxmoxSdnZone = Resource29("Proxmox.SdnZone");
2069
+ var sameIpam = (live, declared) => (live === "" ? "pve" : live) === (declared === undefined || declared === "" ? "pve" : declared);
2070
+ var body6 = (props) => ({
2071
+ ...props.bridge === undefined ? {} : { bridge: props.bridge },
2072
+ ...props.dns === undefined ? {} : { dns: props.dns },
2073
+ ...props.dnszone === undefined ? {} : { dnszone: props.dnszone },
2074
+ ...props.ipam === undefined ? {} : { ipam: props.ipam },
2075
+ ...props.mtu === undefined ? {} : { mtu: String(props.mtu) },
2076
+ ...props.nodes === undefined ? {} : { nodes: csv(props.nodes) },
2077
+ ...props.peers === undefined ? {} : { peers: csv(props.peers) },
2078
+ ...props.tag === undefined ? {} : { tag: String(props.tag) },
2079
+ ...props["vrf-vxlan"] === undefined ? {} : { "vrf-vxlan": String(props["vrf-vxlan"]) }
2080
+ });
2081
+ var clearable = {
2082
+ evpn: ["dns", "dnszone", "mtu", "nodes"],
2083
+ qinq: ["dns", "dnszone", "ipam", "mtu", "nodes"],
2084
+ simple: ["dns", "dnszone", "ipam", "mtu", "nodes"],
2085
+ vlan: ["dns", "dnszone", "ipam", "mtu", "nodes"],
2086
+ vxlan: ["dns", "dnszone", "ipam", "mtu", "nodes"]
2087
+ };
2088
+ var handlers24 = pveHandlers({
2089
+ readRole: "provision",
2090
+ attributes: (live, props) => {
2091
+ const liveType = text(live["type"]);
2092
+ if (liveType !== "" && liveType !== props.type)
2093
+ return;
2094
+ return {
2095
+ bridge: text(live["bridge"]),
2096
+ dns: text(live["dns"]),
2097
+ dnszone: text(live["dnszone"]),
2098
+ ipam: text(live["ipam"]) === "" ? "pve" : text(live["ipam"]),
2099
+ mtu: int(live["mtu"], 0),
2100
+ nodes: csv(text(live["nodes"])),
2101
+ peers: csv(text(live["peers"])),
2102
+ staged: true,
2103
+ tag: int(live["tag"], 0),
2104
+ type: props.type,
2105
+ "vrf-vxlan": int(live["vrf-vxlan"], 0),
2106
+ zone: props.zone
2107
+ };
2108
+ },
2109
+ collection: () => "cluster/sdn/zones",
2110
+ createForm: (props) => ({ ...body6(props), type: props.type, zone: props.zone }),
2111
+ matches: (attributes2, props) => attributes2.mtu === (props.mtu ?? 0) && attributes2.nodes === csv(props.nodes) && sameIpam(attributes2.ipam, props.ipam) && attributes2.dns === (props.dns ?? "") && attributes2.dnszone === (props.dnszone ?? "") && attributes2.peers === csv(props.peers) && attributes2["vrf-vxlan"] === (props["vrf-vxlan"] ?? 0) && attributes2.tag === (props.tag ?? 0) && attributes2.bridge === (props.bridge ?? ""),
2112
+ path: (props) => `cluster/sdn/zones/${props.zone}`,
2113
+ updateForm: (props) => {
2114
+ const fields = body6(props);
2115
+ const clear = clearable[props.type].filter((option) => fields[option] === undefined);
2116
+ return withClears(fields, clear);
2117
+ }
2118
+ });
2119
+ var ProxmoxSdnZoneProvider = () => Provider29.effect(ProxmoxSdnZone, Effect42.succeed(ProxmoxSdnZone.Provider.of(handlers24)));
2120
+ // src/proxmox/storage.ts
2121
+ import { Resource as Resource30 } from "alchemy";
2122
+ import * as Provider30 from "alchemy/Provider";
2123
+ import * as Effect43 from "effect/Effect";
2124
+ var ProxmoxStorage = Resource30("Proxmox.Storage", {
2125
+ defaultRemovalPolicy: "retain"
2126
+ });
2127
+ var str2 = (value, fallback = "") => typeof value === "string" ? value : fallback;
2128
+ var field5 = (name, value) => value === undefined ? {} : { [name]: value };
2129
+ var locatorForm = (locator) => ({ ...locator });
2130
+ var tokens = (value) => value.split(",").map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
2131
+ var sameList2 = (declared, live) => declared === undefined || tokens(declared) === tokens(live);
2132
+ var same5 = (declared, live) => declared === undefined || declared === live;
2133
+ var mutable2 = (props) => ({
2134
+ ...field5("comment", props.comment),
2135
+ ...field5("content", props.content),
2136
+ ...field5("disable", flag(props.disable)),
2137
+ ...field5("maxfiles", props.maxfiles === undefined ? undefined : String(props.maxfiles)),
2138
+ ...field5("nodes", props.nodes),
2139
+ ...field5("preallocation", props.preallocation),
2140
+ ...field5("prune-backups", props["prune-backups"]),
2141
+ ...field5("shared", flag(props.shared))
2142
+ });
2143
+ var handlers25 = pveHandlers({
2144
+ readRole: "provision",
2145
+ attributes: (live, props) => ({
2146
+ comment: str2(live["comment"]),
2147
+ content: str2(live["content"]),
2148
+ disable: bool(live["disable"]),
2149
+ maxfiles: typeof live["maxfiles"] === "number" ? live["maxfiles"] : -1,
2150
+ nodes: str2(live["nodes"]),
2151
+ preallocation: str2(live["preallocation"]),
2152
+ "prune-backups": str2(live["prune-backups"]),
2153
+ shared: bool(live["shared"]),
2154
+ storage: props.storage,
2155
+ type: str2(live["type"], props.type)
2156
+ }),
2157
+ collection: () => "storage",
2158
+ createForm: (props) => ({
2159
+ ...locatorForm(props.locator),
2160
+ ...mutable2(props),
2161
+ storage: props.storage,
2162
+ type: props.type
2163
+ }),
2164
+ matches: (attributes2, props) => sameList2(props.content, attributes2.content) && sameList2(props.nodes, attributes2.nodes) && sameList2(props["prune-backups"], attributes2["prune-backups"]) && same5(props.disable, attributes2.disable) && same5(props.shared, attributes2.shared) && same5(props.preallocation, attributes2.preallocation) && same5(props.comment, attributes2.comment) && same5(props.maxfiles, attributes2.maxfiles),
2165
+ path: (props) => `storage/${props.storage}`,
2166
+ updateForm: mutable2
2167
+ });
2168
+ var ProxmoxStorageProvider = () => Provider30.effect(ProxmoxStorage, Effect43.succeed(ProxmoxStorage.Provider.of(handlers25)));
2169
+ // src/proxmox/user.ts
2170
+ import { Resource as Resource31 } from "alchemy";
2171
+ import * as Provider31 from "alchemy/Provider";
2172
+ import * as Effect44 from "effect/Effect";
2173
+ var ProxmoxUser = Resource31("Proxmox.User");
2174
+ var str3 = (value) => typeof value === "string" ? value : "";
2175
+ var int2 = (value) => {
2176
+ const parsed = typeof value === "number" ? value : Number(value ?? 0);
2177
+ return Number.isFinite(parsed) ? parsed : 0;
2178
+ };
2179
+ var groupSet = (value) => {
2180
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
2181
+ const names = raw.map((group) => str3(group).trim()).filter((group) => group !== "");
2182
+ return [...new Set(names)].sort();
2183
+ };
2184
+ var tokenNames = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.keys(value).sort() : [];
2185
+ var shape11 = (props) => ({
2186
+ comment: props.comment ?? "",
2187
+ email: props.email ?? "",
2188
+ enable: props.enable === false ? "0" : "1",
2189
+ expire: String(props.expire ?? 0),
2190
+ firstname: props.firstname ?? "",
2191
+ groups: groupSet(props.groups).join(","),
2192
+ lastname: props.lastname ?? ""
2193
+ });
2194
+ var handlers26 = pveHandlers({
2195
+ attributes: (live, props) => ({
2196
+ comment: str3(live["comment"]),
2197
+ email: str3(live["email"]),
2198
+ enable: bool(live["enable"], true),
2199
+ expire: int2(live["expire"]),
2200
+ firstname: str3(live["firstname"]),
2201
+ groups: groupSet(live["groups"]),
2202
+ lastname: str3(live["lastname"]),
2203
+ tokens: tokenNames(live["tokens"]),
2204
+ userid: props.userid
2205
+ }),
2206
+ collection: () => "access/users",
2207
+ createForm: (props) => ({ ...shape11(props), userid: props.userid }),
2208
+ matches: (attributes2, props) => attributes2.comment === (props.comment ?? "") && attributes2.email === (props.email ?? "") && attributes2.enable === (props.enable !== false) && attributes2.expire === (props.expire ?? 0) && attributes2.firstname === (props.firstname ?? "") && attributes2.lastname === (props.lastname ?? "") && attributes2.groups.join(",") === groupSet(props.groups).join(","),
2209
+ path: (props) => `access/users/${props.userid}`,
2210
+ updateForm: shape11
2211
+ });
2212
+ var ProxmoxUserProvider = () => Provider31.effect(ProxmoxUser, Effect44.succeed(ProxmoxUser.Provider.of(handlers26)));
2213
+ // src/proxmox/zfs-pool.ts
2214
+ import { Resource as Resource32 } from "alchemy";
2215
+ import * as Provider32 from "alchemy/Provider";
2216
+ import * as Effect46 from "effect/Effect";
2217
+
2218
+ // src/proxmox/zfs-pool-write.ts
2219
+ import * as Effect45 from "effect/Effect";
2220
+ var createForm5 = (props) => ({
2221
+ devices: props.devices.join(","),
2222
+ name: props.name,
2223
+ raidlevel: props.raidlevel,
2224
+ ...props.ashift === undefined ? {} : { ashift: String(props.ashift) },
2225
+ ...props.compression === undefined ? {} : { compression: props.compression },
2226
+ ...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
2227
+ });
2228
+ var SETTLE_ATTEMPTS = 30;
2229
+ var createPool = (props, collection2, path, read3) => Effect45.gen(function* () {
2230
+ const existing = yield* read3(props);
2231
+ if (existing !== undefined)
2232
+ return existing;
2233
+ const upid = yield* pve(props.target, "provision", "POST", collection2, createForm5(props));
2234
+ const settle4 = (attempts) => Effect45.gen(function* () {
2235
+ const live = yield* read3(props);
2236
+ if (live !== undefined || attempts <= 0)
2237
+ return live;
2238
+ yield* Effect45.sleep("2 seconds");
2239
+ return yield* settle4(attempts - 1);
2240
+ });
2241
+ const after = yield* settle4(SETTLE_ATTEMPTS);
2242
+ if (after === undefined) {
2243
+ return yield* Effect45.die(new Error(`${path}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
2244
+ }
2245
+ return after;
2246
+ });
2247
+ var destroyPool = (olds, path) => pve(olds.target, "provision", "DELETE", path);
2248
+
2249
+ // src/proxmox/zfs-pool.ts
2250
+ var ProxmoxZfsPool = Resource32("Proxmox.ZfsPool", {
2251
+ defaultRemovalPolicy: "retain"
2252
+ });
2253
+ var leafDevices = (children) => Array.isArray(children) ? children.flatMap((entry) => {
2254
+ const vdev = entry;
2255
+ const nested = leafDevices(vdev.children);
2256
+ return nested.length > 0 ? nested : [text(vdev.name)].filter((name) => name !== "");
2257
+ }) : [];
2258
+ var spec2 = {
2259
+ attributes: (live, props) => {
2260
+ if (text(live["name"]) === "")
2261
+ return;
2262
+ return {
2263
+ devices: leafDevices(live["children"]).join(","),
2264
+ errors: text(live["errors"], "unknown"),
2265
+ name: props.name,
2266
+ node: props.node,
2267
+ state: text(live["state"], "unknown")
2268
+ };
2269
+ },
2270
+ collection: (props) => `nodes/${props.node}/disks/zfs`,
2271
+ createForm: createForm5,
2272
+ matches: () => true,
2273
+ path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
2274
+ };
2275
+ var ops5 = pveOperations(spec2);
2276
+ var handlers27 = {
2277
+ ...pveHandlers(spec2),
2278
+ delete: Effect46.fn(function* ({ olds }) {
2279
+ yield* destroyPool(olds, spec2.path(olds));
2280
+ }),
2281
+ reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops5.read)
2282
+ };
2283
+ var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool, Effect46.succeed(ProxmoxZfsPool.Provider.of(handlers27)));
2284
+ export {
2285
+ PbsDatastoreProvider,
2286
+ PbsPruneJobProvider,
2287
+ PbsSyncJobProvider,
2288
+ PbsVerifyJobProvider,
2289
+ ProxmoxAclProvider,
2290
+ ProxmoxApiTokenProvider,
2291
+ ProxmoxBackupJobProvider,
2292
+ ProxmoxCephDaemonProvider,
2293
+ ProxmoxCephFlagProvider,
2294
+ ProxmoxCephFsProvider,
2295
+ ProxmoxCephOsdProvider,
2296
+ ProxmoxCephPool,
2297
+ ProxmoxCephPoolProvider,
2298
+ ProxmoxFirewallAliasProvider,
2299
+ ProxmoxGroupProvider,
2300
+ ProxmoxHaResourceProvider,
2301
+ ProxmoxHaRuleProvider,
2302
+ ProxmoxLxcProvider,
2303
+ ProxmoxMetricServerProvider,
2304
+ ProxmoxNetworkApplyProvider,
2305
+ ProxmoxNodeNetworkProvider,
2306
+ ProxmoxNotificationTargetProvider,
2307
+ ProxmoxPool,
2308
+ ProxmoxPoolProvider,
2309
+ ProxmoxReplicationJobProvider,
2310
+ ProxmoxRoleProvider,
2311
+ ProxmoxSdnApplyProvider,
2312
+ ProxmoxSdnSubnetProvider,
2313
+ ProxmoxSdnVnetProvider,
2314
+ ProxmoxSdnZoneProvider,
2315
+ ProxmoxStorageProvider,
2316
+ ProxmoxUserProvider,
2317
+ ProxmoxVmProvider,
2318
+ ProxmoxZfsPoolProvider
2319
+ };
2320
+
2321
+ //# debugId=DDFA46749A442EED64756E2164756E21
2322
+ //# sourceMappingURL=index.js.map