@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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.Pool` — a PVE resource pool. Guests grouped for permissions and quota.
|
|
3
|
+
*
|
|
4
|
+
* ★ PROVEN END TO END AGAINST THE LIVE CLUSTER, 2026-09-13. create -> verified with `pvesh get
|
|
5
|
+
* /pools` on the node rather than by believing Alchemy; second plan -> `no changes / noop`; then
|
|
6
|
+
* the comment was edited ON THE CLUSTER and plan said `1 to update`, and a deploy repaired it.
|
|
7
|
+
* That drift case is the one that matters: `diff` reads the LIVE pool, so a provider trusting
|
|
8
|
+
* its own state would have reported noop straight past a hand-edit in the UI.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ IT 403'd UNTIL THE ROLE WAS WIDENED, AND THE HISTORY IS WORTH KEEPING. `hf-provision@pve`
|
|
11
|
+
* held `LXCProvisioner` on `/` with no `Pool.Allocate`, so reconcile answered
|
|
12
|
+
* "Permission check failed (/pool/house, Pool.Allocate)". The credential could not widen itself
|
|
13
|
+
* — `PUT /access/roles` is 403 for it too — so the role was extended over SSH with
|
|
14
|
+
* `pveum role modify LXCProvisioner --privs "<existing>,Pool.Allocate,Pool.Audit"`, preserving
|
|
15
|
+
* every existing privilege. Each new resource in this package should state the privileges its
|
|
16
|
+
* reconcile needs, so widening stays a deliberate act rather than a reaction to a 403.
|
|
17
|
+
*/
|
|
18
|
+
import { Resource } from 'alchemy';
|
|
19
|
+
import * as Provider from 'alchemy/Provider';
|
|
20
|
+
import type { PoolsPoolidPutParams, PoolsPostParams } from './generated/pve.ts';
|
|
21
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
22
|
+
export interface PoolProps extends WithTarget {
|
|
23
|
+
/** PVE's primary key for a pool. Changing it is a replace, not an update. */
|
|
24
|
+
poolid: string;
|
|
25
|
+
/** Free text shown in the UI — the only mutable field a pool has. */
|
|
26
|
+
comment?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface PoolAttributes {
|
|
29
|
+
poolid: string;
|
|
30
|
+
comment: string;
|
|
31
|
+
/** Guests currently in the pool, so a plan can say what a delete would refuse to orphan. */
|
|
32
|
+
members: number;
|
|
33
|
+
}
|
|
34
|
+
export interface ProxmoxPool extends Resource<'Proxmox.Pool', PoolProps, PoolAttributes, never, PveRequirements> {
|
|
35
|
+
}
|
|
36
|
+
export declare const ProxmoxPool: import("alchemy").ResourceClass<ProxmoxPool>;
|
|
37
|
+
/** Form bodies this family sends — exported for the drift test in pool.test.ts. */
|
|
38
|
+
export declare const poolCreateForm: (props: PoolProps) => PoolsPostParams;
|
|
39
|
+
/** PUT body — only `comment`; path carries `poolid`. Matches deprecated `/pools/{poolid}`. */
|
|
40
|
+
export declare const poolUpdateForm: (props: PoolProps) => Pick<PoolsPoolidPutParams, 'comment'>;
|
|
41
|
+
export declare const ProxmoxPoolProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxPool>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
42
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,cAAc,UAAW,SAAS,KAAG,eAGhD,CAAC;AAEH,8FAA8F;AAC9F,eAAO,MAAM,cAAc,UAAW,SAAS,KAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAEpF,CAAC;AAkBH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.Vm` — a QEMU virtual machine, declared.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ THE SAME FOUR OPERATIONS AS AN LXC OVER A DIFFERENT PATH, which is the point of the factory:
|
|
5
|
+
* `nodes/{node}/qemu` to create, `nodes/{node}/qemu/{vmid}/config` to read and update. A VM and a
|
|
6
|
+
* container differ in what they carry, not in how they are reconciled.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ `vmid` IS CLUSTER-WIDE AND SHARED WITH CONTAINERS. A VM and an LXC cannot hold the same id, so
|
|
9
|
+
* these two resources compete for one number space. Declaring both with vmid 101 is not a
|
|
10
|
+
* collision Alchemy can see -- they are different resource types -- and PVE refuses the second
|
|
11
|
+
* with "already exists". Pick ids per cluster, not per kind.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ POWER STATE IS REPORTED, NEVER DECLARED. `status` is an attribute so a plan can show it; there
|
|
14
|
+
* is no `running` prop. Starting and stopping a VM from a plan would make a deploy a maintenance
|
|
15
|
+
* window, and the estate has one of those already.
|
|
16
|
+
*/
|
|
17
|
+
import { Resource } from 'alchemy';
|
|
18
|
+
import * as Provider from 'alchemy/Provider';
|
|
19
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
20
|
+
export interface VmProps extends WithTarget {
|
|
21
|
+
node: string;
|
|
22
|
+
/** ⛔ Cluster-wide, and shared with LXC. See the ⛔ above. */
|
|
23
|
+
vmid: number;
|
|
24
|
+
name?: string;
|
|
25
|
+
/** MiB. */
|
|
26
|
+
memory?: number;
|
|
27
|
+
cores?: number;
|
|
28
|
+
sockets?: number;
|
|
29
|
+
/** e.g. `virtio=<mac>,bridge=vmbr0`. */
|
|
30
|
+
net0?: string;
|
|
31
|
+
onboot?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface VmAttributes {
|
|
34
|
+
vmid: number;
|
|
35
|
+
node: string;
|
|
36
|
+
name: string;
|
|
37
|
+
memory: number;
|
|
38
|
+
cores: number;
|
|
39
|
+
sockets: number;
|
|
40
|
+
onboot: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface ProxmoxVm extends Resource<'Proxmox.Vm', VmProps, VmAttributes, never, PveRequirements> {
|
|
43
|
+
}
|
|
44
|
+
export declare const ProxmoxVm: import("alchemy").ResourceClass<ProxmoxVm>;
|
|
45
|
+
export declare const ProxmoxVmProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxVm>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
46
|
+
//# sourceMappingURL=qemu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qemu.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,OAAQ,SAAQ,UAAU;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ,CACzC,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,SAAS,4CAAoC,CAAC;AAiC3D,eAAO,MAAM,iBAAiB,+HAC+C,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A replication job's props, as the id and the forms PVE wants.
|
|
3
|
+
*
|
|
4
|
+
* ★ SPLIT OUT OF replication-job.ts TO KEEP BOTH FILES UNDER THE 250-LINE CAP, on the same seam
|
|
5
|
+
* metric-server-form.ts uses and for the same reason: this file answers "how does a declaration
|
|
6
|
+
* become a PVE write", and replication-job.ts answers "what is a replication job and when has it
|
|
7
|
+
* changed". Nothing here reads the cluster and nothing here decides a diff.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ THE `import type` BACK TO replication-job.ts IS A CYCLE ON PAPER ONLY. It is type-only, so it
|
|
10
|
+
* is erased before anything runs, and `ReplicationJobProps` stays the resource's public shape in
|
|
11
|
+
* the file that declares the resource rather than being moved somewhere odd to dodge the arrow.
|
|
12
|
+
*/
|
|
13
|
+
import type { ReplicationJobProps } from './replication-job.ts';
|
|
14
|
+
/**
|
|
15
|
+
* PVE's own documented default for `schedule`, applied on BOTH sides of the comparison.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ IT LIVES HERE BECAUSE BOTH HALVES NEED THE SAME STRING. `shape` sends it and `attributes`
|
|
18
|
+
* falls back to it; two copies of that literal in two files is exactly the drift that makes a
|
|
19
|
+
* plan report an update over a value nobody set.
|
|
20
|
+
* ⚠️ AND IT IS NEVER SPELLED OUT IN A COMMENT, because the string PVE chose ENDS A BLOCK COMMENT:
|
|
21
|
+
* the two characters before the `15` are the comment terminator, and tsc says so loudly. It is
|
|
22
|
+
* referred to by name everywhere else in these two files for that reason, not for style.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_SCHEDULE = "*/15";
|
|
25
|
+
/**
|
|
26
|
+
* `{guest, jobnum}` -> `100-0`, the id in the only spelling PVE ever answers with.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ NUMBERS IN, SO CANONICAL OUT. PVE runs both halves of a job id through `int()` on parse — see
|
|
29
|
+
* the ⛔ in replication-job.ts — and a JavaScript number has no leading-zero spelling to lose.
|
|
30
|
+
* That is the whole reason this resource is keyed on two numbers instead of one string.
|
|
31
|
+
*/
|
|
32
|
+
export declare const jobId: (props: ReplicationJobProps) => string;
|
|
33
|
+
/**
|
|
34
|
+
* ⚠️ `type` IS A CONSTANT RATHER THAN A PROP: `local` is the only section type PVE registers, and
|
|
35
|
+
* an enum of one value is a prop that can only ever be wrong. `id` and `target` are both required
|
|
36
|
+
* by the create schema and neither belongs in `shape` — `target` because PUT will not take it.
|
|
37
|
+
* ⛔ THIS LINE IS THE ONLY PLACE PVE'S KEY `target` IS SPELLED, and the value beside it comes from
|
|
38
|
+
* `targetNode`. The two words are different objects — a node name here, the whole cluster in
|
|
39
|
+
* `WithTarget.target` — and the ⛔ in replication-job.ts explains why the rename was forced.
|
|
40
|
+
*/
|
|
41
|
+
export declare const createBody: (props: ReplicationJobProps) => {
|
|
42
|
+
comment: string;
|
|
43
|
+
disable: string;
|
|
44
|
+
schedule: string;
|
|
45
|
+
rate?: string;
|
|
46
|
+
id: string;
|
|
47
|
+
target: string;
|
|
48
|
+
type: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* ⛔ `delete=remove_job` ON EVERY UPDATE IS THE DELIBERATE PART. PVE's PUT merges the form into the
|
|
52
|
+
* section and cannot unset anything, so without this the removal marker a DELETE wrote would
|
|
53
|
+
* survive every reconcile and `matches` would report an update that never lands. MEASURED as
|
|
54
|
+
* legal: the update handler refuses `delete` only for required or `fixed` options, and
|
|
55
|
+
* `remove_job => { optional => 1 }` is neither; deleting a key that is not set is a no-op, so
|
|
56
|
+
* this is safe to send unconditionally.
|
|
57
|
+
* ⛔ AND THE DELETE LIST IS THIS ONE CONSTANT KEY, NEVER A COMPUTED "everything you did not
|
|
58
|
+
* declare". backup-job.ts spells out why the computed form is dangerous — it strips settings the
|
|
59
|
+
* resource does not even model off a job adopted from the UI. `remove_job` cannot be declared
|
|
60
|
+
* here at all, so clearing it can only ever undo a removal this stack did not ask for.
|
|
61
|
+
* ⚠️ IT CANCELS A REMOVAL SOMEBODY STARTED, WHICH IS THE POINT AND IS STILL WORTH KNOWING. If the
|
|
62
|
+
* removal pass already ran its local-snapshot half, the revived job re-sends in full.
|
|
63
|
+
*/
|
|
64
|
+
export declare const updateBody: (props: ReplicationJobProps) => {
|
|
65
|
+
comment: string;
|
|
66
|
+
disable: string;
|
|
67
|
+
schedule: string;
|
|
68
|
+
rate?: string;
|
|
69
|
+
delete: string;
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=replication-job-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replication-job-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/replication-job-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,UAAW,mBAAmB,WACE,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,UAAW,mBAAmB;;;;;;;;CAKnD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,UAAW,mBAAmB;;;;;;CAGnD,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.ReplicationJob` — a guest's ZFS storage replication to a second node, declared.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ NOTHING ON TB4 CAN USE THIS FAMILY, AND THAT IS SAID HERE RATHER THAN FOUND OUT LATER. PVE
|
|
5
|
+
* storage replication is ZFS-only: `POST /cluster/replication` calls `get_replicatable_volumes`
|
|
6
|
+
* and refuses a guest whose disks are anywhere else with "No replicatable volumes found". TB4 is
|
|
7
|
+
* Ceph/RBD-backed — shared storage, where replication means nothing because every node already
|
|
8
|
+
* sees the volume — so `GET /cluster/replication` answers `[]` (MEASURED on n2, 2026-09-13) and
|
|
9
|
+
* on this cluster always will. The file is written for a ZFS cluster; against TB4 a declaration
|
|
10
|
+
* is a create that fails with PVE's own sentence, not a plan that quietly does nothing.
|
|
11
|
+
*
|
|
12
|
+
* ★ WHAT IS MEASURED HERE AND WHAT IS REASONED. Measured on n2 (pve-manager 9.2.11), read-only:
|
|
13
|
+
* the published schema for all five methods; `PVE/ReplicationConfig.pm` and
|
|
14
|
+
* `PVE/API2/ReplicationConfig.pm` line by line; `GET /cluster/replication` -> `[]`; and the ACL
|
|
15
|
+
* row `{"path":"/","propagate":1,"roleid":"PVEAuditor","ugid":"hf-read@pve"}`. NOT measured: any
|
|
16
|
+
* round trip. There is no live job to adopt and none can be created on this cluster, so the
|
|
17
|
+
* argument that a matching declaration plans as `noop` rests on the schema and on PVE's own
|
|
18
|
+
* source — not on a plan anybody ran. Nobody should claim otherwise until a ZFS cluster exists.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ THE ID IS STRUCTURED AND PVE RE-SPELLS IT, so this resource is keyed on the two numbers rather
|
|
21
|
+
* than on the string — a deliberate break from `backup-job.ts`'s "props are PVE's own key names".
|
|
22
|
+
* MEASURED: `parse_replication_job_id` matches `^(\d+)-(\d+)$`, runs BOTH halves through `int()`
|
|
23
|
+
* and returns "$guest-$jobnum", and `parse_section_header` does the same when reading
|
|
24
|
+
* replication.cfg. So a declared `100-007` is read back as `100-7`, and a provider holding the
|
|
25
|
+
* raw string would compare two spellings of one id forever. Two numbers cannot be spelled two
|
|
26
|
+
* ways. PVE also refuses `guest < 100` ("guest IDs < 100 are reserved") and the API pattern
|
|
27
|
+
* `[1-9][0-9]{2,8}-\d{1,9}` caps both halves at nine digits.
|
|
28
|
+
* ⛔ BOTH HALVES ARE REQUIRED AND NEITHER IS DEFAULTED, for `backup-job.ts`'s reason: a provider
|
|
29
|
+
* that picks a job number picks a DIFFERENT job from the one a person meant, and then adopts or
|
|
30
|
+
* creates beside the real one. Declare the numbers of the job you mean.
|
|
31
|
+
*
|
|
32
|
+
* ⛔ PVE'S `target` AND THIS PACKAGE'S `target` ARE DIFFERENT THINGS, AND THE COMPILER SAID SO
|
|
33
|
+
* BEFORE A REVIEWER DID. Every resource here extends `WithTarget`, whose `target` is the CLUSTER
|
|
34
|
+
* a call goes to (`PveTarget`, carrying the API base and the credential mount). PVE's replication
|
|
35
|
+
* `target` is a NODE NAME. They cannot both be called `target` in one props interface — tsc:
|
|
36
|
+
* "Interface 'ReplicationJobProps' incorrectly extends interface 'WithTarget'" — so the prop and
|
|
37
|
+
* the attribute are `targetNode`, and PVE's spelling appears exactly once, in `createBody`, where
|
|
38
|
+
* it is the wire key. That is the only safe shape: had the collision been resolvable by silence,
|
|
39
|
+
* `createForm` would have serialised a whole `PveTarget` object into a form field called `target`
|
|
40
|
+
* and PVE would have answered that the node does not exist.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ ONE JOB PER (GUEST, TARGET), ENFORCED BY PVE AND NOT BY THIS FILE. MEASURED: `write_config`
|
|
43
|
+
* dies "replication job for guest '$vmid' to target '$tid' already exists" where `$tid` is
|
|
44
|
+
* `local/<target>`, and `parse_config` DELETES the loser on read if two ever reach the file. Two
|
|
45
|
+
* declarations differing only in `jobnum` but naming the same target are not two jobs; the second
|
|
46
|
+
* is a create that fails.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ RECONCILE NEEDS `VM.Replicate` ON `/vms/<guest>`, WHICH `LXCProvisioner` DOES NOT HOLD. All
|
|
49
|
+
* three writes check it (MEASURED in API2/ReplicationConfig.pm: create, update and delete each
|
|
50
|
+
* call `$rpcenv->check($authuser, "/vms/$vmid", ['VM.Replicate'])`), and the role's 27 privileges
|
|
51
|
+
* include `VM.Allocate`, `VM.Audit`, `VM.Backup`, `VM.Config.*` and `VM.PowerMgmt` but not
|
|
52
|
+
* `VM.Replicate`. Widen it deliberately over SSH, preserving what is there, as `pool.ts` records:
|
|
53
|
+
* `pveum role modify LXCProvisioner --privs "<existing>,VM.Replicate"`.
|
|
54
|
+
* ★ THE READ LANE NEEDS NOTHING EXTRA, which is why there is no `readRole` below. The ITEM read
|
|
55
|
+
* checks `VM.Audit` on `/vms/<guest>`, `hf-read@pve` holds the built-in `PVEAuditor` at `/`
|
|
56
|
+
* with propagate — both MEASURED — and `PVEAuditor` carries `VM.Audit`. This family is not a
|
|
57
|
+
* fourth one that must borrow the provision lease.
|
|
58
|
+
* ⚠️ A MISSING JOB IS A 500, NOT A 404 — "no such replication job '100-0'" — and the factory
|
|
59
|
+
* folds any failed read into "absent", so a 403 reads as absent too. A plan insisting on
|
|
60
|
+
* creating a job that plainly exists is a credential problem; the honest error arrives one step
|
|
61
|
+
* later, from the create.
|
|
62
|
+
*
|
|
63
|
+
* ⛔ DELETE DOES NOT DELETE, AND A DESTROY IS NOT FREE. MEASURED: with neither `force` nor `keep` —
|
|
64
|
+
* and the factory's `destroy` sends no form at all — the handler only sets `remove_job = 'full'`
|
|
65
|
+
* on the job and writes the config back. The job stays in replication.cfg until the SOURCE node's
|
|
66
|
+
* `pvesr` timer next runs it, at which point it removes the local replication snapshots, removes
|
|
67
|
+
* the replicated volumes on the target, and finally removes itself. So a destroy costs the
|
|
68
|
+
* standby copy and the incremental base: re-declaring the job afterwards is a FULL send, not a
|
|
69
|
+
* resume. The guest's own disks are untouched, which is exactly why this family does NOT carry
|
|
70
|
+
* `defaultRemovalPolicy: 'retain'` the way `ZfsPool` and `Storage` do — what it destroys is a
|
|
71
|
+
* copy a re-sync rebuilds, expensively, rather than something irreplaceable.
|
|
72
|
+
* ⚠️ AND IF THE SOURCE NODE IS DOWN THE REMOVAL NEVER RUNS. Alchemy drops the state row on a
|
|
73
|
+
* DELETE that returned no error, so a destroy against a dead source leaves a marked job on the
|
|
74
|
+
* cluster that nothing declares any more. `GET /cluster/replication` is where it shows up.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ THE RUNTIME STATUS IS NOT HERE, AND NOT BECAUSE IT WAS FORGOTTEN. `last_sync`, `last_try`,
|
|
77
|
+
* `fail_count`, `error`, `duration` and `pid` come from the per-node replication STATE file and
|
|
78
|
+
* are merged in by `GET /nodes/{node}/replication` (MEASURED: API2/Replication.pm line 179,
|
|
79
|
+
* `foreach my $k (qw(last_sync last_try fail_count error duration))`). `GET
|
|
80
|
+
* /cluster/replication/{id}` — the only endpoint this resource reads — returns not one of them.
|
|
81
|
+
* Reporting them would mean a second, per-node call on the read path answering a different value
|
|
82
|
+
* every fifteen minutes, and a plan whose output changes when nothing changed is a plan people
|
|
83
|
+
* stop reading. Ask the node for status; ask the cluster for configuration.
|
|
84
|
+
*/
|
|
85
|
+
import { Resource } from 'alchemy';
|
|
86
|
+
import * as Provider from 'alchemy/Provider';
|
|
87
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
88
|
+
export interface ReplicationJobProps extends WithTarget {
|
|
89
|
+
/** ⛔ The guest being replicated. 100 or above — PVE reserves everything below. Identity. */
|
|
90
|
+
guest: number;
|
|
91
|
+
/** ⛔ The job number within that guest, 0 upwards. Identity. Never generated — see the header. */
|
|
92
|
+
jobnum: number;
|
|
93
|
+
/**
|
|
94
|
+
* ⛔ THE NODE THE COPY LANDS ON — PVE's `target`, RENAMED; see the ⛔ in the header — AND IT IS
|
|
95
|
+
* CREATE-ONLY. MEASURED: the plugin declares `target => { fixed => 1, optional => 0 }`, so
|
|
96
|
+
* `updateSchema()` omits it entirely — `PUT` has no `target` parameter at all, and
|
|
97
|
+
* `delete=target` answers "unable to delete fixed option". It is therefore NOT compared; see
|
|
98
|
+
* the ⚠️ on `matches`. Changing it in a declaration plans as NOOP. Retire the declaration and
|
|
99
|
+
* add another, exactly as `user.ts` says for `userid`.
|
|
100
|
+
* ⚠️ PVE refuses a target equal to the guest's current node ("Source and target must not be
|
|
101
|
+
* identical") and one that is not a cluster member. Neither is checkable from here.
|
|
102
|
+
*/
|
|
103
|
+
targetNode: string;
|
|
104
|
+
/**
|
|
105
|
+
* A subset of systemd calendar events. Unset means PVE's own default — `DEFAULT_SCHEDULE`,
|
|
106
|
+
* every fifteen minutes — and that default is what gets SENT and compared, not silence.
|
|
107
|
+
* ⚠️ THIS COMPARES STRINGS, NOT MEANINGS. A calendar event has many equivalent spellings and
|
|
108
|
+
* PVE stores the one it was handed, verbatim, so declare the spelling the cluster echoes.
|
|
109
|
+
*/
|
|
110
|
+
schedule?: string;
|
|
111
|
+
/** Rate limit in mbps as a float, 1 or above. Unset sends nothing and compares nothing. */
|
|
112
|
+
rate?: number;
|
|
113
|
+
/** Keep the job but stop replicating. Unset is an ACTIVE job, and that is what is compared. */
|
|
114
|
+
disable?: boolean;
|
|
115
|
+
/** Free text shown in the UI. Round-trips through PVE's `encode_text`/`decode_text`. */
|
|
116
|
+
comment?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface ReplicationJobAttributes {
|
|
119
|
+
/** `100-0`, in PVE's own int-parsed spelling. */
|
|
120
|
+
id: string;
|
|
121
|
+
/**
|
|
122
|
+
* ⚠️ REPORTED AS INTEGERS, NEVER COMPARED, AND THIS IS THE "id written as a name" TRAP. MEASURED:
|
|
123
|
+
* `parse_config` sets both from the section header via `int()`, so they are the id re-spelled
|
|
124
|
+
* as numbers and no write accepts either. Compared against anything they would be an update
|
|
125
|
+
* forever; reported, they let a plan say which guest is about to lose a replica.
|
|
126
|
+
*/
|
|
127
|
+
guest: number;
|
|
128
|
+
jobnum: number;
|
|
129
|
+
/** Always `local` — the only section type PVE registers. Create-only, reported, not compared. */
|
|
130
|
+
type: string;
|
|
131
|
+
/**
|
|
132
|
+
* ⚠️ REPORTED, NEVER COMPARED. Create-only AND rewritten by PVE — see `matches`. Named for the
|
|
133
|
+
* prop rather than for PVE's wire key `target`, so that one file never means two things by one
|
|
134
|
+
* word; the header says why the wire name could not be kept.
|
|
135
|
+
*/
|
|
136
|
+
targetNode: string;
|
|
137
|
+
/** ⚠️ REPORTED, NEVER SENT. PVE's own bookkeeping — see the ⛔ in replication-job-form.ts. */
|
|
138
|
+
source: string;
|
|
139
|
+
schedule: string;
|
|
140
|
+
/** 0 when unset. See `rateOf`. */
|
|
141
|
+
rate: number;
|
|
142
|
+
disable: boolean;
|
|
143
|
+
comment: string;
|
|
144
|
+
/**
|
|
145
|
+
* `''`, `local` or `full`. ⛔ NOT COSMETIC: a non-empty value is a job that is deleting itself,
|
|
146
|
+
* so `matches` refuses it and `updateBody` clears it. See both.
|
|
147
|
+
*/
|
|
148
|
+
remove_job: string;
|
|
149
|
+
}
|
|
150
|
+
export interface ProxmoxReplicationJob extends Resource<'Proxmox.ReplicationJob', ReplicationJobProps, ReplicationJobAttributes, never, PveRequirements> {
|
|
151
|
+
}
|
|
152
|
+
export declare const ProxmoxReplicationJob: import("alchemy").ResourceClass<ProxmoxReplicationJob>;
|
|
153
|
+
/**
|
|
154
|
+
* ⛔ Empty `list` like every other resource here: `GET /cluster/replication` hands back every job an
|
|
155
|
+
* operator ever made, and adopting one is how a later `alchemy destroy` takes away a standby copy
|
|
156
|
+
* nobody declared. Adoption is an explicit act — declare the `guest` and `jobnum` you mean.
|
|
157
|
+
* ⚠️ NOTHING BRAKES THIS DESTROY. PVE refuses to delete a pool that still holds guests; it accepts
|
|
158
|
+
* this without a word, because nothing depends on a replication job. Re-read the DELETE ⛔ at the
|
|
159
|
+
* top of this file before approving a plan line that removes one.
|
|
160
|
+
*/
|
|
161
|
+
export declare const ProxmoxReplicationJobProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxReplicationJob>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
162
|
+
//# sourceMappingURL=replication-job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replication-job.d.ts","sourceRoot":"","sources":["../../src/proxmox/replication-job.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,mBAAoB,SAAQ,UAAU;IACrD,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;IACd,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAA4D,CAAC;AAwE/F;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,2IAIvC,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Input } from 'alchemy/Input';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import type * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
4
|
+
import type { ApiTarget, PbsTarget, PveRole, PveTarget } from './credentials.ts';
|
|
5
|
+
/**
|
|
6
|
+
* ★ WHY SEVERAL FAMILIES HERE DECLARE `defaultRemovalPolicy: 'retain'`, WRITTEN ONCE.
|
|
7
|
+
*
|
|
8
|
+
* Alchemy's own convention: a few resource types default to retain "because their contents are
|
|
9
|
+
* irreplaceable" — `GitHub.Repository` and `Cloudflare.Zone` do — and those opt into deletion
|
|
10
|
+
* with `destroy()`. `retain` means the engine SKIPS `provider.delete` entirely when a resource
|
|
11
|
+
* is orphaned or destroyed: the state row is dropped, the cluster object lives on.
|
|
12
|
+
*
|
|
13
|
+
* So the PVE families whose contents cannot be rebuilt from a line of TypeScript — CephOsd,
|
|
14
|
+
* CephPool, CephFs, CephDaemon, ZfsPool, Storage, NodeNetwork — carry that default. Their
|
|
15
|
+
* `delete` is FULLY IMPLEMENTED and runs the moment a caller opts in with
|
|
16
|
+
* `.pipe(RemovalPolicy.destroy())`. This is deliberately the Terraform `prevent_destroy` shape
|
|
17
|
+
* rather than a stubbed-out operation: a `delete` that silently does nothing lies to whoever is
|
|
18
|
+
* reading the plan, and the lie is discovered at the worst possible time.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ THE POLICY IS A DECORATION, NOT A PROP, so changing it produces NO DIFF — the resource plans
|
|
21
|
+
* as a noop and the deploy re-commits the row's policy in place. A policy change therefore takes
|
|
22
|
+
* effect from the very next deploy even though the plan shows nothing.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* ⛔ A RESOURCE IS PINNED TO ITS PRODUCT; ONLY THE FACTORY TAKES EITHER. The two hosts are the same
|
|
26
|
+
* two strings, so without a required discriminant TypeScript would accept a PBS host wherever a
|
|
27
|
+
* PVE one belongs — silently, with every call 401ing and `read` folding that into "absent". The
|
|
28
|
+
* whole argument is on `scheme` in credentials.ts.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* ★ WHAT EVERY OPERATION IN THIS PACKAGE NEEDS FROM THE RUNTIME, NAMED ONCE.
|
|
32
|
+
*
|
|
33
|
+
* `HttpClient`, for both halves of every call: the credential is minted from OpenBao's HTTP API
|
|
34
|
+
* and the PVE call goes through the same client. That is what Alchemy's own providers do
|
|
35
|
+
* (src/Hetzner/Providers.ts builds on `FetchHttpClient.layer`, and there is no bare `fetch` in
|
|
36
|
+
* its Docker, Kubernetes or GitHub providers).
|
|
37
|
+
* ★ `ChildProcessSpawner` WAS HERE UNTIL 2026-09-14, while the mint shelled out to `bao`. The HTTP
|
|
38
|
+
* client replaced that (credentials.ts), so the requirement went with it.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ SPELLED OUT AT EACH OF THE FORTY-ODD RESOURCES this would be a union nobody keeps in step —
|
|
41
|
+
* one file left on the old shape is a type error at the stack, far from the cause. Named here,
|
|
42
|
+
* adding a third service later is one edit.
|
|
43
|
+
*/
|
|
44
|
+
export type PveRequirements = HttpClient.HttpClient;
|
|
45
|
+
export type WithTarget = {
|
|
46
|
+
target: PveTarget;
|
|
47
|
+
};
|
|
48
|
+
export type WithPbsTarget = {
|
|
49
|
+
target: PbsTarget;
|
|
50
|
+
};
|
|
51
|
+
export type WithApiTarget = {
|
|
52
|
+
target: ApiTarget;
|
|
53
|
+
};
|
|
54
|
+
export type PveSpec<Props extends WithApiTarget, Attributes> = {
|
|
55
|
+
/** `pools/house`, `nodes/n2/lxc/101` — where ONE object is read, updated and deleted. */
|
|
56
|
+
readonly path: (props: Props) => string;
|
|
57
|
+
/** `pools`, `nodes/n2/lxc` — where a NEW one is POSTed. */
|
|
58
|
+
readonly collection: (props: Props) => string;
|
|
59
|
+
/** Live JSON to attributes. Returning undefined means "this is not really there". */
|
|
60
|
+
readonly attributes: (live: Record<string, unknown>, props: Props) => Attributes | undefined;
|
|
61
|
+
/** The form PVE wants on create. ⚠️ PVE takes form encoding, not JSON. */
|
|
62
|
+
readonly createForm: (props: Props) => Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* The form for an update, or undefined when the object has no mutable fields.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ SOME PVE OBJECTS CANNOT BE UPDATED AT ALL. Returning undefined makes a changed prop a
|
|
67
|
+
* REPLACE rather than a silent no-op, which is the honest answer for an immutable object.
|
|
68
|
+
*/
|
|
69
|
+
readonly updateForm?: (props: Props) => Record<string, string>;
|
|
70
|
+
/** True when live already matches props. Decides noop vs update. */
|
|
71
|
+
readonly matches: (attributes: Attributes, props: Props) => boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Which lease reads this family. Defaults to `read`, the 3600s auditor-shaped one.
|
|
74
|
+
*
|
|
75
|
+
* ⛔ THREE FAMILIES SET THIS TO `provision`, AND THE FAILURE IT AVOIDS IS SILENT. PVE gates some
|
|
76
|
+
* SINGLE-OBJECT reads on the allocate privilege rather than the audit one — `/storage/{id}`,
|
|
77
|
+
* `/cluster/sdn/zones/{zone}`, `/cluster/sdn/vnets/{vnet}` — while their COLLECTION reads
|
|
78
|
+
* accept audit, so nothing looks wrong until a resource reads one object. `read` below folds
|
|
79
|
+
* every failure into `undefined`, right for a 404 and wrong for a 403, so the plan says create
|
|
80
|
+
* and PVE answers that the object already exists. Measured both ways on `local`, and written
|
|
81
|
+
* up with the alternative that was rejected, in docs/privileges.md.
|
|
82
|
+
*/
|
|
83
|
+
readonly readRole?: PveRole;
|
|
84
|
+
};
|
|
85
|
+
export declare const pveOperations: <Props extends WithApiTarget, Attributes>(spec: PveSpec<Props, Attributes>) => {
|
|
86
|
+
read: (props: Props) => Effect.Effect<Attributes | undefined, never, HttpClient.HttpClient>;
|
|
87
|
+
diff: (news: Input<Props>, output: Attributes | undefined) => Effect.Effect<{
|
|
88
|
+
readonly action: 'update';
|
|
89
|
+
} | {
|
|
90
|
+
readonly action: 'noop';
|
|
91
|
+
} | {
|
|
92
|
+
readonly action: 'replace';
|
|
93
|
+
} | undefined, never, HttpClient.HttpClient>;
|
|
94
|
+
reconcile: (news: Props) => Effect.Effect<Attributes & ({} | null), Error, HttpClient.HttpClient>;
|
|
95
|
+
/**
|
|
96
|
+
* ⚠️ PVE REFUSES TO DELETE THINGS THAT ARE STILL IN USE — a pool holding guests, a storage with
|
|
97
|
+
* volumes. That refusal is the cluster declining to orphan something because a line left a
|
|
98
|
+
* file, so it is surfaced as-is rather than retried with a force flag.
|
|
99
|
+
*/
|
|
100
|
+
destroy: (olds: Props) => Effect.Effect<unknown, Error, HttpClient.HttpClient>;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* The five provider handlers for a spec'd PVE object, wired once.
|
|
104
|
+
*
|
|
105
|
+
* ⛔ THIS BLOCK WAS WRITTEN TEN TIMES BEFORE IT WAS EXTRACTED, and every copy was character for
|
|
106
|
+
* character identical apart from its comments: `list` answering empty, and four handlers whose
|
|
107
|
+
* entire body was `yield* ops.<same name>(<same argument>)`. Ten copies of a delegation is ten
|
|
108
|
+
* chances to delegate to the wrong one — `ops.read(olds)` inside `reconcile` typechecks, returns
|
|
109
|
+
* a plausible value, and turns every deploy into a no-op that reports success.
|
|
110
|
+
*
|
|
111
|
+
* ★ IT TAKES THE SPEC, NOT THE OPERATIONS, so a resource declares its four spec functions and
|
|
112
|
+
* stops. `pveOperations` stays exported for an object that genuinely needs a handler of its own;
|
|
113
|
+
* nothing in this package does yet, and the day one does it should be visibly different from the
|
|
114
|
+
* ten that are not.
|
|
115
|
+
*
|
|
116
|
+
* ⛔ IT STOPS AT THE HANDLERS AND DOES NOT RETURN THE LAYER, AND THAT IS NOT AN OVERSIGHT — it was
|
|
117
|
+
* written the other way first and reverted. Wrapping `Provider.effect(cls, …)` here means naming
|
|
118
|
+
* the resource class generically, and Alchemy's `Props<R>` and `R["Attributes"]` are not the
|
|
119
|
+
* plain `Props` and `Attributes` this spec is written against: `Attributes` comes back as
|
|
120
|
+
* `Attributes & AttrOutput<Attributes["Attributes"]>`. Against a free type variable TypeScript
|
|
121
|
+
* cannot prove those line up — "could be instantiated with a different subtype" — so the only
|
|
122
|
+
* way to compile a Layer-returning version is a cast, and a cast is a LIE ABOUT WHETHER THE
|
|
123
|
+
* HANDLERS MATCH THE RESOURCE, which is the one thing the call site exists to check. Leaving
|
|
124
|
+
* `Cls.Provider.of(...)` in each file keeps that check where the types are concrete and real.
|
|
125
|
+
* One line per resource is the price of it, and it is worth paying.
|
|
126
|
+
*
|
|
127
|
+
* ⚠️ `list` IS OPTIONAL IN `ProviderServiceInput` AND IS PASSED ANYWAY. An empty list is a claim —
|
|
128
|
+
* this provider adopts nothing — and it is made here for every PVE object at once, because the
|
|
129
|
+
* reasoning is identical for all of them: every PVE index endpoint answers with the whole
|
|
130
|
+
* cluster's objects, PVE's own built-ins included. `GET /pools` returns pools a human made years
|
|
131
|
+
* ago; `GET /access/roles` returns `Administrator`. Returning any of them would invite Alchemy to
|
|
132
|
+
* adopt an object it never created, and therefore one day to narrow or delete it. Adoption stays
|
|
133
|
+
* an explicit act. A resource with a reason to differ passes its own `list` and says why.
|
|
134
|
+
*/
|
|
135
|
+
export declare const pveHandlers: <Props extends WithApiTarget, Attributes extends object>(spec: PveSpec<Props, Attributes>) => {
|
|
136
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
137
|
+
read: ({ olds }: {
|
|
138
|
+
olds: Props;
|
|
139
|
+
}) => Effect.Effect<Attributes | undefined, never, HttpClient.HttpClient>;
|
|
140
|
+
diff: ({ news, output }: {
|
|
141
|
+
news: Input<Props>;
|
|
142
|
+
output: Attributes | undefined;
|
|
143
|
+
}) => Effect.Effect<{
|
|
144
|
+
readonly action: 'update';
|
|
145
|
+
} | {
|
|
146
|
+
readonly action: 'noop';
|
|
147
|
+
} | {
|
|
148
|
+
readonly action: 'replace';
|
|
149
|
+
} | undefined, never, HttpClient.HttpClient>;
|
|
150
|
+
reconcile: ({ news }: {
|
|
151
|
+
news: Props;
|
|
152
|
+
}) => Effect.Effect<Attributes, Error, HttpClient.HttpClient>;
|
|
153
|
+
delete: ({ olds }: {
|
|
154
|
+
olds: Props;
|
|
155
|
+
}) => Effect.Effect<unknown, Error, HttpClient.HttpClient>;
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/proxmox/resource.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,UAAU,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAC/C,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,OAAO,CAAC,KAAK,SAAS,aAAa,EAAE,UAAU,IAAI;IAC7D,yFAAyF;IACzF,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IACxC,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC9C,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,UAAU,GAAG,SAAS,CAAC;IAC7F,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9D;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/D,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACpE;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,SAAS,aAAa,EAAE,UAAU,QAC7D,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC;kBAGX,KAAK;iBAiBX,KAAK,CAAC,KAAK,CAAC,UAAU,UAAU,GAAG,SAAS;yBASZ,QAAQ;;yBACF,MAAM;;yBAGtC,SAAS;;sBAIV,KAAK;IA6DvB;;;;OAIG;oBACa,KAAK;CAExB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,SAAS,aAAa,EAAE,UAAU,SAAS,MAAM,QAC1E,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC;IAI9B,IAAI;IACJ,IAAI,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IAChC,IAAI,qBAAqB;QAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;KAAE;yBAtHlC,QAAQ;;yBACF,MAAM;;yBAGtC,SAAS;;IAoH5B,SAAS,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IACrC,MAAM,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;CAErC,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.Role` — a PVE role: one name, one set of privileges. The object every other resource
|
|
3
|
+
* in this package stands on, because a role is what the provision credential actually holds.
|
|
4
|
+
*
|
|
5
|
+
* ★ THIS IS WHERE pool.ts's ⚠️ STOPS BEING A MEMORY. That file records, in prose, that the
|
|
6
|
+
* provision role lacked `Pool.Allocate`, that reconcile answered "Permission check failed", and
|
|
7
|
+
* that a human widened the role over SSH with `pveum role modify`. Prose cannot be planned
|
|
8
|
+
* against: it goes stale the first time somebody edits the role in the UI and tells nobody.
|
|
9
|
+
* Declared here, the same fact is a diff — the privileges the role is SUPPOSED to hold are read
|
|
10
|
+
* off the cluster on every plan, and a hand edit shows up as `1 to update` rather than as a 403
|
|
11
|
+
* six weeks later in the middle of something else.
|
|
12
|
+
*
|
|
13
|
+
* ⛔ THE INDEX AND THE ITEM DISAGREE ABOUT WHAT `privs` IS, AND MISSING THAT COSTS A DIFF THAT
|
|
14
|
+
* NEVER CONVERGES. `GET /access/roles` (the index) reports each role's privileges as a COMMA
|
|
15
|
+
* STRING. `GET /access/roles/{roleid}` — the path this resource reads — returns a privilege MAP
|
|
16
|
+
* instead, `{"VM.Allocate":1,"Sys.Audit":1,…}`, and its key order is a Perl hash's order, so it
|
|
17
|
+
* is not stable between two calls to the same endpoint. Compare the raw string, or the raw
|
|
18
|
+
* object, or the keys in the order they arrived, and every plan reports an update forever.
|
|
19
|
+
* `canonical` below is the whole answer: both sides become a sorted, de-duplicated set before
|
|
20
|
+
* anything is compared, and `attributes` STORES the sorted form so Alchemy's state does not
|
|
21
|
+
* churn either.
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ A DECLARATION REPLACES THE PRIVILEGE SET; IT DOES NOT ADD TO IT. `PUT /access/roles/{roleid}`
|
|
24
|
+
* accepts an `append` flag and this resource deliberately never sends it — with append a role
|
|
25
|
+
* could only ever grow, so `matches` would report an update forever whenever props were a subset
|
|
26
|
+
* of live, which is the same perpetual diff by a different road. The consequence is the one that
|
|
27
|
+
* bites: anything a human added by hand is REVOKED on the next deploy unless it is in `privs`.
|
|
28
|
+
* Copy the live set out of `pveum role list` before declaring a role that already exists; do not
|
|
29
|
+
* type it from memory.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ A ROLE CAN LOCK ITS OWN PROVIDER OUT, AND NOTHING IN THIS PACKAGE CAN UNDO IT. Reconcile runs
|
|
32
|
+
* as the provision credential, and that credential holds a role. Declare THAT role without the
|
|
33
|
+
* privileges the provider needs — `Sys.Modify` on `/access` above all — and the write succeeds,
|
|
34
|
+
* after which every later plan reads 403 and the repair has to happen out of band as `root@pam`
|
|
35
|
+
* over SSH. It is a one-way door, which is why the privileges a reconcile needs are written
|
|
36
|
+
* down at the bottom of this comment rather than left to be rediscovered from an error message.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ PVE REFUSES TO EDIT ITS OWN BUILT-IN ROLES (`Administrator`, `NoAccess`, the `PVE*` set), and
|
|
39
|
+
* the item endpoint gives no sign of which those are — only the index carries the `special`
|
|
40
|
+
* flag. A declaration aimed at a built-in role therefore READS BACK CLEANLY and fails at write
|
|
41
|
+
* time, which reads like a broken provider rather than like a refusal. Declare roles you own.
|
|
42
|
+
*
|
|
43
|
+
* ★ PRIVILEGES A RECONCILE NEEDS: `Sys.Audit` on `/access` to read and diff, `Sys.Modify` on
|
|
44
|
+
* `/access` to create, update or delete. Not `Permissions.Modify` — binding a role to a user is
|
|
45
|
+
* `/access/acl`, a different object, and nothing here writes it. An auditor-shaped credential
|
|
46
|
+
* can already PLAN this resource and cannot DEPLOY it, which is the honest failure: the plan is
|
|
47
|
+
* true and the deploy says exactly which privilege is missing.
|
|
48
|
+
*/
|
|
49
|
+
import { Resource } from 'alchemy';
|
|
50
|
+
import * as Provider from 'alchemy/Provider';
|
|
51
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
52
|
+
export interface RoleProps extends WithTarget {
|
|
53
|
+
/**
|
|
54
|
+
* PVE's primary key for a role.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ THERE IS NO RENAME. `path` is built from this, so changing it makes the factory read
|
|
57
|
+
* nothing live, create a role under the new name, and leave the old one on the cluster with
|
|
58
|
+
* nobody managing it. Delete the resource and declare a new one rather than editing this.
|
|
59
|
+
*/
|
|
60
|
+
roleid: string;
|
|
61
|
+
/**
|
|
62
|
+
* The COMPLETE privilege set, e.g. `['VM.Allocate', 'VM.Audit', 'Sys.Audit']`. Order and
|
|
63
|
+
* duplicates do not matter — see the ⛔ in the header — but completeness does: this list is the
|
|
64
|
+
* role, not an addition to it.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ DECLARE AT LEAST ONE. A role with no privileges grants nothing and reads back as an empty
|
|
67
|
+
* document, which is the one shape the factory cannot tell apart from an object that is not
|
|
68
|
+
* there — reconcile would then refuse with its "the write returned no error but the object is
|
|
69
|
+
* still absent" message, which is true of the read and misleading about the cause.
|
|
70
|
+
*/
|
|
71
|
+
privs: string[];
|
|
72
|
+
}
|
|
73
|
+
export interface RoleAttributes {
|
|
74
|
+
roleid: string;
|
|
75
|
+
/** Sorted and de-duplicated on the way in, so persisted state is identical across plans. */
|
|
76
|
+
privs: string[];
|
|
77
|
+
}
|
|
78
|
+
export interface ProxmoxRole extends Resource<'Proxmox.Role', RoleProps, RoleAttributes, never, PveRequirements> {
|
|
79
|
+
}
|
|
80
|
+
export declare const ProxmoxRole: import("alchemy").ResourceClass<ProxmoxRole>;
|
|
81
|
+
/**
|
|
82
|
+
* ⛔ AN EMPTY LIST, AND NOWHERE DOES IT MATTER MORE. `GET /access/roles` returns every
|
|
83
|
+
* role on the cluster, PVE's own built-ins included. Returning them would invite Alchemy
|
|
84
|
+
* to adopt `Administrator` — and therefore one day to narrow or delete it. Adoption is
|
|
85
|
+
* an explicit act, here as everywhere else in this package.
|
|
86
|
+
|
|
87
|
+
*
|
|
88
|
+
* ⛔ DO NOT CARRY THE FACTORY'S REASSURANCE OVER TO THIS ONE. `destroy` there notes that
|
|
89
|
+
* PVE refuses to delete things still in use — a pool holding guests, a storage with
|
|
90
|
+
* volumes — and a role is NOT protected that way. Whoever is bound to the role loses
|
|
91
|
+
* those privileges the moment it goes, and the credential this provider runs with is
|
|
92
|
+
* bound through exactly such a binding. Checking who holds a role before removing it is
|
|
93
|
+
* an operator's job, and it is not done here on their behalf while they read a diff.
|
|
94
|
+
|
|
95
|
+
*/
|
|
96
|
+
export declare const ProxmoxRoleProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxRole>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
97
|
+
//# sourceMappingURL=role.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAuDjE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}
|