@homeflare/alchemy 0.8.0 → 0.10.1
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 -11
- package/dist/caddy/config-lifecycle.d.ts +1 -1
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/index.js +221 -53
- package/dist/caddy/index.js.map +13 -9
- package/dist/launchd/fake-runner.d.ts +1 -0
- package/dist/launchd/fake-runner.d.ts.map +1 -1
- package/dist/launchd/fake-sudo.d.ts +1 -0
- package/dist/launchd/fake-sudo.d.ts.map +1 -1
- package/dist/launchd/host-effect.d.ts +9 -18
- package/dist/launchd/host-effect.d.ts.map +1 -1
- package/dist/launchd/host-file-lifecycle.d.ts +14 -1
- package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
- package/dist/launchd/host-file.d.ts +2 -1
- package/dist/launchd/host-file.d.ts.map +1 -1
- package/dist/launchd/index.js +345 -86
- package/dist/launchd/index.js.map +18 -12
- package/dist/launchd/job-lifecycle.d.ts +8 -1
- package/dist/launchd/job-lifecycle.d.ts.map +1 -1
- package/dist/launchd/job-preflight.d.ts +13 -1
- package/dist/launchd/job-preflight.d.ts.map +1 -1
- package/dist/launchd/job.d.ts +2 -1
- package/dist/launchd/job.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +7 -0
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-acl.d.ts +25 -0
- package/dist/launchd/sudo-acl.d.ts.map +1 -0
- package/dist/launchd/sudo-guard.d.ts +31 -4
- package/dist/launchd/sudo-guard.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts +3 -3
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/launchd/sudo-said.d.ts +10 -0
- package/dist/launchd/sudo-said.d.ts.map +1 -0
- package/dist/openbao/auth-method-reconcile.d.ts +6 -2
- package/dist/openbao/auth-method-reconcile.d.ts.map +1 -1
- package/dist/openbao/auth-method.d.ts.map +1 -1
- package/dist/openbao/auth-role.d.ts.map +1 -1
- package/dist/openbao/cloudflare-role.d.ts.map +1 -1
- package/dist/openbao/fake-bao.d.ts +18 -0
- package/dist/openbao/fake-bao.d.ts.map +1 -1
- package/dist/openbao/fake-core.d.ts +39 -0
- package/dist/openbao/fake-core.d.ts.map +1 -0
- package/dist/openbao/fake-families.d.ts +6 -3
- package/dist/openbao/fake-families.d.ts.map +1 -1
- package/dist/openbao/fake-mounts.d.ts +16 -0
- package/dist/openbao/fake-mounts.d.ts.map +1 -0
- package/dist/openbao/fake-stack.d.ts +5 -1
- package/dist/openbao/fake-stack.d.ts.map +1 -1
- package/dist/openbao/index.js +541 -270
- package/dist/openbao/index.js.map +26 -21
- package/dist/openbao/jwt-config.d.ts.map +1 -1
- package/dist/openbao/jwt-role.d.ts.map +1 -1
- package/dist/openbao/kubernetes-role.d.ts.map +1 -1
- package/dist/openbao/mfa-enforcement.d.ts +6 -2
- package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
- package/dist/openbao/mfa-totp.d.ts +6 -2
- package/dist/openbao/mfa-totp.d.ts.map +1 -1
- package/dist/openbao/mount-reconcile.d.ts +5 -1
- package/dist/openbao/mount-reconcile.d.ts.map +1 -1
- package/dist/openbao/mount.d.ts.map +1 -1
- package/dist/openbao/pki-role.d.ts.map +1 -1
- package/dist/openbao/plugin-reconcile.d.ts +6 -2
- package/dist/openbao/plugin-reconcile.d.ts.map +1 -1
- package/dist/openbao/plugin.d.ts.map +1 -1
- package/dist/openbao/policy.d.ts.map +1 -1
- package/dist/openbao/proxmox-role.d.ts.map +1 -1
- package/dist/openbao/role-reconcile.d.ts +11 -1
- package/dist/openbao/role-reconcile.d.ts.map +1 -1
- package/dist/openbao/ssh-role.d.ts.map +1 -1
- package/dist/ownership/adopt.d.ts +52 -0
- package/dist/ownership/adopt.d.ts.map +1 -0
- package/dist/ownership/probe.d.ts +13 -0
- package/dist/ownership/probe.d.ts.map +1 -0
- package/dist/ownership/resume.d.ts +40 -0
- package/dist/ownership/resume.d.ts.map +1 -0
- package/dist/ownership/rows.d.ts +33 -0
- package/dist/ownership/rows.d.ts.map +1 -0
- package/dist/ownership/whole.d.ts +14 -0
- package/dist/ownership/whole.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool-form.d.ts +3 -3
- package/dist/proxmox/ceph-pool.d.ts.map +1 -1
- package/dist/proxmox/fake-bao-env.d.ts +3 -0
- package/dist/proxmox/fake-bao-env.d.ts.map +1 -0
- package/dist/proxmox/fake-pve-lxc.d.ts +42 -0
- package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -0
- package/dist/proxmox/fake-pve.d.ts +38 -0
- package/dist/proxmox/fake-pve.d.ts.map +1 -0
- package/dist/proxmox/index.d.ts +6 -2
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +970 -186
- package/dist/proxmox/index.js.map +22 -9
- package/dist/proxmox/lxc-create-form.d.ts +6 -0
- package/dist/proxmox/lxc-create-form.d.ts.map +1 -0
- package/dist/proxmox/lxc-harness.d.ts +34 -0
- package/dist/proxmox/lxc-harness.d.ts.map +1 -0
- package/dist/proxmox/lxc-identity.d.ts +18 -0
- package/dist/proxmox/lxc-identity.d.ts.map +1 -0
- package/dist/proxmox/lxc-judge.d.ts +46 -0
- package/dist/proxmox/lxc-judge.d.ts.map +1 -0
- package/dist/proxmox/lxc-lifecycle.d.ts +68 -0
- package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/lxc-props.d.ts +139 -0
- package/dist/proxmox/lxc-props.d.ts.map +1 -0
- package/dist/proxmox/lxc-volume.d.ts +29 -0
- package/dist/proxmox/lxc-volume.d.ts.map +1 -0
- package/dist/proxmox/lxc-wire.d.ts +51 -0
- package/dist/proxmox/lxc-wire.d.ts.map +1 -0
- package/dist/proxmox/lxc.d.ts +27 -47
- package/dist/proxmox/lxc.d.ts.map +1 -1
- package/dist/proxmox/network-apply-read.d.ts +1 -1
- package/dist/proxmox/network-apply-read.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/update-guard.d.ts +27 -0
- package/dist/proxmox/update-guard.d.ts.map +1 -0
- package/dist/verify/args.d.ts +16 -0
- package/dist/verify/args.d.ts.map +1 -0
- package/dist/verify/cli.d.ts +3 -0
- package/dist/verify/cli.d.ts.map +1 -0
- package/dist/verify/cli.js +370 -0
- package/dist/verify/cli.js.map +17 -0
- package/dist/verify/fake-engine.d.ts +65 -0
- package/dist/verify/fake-engine.d.ts.map +1 -0
- package/dist/verify/index.d.ts +16 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +347 -0
- package/dist/verify/index.js.map +16 -0
- package/dist/verify/plan-scope.d.ts +14 -0
- package/dist/verify/plan-scope.d.ts.map +1 -0
- package/dist/verify/recheck.d.ts +42 -0
- package/dist/verify/recheck.d.ts.map +1 -0
- package/dist/verify/report.d.ts +6 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/rows.d.ts +67 -0
- package/dist/verify/rows.d.ts.map +1 -0
- package/dist/verify/spy.d.ts +43 -0
- package/dist/verify/spy.d.ts.map +1 -0
- package/dist/verify/verify.d.ts +43 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/docs/adopt-verify.md +119 -0
- package/docs/adopted-deploys.md +85 -0
- package/docs/credentials.md +15 -0
- package/docs/launchd-sudo.md +31 -6
- package/docs/launchd.md +7 -3
- package/docs/ownership.md +113 -0
- package/docs/proxmox-lxc.md +196 -0
- package/docs/proxmox.md +12 -0
- package/package.json +9 -2
- package/src/caddy/adopt-scope.test.ts +1 -1
- package/src/caddy/config-lifecycle.ts +1 -1
- package/src/caddy/config.ts +2 -1
- package/src/launchd/adopt-apply.test.ts +166 -0
- package/src/launchd/fake-runner.ts +26 -4
- package/src/launchd/host-effect.ts +1 -33
- package/src/launchd/host-file-lifecycle.ts +39 -14
- package/src/launchd/host-file.ts +13 -4
- package/src/launchd/job-lifecycle.ts +22 -14
- package/src/launchd/job-preflight.ts +18 -1
- package/src/launchd/job.ts +13 -4
- package/src/launchd/runner.ts +7 -0
- package/src/launchd/sudo-acl.ts +68 -0
- package/src/launchd/sudo-chain.test.ts +121 -0
- package/src/launchd/sudo-guard.ts +89 -10
- package/src/launchd/sudo-modes.test.ts +135 -0
- package/src/launchd/sudo-runner.ts +40 -59
- package/src/launchd/sudo-said.ts +58 -0
- package/src/openbao/README.md +5 -2
- package/src/openbao/REPLACE.md +13 -11
- package/src/openbao/adopt-core.test.ts +119 -0
- package/src/openbao/adopt-families.test.ts +97 -0
- package/src/openbao/adopt-holes.test.ts +168 -0
- package/src/openbao/adopt-recovery.test.ts +71 -0
- package/src/openbao/auth-method-reconcile.ts +15 -3
- package/src/openbao/auth-method.ts +15 -6
- package/src/openbao/auth-role.ts +14 -4
- package/src/openbao/cloudflare-role.ts +17 -4
- package/src/openbao/fake-bao.ts +52 -0
- package/src/openbao/fake-core.ts +141 -0
- package/src/openbao/fake-families.ts +11 -5
- package/src/openbao/fake-mounts.ts +74 -0
- package/src/openbao/fake-stack.ts +17 -4
- package/src/openbao/jwt-config.ts +10 -7
- package/src/openbao/jwt-role.ts +10 -7
- package/src/openbao/kubernetes-role.ts +13 -7
- package/src/openbao/mfa-enforcement.ts +20 -6
- package/src/openbao/mfa-totp.ts +22 -7
- package/src/openbao/mount-move.test.ts +2 -35
- package/src/openbao/mount-reconcile.ts +13 -2
- package/src/openbao/mount.ts +15 -6
- package/src/openbao/pki-role.ts +15 -4
- package/src/openbao/plugin-reconcile.ts +16 -5
- package/src/openbao/plugin.ts +17 -6
- package/src/openbao/policy.ts +17 -4
- package/src/openbao/proxmox-role.ts +13 -4
- package/src/openbao/rename-adoption.test.ts +32 -19
- package/src/openbao/role-reconcile.ts +23 -0
- package/src/openbao/ssh-role.ts +14 -4
- package/src/ownership/adopt.ts +122 -0
- package/src/ownership/ownership.test.ts +200 -0
- package/src/ownership/probe.ts +65 -0
- package/src/ownership/resume.ts +118 -0
- package/src/ownership/rows.ts +117 -0
- package/src/ownership/whole.test.ts +39 -0
- package/src/ownership/whole.ts +60 -0
- package/src/proxmox/adopt-noop.test.ts +112 -0
- package/src/proxmox/ceph-pool-adopt.test.ts +120 -0
- package/src/proxmox/ceph-pool-form.ts +3 -3
- package/src/proxmox/ceph-pool.ts +10 -3
- package/src/proxmox/fake-bao-env.ts +28 -0
- package/src/proxmox/fake-pve-lxc.ts +210 -0
- package/src/proxmox/fake-pve.ts +75 -0
- package/src/proxmox/index.test.ts +6 -6
- package/src/proxmox/index.ts +6 -2
- package/src/proxmox/lxc-adopt.test.ts +170 -0
- package/src/proxmox/lxc-create-form.ts +76 -0
- package/src/proxmox/lxc-create.test.ts +126 -0
- package/src/proxmox/lxc-guards.test.ts +158 -0
- package/src/proxmox/lxc-harness.ts +150 -0
- package/src/proxmox/lxc-identity.ts +43 -0
- package/src/proxmox/lxc-judge.test.ts +175 -0
- package/src/proxmox/lxc-judge.ts +196 -0
- package/src/proxmox/lxc-lifecycle.ts +200 -0
- package/src/proxmox/lxc-ownership.test.ts +159 -0
- package/src/proxmox/lxc-props.ts +215 -0
- package/src/proxmox/lxc-read.test.ts +60 -0
- package/src/proxmox/lxc-volume.test.ts +96 -0
- package/src/proxmox/lxc-volume.ts +153 -0
- package/src/proxmox/lxc-wire.ts +210 -0
- package/src/proxmox/lxc.ts +216 -93
- package/src/proxmox/network-apply-read.ts +3 -1
- package/src/proxmox/pbs-datastore.ts +4 -3
- package/src/proxmox/resource.ts +7 -7
- package/src/proxmox/update-guard.ts +31 -0
- package/src/verify/args.ts +82 -0
- package/src/verify/cli.test.ts +86 -0
- package/src/verify/cli.ts +49 -0
- package/src/verify/fake-engine.ts +182 -0
- package/src/verify/index.ts +37 -0
- package/src/verify/plan-scope.ts +34 -0
- package/src/verify/recheck.test.ts +56 -0
- package/src/verify/recheck.ts +71 -0
- package/src/verify/report.ts +52 -0
- package/src/verify/rows.ts +210 -0
- package/src/verify/spy.test.ts +141 -0
- package/src/verify/spy.ts +160 -0
- package/src/verify/verify.test.ts +146 -0
- package/src/verify/verify.ts +185 -0
package/README.md
CHANGED
|
@@ -104,6 +104,11 @@ Cloudflare and Proxmox roles, plugins — plus `assertBaoIdentity` (call it firs
|
|
|
104
104
|
⛔ **Metadata only.** Alchemy stores props and attributes unencrypted, so no secret, CA key,
|
|
105
105
|
plugin `env` or OIDC client secret is declarable. Every family defaults to `retain` on destroy.
|
|
106
106
|
|
|
107
|
+
⛔ **Nothing is adopted without `--adopt`** (0.9.0), for every family and even when the live object
|
|
108
|
+
is identical to the declaration; an interrupted create of this stack's own resumes when its row
|
|
109
|
+
can prove it (one killed while a prop was an `Output` needs `--adopt`). The same rule holds for
|
|
110
|
+
`HostFile`, `LaunchdJob`, `CaddyConfig` and `ProxmoxLxc`: [docs/ownership.md](./docs/ownership.md).
|
|
111
|
+
|
|
107
112
|
★ **Usage lives beside the code**, so it ships in the tarball with it:
|
|
108
113
|
[src/openbao/README.md](./src/openbao/README.md). What each resource does on a rename is in
|
|
109
114
|
[src/openbao/REPLACE.md](./src/openbao/REPLACE.md) — read it before changing a path or name.
|
|
@@ -131,9 +136,10 @@ one is logged (argv only, never content) before it runs. A plan never calls sudo
|
|
|
131
136
|
|
|
132
137
|
- ⛔ **Opt-in, never a fallback**, and it never prompts: if sudo needs a password, the call fails at
|
|
133
138
|
once and says to run `sudo -v`, or to grant exactly these commands `NOPASSWD`.
|
|
134
|
-
- ⚠️ Refused before sudo is asked: any other argv, a prefix
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
- ⚠️ Refused before sudo is asked: any other argv, a prefix — or any directory below it on the way
|
|
140
|
+
to the file — that is not a real directory only root may write, a root-owned file that would be
|
|
141
|
+
group/world-writable, setuid or setgid, a path outside every prefix that needs root, a file you
|
|
142
|
+
could not read back, and another user's `gui/<uid>`. A plan that will write runs the same checks.
|
|
137
143
|
- Full list, sudoers cautions and limits: [docs/launchd-sudo.md](./docs/launchd-sudo.md).
|
|
138
144
|
|
|
139
145
|
## Caddy — `@homeflare/alchemy/caddy`
|
|
@@ -151,16 +157,21 @@ with `HostFile`; `caddyProviders()` provides the transport, `http://127.0.0.1:20
|
|
|
151
157
|
- ★ **Managed Caddies run `--resume` with their own `XDG_CONFIG_HOME`**, so a restart runs the last
|
|
152
158
|
config Caddy accepted — and after one, SIGUSR1 has no file to reload. Why, and the rest: same doc.
|
|
153
159
|
|
|
154
|
-
##
|
|
160
|
+
## Proxmox — `@homeflare/alchemy/proxmox`
|
|
161
|
+
|
|
162
|
+
PVE and PBS objects over the PVE API, and `ProxmoxLxc` for containers. What the subpath never does
|
|
163
|
+
to a guest, and why: [docs/proxmox.md](./docs/proxmox.md).
|
|
155
164
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
## Adopt verifier — `hf-adopt-verify` / `@homeflare/alchemy/verify`
|
|
166
|
+
|
|
167
|
+
⛔ A plan prints `adopted` for a match and for a drift alike. Before a gated deploy run
|
|
168
|
+
`bunx --bun hf-adopt-verify --config alchemy.run.ts --stage live`: Alchemy's planner, no writes,
|
|
169
|
+
each row's own diff, exit 0 only when all are no-ops. [docs/adopt-verify.md](./docs/adopt-verify.md)
|
|
170
|
+
|
|
171
|
+
## Credentials
|
|
159
172
|
|
|
160
|
-
⛔ **Mint a short-lived, scoped token** —
|
|
161
|
-
|
|
162
|
-
a denied grant rather than a missing file, and `Bearer ` + nothing 401s in a way that reads
|
|
163
|
-
like a bad credential.
|
|
173
|
+
⛔ **Mint a short-lived, scoped token** — never a long-lived one, never a Global API Key. How each
|
|
174
|
+
subpath reads its credential, and why an empty one fails closed: [docs/credentials.md](./docs/credentials.md).
|
|
164
175
|
|
|
165
176
|
## Peers — and one override you need
|
|
166
177
|
|
|
@@ -70,7 +70,7 @@ export type Applied = {
|
|
|
70
70
|
* What this apply may load over — config.ts decides from the state and the adopt setting:
|
|
71
71
|
* · `takeOver` — any running config: the state was applied to THIS Caddy (an update, drift
|
|
72
72
|
* correction, or a create the engine adopted), or adoption is on (`--adopt`, or the
|
|
73
|
-
* resource's own `adopt(true)` —
|
|
73
|
+
* resource's own `adopt(true)` — ownership/adopt.ts adoptEnabled).
|
|
74
74
|
* · otherwise only a claimable one — plus, as `stored`, the digest the state last recorded: a
|
|
75
75
|
* Caddy reached at a NEW endpoint that runs exactly that is the same config under another name.
|
|
76
76
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAAE,iBAAiB,EAAyB,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGhF,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,8CAEtB,CAAC;AAsBH,eAAO,MAAM,mBAAmB,8FAsF7B,CAAC"}
|
package/dist/caddy/index.js
CHANGED
|
@@ -292,17 +292,13 @@ var loadCaddyfile = async (admin, caddyfile, sourceFile) => {
|
|
|
292
292
|
};
|
|
293
293
|
// src/caddy/config.ts
|
|
294
294
|
import { Resource } from "alchemy";
|
|
295
|
-
import { Unowned } from "alchemy/AdoptPolicy";
|
|
295
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
296
296
|
import { isResolved } from "alchemy/Diff";
|
|
297
297
|
import * as Provider from "alchemy/Provider";
|
|
298
|
-
import * as
|
|
298
|
+
import * as Effect6 from "effect/Effect";
|
|
299
299
|
|
|
300
300
|
// src/launchd/host-effect.ts
|
|
301
|
-
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
302
|
-
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
303
|
-
import { Stack } from "alchemy/Stack";
|
|
304
301
|
import * as Effect from "effect/Effect";
|
|
305
|
-
import * as Option from "effect/Option";
|
|
306
302
|
var lift = (step) => Effect.tryPromise({
|
|
307
303
|
catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
|
|
308
304
|
try: step
|
|
@@ -313,17 +309,183 @@ var resolvedString = (news, key) => {
|
|
|
313
309
|
const value = news[key];
|
|
314
310
|
return typeof value === "string" ? value : undefined;
|
|
315
311
|
};
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
312
|
+
|
|
313
|
+
// src/ownership/adopt.ts
|
|
314
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
315
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
316
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
317
|
+
import * as Effect5 from "effect/Effect";
|
|
318
|
+
import * as Option4 from "effect/Option";
|
|
319
|
+
|
|
320
|
+
// src/ownership/resume.ts
|
|
321
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
322
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
323
|
+
import * as Effect2 from "effect/Effect";
|
|
324
|
+
import * as Option from "effect/Option";
|
|
325
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
326
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
327
|
+
var note = (key, instanceId) => Effect2.gen(function* () {
|
|
328
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
329
|
+
if (Option.isSome(bag))
|
|
330
|
+
yield* bag.value.set(key, instanceId);
|
|
331
|
+
});
|
|
332
|
+
var noted = (key, instanceId) => Effect2.gen(function* () {
|
|
333
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
334
|
+
if (Option.isNone(bag))
|
|
335
|
+
return false;
|
|
336
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
337
|
+
});
|
|
338
|
+
var noteUnfinished = (instanceId) => Effect2.as(note(UNFINISHED, instanceId), undefined);
|
|
339
|
+
var noteResume = (instanceId, recovered) => Effect2.gen(function* () {
|
|
340
|
+
yield* noteUnfinished(instanceId);
|
|
341
|
+
const unproven = () => Effect2.succeed(undefined);
|
|
342
|
+
const found = yield* recovered.pipe(Effect2.catch(unproven), Effect2.catchDefect(unproven));
|
|
343
|
+
if (found === undefined || found === null || Unowned.is(found))
|
|
344
|
+
return;
|
|
345
|
+
yield* note(PROVEN, instanceId);
|
|
346
|
+
});
|
|
347
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
348
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
349
|
+
var provingResumes = (service) => {
|
|
350
|
+
const { diff, read } = service;
|
|
351
|
+
if (diff === undefined || read === undefined)
|
|
352
|
+
return service;
|
|
353
|
+
const proving = (input) => Effect2.gen(function* () {
|
|
354
|
+
if (input.output === undefined) {
|
|
355
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
356
|
+
}
|
|
357
|
+
return yield* diff(input);
|
|
358
|
+
});
|
|
359
|
+
return { ...service, diff: proving };
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/ownership/rows.ts
|
|
363
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
364
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
365
|
+
import * as Effect4 from "effect/Effect";
|
|
366
|
+
import * as Option3 from "effect/Option";
|
|
367
|
+
|
|
368
|
+
// src/ownership/whole.ts
|
|
369
|
+
import { Stack } from "alchemy/Stack";
|
|
370
|
+
import * as Effect3 from "effect/Effect";
|
|
371
|
+
import * as Option2 from "effect/Option";
|
|
372
|
+
var absent = (value) => value === undefined || value === null;
|
|
373
|
+
var isPlainObject2 = (value) => {
|
|
374
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
375
|
+
return false;
|
|
376
|
+
const proto = Object.getPrototypeOf(value);
|
|
377
|
+
return proto === Object.prototype || proto === null;
|
|
378
|
+
};
|
|
379
|
+
var carries = (declared, recorded) => {
|
|
380
|
+
if (absent(declared))
|
|
381
|
+
return true;
|
|
382
|
+
if (absent(recorded))
|
|
383
|
+
return false;
|
|
384
|
+
if (Array.isArray(declared)) {
|
|
385
|
+
if (!Array.isArray(recorded))
|
|
386
|
+
return false;
|
|
387
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
388
|
+
}
|
|
389
|
+
if (isPlainObject2(declared)) {
|
|
390
|
+
if (!isPlainObject2(recorded))
|
|
391
|
+
return false;
|
|
392
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
};
|
|
396
|
+
var wholeDeclaration = (fqn, props) => Effect3.map(Effect3.serviceOption(Stack), (stack) => {
|
|
397
|
+
const declared = Option2.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
398
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/ownership/rows.ts
|
|
402
|
+
var storeOf = Effect4.gen(function* () {
|
|
403
|
+
const stack = yield* Effect4.serviceOption(Stack2);
|
|
404
|
+
const state = yield* Effect4.serviceOption(State);
|
|
405
|
+
if (Option3.isNone(stack) || Option3.isNone(state))
|
|
406
|
+
return;
|
|
407
|
+
const store = yield* state.value;
|
|
408
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
409
|
+
return { resources: stack.value.resources, store, where };
|
|
410
|
+
});
|
|
411
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect4.orElseSucceed(() => {
|
|
412
|
+
return;
|
|
413
|
+
}));
|
|
414
|
+
var generationOf = (row, instanceId) => {
|
|
415
|
+
let at = row;
|
|
416
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
417
|
+
const generation = at;
|
|
418
|
+
if (generation.instanceId === instanceId)
|
|
419
|
+
return generation;
|
|
420
|
+
at = generation.old;
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
};
|
|
424
|
+
var recordedGeneration = (fqn, instanceId) => Effect4.gen(function* () {
|
|
425
|
+
const found = yield* storeOf;
|
|
426
|
+
if (found === undefined)
|
|
427
|
+
return;
|
|
428
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
429
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
430
|
+
if (generation !== undefined)
|
|
431
|
+
return generation;
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
});
|
|
435
|
+
var recordedInstance = (fqn, instanceId) => Effect4.gen(function* () {
|
|
436
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
437
|
+
if (generation === undefined)
|
|
438
|
+
return "absent";
|
|
439
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
440
|
+
});
|
|
441
|
+
var isCreate = (fqn, instanceId) => Effect4.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
442
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect4.gen(function* () {
|
|
443
|
+
const found = yield* storeOf;
|
|
444
|
+
if (found === undefined)
|
|
445
|
+
return;
|
|
446
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
447
|
+
const mine = row;
|
|
448
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect4.ignore);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// src/ownership/adopt.ts
|
|
455
|
+
var adoptEnabled = (fqn) => Effect5.gen(function* () {
|
|
456
|
+
const stack = yield* Effect5.serviceOption(Stack3);
|
|
457
|
+
const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
319
458
|
if (scoped !== undefined)
|
|
320
459
|
return scoped;
|
|
321
|
-
const policy = yield*
|
|
322
|
-
if (
|
|
460
|
+
const policy = yield* Effect5.serviceOption(AdoptPolicy);
|
|
461
|
+
if (Option4.isSome(policy))
|
|
323
462
|
return policy.value;
|
|
324
|
-
const context = yield*
|
|
325
|
-
return
|
|
463
|
+
const context = yield* Effect5.serviceOption(AlchemyContext);
|
|
464
|
+
return Option4.isSome(context) ? context.value.adopt : false;
|
|
465
|
+
});
|
|
466
|
+
var adoptable = (owner) => Effect5.gen(function* () {
|
|
467
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
468
|
+
return true;
|
|
469
|
+
return yield* unfinished(owner.instanceId);
|
|
470
|
+
});
|
|
471
|
+
var adoptsAtApply = (owner) => Effect5.gen(function* () {
|
|
472
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
473
|
+
return false;
|
|
474
|
+
return yield* adoptable(owner);
|
|
475
|
+
});
|
|
476
|
+
var refuseTakeover = (owner, what) => Effect5.gen(function* () {
|
|
477
|
+
if (owner.output !== undefined)
|
|
478
|
+
return;
|
|
479
|
+
if (yield* resumes(owner.instanceId))
|
|
480
|
+
return;
|
|
481
|
+
const may = yield* adoptable(owner);
|
|
482
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
483
|
+
return;
|
|
484
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
485
|
+
const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
|
|
486
|
+
return yield* Effect5.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
|
|
326
487
|
});
|
|
488
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
327
489
|
|
|
328
490
|
// src/caddy/admin-guard.ts
|
|
329
491
|
var DEFAULT_ADMIN_PORT = 2019;
|
|
@@ -629,46 +791,46 @@ var CaddyConfig = Resource("Caddy.Config", {
|
|
|
629
791
|
defaultRemovalPolicy: "retain"
|
|
630
792
|
});
|
|
631
793
|
var unreachable = (error) => error instanceof CaddyUnreachableError;
|
|
632
|
-
var planWithoutCaddy = (admin, fallback) => (error) =>
|
|
633
|
-
var CaddyConfigProvider = () => Provider.effect(CaddyConfig,
|
|
794
|
+
var planWithoutCaddy = (admin, fallback) => (error) => Effect6.as(Effect6.logWarning(`Caddy.Config: no Caddy at ${admin.endpoint} (${error.message}); planning without it — ` + "the Caddyfile is validated when the deploy reaches Caddy"), fallback);
|
|
795
|
+
var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect6.gen(function* () {
|
|
634
796
|
const admin = yield* CaddyAdminService;
|
|
635
|
-
const probe = (caddyfile, sourceFile) =>
|
|
797
|
+
const probe = (caddyfile, sourceFile) => Effect6.flatMap(lift(() => probeLive(admin, caddyfile, sourceFile)), (found) => {
|
|
636
798
|
if (found === undefined)
|
|
637
|
-
return
|
|
799
|
+
return Effect6.succeed(undefined);
|
|
638
800
|
if (found.ours)
|
|
639
|
-
return
|
|
640
|
-
const unowned =
|
|
641
|
-
return found.unchecked === undefined ?
|
|
801
|
+
return Effect6.succeed(found.attributes);
|
|
802
|
+
const unowned = Unowned2(found.attributes);
|
|
803
|
+
return found.unchecked === undefined ? Effect6.succeed(unowned) : Effect6.as(Effect6.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);
|
|
642
804
|
});
|
|
643
805
|
return CaddyConfig.Provider.of({
|
|
644
|
-
list: () =>
|
|
645
|
-
read: ({ olds, output }) => (output === undefined ? probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : lift(() => readLive(admin, resolvedString(olds, "sourceFile")))).pipe(
|
|
806
|
+
list: () => Effect6.succeed([]),
|
|
807
|
+
read: ({ olds, output }) => (output === undefined ? probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : lift(() => readLive(admin, resolvedString(olds, "sourceFile")))).pipe(Effect6.catchIf(unreachable, planWithoutCaddy(admin, undefined))),
|
|
646
808
|
diff: ({ news, output }) => {
|
|
647
809
|
if (output === undefined)
|
|
648
|
-
return
|
|
810
|
+
return Effect6.succeed(undefined);
|
|
649
811
|
const update = planWithoutCaddy(admin, { action: "update" });
|
|
650
812
|
if (isResolved(news)) {
|
|
651
|
-
return lift(() => diffConfig(admin, news, output)).pipe(
|
|
813
|
+
return lift(() => diffConfig(admin, news, output)).pipe(Effect6.catchIf(unreachable, update));
|
|
652
814
|
}
|
|
653
815
|
const caddyfile = resolvedString(news, "caddyfile");
|
|
654
816
|
if (caddyfile === undefined)
|
|
655
|
-
return
|
|
817
|
+
return Effect6.succeed(undefined);
|
|
656
818
|
return lift(async () => {
|
|
657
819
|
await diffConfig(admin, { caddyfile }, output);
|
|
658
820
|
return { action: "update" };
|
|
659
|
-
}).pipe(
|
|
821
|
+
}).pipe(Effect6.catchIf(unreachable, update));
|
|
660
822
|
},
|
|
661
|
-
reconcile: ({ fqn, news, output }) =>
|
|
823
|
+
reconcile: ({ fqn, news, output }) => Effect6.gen(function* () {
|
|
662
824
|
const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
|
|
663
825
|
const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
|
|
664
826
|
const stored = output === undefined ? {} : { stored: output.configSha256 };
|
|
665
827
|
const applied = yield* lift(() => reconcileConfig(admin, news, { takeOver, ...stored }));
|
|
666
828
|
for (const warning of applied.warnings) {
|
|
667
|
-
yield*
|
|
829
|
+
yield* Effect6.logWarning(`Caddy.Config at ${admin.endpoint}: ${warning}`);
|
|
668
830
|
}
|
|
669
831
|
return applied.attributes;
|
|
670
832
|
}),
|
|
671
|
-
delete: () =>
|
|
833
|
+
delete: () => Effect6.void
|
|
672
834
|
});
|
|
673
835
|
}));
|
|
674
836
|
// src/caddy/local-admin.ts
|
|
@@ -766,15 +928,15 @@ var localCaddyAdmin = (options = {}) => {
|
|
|
766
928
|
import * as Layer2 from "effect/Layer";
|
|
767
929
|
var caddyProviders = (admin = localCaddyAdmin()) => CaddyConfigProvider().pipe(Layer2.provide(caddyAdminLayer(admin)));
|
|
768
930
|
// src/caddy/with-file.ts
|
|
769
|
-
import * as
|
|
931
|
+
import * as Effect8 from "effect/Effect";
|
|
770
932
|
import * as RemovalPolicy from "alchemy/RemovalPolicy";
|
|
771
933
|
|
|
772
934
|
// src/launchd/host-file.ts
|
|
773
935
|
import { Resource as Resource2 } from "alchemy";
|
|
774
|
-
import { Unowned as
|
|
936
|
+
import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
|
|
775
937
|
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
776
938
|
import * as Provider2 from "alchemy/Provider";
|
|
777
|
-
import * as
|
|
939
|
+
import * as Effect7 from "effect/Effect";
|
|
778
940
|
|
|
779
941
|
// src/launchd/host-file-form.ts
|
|
780
942
|
var DEFAULT_MODE = 420;
|
|
@@ -859,17 +1021,26 @@ var readFileAttributes = async (runner, path) => {
|
|
|
859
1021
|
uid: stat.uid
|
|
860
1022
|
};
|
|
861
1023
|
};
|
|
1024
|
+
var writeOptionsOf = (want) => ({
|
|
1025
|
+
mode: want.mode,
|
|
1026
|
+
...want.uid === undefined ? {} : { uid: want.uid },
|
|
1027
|
+
...want.gid === undefined ? {} : { gid: want.gid }
|
|
1028
|
+
});
|
|
862
1029
|
var matches = (live, want) => live.sha256 === want.sha256 && live.mode === want.mode && (want.uid === undefined || live.uid === want.uid) && (want.gid === undefined || live.gid === want.gid);
|
|
863
1030
|
var diffFile = async (runner, news, output) => {
|
|
864
|
-
if (news.path !== output.path)
|
|
865
|
-
return { action: "replace" };
|
|
866
1031
|
const want = await desiredFile(runner, news);
|
|
1032
|
+
const writes = async (diff) => {
|
|
1033
|
+
await runner.checkWrite?.(news.path, writeOptionsOf(want));
|
|
1034
|
+
return diff;
|
|
1035
|
+
};
|
|
1036
|
+
if (news.path !== output.path)
|
|
1037
|
+
return writes({ action: "replace" });
|
|
867
1038
|
if (want.sha256 !== output.sha256)
|
|
868
|
-
return { action: "update" };
|
|
1039
|
+
return writes({ action: "update" });
|
|
869
1040
|
const live = await readFileAttributes(runner, news.path);
|
|
870
|
-
return live !== undefined && matches(live, want) ? { action: "noop" } : { action: "update" };
|
|
1041
|
+
return live !== undefined && matches(live, want) ? { action: "noop" } : writes({ action: "update" });
|
|
871
1042
|
};
|
|
872
|
-
var reconcileFile = async (runner, props, output) => {
|
|
1043
|
+
var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
873
1044
|
const want = await desiredFile(runner, props);
|
|
874
1045
|
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
875
1046
|
throw refuse3(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy as root ` + "or through a privileged HostRunner (sudoRunner() with this path under a prefix). This " + "provider never calls sudo itself.");
|
|
@@ -881,15 +1052,12 @@ var reconcileFile = async (runner, props, output) => {
|
|
|
881
1052
|
const before = stat === undefined ? undefined : await readFileAttributes(runner, props.path);
|
|
882
1053
|
const moved = output !== undefined && output.path !== props.path;
|
|
883
1054
|
const prior = moved ? undefined : output;
|
|
884
|
-
|
|
1055
|
+
const takeOver = adopt && output === undefined;
|
|
1056
|
+
if (prior === undefined && before !== undefined && !matches(before, want) && !takeOver) {
|
|
885
1057
|
throw refuse3(props.path, "already exists and is not this resource. Remove it, or declare it as a new resource and " + "deploy with --adopt.");
|
|
886
1058
|
}
|
|
887
1059
|
if (before === undefined || !matches(before, want)) {
|
|
888
|
-
await runner.writeFileAtomic(props.path, want.bytes,
|
|
889
|
-
mode: want.mode,
|
|
890
|
-
...want.uid === undefined ? {} : { uid: want.uid },
|
|
891
|
-
...want.gid === undefined ? {} : { gid: want.gid }
|
|
892
|
-
});
|
|
1060
|
+
await runner.writeFileAtomic(props.path, want.bytes, writeOptionsOf(want));
|
|
893
1061
|
}
|
|
894
1062
|
const after = await readFileAttributes(runner, props.path);
|
|
895
1063
|
if (after === undefined || !matches(after, want)) {
|
|
@@ -914,34 +1082,34 @@ var deleteFile = async (runner, output) => {
|
|
|
914
1082
|
|
|
915
1083
|
// src/launchd/host-file.ts
|
|
916
1084
|
var HostFile = Resource2("Host.File");
|
|
917
|
-
var HostFileProvider = () => Provider2.effect(HostFile,
|
|
1085
|
+
var HostFileProvider = () => Provider2.effect(HostFile, Effect7.gen(function* () {
|
|
918
1086
|
const runner = yield* HostRunnerService;
|
|
919
1087
|
return HostFile.Provider.of({
|
|
920
|
-
list: () =>
|
|
1088
|
+
list: () => Effect7.succeed([]),
|
|
921
1089
|
read: ({ olds, output }) => lift(async () => {
|
|
922
1090
|
const found = await readFileAttributes(runner, olds.path);
|
|
923
1091
|
if (found === undefined)
|
|
924
1092
|
return;
|
|
925
|
-
return output === undefined ?
|
|
1093
|
+
return output === undefined ? Unowned3(found) : found;
|
|
926
1094
|
}),
|
|
927
|
-
diff: ({ news, output }) => {
|
|
1095
|
+
diff: ({ instanceId, news, output }) => {
|
|
928
1096
|
if (output === undefined)
|
|
929
|
-
return
|
|
1097
|
+
return noteUnfinished(instanceId);
|
|
930
1098
|
if (isResolved2(news))
|
|
931
1099
|
return lift(() => diffFile(runner, news, output));
|
|
932
1100
|
const path = resolvedString(news, "path");
|
|
933
|
-
return
|
|
1101
|
+
return Effect7.succeed(path !== undefined && path !== output.path ? { action: "replace" } : undefined);
|
|
934
1102
|
},
|
|
935
|
-
reconcile: ({ news, output }) => lift(() => reconcileFile(runner, news, output)),
|
|
1103
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect7.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
|
|
936
1104
|
delete: ({ output }) => lift(() => deleteFile(runner, output))
|
|
937
1105
|
});
|
|
938
1106
|
}));
|
|
939
1107
|
|
|
940
1108
|
// src/caddy/with-file.ts
|
|
941
|
-
var caddyWithFile = (id, props) =>
|
|
1109
|
+
var caddyWithFile = (id, props) => Effect8.gen(function* () {
|
|
942
1110
|
const found = configProblems({ caddyfile: props.caddyfile, sourceFile: props.path });
|
|
943
1111
|
if (found.length > 0) {
|
|
944
|
-
return yield*
|
|
1112
|
+
return yield* Effect8.fail(new Error(`caddyWithFile ${id}: ${found.join("; ")}`));
|
|
945
1113
|
}
|
|
946
1114
|
const file = yield* HostFile(`${id}-file`, {
|
|
947
1115
|
content: props.caddyfile,
|
|
@@ -966,5 +1134,5 @@ export {
|
|
|
966
1134
|
localCaddyAdmin
|
|
967
1135
|
};
|
|
968
1136
|
|
|
969
|
-
//# debugId=
|
|
1137
|
+
//# debugId=13AB2C6EDF123C4E64756E2164756E21
|
|
970
1138
|
//# sourceMappingURL=index.js.map
|