@homeflare/alchemy 0.11.0 → 0.13.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/README.md +3 -3
- package/dist/caddy/admin-guard.d.ts +1 -1
- package/dist/caddy/index.js.map +2 -2
- package/dist/cloudflare/client.d.ts +1 -1
- package/dist/cloudflare/index.js.map +2 -2
- package/dist/cloudflare/lock-rules.d.ts +1 -1
- package/dist/forgejo/branch-protection-form.d.ts +1 -1
- package/dist/forgejo/branch-protection.d.ts +1 -1
- package/dist/forgejo/index.js.map +10 -10
- package/dist/forgejo/org-actions-secrets.d.ts +1 -1
- package/dist/forgejo/org-label.d.ts +1 -1
- package/dist/forgejo/org-team.d.ts +1 -1
- package/dist/forgejo/repo-webhook.d.ts +1 -1
- package/dist/forgejo/repository.d.ts +2 -2
- package/dist/forgejo/repository.d.ts.map +1 -1
- package/dist/forgejo/resource.d.ts +2 -2
- package/dist/forgejo/team-member.d.ts +1 -1
- package/dist/openbao/auth-role.d.ts +1 -1
- package/dist/openbao/bao-http.d.ts +4 -4
- package/dist/openbao/cloudflare-permission-groups.d.ts +1 -1
- package/dist/openbao/cloudflare-role-form.d.ts +2 -2
- package/dist/openbao/cloudflare-role-form.d.ts.map +1 -1
- package/dist/openbao/cloudflare-roles-expand.d.ts +1 -1
- package/dist/openbao/digest.d.ts +1 -1
- package/dist/openbao/forgejo-bootstrap-parse.d.ts +2 -2
- package/dist/openbao/forgejo-bootstrap-parse.d.ts.map +1 -1
- package/dist/openbao/forgejo-bootstrap.d.ts +1 -1
- package/dist/openbao/index.js.map +14 -14
- package/dist/openbao/mount.d.ts +1 -1
- package/dist/openbao/pki-role.d.ts +1 -1
- package/dist/openbao/policy.d.ts +2 -2
- package/dist/openbao/proxmox-role-form.d.ts +2 -2
- package/dist/openbao/proxmox-role-form.d.ts.map +1 -1
- package/dist/openbao/proxmox-role.d.ts +10 -10
- package/dist/openbao/ssh-role.d.ts +3 -3
- package/dist/proxmox/acl.d.ts +2 -2
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/alertmanager-body.d.ts +54 -0
- package/dist/proxmox/alertmanager-body.d.ts.map +1 -0
- package/dist/proxmox/api-token-form.d.ts +2 -2
- package/dist/proxmox/api-token.d.ts +11 -11
- package/dist/proxmox/ceph-daemon-form.d.ts +3 -3
- package/dist/proxmox/ceph-daemon.d.ts +19 -19
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag.d.ts +5 -5
- package/dist/proxmox/ceph-fs-wire.d.ts +5 -5
- package/dist/proxmox/ceph-fs.d.ts +16 -16
- package/dist/proxmox/ceph-osd-tree.d.ts +16 -16
- package/dist/proxmox/ceph-osd.d.ts +5 -5
- package/dist/proxmox/ceph-pool-form.d.ts +13 -13
- package/dist/proxmox/ceph-pool-form.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool.d.ts +3 -3
- package/dist/proxmox/client.d.ts +2 -2
- package/dist/proxmox/credential-literals.d.ts +36 -0
- package/dist/proxmox/credential-literals.d.ts.map +1 -0
- package/dist/proxmox/credentials.d.ts +8 -8
- package/dist/proxmox/credentials.d.ts.map +1 -1
- package/dist/proxmox/fake-pbs-notify.d.ts +23 -0
- package/dist/proxmox/fake-pbs-notify.d.ts.map +1 -0
- package/dist/proxmox/fake-pve.d.ts +5 -0
- package/dist/proxmox/fake-pve.d.ts.map +1 -1
- package/dist/proxmox/firewall-alias-form.d.ts +1 -1
- package/dist/proxmox/firewall-alias.d.ts +1 -1
- package/dist/proxmox/generated/pbs.d.ts +279 -1
- package/dist/proxmox/generated/pbs.d.ts.map +1 -1
- package/dist/proxmox/generated/pve.d.ts +58 -1
- package/dist/proxmox/generated/pve.d.ts.map +1 -1
- package/dist/proxmox/group.d.ts +8 -8
- package/dist/proxmox/ha-resource.d.ts +3 -2
- package/dist/proxmox/ha-resource.d.ts.map +1 -1
- package/dist/proxmox/ha-rule-form.d.ts +5 -5
- package/dist/proxmox/ha-rule.d.ts +3 -3
- package/dist/proxmox/index.d.ts +14 -0
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +935 -223
- package/dist/proxmox/index.js.map +69 -56
- package/dist/proxmox/lease-cache.d.ts +4 -4
- package/dist/proxmox/lxc-harness.d.ts +1 -1
- package/dist/proxmox/lxc-props.d.ts +2 -2
- package/dist/proxmox/lxc.d.ts +3 -3
- package/dist/proxmox/members.d.ts +3 -3
- package/dist/proxmox/network-apply-read.d.ts +5 -5
- package/dist/proxmox/network-apply.d.ts +9 -9
- package/dist/proxmox/node-network-form.d.ts +6 -6
- package/dist/proxmox/node-network.d.ts +17 -17
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/notification-matcher-form.d.ts +100 -0
- package/dist/proxmox/notification-matcher-form.d.ts.map +1 -0
- package/dist/proxmox/notification-matcher.d.ts +46 -0
- package/dist/proxmox/notification-matcher.d.ts.map +1 -0
- package/dist/proxmox/notification-target.d.ts +3 -2
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-guard.d.ts +1 -1
- package/dist/proxmox/pbs-datastore.d.ts +10 -10
- package/dist/proxmox/pbs-notification-matcher.d.ts +53 -0
- package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-form.d.ts +63 -0
- package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +100 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-wire.d.ts +30 -0
- package/dist/proxmox/pbs-notification-target-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target.d.ts +123 -0
- package/dist/proxmox/pbs-notification-target.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job.d.ts +2 -2
- package/dist/proxmox/pbs-sync-job.d.ts +4 -4
- package/dist/proxmox/pbs-verify-job.d.ts +1 -1
- package/dist/proxmox/pool.d.ts +4 -4
- package/dist/proxmox/provision-baseline.d.ts +74 -0
- package/dist/proxmox/provision-baseline.d.ts.map +1 -0
- package/dist/proxmox/provision-bootstrap.d.ts +33 -0
- package/dist/proxmox/provision-bootstrap.d.ts.map +1 -0
- package/dist/proxmox/provision-cli-fake.d.ts +38 -0
- package/dist/proxmox/provision-cli-fake.d.ts.map +1 -0
- package/dist/proxmox/provision-declare.d.ts +25 -0
- package/dist/proxmox/provision-declare.d.ts.map +1 -0
- package/dist/proxmox/replication-job.d.ts +9 -8
- package/dist/proxmox/replication-job.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts +6 -5
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/sdn-apply-read.d.ts +7 -7
- package/dist/proxmox/sdn-apply.d.ts +1 -1
- package/dist/proxmox/sdn-subnet-form.d.ts +1 -1
- package/dist/proxmox/sdn-subnet.d.ts +5 -5
- package/dist/proxmox/sdn-vnet.d.ts +5 -6
- package/dist/proxmox/sdn-vnet.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts +4 -3
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts +3 -2
- package/dist/proxmox/user.d.ts.map +1 -1
- package/dist/proxmox/values.d.ts +1 -1
- package/dist/proxmox/write-only.d.ts +35 -0
- package/dist/proxmox/write-only.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool-write.d.ts +1 -1
- package/dist/proxmox/zfs-pool.d.ts +5 -5
- package/dist/talos/credentials.d.ts +1 -1
- package/dist/talos/credentials.d.ts.map +1 -1
- package/dist/talos/index.js.map +1 -1
- package/dist/verify/fake-engine.d.ts +5 -0
- package/dist/verify/fake-engine.d.ts.map +1 -1
- package/docs/adopt-verify.md +21 -5
- package/docs/adopted-deploys.md +9 -1
- package/docs/ownership.md +26 -1
- package/docs/pbs-alertmanager-body.md +73 -0
- package/docs/pbs-notifications.md +163 -0
- package/docs/provision-baseline.md +106 -0
- package/docs/proxmox-lxc-adopt.md +2 -2
- package/docs/proxmox-lxc.md +5 -4
- package/docs/proxmox.md +9 -0
- package/package.json +1 -1
- package/src/caddy/admin-guard.ts +1 -1
- package/src/caddy/local-admin.ts +1 -1
- package/src/cloudflare/client.ts +1 -1
- package/src/cloudflare/lock-rules.ts +1 -1
- package/src/forgejo/branch-protection-form.ts +1 -1
- package/src/forgejo/branch-protection.ts +1 -1
- package/src/forgejo/client.ts +1 -1
- package/src/forgejo/org-actions-secrets.ts +1 -1
- package/src/forgejo/org-label.ts +1 -1
- package/src/forgejo/org-team.ts +1 -1
- package/src/forgejo/repo-webhook.ts +1 -1
- package/src/forgejo/repository.ts +2 -2
- package/src/forgejo/resource.ts +2 -2
- package/src/forgejo/team-member.ts +1 -1
- package/src/openbao/README.md +4 -4
- package/src/openbao/auth-role.ts +1 -1
- package/src/openbao/bao-http.test.ts +2 -2
- package/src/openbao/bao-http.ts +6 -6
- package/src/openbao/bao-status.test.ts +1 -1
- package/src/openbao/bao-status.ts +1 -1
- package/src/openbao/cloudflare-group-scope.test.ts +1 -1
- package/src/openbao/cloudflare-permission-groups.test.ts +1 -1
- package/src/openbao/cloudflare-permission-groups.ts +3 -3
- package/src/openbao/cloudflare-policy.ts +1 -1
- package/src/openbao/cloudflare-role-form.ts +2 -2
- package/src/openbao/cloudflare-roles-expand.ts +1 -1
- package/src/openbao/digest.ts +1 -1
- package/src/openbao/forgejo-bootstrap-parse.test.ts +3 -3
- package/src/openbao/forgejo-bootstrap-parse.ts +2 -2
- package/src/openbao/forgejo-bootstrap.ts +4 -4
- package/src/openbao/kubernetes-role-form.test.ts +5 -5
- package/src/openbao/mount.ts +1 -1
- package/src/openbao/pki-role.ts +1 -1
- package/src/openbao/policy-wire.test.ts +3 -3
- package/src/openbao/policy.ts +2 -2
- package/src/openbao/proxmox-role-form.ts +5 -5
- package/src/openbao/proxmox-role.ts +11 -11
- package/src/openbao/ssh-role.ts +3 -3
- package/src/proxmox/acl.ts +4 -4
- package/src/proxmox/adopt-noop.test.ts +6 -6
- package/src/proxmox/alertmanager-body.ts +125 -0
- package/src/proxmox/api-token-form.ts +4 -4
- package/src/proxmox/api-token.ts +13 -13
- package/src/proxmox/ceph-daemon-form.ts +3 -3
- package/src/proxmox/ceph-daemon.ts +20 -20
- package/src/proxmox/ceph-flag.ts +7 -7
- package/src/proxmox/ceph-fs-wire.ts +7 -7
- package/src/proxmox/ceph-fs.ts +18 -18
- package/src/proxmox/ceph-osd-tree.ts +16 -16
- package/src/proxmox/ceph-osd.ts +6 -6
- package/src/proxmox/ceph-pool-adopt.test.ts +14 -14
- package/src/proxmox/ceph-pool-form.ts +13 -13
- package/src/proxmox/ceph-pool.ts +6 -6
- package/src/proxmox/client.test.ts +3 -3
- package/src/proxmox/client.ts +3 -3
- package/src/proxmox/credential-literals.test.ts +148 -0
- package/src/proxmox/credential-literals.ts +74 -0
- package/src/proxmox/credentials.test.ts +6 -8
- package/src/proxmox/credentials.ts +8 -8
- package/src/proxmox/fake-pbs-notify.ts +108 -0
- package/src/proxmox/fake-pve.ts +12 -2
- package/src/proxmox/firewall-alias-form.ts +1 -1
- package/src/proxmox/firewall-alias.ts +1 -1
- package/src/proxmox/generated/pbs.ts +330 -1
- package/src/proxmox/generated/pve.ts +63 -1
- package/src/proxmox/group.ts +10 -10
- package/src/proxmox/ha-resource.ts +3 -2
- package/src/proxmox/ha-rule-form.ts +5 -5
- package/src/proxmox/ha-rule.ts +3 -3
- package/src/proxmox/index.test.ts +10 -0
- package/src/proxmox/index.ts +32 -0
- package/src/proxmox/lease-cache.test.ts +19 -19
- package/src/proxmox/lease-cache.ts +4 -4
- package/src/proxmox/lxc-adopt.test.ts +3 -3
- package/src/proxmox/lxc-create-form.ts +1 -1
- package/src/proxmox/lxc-guards.test.ts +4 -4
- package/src/proxmox/lxc-harness.ts +3 -3
- package/src/proxmox/lxc-judge.test.ts +5 -5
- package/src/proxmox/lxc-lifecycle.ts +1 -1
- package/src/proxmox/lxc-props.ts +2 -2
- package/src/proxmox/lxc-read.test.ts +3 -3
- package/src/proxmox/lxc-strict-adopt.test.ts +1 -1
- package/src/proxmox/lxc-volume.test.ts +9 -9
- package/src/proxmox/lxc-volume.ts +1 -1
- package/src/proxmox/lxc-wire.ts +1 -1
- package/src/proxmox/lxc.ts +3 -3
- package/src/proxmox/members.test.ts +1 -1
- package/src/proxmox/members.ts +3 -3
- package/src/proxmox/metric-server-otel.ts +2 -2
- package/src/proxmox/network-apply-read.ts +10 -10
- package/src/proxmox/network-apply.ts +10 -10
- package/src/proxmox/node-network-form.ts +7 -7
- package/src/proxmox/node-network.ts +18 -18
- package/src/proxmox/notification-matcher-form.ts +221 -0
- package/src/proxmox/notification-matcher.test.ts +234 -0
- package/src/proxmox/notification-matcher.ts +76 -0
- package/src/proxmox/notification-target.ts +3 -2
- package/src/proxmox/pbs-datastore-guard.ts +1 -1
- package/src/proxmox/pbs-datastore.ts +10 -10
- package/src/proxmox/pbs-notification-matcher.ts +81 -0
- package/src/proxmox/pbs-notification-target-drift.test.ts +83 -0
- package/src/proxmox/pbs-notification-target-form.ts +229 -0
- package/src/proxmox/pbs-notification-target-lifecycle.ts +177 -0
- package/src/proxmox/pbs-notification-target-state.test.ts +191 -0
- package/src/proxmox/pbs-notification-target-wire.ts +112 -0
- package/src/proxmox/pbs-notification-target.test.ts +226 -0
- package/src/proxmox/pbs-notification-target.ts +139 -0
- package/src/proxmox/pbs-notifications-stack.test.ts +94 -0
- package/src/proxmox/pbs-prune-job.ts +2 -2
- package/src/proxmox/pbs-sync-job.ts +4 -4
- package/src/proxmox/pbs-verify-job.ts +1 -1
- package/src/proxmox/pool.test.ts +2 -2
- package/src/proxmox/pool.ts +4 -4
- package/src/proxmox/provision-baseline.test.ts +87 -0
- package/src/proxmox/provision-baseline.ts +162 -0
- package/src/proxmox/provision-bootstrap.test.ts +132 -0
- package/src/proxmox/provision-bootstrap.ts +135 -0
- package/src/proxmox/provision-cli-fake.ts +217 -0
- package/src/proxmox/provision-declare.test.ts +111 -0
- package/src/proxmox/provision-declare.ts +103 -0
- package/src/proxmox/replication-job.ts +9 -8
- package/src/proxmox/resource.ts +9 -9
- package/src/proxmox/sdn-apply-read.test.ts +16 -16
- package/src/proxmox/sdn-apply-read.ts +7 -7
- package/src/proxmox/sdn-apply.ts +3 -3
- package/src/proxmox/sdn-subnet-form.ts +1 -1
- package/src/proxmox/sdn-subnet.ts +5 -5
- package/src/proxmox/sdn-vnet.ts +5 -6
- package/src/proxmox/storage.ts +4 -3
- package/src/proxmox/user.ts +3 -2
- package/src/proxmox/values.test.ts +2 -2
- package/src/proxmox/values.ts +1 -1
- package/src/proxmox/write-only.test.ts +75 -0
- package/src/proxmox/write-only.ts +106 -0
- package/src/proxmox/zfs-pool-write.ts +1 -1
- package/src/proxmox/zfs-pool.ts +6 -6
- package/src/talos/credentials.ts +3 -3
- package/src/talos/values.test.ts +9 -9
- package/src/verify/cli.test.ts +1 -1
- package/src/verify/fake-engine.ts +8 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One credential per (target, role) for the life of a run, instead of one per API call.
|
|
3
3
|
*
|
|
4
|
-
* 🔴 THE MEASUREMENT THAT MADE THIS NECESSARY. A single `alchemy plan` plus `deploy` of the
|
|
5
|
-
* stack — 98 resources — left **760 API token entries in `/etc/pve/user.cfg`**, counted on
|
|
4
|
+
* 🔴 THE MEASUREMENT THAT MADE THIS NECESSARY. A single `alchemy plan` plus `deploy` of the C1
|
|
5
|
+
* stack — 98 resources — left **760 API token entries in `/etc/pve/user.cfg`**, counted on node-b
|
|
6
6
|
* immediately afterwards. `mint()` asked OpenBao for a new credential on EVERY call, and each
|
|
7
7
|
* mint creates a real Proxmox token: read, diff, reconcile and read-back each got their own. They
|
|
8
8
|
* drain as the leases expire (760 down to 712 over five minutes), so nothing was broken — but
|
|
@@ -52,9 +52,9 @@ import { type ApiTarget, type PveCredential, type PveRole } from './credentials.
|
|
|
52
52
|
* What a credential is cached under.
|
|
53
53
|
*
|
|
54
54
|
* ⛔ KEYED ON THE OPENBAO MOUNT AND ROLE, NOT ON A MEMBER HOSTNAME. A PVE API token is
|
|
55
|
-
* cluster-wide — `user.cfg` is replicated — so
|
|
55
|
+
* cluster-wide — `user.cfg` is replicated — so node-b, node-c and node-d share one credential. Keying on
|
|
56
56
|
* `api` would mint three identical tokens when failover rotates members.
|
|
57
|
-
* ★ THE MOUNT CANNOT COLLIDE ACROSS CLUSTERS: `proxmox-
|
|
57
|
+
* ★ THE MOUNT CANNOT COLLIDE ACROSS CLUSTERS: `proxmox-c1`, `proxmox-c2` and `pbs-c1` are
|
|
58
58
|
* distinct OpenBao mounts with distinct policies; two estates never share a mount name.
|
|
59
59
|
* ★ A PLAIN OBJECT IS A SAFE KEY BECAUSE EFFECT COMPARES KEYS STRUCTURALLY — two separately built
|
|
60
60
|
* literals with the same fields are one entry, which the probe above measured.
|
|
@@ -13,7 +13,7 @@ export declare const TARGET: PveTarget;
|
|
|
13
13
|
export declare const LIVE: Readonly<Record<string, unknown>>;
|
|
14
14
|
/** Where `seed` puts the production-shaped guest by default, and its `node/vmid` key. */
|
|
15
15
|
export declare const NODE = "pve1";
|
|
16
|
-
export declare const VMID =
|
|
16
|
+
export declare const VMID = 900;
|
|
17
17
|
export declare const KEY: string;
|
|
18
18
|
/** The live config as a declaration: every key, minus what pvesh adds that is not a prop. */
|
|
19
19
|
export declare const pasted: (over?: Partial<LxcProps>) => LxcProps;
|
|
@@ -35,7 +35,7 @@ export interface LxcProps extends WithTarget {
|
|
|
35
35
|
/** MiB. */
|
|
36
36
|
swap?: number;
|
|
37
37
|
/**
|
|
38
|
-
* `local-zfs:subvol-
|
|
38
|
+
* `local-zfs:subvol-900-disk-0,size=40G` as read, or `local-zfs:40` (40 GiB, new volume) to
|
|
39
39
|
* create. See lxc-volume.ts for what may change: options in place, size up only.
|
|
40
40
|
*/
|
|
41
41
|
rootfs?: string;
|
|
@@ -56,7 +56,7 @@ export interface LxcProps extends WithTarget {
|
|
|
56
56
|
console?: PveFlag;
|
|
57
57
|
tty?: number;
|
|
58
58
|
cmode?: string;
|
|
59
|
-
/** `tank:subvol-
|
|
59
|
+
/** `tank:subvol-900-disk-1,mp=/data,backup=1,size=100G`, or `tank:100,mp=/data` to create. */
|
|
60
60
|
[mount: `mp${number}`]: string | undefined;
|
|
61
61
|
/** `name=eth0,bridge=vmbr0,ip=dhcp`. ⚠️ An undeclared `hwaddr` keeps the live MAC. */
|
|
62
62
|
[nic: `net${number}`]: string | undefined;
|
package/dist/proxmox/lxc.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Proxmox.Lxc` — a container, declared: adopted as it runs, or created from a template.
|
|
3
3
|
*
|
|
4
|
-
* ★
|
|
5
|
-
* VM.
|
|
6
|
-
* Datastore.AllocateSpace, Datastore.Audit, SDN.Use, Sys.Audit. What
|
|
4
|
+
* ★ THE PROVISIONING BASELINE HOLDS WHAT THIS NEEDS (provision-baseline.ts): on `/`, VM.Allocate,
|
|
5
|
+
* VM.Audit, VM.Config.{CPU,Disk,HWType,Memory,Network,Options}, VM.PowerMgmt,
|
|
6
|
+
* Datastore.AllocateSpace, Datastore.Audit, SDN.Use, Sys.Audit. What no token can do — device
|
|
7
7
|
* passthrough, bind mounts, features beyond nesting — PVE keeps for root@pam, and lxc-judge.ts
|
|
8
8
|
* refuses at plan with the `pct set` to run instead.
|
|
9
9
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* C1 cluster members: ordered failover, last-good preference, transport classification.
|
|
3
3
|
*
|
|
4
4
|
* ★ ANY MEMBER'S :8006 API MANAGES THE WHOLE CLUSTER — PVE proxies node paths itself — so the
|
|
5
5
|
* provider reaches the cluster through whichever member is up, not through one pinned node.
|
|
6
6
|
*
|
|
7
|
-
* ⛔ NEVER cluster-
|
|
7
|
+
* ⛔ NEVER cluster-c1.example.com: it resolves to 192.0.2.250, which nothing serves.
|
|
8
8
|
*/
|
|
9
9
|
import * as Effect from 'effect/Effect';
|
|
10
10
|
import * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
@@ -44,7 +44,7 @@ export type PveMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
|
44
44
|
* elsewhere can repeat a write.
|
|
45
45
|
*
|
|
46
46
|
* ★ Reads fail over on any transport failure, including timeout — no side effect to duplicate.
|
|
47
|
-
* MEASURED 2026-09-14: full
|
|
47
|
+
* MEASURED 2026-09-14: full C1 plans stayed all-noop with the first member refused (127.0.0.1)
|
|
48
48
|
* and with it unresolvable (n9.invalid).
|
|
49
49
|
*/
|
|
50
50
|
export declare const executeOnCluster: (target: PveTarget, method: PveMethod, path: string, build: (apiBase: string) => HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* diff, and the only write in either file is the apply itself.
|
|
8
8
|
*
|
|
9
9
|
* ⛔ THE PENDING STATE IS NOT IN `data`, WHICH IS WHY THIS FILE DOES ITS OWN FETCH. MEASURED in the
|
|
10
|
-
* cluster's own source on
|
|
10
|
+
* cluster's own source on node-b, 2026-09-13 — not inferred from documentation:
|
|
11
11
|
* PVE/API2/Network.pm my $tmp = PVE::INotify::read_file('interfaces', 1);
|
|
12
12
|
* $rpcenv->set_result_attrib('changes', $changes) if $changes;
|
|
13
13
|
* PVE/HTTPServer.pm if (my $diff = $rpcenv->get_result_attrib('changes')) {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* `GET /nodes/{node}/network` answers with the STAGED config and a node about to be reconfigured
|
|
25
25
|
* looks identical to one already running it. Same trap `sdn-apply.ts` documents for `?running=1`.
|
|
26
26
|
*
|
|
27
|
-
* ⚠️ `active` IS NOT A PENDING SIGNAL, AND THE OBVIOUS SHORTCUT IS MEASURABLY WRONG. On
|
|
28
|
-
* with nothing staged anywhere (VERIFIED 2026-09-13: `/etc/network/interfaces.new` absent on
|
|
29
|
-
*
|
|
27
|
+
* ⚠️ `active` IS NOT A PENDING SIGNAL, AND THE OBVIOUS SHORTCUT IS MEASURABLY WRONG. On node-b and node-c,
|
|
28
|
+
* with nothing staged anywhere (VERIFIED 2026-09-13: `/etc/network/interfaces.new` absent on node-b,
|
|
29
|
+
* node-c and node-d), `wlan0` already comes back with no `active` key at all. Counting interfaces that
|
|
30
30
|
* are not up as "pending" would report an update on two of the three nodes forever.
|
|
31
31
|
*
|
|
32
32
|
* ⚠️ ONE THING GENUINELY CANNOT BE SEEN FROM HERE, AND IT IS SMALL. The diff is computed as
|
|
@@ -60,7 +60,7 @@ export declare const pendingCount: (target: PveTarget, node: string) => Effect.E
|
|
|
60
60
|
*
|
|
61
61
|
* ★ A REASON RATHER THAN A BOOLEAN, BECAUSE THE CALLER'S ONLY USE FOR IT IS A REFUSAL MESSAGE.
|
|
62
62
|
* "The deploy stopped because the cluster was degraded" is not actionable at three in the
|
|
63
|
-
* morning; "quorate=true offline=[
|
|
63
|
+
* morning; "quorate=true offline=[node-c]" is. The two callers in network-apply.ts ask this before
|
|
64
64
|
* the reload and again after it, and the second answer is the one that stops the chain from
|
|
65
65
|
* reaching the next node.
|
|
66
66
|
*
|
|
@@ -4,23 +4,23 @@
|
|
|
4
4
|
* ⛔ WITHOUT IT EVERY NETWORK DECLARATION IS A GREEN PLAN THAT CHANGES NO NETWORK; WITH IT A GREEN
|
|
5
5
|
* PLAN CAN TAKE A NODE OFF THE NETWORK. Writes under `nodes/{node}/network/{iface}` edit
|
|
6
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
|
|
7
|
+
* MEASURED in the cluster's own source on node-b, 2026-09-13 — `PVE/API2/Network.pm`:
|
|
8
8
|
* rename($new_config_file, $current_config_file) if -e $new_config_file;
|
|
9
9
|
* PVE::Tools::run_command(['ifreload', '-a'], errfunc => $err);
|
|
10
10
|
* return $rpcenv->fork_worker('srvreload', 'networking', $authuser, $worker);
|
|
11
11
|
*
|
|
12
|
-
* ⛔ THIS IS THE MOST DANGEROUS RESOURCE IN THE PACKAGE, AND ON
|
|
13
|
-
* Ceph runs over `vmbr1.
|
|
12
|
+
* ⛔ THIS IS THE MOST DANGEROUS RESOURCE IN THE PACKAGE, AND ON C1 THE DANGER IS NOT ABSTRACT.
|
|
13
|
+
* Ceph runs over `vmbr1.42` on node-b, node-c AND node-d (MEASURED 2026-09-13: four pools, size 3 /
|
|
14
14
|
* min_size 2, mon+mgr+mds on all three). `ifreload -a` takes that node's OSDs, mons and MDS off
|
|
15
15
|
* the cluster network for the length of the reload; two nodes at once puts every pool below
|
|
16
16
|
* min_size and blocks IO for every guest with an RBD disk. The management address the API is
|
|
17
|
-
* reached on, `vmbr0.
|
|
17
|
+
* reached on, `vmbr0.41`, is reloaded by the same call.
|
|
18
18
|
*
|
|
19
19
|
* ⛔ AND UNLIKE `SdnApply` IT IS PER NODE, SO A HALF-RECONFIGURED CLUSTER IS THE DEFAULT FAILURE
|
|
20
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
|
|
22
|
-
* and
|
|
23
|
-
* 1. `after` SERIALISES THEM — chain
|
|
21
|
+
* publishes exactly one node, so a stack holding three of these can apply node-b, fail, and leave node-c
|
|
22
|
+
* and node-d on the old layout with Ceph spanning all three. Four things here exist only for that:
|
|
23
|
+
* 1. `after` SERIALISES THEM — chain node-c after node-b and node-d after node-c, so at most one node is ever
|
|
24
24
|
* mid-reload. Three declared in parallel is the outage, and nothing in Alchemy stops you.
|
|
25
25
|
* 2. `reconcile` REFUSES TO START on a cluster that is already degraded.
|
|
26
26
|
* 3. `reconcile` RE-CHECKS quorum and every member's `online` flag AFTER the reload and dies if
|
|
@@ -51,7 +51,7 @@ import type { PveRequirements } from './resource.ts';
|
|
|
51
51
|
import type { WithTarget } from './resource.ts';
|
|
52
52
|
export interface NetworkApplyProps extends WithTarget {
|
|
53
53
|
/**
|
|
54
|
-
* The node whose staged interfaces file this applies — `
|
|
54
|
+
* The node whose staged interfaces file this applies — `node-b`.
|
|
55
55
|
*
|
|
56
56
|
* ⚠️ ONE APPLY PER NODE, AND NEVER TWO FOR THE SAME NODE. Two would each publish the other's
|
|
57
57
|
* staged half, and both would run `ifreload` on a file the other had not finished writing.
|
|
@@ -89,7 +89,7 @@ export interface NetworkApplyAttributes {
|
|
|
89
89
|
* `active`, `exists`, `families`, `method`, `method6`, `options`, `priority`, `link-type` and
|
|
90
90
|
* ten more; `altnames` is returned and is not even in the schema. `priority` is the worst of
|
|
91
91
|
* them: PVE ASSIGNS it from the order of the interfaces file, so the same logical layout
|
|
92
|
-
* carries different numbers on different nodes (MEASURED:
|
|
92
|
+
* carries different numbers on different nodes (MEASURED: node-d's differ from node-b/node-c's by one).
|
|
93
93
|
* Every one of those is a forever-diff waiting for whoever writes `Proxmox.NetworkInterface`.
|
|
94
94
|
* This resource compares a single integer against zero and never looks at a row at all.
|
|
95
95
|
*/
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* * ⛔ THE LAST SEVEN ARE REPORTED AND NEVER COMPARED, AND EACH ONE IS A MEASURED FOREVER-DIFF.
|
|
16
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
|
|
18
|
-
* priority 16 on
|
|
17
|
+
* $priority++` — and it is not a POST or PUT parameter at all. MEASURED ACROSS C1: vmbr1.42 is
|
|
18
|
+
* priority 16 on node-b and node-c and 15 on node-d, because node-d has no `wlan0` above it. One declaration
|
|
19
19
|
* reused across three nodes would diff on exactly one of them, forever, over nothing.
|
|
20
20
|
* `method` is RECOMPUTED on every write from whether the form carried an address, `families` is
|
|
21
21
|
* recomputed the same way and never written to the file, `active` and `exists` describe the
|
|
22
22
|
* kernel rather than the config, and `bond_miimon`, `bridge_stp` and `bridge_fd` are emitted by
|
|
23
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:
|
|
24
|
+
* `additionalProperties => 0` means sending one is a 400. Live proof of all three: node-b's bond0
|
|
25
25
|
* returns `bond_miimon "100"` and vmbr1 returns `bridge_stp "off"`, `bridge_fd "0"`, none of
|
|
26
26
|
* which any declaration can set.
|
|
27
27
|
*/
|
|
@@ -65,7 +65,7 @@ export declare const ifaceList: (value: unknown) => string;
|
|
|
65
65
|
* `ceph transport` comes back as `ceph transport\n` and the two never compare equal.
|
|
66
66
|
* ⚠️ AND THE READER FOLDS `comments6` INTO `comments` (`$d->{comments} .= $comments6`), so on an
|
|
67
67
|
* interface that carries an IPv6 comment a declared `comments` CANNOT match what comes back.
|
|
68
|
-
*
|
|
68
|
+
* C1 has none; on an interface that does, leave `comments` undeclared rather than fighting it.
|
|
69
69
|
*/
|
|
70
70
|
export declare const comment: (value: unknown) => string;
|
|
71
71
|
/** Undeclared is unmanaged: neither sent nor compared. See the ⚠️ on `body`. */
|
|
@@ -92,7 +92,7 @@ export declare const sameComment: (declared: string | undefined, live: string) =
|
|
|
92
92
|
*
|
|
93
93
|
* ⚠️ `netmask` IS A WRITABLE PARAMETER AND IS DELIBERATELY NOT OFFERED. PVE raises
|
|
94
94
|
* "netmask conflicts with cidr" when both are sent, and the reader rewrites whatever was written
|
|
95
|
-
* into prefix form — MEASURED:
|
|
95
|
+
* into prefix form — MEASURED: node-b's vmbr1.42 reports `"netmask":"24"`, never `255.255.255.0`.
|
|
96
96
|
* A resource accepting both spellings would let a declaration diff against its own value.
|
|
97
97
|
*/
|
|
98
98
|
export declare const body: (props: NodeNetworkProps) => Record<string, string>;
|
|
@@ -108,7 +108,7 @@ export declare const body: (props: NodeNetworkProps) => Record<string, string>;
|
|
|
108
108
|
* `ifreload -a` I did NOT measure. Clearing cidr explicitly at least produces a clean, honest
|
|
109
109
|
* manual interface — and `matches` compares `cidr` UNCONDITIONALLY, so the plan says so first.
|
|
110
110
|
*
|
|
111
|
-
* ⛔ ON vmbr1.
|
|
111
|
+
* ⛔ ON vmbr1.42 THAT IS THE CEPH TRANSPORT. A declaration of that interface without its `cidr` is
|
|
112
112
|
* a declaration that it should have no address, applied across three nodes. The plan will read
|
|
113
113
|
* `1 to update` rather than `noop`; do not wave it through.
|
|
114
114
|
*
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* that apply — this one never calls it, exactly as `Proxmox.SdnZone` never calls
|
|
12
12
|
* `Proxmox.SdnApply`.
|
|
13
13
|
*
|
|
14
|
-
* ⛔ CEPH ON
|
|
14
|
+
* ⛔ CEPH ON C1 RIDES TWO NETWORKS AND THIS API ONLY SEES ONE OF THEM. Measured from
|
|
15
15
|
* `/etc/pve/ceph.conf` on 2026-09-13:
|
|
16
16
|
*
|
|
17
|
-
* public_network =
|
|
18
|
-
* cluster_network =
|
|
17
|
+
* public_network = 198.51.100.0/24 -> vmbr1.42, which this resource DOES see
|
|
18
|
+
* cluster_network = 203.0.113.0/24 -> every OSD's cluster_addr: .102 / .103 / .104
|
|
19
19
|
*
|
|
20
20
|
* The cluster network — OSD REPLICATION, the traffic that rebuilds a lost replica — lives on
|
|
21
|
-
* `
|
|
21
|
+
* `203.0.113.10X/32` addresses carried by `tb0`, `tb1` and `dummy_c1`: the Thunderbolt mesh.
|
|
22
22
|
*
|
|
23
23
|
* ⛔ AND THOSE ADDRESSES ARE INVISIBLE HERE, WHICH IS THE TRAP. They are declared in
|
|
24
24
|
* `/etc/network/interfaces.d/sdn`, and PVE's network API does not parse that directory.
|
|
25
|
-
* MEASURED: `GET /nodes/
|
|
26
|
-
* address, while `ip -br -4 addr` shows `
|
|
27
|
-
*
|
|
25
|
+
* MEASURED: `GET /nodes/node-b/network/tb0` answers `{"method":"manual","type":"eth",…}` with NO
|
|
26
|
+
* address, while `ip -br -4 addr` shows `tb0 UP 203.0.113.102/32`. So this resource reports
|
|
27
|
+
* tb0 and tb1 as address-less manual ports and will plan `noop` over them — implicitly
|
|
28
28
|
* asserting they carry no address, which is FALSE.
|
|
29
29
|
*
|
|
30
|
-
* The consequence, stated plainly: declaring
|
|
30
|
+
* The consequence, stated plainly: declaring tb0/tb1 from what this API reports and then
|
|
31
31
|
* DESTROYING or applying over them takes out OSD replication on a cluster whose pools are
|
|
32
32
|
* size 3 / min_size 2. They read as the most boring interfaces on the node and are the two most
|
|
33
33
|
* dangerous. Leave them undeclared; if they must be managed, manage the file that actually
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* interface a node already has would otherwise leave it holding a pending network change.
|
|
56
56
|
*
|
|
57
57
|
* ⚠️ WHAT IS STILL TRUE: when `matches` is false the PUT rewrites the whole stanza, and that can
|
|
58
|
-
* ADD lines the file did not have — a `vlan-raw-device vmbr1` under `vmbr1.
|
|
58
|
+
* ADD lines the file did not have — a `vlan-raw-device vmbr1` under `vmbr1.42`, where PVE had
|
|
59
59
|
* been deriving it from the name. Harmless in content, and it costs nothing until somebody
|
|
60
60
|
* applies; one more reason the apply is a separate resource.
|
|
61
61
|
*
|
|
@@ -69,10 +69,10 @@ import { Resource } from 'alchemy';
|
|
|
69
69
|
import * as Provider from 'alchemy/Provider';
|
|
70
70
|
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
71
71
|
/**
|
|
72
|
-
* PVE's interface types. `unknown` is what a NIC it does not manage reports —
|
|
73
|
-
* report `
|
|
72
|
+
* PVE's interface types. `unknown` is what a NIC it does not manage reports — node-b and node-c both
|
|
73
|
+
* report `wlan0` that way, and node-d has no such interface at all.
|
|
74
74
|
*
|
|
75
|
-
* ⚠️ THAT ASYMMETRY IS A TRAP FOR A DECLARATION SHARED ACROSS NODES. On
|
|
75
|
+
* ⚠️ THAT ASYMMETRY IS A TRAP FOR A DECLARATION SHARED ACROSS NODES. On node-d the read answers 404,
|
|
76
76
|
* which this provider cannot tell from "deleted", so reconcile POSTs — writing a stanza for a
|
|
77
77
|
* card that is not in the machine. Declare per node what each node actually has; a physical
|
|
78
78
|
* interface is discovered, not decided.
|
|
@@ -81,24 +81,24 @@ export type NodeNetworkType = 'OVSBond' | 'OVSBridge' | 'OVSIntPort' | 'OVSPort'
|
|
|
81
81
|
export interface NodeNetworkProps extends WithTarget {
|
|
82
82
|
/** Which node's file this stanza lives in. Interfaces are per node, never cluster-wide. */
|
|
83
83
|
node: string;
|
|
84
|
-
/** `vmbr0`, `bond0`, `vmbr1.
|
|
84
|
+
/** `vmbr0`, `bond0`, `vmbr1.42`. 2-20 characters, PVE's `pve-iface` format. */
|
|
85
85
|
iface: string;
|
|
86
86
|
/** ⛔ REQUIRED ON EVERY WRITE, update included, never used to retype — see `readAttributes`. */
|
|
87
87
|
type: NodeNetworkType;
|
|
88
88
|
/**
|
|
89
|
-
* `
|
|
89
|
+
* `198.51.100.12/24`. ⛔ THE ONE FIELD WITH NO UNMANAGED MODE: leaving it out is an instruction
|
|
90
90
|
* to make the interface `manual`, not an instruction to leave its address alone. The ⛔ on
|
|
91
91
|
* `updateBody` in node-network-form.ts has the measurement and the consequence.
|
|
92
92
|
*/
|
|
93
93
|
cidr?: string;
|
|
94
94
|
/** ⚠️ PVE allows exactly ONE default gateway per node and refuses a second with "Default
|
|
95
|
-
* gateway already exists on interface '<other>'". On
|
|
95
|
+
* gateway already exists on interface '<other>'". On C1 it is vmbr0.41's. */
|
|
96
96
|
gateway?: string;
|
|
97
97
|
/**
|
|
98
98
|
* `auto <iface>` in the file. Absent on read means off, which is why `bool`'s fallback is used.
|
|
99
99
|
* ⚠️ UNDECLARED IS UNMANAGED ON AN UPDATE AND OFF ON A CREATE — the same asymmetry storage.ts
|
|
100
100
|
* has, and it bites harder here: an interface created without `autostart` is one the node
|
|
101
|
-
* will not bring up at boot. Every
|
|
101
|
+
* will not bring up at boot. Every C1 bridge, bond and vlan carries it.
|
|
102
102
|
*/
|
|
103
103
|
autostart?: boolean;
|
|
104
104
|
/** 1280-65520. Unset leaves the file without an `mtu` line and the kernel default in force. */
|
|
@@ -122,7 +122,7 @@ export interface NodeNetworkProps extends WithTarget {
|
|
|
122
122
|
bond_xmit_hash_policy?: string;
|
|
123
123
|
/** active-backup only. Kept hyphenated because that is the wire name. */
|
|
124
124
|
'bond-primary'?: string;
|
|
125
|
-
/** ⚠️ DERIVED FROM A DOTTED NAME. `vmbr1.
|
|
125
|
+
/** ⚠️ DERIVED FROM A DOTTED NAME. `vmbr1.42` reports `vlan-id` 42 with no such line in the file;
|
|
126
126
|
* declaring it there is harmless but adds a line the file did not have. */
|
|
127
127
|
'vlan-id'?: number;
|
|
128
128
|
'vlan-raw-device'?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-network.d.ts","sourceRoot":"","sources":["../../src/proxmox/node-network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAU7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,SAAS,GACT,OAAO,GACP,MAAM,GACN,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,IAAI,EAAE,eAAe,CAAC;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;
|
|
1
|
+
{"version":3,"file":"node-network.d.ts","sourceRoot":"","sources":["../../src/proxmox/node-network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAU7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,SAAS,GACT,OAAO,GACP,MAAM,GACN,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,IAAI,EAAE,eAAe,CAAC;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;kFAC8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+FAA+F;IAC/F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;gFAC4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,qDAE7B,CAAC;AAsCH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,wIACwD,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A notification matcher — the rule that routes a notification to its targets — as ONE shape
|
|
3
|
+
* shared by PVE (`Proxmox.NotificationMatcher`) and PBS (`Pbs.NotificationMatcher`).
|
|
4
|
+
*
|
|
5
|
+
* ★ ONE FILE FOR BOTH PRODUCTS BECAUSE IT IS ONE IMPLEMENTATION. Both servers run the same Rust
|
|
6
|
+
* crate, `proxmox-notify` (matcher.rs), PVE through its Perl bindings; the schemas in
|
|
7
|
+
* generated/pve.ts and generated/pbs.ts carry the same nine fields, and the forms below are
|
|
8
|
+
* typed against BOTH, so a drift on either side fails `tsc` here rather than on a server.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ A MATCHER IS ITS WHOLE RULE, SO EVERY RULE FIELD IS COMPARED, DECLARED OR NOT. Most families
|
|
11
|
+
* here leave an undeclared field alone (backup-job.ts's trade). A matcher cannot: an undeclared
|
|
12
|
+
* `match-field` on a matcher that has one is a declaration claiming "route everything" over a
|
|
13
|
+
* server that routes one job type — and `invert-match` flips the meaning of every other line.
|
|
14
|
+
* So `match-*` default to empty, `mode` to `all`, `invert-match` and `disable` to false, all are
|
|
15
|
+
* compared, and a PUT sends all of them (an emptied list as `delete`). Only `comment` keeps the
|
|
16
|
+
* usual "undeclared is unmanaged" rule, because nothing routes on it.
|
|
17
|
+
* ★ THE COST IS THAT ADOPTING A MATCHER MEANS DECLARING IT AS IT IS. PBS ships `default-matcher`
|
|
18
|
+
* with `invert-match`, a `match-field` and a `match-severity` (measured on PBS 4.2, 2026-09-22),
|
|
19
|
+
* so adopting it as-is means writing those three down. A declaration that leaves them out
|
|
20
|
+
* plans an UPDATE, which is the plan telling the truth about what the deploy would do.
|
|
21
|
+
*/
|
|
22
|
+
import type { ClusterNotificationsMatchersNamePutParams, ClusterNotificationsMatchersPostParams } from './generated/pve.ts';
|
|
23
|
+
import type { ConfigNotificationsMatchersNamePutParams, ConfigNotificationsMatchersPostParams } from './generated/pbs.ts';
|
|
24
|
+
/** Everything a matcher declares except where it lives — the resource files add `target`. */
|
|
25
|
+
export interface NotificationMatcherFields {
|
|
26
|
+
/** The primary key, a safe id (2–32 chars on PBS). Changing it is a replace. */
|
|
27
|
+
name: string;
|
|
28
|
+
/**
|
|
29
|
+
* ⛔ THE TARGETS, BY NAME, AND REQUIRED: a matcher that notifies nobody is a routing rule for
|
|
30
|
+
* silence. `[]` is still declarable, and clears the list.
|
|
31
|
+
* ⚠️ `targets`, NOT THE SERVER'S `target`: every resource here already spends `target` on the
|
|
32
|
+
* HOST (resource.ts `WithTarget`), and one key cannot mean both. The wire still says `target`.
|
|
33
|
+
*/
|
|
34
|
+
targets: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Severity rules. EACH ITEM IS ITSELF A COMMA LIST: `'warning,error'` matches either.
|
|
37
|
+
* ⛔ `['warning', 'error']` IS NOT THE SAME RULE AND UNDER `mode: 'all'` NEVER MATCHES. Items are
|
|
38
|
+
* combined with `mode`; the severities inside one item are always OR-ed (matcher.rs
|
|
39
|
+
* `SeverityMatcher`). A notification has one severity, so two items under AND ask for a
|
|
40
|
+
* notification that is both — and routing nothing is the quietest failure a matcher has.
|
|
41
|
+
* Severities: `info`, `notice`, `warning`, `error`, `unknown`.
|
|
42
|
+
*/
|
|
43
|
+
'match-severity'?: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Metadata rules, `exact:<field>=<v1>,<v2>` or `regex:<field>=<re>`. ⚠️ THE PREFIX IS REQUIRED:
|
|
46
|
+
* the schema's pattern makes it optional and the parser then refuses it. A notification WITHOUT
|
|
47
|
+
* the field never matches its rule — so `exact:job-id=…` excludes every garbage collection.
|
|
48
|
+
*/
|
|
49
|
+
'match-field'?: readonly string[];
|
|
50
|
+
/** Time windows, e.g. `mon-fri 9:00-17:00`. Compared as written; the server keeps the text. */
|
|
51
|
+
'match-calendar'?: readonly string[];
|
|
52
|
+
/** How the rule items combine. The server's default, and this package's, is `all`. */
|
|
53
|
+
mode?: 'all' | 'any';
|
|
54
|
+
/** Invert the whole rule. ⚠️ IGNORED WHEN THERE ARE NO RULES — an empty matcher always matches. */
|
|
55
|
+
'invert-match'?: boolean;
|
|
56
|
+
/** A disabled matcher routes nothing. */
|
|
57
|
+
disable?: boolean;
|
|
58
|
+
/** Free text. The one field left alone when undeclared; `''` clears it. */
|
|
59
|
+
comment?: string;
|
|
60
|
+
}
|
|
61
|
+
/** ⚠️ Lists are CANONICAL and SORTED, so a re-ordered declaration is not a diff. */
|
|
62
|
+
export interface NotificationMatcherAttributes {
|
|
63
|
+
name: string;
|
|
64
|
+
/**
|
|
65
|
+
* `builtin`, `modified-builtin` or `user-created`. REPORTED, NEVER COMPARED.
|
|
66
|
+
* ⛔ ON A BUILT-IN A DELETE REVERTS RATHER THAN REMOVES, and reports success either way — Alchemy
|
|
67
|
+
* drops the row while `default-matcher` goes on routing. Read this before destroying one.
|
|
68
|
+
* ★ MEASURED ON THE PER-OBJECT READ, which the published schema does not list: PVE 9.2.11 and
|
|
69
|
+
* PBS 4.2 both return it from `…/matchers/default-matcher` (2026-09-22).
|
|
70
|
+
*/
|
|
71
|
+
origin: string;
|
|
72
|
+
comment: string;
|
|
73
|
+
disable: boolean;
|
|
74
|
+
'invert-match': boolean;
|
|
75
|
+
mode: string;
|
|
76
|
+
targets: readonly string[];
|
|
77
|
+
'match-severity': readonly string[];
|
|
78
|
+
'match-field': readonly string[];
|
|
79
|
+
'match-calendar': readonly string[];
|
|
80
|
+
}
|
|
81
|
+
/** A declaration's lists, canonical and sorted — the form `matches` compares. */
|
|
82
|
+
export declare const canonicalRules: (fields: NotificationMatcherFields) => {
|
|
83
|
+
'match-calendar': string[];
|
|
84
|
+
'match-field': string[];
|
|
85
|
+
'match-severity': string[];
|
|
86
|
+
targets: string[];
|
|
87
|
+
};
|
|
88
|
+
export declare const matcherAttributes: (live: Record<string, unknown>, name: string) => NotificationMatcherAttributes;
|
|
89
|
+
export declare const matcherMatches: (attributes: NotificationMatcherAttributes, fields: NotificationMatcherFields) => boolean;
|
|
90
|
+
export type MatcherCreateForm = ConfigNotificationsMatchersPostParams & ClusterNotificationsMatchersPostParams;
|
|
91
|
+
export type MatcherUpdateForm = ConfigNotificationsMatchersNamePutParams & ClusterNotificationsMatchersNamePutParams;
|
|
92
|
+
/** ⛔ `name` is create-only: the body carries it once and the path forever after. */
|
|
93
|
+
export declare const matcherCreateForm: (fields: NotificationMatcherFields) => MatcherCreateForm;
|
|
94
|
+
/**
|
|
95
|
+
* ⛔ AN EMPTIED LIST IS A `delete`, NOT AN EMPTY VALUE. A form cannot carry an empty array — no
|
|
96
|
+
* key is sent at all — so without the `delete` entry a declaration of `'match-field': []`
|
|
97
|
+
* would plan an update, PUT nothing for that field, and plan the same update forever.
|
|
98
|
+
*/
|
|
99
|
+
export declare const matcherUpdateForm: (fields: NotificationMatcherFields) => MatcherUpdateForm;
|
|
100
|
+
//# sourceMappingURL=notification-matcher-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-matcher-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/notification-matcher-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,yCAAyC,EACzC,sCAAsC,EACvC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,wCAAwC,EACxC,qCAAqC,EACtC,MAAM,oBAAoB,CAAC;AAG5B,6FAA6F;AAC7F,MAAM,WAAW,yBAAyB;IACxC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,sFAAsF;IACtF,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACrB,mGAAmG;IACnG,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yCAAyC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,oFAAoF;AACpF,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAiCD,iFAAiF;AACjF,eAAO,MAAM,cAAc,WAAY,yBAAyB;IAC9D,gBAAgB;IAChB,aAAa;IACb,gBAAgB;IAChB,OAAO;CACP,CAAC;AAEH,eAAO,MAAM,iBAAiB,SACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QACvB,MAAM,KACX,6BAYD,CAAC;AAKH,eAAO,MAAM,cAAc,eACb,6BAA6B,UACjC,yBAAyB,KAChC,OAYF,CAAC;AA0BF,MAAM,MAAM,iBAAiB,GAAG,qCAAqC,GACnE,sCAAsC,CAAC;AACzC,MAAM,MAAM,iBAAiB,GAAG,wCAAwC,GACtE,yCAAyC,CAAC;AAE5C,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,WAAY,yBAAyB,KAAG,iBAKpE,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,WAAY,yBAAyB,KAAG,iBAYrE,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.NotificationMatcher` — which notifications a PVE cluster sends to which targets.
|
|
3
|
+
*
|
|
4
|
+
* ★ THE HALF OF THE NOTIFICATION SYSTEM notification-target.ts LEAVES OPEN. A target is where a
|
|
5
|
+
* message CAN go; a matcher decides whether it DOES. A cluster ships one, `default-matcher`,
|
|
6
|
+
* routing everything to `mail-to-root`, and a target no matcher names receives nothing — so a
|
|
7
|
+
* declared webhook with no matcher is a delivery path that is never used, and looks finished.
|
|
8
|
+
*
|
|
9
|
+
* ★ ADOPTING THE BUILT-IN AS IT IS: declare what it holds and nothing more. MEASURED on PVE
|
|
10
|
+
* 9.2.11 (2026-09-22, `pvesh get /cluster/notifications/matchers/default-matcher`): mode `all`,
|
|
11
|
+
* one target `mail-to-root`, no rules, the comment "Route all notifications to mail-to-root".
|
|
12
|
+
* ```ts
|
|
13
|
+
* ProxmoxNotificationMatcher('default-matcher', {
|
|
14
|
+
* target: cluster, name: 'default-matcher', targets: ['mail-to-root'],
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
* That plans `noop` and deploys with no write (notification-matcher.test.ts). Leaving `comment`
|
|
18
|
+
* out leaves it alone; every rule field is compared — notification-matcher-form.ts says why.
|
|
19
|
+
*
|
|
20
|
+
* ★ WHAT THIS NEEDS, READ OFF THE CLUSTER'S OWN SCHEMA (generated/pve.ts source, 9.2.11):
|
|
21
|
+
* read GET matchers/{name} Mapping.Audit or Mapping.Modify on /mapping/notifications
|
|
22
|
+
* write POST, PUT, DELETE Mapping.Modify on /mapping/notifications
|
|
23
|
+
* The same pair as `Proxmox.NotificationTarget`, with that file's ⚠️ about granting it on `/`.
|
|
24
|
+
* ⛔ `Mapping.Use` IS NOT ENOUGH, though the COLLECTION GET accepts it: the per-object read this
|
|
25
|
+
* family makes lists only Audit and Modify (pve-manager API2/Cluster/Notifications.pm
|
|
26
|
+
* `get_matcher`, read at HEAD 2026-09-22 — corrected here; PR 95 said Use sufficed).
|
|
27
|
+
* ⛔ A MISSING READ PRIVILEGE LOOKS LIKE "ABSENT", not like a 403 — see the same file. For
|
|
28
|
+
* `default-matcher` that turns an adoption into a planned CREATE that PVE refuses as a
|
|
29
|
+
* duplicate: the noop below needs a read lease holding Mapping.Audit.
|
|
30
|
+
*/
|
|
31
|
+
import { Resource } from 'alchemy';
|
|
32
|
+
import * as Provider from 'alchemy/Provider';
|
|
33
|
+
import { type NotificationMatcherAttributes, type NotificationMatcherFields } from './notification-matcher-form.ts';
|
|
34
|
+
import { type PveRequirements, type WithTarget } from './resource.ts';
|
|
35
|
+
export interface NotificationMatcherProps extends NotificationMatcherFields, WithTarget {
|
|
36
|
+
}
|
|
37
|
+
export interface ProxmoxNotificationMatcher extends Resource<'Proxmox.NotificationMatcher', NotificationMatcherProps, NotificationMatcherAttributes, never, PveRequirements> {
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* ⚠️ NO `defaultRemovalPolicy: 'retain'`, for notification-target.ts's reason: a matcher holds no
|
|
41
|
+
* data, and a plan line is the only warning a delete gets. On `default-matcher` the delete
|
|
42
|
+
* REVERTS it to the shipped rule instead — see `origin` on the attributes.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ProxmoxNotificationMatcher: import("alchemy").ResourceClass<ProxmoxNotificationMatcher>;
|
|
45
|
+
export declare const ProxmoxNotificationMatcherProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxNotificationMatcher>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
46
|
+
//# sourceMappingURL=notification-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-matcher.d.ts","sourceRoot":"","sources":["../../src/proxmox/notification-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAK/B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,wBAAyB,SAAQ,yBAAyB,EAAE,UAAU;CAAG;AAE1F,MAAM,WAAW,0BAA2B,SAAQ,QAAQ,CAC1D,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,EAC7B,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,6DAEtC,CAAC;AAWF,eAAO,MAAM,kCAAkC,gJAI5C,CAAC"}
|
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
* reconcile POST + PUT Mapping.Modify on /mapping/notifications AND one of
|
|
40
40
|
* Sys.Audit / Sys.Modify / Sys.AccessNetwork on /
|
|
41
41
|
* delete DELETE Mapping.Modify on /mapping/notifications
|
|
42
|
-
*
|
|
43
|
-
* privileges — notably NOT the `Sys.Modify` a backup job costs.
|
|
42
|
+
* The provision role already carried Sys.Audit on `/` (see lxc.ts), so the delta was the two
|
|
43
|
+
* Mapping privileges — notably NOT the `Sys.Modify` a backup job costs. `PROVISION_PRIVILEGES`
|
|
44
|
+
* carries both on `/`: the baseline took the trade below knowingly.
|
|
44
45
|
* ⚠️ PREFER A SECOND ROLE GRANTED AT `/mapping/notifications` OVER WIDENING THE PROVISIONING ROLE
|
|
45
46
|
* ON `/`. `/mapping` also holds the PCI and USB passthrough maps, so Mapping.Modify at the root
|
|
46
47
|
* lets a credential scoped to make containers rewire somebody's hardware.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-target.d.ts","sourceRoot":"","sources":["../../src/proxmox/notification-target.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"notification-target.d.ts","sourceRoot":"","sources":["../../src/proxmox/notification-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,+FAA+F;AAC/F,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhF;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAwB,SAAQ,UAAU;IACzD,gFAAgF;IAChF,IAAI,EAAE,sBAAsB,CAAC;IAC7B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,iFAAiF;IACjF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,IAAI,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC;IACvC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAA0B,SAAQ,QAAQ,CACzD,4BAA4B,EAC5B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,yBAAyB,4DAErC,CAAC;AA2DF;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,+IAI3C,CAAC"}
|
|
@@ -24,7 +24,7 @@ export declare const guardPath: (live: PbsDatastoreAttributes, props: PbsDatasto
|
|
|
24
24
|
*
|
|
25
25
|
* ⚠️ AN UNDECLARED `backend` IS NOT A DIVERGENCE. Undeclared is unmanaged everywhere in this
|
|
26
26
|
* family, and this guard keeps that rule: it fires only when the declaration says something and
|
|
27
|
-
* PBS says something else. Adopting `
|
|
27
|
+
* PBS says something else. Adopting `offsite` without mentioning `backend` still plans noop.
|
|
28
28
|
*
|
|
29
29
|
* ⚠️ COMPARED AS A CANONICAL PROPERTY STRING, so `bucket=x,type=s3` and `type=s3,bucket=x` are the
|
|
30
30
|
* same backend and key order cannot deadlock a plan — the reason `samePath` trims a slash.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* `{"data":{"pong":true}}` against a Let's Encrypt certificate for CN=pbs.example.com;
|
|
11
11
|
* `/version` and `/config/datastore` answer 401 "authentication failed";
|
|
12
12
|
* `pbs.mgmt.example.com:8007` answers 401 and its certificate verifies too; and
|
|
13
|
-
* `/access/domains` lists the realms `pbs`, `pam` and an openid realm
|
|
13
|
+
* `/access/domains` lists the realms `pbs`, `pam` and an openid realm. So the
|
|
14
14
|
* prefix, the `{"data": …}` envelope and the strict-TLS story are PVE's exactly — and only the
|
|
15
15
|
* credential differs. There is no `pve` realm here; a PVE token cannot authenticate at all.
|
|
16
16
|
* The header differs too, which is why `pve()` exists rather than `pve()` being reused — see the
|
|
@@ -18,18 +18,18 @@
|
|
|
18
18
|
*
|
|
19
19
|
* ⛔ THERE IS NO OPENBAO MOUNT FOR PBS TODAY, SO NOTHING HERE CAN DEPLOY YET. `PbsTarget.mount` is
|
|
20
20
|
* a parameter for the reason `PveTarget.mount` is — the provider must be able to leave this
|
|
21
|
-
* estate — but the estate has nothing to put in it. MEASURED: under the
|
|
22
|
-
* `bao token capabilities` answers `deny` for `proxmox-
|
|
23
|
-
* `proxmox-
|
|
21
|
+
* estate — but the estate has nothing to put in it. MEASURED: under the agent's approle,
|
|
22
|
+
* `bao token capabilities` answers `deny` for `proxmox-c1/creds/{read,provision}`,
|
|
23
|
+
* `proxmox-c2/creds/read` and every `…pbs…` path tried, and `sys/mounts` is 403 — so I could
|
|
24
24
|
* not enumerate the mounts to PROVE a PBS one is absent. That it is absent is the brief's
|
|
25
25
|
* statement, not my measurement.
|
|
26
26
|
* ★ WHAT THE MOUNT MUST VEND, so `mint()` is reused unchanged: `bao read -format=json
|
|
27
27
|
* <mount>/creds/<role>` answering `{"data":{"token_id":…,"secret":…}}`, where `token_id` is a
|
|
28
28
|
* PBS token id `user@realm!tokenname` — `hf-read@pbs!…` and `hf-provision@pbs!…`. Same short,
|
|
29
|
-
* non-renewable lease contract as `proxmox-
|
|
29
|
+
* non-renewable lease contract as `proxmox-c1`.
|
|
30
30
|
* ⚠️ PBS HAS NO DYNAMIC-SECRETS PLUGIN OF ITS OWN — OpenBao's Proxmox support is a PVE thing — so
|
|
31
31
|
* the mount is a small custom vendor against `POST /access/users/{id}/token/{name}`. Say which
|
|
32
|
-
* mount and role you needed rather than reaching for the read-only `
|
|
32
|
+
* mount and role you needed rather than reaching for the read-only `metrics@pbs` token on
|
|
33
33
|
* the kv shelf: widening that would delete the outer lock for every reader of the shelf.
|
|
34
34
|
*
|
|
35
35
|
* ⛔ A DATASTORE HOLDS THE BACKUPS, SO `retain` IS THE DEFAULT AND `delete` IS FULLY IMPLEMENTED —
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* ⛔ NO SECRET IS A PROP OR AN ATTRIBUTE, AND THIS FAMILY IS CLEAN BY CONSTRUCTION: a datastore
|
|
48
48
|
* section holds no password, token or key — encryption keys belong to the backup CLIENT. Every
|
|
49
49
|
* field below is a name, a path, a schedule, a count or a policy string, all of which are safe in
|
|
50
|
-
* a state store Alchemy writes UNENCRYPTED and this estate
|
|
50
|
+
* a state store Alchemy writes UNENCRYPTED and this estate copies to a backup guest nightly.
|
|
51
51
|
*/
|
|
52
52
|
import { Resource } from 'alchemy';
|
|
53
53
|
import * as Provider from 'alchemy/Provider';
|
|
@@ -116,11 +116,11 @@ export interface PbsDatastoreProps {
|
|
|
116
116
|
* `datastore create --help` offers `--backend`, `datastore update --help` does NOT, and
|
|
117
117
|
* `backend` is absent from update's `--delete` enum. PBS will not move a datastore between
|
|
118
118
|
* local disk and object storage, ever.
|
|
119
|
-
* ★ IT IS A PROP AT ALL BECAUSE THE ESTATE HAS ONE. `
|
|
120
|
-
* `type=s3,client=cloudflare-r2,bucket=
|
|
119
|
+
* ★ IT IS A PROP AT ALL BECAUSE THE ESTATE HAS ONE. `offsite` holds
|
|
120
|
+
* `type=s3,client=cloudflare-r2,bucket=example-pbs`. Leaving it undeclared would still plan
|
|
121
121
|
* noop — undeclared is unmanaged here — but the declaration would then describe a LOCAL
|
|
122
122
|
* datastore, and anyone recreating from it would get exactly that: an empty directory on the
|
|
123
|
-
*
|
|
123
|
+
* host's disk where the offsite copy used to be, behind a green plan the whole way.
|
|
124
124
|
* ⚠️ A property string. Compared canonically, so key order is never a diff.
|
|
125
125
|
*/
|
|
126
126
|
backend?: string;
|