@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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.NetworkApply` — the call that makes a declared interfaces file real, ON ONE NODE.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ WITHOUT IT EVERY NETWORK DECLARATION IS A GREEN PLAN THAT CHANGES NO NETWORK; WITH IT A GREEN
|
|
5
|
+
* PLAN CAN TAKE A NODE OFF THE NETWORK. Writes under `nodes/{node}/network/{iface}` edit
|
|
6
|
+
* `/etc/network/interfaces.new` and touch no interface. `PUT /nodes/{node}/network` is the apply.
|
|
7
|
+
* MEASURED in the cluster's own source on n2, 2026-09-13 — `PVE/API2/Network.pm`:
|
|
8
|
+
* rename($new_config_file, $current_config_file) if -e $new_config_file;
|
|
9
|
+
* PVE::Tools::run_command(['ifreload', '-a'], errfunc => $err);
|
|
10
|
+
* return $rpcenv->fork_worker('srvreload', 'networking', $authuser, $worker);
|
|
11
|
+
*
|
|
12
|
+
* ⛔ THIS IS THE MOST DANGEROUS RESOURCE IN THE PACKAGE, AND ON TB4 THE DANGER IS NOT ABSTRACT.
|
|
13
|
+
* Ceph runs over `vmbr1.11` on n2, n3 AND n4 (MEASURED 2026-09-13: four pools, size 3 /
|
|
14
|
+
* min_size 2, mon+mgr+mds on all three). `ifreload -a` takes that node's OSDs, mons and MDS off
|
|
15
|
+
* the cluster network for the length of the reload; two nodes at once puts every pool below
|
|
16
|
+
* min_size and blocks IO for every guest with an RBD disk. The management address the API is
|
|
17
|
+
* reached on, `vmbr0.10`, is reloaded by the same call.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ AND UNLIKE `SdnApply` IT IS PER NODE, SO A HALF-RECONFIGURED CLUSTER IS THE DEFAULT FAILURE
|
|
20
|
+
* MODE, NOT AN EDGE CASE. `PUT /cluster/sdn` publishes the whole cluster in one call; this
|
|
21
|
+
* publishes exactly one node, so a stack holding three of these can apply n2, fail, and leave n3
|
|
22
|
+
* and n4 on the old layout with Ceph spanning all three. Four things here exist only for that:
|
|
23
|
+
* 1. `after` SERIALISES THEM — chain n3 after n2 and n4 after n3, so at most one node is ever
|
|
24
|
+
* mid-reload. Three declared in parallel is the outage, and nothing in Alchemy stops you.
|
|
25
|
+
* 2. `reconcile` REFUSES TO START on a cluster that is already degraded.
|
|
26
|
+
* 3. `reconcile` RE-CHECKS quorum and every member's `online` flag AFTER the reload and dies if
|
|
27
|
+
* the cluster came back worse — which stops the chain before it reaches the second node.
|
|
28
|
+
* 4. NOTHING IS EVER APPLIED WITH NOTHING STAGED. See the first branch of `apply`.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ IF THE MEMBER SERVING THE POLL IS THE NODE BEING RELOADED, the reload can drop the connection
|
|
31
|
+
* carrying its own status poll — see network-apply-read.ts. Every endpoint is `proxyto =>
|
|
32
|
+
* 'node'`, so any cluster member can serve any other node's paths; failover in client.ts picks
|
|
33
|
+
* whichever member is up, not necessarily one you are not applying.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ `DELETE /nodes/{node}/network` IS THE REVERT, AND IT IS DELIBERATELY NOT WIRED TO `delete`.
|
|
36
|
+
* MEASURED: the whole body of `revert_network_changes` is `unlink "/etc/network/interfaces.new"`.
|
|
37
|
+
* It discards STAGED edits and cannot undo an APPLIED one, so pointing `delete` at it would throw
|
|
38
|
+
* away whatever happened to be staged the moment somebody removed a line from a stack file — the
|
|
39
|
+
* worst possible time to do it. Revert by hand, deliberately: `pvesh delete /nodes/<node>/network`.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ PRIVILEGES, so widening stays a deliberate act: the `read` role needs nothing for the pending
|
|
42
|
+
* read (`GET /nodes/{node}/network` is `"user": "all"`) and `Sys.Audit` on `/` for the health
|
|
43
|
+
* gate; the `provision` role needs `Sys.Modify` on `/nodes/{node}` for the apply, and owns the
|
|
44
|
+
* task it starts so it can poll it without `Sys.Audit`. ⛔ `Sys.Modify` ON A NODE IS NOT A SMALL
|
|
45
|
+
* GRANT — it also carries that node's DNS, hosts file, time and service configuration. Granting
|
|
46
|
+
* it cluster-wide to run this resource widens the provisioning role well beyond networking.
|
|
47
|
+
*/
|
|
48
|
+
import { Resource } from 'alchemy';
|
|
49
|
+
import { isResolved } from 'alchemy/Diff';
|
|
50
|
+
import type { Input } from 'alchemy/Input';
|
|
51
|
+
import * as Provider from 'alchemy/Provider';
|
|
52
|
+
import * as Effect from 'effect/Effect';
|
|
53
|
+
import { pveWith } from './client.ts';
|
|
54
|
+
import { mint } from './credentials.ts';
|
|
55
|
+
import { awaitTask, degradedReason, pendingCount } from './network-apply-read.ts';
|
|
56
|
+
import type { PveRequirements } from './resource.ts';
|
|
57
|
+
import type { WithTarget } from './resource.ts';
|
|
58
|
+
|
|
59
|
+
export interface NetworkApplyProps extends WithTarget {
|
|
60
|
+
/**
|
|
61
|
+
* The node whose staged interfaces file this applies — `n2`.
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ ONE APPLY PER NODE, AND NEVER TWO FOR THE SAME NODE. Two would each publish the other's
|
|
64
|
+
* staged half, and both would run `ifreload` on a file the other had not finished writing.
|
|
65
|
+
*/
|
|
66
|
+
node: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The staged interface changes this apply publishes, and the apply that must go before it.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ IT IS NEVER READ, AND IT IS NOT DECORATION — the same contract as `SdnApply.after`. Alchemy
|
|
72
|
+
* orders resources by DATA FLOW, so consuming an attribute is the only way to say "after".
|
|
73
|
+
* Pass the interfaces this node declares AND the previous node's apply:
|
|
74
|
+
* `after: [vmbr1.iface, n2Apply.pending]`. Leave the previous apply out and the three nodes
|
|
75
|
+
* reload in parallel, which is the one thing this resource is built to prevent.
|
|
76
|
+
*/
|
|
77
|
+
after?: readonly unknown[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface NetworkApplyAttributes {
|
|
81
|
+
/** The node this applies. Identity, echoed so a plan line says which member it will reload. */
|
|
82
|
+
node: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Changed lines still staged on this node.
|
|
86
|
+
*
|
|
87
|
+
* ⚠️ LINES OF A UNIFIED DIFF, NOT INTERFACES, and the number is reported rather than trusted —
|
|
88
|
+
* `pendingLines` explains why it can undercount by one and never to zero. Zero is the only
|
|
89
|
+
* settled value: anything above it means the running config and the staged config disagree,
|
|
90
|
+
* whether this stack staged it or somebody edited the node in the UI and walked away.
|
|
91
|
+
*
|
|
92
|
+
* ⛔ THE DIFF ITSELF IS NEVER AN ATTRIBUTE. Alchemy writes attributes to its state store
|
|
93
|
+
* UNENCRYPTED, and `/etc/network/interfaces` can carry a `wpa-psk` or a `pre-up` command. Only
|
|
94
|
+
* the count leaves `network-apply-read.ts`.
|
|
95
|
+
*
|
|
96
|
+
* ⚠️ AND NOTHING OUT OF THE INTERFACE ROWS IS AN ATTRIBUTE EITHER, WHICH IS WHAT KEEPS THIS
|
|
97
|
+
* FAMILY NOOP. `GET /nodes/{node}/network` returns eighteen fields it will not accept on write
|
|
98
|
+
* — MEASURED by differencing the schema's GET return properties against its POST parameters:
|
|
99
|
+
* `active`, `exists`, `families`, `method`, `method6`, `options`, `priority`, `link-type` and
|
|
100
|
+
* ten more; `altnames` is returned and is not even in the schema. `priority` is the worst of
|
|
101
|
+
* them: PVE ASSIGNS it from the order of the interfaces file, so the same logical layout
|
|
102
|
+
* carries different numbers on different nodes (MEASURED: n4's differ from n2/n3's by one).
|
|
103
|
+
* Every one of those is a forever-diff waiting for whoever writes `Proxmox.NetworkInterface`.
|
|
104
|
+
* This resource compares a single integer against zero and never looks at a row at all.
|
|
105
|
+
*/
|
|
106
|
+
pending: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ProxmoxNetworkApply extends Resource<
|
|
110
|
+
'Proxmox.NetworkApply',
|
|
111
|
+
NetworkApplyProps,
|
|
112
|
+
NetworkApplyAttributes,
|
|
113
|
+
never,
|
|
114
|
+
PveRequirements
|
|
115
|
+
> {}
|
|
116
|
+
|
|
117
|
+
export const ProxmoxNetworkApply = Resource<ProxmoxNetworkApply>('Proxmox.NetworkApply');
|
|
118
|
+
|
|
119
|
+
const read = (props: NetworkApplyProps) =>
|
|
120
|
+
pendingCount(props.target, props.node).pipe(
|
|
121
|
+
Effect.map((pending) => ({ node: props.node, pending })),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* ⚠️ THE DIFF IS ABOUT THE NODE, NOT ABOUT THE PROPS — the same reasoning as `SdnApply`. This
|
|
126
|
+
* resource has no settable field (`node` is identity, `after` is an ordering edge), so comparing
|
|
127
|
+
* props to props would report `noop` forever and the apply would run exactly once, ever. What
|
|
128
|
+
* decides it is whether the node is carrying a staged change right now.
|
|
129
|
+
*
|
|
130
|
+
* ★ THIS IS ALSO WHY DECLARING THE LIVE CLUSTER PLANS AS `noop`. VERIFIED 2026-09-13:
|
|
131
|
+
* `/etc/network/interfaces.new` is absent on n2, n3 and n4, so `changes` is absent from all three
|
|
132
|
+
* answers, so `pending` is 0 on all three and every one of them plans `noop`.
|
|
133
|
+
*/
|
|
134
|
+
const diff = (news: Input<NetworkApplyProps>) =>
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
if (!isResolved(news)) return undefined;
|
|
137
|
+
const { pending } = yield* read(news);
|
|
138
|
+
return pending === 0 ? ({ action: 'noop' } as const) : ({ action: 'update' } as const);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/** Stop the deploy, loudly, with the node in the message. ⚠️ A die here is the SAFE outcome. */
|
|
142
|
+
const refuse = (node: string, why: string) =>
|
|
143
|
+
Effect.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
|
|
144
|
+
|
|
145
|
+
const apply = (props: NetworkApplyProps) =>
|
|
146
|
+
Effect.gen(function* () {
|
|
147
|
+
const node = props.node;
|
|
148
|
+
const staged = yield* pendingCount(props.target, node);
|
|
149
|
+
/**
|
|
150
|
+
* ⛔ NOTHING STAGED MEANS NOTHING TO APPLY, AND THIS BRANCH IS A SAFETY PROPERTY RATHER THAN AN
|
|
151
|
+
* OPTIMISATION. `reconcile` also runs on CREATE — the first time this resource appears in a
|
|
152
|
+
* stack, before `diff` has ever been consulted — and the PVE worker runs `ifreload -a`
|
|
153
|
+
* UNCONDITIONALLY, staged file or not. Without this branch, adding `Proxmox.NetworkApply` to
|
|
154
|
+
* a stack describing the cluster as it already is would reload networking on every node it
|
|
155
|
+
* names, for nothing. Adoption must cost nothing; that is true here in the strongest sense.
|
|
156
|
+
*/
|
|
157
|
+
if (staged === 0) return { node, pending: 0 };
|
|
158
|
+
|
|
159
|
+
const before = yield* degradedReason(props.target);
|
|
160
|
+
if (before !== undefined) {
|
|
161
|
+
return yield* refuse(
|
|
162
|
+
node,
|
|
163
|
+
`refusing to reload networking on a cluster that is already degraded (${before}). ` +
|
|
164
|
+
'Ceph spans these nodes -- fix the cluster first, then re-run the deploy.',
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ⚠️ ONE LEASE FOR BOTH CALLS, VIA `pveWith` RATHER THAN `pve`. Each mint returns a NEW token
|
|
170
|
+
* id, and the task-status endpoint only skips its `Sys.Audit` check for the task's OWNER —
|
|
171
|
+
* so polling with a fresh mint is a different identity and needs a privilege this role does
|
|
172
|
+
* not have. The `provision` lease is 300s and non-renewable, which is far longer than a
|
|
173
|
+
* reload and is the reason no renew path is reached for.
|
|
174
|
+
*/
|
|
175
|
+
const credential = yield* mint(props.target, 'provision');
|
|
176
|
+
const upid = yield* pveWith<string>(props.target, credential, 'PUT', `nodes/${node}/network`);
|
|
177
|
+
if (upid === undefined) {
|
|
178
|
+
return yield* refuse(
|
|
179
|
+
node,
|
|
180
|
+
'the apply returned no UPID. PVE wraps every answer in {"data":...} and can report success ' +
|
|
181
|
+
'on a call that did nothing -- the reload cannot be confirmed, so it is not claimed.',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ⛔ POLLING `pending` INSTEAD OF THE TASK WOULD REPORT SUCCESS BEFORE THE NETWORK WAS TOUCHED.
|
|
187
|
+
* MEASURED in the worker above: the `rename` happens FIRST and `ifreload -a` second, so the
|
|
188
|
+
* staged file — and therefore `changes`, and therefore `pending` — disappears while the
|
|
189
|
+
* reload has not yet started. A provider that watched the count would call a reload that had
|
|
190
|
+
* not run, and a reload that then FAILED, a success. The task's exitstatus is the only
|
|
191
|
+
* honest answer.
|
|
192
|
+
*/
|
|
193
|
+
const exitstatus = yield* awaitTask(props.target, credential, node, upid);
|
|
194
|
+
if (exitstatus !== 'OK') {
|
|
195
|
+
return yield* refuse(
|
|
196
|
+
node,
|
|
197
|
+
`the reload ended "${exitstatus}" (${upid}). Read it from ANOTHER node -- ` +
|
|
198
|
+
`\`pvesh get /nodes/${node}/tasks/${upid}/log\` -- and do not apply the next node until ` +
|
|
199
|
+
'this one is understood.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const after = yield* degradedReason(props.target);
|
|
204
|
+
if (after !== undefined) {
|
|
205
|
+
return yield* refuse(
|
|
206
|
+
node,
|
|
207
|
+
`the reload reported OK but the cluster is now degraded (${after}). The next ` +
|
|
208
|
+
'node has NOT been applied. Restore this one before continuing -- Ceph runs over these ' +
|
|
209
|
+
'links and a second reload now would take pools below min_size.',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const left = yield* pendingCount(props.target, node);
|
|
214
|
+
if (left !== 0) {
|
|
215
|
+
return yield* refuse(
|
|
216
|
+
node,
|
|
217
|
+
`the reload reported OK but ${String(left)} staged line(s) remain. The running config does ` +
|
|
218
|
+
'not match the declared one, and saying otherwise is the lie this resource exists to stop.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return { node, pending: left };
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* ⛔ HAND-WRITTEN RATHER THAN `pveHandlers`, FOR THE SAME REASON `sdn-apply.ts` IS. The factory's
|
|
226
|
+
* spec is create/update/delete over ONE object at ONE path, and an apply is not an object: there
|
|
227
|
+
* is nothing to POST, nothing to DELETE, and `matches` would compare a declaration to itself and
|
|
228
|
+
* answer `noop` forever, so the apply would run exactly once in the life of the stack. `list` is
|
|
229
|
+
* empty for the reason it is everywhere in this package — adoption stays an explicit act.
|
|
230
|
+
*
|
|
231
|
+
* ⛔ `delete` APPLIES NOTHING AND UNDOES NOTHING, AND THAT IS CORRECT. There is no un-apply: the
|
|
232
|
+
* config is already live on the node, and the way to remove an interface is to delete it and
|
|
233
|
+
* apply AGAIN. See the ⚠️ about `DELETE /nodes/{node}/network` at the top of this file.
|
|
234
|
+
*/
|
|
235
|
+
const handlers = {
|
|
236
|
+
delete: () => Effect.void,
|
|
237
|
+
diff: ({ news }: { news: Input<NetworkApplyProps> }) => diff(news),
|
|
238
|
+
list: () => Effect.succeed([]),
|
|
239
|
+
read: ({ olds }: { olds: NetworkApplyProps }) => read(olds),
|
|
240
|
+
reconcile: ({ news }: { news: NetworkApplyProps }) => apply(news),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const ProxmoxNetworkApplyProvider = () =>
|
|
244
|
+
Provider.effect(ProxmoxNetworkApply, Effect.succeed(ProxmoxNetworkApply.Provider.of(handlers)));
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a node interface crosses the wire in both directions — props to a PVE form, a PVE answer to
|
|
3
|
+
* a comparable attribute — and when two of those values count as the same value.
|
|
4
|
+
*
|
|
5
|
+
* ★ SPLIT OUT OF node-network.ts TO KEEP BOTH FILES UNDER THE 250-LINE CAP, and the seam is the
|
|
6
|
+
* same one metric-server-form.ts uses, one field wider: this file owns the COERCIONS, in both
|
|
7
|
+
* directions, because a read that normalises differently from the write that produced it is
|
|
8
|
+
* exactly how a forever-diff is born — keeping `ifaceList` next to both ends is what stops that.
|
|
9
|
+
* node-network.ts answers "what is an interface, and which of its fields are safe to compare at
|
|
10
|
+
* all". Nothing here calls the cluster.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ THE `import type` BACK TO node-network.ts IS A CYCLE ON PAPER ONLY — type-only, erased before
|
|
13
|
+
* anything runs, so `NodeNetworkProps` stays in the file that declares the resource.
|
|
14
|
+
*
|
|
15
|
+
* * ⛔ THE LAST SEVEN ARE REPORTED AND NEVER COMPARED, AND EACH ONE IS A MEASURED FOREVER-DIFF.
|
|
16
|
+
* `priority` is assigned by PVE FROM FILE ORDER — `my $priority = 2; ... $d->{priority} =
|
|
17
|
+
* $priority++` — and it is not a POST or PUT parameter at all. MEASURED ACROSS TB4: vmbr1.11 is
|
|
18
|
+
* priority 16 on n2 and n3 and 15 on n4, because n4 has no `wlp91s0` above it. One declaration
|
|
19
|
+
* reused across three nodes would diff on exactly one of them, forever, over nothing.
|
|
20
|
+
* `method` is RECOMPUTED on every write from whether the form carried an address, `families` is
|
|
21
|
+
* recomputed the same way and never written to the file, `active` and `exists` describe the
|
|
22
|
+
* kernel rather than the config, and `bond_miimon`, `bridge_stp` and `bridge_fd` are emitted by
|
|
23
|
+
* PVE's writer with defaults (100, `off`, 0) while appearing in NEITHER write schema —
|
|
24
|
+
* `additionalProperties => 0` means sending one is a 400. Live proof of all three: n2's bond0
|
|
25
|
+
* returns `bond_miimon "100"` and vmbr1 returns `bridge_stp "off"`, `bridge_fd "0"`, none of
|
|
26
|
+
* which any declaration can set.
|
|
27
|
+
*/
|
|
28
|
+
import type { NodeNetworkAttributes, NodeNetworkProps } from './node-network.ts';
|
|
29
|
+
import { bool, flag, int, text, withClears } from './values.ts';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* "Not set", for the three integers this family reports.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ 0 CANNOT BE THE ABSENT-MARKER AND -1 CAN. `mtu` is 1280-65520, `vlan-id` 1-4094 and PVE's
|
|
35
|
+
* `priority` starts at 1 (1 is reserved for `lo`), so -1 is outside every one of their ranges
|
|
36
|
+
* while 0 is merely outside today's. metric-server-form.ts keeps its own constant for the same
|
|
37
|
+
* reason and against different ranges; one shared UNSET would have to be right for both.
|
|
38
|
+
*/
|
|
39
|
+
export const UNSET = -1;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A space-separated interface list, flattened to one comparable form.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ `bridge_ports` AND `slaves` ARE SETS, AND PVE NORMALISES THEIR SEPARATORS BUT NOT THEIR ORDER.
|
|
45
|
+
* MEASURED in PVE::Network::Interfaces: the writer does `s/[;,\s]+/ /g` on both before emitting
|
|
46
|
+
* them, and the reader hands the resulting string straight back — so `enp2s0f1np1 enp2s0f0np0`
|
|
47
|
+
* survives a round trip in the order it was written. Order carries no meaning for a bridge's
|
|
48
|
+
* ports or a bond's slaves, so a declaration that lists them the other way round is the SAME
|
|
49
|
+
* declaration; comparing raw strings would report an update that rewrites the file to say
|
|
50
|
+
* exactly what it already said. Both sides are sorted here and only then compared.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ IT IS NOT `csv` FROM values.ts. That one joins with commas, and a comma in
|
|
53
|
+
* `/etc/network/interfaces` is not a separator — PVE's own comment above the writer says the
|
|
54
|
+
* list "must be space separated! other separators will cause problems". Sending a comma-joined
|
|
55
|
+
* value would be accepted by `split_list` and then written back as spaces, which is a second
|
|
56
|
+
* spelling of one value and therefore a second chance to diff.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ `bridge_vids` GOES THROUGH THE SAME FUNNEL and its sort is LEXICAL, which is correct only
|
|
59
|
+
* because a vid list is a set: `2-4094` is one token, and `100-200 2` and `2 100-200` allow the
|
|
60
|
+
* same VLANs. Nothing here should be reused for a list whose order is meaning.
|
|
61
|
+
*/
|
|
62
|
+
export const ifaceList = (value: unknown) =>
|
|
63
|
+
text(value)
|
|
64
|
+
.split(/[\s,;]+/)
|
|
65
|
+
.filter((part) => part !== '')
|
|
66
|
+
.sort()
|
|
67
|
+
.join(' ');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A comment off the wire, with the newline PVE adds taken back off.
|
|
71
|
+
*
|
|
72
|
+
* ⛔ `comments` NEVER ROUND-TRIPS RAW, AND THAT IS A FOREVER-DIFF IF IT IS NOT NORMALISED.
|
|
73
|
+
* MEASURED in the reader: each `#...` line is appended as `$comment . "\n"`, so a declared
|
|
74
|
+
* `ceph transport` comes back as `ceph transport\n` and the two never compare equal.
|
|
75
|
+
* ⚠️ AND THE READER FOLDS `comments6` INTO `comments` (`$d->{comments} .= $comments6`), so on an
|
|
76
|
+
* interface that carries an IPv6 comment a declared `comments` CANNOT match what comes back.
|
|
77
|
+
* TB4 has none; on an interface that does, leave `comments` undeclared rather than fighting it.
|
|
78
|
+
*/
|
|
79
|
+
export const comment = (value: unknown) => text(value).replace(/\s+$/, '');
|
|
80
|
+
|
|
81
|
+
/** Undeclared is unmanaged: neither sent nor compared. See the ⚠️ on `body`. */
|
|
82
|
+
export const same = <T>(declared: T | undefined, live: T) =>
|
|
83
|
+
declared === undefined || declared === live;
|
|
84
|
+
|
|
85
|
+
export const sameList = (declared: string | undefined, live: string) =>
|
|
86
|
+
declared === undefined || ifaceList(declared) === live;
|
|
87
|
+
|
|
88
|
+
export const sameComment = (declared: string | undefined, live: string) =>
|
|
89
|
+
declared === undefined || comment(declared) === live;
|
|
90
|
+
|
|
91
|
+
const field = (name: string, value: string | undefined): Record<string, string> =>
|
|
92
|
+
value === undefined ? {} : { [name]: value };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The fields sent on EVERY write, create and update alike.
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ UNDECLARED IS UNMANAGED — not sent, and not compared either. PVE's PUT MERGES the form into
|
|
98
|
+
* the existing stanza (`foreach my $k (keys %$param) { $ifaces->{$iface}->{$k} = $param->{$k} }`),
|
|
99
|
+
* so an omitted parameter is left exactly as it was. `cidr` is the one exception and it is not
|
|
100
|
+
* a small one — see the ⛔ in node-network.ts.
|
|
101
|
+
*
|
|
102
|
+
* ⛔ `type` IS REQUIRED ON THE PUT, NOT ONLY ON THE POST. It carries no `optional` in either
|
|
103
|
+
* schema, and the merge above writes it straight into the stanza — so a PUT naming the wrong
|
|
104
|
+
* type would RETYPE A LIVE INTERFACE in the file and the writer would then emit a bridge stanza
|
|
105
|
+
* for what is really a vlan. node-network.ts refuses that case before it can reach here.
|
|
106
|
+
*
|
|
107
|
+
* ⛔ `bridge_vlan_aware` IS SENT ONLY WHEN TRUE, AND A FALSE ONE IS A `delete=`. MEASURED in the
|
|
108
|
+
* writer: the test is `if (defined($d->{bridge_vlan_aware}))`, not a truth test, so `0` is
|
|
109
|
+
* defined and PVE writes `bridge-vlan-aware yes` anyway. Sending `0` would therefore turn the
|
|
110
|
+
* flag ON, read back as `1`, and diff forever while lying about which way it went.
|
|
111
|
+
*
|
|
112
|
+
* ⚠️ `netmask` IS A WRITABLE PARAMETER AND IS DELIBERATELY NOT OFFERED. PVE raises
|
|
113
|
+
* "netmask conflicts with cidr" when both are sent, and the reader rewrites whatever was written
|
|
114
|
+
* into prefix form — MEASURED: n2's vmbr1.11 reports `"netmask":"24"`, never `255.255.255.0`.
|
|
115
|
+
* A resource accepting both spellings would let a declaration diff against its own value.
|
|
116
|
+
*/
|
|
117
|
+
export const body = (props: NodeNetworkProps): Record<string, string> => ({
|
|
118
|
+
...field('autostart', flag(props.autostart)),
|
|
119
|
+
...field('bond-primary', props['bond-primary']),
|
|
120
|
+
...field('bond_mode', props.bond_mode),
|
|
121
|
+
...field('bond_xmit_hash_policy', props.bond_xmit_hash_policy),
|
|
122
|
+
...field('bridge_ports', props.bridge_ports),
|
|
123
|
+
...field('bridge_vids', props.bridge_vids),
|
|
124
|
+
...field('bridge_vlan_aware', props.bridge_vlan_aware === true ? '1' : undefined),
|
|
125
|
+
...field('cidr', props.cidr),
|
|
126
|
+
...field('comments', props.comments),
|
|
127
|
+
...field('gateway', props.gateway),
|
|
128
|
+
...field('mtu', props.mtu === undefined ? undefined : String(props.mtu)),
|
|
129
|
+
...field('slaves', props.slaves),
|
|
130
|
+
...field('vlan-id', props['vlan-id'] === undefined ? undefined : String(props['vlan-id'])),
|
|
131
|
+
...field('vlan-raw-device', props['vlan-raw-device']),
|
|
132
|
+
type: props.type,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The update form: `body` plus the two fields that can only be cleared explicitly.
|
|
137
|
+
*
|
|
138
|
+
* ⛔ `delete=cidr` IS SENT WHENEVER `cidr` IS UNDECLARED, AND IT IS THE LESSER OF TWO EVILS RATHER
|
|
139
|
+
* THAN A GOOD OUTCOME. PVE recomputes the method on every write from the form alone —
|
|
140
|
+
* `$param->{method} = $param->{address} ? 'static' : 'manual'` — so a PUT WITHOUT an address
|
|
141
|
+
* makes the interface manual no matter what the caller intended. Left at that, the merge would
|
|
142
|
+
* leave the old `address` and `netmask` in the hash and the writer would emit an `address` line
|
|
143
|
+
* under `iface … inet manual`: a stanza PVE's own UI cannot produce, whose behaviour under
|
|
144
|
+
* `ifreload -a` I did NOT measure. Clearing cidr explicitly at least produces a clean, honest
|
|
145
|
+
* manual interface — and `matches` compares `cidr` UNCONDITIONALLY, so the plan says so first.
|
|
146
|
+
*
|
|
147
|
+
* ⛔ ON vmbr1.11 THAT IS THE CEPH TRANSPORT. A declaration of that interface without its `cidr` is
|
|
148
|
+
* a declaration that it should have no address, applied across three nodes. The plan will read
|
|
149
|
+
* `1 to update` rather than `noop`; do not wave it through.
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ `delete` IS A PUT-ONLY PARAMETER. The POST schema is `additionalProperties => 0` and has no
|
|
152
|
+
* `delete`, so sending it on a create is a 400 — which is why `createForm` calls `body` directly.
|
|
153
|
+
*/
|
|
154
|
+
export const updateBody = (props: NodeNetworkProps): Record<string, string> => {
|
|
155
|
+
const clear = [
|
|
156
|
+
...(props.cidr === undefined ? ['cidr'] : []),
|
|
157
|
+
...(props.bridge_vlan_aware === false ? ['bridge_vlan_aware'] : []),
|
|
158
|
+
];
|
|
159
|
+
const fields = body(props);
|
|
160
|
+
return withClears(fields, clear);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One live interface as attributes, or `undefined` — "this is not really there".
|
|
165
|
+
*
|
|
166
|
+
* ⚠️ EVERY FIELD PVE REPORTS IS CARRIED HERE, INCLUDING THE SEVEN NO DECLARATION CAN SET. A plan
|
|
167
|
+
* that cannot show `priority`, `method` or `bond_miimon` cannot explain why it is ignoring them,
|
|
168
|
+
* and the next person to read a forever-diff would start by adding them to `matches`. They are
|
|
169
|
+
* reported precisely so that they are visibly OUT of it — see the ⛔ above the attribute type.
|
|
170
|
+
*/
|
|
171
|
+
export const readAttributes = (
|
|
172
|
+
live: Record<string, unknown>,
|
|
173
|
+
props: NodeNetworkProps,
|
|
174
|
+
): NodeNetworkAttributes | undefined => {
|
|
175
|
+
/**
|
|
176
|
+
* ⛔ AN INTERFACE OF ANOTHER TYPE IS ANOTHER OBJECT, AND "ABSENT" IS THE SAFE ANSWER — the
|
|
177
|
+
* same call metric-server.ts makes, for a worse reason. `type` is required on the PUT and
|
|
178
|
+
* the PUT MERGES, so a declaration naming `bridge` for what is really a vlan would write
|
|
179
|
+
* `type bridge` into the stanza and PVE's writer would then emit bridge-ports and
|
|
180
|
+
* bridge-stp lines for vmbr1.11. Reporting absent instead makes reconcile POST a create,
|
|
181
|
+
* which PVE refuses with "interface already exists": loud, and it changes nothing.
|
|
182
|
+
*/
|
|
183
|
+
const liveType = text(live['type']);
|
|
184
|
+
if (liveType !== '' && liveType !== props.type) return undefined;
|
|
185
|
+
return {
|
|
186
|
+
active: bool(live['active']),
|
|
187
|
+
autostart: bool(live['autostart']),
|
|
188
|
+
'bond-primary': text(live['bond-primary']),
|
|
189
|
+
bond_miimon: text(live['bond_miimon']),
|
|
190
|
+
bond_mode: text(live['bond_mode']),
|
|
191
|
+
bond_xmit_hash_policy: text(live['bond_xmit_hash_policy']),
|
|
192
|
+
bridge_fd: text(live['bridge_fd']),
|
|
193
|
+
bridge_ports: ifaceList(live['bridge_ports']),
|
|
194
|
+
bridge_stp: text(live['bridge_stp']),
|
|
195
|
+
bridge_vids: ifaceList(live['bridge_vids']),
|
|
196
|
+
bridge_vlan_aware: bool(live['bridge_vlan_aware']),
|
|
197
|
+
/** ⚠️ DERIVED BY THE READER from address+netmask, and always present when an address is. */
|
|
198
|
+
cidr: text(live['cidr']),
|
|
199
|
+
comments: comment(live['comments']),
|
|
200
|
+
exists: bool(live['exists']),
|
|
201
|
+
families: Array.isArray(live['families']) ? live['families'].join(',') : '',
|
|
202
|
+
gateway: text(live['gateway']),
|
|
203
|
+
iface: props.iface,
|
|
204
|
+
method: text(live['method']),
|
|
205
|
+
mtu: int(live['mtu'], UNSET),
|
|
206
|
+
node: props.node,
|
|
207
|
+
priority: int(live['priority'], UNSET),
|
|
208
|
+
slaves: ifaceList(live['slaves']),
|
|
209
|
+
type: liveType === '' ? props.type : liveType,
|
|
210
|
+
'vlan-id': int(live['vlan-id'], UNSET),
|
|
211
|
+
'vlan-raw-device': text(live['vlan-raw-device']),
|
|
212
|
+
};
|
|
213
|
+
};
|