@sandbank.dev/boxlite 0.3.3 → 0.3.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/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +4 -7
- package/dist/local-client.d.ts.map +1 -1
- package/dist/local-client.js +122 -4
- package/package.json +2 -2
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,YAAY,EAGZ,UAAU,EACV,cAAc,EACd,WAAW,EAIZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,KAAK,EAAE,oBAAoB,EAA6B,MAAM,YAAY,CAAA;AAoKjF,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,IAAI,aAAY;IACzB,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAA;IAE9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAQ;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;gBAEtD,MAAM,EAAE,oBAAoB;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,YAAY,EAGZ,UAAU,EACV,cAAc,EACd,WAAW,EAIZ,MAAM,oBAAoB,CAAA;AAI3B,OAAO,KAAK,EAAE,oBAAoB,EAA6B,MAAM,YAAY,CAAA;AAoKjF,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,IAAI,aAAY;IACzB,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAA;IAE9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAQ;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;gBAEtD,MAAM,EAAE,oBAAoB;IASlC,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAiD5D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAW/C,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA0B1D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,8EAA8E;IACxE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
|
package/dist/adapter.js
CHANGED
|
@@ -153,19 +153,16 @@ export class BoxLiteAdapter {
|
|
|
153
153
|
this.config = config;
|
|
154
154
|
this.host = resolveHost(config);
|
|
155
155
|
this.client = createClient(config);
|
|
156
|
-
|
|
157
|
-
const caps = ['exec.stream', 'terminal', 'sleep', 'port.expose'];
|
|
158
|
-
if (config.mode !== 'local') {
|
|
159
|
-
caps.push('snapshot');
|
|
160
|
-
}
|
|
156
|
+
const caps = ['exec.stream', 'terminal', 'sleep', 'port.expose', 'snapshot'];
|
|
161
157
|
this.capabilities = new Set(caps);
|
|
162
158
|
}
|
|
163
159
|
async createSandbox(config) {
|
|
164
160
|
try {
|
|
165
161
|
// If image looks like an absolute path, treat it as a local OCI rootfs
|
|
166
|
-
const
|
|
162
|
+
const image = config.image ?? 'ubuntu:24.04';
|
|
163
|
+
const isLocalPath = image.startsWith('/');
|
|
167
164
|
const box = await this.client.createBox({
|
|
168
|
-
...(isLocalPath ? { rootfs_path:
|
|
165
|
+
...(isLocalPath ? { rootfs_path: image } : { image }),
|
|
169
166
|
cpu: config.resources?.cpu,
|
|
170
167
|
memory_mb: config.resources?.memory,
|
|
171
168
|
disk_size_gb: config.resources?.disk,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-client.d.ts","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,aAAa,EAGb,kBAAkB,EAEnB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"local-client.d.ts","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,aAAa,EAGb,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AAmfnB;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa,CAsRlF"}
|
package/dist/local-client.js
CHANGED
|
@@ -86,6 +86,9 @@ class Bridge:
|
|
|
86
86
|
if ports is not None:
|
|
87
87
|
sb_kwargs["ports"] = ports
|
|
88
88
|
|
|
89
|
+
# Disable auto_remove so runtime.get() works after stop (needed for snapshot restore)
|
|
90
|
+
sb_kwargs["auto_remove"] = params.get("auto_remove", False)
|
|
91
|
+
|
|
89
92
|
# Pass the Boxlite runtime to SimpleBox so it uses the correct home_dir
|
|
90
93
|
if getattr(self, "_boxlite_rt", None) is not None:
|
|
91
94
|
sb_kwargs["runtime"] = self._boxlite_rt
|
|
@@ -298,6 +301,111 @@ class Bridge:
|
|
|
298
301
|
if box and hasattr(box, "start"):
|
|
299
302
|
await box.start()
|
|
300
303
|
|
|
304
|
+
def _get_box(self, box_id):
|
|
305
|
+
box = self._boxes.get(box_id)
|
|
306
|
+
if box is None:
|
|
307
|
+
raise ValueError(f"Box not found: {box_id}")
|
|
308
|
+
# SimpleBox wraps the real Box — unwrap to get snapshot handle
|
|
309
|
+
inner = getattr(box, "_box", box)
|
|
310
|
+
return inner
|
|
311
|
+
|
|
312
|
+
async def create_snapshot(self, box_id, name):
|
|
313
|
+
# boxlite snapshot uses fork_qcow2 (rename + COW child). If QEMU is
|
|
314
|
+
# running, its FD still points to the renamed inode, so post-snapshot
|
|
315
|
+
# writes corrupt the snapshot file. Must stop → snapshot → restart.
|
|
316
|
+
inner = self._get_box(box_id)
|
|
317
|
+
await inner.stop()
|
|
318
|
+
|
|
319
|
+
rt = getattr(self, "_boxlite_rt", None)
|
|
320
|
+
if rt is None:
|
|
321
|
+
raise RuntimeError("Cannot create snapshot: no Boxlite runtime")
|
|
322
|
+
fresh = await rt.get(box_id)
|
|
323
|
+
if fresh is None:
|
|
324
|
+
raise RuntimeError(f"Cannot get fresh handle for box {box_id}")
|
|
325
|
+
snap_handle = getattr(fresh, "snapshot", None)
|
|
326
|
+
if snap_handle is None:
|
|
327
|
+
raise RuntimeError("Fresh handle has no snapshot support")
|
|
328
|
+
|
|
329
|
+
info = await snap_handle.create(name=name)
|
|
330
|
+
snap_name = str(getattr(info, "name", name))
|
|
331
|
+
|
|
332
|
+
# Restart the VM with the new COW child disk
|
|
333
|
+
await fresh.__aenter__()
|
|
334
|
+
# Update SimpleBox/Box internal references
|
|
335
|
+
old_sb = self._simple_boxes.get(box_id)
|
|
336
|
+
old_box = self._boxes.get(box_id)
|
|
337
|
+
if old_sb and hasattr(old_sb, "_box"):
|
|
338
|
+
old_sb._box = fresh
|
|
339
|
+
old_sb._started = True
|
|
340
|
+
if old_box and hasattr(old_box, "_box"):
|
|
341
|
+
old_box._box = fresh
|
|
342
|
+
else:
|
|
343
|
+
self._boxes[box_id] = fresh
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
"id": str(getattr(info, "id", snap_name)),
|
|
347
|
+
"box_id": box_id,
|
|
348
|
+
"name": snap_name,
|
|
349
|
+
"created_at": int(getattr(info, "created_at", 0)),
|
|
350
|
+
"size_bytes": int(getattr(info, "size_bytes", 0)),
|
|
351
|
+
"guest_disk_bytes": int(getattr(info, "guest_disk_bytes", 0) or 0),
|
|
352
|
+
"container_disk_bytes": int(getattr(info, "container_disk_bytes", 0) or 0),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async def restore_snapshot(self, box_id, name):
|
|
356
|
+
# Same stop → fresh handle pattern as create_snapshot.
|
|
357
|
+
# stop() also invalidates the LiteBox handle (cancels shutdown_token).
|
|
358
|
+
inner = self._get_box(box_id)
|
|
359
|
+
await inner.stop()
|
|
360
|
+
|
|
361
|
+
rt = getattr(self, "_boxlite_rt", None)
|
|
362
|
+
if rt is None:
|
|
363
|
+
raise RuntimeError("Cannot restore snapshot: no Boxlite runtime")
|
|
364
|
+
fresh = await rt.get(box_id)
|
|
365
|
+
if fresh is None:
|
|
366
|
+
raise RuntimeError(f"Cannot get fresh handle for box {box_id}")
|
|
367
|
+
fresh_snap = getattr(fresh, "snapshot", None)
|
|
368
|
+
if fresh_snap is None:
|
|
369
|
+
raise RuntimeError("Fresh handle has no snapshot support")
|
|
370
|
+
|
|
371
|
+
await fresh_snap.restore(name)
|
|
372
|
+
|
|
373
|
+
# Restart with the restored disk
|
|
374
|
+
await fresh.__aenter__()
|
|
375
|
+
# Update SimpleBox/Box internal references
|
|
376
|
+
old_sb = self._simple_boxes.get(box_id)
|
|
377
|
+
old_box = self._boxes.get(box_id)
|
|
378
|
+
if old_sb and hasattr(old_sb, "_box"):
|
|
379
|
+
old_sb._box = fresh
|
|
380
|
+
old_sb._started = True
|
|
381
|
+
if old_box and hasattr(old_box, "_box"):
|
|
382
|
+
old_box._box = fresh
|
|
383
|
+
else:
|
|
384
|
+
self._boxes[box_id] = fresh
|
|
385
|
+
|
|
386
|
+
async def list_snapshots(self, box_id):
|
|
387
|
+
inner = self._get_box(box_id)
|
|
388
|
+
snap_handle = getattr(inner, "snapshot", None)
|
|
389
|
+
if snap_handle is None:
|
|
390
|
+
raise RuntimeError("Box does not support snapshots")
|
|
391
|
+
snapshots = await snap_handle.list()
|
|
392
|
+
return [{
|
|
393
|
+
"id": str(getattr(s, "id", "")),
|
|
394
|
+
"box_id": box_id,
|
|
395
|
+
"name": str(getattr(s, "name", "")),
|
|
396
|
+
"created_at": int(getattr(s, "created_at", 0)),
|
|
397
|
+
"size_bytes": int(getattr(s, "size_bytes", 0)),
|
|
398
|
+
"guest_disk_bytes": int(getattr(s, "guest_disk_bytes", 0) or 0),
|
|
399
|
+
"container_disk_bytes": int(getattr(s, "container_disk_bytes", 0) or 0),
|
|
400
|
+
} for s in snapshots]
|
|
401
|
+
|
|
402
|
+
async def delete_snapshot(self, box_id, name):
|
|
403
|
+
inner = self._get_box(box_id)
|
|
404
|
+
snap_handle = getattr(inner, "snapshot", None)
|
|
405
|
+
if snap_handle is None:
|
|
406
|
+
raise RuntimeError("Box does not support snapshots")
|
|
407
|
+
await snap_handle.remove(name)
|
|
408
|
+
|
|
301
409
|
async def cleanup(self):
|
|
302
410
|
for box_id in list(self._boxes.keys()):
|
|
303
411
|
try:
|
|
@@ -349,6 +457,16 @@ async def main():
|
|
|
349
457
|
elif action == "stop":
|
|
350
458
|
await bridge.stop(cmd["box_id"])
|
|
351
459
|
result = {}
|
|
460
|
+
elif action == "create_snapshot":
|
|
461
|
+
result = await bridge.create_snapshot(cmd["box_id"], cmd["name"])
|
|
462
|
+
elif action == "restore_snapshot":
|
|
463
|
+
await bridge.restore_snapshot(cmd["box_id"], cmd["name"])
|
|
464
|
+
result = {}
|
|
465
|
+
elif action == "list_snapshots":
|
|
466
|
+
result = await bridge.list_snapshots(cmd["box_id"])
|
|
467
|
+
elif action == "delete_snapshot":
|
|
468
|
+
await bridge.delete_snapshot(cmd["box_id"], cmd["name"])
|
|
469
|
+
result = {}
|
|
352
470
|
elif action == "ping":
|
|
353
471
|
result = {"pong": True}
|
|
354
472
|
else:
|
|
@@ -580,16 +698,16 @@ export function createBoxLiteLocalClient(config) {
|
|
|
580
698
|
});
|
|
581
699
|
},
|
|
582
700
|
async createSnapshot(boxId, name) {
|
|
583
|
-
|
|
701
|
+
return send({ action: 'create_snapshot', box_id: boxId, name });
|
|
584
702
|
},
|
|
585
703
|
async restoreSnapshot(boxId, name) {
|
|
586
|
-
|
|
704
|
+
await send({ action: 'restore_snapshot', box_id: boxId, name });
|
|
587
705
|
},
|
|
588
706
|
async listSnapshots(boxId) {
|
|
589
|
-
|
|
707
|
+
return send({ action: 'list_snapshots', box_id: boxId });
|
|
590
708
|
},
|
|
591
709
|
async deleteSnapshot(boxId, name) {
|
|
592
|
-
|
|
710
|
+
await send({ action: 'delete_snapshot', box_id: boxId, name });
|
|
593
711
|
},
|
|
594
712
|
async dispose() {
|
|
595
713
|
if (process?.stdin?.writable) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandbank.dev/boxlite",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "BoxLite bare-metal sandbox adapter for Sandbank",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"clean": "rm -rf dist"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@sandbank.dev/core": "^0.3.
|
|
35
|
+
"@sandbank.dev/core": "^0.3.4"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.3.0",
|