@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,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is staged on the SDN, across the WHOLE surface the apply publishes — not just the two
|
|
3
|
+
* collections this package declares resources for.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 THE GAP THIS FILE CLOSES, AND IT WAS FOUND BY ALMOST DECLARING THE APPLY. `pendingCount` in
|
|
6
|
+
* sdn-apply.ts counted `cluster/sdn/zones` and `cluster/sdn/vnets`. TB4 has zero of each — and a
|
|
7
|
+
* staged OSPF FABRIC that nothing counted. MEASURED 2026-09-13 on n2:
|
|
8
|
+
*
|
|
9
|
+
* /etc/pve/sdn/fabrics.cfg ospf_fabric: tb4, area 1, ip_prefix 10.100.0.0/24
|
|
10
|
+
* ospf_node: tb4_n2/n3/n4, interfaces en05 + en06,
|
|
11
|
+
* ip 10.100.0.102 / .103 / .104
|
|
12
|
+
* /etc/network/interfaces.d/sdn the file PVE GENERATES from it, carrying those /32s
|
|
13
|
+
*
|
|
14
|
+
* Those addresses are Ceph's `cluster_network`. So `diff` would have answered `noop` for ever
|
|
15
|
+
* while `PUT /cluster/sdn` republished the fabric and reloaded networking on all three nodes at
|
|
16
|
+
* once — the simultaneity the NetworkApply chain exists to prevent, arriving through the door
|
|
17
|
+
* nobody was watching.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ THE SURFACE IS NOT UNIFORM, AND ASSUMING IT WAS IS WHAT MADE THE GAP INVISIBLE. Measured, one
|
|
20
|
+
* request per collection, per query:
|
|
21
|
+
*
|
|
22
|
+
* zones, vnets, controllers, prefix-lists, route-maps accept ?pending=1 and ?running=1,
|
|
23
|
+
* and mark a staged row with `state`
|
|
24
|
+
* fabrics accepts both, and its rows carry NO
|
|
25
|
+
* `state` marker at all
|
|
26
|
+
* ipams, dns REJECT both with HTTP 400,
|
|
27
|
+
* "property is not defined in schema"
|
|
28
|
+
*
|
|
29
|
+
* ⛔ SO A `state`-ONLY TEST IS WRONG FOR FABRICS, AND `orElseSucceed(() => 0)` IS WORSE THAN WRONG
|
|
30
|
+
* FOR ipams AND dns. The old code folded an unreadable collection to zero — right for a cluster
|
|
31
|
+
* with no SDN at all, and indistinguishable from "this collection rejects the question". Adding
|
|
32
|
+
* ipams and dns to the old list would have counted them as 0 for ever and read as coverage.
|
|
33
|
+
*/
|
|
34
|
+
import * as Effect from 'effect/Effect';
|
|
35
|
+
import type { PveTarget } from './credentials.ts';
|
|
36
|
+
/**
|
|
37
|
+
* Collections with NO pending view at all.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ NAMED RATHER THAN SILENTLY SKIPPED. `ipams` and `dns` answer HTTP 400 to both `?pending=1` and
|
|
40
|
+
* `?running=1`, so there is no way to ask them what is staged. They are a REAL blind spot: an
|
|
41
|
+
* ipam edited by hand is published by the apply and cannot be seen from here. TB4 has one ipam
|
|
42
|
+
* (`pve`, the built-in) and zero dns entries, so the blind spot is currently empty — which is a
|
|
43
|
+
* fact about today, not a property of the design.
|
|
44
|
+
*/
|
|
45
|
+
export declare const UNDIFFABLE: readonly ['cluster/sdn/ipams', 'cluster/sdn/dns'];
|
|
46
|
+
/**
|
|
47
|
+
* Whether a failed read means "this SDN subsystem does not exist here" — the ONE failure that may
|
|
48
|
+
* read as nothing staged.
|
|
49
|
+
*
|
|
50
|
+
* 🔴 IT USED TO BE EVERY FAILURE. `orElseSucceed` caught 401, 403, 5xx and a dropped connection
|
|
51
|
+
* along with the 501 it was written for, so an expired read lease or a missing grant answered
|
|
52
|
+
* "nothing staged": `diff` said noop, and the read-back after a real apply said settled while
|
|
53
|
+
* objects were still staged. The fabric views were worse because they are COMPARED — one view
|
|
54
|
+
* failing while the other succeeded read as a DIFFERENCE, which is `PUT /cluster/sdn` and a
|
|
55
|
+
* network reload carrying Ceph's cluster network on all three nodes, over a timeout. Found in
|
|
56
|
+
* review on 2026-09-14 and confirmed by reading.
|
|
57
|
+
* ⛔ SO ANYTHING ELSE FAILS THE READ. A plan that stops on an unreadable SDN is loud and safe; a
|
|
58
|
+
* plan that guesses is neither.
|
|
59
|
+
*/
|
|
60
|
+
export declare const subsystemAbsent: (error: unknown) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Staged fabric changes from the two canonical views, where `undefined` is a view whose subsystem
|
|
63
|
+
* answered 501.
|
|
64
|
+
*
|
|
65
|
+
* ⛔ ONE VIEW ABSENT AND ONE PRESENT IS NOT A DIFFERENCE. It is one endpoint answering two
|
|
66
|
+
* questions inconsistently, and a difference is exactly what triggers the publish — so it is
|
|
67
|
+
* refused rather than counted.
|
|
68
|
+
*/
|
|
69
|
+
export declare const fabricStaged: (staged: string | undefined, running: string | undefined) => number | 'inconsistent';
|
|
70
|
+
/** Stable JSON with `digest` removed and every array sorted, so only real differences show. */
|
|
71
|
+
export declare const canonical: (value: unknown) => string;
|
|
72
|
+
/**
|
|
73
|
+
* How many staged changes the apply would publish, across everything it is capable of publishing.
|
|
74
|
+
*
|
|
75
|
+
* ⛔ ZERO HERE IS THE ONLY THING THAT MAKES DECLARING THE APPLY SAFE. `reconcile` runs on CREATE —
|
|
76
|
+
* Alchemy always calls it the first time a resource appears — and `PUT /cluster/sdn` regenerates
|
|
77
|
+
* `/etc/network/interfaces.d/sdn` on every node. On TB4 that file carries Ceph's cluster network.
|
|
78
|
+
*/
|
|
79
|
+
export declare const sdnPendingCount: (target: PveTarget) => Effect.Effect<number, Error, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
80
|
+
//# sourceMappingURL=sdn-apply-read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdn-apply-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-apply-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAwBlD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,YAAI,mBAAmB,EAAE,iBAAiB,CAAU,CAAC;AAE5E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,OACE,CAAC;AAqBpD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,WACf,MAAM,GAAG,SAAS,WACjB,MAAM,GAAG,SAAS,KAC1B,MAAM,GAAG,cAIX,CAAC;AAuCF,+FAA+F;AAC/F,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,MAAyC,CAAC;AAiBrF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,WAAY,SAAS,uFAG9C,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.SdnApply` — the call that makes a declared network real.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ WITHOUT THIS RESOURCE, EVERY SDN DEPLOY IS A GREEN PLAN THAT CHANGES NO NETWORK. Writes under
|
|
5
|
+
* `cluster/sdn/*` edit files in `/etc/pve/sdn/` and touch nothing on any node. `PUT /cluster/sdn`
|
|
6
|
+
* is the apply, and it is CLUSTER-WIDE — it publishes every staged object at once, not the one
|
|
7
|
+
* that happened to reconcile.
|
|
8
|
+
*
|
|
9
|
+
* ★ MEASURED ON TB4 ON 2026-09-13, NOT INFERRED. A probe zone was created, inspected and removed:
|
|
10
|
+
*
|
|
11
|
+
* pvesh create /cluster/sdn/zones --zone zzprobe --type simple
|
|
12
|
+
* GET /cluster/sdn/zones/zzprobe -> {"digest":"c438…","type":"simple","zone":"zzprobe"}
|
|
13
|
+
* GET /cluster/sdn/zones/zzprobe?pending=1 -> {"digest":null,"pending":{},"state":"new",…}
|
|
14
|
+
* GET /cluster/sdn/zones?running=1 -> [] ⬅ THE APPLIED CONFIG WAS EMPTY
|
|
15
|
+
*
|
|
16
|
+
* So the plain read — the one `pveOperations` performs — answers from the STAGED file and cannot
|
|
17
|
+
* tell staged from applied. `state` appears only under `?pending=1`, and `?running=1` is the
|
|
18
|
+
* applied view. That is the whole basis for this resource.
|
|
19
|
+
*
|
|
20
|
+
* ★ IT IS A RESOURCE RATHER THAN A HOOK SO THAT THE GRAPH ORDERS IT. Pass the zones and vnets it
|
|
21
|
+
* should follow in `after`; Alchemy orders by data flow, so reading their attributes is what
|
|
22
|
+
* puts the apply last. A stack-level hook would fire on every run, including runs where no SDN
|
|
23
|
+
* object changed, and would be invisible in `plan`.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ ONE APPLY PER STACK. Two of these would each publish the other's staged half, so whichever
|
|
26
|
+
* ran first would apply a zone its own declaration had not finished writing. Declare one.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ THE COLLECTION READ IS DELIBERATE, AND IT IS WHAT LETS THE READ LEASE SEE ANYTHING AT ALL.
|
|
29
|
+
* `GET /cluster/sdn/zones/{zone}` is checked against SDN.Allocate — read the ⛔ in sdn-zone.ts —
|
|
30
|
+
* but `GET /cluster/sdn/zones` is "list entries where you have SDN.Audit or SDN.Allocate", so
|
|
31
|
+
* the collection answers an auditor-shaped credential. This resource only ever reads collections.
|
|
32
|
+
*/
|
|
33
|
+
import { Resource } from 'alchemy';
|
|
34
|
+
import * as Provider from 'alchemy/Provider';
|
|
35
|
+
import type { PveRequirements } from './resource.ts';
|
|
36
|
+
import type { WithTarget } from './resource.ts';
|
|
37
|
+
export interface SdnApplyProps extends WithTarget {
|
|
38
|
+
/**
|
|
39
|
+
* The staged objects this apply publishes.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ IT IS NEVER READ, AND IT IS NOT DECORATION. Alchemy orders resources by DATA FLOW, so the
|
|
42
|
+
* only way to say "after the zones" is to consume something the zones produced. Pass their
|
|
43
|
+
* attributes here — `after: [zone.zone, vnet.vnet]` — and the apply lands last. Leave it out
|
|
44
|
+
* and the apply may run BEFORE the zone it was meant to publish, which plans green and
|
|
45
|
+
* changes nothing.
|
|
46
|
+
*/
|
|
47
|
+
after?: readonly unknown[];
|
|
48
|
+
}
|
|
49
|
+
export interface SdnApplyAttributes {
|
|
50
|
+
/**
|
|
51
|
+
* How many SDN objects are still staged.
|
|
52
|
+
*
|
|
53
|
+
* ⚠️ ZERO IS THE ONLY SETTLED VALUE. Anything above it means the running config and the staged
|
|
54
|
+
* config disagree — which is drift whether this stack caused it or somebody staged a zone in
|
|
55
|
+
* the UI and walked away.
|
|
56
|
+
*/
|
|
57
|
+
pending: number;
|
|
58
|
+
}
|
|
59
|
+
export interface ProxmoxSdnApply extends Resource<'Proxmox.SdnApply', SdnApplyProps, SdnApplyAttributes, never, PveRequirements> {
|
|
60
|
+
}
|
|
61
|
+
export declare const ProxmoxSdnApply: import("alchemy").ResourceClass<ProxmoxSdnApply>;
|
|
62
|
+
export declare const ProxmoxSdnApplyProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxSdnApply>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
63
|
+
//# sourceMappingURL=sdn-apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdn-apply.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,QAAQ,CAC/C,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,eAAe,kDAAgD,CAAC;AAK7E,eAAO,MAAM,uBAAuB,qIA0EjC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a subnet crosses the wire in both directions, and what PVE actually calls its path.
|
|
3
|
+
*
|
|
4
|
+
* ★ SPLIT OUT OF sdn-subnet.ts TO KEEP BOTH FILES UNDER THE 250-LINE CAP, on the seam
|
|
5
|
+
* node-network-form.ts cuts rather than at a convenient line number: this file owns the
|
|
6
|
+
* COERCIONS in BOTH directions plus the derived id, because a read that normalises differently
|
|
7
|
+
* from the write that produced it is exactly how a forever-diff is born. sdn-subnet.ts answers
|
|
8
|
+
* "what is a subnet, and when has it changed". Nothing here calls the cluster.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ THE `import type` BACK TO sdn-subnet.ts IS A CYCLE ON PAPER ONLY — type-only, so it is erased
|
|
11
|
+
* before anything runs and `SdnSubnetProps` stays in the file that declares the resource.
|
|
12
|
+
*/
|
|
13
|
+
import type { SdnSubnetProps } from './sdn-subnet.ts';
|
|
14
|
+
/** One DHCP pool. PVE's property-string keys are `start-address`/`end-address`; see `rangeString`. */
|
|
15
|
+
export type DhcpRange = {
|
|
16
|
+
start: string;
|
|
17
|
+
end: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The id PVE files a subnet under — and therefore the path segment GET, PUT and DELETE address it
|
|
21
|
+
* by, which is NOT the value POST is handed.
|
|
22
|
+
*
|
|
23
|
+
* ⛔ MEASURED from `/usr/share/perl5/PVE/API2/Network/SDN/Subnets.pm` on pve-manager 9.2.11, in
|
|
24
|
+
* the create handler: `my $id = $cidr =~ s/\//-/r; $id = "$zoneid-$id";`. So `subnet=10.0.0.0/24`
|
|
25
|
+
* goes out and `house-10.0.0.0-24` is what exists afterwards. A CIDR cannot be a path segment
|
|
26
|
+
* anyway — it carries a slash — so there is no spelling of this endpoint that takes the CIDR.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ `.replace`, NOT `.replaceAll`, AND THE PARITY IS DELIBERATE. Perl's `s/\//-/r` has no `/g`, so
|
|
29
|
+
* it rewrites the FIRST slash only. A CIDR has exactly one, so the two agree on every legal
|
|
30
|
+
* input; they would stop agreeing on an illegal one, and agreeing with PVE is the point.
|
|
31
|
+
*/
|
|
32
|
+
export declare const subnetId: (props: Pick<SdnSubnetProps, 'cidr' | 'zone'>) => string;
|
|
33
|
+
/**
|
|
34
|
+
* DHCP ranges, from either shape, as one comparable string.
|
|
35
|
+
*
|
|
36
|
+
* ⛔ THIS FAMILY'S WRITE SHAPE AND READ SHAPE ARE DIFFERENT, WHICH IS THE CLASSIC FOREVER-DIFF.
|
|
37
|
+
* MEASURED from the API module: `$scfg->{'dhcp-range'} = get_dhcp_ranges($scfg)` runs
|
|
38
|
+
* `parse_property_string` over every stored element, so a range WRITTEN as the string
|
|
39
|
+
* `start-address=10.0.0.10,end-address=10.0.0.20` is READ BACK as the object
|
|
40
|
+
* `{"start-address":"10.0.0.10","end-address":"10.0.0.20"}`. `propertyString` in values.ts
|
|
41
|
+
* flattens both shapes to one sorted form and exists for exactly this — see its own ⚠️.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ AND IT IS A LIST, SO ORDER MUST NOT BE MEANING. PVE validates only that ranges do not overlap
|
|
44
|
+
* and stores them in the order given; two declarations naming the same pools the other way round
|
|
45
|
+
* are the same subnet. Sorting is what stops that being an update, exactly as `csv` does for a
|
|
46
|
+
* zone's `nodes`.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ `;` IS THE JOINER BECAUSE `,` AND `=` ARE BOTH INSIDE THE VALUES. It never appears in an IPv4
|
|
49
|
+
* or IPv6 address or in a property-string key, so the joined form cannot be ambiguous.
|
|
50
|
+
*/
|
|
51
|
+
export declare const dhcpRanges: (value: unknown) => string;
|
|
52
|
+
/** The declared side of that same comparison, through the same funnel so it cannot drift. */
|
|
53
|
+
export declare const declaredRanges: (range: DhcpRange | undefined) => string;
|
|
54
|
+
/**
|
|
55
|
+
* Every optional this resource MANAGES: the value to send, or `undefined` meaning "clear it".
|
|
56
|
+
*
|
|
57
|
+
* ⛔ ONE TABLE FEEDS BOTH HALVES OF AN UPDATE, the way metric-server-form.ts does it, and for the
|
|
58
|
+
* same measured reason: A PUT THAT OMITS A FIELD DOES NOT CLEAR IT. PVE merges the form into the
|
|
59
|
+
* existing section (`$data->{$_} = $opts->{$_} for keys $opts->%*`), so dropping `gateway` from a
|
|
60
|
+
* declaration leaves the old gateway in place and `matches` asks for the same update forever.
|
|
61
|
+
* Deriving the form and the `delete=` list from one map makes "managed but not clearable"
|
|
62
|
+
* impossible to write here.
|
|
63
|
+
*
|
|
64
|
+
* ⛔ AND THE TWO HALVES MUST NOT OVERLAP. MEASURED in `PVE::SectionConfig::delete_from_config`
|
|
65
|
+
* (SectionConfig.pm:1853): naming a key in BOTH the form and `delete=` dies with "cannot set and
|
|
66
|
+
* delete property '<k>' at the same time!". Because both halves are read off this one map, an
|
|
67
|
+
* entry is in exactly one of them by construction.
|
|
68
|
+
*
|
|
69
|
+
* ⛔ NOTHING SET OUT OF BAND MAY BE LISTED HERE. A field in this map is CLEARED the moment it is
|
|
70
|
+
* undeclared, so a live subnet whose DHCP pool an operator widened in the UI is narrowed back on
|
|
71
|
+
* the next deploy. That is full ownership, the same bargain sdn-vnet.ts strikes over `alias`, and
|
|
72
|
+
* it is a bargain rather than an accident only while this list stays deliberate.
|
|
73
|
+
*
|
|
74
|
+
* ⚠️ `snat` IS ABSENT ON PURPOSE AND LIVES IN `body` INSTEAD. It is a boolean, and `0` and "never
|
|
75
|
+
* declared" are the same subnet to PVE and to `matches`, so sending an explicit `0` converges
|
|
76
|
+
* without a `delete=` — the same choice sdn-vnet.ts makes for `vlanaware` and `isolate-ports`.
|
|
77
|
+
*/
|
|
78
|
+
export declare const optional: (props: SdnSubnetProps) => Record<string, string | undefined>;
|
|
79
|
+
/**
|
|
80
|
+
* The fields sent on EVERY write, create and update alike.
|
|
81
|
+
*
|
|
82
|
+
* ⚠️ `vnet` IS SENT THOUGH THE URL ALREADY CARRIES IT. PVE's handler reads `$param->{vnet}` to
|
|
83
|
+
* resolve the zone for its permission check, and it is a declared parameter of both the create
|
|
84
|
+
* and the update schema, so sending the same value the path carries is accepted and removes this
|
|
85
|
+
* file's dependence on the router injecting a path placeholder into the parameter hash.
|
|
86
|
+
*/
|
|
87
|
+
export declare const body: (props: SdnSubnetProps) => Record<string, string>;
|
|
88
|
+
/**
|
|
89
|
+
* ⚠️ `type` IS HARDCODED, NOT A PROP. PVE's create schema declares it as an enum with exactly one
|
|
90
|
+
* member, `subnet`, so a prop for it could only ever hold one value — and the POST refuses to
|
|
91
|
+
* proceed without it, which is the parameter-verification error sdn-vnet.ts records.
|
|
92
|
+
*/
|
|
93
|
+
export declare const createForm: (props: SdnSubnetProps) => Record<string, string>;
|
|
94
|
+
/**
|
|
95
|
+
* ★ CLEARING AN OPTION THAT WAS NEVER SET IS A SILENT NO-OP, AND THIS IS MEASURED RATHER THAN
|
|
96
|
+
* HOPED FOR. sdn-vnet.ts flags the same `delete=` habit as REASONED-not-measured and warns that a
|
|
97
|
+
* first update on a bare object would carry `delete=alias,tag`. Read off the code both endpoints
|
|
98
|
+
* share — `PVE::SectionConfig::delete_from_config`, SectionConfig.pm:1853-1866 — the loop dies
|
|
99
|
+
* only for an option that is unknown to the plugin, not optional, fixed, or also being set; for
|
|
100
|
+
* an option simply absent from the section it runs `delete $config->{$k}` on a missing key and
|
|
101
|
+
* moves on. All four keys above are `optional => 1` in `SubnetPlugin::options`, so a bare
|
|
102
|
+
* subnet's first update carries `delete=dhcp-dns-server,dhcp-range,dnszoneprefix,gateway` and
|
|
103
|
+
* PVE accepts it. `vnet` is `optional => 0` there and is never in this list.
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ TWO OF THOSE FOUR NAMES ARE HYPHENATED, WHICH NO OTHER FAMILY HERE HAS PUT IN A `delete=`.
|
|
106
|
+
* The parameter's format is `pve-configid-list`, and a name that failed it would 400 the whole
|
|
107
|
+
* update rather than skip one field. MEASURED: `$CONFIGID_RE` is `qr/[a-z][a-z0-9_-]+/i`
|
|
108
|
+
* (JSONSchema.pm:30), so a hyphen after the first character is legal and all four pass.
|
|
109
|
+
*/
|
|
110
|
+
export declare const updateForm: (props: SdnSubnetProps) => Record<string, string>;
|
|
111
|
+
//# sourceMappingURL=sdn-subnet-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdn-subnet-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-subnet-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,UAAW,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC,WACpB,CAAC;AAKlD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,MAK9B,CAAC;AAEf,6FAA6F;AAC7F,eAAO,MAAM,cAAc,UAAW,SAAS,GAAG,SAAS,WACE,CAAC;AAU9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,QAAQ,UAAW,cAAc,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAKhF,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,UAAW,cAAc,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAQhE,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,cAAc,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAItE,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,UAAW,cAAc,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAKvE,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.SdnSubnet` — the addresses on a declared bridge: the CIDR, its gateway, its DHCP pool.
|
|
3
|
+
*
|
|
4
|
+
* ★ THE THIRD LINK OF ONE CHAIN, AND THE ORDER IS DATA FLOW RATHER THAN CONVENTION. A subnet
|
|
5
|
+
* belongs to a vnet and a vnet belongs to a zone. MEASURED and recorded in sdn-vnet.ts: creating
|
|
6
|
+
* a vnet is permission-checked at `/sdn/zones/-`, so a missing zone surfaces as a PERMISSION
|
|
7
|
+
* error rather than as "no such zone". One level deeper the same shape holds — PVE's
|
|
8
|
+
* `check_vnet_access` looks the vnet's ZONE up and checks `/sdn/zones/<zone>/<vnet>` — so a
|
|
9
|
+
* subnet declared against a vnet that does not exist yet dies inside that lookup, with the vnet
|
|
10
|
+
* named and the zone not. Pass the zone and vnet resources' OWN attributes into `zone` and
|
|
11
|
+
* `vnet`; a literal buys no ordering, and here it also buys a wrong path.
|
|
12
|
+
*
|
|
13
|
+
* ⛔ THE ID PVE FILES A SUBNET UNDER IS NOT THE ONE YOU WRITE, AND IT IS THIS FAMILY'S SHARPEST
|
|
14
|
+
* EDGE. `POST` takes `subnet=10.0.0.0/24` while GET, PUT and DELETE address `house-10.0.0.0-24`.
|
|
15
|
+
* One PVE parameter name, `subnet`, carrying two different values — so they are named APART here,
|
|
16
|
+
* `cidr` for the declared one and `subnetId` for the derived one, for the reason values.ts gives
|
|
17
|
+
* about `flag`: a name that means two things is a bug waiting for whoever reads one meaning. The
|
|
18
|
+
* derivation, and its measurement, live in `subnetId` in sdn-subnet-form.ts.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ WHICH IS WHY `zone` IS A PROP THOUGH PVE NEVER ACCEPTS IT ON A SUBNET WRITE. The path cannot
|
|
21
|
+
* be built without it and it is knowable only from the VNET. Declare the wrong zone and the id is
|
|
22
|
+
* wrong: the POST still succeeds, because PVE computes the real id itself from the vnet, and the
|
|
23
|
+
* read-back then looks for a subnet that is not there. The factory's guard in resource.ts DIES on
|
|
24
|
+
* that first deploy — "the write returned no error but the object is still absent" — which is
|
|
25
|
+
* loud and immediate, and much better than the alternative it replaces: a plan that says create,
|
|
26
|
+
* forever, against a subnet PVE insists is already defined.
|
|
27
|
+
*
|
|
28
|
+
* ⛔ SDN WRITES ARE STAGED, NOT APPLIED — the same trap as sdn-zone.ts and sdn-vnet.ts, and it
|
|
29
|
+
* walks straight through the factory's read-back, which proves the staging file and nothing more.
|
|
30
|
+
* Nothing here applies. `Proxmox.SdnApply` (sdn-apply.ts) publishes the whole staged config with
|
|
31
|
+
* `PUT /cluster/sdn`; declare one and pass this resource's `subnetId` in its `after`.
|
|
32
|
+
*
|
|
33
|
+
* ⛔ AND `Proxmox.SdnApply` CANNOT YET SEE A STAGED SUBNET, SO ITS PLAN WILL LIE ABOUT THIS FAMILY.
|
|
34
|
+
* MEASURED: its `COLLECTIONS` list is `cluster/sdn/zones` and `cluster/sdn/vnets`, while
|
|
35
|
+
* `PVE::Network::SDN::pending_config` keys the pending view on a SEPARATE `subnets` section of
|
|
36
|
+
* the running config. A run that stages only a subnet therefore leaves both counted collections
|
|
37
|
+
* at zero, the apply diffs as `noop`, and the subnet never reaches a node. It is NOT fixable by
|
|
38
|
+
* adding a string to that list: the subnet index is PER VNET
|
|
39
|
+
* (`cluster/sdn/vnets/{vnet}/subnets?pending=1`), so SdnApply needs the vnet names before it can
|
|
40
|
+
* count them. Until that lands, a subnet-only change needs `pvesh set /cluster/sdn` by hand.
|
|
41
|
+
*
|
|
42
|
+
* ★ THE READ LANE IS ENOUGH HERE, AND THAT IS A REAL DIFFERENCE FROM BOTH SIBLINGS. The zone and
|
|
43
|
+
* vnet ITEM reads are gated on SDN.Allocate, which is why each sets `readRole: 'provision'`. This
|
|
44
|
+
* one is not. MEASURED from the cluster's own schema on 2026-09-13:
|
|
45
|
+
*
|
|
46
|
+
* GET /cluster/sdn/vnets/{vnet}/subnets/{subnet}
|
|
47
|
+
* -> "Require 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'"
|
|
48
|
+
*
|
|
49
|
+
* and `GET /access/acl` the same day shows `hf-read@pve` holding the built-in PVEAuditor — whose
|
|
50
|
+
* seven privileges include SDN.Audit — at `/` with propagate=1. So the default 3600s auditor
|
|
51
|
+
* lease reads this family, and a `readRole: 'provision'` here would be privilege nobody needed.
|
|
52
|
+
* Writes are a different answer: POST, PUT and DELETE all require SDN.Allocate on
|
|
53
|
+
* `/sdn/zones/<zone>/<vnet>`, which `LXCProvisioner` gained on 2026-09-13.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ NO LIVE SUBNET EXISTS ON TB4 TO ROUND-TRIP AGAINST — there are no SDN zones and no vnets, so
|
|
56
|
+
* there can be no subnets. Every claim above is read off PVE 9.2.11's own schema and Perl sources
|
|
57
|
+
* on n2, and the noop argument below rests on those rather than on a plan this file has watched.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ TWO REFUSALS TO EXPECT, both from `SubnetPlugin::on_update_hook`: PVE will not put a subnet on
|
|
60
|
+
* a vnet with `vlanaware` set ("you can't add a subnet on a vlanaware vnet"), and it rejects a
|
|
61
|
+
* gateway outside the CIDR unless the mask is /32. Both are create-time 400s, not drift.
|
|
62
|
+
*/
|
|
63
|
+
import { Resource } from 'alchemy';
|
|
64
|
+
import * as Provider from 'alchemy/Provider';
|
|
65
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
66
|
+
import { type DhcpRange } from './sdn-subnet-form.ts';
|
|
67
|
+
export interface SdnSubnetProps extends WithTarget {
|
|
68
|
+
/**
|
|
69
|
+
* The vnet whose bridge carries these addresses. Identity AND a written field.
|
|
70
|
+
*
|
|
71
|
+
* ⛔ MOVING A SUBNET BETWEEN VNETS IS NOT SOMETHING THIS RESOURCE CAN DO, and the symptom is
|
|
72
|
+
* worth knowing before you meet it. The id is built from the zone and the CIDR, so it does not
|
|
73
|
+
* change; but the read goes to `.../vnets/<declared>/subnets/<id>`, and PVE answers a subnet
|
|
74
|
+
* filed under another vnet with `raise_param_exc({ vnet => "wrong vnet" })`. `read` folds that
|
|
75
|
+
* into "absent", reconcile POSTs, and PVE refuses with "sdn subnet object ID ... already
|
|
76
|
+
* defined". Loud and accurate, but it does not converge: change the vnet by removing the
|
|
77
|
+
* declaration and writing a new one.
|
|
78
|
+
*/
|
|
79
|
+
vnet: string;
|
|
80
|
+
/**
|
|
81
|
+
* The zone that vnet lives in. ⚠️ PATH INPUT ONLY — PVE accepts no `zone` on a subnet write, and
|
|
82
|
+
* nothing here sends one. It is a prop because the path cannot be spelled without it.
|
|
83
|
+
*/
|
|
84
|
+
zone: string;
|
|
85
|
+
/** `10.0.0.0/24`. Identity: PVE builds the id from it, so a new CIDR is a new subnet. */
|
|
86
|
+
cidr: string;
|
|
87
|
+
/** Handed to guests as their default route, and registered in the zone's IPAM as `<vnet>-gw`. */
|
|
88
|
+
gateway?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Masquerade this subnet. ⚠️ INERT UNTIL THE CLUSTER FIREWALL IS ON — PVE's own description is
|
|
91
|
+
* "enable masquerade for this subnet if pve-firewall", and TB4's cluster firewall was empty and
|
|
92
|
+
* disabled when this was written. It still round-trips, so declaring it does not diff.
|
|
93
|
+
*/
|
|
94
|
+
snat?: boolean;
|
|
95
|
+
/** `adm` -> `<hostname>.adm.example.internal` for records the zone's DNS plugin registers. */
|
|
96
|
+
dnszoneprefix?: string;
|
|
97
|
+
/** ⚠️ WIRE NAME `dhcp-dns-server`; see the ⚠️ on `isolatePorts` in sdn-vnet.ts about mappings. */
|
|
98
|
+
dhcpDnsServer?: string;
|
|
99
|
+
/**
|
|
100
|
+
* ⛔ ONE POOL, NOT A LIST, AND THE LIMIT IS THE CLIENT'S RATHER THAN PVE'S. `dhcp-range` is a PVE
|
|
101
|
+
* `type => 'array'` parameter, and an array reaches a form-encoded body only as a REPEATED KEY
|
|
102
|
+
* — which `client.ts` cannot express, because `pve()` takes `Record<string, string>` and one
|
|
103
|
+
* key holds one value. A single occurrence works today because `PVE::RESTHandler`'s
|
|
104
|
+
* `$normalize_legacy_param_formats` wraps a scalar into `[$value]` for an array parameter, so
|
|
105
|
+
* the type is declared as one range and a second one is unspellable rather than silently lost.
|
|
106
|
+
* ⛔ THAT COERCION IS MARKED FOR REMOVAL. Its own comment in RESTHandler.pm reads "mark the
|
|
107
|
+
* behaviour deprecated with 9.x, and remove it with 10.x", and this cluster runs 9.2.11 — so on
|
|
108
|
+
* PVE 10 every write carrying a range becomes a hard 400. The fix is not here: it is a form
|
|
109
|
+
* type in client.ts that admits repeated keys.
|
|
110
|
+
* ⚠️ A LIVE SUBNET CARRYING TWO POOLS IS THEREFORE NARROWED TO ONE by the first deploy that
|
|
111
|
+
* declares one. `matches` compares the WHOLE live list, so the narrowing is visible in the plan
|
|
112
|
+
* as an update rather than happening quietly, and it converges.
|
|
113
|
+
*/
|
|
114
|
+
dhcpRange?: DhcpRange;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* ⛔ NO `digest` HERE, for the reason sdn-zone.ts gives: PVE returns the digest of the WHOLE
|
|
118
|
+
* subnets.cfg, so declaring a SECOND subnet would rewrite this one's stored attributes and
|
|
119
|
+
* comparing it would report an update on a subnet nobody touched.
|
|
120
|
+
*/
|
|
121
|
+
export interface SdnSubnetAttributes {
|
|
122
|
+
/** `<zone>-<network>-<mask>` — the path segment, and the name to use in `pvesh`. */
|
|
123
|
+
subnetId: string;
|
|
124
|
+
vnet: string;
|
|
125
|
+
zone: string;
|
|
126
|
+
/** `10.0.0.0/24`, as PVE reassembles it from the id. */
|
|
127
|
+
cidr: string;
|
|
128
|
+
/** Prefix length, so a guest's `ip=10.0.0.5/24` can be built from a reference. */
|
|
129
|
+
mask: number;
|
|
130
|
+
gateway: string;
|
|
131
|
+
snat: boolean;
|
|
132
|
+
dnszoneprefix: string;
|
|
133
|
+
dhcpDnsServer: string;
|
|
134
|
+
/** Normalised and sorted — never compare a raw PVE value to it. See `dhcpRanges`. */
|
|
135
|
+
dhcpRange: string;
|
|
136
|
+
/** ⚠️ THE REMINDER IN THE STATE ITSELF: staged config. No value here says a node has the subnet. */
|
|
137
|
+
readonly staged: true;
|
|
138
|
+
}
|
|
139
|
+
export interface ProxmoxSdnSubnet extends Resource<'Proxmox.SdnSubnet', SdnSubnetProps, SdnSubnetAttributes, never, PveRequirements> {
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* ⚠️ NO `defaultRemovalPolicy: 'retain'`, UNLIKE THE SEVEN FAMILIES THAT CARRY IT, AND THE ★ IN
|
|
143
|
+
* resource.ts IS THE TEST IT FAILS: a subnet's whole content is a CIDR, a gateway and a pool,
|
|
144
|
+
* which a line of TypeScript rebuilds exactly. The part that is NOT rebuildable is the guest
|
|
145
|
+
* addresses the zone's IPAM handed out, and PVE guards that itself — MEASURED in the built-in
|
|
146
|
+
* `pve` IPAM plugin, which answers a delete with "cannot delete subnet '<cidr>', not empty"
|
|
147
|
+
* whenever anything beyond the gateway still holds an address. That refusal is surfaced as-is.
|
|
148
|
+
*/
|
|
149
|
+
export declare const ProxmoxSdnSubnet: import("alchemy").ResourceClass<ProxmoxSdnSubnet>;
|
|
150
|
+
/**
|
|
151
|
+
* ⛔ `list` IS EMPTY LIKE EVERY OTHER RESOURCE HERE. `GET /cluster/sdn/vnets/{vnet}/subnets` answers
|
|
152
|
+
* with every subnet on that bridge, guest addresses and all; adopting one would invite Alchemy to
|
|
153
|
+
* narrow or delete a network nobody declared. Adoption stays an explicit act.
|
|
154
|
+
*
|
|
155
|
+
* ⛔ AND A DELETE IS STAGED TOO, WHICH IS THE HALF PEOPLE FORGET. The section leaves subnets.cfg and
|
|
156
|
+
* Alchemy drops the resource, but the gateway stays on the bridge and DHCP keeps answering until
|
|
157
|
+
* something applies `PUT /cluster/sdn`: a destroy nobody applies reads as complete in the plan and
|
|
158
|
+
* has not happened on the cluster.
|
|
159
|
+
*/
|
|
160
|
+
export declare const ProxmoxSdnSubnetProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxSdnSubnet>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
161
|
+
//# sourceMappingURL=sdn-subnet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdn-subnet.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-subnet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,sBAAsB,CAAC;AAG9B,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD;;;;;;;;;;OAUG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8FAA8F;IAC9F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kGAAkG;IAClG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAiB,SAAQ,QAAQ,CAChD,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAAkD,CAAC;AA4DhF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,sIACsD,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.SdnVnet` — the bridge a guest actually attaches to.
|
|
3
|
+
*
|
|
4
|
+
* ★ THIS IS THE OBJECT THAT CLOSES THE GAP BETWEEN A DECLARED CONTAINER AND A DECLARED NETWORK.
|
|
5
|
+
* `Proxmox.Lxc` takes `net0: name=eth0,bridge=vmbr0,ip=dhcp`, in which the bridge is a string
|
|
6
|
+
* nothing checks. A vnet's id IS the bridge PVE builds on every node of its zone, so declaring
|
|
7
|
+
* the vnet and passing its `bridge` attribute into `net0` turns that string into a reference
|
|
8
|
+
* Alchemy can order and a plan can show.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ MEASURED: CREATING A VNET CHECKS PERMISSION AT `/sdn/zones/-`, NOT AT THE VNET. The ACL path
|
|
11
|
+
* is the ZONE's, so a vnet whose zone does not exist yet fails as a PERMISSION error rather than
|
|
12
|
+
* as "no such zone" — and you go off widening a role that was never the problem. `zone` is
|
|
13
|
+
* therefore a hard dependency edge, not a convention: pass the zone resource's own attribute so
|
|
14
|
+
* Alchemy creates the zone first. A zone name typed in as a literal buys no ordering at all.
|
|
15
|
+
*
|
|
16
|
+
* ⛔ THE READ-BACK AFTER A WRITE PROVES THE STAGING FILE AND NOTHING MORE — the same trap as
|
|
17
|
+
* `sdn-zone`, and it walks straight through the factory's guard. PVE's SDN endpoints write
|
|
18
|
+
* `/etc/pve/sdn/vnets.cfg`; nothing reaches a node's `/etc/network/interfaces` until
|
|
19
|
+
* `PUT /cluster/sdn` applies the staged config. `pveOperations.reconcile` reads back and refuses
|
|
20
|
+
* when an object is absent, and a vnet that exists only on paper SATISFIES that read: reconcile
|
|
21
|
+
* reports success, the next plan says noop, and no bridge exists anywhere on the cluster.
|
|
22
|
+
* This resource does NOT issue the apply, deliberately. One vnet's reconcile cannot decide to
|
|
23
|
+
* commit every other pending SDN change in the cluster, including ones a human staged in the UI
|
|
24
|
+
* and has not finished. Apply stays an operator's act — `pvesh set /cluster/sdn`.
|
|
25
|
+
*
|
|
26
|
+
* ★ SUBNETS ARE A SIBLING RESOURCE, NOT A FIELD HERE. The gateway and the DHCP ranges guests take
|
|
27
|
+
* addresses from hang off `cluster/sdn/vnets/{vnet}/subnets`, which MEASURED refuses a POST that
|
|
28
|
+
* omits `type` or `subnet` ("parameter verification failed"). Same factory, one path deeper, and
|
|
29
|
+
* it depends on this resource the way this one depends on the zone.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ read/diff NEED SDN.Allocate TOO, NOT SDN.Audit — corrected 2026-09-13 against the cluster's
|
|
32
|
+
* schema. `GET /cluster/sdn/vnets/{vnet}` is checked against SDN.Allocate exactly as the zone
|
|
33
|
+
* item endpoint is, while only the COLLECTION read accepts SDN.Audit. An auditor-shaped `read`
|
|
34
|
+
* lease is therefore refused, and `pveOperations.read` turns that refusal into "absent" rather
|
|
35
|
+
* than an error — see the ⛔ in sdn-zone.ts and docs/privileges.md.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ PRIVILEGES, AND THE ESTATE'S CREDENTIAL HELD NONE OF THEM UNTIL 2026-09-13. reconcile and
|
|
38
|
+
* delete mint `provision` and need SDN.Allocate on `/sdn/zones`, or on the one zone if scoped. `LXCProvisioner` grants
|
|
39
|
+
* SDN.Use, which is what a GUEST needs in order to attach to a vnet and NOT what creating one
|
|
40
|
+
* needs, so a container declared on this vnet works today while the vnet itself 403s. Widen
|
|
41
|
+
* deliberately, the way Pool.Allocate was:
|
|
42
|
+
* `pveum role modify LXCProvisioner --privs "<existing>,SDN.Allocate,SDN.Audit"` over SSH,
|
|
43
|
+
* preserving every existing privilege.
|
|
44
|
+
*/
|
|
45
|
+
import { Resource } from 'alchemy';
|
|
46
|
+
import * as Provider from 'alchemy/Provider';
|
|
47
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
48
|
+
export interface SdnVnetProps extends WithTarget {
|
|
49
|
+
/**
|
|
50
|
+
* ⛔ THE PRIMARY KEY AND THE BRIDGE NAME AT ONCE. PVE caps it at eight alphanumeric characters
|
|
51
|
+
* because it becomes a real interface on every node in the zone. There is no rename: a new id
|
|
52
|
+
* is a new bridge, and every guest whose `net0` still names the old one stays on the old one.
|
|
53
|
+
*/
|
|
54
|
+
vnet: string;
|
|
55
|
+
/**
|
|
56
|
+
* The SDN zone that owns it. Mutable — a vnet can be moved between zones — but see the ⚠️ at the
|
|
57
|
+
* top of this file: the zone has to exist first, and passing a zone resource's attribute instead
|
|
58
|
+
* of a literal is the only thing that makes Alchemy order them.
|
|
59
|
+
*/
|
|
60
|
+
zone: string;
|
|
61
|
+
/** Free text shown in the UI. Mutable; clear it by dropping the prop, never by passing `''`. */
|
|
62
|
+
alias?: string;
|
|
63
|
+
/** VLAN id or VXLAN VNI, depending on the zone's type. Mutable. Absent means untagged. */
|
|
64
|
+
tag?: number;
|
|
65
|
+
/** Let guests on this bridge carry their own VLAN tags. Mutable. */
|
|
66
|
+
vlanaware?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Stop guests on this bridge from reaching each other directly. Mutable.
|
|
69
|
+
* ⚠️ The wire name is `isolate-ports`, with a hyphen — see the ⚠️ on `shape` for why that costs
|
|
70
|
+
* attention in two places rather than one.
|
|
71
|
+
*/
|
|
72
|
+
isolatePorts?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface SdnVnetAttributes {
|
|
75
|
+
vnet: string;
|
|
76
|
+
/**
|
|
77
|
+
* The same string as `vnet`, duplicated on purpose: it is the name a guest's `net0` must carry.
|
|
78
|
+
* Referencing it from a `Proxmox.Lxc`'s props is what makes Alchemy build the network before the
|
|
79
|
+
* container. A hardcoded `vmbr1` in `net0` is a string nothing checks and nothing orders.
|
|
80
|
+
*/
|
|
81
|
+
bridge: string;
|
|
82
|
+
zone: string;
|
|
83
|
+
alias: string;
|
|
84
|
+
/**
|
|
85
|
+
* Absent when the vnet is untagged — see `tagOf` for why this is optional and not `0`.
|
|
86
|
+
* ⚠️ `| undefined` IS LOAD-BEARING under exactOptionalPropertyTypes: the attributes
|
|
87
|
+
* builder always sets this key, writing `undefined` for an untagged vnet, so the type
|
|
88
|
+
* must permit a present-but-undefined value as well as an absent one. Without it the
|
|
89
|
+
* read path does not typecheck, and 0 is a real VLAN id so it cannot be the sentinel.
|
|
90
|
+
*/
|
|
91
|
+
tag?: number | undefined;
|
|
92
|
+
vlanaware: boolean;
|
|
93
|
+
isolatePorts: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface ProxmoxSdnVnet extends Resource<'Proxmox.SdnVnet', SdnVnetProps, SdnVnetAttributes, never, PveRequirements> {
|
|
96
|
+
}
|
|
97
|
+
export declare const ProxmoxSdnVnet: import("alchemy").ResourceClass<ProxmoxSdnVnet>;
|
|
98
|
+
/**
|
|
99
|
+
* ⛔ EMPTY, LIKE EVERY OTHER RESOURCE IN THIS PACKAGE. `GET /cluster/sdn/vnets` answers
|
|
100
|
+
* with every vnet on the cluster, and on an estate already running SDN those are
|
|
101
|
+
* load-bearing bridges with guests on them. Returning them would invite Alchemy to
|
|
102
|
+
* adopt — and therefore one day delete — a network nobody declared here. Adoption is an
|
|
103
|
+
* explicit act.
|
|
104
|
+
|
|
105
|
+
*
|
|
106
|
+
* ⛔ A DELETE IS STAGED LIKE EVERY OTHER SDN WRITE. The vnet leaves `vnets.cfg` at once and
|
|
107
|
+
* the bridge stays up on every node until `PUT /cluster/sdn` applies the removal, so a
|
|
108
|
+
* plan reporting "1 deleted" has taken nothing off the network yet. Nothing here applies
|
|
109
|
+
* on the operator's behalf: see the ⛔ at the top of this file.
|
|
110
|
+
|
|
111
|
+
*/
|
|
112
|
+
export declare const ProxmoxSdnVnetProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxSdnVnet>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
113
|
+
//# sourceMappingURL=sdn-vnet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdn-vnet.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-vnet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,cAAc,iDAA8C,CAAC;AAyG1E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,oIACoD,CAAC"}
|