@homeflare/alchemy 0.57.0 → 0.59.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 +17 -0
- package/dist/caddy/config.d.ts +6 -0
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/index.js +128 -61
- package/dist/caddy/index.js.map +6 -4
- package/dist/scope/duplicate-id.d.ts +32 -0
- package/dist/scope/duplicate-id.d.ts.map +1 -0
- package/dist/scope/guard.d.ts +28 -0
- package/dist/scope/guard.d.ts.map +1 -0
- package/dist/scope/index.d.ts +21 -0
- package/dist/scope/index.d.ts.map +1 -0
- package/dist/scope/index.js +71 -0
- package/dist/scope/index.js.map +11 -0
- package/dist/unifi/firewall-policy-drift.d.ts +3 -3
- package/dist/unifi/firewall-policy-drift.d.ts.map +1 -1
- package/dist/unifi/firewall-policy-form.d.ts +18 -15
- package/dist/unifi/firewall-policy-form.d.ts.map +1 -1
- package/dist/unifi/index.js +64 -8
- package/dist/unifi/index.js.map +5 -5
- package/dist/unifi/network-update.fixtures.d.ts +53 -0
- package/dist/unifi/network-update.fixtures.d.ts.map +1 -0
- package/dist/unifi/update-reconcile.d.ts.map +1 -1
- package/docs/duplicate-resource-id.md +91 -0
- package/docs/provider-standard.md +9 -0
- package/package.json +5 -1
- package/src/caddy/config.ts +12 -3
- package/src/scope/duplicate-id.test.ts +95 -0
- package/src/scope/duplicate-id.ts +113 -0
- package/src/scope/guard.test.ts +128 -0
- package/src/scope/guard.ts +49 -0
- package/src/scope/index.ts +20 -0
- package/src/unifi/firewall-policy-drift.test.ts +64 -0
- package/src/unifi/firewall-policy-drift.ts +24 -7
- package/src/unifi/firewall-policy-form.ts +61 -15
- package/src/unifi/network-update-accept.test.ts +59 -0
- package/src/unifi/network-update.fixtures.ts +74 -0
- package/src/unifi/network-update.test.ts +11 -69
- package/src/unifi/update-reconcile.ts +37 -3
package/README.md
CHANGED
|
@@ -385,6 +385,23 @@ before export: every header dropped, every query string stripped, a denylisted h
|
|
|
385
385
|
blanked. Options, the redaction policy and what a spike measured actually arriving at a collector:
|
|
386
386
|
[docs/telemetry.md](./docs/telemetry.md) · [docs/telemetry-spike.md](./docs/telemetry-spike.md).
|
|
387
387
|
|
|
388
|
+
## Duplicate resource ids — `@homeflare/alchemy/scope`
|
|
389
|
+
|
|
390
|
+
⛔ Two resources of different types declaring the same logical id in one stack scope used to
|
|
391
|
+
collide silently — Alchemy's own idempotent-registration path treats the second one as a
|
|
392
|
+
reference to the first, and the second resource's props never reach a plan. `guardDuplicateIds`
|
|
393
|
+
wraps a resource class so a collision dies at registration, naming both types:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
import { Resource } from 'alchemy';
|
|
397
|
+
import { guardDuplicateIds } from '@homeflare/alchemy/scope';
|
|
398
|
+
|
|
399
|
+
export const MyResource = guardDuplicateIds(Resource<MyResource>('My.Resource'));
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The measured incident, what this does and does not cover, and the upstream issue proposed for
|
|
403
|
+
`alchemy` itself: [docs/duplicate-resource-id.md](./docs/duplicate-resource-id.md).
|
|
404
|
+
|
|
388
405
|
## Credentials
|
|
389
406
|
|
|
390
407
|
⛔ **Mint a short-lived, scoped token** — never a long-lived one, never a Global API Key. How each
|
package/dist/caddy/config.d.ts
CHANGED
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
* place that discharges them — scoped to the handful of effects that actually call
|
|
29
29
|
* `@distilled.cloud/caddy`'s operations, never leaking into a sibling fetch-based provider merged
|
|
30
30
|
* into the same stack (admin.ts's own ⛔; MEASURED 2026-09-26, admin-transport-scope.test.ts).
|
|
31
|
+
* ⛔ `CaddyConfig` IS WRAPPED IN `guardDuplicateIds` (scope/guard.ts). MEASURED, homeflare-mini PR
|
|
32
|
+
* 329: a `NixJobRetirement('caddy', …)` declared in the same stack as this resource's `CADDY_ID
|
|
33
|
+
* = 'caddy'` collided silently — Alchemy's own `Resource()` treated the second registration as
|
|
34
|
+
* an idempotent re-reference to the first, so the retirement's props were never written
|
|
35
|
+
* anywhere a plan could see (`scope/duplicate-id.ts` has the full mechanism). This resource was
|
|
36
|
+
* one half of that collision, so it is the first to carry the guard.
|
|
31
37
|
*/
|
|
32
38
|
import { Resource } from 'alchemy';
|
|
33
39
|
import * as Provider from 'alchemy/Provider';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAK7C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAKhF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,gBAAgB,EAChB,qBAAqB,CACtB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAIvB,CAAC;AAoBF,eAAO,MAAM,mBAAmB,oHA+F7B,CAAC"}
|
package/dist/caddy/index.js
CHANGED
|
@@ -169,7 +169,7 @@ import { Resource } from "alchemy";
|
|
|
169
169
|
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
170
170
|
import { isResolved } from "alchemy/Diff";
|
|
171
171
|
import * as Provider from "alchemy/Provider";
|
|
172
|
-
import * as
|
|
172
|
+
import * as Effect13 from "effect/Effect";
|
|
173
173
|
|
|
174
174
|
// src/launchd/host-effect.ts
|
|
175
175
|
import * as Effect2 from "effect/Effect";
|
|
@@ -395,21 +395,88 @@ var refuseTakeover = (owner, what) => Effect6.gen(function* () {
|
|
|
395
395
|
});
|
|
396
396
|
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
397
397
|
|
|
398
|
+
// src/scope/guard.ts
|
|
399
|
+
import * as Effect8 from "effect/Effect";
|
|
400
|
+
|
|
401
|
+
// src/scope/duplicate-id.ts
|
|
402
|
+
import { toFqn } from "alchemy/FQN";
|
|
403
|
+
import { CurrentNamespace } from "alchemy/Namespace";
|
|
404
|
+
import { Stack as Stack4 } from "alchemy/Stack";
|
|
405
|
+
import * as Data from "effect/Data";
|
|
406
|
+
import * as Effect7 from "effect/Effect";
|
|
407
|
+
|
|
408
|
+
class DuplicateResourceIdError extends Data.TaggedError("DuplicateResourceIdError") {
|
|
409
|
+
}
|
|
410
|
+
var scopeLabel = (namespace) => {
|
|
411
|
+
if (!namespace)
|
|
412
|
+
return "(root)";
|
|
413
|
+
const path = [];
|
|
414
|
+
let node = namespace;
|
|
415
|
+
while (node) {
|
|
416
|
+
path.unshift(node.Id);
|
|
417
|
+
node = node.Parent;
|
|
418
|
+
}
|
|
419
|
+
return path.join("/");
|
|
420
|
+
};
|
|
421
|
+
var registries = new WeakMap;
|
|
422
|
+
var registryFor = (stack) => {
|
|
423
|
+
const existing = registries.get(stack);
|
|
424
|
+
if (existing)
|
|
425
|
+
return existing;
|
|
426
|
+
const created = new Map;
|
|
427
|
+
registries.set(stack, created);
|
|
428
|
+
return created;
|
|
429
|
+
};
|
|
430
|
+
var claimResourceId = (type, id) => Effect7.gen(function* () {
|
|
431
|
+
const stack = yield* Stack4;
|
|
432
|
+
const namespace = yield* CurrentNamespace;
|
|
433
|
+
const fqn = toFqn(namespace, id);
|
|
434
|
+
const registry = registryFor(stack);
|
|
435
|
+
const firstType = registry.get(fqn);
|
|
436
|
+
if (firstType === undefined) {
|
|
437
|
+
registry.set(fqn, type);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (firstType !== type) {
|
|
441
|
+
return yield* Effect7.die(new DuplicateResourceIdError({
|
|
442
|
+
message: `REFUSING: duplicate resource id "${fqn}" in scope ${scopeLabel(namespace)}: ` + `first declared by ${firstType}, again by ${type}`,
|
|
443
|
+
fqn,
|
|
444
|
+
scope: scopeLabel(namespace),
|
|
445
|
+
firstType,
|
|
446
|
+
secondType: type
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// src/scope/guard.ts
|
|
452
|
+
var guardDuplicateIds = (resourceClass) => new Proxy(resourceClass, {
|
|
453
|
+
apply(target, thisArg, args) {
|
|
454
|
+
const id = args[0];
|
|
455
|
+
if (typeof id !== "string") {
|
|
456
|
+
return Reflect.apply(target, thisArg, args);
|
|
457
|
+
}
|
|
458
|
+
return Effect8.gen(function* () {
|
|
459
|
+
yield* claimResourceId(resourceClass.Type, id);
|
|
460
|
+
return yield* Reflect.apply(target, thisArg, args);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
398
465
|
// src/caddy/config-lifecycle.ts
|
|
399
|
-
import * as
|
|
466
|
+
import * as Effect11 from "effect/Effect";
|
|
400
467
|
import * as Result2 from "effect/Result";
|
|
401
468
|
|
|
402
469
|
// src/caddy/admin-calls.ts
|
|
403
|
-
import * as
|
|
470
|
+
import * as Effect9 from "effect/Effect";
|
|
404
471
|
import * as Caddy from "@distilled.cloud/caddy";
|
|
405
472
|
var CADDYFILE = "text/caddyfile";
|
|
406
473
|
var describe = (warning) => `${warning.file ?? "Caddyfile"}:${String(warning.line ?? 0)}: ${warning.message ?? "(no message)"}`;
|
|
407
|
-
var adaptCaddyfile = (caddyfile) =>
|
|
474
|
+
var adaptCaddyfile = (caddyfile) => Effect9.map(Caddy.Services.admin.adaptConfig({ config: caddyfile, contentType: CADDYFILE }), (response) => ({
|
|
408
475
|
config: response.result ?? null,
|
|
409
476
|
warnings: (response.warnings ?? []).map(describe)
|
|
410
477
|
}));
|
|
411
|
-
var readRunningConfig = () =>
|
|
412
|
-
var loadCaddyfile = (caddyfile, sourceFile) =>
|
|
478
|
+
var readRunningConfig = () => Effect9.map(Caddy.Services.admin.getConfig({ path: "" }), (response) => response.value ?? null);
|
|
479
|
+
var loadCaddyfile = (caddyfile, sourceFile) => Effect9.map(Caddy.Services.admin.loadConfig({
|
|
413
480
|
config: caddyfile,
|
|
414
481
|
contentType: CADDYFILE,
|
|
415
482
|
...sourceFile === undefined ? {} : { sourceAdapter: "caddyfile", sourceFile }
|
|
@@ -494,12 +561,12 @@ var adminProblems = (adapted, transport) => {
|
|
|
494
561
|
};
|
|
495
562
|
|
|
496
563
|
// src/caddy/config-errors.ts
|
|
497
|
-
import * as
|
|
564
|
+
import * as Effect10 from "effect/Effect";
|
|
498
565
|
var refuse = (endpoint, message) => new Error(`Caddy.Config at ${endpoint}: ${message}`);
|
|
499
566
|
var short = (digest) => digest.slice(0, 12);
|
|
500
567
|
var messageOf = (error) => ("message" in error) && typeof error.message === "string" ? error.message : String(error);
|
|
501
568
|
var operationFailure = (endpoint, op, error) => error._tag === "LoadRefused" ? refuse(endpoint, `Caddy refused the Caddyfile (${error._tag}, a 200 whose body carried the error, not a ` + `failure status) — ${error.message}`) : refuse(endpoint, `${op} failed (${error._tag}) — ${messageOf(error)}`);
|
|
502
|
-
var wrapOperation = (endpoint, op) => (effect) => effect.pipe(
|
|
569
|
+
var wrapOperation = (endpoint, op) => (effect) => effect.pipe(Effect10.mapError((error) => isUnreachable(error) ? error : operationFailure(endpoint, op, error)));
|
|
503
570
|
|
|
504
571
|
// src/launchd/secret-tripwire.ts
|
|
505
572
|
var ALTERNATIVE = "props are stored unencrypted in Alchemy state. Have a secret renderer (e.g. openbao-agent) " + "write it to a root-owned file and declare the file path instead (FOO_FILE=/path, --foo-file /path).";
|
|
@@ -848,18 +915,18 @@ var servesNothing = (config) => {
|
|
|
848
915
|
const apps = typeof config === "object" && config !== null ? config.apps : undefined;
|
|
849
916
|
return typeof apps !== "object" || apps === null || Object.keys(apps).length === 0;
|
|
850
917
|
};
|
|
851
|
-
var desiredConfig = (props) =>
|
|
918
|
+
var desiredConfig = (props) => Effect11.gen(function* () {
|
|
852
919
|
const { endpoint, listener } = yield* CaddyAdminService;
|
|
853
920
|
const found = configProblems(props);
|
|
854
921
|
if (found.length > 0)
|
|
855
|
-
return yield*
|
|
922
|
+
return yield* Effect11.fail(refuse(endpoint, found.join("; ")));
|
|
856
923
|
const adapted = yield* adaptCaddyfile(props.caddyfile).pipe(wrapOperation(endpoint, "POST /adapt"));
|
|
857
924
|
if (servesNothing(adapted.config)) {
|
|
858
|
-
return yield*
|
|
925
|
+
return yield* Effect11.fail(refuse(endpoint, "the Caddyfile adapts to no apps — loading it would stop every site"));
|
|
859
926
|
}
|
|
860
927
|
const guard = adminProblems(adapted.config, listener);
|
|
861
928
|
if (guard.length > 0)
|
|
862
|
-
return yield*
|
|
929
|
+
return yield* Effect11.fail(refuse(endpoint, guard.join("; ")));
|
|
863
930
|
return { digest: configDigest(adapted.config), warnings: adapted.warnings };
|
|
864
931
|
});
|
|
865
932
|
var attributesOf = (endpoint, running, sourceFile) => ({
|
|
@@ -867,13 +934,13 @@ var attributesOf = (endpoint, running, sourceFile) => ({
|
|
|
867
934
|
endpoint,
|
|
868
935
|
...sourceFile === undefined ? {} : { sourceFile }
|
|
869
936
|
});
|
|
870
|
-
var readLive = (sourceFile) =>
|
|
937
|
+
var readLive = (sourceFile) => Effect11.gen(function* () {
|
|
871
938
|
const { endpoint } = yield* CaddyAdminService;
|
|
872
939
|
const running = yield* readRunningConfig().pipe(wrapOperation(endpoint, "GET /config/"));
|
|
873
940
|
return attributesOf(endpoint, running, sourceFile);
|
|
874
941
|
});
|
|
875
942
|
var claimable = (running, declaredDigest) => servesNothing(running) || configDigest(running) === declaredDigest;
|
|
876
|
-
var probeLive = (caddyfile, sourceFile) =>
|
|
943
|
+
var probeLive = (caddyfile, sourceFile) => Effect11.gen(function* () {
|
|
877
944
|
const { endpoint } = yield* CaddyAdminService;
|
|
878
945
|
const running = yield* readRunningConfig();
|
|
879
946
|
if (servesNothing(running))
|
|
@@ -882,12 +949,12 @@ var probeLive = (caddyfile, sourceFile) => Effect9.gen(function* () {
|
|
|
882
949
|
if (caddyfile === undefined) {
|
|
883
950
|
return { attributes, ours: false, unchecked: "the stored Caddyfile is not plain text" };
|
|
884
951
|
}
|
|
885
|
-
const desired = yield*
|
|
952
|
+
const desired = yield* Effect11.result(desiredConfig({ caddyfile }));
|
|
886
953
|
if (Result2.isSuccess(desired)) {
|
|
887
954
|
return { attributes, ours: claimable(running, desired.success.digest) };
|
|
888
955
|
}
|
|
889
956
|
if (isUnreachable(desired.failure))
|
|
890
|
-
return yield*
|
|
957
|
+
return yield* Effect11.fail(desired.failure);
|
|
891
958
|
const cause = desired.failure;
|
|
892
959
|
return {
|
|
893
960
|
attributes,
|
|
@@ -897,7 +964,7 @@ var probeLive = (caddyfile, sourceFile) => Effect9.gen(function* () {
|
|
|
897
964
|
});
|
|
898
965
|
|
|
899
966
|
// src/caddy/config-reconcile.ts
|
|
900
|
-
import * as
|
|
967
|
+
import * as Effect12 from "effect/Effect";
|
|
901
968
|
import * as Result3 from "effect/Result";
|
|
902
969
|
|
|
903
970
|
// src/caddy/format-warnings.ts
|
|
@@ -909,100 +976,100 @@ var splitFormattingWarning = (warnings) => ({
|
|
|
909
976
|
var formattingFixLine = (endpoint) => `Caddy.Config at ${endpoint}: Caddyfile is not formatted — run \`caddy fmt\` on it, or format ` + "it with this package's formatCaddyfile() before declaring it, to silence this warning.";
|
|
910
977
|
|
|
911
978
|
// src/caddy/config-reconcile.ts
|
|
912
|
-
var diffConfig = (news, output) =>
|
|
979
|
+
var diffConfig = (news, output) => Effect12.gen(function* () {
|
|
913
980
|
const { endpoint } = yield* CaddyAdminService;
|
|
914
981
|
const want = yield* desiredConfig(news);
|
|
915
982
|
const { formatting } = splitFormattingWarning(want.warnings);
|
|
916
983
|
if (formatting.length > 0)
|
|
917
|
-
yield*
|
|
984
|
+
yield* Effect12.logWarning(formattingFixLine(endpoint));
|
|
918
985
|
const live = yield* readLive();
|
|
919
986
|
const converged = want.digest === live.configSha256 && live.configSha256 === output.configSha256 && news.sourceFile === output.sourceFile && endpoint === output.endpoint;
|
|
920
987
|
return converged ? { action: "noop" } : { action: "update" };
|
|
921
988
|
});
|
|
922
|
-
var reconcileConfig = (props, authority) =>
|
|
989
|
+
var reconcileConfig = (props, authority) => Effect12.gen(function* () {
|
|
923
990
|
const { endpoint } = yield* CaddyAdminService;
|
|
924
991
|
const want = yield* desiredConfig(props);
|
|
925
992
|
const running = yield* readRunningConfig().pipe(wrapOperation(endpoint, "GET /config/"));
|
|
926
993
|
const before = configDigest(running);
|
|
927
994
|
const known = before === authority.stored || claimable(running, want.digest);
|
|
928
995
|
if (!authority.takeOver && !known) {
|
|
929
|
-
return yield*
|
|
996
|
+
return yield* Effect12.fail(refuse(endpoint, `Caddy runs config ${short(before)}, which this stack did not load, and the Caddyfile ` + `adapts to ${short(want.digest)} — loading it would replace every site that config ` + "serves. Deploy with --adopt (or wrap the resource in adopt(true)) to take this Caddy " + "over, or point caddyProviders() at the Caddy you meant."));
|
|
930
997
|
}
|
|
931
998
|
let warnings = want.warnings;
|
|
932
999
|
const loaded = before !== want.digest;
|
|
933
1000
|
if (loaded) {
|
|
934
|
-
const outcome = yield*
|
|
1001
|
+
const outcome = yield* Effect12.result(loadCaddyfile(props.caddyfile, props.sourceFile));
|
|
935
1002
|
if (Result3.isFailure(outcome)) {
|
|
936
1003
|
const error = outcome.failure;
|
|
937
1004
|
if (isUnreachable(error))
|
|
938
|
-
return yield*
|
|
939
|
-
const afterRead = yield*
|
|
1005
|
+
return yield* Effect12.fail(error);
|
|
1006
|
+
const afterRead = yield* Effect12.result(readLive());
|
|
940
1007
|
const after = Result3.isSuccess(afterRead) ? afterRead.success : undefined;
|
|
941
1008
|
const state = after === undefined ? "and the running config could not be read back" : after.configSha256 === before ? "Caddy kept the previous config (still serving)" : `and the running config is now ${short(after.configSha256)}, not the previous ${short(before)}`;
|
|
942
1009
|
const reason = error._tag === "LoadRefused" ? `${error.message} (LoadRefused: a 200 whose body carried the error, not a failure status)` : messageOf(error);
|
|
943
|
-
return yield*
|
|
1010
|
+
return yield* Effect12.fail(refuse(endpoint, `Caddy refused the Caddyfile — ${reason}; ${state}`));
|
|
944
1011
|
}
|
|
945
1012
|
warnings = outcome.success;
|
|
946
1013
|
}
|
|
947
1014
|
const attributes = yield* readLive(props.sourceFile);
|
|
948
1015
|
if (attributes.configSha256 !== want.digest) {
|
|
949
|
-
return yield*
|
|
1016
|
+
return yield* Effect12.fail(refuse(endpoint, `after the load Caddy runs config ${short(attributes.configSha256)}, not the ` + `${short(want.digest)} this Caddyfile adapts to — another writer changed it, or it was ` + "not applied"));
|
|
950
1017
|
}
|
|
951
1018
|
return { attributes, loaded, warnings };
|
|
952
1019
|
});
|
|
953
1020
|
|
|
954
1021
|
// src/caddy/config.ts
|
|
955
|
-
var CaddyConfig = Resource("Caddy.Config", {
|
|
1022
|
+
var CaddyConfig = guardDuplicateIds(Resource("Caddy.Config", {
|
|
956
1023
|
defaultRemovalPolicy: "retain"
|
|
957
|
-
});
|
|
958
|
-
var planWithoutCaddy = (admin, fallback) => (error) =>
|
|
959
|
-
var CaddyConfigProvider = () => Provider.effect(CaddyConfig,
|
|
1024
|
+
}));
|
|
1025
|
+
var planWithoutCaddy = (admin, fallback) => (error) => Effect13.as(Effect13.logWarning(`Caddy.Config: no Caddy at ${admin.endpoint} ` + `(${error instanceof Error ? error.message : String(error)}); planning without it — ` + "the Caddyfile is validated when the deploy reaches Caddy"), fallback);
|
|
1026
|
+
var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect13.gen(function* () {
|
|
960
1027
|
const admin = yield* CaddyAdminService;
|
|
961
1028
|
const transportLayer = yield* CaddyAdminTransport;
|
|
962
|
-
const probe = (caddyfile, sourceFile) =>
|
|
1029
|
+
const probe = (caddyfile, sourceFile) => Effect13.flatMap(probeLive(caddyfile, sourceFile), (found) => {
|
|
963
1030
|
if (found === undefined)
|
|
964
|
-
return
|
|
1031
|
+
return Effect13.succeed(undefined);
|
|
965
1032
|
if (found.ours)
|
|
966
|
-
return
|
|
1033
|
+
return Effect13.succeed(found.attributes);
|
|
967
1034
|
const unowned = Unowned2(found.attributes);
|
|
968
|
-
return found.unchecked === undefined ?
|
|
1035
|
+
return found.unchecked === undefined ? Effect13.succeed(unowned) : Effect13.as(Effect13.logWarning(`Caddy.Config at ${admin.endpoint}: the running config could not be compared ` + `with the declared Caddyfile (${found.unchecked}), so it is not treated as ours`), unowned);
|
|
969
1036
|
});
|
|
970
1037
|
return CaddyConfig.Provider.of({
|
|
971
|
-
list: () =>
|
|
972
|
-
read: ({ olds, output }) =>
|
|
1038
|
+
list: () => Effect13.succeed([]),
|
|
1039
|
+
read: ({ olds, output }) => Effect13.gen(function* () {
|
|
973
1040
|
return output === undefined ? yield* probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : yield* readLive(resolvedString(olds, "sourceFile"));
|
|
974
|
-
}).pipe(
|
|
1041
|
+
}).pipe(Effect13.provide(transportLayer), Effect13.catchIf(isUnreachable, planWithoutCaddy(admin, undefined))),
|
|
975
1042
|
diff: ({ news, output }) => {
|
|
976
1043
|
if (output === undefined)
|
|
977
|
-
return
|
|
1044
|
+
return Effect13.succeed(undefined);
|
|
978
1045
|
const update = planWithoutCaddy(admin, { action: "update" });
|
|
979
1046
|
if (isResolved(news)) {
|
|
980
|
-
return diffConfig(news, output).pipe(
|
|
1047
|
+
return diffConfig(news, output).pipe(Effect13.provide(transportLayer), Effect13.catchIf(isUnreachable, update));
|
|
981
1048
|
}
|
|
982
1049
|
const caddyfile = resolvedString(news, "caddyfile");
|
|
983
1050
|
if (caddyfile === undefined)
|
|
984
|
-
return
|
|
985
|
-
return
|
|
1051
|
+
return Effect13.succeed(undefined);
|
|
1052
|
+
return Effect13.as(diffConfig({ caddyfile }, output), { action: "update" }).pipe(Effect13.provide(transportLayer), Effect13.catchIf(isUnreachable, update));
|
|
986
1053
|
},
|
|
987
|
-
reconcile: ({ fqn, news, output }) =>
|
|
1054
|
+
reconcile: ({ fqn, news, output }) => Effect13.gen(function* () {
|
|
988
1055
|
const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
|
|
989
1056
|
const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
|
|
990
1057
|
const stored = output === undefined ? {} : { stored: output.configSha256 };
|
|
991
|
-
const applied = yield* reconcileConfig(news, { takeOver, ...stored }).pipe(
|
|
1058
|
+
const applied = yield* reconcileConfig(news, { takeOver, ...stored }).pipe(Effect13.provide(transportLayer));
|
|
992
1059
|
const { formatting, rest } = splitFormattingWarning(applied.warnings);
|
|
993
1060
|
if (formatting.length > 0) {
|
|
994
|
-
yield*
|
|
1061
|
+
yield* Effect13.logWarning(formattingFixLine(admin.endpoint));
|
|
995
1062
|
}
|
|
996
1063
|
for (const warning of rest) {
|
|
997
|
-
yield*
|
|
1064
|
+
yield* Effect13.logWarning(`Caddy.Config at ${admin.endpoint}: ${warning}`);
|
|
998
1065
|
}
|
|
999
1066
|
return applied.attributes;
|
|
1000
1067
|
}),
|
|
1001
|
-
delete: () =>
|
|
1068
|
+
delete: () => Effect13.void
|
|
1002
1069
|
});
|
|
1003
1070
|
}));
|
|
1004
1071
|
// src/caddy/format.ts
|
|
1005
|
-
import * as
|
|
1072
|
+
import * as Effect14 from "effect/Effect";
|
|
1006
1073
|
import * as Stream from "effect/Stream";
|
|
1007
1074
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
1008
1075
|
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
|
|
@@ -1029,7 +1096,7 @@ class CaddyFmtFailed extends Error {
|
|
|
1029
1096
|
this.name = "CaddyFmtFailed";
|
|
1030
1097
|
}
|
|
1031
1098
|
}
|
|
1032
|
-
var formatCaddyfile = (text, options = {}) =>
|
|
1099
|
+
var formatCaddyfile = (text, options = {}) => Effect14.gen(function* () {
|
|
1033
1100
|
const binaryPath = options.binaryPath ?? DEFAULT_CADDY_BINARY;
|
|
1034
1101
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
1035
1102
|
const input = new TextEncoder().encode(text);
|
|
@@ -1038,13 +1105,13 @@ var formatCaddyfile = (text, options = {}) => Effect12.gen(function* () {
|
|
|
1038
1105
|
stderr: "pipe",
|
|
1039
1106
|
stdin: Stream.make(input),
|
|
1040
1107
|
stdout: "pipe"
|
|
1041
|
-
}).pipe(spawner.spawn,
|
|
1108
|
+
}).pipe(spawner.spawn, Effect14.flatMap((child) => Effect14.all({
|
|
1042
1109
|
exitCode: child.exitCode,
|
|
1043
|
-
stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString,
|
|
1110
|
+
stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect14.map((out) => out.trim())),
|
|
1044
1111
|
stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString)
|
|
1045
|
-
}, { concurrency: "unbounded" })),
|
|
1112
|
+
}, { concurrency: "unbounded" })), Effect14.scoped, Effect14.catchTag("PlatformError", (error) => error.reason._tag === "NotFound" ? Effect14.fail(new CaddyFmtNotFound(binaryPath, error)) : Effect14.fail(error)));
|
|
1046
1113
|
if (result4.exitCode !== 0) {
|
|
1047
|
-
return yield*
|
|
1114
|
+
return yield* Effect14.fail(new CaddyFmtFailed(binaryPath, result4.exitCode, result4.stderr));
|
|
1048
1115
|
}
|
|
1049
1116
|
return result4.stdout;
|
|
1050
1117
|
});
|
|
@@ -1115,7 +1182,7 @@ var localCaddyAdmin = (options = {}) => {
|
|
|
1115
1182
|
import * as Layer4 from "effect/Layer";
|
|
1116
1183
|
var caddyProviders = (transport = localCaddyAdmin()) => CaddyConfigProvider().pipe(Layer4.provideMerge(caddyAdminLayer(transport)));
|
|
1117
1184
|
// src/caddy/with-file.ts
|
|
1118
|
-
import * as
|
|
1185
|
+
import * as Effect16 from "effect/Effect";
|
|
1119
1186
|
import * as RemovalPolicy from "alchemy/RemovalPolicy";
|
|
1120
1187
|
|
|
1121
1188
|
// src/launchd/host-file.ts
|
|
@@ -1123,7 +1190,7 @@ import { Resource as Resource2 } from "alchemy";
|
|
|
1123
1190
|
import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
|
|
1124
1191
|
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
1125
1192
|
import * as Provider2 from "alchemy/Provider";
|
|
1126
|
-
import * as
|
|
1193
|
+
import * as Effect15 from "effect/Effect";
|
|
1127
1194
|
|
|
1128
1195
|
// src/launchd/file-identity.ts
|
|
1129
1196
|
var sameEntry = (a, b) => {
|
|
@@ -1318,10 +1385,10 @@ var deleteFile = (runner, output) => removeWholeFile(runner, output, refuse3);
|
|
|
1318
1385
|
|
|
1319
1386
|
// src/launchd/host-file.ts
|
|
1320
1387
|
var HostFile = Resource2("Host.File");
|
|
1321
|
-
var HostFileProvider = () => Provider2.effect(HostFile,
|
|
1388
|
+
var HostFileProvider = () => Provider2.effect(HostFile, Effect15.gen(function* () {
|
|
1322
1389
|
const runner = yield* HostRunnerService;
|
|
1323
1390
|
return HostFile.Provider.of({
|
|
1324
|
-
list: () =>
|
|
1391
|
+
list: () => Effect15.succeed([]),
|
|
1325
1392
|
read: ({ olds, output }) => lift(async () => {
|
|
1326
1393
|
const found = await readFileAttributes(runner, olds.path);
|
|
1327
1394
|
if (found === undefined)
|
|
@@ -1335,19 +1402,19 @@ var HostFileProvider = () => Provider2.effect(HostFile, Effect13.gen(function* (
|
|
|
1335
1402
|
return lift(() => diffFile(runner, news, output));
|
|
1336
1403
|
const path = resolvedString(news, "path");
|
|
1337
1404
|
if (path === undefined || path === output.path)
|
|
1338
|
-
return
|
|
1405
|
+
return Effect15.succeed(undefined);
|
|
1339
1406
|
return lift(async () => await oneFile(runner, path, output.path) === true ? undefined : { action: "replace" });
|
|
1340
1407
|
},
|
|
1341
|
-
reconcile: ({ fqn, instanceId, news, output }) =>
|
|
1408
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect15.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
|
|
1342
1409
|
delete: ({ output }) => lift(() => deleteFile(runner, output))
|
|
1343
1410
|
});
|
|
1344
1411
|
}));
|
|
1345
1412
|
|
|
1346
1413
|
// src/caddy/with-file.ts
|
|
1347
|
-
var caddyWithFile = (id, props) =>
|
|
1414
|
+
var caddyWithFile = (id, props) => Effect16.gen(function* () {
|
|
1348
1415
|
const found = configProblems({ caddyfile: props.caddyfile, sourceFile: props.path });
|
|
1349
1416
|
if (found.length > 0) {
|
|
1350
|
-
return yield*
|
|
1417
|
+
return yield* Effect16.fail(new Error(`caddyWithFile ${id}: ${found.join("; ")}`));
|
|
1351
1418
|
}
|
|
1352
1419
|
const file = yield* HostFile(`${id}-file`, {
|
|
1353
1420
|
content: props.caddyfile,
|
|
@@ -1377,5 +1444,5 @@ export {
|
|
|
1377
1444
|
verifierProblems
|
|
1378
1445
|
};
|
|
1379
1446
|
|
|
1380
|
-
//# debugId=
|
|
1447
|
+
//# debugId=E80D7FC971F2355764756E2164756E21
|
|
1381
1448
|
//# sourceMappingURL=index.js.map
|