@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,170 @@
1
+ /**
2
+ * `Talos.Kubeconfig` — fetch admin kubeconfig so `Kubernetes.*` workloads can connect.
3
+ *
4
+ * ★ REASONED FROM talosctl kubeconfig (Talos v1.13 CLI reference): writes a kubeconfig file;
5
+ * `--merge=false` keeps it isolated; `-f/--force` overwrites an existing file.
6
+ *
7
+ * ⛔ THE KUBECONFIG FILE CONTAINS A CLIENT CERTIFICATE — IT MUST NOT BE AN ATTRIBUTE. Alchemy
8
+ * persists attributes unencrypted. This resource:
9
+ * - writes the file to `runtimePath` at reconcile time (re-minted each deploy),
10
+ * - persists ONLY public metadata + fingerprints,
11
+ * - exposes a `connection` field referencing the runtime path for `Kubernetes.ClusterAdapter`.
12
+ *
13
+ * ⚠️ `runtimePath` IS PERSISTED AS A STRING, NOT AS FILE CONTENTS. The path is a contract between
14
+ * this resource and the host filesystem — same pattern as pointing Kubernetes.KubeConfig at
15
+ * `/opt/homeflare/...` rendered outside Postgres.
16
+ */
17
+ import { Resource } from 'alchemy';
18
+ import { isResolved } from 'alchemy/Diff';
19
+ import type { Input } from 'alchemy/Input';
20
+ import type { Connection } from 'alchemy/Kubernetes/Connection';
21
+ import * as Provider from 'alchemy/Provider';
22
+ import * as Effect from 'effect/Effect';
23
+ import { mintTalosconfig } from './credentials.ts';
24
+ import type { TalosRequirements, WithTarget } from './resource.ts';
25
+ import { talosctl } from './talosctl.ts';
26
+ import { kubeconfigMetadata, sha256 } from './values.ts';
27
+
28
+ export interface KubeconfigProps extends WithTarget {
29
+ /** Node that serves `talosctl kubeconfig`. */
30
+ node: string;
31
+ /** Context name inside the generated kubeconfig — not secret. */
32
+ context: string;
33
+ /**
34
+ * Host path where talosctl writes the kubeconfig each reconcile.
35
+ *
36
+ * ⛔ Must be outside Alchemy state and rotated by redeploy, never copied into attributes.
37
+ */
38
+ runtimePath: string;
39
+ after?: readonly unknown[];
40
+ }
41
+
42
+ export interface KubeconfigAttributes {
43
+ context: string;
44
+ endpoint: string;
45
+ certificateAuthorityFingerprint: string;
46
+ clientCertificateFingerprint: string;
47
+ /** Digest of endpoint+context+fingerprints — detects credential rotation without storing PEM. */
48
+ credentialGeneration: string;
49
+ /**
50
+ * Serializable `Kubernetes.Connection` for downstream workloads.
51
+ *
52
+ * ★ auth.kind `kubeconfig` is the stock ClusterAdapter — provider-roadmap.md names this seam.
53
+ * Only `path` and `context` appear here; the adapter reads credentials from disk at call time.
54
+ */
55
+ connection: Connection;
56
+ }
57
+
58
+ export interface TalosKubeconfig extends Resource<
59
+ 'Talos.Kubeconfig',
60
+ KubeconfigProps,
61
+ KubeconfigAttributes,
62
+ never,
63
+ TalosRequirements
64
+ > {}
65
+
66
+ export const TalosKubeconfig = Resource<TalosKubeconfig>('Talos.Kubeconfig');
67
+
68
+ const generation = (meta: {
69
+ endpoint: string;
70
+ caFingerprint: string;
71
+ clientFingerprint: string;
72
+ context: string;
73
+ }) => sha256(`${meta.context}\n${meta.endpoint}\n${meta.caFingerprint}\n${meta.clientFingerprint}`);
74
+
75
+ const toConnection = (props: KubeconfigProps): Connection => ({
76
+ auth: { context: props.context, kind: 'kubeconfig', path: props.runtimePath },
77
+ });
78
+
79
+ const readFileMeta = (props: KubeconfigProps) =>
80
+ Effect.gen(function* () {
81
+ const text = yield* Effect.tryPromise({
82
+ try: () => Bun.file(props.runtimePath).text(),
83
+ catch: (cause) => new Error(String(cause)),
84
+ }).pipe(Effect.orElseSucceed(() => undefined as string | undefined));
85
+ if (text === undefined) return undefined;
86
+ const meta = kubeconfigMetadata(text, props.context);
87
+ if (meta === undefined) return undefined;
88
+ return {
89
+ certificateAuthorityFingerprint: meta.caFingerprint,
90
+ clientCertificateFingerprint: meta.clientFingerprint,
91
+ connection: toConnection(props),
92
+ context: props.context,
93
+ credentialGeneration: generation({ ...meta, context: props.context }),
94
+ endpoint: meta.endpoint,
95
+ };
96
+ });
97
+
98
+ const read = (props: KubeconfigProps) =>
99
+ readFileMeta(props).pipe(
100
+ Effect.map((meta) =>
101
+ meta === undefined
102
+ ? {
103
+ certificateAuthorityFingerprint: '',
104
+ clientCertificateFingerprint: '',
105
+ connection: toConnection(props),
106
+ context: props.context,
107
+ credentialGeneration: '',
108
+ endpoint: '',
109
+ }
110
+ : meta,
111
+ ),
112
+ );
113
+
114
+ const diff = (news: Input<KubeconfigProps>, output: KubeconfigAttributes | undefined) =>
115
+ Effect.gen(function* () {
116
+ if (output === undefined || !isResolved(news)) return undefined;
117
+ const live = yield* read(news);
118
+ if (
119
+ live.credentialGeneration !== '' &&
120
+ output.credentialGeneration === live.credentialGeneration &&
121
+ output.context === live.context &&
122
+ output.endpoint === live.endpoint
123
+ ) {
124
+ return { action: 'noop' } as const;
125
+ }
126
+ return { action: 'update' } as const;
127
+ });
128
+
129
+ const reconcile = (props: KubeconfigProps) =>
130
+ Effect.gen(function* () {
131
+ const credential = yield* mintTalosconfig(props.target);
132
+ yield* talosctl(
133
+ [
134
+ 'kubeconfig',
135
+ props.runtimePath,
136
+ '--merge=false',
137
+ '--force',
138
+ '--force-context-name',
139
+ props.context,
140
+ ],
141
+ { nodes: [props.node], talosconfigPath: credential.talosconfigPath },
142
+ );
143
+ const meta = yield* readFileMeta(props);
144
+ if (meta === undefined) {
145
+ return yield* Effect.die(
146
+ new Error(
147
+ `${props.runtimePath}: talosctl kubeconfig returned no error but the file is missing ` +
148
+ 'or unparsable. Read back rather than trusting the exit code alone.',
149
+ ),
150
+ );
151
+ }
152
+ return meta;
153
+ });
154
+
155
+ const handlers = {
156
+ delete: () => Effect.void,
157
+ diff: ({
158
+ news,
159
+ output,
160
+ }: {
161
+ news: Input<KubeconfigProps>;
162
+ output: KubeconfigAttributes | undefined;
163
+ }) => diff(news, output),
164
+ list: () => Effect.succeed([]),
165
+ read: ({ olds }: { olds: KubeconfigProps }) => read(olds),
166
+ reconcile: ({ news }: { news: KubeconfigProps }) => reconcile(news),
167
+ };
168
+
169
+ export const TalosKubeconfigProvider = () =>
170
+ Provider.effect(TalosKubeconfig, Effect.succeed(TalosKubeconfig.Provider.of(handlers)));
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Talos has no REST CRUD factory — and pretending otherwise would lie in every plan.
3
+ *
4
+ * ⛔ THE FORGEJO/PVE FACTORY IS create / read / update / delete OVER ONE JSON OBJECT AT ONE PATH.
5
+ * Talos machine configuration is `talosctl apply-config`; bootstrap is a one-shot
6
+ * `talosctl bootstrap`; health is `talosctl health`; kubeconfig is `talosctl kubeconfig`. None
7
+ * of those are the same four operations with a different path — bootstrap cannot be deleted,
8
+ * health is a gate not an object, and kubeconfig must not round-trip credential bytes through
9
+ * Postgres. Copying forgejoOperations here would force `matches` to compare a declaration to
10
+ * itself and answer `noop` forever, exactly the failure mode documented on Proxmox.NetworkApply.
11
+ *
12
+ * ★ SO EACH FAMILY BELOW HAND-WRITES THE FIVE PROVIDER HANDLERS, LIKE network-apply.ts and
13
+ * sdn-apply.ts. Shared pieces are credentials.ts (mint) and talosctl.ts (spawn).
14
+ *
15
+ * ⚠️ EVERY CLAIM ABOUT talosctl FLAGS IN THIS PACKAGE IS REASONED FROM THE PUBLISHED CLI REFERENCE
16
+ * (Talos v1.13 docs, fetched 2026-09-13 in this session). Nothing was measured on a live cluster.
17
+ */
18
+ import type * as ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';
19
+ import type { TalosTarget } from './credentials.ts';
20
+
21
+ /**
22
+ * ★ WHAT EVERY OPERATION HERE NEEDS FROM THE RUNTIME — ChildProcessSpawner only.
23
+ *
24
+ * Talos credentials are minted by shelling out to `bao` and then shelling out to `talosctl`.
25
+ * There is no HttpClient call in this package on purpose: the published operator surface is the
26
+ * CLI, and a parallel gRPC client would be a second SDK the repo rules forbid.
27
+ */
28
+ export type TalosRequirements = ChildProcessSpawner.ChildProcessSpawner;
29
+
30
+ /** Every resource names which cluster's OpenBao mount it uses. */
31
+ export type WithTarget = { target: TalosTarget };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `Talos.Bootstrap` — initialize etcd once on ONE control-plane node.
3
+ *
4
+ * ★ REASONED FROM talosctl bootstrap (Talos v1.13 CLI reference): one node aborts the etcd join
5
+ * loop and forms the initial cluster; other control-plane nodes join after Kubernetes starts on
6
+ * the bootstrap node. This command must run exactly once per cluster.
7
+ *
8
+ * ⛔ NOT A FACTORY RESOURCE — bootstrap cannot be deleted or updated in place; `delete` is a no-op.
9
+ */
10
+ import { Resource } from 'alchemy';
11
+ import { isResolved } from 'alchemy/Diff';
12
+ import type { Input } from 'alchemy/Input';
13
+ import * as Provider from 'alchemy/Provider';
14
+ import * as Effect from 'effect/Effect';
15
+ import { mintTalosconfig } from './credentials.ts';
16
+ import type { TalosRequirements, WithTarget } from './resource.ts';
17
+ import { talosctl, talosctlOrAlready } from './talosctl.ts';
18
+
19
+ export interface BootstrapProps extends WithTarget {
20
+ /** The ONE control-plane node that receives `talosctl bootstrap`. */
21
+ node: string;
22
+ /** Machine configs for this node (and peers) must be applied first. */
23
+ after?: readonly unknown[];
24
+ }
25
+
26
+ export interface BootstrapAttributes {
27
+ node: string;
28
+ bootstrapped: boolean;
29
+ }
30
+
31
+ export interface TalosBootstrap extends Resource<
32
+ 'Talos.Bootstrap',
33
+ BootstrapProps,
34
+ BootstrapAttributes,
35
+ never,
36
+ TalosRequirements
37
+ > {}
38
+
39
+ export const TalosBootstrap = Resource<TalosBootstrap>('Talos.Bootstrap', {
40
+ /** etcd data is irreplaceable — opt into destroy explicitly if Talos ever adds an undo. */
41
+ defaultRemovalPolicy: 'retain',
42
+ });
43
+
44
+ /**
45
+ * REASONED: `talosctl get etcdmembers -o json` should list members after bootstrap.
46
+ * ⚠️ NOT MEASURED — first live deploy confirms the resource type name.
47
+ */
48
+ const isBootstrapped = (props: BootstrapProps, talosconfigPath: string) =>
49
+ talosctl(['get', 'etcdmembers', '-o', 'json'], {
50
+ nodes: [props.node],
51
+ talosconfigPath,
52
+ }).pipe(
53
+ Effect.map((text) => text.includes('"id"') || text.includes('"member"')),
54
+ Effect.orElseSucceed(() => false),
55
+ );
56
+
57
+ const read = (props: BootstrapProps) =>
58
+ Effect.gen(function* () {
59
+ const credential = yield* mintTalosconfig(props.target);
60
+ const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);
61
+ return { bootstrapped, node: props.node };
62
+ });
63
+
64
+ const diff = (news: Input<BootstrapProps>, output: BootstrapAttributes | undefined) =>
65
+ Effect.gen(function* () {
66
+ if (output === undefined || !isResolved(news)) return undefined;
67
+ const live = yield* read(news);
68
+ if (live.bootstrapped) return { action: 'noop' } as const;
69
+ return { action: 'update' } as const;
70
+ });
71
+
72
+ const reconcile = (props: BootstrapProps) =>
73
+ Effect.gen(function* () {
74
+ const credential = yield* mintTalosconfig(props.target);
75
+ const before = yield* isBootstrapped(props, credential.talosconfigPath);
76
+ if (!before) {
77
+ yield* talosctlOrAlready(['bootstrap'], {
78
+ nodes: [props.node],
79
+ talosconfigPath: credential.talosconfigPath,
80
+ });
81
+ }
82
+ const after = yield* isBootstrapped(props, credential.talosconfigPath);
83
+ if (!after) {
84
+ return yield* Effect.die(
85
+ new Error(
86
+ `${props.node}: bootstrap returned no error but etcd members are still absent. ` +
87
+ 'Read back rather than trusting the exit code alone.',
88
+ ),
89
+ );
90
+ }
91
+ return { bootstrapped: true, node: props.node };
92
+ });
93
+
94
+ const handlers = {
95
+ delete: () => Effect.void,
96
+ diff: ({
97
+ news,
98
+ output,
99
+ }: {
100
+ news: Input<BootstrapProps>;
101
+ output: BootstrapAttributes | undefined;
102
+ }) => diff(news, output),
103
+ list: () => Effect.succeed([]),
104
+ read: ({ olds }: { olds: BootstrapProps }) => read(olds),
105
+ reconcile: ({ news }: { news: BootstrapProps }) => reconcile(news),
106
+ };
107
+
108
+ export const TalosBootstrapProvider = () =>
109
+ Provider.effect(TalosBootstrap, Effect.succeed(TalosBootstrap.Provider.of(handlers)));
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `Talos.ClusterHealth` — readiness gate other resources can depend on via `after`.
3
+ *
4
+ * ★ REASONED FROM talosctl health (Talos v1.13 CLI reference): checks Talos services, etcd, and
5
+ * Kubernetes component health; `--wait-timeout` defaults to 20m0s.
6
+ *
7
+ * ⚠️ PLAN READS USE A SHORT TIMEOUT SO `plan` DOES NOT BLOCK TWENTY MINUTES. Reconcile uses the
8
+ * declared timeout — that is the deploy gate, not the plan gate.
9
+ */
10
+ import { Resource } from 'alchemy';
11
+ import { isResolved } from 'alchemy/Diff';
12
+ import type { Input } from 'alchemy/Input';
13
+ import * as Provider from 'alchemy/Provider';
14
+ import * as Effect from 'effect/Effect';
15
+ import { mintTalosconfig } from './credentials.ts';
16
+ import type { TalosRequirements, WithTarget } from './resource.ts';
17
+ import { talosctl } from './talosctl.ts';
18
+
19
+ export interface ClusterHealthProps extends WithTarget {
20
+ /** Control-plane node IPs passed to `--control-plane-nodes`. */
21
+ controlPlaneNodes: readonly string[];
22
+ /** Worker node IPs passed to `--worker-nodes`. */
23
+ workerNodes?: readonly string[];
24
+ /**
25
+ * Reconcile wait (`--wait-timeout`), e.g. `20m0s`. Default from published CLI.
26
+ *
27
+ * ⚠️ REASONED default — not measured here.
28
+ */
29
+ waitTimeout?: string;
30
+ /** Bootstrap and kubeconfig should complete before health is enforced. */
31
+ after?: readonly unknown[];
32
+ }
33
+
34
+ export interface ClusterHealthAttributes {
35
+ healthy: boolean;
36
+ controlPlaneNodes: string;
37
+ workerNodes: string;
38
+ }
39
+
40
+ export interface TalosClusterHealth extends Resource<
41
+ 'Talos.ClusterHealth',
42
+ ClusterHealthProps,
43
+ ClusterHealthAttributes,
44
+ never,
45
+ TalosRequirements
46
+ > {}
47
+
48
+ export const TalosClusterHealth = Resource<TalosClusterHealth>('Talos.ClusterHealth');
49
+
50
+ const nodeCsv = (nodes: readonly string[] | undefined) =>
51
+ nodes === undefined || nodes.length === 0 ? '' : nodes.join(',');
52
+
53
+ const healthArgs = (props: ClusterHealthProps, waitTimeout: string) => {
54
+ const args = ['health', '--wait-timeout', waitTimeout];
55
+ const cp = nodeCsv(props.controlPlaneNodes);
56
+ const workers = nodeCsv(props.workerNodes);
57
+ if (cp !== '') args.push('--control-plane-nodes', cp);
58
+ if (workers !== '') args.push('--worker-nodes', workers);
59
+ return args;
60
+ };
61
+
62
+ const check = (props: ClusterHealthProps, waitTimeout: string) =>
63
+ Effect.gen(function* () {
64
+ const credential = yield* mintTalosconfig(props.target);
65
+ yield* talosctl(healthArgs(props, waitTimeout), {
66
+ nodes: [...props.controlPlaneNodes],
67
+ talosconfigPath: credential.talosconfigPath,
68
+ });
69
+ return {
70
+ controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
71
+ healthy: true,
72
+ workerNodes: nodeCsv(props.workerNodes),
73
+ };
74
+ });
75
+
76
+ const read = (props: ClusterHealthProps) =>
77
+ check(props, '5s').pipe(
78
+ Effect.orElseSucceed(() => ({
79
+ controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
80
+ healthy: false,
81
+ workerNodes: nodeCsv(props.workerNodes),
82
+ })),
83
+ );
84
+
85
+ const diff = (news: Input<ClusterHealthProps>, output: ClusterHealthAttributes | undefined) =>
86
+ Effect.gen(function* () {
87
+ if (output === undefined || !isResolved(news)) return undefined;
88
+ const live = yield* read(news);
89
+ if (live.healthy) return { action: 'noop' } as const;
90
+ return { action: 'update' } as const;
91
+ });
92
+
93
+ const reconcile = (props: ClusterHealthProps) =>
94
+ check(props, props.waitTimeout ?? '20m0s').pipe(
95
+ Effect.mapError(
96
+ (cause) =>
97
+ new Error(
98
+ `Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` +
99
+ `${props.waitTimeout ?? '20m0s'} — ${String(cause)}`,
100
+ ),
101
+ ),
102
+ );
103
+
104
+ const handlers = {
105
+ delete: () => Effect.void,
106
+ diff: ({
107
+ news,
108
+ output,
109
+ }: {
110
+ news: Input<ClusterHealthProps>;
111
+ output: ClusterHealthAttributes | undefined;
112
+ }) => diff(news, output),
113
+ list: () => Effect.succeed([]),
114
+ read: ({ olds }: { olds: ClusterHealthProps }) => read(olds),
115
+ reconcile: ({ news }: { news: ClusterHealthProps }) => reconcile(news),
116
+ };
117
+
118
+ export const TalosClusterHealthProvider = () =>
119
+ Provider.effect(TalosClusterHealth, Effect.succeed(TalosClusterHealth.Provider.of(handlers)));
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `Talos.MachineConfig` — apply one machine configuration document to one node.
3
+ *
4
+ * ★ REASONED FROM talosctl apply-config (Talos v1.13 CLI reference): `-f/--file`, `-n/--nodes`,
5
+ * `-m/--mode`, `-i/--insecure` for maintenance mode, `--dry-run` for planning without a write.
6
+ *
7
+ * ⛔ THE CONFIG BODY IS NEVER AN ATTRIBUTE. Machine configs can embed cluster tokens; only a digest
8
+ * of the repo file is persisted. Read `configFile` from disk at reconcile time.
9
+ */
10
+ import { Resource } from 'alchemy';
11
+ import { isResolved } from 'alchemy/Diff';
12
+ import type { Input } from 'alchemy/Input';
13
+ import * as Provider from 'alchemy/Provider';
14
+ import * as Effect from 'effect/Effect';
15
+ import { mintTalosconfig } from './credentials.ts';
16
+ import type { TalosRequirements, WithTarget } from './resource.ts';
17
+ import { talosctl } from './talosctl.ts';
18
+ import { configDigest, resolveConfigPath } from './values.ts';
19
+
20
+ const STACK_DIR = new URL('..', import.meta.url).pathname;
21
+
22
+ export type ApplyMode = 'auto' | 'no-reboot' | 'reboot' | 'staged' | 'try';
23
+
24
+ export interface MachineConfigProps extends WithTarget {
25
+ /** Node IP or hostname — the `-n` target. */
26
+ node: string;
27
+ /** Repo-relative or absolute path to the YAML file passed to `-f`. */
28
+ configFile: string;
29
+ /** talosctl `-m` mode. Default `auto` per published CLI. */
30
+ mode?: ApplyMode;
31
+ /**
32
+ * Maintenance-mode apply only (`-i`). Default false — post-bootstrap applies use talosconfig auth.
33
+ *
34
+ * ⚠️ REASONED: docs describe `--insecure` for the first apply before talosconfig exists.
35
+ */
36
+ insecure?: boolean;
37
+ /** Ordering edge — e.g. wait for Proxmox.Vm. */
38
+ after?: readonly unknown[];
39
+ }
40
+
41
+ export interface MachineConfigAttributes {
42
+ node: string;
43
+ configDigest: string;
44
+ mode: ApplyMode;
45
+ /** True when the node's reported config digest matches the declared file. */
46
+ converged: boolean;
47
+ }
48
+
49
+ export interface TalosMachineConfig extends Resource<
50
+ 'Talos.MachineConfig',
51
+ MachineConfigProps,
52
+ MachineConfigAttributes,
53
+ never,
54
+ TalosRequirements
55
+ > {}
56
+
57
+ export const TalosMachineConfig = Resource<TalosMachineConfig>('Talos.MachineConfig');
58
+
59
+ const loadDigest = (props: MachineConfigProps) =>
60
+ Effect.gen(function* () {
61
+ const path = resolveConfigPath(STACK_DIR, props.configFile);
62
+ const text = yield* Effect.tryPromise({
63
+ try: () => Bun.file(path).text(),
64
+ catch: (cause) => new Error(`reading ${props.configFile}: ${String(cause)}`),
65
+ });
66
+ return configDigest(text);
67
+ });
68
+
69
+ /** REASONED: `talosctl get machineconfig -o yaml` returns the live document. */
70
+ const liveDigest = (props: MachineConfigProps, talosconfigPath: string) =>
71
+ talosctl(['get', 'machineconfig', '-o', 'yaml'], {
72
+ nodes: [props.node],
73
+ talosconfigPath,
74
+ }).pipe(
75
+ Effect.map((yaml) => configDigest(yaml)),
76
+ Effect.orElseSucceed(() => undefined),
77
+ );
78
+
79
+ const read = (props: MachineConfigProps) =>
80
+ Effect.gen(function* () {
81
+ const wanted = yield* loadDigest(props);
82
+ const credential = yield* mintTalosconfig(props.target);
83
+ const live = yield* liveDigest(props, credential.talosconfigPath);
84
+ return {
85
+ configDigest: wanted,
86
+ converged: live === wanted,
87
+ mode: props.mode ?? 'auto',
88
+ node: props.node,
89
+ };
90
+ });
91
+
92
+ const diff = (news: Input<MachineConfigProps>, output: MachineConfigAttributes | undefined) =>
93
+ Effect.gen(function* () {
94
+ if (output === undefined || !isResolved(news)) return undefined;
95
+ const live = yield* read(news);
96
+ if (live.converged) return { action: 'noop' } as const;
97
+ return { action: 'update' } as const;
98
+ });
99
+
100
+ const reconcile = (props: MachineConfigProps) =>
101
+ Effect.gen(function* () {
102
+ const path = resolveConfigPath(STACK_DIR, props.configFile);
103
+ const wanted = yield* loadDigest(props);
104
+ const credential = yield* mintTalosconfig(props.target);
105
+ const args = [
106
+ 'apply-config',
107
+ '--file',
108
+ path,
109
+ '--mode',
110
+ props.mode ?? 'auto',
111
+ ...(props.insecure === true ? ['--insecure'] : []),
112
+ ];
113
+ yield* talosctl(args, { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
114
+ const live = yield* liveDigest(props, credential.talosconfigPath);
115
+ if (live !== wanted) {
116
+ return yield* Effect.die(
117
+ new Error(
118
+ `${props.node}: apply-config returned no error but machineconfig digest still differs. ` +
119
+ 'Read back rather than trusting the exit code alone.',
120
+ ),
121
+ );
122
+ }
123
+ return {
124
+ configDigest: wanted,
125
+ converged: true,
126
+ mode: props.mode ?? 'auto',
127
+ node: props.node,
128
+ };
129
+ });
130
+
131
+ const handlers = {
132
+ delete: () => Effect.void,
133
+ diff: ({
134
+ news,
135
+ output,
136
+ }: {
137
+ news: Input<MachineConfigProps>;
138
+ output: MachineConfigAttributes | undefined;
139
+ }) => diff(news, output),
140
+ list: () => Effect.succeed([]),
141
+ read: ({ olds }: { olds: MachineConfigProps }) => read(olds),
142
+ reconcile: ({ news }: { news: MachineConfigProps }) => reconcile(news),
143
+ };
144
+
145
+ export const TalosMachineConfigProvider = () =>
146
+ Provider.effect(TalosMachineConfig, Effect.succeed(TalosMachineConfig.Provider.of(handlers)));
@@ -0,0 +1,94 @@
1
+ /**
2
+ * One `talosctl` invocation — stdout/stderr captured, secrets never echoed.
3
+ *
4
+ * ★ SHELL OUT LIKE ALCHEMY'S DOCKER PROVIDER (ChildProcessSpawner), NOT A SECOND SDK. Talos speaks
5
+ * gRPC; the published, stable surface for operators is `talosctl`. No Talos npm package is added.
6
+ *
7
+ * ⚠️ REASONED FROM docs.siderolabs.com/talos/v1.13/reference/cli — nothing here was measured on a
8
+ * live cluster in this session.
9
+ *
10
+ * ⛔ DO NOT LOG STDOUT ON FAILURE. `talosctl kubeconfig` and error paths have been observed in the
11
+ * wild to include credential material; stderr is the safe diagnostic channel.
12
+ */
13
+ import * as Effect from 'effect/Effect';
14
+ import * as Stream from 'effect/Stream';
15
+ import * as ChildProcess from 'effect/unstable/process/ChildProcess';
16
+ import * as ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';
17
+
18
+ export class TalosError extends Error {
19
+ constructor(
20
+ readonly command: string,
21
+ readonly exitCode: number,
22
+ detail: string,
23
+ ) {
24
+ super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);
25
+ this.name = 'TalosError';
26
+ }
27
+ }
28
+
29
+ export type TalosRunOptions = {
30
+ readonly talosconfigPath: string;
31
+ readonly nodes?: readonly string[];
32
+ readonly endpoints?: readonly string[];
33
+ };
34
+
35
+ /** Run `talosctl <args…>`. Returns trimmed stdout, or '' when empty. */
36
+ export const talosctl = (args: readonly string[], options: TalosRunOptions) =>
37
+ Effect.gen(function* () {
38
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
39
+ const argv = [
40
+ ...args,
41
+ '--talosconfig',
42
+ options.talosconfigPath,
43
+ ...(options.nodes === undefined ? [] : ['--nodes', options.nodes.join(',')]),
44
+ ...(options.endpoints === undefined ? [] : ['--endpoints', options.endpoints.join(',')]),
45
+ ];
46
+ const result = yield* ChildProcess.make('talosctl', argv, {
47
+ detached: false,
48
+ extendEnv: true,
49
+ stderr: 'pipe',
50
+ stdin: 'ignore',
51
+ stdout: 'pipe',
52
+ }).pipe(
53
+ spawner.spawn,
54
+ Effect.flatMap((child) =>
55
+ Effect.all(
56
+ {
57
+ exitCode: child.exitCode,
58
+ stderr: child.stderr.pipe(
59
+ Stream.decodeText,
60
+ Stream.mkString,
61
+ Effect.map((text) => text.trim()),
62
+ ),
63
+ stdout: child.stdout.pipe(
64
+ Stream.decodeText,
65
+ Stream.mkString,
66
+ Effect.map((text) => text.trim()),
67
+ ),
68
+ },
69
+ { concurrency: 'unbounded' },
70
+ ),
71
+ ),
72
+ Effect.scoped,
73
+ );
74
+
75
+ const command = args.join(' ');
76
+ if (result.exitCode !== 0) {
77
+ return yield* Effect.fail(
78
+ new TalosError(command, result.exitCode, result.stderr.slice(0, 300)),
79
+ );
80
+ }
81
+ return result.stdout;
82
+ });
83
+
84
+ /** Like `talosctl`, but exit code 1 with "already" in stderr is treated as success. */
85
+ export const talosctlOrAlready = (args: readonly string[], options: TalosRunOptions) =>
86
+ talosctl(args, options).pipe(
87
+ Effect.catchIf(
88
+ (cause): cause is TalosError =>
89
+ cause instanceof TalosError &&
90
+ cause.exitCode !== 0 &&
91
+ /already|exists|bootstrapped/i.test(cause.message),
92
+ () => Effect.succeed(''),
93
+ ),
94
+ );