@homeflare/alchemy 0.37.8 → 0.39.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 +22 -0
- package/dist/caddy/admin.d.ts +42 -8
- package/dist/caddy/admin.d.ts.map +1 -1
- package/dist/caddy/config.d.ts +9 -3
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/fake-caddy.d.ts +5 -0
- package/dist/caddy/fake-caddy.d.ts.map +1 -1
- package/dist/caddy/index.js +10 -6
- package/dist/caddy/index.js.map +5 -5
- package/dist/caddy/providers.d.ts +19 -12
- package/dist/caddy/providers.d.ts.map +1 -1
- package/dist/forgejo/index.js +4 -2
- package/dist/forgejo/index.js.map +3 -3
- package/dist/forgejo/team-member.d.ts +25 -0
- package/dist/forgejo/team-member.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
- package/dist/proxmox/index.d.ts +14 -3
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +565 -133
- package/dist/proxmox/index.js.map +33 -23
- package/dist/proxmox/qemu-form.d.ts +27 -18
- package/dist/proxmox/qemu-form.d.ts.map +1 -1
- package/dist/proxmox/qemu-judge.d.ts +30 -0
- package/dist/proxmox/qemu-judge.d.ts.map +1 -0
- package/dist/proxmox/qemu-lifecycle.d.ts +16 -2
- package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/qemu-net.d.ts +9 -0
- package/dist/proxmox/qemu-net.d.ts.map +1 -0
- package/dist/proxmox/qemu-props.d.ts +112 -0
- package/dist/proxmox/qemu-props.d.ts.map +1 -0
- package/dist/proxmox/qemu-volume.d.ts +17 -0
- package/dist/proxmox/qemu-volume.d.ts.map +1 -0
- package/dist/proxmox/qemu.d.ts +14 -27
- package/dist/proxmox/qemu.d.ts.map +1 -1
- package/dist/proxmox/storage-download-errors.d.ts +5 -0
- package/dist/proxmox/storage-download-errors.d.ts.map +1 -0
- package/dist/proxmox/storage-download-identity.d.ts +27 -0
- package/dist/proxmox/storage-download-identity.d.ts.map +1 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts +97 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/storage-download-props.d.ts +36 -0
- package/dist/proxmox/storage-download-props.d.ts.map +1 -0
- package/dist/proxmox/storage-download-task.d.ts +17 -0
- package/dist/proxmox/storage-download-task.d.ts.map +1 -0
- package/dist/proxmox/storage-download.d.ts +44 -0
- package/dist/proxmox/storage-download.d.ts.map +1 -0
- package/dist/telemetry/fake-collector.d.ts +36 -0
- package/dist/telemetry/fake-collector.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +11 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +223 -0
- package/dist/telemetry/index.js.map +12 -0
- package/dist/telemetry/layer.d.ts +86 -0
- package/dist/telemetry/layer.d.ts.map +1 -0
- package/dist/telemetry/redact-serialization.d.ts +21 -0
- package/dist/telemetry/redact-serialization.d.ts.map +1 -0
- package/dist/telemetry/redact.d.ts +46 -0
- package/dist/telemetry/redact.d.ts.map +1 -0
- package/dist/unifi/drift.d.ts +27 -0
- package/dist/unifi/drift.d.ts.map +1 -0
- package/dist/unifi/fake-unifi.d.ts +5 -1
- package/dist/unifi/fake-unifi.d.ts.map +1 -1
- package/dist/unifi/firewall-zone-form.d.ts +6 -0
- package/dist/unifi/firewall-zone-form.d.ts.map +1 -1
- package/dist/unifi/firewall-zone.d.ts +1 -1
- package/dist/unifi/firewall-zone.d.ts.map +1 -1
- package/dist/unifi/index.d.ts +4 -3
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +138 -58
- package/dist/unifi/index.js.map +10 -8
- package/dist/unifi/network-drift.d.ts +26 -0
- package/dist/unifi/network-drift.d.ts.map +1 -0
- package/dist/unifi/network-form.d.ts +40 -7
- package/dist/unifi/network-form.d.ts.map +1 -1
- package/dist/unifi/network.d.ts +1 -0
- package/dist/unifi/network.d.ts.map +1 -1
- package/dist/unifi/paginate.d.ts +36 -0
- package/dist/unifi/paginate.d.ts.map +1 -0
- package/dist/unifi/resource.d.ts +36 -1
- package/dist/unifi/resource.d.ts.map +1 -1
- package/docs/telemetry-spike.md +94 -0
- package/docs/telemetry.md +109 -0
- package/docs/unifi.md +56 -0
- package/package.json +6 -2
- package/src/caddy/admin-transport-scope.test.ts +127 -0
- package/src/caddy/admin.ts +45 -9
- package/src/caddy/config-adopt-provider.test.ts +2 -2
- package/src/caddy/config-format-warning.test.ts +9 -3
- package/src/caddy/config.ts +20 -4
- package/src/caddy/fake-caddy.ts +10 -1
- package/src/caddy/providers.test.ts +3 -2
- package/src/caddy/providers.ts +18 -11
- package/src/forgejo/team-member.test.ts +14 -0
- package/src/forgejo/team-member.ts +28 -1
- package/src/proxmox/constraints-required.test.ts +1 -0
- package/src/proxmox/generated/constraints/index.ts +3 -1
- package/src/proxmox/generated/constraints/pve-access.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-storage.ts +30 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
- package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
- package/src/proxmox/index.test.ts +11 -6
- package/src/proxmox/index.ts +19 -3
- package/src/proxmox/qemu-form.ts +49 -13
- package/src/proxmox/qemu-identity.test.ts +30 -0
- package/src/proxmox/qemu-judge.test.ts +65 -0
- package/src/proxmox/qemu-judge.ts +84 -0
- package/src/proxmox/qemu-lifecycle.test.ts +78 -19
- package/src/proxmox/qemu-lifecycle.ts +131 -32
- package/src/proxmox/qemu-net.test.ts +48 -0
- package/src/proxmox/qemu-net.ts +78 -0
- package/src/proxmox/qemu-ownership.test.ts +44 -0
- package/src/proxmox/qemu-props.test.ts +87 -0
- package/src/proxmox/qemu-props.ts +157 -0
- package/src/proxmox/qemu-volume.test.ts +80 -0
- package/src/proxmox/qemu-volume.ts +121 -0
- package/src/proxmox/qemu.ts +15 -29
- package/src/proxmox/storage-download-errors.ts +7 -0
- package/src/proxmox/storage-download-identity.test.ts +54 -0
- package/src/proxmox/storage-download-identity.ts +50 -0
- package/src/proxmox/storage-download-lifecycle.ts +226 -0
- package/src/proxmox/storage-download-ownership.test.ts +50 -0
- package/src/proxmox/storage-download-props.ts +38 -0
- package/src/proxmox/storage-download-task.ts +72 -0
- package/src/proxmox/storage-download.test.ts +216 -0
- package/src/proxmox/storage-download.ts +58 -0
- package/src/telemetry/fake-collector.ts +59 -0
- package/src/telemetry/http-client-sealing.test.ts +82 -0
- package/src/telemetry/index.ts +10 -0
- package/src/telemetry/layer.test.ts +204 -0
- package/src/telemetry/layer.ts +152 -0
- package/src/telemetry/redact-serialization.ts +178 -0
- package/src/telemetry/redact.test.ts +124 -0
- package/src/telemetry/redact.ts +147 -0
- package/src/unifi/drift.test.ts +78 -0
- package/src/unifi/drift.ts +56 -0
- package/src/unifi/errors-and-secrets.test.ts +127 -0
- package/src/unifi/fake-unifi.ts +5 -1
- package/src/unifi/firewall-zone-form.ts +31 -2
- package/src/unifi/firewall-zone.test.ts +21 -1
- package/src/unifi/firewall-zone.ts +1 -1
- package/src/unifi/get-only-guard.test.ts +165 -0
- package/src/unifi/index.ts +15 -1
- package/src/unifi/network-drift.test.ts +207 -0
- package/src/unifi/network-drift.ts +80 -0
- package/src/unifi/network-form.ts +11 -39
- package/src/unifi/network.test.ts +7 -82
- package/src/unifi/network.ts +6 -6
- package/src/unifi/paginate.test.ts +228 -0
- package/src/unifi/paginate.ts +129 -0
- package/src/unifi/resource.ts +73 -3
- package/src/unifi/write-op-reference.test.ts +236 -0
package/src/caddy/admin.ts
CHANGED
|
@@ -10,12 +10,25 @@
|
|
|
10
10
|
* caddyconfig/load.go and admin.go, and in the API docs (caddyserver.com/docs/api).
|
|
11
11
|
* ★ `CaddyAdminService` CARRIES ONLY MESSAGE/GUARD METADATA (endpoint, listener) — never a
|
|
12
12
|
* credential, and never the transport itself. The transport is `@distilled.cloud/caddy`'s own
|
|
13
|
-
* `Credentials` and `HttpClient.HttpClient` services
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* `Credentials` and `HttpClient.HttpClient` services — admin-calls.ts calls the SDK's operations
|
|
14
|
+
* directly rather than through a house-specific `.request()` seam. A consumer plugs in another
|
|
15
|
+
* route to the API (an SSH-forwarded socket today; a remote runner later) by providing a different
|
|
16
|
+
* `CaddyTransport` to `caddyAdminLayer`.
|
|
17
17
|
* ⛔ THE ADMIN API HAS NO AUTHENTICATION. Anyone who reaches it can replace every site. It stays on
|
|
18
18
|
* loopback or a unix socket; localCaddyAdmin() refuses anything else (local-admin.ts).
|
|
19
|
+
* ⛔ `caddyAdminLayer`'s OWN OUTPUT NEVER CARRIES `Credentials`/`HttpClient.HttpClient` DIRECTLY —
|
|
20
|
+
* MEASURED 2026-09-26: it used to (`Layer.mergeAll(CaddyAdminService-layer, transport.layer)`,
|
|
21
|
+
* what `caddyProviders()` then `provideMerge`d into a stack), and a stack that applies its own
|
|
22
|
+
* ambient client OUTSIDE the providers' merge — `Layer.mergeAll(providers…).pipe(Layer.provide
|
|
23
|
+
* (ambient))`, the shape `argocd`/`discord`/`opnsense`/`unifi`'s own `providers.ts` document —
|
|
24
|
+
* never got it back: `Layer.provide(A, B)`'s result outputs only `A`'s own services, so once `A`
|
|
25
|
+
* (the merged providers) already carries `HttpClient.HttpClient` from Caddy, no outer `B` overrides
|
|
26
|
+
* it. Every OTHER fetch-based provider merged alongside `caddyProviders()` (homeflare-mini's
|
|
27
|
+
* Forgejo, LiteLLM) then dialled Caddy's admin API instead of its own target — reproduced in
|
|
28
|
+
* admin-transport-scope.test.ts. `caddyAdminLayer` now stores `transport.layer` itself as a plain
|
|
29
|
+
* VALUE, under `CaddyAdminTransport` — a house-only tag nothing outside caddy/ ever reads — and
|
|
30
|
+
* config.ts is the one place that `Effect.provide`s it, scoped to exactly the effects that call
|
|
31
|
+
* `@distilled.cloud/caddy`'s operations.
|
|
19
32
|
*/
|
|
20
33
|
import type * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
21
34
|
import * as Context from 'effect/Context';
|
|
@@ -43,7 +56,16 @@ export interface CaddyTarget {
|
|
|
43
56
|
|
|
44
57
|
/** What `localCaddyAdmin()` (or a consumer's own transport) hands `caddyAdminLayer`. */
|
|
45
58
|
export interface CaddyTransport extends CaddyTarget {
|
|
46
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Provides the SDK's own `Credentials` and `HttpClient.HttpClient` — see local-admin.ts.
|
|
61
|
+
* ⚠️ BUILT AND TORN DOWN ON EVERY `read`/`diff`/`reconcile` CALL — config.ts's `Effect.provide
|
|
62
|
+
* (transportLayer)` runs fresh each time, scoped to that one call (the scoping fix this file's
|
|
63
|
+
* own ⛔ documents). `localCaddyAdmin()`'s layer is three plain `Layer.succeed` values, so this
|
|
64
|
+
* costs nothing; a transport that opens something stateful (an SSH forward, a connection pool)
|
|
65
|
+
* would open and close it per call instead of once per stack. No such transport exists in the
|
|
66
|
+
* tray today (2026-09-26) — keep `layer` cheap and stateless, or build once and hand config.ts a
|
|
67
|
+
* pre-built `Context` instead, if that changes.
|
|
68
|
+
*/
|
|
47
69
|
readonly layer: Layer.Layer<Credentials | HttpClient.HttpClient>;
|
|
48
70
|
}
|
|
49
71
|
|
|
@@ -52,19 +74,33 @@ export class CaddyAdminService extends Context.Service<CaddyAdminService, CaddyT
|
|
|
52
74
|
'homeflare/caddy/CaddyAdmin',
|
|
53
75
|
) {}
|
|
54
76
|
|
|
77
|
+
/**
|
|
78
|
+
* `transport.layer` ITSELF, carried as a plain value rather than merged into the ambient context —
|
|
79
|
+
* the scoping fix (see the file header's ⛔). Only config.ts reads this tag, and only to
|
|
80
|
+
* `Effect.provide` it locally around the handful of effects that actually call
|
|
81
|
+
* `@distilled.cloud/caddy`'s operations; nothing else in this directory, and nothing outside it,
|
|
82
|
+
* has a reason to.
|
|
83
|
+
*/
|
|
84
|
+
export class CaddyAdminTransport extends Context.Service<
|
|
85
|
+
CaddyAdminTransport,
|
|
86
|
+
Layer.Layer<Credentials | HttpClient.HttpClient>
|
|
87
|
+
>()('homeflare/caddy/CaddyAdminTransport') {}
|
|
88
|
+
|
|
55
89
|
/**
|
|
56
90
|
* Provide a transport to the provider: `CaddyConfigProvider().pipe(Layer.provideMerge(caddyAdminLayer(localCaddyAdmin())))`
|
|
57
91
|
* — never plain `Layer.provide` here (see providers.ts's own ⚠️): `CaddyConfigProvider()`'s handlers
|
|
58
|
-
* keep needing
|
|
59
|
-
*
|
|
92
|
+
* keep needing `CaddyAdminService`/`CaddyAdminTransport` every time the engine calls them, not just
|
|
93
|
+
* once while it is built. Neither tag is a generic platform service, so merging them into a stack's
|
|
94
|
+
* ambient context (unlike the raw `Credentials`/`HttpClient.HttpClient` this used to carry — the
|
|
95
|
+
* file header's ⛔) is safe: nothing outside this directory looks either up.
|
|
60
96
|
*/
|
|
61
97
|
export const caddyAdminLayer = (
|
|
62
98
|
transport: CaddyTransport,
|
|
63
|
-
): Layer.Layer<CaddyAdminService |
|
|
99
|
+
): Layer.Layer<CaddyAdminService | CaddyAdminTransport> =>
|
|
64
100
|
Layer.mergeAll(
|
|
65
101
|
Layer.succeed(CaddyAdminService, {
|
|
66
102
|
endpoint: transport.endpoint,
|
|
67
103
|
listener: transport.listener,
|
|
68
104
|
}),
|
|
69
|
-
transport.layer,
|
|
105
|
+
Layer.succeed(CaddyAdminTransport, transport.layer),
|
|
70
106
|
);
|
|
@@ -9,7 +9,6 @@ import { AdoptPolicy, Unowned } from 'alchemy/AdoptPolicy';
|
|
|
9
9
|
import { AlchemyContext } from 'alchemy/AlchemyContext';
|
|
10
10
|
import * as Effect from 'effect/Effect';
|
|
11
11
|
import * as Layer from 'effect/Layer';
|
|
12
|
-
import type * as Caddy from '@distilled.cloud/caddy';
|
|
13
12
|
import { type CaddyAdminService, type CaddyTransport, caddyAdminLayer } from './admin.ts';
|
|
14
13
|
import { CaddyConfig, CaddyConfigProvider } from './config.ts';
|
|
15
14
|
import { configDigest } from './digest.ts';
|
|
@@ -40,9 +39,10 @@ type Deploy = { readonly adopt?: boolean; readonly contextAdopt?: boolean };
|
|
|
40
39
|
const withProvider = <A>(
|
|
41
40
|
admin: CaddyTransport,
|
|
42
41
|
deploy: Deploy,
|
|
42
|
+
// ★ `CaddyAdminService` ONLY — see providers.test.ts's own `withProvider` for why.
|
|
43
43
|
use: (
|
|
44
44
|
p: Effect.Success<typeof CaddyConfig.Provider>,
|
|
45
|
-
) => Effect.Effect<A, unknown, CaddyAdminService
|
|
45
|
+
) => Effect.Effect<A, unknown, CaddyAdminService>,
|
|
46
46
|
) => {
|
|
47
47
|
// ⚠️ `Layer.provideMerge`, not `Layer.provide` — providers.ts's own doc on `caddyAdminLayer` says
|
|
48
48
|
// why: `CaddyConfigProvider()`'s handlers still need these services every time they run, not
|
|
@@ -13,7 +13,7 @@ import { AdoptPolicy } from 'alchemy/AdoptPolicy';
|
|
|
13
13
|
import * as Effect from 'effect/Effect';
|
|
14
14
|
import * as Layer from 'effect/Layer';
|
|
15
15
|
import * as Logger from 'effect/Logger';
|
|
16
|
-
import { caddyAdminLayer } from './admin.ts';
|
|
16
|
+
import { type CaddyTransport, caddyAdminLayer } from './admin.ts';
|
|
17
17
|
import { CaddyConfig, CaddyConfigProvider } from './config.ts';
|
|
18
18
|
import { diffConfig } from './config-reconcile.ts';
|
|
19
19
|
import { type FakeCaddy, fakeDefaultCaddy } from './fake-caddy.ts';
|
|
@@ -34,6 +34,12 @@ const setup = () => {
|
|
|
34
34
|
return made;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
/** `diffConfig` still needs the raw `Credentials`/`HttpClient` directly — `caddyAdminLayer` alone
|
|
38
|
+
* (admin.ts's own scoping fix) only carries `CaddyAdminService`/`CaddyAdminTransport`; merge
|
|
39
|
+
* `admin.layer` in too, the same fix `fake-caddy.ts`'s `runCaddy` makes for this exact shape. */
|
|
40
|
+
const withTransport = (admin: CaddyTransport) =>
|
|
41
|
+
Layer.mergeAll(caddyAdminLayer(admin), admin.layer);
|
|
42
|
+
|
|
37
43
|
/** Runs `effect` and returns its result plus every `Effect.logWarning` line it emitted. */
|
|
38
44
|
const withWarnings = async <A>(effect: Effect.Effect<A, unknown, never>) => {
|
|
39
45
|
const warnings: string[] = [];
|
|
@@ -49,7 +55,7 @@ describe('the formatting warning is surfaced distinctly', () => {
|
|
|
49
55
|
const { admin } = setup();
|
|
50
56
|
const output = { configSha256: 'stub', endpoint: admin.endpoint };
|
|
51
57
|
const { value, warnings } = await withWarnings(
|
|
52
|
-
diffConfig({ caddyfile: UNFORMATTED }, output).pipe(Effect.provide(
|
|
58
|
+
diffConfig({ caddyfile: UNFORMATTED }, output).pipe(Effect.provide(withTransport(admin))),
|
|
53
59
|
);
|
|
54
60
|
expect(value).toEqual({ action: 'update' });
|
|
55
61
|
expect(warnings).toHaveLength(1);
|
|
@@ -61,7 +67,7 @@ describe('the formatting warning is surfaced distinctly', () => {
|
|
|
61
67
|
const { admin } = setup();
|
|
62
68
|
const output = { configSha256: 'stub', endpoint: admin.endpoint };
|
|
63
69
|
const { warnings } = await withWarnings(
|
|
64
|
-
diffConfig({ caddyfile: SITE }, output).pipe(Effect.provide(
|
|
70
|
+
diffConfig({ caddyfile: SITE }, output).pipe(Effect.provide(withTransport(admin))),
|
|
65
71
|
);
|
|
66
72
|
expect(warnings).toEqual([]);
|
|
67
73
|
});
|
package/src/caddy/config.ts
CHANGED
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
* engine skips that probe, reconcile refuses the same takeover (config-lifecycle.ts).
|
|
22
22
|
* ⛔ `caddyfile` IS NEVER A SECRET — see config-form.ts; refused before anything is sent.
|
|
23
23
|
* ⚠️ WHICH CADDY is the transport's (providers.ts): `CaddyAdminService` plus the `Credentials`/
|
|
24
|
-
* `HttpClient`
|
|
24
|
+
* `HttpClient` layer `CaddyAdminTransport` carries — one per stack, not a prop.
|
|
25
|
+
* ⚠️ `transportLayer` IS `Effect.provide`D LOCALLY IN EACH HANDLER BELOW, NEVER MERGED AHEAD OF
|
|
26
|
+
* TIME. `caddyAdminLayer` (admin.ts) stores it under `CaddyAdminTransport` rather than merging its
|
|
27
|
+
* `Credentials`/`HttpClient.HttpClient` into the ambient context precisely so this file is the ONE
|
|
28
|
+
* place that discharges them — scoped to the handful of effects that actually call
|
|
29
|
+
* `@distilled.cloud/caddy`'s operations, never leaking into a sibling fetch-based provider merged
|
|
30
|
+
* into the same stack (admin.ts's own ⛔; MEASURED 2026-09-26, admin-transport-scope.test.ts).
|
|
25
31
|
*/
|
|
26
32
|
import { Resource } from 'alchemy';
|
|
27
33
|
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
@@ -30,7 +36,7 @@ import * as Provider from 'alchemy/Provider';
|
|
|
30
36
|
import * as Effect from 'effect/Effect';
|
|
31
37
|
import { resolvedString } from '../launchd/host-effect.ts';
|
|
32
38
|
import { adoptEnabled } from '../ownership/adopt.ts';
|
|
33
|
-
import { CaddyAdminService } from './admin.ts';
|
|
39
|
+
import { CaddyAdminService, CaddyAdminTransport } from './admin.ts';
|
|
34
40
|
import { isUnreachable } from './caddy-http-client.ts';
|
|
35
41
|
import type { CaddyConfigAttributes, CaddyConfigProps } from './config-form.ts';
|
|
36
42
|
import { probeLive, readLive } from './config-lifecycle.ts';
|
|
@@ -72,6 +78,7 @@ export const CaddyConfigProvider = () =>
|
|
|
72
78
|
CaddyConfig,
|
|
73
79
|
Effect.gen(function* () {
|
|
74
80
|
const admin = yield* CaddyAdminService;
|
|
81
|
+
const transportLayer = yield* CaddyAdminTransport;
|
|
75
82
|
/** The probe, branded for the engine: plain when ours, `Unowned` otherwise. */
|
|
76
83
|
const probe = (caddyfile: string | undefined, sourceFile: string | undefined) =>
|
|
77
84
|
Effect.flatMap(probeLive(caddyfile, sourceFile), (found) => {
|
|
@@ -104,6 +111,9 @@ export const CaddyConfigProvider = () =>
|
|
|
104
111
|
? yield* probe(resolvedString(olds, 'caddyfile'), resolvedString(olds, 'sourceFile'))
|
|
105
112
|
: yield* readLive(resolvedString(olds, 'sourceFile'));
|
|
106
113
|
}).pipe(
|
|
114
|
+
// ★ Scoped to Caddy's own transport — see the file header's ⚠️ — before the catch below,
|
|
115
|
+
// so `isUnreachable` still narrows the SAME raw error this always raised.
|
|
116
|
+
Effect.provide(transportLayer),
|
|
107
117
|
// ★ Nothing to adopt from a Caddy that is down: plan a create, which is the same load.
|
|
108
118
|
Effect.catchIf(isUnreachable, planWithoutCaddy(admin, undefined)),
|
|
109
119
|
),
|
|
@@ -112,7 +122,10 @@ export const CaddyConfigProvider = () =>
|
|
|
112
122
|
if (output === undefined) return Effect.succeed(undefined);
|
|
113
123
|
const update = planWithoutCaddy(admin, { action: 'update' as const });
|
|
114
124
|
if (isResolved(news)) {
|
|
115
|
-
return diffConfig(news, output).pipe(
|
|
125
|
+
return diffConfig(news, output).pipe(
|
|
126
|
+
Effect.provide(transportLayer),
|
|
127
|
+
Effect.catchIf(isUnreachable, update),
|
|
128
|
+
);
|
|
116
129
|
}
|
|
117
130
|
/**
|
|
118
131
|
* ⚠️ `sourceFile` is an Output while its HostFile changes in the same deploy
|
|
@@ -123,6 +136,7 @@ export const CaddyConfigProvider = () =>
|
|
|
123
136
|
const caddyfile = resolvedString(news, 'caddyfile');
|
|
124
137
|
if (caddyfile === undefined) return Effect.succeed(undefined);
|
|
125
138
|
return Effect.as(diffConfig({ caddyfile }, output), { action: 'update' as const }).pipe(
|
|
139
|
+
Effect.provide(transportLayer),
|
|
126
140
|
Effect.catchIf(isUnreachable, update),
|
|
127
141
|
);
|
|
128
142
|
},
|
|
@@ -136,7 +150,9 @@ export const CaddyConfigProvider = () =>
|
|
|
136
150
|
const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
|
|
137
151
|
const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
|
|
138
152
|
const stored = output === undefined ? {} : { stored: output.configSha256 };
|
|
139
|
-
const applied = yield* reconcileConfig(news, { takeOver, ...stored })
|
|
153
|
+
const applied = yield* reconcileConfig(news, { takeOver, ...stored }).pipe(
|
|
154
|
+
Effect.provide(transportLayer),
|
|
155
|
+
);
|
|
140
156
|
// ★ THE FORMATTING WARNING GETS ITS OWN LINE, NAMING THE FIX — separate from the rest,
|
|
141
157
|
// which keep logging exactly as before (format-warnings.ts).
|
|
142
158
|
const { formatting, rest } = splitFormattingWarning(applied.warnings);
|
package/src/caddy/fake-caddy.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* fake really listens on an ephemeral one: a Caddy on its default :2019 behind a forward.
|
|
19
19
|
*/
|
|
20
20
|
import * as Effect from 'effect/Effect';
|
|
21
|
+
import * as Layer from 'effect/Layer';
|
|
21
22
|
import type * as Caddy from '@distilled.cloud/caddy';
|
|
22
23
|
import { type CaddyAdminService, type CaddyTransport, caddyAdminLayer } from './admin.ts';
|
|
23
24
|
import { localCaddyAdmin } from './local-admin.ts';
|
|
@@ -190,8 +191,16 @@ export const fakeDefaultCaddy = (
|
|
|
190
191
|
* Runs an Effect that needs `CaddyAdminService` and `@distilled.cloud/caddy`'s own `Credentials`/
|
|
191
192
|
* `HttpClient` against one transport — the one line every test in this directory used to spend on
|
|
192
193
|
* `Effect.provide(caddyAdminLayer(admin))` before this helper existed.
|
|
194
|
+
* ⚠️ TWO LAYERS, NOT ONE: `caddyAdminLayer(admin)` only carries `CaddyAdminService` and the
|
|
195
|
+
* `CaddyAdminTransport`-wrapped VALUE of `admin.layer` (admin.ts's own scoping fix) — the
|
|
196
|
+
* lifecycle functions this helper drives (`probeLive`, `reconcileConfig`, …) still need the raw
|
|
197
|
+
* `Credentials`/`HttpClient.HttpClient` directly in context, so `admin.layer` is provided again,
|
|
198
|
+
* built this time, alongside it.
|
|
193
199
|
*/
|
|
194
200
|
export const runCaddy = <A, E>(
|
|
195
201
|
effect: Effect.Effect<A, E, CaddyAdminService | Caddy.CaddyOpContext>,
|
|
196
202
|
admin: CaddyTransport,
|
|
197
|
-
): Promise<A> =>
|
|
203
|
+
): Promise<A> =>
|
|
204
|
+
Effect.runPromise(
|
|
205
|
+
effect.pipe(Effect.provide(Layer.mergeAll(caddyAdminLayer(admin), admin.layer))),
|
|
206
|
+
);
|
|
@@ -7,7 +7,6 @@ import * as Output from 'alchemy/Output';
|
|
|
7
7
|
import { Stack } from 'alchemy/Stack';
|
|
8
8
|
import * as Effect from 'effect/Effect';
|
|
9
9
|
import * as Layer from 'effect/Layer';
|
|
10
|
-
import type * as Caddy from '@distilled.cloud/caddy';
|
|
11
10
|
import { type CaddyAdminService, caddyAdminLayer } from './admin.ts';
|
|
12
11
|
import { CaddyConfig, CaddyConfigProvider } from './config.ts';
|
|
13
12
|
import { type FakeCaddy, fakeCaddy } from './fake-caddy.ts';
|
|
@@ -31,9 +30,11 @@ const handler = <F>(name: string, fn: F | undefined): F => {
|
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
const withProvider = <A>(
|
|
33
|
+
// ★ `CaddyAdminService` ONLY — `read`/`diff`/`reconcile` no longer need the admin transport from
|
|
34
|
+
// OUTSIDE post-fix: config.ts's handlers `Effect.provide` it locally now (admin.ts's own ⛔).
|
|
34
35
|
use: (
|
|
35
36
|
p: Effect.Success<typeof CaddyConfig.Provider>,
|
|
36
|
-
) => Effect.Effect<A, unknown, CaddyAdminService
|
|
37
|
+
) => Effect.Effect<A, unknown, CaddyAdminService>,
|
|
37
38
|
address?: string,
|
|
38
39
|
) => {
|
|
39
40
|
// ★ A Caddy on its default :2019 behind a forward, as in config-lifecycle.test.ts.
|
package/src/caddy/providers.ts
CHANGED
|
@@ -12,17 +12,24 @@
|
|
|
12
12
|
* and `HttpClient.HttpClient`.
|
|
13
13
|
*
|
|
14
14
|
* ⚠️ `Layer.provideMerge`, NEVER PLAIN `Layer.provide`, for `caddyAdminLayer`. `CaddyConfigProvider()`'s
|
|
15
|
-
* `read`/`diff`/`reconcile` handlers each still need `CaddyAdminService` and
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
15
|
+
* `read`/`diff`/`reconcile` handlers each still need `CaddyAdminService` and `CaddyAdminTransport`
|
|
16
|
+
* — Alchemy's own `Provider.effect` types those as part of the LAYER's overall requirement
|
|
17
|
+
* (`ProviderService`'s per-handler `Req` type parameters, `alchemy/Provider.ts`), for exactly this
|
|
18
|
+
* reason. Plain `Layer.provide` SEALS a layer — it satisfies `CaddyConfigProvider()`'s requirement
|
|
19
|
+
* only for BUILDING the `Provider` value (the `admin`/`transportLayer` in its outer `Effect.gen`),
|
|
20
|
+
* then hides those services from anything that runs LATER, when the returned `read`/`diff`/
|
|
21
|
+
* `reconcile` FUNCTIONS actually execute. MEASURED 2026-09-23: with plain `Layer.provide`,
|
|
22
|
+
* `caddyProviders()`'s own `reconcile` handler dies with "Service not found: homeflare/caddy/
|
|
23
|
+
* CaddyAdmin" the moment the ENGINE calls it. `provideMerge` feeds `caddyAdminLayer`'s output into
|
|
24
|
+
* `CaddyConfigProvider()`'s requirement AND keeps that same output live in the result, so every
|
|
25
|
+
* later handler call still runs with it in context.
|
|
26
|
+
* ⛔ THIS `caddyProviders()` LAYER NEVER CARRIES `Credentials`/`HttpClient.HttpClient` ITSELF (fixed
|
|
27
|
+
* 2026-09-26 — admin.ts's own ⛔ has the mechanism and the measured leak). `caddyAdminLayer`'s
|
|
28
|
+
* output is `CaddyAdminService | CaddyAdminTransport`, both house-only tags safe to `provideMerge`
|
|
29
|
+
* into any host stack; the SDK's own `Credentials`/`HttpClient.HttpClient` stay inside
|
|
30
|
+
* `CaddyAdminTransport`'s stored value, `Effect.provide`d locally by config.ts around exactly the
|
|
31
|
+
* effects that need them. A stack merging `caddyProviders()` with any other fetch-based provider
|
|
32
|
+
* (`forgejoProviders()`, `litellmProviders()`, …) is safe from this file's own composition alone.
|
|
26
33
|
*/
|
|
27
34
|
import * as Layer from 'effect/Layer';
|
|
28
35
|
import { type CaddyTransport, caddyAdminLayer } from './admin.ts';
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* alongside the SDK's own `NotFound`. Proves the two stay distinct: only the SDK's tag folds.
|
|
5
5
|
*/
|
|
6
6
|
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
7
8
|
import * as organization from '@distilled.cloud/forgejo/organization';
|
|
8
9
|
import * as Effect from 'effect/Effect';
|
|
9
10
|
import { fakeFailure, fakeForgejo, fakeForgejoLayer } from './fake-forgejo.ts';
|
|
@@ -95,3 +96,16 @@ describe('Forgejo.TeamMember spec — the actual exported production functions',
|
|
|
95
96
|
expect(await readThrough(fake.fetch)).toBeUndefined();
|
|
96
97
|
});
|
|
97
98
|
});
|
|
99
|
+
|
|
100
|
+
test('ForgejoTeamMember declares defaultRemovalPolicy: retain on the Resource() call itself', () => {
|
|
101
|
+
// ⛔ Anchored on the `Resource<ForgejoTeamMember>(...)` CALL, not on the substring appearing
|
|
102
|
+
// anywhere in the file — team-member.ts's own header comment also says the phrase in prose, so
|
|
103
|
+
// a plain `source.toContain(...)` (database-refusals.test.ts's simpler version, safe there
|
|
104
|
+
// because database.ts's header never repeats the phrase) would pass here even if the option
|
|
105
|
+
// were dropped from the actual call. mini PR 82's red team (970e8be) had to pipe
|
|
106
|
+
// `RemovalPolicy.retain()` onto every call site because the kit resource had no default; this
|
|
107
|
+
// proves the kit default carries the fix so a caller no longer has to remember its own.
|
|
108
|
+
const source = readFileSync(new URL('./team-member.ts', import.meta.url), 'utf8');
|
|
109
|
+
const call = source.match(/Resource<ForgejoTeamMember>\([\s\S]*?\);/);
|
|
110
|
+
expect(call?.[0]).toContain("defaultRemovalPolicy: 'retain'");
|
|
111
|
+
});
|
|
@@ -22,6 +22,31 @@
|
|
|
22
22
|
* cannot create a team.
|
|
23
23
|
*
|
|
24
24
|
* ⛔ TOKEN NEEDS `write:organization` TO ADD OR REMOVE, and an identity allowed to manage the team.
|
|
25
|
+
*
|
|
26
|
+
* ⛔ `defaultRemovalPolicy: 'retain'` — FOUND 2026-09-25 by reading the source, not by measuring
|
|
27
|
+
* anything (mini PR 82's red team, `homeflare-mini` 970e8be): before this, `ForgejoRepository`/
|
|
28
|
+
* `ForgejoOrgLabel` defaulted to retain in the kit itself but this family did not, so alchemy's
|
|
29
|
+
* own engine fallback (`destroy`) applied to it — every caller had to remember its own
|
|
30
|
+
* `.pipe(RemovalPolicy.retain())`, the way mini's `src/forgejo/declare.ts` did for
|
|
31
|
+
* `forgejo-provision`'s row. Dropping a `TeamMember` declaration — toggling a feature gate off,
|
|
32
|
+
* renaming the resource id — must not silently call `organization.orgRemoveTeamMember` and
|
|
33
|
+
* revoke a real membership; that is the same "removal from the stack is not removal from
|
|
34
|
+
* Forgejo" posture `ForgejoOrgLabel` and `ForgejoBranchProtection` already take, this family
|
|
35
|
+
* exists to attach a stack's identity to a team it never creates or destroys (`teamIdOf` below
|
|
36
|
+
* only ever locates the team, never manages it), and the same trade-off alchemy's own
|
|
37
|
+
* `GitHub.Collaborator` and `GitHub.TeamAccess` make for the identical reason — "preventing
|
|
38
|
+
* accidental lockout" (`GitHub/Collaborator.ts:127`, `GitHub/TeamAccess.ts:145` in the pinned
|
|
39
|
+
* alchemy source). The trade-off, stated plainly: removing a declaration no longer revokes
|
|
40
|
+
* access on its own — the plan shows that row as `orphaned`, not `delete`. Opt into a real
|
|
41
|
+
* revoke with `.pipe(RemovalPolicy.destroy())`; `destroy` below is fully implemented.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ THE DEFAULT ONLY PROTECTS A ROW ALREADY IN STATE ONCE THIS RELEASE IS DEPLOYED. Alchemy's
|
|
44
|
+
* plan reads `removalPolicy` off the *persisted row* (`Plan.ts`'s delete-node builder), not off
|
|
45
|
+
* this default; the default only reaches an existing row's state on a deploy where the resource
|
|
46
|
+
* plans as a no-op (`Apply.ts`'s noop path rewrites it and logs "removal policy destroy →
|
|
47
|
+
* retain"). A consumer that bumps to this release and drops a `TeamMember` declaration in the
|
|
48
|
+
* SAME deploy still gets `delete`, not `orphaned` — see the changeset for the two-deploy shape
|
|
49
|
+
* this implies for both adopting retain and opting back into destroy.
|
|
25
50
|
*/
|
|
26
51
|
import { Resource } from 'alchemy';
|
|
27
52
|
import * as Provider from 'alchemy/Provider';
|
|
@@ -52,7 +77,9 @@ export interface ForgejoTeamMember extends Resource<
|
|
|
52
77
|
ForgejoRequirements
|
|
53
78
|
> {}
|
|
54
79
|
|
|
55
|
-
export const ForgejoTeamMember = Resource<ForgejoTeamMember>('Forgejo.TeamMember'
|
|
80
|
+
export const ForgejoTeamMember = Resource<ForgejoTeamMember>('Forgejo.TeamMember', {
|
|
81
|
+
defaultRemovalPolicy: 'retain',
|
|
82
|
+
});
|
|
56
83
|
|
|
57
84
|
export class ForgejoTeamNotFoundError extends Data.TaggedError('ForgejoTeamNotFoundError')<{
|
|
58
85
|
readonly message: string;
|
|
@@ -80,6 +80,7 @@ describe('every create this package makes, and what the vendor requires of it',
|
|
|
80
80
|
'pve:POST /nodes/{node}/lxc': ['ostemplate', 'vmid'],
|
|
81
81
|
'pve:POST /nodes/{node}/network': ['iface', 'type'],
|
|
82
82
|
'pve:POST /nodes/{node}/qemu': ['vmid'],
|
|
83
|
+
'pve:POST /nodes/{node}/storage/{storage}/download-url': ['content', 'filename', 'url'],
|
|
83
84
|
'pve:POST /pools': ['poolid'],
|
|
84
85
|
'pve:POST /storage': ['storage', 'type'],
|
|
85
86
|
});
|
|
@@ -22,6 +22,7 @@ import { PVE_NODES_DISKS_CONSTRAINTS } from './pve-nodes-disks.ts';
|
|
|
22
22
|
import { PVE_NODES_LXC_CONSTRAINTS } from './pve-nodes-lxc.ts';
|
|
23
23
|
import { PVE_NODES_NETWORK_CONSTRAINTS } from './pve-nodes-network.ts';
|
|
24
24
|
import { PVE_NODES_QEMU_CONSTRAINTS } from './pve-nodes-qemu.ts';
|
|
25
|
+
import { PVE_NODES_STORAGE_CONSTRAINTS } from './pve-nodes-storage.ts';
|
|
25
26
|
import { PVE_POOLS_CONSTRAINTS } from './pve-pools.ts';
|
|
26
27
|
import { PVE_STORAGE_CONSTRAINTS } from './pve-storage.ts';
|
|
27
28
|
import { PBS_CONFIG_CONSTRAINTS } from './pbs-config.ts';
|
|
@@ -41,6 +42,7 @@ export const PROXMOX_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>>
|
|
|
41
42
|
...PVE_NODES_LXC_CONSTRAINTS,
|
|
42
43
|
...PVE_NODES_NETWORK_CONSTRAINTS,
|
|
43
44
|
...PVE_NODES_QEMU_CONSTRAINTS,
|
|
45
|
+
...PVE_NODES_STORAGE_CONSTRAINTS,
|
|
44
46
|
...PVE_POOLS_CONSTRAINTS,
|
|
45
47
|
...PVE_STORAGE_CONSTRAINTS,
|
|
46
48
|
...PBS_CONFIG_CONSTRAINTS,
|
|
@@ -52,4 +54,4 @@ export const PROXMOX_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>>
|
|
|
52
54
|
* ★ A DIGEST OF THE DATA, NOT OF THE FILE TEXT, so reformatting is not a stale generation while
|
|
53
55
|
* changing a 128 to a 129 by hand is. `tests/schema-manifest.test.ts` recomputes it.
|
|
54
56
|
*/
|
|
55
|
-
export const PROXMOX_CONSTRAINTS_DIGEST = '
|
|
57
|
+
export const PROXMOX_CONSTRAINTS_DIGEST = '8b6745a471839974';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated pve-manager parameter constraints for `/nodes/{node}/storage` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Run: bun codegen/constraints.ts
|
|
5
|
+
* Manifest entry: `pve-apidoc` — pve-manager 9.2.11/f6997e698c7933ea
|
|
6
|
+
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
|
+
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
|
+
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
|
+
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
|
+
* and therefore unchecked at plan time.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ A `patternSource` with no `pattern` beside it is a rule that could NOT be carried into a
|
|
14
|
+
* JavaScript RegExp faithfully (codegen/pattern.ts). It is recorded and NOT enforced.
|
|
15
|
+
* ⚠️ `pattern` IS NOT THE VENDOR'S SPELLING. For PVE it is anchored, because PVE applies
|
|
16
|
+
* `m/^$pattern$/` itself (JSONSchema.pm); for PBS it is the vendor's own, which already
|
|
17
|
+
* carries its anchors. `patternSource` is the spelling to quote at a human — param-rules.ts.
|
|
18
|
+
* ⚠️ `each: true` means the value rules describe every ELEMENT of a repeated key, because the
|
|
19
|
+
* parameter is an array and stated its limits on `items`.
|
|
20
|
+
*/
|
|
21
|
+
import type { EndpointConstraints } from '../../constraints.ts';
|
|
22
|
+
|
|
23
|
+
export const PVE_NODES_STORAGE_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>> = {
|
|
24
|
+
"pve:POST /nodes/{node}/storage/{storage}/download-url": {
|
|
25
|
+
"checksum-algorithm": {"enum":["md5","sha1","sha224","sha256","sha384","sha512"],"type":"string"},
|
|
26
|
+
"content": {"enum":["iso","vztmpl","import"],"format":"pve-storage-content","required":true,"type":"string"},
|
|
27
|
+
"filename": {"maxLength":255,"required":true,"type":"string"},
|
|
28
|
+
"url": {"pattern":"^https?://.*\\n?$","patternSource":"https?://.*","required":true,"type":"string"},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
7
|
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
10
|
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
11
|
* and therefore unchecked at plan time.
|
|
12
12
|
*
|