warpmetal 0.8.3 → 0.8.4

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 CHANGED
@@ -296,6 +296,11 @@ compatible external signer.
296
296
  - Sandboxes use the fixed runtime image and fixed sizes. Persistent is the
297
297
  default; temporary sandboxes require explicit confirmation and permanently
298
298
  delete their workspace after 15 minutes to 24 hours.
299
+ - `sandbox action --action refresh_image --confirm refresh_image --wait`
300
+ explicitly replaces one sandbox's root filesystem with the current immutable
301
+ production image. It briefly disconnects active sessions but preserves the
302
+ external workspace, lifetime, and start time. The wait completes only when
303
+ the observed digest and generation both match the accepted target.
299
304
  - Guarded reload powers the server off first. Runtime-enabled reload requires a
300
305
  second acknowledgment, after which the CLI guides supervisor reinstall and
301
306
  pinned connection-profile refresh. Post-reload owner SSH host keys must be
@@ -319,6 +324,13 @@ warpmetal sandbox create \
319
324
  --size small \
320
325
  --wait \
321
326
  --json
327
+ warpmetal sandbox action \
328
+ --server <serverId> \
329
+ --sandbox <sandboxId> \
330
+ --action refresh_image \
331
+ --confirm refresh_image \
332
+ --wait \
333
+ --json
322
334
  warpmetal sandbox access keygen \
323
335
  --output ~/.ssh/warpmetal-planner \
324
336
  --confirm GENERATE \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "warpmetal",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Agent-safe CLI and skill for purchasing, renewing, and managing WarpMetal VPS servers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -215,7 +215,7 @@ warpmetal sandbox list --server <serverId> --json
215
215
  warpmetal sandbox get --server <serverId> --sandbox <sandboxId> [--wait] --json
216
216
  warpmetal sandbox action \
217
217
  --server <serverId> --sandbox <sandboxId> \
218
- --action <start|stop|restart|make_persistent> --confirm <same-action> \
218
+ --action <start|stop|restart|make_persistent|refresh_image> --confirm <same-action> \
219
219
  [--wait] --json
220
220
  warpmetal sandbox delete \
221
221
  --server <serverId> --sandbox <sandboxId> --confirm DELETE [--wait] --json
@@ -139,6 +139,25 @@ Use `sandbox list`, `sandbox get --wait`, and guarded `sandbox action` commands
139
139
  to observe and change desired state. HTTP 202 and CLI exit 8 mean accepted or
140
140
  pending, not complete.
141
141
 
142
+ Refresh an existing sandbox to the current immutable production image only
143
+ after the owner approves the brief connection interruption:
144
+
145
+ ```sh
146
+ warpmetal sandbox action \
147
+ --server <serverId> \
148
+ --sandbox <sandboxId> \
149
+ --action refresh_image \
150
+ --confirm refresh_image \
151
+ --wait \
152
+ --json
153
+ ```
154
+
155
+ The supervisor must already support image refresh. The action pre-pulls and
156
+ replaces only the container root filesystem while retaining the external
157
+ workspace, sandbox lifetime, and original start time. `--wait` requires both
158
+ the observed digest and generation to match the accepted target. A change to
159
+ the global production image does not refresh existing sandboxes implicitly.
160
+
142
161
  Manual deletion is irreversible:
143
162
 
144
163
  ```sh
package/src/api.js CHANGED
@@ -347,10 +347,14 @@ export class WarpMetalClient {
347
347
  }
348
348
 
349
349
  sandboxAction(serverId, sandboxId, action, token, idempotencyKey) {
350
+ const body =
351
+ action === "refresh_image"
352
+ ? { action, confirm: "refresh_image" }
353
+ : { action };
350
354
  return this.request(
351
355
  "POST",
352
356
  `/servers/${encodeURIComponent(serverId)}/sandboxes/${encodeURIComponent(sandboxId)}/actions`,
353
- { body: { action }, token, idempotencyKey },
357
+ { body, token, idempotencyKey },
354
358
  );
355
359
  }
356
360
 
package/src/cli.js CHANGED
@@ -114,6 +114,8 @@ Usage:
114
114
  warpmetal sandbox create --server <serverId> --name <name> --size <size>
115
115
  [--lifetime temporary] [--expires-in-seconds <n>] [--confirm TEMPORARY] [--wait]
116
116
  warpmetal sandbox create --server <serverId> --file <batch.json> [--confirm TEMPORARY]
117
+ warpmetal sandbox action --server <serverId> --sandbox <sandboxId>
118
+ --action <start|stop|restart|make_persistent|refresh_image> --confirm <same-action> [--wait]
117
119
  warpmetal sandbox list|get|action|delete ...
118
120
  warpmetal sandbox access keygen --output <private-key-path> --confirm GENERATE
119
121
  warpmetal sandbox access grant|list|get|revoke ...
@@ -2205,9 +2207,17 @@ async function handleSandboxAction(client, store, options, context) {
2205
2207
  const serverId = stringOption(options, "server", { required: true });
2206
2208
  const sandboxId = stringOption(options, "sandbox", { required: true });
2207
2209
  const action = stringOption(options, "action", { required: true });
2208
- if (!["start", "stop", "restart", "make_persistent"].includes(action)) {
2210
+ if (
2211
+ ![
2212
+ "start",
2213
+ "stop",
2214
+ "restart",
2215
+ "make_persistent",
2216
+ "refresh_image",
2217
+ ].includes(action)
2218
+ ) {
2209
2219
  throw new CliError(
2210
- "--action must be start, stop, restart, or make_persistent.",
2220
+ "--action must be start, stop, restart, make_persistent, or refresh_image.",
2211
2221
  {
2212
2222
  exitCode: 2,
2213
2223
  },
@@ -2231,6 +2241,11 @@ async function handleSandboxAction(client, store, options, context) {
2231
2241
  );
2232
2242
  if (booleanOption(options, "wait")) {
2233
2243
  const accepted = result.data.sandbox;
2244
+ if (action === "refresh_image" && !accepted.desiredImageDigest) {
2245
+ throw new CliError(
2246
+ "WarpMetal accepted image refresh without an immutable desired image digest.",
2247
+ );
2248
+ }
2234
2249
  const expectedState =
2235
2250
  accepted.desiredState === "stopped" ? "stopped" : "running";
2236
2251
  result = await pollSandbox(
@@ -2241,7 +2256,9 @@ async function handleSandboxAction(client, store, options, context) {
2241
2256
  integerOption(options, "timeout-seconds", 900),
2242
2257
  (sandbox) =>
2243
2258
  sandbox.observedState === expectedState &&
2244
- Number(sandbox.observedGeneration) >= Number(accepted.generation),
2259
+ Number(sandbox.observedGeneration) >= Number(accepted.generation) &&
2260
+ (action !== "refresh_image" ||
2261
+ sandbox.imageDigest === accepted.desiredImageDigest),
2245
2262
  );
2246
2263
  }
2247
2264
  await store.saveSandboxes(serverId, [result.data.sandbox]);