@homeflare/alchemy 0.19.1 → 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.
Files changed (198) hide show
  1. package/README.md +25 -0
  2. package/dist/caddy/access-forward-auth.d.ts +56 -0
  3. package/dist/caddy/access-forward-auth.d.ts.map +1 -0
  4. package/dist/caddy/index.d.ts +6 -0
  5. package/dist/caddy/index.d.ts.map +1 -1
  6. package/dist/caddy/index.js +169 -75
  7. package/dist/caddy/index.js.map +10 -7
  8. package/dist/cloudflare/mcp-server-api.d.ts +135 -0
  9. package/dist/cloudflare/mcp-server-api.d.ts.map +1 -0
  10. package/dist/cloudflare/mcp-server-form.d.ts +116 -0
  11. package/dist/cloudflare/mcp-server-form.d.ts.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/launchd/file-converge.d.ts +70 -0
  15. package/dist/launchd/file-converge.d.ts.map +1 -0
  16. package/dist/launchd/file-identity.d.ts +32 -0
  17. package/dist/launchd/file-identity.d.ts.map +1 -0
  18. package/dist/launchd/host-file-form.d.ts +2 -0
  19. package/dist/launchd/host-file-form.d.ts.map +1 -1
  20. package/dist/launchd/host-file-lifecycle.d.ts +11 -24
  21. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  22. package/dist/launchd/host-file.d.ts.map +1 -1
  23. package/dist/launchd/index.js +173 -120
  24. package/dist/launchd/index.js.map +12 -10
  25. package/dist/launchd/local-runner.d.ts.map +1 -1
  26. package/dist/launchd/runner.d.ts +17 -2
  27. package/dist/launchd/runner.d.ts.map +1 -1
  28. package/dist/launchd/sudo-runner.d.ts +11 -0
  29. package/dist/launchd/sudo-runner.d.ts.map +1 -1
  30. package/dist/linux/index.js +37 -11
  31. package/dist/linux/index.js.map +10 -10
  32. package/dist/linux/region.d.ts +11 -0
  33. package/dist/linux/region.d.ts.map +1 -1
  34. package/dist/linux/remote-file-lifecycle.d.ts.map +1 -1
  35. package/dist/linux/remote-file-plan.d.ts +22 -0
  36. package/dist/linux/remote-file-plan.d.ts.map +1 -1
  37. package/dist/linux/remote-file.d.ts +4 -1
  38. package/dist/linux/remote-file.d.ts.map +1 -1
  39. package/dist/linux/sudo-listing.d.ts +47 -0
  40. package/dist/linux/sudo-listing.d.ts.map +1 -0
  41. package/dist/linux/unit-handlers.d.ts.map +1 -1
  42. package/dist/linux/unit-preflight.d.ts +10 -1
  43. package/dist/linux/unit-preflight.d.ts.map +1 -1
  44. package/dist/proxmox/credential-literals.d.ts +1 -1
  45. package/dist/proxmox/credential-literals.d.ts.map +1 -1
  46. package/dist/proxmox/generated/pbs/nodes-node.d.ts +1 -1
  47. package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -1
  48. package/dist/proxmox/generated/pbs.d.ts +7 -0
  49. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  50. package/dist/proxmox/generated/pve.d.ts +7 -0
  51. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  52. package/dist/proxmox/index.d.ts +16 -4
  53. package/dist/proxmox/index.d.ts.map +1 -1
  54. package/dist/proxmox/index.js +5 -2
  55. package/dist/proxmox/index.js.map +9 -9
  56. package/dist/proxmox/pbs-notification-target-form.d.ts +1 -1
  57. package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -1
  58. package/dist/proxmox/pbs-notification-target.d.ts +1 -1
  59. package/dist/proxmox/pbs-notification-target.d.ts.map +1 -1
  60. package/dist/release/archive.d.ts +11 -0
  61. package/dist/release/archive.d.ts.map +1 -0
  62. package/dist/release/binary-claim.d.ts +53 -0
  63. package/dist/release/binary-claim.d.ts.map +1 -0
  64. package/dist/release/binary-diff.d.ts +38 -0
  65. package/dist/release/binary-diff.d.ts.map +1 -0
  66. package/dist/release/binary-form.d.ts +118 -0
  67. package/dist/release/binary-form.d.ts.map +1 -0
  68. package/dist/release/binary-lifecycle.d.ts +55 -0
  69. package/dist/release/binary-lifecycle.d.ts.map +1 -0
  70. package/dist/release/binary-read.d.ts +11 -0
  71. package/dist/release/binary-read.d.ts.map +1 -0
  72. package/dist/release/binary.d.ts +68 -0
  73. package/dist/release/binary.d.ts.map +1 -0
  74. package/dist/release/catalog.d.ts +76 -0
  75. package/dist/release/catalog.d.ts.map +1 -0
  76. package/dist/release/checksums.d.ts +22 -0
  77. package/dist/release/checksums.d.ts.map +1 -0
  78. package/dist/release/declared-pins.d.ts +4 -0
  79. package/dist/release/declared-pins.d.ts.map +1 -0
  80. package/dist/release/download.d.ts +44 -0
  81. package/dist/release/download.d.ts.map +1 -0
  82. package/dist/release/fake-deploy.d.ts +48 -0
  83. package/dist/release/fake-deploy.d.ts.map +1 -0
  84. package/dist/release/fake-engine.d.ts +8 -0
  85. package/dist/release/fake-engine.d.ts.map +1 -0
  86. package/dist/release/fake-install.d.ts +39 -0
  87. package/dist/release/fake-install.d.ts.map +1 -0
  88. package/dist/release/fake-provider.d.ts +58 -0
  89. package/dist/release/fake-provider.d.ts.map +1 -0
  90. package/dist/release/fake-release.d.ts +95 -0
  91. package/dist/release/fake-release.d.ts.map +1 -0
  92. package/dist/release/index.d.ts +24 -0
  93. package/dist/release/index.d.ts.map +1 -0
  94. package/dist/release/index.js +1365 -0
  95. package/dist/release/index.js.map +35 -0
  96. package/dist/release/providers.d.ts +19 -0
  97. package/dist/release/providers.d.ts.map +1 -0
  98. package/dist/release/refused.d.ts +39 -0
  99. package/dist/release/refused.d.ts.map +1 -0
  100. package/dist/release/tar.d.ts +14 -0
  101. package/dist/release/tar.d.ts.map +1 -0
  102. package/dist/release/victoria.d.ts +36 -0
  103. package/dist/release/victoria.d.ts.map +1 -0
  104. package/dist/{proxmox → secrets}/write-only.d.ts.map +1 -1
  105. package/docs/adopted-deploys.md +13 -0
  106. package/docs/linux-host.md +5 -1
  107. package/docs/provider-standard.md +138 -0
  108. package/docs/release-binary-catalogs.md +113 -0
  109. package/docs/release-binary-measured.md +163 -0
  110. package/docs/release-binary-refusals.md +57 -0
  111. package/docs/release-binary-upstream.md +163 -0
  112. package/docs/release-binary.md +154 -0
  113. package/docs/upstream-conformance.md +164 -0
  114. package/package.json +5 -1
  115. package/src/caddy/access-forward-auth.test.ts +63 -0
  116. package/src/caddy/access-forward-auth.ts +120 -0
  117. package/src/caddy/index.ts +10 -0
  118. package/src/caddy/providers.test.ts +4 -0
  119. package/src/cloudflare/mcp-server-api.ts +294 -0
  120. package/src/cloudflare/mcp-server-form.ts +219 -0
  121. package/src/index.ts +1 -0
  122. package/src/launchd/file-converge.ts +233 -0
  123. package/src/launchd/file-identity.ts +69 -0
  124. package/src/launchd/host-file-alias.test.ts +65 -0
  125. package/src/launchd/host-file-form.ts +2 -1
  126. package/src/launchd/host-file-lifecycle.ts +25 -144
  127. package/src/launchd/host-file.ts +8 -2
  128. package/src/launchd/local-runner.test.ts +12 -1
  129. package/src/launchd/local-runner.ts +2 -0
  130. package/src/launchd/runner.ts +17 -2
  131. package/src/launchd/sudo-runner.ts +11 -0
  132. package/src/linux/region.ts +15 -0
  133. package/src/linux/remote-file-lifecycle.test.ts +0 -88
  134. package/src/linux/remote-file-lifecycle.ts +25 -7
  135. package/src/linux/remote-file-plan.ts +37 -1
  136. package/src/linux/remote-file-region.test.ts +181 -0
  137. package/src/linux/remote-file.ts +4 -1
  138. package/src/linux/sudo-listing.ts +122 -0
  139. package/src/linux/unit-handlers.ts +12 -5
  140. package/src/linux/unit-preflight.ts +20 -4
  141. package/src/linux/unit-rename.test.ts +76 -0
  142. package/src/proxmox/ceph-adopt.test.ts +210 -0
  143. package/src/proxmox/credential-literals.ts +1 -1
  144. package/src/proxmox/generated/pbs/nodes-node.ts +1 -1
  145. package/src/proxmox/generated/pbs.ts +7 -0
  146. package/src/proxmox/generated/pve.ts +7 -0
  147. package/src/proxmox/index.test.ts +7 -4
  148. package/src/proxmox/index.ts +16 -4
  149. package/src/proxmox/pbs-notification-target-form.ts +1 -1
  150. package/src/proxmox/pbs-notification-target-lifecycle.ts +1 -1
  151. package/src/proxmox/pbs-notification-target-wire.ts +1 -1
  152. package/src/proxmox/pbs-notification-target.ts +1 -1
  153. package/src/release/adopt-parity.test.ts +106 -0
  154. package/src/release/apply-pins.test.ts +128 -0
  155. package/src/release/archive.ts +94 -0
  156. package/src/release/binary-alias.test.ts +109 -0
  157. package/src/release/binary-claim.ts +80 -0
  158. package/src/release/binary-claims.test.ts +47 -0
  159. package/src/release/binary-diff.ts +73 -0
  160. package/src/release/binary-form.ts +229 -0
  161. package/src/release/binary-lifecycle.test.ts +90 -0
  162. package/src/release/binary-lifecycle.ts +202 -0
  163. package/src/release/binary-read.ts +55 -0
  164. package/src/release/binary-refusals.test.ts +192 -0
  165. package/src/release/binary-sudo.test.ts +93 -0
  166. package/src/release/binary.ts +181 -0
  167. package/src/release/catalog.test.ts +96 -0
  168. package/src/release/catalog.ts +175 -0
  169. package/src/release/checksums.test.ts +35 -0
  170. package/src/release/checksums.ts +43 -0
  171. package/src/release/declared-pins.ts +39 -0
  172. package/src/release/download.test.ts +138 -0
  173. package/src/release/download.ts +110 -0
  174. package/src/release/error-channel.test.ts +69 -0
  175. package/src/release/fake-deploy.ts +87 -0
  176. package/src/release/fake-engine.ts +50 -0
  177. package/src/release/fake-install.ts +59 -0
  178. package/src/release/fake-provider.ts +68 -0
  179. package/src/release/fake-release.ts +185 -0
  180. package/src/release/fixtures/victoria/darwin-arm64-assets.txt +22 -0
  181. package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +2 -0
  182. package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +2 -0
  183. package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +2 -0
  184. package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +8 -0
  185. package/src/release/index.ts +41 -0
  186. package/src/release/plan.test.ts +189 -0
  187. package/src/release/provider-refusals.test.ts +48 -0
  188. package/src/release/providers.test.ts +174 -0
  189. package/src/release/providers.ts +24 -0
  190. package/src/release/recovery-read.test.ts +44 -0
  191. package/src/release/refused.ts +40 -0
  192. package/src/release/tar.test.ts +142 -0
  193. package/src/release/tar.ts +195 -0
  194. package/src/release/victoria.test.ts +108 -0
  195. package/src/release/victoria.ts +154 -0
  196. package/src/{proxmox → secrets}/write-only.ts +6 -0
  197. /package/dist/{proxmox → secrets}/write-only.d.ts +0 -0
  198. /package/src/{proxmox → secrets}/write-only.test.ts +0 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Release.Binary and HostDirectory deployed through Alchemy's OWN Plan and Apply
3
+ * (openbao/fake-stack.ts, in-memory state), over a fake host (linux/fake-linux-host.ts) and a fake
4
+ * server holding the synthetic vmutils archive at its real URL. `events` is every host change and
5
+ * request, in order; `serving = false` answers every request 404.
6
+ *
7
+ * ⛔ TEST-ONLY, like fake-release.ts: no provider imports it and it is not on the barrel.
8
+ */
9
+ import { renamedFrom } from 'alchemy/Rename';
10
+ import * as Effect from 'effect/Effect';
11
+ import * as Layer from 'effect/Layer';
12
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
13
+ import * as HttpClientResponse from 'effect/unstable/http/HttpClientResponse';
14
+ import { type HostRunner, hostRunnerLayer } from '../launchd/runner.ts';
15
+ import { HostDirectory, HostDirectoryProvider } from '../linux/directory.ts';
16
+ import { fakeLinuxHost } from '../linux/fake-linux-host.ts';
17
+ import { fakeStack } from '../openbao/fake-stack.ts';
18
+ import type { ReleaseBinaryProps } from './binary-form.ts';
19
+ import { ReleaseBinary, makeReleaseBinaryProvider } from './binary.ts';
20
+ import { catalogBinary } from './catalog.ts';
21
+ import { VMALERT_REQUEST, VMUTILS_URL, bytesOf, syntheticRelease } from './fake-release.ts';
22
+
23
+ export const ROOT = '/opt/example/bin';
24
+ export const DIR = `${ROOT}/vmutils-1.151.0`;
25
+ export const PATH = `${DIR}/vmalert`;
26
+ export const THEIRS = bytesOf('#!someone else built this vmalert\n');
27
+
28
+ /** One fake host (with DIR already there when `dirExists`), one fake server, one stack. */
29
+ export const deployHarness = (dirExists: boolean) => {
30
+ const release = syntheticRelease();
31
+ const pins = catalogBinary(release.catalog, VMALERT_REQUEST);
32
+ const fake = fakeLinuxHost({
33
+ dirs: dirExists ? { [DIR]: 0, [ROOT]: 0 } : { [ROOT]: 0 },
34
+ euid: 0,
35
+ });
36
+ const events: string[] = [];
37
+ const server = { serving: true };
38
+ const base = fake.runner;
39
+ const runner: HostRunner = {
40
+ ...base,
41
+ exec: (argv) => {
42
+ events.push(argv.filter((arg) => arg !== '--').join(' '));
43
+ return base.exec(argv);
44
+ },
45
+ removeFile: (path) => {
46
+ events.push(`remove ${path}`);
47
+ return base.removeFile(path);
48
+ },
49
+ writeFileAtomic: (path, bytes, options) => {
50
+ events.push(`write ${path}`);
51
+ return base.writeFileAtomic(path, bytes, options);
52
+ },
53
+ };
54
+ const client = HttpClient.make((request, url) => {
55
+ events.push(`GET ${url.toString()}`);
56
+ const found = server.serving && url.toString() === VMUTILS_URL;
57
+ const body = found ? new Blob([release.vmutils.slice()]) : null;
58
+ return Effect.succeed(
59
+ HttpClientResponse.fromWeb(request, new Response(body, { status: found ? 200 : 404 })),
60
+ );
61
+ });
62
+ const providers = Layer.mergeAll(makeReleaseBinaryProvider(), HostDirectoryProvider()).pipe(
63
+ Layer.provide(
64
+ Layer.mergeAll(hostRunnerLayer(runner), Layer.succeed(HttpClient.HttpClient, client)),
65
+ ),
66
+ );
67
+ const place = (bytes: Uint8Array, mode = 0o755, kind: 'file' | 'symlink' = 'file') =>
68
+ fake.files.set(PATH, { bytes, gid: 0, kind, mode, uid: 0 });
69
+ /** The binary declared with the directory as a literal, so Alchemy probes it. */
70
+ const literal = ReleaseBinary('vmalert', { ...pins, directory: DIR });
71
+ /** The documented shape: the directory is HostDirectory's `path`, an Output at a first plan. */
72
+ const wired = (
73
+ id = 'vmalert',
74
+ dirMode = 0o755,
75
+ formerId?: string,
76
+ more: Partial<ReleaseBinaryProps> = {},
77
+ ) =>
78
+ Effect.gen(function* () {
79
+ const dir = yield* HostDirectory('dir', { mode: dirMode, path: DIR });
80
+ const binary = ReleaseBinary(id, { ...pins, ...more, directory: dir.path });
81
+ yield* formerId === undefined ? binary : binary.pipe(renamedFrom(formerId));
82
+ });
83
+ /** The directory alone: the binary's declaration removed. */
84
+ const dirOnly = HostDirectory('dir', { mode: 0o755, path: DIR });
85
+ const stack = fakeStack(providers, {}, 'ReleaseStack');
86
+ return { dirOnly, events, fake, literal, place, server, stack, wired };
87
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Release.Binary through Alchemy's own Plan and Apply (openbao/fake-stack.ts) over the REAL
3
+ * localRunner, for tests that need what a fake host cannot model: two spellings of one file, and
4
+ * the engine's order across resources. The HTTP server is a fake serving the synthetic vmutils.
5
+ *
6
+ * ⛔ TEST-ONLY, like fake-release.ts: no provider imports it and it is not on the barrel. Every
7
+ * directory a test passes must be under its own `mkdtemp`; nothing here chooses a path.
8
+ */
9
+ import * as Effect from 'effect/Effect';
10
+ import * as Layer from 'effect/Layer';
11
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
12
+ import * as HttpClientResponse from 'effect/unstable/http/HttpClientResponse';
13
+ import { localRunner } from '../launchd/local-runner.ts';
14
+ import { hostRunnerLayer } from '../launchd/runner.ts';
15
+ import { fakeStack } from '../openbao/fake-stack.ts';
16
+ import { ReleaseBinary, makeReleaseBinaryProvider } from './binary.ts';
17
+ import { catalogBinary } from './catalog.ts';
18
+ import { BINARY, VMALERT_REQUEST, VMUTILS_URL, syntheticRelease } from './fake-release.ts';
19
+
20
+ /** vmalert's bytes as text, to compare with what a test reads off the disk. */
21
+ export const VMALERT_TEXT = new TextDecoder().decode(BINARY.vmalert);
22
+
23
+ /** One stack over localRunner; `deploy` takes logical id → directory; `gets` counts downloads. */
24
+ export const realEngine = () => {
25
+ const { catalog, vmutils } = syntheticRelease();
26
+ const gets: string[] = [];
27
+ const client = HttpClient.make((request, url) => {
28
+ gets.push(url.toString());
29
+ const body = url.toString() === VMUTILS_URL ? new Blob([vmutils.slice()]) : null;
30
+ return Effect.succeed(
31
+ HttpClientResponse.fromWeb(request, new Response(body, { status: body ? 200 : 404 })),
32
+ );
33
+ });
34
+ const providers = makeReleaseBinaryProvider().pipe(
35
+ Layer.provide(
36
+ Layer.mergeAll(hostRunnerLayer(localRunner()), Layer.succeed(HttpClient.HttpClient, client)),
37
+ ),
38
+ );
39
+ const stack = fakeStack(providers, {}, 'RealEngineStack');
40
+ const pins = catalogBinary(catalog, VMALERT_REQUEST);
41
+ const deploy = (binaries: Readonly<Record<string, string>>) =>
42
+ stack.deploy(
43
+ Effect.gen(function* () {
44
+ for (const [id, directory] of Object.entries(binaries)) {
45
+ yield* ReleaseBinary(id, { ...pins, directory });
46
+ }
47
+ }),
48
+ );
49
+ return { deploy, gets };
50
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The install lifecycle's test bench: a fake host, a synthetic release served at the REAL URLs next
3
+ * to an `-enterprise` decoy, and `refused()`, which asserts a refusal left the host exactly as it
4
+ * was and wrote nothing.
5
+ *
6
+ * ⛔ TEST-ONLY, like fake-release.ts: no provider imports it and it is not on the barrel.
7
+ */
8
+ import { expect } from 'bun:test';
9
+ import type { ReleaseBinaryProps } from './binary-form.ts';
10
+ import { reconcileBinary } from './binary-lifecycle.ts';
11
+ import {
12
+ ENTERPRISE_URL,
13
+ TRACES_URL,
14
+ VMUTILS_DIR,
15
+ VMUTILS_ENTRIES,
16
+ VMUTILS_URL,
17
+ bytesOf,
18
+ fakeTransport,
19
+ gzip,
20
+ pathsOn,
21
+ releaseHost,
22
+ syntheticRelease,
23
+ tarOf,
24
+ vmalertProps,
25
+ } from './fake-release.ts';
26
+
27
+ export const PATH = `${VMUTILS_DIR}/vmalert`;
28
+ export const writes = (fake: ReturnType<typeof releaseHost>) =>
29
+ fake.calls.filter((c) => c[0] === 'write');
30
+
31
+ /** A host, a synthetic release served at the real URLs (plus an enterprise decoy), and a runner. */
32
+ export const setup = (entries = VMUTILS_ENTRIES) => {
33
+ const { catalog, traces, vmutils } = syntheticRelease(entries);
34
+ const decoy = gzip(tarOf([{ bytes: bytesOf('#!enterprise\n'), name: 'vmalert-prod' }]));
35
+ const transport = fakeTransport({
36
+ [ENTERPRISE_URL]: decoy,
37
+ [TRACES_URL]: traces,
38
+ [VMUTILS_URL]: vmutils,
39
+ });
40
+ const fake = releaseHost();
41
+ const props = (more: Partial<ReleaseBinaryProps> = {}) => vmalertProps(catalog, more);
42
+ const install = (declared = props(), options = {}) =>
43
+ reconcileBinary(fake.runner, transport.fetch, declared, options);
44
+ return { fake, install, props, transport };
45
+ };
46
+
47
+ /** Run a refusal; return it, having checked the host is exactly as it was and nothing was written. */
48
+ export const refused = async (s: ReturnType<typeof setup>, run: () => Promise<unknown>) => {
49
+ const before = pathsOn(s.fake);
50
+ const error = await run().then(
51
+ () => {
52
+ throw new Error('expected a refusal');
53
+ },
54
+ (cause: unknown) => cause,
55
+ );
56
+ expect(pathsOn(s.fake)).toEqual(before);
57
+ expect(writes(s.fake)).toEqual([]);
58
+ return error as Error;
59
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * One Release.Binary provider over one fake host and one fake HTTP server, for the handler tests
3
+ * beside it: `requests` counts every URL asked, `notes` every `session.note`.
4
+ *
5
+ * ⛔ TEST-ONLY, like fake-release.ts: no provider imports it and it is not on the barrel.
6
+ */
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Layer from 'effect/Layer';
9
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
10
+ import * as HttpClientResponse from 'effect/unstable/http/HttpClientResponse';
11
+ import { hostRunnerLayer } from '../launchd/runner.ts';
12
+ import { ReleaseBinary, makeReleaseBinaryProvider } from './binary.ts';
13
+ import {
14
+ VMUTILS_DIR,
15
+ VMUTILS_URL,
16
+ releaseHost,
17
+ syntheticRelease,
18
+ vmalertProps,
19
+ } from './fake-release.ts';
20
+
21
+ export const ids = { fqn: 'stack/vmalert', id: 'vmalert', instanceId: 'i-1' };
22
+ export const call = { bindings: [] as never, newBindings: [] as never, oldBindings: [] as never };
23
+ export const PATH = `${VMUTILS_DIR}/vmalert`;
24
+
25
+ export type Service = Effect.Success<typeof ReleaseBinary.Provider>;
26
+
27
+ /** One provider over one fake host and one fake HTTP server; `requests` counts every URL asked. */
28
+ export const harness = () => {
29
+ const release = syntheticRelease();
30
+ const VMALERT = vmalertProps(release.catalog);
31
+ const fake = releaseHost();
32
+ const requests: string[] = [];
33
+ const notes: string[] = [];
34
+ const client = HttpClient.make((request, url) => {
35
+ requests.push(url.toString());
36
+ const body = url.toString() === VMUTILS_URL ? new Blob([release.vmutils.slice()]) : null;
37
+ return Effect.succeed(
38
+ HttpClientResponse.fromWeb(request, new Response(body, { status: body ? 200 : 404 })),
39
+ );
40
+ });
41
+ const layer = makeReleaseBinaryProvider().pipe(
42
+ Layer.provide(
43
+ Layer.mergeAll(hostRunnerLayer(fake.runner), Layer.succeed(HttpClient.HttpClient, client)),
44
+ ),
45
+ );
46
+ const session = { note: (m: string) => Effect.sync(() => void notes.push(m)) } as never;
47
+ const run = <A>(use: (p: Service) => Effect.Effect<A, unknown>) =>
48
+ Effect.runPromise(
49
+ Effect.gen(function* () {
50
+ return yield* use(yield* ReleaseBinary.Provider);
51
+ }).pipe(Effect.provide(layer)) as Effect.Effect<A>,
52
+ );
53
+ const reconcile = (p: Service, news: object, output?: unknown, olds?: object) =>
54
+ p.reconcile({
55
+ ...ids,
56
+ ...call,
57
+ news: news as never,
58
+ olds: olds as never,
59
+ output: output as never,
60
+ session,
61
+ });
62
+ return { VMALERT, fake, notes, reconcile, release, requests, run };
63
+ };
64
+
65
+ export const must = <F>(fn: F | undefined): F => {
66
+ if (fn === undefined) throw new Error('provider has no such handler');
67
+ return fn;
68
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Synthetic release archives for the tests beside it: a tar writer, gzip, a catalog pinned to what
3
+ * they build, a counting transport, and a host with the versioned directories in place.
4
+ *
5
+ * ⛔ TEST-ONLY. No provider imports this file, it is not on the barrel, and nothing here reaches a
6
+ * network or a real filesystem. The catalog is synthetic because a test cannot build bytes that
7
+ * hash to the vendor's pins; victoria.test.ts holds the REAL data set to the vendor's own files.
8
+ * ★ THE ASSET NAMES AND URLS ARE THE REAL ONES, so a test that serves the `-enterprise` sibling
9
+ * next to the plain archive proves the provider asks for exactly one of them.
10
+ */
11
+ import { fakeRunner } from '../launchd/fake-runner.ts';
12
+ import { sha256Hex } from '../launchd/job-form.ts';
13
+ import { type ReleaseBinaryProps, releaseUrl } from './binary-form.ts';
14
+ import { type PinnedArchive, type ReleaseCatalog, catalogBinary } from './catalog.ts';
15
+ import type { FetchArchive } from './download.ts';
16
+ import { DownloadFailed } from './refused.ts';
17
+ import { VICTORIA_RELEASES } from './victoria.ts';
18
+
19
+ export type TarEntry = {
20
+ readonly name: string;
21
+ readonly bytes?: Uint8Array;
22
+ /** The typeflag: '0' regular (default), '1' hard link, '2' symlink, '5' directory, 'L', 'x', … */
23
+ readonly type?: string;
24
+ readonly format?: 'gnu' | 'posix';
25
+ readonly prefix?: string;
26
+ /** Size field written as GNU base-256. */
27
+ readonly base256?: boolean;
28
+ readonly corruptChecksum?: boolean;
29
+ };
30
+
31
+ const put = (block: Uint8Array, at: number, text: string) =>
32
+ block.set(new TextEncoder().encode(text), at);
33
+ const octal = (value: number, width: number) => `${value.toString(8).padStart(width - 1, '0')}\0`;
34
+
35
+ export const tarHeader = (entry: TarEntry): Uint8Array => {
36
+ const block = new Uint8Array(512);
37
+ const size = entry.bytes?.length ?? 0;
38
+ put(block, 0, entry.name);
39
+ put(block, 100, octal(0o755, 8));
40
+ put(block, 108, octal(1000, 8));
41
+ put(block, 116, octal(1000, 8));
42
+ if (entry.base256 === true) block.set([0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, size], 124);
43
+ else put(block, 124, octal(size, 12));
44
+ put(block, 136, octal(1_750_000_000, 12));
45
+ block.fill(0x20, 148, 156);
46
+ put(block, 156, entry.type ?? '0');
47
+ put(block, 257, entry.format === 'posix' ? 'ustar\u000000' : 'ustar \u0000');
48
+ if (entry.prefix !== undefined) put(block, 345, entry.prefix);
49
+ const sum = block.reduce((total, byte) => total + byte, 0) + (entry.corruptChecksum ? 1 : 0);
50
+ put(block, 148, `${sum.toString(8).padStart(6, '0')}\0 `);
51
+ return block;
52
+ };
53
+
54
+ /** A tar: each header, its data padded to 512, then the two zero blocks. */
55
+ export const tarOf = (entries: readonly TarEntry[], end = 1024): Uint8Array => {
56
+ const blocks: Uint8Array[] = [];
57
+ for (const entry of entries) {
58
+ blocks.push(tarHeader(entry));
59
+ const bytes = entry.bytes ?? new Uint8Array();
60
+ const padded = new Uint8Array(Math.ceil(bytes.length / 512) * 512);
61
+ padded.set(bytes);
62
+ blocks.push(padded);
63
+ }
64
+ blocks.push(new Uint8Array(end));
65
+ const out = new Uint8Array(blocks.reduce((total, block) => total + block.length, 0));
66
+ let at = 0;
67
+ for (const block of blocks) {
68
+ out.set(block, at);
69
+ at += block.length;
70
+ }
71
+ return out;
72
+ };
73
+
74
+ export const gzip = (bytes: Uint8Array): Uint8Array => Bun.gzipSync(bytes.slice());
75
+
76
+ export const bytesOf = (text: string): Uint8Array => new TextEncoder().encode(text);
77
+
78
+ /** Stand-ins for the vendor binaries: distinct bytes, so each digest is distinct. */
79
+ export const BINARY = {
80
+ vmagent: bytesOf('#!fake vmagent 1.151.0\n'),
81
+ vmalert: bytesOf('#!fake vmalert 1.151.0\n'),
82
+ vmauth: bytesOf('#!fake vmauth 1.151.0\n'),
83
+ 'victoria-traces': bytesOf('#!fake victoria-traces 0.10.0\n'),
84
+ };
85
+
86
+ const present = (archive: PinnedArchive | undefined): PinnedArchive => {
87
+ if (archive === undefined) throw new Error('the data set lost a pinned archive');
88
+ return archive;
89
+ };
90
+ const REAL = VICTORIA_RELEASES.packages;
91
+ export const VMUTILS = present(REAL.vmutils?.versions['1.151.0']?.['darwin-arm64']);
92
+ export const TRACES = present(REAL['victoria-traces']?.versions['0.10.0']?.['darwin-arm64']);
93
+ export const urlOf = (archive: PinnedArchive): string =>
94
+ releaseUrl(archive.repo, archive.tag, archive.asset);
95
+ export const VMUTILS_URL = urlOf(VMUTILS);
96
+ export const TRACES_URL = urlOf(TRACES);
97
+ export const ENTERPRISE_URL = VMUTILS_URL.replace('.tar.gz', '-enterprise.tar.gz');
98
+
99
+ export const VMUTILS_ENTRIES: readonly TarEntry[] = [
100
+ { bytes: BINARY.vmagent, name: 'vmagent-prod' },
101
+ { bytes: BINARY.vmalert, name: 'vmalert-prod' },
102
+ { bytes: BINARY.vmauth, name: 'vmauth-prod' },
103
+ ];
104
+
105
+ /** Pin an archive the way victoria.ts pins a vendor one, from the bytes themselves. */
106
+ export const pinned = (
107
+ real: PinnedArchive,
108
+ archive: Uint8Array,
109
+ members: Readonly<Record<string, Uint8Array>>,
110
+ ): PinnedArchive => ({
111
+ ...real,
112
+ members: Object.fromEntries(Object.entries(members).map(([n, b]) => [n, sha256Hex(b)])),
113
+ sha256: sha256Hex(archive),
114
+ size: archive.length,
115
+ });
116
+
117
+ /** The real data set with vmutils 1.151.0 and victoria-traces 0.10.0 re-pinned to synthetic bytes. */
118
+ export const syntheticRelease = (vmutilsEntries: readonly TarEntry[] = VMUTILS_ENTRIES) => {
119
+ const vmutils = gzip(tarOf(vmutilsEntries));
120
+ const traces = gzip(tarOf([{ bytes: BINARY['victoria-traces'], name: 'victoria-traces-prod' }]));
121
+ // ★ vmalert-prod is always pinned, as the vendor file lists it — so an archive built WITHOUT it
122
+ // models "the checksum file names a member the archive does not hold".
123
+ const memberBytes = {
124
+ 'vmalert-prod': BINARY.vmalert,
125
+ ...Object.fromEntries(vmutilsEntries.map((e) => [e.name, e.bytes ?? new Uint8Array()])),
126
+ };
127
+ const tracesPin = pinned(TRACES, traces, { 'victoria-traces-prod': BINARY['victoria-traces'] });
128
+ const catalog: ReleaseCatalog = {
129
+ ...VICTORIA_RELEASES,
130
+ packages: {
131
+ ...REAL,
132
+ 'victoria-traces': {
133
+ binaries: { 'victoria-traces': 'victoria-traces-prod' },
134
+ versions: { '0.10.0': { 'darwin-arm64': tracesPin } },
135
+ },
136
+ vmutils: {
137
+ binaries: REAL.vmutils?.binaries ?? {},
138
+ versions: { '1.151.0': { 'darwin-arm64': pinned(VMUTILS, vmutils, memberBytes) } },
139
+ },
140
+ },
141
+ };
142
+ return { catalog, traces, vmutils };
143
+ };
144
+
145
+ /** A transport over a URL → bytes table that records every request, in order. */
146
+ export const fakeTransport = (served: Readonly<Record<string, Uint8Array>>) => {
147
+ const requests: string[] = [];
148
+ const fetch: FetchArchive = async (url) => {
149
+ requests.push(url);
150
+ const bytes = served[url];
151
+ if (bytes === undefined)
152
+ throw new DownloadFailed({ message: `${url}: HTTP 404`, retryable: false });
153
+ return bytes;
154
+ };
155
+ return { fetch, requests };
156
+ };
157
+
158
+ export const ROOT = '/opt/example/bin';
159
+ export const VMUTILS_DIR = `${ROOT}/vmutils-1.151.0`;
160
+ export const TRACES_DIR = `${ROOT}/victoria-traces-0.10.0`;
161
+
162
+ /** A host deploying as root, with the two versioned directories already declared. */
163
+ export const releaseHost = () =>
164
+ fakeRunner({ dirs: { [ROOT]: 0, [TRACES_DIR]: 0, [VMUTILS_DIR]: 0 }, euid: 0 });
165
+
166
+ export const VMALERT_REQUEST = {
167
+ binary: 'vmalert',
168
+ package: 'vmutils',
169
+ platform: 'darwin-arm64',
170
+ version: '1.151.0',
171
+ } as const;
172
+
173
+ /** vmalert's props out of `catalog` (the synthetic one, normally), in its versioned directory. */
174
+ export const vmalertProps = (
175
+ catalog: ReleaseCatalog,
176
+ more: Partial<ReleaseBinaryProps> = {},
177
+ ): ReleaseBinaryProps => ({
178
+ ...catalogBinary(catalog, VMALERT_REQUEST),
179
+ directory: VMUTILS_DIR,
180
+ ...more,
181
+ });
182
+
183
+ /** Every path on the fake host, sorted — equal before and after means nothing was left behind. */
184
+ export const pathsOn = (fake: ReturnType<typeof fakeRunner>): string[] =>
185
+ [...fake.files.keys()].sort();
@@ -0,0 +1,22 @@
1
+ victoria-logs-darwin-arm64-v1.52.0-enterprise.tar.gz
2
+ victoria-logs-darwin-arm64-v1.52.0-enterprise_checksums.txt
3
+ victoria-logs-darwin-arm64-v1.52.0.tar.gz
4
+ victoria-logs-darwin-arm64-v1.52.0_checksums.txt
5
+ victoria-metrics-darwin-arm64-v1.151.0-cluster.tar.gz
6
+ victoria-metrics-darwin-arm64-v1.151.0-cluster_checksums.txt
7
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise-cluster.tar.gz
8
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise-cluster_checksums.txt
9
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise.tar.gz
10
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise_checksums.txt
11
+ victoria-metrics-darwin-arm64-v1.151.0.tar.gz
12
+ victoria-metrics-darwin-arm64-v1.151.0_checksums.txt
13
+ victoria-traces-darwin-arm64-v0.10.0.tar.gz
14
+ victoria-traces-darwin-arm64-v0.10.0_checksums.txt
15
+ vlutils-darwin-arm64-v1.52.0-enterprise.tar.gz
16
+ vlutils-darwin-arm64-v1.52.0-enterprise_checksums.txt
17
+ vlutils-darwin-arm64-v1.52.0.tar.gz
18
+ vlutils-darwin-arm64-v1.52.0_checksums.txt
19
+ vmutils-darwin-arm64-v1.151.0-enterprise.tar.gz
20
+ vmutils-darwin-arm64-v1.151.0-enterprise_checksums.txt
21
+ vmutils-darwin-arm64-v1.151.0.tar.gz
22
+ vmutils-darwin-arm64-v1.151.0_checksums.txt
@@ -0,0 +1,2 @@
1
+ 3157d4b6181d8a7e3e30918e2cbfcd4cc4cb66263e3ef21ea91e4f20f8980883 victoria-logs-darwin-arm64-v1.52.0.tar.gz
2
+ 9e48809e314902782959b3a4e683a3087476759d0293791a0564921c676ca7be victoria-logs-prod
@@ -0,0 +1,2 @@
1
+ 8792437c4c0b63719fed7b44ad18d4b4167bbe6861ab3e84c7bb010c922e7582 victoria-metrics-darwin-arm64-v1.151.0.tar.gz
2
+ 3c493a95ac1e9034a3261b230c4d62f3b186a04aafcdbacc99bfc110dc5de6a1 victoria-metrics-prod
@@ -0,0 +1,2 @@
1
+ cbe83f1d409cbb85fbf1c890c4a9b7fd34c1d74acec4dce2e0ebe46fcb260e38 victoria-traces-darwin-arm64-v0.10.0.tar.gz
2
+ 4759ec89a466129b9eefc22244a111e1f489739340747805fd93b58fd0f7b2dd victoria-traces-prod
@@ -0,0 +1,8 @@
1
+ 27d68bac90e28929214091ed9d27f2e9fef25a080407e2c8661ea9b52dd6b183 vmutils-darwin-arm64-v1.151.0.tar.gz
2
+ d3b6fa1f71ef48beac619f15f8adfa7610e76605caf37db1ebfbb379b6692329 vmagent-prod
3
+ 62845795167e9ff47890e83ac4ac5934693c105767db88591c95a08f3c4fb354 vmalert-prod
4
+ cbffb9495e4bb1457c9d45e0a7064b3a3a29b2cfd1ae24bfe8f00ec327c26998 vmalert-tool-prod
5
+ 398df9e0f5b7035d037e39ee9ff66a10ddde5333d832602463aa4d2865fdb508 vmauth-prod
6
+ 1974a2ee211b49bf581287808e010846f6c47ff135276214c6da628567777b9e vmbackup-prod
7
+ fc824df7215b01b57921ed46afde7fea6b6698adf13d9ea8e3fe36e194ef8190 vmrestore-prod
8
+ 998b111eee206b98e6d25fdf2fbaa455ecc17c7f9e7c3b2373e127cef762459f vmctl-prod
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Release binaries for Alchemy — one binary out of a pinned release archive, installed onto a host
3
+ * and verified against digests the stack pins in code, plus the vendor data sets those pins come
4
+ * from (VictoriaMetrics' is the first).
5
+ *
6
+ * ⛔ THIS BARREL IS THE PUBLIC API, AND IT IS DELIBERATELY SMALLER THAN THE DIRECTORY. The tar
7
+ * reader, the downloader, the checksum parser, the lifecycle and the test seams are internals; an
8
+ * `export *` would publish them as API and make the next refactor a breaking change.
9
+ * ⛔ IT INSTALLS; IT NEVER STARTS. Run the binary with `LaunchdJob` (the launchd subpath) or a
10
+ * systemd unit, putting `ReleaseBinary`'s `path` in its argv. See docs/release-binary.md.
11
+ * ★ THE SEAM IS SHARED, NOT COPIED. `HostRunner` is re-exported so a stack types its runner without
12
+ * importing from another subpath.
13
+ */
14
+ export type {
15
+ PinnedDownload,
16
+ ReleaseArchive,
17
+ ReleaseBinaryAttributes,
18
+ ReleaseBinaryProps,
19
+ } from './binary-form.ts';
20
+ export { releaseBinaryPath, releaseUrl } from './binary-form.ts';
21
+ export { ReleaseBinary, ReleaseBinaryProvider } from './binary.ts';
22
+ export type {
23
+ CatalogPackage,
24
+ CatalogRequest,
25
+ IdentifiedBinary,
26
+ PinnedArchive,
27
+ PinnedBinary,
28
+ ReleaseCatalog,
29
+ } from './catalog.ts';
30
+ export { catalogBinary, catalogDirectory, catalogProblems, identifyBinary } from './catalog.ts';
31
+ export { releaseProviders } from './providers.ts';
32
+ export { ArchiveRefused, BinaryRefused, ChecksumMismatch, DownloadFailed } from './refused.ts';
33
+ export { VICTORIA_RELEASES } from './victoria.ts';
34
+ export type {
35
+ ExecResult,
36
+ FileStat,
37
+ HostRunner,
38
+ HostUser,
39
+ WriteOptions,
40
+ } from '../launchd/runner.ts';
41
+ export { HostRunnerService, hostRunnerLayer } from '../launchd/runner.ts';