@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/dist/launchd/index.js
CHANGED
|
@@ -182,6 +182,31 @@ var refuseTakeover = (owner, what) => Effect4.gen(function* () {
|
|
|
182
182
|
});
|
|
183
183
|
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
184
184
|
|
|
185
|
+
// src/launchd/file-identity.ts
|
|
186
|
+
var sameEntry = (a, b) => {
|
|
187
|
+
if (a === undefined || b === undefined)
|
|
188
|
+
return false;
|
|
189
|
+
if (a.dev === undefined || a.ino === undefined || b.dev === undefined || b.ino === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
193
|
+
};
|
|
194
|
+
var oneFile = async (runner, a, b) => {
|
|
195
|
+
if (a === b)
|
|
196
|
+
return true;
|
|
197
|
+
const [left, right] = await Promise.all([runner.stat(a), runner.stat(b)]);
|
|
198
|
+
return sameEntry(left, right);
|
|
199
|
+
};
|
|
200
|
+
var leaveOldPath = async (runner, oldPath, path, remove, refuse) => {
|
|
201
|
+
const same = await oneFile(runner, oldPath, path);
|
|
202
|
+
if (same === true)
|
|
203
|
+
return;
|
|
204
|
+
await remove();
|
|
205
|
+
if (same === undefined && await runner.stat(path) === undefined) {
|
|
206
|
+
throw refuse(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.");
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
185
210
|
// src/launchd/host-effect.ts
|
|
186
211
|
import * as Effect5 from "effect/Effect";
|
|
187
212
|
var lift = (step) => Effect5.tryPromise({
|
|
@@ -195,79 +220,6 @@ var resolvedString = (news, key) => {
|
|
|
195
220
|
return typeof value === "string" ? value : undefined;
|
|
196
221
|
};
|
|
197
222
|
|
|
198
|
-
// src/launchd/secret-tripwire.ts
|
|
199
|
-
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).";
|
|
200
|
-
var SECRET_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|SECRET_KEY|CREDENTIALS?)$/i;
|
|
201
|
-
var INLINE_FLAG = /^--?[a-z0-9-]*(?:token|password|passwd|passphrase|secret|api-?key)=./i;
|
|
202
|
-
var BARE_FLAG = /^--?(?:[a-z0-9]+-)*(?:token|password|passwd|passphrase|secret|api-?key)$/i;
|
|
203
|
-
var PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
|
|
204
|
-
var keyLeaves = (value, path) => {
|
|
205
|
-
if (typeof value === "string")
|
|
206
|
-
return PRIVATE_KEY.test(value) ? [path] : [];
|
|
207
|
-
if (Array.isArray(value)) {
|
|
208
|
-
return value.flatMap((item, index) => keyLeaves(item, `${path}[${String(index)}]`));
|
|
209
|
-
}
|
|
210
|
-
if (typeof value === "object" && value !== null) {
|
|
211
|
-
return Object.entries(value).flatMap(([key, item]) => keyLeaves(item, `${path}.${key}`));
|
|
212
|
-
}
|
|
213
|
-
return [];
|
|
214
|
-
};
|
|
215
|
-
var jobSecretProblems = (environment, argv, extraKeys) => {
|
|
216
|
-
const found = [];
|
|
217
|
-
for (const [name, value] of Object.entries(environment ?? {})) {
|
|
218
|
-
if (SECRET_NAME.test(name))
|
|
219
|
-
found.push(`environment ${name} looks like a secret: ${ALTERNATIVE}`);
|
|
220
|
-
else if (PRIVATE_KEY.test(value))
|
|
221
|
-
found.push(`environment ${name} holds a private key: ${ALTERNATIVE}`);
|
|
222
|
-
}
|
|
223
|
-
for (const [index, arg] of argv.entries()) {
|
|
224
|
-
if (INLINE_FLAG.test(arg)) {
|
|
225
|
-
found.push(`programArguments[${String(index)}] passes a secret inline: ${ALTERNATIVE}`);
|
|
226
|
-
} else if (BARE_FLAG.test(arg) && index + 1 < argv.length) {
|
|
227
|
-
found.push(`programArguments[${String(index + 1)}] follows a secret flag: ${ALTERNATIVE}`);
|
|
228
|
-
} else if (PRIVATE_KEY.test(arg)) {
|
|
229
|
-
found.push(`programArguments[${String(index)}] holds a private key: ${ALTERNATIVE}`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
for (const path of keyLeaves(extraKeys ?? {}, "extraKeys")) {
|
|
233
|
-
found.push(`${path} holds a private key: ${ALTERNATIVE}`);
|
|
234
|
-
}
|
|
235
|
-
return found;
|
|
236
|
-
};
|
|
237
|
-
var fileSecretProblems = (content) => PRIVATE_KEY.test(content) ? [`content holds a private key: ${ALTERNATIVE}`] : [];
|
|
238
|
-
|
|
239
|
-
// src/launchd/host-file-form.ts
|
|
240
|
-
var DEFAULT_MODE = 420;
|
|
241
|
-
var identityProblems = (name, value) => {
|
|
242
|
-
if (value === undefined)
|
|
243
|
-
return [];
|
|
244
|
-
if (typeof value === "number") {
|
|
245
|
-
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
246
|
-
}
|
|
247
|
-
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`];
|
|
248
|
-
};
|
|
249
|
-
var pathProblems = (path) => {
|
|
250
|
-
const found = [];
|
|
251
|
-
if (!path.startsWith("/"))
|
|
252
|
-
found.push("path must be absolute");
|
|
253
|
-
if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
|
|
254
|
-
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
255
|
-
}
|
|
256
|
-
if (path.includes("\x00"))
|
|
257
|
-
found.push("path contains NUL");
|
|
258
|
-
return found;
|
|
259
|
-
};
|
|
260
|
-
var fileProblems = (props) => {
|
|
261
|
-
const found = pathProblems(props.path);
|
|
262
|
-
const mode = props.mode ?? DEFAULT_MODE;
|
|
263
|
-
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
264
|
-
found.push("mode must be 0–0o7777");
|
|
265
|
-
found.push(...identityProblems("owner", props.owner));
|
|
266
|
-
found.push(...identityProblems("group", props.group));
|
|
267
|
-
found.push(...fileSecretProblems(props.content));
|
|
268
|
-
return found;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
223
|
// src/launchd/job-form.ts
|
|
272
224
|
import { createHash } from "node:crypto";
|
|
273
225
|
|
|
@@ -466,33 +418,22 @@ class HostRunnerService extends Context.Service()("homeflare/launchd/HostRunner"
|
|
|
466
418
|
var hostRunnerLayer = (runner) => Layer.succeed(HostRunnerService, runner);
|
|
467
419
|
var canActAsRoot = (runner) => runner.privileged || runner.effectiveUid() === 0;
|
|
468
420
|
|
|
469
|
-
// src/launchd/
|
|
470
|
-
var refuse = (path, message) => new Error(`Host.File ${path}: ${message}`);
|
|
421
|
+
// src/launchd/file-converge.ts
|
|
471
422
|
var numericId = (value) => typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
472
|
-
var
|
|
473
|
-
const found = fileProblems(props);
|
|
474
|
-
if (found.length > 0)
|
|
475
|
-
throw refuse(props.path, found.join("; "));
|
|
476
|
-
const bytes = new TextEncoder().encode(props.content);
|
|
423
|
+
var resolveIds = async (runner, declared, path, refuse) => {
|
|
477
424
|
let uid;
|
|
478
425
|
let gid;
|
|
479
|
-
if (
|
|
480
|
-
uid = numericId(
|
|
426
|
+
if (declared.owner !== undefined) {
|
|
427
|
+
uid = numericId(declared.owner) ?? (await runner.lookupUser(String(declared.owner)))?.uid;
|
|
481
428
|
if (uid === undefined)
|
|
482
|
-
throw refuse(
|
|
429
|
+
throw refuse(path, `no user ${String(declared.owner)} on this host`);
|
|
483
430
|
}
|
|
484
|
-
if (
|
|
485
|
-
gid = numericId(
|
|
431
|
+
if (declared.group !== undefined) {
|
|
432
|
+
gid = numericId(declared.group) ?? await runner.lookupGroup(String(declared.group));
|
|
486
433
|
if (gid === undefined)
|
|
487
|
-
throw refuse(
|
|
434
|
+
throw refuse(path, `no group ${String(declared.group)} on this host`);
|
|
488
435
|
}
|
|
489
|
-
return {
|
|
490
|
-
bytes,
|
|
491
|
-
mode: props.mode ?? DEFAULT_MODE,
|
|
492
|
-
sha256: sha256Hex(bytes),
|
|
493
|
-
...uid === undefined ? {} : { uid },
|
|
494
|
-
...gid === undefined ? {} : { gid }
|
|
495
|
-
};
|
|
436
|
+
return { ...uid === undefined ? {} : { uid }, ...gid === undefined ? {} : { gid } };
|
|
496
437
|
};
|
|
497
438
|
var readFileAttributes = async (runner, path) => {
|
|
498
439
|
const stat = await runner.stat(path);
|
|
@@ -513,52 +454,64 @@ var writeOptionsOf = (want) => ({
|
|
|
513
454
|
...want.uid === undefined ? {} : { uid: want.uid },
|
|
514
455
|
...want.gid === undefined ? {} : { gid: want.gid }
|
|
515
456
|
});
|
|
516
|
-
var
|
|
517
|
-
var
|
|
518
|
-
const want = await desiredFile(runner, news);
|
|
457
|
+
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);
|
|
458
|
+
var diffTarget = async (runner, want, output) => {
|
|
519
459
|
const writes = async (diff) => {
|
|
520
|
-
await runner.checkWrite?.(
|
|
460
|
+
await runner.checkWrite?.(want.path, writeOptionsOf(want));
|
|
521
461
|
return diff;
|
|
522
462
|
};
|
|
523
|
-
if (
|
|
524
|
-
|
|
463
|
+
if (want.path !== output.path) {
|
|
464
|
+
const same = await oneFile(runner, want.path, output.path);
|
|
465
|
+
return writes({ action: same === true ? "update" : "replace" });
|
|
466
|
+
}
|
|
525
467
|
if (want.sha256 !== output.sha256)
|
|
526
468
|
return writes({ action: "update" });
|
|
527
|
-
const live = await readFileAttributes(runner,
|
|
528
|
-
return live !== undefined &&
|
|
469
|
+
const live = await readFileAttributes(runner, want.path);
|
|
470
|
+
return live !== undefined && fileMatches(live, want) ? { action: "noop" } : writes({ action: "update" });
|
|
529
471
|
};
|
|
530
|
-
var
|
|
531
|
-
const want
|
|
472
|
+
var convergeFile = async (runner, spec, output, adopt = false) => {
|
|
473
|
+
const { want, refuse } = spec;
|
|
474
|
+
const path = want.path;
|
|
532
475
|
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
533
|
-
throw refuse(
|
|
476
|
+
throw refuse(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.");
|
|
534
477
|
}
|
|
535
|
-
const stat = await runner.stat(
|
|
478
|
+
const stat = await runner.stat(path);
|
|
536
479
|
if (stat !== undefined && stat.kind !== "file") {
|
|
537
|
-
throw refuse(
|
|
480
|
+
throw refuse(path, `is a ${stat.kind}; remove it from whatever owns it before declaring it here`);
|
|
538
481
|
}
|
|
539
|
-
const before = stat === undefined ? undefined : await readFileAttributes(runner,
|
|
540
|
-
const moved = output !== undefined && output.path !==
|
|
482
|
+
const before = stat === undefined ? undefined : await readFileAttributes(runner, path);
|
|
483
|
+
const moved = output !== undefined && output.path !== path;
|
|
541
484
|
const prior = moved ? undefined : output;
|
|
542
485
|
const takeOver = adopt && output === undefined;
|
|
543
|
-
if (prior === undefined && before !== undefined && !
|
|
544
|
-
throw refuse(
|
|
486
|
+
if (prior === undefined && before !== undefined && !fileMatches(before, want) && !takeOver) {
|
|
487
|
+
throw refuse(path, "already exists and is not this resource. Remove it, or declare it as a new resource and " + "deploy with --adopt.");
|
|
545
488
|
}
|
|
546
|
-
if (before === undefined || !
|
|
547
|
-
await
|
|
489
|
+
if (before === undefined || !fileMatches(before, want)) {
|
|
490
|
+
const bytes = await spec.bytesFor(before);
|
|
491
|
+
if (sha256Hex(bytes) !== want.sha256) {
|
|
492
|
+
throw refuse(path, `the bytes to write do not hash to ${want.sha256}. Nothing was written.`);
|
|
493
|
+
}
|
|
494
|
+
await runner.writeFileAtomic(path, bytes, writeOptionsOf(want));
|
|
548
495
|
}
|
|
549
|
-
const
|
|
550
|
-
if (after === undefined || !matches(after, want)) {
|
|
496
|
+
const rollBack = async () => {
|
|
551
497
|
if (before === undefined)
|
|
552
|
-
await runner.removeFile(
|
|
498
|
+
await runner.removeFile(path).catch(() => {
|
|
553
499
|
return;
|
|
554
500
|
});
|
|
555
|
-
|
|
501
|
+
};
|
|
502
|
+
const after = await readFileAttributes(runner, path).catch(async (cause) => {
|
|
503
|
+
await rollBack();
|
|
504
|
+
throw refuse(path, `the write returned but reading it back failed: ${String(cause)}`);
|
|
505
|
+
});
|
|
506
|
+
if (after === undefined || !fileMatches(after, want)) {
|
|
507
|
+
await rollBack();
|
|
508
|
+
throw refuse(path, "the write returned but the file on disk does not match the declaration");
|
|
556
509
|
}
|
|
557
510
|
if (moved)
|
|
558
|
-
await
|
|
511
|
+
await leaveOldPath(runner, output.path, path, () => removeWholeFile(runner, output, refuse), refuse);
|
|
559
512
|
return after;
|
|
560
513
|
};
|
|
561
|
-
var
|
|
514
|
+
var removeWholeFile = async (runner, output, refuse) => {
|
|
562
515
|
const stat = await runner.stat(output.path);
|
|
563
516
|
if (stat === undefined)
|
|
564
517
|
return;
|
|
@@ -567,6 +520,102 @@ var deleteFile = async (runner, output) => {
|
|
|
567
520
|
await runner.removeFile(output.path);
|
|
568
521
|
};
|
|
569
522
|
|
|
523
|
+
// src/launchd/secret-tripwire.ts
|
|
524
|
+
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).";
|
|
525
|
+
var SECRET_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|SECRET_KEY|CREDENTIALS?)$/i;
|
|
526
|
+
var INLINE_FLAG = /^--?[a-z0-9-]*(?:token|password|passwd|passphrase|secret|api-?key)=./i;
|
|
527
|
+
var BARE_FLAG = /^--?(?:[a-z0-9]+-)*(?:token|password|passwd|passphrase|secret|api-?key)$/i;
|
|
528
|
+
var PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
|
|
529
|
+
var keyLeaves = (value, path) => {
|
|
530
|
+
if (typeof value === "string")
|
|
531
|
+
return PRIVATE_KEY.test(value) ? [path] : [];
|
|
532
|
+
if (Array.isArray(value)) {
|
|
533
|
+
return value.flatMap((item, index) => keyLeaves(item, `${path}[${String(index)}]`));
|
|
534
|
+
}
|
|
535
|
+
if (typeof value === "object" && value !== null) {
|
|
536
|
+
return Object.entries(value).flatMap(([key, item]) => keyLeaves(item, `${path}.${key}`));
|
|
537
|
+
}
|
|
538
|
+
return [];
|
|
539
|
+
};
|
|
540
|
+
var jobSecretProblems = (environment, argv, extraKeys) => {
|
|
541
|
+
const found = [];
|
|
542
|
+
for (const [name, value] of Object.entries(environment ?? {})) {
|
|
543
|
+
if (SECRET_NAME.test(name))
|
|
544
|
+
found.push(`environment ${name} looks like a secret: ${ALTERNATIVE}`);
|
|
545
|
+
else if (PRIVATE_KEY.test(value))
|
|
546
|
+
found.push(`environment ${name} holds a private key: ${ALTERNATIVE}`);
|
|
547
|
+
}
|
|
548
|
+
for (const [index, arg] of argv.entries()) {
|
|
549
|
+
if (INLINE_FLAG.test(arg)) {
|
|
550
|
+
found.push(`programArguments[${String(index)}] passes a secret inline: ${ALTERNATIVE}`);
|
|
551
|
+
} else if (BARE_FLAG.test(arg) && index + 1 < argv.length) {
|
|
552
|
+
found.push(`programArguments[${String(index + 1)}] follows a secret flag: ${ALTERNATIVE}`);
|
|
553
|
+
} else if (PRIVATE_KEY.test(arg)) {
|
|
554
|
+
found.push(`programArguments[${String(index)}] holds a private key: ${ALTERNATIVE}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
for (const path of keyLeaves(extraKeys ?? {}, "extraKeys")) {
|
|
558
|
+
found.push(`${path} holds a private key: ${ALTERNATIVE}`);
|
|
559
|
+
}
|
|
560
|
+
return found;
|
|
561
|
+
};
|
|
562
|
+
var fileSecretProblems = (content) => PRIVATE_KEY.test(content) ? [`content holds a private key: ${ALTERNATIVE}`] : [];
|
|
563
|
+
|
|
564
|
+
// src/launchd/host-file-form.ts
|
|
565
|
+
var DEFAULT_MODE = 420;
|
|
566
|
+
var identityProblems = (name, value) => {
|
|
567
|
+
if (value === undefined)
|
|
568
|
+
return [];
|
|
569
|
+
if (typeof value === "number") {
|
|
570
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
571
|
+
}
|
|
572
|
+
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`];
|
|
573
|
+
};
|
|
574
|
+
var pathProblems = (path) => {
|
|
575
|
+
const found = [];
|
|
576
|
+
if (!path.startsWith("/"))
|
|
577
|
+
found.push("path must be absolute");
|
|
578
|
+
if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
|
|
579
|
+
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
580
|
+
}
|
|
581
|
+
if (path.includes("\x00"))
|
|
582
|
+
found.push("path contains NUL");
|
|
583
|
+
return found;
|
|
584
|
+
};
|
|
585
|
+
var fileProblems = (props) => {
|
|
586
|
+
const found = pathProblems(props.path);
|
|
587
|
+
const mode = props.mode ?? DEFAULT_MODE;
|
|
588
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
589
|
+
found.push("mode must be 0–0o7777");
|
|
590
|
+
found.push(...identityProblems("owner", props.owner));
|
|
591
|
+
found.push(...identityProblems("group", props.group));
|
|
592
|
+
found.push(...fileSecretProblems(props.content));
|
|
593
|
+
return found;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// src/launchd/host-file-lifecycle.ts
|
|
597
|
+
var refuse = (path, message) => new Error(`Host.File ${path}: ${message}`);
|
|
598
|
+
var desiredFile = async (runner, props) => {
|
|
599
|
+
const found = fileProblems(props);
|
|
600
|
+
if (found.length > 0)
|
|
601
|
+
throw refuse(props.path, found.join("; "));
|
|
602
|
+
const bytes = new TextEncoder().encode(props.content);
|
|
603
|
+
return {
|
|
604
|
+
bytes,
|
|
605
|
+
mode: props.mode ?? DEFAULT_MODE,
|
|
606
|
+
path: props.path,
|
|
607
|
+
sha256: sha256Hex(bytes),
|
|
608
|
+
...await resolveIds(runner, props, props.path, refuse)
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
var diffFile = async (runner, news, output) => diffTarget(runner, await desiredFile(runner, news), output);
|
|
612
|
+
var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
613
|
+
const want = await desiredFile(runner, props);
|
|
614
|
+
const spec = { bytesFor: async () => want.bytes, owner: props.owner, refuse, want };
|
|
615
|
+
return convergeFile(runner, spec, output, adopt);
|
|
616
|
+
};
|
|
617
|
+
var deleteFile = (runner, output) => removeWholeFile(runner, output, refuse);
|
|
618
|
+
|
|
570
619
|
// src/launchd/host-file.ts
|
|
571
620
|
var HostFile = Resource("Host.File");
|
|
572
621
|
var HostFileProvider = () => Provider.effect(HostFile, Effect6.gen(function* () {
|
|
@@ -585,7 +634,9 @@ var HostFileProvider = () => Provider.effect(HostFile, Effect6.gen(function* ()
|
|
|
585
634
|
if (isResolved(news))
|
|
586
635
|
return lift(() => diffFile(runner, news, output));
|
|
587
636
|
const path = resolvedString(news, "path");
|
|
588
|
-
|
|
637
|
+
if (path === undefined || path === output.path)
|
|
638
|
+
return Effect6.succeed(undefined);
|
|
639
|
+
return lift(async () => await oneFile(runner, path, output.path) === true ? undefined : { action: "replace" });
|
|
589
640
|
},
|
|
590
641
|
reconcile: ({ fqn, instanceId, news, output }) => Effect6.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
|
|
591
642
|
delete: ({ output }) => lift(() => deleteFile(runner, output))
|
|
@@ -1126,7 +1177,9 @@ var localRunner = (options = {}) => {
|
|
|
1126
1177
|
try {
|
|
1127
1178
|
const stats = await lstat(path);
|
|
1128
1179
|
return {
|
|
1180
|
+
dev: stats.dev,
|
|
1129
1181
|
gid: stats.gid,
|
|
1182
|
+
ino: stats.ino,
|
|
1130
1183
|
kind: kindOf(stats),
|
|
1131
1184
|
mode: stats.mode & 4095,
|
|
1132
1185
|
size: stats.size,
|
|
@@ -1558,5 +1611,5 @@ export {
|
|
|
1558
1611
|
sudoRunner
|
|
1559
1612
|
};
|
|
1560
1613
|
|
|
1561
|
-
//# debugId=
|
|
1614
|
+
//# debugId=3EE0567FCAB27DCF64756E2164756E21
|
|
1562
1615
|
//# sourceMappingURL=index.js.map
|