@omg-dev/sandbox 0.4.30 → 0.4.32

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,2 +1,2 @@
1
- import { _ as SandboxClient, a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, g as SandboxApiError, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run, v as assertTemplateId } from "./templates-C3mBSGdT.mjs";
1
+ import { _ as SandboxClient, a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, g as SandboxApiError, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run, v as assertTemplateId } from "./templates-BLd13wPT.mjs";
2
2
  export { SandboxApiError, SandboxClient, applyTemplate, apt, assertTemplateId, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
@@ -96,7 +96,7 @@ var SandboxClient = class {
96
96
  mode: file.mode
97
97
  })));
98
98
  },
99
- snapshot: () => this.request("POST", `/v1/sandboxes/${id}/snapshot`),
99
+ snapshot: (options = {}) => this.request("POST", `/v1/sandboxes/${id}/snapshot`, options),
100
100
  stop: () => this.request("DELETE", `/v1/sandboxes/${id}`)
101
101
  };
102
102
  }
@@ -150,6 +150,7 @@ function defineTemplate(definition) {
150
150
  assertTemplateId(definition.id);
151
151
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,19}$/.test(definition.version)) throw new Error("invalid template version");
152
152
  if (!definition.title.trim()) throw new Error("template title is required");
153
+ if (definition.restoreKind !== "project_only" && definition.restoreKind !== "full_rootfs") throw new Error("template restoreKind must be project_only or full_rootfs");
153
154
  for (const port of definition.ports ?? []) if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`invalid template port ${port}`);
154
155
  return Object.freeze(definition);
155
156
  }
@@ -172,6 +173,7 @@ function templateDefinitionHash(definition) {
172
173
  function templateRuntimeContract(definition, immutableRef) {
173
174
  return {
174
175
  immutableRef,
176
+ restoreKind: definition.restoreKind,
175
177
  startCommand: definition.start ? "exec /home/user/.omg/template/bootstrap.sh" : void 0,
176
178
  readinessPort: definition.start?.readiness?.port,
177
179
  readinessPath: definition.start?.readiness?.path,
@@ -218,12 +220,14 @@ function compileStep(step) {
218
220
  function compileStartScript(start) {
219
221
  const port = start.readiness?.port;
220
222
  const timeout = start.readiness?.timeoutSeconds ?? 60;
223
+ const supervisorMarker = "omg-template-supervisor";
221
224
  const foreground = commandForUser(`${shellEnvAssignments({
222
225
  HOME: start.user ? `/home/${start.user}` : "/root",
223
226
  ...start.env ?? {}
224
227
  }).map((assignment) => `export ${assignment}`).join("\n")}\n${`${start.cwd ? `cd ${quote(start.cwd)} && ` : ""}${start.command}`}`, start.user);
225
228
  const probe = port ? `for _ in $(seq 1 ${timeout}); do curl -fsS -m1 -o /dev/null ${quote(`http://127.0.0.1:${port}${start.readiness?.path ?? "/"}`)} && exit 0; sleep 1; done\necho 'template service did not become ready on :${port}' >&2\nexit 1` : "exit 0";
226
- return `#!/usr/bin/env bash\nset -euo pipefail\nmkdir -p /home/user/.omg/template\npidfile=/home/user/.omg/template/start.pid\nif [ -s \"$pidfile\" ] && kill -0 \"$(cat \"$pidfile\")\" 2>/dev/null; then exit 0; fi\nnohup /bin/bash -lc ${quote(`while true; do ${foreground} >>/home/user/.omg/template/start.log 2>&1 || true; sleep 2; done`)} >/dev/null 2>&1 &\necho $! > \"$pidfile\"\n${probe}\n`;
229
+ const supervisor = `while true; do ${foreground} >>/home/user/.omg/template/start.log 2>&1 || true; sleep 2; done`;
230
+ return `#!/usr/bin/env bash\nset -euo pipefail\nmkdir -p /home/user/.omg/template\npidfile=/home/user/.omg/template/start.pid\npid=\"\"\nif [ -s \"$pidfile\" ]; then pid=\"$(cat \"$pidfile\" 2>/dev/null || true)\"; fi\nsupervisor_alive=false\nif [[ \"$pid\" =~ ^[0-9]+$ ]] && kill -0 \"$pid\" 2>/dev/null && tr '\\0' '\\n' < \"/proc/$pid/cmdline\" 2>/dev/null | grep -Fxq ${quote(supervisorMarker)}; then supervisor_alive=true; fi\nif [ \"$supervisor_alive\" != true ]; then\n rm -f \"$pidfile\"\n nohup /bin/bash -lc ${quote(supervisor)} ${quote(supervisorMarker)} >/dev/null 2>&1 &\n echo $! > \"$pidfile\"\nfi\n${probe}\n`;
227
231
  }
228
232
  async function assertExec(label, result) {
229
233
  if (result.exitCode !== 0) throw new Error(`${label} failed (${result.exitCode}): ${(result.stderr || result.stdout).trim().slice(-1200)}`);
@@ -289,7 +293,7 @@ async function runTemplateStep(sandbox, label, script, timeoutMs) {
289
293
  await sandbox.shell(`rm -rf ${quote(workDir)}`, { timeoutMs: 1e4 }).catch(() => {});
290
294
  }
291
295
  }
292
- const FILES_STEP_SKIP_NAMES = new Set([".gitkeep", ".DS_Store"]);
296
+ const FILES_STEP_SKIP_NAMES = /* @__PURE__ */ new Set([".gitkeep", ".DS_Store"]);
293
297
  async function walkSourceDir(dir) {
294
298
  const out = [];
295
299
  async function visit(d) {
@@ -374,7 +378,7 @@ async function bakeTemplate(client, definition, options = {}) {
374
378
  let snapshotted = false;
375
379
  try {
376
380
  await applyTemplate(sandbox, definition, log);
377
- const snapshot = await sandbox.snapshot();
381
+ const snapshot = await sandbox.snapshot({ restoreKind: definition.restoreKind });
378
382
  snapshotted = true;
379
383
  const rootfsSha = (await waitForSnapshotUpload(client, snapshot)).rootfsSha?.trim();
380
384
  if (!rootfsSha) throw new Error(`snapshot ${snapshot.id} has no rootfsSha — cannot derive an immutable template version identity`);
@@ -1,2 +1,2 @@
1
- import { a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run } from "./templates-C3mBSGdT.mjs";
1
+ import { a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run } from "./templates-BLd13wPT.mjs";
2
2
  export { applyTemplate, apt, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omg-dev/sandbox",
3
- "version": "0.4.30",
3
+ "version": "0.4.32",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -44,6 +44,7 @@ describe("template version identity", () => {
44
44
  id: "my-agent",
45
45
  version: "3",
46
46
  title: "Mine",
47
+ restoreKind: "full_rootfs",
47
48
  install: [],
48
49
  });
49
50
 
@@ -68,7 +69,7 @@ describe("template version identity", () => {
68
69
  });
69
70
 
70
71
  test("long ids truncate to fit the 40-char registry limit", () => {
71
- const long = defineTemplate({ id: "a".repeat(40), version: "12", title: "Long", install: [] });
72
+ const long = defineTemplate({ id: "a".repeat(40), version: "12", title: "Long", restoreKind: "full_rootfs", install: [] });
72
73
  const ref = templateContentVersionRef(long, "rootfs-sha-1");
73
74
  expect(ref.length).toBeLessThanOrEqual(40);
74
75
  expect(ref).toMatch(/-v12-[0-9a-f]{8}$/);
@@ -84,6 +85,7 @@ describe("SandboxClient template bake", () => {
84
85
  id: "my-agent",
85
86
  version: "3",
86
87
  title: "Mine",
88
+ restoreKind: "full_rootfs",
87
89
  ports: [8766],
88
90
  install: [],
89
91
  start: { command: "bun start", readiness: { port: 8766, path: "/health" } },
@@ -101,6 +103,7 @@ describe("SandboxClient template bake", () => {
101
103
  expect(calls.filter((call) => call.path.includes("/v1/templates/") && call.method === "POST").map((call) => call.body)).toEqual([
102
104
  {
103
105
  snapshotId: "snap-1",
106
+ restoreKind: "full_rootfs",
104
107
  startCommand: "exec /home/user/.omg/template/bootstrap.sh",
105
108
  readinessPort: 8766,
106
109
  readinessPath: "/health",
@@ -108,6 +111,7 @@ describe("SandboxClient template bake", () => {
108
111
  },
109
112
  {
110
113
  snapshotId: "snap-1",
114
+ restoreKind: "full_rootfs",
111
115
  startCommand: "exec /home/user/.omg/template/bootstrap.sh",
112
116
  readinessPort: 8766,
113
117
  readinessPath: "/health",
@@ -124,6 +128,7 @@ describe("SandboxClient template bake", () => {
124
128
  id: "my-agent",
125
129
  version: "3",
126
130
  title: "Mine",
131
+ restoreKind: "full_rootfs",
127
132
  install: [],
128
133
  }), { systemClient });
129
134
  const sandboxCalls = calls.filter((call) => !call.path.includes("/v1/templates/"));
@@ -133,7 +138,7 @@ describe("SandboxClient template bake", () => {
133
138
  });
134
139
 
135
140
  test("an already-published content identity is reused: latest repoints, the immutable version is never republished", async () => {
136
- const definition = defineTemplate({ id: "my-agent", version: "3", title: "Mine", install: [] });
141
+ const definition = defineTemplate({ id: "my-agent", version: "3", title: "Mine", restoreKind: "full_rootfs", install: [] });
137
142
  const ref = templateContentVersionRef(definition, "rootfs-sha-1");
138
143
  const { calls, fakeFetch } = fakeInfra({ existingVersions: { [ref]: "snap-old" } });
139
144
  const client = new SandboxClient({ baseUrl: "https://infra.example", token: "secret", ownerId: "user-123", fetch: fakeFetch });
@@ -154,7 +159,7 @@ describe("SandboxClient template bake", () => {
154
159
  test("missing rootfsSha fails loud instead of publishing a collision-prone static ref", async () => {
155
160
  const { fakeFetch } = fakeInfra({ snapshotRootfsSha: null });
156
161
  const client = new SandboxClient({ baseUrl: "https://infra.example", token: "secret", ownerId: "user-123", fetch: fakeFetch });
157
- await expect(bakeTemplate(client, defineTemplate({ id: "my-agent", version: "3", title: "Mine", install: [] })))
162
+ await expect(bakeTemplate(client, defineTemplate({ id: "my-agent", version: "3", title: "Mine", restoreKind: "full_rootfs", install: [] })))
158
163
  .rejects.toThrow(/rootfsSha/);
159
164
  });
160
165
  });
package/src/client.ts CHANGED
@@ -29,6 +29,10 @@ export interface Snapshot {
29
29
  rootfsSha?: string;
30
30
  }
31
31
 
32
+ export interface SnapshotOptions {
33
+ restoreKind?: "project_only" | "full_rootfs";
34
+ }
35
+
32
36
  /** Error thrown by SandboxClient.request carrying the HTTP status, so callers
33
37
  * can branch on well-defined statuses (e.g. 404 template probe) instead of
34
38
  * string-matching error messages. */
@@ -53,6 +57,7 @@ export interface CreateSandboxOptions {
53
57
  export interface TemplateRuntimeContract {
54
58
  /** Reserved for service-owned system template versions. */
55
59
  immutableRef?: string;
60
+ restoreKind?: "project_only" | "full_rootfs";
56
61
  startCommand?: string;
57
62
  readinessPort?: number;
58
63
  readinessPath?: string;
@@ -64,7 +69,7 @@ export interface Sandbox {
64
69
  exec(command: string, args?: string[], options?: ExecOptions): Promise<ExecResult>;
65
70
  shell(script: string, options?: ExecOptions): Promise<ExecResult>;
66
71
  writeFiles(files: Array<{ path: string; content: Uint8Array | string; mode?: number }>): Promise<void>;
67
- snapshot(): Promise<Snapshot>;
72
+ snapshot(options?: SnapshotOptions): Promise<Snapshot>;
68
73
  stop(): Promise<void>;
69
74
  }
70
75
 
@@ -156,7 +161,7 @@ export class SandboxClient {
156
161
  mode: file.mode,
157
162
  })));
158
163
  },
159
- snapshot: () => this.request("POST", `/v1/sandboxes/${id}/snapshot`),
164
+ snapshot: (options = {}) => this.request("POST", `/v1/sandboxes/${id}/snapshot`, options),
160
165
  stop: () => this.request("DELETE", `/v1/sandboxes/${id}`),
161
166
  };
162
167
  }
@@ -28,7 +28,7 @@ function fakeSandbox(): Sandbox & { writeBatches: Array<Array<{ path: string; co
28
28
 
29
29
  describe("sandbox templates", () => {
30
30
  test("defines a typed, immutable versioned template", () => {
31
- const template = defineTemplate({ id: "angel-lfg", version: "12", title: "Angel", install: [apt.packages(["tmux"])], checks: [check.command("tmux")] });
31
+ const template = defineTemplate({ id: "angel-lfg", version: "12", title: "Angel", restoreKind: "full_rootfs", install: [apt.packages(["tmux"])], checks: [check.command("tmux")] });
32
32
  expect(templateVersionRef(template)).toBe("angel-lfg-v12");
33
33
  expect(Object.isFrozen(template)).toBe(true);
34
34
  });
@@ -44,6 +44,9 @@ describe("sandbox templates", () => {
44
44
  const script = compileStartScript({ command: "bun start", user: "user", readiness: { port: 8766 } });
45
45
  expect(script).toContain("/home/user/.omg/template/start.pid");
46
46
  expect(script).toContain("http://127.0.0.1:8766/");
47
+ expect(script).toContain("grep -Fxq 'omg-template-supervisor'");
48
+ expect(script).toContain("nohup /bin/bash -lc");
49
+ expect(script).not.toContain(`kill -0 "$(cat "$pidfile")" 2>/dev/null; then exit 0`);
47
50
  expect(() => compileStep(run({ command: "true", env: { "BAD-NAME": "x" } }))).toThrow("invalid environment variable");
48
51
  });
49
52
 
@@ -62,6 +65,7 @@ describe("sandbox templates", () => {
62
65
  id: "files-fixture",
63
66
  version: "1",
64
67
  title: "files fixture",
68
+ restoreKind: "full_rootfs",
65
69
  install: [files.copy({ sourceDir: FIXTURE_DIR, destination: "/home/user/app/" })],
66
70
  });
67
71
  await applyTemplate(sandbox, definition);
@@ -82,6 +86,7 @@ describe("sandbox templates", () => {
82
86
  id: "empty-fixture",
83
87
  version: "1",
84
88
  title: "empty fixture",
89
+ restoreKind: "full_rootfs",
85
90
  install: [files.copy({ sourceDir: join(FIXTURE_DIR, "nested", "does-not-exist"), destination: "/home/user/app" })],
86
91
  });
87
92
  await expect(applyTemplate(sandbox, definition)).rejects.toThrow();
@@ -110,6 +115,7 @@ describe("sandbox templates", () => {
110
115
  id: "async-install",
111
116
  version: "1",
112
117
  title: "async install",
118
+ restoreKind: "full_rootfs",
113
119
  install: [run({ command: "sleep 120 && echo installed" })],
114
120
  }));
115
121
 
@@ -135,24 +141,37 @@ describe("sandbox templates", () => {
135
141
  id: "async-failure",
136
142
  version: "1",
137
143
  title: "async failure",
144
+ restoreKind: "full_rootfs",
138
145
  install: [run({ command: "exit 42" })],
139
146
  }))).rejects.toThrow("package exploded");
140
147
  });
141
148
 
142
149
  test("LFG owns every runtime dependency and zero-config transport default", () => {
143
150
  const lfg = agentTemplates.lfg;
144
- expect(templateVersionRef(lfg)).toBe("agent-lfg-v17");
151
+ expect(templateVersionRef(lfg)).toBe("agent-lfg-v33");
145
152
  expect(lfg.install).toContainEqual(apt.packages(["tmux"]));
146
153
  expect(lfg.checks).toContainEqual(check.command("tmux"));
154
+ expect(lfg.checks).toContainEqual(check.file("/home/user/lfg/omg-start.sh"));
155
+ expect(lfg.start?.command).toBe("exec /home/user/lfg/omg-start.sh");
147
156
  expect(lfg.start?.env?.LIVE_TRANSPORT).toBe("ws");
148
157
  expect(lfg.start?.readiness?.port).toBe(8766);
149
158
  expect(templateRuntimeContract(lfg, templateVersionRef(lfg))).toEqual({
150
- immutableRef: "agent-lfg-v17",
159
+ immutableRef: "agent-lfg-v33",
160
+ restoreKind: "full_rootfs",
151
161
  startCommand: "exec /home/user/.omg/template/bootstrap.sh",
152
162
  readinessPort: 8766,
153
163
  readinessPath: "/",
154
164
  ports: [8766, 5173],
155
165
  });
166
+
167
+ const commands = lfg.install
168
+ .filter((step): step is Extract<typeof step, { kind: "run" }> => step.kind === "run")
169
+ .map((step) => step.command);
170
+ const startScript = commands.find((command) => command.includes("OMG_START_EOF"));
171
+ expect(startScript).toBeDefined();
172
+ expect(startScript!).toContain("LFG_CONNECT_EVENTS=1");
173
+ expect(startScript!).toContain("relay-credentials.json");
174
+ expect(startScript!).toContain("trap cleanup EXIT INT TERM");
156
175
  });
157
176
 
158
177
  test("LFG bakes the react-ts scaffold into /home/user/project at bake time", () => {
@@ -169,6 +188,10 @@ describe("sandbox templates", () => {
169
188
  .filter((step): step is Extract<typeof step, { kind: "run" }> => step.kind === "run")
170
189
  .map((step) => step.command);
171
190
  expect(commands.some((c) => c.includes("recipe.sh"))).toBe(true);
172
- expect(lfg.metadata?.lfgRelease).toBe("v0.1.40");
191
+ expect(lfg.metadata?.lfgRelease).toBe("v0.1.167");
192
+ const lfgInstall = commands.find((c) => c.includes("bun install --production"));
193
+ expect(lfgInstall).toBeDefined();
194
+ expect(lfgInstall!).not.toContain("delete manifest.workspaces");
195
+ expect(lfgInstall!).not.toContain("rm -f bun.lock bun.lockb");
173
196
  });
174
197
  });
package/src/templates.ts CHANGED
@@ -34,6 +34,12 @@ export interface SandboxTemplate {
34
34
  version: string;
35
35
  title: string;
36
36
  description?: string;
37
+ /**
38
+ * Declares whether the recipe's complete mutable payload is confined to
39
+ * /home/user/project. Typed recipes must choose explicitly: the baker cannot
40
+ * safely infer arbitrary shell-command side effects.
41
+ */
42
+ restoreKind: "project_only" | "full_rootfs";
37
43
  ports?: readonly number[];
38
44
  install: readonly TemplateStep[];
39
45
  checks?: readonly TemplateCheck[];
@@ -89,6 +95,9 @@ export function defineTemplate<const T extends SandboxTemplate>(definition: T):
89
95
  assertTemplateId(definition.id);
90
96
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,19}$/.test(definition.version)) throw new Error("invalid template version");
91
97
  if (!definition.title.trim()) throw new Error("template title is required");
98
+ if (definition.restoreKind !== "project_only" && definition.restoreKind !== "full_rootfs") {
99
+ throw new Error("template restoreKind must be project_only or full_rootfs");
100
+ }
92
101
  for (const port of definition.ports ?? []) {
93
102
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`invalid template port ${port}`);
94
103
  }
@@ -136,6 +145,7 @@ export function templateDefinitionHash(definition: SandboxTemplate): string {
136
145
  export function templateRuntimeContract(definition: SandboxTemplate, immutableRef?: string) {
137
146
  return {
138
147
  immutableRef,
148
+ restoreKind: definition.restoreKind,
139
149
  startCommand: definition.start ? "exec /home/user/.omg/template/bootstrap.sh" : undefined,
140
150
  readinessPort: definition.start?.readiness?.port,
141
151
  readinessPath: definition.start?.readiness?.path,
@@ -189,6 +199,7 @@ export function compileStep(step: TemplateStep): { script: string; timeoutMs?: n
189
199
  export function compileStartScript(start: TemplateStart): string {
190
200
  const port = start.readiness?.port;
191
201
  const timeout = start.readiness?.timeoutSeconds ?? 60;
202
+ const supervisorMarker = "omg-template-supervisor";
192
203
  const env = { HOME: start.user ? `/home/${start.user}` : "/root", ...(start.env ?? {}) };
193
204
  const exports = shellEnvAssignments(env).map((assignment) => `export ${assignment}`).join("\n");
194
205
  const command = `${start.cwd ? `cd ${quote(start.cwd)} && ` : ""}${start.command}`;
@@ -196,7 +207,8 @@ export function compileStartScript(start: TemplateStart): string {
196
207
  const probe = port
197
208
  ? `for _ in $(seq 1 ${timeout}); do curl -fsS -m1 -o /dev/null ${quote(`http://127.0.0.1:${port}${start.readiness?.path ?? "/"}`)} && exit 0; sleep 1; done\necho 'template service did not become ready on :${port}' >&2\nexit 1`
198
209
  : "exit 0";
199
- return `#!/usr/bin/env bash\nset -euo pipefail\nmkdir -p /home/user/.omg/template\npidfile=/home/user/.omg/template/start.pid\nif [ -s \"$pidfile\" ] && kill -0 \"$(cat \"$pidfile\")\" 2>/dev/null; then exit 0; fi\nnohup /bin/bash -lc ${quote(`while true; do ${foreground} >>/home/user/.omg/template/start.log 2>&1 || true; sleep 2; done`)} >/dev/null 2>&1 &\necho $! > \"$pidfile\"\n${probe}\n`;
210
+ const supervisor = `while true; do ${foreground} >>/home/user/.omg/template/start.log 2>&1 || true; sleep 2; done`;
211
+ return `#!/usr/bin/env bash\nset -euo pipefail\nmkdir -p /home/user/.omg/template\npidfile=/home/user/.omg/template/start.pid\npid=\"\"\nif [ -s \"$pidfile\" ]; then pid=\"$(cat \"$pidfile\" 2>/dev/null || true)\"; fi\nsupervisor_alive=false\nif [[ \"$pid\" =~ ^[0-9]+$ ]] && kill -0 \"$pid\" 2>/dev/null && tr '\\0' '\\n' < \"/proc/$pid/cmdline\" 2>/dev/null | grep -Fxq ${quote(supervisorMarker)}; then supervisor_alive=true; fi\nif [ \"$supervisor_alive\" != true ]; then\n rm -f \"$pidfile\"\n nohup /bin/bash -lc ${quote(supervisor)} ${quote(supervisorMarker)} >/dev/null 2>&1 &\n echo $! > \"$pidfile\"\nfi\n${probe}\n`;
200
212
  }
201
213
 
202
214
  async function assertExec(label: string, result: { exitCode: number; stdout: string; stderr: string }): Promise<void> {
@@ -356,7 +368,7 @@ export async function bakeTemplate(
356
368
  let snapshotted = false;
357
369
  try {
358
370
  await applyTemplate(sandbox, definition, log);
359
- const snapshot = await sandbox.snapshot();
371
+ const snapshot = await sandbox.snapshot({ restoreKind: definition.restoreKind });
360
372
  snapshotted = true;
361
373
  const uploaded = await waitForSnapshotUpload(client, snapshot);
362
374
  // The immutable version ref is content-addressed: definition hash + the