feat(docker): export/import (move a container to another machine) + boot reaper

- src/docker-export.ts: full-image export (pause-consistent commit + save|stream +
  workspace tar + manifest -> one .codeman-container.tgz) and workspace-only; import
  validates manifest + per-member sha256, traversal-guards the workspace tar, docker
  load + quarantine re-tag (never overwrites a local tag). Bounded by
  runWithConversionLimit; free-space precheck; docker rmi in finally; sealed
  containers refuse full-image export.
- routes: POST /api/docker-cases/:name/export (background + SSE), GET/DELETE
  /api/docker-exports, GET download, POST /api/docker-cases/import (-> new host+case)
- instance-scoped boot reaper (docker-hosts.reapOrphanedDockerContainers) wired after
  restoreMuxSessions; never touches another instance's containers
- SSE docker:exportComplete/exportFailed/importComplete (both registries)
- fix: stream pipeline in saveImageToTar so the bundle isn't truncated

VERIFIED end-to-end on real docker: full export -> 326MB valid bundle -> delete
case -> import -> new container runs from the quarantined image with the workspace
file AND the in-image change both restored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-07-19 17:38:01 +02:00
parent 814362b67b
commit 39ef17b6af
8 changed files with 788 additions and 3 deletions
+13
View File
@@ -370,6 +370,14 @@ export const CaseDeleted = 'case:deleted' as const;
/** Case ordering changed. */
export const CaseOrderChanged = 'case:order-changed' as const;
// ─── Docker cases ────────────────────────────────────────────────────────────
/** A docker case export bundle finished writing. */
export const DockerExportComplete = 'docker:exportComplete' as const;
/** A docker case export failed. */
export const DockerExportFailed = 'docker:exportFailed' as const;
/** A docker bundle was imported into a new case. */
export const DockerImportComplete = 'docker:importComplete' as const;
// ─── Namespace Re-export ─────────────────────────────────────────────────────
/**
@@ -551,4 +559,9 @@ export const SseEvent = {
CaseLinked,
CaseDeleted,
CaseOrderChanged,
// Docker cases
DockerExportComplete,
DockerExportFailed,
DockerImportComplete,
} as const;