@gjermundgaraba/clankerbox-sdk 0.18.3 → 0.20.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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { A as parseId, B as NotFound, C as Usage, D as Id, E as HostId, F as ConflictKind, G as Refused, H as NotOffered, I as HostError, J as Unsizable, K as ScriptFailed, L as Internal, M as Capacity, N as ClankerboxError, O as Name, P as Conflict, R as Invalid, S as SshLogin, T as stateLabel, U as NotReady, V as NotMade, W as PinMismatch, X as hostErrors, Y as WrongState, Z as retryable, _ as Machine, a as release, b as Setup, c as Http, d as ActionName, f as ActionRecord, g as Host, h as CreateRequest, i as describeIssues, j as parseName, k as formatId, l as actionsByName, m as Checkpoint, o as version, p as ActionStatus, q as Unavailable, r as describeFileError, s as versionHeader, t as client_exports, u as readTimeout, v as MachineSpec, w as isReady, x as SshEndpoint, y as Runtime, z as Missing } from "./client-DHNZ57xn.mjs";
2
+ import { A as Name, B as Missing, C as SshEndpoint, D as stateLabel, E as Usage, F as ClankerboxError, G as Refused, H as NotMade, I as Conflict, J as Unavailable, K as ScriptFailed, L as HostError, M as parseId, N as parseName, O as HostId, P as Capacity, Q as retryable, R as Internal, S as Setup, T as TailnetStatus, U as NotOffered, V as NotFound, W as PinMismatch, X as WrongState, Y as Unsizable, Z as hostErrors, _ as Phase, a as release, b as PublicKeys, c as Http, d as Checkpoint, f as CreateRequest, g as MachineSpec, h as Machine, i as describeIssues, j as formatId, k as Id, l as actionsByName, m as Host, o as version, p as Desired, q as Superseded, r as describeFileError, s as versionHeader, t as client_exports, u as readTimeout, v as PrebuildRequest, w as SshLogin, x as Runtime, y as Prebuilt, z as Invalid } from "./client-S3cTnrsp.mjs";
3
3
  import { Effect, FileSystem, Option, Path, Predicate, Record, Schema } from "effect";
4
4
  import { Base64, Hex } from "effect/encoding";
5
5
  //#region src/prebuild.ts
@@ -10,10 +10,9 @@ var prebuild_exports = /* @__PURE__ */ __exportAll({
10
10
  });
11
11
  const prefix = "prebuilt-";
12
12
  const digestDigits = 12;
13
- const builderBytes = 8;
14
13
  /**
15
- * A digest of the spec's label, base, sizes, setup script as sent, and the host's runtime
16
- * version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
14
+ * A digest of the spec's label, base, sizes, setup script as sent, authorized keys, and the host's
15
+ * runtime version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
17
16
  * bump a create misses the old prebuild instead of failing to restore it. The setup's timeout and
18
17
  * the base's image are no part of it.
19
18
  */
@@ -24,19 +23,20 @@ const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.dig
24
23
  spec.ramMib,
25
24
  spec.diskGib,
26
25
  spec.setup?.script ?? null,
26
+ spec.setup?.authorizedKeys ?? null,
27
27
  host.runtimeVersion
28
28
  ])))).pipe(Effect.map((digest) => `${prefix}${Hex.encode(new Uint8Array(digest)).slice(0, digestDigits)}`));
29
29
  const lookup = (client, id) => client.getCheckpoint({ id }).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
30
30
  /**
31
- * `client.create`, restoring the spec's prebuild when the host it is placed on has one whose
32
- * capture is done, unless `fresh`. A failed restore is the reply.
31
+ * `client.create`, restoring the spec's prebuild when the host it is placed on has one, unless
32
+ * `fresh`. A failed restore is the reply.
33
33
  */
34
34
  const create = (client, name, spec, options) => Effect.gen(function* () {
35
35
  if (spec.setup === void 0 || options?.fresh === true) return { machine: yield* client.create(name, spec, { host: options?.host }) };
36
36
  const host = yield* client.place(spec.base, options);
37
37
  const id = yield* formatId(host.id, yield* nameOf(spec, host));
38
38
  const found = yield* lookup(client, id);
39
- if (Option.isSome(found) && isReady(found.value)) return {
39
+ if (Option.isSome(found)) return {
40
40
  machine: yield* client.restoreMachine({
41
41
  checkpoint: id,
42
42
  name
@@ -46,47 +46,16 @@ const create = (client, name, spec, options) => Effect.gen(function* () {
46
46
  return { machine: yield* client.create(name, spec, { host: host.id }) };
47
47
  });
48
48
  /**
49
- * Builds the spec's prebuild on the host a create of it is placed on, with a machine of its own
50
- * that it deletes however it ends. A prebuild that is there and done is the answer, one that
51
- * failed is deleted and built again, and one still running is `Conflict{busy}`, as its capture
52
- * holds it. Of two builds of one spec at once, the second's capture is `Conflict`, and neither
53
- * touches the other's machine.
49
+ * Asks the host a create of the spec is placed on for the spec's prebuild, which it builds with a
50
+ * machine of its own unless it has it. Two builds of one spec at once share the host's one build.
54
51
  */
55
52
  const build = (client, spec, options) => Effect.gen(function* () {
56
53
  const host = yield* client.place(spec.base, options);
57
- const name = yield* nameOf(spec, host);
58
- const id = yield* formatId(host.id, name);
59
- const found = yield* lookup(client, id);
60
- if (Option.isSome(found)) {
61
- const { action } = found.value;
62
- if (isReady(found.value)) return {
63
- checkpoint: found.value,
64
- built: false
65
- };
66
- if (action.status === "running") return yield* new Conflict({
67
- message: `prebuild ${id} is busy: its ${action.name} is running`,
68
- kind: "busy",
69
- host: host.id,
70
- action: "captureCheckpoint",
71
- id
72
- });
73
- yield* client.deleteCheckpoint({ id });
74
- }
75
- const builder = `prebuilding-${Hex.encode(globalThis.crypto.getRandomValues(new Uint8Array(builderBytes)))}`;
76
- const builderId = yield* formatId(host.id, builder);
77
- const removal = client.deleteMachine({ id: builderId });
78
- return {
79
- checkpoint: yield* Effect.gen(function* () {
80
- yield* client.create(builder, spec, { host: host.id });
81
- if (host.captureFrom === "stopped") yield* client.stopMachine({ id: builderId });
82
- return yield* client.captureCheckpoint({
83
- machine: builderId,
84
- name
85
- });
86
- }).pipe(Effect.onError(() => Effect.ignore(removal))),
87
- built: true,
88
- leftover: yield* removal.pipe(Effect.as(void 0), Effect.catch(() => Effect.succeed(builderId)))
89
- };
54
+ const id = yield* formatId(host.id, yield* nameOf(spec, host));
55
+ return yield* client.prebuildCheckpoint({
56
+ id,
57
+ ...spec
58
+ });
90
59
  });
91
60
  //#endregion
92
61
  //#region src/setup.ts
@@ -163,6 +132,21 @@ const readSetup = (path) => Effect.gen(function* () {
163
132
  message: `couldn't read setup ${path}: ${describeFileError(error)}`,
164
133
  file: path
165
134
  }))));
135
+ const decodePublicKeys = Schema.decodeUnknownEffect(PublicKeys);
136
+ /**
137
+ * A public-key file's text, such as `~/.ssh/id_ed25519.pub`'s, for a setup's `authorizedKeys`, with
138
+ * its line endings as `\n`.
139
+ */
140
+ const readAuthorizedKeys = (path) => Effect.gen(function* () {
141
+ const text = yield* (yield* FileSystem.FileSystem).readFileString(path).pipe(Effect.mapError((error) => new Invalid({
142
+ message: `couldn't read authorized keys ${path}: ${describeFileError(error)}`,
143
+ file: path
144
+ })));
145
+ return yield* decodePublicKeys(text.replaceAll("\r\n", "\n")).pipe(Effect.mapError((error) => new Invalid({
146
+ message: `authorized keys ${path}: ${describeIssues(error)}`,
147
+ file: path
148
+ })));
149
+ });
166
150
  //#endregion
167
151
  //#region src/profile.ts
168
152
  /**
@@ -221,16 +205,18 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
221
205
  const file = overrides.setup ?? profile?.setup?.path;
222
206
  const timeoutSeconds = overrides.setupTimeoutSeconds ?? profile?.setup?.timeoutSeconds;
223
207
  if (file === void 0 && overrides.setupTimeoutSeconds !== void 0) return yield* new Invalid({ message: `${what}: ${names?.get("setupTimeoutSeconds") ?? "the setup timeout"} needs a setup to time` });
224
- const script = file === void 0 ? void 0 : yield* readSetup(file);
208
+ const keys = overrides.authorizedKeys;
209
+ const setup = file === void 0 ? void 0 : keys === void 0 ? yield* new Invalid({ message: `${what}: a setup needs ${names?.get("authorizedKeys") ?? "authorizedKeys"}, the public keys the host installs for the login user after it` }) : Record.filter({
210
+ script: yield* readSetup(file),
211
+ timeoutSeconds,
212
+ authorizedKeys: yield* readAuthorizedKeys(keys)
213
+ }, Predicate.isNotUndefined);
225
214
  const fields = {
226
215
  base: overrides.base ?? profile?.base,
227
216
  cpu: overrides.cpu ?? profile?.cpu,
228
217
  ramMib: overrides.ramMib ?? profile?.ramMib,
229
218
  diskGib: overrides.diskGib ?? profile?.diskGib,
230
- setup: script === void 0 ? void 0 : Record.filter({
231
- script,
232
- timeoutSeconds
233
- }, Predicate.isNotUndefined),
219
+ setup,
234
220
  profile: profile?.label
235
221
  };
236
222
  return {
@@ -248,7 +234,7 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
248
234
  * either is `WrongState`, naming what it lacks and why.
249
235
  */
250
236
  const sshTarget = (machine) => {
251
- const { id, ssh: login, hostKey, action } = machine;
237
+ const { id, ssh: login, hostKey } = machine;
252
238
  if (login !== void 0 && hostKey !== void 0) return Effect.succeed({
253
239
  id,
254
240
  login,
@@ -256,7 +242,7 @@ const sshTarget = (machine) => {
256
242
  });
257
243
  const lacks = login === void 0 ? "SSH endpoint" : "SSH host key on record";
258
244
  const state = stateLabel(machine);
259
- const message = `${id} has no ${lacks} (${state}, ${action.name} ${action.status}): see clankerbox get ${id}`;
245
+ const message = `${id} has no ${lacks} (${state}): see clankerbox get ${id}`;
260
246
  return Effect.fail(new WrongState({
261
247
  message,
262
248
  machine: id,
@@ -306,4 +292,4 @@ const formatMib = (mib) => mib >= 1024 && mib % 512 === 0 ? `${mib / mibPerGib}G
306
292
  /** As a size flag takes it: `60G`. */
307
293
  const formatGib = (gib) => `${gib}G`;
308
294
  //#endregion
309
- export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, ClankerboxError, client_exports as Client, Conflict, ConflictKind, CreateRequest, Host, HostError, HostId, Http, Id, Internal, Invalid, Machine, MachineSpec, Missing, Name, NotFound, NotMade, NotOffered, NotReady, PinMismatch, prebuild_exports as Prebuild, Profile, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Unavailable, Unsizable, Usage, WrongState, actionsByName, describeFileError, describeIssues, formatGib, formatId, formatMib, hostErrors, isReady, knownHostsLine, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readSetup, readTimeout, release, retryable, specRequest, sshTarget, stateLabel, version, versionHeader };
295
+ export { Capacity, Checkpoint, ClankerboxError, client_exports as Client, Conflict, CreateRequest, Desired, Host, HostError, HostId, Http, Id, Internal, Invalid, Machine, MachineSpec, Missing, Name, NotFound, NotMade, NotOffered, Phase, PinMismatch, prebuild_exports as Prebuild, PrebuildRequest, Prebuilt, Profile, PublicKeys, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Superseded, TailnetStatus, Unavailable, Unsizable, Usage, WrongState, actionsByName, describeFileError, describeIssues, formatGib, formatId, formatMib, hostErrors, knownHostsLine, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readAuthorizedKeys, readSetup, readTimeout, release, retryable, specRequest, sshTarget, stateLabel, version, versionHeader };
package/dist/node.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { N as Invalid, a as Options, r as HostEntry, t as Client } from "./client-8af586hb.mjs";
1
+ import { P as Invalid, a as Options, r as HostEntry, t as Client } from "./client-Dr3PcuCP.mjs";
2
2
  import { Layer } from "effect";
3
3
  //#region src/node.d.ts
4
4
  /**
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as layer$1 } from "./client-DHNZ57xn.mjs";
1
+ import { n as layer$1 } from "./client-S3cTnrsp.mjs";
2
2
  import { Layer } from "effect";
3
3
  import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
4
4
  //#region src/node.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerbox-sdk",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "description": "clankerbox's API contract and client library, for Effect",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,21 +27,20 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "dependencies": {
31
- "@gjermundgaraba/effect-actions": "0.17.0"
32
- },
33
30
  "devDependencies": {
34
- "@effect/platform-node": "4.0.4",
35
- "@effect/vitest": "4.0.4",
36
- "@types/node": "26.6.4",
37
- "effect": "4.0.4",
31
+ "@effect/platform-node": "4.0.5",
32
+ "@effect/vitest": "4.0.5",
33
+ "@gjermundgaraba/effect-actions": "0.18.2",
34
+ "@types/node": "26.6.5",
35
+ "effect": "4.0.5",
38
36
  "typescript": "7.0.2",
39
- "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
40
- "vite-plus": "1.2.0",
41
- "vitest": "5.0.4"
37
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
38
+ "vite-plus": "1.3.0",
39
+ "vitest": "5.0.5"
42
40
  },
43
41
  "peerDependencies": {
44
42
  "@effect/platform-node": "~4.0.4",
43
+ "@gjermundgaraba/effect-actions": "^0.18.0",
45
44
  "effect": "~4.0.4"
46
45
  },
47
46
  "peerDependenciesMeta": {