@homeflare/alchemy 0.31.2 → 0.33.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 +39 -3
- package/dist/grafana/dashboard-model.d.ts +77 -0
- package/dist/grafana/dashboard-model.d.ts.map +1 -0
- package/dist/grafana/dashboard.d.ts +98 -0
- package/dist/grafana/dashboard.d.ts.map +1 -0
- package/dist/grafana/folder.d.ts +100 -0
- package/dist/grafana/folder.d.ts.map +1 -0
- package/dist/grafana/index.d.ts +9 -8
- package/dist/grafana/index.d.ts.map +1 -1
- package/dist/grafana/index.js +158 -6
- package/dist/grafana/index.js.map +8 -4
- package/dist/grafana/providers.d.ts +1 -1
- package/dist/grafana/providers.d.ts.map +1 -1
- package/dist/grafana/provisioned.d.ts +11 -0
- package/dist/grafana/provisioned.d.ts.map +1 -0
- package/dist/linux/container-form.d.ts +129 -0
- package/dist/linux/container-form.d.ts.map +1 -0
- package/dist/linux/container-generator.d.ts +74 -0
- package/dist/linux/container-generator.d.ts.map +1 -0
- package/dist/linux/container-handlers.d.ts +15 -0
- package/dist/linux/container-handlers.d.ts.map +1 -0
- package/dist/linux/container-lifecycle.d.ts +37 -0
- package/dist/linux/container-lifecycle.d.ts.map +1 -0
- package/dist/linux/container-preflight.d.ts +34 -0
- package/dist/linux/container-preflight.d.ts.map +1 -0
- package/dist/linux/container-secrets.d.ts +37 -0
- package/dist/linux/container-secrets.d.ts.map +1 -0
- package/dist/linux/container-settle.d.ts +34 -0
- package/dist/linux/container-settle.d.ts.map +1 -0
- package/dist/linux/container.d.ts +30 -0
- package/dist/linux/container.d.ts.map +1 -0
- package/dist/linux/fake-quadlet.d.ts +25 -0
- package/dist/linux/fake-quadlet.d.ts.map +1 -0
- package/dist/linux/index.d.ts +6 -3
- package/dist/linux/index.d.ts.map +1 -1
- package/dist/linux/index.js +641 -268
- package/dist/linux/index.js.map +15 -7
- package/dist/linux/providers.d.ts +1 -1
- package/dist/linux/providers.d.ts.map +1 -1
- package/dist/linux/systemctl.d.ts +7 -1
- package/dist/linux/systemctl.d.ts.map +1 -1
- package/dist/opnsense/alias-form.d.ts +29 -0
- package/dist/opnsense/alias-form.d.ts.map +1 -0
- package/dist/opnsense/alias.d.ts +81 -0
- package/dist/opnsense/alias.d.ts.map +1 -0
- package/dist/opnsense/category-form.d.ts +10 -0
- package/dist/opnsense/category-form.d.ts.map +1 -0
- package/dist/opnsense/category.d.ts +58 -0
- package/dist/opnsense/category.d.ts.map +1 -0
- package/dist/opnsense/credentials.d.ts +36 -0
- package/dist/opnsense/credentials.d.ts.map +1 -0
- package/dist/opnsense/fake-opnsense.d.ts +51 -0
- package/dist/opnsense/fake-opnsense.d.ts.map +1 -0
- package/dist/opnsense/group-form.d.ts +11 -0
- package/dist/opnsense/group-form.d.ts.map +1 -0
- package/dist/opnsense/group.d.ts +62 -0
- package/dist/opnsense/group.d.ts.map +1 -0
- package/dist/opnsense/index.d.ts +16 -0
- package/dist/opnsense/index.d.ts.map +1 -0
- package/dist/opnsense/index.js +211 -0
- package/dist/opnsense/index.js.map +20 -0
- package/dist/opnsense/policy.d.ts +20 -0
- package/dist/opnsense/policy.d.ts.map +1 -0
- package/dist/opnsense/providers.d.ts +13 -0
- package/dist/opnsense/providers.d.ts.map +1 -0
- package/dist/opnsense/resource.d.ts +74 -0
- package/dist/opnsense/resource.d.ts.map +1 -0
- package/dist/opnsense/wire.d.ts +39 -0
- package/dist/opnsense/wire.d.ts.map +1 -0
- package/dist/proxmox/index.js +170 -80
- package/dist/proxmox/index.js.map +9 -7
- package/dist/proxmox/storage-form.d.ts +54 -0
- package/dist/proxmox/storage-form.d.ts.map +1 -0
- package/dist/proxmox/storage-wire.d.ts +56 -0
- package/dist/proxmox/storage-wire.d.ts.map +1 -0
- package/dist/proxmox/storage.d.ts +55 -60
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool-form.d.ts +124 -0
- package/dist/proxmox/zfs-pool-form.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool-wire.d.ts +78 -0
- package/dist/proxmox/zfs-pool-wire.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool.d.ts +27 -52
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/unifi/fake-unifi.d.ts +48 -0
- package/dist/unifi/fake-unifi.d.ts.map +1 -0
- package/dist/unifi/firewall-zone-form.d.ts +40 -0
- package/dist/unifi/firewall-zone-form.d.ts.map +1 -0
- package/dist/unifi/firewall-zone.d.ts +50 -0
- package/dist/unifi/firewall-zone.d.ts.map +1 -0
- package/dist/unifi/index.d.ts +13 -0
- package/dist/unifi/index.d.ts.map +1 -0
- package/dist/unifi/index.js +190 -0
- package/dist/unifi/index.js.map +16 -0
- package/dist/unifi/network-form.d.ts +67 -0
- package/dist/unifi/network-form.d.ts.map +1 -0
- package/dist/unifi/network.d.ts +67 -0
- package/dist/unifi/network.d.ts.map +1 -0
- package/dist/unifi/policy.d.ts +20 -0
- package/dist/unifi/policy.d.ts.map +1 -0
- package/dist/unifi/providers.d.ts +13 -0
- package/dist/unifi/providers.d.ts.map +1 -0
- package/dist/unifi/resource.d.ts +64 -0
- package/dist/unifi/resource.d.ts.map +1 -0
- package/docs/grafana-folder-dashboard.md +145 -0
- package/docs/grafana.md +11 -10
- package/docs/opnsense.md +146 -0
- package/docs/provider-standard.md +1 -1
- package/docs/quadlet-container.md +132 -0
- package/docs/unifi.md +93 -0
- package/docs/upstream-conformance.md +9 -4
- package/package.json +12 -2
- package/src/grafana/dashboard-model.test.ts +111 -0
- package/src/grafana/dashboard-model.ts +111 -0
- package/src/grafana/dashboard.test.ts +232 -0
- package/src/grafana/dashboard.ts +161 -0
- package/src/grafana/folder.test.ts +212 -0
- package/src/grafana/folder.ts +161 -0
- package/src/grafana/index.ts +21 -8
- package/src/grafana/providers.ts +7 -1
- package/src/grafana/provisioned.ts +49 -0
- package/src/linux/container-fixture.test.ts +92 -0
- package/src/linux/container-form.ts +243 -0
- package/src/linux/container-generator.test.ts +195 -0
- package/src/linux/container-generator.ts +113 -0
- package/src/linux/container-handlers.ts +73 -0
- package/src/linux/container-lifecycle.test.ts +209 -0
- package/src/linux/container-lifecycle.ts +217 -0
- package/src/linux/container-preflight.ts +97 -0
- package/src/linux/container-secrets.test.ts +171 -0
- package/src/linux/container-secrets.ts +112 -0
- package/src/linux/container-settle.ts +97 -0
- package/src/linux/container.ts +60 -0
- package/src/linux/fake-quadlet.ts +127 -0
- package/src/linux/index.ts +11 -3
- package/src/linux/providers.ts +2 -0
- package/src/linux/systemctl.ts +9 -0
- package/src/opnsense/alias-form.test.ts +89 -0
- package/src/opnsense/alias-form.ts +53 -0
- package/src/opnsense/alias.test.ts +203 -0
- package/src/opnsense/alias.ts +117 -0
- package/src/opnsense/category-form.test.ts +54 -0
- package/src/opnsense/category-form.ts +20 -0
- package/src/opnsense/category.test.ts +138 -0
- package/src/opnsense/category.ts +82 -0
- package/src/opnsense/credentials.ts +41 -0
- package/src/opnsense/fake-opnsense.ts +79 -0
- package/src/opnsense/group-form.test.ts +75 -0
- package/src/opnsense/group-form.ts +25 -0
- package/src/opnsense/group.test.ts +141 -0
- package/src/opnsense/group.ts +78 -0
- package/src/opnsense/index.ts +45 -0
- package/src/opnsense/policy.ts +49 -0
- package/src/opnsense/providers.ts +21 -0
- package/src/opnsense/resource.ts +146 -0
- package/src/opnsense/wire.ts +56 -0
- package/src/opnsense/write-refusal.test.ts +148 -0
- package/src/proxmox/constraints-host-forms.test.ts +1 -1
- package/src/proxmox/storage-form.ts +144 -0
- package/src/proxmox/storage-read-failure.test.ts +214 -0
- package/src/proxmox/storage-wire.ts +124 -0
- package/src/proxmox/storage.ts +165 -175
- package/src/proxmox/zfs-pool-adopt.test.ts +26 -0
- package/src/proxmox/zfs-pool-form.ts +236 -0
- package/src/proxmox/zfs-pool-read-failure.test.ts +118 -0
- package/src/proxmox/zfs-pool-wire.ts +132 -0
- package/src/proxmox/zfs-pool.ts +106 -76
- package/src/unifi/fake-unifi.ts +60 -0
- package/src/unifi/firewall-zone-form.ts +65 -0
- package/src/unifi/firewall-zone.test.ts +88 -0
- package/src/unifi/firewall-zone.ts +66 -0
- package/src/unifi/index.ts +26 -0
- package/src/unifi/network-form.ts +207 -0
- package/src/unifi/network.test.ts +209 -0
- package/src/unifi/network.ts +83 -0
- package/src/unifi/policy.ts +37 -0
- package/src/unifi/providers.ts +15 -0
- package/src/unifi/resource.test.ts +133 -0
- package/src/unifi/resource.ts +131 -0
- package/dist/proxmox/zfs-pool-spec.d.ts +0 -27
- package/dist/proxmox/zfs-pool-spec.d.ts.map +0 -1
- package/dist/proxmox/zfs-pool-write.d.ts +0 -128
- package/dist/proxmox/zfs-pool-write.d.ts.map +0 -1
- package/src/proxmox/zfs-pool-spec.ts +0 -76
- package/src/proxmox/zfs-pool-write.ts +0 -196
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.Storage`'s WRITE side: the locator bag, the create/update forms, and the translation
|
|
3
|
+
* into distilled's own typed request shape. Split out of storage-wire.ts (2026-09-24) to keep
|
|
4
|
+
* both files under the 250-line cap — the api-token.ts/api-token-form.ts seam, same reasoning.
|
|
5
|
+
*/
|
|
6
|
+
import type * as storage from '@distilled.cloud/proxmox/storage';
|
|
7
|
+
import type { StorageProps } from './storage.ts';
|
|
8
|
+
export declare const STORAGE_CREATE = "pve:POST /storage";
|
|
9
|
+
export declare const STORAGE_UPDATE = "pve:PUT /storage/{storage}";
|
|
10
|
+
/**
|
|
11
|
+
* The create-time, plugin-specific half of a storage: `dir` wants `path`, `nfs` `server`+`export`,
|
|
12
|
+
* `cifs` `server`+`share`, `pbs` `server`+`datastore`, `rbd` `pool`+`monhost`, `lvmthin`
|
|
13
|
+
* `vgname`+`thinpool`, `zfspool` `pool`. A bag rather than two dozen named fields, because PVE
|
|
14
|
+
* ships about that many plugins and each brings its own locator. ⚠️ Never diffed — see `matches`
|
|
15
|
+
* (storage-wire.ts).
|
|
16
|
+
*
|
|
17
|
+
* ⛔ `password`, `keyring` AND `encryption-key` ARE `never` ON PURPOSE — see the ⛔ in storage.ts's
|
|
18
|
+
* header. POSITIVE-CONTROLLED rather than assumed: all three are a compile error, `TS2322: Type
|
|
19
|
+
* 'string' is not assignable to type 'undefined'`.
|
|
20
|
+
* ⚠️ EVERY KEY IN THIS BAG GOES THROUGH `underscored` (below) BEFORE IT REACHES DISTILLED — see
|
|
21
|
+
* that function's own header. A caller writes PVE's own field names here (`fs-name`, matching
|
|
22
|
+
* `pvesm`/pve-docs), never distilled's renamed ones.
|
|
23
|
+
*/
|
|
24
|
+
export type StorageLocator = Record<string, string> & {
|
|
25
|
+
password?: never;
|
|
26
|
+
keyring?: never;
|
|
27
|
+
'encryption-key'?: never;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* ⚠️ THE BAG IS SPREAD FIRST SO NOTHING IN IT CAN SHADOW A FIELD THIS RESOURCE MANAGES. A stray
|
|
31
|
+
* `storage` or `type` in a locator would otherwise rename the object being created, and PVE
|
|
32
|
+
* would build the wrong thing under a name Alchemy then records as the declared one.
|
|
33
|
+
* ★ HYPHENATED, THE WIRE SHAPE — `guardWrite` (storage.ts) checks this form directly; see
|
|
34
|
+
* `underscored`'s header for why translating it here would blind that check.
|
|
35
|
+
*/
|
|
36
|
+
export declare const createForm: (props: StorageProps) => Record<string, string>;
|
|
37
|
+
/**
|
|
38
|
+
* ⚠️ `type` AND THE LOCATOR ARE NEVER SENT ON UPDATE — PVE's PUT refuses the create-only fields
|
|
39
|
+
* (`PutStorageRequest` has no `type`, and the plugin locator fields it does carry are for a
|
|
40
|
+
* different purpose: moving an existing volume, not re-pointing the definition), and comparing
|
|
41
|
+
* either could only plan an update that no write can apply: a plan that reports work forever.
|
|
42
|
+
*/
|
|
43
|
+
export declare const updateForm: (props: StorageProps) => Record<string, string>;
|
|
44
|
+
/**
|
|
45
|
+
* The actual `createStorage` call body — `createForm`, translated once. See `underscored`.
|
|
46
|
+
* ⚠️ `as unknown as` — same load-bearing cast as `locatorForm`'s own ⚠️: a plain
|
|
47
|
+
* `Record<string,string>` does not structurally overlap a named-field interface enough for
|
|
48
|
+
* TypeScript to accept a direct cast, even though every field it carries fits one of the
|
|
49
|
+
* interface's own (all-optional-but-two) properties.
|
|
50
|
+
*/
|
|
51
|
+
export declare const toDistilledCreate: (props: StorageProps) => storage.CreateStorageRequest;
|
|
52
|
+
/** The actual `putStorage` call body — `updateForm`, translated once. See `toDistilledCreate`. */
|
|
53
|
+
export declare const toDistilledUpdate: (props: StorageProps) => storage.PutStorageRequest;
|
|
54
|
+
//# sourceMappingURL=storage-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage-form.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,KAAK,OAAO,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAClD,eAAO,MAAM,cAAc,+BAA+B,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;IACpD,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,gBAAgB,CAAC,EAAE,KAAK,CAAC;CAC1B,CAAC;AA4EF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,UAAW,YAAY,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAKpE,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,UAAW,YAAY,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAGpE,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,UAAW,YAAY,KAAG,OAAO,CAAC,oBACW,CAAC;AAE5E,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,UAAW,YAAY,KAAG,OAAO,CAAC,iBACQ,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import type { StorageProps } from './storage.ts';
|
|
3
|
+
import { UNREADABLE, type Unreadable } from './unreadable-read.ts';
|
|
4
|
+
export interface StorageAttributes {
|
|
5
|
+
storage: string;
|
|
6
|
+
/** Reported so a plan can show what it points at; never diffed — storage.ts's `type` doc. */
|
|
7
|
+
type: string;
|
|
8
|
+
content: string;
|
|
9
|
+
nodes: string;
|
|
10
|
+
disable: boolean;
|
|
11
|
+
shared: boolean;
|
|
12
|
+
preallocation: string;
|
|
13
|
+
'prune-backups': string;
|
|
14
|
+
comment: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const sameList: (declared: string | undefined, live: string) => boolean;
|
|
17
|
+
export declare const same: <T>(declared: T | undefined, live: T) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* ★ Default `read` role WOULD READ 403 — see storage.ts's header on why this family reads with
|
|
20
|
+
* `provision`, the same as acl.ts and unlike group.ts/user.ts.
|
|
21
|
+
* ⛔ `{ storage: props.storage }`, NEVER THE WHOLE `props` — user.ts/group.ts's own ⛔, the
|
|
22
|
+
* kit 0.31.1 regression: `GetStorageRequest`'s schema declares only `storage` (a path label);
|
|
23
|
+
* any OTHER key on the object passed to `storage.getStorage` is treated by distilled's
|
|
24
|
+
* `buildRequest` as an "unknown key" and JSON-encoded onto this bodyless GET, which a stricter
|
|
25
|
+
* fetch client refuses outright.
|
|
26
|
+
* ⛔ A MISSING STORAGE IS A 500, NOT A 404 — MEASURED against the live cluster (TB4, 2026-09-24,
|
|
27
|
+
* admin lane, read-only probe): `GET /storage/hf-measure-nonexistent-probe` answers
|
|
28
|
+
* `{"message":"storage 'hf-measure-nonexistent-probe' does not exist\n","data":null}` at HTTP
|
|
29
|
+
* 500 — the SAME shape as user.ts's and group.ts's own measured 500, not the clean
|
|
30
|
+
* `{"data":null}` at 200 this file's pre-migration comment assumed for every read failure
|
|
31
|
+
* (client.ts's `pveOperations.read` could not tell the two apart either way, folding both into
|
|
32
|
+
* "absent"). `readStorage` below (used by `read`/`reconcile`) still folds it, exactly as
|
|
33
|
+
* `readUser`/`readGroup` do, so a brand-new declaration can still be created; `readStorageOrFail`
|
|
34
|
+
* (used by `diff`) does not.
|
|
35
|
+
*/
|
|
36
|
+
export declare const readStorageOrFail: (props: StorageProps) => Effect.Effect<typeof UNREADABLE | StorageAttributes, import("@distilled.cloud/caddy").BadGateway | import("@distilled.cloud/caddy").BadRequest | import("@distilled.cloud/caddy").ConfigError | Error | import("@distilled.cloud/caddy").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | import("@distilled.cloud/proxmox/Errors").ParameterVerificationFailed | import("@distilled.cloud/proxmox/Errors").ProxmoxParseError | import("./distilled-pve.ts").PveClusterExhausted | import("@distilled.cloud/caddy").ServiceUnavailable | import("effect/Cause").TimeoutError | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/proxmox/Errors").UnknownProxmoxError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
37
|
+
/**
|
|
38
|
+
* ⛔ FOLDS A GENUINE READ FAILURE TO `undefined` TOO, AND ONLY `read`/`reconcile` MAY USE IT —
|
|
39
|
+
* user.ts's `readUser` has the full reasoning. Safe here because a wrongful fold costs at most a
|
|
40
|
+
* redundant `createStorage` POST, which PVE refuses loudly ("storage ID already exists") rather
|
|
41
|
+
* than silently corrupting anything.
|
|
42
|
+
* ⛔ `storage.ts`'s `diff` does **NOT** use this — the SAME bug class as the credential denial fix:
|
|
43
|
+
* folding a TRANSIENT failure into "absent" at PLAN TIME is what forced a false `update` with
|
|
44
|
+
* nothing compared (the ORIGINAL cries-wolf bug this family shipped with, per unreadable-read.ts's
|
|
45
|
+
* own header). `diff` calls `readStorageOrFail` directly, so a genuine failure there propagates
|
|
46
|
+
* and fails the whole plan loudly instead.
|
|
47
|
+
*/
|
|
48
|
+
export declare const readStorage: (props: StorageProps) => Effect.Effect<typeof UNREADABLE | StorageAttributes | undefined, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
49
|
+
/** `read`/`reconcile` return `Attributes | undefined`; only `diff` tells `UNREADABLE` apart. */
|
|
50
|
+
export declare const dropUnreadable: (live: StorageAttributes | Unreadable | undefined) => StorageAttributes | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* ⚠️ `type` IS NEVER COMPARED — CREATE-ONLY (`storage.ts`'s own `StorageProps.type` doc), so a
|
|
53
|
+
* changed declaration plans `noop` on it, same as before the migration.
|
|
54
|
+
*/
|
|
55
|
+
export declare const matches: (attributes: StorageAttributes, props: StorageProps) => boolean;
|
|
56
|
+
//# sourceMappingURL=storage-wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-wire.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage-wire.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAoB,MAAM,sBAAsB,CAAC;AAGrF,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAmBD,eAAO,MAAM,QAAQ,aAAc,MAAM,GAAG,SAAS,QAAQ,MAAM,YACN,CAAC;AAE9D,eAAO,MAAM,IAAI,GAAI,CAAC,YAAY,CAAC,GAAG,SAAS,QAAQ,CAAC,YACX,CAAC;AAwB9C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,iBAAiB,UAAW,YAAY,m2BAGyC,CAAC;AAE/F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,UAAW,YAAY,kIACuB,CAAC;AAEvE,gGAAgG;AAChG,eAAO,MAAM,cAAc,SAAU,iBAAiB,GAAG,UAAU,GAAG,SAAS,kCACvC,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,OAAO,eAAgB,iBAAiB,SAAS,YAAY,YAOjC,CAAC"}
|
|
@@ -12,19 +12,48 @@
|
|
|
12
12
|
* ⛔ ONE PRIVILEGE COVERS THIS WHOLE FAMILY AND IT IS `Datastore.Allocate` ON `/storage`. Create,
|
|
13
13
|
* update, delete AND the single-object read all check it — `Datastore.Audit` is not enough for
|
|
14
14
|
* any of them. MEASURED: `GET /storage/local` under the auditor-scoped `read` lease answers 403
|
|
15
|
-
* "Permission check failed (/storage/local, Datastore.Allocate)".
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
15
|
+
* "Permission check failed (/storage/local, Datastore.Allocate)". So this family reads with
|
|
16
|
+
* `provision`, same as acl.ts and unlike group.ts/user.ts — `provision-baseline.ts` carries
|
|
17
|
+
* `Datastore.Allocate`.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ ON A LANE WHOSE CREDENTIAL CANNOT MINT `provision` (the agent's `read`-role lane), THIS
|
|
20
|
+
* FAMILY SHIPPED WITH THE ORIGINAL CRIES-WOLF BUG — measured live, 2026-09-24: `bun run plan`
|
|
21
|
+
* showed all 5 storages (`cephfs-tb4`, `cephtb4`, `local`, `local-zfs`, `pbs`) as `update`, with
|
|
22
|
+
* no warning, because `client.ts`'s `pveOperations.read` folded the REFUSED mint into `undefined`
|
|
23
|
+
* the same way it folds a genuine absence — `unreadable-read.ts`'s own header names this family
|
|
24
|
+
* by name as one of the two the fix was written for. Migrated here the way acl.ts/group.ts/
|
|
25
|
+
* user.ts were (kit 0.31.1/0.31.2): a refused `provision` mint now reports `noop` with a logged
|
|
26
|
+
* warning instead of forcing `update` with nothing compared.
|
|
27
|
+
*
|
|
28
|
+
* ⛔ A MISSING STORAGE IS A 500, NOT A 404 — MEASURED against the live cluster (TB4, 2026-09-24,
|
|
29
|
+
* admin lane, read-only `GET /storage/hf-measure-nonexistent-probe`): `{"message":"storage
|
|
30
|
+
* '...' does not exist\n"}` at HTTP 500. `storage-wire.ts`'s `readStorageOrFail`/`readStorage`
|
|
31
|
+
* carry the same dual-path split as user.ts/group.ts for exactly this reason — see that file.
|
|
32
|
+
*
|
|
33
|
+
* ⛔ `maxfiles` IS GONE — A MEASURED `@distilled.cloud/proxmox` SDK GAP, NOT A SCOPE TRIM. The
|
|
34
|
+
* vendor's generated `CreateStorageRequest`/`PutStorageRequest` (storage-wire.ts's `mutable`)
|
|
35
|
+
* have no `maxfiles` field at all — grepped across the whole package, not just this endpoint —
|
|
36
|
+
* where `prune-backups`, its documented replacement, is present and correctly wired
|
|
37
|
+
* (`T.Body("prune-backups")` on the generated `prune_backups` property). Silently dropping a
|
|
38
|
+
* declared `maxfiles` value would have been the same class of bug this whole migration exists to
|
|
39
|
+
* close: a write that looks like it landed and did not. So `maxfiles?: never` below, the same
|
|
40
|
+
* COMPILE-ERROR treatment `StorageLocator`'s `password`/`keyring`/`encryption-key` already get —
|
|
41
|
+
* a declaration that sets it is refused before a plan ever runs, not silently ignored by one.
|
|
42
|
+
* None of this cluster's 5 live storages set it (measured, live response bodies below). If a
|
|
43
|
+
* consumer genuinely needs it, the fix belongs in `@distilled.cloud/proxmox`'s generator, not a
|
|
44
|
+
* hand-rolled bypass here — see the provider-walk-down doctrine on vendor gaps.
|
|
45
|
+
*
|
|
46
|
+
* ⛔ EVERY HYPHENATED PVE FIELD NAME BECOMES AN UNDERSCORE IN DISTILLED'S GENERATED TYPESCRIPT —
|
|
47
|
+
* MEASURED across the WHOLE generated `CreateStorageRequest`/`PutStorageRequest`, not one field:
|
|
48
|
+
* `prune-backups` -> `prune_backups`, `fs-name` -> `fs_name`, `max-protected-backups` ->
|
|
49
|
+
* `max_protected_backups`, and so on for every plugin field `StorageLocator`'s free-form bag can
|
|
50
|
+
* carry (`cephfs-tb4`'s own live locator uses `fs-name`, measured below). No other family
|
|
51
|
+
* migrated so far has a hyphenated field at all, so this is new here. `storage-wire.ts`'s
|
|
52
|
+
* `underscored` translates a form BUILT with PVE's own names into distilled's shape, but ONLY
|
|
53
|
+
* for the actual SDK call (`toDistilledCreate`/`toDistilledUpdate`) — never for the form
|
|
54
|
+
* `guardWrite` checks (`createForm`/`updateForm`), which the generated vendor-constraint tables
|
|
55
|
+
* key by PVE's own hyphenated names. Running the translation before that check would make it
|
|
56
|
+
* silently stop inspecting the very fields it exists to catch.
|
|
28
57
|
*
|
|
29
58
|
* ⛔ THERE IS NO `password` PROP AND THERE MUST NEVER BE ONE. PBS and CIFS storages take one at
|
|
30
59
|
* create time, and Alchemy persists attributes UNENCRYPTED — a secret in props is one careless
|
|
@@ -33,25 +62,20 @@
|
|
|
33
62
|
* a COMPILE ERROR rather than a leak somebody finds in a backup months later. Set it once out of
|
|
34
63
|
* band (`pvesm set <id> --password`); PVE never returns it on read, so nothing here could diff it
|
|
35
64
|
* anyway.
|
|
65
|
+
*
|
|
66
|
+
* ★ MIGRATED OFF `client.ts`'s generic `pve()`/`pveHandlers` (`resource.ts`) ONTO
|
|
67
|
+
* `@distilled.cloud/proxmox`'s typed `storage.getStorage`/`createStorage`/`putStorage`/
|
|
68
|
+
* `deleteStorage` (2026-09-24, decision 43's proxmox walk-down, nodes/storage sub-area, first
|
|
69
|
+
* resource). `distilled-pve.ts`'s `runPve` replaces `pve()`; the cries-wolf fix is wired the
|
|
70
|
+
* same way as acl.ts/group.ts/user.ts.
|
|
36
71
|
*/
|
|
37
72
|
import { Resource } from 'alchemy';
|
|
38
73
|
import * as Provider from 'alchemy/Provider';
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* ships about that many plugins and each brings its own locator. ⚠️ Never diffed — see `matches`.
|
|
45
|
-
*
|
|
46
|
-
* ⛔ `password`, `keyring` AND `encryption-key` ARE `never` ON PURPOSE — see the ⛔ in the header.
|
|
47
|
-
* POSITIVE-CONTROLLED rather than assumed: all three are a compile error, `TS2322: Type 'string'
|
|
48
|
-
* is not assignable to type 'undefined'`.
|
|
49
|
-
*/
|
|
50
|
-
export type StorageLocator = Record<string, string> & {
|
|
51
|
-
password?: never;
|
|
52
|
-
keyring?: never;
|
|
53
|
-
'encryption-key'?: never;
|
|
54
|
-
};
|
|
74
|
+
import type { PveRequirements, WithTarget } from './resource-spec.ts';
|
|
75
|
+
import type { StorageAttributes } from './storage-wire.ts';
|
|
76
|
+
import type { StorageLocator } from './storage-form.ts';
|
|
77
|
+
/** Re-exported so a caller of this file still finds them here, unchanged by the split below. */
|
|
78
|
+
export type { StorageAttributes, StorageLocator };
|
|
55
79
|
export interface StorageProps extends WithTarget {
|
|
56
80
|
/** PVE's primary key, cluster-wide — the string an LXC's `storage` and `ostemplate` name. */
|
|
57
81
|
storage: string;
|
|
@@ -81,41 +105,12 @@ export interface StorageProps extends WithTarget {
|
|
|
81
105
|
*/
|
|
82
106
|
'prune-backups'?: string;
|
|
83
107
|
comment?: string;
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
* other. `0` means UNLIMITED to PVE rather than "unset", hence the `-1` in the attribute.
|
|
87
|
-
*/
|
|
88
|
-
maxfiles?: number;
|
|
89
|
-
}
|
|
90
|
-
export interface StorageAttributes {
|
|
91
|
-
storage: string;
|
|
92
|
-
/** Reported so a plan can show what it points at; never diffed — see `type` above. */
|
|
93
|
-
type: string;
|
|
94
|
-
content: string;
|
|
95
|
-
nodes: string;
|
|
96
|
-
disable: boolean;
|
|
97
|
-
shared: boolean;
|
|
98
|
-
preallocation: string;
|
|
99
|
-
'prune-backups': string;
|
|
100
|
-
comment: string;
|
|
101
|
-
/** ⚠️ `-1` means "no maxfiles in storage.cfg". `0` is a real setting meaning unlimited. */
|
|
102
|
-
maxfiles: number;
|
|
108
|
+
/** ⛔ REMOVED — a measured `@distilled.cloud/proxmox` gap, not a scope trim. See the header. */
|
|
109
|
+
maxfiles?: never;
|
|
103
110
|
}
|
|
104
111
|
export interface ProxmoxStorage extends Resource<'Proxmox.Storage', StorageProps, StorageAttributes, never, PveRequirements> {
|
|
105
112
|
}
|
|
106
113
|
/** ★ `retain` by default — a storage holding volumes cannot be rebuilt. See the ★ in resource.ts. */
|
|
107
114
|
export declare const ProxmoxStorage: import("alchemy").ResourceClass<ProxmoxStorage>;
|
|
108
|
-
/**
|
|
109
|
-
* ⛔ EMPTY, AND HERE IT MATTERS MORE THAN ANYWHERE ELSE IN THIS PACKAGE. `GET /storage`
|
|
110
|
-
* returns every definition on the cluster, `local` and `local-lvm`/`local-zfs` included:
|
|
111
|
-
* the installer's own, that every guest's rootfs sits on. Adopting those would put
|
|
112
|
-
* Alchemy one `delete` away from the cluster's disks. Adoption is explicit, always.
|
|
113
|
-
|
|
114
|
-
*
|
|
115
|
-
* ⚠️ THIS REMOVES THE DEFINITION, NOT THE DATA. PVE drops the section from storage.cfg and
|
|
116
|
-
* leaves the volumes on disk untouched — so the loss is not the bytes, it is that guests
|
|
117
|
-
* whose config still names this storage can no longer resolve their own disks.
|
|
118
|
-
|
|
119
|
-
*/
|
|
120
115
|
export declare const ProxmoxStorageProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxStorage>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
121
116
|
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAYtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,gGAAgG;AAChG,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAElD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,qGAAqG;AACrG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAEH,eAAO,MAAM,sBAAsB,oIAgGhC,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The create lane for `Proxmox.ZfsPool`: the form PVE wants, and the wait for the worker it forks.
|
|
3
|
+
* Split out of zfs-pool.ts (2026-09-24, the distilled migration) — same seam as before, now naming
|
|
4
|
+
* the write side the way storage.ts/storage-form.ts does.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ `createPool` TAKES THE READ AS A PARAMETER, GENERIC IN ITS ERROR AND REQUIREMENT CHANNELS,
|
|
7
|
+
* rather than importing the read module and coupling to its exact effect shape here. This file
|
|
8
|
+
* answers "how does a declaration become a pool on disks"; zfs-pool-wire.ts answers "what is a
|
|
9
|
+
* zpool, and when has it changed" — the caller (zfs-pool.ts) is what ties the two together, so
|
|
10
|
+
* this file cannot accidentally read the cluster differently from `diff`.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from 'effect/Effect';
|
|
13
|
+
import * as nodes from '@distilled.cloud/proxmox/nodes';
|
|
14
|
+
import type { ZfsPoolAttributes, ZfsPoolProps } from './zfs-pool.ts';
|
|
15
|
+
import type { NodesNodeDisksZfsPostParams } from './generated/pve.ts';
|
|
16
|
+
export declare const ZFS_POOL_CREATE = "pve:POST /nodes/{node}/disks/zfs";
|
|
17
|
+
/**
|
|
18
|
+
* PVE's layouts, generated (2026-09-23) from `NodesNodeDisksZfsPostParams['raidlevel']`
|
|
19
|
+
* (`generated/pve.ts`, manifest `pve-apidoc` pve-manager 9.2.11 sha256 9def8f13…) rather than
|
|
20
|
+
* hand-typed, per decision 9. ⚠️ Each has a minimum disk count PVE enforces, and `raid10` needs an
|
|
21
|
+
* even one. ⛔ THERE IS NO `stripe` IN THIS ENUM. n1's `speed` pool is one (decision 14 accepts it
|
|
22
|
+
* as-is): `ZfsPoolProps` (zfs-pool.ts) is why a stripe pool is declarable at all — as an
|
|
23
|
+
* ADOPT-ONLY declaration with no `raidlevel`, never as a full one.
|
|
24
|
+
*/
|
|
25
|
+
export type ZfsRaidLevel = NonNullable<NodesNodeDisksZfsPostParams['raidlevel']>;
|
|
26
|
+
/** Generated the same way, from `NodesNodeDisksZfsPostParams['compression']`. */
|
|
27
|
+
export type ZfsCompression = NonNullable<NodesNodeDisksZfsPostParams['compression']>;
|
|
28
|
+
/**
|
|
29
|
+
* ★ CHECKED AGAINST THE VENDOR TABLE ONLY FOR A FULL DECLARATION — decision 9's adopt-only mode.
|
|
30
|
+
* `guardCreate` (distilled-guard.ts's `guardWrite`) runs on the FIRST plan of ANY declaration and
|
|
31
|
+
* checks the create form's REQUIRED fields whenever this resolves to the real endpoint key — and
|
|
32
|
+
* PVE marks both `devices` and `raidlevel` required (codegen/constraints's `pve-nodes-disks`
|
|
33
|
+
* row). An adopt-only declaration deliberately omits both, so checking it would refuse a MATCHING
|
|
34
|
+
* adoption at plan time, before `read` ever ran. Returning `undefined` (no key) makes the guard a
|
|
35
|
+
* no-op for an adopt-only declaration, while a FULL one (`devices` and `raidlevel` both present)
|
|
36
|
+
* is checked exactly as before — zfs-pool.ts's `diff` passes this as the `guardWrite` endpoint.
|
|
37
|
+
* ⚠️ THE RUNTIME BACKSTOP IS `createPool` BELOW, NOT THIS FUNCTION. Skipping the plan-time guard
|
|
38
|
+
* for an adopt-only declaration does not skip the check that the pool is actually there —
|
|
39
|
+
* `createPool` refuses before any POST when the read comes back absent and either field is
|
|
40
|
+
* undefined, which is what a genuinely absent adopt-only declaration hits at apply time.
|
|
41
|
+
*/
|
|
42
|
+
export declare const zfsPoolCreateEndpoint: (props: ZfsPoolProps) => "pve:POST /nodes/{node}/disks/zfs" | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The wire form, PVE's own field names — what `guardWrite` (zfs-pool.ts) checks.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ `devices` IS JOINED IN DECLARED ORDER AND NOT PUT THROUGH `csv()` FROM values.ts. `csv` sorts,
|
|
47
|
+
* and PVE walks this list two at a time to pair `raid10` mirrors — MEASURED in the node's own
|
|
48
|
+
* `PVE::API2::Disks::ZFS` — so sorting it builds a different pool from the one declared. The list
|
|
49
|
+
* is never compared against anything, so it needs no normalisation, only faithful order.
|
|
50
|
+
* ⚠️ AN OMITTED OPTIONAL IS NOT SENT. PVE's own defaults (ashift 12, compression on) are then
|
|
51
|
+
* applied by the node. Sending a guessed default would be indistinguishable here — the values
|
|
52
|
+
* are unreadable afterwards either way — but it would put a number in the request that no
|
|
53
|
+
* declaration asked for, and this is the one call that writes to physical disks.
|
|
54
|
+
* ⚠️ `node` IS NOT IN THE FORM: it is the `nodes/{node}` path label, and a second copy could only
|
|
55
|
+
* disagree with it.
|
|
56
|
+
* ⚠️ EVERY KEY IS OMITTED WHEN ABSENT, `devices` AND `raidlevel` INCLUDED — not just the optional
|
|
57
|
+
* ones. An adopt-only declaration (both undefined, decision 9) must produce a form `createPool`'s
|
|
58
|
+
* own guard can inspect for their absence, and one this function itself does not throw building.
|
|
59
|
+
*/
|
|
60
|
+
export declare const createForm: (props: ZfsPoolProps) => Record<string, string>;
|
|
61
|
+
/**
|
|
62
|
+
* Create the pool if it is absent, and otherwise leave it completely alone.
|
|
63
|
+
*
|
|
64
|
+
* ⛔ THE POST ONLY FORKS A WORKER. MEASURED in `/usr/share/perl5/PVE/API2/Disks/ZFS.pm` on node-b:
|
|
65
|
+
* the create handler ends in `$rpcenv->fork_worker('zfscreate', ...)` and its HTTP answer is a
|
|
66
|
+
* UPID returned the instant the worker is forked — before `zpool create` has run, let alone
|
|
67
|
+
* finished. A read-back taken immediately would report a successful create as the failure "the
|
|
68
|
+
* write returned no error but the object is still absent". THAT is why this function polls.
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ THERE IS NO UPDATE BRANCH BECAUSE PVE HAS NO PUT HERE. An existing pool is returned exactly as
|
|
71
|
+
* read, unwritten: the only write this resource ever makes is the create of a pool that is not
|
|
72
|
+
* there. Do not add a "repair" write; there is no field it could set.
|
|
73
|
+
*
|
|
74
|
+
* ⛔ AND IF THE READ IS WRONG, THE POST IS STILL SAFE — measured, not hoped for. `readPool` folds a
|
|
75
|
+
* 403 or an unreachable node into "absent", so a too-narrow lease would send this down the create
|
|
76
|
+
* path over a live pool. PVE's handler calls `get_pool_data()` before it forks anything and dies
|
|
77
|
+
* with "pool '<name>' already exists on node '<node>'", and `assert_disk_unused` refuses every
|
|
78
|
+
* device that pool is holding. A create aimed at a pool that is really there fails loudly rather
|
|
79
|
+
* than wiping it.
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ SHORT POLLS WITH A HARD CAP, NOT ONE LONG WAIT, NOT `@distilled.cloud/proxmox`'s `awaitTask`.
|
|
82
|
+
* `readPool` cannot tell "not yet" from "forbidden" — both fold to `undefined` — so the loop
|
|
83
|
+
* gives up with the UPID rather than retrying forever, and the UPID is the only thing that leads
|
|
84
|
+
* to the worker's real error. Distilled's own task-status poller exists (`Task.ts`) and would
|
|
85
|
+
* remove this ambiguity, but swapping to it changes this resource's OBSERVABLE behaviour (what a
|
|
86
|
+
* caller sees on a slow create) beyond a transport swap — left as a follow-up, not folded into a
|
|
87
|
+
* migration PR silently.
|
|
88
|
+
*
|
|
89
|
+
* ⛔ THE SAME AMBIGUITY MAKES A RE-RUN AFTER THE GIVE-UP DIE GENUINELY UNSAFE, NOT MERELY SLOW TO
|
|
90
|
+
* CONFIRM — FOUND BY ADVERSARIAL REVIEW, 2026-09-24, of THIS migration, though the shape is
|
|
91
|
+
* unchanged from before it (`git blame` shows the same settle-poll pre-dates this PR). "If the
|
|
92
|
+
* read is wrong, the POST is still safe" above is true for the FIRST call into this function —
|
|
93
|
+
* PVE's own `get_pool_data()` refuses a create over a pool it can already see. It is NOT true
|
|
94
|
+
* for a SECOND deploy attempt started while the FIRST worker is still mid-`zpool create`: that
|
|
95
|
+
* worker has not yet made the pool visible to a read, so `existing = yield* read(props)` at the
|
|
96
|
+
* top of THIS call can also fold to `undefined` — not because the pool is gone, but because the
|
|
97
|
+
* first attempt has not finished — and this function would POST a SECOND `zpool create` at the
|
|
98
|
+
* SAME devices while the first is still writing them. There is no lock or marker anywhere in
|
|
99
|
+
* this package that would stop it. The give-up message below says so explicitly and names the
|
|
100
|
+
* task log to check FIRST; that message, and an operator reading it, are the only safeguard.
|
|
101
|
+
*
|
|
102
|
+
* ⛔ THE ADOPT-ONLY GUARD, RIGHT AFTER THE READ AND BEFORE ANYTHING ELSE. Decision 9 (2026-09-23):
|
|
103
|
+
* omitting `devices` and `raidlevel` declares a pool this resource may only ADOPT, for a pool
|
|
104
|
+
* PVE's own schema cannot fully describe — n1's `speed` is a stripe, and `raidlevel`'s enum has
|
|
105
|
+
* no stripe (zfs-pool.ts). `zfsPoolCreateEndpoint` above already keeps the vendor constraint
|
|
106
|
+
* guard from refusing such a declaration at PLAN time; this is the matching refusal at APPLY
|
|
107
|
+
* time, for the one case that guard cannot see — the pool genuinely is not there.
|
|
108
|
+
*/
|
|
109
|
+
export declare const createPool: <E, R>(props: ZfsPoolProps, read: (props: ZfsPoolProps) => Effect.Effect<ZfsPoolAttributes | undefined, E, R>) => Effect.Effect<ZfsPoolAttributes, E | Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, R | import("effect/unstable/http/HttpClient").HttpClient>;
|
|
110
|
+
/**
|
|
111
|
+
* ⛔ `DELETE /nodes/{node}/disks/zfs/{name}` RUNS `zpool destroy` AND THE DATASETS GO WITH IT. Per
|
|
112
|
+
* S14 (alchemy-provider-standard), it is implemented rather than stubbed — a `delete` that
|
|
113
|
+
* silently does nothing lies to whoever reads the plan — and what keeps an ordinary removed
|
|
114
|
+
* declaration from reaching it is the resource's `defaultRemovalPolicy: 'retain'` (zfs-pool.ts):
|
|
115
|
+
* an orphaned pool is forgotten, not destroyed, unless a caller opts in with
|
|
116
|
+
* `.pipe(RemovalPolicy.destroy())`. See the corrected header in zfs-pool.ts.
|
|
117
|
+
*
|
|
118
|
+
* ⚠️ `cleanup-config`/`cleanup-disks` ARE NOT SENT. The first would additionally remove the PVE
|
|
119
|
+
* storage entry pointing at this pool, which is a different object with its own resource
|
|
120
|
+
* (`Proxmox.Storage`); one resource reaching over to delete another's is how a stack ends up with
|
|
121
|
+
* state describing something that is gone. Neither is offered as a prop, for the same reason.
|
|
122
|
+
*/
|
|
123
|
+
export declare const destroyPool: (props: ZfsPoolProps) => Effect.Effect<string, Error | import("./distilled-pve.ts").PveClusterExhausted | import("./credential-errors.ts").PveCredentialDenied | import("effect/Cause").TimeoutError | nodes.ProxmoxOpError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
124
|
+
//# sourceMappingURL=zfs-pool-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zfs-pool-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,gCAAgC,CAAC;AAKxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEtE,eAAO,MAAM,eAAe,qCAAqC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAAC,CAAC;AAEjF,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,2BAA2B,CAAC,aAAa,CAAC,CAAC,CAAC;AAErF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,UAAW,YAAY,mDACmC,CAAC;AAE7F;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,UAAW,YAAY,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAOpE,CAAC;AA0BH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,SACtB,YAAY,QACb,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,GAAG,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,kOA8D/E,CAAC;AAEL;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,UAAW,YAAY,8PAM5C,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import type { ZfsPoolProps } from './zfs-pool.ts';
|
|
3
|
+
import { UNREADABLE, type Unreadable } from './unreadable-read.ts';
|
|
4
|
+
/**
|
|
5
|
+
* ⚠️ EVERYTHING HERE IS REPORTED, NOTHING HERE IS COMPARED — see the ⛔ on `matches` below. These
|
|
6
|
+
* exist so a plan and the state store can say what the pool actually is, on a family where the
|
|
7
|
+
* declaration and the live object share no comparable field at all.
|
|
8
|
+
*/
|
|
9
|
+
export interface ZfsPoolAttributes {
|
|
10
|
+
/** ★ The value a `Proxmox.Storage` should read to order itself after this pool. */
|
|
11
|
+
name: string;
|
|
12
|
+
node: string;
|
|
13
|
+
/** `ONLINE` | `DEGRADED` | `FAULTED` | … Health, not configuration. */
|
|
14
|
+
state: string;
|
|
15
|
+
/** ZFS's own error summary, e.g. `No known data errors`. */
|
|
16
|
+
errors: string;
|
|
17
|
+
/**
|
|
18
|
+
* The leaf devices ZFS reports, in vdev order, comma-joined.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ THIS IS WHAT ZFS RESOLVED, NOT WHAT WAS DECLARED, and the two normally differ: PVE rewrites
|
|
21
|
+
* a `/dev/sdb` into a by-id link before creating, and ZFS reports partition paths (`…-part3`)
|
|
22
|
+
* for a pool built on partitions — MEASURED, that is exactly what node-b's `rpool` reports.
|
|
23
|
+
* Never compare it with `props.devices`.
|
|
24
|
+
*/
|
|
25
|
+
devices: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The device paths at the bottom of PVE's vdev tree, in the order ZFS reports them.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ "NO CHILDREN" IS THE LEAF TEST, AND IT IS NOT A GUESS — MEASURED in the node's `preparetree`,
|
|
31
|
+
* which sets `leaf` to 0 when there are children and 1 otherwise. Reading the flag would work
|
|
32
|
+
* equally well; recursing on `children` needs no second field to be present and is what makes
|
|
33
|
+
* nested sections (mirrors inside a raid10, `spares`, `cache`) flatten correctly.
|
|
34
|
+
*/
|
|
35
|
+
export declare const leafDevices: (children: unknown) => string[];
|
|
36
|
+
/**
|
|
37
|
+
* ★ Default `read` role — `Sys.Audit` on `/` is all this GET checks (zfs-pool.ts's header).
|
|
38
|
+
* ⛔ `{ node: props.node, name: props.name }`, NEVER THE WHOLE `props` — storage.ts's/user.ts's own
|
|
39
|
+
* ⛔, the kit 0.31.1 regression: `GetNodeDiskZfsRequest`'s schema declares only `node` and `name`
|
|
40
|
+
* (both path labels); any OTHER key on the object passed to `nodes.getNodeDiskZfs` is treated by
|
|
41
|
+
* distilled's `buildRequest` as an "unknown key" and JSON-encoded onto this bodyless GET, which a
|
|
42
|
+
* stricter fetch client refuses outright.
|
|
43
|
+
* ⛔ A MISSING POOL IS A 500, NOT A 404 — MEASURED against the live cluster (TB4 n2, 2026-09-24,
|
|
44
|
+
* read-role, read-only probe): `GET /nodes/n2/disks/zfs/hf-measure-nonexistent-pool` answers
|
|
45
|
+
* `{"message":"command '/sbin/zpool status -P hf-measure-nonexistent-pool' failed: exit code
|
|
46
|
+
* 1\n","data":null}` at HTTP 500 — PVE shells out to `zpool status` and reports its exit code
|
|
47
|
+
* as a generic command failure, not a typed "not found". The same shape as user.ts's/group.ts's/
|
|
48
|
+
* storage.ts's own measured 500s. `readPool` below (used by `createPool`'s settle-poll and by
|
|
49
|
+
* `reconcile`/`read`) still folds it, exactly as `readUser`/`readGroup`/`readStorage` do — the
|
|
50
|
+
* whole reason `createPool` can tell "not built yet" from "forbidden" only by giving up after
|
|
51
|
+
* `SETTLE_ATTEMPTS` (zfs-pool-form.ts's own ⚠️ on that); `readPoolOrFail` (used by `diff`) does
|
|
52
|
+
* not fold it, so a genuine transient failure there propagates and fails the plan loudly instead.
|
|
53
|
+
*/
|
|
54
|
+
export declare const readPoolOrFail: (props: ZfsPoolProps) => Effect.Effect<typeof UNREADABLE | ZfsPoolAttributes | undefined, import("@distilled.cloud/caddy").BadGateway | import("@distilled.cloud/caddy").BadRequest | import("@distilled.cloud/caddy").ConfigError | Error | import("@distilled.cloud/caddy").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | import("@distilled.cloud/proxmox/Errors").ParameterVerificationFailed | import("@distilled.cloud/proxmox/Errors").ProxmoxParseError | import("./distilled-pve.ts").PveClusterExhausted | import("@distilled.cloud/caddy").ServiceUnavailable | import("effect/Cause").TimeoutError | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/proxmox/Errors").UnknownProxmoxError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
55
|
+
/**
|
|
56
|
+
* ⛔ FOLDS A GENUINE READ FAILURE TO `undefined` TOO, AND ONLY `createPool`'s settle-poll AND
|
|
57
|
+
* `read`/`reconcile` MAY USE IT — user.ts's `readUser` has the full reasoning. Safe here because
|
|
58
|
+
* a wrongful fold during create costs at most a redundant `createNodeDiskZfs` POST, which PVE
|
|
59
|
+
* refuses loudly ("pool already exists") rather than silently corrupting anything — zfs-pool.ts's
|
|
60
|
+
* own header, measured.
|
|
61
|
+
* ⛔ `zfs-pool.ts`'s `diff` does **NOT** use this — the same bug class as the credential denial fix:
|
|
62
|
+
* folding a TRANSIENT failure into "absent" at PLAN TIME would force a false `update` with
|
|
63
|
+
* nothing compared. `diff` calls `readPoolOrFail` directly, so a genuine failure there propagates
|
|
64
|
+
* and fails the whole plan loudly instead.
|
|
65
|
+
*/
|
|
66
|
+
export declare const readPool: (props: ZfsPoolProps) => Effect.Effect<typeof UNREADABLE | ZfsPoolAttributes | undefined, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
67
|
+
/** `read`/`reconcile`/`createPool` return `Attributes | undefined`; only `diff` sees `UNREADABLE`. */
|
|
68
|
+
export declare const dropUnreadable: (live: ZfsPoolAttributes | Unreadable | undefined) => ZfsPoolAttributes | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* ⛔ THERE IS NO `matches` FUNCTION HERE — UNLIKE EVERY OTHER MIGRATED FAMILY, AND DELIBERATELY.
|
|
71
|
+
* Every create parameter on this family is write-only (this file's own `attributesOf`), so
|
|
72
|
+
* there is nothing a declaration and a live pool can ever be compared on — a `matches` that
|
|
73
|
+
* invented one would plan a replace (`zpool destroy`, since this family has no PUT) over a
|
|
74
|
+
* difference it could never verify. `zfs-pool.ts`'s `diff` returns `noop` unconditionally once a
|
|
75
|
+
* pool is found, rather than calling a function that would only ever return `true` — found by
|
|
76
|
+
* adversarial review that an earlier draft exported one here anyway, unused by anything.
|
|
77
|
+
*/
|
|
78
|
+
//# sourceMappingURL=zfs-pool-wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zfs-pool-wire.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool-wire.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAoB,MAAM,sBAAsB,CAAC;AAGrF;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,aAAc,OAAO,KAAG,MAAM,EAO9C,CAAC;AAmBT;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,UAAW,YAAY,+2BAe/C,CAAC;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,UAAW,YAAY,kIACuB,CAAC;AAEpE,sGAAsG;AACtG,eAAO,MAAM,cAAc,SAAU,iBAAiB,GAAG,UAAU,GAAG,SAAS,kCACvC,CAAC;AAEzC;;;;;;;;GAQG"}
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
* ⛔ THERE IS NO PUT ON THIS FAMILY AT ALL. MEASURED from the cluster's own published schema
|
|
27
27
|
* (`/usr/share/pve-docs/api-viewer/apidoc.js`, read on node-b 2026-09-13): `/nodes/{node}/disks/zfs`
|
|
28
28
|
* has GET and POST, `/nodes/{node}/disks/zfs/{name}` has GET and DELETE, and that is the whole
|
|
29
|
-
* surface
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* surface — CONFIRMED again in `@distilled.cloud/proxmox`'s generated `nodes.ts`, which declares
|
|
30
|
+
* no `putNodeDiskZfs` at all. So `matches` (zfs-pool-wire.ts) always answers `true`: there is
|
|
31
|
+
* nothing to plan a replace over, and the family's one write is the create below.
|
|
32
32
|
*
|
|
33
33
|
* ⛔ NOT ONE CREATE PARAMETER COMES BACK ON READ, SO `matches` COMPARES NOTHING. MEASURED against
|
|
34
|
-
* the live cluster: `GET /nodes/
|
|
35
|
-
* `{action
|
|
36
|
-
*
|
|
34
|
+
* the live cluster: `GET /nodes/n2/disks/zfs/rpool` answers exactly
|
|
35
|
+
* `{action?, children, errors, name, scan?, state, status?}` — no `ashift`, no `compression`, no
|
|
36
|
+
* `raidlevel`, no `devices`, no `add_storage`. Every create parameter is write-only. There is
|
|
37
37
|
* therefore no field a declaration and a live pool can both be asked about, and a `matches` that
|
|
38
38
|
* invented one would plan a replace — i.e. a `zpool destroy` — over a difference it could never
|
|
39
39
|
* verify in the first place.
|
|
@@ -46,11 +46,20 @@
|
|
|
46
46
|
* is compared, and the three churning strings are kept out of the state store entirely rather
|
|
47
47
|
* than rewriting this resource's row every time somebody writes a file.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* ⛔ A MISSING POOL IS A 500, NOT A 404 — MEASURED against the live cluster (TB4 n2, 2026-09-24,
|
|
50
|
+
* read-role, read-only probe): `GET /nodes/n2/disks/zfs/hf-measure-nonexistent-pool` answers a
|
|
51
|
+
* generic shelled-out command failure at HTTP 500, the same shape as every other family this
|
|
52
|
+
* package has migrated. `zfs-pool-wire.ts` carries the same dual-path read PR 239/243
|
|
53
|
+
* established: `readPoolOrFail` (non-folding, used by `diff`) and `readPool` (folding, used by
|
|
54
|
+
* `createPool`'s settle-poll and by `read`/`reconcile`).
|
|
55
|
+
*
|
|
56
|
+
* ★ MIGRATED OFF `client.ts`'s generic `pve()`/`pveOperations` ONTO `@distilled.cloud/proxmox`'s
|
|
57
|
+
* typed `nodes.getNodeDiskZfs`/`createNodeDiskZfs`/`deleteNodeDiskZfs` (2026-09-24, decision
|
|
58
|
+
* 43's proxmox walk-down, nodes/storage sub-area). `distilled-pve.ts`'s `runPve` replaces
|
|
59
|
+
* `pve()`; the cries-wolf fix is wired the same way as every other migrated family. `reconcile`
|
|
60
|
+
* and `delete` are still hand-written (never the factory) for the SAME two reasons as before the
|
|
61
|
+
* migration — the forked-worker settle-poll, and a DELETE that destroys data — now calling
|
|
62
|
+
* `zfs-pool-form.ts`'s `createPool`/`destroyPool` directly instead of through `pveHandlers`.
|
|
54
63
|
*
|
|
55
64
|
* ⚠️ PRIVILEGES, FROM THE SCHEMA. Read and diff need `Sys.Audit` on `/` — both GETs check it.
|
|
56
65
|
* Reconcile needs `Sys.Modify` on `/` for the POST. `Datastore.Allocate` on `/storage` is NOT
|
|
@@ -60,26 +69,15 @@
|
|
|
60
69
|
* family checks it, not only the POST. MEASURED the same day as the rest of this paragraph:
|
|
61
70
|
* `ceph-osd.ts`'s own privilege comparison names `disks/zfs` as one of the sibling families
|
|
62
71
|
* whose write verbs DO carry a `Sys.Modify` check, unlike `ceph/osd`'s create and delete, which
|
|
63
|
-
* carry no permissions block at all and 403 for any identity but `root@pam`.
|
|
64
|
-
* previously said delete "needs nothing at all, since it calls nothing", which repeated the same
|
|
65
|
-
* mistake the header above corrects: `delete` calls the DELETE endpoint, so it needs whatever
|
|
66
|
-
* that endpoint checks.
|
|
72
|
+
* carry no permissions block at all and 403 for any identity but `root@pam`.
|
|
67
73
|
*/
|
|
68
74
|
import { Resource } from 'alchemy';
|
|
69
75
|
import * as Provider from 'alchemy/Provider';
|
|
70
|
-
import type {
|
|
71
|
-
import
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* hand-typed, per decision 9. ⚠️ Each has a minimum disk count PVE enforces, and `raid10` needs an
|
|
76
|
-
* even one. ⛔ THERE IS NO `stripe` IN THIS ENUM. n1's `speed` pool is one (decision 14 accepts it
|
|
77
|
-
* as-is): `ZfsPoolProps` below is why a stripe pool is declarable at all — as an ADOPT-ONLY
|
|
78
|
-
* declaration with no `raidlevel`, never as a full one.
|
|
79
|
-
*/
|
|
80
|
-
export type ZfsRaidLevel = NonNullable<NodesNodeDisksZfsPostParams['raidlevel']>;
|
|
81
|
-
/** Generated the same way, from `NodesNodeDisksZfsPostParams['compression']`. */
|
|
82
|
-
export type ZfsCompression = NonNullable<NodesNodeDisksZfsPostParams['compression']>;
|
|
76
|
+
import type { PveRequirements, WithTarget } from './resource-spec.ts';
|
|
77
|
+
import type { ZfsPoolAttributes } from './zfs-pool-wire.ts';
|
|
78
|
+
import type { ZfsCompression, ZfsRaidLevel } from './zfs-pool-form.ts';
|
|
79
|
+
export type { ZfsPoolAttributes } from './zfs-pool-wire.ts';
|
|
80
|
+
export type { ZfsCompression, ZfsRaidLevel } from './zfs-pool-form.ts';
|
|
83
81
|
/**
|
|
84
82
|
* ⛔ THERE IS NO `add_storage`, `cleanup-config` OR `cleanup-disks` PROP, AND THAT IS NOT AN
|
|
85
83
|
* OVERSIGHT. The last two belong to the DELETE this resource refuses to make. `add_storage=1`
|
|
@@ -116,7 +114,7 @@ export interface ZfsPoolProps extends WithTarget {
|
|
|
116
114
|
* both this and `raidlevel` undeclared declares an ADOPT-ONLY pool — one this resource may
|
|
117
115
|
* read and report but never build. That is the only shape available for a pool PVE's own POST
|
|
118
116
|
* schema cannot fully describe, such as n1's `speed`, whose layout is a stripe and whose
|
|
119
|
-
* `raidlevel` enum (above) has no stripe to declare. `createForm` (zfs-pool-
|
|
117
|
+
* `raidlevel` enum (above) has no stripe to declare. `createForm` (zfs-pool-form.ts) omits
|
|
120
118
|
* both keys when they are absent, and `createPool` refuses before any POST if the pool it
|
|
121
119
|
* would need to build is not already there — an adopt-only declaration never creates. Declare
|
|
122
120
|
* both fields together for a pool this resource should build from scratch; declaring only one
|
|
@@ -145,29 +143,6 @@ export interface ZfsPoolProps extends WithTarget {
|
|
|
145
143
|
*/
|
|
146
144
|
'draid-config'?: string;
|
|
147
145
|
}
|
|
148
|
-
/**
|
|
149
|
-
* ⚠️ EVERYTHING HERE IS REPORTED, NOTHING HERE IS COMPARED — see the ⛔ on `matches`. These exist
|
|
150
|
-
* so a plan and the state store can say what the pool actually is, on a family where the
|
|
151
|
-
* declaration and the live object share no comparable field at all.
|
|
152
|
-
*/
|
|
153
|
-
export interface ZfsPoolAttributes {
|
|
154
|
-
/** ★ The value a `Proxmox.Storage` should read to order itself after this pool. */
|
|
155
|
-
name: string;
|
|
156
|
-
node: string;
|
|
157
|
-
/** `ONLINE` | `DEGRADED` | `FAULTED` | … Health, not configuration. */
|
|
158
|
-
state: string;
|
|
159
|
-
/** ZFS's own error summary, e.g. `No known data errors`. */
|
|
160
|
-
errors: string;
|
|
161
|
-
/**
|
|
162
|
-
* The leaf devices ZFS reports, in vdev order, comma-joined.
|
|
163
|
-
*
|
|
164
|
-
* ⚠️ THIS IS WHAT ZFS RESOLVED, NOT WHAT WAS DECLARED, and the two normally differ: PVE rewrites
|
|
165
|
-
* a `/dev/sdb` into a by-id link before creating, and ZFS reports partition paths (`…-part3`)
|
|
166
|
-
* for a pool built on partitions — MEASURED, that is exactly what node-b's `rpool` reports. Never
|
|
167
|
-
* compare it with `props.devices`.
|
|
168
|
-
*/
|
|
169
|
-
devices: string;
|
|
170
|
-
}
|
|
171
146
|
export interface ProxmoxZfsPool extends Resource<'Proxmox.ZfsPool', ZfsPoolProps, ZfsPoolAttributes, never, PveRequirements> {
|
|
172
147
|
}
|
|
173
148
|
/** ★ `retain` by default — a pool holding datasets cannot be rebuilt. See the ★ in resource.ts. */
|