@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,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A prune job's wire half — and, deliberately, EVERY LINE THAT CAN DELETE A BACKUP.
|
|
3
|
+
*
|
|
4
|
+
* ★ SPLIT OUT OF pbs-prune-job.ts ON THE SEAM replication-job-form.ts USES: that file answers
|
|
5
|
+
* "what is a prune job and when has it changed", this one answers "how does a declaration become
|
|
6
|
+
* a PBS write". The one deliberate departure from that seam is `keepsMatch`, which is diff logic
|
|
7
|
+
* and lives here anyway — see the ★ below, which is the reason the cut falls where it does.
|
|
8
|
+
*
|
|
9
|
+
* ★ THE SIX RETENTION WINDOWS ARE TOUCHED IN THREE PLACES AND ALL THREE ARE IN THIS FILE: sent by
|
|
10
|
+
* `keepForm`, read back by `keepAttributes`, compared by `keepsMatch`. The three must stay in
|
|
11
|
+
* step — a window written but not compared is a diff that never settles, and one compared but not
|
|
12
|
+
* written is an update reported on every plan forever — and this is the half of the resource
|
|
13
|
+
* where being wrong costs backups rather than a confusing plan. Keeping all three on one screen
|
|
14
|
+
* is what makes that review possible in one sitting instead of a grep across two files.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ THE `import type` BACK TO pbs-prune-job.ts IS A CYCLE ON PAPER ONLY. It is type-only, erased
|
|
17
|
+
* before anything runs, so `PruneJobProps` stays the resource's public shape in the file that
|
|
18
|
+
* declares the resource rather than being moved somewhere odd to dodge the arrow.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ FORM ENCODING IS THE ONE TRANSPORT ASSUMPTION THIS FAMILY RESTS ON, AND IT IS REASONED RATHER
|
|
21
|
+
* THAN MEASURED. `pveWith` sends `application/x-www-form-urlencoded`; PBS's REST server accepts
|
|
22
|
+
* that alongside JSON and parses each value through the parameter schema, which is how its own
|
|
23
|
+
* ExtJS GUI talks to it. I could not prove it: there is no credential for this host (see the ⛔ in
|
|
24
|
+
* pbs-prune-job.ts) and the only unauthenticated endpoint that would have distinguished a 400
|
|
25
|
+
* "bad parameters" from a 401 is the login form, which is not a thing to poke on a live box. If
|
|
26
|
+
* PBS turns out to want JSON here, every key and value below is still right and only the
|
|
27
|
+
* transport is wrong — VERIFY THIS FIRST, before anything else in these two files.
|
|
28
|
+
*/
|
|
29
|
+
import type { PruneJobAttributes, PruneJobProps } from './pbs-prune-job.ts';
|
|
30
|
+
import { flag, int } from './values.ts';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The six retention windows, in PBS's own spelling.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ ONE LIST, READ THREE TIMES — by `keepForm` on the way to the wire, by `keepsMatch` on the way
|
|
36
|
+
* back, and by `keepsSomething` in the guard. Two hand-kept copies of six key names is how a
|
|
37
|
+
* seventh window gets added to one of them and silently never sent, which for this family means
|
|
38
|
+
* never applied and never noticed until a restore.
|
|
39
|
+
*/
|
|
40
|
+
export const KEEP_KEYS = [
|
|
41
|
+
'keep-last',
|
|
42
|
+
'keep-hourly',
|
|
43
|
+
'keep-daily',
|
|
44
|
+
'keep-weekly',
|
|
45
|
+
'keep-monthly',
|
|
46
|
+
'keep-yearly',
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export type KeepKey = (typeof KEEP_KEYS)[number];
|
|
50
|
+
|
|
51
|
+
/** ⚠️ Undeclared windows are simply absent — see `requireKeeps` for why zero is never sent. */
|
|
52
|
+
const keepForm = (props: PruneJobProps): Record<string, string> => {
|
|
53
|
+
const form: Record<string, string> = {};
|
|
54
|
+
for (const key of KEEP_KEYS) {
|
|
55
|
+
const value = props[key];
|
|
56
|
+
if (value !== undefined) form[key] = String(value);
|
|
57
|
+
}
|
|
58
|
+
return form;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The six windows off the wire, where `0` MEANS ABSENT.
|
|
63
|
+
*
|
|
64
|
+
* ⛔ ZERO IS A SENTINEL HERE AND CAN NEVER BE A LIVE VALUE, which is the only reason it is safe to
|
|
65
|
+
* use one. MEASURED from the published PBS schema: every `keep-*` carries `minimum: 1`, so PBS
|
|
66
|
+
* cannot store a zero and cannot be sent one — `keep-daily=0` is a 400, not a wipe. A field that
|
|
67
|
+
* comes back missing is a window that is not part of this job's policy, and `keepsMatch` must be
|
|
68
|
+
* able to tell that apart from "the declaration asked for zero", which it cannot express either.
|
|
69
|
+
*/
|
|
70
|
+
export const keepAttributes = (
|
|
71
|
+
live: Record<string, unknown>,
|
|
72
|
+
): Pick<PruneJobAttributes, KeepKey> => ({
|
|
73
|
+
'keep-daily': int(live['keep-daily'], 0),
|
|
74
|
+
'keep-hourly': int(live['keep-hourly'], 0),
|
|
75
|
+
'keep-last': int(live['keep-last'], 0),
|
|
76
|
+
'keep-monthly': int(live['keep-monthly'], 0),
|
|
77
|
+
'keep-weekly': int(live['keep-weekly'], 0),
|
|
78
|
+
'keep-yearly': int(live['keep-yearly'], 0),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* ⚠️ AN UNDECLARED WINDOW IS NOT COMPARED, AND THAT IS THE SAFETY MODEL RATHER THAN AN OVERSIGHT.
|
|
83
|
+
* A `keep-weekly` an operator set by hand on an adopted job is not drift this stack repairs —
|
|
84
|
+
* repairing it would mean narrowing retention nobody asked to narrow. The cost is stated rather
|
|
85
|
+
* than hidden: deleting a `keep-daily` line from a declaration plans as a NOOP and changes
|
|
86
|
+
* nothing on the server. See the ⛔ on `updateBody` for why clearing is a hand operation.
|
|
87
|
+
*/
|
|
88
|
+
export const keepsMatch = (attributes: Pick<PruneJobAttributes, KeepKey>, props: PruneJobProps) =>
|
|
89
|
+
KEEP_KEYS.every((key) => props[key] === undefined || props[key] === attributes[key]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* ⛔ A PRUNE JOB WITH NO `keep-*` KEEPS NOTHING, AND THIS REFUSES TO BUILD ONE.
|
|
93
|
+
*
|
|
94
|
+
* MEASURED from the published PBS API: all six windows are `optional: 1` on POST and on PUT. So
|
|
95
|
+
* on the wire "I forgot the retention" and "I want no retention" are the SAME REQUEST, and the
|
|
96
|
+
* second one is not a configuration — it is a scheduled deletion of every backup in scope. This
|
|
97
|
+
* is the one mistake this family must not be able to make.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ PBS PROBABLY REFUSES IT TOO, AND THAT IS NOT GOOD ENOUGH TO LEAN ON. Its GUI warns about a
|
|
100
|
+
* keep-less prune job, which suggests the API guards it as well, but I could not reach a PBS to
|
|
101
|
+
* prove it and "probably refused upstream" is not a thing to bet a restore on. The guard is
|
|
102
|
+
* local, it is cheap, and it costs nothing at all on a declaration that is already correct.
|
|
103
|
+
*
|
|
104
|
+
* ⛔ IT THROWS RATHER THAN RETURNING AN EMPTY FORM. `reconcile` calls these builders inside
|
|
105
|
+
* `Effect.gen`, so the throw lands as a defect and stops the deploy carrying this sentence. A
|
|
106
|
+
* builder that quietly answered `{}` instead would be a PUT of nothing — reported as success —
|
|
107
|
+
* over the retention the declaration existed to set.
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ IT FIRES AT DEPLOY, NOT AT PLAN, because `diff` never calls a form builder: a keep-less
|
|
110
|
+
* declaration plans as a clean create and then dies on apply. Loud and safe, but not early. The
|
|
111
|
+
* stronger guard is a props type where one window is mandatory — a `Base & ({'keep-last': number}
|
|
112
|
+
* | …)` union — and it is NOT here because I could not satisfy myself that Alchemy's
|
|
113
|
+
* `Input<Props>` narrowing distributes over that union, and an unverified type trick in the file
|
|
114
|
+
* that decides what gets deleted is worse than an honest runtime throw.
|
|
115
|
+
*/
|
|
116
|
+
const keepsSomething = (props: PruneJobProps) => KEEP_KEYS.some((key) => props[key] !== undefined);
|
|
117
|
+
|
|
118
|
+
const requireKeeps = (props: PruneJobProps) => {
|
|
119
|
+
if (keepsSomething(props)) return;
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Pbs.PruneJob ${props.id}: declares no keep-* window, which tells PBS to keep NOTHING on ` +
|
|
122
|
+
`datastore '${props.store}'. Every keep-* is optional on the wire, so an omission and a ` +
|
|
123
|
+
'deliberate "retain nothing" are indistinguishable; declare at least one window (keep-last, ' +
|
|
124
|
+
'keep-hourly, keep-daily, keep-weekly, keep-monthly or keep-yearly) or delete the job.',
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** ⚠️ An undeclared field emits NO KEY AT ALL, not an empty one — see the ⚠️ on `shape`. */
|
|
129
|
+
const field = (name: string, value: string | undefined): Record<string, string> =>
|
|
130
|
+
value === undefined ? {} : { [name]: value };
|
|
131
|
+
|
|
132
|
+
/** ⚠️ `undefined` in, `undefined` out, so `field` can drop it — never the text "undefined". */
|
|
133
|
+
const integer = (value: number | undefined) => (value === undefined ? undefined : String(value));
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The mutable half, shared by create and update.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ `store` AND `schedule` ARE ALWAYS SENT because they are always declared and always compared —
|
|
139
|
+
* a field compared against a value but NOT SENT as that value is an update reported on every
|
|
140
|
+
* plan that the PUT never performs (backup-job.ts and replication-job-form.ts both record that
|
|
141
|
+
* trade). PBS makes both required on POST and optional on PUT, so sending them on both calls is
|
|
142
|
+
* legal and keeps one shape for the two.
|
|
143
|
+
*
|
|
144
|
+
* ⚠️ EVERYTHING ELSE IS SENT ONLY WHEN DECLARED, AND UNDECLARED MEANS UNMANAGED — the rule
|
|
145
|
+
* storage.ts sets, not backup-job.ts's "always send the documented default". The difference is
|
|
146
|
+
* what the drift costs: an undetected `enabled 0` on a vzdump job means backups quietly are not
|
|
147
|
+
* happening, while an unasked-for change here means backups quietly ARE being deleted. Writing a
|
|
148
|
+
* default this stack never declared is the worse error for a destructive job, so it is not done.
|
|
149
|
+
* ⛔ `max-depth` IS THE EXCEPTION THAT PROVES IT AND IT RUNS THE OTHER WAY — see the ⛔ in
|
|
150
|
+
* pbs-prune-job.ts. Its unset value is FULL RECURSION, so leaving it unmanaged is the WIDE
|
|
151
|
+
* choice rather than the narrow one. Declare it.
|
|
152
|
+
*/
|
|
153
|
+
const shape = (props: PruneJobProps): Record<string, string> => ({
|
|
154
|
+
...keepForm(props),
|
|
155
|
+
...field('comment', props.comment),
|
|
156
|
+
...field('disable', flag(props.disable)),
|
|
157
|
+
...field('max-depth', integer(props['max-depth'])),
|
|
158
|
+
...field('ns', props.ns),
|
|
159
|
+
schedule: props.schedule,
|
|
160
|
+
store: props.store,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* ⛔ `id` IS SENT AND IS NOT OPTIONAL, for backup-job.ts's reason sharpened by this API's shape.
|
|
165
|
+
* MEASURED: `id` is a REQUIRED parameter of `POST /config/prune` — PBS does not generate one —
|
|
166
|
+
* so an omitted id is a 400 rather than a duplicate job. That is the good case; the bad one is
|
|
167
|
+
* the id being WRONG. `pveOperations.read` folds every failed GET into "absent", so a 403 or an
|
|
168
|
+
* expired lease reads as "no such job", the plan says create, and a second prune job appears
|
|
169
|
+
* beside the first with this declaration's retention applied to the same datastore. Two prune
|
|
170
|
+
* jobs on one store do not negotiate: each deletes what its own windows do not keep.
|
|
171
|
+
*/
|
|
172
|
+
export const createBody = (props: PruneJobProps): Record<string, string> => {
|
|
173
|
+
requireKeeps(props);
|
|
174
|
+
return { ...shape(props), id: props.id };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* ⛔ THERE IS NO `delete` LIST HERE AND `withClears()` IS DELIBERATELY NOT IMPORTED. Two
|
|
179
|
+
* independent reasons, either of which alone would be enough.
|
|
180
|
+
*
|
|
181
|
+
* FIRST, THE WIRE SHAPE IS NOT PVE'S. MEASURED from the published PBS schema, `PUT
|
|
182
|
+
* /config/prune/{id}` declares `delete` as `{"type":"array","items":{"type":"string","enum":[
|
|
183
|
+
* "comment","disable","ns","max-depth","keep-last","keep-hourly","keep-daily","keep-weekly",
|
|
184
|
+
* "keep-monthly","keep-yearly"]}}` — an ARRAY of enum members. PVE's `delete` is a comma-joined
|
|
185
|
+
* string, which is exactly what `withClears()` builds, and a form-encoded array is built by
|
|
186
|
+
* REPEATING the key. `delete=comment,disable` is therefore ONE item that is in no enum: a 400,
|
|
187
|
+
* not a clear.
|
|
188
|
+
* ⚠️ AND THE CORRECT SHAPE IS NOT EXPRESSIBLE TODAY. `pveWith` takes `Record<string, string>` and
|
|
189
|
+
* runs it through `new URLSearchParams`, which cannot repeat a key from a plain object. A PBS
|
|
190
|
+
* clear list needs a client that can, and that is a change to client.ts rather than to this
|
|
191
|
+
* file — so a clear list could not be shipped here even if it were wanted.
|
|
192
|
+
*
|
|
193
|
+
* SECOND, AND THIS ONE WOULD STAND ANYWAY: CLEARING A `keep-*` IS THE MOST DESTRUCTIVE THING
|
|
194
|
+
* THIS API CAN DO. Removing `keep-last` from a job that had it does not restore a default — PBS
|
|
195
|
+
* has no default retention — it NARROWS what survives the next run. So an undeclared field is
|
|
196
|
+
* unmanaged, and a window this stack never mentioned is one it never widens and never removes.
|
|
197
|
+
* Dropping a `keep-daily` line from a declaration therefore does NOTHING on purpose; clear it by
|
|
198
|
+
* hand, having first looked at what it is holding.
|
|
199
|
+
*
|
|
200
|
+
* ★ THE DISJOINTNESS `withClears()` EXISTS TO ENFORCE IS THEREFORE VACUOUS HERE: with no clear list
|
|
201
|
+
* there is no key that can be set and deleted in one call, which is the failure it guards. If a
|
|
202
|
+
* later change does add one it must go through `withClears()` AND teach the client to repeat the
|
|
203
|
+
* key — half of that is a 400, and the other half is PVE's spelling sent to a PBS host.
|
|
204
|
+
*/
|
|
205
|
+
export const updateBody = (props: PruneJobProps): Record<string, string> => {
|
|
206
|
+
requireKeeps(props);
|
|
207
|
+
return shape(props);
|
|
208
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Pbs.PruneJob` — a Proxmox Backup Server retention schedule, declared. PBS is a SIBLING of PVE,
|
|
3
|
+
* so this family reuses `pveHandlers` wholesale rather than growing a second factory.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ THIS IS THE ONE RESOURCE IN THIS PACKAGE THAT DESTROYS DATA ON AN ORDINARY *UPDATE*. CephPool,
|
|
6
|
+
* ZfsPool and Storage can destroy data too — which is exactly why they default to `retain` and
|
|
7
|
+
* make a caller opt in with `destroy()`, and that guard covers DELETE and nothing else. Here the
|
|
8
|
+
* destruction rides a plain `1 to update` that no removal policy has any opinion about. A wrong
|
|
9
|
+
* `keep-*` does not fail: it succeeds. The PUT returns 200, and the NEXT SCHEDULED RUN — minutes
|
|
10
|
+
* or days later, with nobody watching — removes every snapshot that fell outside the new window.
|
|
11
|
+
* There is no undo, PBS does not ask, and nothing about the deploy looks unusual.
|
|
12
|
+
* ★ CONCRETELY, SO NOBODY HAS TO IMAGINE IT: a live job with `keep-daily 30`, a declaration
|
|
13
|
+
* saying `keep-daily: 7`. `matches` reports an update, the PUT lands, and twenty-three days of
|
|
14
|
+
* daily backups are gone at the next run. The plan showed one word. READ THE `keep-*` HALF OF
|
|
15
|
+
* EVERY PLAN TWICE; it is the only warning there is.
|
|
16
|
+
* ⚠️ THE ONE THING THAT CANNOT HAPPEN IS A SILENT ZERO. MEASURED from the published schema: every
|
|
17
|
+
* `keep-*` carries `minimum: 1`, so a literal `keep-daily=0` is a 400 rather than a wipe. And
|
|
18
|
+
* an UNDECLARED window is not sent as zero either — it is not sent at all, because undeclared
|
|
19
|
+
* means unmanaged here exactly as it does in storage.ts. A window this stack never named is one
|
|
20
|
+
* it never narrows. `0` in the attributes below therefore means ABSENT, never a real setting.
|
|
21
|
+
*
|
|
22
|
+
* ⛔ `max-depth` UNSET IS THE WIDEST SCOPE, NOT THE NARROWEST, AND IT IS THE ONE FIELD WHERE
|
|
23
|
+
* "UNMANAGED" IS NOT THE SAFE ANSWER. MEASURED, quoting the published schema's own description:
|
|
24
|
+
* "0 == no recursion, empty == automatic full recursion". So a job that names an `ns` but leaves
|
|
25
|
+
* `max-depth` alone prunes every namespace BENEATH that one too, with these windows. Declare it.
|
|
26
|
+
* `-1` in the attributes means "absent from prune.cfg", the sentinel storage.ts uses for
|
|
27
|
+
* `maxfiles`, because `0` here is a real and very different setting.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ THE CREDENTIAL DOES NOT EXIST YET, SO NOTHING BELOW HAS EVER MADE A CALL. PBS is a different
|
|
30
|
+
* host and a different auth realm from PVE. MEASURED 2026-09-13: pbs.example.com:8007 answers
|
|
31
|
+
* HTTP 401 under STRICT TLS behind a real Let's Encrypt certificate (CN=pbs.example.com, issuer
|
|
32
|
+
* YR1, valid 2026-09-02 to 2026-12-01) — so there is no `-k` to reach for and `client.ts` needs
|
|
33
|
+
* no TLS escape hatch, exactly as on PVE. What it does need is a header it cannot build today.
|
|
34
|
+
* PBS's documented form is `Authorization: PBSAPIToken=TOKENID:TOKENSECRET` (measured from the
|
|
35
|
+
* published PBS documentation, 2026-09-13) while `authorization()` in credentials.ts builds
|
|
36
|
+
* `PVEAPIToken=<tokenid>=<secret>`: BOTH the scheme and the separator differ. A wrong separator
|
|
37
|
+
* answers 401 "authentication failure", which reads as a bad credential rather than as a
|
|
38
|
+
* malformed header — the same trap credentials.ts already records for PVE's colon form.
|
|
39
|
+
* ⛔ AND THERE IS NO MOUNT TO MINT FROM. `mint()` runs `bao read <mount>/creds/<role>`; no
|
|
40
|
+
* OpenBao mount vends PBS tokens — `proxmox-tb4` and `proxmox-ops` vend PVE ones. The
|
|
41
|
+
* `kv/infra/proxmox` shelf holds a READ-ONLY `Audit` token for PBS, and a read-only token
|
|
42
|
+
* cannot create a prune job. I could not confirm that shelf's contents: this machine's
|
|
43
|
+
* claude-code approle answers 403 on `sys/mounts` AND on `kv/metadata/infra/proxmox`
|
|
44
|
+
* (MEASURED). What is needed is a dynamic PBS mount vending a `provision` role with
|
|
45
|
+
* `Datastore.Modify` — say that, rather than reaching for the stored auditor token.
|
|
46
|
+
*
|
|
47
|
+
* ★ WHAT A `PbsTarget` NEEDS, AND WHY IT IS ALMOST `PveTarget`. Structurally `PveTarget` already
|
|
48
|
+
* fits a PBS host: a credential mount name and an `/api2/json` base are the whole of it, and PBS
|
|
49
|
+
* shares the `{"data": …}` envelope, the four-operation shape and the read-back discipline —
|
|
50
|
+
* which is exactly why this file declares a spec and stops. The single thing `PveTarget` cannot
|
|
51
|
+
* carry is WHICH AUTHORIZATION SCHEME the host at the other end speaks, so `PbsTarget` below adds
|
|
52
|
+
* that and nothing else rather than bending the PVE type in silence.
|
|
53
|
+
*
|
|
54
|
+
* ⚠️ WHICH VERSION THIS IS AUTHORED AGAINST, BECAUSE PRUNE JOBS MOVED. The paths and every field
|
|
55
|
+
* below are MEASURED from the PBS API as published at pbs.proxmox.com/docs/api-viewer, fetched
|
|
56
|
+
* 2026-09-13 — current PBS 4.x. Before PBS 2.2 there was no `/config/prune` at all: retention
|
|
57
|
+
* lived on the datastore section itself, as `keep-*` plus `prune-schedule` under
|
|
58
|
+
* `/config/datastore/{name}`. Against anything older every path here 404s, `pveOperations.read`
|
|
59
|
+
* folds that into "absent", and the plan says create forever. Check `GET /api2/json/version`
|
|
60
|
+
* before the first plan.
|
|
61
|
+
*
|
|
62
|
+
* ★ THE READ LANE NEEDS NOTHING EXTRA, UNLIKE THREE PVE FAMILIES — so there is no `readRole:
|
|
63
|
+
* 'provision'` below and there should not be one. MEASURED: `GET /config/prune/{id}` requires
|
|
64
|
+
* "Datastore.Audit or Datastore.Verify on job's datastore", i.e. the AUDIT privilege is enough
|
|
65
|
+
* for the ITEM read. That is precisely what `Proxmox.Storage` and the two SDN families could not
|
|
66
|
+
* say, and it is why the ⛔ on `readRole` in resource.ts does not claim a fourth family here.
|
|
67
|
+
* ⚠️ THE WRITE PRIVILEGES ARE NOT ONE PRIVILEGE, AND DELETE IS THE ODD ONE OUT. MEASURED: POST
|
|
68
|
+
* and PUT require `Datastore.Modify` on the job's datastore; DELETE requires `Datastore.Verify`.
|
|
69
|
+
* A credential that can create and edit a prune job cannot necessarily remove one, and the
|
|
70
|
+
* symptom is a destroy that 403s long after the creates worked.
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ RUNTIME STATUS IS NOT HERE AND WAS NOT FORGOTTEN. `last-run-endtime` and friends come from
|
|
73
|
+
* `/admin/prune`, not from this endpoint: MEASURED, `GET /config/prune/{id}` returns exactly
|
|
74
|
+
* comment, disable, id, the six keep-*, max-depth, ns, schedule and store — no digest, no status.
|
|
75
|
+
* Reporting a last-run time would make a plan's output change when nothing changed, and a plan
|
|
76
|
+
* whose output moves on its own is one people stop reading. `/admin/prune/{id}/run` triggers a
|
|
77
|
+
* prune by hand and is deliberately not modelled: running one is an act, not a declaration.
|
|
78
|
+
*/
|
|
79
|
+
import { Resource } from 'alchemy';
|
|
80
|
+
import * as Provider from 'alchemy/Provider';
|
|
81
|
+
import * as Effect from 'effect/Effect';
|
|
82
|
+
import type { PbsTarget } from './credentials.ts';
|
|
83
|
+
import { createBody, keepAttributes, keepsMatch, updateBody } from './pbs-prune-job-form.ts';
|
|
84
|
+
import { type PveRequirements, type WithPbsTarget, pveHandlers } from './resource.ts';
|
|
85
|
+
import { bool, int, text } from './values.ts';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ⚠️ THE PROPS ARE PBS'S OWN KEY NAMES, HYPHENS INCLUDED, for backup-job.ts's reason: the form
|
|
89
|
+
* builder is then a copy rather than a translation table, and a translation table is one more
|
|
90
|
+
* place for a key to be renamed and silently never sent. Here a field that is never sent is a
|
|
91
|
+
* retention window that never applies.
|
|
92
|
+
*/
|
|
93
|
+
export interface PruneJobProps extends WithPbsTarget {
|
|
94
|
+
/** ⛔ A PBS host, not a PVE one. See `PbsTarget` and the credential ⛔ in the header. */
|
|
95
|
+
target: PbsTarget;
|
|
96
|
+
/**
|
|
97
|
+
* ⛔ The primary key, REQUIRED, and PBS does not generate one. Measured: 3–32 chars matching
|
|
98
|
+
* `^[A-Za-z0-9_][A-Za-z0-9._\-]*$`. To adopt a job that exists, declare its existing id — any
|
|
99
|
+
* other id is a SECOND prune job on the same datastore, and two prune jobs do not negotiate.
|
|
100
|
+
*/
|
|
101
|
+
id: string;
|
|
102
|
+
/** ⚠️ The datastore this prunes. Changing it re-points the job rather than adding one: the old
|
|
103
|
+
* store stops being pruned and the new one starts, with these windows, at the next run. */
|
|
104
|
+
store: string;
|
|
105
|
+
/**
|
|
106
|
+
* A systemd calendar event, e.g. `daily` or `sat 02:30`. ⛔ REQUIRED — measured, it is the one
|
|
107
|
+
* non-optional field of `POST /config/prune` besides `id` and `store`, and it is NOT in the PUT
|
|
108
|
+
* `delete` enum, so there is no way to un-schedule a job short of disabling or removing it.
|
|
109
|
+
* ⚠️ THIS COMPARES STRINGS, NOT MEANINGS. A calendar event has many equivalent spellings and PBS
|
|
110
|
+
* stores the one it was handed; declare the spelling the server echoes back.
|
|
111
|
+
*/
|
|
112
|
+
schedule: string;
|
|
113
|
+
/** Namespace to prune, `''` or unset for the root one. ⚠️ Scope, so read it with `max-depth`. */
|
|
114
|
+
ns?: string;
|
|
115
|
+
/** ⛔ UNSET MEANS FULL RECURSION, NOT NONE — see the ⛔ in the header. 0–7. Declare it. */
|
|
116
|
+
'max-depth'?: number;
|
|
117
|
+
/** ⚠️ Undeclared is UNMANAGED, so a job somebody disabled by hand stays disabled. That is the
|
|
118
|
+
* deliberate direction for a destructive job: never switch one on that nobody asked for. */
|
|
119
|
+
disable?: boolean;
|
|
120
|
+
comment?: string;
|
|
121
|
+
/** ⚠️ Every window below is `minimum: 1` (measured) and undeclared means unmanaged. */
|
|
122
|
+
'keep-last'?: number;
|
|
123
|
+
'keep-hourly'?: number;
|
|
124
|
+
'keep-daily'?: number;
|
|
125
|
+
'keep-weekly'?: number;
|
|
126
|
+
'keep-monthly'?: number;
|
|
127
|
+
'keep-yearly'?: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface PruneJobAttributes {
|
|
131
|
+
id: string;
|
|
132
|
+
store: string;
|
|
133
|
+
schedule: string;
|
|
134
|
+
ns: string;
|
|
135
|
+
/** ⚠️ `-1` is ABSENT — and absent means full recursion. `0` is the real "no recursion" setting. */
|
|
136
|
+
'max-depth': number;
|
|
137
|
+
disable: boolean;
|
|
138
|
+
comment: string;
|
|
139
|
+
/** ⚠️ `0` is ABSENT in all six. PBS's own minimum is 1, so zero can never be a live value. */
|
|
140
|
+
'keep-last': number;
|
|
141
|
+
'keep-hourly': number;
|
|
142
|
+
'keep-daily': number;
|
|
143
|
+
'keep-weekly': number;
|
|
144
|
+
'keep-monthly': number;
|
|
145
|
+
'keep-yearly': number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface PbsPruneJob extends Resource<
|
|
149
|
+
'Pbs.PruneJob',
|
|
150
|
+
PruneJobProps,
|
|
151
|
+
PruneJobAttributes,
|
|
152
|
+
never,
|
|
153
|
+
PveRequirements
|
|
154
|
+
> {}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* ★ NO `defaultRemovalPolicy: 'retain'`, UNLIKE Storage AND ZfsPool, AND THE REASON IS THE REVERSE
|
|
158
|
+
* OF THEIRS. Destroying this resource destroys a POLICY, and a policy is a line of TypeScript.
|
|
159
|
+
* The archives it would have trimmed are not touched by the DELETE — see the ⚠️ on the provider.
|
|
160
|
+
*/
|
|
161
|
+
export const PbsPruneJob = Resource<PbsPruneJob>('Pbs.PruneJob');
|
|
162
|
+
|
|
163
|
+
/** "Not declared, or equal" — the single shape every unmanaged field is compared with. */
|
|
164
|
+
const same = <T>(declared: T | undefined, live: T) => declared === undefined || declared === live;
|
|
165
|
+
|
|
166
|
+
const handlers = pveHandlers<PruneJobProps, PruneJobAttributes>({
|
|
167
|
+
/**
|
|
168
|
+
* ⚠️ EVERY OPTIONAL FIELD FALLS BACK TO ITS ABSENT SENTINEL RATHER THAN TO PBS'S DEFAULT, because
|
|
169
|
+
* for this family the two are different questions. `keep-daily` absent does not mean "keep zero
|
|
170
|
+
* dailies", it means "this window is not part of the policy", and `matches` must not compare a
|
|
171
|
+
* window the declaration never mentioned against an invented value.
|
|
172
|
+
* ⛔ NO "IS IT REALLY THERE" GUARD, for backup-job.ts's reason: returning undefined for a job
|
|
173
|
+
* whose JSON is missing a key would be a guess about which keys PBS echoes, and a wrong guess
|
|
174
|
+
* sends reconcile down the POST branch — which creates ANOTHER prune job rather than failing.
|
|
175
|
+
*/
|
|
176
|
+
attributes: (live, props) => ({
|
|
177
|
+
comment: text(live['comment'], ''),
|
|
178
|
+
disable: bool(live['disable'], false),
|
|
179
|
+
id: props.id,
|
|
180
|
+
...keepAttributes(live),
|
|
181
|
+
'max-depth': int(live['max-depth'], -1),
|
|
182
|
+
ns: text(live['ns'], ''),
|
|
183
|
+
schedule: text(live['schedule'], ''),
|
|
184
|
+
store: text(live['store'], props.store),
|
|
185
|
+
}),
|
|
186
|
+
collection: () => 'config/prune',
|
|
187
|
+
createForm: createBody,
|
|
188
|
+
/**
|
|
189
|
+
* ⚠️ THE UNDECLARED HALF IS NOT COMPARED, AND THAT IS THE WHOLE SAFETY MODEL. `same` reads "not
|
|
190
|
+
* declared, or equal", so a field set by hand on an adopted job survives adoption untouched and
|
|
191
|
+
* never appears as drift. `keepsMatch` applies the identical rule to the six windows, from the
|
|
192
|
+
* file that also writes and reads them — see the ★ at the top of pbs-prune-job-form.ts.
|
|
193
|
+
* ⚠️ `store` AND `schedule` HAVE NO UNDECLARED CASE because both are required props, so both are
|
|
194
|
+
* compared unconditionally. A changed `store` is an update, not a replace: PBS accepts `store`
|
|
195
|
+
* on PUT, so the job is re-pointed in place.
|
|
196
|
+
*/
|
|
197
|
+
matches: (attributes, props) =>
|
|
198
|
+
attributes.schedule === props.schedule &&
|
|
199
|
+
attributes.store === props.store &&
|
|
200
|
+
same(props.ns, attributes.ns) &&
|
|
201
|
+
same(props['max-depth'], attributes['max-depth']) &&
|
|
202
|
+
same(props.disable, attributes.disable) &&
|
|
203
|
+
same(props.comment, attributes.comment) &&
|
|
204
|
+
keepsMatch(attributes, props),
|
|
205
|
+
path: (props) => `config/prune/${props.id}`,
|
|
206
|
+
updateForm: updateBody,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* ⛔ `list` IS EMPTY like every other family here, and the stakes are the usual ones read backwards.
|
|
211
|
+
* `GET /config/prune` hands back every prune job on the server, the ones an operator tuned by
|
|
212
|
+
* hand included. Adopting those would give Alchemy the right to REWRITE their retention the first
|
|
213
|
+
* time a declaration disagreed — and rewriting retention is deleting backups. Adoption stays an
|
|
214
|
+
* explicit act: name the id you mean.
|
|
215
|
+
*
|
|
216
|
+
* ⚠️ THE DELETE IS THE SAFE DIRECTION, AND IT IS STILL NOT FREE. Removing a prune job deletes
|
|
217
|
+
* NOTHING: the archives that survived the last run stay exactly where they are. What goes away is
|
|
218
|
+
* the only thing trimming that datastore, so the failure mode is a datastore that fills up weeks
|
|
219
|
+
* later — an outage with no obvious cause and no backup written during it. Nothing on the server
|
|
220
|
+
* depends on a prune job, so PBS will not refuse this the way PVE refuses to delete a pool still
|
|
221
|
+
* holding guests; the plan line is the only warning anyone gets.
|
|
222
|
+
* ⚠️ DELETE ALSO NEEDS A DIFFERENT PRIVILEGE FROM THE WRITES — `Datastore.Verify`, not
|
|
223
|
+
* `Datastore.Modify`. See the ⚠️ in the header before assuming a working create means a working
|
|
224
|
+
* destroy.
|
|
225
|
+
*/
|
|
226
|
+
export const PbsPruneJobProvider = () =>
|
|
227
|
+
Provider.effect(PbsPruneJob, Effect.succeed(PbsPruneJob.Provider.of(handlers)));
|