@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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/cloudflare/client.d.ts +45 -0
- package/dist/cloudflare/client.d.ts.map +1 -0
- package/dist/cloudflare/index.d.ts +11 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +129 -0
- package/dist/cloudflare/index.js.map +13 -0
- package/dist/cloudflare/lock-rules.d.ts +50 -0
- package/dist/cloudflare/lock-rules.d.ts.map +1 -0
- package/dist/cloudflare/providers.d.ts +19 -0
- package/dist/cloudflare/providers.d.ts.map +1 -0
- package/dist/cloudflare/r2-bucket-lock.d.ts +100 -0
- package/dist/cloudflare/r2-bucket-lock.d.ts.map +1 -0
- package/dist/forgejo/branch-protection-form.d.ts +19 -0
- package/dist/forgejo/branch-protection-form.d.ts.map +1 -0
- package/dist/forgejo/branch-protection.d.ts +47 -0
- package/dist/forgejo/branch-protection.d.ts.map +1 -0
- package/dist/forgejo/client.d.ts +37 -0
- package/dist/forgejo/client.d.ts.map +1 -0
- package/dist/forgejo/index.d.ts +18 -0
- package/dist/forgejo/index.d.ts.map +1 -0
- package/dist/forgejo/index.js +479 -0
- package/dist/forgejo/index.js.map +21 -0
- package/dist/forgejo/org-actions-secrets.d.ts +45 -0
- package/dist/forgejo/org-actions-secrets.d.ts.map +1 -0
- package/dist/forgejo/org-label.d.ts +46 -0
- package/dist/forgejo/org-label.d.ts.map +1 -0
- package/dist/forgejo/org-team.d.ts +42 -0
- package/dist/forgejo/org-team.d.ts.map +1 -0
- package/dist/forgejo/repo-webhook-form.d.ts +27 -0
- package/dist/forgejo/repo-webhook-form.d.ts.map +1 -0
- package/dist/forgejo/repo-webhook.d.ts +46 -0
- package/dist/forgejo/repo-webhook.d.ts.map +1 -0
- package/dist/forgejo/repository.d.ts +45 -0
- package/dist/forgejo/repository.d.ts.map +1 -0
- package/dist/forgejo/resource.d.ts +83 -0
- package/dist/forgejo/resource.d.ts.map +1 -0
- package/dist/forgejo/team-member.d.ts +39 -0
- package/dist/forgejo/team-member.d.ts.map +1 -0
- package/dist/forgejo/values.d.ts +24 -0
- package/dist/forgejo/values.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +9 -0
- package/dist/openbao/auth-role-form.d.ts +38 -0
- package/dist/openbao/auth-role-form.d.ts.map +1 -0
- package/dist/openbao/auth-role.d.ts +20 -0
- package/dist/openbao/auth-role.d.ts.map +1 -0
- package/dist/openbao/bao-address.d.ts +72 -0
- package/dist/openbao/bao-address.d.ts.map +1 -0
- package/dist/openbao/bao-http.d.ts +71 -0
- package/dist/openbao/bao-http.d.ts.map +1 -0
- package/dist/openbao/bao-status.d.ts +74 -0
- package/dist/openbao/bao-status.d.ts.map +1 -0
- package/dist/openbao/cloudflare-group-scope.d.ts +73 -0
- package/dist/openbao/cloudflare-group-scope.d.ts.map +1 -0
- package/dist/openbao/cloudflare-parity-catalog.d.ts +28 -0
- package/dist/openbao/cloudflare-parity-catalog.d.ts.map +1 -0
- package/dist/openbao/cloudflare-parity-snapshot.d.ts +31 -0
- package/dist/openbao/cloudflare-parity-snapshot.d.ts.map +1 -0
- package/dist/openbao/cloudflare-parity.d.ts +14 -0
- package/dist/openbao/cloudflare-parity.d.ts.map +1 -0
- package/dist/openbao/cloudflare-permission-groups-solve.d.ts +85 -0
- package/dist/openbao/cloudflare-permission-groups-solve.d.ts.map +1 -0
- package/dist/openbao/cloudflare-permission-groups.d.ts +87 -0
- package/dist/openbao/cloudflare-permission-groups.d.ts.map +1 -0
- package/dist/openbao/cloudflare-policy.d.ts +65 -0
- package/dist/openbao/cloudflare-policy.d.ts.map +1 -0
- package/dist/openbao/cloudflare-role-form.d.ts +110 -0
- package/dist/openbao/cloudflare-role-form.d.ts.map +1 -0
- package/dist/openbao/cloudflare-role-wire.d.ts +27 -0
- package/dist/openbao/cloudflare-role-wire.d.ts.map +1 -0
- package/dist/openbao/cloudflare-role.d.ts +35 -0
- package/dist/openbao/cloudflare-role.d.ts.map +1 -0
- package/dist/openbao/cloudflare-roles-config.d.ts +67 -0
- package/dist/openbao/cloudflare-roles-config.d.ts.map +1 -0
- package/dist/openbao/cloudflare-roles-expand.d.ts +76 -0
- package/dist/openbao/cloudflare-roles-expand.d.ts.map +1 -0
- package/dist/openbao/digest.d.ts +24 -0
- package/dist/openbao/digest.d.ts.map +1 -0
- package/dist/openbao/fake-bao.d.ts +40 -0
- package/dist/openbao/fake-bao.d.ts.map +1 -0
- package/dist/openbao/forgejo-bootstrap-parse.d.ts +46 -0
- package/dist/openbao/forgejo-bootstrap-parse.d.ts.map +1 -0
- package/dist/openbao/forgejo-bootstrap.d.ts +29 -0
- package/dist/openbao/forgejo-bootstrap.d.ts.map +1 -0
- package/dist/openbao/index.d.ts +18 -0
- package/dist/openbao/index.d.ts.map +1 -0
- package/dist/openbao/index.js +1158 -0
- package/dist/openbao/index.js.map +33 -0
- package/dist/openbao/mount-form.d.ts +60 -0
- package/dist/openbao/mount-form.d.ts.map +1 -0
- package/dist/openbao/mount-wire.d.ts +24 -0
- package/dist/openbao/mount-wire.d.ts.map +1 -0
- package/dist/openbao/mount.d.ts +20 -0
- package/dist/openbao/mount.d.ts.map +1 -0
- package/dist/openbao/pki-role-form.d.ts +118 -0
- package/dist/openbao/pki-role-form.d.ts.map +1 -0
- package/dist/openbao/pki-role.d.ts +50 -0
- package/dist/openbao/pki-role.d.ts.map +1 -0
- package/dist/openbao/policy-assembly.d.ts +13 -0
- package/dist/openbao/policy-assembly.d.ts.map +1 -0
- package/dist/openbao/policy-wire.d.ts +41 -0
- package/dist/openbao/policy-wire.d.ts.map +1 -0
- package/dist/openbao/policy.d.ts +83 -0
- package/dist/openbao/policy.d.ts.map +1 -0
- package/dist/openbao/proxmox-role-form.d.ts +92 -0
- package/dist/openbao/proxmox-role-form.d.ts.map +1 -0
- package/dist/openbao/proxmox-role.d.ts +47 -0
- package/dist/openbao/proxmox-role.d.ts.map +1 -0
- package/dist/openbao/ssh-role-form.d.ts +99 -0
- package/dist/openbao/ssh-role-form.d.ts.map +1 -0
- package/dist/openbao/ssh-role.d.ts +43 -0
- package/dist/openbao/ssh-role.d.ts.map +1 -0
- package/dist/proxmox/acl.d.ts +72 -0
- package/dist/proxmox/acl.d.ts.map +1 -0
- package/dist/proxmox/api-token-form.d.ts +59 -0
- package/dist/proxmox/api-token-form.d.ts.map +1 -0
- package/dist/proxmox/api-token.d.ts +159 -0
- package/dist/proxmox/api-token.d.ts.map +1 -0
- package/dist/proxmox/backup-job.d.ts +112 -0
- package/dist/proxmox/backup-job.d.ts.map +1 -0
- package/dist/proxmox/ceph-daemon-form.d.ts +70 -0
- package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -0
- package/dist/proxmox/ceph-daemon.d.ts +162 -0
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag.d.ts +131 -0
- package/dist/proxmox/ceph-flag.d.ts.map +1 -0
- package/dist/proxmox/ceph-fs-wire.d.ts +106 -0
- package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-fs.d.ts +124 -0
- package/dist/proxmox/ceph-fs.d.ts.map +1 -0
- package/dist/proxmox/ceph-osd-tree.d.ts +154 -0
- package/dist/proxmox/ceph-osd-tree.d.ts.map +1 -0
- package/dist/proxmox/ceph-osd-write.d.ts +36 -0
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -0
- package/dist/proxmox/ceph-osd.d.ts +102 -0
- package/dist/proxmox/ceph-osd.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool-form.d.ts +217 -0
- package/dist/proxmox/ceph-pool-form.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool-settle.d.ts +39 -0
- package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool.d.ts +48 -0
- package/dist/proxmox/ceph-pool.d.ts.map +1 -0
- package/dist/proxmox/client.d.ts +64 -0
- package/dist/proxmox/client.d.ts.map +1 -0
- package/dist/proxmox/credentials.d.ts +153 -0
- package/dist/proxmox/credentials.d.ts.map +1 -0
- package/dist/proxmox/firewall-alias-form.d.ts +98 -0
- package/dist/proxmox/firewall-alias-form.d.ts.map +1 -0
- package/dist/proxmox/firewall-alias.d.ts +141 -0
- package/dist/proxmox/firewall-alias.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs.d.ts +388 -0
- package/dist/proxmox/generated/pbs.d.ts.map +1 -0
- package/dist/proxmox/generated/pve.d.ts +4713 -0
- package/dist/proxmox/generated/pve.d.ts.map +1 -0
- package/dist/proxmox/group.d.ts +104 -0
- package/dist/proxmox/group.d.ts.map +1 -0
- package/dist/proxmox/ha-resource.d.ts +104 -0
- package/dist/proxmox/ha-resource.d.ts.map +1 -0
- package/dist/proxmox/ha-rule-form.d.ts +103 -0
- package/dist/proxmox/ha-rule-form.d.ts.map +1 -0
- package/dist/proxmox/ha-rule.d.ts +156 -0
- package/dist/proxmox/ha-rule.d.ts.map +1 -0
- package/dist/proxmox/index.d.ts +43 -0
- package/dist/proxmox/index.d.ts.map +1 -0
- package/dist/proxmox/index.js +2322 -0
- package/dist/proxmox/index.js.map +70 -0
- package/dist/proxmox/lease-cache.d.ts +98 -0
- package/dist/proxmox/lease-cache.d.ts.map +1 -0
- package/dist/proxmox/lxc.d.ts +59 -0
- package/dist/proxmox/lxc.d.ts.map +1 -0
- package/dist/proxmox/members.d.ts +56 -0
- package/dist/proxmox/members.d.ts.map +1 -0
- package/dist/proxmox/metric-server-form.d.ts +53 -0
- package/dist/proxmox/metric-server-form.d.ts.map +1 -0
- package/dist/proxmox/metric-server-otel.d.ts +49 -0
- package/dist/proxmox/metric-server-otel.d.ts.map +1 -0
- package/dist/proxmox/metric-server.d.ts +123 -0
- package/dist/proxmox/metric-server.d.ts.map +1 -0
- package/dist/proxmox/network-apply-read.d.ts +93 -0
- package/dist/proxmox/network-apply-read.d.ts.map +1 -0
- package/dist/proxmox/network-apply.d.ts +102 -0
- package/dist/proxmox/network-apply.d.ts.map +1 -0
- package/dist/proxmox/node-network-form.d.ts +128 -0
- package/dist/proxmox/node-network-form.d.ts.map +1 -0
- package/dist/proxmox/node-network.d.ts +173 -0
- package/dist/proxmox/node-network.d.ts.map +1 -0
- package/dist/proxmox/notification-target-form.d.ts +44 -0
- package/dist/proxmox/notification-target-form.d.ts.map +1 -0
- package/dist/proxmox/notification-target.d.ts +146 -0
- package/dist/proxmox/notification-target.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore-form.d.ts +147 -0
- package/dist/proxmox/pbs-datastore-form.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore-guard.d.ts +55 -0
- package/dist/proxmox/pbs-datastore-guard.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore.d.ts +140 -0
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job-form.d.ts +97 -0
- package/dist/proxmox/pbs-prune-job-form.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job.d.ts +168 -0
- package/dist/proxmox/pbs-prune-job.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job-form.d.ts +83 -0
- package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job.d.ts +246 -0
- package/dist/proxmox/pbs-sync-job.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job-form.d.ts +114 -0
- package/dist/proxmox/pbs-verify-job-form.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job.d.ts +145 -0
- package/dist/proxmox/pbs-verify-job.d.ts.map +1 -0
- package/dist/proxmox/pool.d.ts +42 -0
- package/dist/proxmox/pool.d.ts.map +1 -0
- package/dist/proxmox/qemu.d.ts +46 -0
- package/dist/proxmox/qemu.d.ts.map +1 -0
- package/dist/proxmox/replication-job-form.d.ts +71 -0
- package/dist/proxmox/replication-job-form.d.ts.map +1 -0
- package/dist/proxmox/replication-job.d.ts +162 -0
- package/dist/proxmox/replication-job.d.ts.map +1 -0
- package/dist/proxmox/resource.d.ts +157 -0
- package/dist/proxmox/resource.d.ts.map +1 -0
- package/dist/proxmox/role.d.ts +97 -0
- package/dist/proxmox/role.d.ts.map +1 -0
- package/dist/proxmox/sdn-apply-read.d.ts +80 -0
- package/dist/proxmox/sdn-apply-read.d.ts.map +1 -0
- package/dist/proxmox/sdn-apply.d.ts +63 -0
- package/dist/proxmox/sdn-apply.d.ts.map +1 -0
- package/dist/proxmox/sdn-subnet-form.d.ts +111 -0
- package/dist/proxmox/sdn-subnet-form.d.ts.map +1 -0
- package/dist/proxmox/sdn-subnet.d.ts +161 -0
- package/dist/proxmox/sdn-subnet.d.ts.map +1 -0
- package/dist/proxmox/sdn-vnet.d.ts +113 -0
- package/dist/proxmox/sdn-vnet.d.ts.map +1 -0
- package/dist/proxmox/sdn-zone.d.ts +126 -0
- package/dist/proxmox/sdn-zone.d.ts.map +1 -0
- package/dist/proxmox/storage.d.ts +120 -0
- package/dist/proxmox/storage.d.ts.map +1 -0
- package/dist/proxmox/user.d.ts +107 -0
- package/dist/proxmox/user.d.ts.map +1 -0
- package/dist/proxmox/values.d.ts +134 -0
- package/dist/proxmox/values.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool-write.d.ts +76 -0
- package/dist/proxmox/zfs-pool-write.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool.d.ts +146 -0
- package/dist/proxmox/zfs-pool.d.ts.map +1 -0
- package/dist/talos/credentials.d.ts +30 -0
- package/dist/talos/credentials.d.ts.map +1 -0
- package/dist/talos/index.d.ts +15 -0
- package/dist/talos/index.d.ts.map +1 -0
- package/dist/talos/index.js +392 -0
- package/dist/talos/index.js.map +16 -0
- package/dist/talos/kubeconfig.d.ts +53 -0
- package/dist/talos/kubeconfig.d.ts.map +1 -0
- package/dist/talos/resource.d.ts +32 -0
- package/dist/talos/resource.d.ts.map +1 -0
- package/dist/talos/talos-bootstrap.d.ts +27 -0
- package/dist/talos/talos-bootstrap.d.ts.map +1 -0
- package/dist/talos/talos-cluster-health.d.ts +36 -0
- package/dist/talos/talos-cluster-health.d.ts.map +1 -0
- package/dist/talos/talos-machine-config.d.ts +41 -0
- package/dist/talos/talos-machine-config.d.ts.map +1 -0
- package/dist/talos/talosctl.d.ts +29 -0
- package/dist/talos/talosctl.d.ts.map +1 -0
- package/dist/talos/values.d.ts +20 -0
- package/dist/talos/values.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/cloudflare/client.ts +76 -0
- package/src/cloudflare/index.ts +10 -0
- package/src/cloudflare/lock-rules.ts +60 -0
- package/src/cloudflare/providers.ts +23 -0
- package/src/cloudflare/r2-bucket-lock.ts +220 -0
- package/src/forgejo/branch-protection-form.ts +29 -0
- package/src/forgejo/branch-protection.ts +101 -0
- package/src/forgejo/client.ts +136 -0
- package/src/forgejo/index.ts +17 -0
- package/src/forgejo/org-actions-secrets.ts +99 -0
- package/src/forgejo/org-label.ts +100 -0
- package/src/forgejo/org-team.ts +105 -0
- package/src/forgejo/repo-webhook-form.ts +47 -0
- package/src/forgejo/repo-webhook.ts +118 -0
- package/src/forgejo/repository.ts +126 -0
- package/src/forgejo/resource.ts +171 -0
- package/src/forgejo/team-member.ts +97 -0
- package/src/forgejo/values.test.ts +48 -0
- package/src/forgejo/values.ts +61 -0
- package/src/index.ts +18 -0
- package/src/openbao/auth-role-form.ts +112 -0
- package/src/openbao/auth-role.ts +101 -0
- package/src/openbao/bao-address.test.ts +89 -0
- package/src/openbao/bao-address.ts +97 -0
- package/src/openbao/bao-http-gate.test.ts +88 -0
- package/src/openbao/bao-http-unix.test.ts +70 -0
- package/src/openbao/bao-http.test.ts +161 -0
- package/src/openbao/bao-http.ts +185 -0
- package/src/openbao/bao-status.test.ts +114 -0
- package/src/openbao/bao-status.ts +137 -0
- package/src/openbao/cloudflare-group-scope.test.ts +103 -0
- package/src/openbao/cloudflare-group-scope.ts +83 -0
- package/src/openbao/cloudflare-parity-catalog.ts +104 -0
- package/src/openbao/cloudflare-parity-snapshot.ts +84 -0
- package/src/openbao/cloudflare-parity.ts +152 -0
- package/src/openbao/cloudflare-permission-groups-solve.test.ts +171 -0
- package/src/openbao/cloudflare-permission-groups-solve.ts +197 -0
- package/src/openbao/cloudflare-permission-groups.test.ts +369 -0
- package/src/openbao/cloudflare-permission-groups.ts +299 -0
- package/src/openbao/cloudflare-policy.ts +180 -0
- package/src/openbao/cloudflare-role-form.test.ts +177 -0
- package/src/openbao/cloudflare-role-form.ts +183 -0
- package/src/openbao/cloudflare-role-wire.test.ts +132 -0
- package/src/openbao/cloudflare-role-wire.ts +41 -0
- package/src/openbao/cloudflare-role.ts +189 -0
- package/src/openbao/cloudflare-roles-config.ts +230 -0
- package/src/openbao/cloudflare-roles-expand-bucket.test.ts +134 -0
- package/src/openbao/cloudflare-roles-expand.ts +231 -0
- package/src/openbao/digest.ts +26 -0
- package/src/openbao/fake-bao.ts +84 -0
- package/src/openbao/forgejo-bootstrap-parse.test.ts +76 -0
- package/src/openbao/forgejo-bootstrap-parse.ts +90 -0
- package/src/openbao/forgejo-bootstrap.ts +181 -0
- package/src/openbao/index.ts +17 -0
- package/src/openbao/mount-form.ts +147 -0
- package/src/openbao/mount-wire.test.ts +108 -0
- package/src/openbao/mount-wire.ts +79 -0
- package/src/openbao/mount.ts +132 -0
- package/src/openbao/pki-role-form.ts +248 -0
- package/src/openbao/pki-role.ts +202 -0
- package/src/openbao/policy-assembly.test.ts +28 -0
- package/src/openbao/policy-assembly.ts +13 -0
- package/src/openbao/policy-wire.test.ts +90 -0
- package/src/openbao/policy-wire.ts +53 -0
- package/src/openbao/policy.ts +212 -0
- package/src/openbao/proxmox-role-form.ts +174 -0
- package/src/openbao/proxmox-role.ts +214 -0
- package/src/openbao/ssh-role-form.ts +248 -0
- package/src/openbao/ssh-role.ts +240 -0
- package/src/proxmox/acl.ts +267 -0
- package/src/proxmox/api-token-form.ts +122 -0
- package/src/proxmox/api-token.ts +222 -0
- package/src/proxmox/backup-job.ts +213 -0
- package/src/proxmox/ceph-daemon-form.ts +84 -0
- package/src/proxmox/ceph-daemon.ts +243 -0
- package/src/proxmox/ceph-flag.ts +249 -0
- package/src/proxmox/ceph-fs-wire.ts +246 -0
- package/src/proxmox/ceph-fs.ts +249 -0
- package/src/proxmox/ceph-osd-tree.ts +193 -0
- package/src/proxmox/ceph-osd-write.ts +76 -0
- package/src/proxmox/ceph-osd.ts +244 -0
- package/src/proxmox/ceph-pool-form.ts +249 -0
- package/src/proxmox/ceph-pool-settle.ts +70 -0
- package/src/proxmox/ceph-pool.ts +212 -0
- package/src/proxmox/client.test.ts +185 -0
- package/src/proxmox/client.ts +240 -0
- package/src/proxmox/credentials.test.ts +144 -0
- package/src/proxmox/credentials.ts +254 -0
- package/src/proxmox/firewall-alias-form.ts +112 -0
- package/src/proxmox/firewall-alias.ts +208 -0
- package/src/proxmox/generated/pbs.ts +461 -0
- package/src/proxmox/generated/pve.ts +7344 -0
- package/src/proxmox/group.ts +199 -0
- package/src/proxmox/ha-resource.ts +169 -0
- package/src/proxmox/ha-rule-form.ts +185 -0
- package/src/proxmox/ha-rule.ts +240 -0
- package/src/proxmox/index.ts +42 -0
- package/src/proxmox/lease-cache.test.ts +163 -0
- package/src/proxmox/lease-cache.ts +131 -0
- package/src/proxmox/lxc.ts +117 -0
- package/src/proxmox/members.test.ts +54 -0
- package/src/proxmox/members.ts +174 -0
- package/src/proxmox/metric-server-form.ts +128 -0
- package/src/proxmox/metric-server-otel.ts +99 -0
- package/src/proxmox/metric-server.ts +214 -0
- package/src/proxmox/network-apply-read.ts +228 -0
- package/src/proxmox/network-apply.ts +244 -0
- package/src/proxmox/node-network-form.ts +213 -0
- package/src/proxmox/node-network.ts +243 -0
- package/src/proxmox/notification-target-form.ts +104 -0
- package/src/proxmox/notification-target.ts +222 -0
- package/src/proxmox/pbs-datastore-form.ts +248 -0
- package/src/proxmox/pbs-datastore-guard.ts +105 -0
- package/src/proxmox/pbs-datastore.ts +271 -0
- package/src/proxmox/pbs-prune-job-form.ts +208 -0
- package/src/proxmox/pbs-prune-job.ts +227 -0
- package/src/proxmox/pbs-sync-job-form.ts +234 -0
- package/src/proxmox/pbs-sync-job.ts +258 -0
- package/src/proxmox/pbs-verify-job-form.ts +131 -0
- package/src/proxmox/pbs-verify-job.ts +227 -0
- package/src/proxmox/pool.test.ts +42 -0
- package/src/proxmox/pool.ts +80 -0
- package/src/proxmox/qemu.ts +89 -0
- package/src/proxmox/replication-job-form.ts +89 -0
- package/src/proxmox/replication-job.ts +248 -0
- package/src/proxmox/resource.ts +257 -0
- package/src/proxmox/role.ts +161 -0
- package/src/proxmox/sdn-apply-read.test.ts +152 -0
- package/src/proxmox/sdn-apply-read.ts +189 -0
- package/src/proxmox/sdn-apply.ts +154 -0
- package/src/proxmox/sdn-subnet-form.ts +156 -0
- package/src/proxmox/sdn-subnet.ts +238 -0
- package/src/proxmox/sdn-vnet.ts +228 -0
- package/src/proxmox/sdn-zone.ts +246 -0
- package/src/proxmox/storage.ts +245 -0
- package/src/proxmox/user.ts +214 -0
- package/src/proxmox/values.test.ts +193 -0
- package/src/proxmox/values.ts +203 -0
- package/src/proxmox/zfs-pool-write.ts +130 -0
- package/src/proxmox/zfs-pool.ts +253 -0
- package/src/talos/credentials.ts +136 -0
- package/src/talos/index.ts +14 -0
- package/src/talos/kubeconfig.ts +170 -0
- package/src/talos/resource.ts +31 -0
- package/src/talos/talos-bootstrap.ts +109 -0
- package/src/talos/talos-cluster-health.ts +119 -0
- package/src/talos/talos-machine-config.ts +146 -0
- package/src/talos/talosctl.ts +94 -0
- package/src/talos/values.test.ts +80 -0
- package/src/talos/values.ts +70 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.ZfsPool` — a zpool built on one node's physical disks, declared.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ A CREATE HERE WIPES THE DISKS IT IS GIVEN AND THERE IS NO UNDO. `POST /nodes/{node}/disks/zfs`
|
|
5
|
+
* runs `zpool create` on the block devices named in `devices`; whatever was on them is gone the
|
|
6
|
+
* moment the worker runs. Every other decision in this file follows from that one sentence, and
|
|
7
|
+
* each of them is written out below rather than left to be inferred.
|
|
8
|
+
*
|
|
9
|
+
* ⛔ DESTROY IS REFUSED. `delete` MAKES NO API CALL — removing this resource from a stack leaves
|
|
10
|
+
* the zpool and its data exactly where they are, and Alchemy simply forgets about them. The
|
|
11
|
+
* asymmetry is the whole argument: an ORPHANED pool is recoverable (declare it again, or destroy
|
|
12
|
+
* it by hand once a human has looked at which disks are in it), a DESTROYED pool is not. Alchemy
|
|
13
|
+
* deletes for reasons that have nothing to do with anybody intending it — a line removed from a
|
|
14
|
+
* stack file, a resource renamed so the old id is dropped, a whole stack torn down by the wrong
|
|
15
|
+
* command, a replace triggered by a prop nobody meant to touch. Handing `zpool destroy` to any
|
|
16
|
+
* of those is not a feature. MEASURED in the node's own source: the DELETE handler forks
|
|
17
|
+
* `zfsremove`, which runs `zpool destroy <name>` unconditionally, and with `cleanup-disks` then
|
|
18
|
+
* calls `wipe_blockdev` on every member. Neither flag is offered as a prop, because there is no
|
|
19
|
+
* call here for them to reach.
|
|
20
|
+
* ★ THE OPERATOR'S PATH IS `pvesh delete /nodes/<node>/disks/zfs/<name>`, typed by a person who
|
|
21
|
+
* has just looked at the pool. Do not "complete the CRUD" by wiring `delete` to `ops.destroy`:
|
|
22
|
+
* that one line turns a text edit into an unrecoverable one.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ THERE IS NO PUT ON THIS FAMILY AT ALL. MEASURED from the cluster's own published schema
|
|
25
|
+
* (`/usr/share/pve-docs/api-viewer/apidoc.js`, read on n2 2026-09-13): `/nodes/{node}/disks/zfs`
|
|
26
|
+
* has GET and POST, `/nodes/{node}/disks/zfs/{name}` has GET and DELETE, and that is the whole
|
|
27
|
+
* surface. So `updateForm` is omitted — which, per `resource.ts`, makes any `matches` = false a
|
|
28
|
+
* REPLACE. A replace is a delete followed by a create, and on this family the delete is the
|
|
29
|
+
* thing above. Read the ⛔ on `matches` before adding anything to it.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ NOT ONE CREATE PARAMETER COMES BACK ON READ, SO `matches` COMPARES NOTHING. MEASURED against
|
|
32
|
+
* the live cluster: `GET /nodes/n2/disks/zfs/rpool` answers exactly
|
|
33
|
+
* `{action, children, errors, leaf, name, scan, state, status}` — no `ashift`, no `compression`,
|
|
34
|
+
* no `raidlevel`, no `devices`, no `add_storage`. Every create parameter is write-only. There is
|
|
35
|
+
* therefore no field a declaration and a live pool can both be asked about, and a `matches` that
|
|
36
|
+
* invented one would plan a replace — i.e. a `zpool destroy` — over a difference it could never
|
|
37
|
+
* verify in the first place.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ AND WHAT THE INDEX RETURNS IS LIVE TELEMETRY, NOT CONFIGURATION. `GET /nodes/n2/disks/zfs`
|
|
40
|
+
* gives `alloc`, `free`, `frag`, `dedup`, `size`, `health`. MEASURED: two reads of n2 seconds
|
|
41
|
+
* apart returned `alloc` 14474944512 then 14472740864 — it moves on its own, with no declaration
|
|
42
|
+
* anywhere near it. `scan` in the detail read moves the same way (it carries the last scrub), and
|
|
43
|
+
* `status`/`action` appear and vanish as ZFS feature flags and faults come and go. None of them
|
|
44
|
+
* is compared, and the three churning strings are kept out of the state store entirely rather
|
|
45
|
+
* than rewriting this resource's row every time somebody writes a file.
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ TWO HANDLERS ARE OVERRIDDEN AND THE OTHER THREE COME FROM THE FACTORY — see the ★ above
|
|
48
|
+
* `handlers`. `delete` is the ⛔ above. `reconcile` is because the create is a FORKED WORKER, so
|
|
49
|
+
* the factory's immediate read-back would report a successful create as a failure; the measured
|
|
50
|
+
* detail is in the ⛔ on `createPool` in zfs-pool-create.ts.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ PRIVILEGES, FROM THE SCHEMA. Read and diff need `Sys.Audit` on `/` — both GETs check it.
|
|
53
|
+
* Reconcile needs `Sys.Modify` on `/` for the POST. `Datastore.Allocate` on `/storage` is NOT
|
|
54
|
+
* needed, because `add_storage` is not offered — see the ⛔ on `ZfsPoolProps`. `delete` needs
|
|
55
|
+
* nothing at all, since it calls nothing.
|
|
56
|
+
*/
|
|
57
|
+
import { Resource } from 'alchemy';
|
|
58
|
+
import * as Provider from 'alchemy/Provider';
|
|
59
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
60
|
+
/** PVE's layouts. ⚠️ Each has a minimum disk count PVE enforces, and `raid10` needs an even one. */
|
|
61
|
+
export type ZfsRaidLevel = 'single' | 'mirror' | 'raid10' | 'raidz' | 'raidz2' | 'raidz3' | 'draid' | 'draid2' | 'draid3';
|
|
62
|
+
export type ZfsCompression = 'on' | 'off' | 'gzip' | 'lz4' | 'lzjb' | 'zle' | 'zstd';
|
|
63
|
+
/**
|
|
64
|
+
* ⛔ THERE IS NO `add_storage`, `cleanup-config` OR `cleanup-disks` PROP, AND THAT IS NOT AN
|
|
65
|
+
* OVERSIGHT. The last two belong to the DELETE this resource refuses to make. `add_storage=1`
|
|
66
|
+
* would have the POST build a `Proxmox.Storage`-shaped object this resource does not own, cannot
|
|
67
|
+
* see on any later read, and cannot remove — invisible to every diff forever, and a collision
|
|
68
|
+
* waiting for the day somebody also declares the storage properly. Declare a `Proxmox.Storage`
|
|
69
|
+
* with `type: 'zfspool'` and `locator: { pool: <this pool's name> }` instead, reading the `name`
|
|
70
|
+
* attribute below so Alchemy orders the storage after the pool.
|
|
71
|
+
*/
|
|
72
|
+
export interface ZfsPoolProps extends WithTarget {
|
|
73
|
+
/**
|
|
74
|
+
* ⛔ A ZPOOL IS NODE-LOCAL AND THIS IS HALF ITS IDENTITY. Changing it moves nothing: `path` then
|
|
75
|
+
* points at a different node, the read answers absent, and reconcile tries to CREATE the pool
|
|
76
|
+
* over there — on whatever `/dev/...` happens to answer to those names on that machine.
|
|
77
|
+
* Declare a separate resource per node instead.
|
|
78
|
+
*/
|
|
79
|
+
node: string;
|
|
80
|
+
/** The zpool name, and PVE's key for it on this node. ⚠️ Changing it creates a SECOND pool. */
|
|
81
|
+
name: string;
|
|
82
|
+
/**
|
|
83
|
+
* The block devices to build it on.
|
|
84
|
+
*
|
|
85
|
+
* ⛔ NAME THEM BY `/dev/disk/by-id/...`, NOT `/dev/sdb`. Kernel names are not stable across
|
|
86
|
+
* reboots; a by-id link is. MEASURED in the node's source: PVE resolves what it is handed to a
|
|
87
|
+
* by-id link only AFTER it has decided the device is free, so a stale `/dev/sdb` in a stack
|
|
88
|
+
* file is checked against, and then wiped, whichever disk currently answers to that name.
|
|
89
|
+
* ⚠️ ORDER IS MEANING FOR `raid10` AND IS PRESERVED — PVE walks the list two at a time and makes
|
|
90
|
+
* each pair a mirror. This is why the form uses a plain join and NOT `csv()` from values.ts:
|
|
91
|
+
* `csv` sorts, and sorting this list silently re-pairs the mirrors into a different pool.
|
|
92
|
+
* ⚠️ PVE's only guard is `assert_disk_unused`, i.e. its own `disk_is_used` over the same `used`
|
|
93
|
+
* column the Disks view shows. A blank disk it considers free is wiped with no confirmation.
|
|
94
|
+
*/
|
|
95
|
+
devices: readonly string[];
|
|
96
|
+
raidlevel: ZfsRaidLevel;
|
|
97
|
+
/**
|
|
98
|
+
* Sector size exponent, 9–16, PVE default 12.
|
|
99
|
+
*
|
|
100
|
+
* ⛔ WRITE-ONLY AND IMMUTABLE FOR THE LIFE OF THE POOL. It is consumed by `zpool create -o
|
|
101
|
+
* ashift=` and never returned by any read on this family, so changing it later plans as `noop`
|
|
102
|
+
* and changes nothing. A different ashift means destroying and rebuilding the pool, by hand,
|
|
103
|
+
* with the data moved off first.
|
|
104
|
+
*/
|
|
105
|
+
ashift?: number;
|
|
106
|
+
/**
|
|
107
|
+
* ⚠️ Write-only in the same way as `ashift`: applied by `zfs set compression=` at create time and
|
|
108
|
+
* never read back, so a later change plans `noop`. Change it with `zfs set` on the node.
|
|
109
|
+
*/
|
|
110
|
+
compression?: ZfsCompression;
|
|
111
|
+
/**
|
|
112
|
+
* `data=<integer>,spares=<integer>`. ⚠️ THE WIRE NAME IS KEPT, HYPHEN AND ALL, for the reason
|
|
113
|
+
* `storage.ts` keeps `prune-backups`: a second spelling is a second name for one thing.
|
|
114
|
+
* ⚠️ PVE refuses it outright unless `raidlevel` is one of the `draid*` levels.
|
|
115
|
+
*/
|
|
116
|
+
'draid-config'?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* ⚠️ EVERYTHING HERE IS REPORTED, NOTHING HERE IS COMPARED — see the ⛔ on `matches`. These exist
|
|
120
|
+
* so a plan and the state store can say what the pool actually is, on a family where the
|
|
121
|
+
* declaration and the live object share no comparable field at all.
|
|
122
|
+
*/
|
|
123
|
+
export interface ZfsPoolAttributes {
|
|
124
|
+
/** ★ The value a `Proxmox.Storage` should read to order itself after this pool. */
|
|
125
|
+
name: string;
|
|
126
|
+
node: string;
|
|
127
|
+
/** `ONLINE` | `DEGRADED` | `FAULTED` | … Health, not configuration. */
|
|
128
|
+
state: string;
|
|
129
|
+
/** ZFS's own error summary, e.g. `No known data errors`. */
|
|
130
|
+
errors: string;
|
|
131
|
+
/**
|
|
132
|
+
* The leaf devices ZFS reports, in vdev order, comma-joined.
|
|
133
|
+
*
|
|
134
|
+
* ⚠️ THIS IS WHAT ZFS RESOLVED, NOT WHAT WAS DECLARED, and the two normally differ: PVE rewrites
|
|
135
|
+
* a `/dev/sdb` into a by-id link before creating, and ZFS reports partition paths (`…-part3`)
|
|
136
|
+
* for a pool built on partitions — MEASURED, that is exactly what n2's `rpool` reports. Never
|
|
137
|
+
* compare it with `props.devices`.
|
|
138
|
+
*/
|
|
139
|
+
devices: string;
|
|
140
|
+
}
|
|
141
|
+
export interface ProxmoxZfsPool extends Resource<'Proxmox.ZfsPool', ZfsPoolProps, ZfsPoolAttributes, never, PveRequirements> {
|
|
142
|
+
}
|
|
143
|
+
/** ★ `retain` by default — a pool holding datasets cannot be rebuilt. See the ★ in resource.ts. */
|
|
144
|
+
export declare const ProxmoxZfsPool: import("alchemy").ResourceClass<ProxmoxZfsPool>;
|
|
145
|
+
export declare const ProxmoxZfsPoolProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxZfsPool>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
146
|
+
//# sourceMappingURL=zfs-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zfs-pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,UAAU,EAGhB,MAAM,eAAe,CAAC;AAIvB,oGAAoG;AACpG,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,SAAS,EAAE,YAAY,CAAC;IACxB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,mGAAmG;AACnG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AA6EH,eAAO,MAAM,sBAAsB,oIACoD,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import * as ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';
|
|
3
|
+
/** Where credentials come from. HomeFlare-specific mount names live in the stack, not here. */
|
|
4
|
+
export type TalosTarget = {
|
|
5
|
+
/** OpenBao KV mount holding Talos material, e.g. `talos-tb4`. */
|
|
6
|
+
readonly mount: string;
|
|
7
|
+
/** Logical cluster name — safe to persist and log. */
|
|
8
|
+
readonly cluster: string;
|
|
9
|
+
/**
|
|
10
|
+
* Path under the mount for the talosconfig YAML.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ DEFAULT IS REASONED. The first real deploy will confirm or correct the path; a wrong path
|
|
13
|
+
* fails closed with a bao error rather than writing an empty talosconfig.
|
|
14
|
+
*/
|
|
15
|
+
readonly talosconfigKey?: string;
|
|
16
|
+
};
|
|
17
|
+
export type TalosCredential = {
|
|
18
|
+
/** Absolute path to a temp talosconfig. ⛔ NEVER PERSIST, NEVER LOG CONTENTS. */
|
|
19
|
+
readonly talosconfigPath: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Mint one talosconfig file for `target`.
|
|
23
|
+
*
|
|
24
|
+
* ★ SHELLS OUT TO `bao` RATHER THAN SPEAKING THE HTTP API — same reasoning as proxmox credentials.
|
|
25
|
+
* The estate's BAO_ADDR / BAO_NAMESPACE / approle contract already lives in that binary.
|
|
26
|
+
*/
|
|
27
|
+
export declare const mintTalosconfig: (target: TalosTarget) => Effect.Effect<{
|
|
28
|
+
talosconfigPath: string;
|
|
29
|
+
}, Error | import("effect/PlatformError").PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
30
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/talos/credentials.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,mBAAmB,MAAM,6CAA6C,CAAC;AAEnF,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG;IACxB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,WAAY,WAAW;;iGAsFhD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talos providers for Alchemy.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ THIS BARREL IS THE PUBLIC API, AND IT IS DELIBERATELY SMALLER THAN THE DIRECTORY.
|
|
5
|
+
* These are the symbols a real stack consumes; the rest of the files are internals a
|
|
6
|
+
* provider needs but a consumer should not depend on. An `export *` here would publish
|
|
7
|
+
* every helper as API and make the next refactor a breaking change.
|
|
8
|
+
* ★ Anything unlisted is still reachable by path if you genuinely need it — that is a
|
|
9
|
+
* deliberate, visible act rather than an accident of barrelling.
|
|
10
|
+
*/
|
|
11
|
+
export { TalosKubeconfigProvider } from './kubeconfig.ts';
|
|
12
|
+
export { TalosBootstrapProvider } from './talos-bootstrap.ts';
|
|
13
|
+
export { TalosClusterHealthProvider } from './talos-cluster-health.ts';
|
|
14
|
+
export { TalosMachineConfigProvider } from './talos-machine-config.ts';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/talos/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/talos/kubeconfig.ts
|
|
2
|
+
import { Resource } from "alchemy";
|
|
3
|
+
import { isResolved } from "alchemy/Diff";
|
|
4
|
+
import * as Provider from "alchemy/Provider";
|
|
5
|
+
import * as Effect3 from "effect/Effect";
|
|
6
|
+
|
|
7
|
+
// src/talos/credentials.ts
|
|
8
|
+
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
12
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
13
|
+
var defaultKey = (target) => target.talosconfigKey ?? "data/talosconfig";
|
|
14
|
+
var mintTalosconfig = (target) => Effect.scoped(Effect.gen(function* () {
|
|
15
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
16
|
+
const key = defaultKey(target);
|
|
17
|
+
const result = yield* ChildProcess.make("bao", ["kv", "get", "-format=json", `${target.mount}/${key}`], { detached: false, extendEnv: true, stderr: "pipe", stdin: "ignore", stdout: "pipe" }).pipe(spawner.spawn, Effect.flatMap((child) => Effect.all({
|
|
18
|
+
exitCode: child.exitCode,
|
|
19
|
+
stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim())),
|
|
20
|
+
stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim()))
|
|
21
|
+
}, { concurrency: "unbounded" })), Effect.scoped);
|
|
22
|
+
if (result.exitCode !== 0) {
|
|
23
|
+
return yield* Effect.fail(new Error(`bao kv get ${target.mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
|
|
24
|
+
}
|
|
25
|
+
const parsed = JSON.parse(result.stdout);
|
|
26
|
+
const data = parsed.data?.data;
|
|
27
|
+
const raw = data?.["talosconfig"] ?? data?.["config"];
|
|
28
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
29
|
+
return yield* Effect.fail(new Error(`${target.mount}/${key} returned no talosconfig field. Expected a KV key named 'talosconfig' or 'config' — never store the value in Alchemy props.`));
|
|
30
|
+
}
|
|
31
|
+
const talosconfigPath = `${Bun.env["TMPDIR"] ?? "/tmp"}/hf-talos-${target.cluster}-${Bun.randomUUIDv7()}.yaml`;
|
|
32
|
+
yield* Effect.try({
|
|
33
|
+
try: () => {
|
|
34
|
+
const fd = openSync(talosconfigPath, "wx", 384);
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(fd, raw);
|
|
37
|
+
} finally {
|
|
38
|
+
closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
catch: (cause) => new Error(`writing temp talosconfig: ${String(cause)}`)
|
|
42
|
+
});
|
|
43
|
+
yield* Effect.addFinalizer(() => Effect.tryPromise({
|
|
44
|
+
try: () => Bun.file(talosconfigPath).delete(),
|
|
45
|
+
catch: () => {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}).pipe(Effect.orElseSucceed(() => {
|
|
49
|
+
return;
|
|
50
|
+
})));
|
|
51
|
+
return { talosconfigPath };
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// src/talos/talosctl.ts
|
|
55
|
+
import * as Effect2 from "effect/Effect";
|
|
56
|
+
import * as Stream2 from "effect/Stream";
|
|
57
|
+
import * as ChildProcess2 from "effect/unstable/process/ChildProcess";
|
|
58
|
+
import * as ChildProcessSpawner3 from "effect/unstable/process/ChildProcessSpawner";
|
|
59
|
+
|
|
60
|
+
class TalosError extends Error {
|
|
61
|
+
command;
|
|
62
|
+
exitCode;
|
|
63
|
+
constructor(command, exitCode, detail) {
|
|
64
|
+
super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);
|
|
65
|
+
this.command = command;
|
|
66
|
+
this.exitCode = exitCode;
|
|
67
|
+
this.name = "TalosError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
var talosctl = (args, options) => Effect2.gen(function* () {
|
|
71
|
+
const spawner = yield* ChildProcessSpawner3.ChildProcessSpawner;
|
|
72
|
+
const argv = [
|
|
73
|
+
...args,
|
|
74
|
+
"--talosconfig",
|
|
75
|
+
options.talosconfigPath,
|
|
76
|
+
...options.nodes === undefined ? [] : ["--nodes", options.nodes.join(",")],
|
|
77
|
+
...options.endpoints === undefined ? [] : ["--endpoints", options.endpoints.join(",")]
|
|
78
|
+
];
|
|
79
|
+
const result = yield* ChildProcess2.make("talosctl", argv, {
|
|
80
|
+
detached: false,
|
|
81
|
+
extendEnv: true,
|
|
82
|
+
stderr: "pipe",
|
|
83
|
+
stdin: "ignore",
|
|
84
|
+
stdout: "pipe"
|
|
85
|
+
}).pipe(spawner.spawn, Effect2.flatMap((child) => Effect2.all({
|
|
86
|
+
exitCode: child.exitCode,
|
|
87
|
+
stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim())),
|
|
88
|
+
stdout: child.stdout.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
|
|
89
|
+
}, { concurrency: "unbounded" })), Effect2.scoped);
|
|
90
|
+
const command = args.join(" ");
|
|
91
|
+
if (result.exitCode !== 0) {
|
|
92
|
+
return yield* Effect2.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300)));
|
|
93
|
+
}
|
|
94
|
+
return result.stdout;
|
|
95
|
+
});
|
|
96
|
+
var talosctlOrAlready = (args, options) => talosctl(args, options).pipe(Effect2.catchIf((cause) => cause instanceof TalosError && cause.exitCode !== 0 && /already|exists|bootstrapped/i.test(cause.message), () => Effect2.succeed("")));
|
|
97
|
+
|
|
98
|
+
// src/talos/values.ts
|
|
99
|
+
import { createHash } from "node:crypto";
|
|
100
|
+
var canonicalText = (text) => text.trimEnd();
|
|
101
|
+
var sha256 = (text) => createHash("sha256").update(canonicalText(text)).digest("hex");
|
|
102
|
+
var configDigest = (text) => sha256(text);
|
|
103
|
+
var kubeconfigMetadata = (yamlText, contextName) => {
|
|
104
|
+
let doc;
|
|
105
|
+
try {
|
|
106
|
+
doc = Bun.YAML.parse(yamlText);
|
|
107
|
+
} catch {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;
|
|
111
|
+
const clusterName = ctx?.cluster;
|
|
112
|
+
const userName = ctx?.user;
|
|
113
|
+
if (clusterName === undefined || userName === undefined)
|
|
114
|
+
return;
|
|
115
|
+
const cluster = doc.clusters?.find((row) => row.name === clusterName)?.cluster;
|
|
116
|
+
const user = doc.users?.find((row) => row.name === userName)?.user;
|
|
117
|
+
const endpoint = cluster?.server;
|
|
118
|
+
const ca = cluster?.["certificate-authority-data"];
|
|
119
|
+
const cert = user?.["client-certificate-data"] ?? user?.["client-key-data"];
|
|
120
|
+
if (endpoint === undefined || ca === undefined || cert === undefined)
|
|
121
|
+
return;
|
|
122
|
+
return {
|
|
123
|
+
caFingerprint: sha256(ca),
|
|
124
|
+
clientFingerprint: sha256(cert),
|
|
125
|
+
endpoint
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var resolveConfigPath = (stackDir, configFile) => configFile.startsWith("/") ? configFile : `${stackDir}/${configFile}`;
|
|
129
|
+
|
|
130
|
+
// src/talos/kubeconfig.ts
|
|
131
|
+
var TalosKubeconfig = Resource("Talos.Kubeconfig");
|
|
132
|
+
var generation = (meta) => sha256(`${meta.context}
|
|
133
|
+
${meta.endpoint}
|
|
134
|
+
${meta.caFingerprint}
|
|
135
|
+
${meta.clientFingerprint}`);
|
|
136
|
+
var toConnection = (props) => ({
|
|
137
|
+
auth: { context: props.context, kind: "kubeconfig", path: props.runtimePath }
|
|
138
|
+
});
|
|
139
|
+
var readFileMeta = (props) => Effect3.gen(function* () {
|
|
140
|
+
const text = yield* Effect3.tryPromise({
|
|
141
|
+
try: () => Bun.file(props.runtimePath).text(),
|
|
142
|
+
catch: (cause) => new Error(String(cause))
|
|
143
|
+
}).pipe(Effect3.orElseSucceed(() => {
|
|
144
|
+
return;
|
|
145
|
+
}));
|
|
146
|
+
if (text === undefined)
|
|
147
|
+
return;
|
|
148
|
+
const meta = kubeconfigMetadata(text, props.context);
|
|
149
|
+
if (meta === undefined)
|
|
150
|
+
return;
|
|
151
|
+
return {
|
|
152
|
+
certificateAuthorityFingerprint: meta.caFingerprint,
|
|
153
|
+
clientCertificateFingerprint: meta.clientFingerprint,
|
|
154
|
+
connection: toConnection(props),
|
|
155
|
+
context: props.context,
|
|
156
|
+
credentialGeneration: generation({ ...meta, context: props.context }),
|
|
157
|
+
endpoint: meta.endpoint
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
var read = (props) => readFileMeta(props).pipe(Effect3.map((meta) => meta === undefined ? {
|
|
161
|
+
certificateAuthorityFingerprint: "",
|
|
162
|
+
clientCertificateFingerprint: "",
|
|
163
|
+
connection: toConnection(props),
|
|
164
|
+
context: props.context,
|
|
165
|
+
credentialGeneration: "",
|
|
166
|
+
endpoint: ""
|
|
167
|
+
} : meta));
|
|
168
|
+
var diff = (news, output) => Effect3.gen(function* () {
|
|
169
|
+
if (output === undefined || !isResolved(news))
|
|
170
|
+
return;
|
|
171
|
+
const live = yield* read(news);
|
|
172
|
+
if (live.credentialGeneration !== "" && output.credentialGeneration === live.credentialGeneration && output.context === live.context && output.endpoint === live.endpoint) {
|
|
173
|
+
return { action: "noop" };
|
|
174
|
+
}
|
|
175
|
+
return { action: "update" };
|
|
176
|
+
});
|
|
177
|
+
var reconcile = (props) => Effect3.gen(function* () {
|
|
178
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
179
|
+
yield* talosctl([
|
|
180
|
+
"kubeconfig",
|
|
181
|
+
props.runtimePath,
|
|
182
|
+
"--merge=false",
|
|
183
|
+
"--force",
|
|
184
|
+
"--force-context-name",
|
|
185
|
+
props.context
|
|
186
|
+
], { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
|
|
187
|
+
const meta = yield* readFileMeta(props);
|
|
188
|
+
if (meta === undefined) {
|
|
189
|
+
return yield* Effect3.die(new Error(`${props.runtimePath}: talosctl kubeconfig returned no error but the file is missing ` + "or unparsable. Read back rather than trusting the exit code alone."));
|
|
190
|
+
}
|
|
191
|
+
return meta;
|
|
192
|
+
});
|
|
193
|
+
var handlers = {
|
|
194
|
+
delete: () => Effect3.void,
|
|
195
|
+
diff: ({
|
|
196
|
+
news,
|
|
197
|
+
output
|
|
198
|
+
}) => diff(news, output),
|
|
199
|
+
list: () => Effect3.succeed([]),
|
|
200
|
+
read: ({ olds }) => read(olds),
|
|
201
|
+
reconcile: ({ news }) => reconcile(news)
|
|
202
|
+
};
|
|
203
|
+
var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect3.succeed(TalosKubeconfig.Provider.of(handlers)));
|
|
204
|
+
// src/talos/talos-bootstrap.ts
|
|
205
|
+
import { Resource as Resource2 } from "alchemy";
|
|
206
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
207
|
+
import * as Provider2 from "alchemy/Provider";
|
|
208
|
+
import * as Effect4 from "effect/Effect";
|
|
209
|
+
var TalosBootstrap = Resource2("Talos.Bootstrap", {
|
|
210
|
+
defaultRemovalPolicy: "retain"
|
|
211
|
+
});
|
|
212
|
+
var isBootstrapped = (props, talosconfigPath) => talosctl(["get", "etcdmembers", "-o", "json"], {
|
|
213
|
+
nodes: [props.node],
|
|
214
|
+
talosconfigPath
|
|
215
|
+
}).pipe(Effect4.map((text) => text.includes('"id"') || text.includes('"member"')), Effect4.orElseSucceed(() => false));
|
|
216
|
+
var read2 = (props) => Effect4.gen(function* () {
|
|
217
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
218
|
+
const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
219
|
+
return { bootstrapped, node: props.node };
|
|
220
|
+
});
|
|
221
|
+
var diff2 = (news, output) => Effect4.gen(function* () {
|
|
222
|
+
if (output === undefined || !isResolved2(news))
|
|
223
|
+
return;
|
|
224
|
+
const live = yield* read2(news);
|
|
225
|
+
if (live.bootstrapped)
|
|
226
|
+
return { action: "noop" };
|
|
227
|
+
return { action: "update" };
|
|
228
|
+
});
|
|
229
|
+
var reconcile2 = (props) => Effect4.gen(function* () {
|
|
230
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
231
|
+
const before = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
232
|
+
if (!before) {
|
|
233
|
+
yield* talosctlOrAlready(["bootstrap"], {
|
|
234
|
+
nodes: [props.node],
|
|
235
|
+
talosconfigPath: credential.talosconfigPath
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const after = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
239
|
+
if (!after) {
|
|
240
|
+
return yield* Effect4.die(new Error(`${props.node}: bootstrap returned no error but etcd members are still absent. ` + "Read back rather than trusting the exit code alone."));
|
|
241
|
+
}
|
|
242
|
+
return { bootstrapped: true, node: props.node };
|
|
243
|
+
});
|
|
244
|
+
var handlers2 = {
|
|
245
|
+
delete: () => Effect4.void,
|
|
246
|
+
diff: ({
|
|
247
|
+
news,
|
|
248
|
+
output
|
|
249
|
+
}) => diff2(news, output),
|
|
250
|
+
list: () => Effect4.succeed([]),
|
|
251
|
+
read: ({ olds }) => read2(olds),
|
|
252
|
+
reconcile: ({ news }) => reconcile2(news)
|
|
253
|
+
};
|
|
254
|
+
var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect4.succeed(TalosBootstrap.Provider.of(handlers2)));
|
|
255
|
+
// src/talos/talos-cluster-health.ts
|
|
256
|
+
import { Resource as Resource3 } from "alchemy";
|
|
257
|
+
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
258
|
+
import * as Provider3 from "alchemy/Provider";
|
|
259
|
+
import * as Effect5 from "effect/Effect";
|
|
260
|
+
var TalosClusterHealth = Resource3("Talos.ClusterHealth");
|
|
261
|
+
var nodeCsv = (nodes) => nodes === undefined || nodes.length === 0 ? "" : nodes.join(",");
|
|
262
|
+
var healthArgs = (props, waitTimeout) => {
|
|
263
|
+
const args = ["health", "--wait-timeout", waitTimeout];
|
|
264
|
+
const cp = nodeCsv(props.controlPlaneNodes);
|
|
265
|
+
const workers = nodeCsv(props.workerNodes);
|
|
266
|
+
if (cp !== "")
|
|
267
|
+
args.push("--control-plane-nodes", cp);
|
|
268
|
+
if (workers !== "")
|
|
269
|
+
args.push("--worker-nodes", workers);
|
|
270
|
+
return args;
|
|
271
|
+
};
|
|
272
|
+
var check = (props, waitTimeout) => Effect5.gen(function* () {
|
|
273
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
274
|
+
yield* talosctl(healthArgs(props, waitTimeout), {
|
|
275
|
+
nodes: [...props.controlPlaneNodes],
|
|
276
|
+
talosconfigPath: credential.talosconfigPath
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
|
|
280
|
+
healthy: true,
|
|
281
|
+
workerNodes: nodeCsv(props.workerNodes)
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
var read3 = (props) => check(props, "5s").pipe(Effect5.orElseSucceed(() => ({
|
|
285
|
+
controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
|
|
286
|
+
healthy: false,
|
|
287
|
+
workerNodes: nodeCsv(props.workerNodes)
|
|
288
|
+
})));
|
|
289
|
+
var diff3 = (news, output) => Effect5.gen(function* () {
|
|
290
|
+
if (output === undefined || !isResolved3(news))
|
|
291
|
+
return;
|
|
292
|
+
const live = yield* read3(news);
|
|
293
|
+
if (live.healthy)
|
|
294
|
+
return { action: "noop" };
|
|
295
|
+
return { action: "update" };
|
|
296
|
+
});
|
|
297
|
+
var reconcile3 = (props) => check(props, props.waitTimeout ?? "20m0s").pipe(Effect5.mapError((cause) => new Error(`Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` + `${props.waitTimeout ?? "20m0s"} — ${String(cause)}`)));
|
|
298
|
+
var handlers3 = {
|
|
299
|
+
delete: () => Effect5.void,
|
|
300
|
+
diff: ({
|
|
301
|
+
news,
|
|
302
|
+
output
|
|
303
|
+
}) => diff3(news, output),
|
|
304
|
+
list: () => Effect5.succeed([]),
|
|
305
|
+
read: ({ olds }) => read3(olds),
|
|
306
|
+
reconcile: ({ news }) => reconcile3(news)
|
|
307
|
+
};
|
|
308
|
+
var TalosClusterHealthProvider = () => Provider3.effect(TalosClusterHealth, Effect5.succeed(TalosClusterHealth.Provider.of(handlers3)));
|
|
309
|
+
// src/talos/talos-machine-config.ts
|
|
310
|
+
import { Resource as Resource4 } from "alchemy";
|
|
311
|
+
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
312
|
+
import * as Provider4 from "alchemy/Provider";
|
|
313
|
+
import * as Effect6 from "effect/Effect";
|
|
314
|
+
var STACK_DIR = new URL("..", import.meta.url).pathname;
|
|
315
|
+
var TalosMachineConfig = Resource4("Talos.MachineConfig");
|
|
316
|
+
var loadDigest = (props) => Effect6.gen(function* () {
|
|
317
|
+
const path = resolveConfigPath(STACK_DIR, props.configFile);
|
|
318
|
+
const text = yield* Effect6.tryPromise({
|
|
319
|
+
try: () => Bun.file(path).text(),
|
|
320
|
+
catch: (cause) => new Error(`reading ${props.configFile}: ${String(cause)}`)
|
|
321
|
+
});
|
|
322
|
+
return configDigest(text);
|
|
323
|
+
});
|
|
324
|
+
var liveDigest = (props, talosconfigPath) => talosctl(["get", "machineconfig", "-o", "yaml"], {
|
|
325
|
+
nodes: [props.node],
|
|
326
|
+
talosconfigPath
|
|
327
|
+
}).pipe(Effect6.map((yaml) => configDigest(yaml)), Effect6.orElseSucceed(() => {
|
|
328
|
+
return;
|
|
329
|
+
}));
|
|
330
|
+
var read4 = (props) => Effect6.gen(function* () {
|
|
331
|
+
const wanted = yield* loadDigest(props);
|
|
332
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
333
|
+
const live = yield* liveDigest(props, credential.talosconfigPath);
|
|
334
|
+
return {
|
|
335
|
+
configDigest: wanted,
|
|
336
|
+
converged: live === wanted,
|
|
337
|
+
mode: props.mode ?? "auto",
|
|
338
|
+
node: props.node
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
var diff4 = (news, output) => Effect6.gen(function* () {
|
|
342
|
+
if (output === undefined || !isResolved4(news))
|
|
343
|
+
return;
|
|
344
|
+
const live = yield* read4(news);
|
|
345
|
+
if (live.converged)
|
|
346
|
+
return { action: "noop" };
|
|
347
|
+
return { action: "update" };
|
|
348
|
+
});
|
|
349
|
+
var reconcile4 = (props) => Effect6.gen(function* () {
|
|
350
|
+
const path = resolveConfigPath(STACK_DIR, props.configFile);
|
|
351
|
+
const wanted = yield* loadDigest(props);
|
|
352
|
+
const credential = yield* mintTalosconfig(props.target);
|
|
353
|
+
const args = [
|
|
354
|
+
"apply-config",
|
|
355
|
+
"--file",
|
|
356
|
+
path,
|
|
357
|
+
"--mode",
|
|
358
|
+
props.mode ?? "auto",
|
|
359
|
+
...props.insecure === true ? ["--insecure"] : []
|
|
360
|
+
];
|
|
361
|
+
yield* talosctl(args, { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
|
|
362
|
+
const live = yield* liveDigest(props, credential.talosconfigPath);
|
|
363
|
+
if (live !== wanted) {
|
|
364
|
+
return yield* Effect6.die(new Error(`${props.node}: apply-config returned no error but machineconfig digest still differs. ` + "Read back rather than trusting the exit code alone."));
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
configDigest: wanted,
|
|
368
|
+
converged: true,
|
|
369
|
+
mode: props.mode ?? "auto",
|
|
370
|
+
node: props.node
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
var handlers4 = {
|
|
374
|
+
delete: () => Effect6.void,
|
|
375
|
+
diff: ({
|
|
376
|
+
news,
|
|
377
|
+
output
|
|
378
|
+
}) => diff4(news, output),
|
|
379
|
+
list: () => Effect6.succeed([]),
|
|
380
|
+
read: ({ olds }) => read4(olds),
|
|
381
|
+
reconcile: ({ news }) => reconcile4(news)
|
|
382
|
+
};
|
|
383
|
+
var TalosMachineConfigProvider = () => Provider4.effect(TalosMachineConfig, Effect6.succeed(TalosMachineConfig.Provider.of(handlers4)));
|
|
384
|
+
export {
|
|
385
|
+
TalosBootstrapProvider,
|
|
386
|
+
TalosClusterHealthProvider,
|
|
387
|
+
TalosKubeconfigProvider,
|
|
388
|
+
TalosMachineConfigProvider
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
//# debugId=6CD90391393B548364756E2164756E21
|
|
392
|
+
//# sourceMappingURL=index.js.map
|