@homeflare/alchemy 0.20.0 → 0.22.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 +27 -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.d.ts +2 -0
- package/dist/launchd/index.d.ts.map +1 -1
- package/dist/launchd/index.js +225 -120
- package/dist/launchd/index.js.map +13 -10
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/port-claims.d.ts +31 -0
- package/dist/launchd/port-claims.d.ts.map +1 -0
- 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 +113 -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 +25 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +1425 -0
- package/dist/release/index.js.map +36 -0
- package/dist/release/openbao.d.ts +35 -0
- package/dist/release/openbao.d.ts.map +1 -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/launchd-ports.md +109 -0
- package/docs/launchd.md +5 -0
- package/docs/release-binary-catalogs.md +138 -0
- package/docs/release-binary-measured.md +163 -0
- package/docs/release-binary-openbao.md +151 -0
- package/docs/release-binary-refusals.md +57 -0
- package/docs/release-binary-upstream.md +163 -0
- package/docs/release-binary.md +165 -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/index.ts +2 -0
- package/src/launchd/local-runner.test.ts +12 -1
- package/src/launchd/local-runner.ts +2 -0
- package/src/launchd/port-claims.test.ts +124 -0
- package/src/launchd/port-claims.ts +102 -0
- package/src/launchd/providers.test.ts +3 -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 +167 -0
- package/src/release/catalog.ts +238 -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/openbao/checksums.txt +83 -0
- package/src/release/fixtures/openbao/checksums.txt.gpgsig +0 -0
- package/src/release/fixtures/openbao/darwin-assets.txt +12 -0
- package/src/release/fixtures/openbao/openbao-gpg-pub-20240618.asc +109 -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 +44 -0
- package/src/release/openbao.test.ts +120 -0
- package/src/release/openbao.ts +86 -0
- package/src/release/plan.test.ts +189 -0
- package/src/release/provider-refusals.test.ts +48 -0
- package/src/release/providers.test.ts +175 -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
|
@@ -0,0 +1,1425 @@
|
|
|
1
|
+
// src/launchd/secret-tripwire.ts
|
|
2
|
+
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).";
|
|
3
|
+
var SECRET_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|SECRET_KEY|CREDENTIALS?)$/i;
|
|
4
|
+
var INLINE_FLAG = /^--?[a-z0-9-]*(?:token|password|passwd|passphrase|secret|api-?key)=./i;
|
|
5
|
+
var BARE_FLAG = /^--?(?:[a-z0-9]+-)*(?:token|password|passwd|passphrase|secret|api-?key)$/i;
|
|
6
|
+
var PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
|
|
7
|
+
var keyLeaves = (value, path) => {
|
|
8
|
+
if (typeof value === "string")
|
|
9
|
+
return PRIVATE_KEY.test(value) ? [path] : [];
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.flatMap((item, index) => keyLeaves(item, `${path}[${String(index)}]`));
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "object" && value !== null) {
|
|
14
|
+
return Object.entries(value).flatMap(([key, item]) => keyLeaves(item, `${path}.${key}`));
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
};
|
|
18
|
+
var jobSecretProblems = (environment, argv, extraKeys) => {
|
|
19
|
+
const found = [];
|
|
20
|
+
for (const [name, value] of Object.entries(environment ?? {})) {
|
|
21
|
+
if (SECRET_NAME.test(name))
|
|
22
|
+
found.push(`environment ${name} looks like a secret: ${ALTERNATIVE}`);
|
|
23
|
+
else if (PRIVATE_KEY.test(value))
|
|
24
|
+
found.push(`environment ${name} holds a private key: ${ALTERNATIVE}`);
|
|
25
|
+
}
|
|
26
|
+
for (const [index, arg] of argv.entries()) {
|
|
27
|
+
if (INLINE_FLAG.test(arg)) {
|
|
28
|
+
found.push(`programArguments[${String(index)}] passes a secret inline: ${ALTERNATIVE}`);
|
|
29
|
+
} else if (BARE_FLAG.test(arg) && index + 1 < argv.length) {
|
|
30
|
+
found.push(`programArguments[${String(index + 1)}] follows a secret flag: ${ALTERNATIVE}`);
|
|
31
|
+
} else if (PRIVATE_KEY.test(arg)) {
|
|
32
|
+
found.push(`programArguments[${String(index)}] holds a private key: ${ALTERNATIVE}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const path of keyLeaves(extraKeys ?? {}, "extraKeys")) {
|
|
36
|
+
found.push(`${path} holds a private key: ${ALTERNATIVE}`);
|
|
37
|
+
}
|
|
38
|
+
return found;
|
|
39
|
+
};
|
|
40
|
+
var fileSecretProblems = (content) => PRIVATE_KEY.test(content) ? [`content holds a private key: ${ALTERNATIVE}`] : [];
|
|
41
|
+
|
|
42
|
+
// src/launchd/host-file-form.ts
|
|
43
|
+
var DEFAULT_MODE = 420;
|
|
44
|
+
var identityProblems = (name, value) => {
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return [];
|
|
47
|
+
if (typeof value === "number") {
|
|
48
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
49
|
+
}
|
|
50
|
+
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`];
|
|
51
|
+
};
|
|
52
|
+
var pathProblems = (path) => {
|
|
53
|
+
const found = [];
|
|
54
|
+
if (!path.startsWith("/"))
|
|
55
|
+
found.push("path must be absolute");
|
|
56
|
+
if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
|
|
57
|
+
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
58
|
+
}
|
|
59
|
+
if (path.includes("\x00"))
|
|
60
|
+
found.push("path contains NUL");
|
|
61
|
+
return found;
|
|
62
|
+
};
|
|
63
|
+
var fileProblems = (props) => {
|
|
64
|
+
const found = pathProblems(props.path);
|
|
65
|
+
const mode = props.mode ?? DEFAULT_MODE;
|
|
66
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
67
|
+
found.push("mode must be 0–0o7777");
|
|
68
|
+
found.push(...identityProblems("owner", props.owner));
|
|
69
|
+
found.push(...identityProblems("group", props.group));
|
|
70
|
+
found.push(...fileSecretProblems(props.content));
|
|
71
|
+
return found;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/release/binary-form.ts
|
|
75
|
+
var DEFAULT_BINARY_MODE = 493;
|
|
76
|
+
var releaseUrl = (repo, tag, asset) => `https://github.com/${repo}/releases/download/${tag}/${asset}`;
|
|
77
|
+
var releaseBinaryPath = (props) => `${props.directory}/${props.name}`;
|
|
78
|
+
var pinnedDownload = (props) => ({
|
|
79
|
+
member: props.member,
|
|
80
|
+
memberSha256: props.sha256,
|
|
81
|
+
sha256: props.archive.sha256,
|
|
82
|
+
size: props.archive.size,
|
|
83
|
+
url: releaseUrl(props.archive.repo, props.archive.tag, props.archive.asset)
|
|
84
|
+
});
|
|
85
|
+
var modeProblems = (mode) => {
|
|
86
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
87
|
+
return ["mode must be 0–0o7777"];
|
|
88
|
+
const found = [];
|
|
89
|
+
if ((mode & 3584) !== 0)
|
|
90
|
+
found.push("mode must not set setuid, setgid or sticky");
|
|
91
|
+
if ((mode & 18) !== 0)
|
|
92
|
+
found.push("mode must not be group- or world-writable");
|
|
93
|
+
if ((mode & 64) === 0)
|
|
94
|
+
found.push("mode must let the owner execute it");
|
|
95
|
+
if ((mode & 256) === 0)
|
|
96
|
+
found.push("mode must let the owner read it (it is re-hashed)");
|
|
97
|
+
return found;
|
|
98
|
+
};
|
|
99
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
100
|
+
var MAX_ARCHIVE = 1 << 30;
|
|
101
|
+
var REPO = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\/[A-Za-z0-9._-]+$/;
|
|
102
|
+
var SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
103
|
+
var TARBALL = /\.(tar\.gz|tgz)$/;
|
|
104
|
+
var segmentProblem = (what, value) => !SEGMENT.test(value) || value === "." || value === ".." ? `${what} must be one path segment of letters, digits, '.', '_' or '-': ${JSON.stringify(value)}` : undefined;
|
|
105
|
+
var pinProblems = (props) => {
|
|
106
|
+
const archive = props.archive ?? {};
|
|
107
|
+
const found = [];
|
|
108
|
+
const text = (what, value) => {
|
|
109
|
+
if (typeof value === "string")
|
|
110
|
+
return value;
|
|
111
|
+
found.push(`${what} is required, as a plain string`);
|
|
112
|
+
return;
|
|
113
|
+
};
|
|
114
|
+
const repo = text("archive.repo", archive.repo);
|
|
115
|
+
const repoName = repo?.slice(repo.indexOf("/") + 1);
|
|
116
|
+
if (repo !== undefined && (!REPO.test(repo) || repoName === "." || repoName === ".."))
|
|
117
|
+
found.push(`archive.repo must be "owner/name": ${JSON.stringify(repo)}`);
|
|
118
|
+
for (const [what, value] of [
|
|
119
|
+
["archive.tag", archive.tag],
|
|
120
|
+
["name", props.name]
|
|
121
|
+
]) {
|
|
122
|
+
const given = text(what, value);
|
|
123
|
+
const problem = given === undefined ? undefined : segmentProblem(what, given);
|
|
124
|
+
if (problem !== undefined)
|
|
125
|
+
found.push(problem);
|
|
126
|
+
}
|
|
127
|
+
const asset = text("archive.asset", archive.asset);
|
|
128
|
+
if (asset !== undefined) {
|
|
129
|
+
const problem = segmentProblem("archive.asset", asset);
|
|
130
|
+
if (problem !== undefined)
|
|
131
|
+
found.push(problem);
|
|
132
|
+
else if (!TARBALL.test(asset))
|
|
133
|
+
found.push(`archive.asset must be a .tar.gz or .tgz: "${asset}"`);
|
|
134
|
+
}
|
|
135
|
+
const size = archive.size;
|
|
136
|
+
if (typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0 || size > MAX_ARCHIVE) {
|
|
137
|
+
found.push(`archive.size must be the asset's byte count, 1 to ${String(MAX_ARCHIVE)}`);
|
|
138
|
+
}
|
|
139
|
+
for (const [what, value] of [
|
|
140
|
+
["archive.sha256", archive.sha256],
|
|
141
|
+
["sha256", props.sha256]
|
|
142
|
+
]) {
|
|
143
|
+
const given = text(what, value);
|
|
144
|
+
if (given !== undefined && !HEX64.test(given))
|
|
145
|
+
found.push(`${what} must be 64 lower-case hex digits`);
|
|
146
|
+
}
|
|
147
|
+
const member = text("member", props.member);
|
|
148
|
+
if (member !== undefined && (member === "" || member.startsWith("/") || member.split("/").includes(".."))) {
|
|
149
|
+
found.push(`member must be a relative name inside the archive: ${JSON.stringify(member)}`);
|
|
150
|
+
}
|
|
151
|
+
return found;
|
|
152
|
+
};
|
|
153
|
+
var ARCHIVE_KEYS = ["repo", "tag", "asset", "size", "sha256"];
|
|
154
|
+
var pinsMoved = (olds, news) => olds.member !== news.member || olds.sha256 !== news.sha256 || ARCHIVE_KEYS.some((key) => olds.archive[key] !== news.archive[key]);
|
|
155
|
+
var inPlaceRefusal = (olds, news) => `a new pin (${news.archive.tag} ${news.member} ${news.sha256}) at the path of the installed ` + `one (${olds.archive.tag} ${olds.member} ${olds.sha256}) would overwrite it in place; give each ` + "version its own directory (catalogDirectory), so an upgrade is a new path";
|
|
156
|
+
var binaryProblems = (props) => [
|
|
157
|
+
...pinProblems(props),
|
|
158
|
+
...pathProblems(props.directory).map((problem) => `directory: ${problem}`),
|
|
159
|
+
...modeProblems(props.mode ?? DEFAULT_BINARY_MODE),
|
|
160
|
+
...identityProblems("owner", props.owner),
|
|
161
|
+
...identityProblems("group", props.group)
|
|
162
|
+
];
|
|
163
|
+
// src/release/binary.ts
|
|
164
|
+
import { Resource } from "alchemy";
|
|
165
|
+
import * as Provider from "alchemy/Provider";
|
|
166
|
+
import * as Effect9 from "effect/Effect";
|
|
167
|
+
import * as HttpClient3 from "effect/unstable/http/HttpClient";
|
|
168
|
+
|
|
169
|
+
// src/launchd/host-effect.ts
|
|
170
|
+
import * as Effect from "effect/Effect";
|
|
171
|
+
var lift = (step) => Effect.tryPromise({
|
|
172
|
+
catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
|
|
173
|
+
try: step
|
|
174
|
+
});
|
|
175
|
+
var resolvedString = (news, key) => {
|
|
176
|
+
if (typeof news !== "object" || news === null)
|
|
177
|
+
return;
|
|
178
|
+
const value = news[key];
|
|
179
|
+
return typeof value === "string" ? value : undefined;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/launchd/runner.ts
|
|
183
|
+
import * as Context from "effect/Context";
|
|
184
|
+
import * as Layer from "effect/Layer";
|
|
185
|
+
|
|
186
|
+
class HostRunnerService extends Context.Service()("homeflare/launchd/HostRunner") {
|
|
187
|
+
}
|
|
188
|
+
var hostRunnerLayer = (runner) => Layer.succeed(HostRunnerService, runner);
|
|
189
|
+
var canActAsRoot = (runner) => runner.privileged || runner.effectiveUid() === 0;
|
|
190
|
+
|
|
191
|
+
// src/ownership/adopt.ts
|
|
192
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
193
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
194
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
195
|
+
import * as Effect5 from "effect/Effect";
|
|
196
|
+
import * as Option4 from "effect/Option";
|
|
197
|
+
|
|
198
|
+
// src/ownership/resume.ts
|
|
199
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
200
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
201
|
+
import * as Effect2 from "effect/Effect";
|
|
202
|
+
import * as Option from "effect/Option";
|
|
203
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
204
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
205
|
+
var note = (key, instanceId) => Effect2.gen(function* () {
|
|
206
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
207
|
+
if (Option.isSome(bag))
|
|
208
|
+
yield* bag.value.set(key, instanceId);
|
|
209
|
+
});
|
|
210
|
+
var noted = (key, instanceId) => Effect2.gen(function* () {
|
|
211
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
212
|
+
if (Option.isNone(bag))
|
|
213
|
+
return false;
|
|
214
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
215
|
+
});
|
|
216
|
+
var noteUnfinished = (instanceId) => Effect2.as(note(UNFINISHED, instanceId), undefined);
|
|
217
|
+
var noteResume = (instanceId, recovered) => Effect2.gen(function* () {
|
|
218
|
+
yield* noteUnfinished(instanceId);
|
|
219
|
+
const unproven = () => Effect2.succeed(undefined);
|
|
220
|
+
const found = yield* recovered.pipe(Effect2.catch(unproven), Effect2.catchDefect(unproven));
|
|
221
|
+
if (found === undefined || found === null || Unowned.is(found))
|
|
222
|
+
return;
|
|
223
|
+
yield* note(PROVEN, instanceId);
|
|
224
|
+
});
|
|
225
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
226
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
227
|
+
var provingResumes = (service) => {
|
|
228
|
+
const { diff, read } = service;
|
|
229
|
+
if (diff === undefined || read === undefined)
|
|
230
|
+
return service;
|
|
231
|
+
const proving = (input) => Effect2.gen(function* () {
|
|
232
|
+
if (input.output === undefined) {
|
|
233
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
234
|
+
}
|
|
235
|
+
return yield* diff(input);
|
|
236
|
+
});
|
|
237
|
+
return { ...service, diff: proving };
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/ownership/rows.ts
|
|
241
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
242
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
243
|
+
import * as Effect4 from "effect/Effect";
|
|
244
|
+
import * as Option3 from "effect/Option";
|
|
245
|
+
|
|
246
|
+
// src/ownership/whole.ts
|
|
247
|
+
import { Stack } from "alchemy/Stack";
|
|
248
|
+
import * as Effect3 from "effect/Effect";
|
|
249
|
+
import * as Option2 from "effect/Option";
|
|
250
|
+
var absent = (value) => value === undefined || value === null;
|
|
251
|
+
var isPlainObject = (value) => {
|
|
252
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
253
|
+
return false;
|
|
254
|
+
const proto = Object.getPrototypeOf(value);
|
|
255
|
+
return proto === Object.prototype || proto === null;
|
|
256
|
+
};
|
|
257
|
+
var carries = (declared, recorded) => {
|
|
258
|
+
if (absent(declared))
|
|
259
|
+
return true;
|
|
260
|
+
if (absent(recorded))
|
|
261
|
+
return false;
|
|
262
|
+
if (Array.isArray(declared)) {
|
|
263
|
+
if (!Array.isArray(recorded))
|
|
264
|
+
return false;
|
|
265
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
266
|
+
}
|
|
267
|
+
if (isPlainObject(declared)) {
|
|
268
|
+
if (!isPlainObject(recorded))
|
|
269
|
+
return false;
|
|
270
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
};
|
|
274
|
+
var wholeDeclaration = (fqn, props) => Effect3.map(Effect3.serviceOption(Stack), (stack) => {
|
|
275
|
+
const declared = Option2.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
276
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// src/ownership/rows.ts
|
|
280
|
+
var storeOf = Effect4.gen(function* () {
|
|
281
|
+
const stack = yield* Effect4.serviceOption(Stack2);
|
|
282
|
+
const state = yield* Effect4.serviceOption(State);
|
|
283
|
+
if (Option3.isNone(stack) || Option3.isNone(state))
|
|
284
|
+
return;
|
|
285
|
+
const store = yield* state.value;
|
|
286
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
287
|
+
return { resources: stack.value.resources, store, where };
|
|
288
|
+
});
|
|
289
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect4.orElseSucceed(() => {
|
|
290
|
+
return;
|
|
291
|
+
}));
|
|
292
|
+
var generationOf = (row, instanceId) => {
|
|
293
|
+
let at = row;
|
|
294
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
295
|
+
const generation = at;
|
|
296
|
+
if (generation.instanceId === instanceId)
|
|
297
|
+
return generation;
|
|
298
|
+
at = generation.old;
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
};
|
|
302
|
+
var recordedGeneration = (fqn, instanceId) => Effect4.gen(function* () {
|
|
303
|
+
const found = yield* storeOf;
|
|
304
|
+
if (found === undefined)
|
|
305
|
+
return;
|
|
306
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
307
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
308
|
+
if (generation !== undefined)
|
|
309
|
+
return generation;
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
});
|
|
313
|
+
var recordedInstance = (fqn, instanceId) => Effect4.gen(function* () {
|
|
314
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
315
|
+
if (generation === undefined)
|
|
316
|
+
return "absent";
|
|
317
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
318
|
+
});
|
|
319
|
+
var isCreate = (fqn, instanceId) => Effect4.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
320
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect4.gen(function* () {
|
|
321
|
+
const found = yield* storeOf;
|
|
322
|
+
if (found === undefined)
|
|
323
|
+
return;
|
|
324
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
325
|
+
const mine = row;
|
|
326
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect4.ignore);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// src/ownership/adopt.ts
|
|
333
|
+
var adoptEnabled = (fqn) => Effect5.gen(function* () {
|
|
334
|
+
const stack = yield* Effect5.serviceOption(Stack3);
|
|
335
|
+
const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
336
|
+
if (scoped !== undefined)
|
|
337
|
+
return scoped;
|
|
338
|
+
const policy = yield* Effect5.serviceOption(AdoptPolicy);
|
|
339
|
+
if (Option4.isSome(policy))
|
|
340
|
+
return policy.value;
|
|
341
|
+
const context = yield* Effect5.serviceOption(AlchemyContext);
|
|
342
|
+
return Option4.isSome(context) ? context.value.adopt : false;
|
|
343
|
+
});
|
|
344
|
+
var adoptable = (owner) => Effect5.gen(function* () {
|
|
345
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
346
|
+
return true;
|
|
347
|
+
return yield* unfinished(owner.instanceId);
|
|
348
|
+
});
|
|
349
|
+
var adoptsAtApply = (owner) => Effect5.gen(function* () {
|
|
350
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
351
|
+
return false;
|
|
352
|
+
return yield* adoptable(owner);
|
|
353
|
+
});
|
|
354
|
+
var refuseTakeover = (owner, what) => Effect5.gen(function* () {
|
|
355
|
+
if (owner.output !== undefined)
|
|
356
|
+
return;
|
|
357
|
+
if (yield* resumes(owner.instanceId))
|
|
358
|
+
return;
|
|
359
|
+
const may = yield* adoptable(owner);
|
|
360
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
361
|
+
return;
|
|
362
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
363
|
+
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.";
|
|
364
|
+
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}`));
|
|
365
|
+
});
|
|
366
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
367
|
+
|
|
368
|
+
// src/release/binary-diff.ts
|
|
369
|
+
import { isResolved } from "alchemy/Diff";
|
|
370
|
+
|
|
371
|
+
// src/launchd/file-identity.ts
|
|
372
|
+
var sameEntry = (a, b) => {
|
|
373
|
+
if (a === undefined || b === undefined)
|
|
374
|
+
return false;
|
|
375
|
+
if (a.dev === undefined || a.ino === undefined || b.dev === undefined || b.ino === undefined) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
379
|
+
};
|
|
380
|
+
var oneFile = async (runner, a, b) => {
|
|
381
|
+
if (a === b)
|
|
382
|
+
return true;
|
|
383
|
+
const [left, right] = await Promise.all([runner.stat(a), runner.stat(b)]);
|
|
384
|
+
return sameEntry(left, right);
|
|
385
|
+
};
|
|
386
|
+
var leaveOldPath = async (runner, oldPath, path, remove, refuse) => {
|
|
387
|
+
const same = await oneFile(runner, oldPath, path);
|
|
388
|
+
if (same === true)
|
|
389
|
+
return;
|
|
390
|
+
await remove();
|
|
391
|
+
if (same === undefined && await runner.stat(path) === undefined) {
|
|
392
|
+
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.");
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/launchd/job-form.ts
|
|
397
|
+
import { createHash } from "node:crypto";
|
|
398
|
+
|
|
399
|
+
// src/launchd/plist.ts
|
|
400
|
+
class PlistError extends Error {
|
|
401
|
+
path;
|
|
402
|
+
constructor(path, detail) {
|
|
403
|
+
super(`plist ${path === "" ? "(root)" : path}: ${detail}`);
|
|
404
|
+
this.path = path;
|
|
405
|
+
this.name = "PlistError";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
var HEADER = `<?xml version="1.0" encoding="UTF-8"?>
|
|
409
|
+
` + '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' + `"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
410
|
+
` + `<plist version="1.0">
|
|
411
|
+
`;
|
|
412
|
+
var MAX_DEPTH = 32;
|
|
413
|
+
var FORBIDDEN = /[\x00-\x08\x0B\x0C\x0E-\x1F\uD800-\uDFFF\uFFFE\uFFFF]/u;
|
|
414
|
+
var escapeText = (text, path) => {
|
|
415
|
+
const bad = FORBIDDEN.exec(text);
|
|
416
|
+
if (bad !== null) {
|
|
417
|
+
const code = bad[0].codePointAt(0) ?? 0;
|
|
418
|
+
throw new PlistError(path, `U+${code.toString(16).toUpperCase().padStart(4, "0")} cannot appear in XML 1.0`);
|
|
419
|
+
}
|
|
420
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\r", " ");
|
|
421
|
+
};
|
|
422
|
+
var isPlainObject2 = (value) => {
|
|
423
|
+
const proto = Object.getPrototypeOf(value);
|
|
424
|
+
return proto === Object.prototype || proto === null;
|
|
425
|
+
};
|
|
426
|
+
var renderValue = (value, path, depth, out) => {
|
|
427
|
+
const pad = "\t".repeat(depth);
|
|
428
|
+
if (depth > MAX_DEPTH)
|
|
429
|
+
throw new PlistError(path, `nested deeper than ${String(MAX_DEPTH)}`);
|
|
430
|
+
if (typeof value === "string") {
|
|
431
|
+
out.push(`${pad}<string>${escapeText(value, path)}</string>`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (typeof value === "boolean") {
|
|
435
|
+
out.push(`${pad}<${value ? "true" : "false"}/>`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (typeof value === "number") {
|
|
439
|
+
if (!Number.isSafeInteger(value)) {
|
|
440
|
+
throw new PlistError(path, `${String(value)} is not a safe integer (no <real> support)`);
|
|
441
|
+
}
|
|
442
|
+
out.push(`${pad}<integer>${String(value === 0 ? 0 : value)}</integer>`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (Array.isArray(value)) {
|
|
446
|
+
if (value.length === 0) {
|
|
447
|
+
out.push(`${pad}<array/>`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
out.push(`${pad}<array>`);
|
|
451
|
+
for (const [index, item] of value.entries()) {
|
|
452
|
+
renderValue(item, `${path}[${String(index)}]`, depth + 1, out);
|
|
453
|
+
}
|
|
454
|
+
out.push(`${pad}</array>`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (typeof value === "object" && value !== null && isPlainObject2(value)) {
|
|
458
|
+
renderDict(value, path, depth, out);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const kind = value === null ? "null" : typeof value;
|
|
462
|
+
throw new PlistError(path, `${kind} has no plist form — omit the key instead`);
|
|
463
|
+
};
|
|
464
|
+
var renderDict = (dict, path, depth, out) => {
|
|
465
|
+
const pad = "\t".repeat(depth);
|
|
466
|
+
const keys = Object.keys(dict).sort();
|
|
467
|
+
if (keys.length === 0) {
|
|
468
|
+
out.push(`${pad}<dict/>`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
out.push(`${pad}<dict>`);
|
|
472
|
+
for (const key of keys) {
|
|
473
|
+
const child = path === "" ? key : `${path}.${key}`;
|
|
474
|
+
out.push(`${pad} <key>${escapeText(key, child)}</key>`);
|
|
475
|
+
renderValue(dict[key], child, depth + 1, out);
|
|
476
|
+
}
|
|
477
|
+
out.push(`${pad}</dict>`);
|
|
478
|
+
};
|
|
479
|
+
var renderPlist = (dict) => {
|
|
480
|
+
if (typeof dict !== "object" || dict === null || Array.isArray(dict) || !isPlainObject2(dict)) {
|
|
481
|
+
throw new PlistError("", "the top level of a launchd plist must be a dict");
|
|
482
|
+
}
|
|
483
|
+
const out = [];
|
|
484
|
+
renderDict(dict, "", 0, out);
|
|
485
|
+
return `${HEADER}${out.join(`
|
|
486
|
+
`)}
|
|
487
|
+
</plist>
|
|
488
|
+
`;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/launchd/job-form.ts
|
|
492
|
+
var parseDomain = (domain) => {
|
|
493
|
+
if (domain === "system")
|
|
494
|
+
return { kind: "system" };
|
|
495
|
+
const match = /^gui\/(\d+)$/.exec(domain);
|
|
496
|
+
return match?.[1] === undefined ? undefined : { kind: "gui", uid: Number(match[1]) };
|
|
497
|
+
};
|
|
498
|
+
var serviceTarget = (label, domain) => `${domain}/${label}`;
|
|
499
|
+
var plistPathFor = (label, domain, home) => {
|
|
500
|
+
if (domain.kind === "system")
|
|
501
|
+
return `/Library/LaunchDaemons/${label}.plist`;
|
|
502
|
+
if (home === undefined || !home.startsWith("/")) {
|
|
503
|
+
throw new Error(`gui/${String(domain.uid)}: no home directory for that uid on this host`);
|
|
504
|
+
}
|
|
505
|
+
return `${home}/Library/LaunchAgents/${label}.plist`;
|
|
506
|
+
};
|
|
507
|
+
var OWNED_KEYS = [
|
|
508
|
+
"EnvironmentVariables",
|
|
509
|
+
"GroupName",
|
|
510
|
+
"KeepAlive",
|
|
511
|
+
"Label",
|
|
512
|
+
"Program",
|
|
513
|
+
"ProgramArguments",
|
|
514
|
+
"RunAtLoad",
|
|
515
|
+
"StandardErrorPath",
|
|
516
|
+
"StandardOutPath",
|
|
517
|
+
"StartCalendarInterval",
|
|
518
|
+
"StartInterval",
|
|
519
|
+
"ThrottleInterval",
|
|
520
|
+
"UserName",
|
|
521
|
+
"WorkingDirectory"
|
|
522
|
+
];
|
|
523
|
+
var calendarDict = (entry) => {
|
|
524
|
+
const dict = {};
|
|
525
|
+
if (entry.minute !== undefined)
|
|
526
|
+
dict["Minute"] = entry.minute;
|
|
527
|
+
if (entry.hour !== undefined)
|
|
528
|
+
dict["Hour"] = entry.hour;
|
|
529
|
+
if (entry.day !== undefined)
|
|
530
|
+
dict["Day"] = entry.day;
|
|
531
|
+
if (entry.weekday !== undefined)
|
|
532
|
+
dict["Weekday"] = entry.weekday;
|
|
533
|
+
if (entry.month !== undefined)
|
|
534
|
+
dict["Month"] = entry.month;
|
|
535
|
+
return dict;
|
|
536
|
+
};
|
|
537
|
+
var keepAliveValue = (keepAlive) => {
|
|
538
|
+
if (typeof keepAlive === "boolean")
|
|
539
|
+
return keepAlive;
|
|
540
|
+
const dict = {};
|
|
541
|
+
if (keepAlive.successfulExit !== undefined)
|
|
542
|
+
dict["SuccessfulExit"] = keepAlive.successfulExit;
|
|
543
|
+
if (keepAlive.crashed !== undefined)
|
|
544
|
+
dict["Crashed"] = keepAlive.crashed;
|
|
545
|
+
return dict;
|
|
546
|
+
};
|
|
547
|
+
var jobPlistDict = (props) => {
|
|
548
|
+
const dict = { ...props.extraKeys };
|
|
549
|
+
dict["Label"] = props.label;
|
|
550
|
+
dict["ProgramArguments"] = [...props.programArguments];
|
|
551
|
+
const env = props.environment ?? {};
|
|
552
|
+
if (Object.keys(env).length > 0)
|
|
553
|
+
dict["EnvironmentVariables"] = { ...env };
|
|
554
|
+
if (props.runAtLoad !== undefined)
|
|
555
|
+
dict["RunAtLoad"] = props.runAtLoad;
|
|
556
|
+
if (props.keepAlive !== undefined)
|
|
557
|
+
dict["KeepAlive"] = keepAliveValue(props.keepAlive);
|
|
558
|
+
if (props.startInterval !== undefined)
|
|
559
|
+
dict["StartInterval"] = props.startInterval;
|
|
560
|
+
const calendar = props.startCalendarInterval;
|
|
561
|
+
if (calendar !== undefined) {
|
|
562
|
+
dict["StartCalendarInterval"] = Array.isArray(calendar) ? calendar.map(calendarDict) : calendarDict(calendar);
|
|
563
|
+
}
|
|
564
|
+
if (props.throttleInterval !== undefined)
|
|
565
|
+
dict["ThrottleInterval"] = props.throttleInterval;
|
|
566
|
+
if (props.standardOutPath !== undefined)
|
|
567
|
+
dict["StandardOutPath"] = props.standardOutPath;
|
|
568
|
+
if (props.standardErrorPath !== undefined)
|
|
569
|
+
dict["StandardErrorPath"] = props.standardErrorPath;
|
|
570
|
+
if (props.workingDirectory !== undefined)
|
|
571
|
+
dict["WorkingDirectory"] = props.workingDirectory;
|
|
572
|
+
if (props.userName !== undefined)
|
|
573
|
+
dict["UserName"] = props.userName;
|
|
574
|
+
if (props.groupName !== undefined)
|
|
575
|
+
dict["GroupName"] = props.groupName;
|
|
576
|
+
return dict;
|
|
577
|
+
};
|
|
578
|
+
var sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
579
|
+
var renderJob = (props) => {
|
|
580
|
+
const text = renderPlist(jobPlistDict(props));
|
|
581
|
+
const bytes = new TextEncoder().encode(text);
|
|
582
|
+
return { bytes, sha256: sha256Hex(bytes), text };
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// src/launchd/file-converge.ts
|
|
586
|
+
var numericId = (value) => typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
587
|
+
var resolveIds = async (runner, declared, path, refuse) => {
|
|
588
|
+
let uid;
|
|
589
|
+
let gid;
|
|
590
|
+
if (declared.owner !== undefined) {
|
|
591
|
+
uid = numericId(declared.owner) ?? (await runner.lookupUser(String(declared.owner)))?.uid;
|
|
592
|
+
if (uid === undefined)
|
|
593
|
+
throw refuse(path, `no user ${String(declared.owner)} on this host`);
|
|
594
|
+
}
|
|
595
|
+
if (declared.group !== undefined) {
|
|
596
|
+
gid = numericId(declared.group) ?? await runner.lookupGroup(String(declared.group));
|
|
597
|
+
if (gid === undefined)
|
|
598
|
+
throw refuse(path, `no group ${String(declared.group)} on this host`);
|
|
599
|
+
}
|
|
600
|
+
return { ...uid === undefined ? {} : { uid }, ...gid === undefined ? {} : { gid } };
|
|
601
|
+
};
|
|
602
|
+
var readFileAttributes = async (runner, path) => {
|
|
603
|
+
const stat = await runner.stat(path);
|
|
604
|
+
if (stat === undefined)
|
|
605
|
+
return;
|
|
606
|
+
const bytes = stat.kind === "file" ? await runner.readFile(path) : undefined;
|
|
607
|
+
return {
|
|
608
|
+
gid: stat.gid,
|
|
609
|
+
mode: stat.mode,
|
|
610
|
+
path,
|
|
611
|
+
sha256: bytes === undefined ? "" : sha256Hex(bytes),
|
|
612
|
+
size: stat.size,
|
|
613
|
+
uid: stat.uid
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
var writeOptionsOf = (want) => ({
|
|
617
|
+
mode: want.mode,
|
|
618
|
+
...want.uid === undefined ? {} : { uid: want.uid },
|
|
619
|
+
...want.gid === undefined ? {} : { gid: want.gid }
|
|
620
|
+
});
|
|
621
|
+
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);
|
|
622
|
+
var diffTarget = async (runner, want, output) => {
|
|
623
|
+
const writes = async (diff) => {
|
|
624
|
+
await runner.checkWrite?.(want.path, writeOptionsOf(want));
|
|
625
|
+
return diff;
|
|
626
|
+
};
|
|
627
|
+
if (want.path !== output.path) {
|
|
628
|
+
const same = await oneFile(runner, want.path, output.path);
|
|
629
|
+
return writes({ action: same === true ? "update" : "replace" });
|
|
630
|
+
}
|
|
631
|
+
if (want.sha256 !== output.sha256)
|
|
632
|
+
return writes({ action: "update" });
|
|
633
|
+
const live = await readFileAttributes(runner, want.path);
|
|
634
|
+
return live !== undefined && fileMatches(live, want) ? { action: "noop" } : writes({ action: "update" });
|
|
635
|
+
};
|
|
636
|
+
var convergeFile = async (runner, spec, output, adopt = false) => {
|
|
637
|
+
const { want, refuse } = spec;
|
|
638
|
+
const path = want.path;
|
|
639
|
+
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
640
|
+
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.");
|
|
641
|
+
}
|
|
642
|
+
const stat = await runner.stat(path);
|
|
643
|
+
if (stat !== undefined && stat.kind !== "file") {
|
|
644
|
+
throw refuse(path, `is a ${stat.kind}; remove it from whatever owns it before declaring it here`);
|
|
645
|
+
}
|
|
646
|
+
const before = stat === undefined ? undefined : await readFileAttributes(runner, path);
|
|
647
|
+
const moved = output !== undefined && output.path !== path;
|
|
648
|
+
const prior = moved ? undefined : output;
|
|
649
|
+
const takeOver = adopt && output === undefined;
|
|
650
|
+
if (prior === undefined && before !== undefined && !fileMatches(before, want) && !takeOver) {
|
|
651
|
+
throw refuse(path, "already exists and is not this resource. Remove it, or declare it as a new resource and " + "deploy with --adopt.");
|
|
652
|
+
}
|
|
653
|
+
if (before === undefined || !fileMatches(before, want)) {
|
|
654
|
+
const bytes = await spec.bytesFor(before);
|
|
655
|
+
if (sha256Hex(bytes) !== want.sha256) {
|
|
656
|
+
throw refuse(path, `the bytes to write do not hash to ${want.sha256}. Nothing was written.`);
|
|
657
|
+
}
|
|
658
|
+
await runner.writeFileAtomic(path, bytes, writeOptionsOf(want));
|
|
659
|
+
}
|
|
660
|
+
const rollBack = async () => {
|
|
661
|
+
if (before === undefined)
|
|
662
|
+
await runner.removeFile(path).catch(() => {
|
|
663
|
+
return;
|
|
664
|
+
});
|
|
665
|
+
};
|
|
666
|
+
const after = await readFileAttributes(runner, path).catch(async (cause) => {
|
|
667
|
+
await rollBack();
|
|
668
|
+
throw refuse(path, `the write returned but reading it back failed: ${String(cause)}`);
|
|
669
|
+
});
|
|
670
|
+
if (after === undefined || !fileMatches(after, want)) {
|
|
671
|
+
await rollBack();
|
|
672
|
+
throw refuse(path, "the write returned but the file on disk does not match the declaration");
|
|
673
|
+
}
|
|
674
|
+
if (moved)
|
|
675
|
+
await leaveOldPath(runner, output.path, path, () => removeWholeFile(runner, output, refuse), refuse);
|
|
676
|
+
return after;
|
|
677
|
+
};
|
|
678
|
+
var removeWholeFile = async (runner, output, refuse) => {
|
|
679
|
+
const stat = await runner.stat(output.path);
|
|
680
|
+
if (stat === undefined)
|
|
681
|
+
return;
|
|
682
|
+
if (stat.kind !== "file")
|
|
683
|
+
throw refuse(output.path, `is now a ${stat.kind}; not removing it`);
|
|
684
|
+
await runner.removeFile(output.path);
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/release/binary-claim.ts
|
|
688
|
+
var TAKEN = "already exists and is not this resource";
|
|
689
|
+
var otherBytes = (found, pinned) => `${found === "" ? "something that is not a regular file" : `a file hashing to ${found}`} is ` + `there, not the pinned binary (${pinned}). --adopt takes over only the pinned binary: taking ` + "over other bytes would overwrite them in place. Remove it (or move it aside) and deploy again";
|
|
690
|
+
var adoptionProblem = (found, pinned) => found === pinned ? undefined : `${TAKEN}: ${otherBytes(found, pinned)}`;
|
|
691
|
+
var claimProblem = async (runner, want, adopt) => {
|
|
692
|
+
const stat = await runner.stat(want.path);
|
|
693
|
+
if (stat?.kind !== "file")
|
|
694
|
+
return;
|
|
695
|
+
const found = (await readFileAttributes(runner, want.path))?.sha256 ?? "";
|
|
696
|
+
if (found !== want.sha256)
|
|
697
|
+
return adoptionProblem(found, want.sha256);
|
|
698
|
+
if (adopt)
|
|
699
|
+
return;
|
|
700
|
+
return `${TAKEN}: it holds the pinned binary, but this stack holds no state for it and the plan ` + "could not ask first (a prop was still an Output, or a replace landed on it). " + "Identical is not ours: once claimed, a later delete removes it from whoever put it there. " + "Deploy with --adopt to take it over (a create, or an interrupted install of this one), or " + "remove it. If another declaration in this stack owned it under another name, declare the " + "rename with renamedFrom() instead";
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/release/refused.ts
|
|
704
|
+
import * as Data from "effect/Data";
|
|
705
|
+
|
|
706
|
+
class BinaryRefused extends Data.TaggedError("BinaryRefused") {
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
class DownloadFailed extends Data.TaggedError("DownloadFailed") {
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
class ChecksumMismatch extends Data.TaggedError("ChecksumMismatch") {
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
class ArchiveRefused extends Data.TaggedError("ArchiveRefused") {
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/release/tar.ts
|
|
719
|
+
var BLOCK = 512;
|
|
720
|
+
var KINDS = {
|
|
721
|
+
"1": "a hard link",
|
|
722
|
+
"2": "a symlink",
|
|
723
|
+
"3": "a character device",
|
|
724
|
+
"4": "a block device",
|
|
725
|
+
"5": "a directory",
|
|
726
|
+
"6": "a FIFO",
|
|
727
|
+
"7": "a contiguous file",
|
|
728
|
+
K: "a GNU long-link header",
|
|
729
|
+
L: "a GNU long-name header",
|
|
730
|
+
g: "a PAX global header",
|
|
731
|
+
x: "a PAX extended header"
|
|
732
|
+
};
|
|
733
|
+
var refuse = (message) => new ArchiveRefused({ message });
|
|
734
|
+
var strict = new TextDecoder("utf-8", { fatal: true });
|
|
735
|
+
var ascii = new TextDecoder("latin1");
|
|
736
|
+
var field = (block, start, length) => {
|
|
737
|
+
const slice = block.subarray(start, start + length);
|
|
738
|
+
const end = slice.indexOf(0);
|
|
739
|
+
return end === -1 ? slice : slice.subarray(0, end);
|
|
740
|
+
};
|
|
741
|
+
var text = (block, start, length) => {
|
|
742
|
+
try {
|
|
743
|
+
return strict.decode(field(block, start, length));
|
|
744
|
+
} catch {
|
|
745
|
+
throw refuse("an entry name is not UTF-8");
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
var octal = (block, start, length, what) => {
|
|
749
|
+
const raw = block.subarray(start, start + length);
|
|
750
|
+
if (((raw[0] ?? 0) & 128) !== 0)
|
|
751
|
+
throw refuse(`${what} is base-256 encoded`);
|
|
752
|
+
const digits = ascii.decode(raw).replace(/[\0 ]+$/, "").replace(/^ +/, "");
|
|
753
|
+
if (!/^[0-7]+$/.test(digits))
|
|
754
|
+
throw refuse(`${what} is not octal: ${JSON.stringify(digits)}`);
|
|
755
|
+
return Number.parseInt(digits, 8);
|
|
756
|
+
};
|
|
757
|
+
var checksumHolds = (block) => {
|
|
758
|
+
let sum = 0;
|
|
759
|
+
for (let at = 0;at < BLOCK; at += 1)
|
|
760
|
+
sum += at >= 148 && at < 156 ? 32 : block[at] ?? 0;
|
|
761
|
+
return sum === octal(block, 148, 8, "a header checksum");
|
|
762
|
+
};
|
|
763
|
+
var nameProblem = (name) => {
|
|
764
|
+
if (name === "")
|
|
765
|
+
return "has an empty name";
|
|
766
|
+
if (name.startsWith("/"))
|
|
767
|
+
return "is absolute";
|
|
768
|
+
if (name.split("/").includes(".."))
|
|
769
|
+
return 'climbs out of the archive with ".."';
|
|
770
|
+
return;
|
|
771
|
+
};
|
|
772
|
+
var concatBytes = (parts) => {
|
|
773
|
+
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
774
|
+
let at = 0;
|
|
775
|
+
for (const part of parts) {
|
|
776
|
+
out.set(part, at);
|
|
777
|
+
at += part.length;
|
|
778
|
+
}
|
|
779
|
+
return out;
|
|
780
|
+
};
|
|
781
|
+
var tarReader = (wanted) => {
|
|
782
|
+
const header = new Uint8Array(BLOCK);
|
|
783
|
+
const members = new Map;
|
|
784
|
+
const names = [];
|
|
785
|
+
const seen = new Set;
|
|
786
|
+
let phase = "header";
|
|
787
|
+
let filled = 0;
|
|
788
|
+
let remaining = 0;
|
|
789
|
+
let padding = 0;
|
|
790
|
+
let zeros = 0;
|
|
791
|
+
let name = "";
|
|
792
|
+
let parts;
|
|
793
|
+
const endEntry = () => {
|
|
794
|
+
if (parts !== undefined)
|
|
795
|
+
members.set(name, concatBytes(parts));
|
|
796
|
+
parts = undefined;
|
|
797
|
+
phase = padding > 0 ? "pad" : "header";
|
|
798
|
+
};
|
|
799
|
+
const startEntry = () => {
|
|
800
|
+
if (header.every((byte) => byte === 0)) {
|
|
801
|
+
zeros += 1;
|
|
802
|
+
if (zeros === 2)
|
|
803
|
+
phase = "end";
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (zeros > 0)
|
|
807
|
+
throw refuse("a zero block sits between entries (truncated or spliced)");
|
|
808
|
+
if (!checksumHolds(header))
|
|
809
|
+
throw refuse("an entry header fails its checksum");
|
|
810
|
+
const magic = ascii.decode(header.subarray(257, 265));
|
|
811
|
+
const posix = magic === "ustar\x0000";
|
|
812
|
+
if (!posix && magic !== "ustar \x00")
|
|
813
|
+
throw refuse("an entry header is neither ustar nor GNU");
|
|
814
|
+
const prefix = posix ? text(header, 345, 155) : "";
|
|
815
|
+
name = prefix === "" ? text(header, 0, 100) : `${prefix}/${text(header, 0, 100)}`;
|
|
816
|
+
const problem = nameProblem(name);
|
|
817
|
+
if (problem !== undefined)
|
|
818
|
+
throw refuse(`entry ${JSON.stringify(name)} ${problem}`);
|
|
819
|
+
const type = String.fromCharCode(header[156] ?? 0);
|
|
820
|
+
if (type !== "0" && type !== "\x00") {
|
|
821
|
+
const kind = KINDS[type] ?? `of unknown type ${JSON.stringify(type)}`;
|
|
822
|
+
throw refuse(`entry "${name}" is ${kind}; only regular files are accepted`);
|
|
823
|
+
}
|
|
824
|
+
if (seen.has(name))
|
|
825
|
+
throw refuse(`entry "${name}" appears twice`);
|
|
826
|
+
seen.add(name);
|
|
827
|
+
names.push(name);
|
|
828
|
+
remaining = octal(header, 124, 12, `entry "${name}"'s size`);
|
|
829
|
+
padding = (BLOCK - remaining % BLOCK) % BLOCK;
|
|
830
|
+
parts = wanted.has(name) ? [] : undefined;
|
|
831
|
+
phase = "data";
|
|
832
|
+
if (remaining === 0)
|
|
833
|
+
endEntry();
|
|
834
|
+
};
|
|
835
|
+
const push = (chunk) => {
|
|
836
|
+
let at = 0;
|
|
837
|
+
while (at < chunk.length) {
|
|
838
|
+
if (phase === "end") {
|
|
839
|
+
if (chunk.subarray(at).some((byte) => byte !== 0)) {
|
|
840
|
+
throw refuse("data follows the end-of-archive marker");
|
|
841
|
+
}
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (phase === "header") {
|
|
845
|
+
const take = Math.min(BLOCK - filled, chunk.length - at);
|
|
846
|
+
header.set(chunk.subarray(at, at + take), filled);
|
|
847
|
+
filled += take;
|
|
848
|
+
at += take;
|
|
849
|
+
if (filled === BLOCK) {
|
|
850
|
+
filled = 0;
|
|
851
|
+
startEntry();
|
|
852
|
+
}
|
|
853
|
+
} else if (phase === "data") {
|
|
854
|
+
const take = Math.min(remaining, chunk.length - at);
|
|
855
|
+
parts?.push(chunk.slice(at, at + take));
|
|
856
|
+
remaining -= take;
|
|
857
|
+
at += take;
|
|
858
|
+
if (remaining === 0)
|
|
859
|
+
endEntry();
|
|
860
|
+
} else {
|
|
861
|
+
const take = Math.min(padding, chunk.length - at);
|
|
862
|
+
padding -= take;
|
|
863
|
+
at += take;
|
|
864
|
+
if (padding === 0)
|
|
865
|
+
phase = "header";
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
const finish = () => {
|
|
870
|
+
if (phase !== "end")
|
|
871
|
+
throw refuse("the archive stops before its end-of-archive marker");
|
|
872
|
+
return { members, names };
|
|
873
|
+
};
|
|
874
|
+
return { finish, push };
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// src/release/archive.ts
|
|
878
|
+
var SLICE = 1 << 20;
|
|
879
|
+
var extractMembers = async (gzipped, wanted) => {
|
|
880
|
+
const tar = tarReader(wanted);
|
|
881
|
+
let offset = 0;
|
|
882
|
+
const source = new ReadableStream({
|
|
883
|
+
pull(controller) {
|
|
884
|
+
if (offset >= gzipped.length) {
|
|
885
|
+
controller.close();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
controller.enqueue(gzipped.slice(offset, offset + SLICE));
|
|
889
|
+
offset += SLICE;
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
const unzipped = source.pipeThrough(new DecompressionStream("gzip")).getReader();
|
|
893
|
+
try {
|
|
894
|
+
for (;; ) {
|
|
895
|
+
const { done, value } = await unzipped.read();
|
|
896
|
+
if (done)
|
|
897
|
+
break;
|
|
898
|
+
tar.push(value);
|
|
899
|
+
}
|
|
900
|
+
} catch (cause) {
|
|
901
|
+
await unzipped.cancel().catch(() => {
|
|
902
|
+
return;
|
|
903
|
+
});
|
|
904
|
+
if (cause instanceof ArchiveRefused)
|
|
905
|
+
throw cause;
|
|
906
|
+
throw new ArchiveRefused({ message: `not a gzip stream (${String(cause)})` });
|
|
907
|
+
}
|
|
908
|
+
return tar.finish();
|
|
909
|
+
};
|
|
910
|
+
var mismatch = (subject, expected, actual) => new ChecksumMismatch({
|
|
911
|
+
actual,
|
|
912
|
+
expected,
|
|
913
|
+
message: `${subject} hashes to ${actual}, not the pinned ${expected}`,
|
|
914
|
+
subject
|
|
915
|
+
});
|
|
916
|
+
var verifiedMember = async (archive, release) => {
|
|
917
|
+
const archiveSha = sha256Hex(archive);
|
|
918
|
+
if (archiveSha !== release.sha256)
|
|
919
|
+
throw mismatch(release.url, release.sha256, archiveSha);
|
|
920
|
+
const { members, names } = await extractMembers(archive, new Set([release.member]));
|
|
921
|
+
const bytes = members.get(release.member);
|
|
922
|
+
if (bytes === undefined) {
|
|
923
|
+
throw new ArchiveRefused({
|
|
924
|
+
message: `${release.url} has no member named exactly "${release.member}" (it has ${names.map((n) => `"${n}"`).join(", ") || "none"})`
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
const memberSha = sha256Hex(bytes);
|
|
928
|
+
if (memberSha !== release.memberSha256) {
|
|
929
|
+
throw mismatch(`member "${release.member}"`, release.memberSha256, memberSha);
|
|
930
|
+
}
|
|
931
|
+
return bytes;
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/release/binary-lifecycle.ts
|
|
935
|
+
var NOTHING = "Nothing was written.";
|
|
936
|
+
var refuse2 = (path, message) => new BinaryRefused({ message: `Release.Binary ${path}: ${message}` });
|
|
937
|
+
var restated = (path, cause) => {
|
|
938
|
+
const say = (message) => `Release.Binary ${path}: ${message}. ${NOTHING}`;
|
|
939
|
+
if (cause instanceof ChecksumMismatch) {
|
|
940
|
+
const { actual, expected, subject } = cause;
|
|
941
|
+
return new ChecksumMismatch({ actual, expected, message: say(cause.message), subject });
|
|
942
|
+
}
|
|
943
|
+
if (cause instanceof ArchiveRefused)
|
|
944
|
+
return new ArchiveRefused({ message: say(cause.message) });
|
|
945
|
+
if (cause instanceof DownloadFailed) {
|
|
946
|
+
return new DownloadFailed({ message: say(cause.message), retryable: cause.retryable });
|
|
947
|
+
}
|
|
948
|
+
return cause;
|
|
949
|
+
};
|
|
950
|
+
var desiredBinary = async (runner, props) => {
|
|
951
|
+
const path = releaseBinaryPath(props);
|
|
952
|
+
const problems = binaryProblems(props);
|
|
953
|
+
if (problems.length > 0)
|
|
954
|
+
throw refuse2(path, `${problems.join("; ")}. ${NOTHING}`);
|
|
955
|
+
const ids = await resolveIds(runner, props, path, refuse2);
|
|
956
|
+
const mode = props.mode ?? DEFAULT_BINARY_MODE;
|
|
957
|
+
return { download: pinnedDownload(props), want: { mode, path, sha256: props.sha256, ...ids } };
|
|
958
|
+
};
|
|
959
|
+
var withSource = (found, download) => ({ ...found, member: download.member, url: download.url });
|
|
960
|
+
var readBinary = async (runner, props) => {
|
|
961
|
+
const { download, want } = await desiredBinary(runner, props);
|
|
962
|
+
const found = await readFileAttributes(runner, want.path);
|
|
963
|
+
return found === undefined ? undefined : withSource(found, download);
|
|
964
|
+
};
|
|
965
|
+
var refreshBinary = async (runner, output) => {
|
|
966
|
+
const found = await readFileAttributes(runner, output.path);
|
|
967
|
+
return found === undefined ? undefined : { ...found, member: output.member, url: output.url };
|
|
968
|
+
};
|
|
969
|
+
var directoryReady = async (runner, props) => {
|
|
970
|
+
const stat = await runner.stat(props.directory);
|
|
971
|
+
if (stat?.kind === "directory" && (stat.mode & 18) !== 0) {
|
|
972
|
+
throw refuse2(releaseBinaryPath(props), `directory ${props.directory} is writable by group or other (mode ` + `${stat.mode.toString(8).padStart(4, "0")}), so whoever may write it may replace the ` + `binary; declare it 0755 or narrower. ${NOTHING}`);
|
|
973
|
+
}
|
|
974
|
+
if (stat?.kind === "directory")
|
|
975
|
+
return;
|
|
976
|
+
const why = stat === undefined ? "does not exist" : `is a ${stat.kind}, not a directory`;
|
|
977
|
+
throw refuse2(releaseBinaryPath(props), `directory ${props.directory} ${why}; declare it (HostDirectory) and pass its path. ${NOTHING}`);
|
|
978
|
+
};
|
|
979
|
+
var reconcileBinary = async (runner, fetch, props, options = {}) => {
|
|
980
|
+
const { adopt = false, declared = [], note: note2 = async () => {
|
|
981
|
+
return;
|
|
982
|
+
} } = options;
|
|
983
|
+
if (declared.length > 0) {
|
|
984
|
+
throw refuse2(releaseBinaryPath(props), `${declared.join("; ")}. A pin is copied into reviewed code, never computed during the ` + `deploy (declared-pins.ts). ${NOTHING}`);
|
|
985
|
+
}
|
|
986
|
+
const { download, want } = await desiredBinary(runner, props);
|
|
987
|
+
const olds = options.olds;
|
|
988
|
+
if (olds !== undefined && releaseBinaryPath(olds) === want.path && pinsMoved(olds, props)) {
|
|
989
|
+
throw refuse2(want.path, `${inPlaceRefusal(olds, props)}. ${NOTHING}`);
|
|
990
|
+
}
|
|
991
|
+
await directoryReady(runner, props);
|
|
992
|
+
if (options.output === undefined) {
|
|
993
|
+
const problem = await claimProblem(runner, want, adopt);
|
|
994
|
+
if (problem !== undefined)
|
|
995
|
+
throw refuse2(want.path, `${problem}. ${NOTHING}`);
|
|
996
|
+
}
|
|
997
|
+
const bytesFor = async (before) => {
|
|
998
|
+
if (before?.sha256 === want.sha256) {
|
|
999
|
+
const local = await runner.readFile(want.path);
|
|
1000
|
+
if (local !== undefined && sha256Hex(local) === want.sha256)
|
|
1001
|
+
return local;
|
|
1002
|
+
}
|
|
1003
|
+
await note2(`downloading ${download.url} (${String(download.size)} bytes)`);
|
|
1004
|
+
try {
|
|
1005
|
+
const bytes = await verifiedMember(await fetch(download.url, download.size), download);
|
|
1006
|
+
await note2(`verified ${download.member} ${download.memberSha256}`);
|
|
1007
|
+
return bytes;
|
|
1008
|
+
} catch (cause) {
|
|
1009
|
+
throw restated(want.path, cause);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const spec = { bytesFor, owner: props.owner, refuse: refuse2, want };
|
|
1013
|
+
return withSource(await convergeFile(runner, spec, options.output, adopt), download);
|
|
1014
|
+
};
|
|
1015
|
+
var deleteBinary = (runner, output) => removeWholeFile(runner, output, refuse2);
|
|
1016
|
+
|
|
1017
|
+
// src/release/binary-diff.ts
|
|
1018
|
+
var diffBinary = async (runner, news, olds, output) => {
|
|
1019
|
+
const problems = pinProblems(news ?? {});
|
|
1020
|
+
if (problems.length > 0)
|
|
1021
|
+
throw refuse2(output.path, `${problems.join("; ")}. ${NOTHING}`);
|
|
1022
|
+
const takeover = adoptionProblem(output.sha256, olds.sha256);
|
|
1023
|
+
if (takeover !== undefined)
|
|
1024
|
+
throw refuse2(output.path, `${takeover}. ${NOTHING}`);
|
|
1025
|
+
const pins = news;
|
|
1026
|
+
const directory = resolvedString(news, "directory");
|
|
1027
|
+
const path = directory === undefined ? undefined : `${directory}/${pins.name}`;
|
|
1028
|
+
const pinned = pinsMoved(olds, pins);
|
|
1029
|
+
if (pinned && path === output.path) {
|
|
1030
|
+
throw refuse2(output.path, `${inPlaceRefusal(olds, pins)}. ${NOTHING}`);
|
|
1031
|
+
}
|
|
1032
|
+
if (isResolved(news))
|
|
1033
|
+
return diffTarget(runner, (await desiredBinary(runner, news)).want, output);
|
|
1034
|
+
if (path !== undefined)
|
|
1035
|
+
return path === output.path ? undefined : { action: "replace" };
|
|
1036
|
+
if (pins.name !== olds.name || pins.sha256 !== olds.sha256)
|
|
1037
|
+
return { action: "replace" };
|
|
1038
|
+
return pinned ? { action: "update" } : undefined;
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
// src/release/binary-read.ts
|
|
1042
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
1043
|
+
import * as Effect6 from "effect/Effect";
|
|
1044
|
+
var readWithoutState = (runner, ask) => Effect6.gen(function* () {
|
|
1045
|
+
const recovering = (yield* recordedGeneration(ask.fqn, ask.instanceId)) !== undefined;
|
|
1046
|
+
const read = lift(() => readBinary(runner, ask.olds));
|
|
1047
|
+
const found = recovering ? yield* read.pipe(Effect6.catch((cause) => Effect6.as(Effect6.logWarning(`${ask.fqn}: the interrupted create's row cannot locate its binary (${cause.message}); ` + "nothing is recovered, and the create is re-driven with the declaration as it is now"), undefined))) : yield* read;
|
|
1048
|
+
return found === undefined ? undefined : Unowned2(found);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// src/release/declared-pins.ts
|
|
1052
|
+
import { Stack as Stack4 } from "alchemy/Stack";
|
|
1053
|
+
import * as Effect7 from "effect/Effect";
|
|
1054
|
+
import * as Option5 from "effect/Option";
|
|
1055
|
+
var declaredPinProblems = (fqn) => Effect7.gen(function* () {
|
|
1056
|
+
const stack = yield* Effect7.serviceOption(Stack4);
|
|
1057
|
+
if (Option5.isNone(stack))
|
|
1058
|
+
return [];
|
|
1059
|
+
const declared = stack.value.resources[fqn]?.Props;
|
|
1060
|
+
if (typeof declared !== "object" && typeof declared !== "function")
|
|
1061
|
+
return [];
|
|
1062
|
+
if (declared === null)
|
|
1063
|
+
return [];
|
|
1064
|
+
return pinProblems(declared).map((problem) => `as declared in the stack program, ${problem}`);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// src/release/download.ts
|
|
1068
|
+
import * as Effect8 from "effect/Effect";
|
|
1069
|
+
import * as Schedule from "effect/Schedule";
|
|
1070
|
+
import * as Stream from "effect/Stream";
|
|
1071
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
1072
|
+
var failed = (message, retryable) => new DownloadFailed({ message, retryable });
|
|
1073
|
+
var attempt = (url, size) => Effect8.gen(function* () {
|
|
1074
|
+
const client = yield* HttpClient.HttpClient;
|
|
1075
|
+
const response = yield* client.get(url);
|
|
1076
|
+
if (response.status !== 200) {
|
|
1077
|
+
return yield* failed(`${url}: HTTP ${String(response.status)}`, response.status >= 500);
|
|
1078
|
+
}
|
|
1079
|
+
const archive = new Uint8Array(size);
|
|
1080
|
+
let total = 0;
|
|
1081
|
+
yield* Stream.runForEach(response.stream, (chunk) => {
|
|
1082
|
+
if (total + chunk.length > size) {
|
|
1083
|
+
return Effect8.fail(failed(`${url}: runs past the pinned ${String(size)} bytes`, false));
|
|
1084
|
+
}
|
|
1085
|
+
archive.set(chunk, total);
|
|
1086
|
+
total += chunk.length;
|
|
1087
|
+
return Effect8.void;
|
|
1088
|
+
});
|
|
1089
|
+
if (total !== size) {
|
|
1090
|
+
return yield* failed(`${url}: ${String(total)} bytes, the pin says ${String(size)}`, true);
|
|
1091
|
+
}
|
|
1092
|
+
return archive;
|
|
1093
|
+
}).pipe(Effect8.catchTag("HttpClientError", (cause) => Effect8.fail(failed(cause.message, true))));
|
|
1094
|
+
var downloadPinned = (url, size, policy = {}) => attempt(url, size).pipe(Effect8.timeoutOrElse({
|
|
1095
|
+
duration: policy.timeout ?? "10 minutes",
|
|
1096
|
+
orElse: () => Effect8.fail(failed(`${url}: no complete response in time`, false))
|
|
1097
|
+
}), Effect8.retry({
|
|
1098
|
+
schedule: Schedule.spaced(policy.spacing ?? "2 seconds"),
|
|
1099
|
+
times: policy.retries ?? 2,
|
|
1100
|
+
while: (error) => error.retryable
|
|
1101
|
+
}));
|
|
1102
|
+
var httpFetchArchive = (client, policy = {}) => (url, size) => Effect8.runPromise(downloadPinned(url, size, policy).pipe(Effect8.provideService(HttpClient.HttpClient, client)));
|
|
1103
|
+
var sharingInFlight = (fetch) => {
|
|
1104
|
+
const inFlight = new Map;
|
|
1105
|
+
return (url, size) => {
|
|
1106
|
+
const key = `${String(size)} ${url}`;
|
|
1107
|
+
const running = inFlight.get(key);
|
|
1108
|
+
if (running !== undefined)
|
|
1109
|
+
return running;
|
|
1110
|
+
const started = fetch(url, size).finally(() => inFlight.delete(key));
|
|
1111
|
+
inFlight.set(key, started);
|
|
1112
|
+
return started;
|
|
1113
|
+
};
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
// src/release/binary.ts
|
|
1117
|
+
var ReleaseBinary = Resource("Release.Binary");
|
|
1118
|
+
var makeReleaseBinaryProvider = (internals = {}) => Provider.effect(ReleaseBinary, Effect9.gen(function* () {
|
|
1119
|
+
const runner = yield* HostRunnerService;
|
|
1120
|
+
const client = yield* HttpClient3.HttpClient;
|
|
1121
|
+
const fetch = sharingInFlight(httpFetchArchive(client, internals.download));
|
|
1122
|
+
const claims = new Map;
|
|
1123
|
+
const claim = (fqn, props) => {
|
|
1124
|
+
const path = releaseBinaryPath(props);
|
|
1125
|
+
const holder = claims.get(path);
|
|
1126
|
+
if (holder !== undefined && holder !== fqn) {
|
|
1127
|
+
throw refuse2(path, `is already installed by ${holder} in this deploy; declare each binary path once, ` + `and let every job that runs it share that one resource. ${NOTHING}`);
|
|
1128
|
+
}
|
|
1129
|
+
claims.set(path, fqn);
|
|
1130
|
+
};
|
|
1131
|
+
return ReleaseBinary.Provider.of({
|
|
1132
|
+
list: () => Effect9.succeed([]),
|
|
1133
|
+
read: ({ fqn, instanceId, olds, output }) => output === undefined ? readWithoutState(runner, { fqn, instanceId, olds }) : lift(() => refreshBinary(runner, output)),
|
|
1134
|
+
diff: ({ instanceId, news, olds, output }) => output === undefined ? noteUnfinished(instanceId) : lift(() => diffBinary(runner, news, olds, output)),
|
|
1135
|
+
reconcile: ({ fqn, instanceId, news, olds, output, session }) => Effect9.gen(function* () {
|
|
1136
|
+
const declared = yield* declaredPinProblems(fqn);
|
|
1137
|
+
const adopt = yield* adoptsAtApply({ fqn, instanceId, output });
|
|
1138
|
+
return yield* lift(async () => {
|
|
1139
|
+
claim(fqn, news);
|
|
1140
|
+
return reconcileBinary(runner, fetch, news, {
|
|
1141
|
+
adopt,
|
|
1142
|
+
declared,
|
|
1143
|
+
note: (message) => Effect9.runPromise(session.note(message)),
|
|
1144
|
+
olds,
|
|
1145
|
+
output
|
|
1146
|
+
});
|
|
1147
|
+
});
|
|
1148
|
+
}),
|
|
1149
|
+
delete: ({ fqn, output, session }) => lift(async () => {
|
|
1150
|
+
const holder = claims.get(output.path);
|
|
1151
|
+
if (holder !== undefined && holder !== fqn) {
|
|
1152
|
+
await Effect9.runPromise(session.note(`${output.path} was installed by ${holder} in this deploy; kept`));
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
await deleteBinary(runner, output);
|
|
1156
|
+
if (holder === fqn)
|
|
1157
|
+
claims.delete(output.path);
|
|
1158
|
+
})
|
|
1159
|
+
});
|
|
1160
|
+
}));
|
|
1161
|
+
var ReleaseBinaryProvider = () => makeReleaseBinaryProvider();
|
|
1162
|
+
// src/release/catalog.ts
|
|
1163
|
+
var quoted = (names) => [...names].map((n) => `"${n}"`).join(", ");
|
|
1164
|
+
var has = (record, key) => Object.hasOwn(record, key);
|
|
1165
|
+
var memberDigest = (archive, member) => {
|
|
1166
|
+
const vendor = has(archive.members, member) ? archive.members[member] : undefined;
|
|
1167
|
+
const computed = archive.computed !== undefined && has(archive.computed, member) ? archive.computed[member]?.sha256 : undefined;
|
|
1168
|
+
if (vendor !== undefined && computed !== undefined)
|
|
1169
|
+
return "both";
|
|
1170
|
+
return vendor ?? computed;
|
|
1171
|
+
};
|
|
1172
|
+
var catalogProblems = (catalog, request) => {
|
|
1173
|
+
const entry = has(catalog.packages, request.package) ? catalog.packages[request.package] : undefined;
|
|
1174
|
+
if (entry === undefined) {
|
|
1175
|
+
return [
|
|
1176
|
+
`${catalog.vendor} package "${request.package}" is not in the catalog (${quoted(Object.keys(catalog.packages))})`
|
|
1177
|
+
];
|
|
1178
|
+
}
|
|
1179
|
+
const found = [];
|
|
1180
|
+
if (!has(entry.binaries, request.binary)) {
|
|
1181
|
+
const member = Object.values(entry.binaries).includes(request.binary);
|
|
1182
|
+
const hint = member ? " (that is the archive member; name the installed binary)" : "";
|
|
1183
|
+
found.push(`${request.package} has no binary "${request.binary}"${hint}; it has ${quoted(Object.keys(entry.binaries))}`);
|
|
1184
|
+
}
|
|
1185
|
+
const platforms = has(entry.versions, request.version) ? entry.versions[request.version] : undefined;
|
|
1186
|
+
if (platforms === undefined) {
|
|
1187
|
+
found.push(`${request.package} ${request.version} is not in the catalog (it pins ${quoted(Object.keys(entry.versions))}); ` + "a new version is a reviewed change that pins its digests, never a download of whatever is there");
|
|
1188
|
+
return found;
|
|
1189
|
+
}
|
|
1190
|
+
const archive = has(platforms, request.platform) ? platforms[request.platform] : undefined;
|
|
1191
|
+
if (archive === undefined) {
|
|
1192
|
+
found.push(`${request.package} ${request.version} is not pinned for platform "${request.platform}" (${quoted(Object.keys(platforms))})`);
|
|
1193
|
+
} else if (found.length === 0) {
|
|
1194
|
+
const member = entry.binaries[request.binary] ?? "";
|
|
1195
|
+
const digest = memberDigest(archive, member);
|
|
1196
|
+
if (digest === "both") {
|
|
1197
|
+
found.push(`${request.package} ${request.version}'s member "${member}" is pinned twice — once as a ` + "vendor digest and once as a computed one; a member has exactly one owner");
|
|
1198
|
+
} else if (digest === undefined) {
|
|
1199
|
+
found.push(`${request.package} ${request.version} pins no digest for member "${member}" (checked ` + "the vendor checksum file and any computed record)");
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return found;
|
|
1203
|
+
};
|
|
1204
|
+
var catalogBinary = (catalog, request) => {
|
|
1205
|
+
const problems = catalogProblems(catalog, request);
|
|
1206
|
+
const own = (record, key) => record !== undefined && has(record, key) ? record[key] : undefined;
|
|
1207
|
+
const entry = own(catalog.packages, request.package);
|
|
1208
|
+
const archive = own(own(entry?.versions, request.version), request.platform);
|
|
1209
|
+
const member = own(entry?.binaries, request.binary);
|
|
1210
|
+
const digest = member === undefined || archive === undefined ? undefined : memberDigest(archive, member);
|
|
1211
|
+
const sha256 = digest === "both" ? undefined : digest;
|
|
1212
|
+
if (problems.length > 0 || archive === undefined || member === undefined || sha256 === undefined) {
|
|
1213
|
+
throw new BinaryRefused({
|
|
1214
|
+
message: `Release.Binary ${request.binary}: ${problems.join("; ")}. Nothing was fetched.`
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
const { asset, repo, sha256: archiveSha256, size, tag } = archive;
|
|
1218
|
+
return {
|
|
1219
|
+
archive: { asset, repo, sha256: archiveSha256, size, tag },
|
|
1220
|
+
member,
|
|
1221
|
+
name: request.binary,
|
|
1222
|
+
sha256
|
|
1223
|
+
};
|
|
1224
|
+
};
|
|
1225
|
+
var catalogDirectory = (root, request) => `${root}/${request.package}-${request.version}`;
|
|
1226
|
+
var identifyBinary = (catalogs, sha256) => {
|
|
1227
|
+
for (const catalog of catalogs) {
|
|
1228
|
+
for (const [pkg, entry] of Object.entries(catalog.packages)) {
|
|
1229
|
+
for (const [version, platforms] of Object.entries(entry.versions)) {
|
|
1230
|
+
for (const [platform, archive] of Object.entries(platforms)) {
|
|
1231
|
+
for (const [binary, member] of Object.entries(entry.binaries)) {
|
|
1232
|
+
const digest = memberDigest(archive, member);
|
|
1233
|
+
if (digest !== "both" && digest === sha256) {
|
|
1234
|
+
return { binary, package: pkg, platform, vendor: catalog.vendor, version };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return;
|
|
1242
|
+
};
|
|
1243
|
+
// src/release/openbao.ts
|
|
1244
|
+
var RECORDED = "2026-09-23";
|
|
1245
|
+
var REPO2 = "openbao/openbao";
|
|
1246
|
+
var TAG = "v2.6.2";
|
|
1247
|
+
var DOWNLOAD = `https://github.com/${REPO2}/releases/download/${TAG}`;
|
|
1248
|
+
var OPENBAO_RELEASES = {
|
|
1249
|
+
packages: {
|
|
1250
|
+
openbao: {
|
|
1251
|
+
binaries: { bao: "bao" },
|
|
1252
|
+
versions: {
|
|
1253
|
+
"2.6.2": {
|
|
1254
|
+
darwin_arm64: {
|
|
1255
|
+
asset: "openbao_2.6.2_darwin_arm64.tar.gz",
|
|
1256
|
+
checksums: {
|
|
1257
|
+
recorded: RECORDED,
|
|
1258
|
+
sha256: "3d4a19fdc54a86fd94ce59ea91316dbf4304180bb27ae818e78e85c7f2164645",
|
|
1259
|
+
signature: {
|
|
1260
|
+
kind: "openpgp",
|
|
1261
|
+
key: {
|
|
1262
|
+
fingerprint: "66D15FDD87287219C8E15478D200CD702853E6D0",
|
|
1263
|
+
sha256: "1862a196422947124282e026ea4e09d6f7c5d383b628d90c17906936e6e5a8e0",
|
|
1264
|
+
url: "https://openbao.org/assets/openbao-gpg-pub-20240618.asc"
|
|
1265
|
+
},
|
|
1266
|
+
sha256: "40f294d642a2b4e9adac766ca6b8b2633e15b3dbfdf63969dedbf46d94df53e8",
|
|
1267
|
+
url: `${DOWNLOAD}/checksums.txt.gpgsig`,
|
|
1268
|
+
verified: RECORDED
|
|
1269
|
+
},
|
|
1270
|
+
url: `${DOWNLOAD}/checksums.txt`
|
|
1271
|
+
},
|
|
1272
|
+
computed: {
|
|
1273
|
+
bao: {
|
|
1274
|
+
recorded: RECORDED,
|
|
1275
|
+
sha256: "d476d17e81a35e6d70dd7e86a8ab2a3664313525118f1f1cfe0130e7a2b95f3a"
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
members: {},
|
|
1279
|
+
repo: REPO2,
|
|
1280
|
+
sha256: "4e495376174accc0e014d31e9901f518a974f966850c839f626347eaac05fd52",
|
|
1281
|
+
size: 76833570,
|
|
1282
|
+
tag: TAG
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
vendor: "OpenBao"
|
|
1289
|
+
};
|
|
1290
|
+
// src/release/providers.ts
|
|
1291
|
+
import * as Layer2 from "effect/Layer";
|
|
1292
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
1293
|
+
var releaseProviders = (runner) => ReleaseBinaryProvider().pipe(Layer2.provide(Layer2.mergeAll(hostRunnerLayer(runner), FetchHttpClient.layer)));
|
|
1294
|
+
// src/release/victoria.ts
|
|
1295
|
+
var RECORDED2 = "2026-09-22";
|
|
1296
|
+
var DOWNLOAD2 = "https://github.com/VictoriaMetrics";
|
|
1297
|
+
var VICTORIA_RELEASES = {
|
|
1298
|
+
packages: {
|
|
1299
|
+
"victoria-logs": {
|
|
1300
|
+
binaries: { "victoria-logs": "victoria-logs-prod" },
|
|
1301
|
+
versions: {
|
|
1302
|
+
"1.52.0": {
|
|
1303
|
+
"darwin-arm64": {
|
|
1304
|
+
asset: "victoria-logs-darwin-arm64-v1.52.0.tar.gz",
|
|
1305
|
+
checksums: {
|
|
1306
|
+
recorded: RECORDED2,
|
|
1307
|
+
sha256: "d4ca686c3cceec78a0f4e733ef9ab3f857549131ea48cd6f89f701cda406ac4b",
|
|
1308
|
+
url: `${DOWNLOAD2}/VictoriaLogs/releases/download/v1.52.0/victoria-logs-darwin-arm64-v1.52.0_checksums.txt`
|
|
1309
|
+
},
|
|
1310
|
+
members: {
|
|
1311
|
+
"victoria-logs-prod": "9e48809e314902782959b3a4e683a3087476759d0293791a0564921c676ca7be"
|
|
1312
|
+
},
|
|
1313
|
+
repo: "VictoriaMetrics/VictoriaLogs",
|
|
1314
|
+
sha256: "3157d4b6181d8a7e3e30918e2cbfcd4cc4cb66263e3ef21ea91e4f20f8980883",
|
|
1315
|
+
size: 10943697,
|
|
1316
|
+
tag: "v1.52.0"
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
"victoria-metrics": {
|
|
1322
|
+
binaries: { "victoria-metrics": "victoria-metrics-prod" },
|
|
1323
|
+
versions: {
|
|
1324
|
+
"1.151.0": {
|
|
1325
|
+
"darwin-arm64": {
|
|
1326
|
+
asset: "victoria-metrics-darwin-arm64-v1.151.0.tar.gz",
|
|
1327
|
+
checksums: {
|
|
1328
|
+
recorded: RECORDED2,
|
|
1329
|
+
sha256: "735745ee0e7b2d5b8118883f2d0ed124c2a12d70261c16380128f5e362566d1e",
|
|
1330
|
+
url: `${DOWNLOAD2}/VictoriaMetrics/releases/download/v1.151.0/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt`
|
|
1331
|
+
},
|
|
1332
|
+
members: {
|
|
1333
|
+
"victoria-metrics-prod": "3c493a95ac1e9034a3261b230c4d62f3b186a04aafcdbacc99bfc110dc5de6a1"
|
|
1334
|
+
},
|
|
1335
|
+
repo: "VictoriaMetrics/VictoriaMetrics",
|
|
1336
|
+
sha256: "8792437c4c0b63719fed7b44ad18d4b4167bbe6861ab3e84c7bb010c922e7582",
|
|
1337
|
+
size: 12739105,
|
|
1338
|
+
tag: "v1.151.0"
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
"victoria-traces": {
|
|
1344
|
+
binaries: { "victoria-traces": "victoria-traces-prod" },
|
|
1345
|
+
versions: {
|
|
1346
|
+
"0.10.0": {
|
|
1347
|
+
"darwin-arm64": {
|
|
1348
|
+
asset: "victoria-traces-darwin-arm64-v0.10.0.tar.gz",
|
|
1349
|
+
checksums: {
|
|
1350
|
+
recorded: RECORDED2,
|
|
1351
|
+
sha256: "cd32bfc21900b48ba543124e43827a1d13d735e45d50dbfd742c0ca6640e1aae",
|
|
1352
|
+
url: `${DOWNLOAD2}/VictoriaTraces/releases/download/v0.10.0/victoria-traces-darwin-arm64-v0.10.0_checksums.txt`
|
|
1353
|
+
},
|
|
1354
|
+
members: {
|
|
1355
|
+
"victoria-traces-prod": "4759ec89a466129b9eefc22244a111e1f489739340747805fd93b58fd0f7b2dd"
|
|
1356
|
+
},
|
|
1357
|
+
repo: "VictoriaMetrics/VictoriaTraces",
|
|
1358
|
+
sha256: "cbe83f1d409cbb85fbf1c890c4a9b7fd34c1d74acec4dce2e0ebe46fcb260e38",
|
|
1359
|
+
size: 11126077,
|
|
1360
|
+
tag: "v0.10.0"
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
vmutils: {
|
|
1366
|
+
binaries: {
|
|
1367
|
+
vmagent: "vmagent-prod",
|
|
1368
|
+
vmalert: "vmalert-prod",
|
|
1369
|
+
"vmalert-tool": "vmalert-tool-prod",
|
|
1370
|
+
vmauth: "vmauth-prod",
|
|
1371
|
+
vmbackup: "vmbackup-prod",
|
|
1372
|
+
vmctl: "vmctl-prod",
|
|
1373
|
+
vmrestore: "vmrestore-prod"
|
|
1374
|
+
},
|
|
1375
|
+
versions: {
|
|
1376
|
+
"1.151.0": {
|
|
1377
|
+
"darwin-arm64": {
|
|
1378
|
+
asset: "vmutils-darwin-arm64-v1.151.0.tar.gz",
|
|
1379
|
+
checksums: {
|
|
1380
|
+
recorded: RECORDED2,
|
|
1381
|
+
sha256: "fda5a64fce9022bee7614c3fda27271c0826f1d7b90f8b162e1f48c77c0df04b",
|
|
1382
|
+
url: `${DOWNLOAD2}/VictoriaMetrics/releases/download/v1.151.0/vmutils-darwin-arm64-v1.151.0_checksums.txt`
|
|
1383
|
+
},
|
|
1384
|
+
members: {
|
|
1385
|
+
"vmagent-prod": "d3b6fa1f71ef48beac619f15f8adfa7610e76605caf37db1ebfbb379b6692329",
|
|
1386
|
+
"vmalert-prod": "62845795167e9ff47890e83ac4ac5934693c105767db88591c95a08f3c4fb354",
|
|
1387
|
+
"vmalert-tool-prod": "cbffb9495e4bb1457c9d45e0a7064b3a3a29b2cfd1ae24bfe8f00ec327c26998",
|
|
1388
|
+
"vmauth-prod": "398df9e0f5b7035d037e39ee9ff66a10ddde5333d832602463aa4d2865fdb508",
|
|
1389
|
+
"vmbackup-prod": "1974a2ee211b49bf581287808e010846f6c47ff135276214c6da628567777b9e",
|
|
1390
|
+
"vmctl-prod": "998b111eee206b98e6d25fdf2fbaa455ecc17c7f9e7c3b2373e127cef762459f",
|
|
1391
|
+
"vmrestore-prod": "fc824df7215b01b57921ed46afde7fea6b6698adf13d9ea8e3fe36e194ef8190"
|
|
1392
|
+
},
|
|
1393
|
+
repo: "VictoriaMetrics/VictoriaMetrics",
|
|
1394
|
+
sha256: "27d68bac90e28929214091ed9d27f2e9fef25a080407e2c8661ea9b52dd6b183",
|
|
1395
|
+
size: 123584800,
|
|
1396
|
+
tag: "v1.151.0"
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
vendor: "VictoriaMetrics"
|
|
1403
|
+
};
|
|
1404
|
+
export {
|
|
1405
|
+
ArchiveRefused,
|
|
1406
|
+
BinaryRefused,
|
|
1407
|
+
ChecksumMismatch,
|
|
1408
|
+
DownloadFailed,
|
|
1409
|
+
HostRunnerService,
|
|
1410
|
+
OPENBAO_RELEASES,
|
|
1411
|
+
ReleaseBinary,
|
|
1412
|
+
ReleaseBinaryProvider,
|
|
1413
|
+
VICTORIA_RELEASES,
|
|
1414
|
+
catalogBinary,
|
|
1415
|
+
catalogDirectory,
|
|
1416
|
+
catalogProblems,
|
|
1417
|
+
hostRunnerLayer,
|
|
1418
|
+
identifyBinary,
|
|
1419
|
+
releaseBinaryPath,
|
|
1420
|
+
releaseProviders,
|
|
1421
|
+
releaseUrl
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
//# debugId=95AC8419C10B267864756E2164756E21
|
|
1425
|
+
//# sourceMappingURL=index.js.map
|