@homeflare/alchemy 0.20.0 → 0.21.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 +25 -0
- package/dist/caddy/access-forward-auth.d.ts +56 -0
- package/dist/caddy/access-forward-auth.d.ts.map +1 -0
- package/dist/caddy/index.d.ts +6 -0
- package/dist/caddy/index.d.ts.map +1 -1
- package/dist/caddy/index.js +169 -75
- package/dist/caddy/index.js.map +10 -7
- package/dist/cloudflare/mcp-server-api.d.ts +135 -0
- package/dist/cloudflare/mcp-server-api.d.ts.map +1 -0
- package/dist/cloudflare/mcp-server-form.d.ts +116 -0
- package/dist/cloudflare/mcp-server-form.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/launchd/file-converge.d.ts +70 -0
- package/dist/launchd/file-converge.d.ts.map +1 -0
- package/dist/launchd/file-identity.d.ts +32 -0
- package/dist/launchd/file-identity.d.ts.map +1 -0
- package/dist/launchd/host-file-form.d.ts +2 -0
- package/dist/launchd/host-file-form.d.ts.map +1 -1
- package/dist/launchd/host-file-lifecycle.d.ts +11 -24
- package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
- package/dist/launchd/host-file.d.ts.map +1 -1
- package/dist/launchd/index.js +173 -120
- package/dist/launchd/index.js.map +12 -10
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +17 -2
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts +11 -0
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/linux/sudo-listing.d.ts +47 -0
- package/dist/linux/sudo-listing.d.ts.map +1 -0
- package/dist/proxmox/credential-literals.d.ts +1 -1
- package/dist/proxmox/credential-literals.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs/nodes-node.d.ts +1 -1
- package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs.d.ts +7 -0
- package/dist/proxmox/generated/pbs.d.ts.map +1 -1
- package/dist/proxmox/generated/pve.d.ts +7 -0
- package/dist/proxmox/generated/pve.d.ts.map +1 -1
- package/dist/proxmox/index.d.ts +1 -1
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +2 -2
- package/dist/proxmox/index.js.map +9 -9
- package/dist/proxmox/pbs-notification-target-form.d.ts +1 -1
- package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts.map +1 -1
- package/dist/release/archive.d.ts +11 -0
- package/dist/release/archive.d.ts.map +1 -0
- package/dist/release/binary-claim.d.ts +53 -0
- package/dist/release/binary-claim.d.ts.map +1 -0
- package/dist/release/binary-diff.d.ts +38 -0
- package/dist/release/binary-diff.d.ts.map +1 -0
- package/dist/release/binary-form.d.ts +118 -0
- package/dist/release/binary-form.d.ts.map +1 -0
- package/dist/release/binary-lifecycle.d.ts +55 -0
- package/dist/release/binary-lifecycle.d.ts.map +1 -0
- package/dist/release/binary-read.d.ts +11 -0
- package/dist/release/binary-read.d.ts.map +1 -0
- package/dist/release/binary.d.ts +68 -0
- package/dist/release/binary.d.ts.map +1 -0
- package/dist/release/catalog.d.ts +76 -0
- package/dist/release/catalog.d.ts.map +1 -0
- package/dist/release/checksums.d.ts +22 -0
- package/dist/release/checksums.d.ts.map +1 -0
- package/dist/release/declared-pins.d.ts +4 -0
- package/dist/release/declared-pins.d.ts.map +1 -0
- package/dist/release/download.d.ts +44 -0
- package/dist/release/download.d.ts.map +1 -0
- package/dist/release/fake-deploy.d.ts +48 -0
- package/dist/release/fake-deploy.d.ts.map +1 -0
- package/dist/release/fake-engine.d.ts +8 -0
- package/dist/release/fake-engine.d.ts.map +1 -0
- package/dist/release/fake-install.d.ts +39 -0
- package/dist/release/fake-install.d.ts.map +1 -0
- package/dist/release/fake-provider.d.ts +58 -0
- package/dist/release/fake-provider.d.ts.map +1 -0
- package/dist/release/fake-release.d.ts +95 -0
- package/dist/release/fake-release.d.ts.map +1 -0
- package/dist/release/index.d.ts +24 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +1365 -0
- package/dist/release/index.js.map +35 -0
- package/dist/release/providers.d.ts +19 -0
- package/dist/release/providers.d.ts.map +1 -0
- package/dist/release/refused.d.ts +39 -0
- package/dist/release/refused.d.ts.map +1 -0
- package/dist/release/tar.d.ts +14 -0
- package/dist/release/tar.d.ts.map +1 -0
- package/dist/release/victoria.d.ts +36 -0
- package/dist/release/victoria.d.ts.map +1 -0
- package/dist/{proxmox → secrets}/write-only.d.ts.map +1 -1
- package/docs/release-binary-catalogs.md +113 -0
- package/docs/release-binary-measured.md +163 -0
- package/docs/release-binary-refusals.md +57 -0
- package/docs/release-binary-upstream.md +163 -0
- package/docs/release-binary.md +154 -0
- package/package.json +5 -1
- package/src/caddy/access-forward-auth.test.ts +63 -0
- package/src/caddy/access-forward-auth.ts +120 -0
- package/src/caddy/index.ts +10 -0
- package/src/caddy/providers.test.ts +4 -0
- package/src/cloudflare/mcp-server-api.ts +294 -0
- package/src/cloudflare/mcp-server-form.ts +219 -0
- package/src/index.ts +1 -0
- package/src/launchd/file-converge.ts +233 -0
- package/src/launchd/file-identity.ts +69 -0
- package/src/launchd/host-file-alias.test.ts +65 -0
- package/src/launchd/host-file-form.ts +2 -1
- package/src/launchd/host-file-lifecycle.ts +25 -144
- package/src/launchd/host-file.ts +8 -2
- package/src/launchd/local-runner.test.ts +12 -1
- package/src/launchd/local-runner.ts +2 -0
- package/src/launchd/runner.ts +17 -2
- package/src/launchd/sudo-runner.ts +11 -0
- package/src/linux/sudo-listing.ts +122 -0
- package/src/proxmox/credential-literals.ts +1 -1
- package/src/proxmox/generated/pbs/nodes-node.ts +1 -1
- package/src/proxmox/generated/pbs.ts +7 -0
- package/src/proxmox/generated/pve.ts +7 -0
- package/src/proxmox/index.ts +1 -1
- package/src/proxmox/pbs-notification-target-form.ts +1 -1
- package/src/proxmox/pbs-notification-target-lifecycle.ts +1 -1
- package/src/proxmox/pbs-notification-target-wire.ts +1 -1
- package/src/proxmox/pbs-notification-target.ts +1 -1
- package/src/release/adopt-parity.test.ts +106 -0
- package/src/release/apply-pins.test.ts +128 -0
- package/src/release/archive.ts +94 -0
- package/src/release/binary-alias.test.ts +109 -0
- package/src/release/binary-claim.ts +80 -0
- package/src/release/binary-claims.test.ts +47 -0
- package/src/release/binary-diff.ts +73 -0
- package/src/release/binary-form.ts +229 -0
- package/src/release/binary-lifecycle.test.ts +90 -0
- package/src/release/binary-lifecycle.ts +202 -0
- package/src/release/binary-read.ts +55 -0
- package/src/release/binary-refusals.test.ts +192 -0
- package/src/release/binary-sudo.test.ts +93 -0
- package/src/release/binary.ts +181 -0
- package/src/release/catalog.test.ts +96 -0
- package/src/release/catalog.ts +175 -0
- package/src/release/checksums.test.ts +35 -0
- package/src/release/checksums.ts +43 -0
- package/src/release/declared-pins.ts +39 -0
- package/src/release/download.test.ts +138 -0
- package/src/release/download.ts +110 -0
- package/src/release/error-channel.test.ts +69 -0
- package/src/release/fake-deploy.ts +87 -0
- package/src/release/fake-engine.ts +50 -0
- package/src/release/fake-install.ts +59 -0
- package/src/release/fake-provider.ts +68 -0
- package/src/release/fake-release.ts +185 -0
- package/src/release/fixtures/victoria/darwin-arm64-assets.txt +22 -0
- package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +8 -0
- package/src/release/index.ts +41 -0
- package/src/release/plan.test.ts +189 -0
- package/src/release/provider-refusals.test.ts +48 -0
- package/src/release/providers.test.ts +174 -0
- package/src/release/providers.ts +24 -0
- package/src/release/recovery-read.test.ts +44 -0
- package/src/release/refused.ts +40 -0
- package/src/release/tar.test.ts +142 -0
- package/src/release/tar.ts +195 -0
- package/src/release/victoria.test.ts +108 -0
- package/src/release/victoria.ts +154 -0
- package/src/{proxmox → secrets}/write-only.ts +6 -0
- /package/dist/{proxmox → secrets}/write-only.d.ts +0 -0
- /package/src/{proxmox → secrets}/write-only.test.ts +0 -0
package/README.md
CHANGED
|
@@ -164,6 +164,31 @@ SystemdUnit('thing', { name: 'thing.service', sections: [...], restartOn: [confi
|
|
|
164
164
|
- ⛔ **The directive set is systemd's:** unit files render verbatim; nothing here invents a schema.
|
|
165
165
|
- Guide, the measured `systemctl` shapes and the limits: [docs/linux-host.md](./docs/linux-host.md).
|
|
166
166
|
|
|
167
|
+
## Release binaries — `@homeflare/alchemy/release`
|
|
168
|
+
|
|
169
|
+
`ReleaseBinary` installs one binary out of a pinned release archive into a directory you declare,
|
|
170
|
+
verified twice. The pins are props; each vendor's pinned versions are a data set beside it
|
|
171
|
+
(`VICTORIA_RELEASES` is the first). `releaseProviders(runner)` provides it.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const request = {
|
|
175
|
+
package: 'vmutils',
|
|
176
|
+
version: '1.151.0',
|
|
177
|
+
platform: 'darwin-arm64',
|
|
178
|
+
binary: 'vmalert',
|
|
179
|
+
};
|
|
180
|
+
ReleaseBinary('vmalert', { ...catalogBinary(VICTORIA_RELEASES, request), directory: dir.path });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- ⛔ **It installs; it never starts.** Put `binary.path` in your job's argv; that orders the two.
|
|
184
|
+
- ⛔ **Pinned in code, never fetched:** a version the data set does not pin fails the plan. Exact
|
|
185
|
+
asset names, the archive verified before unpacking and the binary before writing, only the
|
|
186
|
+
declared member extracted, one writer (`HostRunner.writeFileAtomic`).
|
|
187
|
+
- Guide: [docs/release-binary.md](./docs/release-binary.md) · data sets and adding a vendor:
|
|
188
|
+
[docs/release-binary-catalogs.md](./docs/release-binary-catalogs.md) · measured:
|
|
189
|
+
[docs/release-binary-measured.md](./docs/release-binary-measured.md) · upstream:
|
|
190
|
+
[docs/release-binary-upstream.md](./docs/release-binary-upstream.md).
|
|
191
|
+
|
|
167
192
|
## Caddy — `@homeflare/alchemy/caddy`
|
|
168
193
|
|
|
169
194
|
`CaddyConfig` declares a running Caddy's config as Caddyfile text, applied through Caddy's own admin
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Caddyfile half of "protect this route with Cloudflare Access": a `forward_auth` block
|
|
3
|
+
* pointing at a verifier that speaks the contract in `@homeflare/cloudflare/access-auth`.
|
|
4
|
+
*
|
|
5
|
+
* ★ A BUILDER, NOT A RESOURCE. Nothing here is created, adopted or destroyed — it is Caddyfile
|
|
6
|
+
* TEXT that a caller drops into the `caddyfile` prop of a `CaddyConfig` / `caddyWithFile()`.
|
|
7
|
+
* A resource would own a fragment of a config that `CaddyConfig` already owns whole, and two
|
|
8
|
+
* owners of one file is the drift this package exists to prevent.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ WHY IT IS GENERATED RATHER THAN COPY-PASTED. Every field below is load-bearing, and the
|
|
11
|
+
* failure mode of getting one wrong is a route that looks protected and is not:
|
|
12
|
+
* · a missing `copy_headers` — the upstream gets no identity and either breaks or, worse,
|
|
13
|
+
* falls back to trusting a header the CLIENT set;
|
|
14
|
+
* · the verifier on anything but loopback — the JWT check becomes advisory, because the
|
|
15
|
+
* verifier itself can be reached and answered around;
|
|
16
|
+
* · `uri` pointing at a path the verifier does not route — every request 404s, which is
|
|
17
|
+
* non-2xx, which Caddy correctly treats as a denial, so the symptom is "Access is broken".
|
|
18
|
+
*/
|
|
19
|
+
export interface AccessForwardAuthProps {
|
|
20
|
+
/**
|
|
21
|
+
* Where the verifier listens, e.g. `127.0.0.1:9101`.
|
|
22
|
+
* ⛔ LOOPBACK OR A UNIX SOCKET. See `verifierProblems` — a routable verifier is not a check.
|
|
23
|
+
*/
|
|
24
|
+
readonly verifier: string;
|
|
25
|
+
/** The path the verifier answers on. @default '/access/verify' */
|
|
26
|
+
readonly uri?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Response headers copied from the verifier onto the upstream request.
|
|
29
|
+
* @default ['X-Access-Email', 'X-Access-Sub']
|
|
30
|
+
*/
|
|
31
|
+
readonly copyHeaders?: readonly string[];
|
|
32
|
+
/** Lines placed inside the route BEFORE `forward_auth` — typically the upstream. */
|
|
33
|
+
readonly body?: readonly string[];
|
|
34
|
+
/** Indent applied to every emitted line. @default '\t' */
|
|
35
|
+
readonly indent?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare const verifierProblems: (verifier: string) => string[];
|
|
38
|
+
/**
|
|
39
|
+
* Render the `forward_auth` block for one protected route.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const caddyfile = `vnc.example.com {
|
|
43
|
+
* ${accessForwardAuth({ verifier: '127.0.0.1:9101', body: ['reverse_proxy 127.0.0.1:6080'] })}
|
|
44
|
+
* }
|
|
45
|
+
* `;
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ⛔ ONE VERIFIER ENDPOINT PER ACCESS APPLICATION. The AUD is fixed in the verifier's own config,
|
|
49
|
+
* never carried in this request — so two applications mean two `uri`s (or two verifiers), not
|
|
50
|
+
* one endpoint told which app to check. An AUD the request can influence is the vulnerability.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ `forward_auth` COPIES THE VERIFIER'S RESPONSE BACK TO THE CLIENT on any non-2xx, which is why
|
|
53
|
+
* the verifier answers a bare 401 with no body: whatever it returns is what the browser sees.
|
|
54
|
+
*/
|
|
55
|
+
export declare const accessForwardAuth: (props: AccessForwardAuthProps) => string;
|
|
56
|
+
//# sourceMappingURL=access-forward-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-forward-auth.d.ts","sourceRoot":"","sources":["../../src/caddy/access-forward-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AA6BD,eAAO,MAAM,gBAAgB,aAAc,MAAM,KAAG,MAAM,EAWzD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,UAAW,sBAAsB,KAAG,MAqBjE,CAAC"}
|
package/dist/caddy/index.d.ts
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* ⛔ NOTHING IS ADOPTED SILENTLY: a running config that is not the declared one needs `--adopt`.
|
|
10
10
|
* Guide, order of file and load, adoption, `--resume` and secrets: docs/caddy.md.
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* ★ `accessForwardAuth` is Caddyfile TEXT, not a resource: it renders the `forward_auth` block
|
|
14
|
+
* that puts a Cloudflare Access check in front of a route, paired with the verifier in
|
|
15
|
+
* `@homeflare/cloudflare/access-auth`. Guide: docs/access-auth.md.
|
|
16
|
+
*/
|
|
17
|
+
export { accessForwardAuth, verifierProblems, type AccessForwardAuthProps, } from './access-forward-auth.ts';
|
|
12
18
|
export type { CaddyAdmin, CaddyAdminListener, CaddyAdminRequest, CaddyAdminResponse, } from './admin.ts';
|
|
13
19
|
export { CaddyAdminService, CaddyUnreachableError, caddyAdminLayer } from './admin.ts';
|
|
14
20
|
export { CaddyAdminError } from './admin-calls.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/caddy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/caddy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,sBAAsB,GAC5B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/caddy/index.js
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
// src/caddy/access-forward-auth.ts
|
|
2
|
+
var DEFAULT_COPY_HEADERS = ["X-Access-Email", "X-Access-Sub"];
|
|
3
|
+
var LOOPBACK = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
4
|
+
var hostOf = (verifier) => {
|
|
5
|
+
const address = verifier.replace(/^https?:\/\//, "");
|
|
6
|
+
if (address.startsWith("unix/"))
|
|
7
|
+
return "unix";
|
|
8
|
+
if (address.startsWith("["))
|
|
9
|
+
return address.slice(0, address.indexOf("]") + 1);
|
|
10
|
+
const colon = address.indexOf(":");
|
|
11
|
+
return colon === -1 ? address : address.slice(0, colon);
|
|
12
|
+
};
|
|
13
|
+
var verifierProblems = (verifier) => {
|
|
14
|
+
const found = [];
|
|
15
|
+
const host = hostOf(verifier.trim());
|
|
16
|
+
if (verifier.trim() === "")
|
|
17
|
+
found.push("verifier is empty");
|
|
18
|
+
else if (host !== "unix" && !LOOPBACK.has(host)) {
|
|
19
|
+
found.push(`verifier must be loopback or a unix socket, got ${verifier} — a routable verifier can be bypassed`);
|
|
20
|
+
}
|
|
21
|
+
return found;
|
|
22
|
+
};
|
|
23
|
+
var accessForwardAuth = (props) => {
|
|
24
|
+
const problems = verifierProblems(props.verifier);
|
|
25
|
+
if (problems.length > 0)
|
|
26
|
+
throw new Error(`accessForwardAuth: ${problems.join("; ")}`);
|
|
27
|
+
const indent = props.indent ?? "\t";
|
|
28
|
+
const uri = props.uri ?? "/access/verify";
|
|
29
|
+
const headers = props.copyHeaders ?? DEFAULT_COPY_HEADERS;
|
|
30
|
+
const lines = [
|
|
31
|
+
...props.body ?? [],
|
|
32
|
+
`forward_auth ${props.verifier} {`,
|
|
33
|
+
`${indent}uri ${uri}`,
|
|
34
|
+
`${indent}copy_headers {`,
|
|
35
|
+
...headers.map((header) => `${indent}${indent}${header}`),
|
|
36
|
+
`${indent}}`,
|
|
37
|
+
"}"
|
|
38
|
+
];
|
|
39
|
+
return lines.map((line) => `${indent}${line}`).join(`
|
|
40
|
+
`);
|
|
41
|
+
};
|
|
1
42
|
// src/caddy/admin.ts
|
|
2
43
|
import * as Context from "effect/Context";
|
|
3
44
|
import * as Layer from "effect/Layer";
|
|
@@ -938,36 +979,29 @@ import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
|
938
979
|
import * as Provider2 from "alchemy/Provider";
|
|
939
980
|
import * as Effect7 from "effect/Effect";
|
|
940
981
|
|
|
941
|
-
// src/launchd/
|
|
942
|
-
var
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
982
|
+
// src/launchd/file-identity.ts
|
|
983
|
+
var sameEntry = (a, b) => {
|
|
984
|
+
if (a === undefined || b === undefined)
|
|
985
|
+
return false;
|
|
986
|
+
if (a.dev === undefined || a.ino === undefined || b.dev === undefined || b.ino === undefined) {
|
|
987
|
+
return;
|
|
948
988
|
}
|
|
949
|
-
return
|
|
989
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
950
990
|
};
|
|
951
|
-
var
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
957
|
-
}
|
|
958
|
-
if (path.includes("\x00"))
|
|
959
|
-
found.push("path contains NUL");
|
|
960
|
-
return found;
|
|
991
|
+
var oneFile = async (runner, a, b) => {
|
|
992
|
+
if (a === b)
|
|
993
|
+
return true;
|
|
994
|
+
const [left, right] = await Promise.all([runner.stat(a), runner.stat(b)]);
|
|
995
|
+
return sameEntry(left, right);
|
|
961
996
|
};
|
|
962
|
-
var
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return found;
|
|
997
|
+
var leaveOldPath = async (runner, oldPath, path, remove, refuse3) => {
|
|
998
|
+
const same = await oneFile(runner, oldPath, path);
|
|
999
|
+
if (same === true)
|
|
1000
|
+
return;
|
|
1001
|
+
await remove();
|
|
1002
|
+
if (same === undefined && await runner.stat(path) === undefined) {
|
|
1003
|
+
throw refuse3(path, `removing the old path ${oldPath} removed this file too: they are one file under two ` + "spellings (a symlinked parent, or case on a case-insensitive volume). Deploy again to " + "reinstall it, and keep one spelling.");
|
|
1004
|
+
}
|
|
971
1005
|
};
|
|
972
1006
|
|
|
973
1007
|
// src/launchd/runner.ts
|
|
@@ -979,33 +1013,22 @@ class HostRunnerService extends Context2.Service()("homeflare/launchd/HostRunner
|
|
|
979
1013
|
var hostRunnerLayer = (runner) => Layer3.succeed(HostRunnerService, runner);
|
|
980
1014
|
var canActAsRoot = (runner) => runner.privileged || runner.effectiveUid() === 0;
|
|
981
1015
|
|
|
982
|
-
// src/launchd/
|
|
983
|
-
var refuse3 = (path, message) => new Error(`Host.File ${path}: ${message}`);
|
|
1016
|
+
// src/launchd/file-converge.ts
|
|
984
1017
|
var numericId = (value) => typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
985
|
-
var
|
|
986
|
-
const found = fileProblems(props);
|
|
987
|
-
if (found.length > 0)
|
|
988
|
-
throw refuse3(props.path, found.join("; "));
|
|
989
|
-
const bytes = new TextEncoder().encode(props.content);
|
|
1018
|
+
var resolveIds = async (runner, declared, path, refuse3) => {
|
|
990
1019
|
let uid;
|
|
991
1020
|
let gid;
|
|
992
|
-
if (
|
|
993
|
-
uid = numericId(
|
|
1021
|
+
if (declared.owner !== undefined) {
|
|
1022
|
+
uid = numericId(declared.owner) ?? (await runner.lookupUser(String(declared.owner)))?.uid;
|
|
994
1023
|
if (uid === undefined)
|
|
995
|
-
throw refuse3(
|
|
1024
|
+
throw refuse3(path, `no user ${String(declared.owner)} on this host`);
|
|
996
1025
|
}
|
|
997
|
-
if (
|
|
998
|
-
gid = numericId(
|
|
1026
|
+
if (declared.group !== undefined) {
|
|
1027
|
+
gid = numericId(declared.group) ?? await runner.lookupGroup(String(declared.group));
|
|
999
1028
|
if (gid === undefined)
|
|
1000
|
-
throw refuse3(
|
|
1029
|
+
throw refuse3(path, `no group ${String(declared.group)} on this host`);
|
|
1001
1030
|
}
|
|
1002
|
-
return {
|
|
1003
|
-
bytes,
|
|
1004
|
-
mode: props.mode ?? DEFAULT_MODE,
|
|
1005
|
-
sha256: sha256Hex(bytes),
|
|
1006
|
-
...uid === undefined ? {} : { uid },
|
|
1007
|
-
...gid === undefined ? {} : { gid }
|
|
1008
|
-
};
|
|
1031
|
+
return { ...uid === undefined ? {} : { uid }, ...gid === undefined ? {} : { gid } };
|
|
1009
1032
|
};
|
|
1010
1033
|
var readFileAttributes = async (runner, path) => {
|
|
1011
1034
|
const stat = await runner.stat(path);
|
|
@@ -1026,52 +1049,64 @@ var writeOptionsOf = (want) => ({
|
|
|
1026
1049
|
...want.uid === undefined ? {} : { uid: want.uid },
|
|
1027
1050
|
...want.gid === undefined ? {} : { gid: want.gid }
|
|
1028
1051
|
});
|
|
1029
|
-
var
|
|
1030
|
-
var
|
|
1031
|
-
const want = await desiredFile(runner, news);
|
|
1052
|
+
var fileMatches = (live, want) => live.sha256 === want.sha256 && live.mode === want.mode && (want.uid === undefined || live.uid === want.uid) && (want.gid === undefined || live.gid === want.gid);
|
|
1053
|
+
var diffTarget = async (runner, want, output) => {
|
|
1032
1054
|
const writes = async (diff) => {
|
|
1033
|
-
await runner.checkWrite?.(
|
|
1055
|
+
await runner.checkWrite?.(want.path, writeOptionsOf(want));
|
|
1034
1056
|
return diff;
|
|
1035
1057
|
};
|
|
1036
|
-
if (
|
|
1037
|
-
|
|
1058
|
+
if (want.path !== output.path) {
|
|
1059
|
+
const same = await oneFile(runner, want.path, output.path);
|
|
1060
|
+
return writes({ action: same === true ? "update" : "replace" });
|
|
1061
|
+
}
|
|
1038
1062
|
if (want.sha256 !== output.sha256)
|
|
1039
1063
|
return writes({ action: "update" });
|
|
1040
|
-
const live = await readFileAttributes(runner,
|
|
1041
|
-
return live !== undefined &&
|
|
1064
|
+
const live = await readFileAttributes(runner, want.path);
|
|
1065
|
+
return live !== undefined && fileMatches(live, want) ? { action: "noop" } : writes({ action: "update" });
|
|
1042
1066
|
};
|
|
1043
|
-
var
|
|
1044
|
-
const want
|
|
1067
|
+
var convergeFile = async (runner, spec, output, adopt = false) => {
|
|
1068
|
+
const { want, refuse: refuse3 } = spec;
|
|
1069
|
+
const path = want.path;
|
|
1045
1070
|
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
1046
|
-
throw refuse3(
|
|
1071
|
+
throw refuse3(path, `owner ${String(spec.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.");
|
|
1047
1072
|
}
|
|
1048
|
-
const stat = await runner.stat(
|
|
1073
|
+
const stat = await runner.stat(path);
|
|
1049
1074
|
if (stat !== undefined && stat.kind !== "file") {
|
|
1050
|
-
throw refuse3(
|
|
1075
|
+
throw refuse3(path, `is a ${stat.kind}; remove it from whatever owns it before declaring it here`);
|
|
1051
1076
|
}
|
|
1052
|
-
const before = stat === undefined ? undefined : await readFileAttributes(runner,
|
|
1053
|
-
const moved = output !== undefined && output.path !==
|
|
1077
|
+
const before = stat === undefined ? undefined : await readFileAttributes(runner, path);
|
|
1078
|
+
const moved = output !== undefined && output.path !== path;
|
|
1054
1079
|
const prior = moved ? undefined : output;
|
|
1055
1080
|
const takeOver = adopt && output === undefined;
|
|
1056
|
-
if (prior === undefined && before !== undefined && !
|
|
1057
|
-
throw refuse3(
|
|
1081
|
+
if (prior === undefined && before !== undefined && !fileMatches(before, want) && !takeOver) {
|
|
1082
|
+
throw refuse3(path, "already exists and is not this resource. Remove it, or declare it as a new resource and " + "deploy with --adopt.");
|
|
1058
1083
|
}
|
|
1059
|
-
if (before === undefined || !
|
|
1060
|
-
await
|
|
1084
|
+
if (before === undefined || !fileMatches(before, want)) {
|
|
1085
|
+
const bytes = await spec.bytesFor(before);
|
|
1086
|
+
if (sha256Hex(bytes) !== want.sha256) {
|
|
1087
|
+
throw refuse3(path, `the bytes to write do not hash to ${want.sha256}. Nothing was written.`);
|
|
1088
|
+
}
|
|
1089
|
+
await runner.writeFileAtomic(path, bytes, writeOptionsOf(want));
|
|
1061
1090
|
}
|
|
1062
|
-
const
|
|
1063
|
-
if (after === undefined || !matches(after, want)) {
|
|
1091
|
+
const rollBack = async () => {
|
|
1064
1092
|
if (before === undefined)
|
|
1065
|
-
await runner.removeFile(
|
|
1093
|
+
await runner.removeFile(path).catch(() => {
|
|
1066
1094
|
return;
|
|
1067
1095
|
});
|
|
1068
|
-
|
|
1096
|
+
};
|
|
1097
|
+
const after = await readFileAttributes(runner, path).catch(async (cause) => {
|
|
1098
|
+
await rollBack();
|
|
1099
|
+
throw refuse3(path, `the write returned but reading it back failed: ${String(cause)}`);
|
|
1100
|
+
});
|
|
1101
|
+
if (after === undefined || !fileMatches(after, want)) {
|
|
1102
|
+
await rollBack();
|
|
1103
|
+
throw refuse3(path, "the write returned but the file on disk does not match the declaration");
|
|
1069
1104
|
}
|
|
1070
1105
|
if (moved)
|
|
1071
|
-
await
|
|
1106
|
+
await leaveOldPath(runner, output.path, path, () => removeWholeFile(runner, output, refuse3), refuse3);
|
|
1072
1107
|
return after;
|
|
1073
1108
|
};
|
|
1074
|
-
var
|
|
1109
|
+
var removeWholeFile = async (runner, output, refuse3) => {
|
|
1075
1110
|
const stat = await runner.stat(output.path);
|
|
1076
1111
|
if (stat === undefined)
|
|
1077
1112
|
return;
|
|
@@ -1080,6 +1115,61 @@ var deleteFile = async (runner, output) => {
|
|
|
1080
1115
|
await runner.removeFile(output.path);
|
|
1081
1116
|
};
|
|
1082
1117
|
|
|
1118
|
+
// src/launchd/host-file-form.ts
|
|
1119
|
+
var DEFAULT_MODE = 420;
|
|
1120
|
+
var identityProblems = (name, value) => {
|
|
1121
|
+
if (value === undefined)
|
|
1122
|
+
return [];
|
|
1123
|
+
if (typeof value === "number") {
|
|
1124
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
1125
|
+
}
|
|
1126
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value) ? [] : [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
|
|
1127
|
+
};
|
|
1128
|
+
var pathProblems = (path) => {
|
|
1129
|
+
const found = [];
|
|
1130
|
+
if (!path.startsWith("/"))
|
|
1131
|
+
found.push("path must be absolute");
|
|
1132
|
+
if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
|
|
1133
|
+
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
1134
|
+
}
|
|
1135
|
+
if (path.includes("\x00"))
|
|
1136
|
+
found.push("path contains NUL");
|
|
1137
|
+
return found;
|
|
1138
|
+
};
|
|
1139
|
+
var fileProblems = (props) => {
|
|
1140
|
+
const found = pathProblems(props.path);
|
|
1141
|
+
const mode = props.mode ?? DEFAULT_MODE;
|
|
1142
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
1143
|
+
found.push("mode must be 0–0o7777");
|
|
1144
|
+
found.push(...identityProblems("owner", props.owner));
|
|
1145
|
+
found.push(...identityProblems("group", props.group));
|
|
1146
|
+
found.push(...fileSecretProblems(props.content));
|
|
1147
|
+
return found;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/launchd/host-file-lifecycle.ts
|
|
1151
|
+
var refuse3 = (path, message) => new Error(`Host.File ${path}: ${message}`);
|
|
1152
|
+
var desiredFile = async (runner, props) => {
|
|
1153
|
+
const found = fileProblems(props);
|
|
1154
|
+
if (found.length > 0)
|
|
1155
|
+
throw refuse3(props.path, found.join("; "));
|
|
1156
|
+
const bytes = new TextEncoder().encode(props.content);
|
|
1157
|
+
return {
|
|
1158
|
+
bytes,
|
|
1159
|
+
mode: props.mode ?? DEFAULT_MODE,
|
|
1160
|
+
path: props.path,
|
|
1161
|
+
sha256: sha256Hex(bytes),
|
|
1162
|
+
...await resolveIds(runner, props, props.path, refuse3)
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
1165
|
+
var diffFile = async (runner, news, output) => diffTarget(runner, await desiredFile(runner, news), output);
|
|
1166
|
+
var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
1167
|
+
const want = await desiredFile(runner, props);
|
|
1168
|
+
const spec = { bytesFor: async () => want.bytes, owner: props.owner, refuse: refuse3, want };
|
|
1169
|
+
return convergeFile(runner, spec, output, adopt);
|
|
1170
|
+
};
|
|
1171
|
+
var deleteFile = (runner, output) => removeWholeFile(runner, output, refuse3);
|
|
1172
|
+
|
|
1083
1173
|
// src/launchd/host-file.ts
|
|
1084
1174
|
var HostFile = Resource2("Host.File");
|
|
1085
1175
|
var HostFileProvider = () => Provider2.effect(HostFile, Effect7.gen(function* () {
|
|
@@ -1098,7 +1188,9 @@ var HostFileProvider = () => Provider2.effect(HostFile, Effect7.gen(function* ()
|
|
|
1098
1188
|
if (isResolved2(news))
|
|
1099
1189
|
return lift(() => diffFile(runner, news, output));
|
|
1100
1190
|
const path = resolvedString(news, "path");
|
|
1101
|
-
|
|
1191
|
+
if (path === undefined || path === output.path)
|
|
1192
|
+
return Effect7.succeed(undefined);
|
|
1193
|
+
return lift(async () => await oneFile(runner, path, output.path) === true ? undefined : { action: "replace" });
|
|
1102
1194
|
},
|
|
1103
1195
|
reconcile: ({ fqn, instanceId, news, output }) => Effect7.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
|
|
1104
1196
|
delete: ({ output }) => lift(() => deleteFile(runner, output))
|
|
@@ -1128,11 +1220,13 @@ export {
|
|
|
1128
1220
|
CaddyConfigProvider,
|
|
1129
1221
|
CaddyUnreachableError,
|
|
1130
1222
|
DEFAULT_ADMIN_ADDRESS,
|
|
1223
|
+
accessForwardAuth,
|
|
1131
1224
|
caddyAdminLayer,
|
|
1132
1225
|
caddyProviders,
|
|
1133
1226
|
caddyWithFile,
|
|
1134
|
-
localCaddyAdmin
|
|
1227
|
+
localCaddyAdmin,
|
|
1228
|
+
verifierProblems
|
|
1135
1229
|
};
|
|
1136
1230
|
|
|
1137
|
-
//# debugId=
|
|
1231
|
+
//# debugId=A91D75C3B7D7145864756E2164756E21
|
|
1138
1232
|
//# sourceMappingURL=index.js.map
|