@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,234 @@
1
+ import type { SyncJobAttributes, SyncJobProps } from './pbs-sync-job.ts';
2
+ /**
3
+ * A sync job on the wire: how a PBS answer becomes comparable values, how a declaration becomes a
4
+ * create or an update, and — because that question is downstream of this one — when it has changed.
5
+ *
6
+ * ★ SPLIT OUT OF pbs-sync-job.ts FOR THE 250-LINE CAP; the ★ SEAM there says why `matches` came
7
+ * with the wire instead of staying with the props. The `import type` back is a cycle on paper
8
+ * only — erased before anything runs, so the runtime arrow points one way, resource to wire.
9
+ *
10
+ * ★ IT USES THE SHARED PVE CLIENT, NOT A PBS ONE. An earlier draft of this file was written
11
+ * against a `./pbs-client.ts` that never existed, and spelled out what it would have to export.
12
+ * That module is not needed: PBS speaks the same `/api2/json` paths and the same `{"data": …}`
13
+ * envelope, its OpenBao mount vends the same `{token_id, secret}`, and the only difference is
14
+ * the authorization header — so `PveTarget` carries a `scheme` and `authorization()` spells
15
+ * `PBSAPIToken=<id>:<secret>` for `'pbs'`. `pveHandlers` and `pveOperations` work unchanged.
16
+ * The one PBS-specific thing that survived is the FORM TYPE: PBS decodes a multi-valued field
17
+ * from repeated keys, and `PveForm` in client.ts admits a list for exactly that reason.
18
+
19
+ */
20
+ import { pveHandlers } from './resource.ts';
21
+ import { bool, int, text } from './values.ts';
22
+
23
+ /**
24
+ * The marker for "PBS has no value for this number".
25
+ *
26
+ * ⚠️ `-1` RATHER THAN `0`, AND metric-server.ts's `UNSET = -1` IS THE PRECEDENT. `max-depth` has a
27
+ * legal value of 0 — sync this namespace and no child of it — which is a completely different
28
+ * instruction from the absent field, where PBS recurses as deep as it is allowed. Using 0 as the
29
+ * marker would make "no recursion" and "full recursion" the same attribute. `rate-in` and
30
+ * `transfer-last` take the same marker for uniformity rather than because 0 is legal for them:
31
+ * depending on a schema minimum I could not measure is exactly the kind of guess that becomes a
32
+ * forever-diff.
33
+ */
34
+ export const UNSET = -1;
35
+
36
+ /** ⚠️ BINARY AND DECIMAL BOTH, because PBS's HumanByte can print either and parses both. */
37
+ const UNITS: Record<string, number> = {
38
+ '': 1,
39
+ B: 1,
40
+ GB: 1e9,
41
+ GiB: 1073741824,
42
+ KB: 1e3,
43
+ KiB: 1024,
44
+ MB: 1e6,
45
+ MiB: 1048576,
46
+ TB: 1e12,
47
+ TiB: 1099511627776,
48
+ };
49
+
50
+ /**
51
+ * A PBS rate limit off the wire, as a plain byte count.
52
+ *
53
+ * ⛔ THE VALUE GOES OUT AS A NUMBER AND COMES BACK AS PROSE. PBS types `rate-in` as a `HumanByte`,
54
+ * which parses `10485760` on the way in and prints something like `10.00 MiB` on the way out, so
55
+ * the two sides of a comparison are never the same string. Flattening both to bytes is what makes
56
+ * the field comparable at all. REASONED from the type's parse/print asymmetry, NOT measured —
57
+ * there was no credential to round-trip one with.
58
+ * ⚠️ IT ROUNDS, AND A RATE THAT DOES NOT DIVIDE EVENLY WILL NEVER SETTLE. `10.00 MiB` carries two
59
+ * decimals, so a declared 10485761 reads back as 10485760, `matches` reports an update, the PUT
60
+ * writes the same rounded value, and the next plan reports it again — forever. Declare a whole
61
+ * multiple of a binary unit. This is the likeliest forever-diff in the family and the first thing
62
+ * to check on the first plan against a live PBS.
63
+ * ⚠️ AN UNPARSEABLE ANSWER READS AS `UNSET`, which for a DECLARED rate means "not equal" and so an
64
+ * update rather than a silent noop. Wrong-but-loud is the right way round here.
65
+ */
66
+ export const bytes = (value: unknown): number => {
67
+ if (typeof value === 'number') return value;
68
+ const raw = text(value).trim();
69
+ const match = /^([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z]*)$/.exec(raw);
70
+ if (match === null) return UNSET;
71
+ const unit = UNITS[match[2] ?? ''];
72
+ return unit === undefined ? UNSET : Math.round(Number(match[1] ?? '0') * unit);
73
+ };
74
+
75
+ /**
76
+ * The live group filters, joined for a human.
77
+ *
78
+ * ⚠️ DISPLAY ONLY — NEVER SENT, NEVER COMPARED. The ⛔ on the `group-filter` prop says why the field
79
+ * is not declarable; this exists so that a plan against an adopted job SHOWS the filters this
80
+ * resource is deliberately leaving alone, rather than making them invisible.
81
+ * ⚠️ THE SEPARATOR IS ` | `, NOT A COMMA: a `regex:` filter may contain either, so no join here is
82
+ * unambiguous — and a comma is the one that would invite somebody to parse this back.
83
+ */
84
+ export const filters = (value: unknown): string =>
85
+ Array.isArray(value) ? value.map((entry: unknown) => String(entry)).join(' | ') : text(value);
86
+
87
+ /**
88
+ * The half of a declaration that create and update both send.
89
+ *
90
+ * ⛔ `remove-vanished` IS ALWAYS SENT, AT ITS DEFAULT INCLUDED, and that is the one field here that
91
+ * breaks the "undeclared is unmanaged" rule on purpose. It is always COMPARED (see `matches`), and
92
+ * backup-job.ts records why the two must agree: a field compared against a default but not sent as
93
+ * that default is an update reported on every plan that the PUT never performs. `0` is the
94
+ * direction that does not delete; the prop says why that is still not a free edit.
95
+ * ⚠️ THE TERNARY IS NOT A REFUSAL TO REUSE values.ts's `flag()`; IT IS WHAT `flag()` IS DOCUMENTED
96
+ * NOT TO DO. That coercion answers `undefined` for an undeclared boolean precisely so an omitted
97
+ * field is never sent — it is the tool for "send it only if declared", and this is the one field
98
+ * that must be sent ALWAYS. Calling it here types as `string | undefined` and tsc rejects the
99
+ * form (MEASURED: TS2322, "Type 'undefined' is not assignable to type 'string'"), which is the
100
+ * type system saying the same thing. replication-job-form.ts writes its always-sent `disable` the
101
+ * same way, for the same reason.
102
+ * ⚠️ `1`/`0` RATHER THAN `true`/`false`: PBS's boolean schema accepts both spellings (DOCUMENTED),
103
+ * and one spelling shared with the PVE families is one fewer thing to keep in step.
104
+ * ⚠️ EVERY OTHER FIELD IS SPREAD IN ONLY WHEN DECLARED. An undeclared key is not sent, so PBS leaves
105
+ * it exactly as it is — which is what makes adopting a hand-made job non-destructive.
106
+ */
107
+ const shape = (props: SyncJobProps) => ({
108
+ 'remote-store': props['remote-store'],
109
+ 'remove-vanished': props['remove-vanished'] === true ? '1' : '0',
110
+ store: props.store,
111
+ ...(props.comment === undefined ? {} : { comment: props.comment }),
112
+ ...(props['max-depth'] === undefined ? {} : { 'max-depth': String(props['max-depth']) }),
113
+ ...(props.ns === undefined ? {} : { ns: props.ns }),
114
+ ...(props.owner === undefined ? {} : { owner: props.owner }),
115
+ ...(props['rate-in'] === undefined ? {} : { 'rate-in': String(props['rate-in']) }),
116
+ ...(props.remote === undefined ? {} : { remote: props.remote }),
117
+ ...(props['remote-ns'] === undefined ? {} : { 'remote-ns': props['remote-ns'] }),
118
+ ...(props.schedule === undefined ? {} : { schedule: props.schedule }),
119
+ ...(props['verified-only'] === undefined
120
+ ? {}
121
+ : { 'verified-only': props['verified-only'] ? '1' : '0' }),
122
+ ...(props['transfer-last'] === undefined
123
+ ? {}
124
+ : { 'transfer-last': String(props['transfer-last']) }),
125
+ });
126
+
127
+ /**
128
+ * ⛔ `id` IS SENT AND IS NOT OPTIONAL. PBS's create takes it rather than inventing one, so this
129
+ * family avoids backup-job.ts's worst failure by construction — but only while the id is here.
130
+ * A POST without one would be refused rather than silently duplicating the job, which is the
131
+ * better failure, and it is still not a reason to leave it out.
132
+ * ⛔ `sync-direction` IS THE ONE FIELD SENT ONLY ON CREATE. It decides which side `remove-vanished`
133
+ * deletes from, it is not compared (see `matches`), and on a PBS old enough to have no push
134
+ * support it is an unknown parameter — so sending it on every update would turn every deploy
135
+ * against such a server into a 400 over a field nobody was changing.
136
+ */
137
+ export const createBody = (props: SyncJobProps): Record<string, string> => ({
138
+ ...shape(props),
139
+ id: props.id,
140
+ ...(props['sync-direction'] === undefined ? {} : { 'sync-direction': props['sync-direction'] }),
141
+ });
142
+
143
+ /**
144
+ * ⚠️ THE UPDATE IS THE SHARED HALF AND NOTHING ELSE — no `id` (it is the path), no
145
+ * `sync-direction`, no `delete` list. The factory only PUTs when `matches` is false and the form
146
+ * is non-empty, and this form is never empty because `remove-vanished` and the two store names
147
+ * are always present. That is fine here: reconcile reaches this line only when something really
148
+ * does differ, and re-sending the two store names it just compared is a write of what is already
149
+ * there rather than a change.
150
+ */
151
+ export const updateBody = (props: SyncJobProps): Record<string, string> => shape(props);
152
+
153
+ export const handlers = pveHandlers<SyncJobProps, SyncJobAttributes>({
154
+ /**
155
+ * ⛔ NO "IS IT REALLY THERE" GUARD, for backup-job.ts's reason: absence is the API declining to
156
+ * answer, which the factory's `read` already handles, not a key missing from an answer that did
157
+ * arrive. Every fallback below is PBS's documented default rather than one picked for
158
+ * convenience, so the answer does not depend on whether a release echoes a key it never wrote.
159
+ * ⛔ EXCEPT `store` AND `remote-store`, WHICH FALL BACK TO `''` RATHER THAN TO THE DECLARED VALUE.
160
+ * They are the only required fields `matches` COMPARES, and a props-shaped fallback would make a
161
+ * malformed answer compare EQUAL — drift that hides itself, where `''` can only be loud. `id`
162
+ * keeps its props fallback: it is the path the object was read by, and is never compared.
163
+ * ⚠️ `digest` IS DELIBERATELY NOT AN ATTRIBUTE. PBS returns one for `sync.cfg` as a FILE, so
164
+ * keeping it would rewrite this resource's state whenever an unrelated sync job was edited —
165
+ * churn that reads like drift. ha-resource.ts hit exactly that on PVE.
166
+ * ⚠️ NEITHER IS THE RUN STATUS. `last-run-state`, `last-run-upid`, `last-run-endtime` and
167
+ * `next-run` come from the LIST endpoint, not from `GET /config/sync/{id}` which this reads.
168
+ * A plan whose output changes every time the job runs is a plan people stop reading.
169
+ */
170
+ attributes: (live, props) => ({
171
+ comment: text(live['comment'], ''),
172
+ 'group-filter': filters(live['group-filter']),
173
+ id: props.id,
174
+ 'max-depth': int(live['max-depth'], UNSET),
175
+ ns: text(live['ns'], ''),
176
+ owner: text(live['owner'], ''),
177
+ 'rate-in': bytes(live['rate-in']),
178
+ remote: text(live['remote'], ''),
179
+ 'remote-ns': text(live['remote-ns'], ''),
180
+ 'remote-store': text(live['remote-store'], ''),
181
+ // ⚠️ PBS answers with a JSON `true`/`false` where PVE answers `1`/`0`; `bool` takes both, and
182
+ // takes the string `'false'` correctly too if some release spells it that way.
183
+ 'remove-vanished': bool(live['remove-vanished'], false),
184
+ schedule: text(live['schedule'], ''),
185
+ // ⚠️ ABSENT MEANS FALSE, the PBS-side default — so an undeclared prop and a job that never had
186
+ // the field compare equal, and adopting one does not plan an edit.
187
+ 'verified-only': bool(live['verified-only'], false),
188
+ store: text(live['store'], ''),
189
+ 'sync-direction': text(live['sync-direction'], 'pull'),
190
+ 'transfer-last': int(live['transfer-last'], UNSET),
191
+ }),
192
+ collection: () => 'config/sync',
193
+ createForm: createBody,
194
+ /**
195
+ * ⛔ ONLY FIELDS A WRITE CAN ACTUALLY SET ARE COMPARED, and on PBS that list is REASONED rather
196
+ * than measured: the published update schema for `PUT /config/sync/{id}` takes every property
197
+ * of the job except its `id`. If a release turns out to refuse one of these, the comparison
198
+ * becomes an update the PUT cannot perform — reported on every plan, forever. That is what the
199
+ * first plan against a live PBS is for.
200
+ * ⚠️ `remove-vanished` IS COMPARED UNCONDITIONALLY — undeclared means false. Every other optional
201
+ * field reads "not declared, or equal": undeclared is UNMANAGED, so a schedule, comment, owner,
202
+ * namespace or limit somebody set by hand survives adoption untouched. The destructive one does
203
+ * not get that courtesy, and the price is that adopting a job with it switched on is a real
204
+ * edit, announced only as "update" — toward not deleting, and toward a datastore that grows.
205
+ * ⚠️ `store` AND `remote-store` ARE COMPARED UNCONDITIONALLY because PBS requires them at create,
206
+ * so there is no undeclared case to protect. `remote` is NOT: an absent `remote` on an existing
207
+ * job would otherwise plan an update clearing it, and `remote=` is not a value PBS's id schema
208
+ * accepts — a failing PUT on every deploy, over a field the declaration never mentioned.
209
+ * ⚠️ `id`, `group-filter` AND `sync-direction` ARE OUT. The first is the path, a change to which
210
+ * is a different object; the other two are argued on their props in pbs-sync-job.ts.
211
+ */
212
+ matches: (attributes, props) =>
213
+ attributes['remove-vanished'] === (props['remove-vanished'] === true) &&
214
+ attributes.store === props.store &&
215
+ attributes['remote-store'] === props['remote-store'] &&
216
+ (props.remote === undefined || attributes.remote === props.remote) &&
217
+ (props.ns === undefined || attributes.ns === props.ns) &&
218
+ (props['remote-ns'] === undefined || attributes['remote-ns'] === props['remote-ns']) &&
219
+ (props.schedule === undefined || attributes.schedule === props.schedule) &&
220
+ (props.comment === undefined || attributes.comment === props.comment) &&
221
+ (props.owner === undefined || attributes.owner === props.owner) &&
222
+ // ⚠️ NOT COMPARED UNCONDITIONALLY, unlike `remove-vanished`. Turning `verified-only` ON is the
223
+ // safe direction (sync less), so undeclared stays UNMANAGED like every other optional field;
224
+ // `remove-vanished` gets the unconditional treatment because its unsafe direction deletes.
225
+ (props['verified-only'] === undefined ||
226
+ attributes['verified-only'] === props['verified-only']) &&
227
+ (props['rate-in'] === undefined || attributes['rate-in'] === props['rate-in']) &&
228
+ (props['max-depth'] === undefined || attributes['max-depth'] === props['max-depth']) &&
229
+ (props['transfer-last'] === undefined ||
230
+ attributes['transfer-last'] === props['transfer-last']),
231
+ /** ⚠️ A Proxmox safe id has no `/` or `:` in it, so there is nothing here to encode. */
232
+ path: (props) => `config/sync/${props.id}`,
233
+ updateForm: updateBody,
234
+ });
@@ -0,0 +1,258 @@
1
+ /**
2
+ * `Pbs.SyncJob` — one section of Proxmox Backup Server's `sync.cfg`, declared: a remote datastore's
3
+ * snapshots pulled into a local one on a schedule.
4
+ *
5
+ * ★ IT USES THE SHARED PVE CLIENT, AND THE TYPES STOP THE TWO HOSTS BEING SWAPPED. An earlier
6
+ * draft imported a `./pbs-client.ts` that never existed, arguing that reusing the PVE machinery
7
+ * would COMPILE and that this was the danger — a PBS host accepted wherever a PVE one belongs,
8
+ * every call 401ing, `read` folding that into "absent". The argument was right and the fix is
9
+ * the discriminant rather than a second client: `scheme` is REQUIRED on a target, so a PBS host
10
+ * is not assignable to a PVE resource or the reverse, and the mistake is a compile error. See
11
+ * the ⛔ on `scheme` in credentials.ts.
12
+ *
13
+ * ⛔ THE CREDENTIAL IS A PBS TOKEN AND IT DOES NOT EXIST YET; NO MOUNT NAME IS INVENTED IN THIS
14
+ * FILE. PBS wants `user@realm!tokenname` plus a UUID secret, issued by PBS — a PVE token is not
15
+ * one, whatever it is spelled like. MEASURED from this machine on 2026-09-13: the `claude-code`
16
+ * approle cannot enumerate mounts (`bao secrets list` -> 403) and
17
+ * `bao token capabilities kv/infra/proxmox/pbs` -> `deny`, so I could not confirm from here what
18
+ * does or does not exist. What credentials.ts records is that every PBS token on the kv shelf is
19
+ * READ-ONLY (`Audit`, as `monitoring@pve`) and STATIC — too narrow to create a sync job, and
20
+ * exactly the kind of credential this provider exists to avoid writing with.
21
+ * ★ SO THE MOUNT IS A `PbsTarget.mount` THE STACK SUPPLIES, and somebody has to build it first:
22
+ * a dynamic PBS mount with `read` and `provision` roles mirroring `proxmox-tb4`. Until it
23
+ * exists this family can be declared and cannot be deployed. `permission denied` means the
24
+ * grant is missing — say which mount and role you needed; do not fall back to the shelf token.
25
+ *
26
+ * ⚠️ WHAT IS MEASURED AND WHAT IS NOT. MEASURED on 2026-09-13 from this machine, read-only and
27
+ * under STRICT TLS (no `-k`; curl reports `ssl_verify_result` 0):
28
+ * `GET https://pbs.example.com:8007/api2/json/ping` -> HTTP 200 `{"data":{"pong":true}}`. So
29
+ * the host is real, its certificate validates, and PBS WRAPS ITS ANSWER IN THE SAME `{"data": …}`
30
+ * ENVELOPE AS PVE — which is what lets the PVE-shaped client body be reused at all.
31
+ * ⛔ EVERYTHING ELSE BELOW IS DOCUMENTED, FROM PBS'S PUBLISHED API SCHEMA, AND NOT MEASURED — the
32
+ * field list, the parameter spellings, which of them a PUT accepts, the privileges. Without a
33
+ * credential every other endpoint answers 401, and PBS answers 401 BEFORE it routes:
34
+ * `/api2/json/config/nosuchthing` -> 401, not 404 (measured). So the 401 from
35
+ * `/api2/json/config/sync` is not even evidence that the endpoint exists. THE FIRST PLAN
36
+ * AGAINST A LIVE PBS IS THE MEASUREMENT. Read it; do not deploy it unseen.
37
+ * ⚠️ AN ERROR BODY IS NOT WRAPPED: the 401 body is the bare text `authentication failed`, not
38
+ * JSON. `pve()` checks `response.ok` before it parses, so a `pbs()` copied from it is safe —
39
+ * provided the check stays in that order.
40
+ *
41
+ * ⚠️ `Proxmox.BackupJob` AND `Pbs.SyncJob` ARE NOT THE SAME FAMILY AND THE NAMES NEARLY COLLIDE.
42
+ * The first is a PVE vzdump schedule; this is a Proxmox Backup SERVER sync job, on a different
43
+ * host. The `Pbs.` prefix is what keeps them apart in a plan, so it is not shortened.
44
+ *
45
+ * ★ THE SEAM, SINCE IT IS NOT THE USUAL ONE. This file answers "what a sync job is and what it
46
+ * costs to declare one": the props, the attributes, the resource. pbs-sync-job-form.ts answers
47
+ * "how it meets PBS": the coercions, the create and update bodies, and — the part that normally
48
+ * lives here — `matches`. `matches` went with the wire because it is DOWNSTREAM of the wire:
49
+ * ha-rule.ts's rule is that only a field a write can actually set may be compared, so every line
50
+ * of that predicate is an argument about what the PUT accepts. Splitting it from the forms would
51
+ * have put the claim and its evidence in different files. `Provider.of(handlers)` stays HERE, for
52
+ * resource.ts's ⛔: it is the one place the handlers are checked against the concrete resource
53
+ * types, and a cast there would be a lie about whether they match.
54
+ */
55
+ import { Resource } from 'alchemy';
56
+ import * as Provider from 'alchemy/Provider';
57
+ import * as Effect from 'effect/Effect';
58
+ import { handlers } from './pbs-sync-job-form.ts';
59
+ import type { PveRequirements } from './resource.ts';
60
+ import type { WithPbsTarget } from './resource.ts';
61
+
62
+ /**
63
+ * ⚠️ THE PROPS ARE PBS'S OWN PARAMETER NAMES, DASHES INCLUDED, for backup-job.ts's reason: the form
64
+ * builder is then a copy rather than a translation table, and a translation table is one more
65
+ * place for a key to be renamed and silently never sent.
66
+ */
67
+ export interface SyncJobProps extends WithPbsTarget {
68
+ /**
69
+ * ⛔ PBS'S SECTION ID AND THIS RESOURCE'S KEY, REQUIRED. Unlike a PVE vzdump job PBS does not
70
+ * autogenerate one — `POST /config/sync` takes `id` — so the backup-job.ts failure of creating
71
+ * a second job beside the first on every reconcile cannot happen here. It is still required
72
+ * rather than derived: to adopt a job that exists, declare the id it already has.
73
+ * ⚠️ A PROXMOX SAFE ID — letters, digits, `_`, `.`, `-`, 3 to 32 characters — so there is nothing
74
+ * in it for `path` below to encode.
75
+ */
76
+ id: string;
77
+ /** The LOCAL datastore the snapshots land in. Required by PBS at create. */
78
+ store: string;
79
+ /** Local namespace, e.g. `tenant/db`. Unset means the root namespace, which PBS spells `''`. */
80
+ ns?: string;
81
+ /**
82
+ * ⛔ A REMOTE BY NAME ONLY. THIS RESOURCE NEVER MODELS THE CREDENTIAL BEHIND IT, and a
83
+ * `Pbs.Remote` family is OUT OF SCOPE for this package rather than merely unwritten:
84
+ * `POST /config/remote` requires `password`, and Alchemy persists props and attributes to its
85
+ * state store UNENCRYPTED (StateEncoding.ts tags Redacted values rather than encrypting them),
86
+ * which here is the `alchemy` Postgres that pg-backup.sh dumps nightly to CT100. A remote's
87
+ * password in a prop is that password in four places, for months. The only Remote resource that
88
+ * could exist would refuse `password` the way storage.ts refuses it — as a `never` — and could
89
+ * therefore never CREATE a remote, which is a resource that cannot do what its name promises.
90
+ * Create remotes out of band (`proxmox-backup-manager remote create`) and name one here.
91
+ * ⚠️ The fingerprint is not the problem: a certificate fingerprint is public. The password is.
92
+ * ⚠️ UNSET MEANS A LOCAL SYNC — datastore to datastore on this same PBS — which is a real and
93
+ * supported mode, not an omission. See `matches`: an unset `remote` is NOT compared, so leaving
94
+ * it out does not claim a job with a remote is local; it claims nothing at all.
95
+ */
96
+ remote?: string;
97
+ /** The datastore on that remote. Required by PBS at create. */
98
+ 'remote-store': string;
99
+ /** Namespace on the remote. Unset means its root namespace. */
100
+ 'remote-ns'?: string;
101
+ /**
102
+ * A PBS calendar event — `daily`, `hourly`, `mon..fri 02:30`. Unset means the job runs ONLY when
103
+ * somebody starts it by hand, which is a sync that is not happening; declare one.
104
+ * ⚠️ THIS COMPARES STRINGS, NOT MEANINGS. A calendar event has many equivalent spellings and PBS
105
+ * stores the one it was handed, so declare the spelling the server echoes back.
106
+ */
107
+ schedule?: string;
108
+ /** Single line only — PBS's comment schema rejects a newline. */
109
+ comment?: string;
110
+ /**
111
+ * ⛔ THIS DELETES SNAPSHOTS. NOT "prunes", NOT "tidies" — DELETES, on a schedule, driven by
112
+ * ANOTHER SERVER'S STATE. On a PULL job (the default), every backup group in the local `store`
113
+ * and `ns` that is no longer on the remote is REMOVED LOCALLY the next time the job runs. The
114
+ * remote decides; this side obeys. Somebody pruning hard on the remote, a remote datastore
115
+ * restored from an older copy, a remote namespace renamed, or a remote that has simply lost
116
+ * data — each of those becomes deletion HERE, and the local copy is usually the copy that was
117
+ * supposed to survive the remote.
118
+ * ⛔ AND ON A PUSH JOB IT DELETES ON THE REMOTE INSTEAD — same flag, opposite victim. Read
119
+ * `sync-direction` before reading this one.
120
+ * ⚠️ IT IS COMPARED AND SENT EVEN WHEN UNDECLARED, unlike every other optional field here, and
121
+ * that is deliberate: undeclared means `false`, so adopting a job somebody had switched this on
122
+ * for plans as an UPDATE that switches it off — a real edit, and NOT a free one. It is the
123
+ * direction that does not delete, but a job that was BOUNDING a datastore now grows it, and a
124
+ * full PBS datastore fails every backup on it. The plan says only "update"; read the live job.
125
+ */
126
+ 'remove-vanished'?: boolean;
127
+ /**
128
+ * The PBS Authid that owns the synced groups, e.g. `sync@pbs` or `sync@pbs!job1`. Unset leaves
129
+ * the config without one, which PBS reads as `root@pam` AT RUNTIME — so unset and `root@pam` are
130
+ * not the same value in the config even though they behave alike. Owning a group is what lets an
131
+ * Authid prune or delete it, so changing this is a permissions change, not a label.
132
+ * ⚠️ AN AUTHID IS AN IDENTIFIER, NEVER A SECRET — it may name an API token (`user@realm!name`),
133
+ * and the token's VALUE is the secret. This resource stores the name and never the value.
134
+ */
135
+ owner?: string;
136
+ /**
137
+ * Incoming rate limit in BYTES PER SECOND. Unset means unlimited.
138
+ * ⚠️ DECLARED IN BYTES, STORED AS A HUMAN STRING. PBS types this as a `HumanByte`, so a written
139
+ * `10485760` comes back as something like `10.00 MiB`; `bytes()` in the form file parses both
140
+ * ends back to a byte count so the two can be compared at all. REASONED from the type, NOT
141
+ * measured — I had no credential to round-trip one.
142
+ * ⛔ DECLARE A VALUE PBS CAN PRINT EXACTLY — a whole multiple of a binary unit. A rate that
143
+ * rounds in the printed form (10485761) reads back as the rounded value, never equals what was
144
+ * declared, and becomes an update reported on every plan forever. This is the single most
145
+ * likely forever-diff in this file; it is the first thing to check on the first plan.
146
+ * ⚠️ `burst-in`, `rate-out` AND `burst-out` ARE NOT MODELLED. PBS's rate limit is four fields
147
+ * flattened into the job; this resource manages one and leaves the others alone, which is safe
148
+ * because PBS's update assigns only the parameters it was sent.
149
+ */
150
+ 'rate-in'?: number;
151
+ /**
152
+ * How many namespace levels below `remote-ns` to sync, 0 (no recursion) upwards. Unset means PBS
153
+ * recurses as deep as it is allowed — which is MORE data, not less, so it is not a safe default
154
+ * to leave implicit on a job whose target namespace has children.
155
+ */
156
+ 'max-depth'?: number;
157
+ /** Sync only the last N snapshots of each group. Unset means all of them. */
158
+ 'transfer-last'?: number;
159
+ /**
160
+ * ⛔ NOT DECLARABLE, AND A COMPILE ERROR RATHER THAN A PROP THAT HALF WORKS — the storage.ts
161
+ * `password` idiom, for two independent reasons, either of which alone would be enough.
162
+ * 1. THE WIRE FORM IS AN ARRAY. PBS takes `group-filter` as a repeated parameter
163
+ * (`group-filter=type:ct&group-filter=regex:^web`), and `pve()`'s form is
164
+ * `Record<string, string>` — one value per key — so a list cannot be expressed at all.
165
+ * 2. NARROWING A FILTER MAY BE A DELETE. Whether `remove-vanished` skips the groups a filter
166
+ * excludes is NOT something I could measure without a credential. If it does not, then
167
+ * editing this field on a job with `remove-vanished` set removes every local group the new
168
+ * filter excludes. A field that MIGHT delete snapshots when changed does not get a
169
+ * half-working prop.
170
+ * ★ IT IS STILL REPORTED in the attributes, so a plan shows the filters a hand-made job carries
171
+ * and this resource never strips them: undeclared is unmanaged, and PBS's update assigns only
172
+ * what it was sent. To make it declarable, fix both halves — a form type that can repeat a
173
+ * key, and a MEASURED answer to (2) — in that order.
174
+ */
175
+ 'group-filter'?: never;
176
+ /**
177
+ * `pull` (default) or `push`. ⛔ IT DECIDES WHICH SIDE `remove-vanished` DELETES FROM. Push also
178
+ * needs a newer PBS: on a server that predates it the parameter is unknown and the create is
179
+ * refused, which is the honest failure.
180
+ * ⚠️ CREATE-ONLY HERE, SO A CHANGED DIRECTION PLANS AS NOOP — see `matches`. That is the
181
+ * deliberately safe half of an unmeasured question: if PBS does accept it on PUT, the cost of
182
+ * this choice is a noop; if PBS refuses it, comparing it would be an update the PUT can never
183
+ * perform, on every plan, forever. Retire the declaration and add another, as user.ts says for
184
+ * `userid`.
185
+ */
186
+ 'sync-direction'?: 'pull' | 'push';
187
+ /**
188
+ * ⚠️ SYNC ONLY SNAPSHOTS THAT HAVE PASSED VERIFICATION. Present on 4.2 (`sync-job update
189
+ * --verified-only <boolean>`, and in its `--delete` enum), and the estate sets it: the parked
190
+ * `sync-all-to-dc` job carries `verified-only: true`, so without this prop that job cannot be
191
+ * declared as it actually is.
192
+ * ⛔ IT PAIRS WITH A VERIFICATION JOB THAT ACTUALLY RUNS. With PBS's defaults a snapshot is
193
+ * verified once and never again — see `rechecks` in pbs-verify-job-form.ts — so `verified-only`
194
+ * on a datastore whose verify job is parked means "sync only what was checked once, long ago".
195
+ */
196
+ 'verified-only'?: boolean;
197
+ }
198
+
199
+ export interface SyncJobAttributes {
200
+ /** From the props — it is the path this object was read by. */
201
+ id: string;
202
+ store: string;
203
+ /** `''` is the root namespace, which is also how PBS spells it. */
204
+ ns: string;
205
+ /** `''` means a LOCAL sync — datastore to datastore on this PBS. */
206
+ remote: string;
207
+ 'remote-store': string;
208
+ 'remote-ns': string;
209
+ /** `''` means the job runs only when somebody starts it. */
210
+ schedule: string;
211
+ comment: string;
212
+ 'remove-vanished': boolean;
213
+ /** ⚠️ Absent on the wire means `false`, PBS's own default. */
214
+ 'verified-only': boolean;
215
+ /** `''` means no owner in the config, i.e. `root@pam` at runtime. An identifier, never a secret. */
216
+ owner: string;
217
+ /** Bytes per second, or `UNSET` (-1). See `bytes()`. */
218
+ 'rate-in': number;
219
+ /** `UNSET` (-1) means unset, which means full recursion. `0` is a real value meaning none. */
220
+ 'max-depth': number;
221
+ /** `UNSET` (-1) means all snapshots. */
222
+ 'transfer-last': number;
223
+ /**
224
+ * ⚠️ REPORTED, NEVER SENT, NEVER COMPARED — the live filter list joined for display only. See the
225
+ * ⛔ on the prop for why it is not declarable.
226
+ */
227
+ 'group-filter': string;
228
+ /** `pull` or `push`. Reported, never compared — create-only here. */
229
+ 'sync-direction': string;
230
+ }
231
+
232
+ export interface PbsSyncJob extends Resource<
233
+ 'Pbs.SyncJob',
234
+ SyncJobProps,
235
+ SyncJobAttributes,
236
+ never,
237
+ PveRequirements
238
+ > {}
239
+
240
+ /**
241
+ * ⚠️ NO `defaultRemovalPolicy: 'retain'`, and the reasoning is replication-job.ts's. Deleting this
242
+ * object removes a SCHEDULE, not data: every snapshot already synced stays in the local
243
+ * datastore, and PBS's own pruning is a different job. What is lost is freshness — which is
244
+ * destructive in six months rather than today, and is exactly why the plan line deserves reading.
245
+ */
246
+ export const PbsSyncJob = Resource<PbsSyncJob>('Pbs.SyncJob');
247
+
248
+ /**
249
+ * ⛔ THE EMPTY `list` FROM THE FACTORY STANDS. `GET /config/sync` hands back every sync job an
250
+ * operator ever made in the UI, and adopting one is how a later `alchemy destroy` silently stops
251
+ * a second copy being refreshed. Adoption is an explicit act — declare the `id` you mean.
252
+ * ⚠️ DELETING THE JOB DOES NOT DELETE SNAPSHOTS, AND `remove-vanished` DOES. Those two sentences
253
+ * are the whole risk model of this family and they point opposite ways: the DELETE plan line is
254
+ * safe today and expensive in six months, while a single `remove-vanished: true` is a standing
255
+ * instruction to delete local snapshots whenever another server loses them.
256
+ */
257
+ export const PbsSyncJobProvider = () =>
258
+ Provider.effect(PbsSyncJob, Effect.succeed(PbsSyncJob.Provider.of(handlers)));
@@ -0,0 +1,131 @@
1
+ /**
2
+ * A verification job's props as the form PBS wants, and the one derived string worth reporting.
3
+ *
4
+ * ★ SPLIT OUT OF pbs-verify-job.ts TO KEEP BOTH FILES UNDER THE 250-LINE CAP, on the seam
5
+ * replication-job-form.ts and ha-rule-form.ts already cut: this file answers "how does a
6
+ * declaration become a PBS write, and what do the two filter fields actually mean", and
7
+ * pbs-verify-job.ts answers "what is a verification job and when has it changed". Nothing here
8
+ * reads the host and nothing here decides a diff.
9
+ *
10
+ * ⚠️ THE `import type` BACK TO pbs-verify-job.ts IS A CYCLE ON PAPER ONLY — type-only, erased
11
+ * before anything runs, and `PbsVerifyJobProps` stays public in the file declaring the resource.
12
+ *
13
+ * ⛔ THERE IS NO `disable` FIELD ON A PBS VERIFICATION JOB, AND SHIPPING A PROP FOR ONE WOULD HAVE
14
+ * BEEN THE WORST BUG IN THIS FAMILY. Prune jobs have `disable` and sync jobs have `disable`;
15
+ * `VerificationJobConfig` does not, in the struct, in the API schema, or in the update handler's
16
+ * `DeletableProperty` enum — read at HEAD of pbs-api-types and src/api2/config/verify.rs on
17
+ * 2026-09-13. A `disable` prop would therefore be a prop that either does nothing or 400s the
18
+ * whole call (the create schema has `additional_properties: false`, so an unknown key is
19
+ * rejected with "schema does not allow additional properties"). Either way it would be a
20
+ * declaration saying "verification is paused" that PBS never agreed to. The real off switch is
21
+ * an absent `schedule`.
22
+ * ★ NOW VERIFIED AGAINST THE RUNNING HOST, which this comment previously said it was not.
23
+ * `proxmox-backup-manager verify-job update --help` on the live 4.2 server offers
24
+ * `--comment --delete --digest --ignore-verified --max-depth --ns --outdated-after
25
+ * --read-threads --schedule --store --verify-threads` — no `--disable` — and its `--delete`
26
+ * enum contains `schedule`. Both halves of the claim hold on this estate's actual version.
27
+ * ⚠️ `read-threads` AND `verify-threads` EXIST ON 4.2 AND ARE NOT PROPS HERE. Nothing on this
28
+ * estate sets either, and a prop nobody uses is an abstraction with no user — the same reason
29
+ * `PveSpec.immutable` was backed out. Add them when something needs them.
30
+ *
31
+ * ⛔ `withClears` FROM values.ts IS DELIBERATELY NOT USED HERE, AND USING IT WOULD BREAK EVERY
32
+ * UPDATE. PVE's `delete` is ONE comma-separated string; PBS's is an ARRAY of an enum. MEASURED in
33
+ * proxmox-schema's `do_parse_parameter_strings`: for an `Schema::Array` property each occurrence
34
+ * of the key pushes ONE element, so a form body must REPEAT the key — `delete=ns&delete=comment`.
35
+ * `withClears` emits `delete: 'ns,comment'`, which PBS parses as a single element `"ns,comment"`,
36
+ * which is not a `DeletableProperty` variant, so the call fails with a parameter error. It fails
37
+ * loudly rather than quietly, which is the only good news in it.
38
+ * ★ AND IT COULD NOT BE FIXED IN THIS FILE ANYWAY. `client.ts` types a form as
39
+ * `Record<string, string>` and builds it with `new URLSearchParams(form)`; neither a plain
40
+ * object nor that constructor overload can express a repeated key. Clearing a PBS field needs
41
+ * a client that takes entry pairs. Until one exists, this form SETS and never CLEARS — so an
42
+ * undeclared field is left alone and, in pbs-verify-job.ts, is not compared either.
43
+ */
44
+ import type { PbsVerifyJobProps } from './pbs-verify-job.ts';
45
+
46
+ /**
47
+ * What the live pair actually causes, rendered for a plan line.
48
+ *
49
+ * ⛔ THE DEFAULTS MEAN "VERIFY EACH SNAPSHOT EXACTLY ONCE, EVER", AND THAT IS THE FAILURE THIS
50
+ * WHOLE FAMILY EXISTS TO MAKE VISIBLE. Read from src/backup/verify.rs `verify_filter` and
51
+ * src/server/verify_job.rs at HEAD on 2026-09-13, the filter is:
52
+ *
53
+ * ignore_verified = ignore_verified.unwrap_or(true) // absent means TRUE
54
+ * if !ignore_verified -> verify
55
+ * manifest has no verify state -> verify
56
+ * outdated_after is None -> SKIP, forever
57
+ * days_since_last_verify > outdated_after -> verify
58
+ *
59
+ * So a job created with neither field set — the shape the PBS web UI produces when nobody
60
+ * touches those two boxes — checks every snapshot once on the run after it is written and then
61
+ * never looks at it again. Bit rot that appears a month later is never found. The job stays
62
+ * green the entire time, because a job that verified nothing verified nothing badly.
63
+ *
64
+ * ⚠️ A FAILED PAST VERIFICATION IS SKIPPED TOO. `verify_filter` looks only at whether a verify
65
+ * state EXISTS, never at whether it says Ok or Failed, so a snapshot that failed once is not
66
+ * re-checked by a defaulted job either. Pair that with a prune job and the failure ages out
67
+ * without anyone confirming it.
68
+ *
69
+ * ⛔ `outdated-after: 0` DOES NOT MEAN "NEVER", THOUGH PBS'S OWN STRUCT DOC SAYS IT DOES. That
70
+ * comment — "Reverify snapshots after X days, never if 0" — contradicts the code beside it: the
71
+ * test is `days_since_last_verify > max_age`, so `0` re-verifies everything checked more than a
72
+ * day ago, i.e. very nearly every run. The schema text calls `0` deprecated and the minimum is
73
+ * `0`, so it is accepted. "Never" is the ABSENT value, not the zero — and this resource cannot
74
+ * get a job back to absent (see the ⛔ on `withClears` above), so a declaration that sets
75
+ * `outdated-after` has made a one-way choice. Undo it by hand:
76
+ * `proxmox-backup-manager verify-job update <id> --delete outdated-after`.
77
+ *
78
+ * ⚠️ THE COMPARISON IS INTEGER DAYS SINCE THE LAST RUN'S START, `(now - upid.starttime) / 86400`,
79
+ * and it is strictly greater-than. `outdated-after: 30` therefore re-checks on day 31.
80
+ */
81
+ export const rechecks = (ignoreVerified: boolean, outdatedAfter: number): string => {
82
+ if (!ignoreVerified) return 'every snapshot on every run';
83
+ // ⚠️ `-1` is this package's spelling of "the key is absent", set in `attributes`. It is not a
84
+ // value PBS accepts: the schema's minimum is 0.
85
+ if (outdatedAfter < 0) return 'never: each snapshot is checked once and then skipped forever';
86
+ return `once the last check is more than ${String(outdatedAfter)} day(s) old`;
87
+ };
88
+
89
+ /**
90
+ * The form for create and update alike.
91
+ *
92
+ * ⛔ `schedule: null` OMITS THE KEY, AND ON AN UPDATE THAT DOES NOT CLEAR AN EXISTING ONE. This
93
+ * form SETS and never CLEARS — the ⛔ on `withClears` above is why — so declaring `null` against
94
+ * a job that already has a schedule plans an update that changes nothing, every time. Park a job
95
+ * by hand once and the declaration then describes it truthfully:
96
+ * `proxmox-backup-manager verify-job update <id> --delete schedule`. The honest failure is a
97
+ * plan that keeps asking; the dishonest one would be a form that quietly reported success.
98
+ *
99
+ * ⚠️ `store` AND `ignore-verified` ARE ALWAYS SENT BECAUSE THEY ARE ALWAYS COMPARED.
100
+ * A field compared against a default but not sent as that default is an update reported on every
101
+ * plan that the write never performs — backup-job.ts and replication-job-form.ts both record the
102
+ * same trade. `ignore-verified` is the only one of the three with a PBS-side default, and it is
103
+ * TRUE, so an undeclared prop is sent as `1` rather than left out.
104
+ *
105
+ * ⚠️ THE BOOLEAN IS SPELLED INLINE RATHER THAN THROUGH `flag()` FROM values.ts, AND THAT IS A TYPE
106
+ * FACT RATHER THAN A STYLE ONE. `flag` answers `'1' | '0' | undefined` so that an undeclared
107
+ * field is dropped; here the field must never be dropped, and the `undefined` in its return type
108
+ * is not assignable to a `Record<string, string>` form body.
109
+ *
110
+ * ⚠️ PBS ACCEPTS BOTH `application/x-www-form-urlencoded` AND `application/json` AND NOTHING ELSE —
111
+ * any other content type is rejected outright with "unsupported content type", measured in
112
+ * proxmox-rest-server's `get_request_parameters`. `client.ts` sends the form encoding whenever a
113
+ * body is present, so it is already speaking one of the two. Numbers are stringified here for
114
+ * that reason; PBS parses them back through the integer schema.
115
+ *
116
+ * ⛔ `ns` IS DROPPED WHEN IT IS EMPTY, NOT SENT AS `''`. The empty string IS a legal namespace
117
+ * value — `BACKUP_NS_RE` is wrapped in `(?:…)?`, so it passes the schema and reaches the update
118
+ * handler — and the handler then throws it away at `if !ns.is_root()`. Sending it would look like
119
+ * a write that moves the job to the root namespace and would silently be nothing at all.
120
+ */
121
+ export const shape = (props: PbsVerifyJobProps): Record<string, string> => ({
122
+ 'ignore-verified': props['ignore-verified'] === false ? '0' : '1',
123
+ store: props.store,
124
+ ...(props.schedule === null ? {} : { schedule: props.schedule }),
125
+ ...(props.comment === undefined ? {} : { comment: props.comment }),
126
+ ...(props['max-depth'] === undefined ? {} : { 'max-depth': String(props['max-depth']) }),
127
+ ...(props.ns === undefined || props.ns === '' ? {} : { ns: props.ns }),
128
+ ...(props['outdated-after'] === undefined
129
+ ? {}
130
+ : { 'outdated-after': String(props['outdated-after']) }),
131
+ });