@clawscarf/cli 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (64) hide show
  1. package/README.md +119 -2
  2. package/THIRD_PARTY_NOTICES.md +18 -11
  3. package/deploy/execution/network/node-ingress.cfg +1 -1
  4. package/deploy/execution/network/public-addresses.json +24 -0
  5. package/package.json +2 -1
  6. package/packs/README.md +4 -11
  7. package/pnpm-lock.yaml +212 -3567
  8. package/recipes/README.md +10 -6
  9. package/recipes/team-server/recipe.json +5 -4
  10. package/release/README.md +122 -58
  11. package/release/components.json +26 -2
  12. package/release/operator.md +27 -53
  13. package/release/telemetry.json +4 -0
  14. package/runtime/configuration.js +15 -1
  15. package/runtime/current.json +63 -0
  16. package/scripts/clawscarf.js +9 -1
  17. package/scripts/controller.js +6 -0
  18. package/scripts/deployment/browser-node-compose.js +0 -1
  19. package/scripts/deployment/certificates.js +2 -1
  20. package/scripts/deployment/compose.js +3 -5
  21. package/scripts/deployment/configuration.js +8 -7
  22. package/scripts/deployment/connection-settings.js +0 -4
  23. package/scripts/deployment/controller-compose.js +14 -6
  24. package/scripts/deployment/entrypoint.js +4 -1
  25. package/scripts/deployment/launch.js +12 -29
  26. package/scripts/deployment/model-gateway-compose.js +6 -0
  27. package/scripts/deployment/model-gateway.js +9 -1
  28. package/scripts/deployment/models.js +5 -1
  29. package/scripts/deployment/network-policy.js +147 -0
  30. package/scripts/deployment/networks.js +21 -0
  31. package/scripts/deployment/policy.js +27 -35
  32. package/scripts/deployment/prepare.js +22 -15
  33. package/scripts/deployment/public-addresses.js +30 -0
  34. package/scripts/deployment/public-web.js +132 -0
  35. package/scripts/deployment/runtime.js +15 -4
  36. package/scripts/deployment/service-network.js +86 -0
  37. package/scripts/deployment/state.js +16 -0
  38. package/scripts/installation/command.js +6 -14
  39. package/scripts/installation/configuration.js +2 -0
  40. package/scripts/installation/configure.js +1 -0
  41. package/scripts/installation/installer/cloud.js +1 -1
  42. package/scripts/installation/installer/collect.js +17 -0
  43. package/scripts/installation/installer/menu.js +3 -2
  44. package/scripts/installation/installer/prompts.js +6 -2
  45. package/scripts/installation/installer/run.js +21 -11
  46. package/scripts/installation/installer/sections/models.js +2 -12
  47. package/scripts/installation/installer/settings.js +10 -47
  48. package/scripts/installation/installer/summary.js +2 -0
  49. package/scripts/installation/options.js +11 -1
  50. package/scripts/installation/plan.js +4 -33
  51. package/scripts/installation/prerequisites.js +10 -5
  52. package/scripts/installation/recipes/definition.js +2 -0
  53. package/scripts/installation/reconfigure.js +69 -18
  54. package/scripts/installation/resolve.js +8 -5
  55. package/scripts/installation/runtime.js +3 -3
  56. package/scripts/installation/setup.js +2 -0
  57. package/scripts/release/create.js +8 -3
  58. package/scripts/release/definition.js +34 -3
  59. package/scripts/telemetry.js +279 -0
  60. package/runtime/releases/0.1.0-alpha.1.json +0 -37
  61. package/scripts/deployment/connection-policy.js +0 -80
  62. package/scripts/deployment/upgrade-rpc.py +0 -148
  63. package/scripts/deployment/upgrade-state.js +0 -47
  64. package/scripts/deployment/upgrade.js +0 -298
@@ -1,47 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { z } from "zod";
4
- import { LocalSetupError } from "./process.js";
5
- export const snapshotSchema = z.strictObject({
6
- create: z.string().min(1),
7
- effective: z.string().min(1),
8
- resourceVersion: z.string().regex(/^[0-9]+$/),
9
- });
10
- export const upgradeSchema = z.strictObject({
11
- ownerId: z.uuid(),
12
- generation: z.uuid(),
13
- fromImage: z.string(),
14
- toImage: z.string(),
15
- oldId: z.uuid(),
16
- oldContainerId: z.string().regex(/^[a-f0-9]{64}$/),
17
- newId: z.uuid().optional(),
18
- snapshot: snapshotSchema,
19
- stage: z.enum([
20
- "prepared",
21
- "delete_sent",
22
- "deleted",
23
- "create_sent",
24
- "created",
25
- "restore_sent",
26
- "restored",
27
- "stopped",
28
- "released",
29
- "committing",
30
- "complete",
31
- ]),
32
- });
33
- export async function readUpgrade(directory) {
34
- try {
35
- return upgradeSchema.parse(JSON.parse(await readFile(join(directory, "upgrade.json"), "utf8")));
36
- }
37
- catch (error) {
38
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
39
- return undefined;
40
- throw error;
41
- }
42
- }
43
- export async function requireNoUpgrade(directory) {
44
- const upgrade = await readUpgrade(directory);
45
- if (upgrade && upgrade.stage !== "complete")
46
- throw new LocalSetupError("upgrade_pending", "An upgrade is unfinished. Resume that upgrade before preparing or starting this installation.");
47
- }
@@ -1,298 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { verifyRuntimeImage } from "./images.js";
3
- import { mkdtemp, writeFile, rm, chmod } from "node:fs/promises";
4
- import { join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { isDeepStrictEqual } from "node:util";
7
- import { setTimeout as delay } from "node:timers/promises";
8
- import { z } from "zod";
9
- import { parseLocalInput } from "./configuration.js";
10
- import { readState, resourceNames, withInstallationLock, writePrivate, } from "./state.js";
11
- import { readUpgrade, snapshotSchema } from "./upgrade-state.js";
12
- import { runtimeManager } from "./runtime.js";
13
- import { verifyRuntimeBinding } from "./runtime-binding.js";
14
- import { verifyLocalNetworks } from "./networks.js";
15
- import { verifyLocalExecutables } from "./preflight.js";
16
- import { LocalSetupError, run } from "./process.js";
17
- function refuse(detail) {
18
- throw new LocalSetupError("upgrade_refused", detail);
19
- }
20
- function uncertain() {
21
- throw new LocalSetupError("upgrade_outcome_unknown", "The upgrade has an unconfirmed external effect. Inspect its private state and controller before resuming; it will not repeat allocation or deletion.");
22
- }
23
- /** Requires exclusive operator control and the installation's own controller running. */
24
- export async function upgradeLocal(directoryInput, image, python, report, command = run) {
25
- const directory = resolve(directoryInput);
26
- await withInstallationLock(directory, async () => {
27
- const state = await readState(directory);
28
- await verifyRuntimeImage(image, command);
29
- const next = {
30
- ...state,
31
- input: parseLocalInput({ ...state.input, runtimeImage: image }),
32
- };
33
- const names = resourceNames(state);
34
- const controller = join(directory, "controller");
35
- if (Object.keys(process.env).some((key) => key.startsWith("OPENSHELL_")))
36
- refuse("Unset OPENSHELL_* overrides before upgrading this installation.");
37
- const env = {
38
- ...process.env,
39
- XDG_CONFIG_HOME: join(controller, "config"),
40
- XDG_STATE_HOME: join(controller, "state"),
41
- XDG_DATA_HOME: join(controller, "data"),
42
- };
43
- await verifyLocalExecutables(state);
44
- await verifyLocalNetworks(directory, state, command);
45
- const running = await command("docker", [
46
- "container",
47
- "ls",
48
- "--quiet",
49
- "--filter",
50
- `label=com.docker.compose.project=${names.project}`,
51
- "--filter",
52
- "label=com.docker.compose.service=companion",
53
- ]);
54
- if (running.trim())
55
- refuse("Stop the installation and its access companion before upgrading. Start only its private controller.");
56
- const gate = await command("docker", [
57
- "image",
58
- "inspect",
59
- "--format",
60
- '{{index .Config.Labels "io.clawscarf.start-gate"}}',
61
- image,
62
- ]);
63
- if (gate.trim() !== "1")
64
- refuse("The selected exact runtime image does not support the replacement startup gate.");
65
- const control = runtimeManager(directory, state, env, command);
66
- let upgrade = await readUpgrade(directory);
67
- if (upgrade?.stage === "complete" &&
68
- upgrade.toImage === image &&
69
- state.input.runtimeImage === image) {
70
- report("This runtime upgrade is already complete.");
71
- return;
72
- }
73
- if (upgrade?.stage === "complete")
74
- upgrade = undefined;
75
- async function rpc(action, value = {}) {
76
- const response = z
77
- .discriminatedUnion("ok", [
78
- z.strictObject({ ok: z.literal(true), value: z.unknown() }),
79
- z.strictObject({ ok: z.literal(false), code: z.string() }),
80
- ])
81
- .parse(JSON.parse(await command(python, [fileURLToPath(new URL("./upgrade-rpc.py", import.meta.url))], {
82
- env,
83
- timeout: 150000,
84
- input: JSON.stringify({
85
- action,
86
- controller,
87
- name: names.sandbox,
88
- ownerId: state.ownerId,
89
- ...value,
90
- }),
91
- })));
92
- if (!response.ok) {
93
- if (response.code === "global_overrides_unsupported")
94
- refuse("This controller has global overrides. The pinned API cannot preserve hidden sandbox settings; remove neither compute nor data until that configuration has a supported upgrade path.");
95
- if (response.code === "policy_not_loaded")
96
- refuse("The current policy is not confirmed loaded. Reconcile it before upgrading.");
97
- if (response.code === "runtime_command_unsupported")
98
- refuse("The runtime uses a custom startup command. Its upgrade needs an explicit supported startup gate.");
99
- refuse("The controller did not verify the upgrade configuration. Inspect the private upgrade record and current native state before resuming.");
100
- }
101
- return response.value;
102
- }
103
- async function save(value) {
104
- await writePrivate(join(directory, "upgrade.json"), JSON.stringify(value) + "\n");
105
- }
106
- if (!upgrade) {
107
- if (image === state.input.runtimeImage)
108
- refuse("Choose a different exact runtime image.");
109
- const records = await control.recorded();
110
- const target = await control.observe();
111
- if (!target ||
112
- target.id !== records.receipt?.id ||
113
- target.phase !== "Stopped")
114
- refuse("Upgrade requires this installation's recorded runtime to be Stopped.");
115
- const binding = await verifyRuntimeBinding(state, target, command);
116
- const snapshot = snapshotSchema.parse(await rpc("snapshot", { id: target.id }));
117
- upgrade = {
118
- ownerId: state.ownerId,
119
- generation: randomUUID(),
120
- fromImage: state.input.runtimeImage,
121
- toImage: image,
122
- oldId: target.id,
123
- oldContainerId: binding.containerId,
124
- snapshot,
125
- stage: "prepared",
126
- };
127
- await save(upgrade);
128
- }
129
- const operation = upgrade;
130
- if (operation.ownerId !== state.ownerId ||
131
- operation.toImage !== image ||
132
- ![operation.fromImage, operation.toImage].includes(state.input.runtimeImage))
133
- refuse("Resume the recorded upgrade with its exact image and installation.");
134
- const advance = async (stage) => {
135
- operation.stage = stage;
136
- await save(operation);
137
- };
138
- const request = () => ({
139
- snapshot: operation.snapshot,
140
- id: operation.newId,
141
- });
142
- async function retainedVolume() {
143
- const volume = z
144
- .object({
145
- Name: z.literal(names.volume),
146
- Labels: z.record(z.string(), z.string()),
147
- })
148
- .parse(JSON.parse(await command("docker", [
149
- "volume",
150
- "inspect",
151
- "--format",
152
- '{"Name":{{json .Name}},"Labels":{{json .Labels}}}',
153
- names.volume,
154
- ])));
155
- if (volume.Labels["clawscarf.installation"] !== state.ownerId)
156
- refuse("The retained home volume's ownership changed.");
157
- }
158
- async function oldAbsent() {
159
- if (await control.observe())
160
- uncertain();
161
- const ids = await command("docker", [
162
- "container",
163
- "ls",
164
- "--all",
165
- "--quiet",
166
- "--no-trunc",
167
- "--filter",
168
- `id=${operation.oldContainerId}`,
169
- ]);
170
- if (ids.trim())
171
- uncertain();
172
- await retainedVolume();
173
- }
174
- if (operation.stage === "prepared") {
175
- const current = snapshotSchema.parse(await rpc("snapshot", { id: operation.oldId }));
176
- if (!isDeepStrictEqual(current, operation.snapshot))
177
- refuse("Runtime configuration changed after the upgrade snapshot. No deletion was sent.");
178
- await verifyRuntimeBinding({
179
- ...state,
180
- input: { ...state.input, runtimeImage: operation.fromImage },
181
- }, { id: operation.oldId, name: names.sandbox }, command);
182
- await advance("delete_sent");
183
- report("Removing stopped compute; retaining its home volume…");
184
- // Native deletion has no atomic UUID precondition. Never resend this request.
185
- try {
186
- await control.shell(["sandbox", "delete", names.sandbox]);
187
- }
188
- catch {
189
- /* Reconcile absence below. */
190
- }
191
- }
192
- if (operation.stage === "delete_sent") {
193
- await oldAbsent();
194
- await advance("deleted");
195
- }
196
- if (operation.stage === "deleted") {
197
- await oldAbsent();
198
- await advance("create_sent");
199
- report("Creating the replacement with application startup gated…");
200
- try {
201
- await rpc("create", {
202
- ...request(),
203
- image,
204
- generation: operation.generation,
205
- });
206
- }
207
- catch {
208
- /* Observe the unique generation; never replay create. */
209
- }
210
- }
211
- if (operation.stage === "create_sent") {
212
- const target = await control.observe();
213
- if (!target ||
214
- target.labels["clawscarf.upgrade"] !== operation.generation ||
215
- target.id === operation.oldId)
216
- uncertain();
217
- operation.newId = target.id;
218
- await advance("created");
219
- }
220
- const id = operation.newId ?? uncertain();
221
- async function confirm() {
222
- const target = await control.confirm(id);
223
- if (target.labels["clawscarf.upgrade"] !== operation.generation)
224
- refuse("The replacement generation changed.");
225
- return target;
226
- }
227
- async function ready() {
228
- const deadline = Date.now() + 90000;
229
- while (Date.now() < deadline) {
230
- const target = await confirm();
231
- if (target.phase === "Ready")
232
- return;
233
- if (["Error", "Stopped"].includes(target.phase))
234
- refuse("The replacement did not become ready. Inspect the controller before resuming.");
235
- await delay(500);
236
- }
237
- refuse("The replacement is still starting. Resume this same upgrade after inspecting the controller.");
238
- }
239
- if (operation.stage === "created") {
240
- await ready();
241
- await verifyRuntimeBinding(next, { id, name: names.sandbox }, command);
242
- await advance("restore_sent");
243
- report("Restoring the current controller settings…");
244
- }
245
- if (operation.stage === "restore_sent") {
246
- await rpc("restore", request());
247
- await advance("restored");
248
- }
249
- if (operation.stage === "restored") {
250
- // Next normal startup reads restored settings before launching the application.
251
- const target = await confirm();
252
- if (target.phase !== "Stopped")
253
- await control.shell(["sandbox", "stop", names.sandbox]);
254
- if ((await confirm()).phase !== "Stopped")
255
- refuse("The replacement has not confirmed Stopped.");
256
- await rpc("verify", request());
257
- await advance("stopped");
258
- }
259
- if (operation.stage === "stopped") {
260
- const binding = await verifyRuntimeBinding(next, { id, name: names.sandbox }, command);
261
- if ((await confirm()).phase !== "Stopped")
262
- refuse("Keep the replacement stopped until the upgrade commits.");
263
- const staging = await mkdtemp(join(directory, "private", "upgrade-gate-"));
264
- try {
265
- const marker = join(staging, "ready");
266
- await writeFile(marker, operation.generation + "\n", { mode: 0o644 });
267
- await chmod(marker, 0o644);
268
- // docker cp assigns root ownership at the container destination. No guest runs here.
269
- await command("docker", [
270
- "cp",
271
- marker,
272
- `${binding.containerId}:/etc/clawscarf-start-ready`,
273
- ]);
274
- }
275
- finally {
276
- await rm(staging, { recursive: true, force: true });
277
- }
278
- await advance("released");
279
- }
280
- if (operation.stage === "released") {
281
- const target = await confirm();
282
- if (target.phase !== "Stopped")
283
- refuse("The upgraded runtime has not confirmed Stopped.");
284
- await verifyRuntimeBinding(next, { id, name: names.sandbox }, command);
285
- await advance("committing");
286
- }
287
- if (operation.stage === "committing") {
288
- await confirm();
289
- await verifyRuntimeBinding(next, { id, name: names.sandbox }, command);
290
- const intent = { ownerId: state.ownerId, name: names.sandbox, image };
291
- await writePrivate(join(directory, "runtime-create.json"), JSON.stringify(intent) + "\n");
292
- await writePrivate(join(directory, "runtime.json"), JSON.stringify({ ...intent, id }) + "\n");
293
- await writePrivate(join(directory, "installation.json"), JSON.stringify(next, null, 2) + "\n");
294
- await advance("complete");
295
- }
296
- report("Runtime replacement complete; data retained. Stop the private controller, then start the installation to verify native administrator access. This is not a backup or data rollback.");
297
- });
298
- }