@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,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Pbs.VerifyJob` — one section of PBS's `verification.cfg`, declared.
|
|
3
|
+
*
|
|
4
|
+
* ★ THIS IS THE FAMILY THAT ANSWERS "ARE THE BACKUPS ACTUALLY GOOD", AND NOTHING ELSE IN THIS
|
|
5
|
+
* PACKAGE DOES. A datastore reports free space, a prune job reports that it ran, and every
|
|
6
|
+
* snapshot in the store can still be unreadable: PBS learns a chunk has rotted only when
|
|
7
|
+
* something reads it back and checks its digest, and verification is the only thing that does
|
|
8
|
+
* that on a schedule. An undeclared verify job is the reason nobody notices for months — and a
|
|
9
|
+
* green `Proxmox.BackupJob` makes it worse rather than better, because a vzdump job that says
|
|
10
|
+
* "OK" every night is exactly the evidence people stop at.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ THIS FILE CANNOT AUTHENTICATE UNTIL TWO THINGS OUTSIDE IT CHANGE, AND IT IS SHIPPED SAYING SO
|
|
13
|
+
* RATHER THAN SHIPPED LOOKING FINISHED. Both are recorded on `PbsTarget` below:
|
|
14
|
+
* 1. `credentials.ts` has no PBS mint. MEASURED 2026-09-13 from this Mac: the `claude-code`
|
|
15
|
+
* approle is refused `sys/mounts`, `kv/infra/proxmox` and `proxmox-pbs/creds/read` alike —
|
|
16
|
+
* 403 permission denied on all three — so there is no mount to name and none is invented.
|
|
17
|
+
* 2. `client.ts`'s `authorization()` builds a PVE header, and PBS will not accept it. See the
|
|
18
|
+
* ⛔ on `PbsTarget`.
|
|
19
|
+
* Until both land, every call 401s, `pveOperations.read` folds that into "absent", and the plan
|
|
20
|
+
* reports `create` for a job that is plainly there. The deploy then FAILS at the POST rather
|
|
21
|
+
* than clobbering anything — an unauthenticated POST 401s, and an authenticated one is refused
|
|
22
|
+
* because PBS will not take a duplicate section id — so the cost is a LYING PLAN and a broken
|
|
23
|
+
* run, not a lost job. That is still reason enough not to wire it into a stack yet: a plan
|
|
24
|
+
* nobody can trust is its own outage.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ THE SHAPES BELOW ARE READ FROM PUBLISHED SOURCE, NOT MEASURED ON THIS ESTATE. Everything is
|
|
27
|
+
* taken from `pbs-api-types/src/jobs.rs`, `src/api2/config/verify.rs`, `src/server/verify_job.rs`
|
|
28
|
+
* and `src/backup/verify.rs` at HEAD of the Proxmox git mirrors on 2026-09-13. What WAS measured
|
|
29
|
+
* here: `pbs.example.com:8007` and `pbs.mgmt.example.com:8007` both answer HTTP 401 under
|
|
30
|
+
* STRICT TLS (curl `ssl_verify_result=0`), so no `-k` is needed and `client.ts`'s plain `fetch`
|
|
31
|
+
* reaches PBS unchanged. The running host's own schema was NOT read — no credential — so a PBS
|
|
32
|
+
* older than HEAD may differ.
|
|
33
|
+
*/
|
|
34
|
+
import { Resource } from 'alchemy';
|
|
35
|
+
import * as Provider from 'alchemy/Provider';
|
|
36
|
+
import * as Effect from 'effect/Effect';
|
|
37
|
+
import type { PbsTarget } from './credentials.ts';
|
|
38
|
+
import { rechecks, shape } from './pbs-verify-job-form.ts';
|
|
39
|
+
import { type PveRequirements, pveHandlers } from './resource.ts';
|
|
40
|
+
import { bool, int, text } from './values.ts';
|
|
41
|
+
|
|
42
|
+
export interface PbsVerifyJobProps {
|
|
43
|
+
/** ⛔ A PBS host, never a PVE cluster. See `PbsTarget`. */
|
|
44
|
+
target: PbsTarget;
|
|
45
|
+
/**
|
|
46
|
+
* ⛔ The primary key, REQUIRED, and never left to PBS. 3–32 chars of
|
|
47
|
+
* `[A-Za-z0-9_][A-Za-z0-9._-]*`. PBS refuses a duplicate section id outright, which is the loud
|
|
48
|
+
* failure `Proxmox.BackupJob` had to engineer for itself — but only because the id is sent.
|
|
49
|
+
*/
|
|
50
|
+
id: string;
|
|
51
|
+
/** Datastore name, 3–32 safe-id chars. ⚠️ MUTABLE — see the ⚠️ on `matches`. */
|
|
52
|
+
store: string;
|
|
53
|
+
/**
|
|
54
|
+
* ⛔ THE KEY IS REQUIRED AND THE VALUE MAY BE `null`. PBS makes the field optional; this family
|
|
55
|
+
* refuses to let you OMIT it, which is not the same thing. A verification job with no schedule
|
|
56
|
+
* never runs on its own and PBS reports it as perfectly healthy — and an absent schedule is also
|
|
57
|
+
* the only way to turn one off, because there is NO `disable` field on a verification job.
|
|
58
|
+
* ⛔ THAT LAST CLAIM IS NOW MEASURED, not read from source. `proxmox-backup-manager verify-job
|
|
59
|
+
* update --help` on the live 4.2 host lists `--delete
|
|
60
|
+
* ignore-verified|comment|schedule|outdated-after|ns|max-depth|read-threads|verify-threads`
|
|
61
|
+
* and no `--disable` anywhere. So `schedule` is deletable — "parked" is a state PBS supports —
|
|
62
|
+
* and `disable` genuinely does not exist. pbs-verify-job-form.ts said this was read off the
|
|
63
|
+
* source and NOT verified against a running host; it is now verified, on this one.
|
|
64
|
+
* ★ SO `null` MEANS "DELIBERATELY PARKED" AND OMITTING IT IS STILL AN ERROR. The original
|
|
65
|
+
* required-string kept you from declaring verification that never happens, which is right, but
|
|
66
|
+
* it also made a genuinely parked job UNDECLARABLE — and the estate has one: `v-datacenter` is
|
|
67
|
+
* parked because syno1 is powered off, comment and runbook and all. A resource that cannot
|
|
68
|
+
* describe what is really there pushes the operator back to the CLI, which is the failure this
|
|
69
|
+
* package exists to end. Writing `schedule: null` costs a word and says the quiet part out
|
|
70
|
+
* loud; leaving the key out still will not compile.
|
|
71
|
+
* ⚠️ ADOPTING A SCHEDULELESS JOB WITH A SCHEDULE DECLARED GIVES IT ONE ON THE FIRST DEPLOY, which
|
|
72
|
+
* is the intended direction and is still a write nobody asked for in words. `matches` compares
|
|
73
|
+
* `schedule`, the live value reads `''`, so the first reconcile PUTs the declared one and
|
|
74
|
+
* verification starts running that night. Read the first plan against an adopted job.
|
|
75
|
+
* ⚠️ A systemd calendar event. Spelled `daily`, `sat 02:00`, `mon..fri 23:00`. A step expression
|
|
76
|
+
* is legal too and is not written out here: those two characters would close this comment.
|
|
77
|
+
*/
|
|
78
|
+
schedule: string | null;
|
|
79
|
+
/**
|
|
80
|
+
* The namespace to verify, e.g. `tier1/prod`. Omitted means the datastore root.
|
|
81
|
+
* ⛔ NEVER SENT AS THE EMPTY STRING. MEASURED in the update handler: `if !ns.is_root()` — a root
|
|
82
|
+
* namespace on a PUT is accepted by the schema, reaches the handler and is then SILENTLY
|
|
83
|
+
* DISCARDED. A job cannot be moved back to the root namespace by this resource at all; that
|
|
84
|
+
* needs `--delete ns` by hand. So an undeclared `ns` is neither sent nor compared.
|
|
85
|
+
*/
|
|
86
|
+
ns?: string;
|
|
87
|
+
/**
|
|
88
|
+
* How many namespace levels below `ns` to descend. `0` is that level only; PBS's maximum is 7.
|
|
89
|
+
* ⚠️ PBS's own default is FULL recursion (7), not 0. Undeclared means undeclared here: not sent,
|
|
90
|
+
* not compared, because "unset" and "7" are stored differently and only one is reachable.
|
|
91
|
+
*/
|
|
92
|
+
'max-depth'?: number;
|
|
93
|
+
/**
|
|
94
|
+
* ⛔ THE HALF OF THE PAIR THAT DECIDES WHETHER THIS JOB EVER RE-CHECKS ANYTHING. Defaults to
|
|
95
|
+
* TRUE. Read `rechecks` and the ⛔ in pbs-verify-job-form.ts before setting either of these.
|
|
96
|
+
*/
|
|
97
|
+
'ignore-verified'?: boolean;
|
|
98
|
+
/** Days after which a past verification is stale. ⛔ See the pair's ⛔ in the form file. */
|
|
99
|
+
'outdated-after'?: number;
|
|
100
|
+
/** ⚠️ PBS TRIMS IT AND STORES AN EMPTY ONE AS ABSENT, so `' '` reads back as `''`. */
|
|
101
|
+
comment?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface PbsVerifyJobAttributes {
|
|
105
|
+
id: string;
|
|
106
|
+
store: string;
|
|
107
|
+
schedule: string;
|
|
108
|
+
ns: string;
|
|
109
|
+
/** ⚠️ `-1` means "no max-depth in the section". `0` is a real setting meaning no recursion. */
|
|
110
|
+
'max-depth': number;
|
|
111
|
+
'ignore-verified': boolean;
|
|
112
|
+
/** ⚠️ `-1` means "unset", which is NOT `0`. See the ⛔ in the form file. */
|
|
113
|
+
'outdated-after': number;
|
|
114
|
+
comment: string;
|
|
115
|
+
/**
|
|
116
|
+
* ★ WHAT THIS JOB ACTUALLY RE-CHECKS, IN ENGLISH, COMPUTED FROM THE LIVE PAIR. REPORTED, NEVER
|
|
117
|
+
* COMPARED — it is derived from two fields `matches` already compares, so diffing it would
|
|
118
|
+
* double-count them. It exists because the failure this family guards against is a job that
|
|
119
|
+
* looks healthy in every column and verifies each snapshot exactly once, ever; that job reads
|
|
120
|
+
* `never` here. A plan line is the only place anyone will see it.
|
|
121
|
+
*/
|
|
122
|
+
rechecks: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface PbsVerifyJob extends Resource<
|
|
126
|
+
'Pbs.VerifyJob',
|
|
127
|
+
PbsVerifyJobProps,
|
|
128
|
+
PbsVerifyJobAttributes,
|
|
129
|
+
never,
|
|
130
|
+
PveRequirements
|
|
131
|
+
> {}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* ⚠️ NO `defaultRemovalPolicy: 'retain'`, DELIBERATELY, AND IT IS THE CLOSER CALL THAN IT LOOKS.
|
|
135
|
+
* The section holds no data — deleting it destroys nothing, the way `Proxmox.BackupJob`'s delete
|
|
136
|
+
* destroys no archives. What it destroys is the CHECKING, silently and permanently, and PBS will
|
|
137
|
+
* not refuse it the way it refuses to remove a datastore in use. `retain` would hide that behind
|
|
138
|
+
* a state-row drop instead of showing it as a plan line, and the plan line is the only warning
|
|
139
|
+
* anyone gets. Pipe `RemovalPolicy.retain()` per declaration where the job must outlive the code.
|
|
140
|
+
*/
|
|
141
|
+
export const PbsVerifyJob = Resource<PbsVerifyJob>('Pbs.VerifyJob');
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* ⚠️ EVERY FIELD IS READ THROUGH `values.ts` THOUGH PBS RETURNS REAL JSON TYPES. Unlike PVE's
|
|
145
|
+
* SectionConfig round-trip, `GET /config/verify/{id}` serialises a Rust struct, so
|
|
146
|
+
* `ignore-verified` arrives as a JSON boolean and `max-depth` as a JSON number. `bool` and `int`
|
|
147
|
+
* accept both spellings, and using them costs nothing while covering the version that does not.
|
|
148
|
+
* ⚠️ `digest` IS NOT AN ATTRIBUTE AND CANNOT BECOME ONE BY ACCIDENT. PBS puts it on the rpcenv, and
|
|
149
|
+
* the JSON formatter adds rpcenv attributes as SIBLINGS of `data` — `{"data":{…},"digest":"…"}` —
|
|
150
|
+
* so `client.ts`, which returns `body.data`, never sees it. That is lucky rather than designed:
|
|
151
|
+
* the digest covers verification.cfg as a FILE, so keeping it would churn this resource's state
|
|
152
|
+
* whenever an unrelated verify job was edited.
|
|
153
|
+
*/
|
|
154
|
+
const handlers = pveHandlers<PbsVerifyJobProps, PbsVerifyJobAttributes>({
|
|
155
|
+
attributes: (live, props) => {
|
|
156
|
+
const ignoreVerified = bool(live['ignore-verified'], true);
|
|
157
|
+
const outdatedAfter = int(live['outdated-after'], -1);
|
|
158
|
+
return {
|
|
159
|
+
comment: text(live['comment'], ''),
|
|
160
|
+
id: props.id,
|
|
161
|
+
'ignore-verified': ignoreVerified,
|
|
162
|
+
'max-depth': int(live['max-depth'], -1),
|
|
163
|
+
ns: text(live['ns'], ''),
|
|
164
|
+
'outdated-after': outdatedAfter,
|
|
165
|
+
rechecks: rechecks(ignoreVerified, outdatedAfter),
|
|
166
|
+
schedule: text(live['schedule'], ''),
|
|
167
|
+
store: text(live['store'], props.store),
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* 🔴 THIS WAS `config/verification` AND PBS ANSWERS 404 FOR IT. MEASURED on the live 4.2 server:
|
|
172
|
+
* `GET /api2/json/config/verification` -> 404 "Path not found"; `GET /api2/json/config/verify`
|
|
173
|
+
* -> 200 with both jobs. The struct is `VerificationJobConfig` and the CLI subcommand is
|
|
174
|
+
* `verify-job`, so the long spelling reads right and is simply not the route.
|
|
175
|
+
* ⛔ THE FAILURE MODE IS THE ONE THIS PACKAGE KEEPS FINDING: `read` folds every failure into
|
|
176
|
+
* `undefined`, so a 404 from a WRONG PATH is indistinguishable from an object that is not
|
|
177
|
+
* there. The first plan against the live estate said `create` for two verification jobs that
|
|
178
|
+
* have existed for weeks — and a create would then have POSTed to a 404 as well, so it fails
|
|
179
|
+
* loudly rather than duplicating anything. Nothing caught it earlier because no verification
|
|
180
|
+
* job had ever been declared; registering the provider exercises no path at all.
|
|
181
|
+
*/
|
|
182
|
+
collection: () => 'config/verify',
|
|
183
|
+
/** ⛔ `id` IS SENT. PBS refuses a duplicate id, and that refusal is this family's safety net. */
|
|
184
|
+
createForm: (props) => ({ ...shape(props), id: props.id }),
|
|
185
|
+
/**
|
|
186
|
+
* ⚠️ AN UNDECLARED FIELD IS NEITHER SENT NOR COMPARED — the `Proxmox.BackupJob` trade, for the
|
|
187
|
+
* same reason and with one extra: this form carries NO `delete` list, so there is nothing it
|
|
188
|
+
* could clear even if it wanted to. See the ⛔ on `withClears` in pbs-verify-job-form.ts.
|
|
189
|
+
* ⚠️ `store` IS COMPARED AND IS MUTABLE, which is unusual and worth reading twice. PBS's update
|
|
190
|
+
* handler assigns it and re-checks privileges on both the old and the new path, so changing
|
|
191
|
+
* `store` MOVES the job — and the datastore it left is then verified by nothing. That is a
|
|
192
|
+
* one-word edit with the same effect as deleting the job, and it plans as a quiet `update`.
|
|
193
|
+
* ⚠️ `rechecks` IS ABSENT HERE ON PURPOSE. It is a rendering of the two fields on the lines
|
|
194
|
+
* above; comparing it too would report the same drift twice.
|
|
195
|
+
*/
|
|
196
|
+
matches: (attributes, props) =>
|
|
197
|
+
attributes.store === props.store &&
|
|
198
|
+
// ⚠️ `null` IS COMPARED AGAINST `''`, NOT SKIPPED. A parked job is a declaration like any
|
|
199
|
+
// other, so declaring `null` against a job that HAS a schedule must plan an update — the
|
|
200
|
+
// form then omits `schedule`, which is a set-only form, so see the ⛔ on clearing below.
|
|
201
|
+
attributes.schedule === (props.schedule ?? '') &&
|
|
202
|
+
attributes['ignore-verified'] === (props['ignore-verified'] !== false) &&
|
|
203
|
+
(props['outdated-after'] === undefined ||
|
|
204
|
+
attributes['outdated-after'] === props['outdated-after']) &&
|
|
205
|
+
(props['max-depth'] === undefined || attributes['max-depth'] === props['max-depth']) &&
|
|
206
|
+
(props.ns === undefined || attributes.ns === props.ns) &&
|
|
207
|
+
(props.comment === undefined || attributes.comment === props.comment),
|
|
208
|
+
path: (props) => `config/verify/${props.id}`,
|
|
209
|
+
updateForm: shape,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* ⛔ `list` IS EMPTY, INHERITED FROM `pveHandlers`, AND THE REASON IS SHARPER HERE THAN ELSEWHERE.
|
|
214
|
+
* `GET /config/verify` returns every verification job the caller can see, including the
|
|
215
|
+
* ones the PBS installer and a human made. Adopting those would put Alchemy one dropped line
|
|
216
|
+
* away from DELETING a datastore's only verification schedule — and deleting it destroys nothing
|
|
217
|
+
* visible, so the loss is discovered the day a restore fails and there is no verification history
|
|
218
|
+
* to say when the rot started. Adoption stays explicit: declare the existing id.
|
|
219
|
+
*
|
|
220
|
+
* ⚠️ NO `digest` IS SENT ON UPDATE OR DELETE, SO THIS IS LAST-WRITER-WINS. PBS accepts an optional
|
|
221
|
+
* `digest` on both and would refuse a write made against a stale read. Sending one would need a
|
|
222
|
+
* read-then-write window this provider does not have — `pveOperations` reads and writes in two
|
|
223
|
+
* separate calls with two separate mints — so a concurrent edit from the PBS UI is overwritten
|
|
224
|
+
* rather than refused. Same behaviour as every PVE family here; stated because PBS offers better.
|
|
225
|
+
*/
|
|
226
|
+
export const PbsVerifyJobProvider = () =>
|
|
227
|
+
Provider.effect(PbsVerifyJob, Effect.succeed(PbsVerifyJob.Provider.of(handlers)));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool family — compile-time and runtime checks against generated `/pools` schema types.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ THE DRIFT TEST IS THE POINT: if codegen renames a parameter or marks `poolid` optional on
|
|
5
|
+
* POST, this file fails `tsc` before anything reaches a cluster.
|
|
6
|
+
*/
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import type { PoolsPoolidPutParams, PoolsPostParams } from './generated/pve.ts';
|
|
10
|
+
import { type PoolProps, poolCreateForm, poolUpdateForm } from './pool.ts';
|
|
11
|
+
|
|
12
|
+
const target: PoolProps['target'] = {
|
|
13
|
+
members: ['example.test'],
|
|
14
|
+
mount: 'proxmox-test',
|
|
15
|
+
scheme: 'pve',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const props = (over: Partial<PoolProps> = {}): PoolProps => ({
|
|
19
|
+
target,
|
|
20
|
+
poolid: 'house',
|
|
21
|
+
...over,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('pool forms match generated POST/PUT parameters', () => {
|
|
25
|
+
it('createForm sends exactly poolid and comment', () => {
|
|
26
|
+
const form: PoolsPostParams = poolCreateForm(props({ comment: 'estate' }));
|
|
27
|
+
assert.deepEqual(form, { poolid: 'house', comment: 'estate' });
|
|
28
|
+
assert.deepEqual(Object.keys(form).sort(), ['comment', 'poolid']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('updateForm sends only comment on the deprecated item path', () => {
|
|
32
|
+
const form: Pick<PoolsPoolidPutParams, 'comment'> = poolUpdateForm(props());
|
|
33
|
+
assert.deepEqual(form, { comment: '' });
|
|
34
|
+
assert.deepEqual(Object.keys(form), ['comment']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('would fail tsc if poolid were dropped from createForm', () => {
|
|
38
|
+
// @ts-expect-error poolid is required on POST /pools
|
|
39
|
+
const bad: PoolsPostParams = { comment: '' };
|
|
40
|
+
assert.equal('poolid' in bad, false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
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 * as Effect from 'effect/Effect';
|
|
21
|
+
import type {
|
|
22
|
+
PoolsPoolidGetReturn,
|
|
23
|
+
PoolsPoolidPutParams,
|
|
24
|
+
PoolsPostParams,
|
|
25
|
+
} from './generated/pve.ts';
|
|
26
|
+
import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
|
|
27
|
+
|
|
28
|
+
export interface PoolProps extends WithTarget {
|
|
29
|
+
/** PVE's primary key for a pool. Changing it is a replace, not an update. */
|
|
30
|
+
poolid: string;
|
|
31
|
+
/** Free text shown in the UI — the only mutable field a pool has. */
|
|
32
|
+
comment?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PoolAttributes {
|
|
36
|
+
poolid: string;
|
|
37
|
+
comment: string;
|
|
38
|
+
/** Guests currently in the pool, so a plan can say what a delete would refuse to orphan. */
|
|
39
|
+
members: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ProxmoxPool extends Resource<
|
|
43
|
+
'Proxmox.Pool',
|
|
44
|
+
PoolProps,
|
|
45
|
+
PoolAttributes,
|
|
46
|
+
never,
|
|
47
|
+
PveRequirements
|
|
48
|
+
> {}
|
|
49
|
+
|
|
50
|
+
export const ProxmoxPool = Resource<ProxmoxPool>('Proxmox.Pool');
|
|
51
|
+
|
|
52
|
+
/** Form bodies this family sends — exported for the drift test in pool.test.ts. */
|
|
53
|
+
export const poolCreateForm = (props: PoolProps): PoolsPostParams => ({
|
|
54
|
+
comment: props.comment ?? '',
|
|
55
|
+
poolid: props.poolid,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** PUT body — only `comment`; path carries `poolid`. Matches deprecated `/pools/{poolid}`. */
|
|
59
|
+
export const poolUpdateForm = (props: PoolProps): Pick<PoolsPoolidPutParams, 'comment'> => ({
|
|
60
|
+
comment: props.comment ?? '',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const handlers = pveHandlers<PoolProps, PoolAttributes>({
|
|
64
|
+
attributes: (live, props) => {
|
|
65
|
+
const row = live as PoolsPoolidGetReturn;
|
|
66
|
+
return {
|
|
67
|
+
comment: typeof row.comment === 'string' ? row.comment : '',
|
|
68
|
+
members: Array.isArray(row.members) ? row.members.length : 0,
|
|
69
|
+
poolid: props.poolid,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
collection: () => 'pools',
|
|
73
|
+
createForm: poolCreateForm,
|
|
74
|
+
matches: (attributes, props) => attributes.comment === (props.comment ?? ''),
|
|
75
|
+
path: (props) => `pools/${props.poolid}`,
|
|
76
|
+
updateForm: poolUpdateForm,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const ProxmoxPoolProvider = () =>
|
|
80
|
+
Provider.effect(ProxmoxPool, Effect.succeed(ProxmoxPool.Provider.of(handlers)));
|
|
@@ -0,0 +1,89 @@
|
|
|
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 * as Effect from 'effect/Effect';
|
|
20
|
+
import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
|
|
21
|
+
import { num } from './values.ts';
|
|
22
|
+
|
|
23
|
+
export interface VmProps extends WithTarget {
|
|
24
|
+
node: string;
|
|
25
|
+
/** ⛔ Cluster-wide, and shared with LXC. See the ⛔ above. */
|
|
26
|
+
vmid: number;
|
|
27
|
+
name?: string;
|
|
28
|
+
/** MiB. */
|
|
29
|
+
memory?: number;
|
|
30
|
+
cores?: number;
|
|
31
|
+
sockets?: number;
|
|
32
|
+
/** e.g. `virtio=<mac>,bridge=vmbr0`. */
|
|
33
|
+
net0?: string;
|
|
34
|
+
onboot?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface VmAttributes {
|
|
38
|
+
vmid: number;
|
|
39
|
+
node: string;
|
|
40
|
+
name: string;
|
|
41
|
+
memory: number;
|
|
42
|
+
cores: number;
|
|
43
|
+
sockets: number;
|
|
44
|
+
onboot: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProxmoxVm extends Resource<
|
|
48
|
+
'Proxmox.Vm',
|
|
49
|
+
VmProps,
|
|
50
|
+
VmAttributes,
|
|
51
|
+
never,
|
|
52
|
+
PveRequirements
|
|
53
|
+
> {}
|
|
54
|
+
|
|
55
|
+
export const ProxmoxVm = Resource<ProxmoxVm>('Proxmox.Vm');
|
|
56
|
+
|
|
57
|
+
const shape = (props: VmProps) => ({
|
|
58
|
+
cores: String(props.cores ?? 1),
|
|
59
|
+
memory: String(props.memory ?? 512),
|
|
60
|
+
name: props.name ?? `vm${String(props.vmid)}`,
|
|
61
|
+
onboot: props.onboot === true ? '1' : '0',
|
|
62
|
+
sockets: String(props.sockets ?? 1),
|
|
63
|
+
...(props.net0 === undefined ? {} : { net0: props.net0 }),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const handlers = pveHandlers<VmProps, VmAttributes>({
|
|
67
|
+
attributes: (live, props) => ({
|
|
68
|
+
cores: num(live['cores'], 1),
|
|
69
|
+
memory: num(live['memory'], 512),
|
|
70
|
+
name: typeof live['name'] === 'string' ? live['name'] : '',
|
|
71
|
+
node: props.node,
|
|
72
|
+
onboot: live['onboot'] === 1 || live['onboot'] === true,
|
|
73
|
+
sockets: num(live['sockets'], 1),
|
|
74
|
+
vmid: props.vmid,
|
|
75
|
+
}),
|
|
76
|
+
collection: (props) => `nodes/${props.node}/qemu`,
|
|
77
|
+
createForm: (props) => ({ ...shape(props), vmid: String(props.vmid) }),
|
|
78
|
+
matches: (attributes, props) =>
|
|
79
|
+
attributes.name === (props.name ?? `vm${String(props.vmid)}`) &&
|
|
80
|
+
attributes.memory === (props.memory ?? 512) &&
|
|
81
|
+
attributes.cores === (props.cores ?? 1) &&
|
|
82
|
+
attributes.sockets === (props.sockets ?? 1) &&
|
|
83
|
+
attributes.onboot === (props.onboot === true),
|
|
84
|
+
path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
|
|
85
|
+
updateForm: shape,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const ProxmoxVmProvider = () =>
|
|
89
|
+
Provider.effect(ProxmoxVm, Effect.succeed(ProxmoxVm.Provider.of(handlers)));
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
/**
|
|
16
|
+
* PVE's own documented default for `schedule`, applied on BOTH sides of the comparison.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ IT LIVES HERE BECAUSE BOTH HALVES NEED THE SAME STRING. `shape` sends it and `attributes`
|
|
19
|
+
* falls back to it; two copies of that literal in two files is exactly the drift that makes a
|
|
20
|
+
* plan report an update over a value nobody set.
|
|
21
|
+
* ⚠️ AND IT IS NEVER SPELLED OUT IN A COMMENT, because the string PVE chose ENDS A BLOCK COMMENT:
|
|
22
|
+
* the two characters before the `15` are the comment terminator, and tsc says so loudly. It is
|
|
23
|
+
* referred to by name everywhere else in these two files for that reason, not for style.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_SCHEDULE = '*/15';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `{guest, jobnum}` -> `100-0`, the id in the only spelling PVE ever answers with.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ NUMBERS IN, SO CANONICAL OUT. PVE runs both halves of a job id through `int()` on parse — see
|
|
31
|
+
* the ⛔ in replication-job.ts — and a JavaScript number has no leading-zero spelling to lose.
|
|
32
|
+
* That is the whole reason this resource is keyed on two numbers instead of one string.
|
|
33
|
+
*/
|
|
34
|
+
export const jobId = (props: ReplicationJobProps) =>
|
|
35
|
+
`${String(props.guest)}-${String(props.jobnum)}`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The mutable half, shared by create and update.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ `schedule`, `disable` AND `comment` ARE ALWAYS SENT, EVEN AT THEIR DEFAULTS, because they are
|
|
41
|
+
* always compared — and a field compared against a default but not SENT as that default is an
|
|
42
|
+
* update reported on every plan that the PUT never performs (backup-job.ts records the same
|
|
43
|
+
* trade). The empty string is how PVE is told to clear a comment. `rate` is the opposite case:
|
|
44
|
+
* not declared, not sent, not compared, so a rate limit set by hand survives adoption untouched.
|
|
45
|
+
* ⛔ `source` IS NEVER SENT, AND SENDING IT WOULD BE WORSE THAN REDUNDANT. MEASURED in
|
|
46
|
+
* API2/ReplicationConfig.pm: create does `$param->{source} //= $guest_info->{node}` — PVE fills
|
|
47
|
+
* it from wherever the guest actually is — and DIES with "Source '<x>' does not match current
|
|
48
|
+
* node of guest" when a declaration disagrees. There is no correct value to hold here.
|
|
49
|
+
*/
|
|
50
|
+
const shape = (props: ReplicationJobProps) => ({
|
|
51
|
+
comment: props.comment ?? '',
|
|
52
|
+
disable: props.disable === true ? '1' : '0',
|
|
53
|
+
schedule: props.schedule ?? DEFAULT_SCHEDULE,
|
|
54
|
+
...(props.rate === undefined ? {} : { rate: String(props.rate) }),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* ⚠️ `type` IS A CONSTANT RATHER THAN A PROP: `local` is the only section type PVE registers, and
|
|
59
|
+
* an enum of one value is a prop that can only ever be wrong. `id` and `target` are both required
|
|
60
|
+
* by the create schema and neither belongs in `shape` — `target` because PUT will not take it.
|
|
61
|
+
* ⛔ THIS LINE IS THE ONLY PLACE PVE'S KEY `target` IS SPELLED, and the value beside it comes from
|
|
62
|
+
* `targetNode`. The two words are different objects — a node name here, the whole cluster in
|
|
63
|
+
* `WithTarget.target` — and the ⛔ in replication-job.ts explains why the rename was forced.
|
|
64
|
+
*/
|
|
65
|
+
export const createBody = (props: ReplicationJobProps) => ({
|
|
66
|
+
...shape(props),
|
|
67
|
+
id: jobId(props),
|
|
68
|
+
target: props.targetNode,
|
|
69
|
+
type: 'local',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* ⛔ `delete=remove_job` ON EVERY UPDATE IS THE DELIBERATE PART. PVE's PUT merges the form into the
|
|
74
|
+
* section and cannot unset anything, so without this the removal marker a DELETE wrote would
|
|
75
|
+
* survive every reconcile and `matches` would report an update that never lands. MEASURED as
|
|
76
|
+
* legal: the update handler refuses `delete` only for required or `fixed` options, and
|
|
77
|
+
* `remove_job => { optional => 1 }` is neither; deleting a key that is not set is a no-op, so
|
|
78
|
+
* this is safe to send unconditionally.
|
|
79
|
+
* ⛔ AND THE DELETE LIST IS THIS ONE CONSTANT KEY, NEVER A COMPUTED "everything you did not
|
|
80
|
+
* declare". backup-job.ts spells out why the computed form is dangerous — it strips settings the
|
|
81
|
+
* resource does not even model off a job adopted from the UI. `remove_job` cannot be declared
|
|
82
|
+
* here at all, so clearing it can only ever undo a removal this stack did not ask for.
|
|
83
|
+
* ⚠️ IT CANCELS A REMOVAL SOMEBODY STARTED, WHICH IS THE POINT AND IS STILL WORTH KNOWING. If the
|
|
84
|
+
* removal pass already ran its local-snapshot half, the revived job re-sends in full.
|
|
85
|
+
*/
|
|
86
|
+
export const updateBody = (props: ReplicationJobProps) => ({
|
|
87
|
+
...shape(props),
|
|
88
|
+
delete: 'remove_job',
|
|
89
|
+
});
|