@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,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/talos/kubeconfig.ts", "../src/talos/credentials.ts", "../src/talos/talosctl.ts", "../src/talos/values.ts", "../src/talos/talos-bootstrap.ts", "../src/talos/talos-cluster-health.ts", "../src/talos/talos-machine-config.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `Talos.Kubeconfig` — fetch admin kubeconfig so `Kubernetes.*` workloads can connect.\n *\n * ★ REASONED FROM talosctl kubeconfig (Talos v1.13 CLI reference): writes a kubeconfig file;\n * `--merge=false` keeps it isolated; `-f/--force` overwrites an existing file.\n *\n * ⛔ THE KUBECONFIG FILE CONTAINS A CLIENT CERTIFICATE — IT MUST NOT BE AN ATTRIBUTE. Alchemy\n * persists attributes unencrypted. This resource:\n * - writes the file to `runtimePath` at reconcile time (re-minted each deploy),\n * - persists ONLY public metadata + fingerprints,\n * - exposes a `connection` field referencing the runtime path for `Kubernetes.ClusterAdapter`.\n *\n * ⚠️ `runtimePath` IS PERSISTED AS A STRING, NOT AS FILE CONTENTS. The path is a contract between\n * this resource and the host filesystem — same pattern as pointing Kubernetes.KubeConfig at\n * `/opt/homeflare/...` rendered outside Postgres.\n */\nimport { Resource } from 'alchemy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { Connection } from 'alchemy/Kubernetes/Connection';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport { mintTalosconfig } from './credentials.ts';\nimport type { TalosRequirements, WithTarget } from './resource.ts';\nimport { talosctl } from './talosctl.ts';\nimport { kubeconfigMetadata, sha256 } from './values.ts';\n\nexport interface KubeconfigProps extends WithTarget {\n /** Node that serves `talosctl kubeconfig`. */\n node: string;\n /** Context name inside the generated kubeconfig — not secret. */\n context: string;\n /**\n * Host path where talosctl writes the kubeconfig each reconcile.\n *\n * ⛔ Must be outside Alchemy state and rotated by redeploy, never copied into attributes.\n */\n runtimePath: string;\n after?: readonly unknown[];\n}\n\nexport interface KubeconfigAttributes {\n context: string;\n endpoint: string;\n certificateAuthorityFingerprint: string;\n clientCertificateFingerprint: string;\n /** Digest of endpoint+context+fingerprints — detects credential rotation without storing PEM. */\n credentialGeneration: string;\n /**\n * Serializable `Kubernetes.Connection` for downstream workloads.\n *\n * ★ auth.kind `kubeconfig` is the stock ClusterAdapter — provider-roadmap.md names this seam.\n * Only `path` and `context` appear here; the adapter reads credentials from disk at call time.\n */\n connection: Connection;\n}\n\nexport interface TalosKubeconfig extends Resource<\n 'Talos.Kubeconfig',\n KubeconfigProps,\n KubeconfigAttributes,\n never,\n TalosRequirements\n> {}\n\nexport const TalosKubeconfig = Resource<TalosKubeconfig>('Talos.Kubeconfig');\n\nconst generation = (meta: {\n endpoint: string;\n caFingerprint: string;\n clientFingerprint: string;\n context: string;\n}) => sha256(`${meta.context}\\n${meta.endpoint}\\n${meta.caFingerprint}\\n${meta.clientFingerprint}`);\n\nconst toConnection = (props: KubeconfigProps): Connection => ({\n auth: { context: props.context, kind: 'kubeconfig', path: props.runtimePath },\n});\n\nconst readFileMeta = (props: KubeconfigProps) =>\n Effect.gen(function* () {\n const text = yield* Effect.tryPromise({\n try: () => Bun.file(props.runtimePath).text(),\n catch: (cause) => new Error(String(cause)),\n }).pipe(Effect.orElseSucceed(() => undefined as string | undefined));\n if (text === undefined) return undefined;\n const meta = kubeconfigMetadata(text, props.context);\n if (meta === undefined) return undefined;\n return {\n certificateAuthorityFingerprint: meta.caFingerprint,\n clientCertificateFingerprint: meta.clientFingerprint,\n connection: toConnection(props),\n context: props.context,\n credentialGeneration: generation({ ...meta, context: props.context }),\n endpoint: meta.endpoint,\n };\n });\n\nconst read = (props: KubeconfigProps) =>\n readFileMeta(props).pipe(\n Effect.map((meta) =>\n meta === undefined\n ? {\n certificateAuthorityFingerprint: '',\n clientCertificateFingerprint: '',\n connection: toConnection(props),\n context: props.context,\n credentialGeneration: '',\n endpoint: '',\n }\n : meta,\n ),\n );\n\nconst diff = (news: Input<KubeconfigProps>, output: KubeconfigAttributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (\n live.credentialGeneration !== '' &&\n output.credentialGeneration === live.credentialGeneration &&\n output.context === live.context &&\n output.endpoint === live.endpoint\n ) {\n return { action: 'noop' } as const;\n }\n return { action: 'update' } as const;\n });\n\nconst reconcile = (props: KubeconfigProps) =>\n Effect.gen(function* () {\n const credential = yield* mintTalosconfig(props.target);\n yield* talosctl(\n [\n 'kubeconfig',\n props.runtimePath,\n '--merge=false',\n '--force',\n '--force-context-name',\n props.context,\n ],\n { nodes: [props.node], talosconfigPath: credential.talosconfigPath },\n );\n const meta = yield* readFileMeta(props);\n if (meta === undefined) {\n return yield* Effect.die(\n new Error(\n `${props.runtimePath}: talosctl kubeconfig returned no error but the file is missing ` +\n 'or unparsable. Read back rather than trusting the exit code alone.',\n ),\n );\n }\n return meta;\n });\n\nconst handlers = {\n delete: () => Effect.void,\n diff: ({\n news,\n output,\n }: {\n news: Input<KubeconfigProps>;\n output: KubeconfigAttributes | undefined;\n }) => diff(news, output),\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: KubeconfigProps }) => read(olds),\n reconcile: ({ news }: { news: KubeconfigProps }) => reconcile(news),\n};\n\nexport const TalosKubeconfigProvider = () =>\n Provider.effect(TalosKubeconfig, Effect.succeed(TalosKubeconfig.Provider.of(handlers)));\n",
|
|
6
|
+
"/**\n * The Talos client credential a reconcile runs with: minted from OpenBao, written to a temp file.\n *\n * ⛔ TALOSCONFIG AND secrets.yaml HOLD CLIENT CERTIFICATES AND CLUSTER CA PRIVATE KEYS. Alchemy\n * persists resource attributes WITHOUT encryption — StateEncoding.ts writes `Redacted` as\n * `{\"@redacted\": <plaintext>}` — and this estate's state store is the `alchemy` Postgres that\n * pg-backup.sh dumps nightly. Neither file may be a prop, an attribute, or a log line.\n *\n * ★ MODELED ON house/proxmox/src/credentials.ts: mint at call time through `ChildProcessSpawner`,\n * use the material to build a client, drop it when the scope ends. The difference is Talos wants a\n * FILE (`--talosconfig` / `TALOSCONFIG`), so mint writes a temp path and registers cleanup.\n *\n * ⚠️ REASONED FROM THE PUBLISHED TALOS WORKFLOW, NOT MEASURED ON THIS ESTATE. There is no Talos\n * cluster here yet; paths below follow the same `mount/data/…` shape other infra mounts use.\n */\nimport { closeSync, openSync, writeFileSync } from 'node:fs';\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\nimport * as ChildProcess from 'effect/unstable/process/ChildProcess';\nimport * as ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';\n\n/** Where credentials come from. HomeFlare-specific mount names live in the stack, not here. */\nexport type TalosTarget = {\n /** OpenBao KV mount holding Talos material, e.g. `talos-tb4`. */\n readonly mount: string;\n /** Logical cluster name — safe to persist and log. */\n readonly cluster: string;\n /**\n * Path under the mount for the talosconfig YAML.\n *\n * ⚠️ DEFAULT IS REASONED. The first real deploy will confirm or correct the path; a wrong path\n * fails closed with a bao error rather than writing an empty talosconfig.\n */\n readonly talosconfigKey?: string;\n};\n\nexport type TalosCredential = {\n /** Absolute path to a temp talosconfig. ⛔ NEVER PERSIST, NEVER LOG CONTENTS. */\n readonly talosconfigPath: string;\n};\n\nconst defaultKey = (target: TalosTarget) => target.talosconfigKey ?? 'data/talosconfig';\n\n/**\n * Mint one talosconfig file for `target`.\n *\n * ★ SHELLS OUT TO `bao` RATHER THAN SPEAKING THE HTTP API — same reasoning as proxmox credentials.\n * The estate's BAO_ADDR / BAO_NAMESPACE / approle contract already lives in that binary.\n */\nexport const mintTalosconfig = (target: TalosTarget) =>\n Effect.scoped(\n Effect.gen(function* () {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const key = defaultKey(target);\n const result = yield* ChildProcess.make(\n 'bao',\n ['kv', 'get', '-format=json', `${target.mount}/${key}`],\n { detached: false, extendEnv: true, stderr: 'pipe', stdin: 'ignore', stdout: 'pipe' },\n ).pipe(\n spawner.spawn,\n Effect.flatMap((child) =>\n Effect.all(\n {\n exitCode: child.exitCode,\n stderr: child.stderr.pipe(\n Stream.decodeText,\n Stream.mkString,\n Effect.map((text) => text.trim()),\n ),\n stdout: child.stdout.pipe(\n Stream.decodeText,\n Stream.mkString,\n Effect.map((text) => text.trim()),\n ),\n },\n { concurrency: 'unbounded' },\n ),\n ),\n Effect.scoped,\n );\n\n if (result.exitCode !== 0) {\n // ⛔ stderr ONLY — stdout may hold talosconfig YAML with client credentials.\n return yield* Effect.fail(\n new Error(\n `bao kv get ${target.mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`,\n ),\n );\n }\n\n const parsed = JSON.parse(result.stdout) as { data?: { data?: Record<string, unknown> } };\n const data = parsed.data?.data;\n const raw = data?.['talosconfig'] ?? data?.['config'];\n if (typeof raw !== 'string' || raw.trim() === '') {\n return yield* Effect.fail(\n new Error(\n `${target.mount}/${key} returned no talosconfig field. Expected a KV key named 'talosconfig' or 'config' — never store the value in Alchemy props.`,\n ),\n );\n }\n\n const talosconfigPath = `${Bun.env['TMPDIR'] ?? '/tmp'}/hf-talos-${target.cluster}-${Bun.randomUUIDv7()}.yaml`;\n /**\n * ⛔ 0600, CREATED EXCLUSIVELY, AND `Bun.write` CANNOT DO EITHER. This file holds the\n * cluster's admin client certificate — everything talosctl needs to reconfigure or wipe a\n * node. `Bun.write` takes no mode, so it lands at the process umask, which on this estate\n * is 0644: world-readable, in a world-readable directory, for as long as the resource\n * runs. The name is unguessable, and \"unguessable\" is not a permission.\n * ⚠️ `wx` IS THE OTHER HALF. Opening with O_EXCL means this cannot be made to write through\n * a path an attacker pre-created — the classic /tmp symlink race — and with a UUIDv7 name\n * a collision is a genuine error rather than something to paper over.\n * ⚠️ THE FINALIZER BELOW REMOVES IT ON EVERY EXIT PATH, but a hard kill (SIGKILL, power\n * loss) leaves it behind. 0600 is what makes that survivable rather than a disclosure.\n */\n yield* Effect.try({\n try: () => {\n const fd = openSync(talosconfigPath, 'wx', 0o600);\n try {\n writeFileSync(fd, raw);\n } finally {\n closeSync(fd);\n }\n },\n catch: (cause) => new Error(`writing temp talosconfig: ${String(cause)}`),\n });\n\n yield* Effect.addFinalizer(() =>\n Effect.tryPromise({\n try: () => Bun.file(talosconfigPath).delete(),\n catch: () => undefined,\n }).pipe(Effect.orElseSucceed(() => undefined)),\n );\n\n return { talosconfigPath } satisfies TalosCredential;\n }),\n );\n",
|
|
7
|
+
"/**\n * One `talosctl` invocation — stdout/stderr captured, secrets never echoed.\n *\n * ★ SHELL OUT LIKE ALCHEMY'S DOCKER PROVIDER (ChildProcessSpawner), NOT A SECOND SDK. Talos speaks\n * gRPC; the published, stable surface for operators is `talosctl`. No Talos npm package is added.\n *\n * ⚠️ REASONED FROM docs.siderolabs.com/talos/v1.13/reference/cli — nothing here was measured on a\n * live cluster in this session.\n *\n * ⛔ DO NOT LOG STDOUT ON FAILURE. `talosctl kubeconfig` and error paths have been observed in the\n * wild to include credential material; stderr is the safe diagnostic channel.\n */\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\nimport * as ChildProcess from 'effect/unstable/process/ChildProcess';\nimport * as ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';\n\nexport class TalosError extends Error {\n constructor(\n readonly command: string,\n readonly exitCode: number,\n detail: string,\n ) {\n super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);\n this.name = 'TalosError';\n }\n}\n\nexport type TalosRunOptions = {\n readonly talosconfigPath: string;\n readonly nodes?: readonly string[];\n readonly endpoints?: readonly string[];\n};\n\n/** Run `talosctl <args…>`. Returns trimmed stdout, or '' when empty. */\nexport const talosctl = (args: readonly string[], options: TalosRunOptions) =>\n Effect.gen(function* () {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const argv = [\n ...args,\n '--talosconfig',\n options.talosconfigPath,\n ...(options.nodes === undefined ? [] : ['--nodes', options.nodes.join(',')]),\n ...(options.endpoints === undefined ? [] : ['--endpoints', options.endpoints.join(',')]),\n ];\n const result = yield* ChildProcess.make('talosctl', argv, {\n detached: false,\n extendEnv: true,\n stderr: 'pipe',\n stdin: 'ignore',\n stdout: 'pipe',\n }).pipe(\n spawner.spawn,\n Effect.flatMap((child) =>\n Effect.all(\n {\n exitCode: child.exitCode,\n stderr: child.stderr.pipe(\n Stream.decodeText,\n Stream.mkString,\n Effect.map((text) => text.trim()),\n ),\n stdout: child.stdout.pipe(\n Stream.decodeText,\n Stream.mkString,\n Effect.map((text) => text.trim()),\n ),\n },\n { concurrency: 'unbounded' },\n ),\n ),\n Effect.scoped,\n );\n\n const command = args.join(' ');\n if (result.exitCode !== 0) {\n return yield* Effect.fail(\n new TalosError(command, result.exitCode, result.stderr.slice(0, 300)),\n );\n }\n return result.stdout;\n });\n\n/** Like `talosctl`, but exit code 1 with \"already\" in stderr is treated as success. */\nexport const talosctlOrAlready = (args: readonly string[], options: TalosRunOptions) =>\n talosctl(args, options).pipe(\n Effect.catchIf(\n (cause): cause is TalosError =>\n cause instanceof TalosError &&\n cause.exitCode !== 0 &&\n /already|exists|bootstrapped/i.test(cause.message),\n () => Effect.succeed(''),\n ),\n );\n",
|
|
8
|
+
"/**\n * Coercions and digests for Talos resources — every function here exists because a trap does.\n *\n * ⚠️ NOTHING IN THIS FILE TOUCHES A LIVE CLUSTER. Digests are computed from repo files or from\n * kubeconfig YAML already on disk at reconcile time; tests use fixtures only.\n */\nimport { createHash } from 'node:crypto';\n\n/** Trim trailing whitespace only — leading space in YAML is meaningful. */\nexport const canonicalText = (text: string) => text.trimEnd();\n\n/** SHA-256 hex digest of canonical text. Safe to persist — it is not reversible to the input. */\nexport const sha256 = (text: string) =>\n createHash('sha256').update(canonicalText(text)).digest('hex');\n\n/** Digest of a machine config file read from the repo. ⛔ Never persist the file body itself. */\nexport const configDigest = (text: string) => sha256(text);\n\ntype KubeconfigDoc = {\n clusters?: {\n cluster?: { server?: string; 'certificate-authority-data'?: string };\n name?: string;\n }[];\n contexts?: { context?: { cluster?: string; user?: string }; name?: string }[];\n users?: {\n name?: string;\n user?: { 'client-certificate-data'?: string; 'client-key-data'?: string };\n }[];\n 'current-context'?: string;\n};\n\n/**\n * Public metadata extracted from a kubeconfig file on disk.\n *\n * ⛔ RETURNS FINGERPRINTS ONLY. The admin kubeconfig contains a client certificate; Alchemy state\n * may hold the API server URL and CA/client cert digests, never the PEM bytes.\n */\nexport const kubeconfigMetadata = (\n yamlText: string,\n contextName: string,\n): { endpoint: string; caFingerprint: string; clientFingerprint: string } | undefined => {\n let doc: KubeconfigDoc;\n try {\n doc = Bun.YAML.parse(yamlText) as KubeconfigDoc;\n } catch {\n return undefined;\n }\n\n const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;\n const clusterName = ctx?.cluster;\n const userName = ctx?.user;\n if (clusterName === undefined || userName === undefined) return undefined;\n\n const cluster = doc.clusters?.find((row) => row.name === clusterName)?.cluster;\n const user = doc.users?.find((row) => row.name === userName)?.user;\n const endpoint = cluster?.server;\n const ca = cluster?.['certificate-authority-data'];\n const cert = user?.['client-certificate-data'] ?? user?.['client-key-data'];\n if (endpoint === undefined || ca === undefined || cert === undefined) return undefined;\n\n return {\n caFingerprint: sha256(ca),\n clientFingerprint: sha256(cert),\n endpoint,\n };\n};\n\n/** Resolve a config file path relative to the declaring stack file. */\nexport const resolveConfigPath = (stackDir: string, configFile: string) =>\n configFile.startsWith('/') ? configFile : `${stackDir}/${configFile}`;\n",
|
|
9
|
+
"/**\n * `Talos.Bootstrap` — initialize etcd once on ONE control-plane node.\n *\n * ★ REASONED FROM talosctl bootstrap (Talos v1.13 CLI reference): one node aborts the etcd join\n * loop and forms the initial cluster; other control-plane nodes join after Kubernetes starts on\n * the bootstrap node. This command must run exactly once per cluster.\n *\n * ⛔ NOT A FACTORY RESOURCE — bootstrap cannot be deleted or updated in place; `delete` is a no-op.\n */\nimport { Resource } from 'alchemy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport { mintTalosconfig } from './credentials.ts';\nimport type { TalosRequirements, WithTarget } from './resource.ts';\nimport { talosctl, talosctlOrAlready } from './talosctl.ts';\n\nexport interface BootstrapProps extends WithTarget {\n /** The ONE control-plane node that receives `talosctl bootstrap`. */\n node: string;\n /** Machine configs for this node (and peers) must be applied first. */\n after?: readonly unknown[];\n}\n\nexport interface BootstrapAttributes {\n node: string;\n bootstrapped: boolean;\n}\n\nexport interface TalosBootstrap extends Resource<\n 'Talos.Bootstrap',\n BootstrapProps,\n BootstrapAttributes,\n never,\n TalosRequirements\n> {}\n\nexport const TalosBootstrap = Resource<TalosBootstrap>('Talos.Bootstrap', {\n /** etcd data is irreplaceable — opt into destroy explicitly if Talos ever adds an undo. */\n defaultRemovalPolicy: 'retain',\n});\n\n/**\n * REASONED: `talosctl get etcdmembers -o json` should list members after bootstrap.\n * ⚠️ NOT MEASURED — first live deploy confirms the resource type name.\n */\nconst isBootstrapped = (props: BootstrapProps, talosconfigPath: string) =>\n talosctl(['get', 'etcdmembers', '-o', 'json'], {\n nodes: [props.node],\n talosconfigPath,\n }).pipe(\n Effect.map((text) => text.includes('\"id\"') || text.includes('\"member\"')),\n Effect.orElseSucceed(() => false),\n );\n\nconst read = (props: BootstrapProps) =>\n Effect.gen(function* () {\n const credential = yield* mintTalosconfig(props.target);\n const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);\n return { bootstrapped, node: props.node };\n });\n\nconst diff = (news: Input<BootstrapProps>, output: BootstrapAttributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live.bootstrapped) return { action: 'noop' } as const;\n return { action: 'update' } as const;\n });\n\nconst reconcile = (props: BootstrapProps) =>\n Effect.gen(function* () {\n const credential = yield* mintTalosconfig(props.target);\n const before = yield* isBootstrapped(props, credential.talosconfigPath);\n if (!before) {\n yield* talosctlOrAlready(['bootstrap'], {\n nodes: [props.node],\n talosconfigPath: credential.talosconfigPath,\n });\n }\n const after = yield* isBootstrapped(props, credential.talosconfigPath);\n if (!after) {\n return yield* Effect.die(\n new Error(\n `${props.node}: bootstrap returned no error but etcd members are still absent. ` +\n 'Read back rather than trusting the exit code alone.',\n ),\n );\n }\n return { bootstrapped: true, node: props.node };\n });\n\nconst handlers = {\n delete: () => Effect.void,\n diff: ({\n news,\n output,\n }: {\n news: Input<BootstrapProps>;\n output: BootstrapAttributes | undefined;\n }) => diff(news, output),\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: BootstrapProps }) => read(olds),\n reconcile: ({ news }: { news: BootstrapProps }) => reconcile(news),\n};\n\nexport const TalosBootstrapProvider = () =>\n Provider.effect(TalosBootstrap, Effect.succeed(TalosBootstrap.Provider.of(handlers)));\n",
|
|
10
|
+
"/**\n * `Talos.ClusterHealth` — readiness gate other resources can depend on via `after`.\n *\n * ★ REASONED FROM talosctl health (Talos v1.13 CLI reference): checks Talos services, etcd, and\n * Kubernetes component health; `--wait-timeout` defaults to 20m0s.\n *\n * ⚠️ PLAN READS USE A SHORT TIMEOUT SO `plan` DOES NOT BLOCK TWENTY MINUTES. Reconcile uses the\n * declared timeout — that is the deploy gate, not the plan gate.\n */\nimport { Resource } from 'alchemy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport { mintTalosconfig } from './credentials.ts';\nimport type { TalosRequirements, WithTarget } from './resource.ts';\nimport { talosctl } from './talosctl.ts';\n\nexport interface ClusterHealthProps extends WithTarget {\n /** Control-plane node IPs passed to `--control-plane-nodes`. */\n controlPlaneNodes: readonly string[];\n /** Worker node IPs passed to `--worker-nodes`. */\n workerNodes?: readonly string[];\n /**\n * Reconcile wait (`--wait-timeout`), e.g. `20m0s`. Default from published CLI.\n *\n * ⚠️ REASONED default — not measured here.\n */\n waitTimeout?: string;\n /** Bootstrap and kubeconfig should complete before health is enforced. */\n after?: readonly unknown[];\n}\n\nexport interface ClusterHealthAttributes {\n healthy: boolean;\n controlPlaneNodes: string;\n workerNodes: string;\n}\n\nexport interface TalosClusterHealth extends Resource<\n 'Talos.ClusterHealth',\n ClusterHealthProps,\n ClusterHealthAttributes,\n never,\n TalosRequirements\n> {}\n\nexport const TalosClusterHealth = Resource<TalosClusterHealth>('Talos.ClusterHealth');\n\nconst nodeCsv = (nodes: readonly string[] | undefined) =>\n nodes === undefined || nodes.length === 0 ? '' : nodes.join(',');\n\nconst healthArgs = (props: ClusterHealthProps, waitTimeout: string) => {\n const args = ['health', '--wait-timeout', waitTimeout];\n const cp = nodeCsv(props.controlPlaneNodes);\n const workers = nodeCsv(props.workerNodes);\n if (cp !== '') args.push('--control-plane-nodes', cp);\n if (workers !== '') args.push('--worker-nodes', workers);\n return args;\n};\n\nconst check = (props: ClusterHealthProps, waitTimeout: string) =>\n Effect.gen(function* () {\n const credential = yield* mintTalosconfig(props.target);\n yield* talosctl(healthArgs(props, waitTimeout), {\n nodes: [...props.controlPlaneNodes],\n talosconfigPath: credential.talosconfigPath,\n });\n return {\n controlPlaneNodes: nodeCsv(props.controlPlaneNodes),\n healthy: true,\n workerNodes: nodeCsv(props.workerNodes),\n };\n });\n\nconst read = (props: ClusterHealthProps) =>\n check(props, '5s').pipe(\n Effect.orElseSucceed(() => ({\n controlPlaneNodes: nodeCsv(props.controlPlaneNodes),\n healthy: false,\n workerNodes: nodeCsv(props.workerNodes),\n })),\n );\n\nconst diff = (news: Input<ClusterHealthProps>, output: ClusterHealthAttributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live.healthy) return { action: 'noop' } as const;\n return { action: 'update' } as const;\n });\n\nconst reconcile = (props: ClusterHealthProps) =>\n check(props, props.waitTimeout ?? '20m0s').pipe(\n Effect.mapError(\n (cause) =>\n new Error(\n `Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` +\n `${props.waitTimeout ?? '20m0s'} — ${String(cause)}`,\n ),\n ),\n );\n\nconst handlers = {\n delete: () => Effect.void,\n diff: ({\n news,\n output,\n }: {\n news: Input<ClusterHealthProps>;\n output: ClusterHealthAttributes | undefined;\n }) => diff(news, output),\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: ClusterHealthProps }) => read(olds),\n reconcile: ({ news }: { news: ClusterHealthProps }) => reconcile(news),\n};\n\nexport const TalosClusterHealthProvider = () =>\n Provider.effect(TalosClusterHealth, Effect.succeed(TalosClusterHealth.Provider.of(handlers)));\n",
|
|
11
|
+
"/**\n * `Talos.MachineConfig` — apply one machine configuration document to one node.\n *\n * ★ REASONED FROM talosctl apply-config (Talos v1.13 CLI reference): `-f/--file`, `-n/--nodes`,\n * `-m/--mode`, `-i/--insecure` for maintenance mode, `--dry-run` for planning without a write.\n *\n * ⛔ THE CONFIG BODY IS NEVER AN ATTRIBUTE. Machine configs can embed cluster tokens; only a digest\n * of the repo file is persisted. Read `configFile` from disk at reconcile time.\n */\nimport { Resource } from 'alchemy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport { mintTalosconfig } from './credentials.ts';\nimport type { TalosRequirements, WithTarget } from './resource.ts';\nimport { talosctl } from './talosctl.ts';\nimport { configDigest, resolveConfigPath } from './values.ts';\n\nconst STACK_DIR = new URL('..', import.meta.url).pathname;\n\nexport type ApplyMode = 'auto' | 'no-reboot' | 'reboot' | 'staged' | 'try';\n\nexport interface MachineConfigProps extends WithTarget {\n /** Node IP or hostname — the `-n` target. */\n node: string;\n /** Repo-relative or absolute path to the YAML file passed to `-f`. */\n configFile: string;\n /** talosctl `-m` mode. Default `auto` per published CLI. */\n mode?: ApplyMode;\n /**\n * Maintenance-mode apply only (`-i`). Default false — post-bootstrap applies use talosconfig auth.\n *\n * ⚠️ REASONED: docs describe `--insecure` for the first apply before talosconfig exists.\n */\n insecure?: boolean;\n /** Ordering edge — e.g. wait for Proxmox.Vm. */\n after?: readonly unknown[];\n}\n\nexport interface MachineConfigAttributes {\n node: string;\n configDigest: string;\n mode: ApplyMode;\n /** True when the node's reported config digest matches the declared file. */\n converged: boolean;\n}\n\nexport interface TalosMachineConfig extends Resource<\n 'Talos.MachineConfig',\n MachineConfigProps,\n MachineConfigAttributes,\n never,\n TalosRequirements\n> {}\n\nexport const TalosMachineConfig = Resource<TalosMachineConfig>('Talos.MachineConfig');\n\nconst loadDigest = (props: MachineConfigProps) =>\n Effect.gen(function* () {\n const path = resolveConfigPath(STACK_DIR, props.configFile);\n const text = yield* Effect.tryPromise({\n try: () => Bun.file(path).text(),\n catch: (cause) => new Error(`reading ${props.configFile}: ${String(cause)}`),\n });\n return configDigest(text);\n });\n\n/** REASONED: `talosctl get machineconfig -o yaml` returns the live document. */\nconst liveDigest = (props: MachineConfigProps, talosconfigPath: string) =>\n talosctl(['get', 'machineconfig', '-o', 'yaml'], {\n nodes: [props.node],\n talosconfigPath,\n }).pipe(\n Effect.map((yaml) => configDigest(yaml)),\n Effect.orElseSucceed(() => undefined),\n );\n\nconst read = (props: MachineConfigProps) =>\n Effect.gen(function* () {\n const wanted = yield* loadDigest(props);\n const credential = yield* mintTalosconfig(props.target);\n const live = yield* liveDigest(props, credential.talosconfigPath);\n return {\n configDigest: wanted,\n converged: live === wanted,\n mode: props.mode ?? 'auto',\n node: props.node,\n };\n });\n\nconst diff = (news: Input<MachineConfigProps>, output: MachineConfigAttributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live.converged) return { action: 'noop' } as const;\n return { action: 'update' } as const;\n });\n\nconst reconcile = (props: MachineConfigProps) =>\n Effect.gen(function* () {\n const path = resolveConfigPath(STACK_DIR, props.configFile);\n const wanted = yield* loadDigest(props);\n const credential = yield* mintTalosconfig(props.target);\n const args = [\n 'apply-config',\n '--file',\n path,\n '--mode',\n props.mode ?? 'auto',\n ...(props.insecure === true ? ['--insecure'] : []),\n ];\n yield* talosctl(args, { nodes: [props.node], talosconfigPath: credential.talosconfigPath });\n const live = yield* liveDigest(props, credential.talosconfigPath);\n if (live !== wanted) {\n return yield* Effect.die(\n new Error(\n `${props.node}: apply-config returned no error but machineconfig digest still differs. ` +\n 'Read back rather than trusting the exit code alone.',\n ),\n );\n }\n return {\n configDigest: wanted,\n converged: true,\n mode: props.mode ?? 'auto',\n node: props.node,\n };\n });\n\nconst handlers = {\n delete: () => Effect.void,\n diff: ({\n news,\n output,\n }: {\n news: Input<MachineConfigProps>;\n output: MachineConfigAttributes | undefined;\n }) => diff(news, output),\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: MachineConfigProps }) => read(olds),\n reconcile: ({ news }: { news: MachineConfigProps }) => reconcile(news),\n};\n\nexport const TalosMachineConfigProvider = () =>\n Provider.effect(TalosMachineConfig, Effect.succeed(TalosMachineConfig.Provider.of(handlers)));\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "6CD90391393B548364756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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 type { Connection } from 'alchemy/Kubernetes/Connection';
|
|
19
|
+
import * as Provider from 'alchemy/Provider';
|
|
20
|
+
import type { TalosRequirements, WithTarget } from './resource.ts';
|
|
21
|
+
export interface KubeconfigProps extends WithTarget {
|
|
22
|
+
/** Node that serves `talosctl kubeconfig`. */
|
|
23
|
+
node: string;
|
|
24
|
+
/** Context name inside the generated kubeconfig — not secret. */
|
|
25
|
+
context: string;
|
|
26
|
+
/**
|
|
27
|
+
* Host path where talosctl writes the kubeconfig each reconcile.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ Must be outside Alchemy state and rotated by redeploy, never copied into attributes.
|
|
30
|
+
*/
|
|
31
|
+
runtimePath: string;
|
|
32
|
+
after?: readonly unknown[];
|
|
33
|
+
}
|
|
34
|
+
export interface KubeconfigAttributes {
|
|
35
|
+
context: string;
|
|
36
|
+
endpoint: string;
|
|
37
|
+
certificateAuthorityFingerprint: string;
|
|
38
|
+
clientCertificateFingerprint: string;
|
|
39
|
+
/** Digest of endpoint+context+fingerprints — detects credential rotation without storing PEM. */
|
|
40
|
+
credentialGeneration: string;
|
|
41
|
+
/**
|
|
42
|
+
* Serializable `Kubernetes.Connection` for downstream workloads.
|
|
43
|
+
*
|
|
44
|
+
* ★ auth.kind `kubeconfig` is the stock ClusterAdapter — provider-roadmap.md names this seam.
|
|
45
|
+
* Only `path` and `context` appear here; the adapter reads credentials from disk at call time.
|
|
46
|
+
*/
|
|
47
|
+
connection: Connection;
|
|
48
|
+
}
|
|
49
|
+
export interface TalosKubeconfig extends Resource<'Talos.Kubeconfig', KubeconfigProps, KubeconfigAttributes, never, TalosRequirements> {
|
|
50
|
+
}
|
|
51
|
+
export declare const TalosKubeconfig: import("alchemy").ResourceClass<TalosKubeconfig>;
|
|
52
|
+
export declare const TalosKubeconfigProvider: () => import("effect/Layer").Layer<Provider.Provider<TalosKubeconfig>, never, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
53
|
+
//# sourceMappingURL=kubeconfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kubeconfig.d.ts","sourceRoot":"","sources":["../../src/talos/kubeconfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAInE,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B,EAAE,MAAM,CAAC;IACxC,4BAA4B,EAAE,MAAM,CAAC;IACrC,iGAAiG;IACjG,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,eAAgB,SAAQ,QAAQ,CAC/C,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,KAAK,EACL,iBAAiB,CAClB;CAAG;AAEJ,eAAO,MAAM,eAAe,kDAAgD,CAAC;AAuG7E,eAAO,MAAM,uBAAuB,0JACqD,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
* ★ WHAT EVERY OPERATION HERE NEEDS FROM THE RUNTIME — ChildProcessSpawner only.
|
|
22
|
+
*
|
|
23
|
+
* Talos credentials are minted by shelling out to `bao` and then shelling out to `talosctl`.
|
|
24
|
+
* There is no HttpClient call in this package on purpose: the published operator surface is the
|
|
25
|
+
* CLI, and a parallel gRPC client would be a second SDK the repo rules forbid.
|
|
26
|
+
*/
|
|
27
|
+
export type TalosRequirements = ChildProcessSpawner.ChildProcessSpawner;
|
|
28
|
+
/** Every resource names which cluster's OpenBao mount it uses. */
|
|
29
|
+
export type WithTarget = {
|
|
30
|
+
target: TalosTarget;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/talos/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,KAAK,mBAAmB,MAAM,6CAA6C,CAAC;AACxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,mBAAmB,CAAC;AAExE,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 * as Provider from 'alchemy/Provider';
|
|
12
|
+
import type { TalosRequirements, WithTarget } from './resource.ts';
|
|
13
|
+
export interface BootstrapProps extends WithTarget {
|
|
14
|
+
/** The ONE control-plane node that receives `talosctl bootstrap`. */
|
|
15
|
+
node: string;
|
|
16
|
+
/** Machine configs for this node (and peers) must be applied first. */
|
|
17
|
+
after?: readonly unknown[];
|
|
18
|
+
}
|
|
19
|
+
export interface BootstrapAttributes {
|
|
20
|
+
node: string;
|
|
21
|
+
bootstrapped: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface TalosBootstrap extends Resource<'Talos.Bootstrap', BootstrapProps, BootstrapAttributes, never, TalosRequirements> {
|
|
24
|
+
}
|
|
25
|
+
export declare const TalosBootstrap: import("alchemy").ResourceClass<TalosBootstrap>;
|
|
26
|
+
export declare const TalosBootstrapProvider: () => import("effect/Layer").Layer<Provider.Provider<TalosBootstrap>, never, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
27
|
+
//# sourceMappingURL=talos-bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"talos-bootstrap.d.ts","sourceRoot":"","sources":["../../src/talos/talos-bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGnE,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,KAAK,EACL,iBAAiB,CAClB;CAAG;AAEJ,eAAO,MAAM,cAAc,iDAGzB,CAAC;AAkEH,eAAO,MAAM,sBAAsB,yJACoD,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 * as Provider from 'alchemy/Provider';
|
|
12
|
+
import type { TalosRequirements, WithTarget } from './resource.ts';
|
|
13
|
+
export interface ClusterHealthProps extends WithTarget {
|
|
14
|
+
/** Control-plane node IPs passed to `--control-plane-nodes`. */
|
|
15
|
+
controlPlaneNodes: readonly string[];
|
|
16
|
+
/** Worker node IPs passed to `--worker-nodes`. */
|
|
17
|
+
workerNodes?: readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* Reconcile wait (`--wait-timeout`), e.g. `20m0s`. Default from published CLI.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ REASONED default — not measured here.
|
|
22
|
+
*/
|
|
23
|
+
waitTimeout?: string;
|
|
24
|
+
/** Bootstrap and kubeconfig should complete before health is enforced. */
|
|
25
|
+
after?: readonly unknown[];
|
|
26
|
+
}
|
|
27
|
+
export interface ClusterHealthAttributes {
|
|
28
|
+
healthy: boolean;
|
|
29
|
+
controlPlaneNodes: string;
|
|
30
|
+
workerNodes: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TalosClusterHealth extends Resource<'Talos.ClusterHealth', ClusterHealthProps, ClusterHealthAttributes, never, TalosRequirements> {
|
|
33
|
+
}
|
|
34
|
+
export declare const TalosClusterHealth: import("alchemy").ResourceClass<TalosClusterHealth>;
|
|
35
|
+
export declare const TalosClusterHealthProvider: () => import("effect/Layer").Layer<Provider.Provider<TalosClusterHealth>, never, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
36
|
+
//# sourceMappingURL=talos-cluster-health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"talos-cluster-health.d.ts","sourceRoot":"","sources":["../../src/talos/talos-cluster-health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGnE,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,gEAAgE;IAChE,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kDAAkD;IAClD,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,EACL,iBAAiB,CAClB;CAAG;AAEJ,eAAO,MAAM,kBAAkB,qDAAsD,CAAC;AAsEtF,eAAO,MAAM,0BAA0B,6JACwD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 * as Provider from 'alchemy/Provider';
|
|
12
|
+
import type { TalosRequirements, WithTarget } from './resource.ts';
|
|
13
|
+
export type ApplyMode = 'auto' | 'no-reboot' | 'reboot' | 'staged' | 'try';
|
|
14
|
+
export interface MachineConfigProps extends WithTarget {
|
|
15
|
+
/** Node IP or hostname — the `-n` target. */
|
|
16
|
+
node: string;
|
|
17
|
+
/** Repo-relative or absolute path to the YAML file passed to `-f`. */
|
|
18
|
+
configFile: string;
|
|
19
|
+
/** talosctl `-m` mode. Default `auto` per published CLI. */
|
|
20
|
+
mode?: ApplyMode;
|
|
21
|
+
/**
|
|
22
|
+
* Maintenance-mode apply only (`-i`). Default false — post-bootstrap applies use talosconfig auth.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ REASONED: docs describe `--insecure` for the first apply before talosconfig exists.
|
|
25
|
+
*/
|
|
26
|
+
insecure?: boolean;
|
|
27
|
+
/** Ordering edge — e.g. wait for Proxmox.Vm. */
|
|
28
|
+
after?: readonly unknown[];
|
|
29
|
+
}
|
|
30
|
+
export interface MachineConfigAttributes {
|
|
31
|
+
node: string;
|
|
32
|
+
configDigest: string;
|
|
33
|
+
mode: ApplyMode;
|
|
34
|
+
/** True when the node's reported config digest matches the declared file. */
|
|
35
|
+
converged: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface TalosMachineConfig extends Resource<'Talos.MachineConfig', MachineConfigProps, MachineConfigAttributes, never, TalosRequirements> {
|
|
38
|
+
}
|
|
39
|
+
export declare const TalosMachineConfig: import("alchemy").ResourceClass<TalosMachineConfig>;
|
|
40
|
+
export declare const TalosMachineConfigProvider: () => import("effect/Layer").Layer<Provider.Provider<TalosMachineConfig>, never, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
41
|
+
//# sourceMappingURL=talos-machine-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"talos-machine-config.d.ts","sourceRoot":"","sources":["../../src/talos/talos-machine-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAMnE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE3E,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,6EAA6E;IAC7E,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,EACL,iBAAiB,CAClB;CAAG;AAEJ,eAAO,MAAM,kBAAkB,qDAAsD,CAAC;AAwFtF,eAAO,MAAM,0BAA0B,6JACwD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 ChildProcessSpawner from 'effect/unstable/process/ChildProcessSpawner';
|
|
15
|
+
export declare class TalosError extends Error {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly exitCode: number;
|
|
18
|
+
constructor(command: string, exitCode: number, detail: string);
|
|
19
|
+
}
|
|
20
|
+
export type TalosRunOptions = {
|
|
21
|
+
readonly talosconfigPath: string;
|
|
22
|
+
readonly nodes?: readonly string[];
|
|
23
|
+
readonly endpoints?: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
/** Run `talosctl <args…>`. Returns trimmed stdout, or '' when empty. */
|
|
26
|
+
export declare const talosctl: (args: readonly string[], options: TalosRunOptions) => Effect.Effect<string, import("effect/PlatformError").PlatformError | TalosError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
27
|
+
/** Like `talosctl`, but exit code 1 with "already" in stderr is treated as success. */
|
|
28
|
+
export declare const talosctlOrAlready: (args: readonly string[], options: TalosRunOptions) => Effect.Effect<string, import("effect/PlatformError").PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
29
|
+
//# sourceMappingURL=talosctl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"talosctl.d.ts","sourceRoot":"","sources":["../../src/talos/talosctl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,mBAAmB,MAAM,6CAA6C,CAAC;AAEnF,qBAAa,UAAW,SAAQ,KAAK;IAEjC,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAF3B,YACW,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,EAIf;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,QAAQ,SAAU,SAAS,MAAM,EAAE,WAAW,eAAe,8HA8CtE,CAAC;AAEL,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,SAAU,SAAS,MAAM,EAAE,WAAW,eAAe,iHAShF,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Trim trailing whitespace only — leading space in YAML is meaningful. */
|
|
2
|
+
export declare const canonicalText: (text: string) => string;
|
|
3
|
+
/** SHA-256 hex digest of canonical text. Safe to persist — it is not reversible to the input. */
|
|
4
|
+
export declare const sha256: (text: string) => string;
|
|
5
|
+
/** Digest of a machine config file read from the repo. ⛔ Never persist the file body itself. */
|
|
6
|
+
export declare const configDigest: (text: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Public metadata extracted from a kubeconfig file on disk.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ RETURNS FINGERPRINTS ONLY. The admin kubeconfig contains a client certificate; Alchemy state
|
|
11
|
+
* may hold the API server URL and CA/client cert digests, never the PEM bytes.
|
|
12
|
+
*/
|
|
13
|
+
export declare const kubeconfigMetadata: (yamlText: string, contextName: string) => {
|
|
14
|
+
endpoint: string;
|
|
15
|
+
caFingerprint: string;
|
|
16
|
+
clientFingerprint: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
/** Resolve a config file path relative to the declaring stack file. */
|
|
19
|
+
export declare const resolveConfigPath: (stackDir: string, configFile: string) => string;
|
|
20
|
+
//# sourceMappingURL=values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../src/talos/values.ts"],"names":[],"mappings":"AAQA,2EAA2E;AAC3E,eAAO,MAAM,aAAa,SAAU,MAAM,WAAmB,CAAC;AAE9D,iGAAiG;AACjG,eAAO,MAAM,MAAM,SAAU,MAAM,WAC6B,CAAC;AAEjE,gGAAgG;AAChG,eAAO,MAAM,YAAY,SAAU,MAAM,WAAiB,CAAC;AAe3D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aACnB,MAAM,eACH,MAAM,KAClB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,SAyB3E,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,aAAc,MAAM,cAAc,MAAM,WACC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@homeflare/alchemy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo and Talos.",
|
|
5
|
+
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
|
+
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Timothy Schneider",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/taslabs-net/homeflare-kit.git",
|
|
12
|
+
"directory": "packages/alchemy"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./cloudflare": {
|
|
28
|
+
"types": "./dist/cloudflare/index.d.ts",
|
|
29
|
+
"default": "./dist/cloudflare/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./forgejo": {
|
|
32
|
+
"types": "./dist/forgejo/index.d.ts",
|
|
33
|
+
"default": "./dist/forgejo/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./openbao": {
|
|
36
|
+
"types": "./dist/openbao/index.d.ts",
|
|
37
|
+
"default": "./dist/openbao/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./talos": {
|
|
40
|
+
"types": "./dist/talos/index.d.ts",
|
|
41
|
+
"default": "./dist/talos/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./proxmox": {
|
|
44
|
+
"types": "./dist/proxmox/index.d.ts",
|
|
45
|
+
"default": "./dist/proxmox/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"alchemy": ">=2.0.0-beta.77",
|
|
54
|
+
"cloudflare": ">=4.5.0",
|
|
55
|
+
"effect": ">=4.0.0-rc.112"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Cloudflare REST API as an Effect service, over `cloudflare-typescript`. Codex standard 5:
|
|
3
|
+
* the vendor's SDK, never a hand-rolled client — and never a hand-rolled `fetch` either.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ THE SDK WAS ALREADY IN THE STORE AND DECLARED BY NOBODY. Measured 2026-09-15:
|
|
6
|
+
* `node_modules/.pnpm/cloudflare@4.5.0` exists as a transitive dependency of `alchemy`, and
|
|
7
|
+
* `pnpm ls cloudflare --depth 0` at the root and in every workspace package returns nothing.
|
|
8
|
+
* `house/local/scripts/vendor-sdk-ratchet.ts` row R2 says exactly that — "not declared anywhere
|
|
9
|
+
* in the workspace yet … the row adds the dependency once, then converts the TS callers" — so
|
|
10
|
+
* this package is that row's first half, and the twenty ratcheted offences keep their rows.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ AND IT IS PINNED EXACTLY, NOT `catalog:`. There is no catalog entry for `cloudflare`, and the
|
|
13
|
+
* version that resolves today is the one alchemy pulls in. Pinning `4.5.0` means a future
|
|
14
|
+
* alchemy bump moves alchemy's copy and not this package's contract — the alternative is a
|
|
15
|
+
* provider whose request shape changes because an unrelated dependency was upgraded.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ `fetch` IS A SEAM, NOT A CONVENIENCE — the same argument `packages/litellm-client` makes. The
|
|
18
|
+
* SDK hands its custom fetch the URL and init it built, so a test double conforming to
|
|
19
|
+
* `Core.Fetch` exercises the real path assembly, the real body serialisation and the real header
|
|
20
|
+
* set. A test that stubbed `client.r2.buckets.locks.update` would prove only that a stub was
|
|
21
|
+
* called, which is precisely the class of check this estate keeps finding green over nothing.
|
|
22
|
+
*/
|
|
23
|
+
import Cloudflare from 'cloudflare';
|
|
24
|
+
import * as Config from 'effect/Config';
|
|
25
|
+
import * as Context from 'effect/Context';
|
|
26
|
+
import * as Effect from 'effect/Effect';
|
|
27
|
+
import * as Layer from 'effect/Layer';
|
|
28
|
+
import * as Redacted from 'effect/Redacted';
|
|
29
|
+
|
|
30
|
+
/** What the SDK accepts for `fetch`, restated so tests can name it without importing SDK internals. */
|
|
31
|
+
export type ClientFetch = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
32
|
+
|
|
33
|
+
export class CloudflareApi extends Context.Service<CloudflareApi, Cloudflare>()(
|
|
34
|
+
'homeflare/CloudflareApi',
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ⚠️ THE FAKE FETCH IS THE ONLY REASON THIS TAKES OPTIONS AT ALL. Everything else the SDK reads
|
|
39
|
+
* from the environment the estate CLI already populates.
|
|
40
|
+
*/
|
|
41
|
+
export const makeClient = (options: { apiToken: string; fetch?: ClientFetch }): Cloudflare =>
|
|
42
|
+
new Cloudflare({
|
|
43
|
+
apiToken: options.apiToken,
|
|
44
|
+
...(options.fetch === undefined ? {} : { fetch: options.fetch }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* ⛔ FAIL CLOSED ON THE VALUE, NEVER ON THE VARIABLE EXISTING. AGENTS.md "the four that cost the
|
|
49
|
+
* most" §4: a denied OpenBao path renders the template EMPTY rather than erroring, and every
|
|
50
|
+
* Cloudflare call then answers 403 exactly like a revoked token — which sends the operator to
|
|
51
|
+
* Cloudflare to rotate a credential that was fine. Your deploy wrapper should write
|
|
52
|
+
* CLOUDFLARE_API_TOKEN from the stack's declared role, short-lived and scoped.
|
|
53
|
+
*/
|
|
54
|
+
const MISSING_TOKEN =
|
|
55
|
+
'CLOUDFLARE_API_TOKEN is not set. Mint a short-lived, scoped token and export it before ' +
|
|
56
|
+
'role named in estate.stack.ts; running `alchemy` directly skips that and there is no default.';
|
|
57
|
+
|
|
58
|
+
const EMPTY_TOKEN =
|
|
59
|
+
'CLOUDFLARE_API_TOKEN is set but EMPTY. That is a denied or missing OpenBao grant on the ' +
|
|
60
|
+
'stack’s role, not a rotated token — re-mint it rather than rotating anything at Cloudflare.';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* ⛔ A LAYER'S ERROR CHANNEL MUST BE `never`, so a ConfigError becomes a defect carrying a sentence
|
|
64
|
+
* rather than a type error pushed up into `Stack`. Same decision, same words, as `LiteLLMLive`.
|
|
65
|
+
*/
|
|
66
|
+
export const CloudflareApiLive = Layer.effect(
|
|
67
|
+
CloudflareApi,
|
|
68
|
+
Effect.gen(function* () {
|
|
69
|
+
const token = yield* Config.redacted('CLOUDFLARE_API_TOKEN').pipe(
|
|
70
|
+
Effect.catchTag('ConfigError', () => Effect.die(new Error(MISSING_TOKEN))),
|
|
71
|
+
);
|
|
72
|
+
const value = Redacted.value(token);
|
|
73
|
+
if (value.length === 0) return yield* Effect.die(new Error(EMPTY_TOKEN));
|
|
74
|
+
return makeClient({ apiToken: value });
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare providers for Alchemy — the gaps the vendor SDK leaves.
|
|
3
|
+
*
|
|
4
|
+
* ★ Built on the official `cloudflare` SDK, not hand-rolled HTTP: it already has
|
|
5
|
+
* `r2.buckets.locks.update/get` over the documented endpoints, so there is no path
|
|
6
|
+
* string this package invents.
|
|
7
|
+
*/
|
|
8
|
+
export type { R2LockRule } from './lock-rules.ts';
|
|
9
|
+
export { R2BucketLock, type R2BucketLockProps } from './r2-bucket-lock.ts';
|
|
10
|
+
export { providers } from './providers.ts';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary of an R2 lock rule, and the comparison that decides whether to write one.
|
|
3
|
+
*
|
|
4
|
+
* ★ ITS OWN MODULE FOR THE REASON `house/litellm/src/equal.ts` IS ONE: the comparison is the
|
|
5
|
+
* riskiest function in the provider and it deserves to be readable and testable without the
|
|
6
|
+
* lifecycle around it. An equality that returned true for everything would make every reconcile
|
|
7
|
+
* a no-op and the resource would silently never write anything — which looks exactly like a
|
|
8
|
+
* converged stack.
|
|
9
|
+
*/
|
|
10
|
+
/** Jurisdictions the API accepts, as the SDK declares them. */
|
|
11
|
+
export type Jurisdiction = 'default' | 'eu' | 'fedramp';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One lock rule. ⚠️ `prefix` OMITTED MEANS THE WHOLE BUCKET — the SDK documents an empty prefix as
|
|
15
|
+
* the way to scope a rule to every object, and omitting the key is how that is spelled on the wire.
|
|
16
|
+
*/
|
|
17
|
+
export type R2LockRule = {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly enabled: boolean;
|
|
20
|
+
readonly prefix?: string;
|
|
21
|
+
readonly condition:
|
|
22
|
+
| { readonly type: 'Age'; readonly maxAgeSeconds: number }
|
|
23
|
+
| { readonly type: 'Date'; readonly date: string }
|
|
24
|
+
| { readonly type: 'Indefinite' };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** ⚠️ `undefined` and `'default'` are the same jurisdiction; normalise so a diff never sees both. */
|
|
28
|
+
export const jurisdictionOf = (props: { readonly jurisdiction?: Jurisdiction }): Jurisdiction =>
|
|
29
|
+
props.jurisdiction ?? 'default';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ⛔ FIELD BY FIELD, IN ORDER, AND NOT `JSON.stringify` OF EITHER SIDE. The API returns rules in
|
|
33
|
+
* its own key order and omits `prefix` where this repo may spell it `''`; a stringify compare
|
|
34
|
+
* would report drift on every plan and PUT an identical body each deploy. Order across the list
|
|
35
|
+
* IS compared, because the rule set is a replace and the list is what gets sent.
|
|
36
|
+
*/
|
|
37
|
+
export function rulesEqual(a: readonly R2LockRule[], b: readonly R2LockRule[]): boolean {
|
|
38
|
+
if (a.length !== b.length) return false;
|
|
39
|
+
return a.every((left, index) => {
|
|
40
|
+
const right = b[index];
|
|
41
|
+
if (right === undefined) return false;
|
|
42
|
+
if (left.id !== right.id || left.enabled !== right.enabled) return false;
|
|
43
|
+
if ((left.prefix ?? '') !== (right.prefix ?? '')) return false;
|
|
44
|
+
const [lc, rc] = [left.condition, right.condition];
|
|
45
|
+
if (lc.type !== rc.type) return false;
|
|
46
|
+
if (lc.type === 'Age') return rc.type === 'Age' && lc.maxAgeSeconds === rc.maxAgeSeconds;
|
|
47
|
+
if (lc.type === 'Date') return rc.type === 'Date' && lc.date === rc.date;
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The body the SDK sends. Split out so the test can assert the wire shape against one function.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ IT TAKES THE RULES, NOT THE PROPS, so this module names nothing from the resource — the
|
|
56
|
+
* comparison and the vocabulary stay readable without the lifecycle around them.
|
|
57
|
+
*/
|
|
58
|
+
export const toBody = (rules: readonly R2LockRule[]): { rules: R2LockRule[] } => ({
|
|
59
|
+
rules: rules.map((rule) => ({ ...rule })),
|
|
60
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HomeFlare Cloudflare provider collection, in the shape `LiteLLM.providers()` has.
|
|
3
|
+
*
|
|
4
|
+
* ★ ONE LAYER, BUILT ONCE, so the single `Config.redacted('CLOUDFLARE_API_TOKEN')` read is shared
|
|
5
|
+
* by every resource in the stack rather than repeated per resource.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ THIS IS NOT `alchemy/Cloudflare`'s COLLECTION AND DOES NOT REPLACE IT. A stack that declares
|
|
8
|
+
* both a `Cloudflare.R2.Bucket` and this lock merges the two:
|
|
9
|
+
* `Layer.mergeAll(Cloudflare.providers(), HomeflareCloudflare.providers())` — the shape
|
|
10
|
+
* a consuming stack names for one talking to more than one account.
|
|
11
|
+
*/
|
|
12
|
+
import * as Provider from 'alchemy/Provider';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
14
|
+
import { CloudflareApiLive } from './client.ts';
|
|
15
|
+
import { R2BucketLock, R2BucketLockProvider } from './r2-bucket-lock.ts';
|
|
16
|
+
|
|
17
|
+
export class Providers extends Provider.ProviderCollection<Providers>()('HomeflareCloudflare') {}
|
|
18
|
+
|
|
19
|
+
export const providers = () =>
|
|
20
|
+
Layer.effect(Providers, Provider.collection([R2BucketLock])).pipe(
|
|
21
|
+
Layer.provide(R2BucketLockProvider()),
|
|
22
|
+
Layer.provideMerge(CloudflareApiLive),
|
|
23
|
+
);
|