aicodeman 1.3.4 → 1.4.0
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/docker-export.d.ts +107 -0
- package/dist/docker-export.d.ts.map +1 -0
- package/dist/docker-export.js +335 -0
- package/dist/docker-export.js.map +1 -0
- package/dist/docker-hosts.d.ts +184 -0
- package/dist/docker-hosts.d.ts.map +1 -0
- package/dist/docker-hosts.js +506 -0
- package/dist/docker-hosts.js.map +1 -0
- package/dist/mux-interface.d.ts +7 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/session.d.ts +9 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +48 -4
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +50 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +214 -7
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/api.d.ts +9 -1
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js.map +1 -1
- package/dist/types/session.d.ts +102 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +13 -0
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/network-auth-policy.d.ts +9 -0
- package/dist/web/network-auth-policy.d.ts.map +1 -1
- package/dist/web/network-auth-policy.js +12 -0
- package/dist/web/network-auth-policy.js.map +1 -1
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.6890f3d0.js → app.07e61fcf.js} +1 -1
- package/dist/web/public/app.07e61fcf.js.br +0 -0
- package/dist/web/public/app.07e61fcf.js.gz +0 -0
- package/dist/web/public/{constants.5b18ed64.js → constants.503109fa.js} +3 -0
- package/dist/web/public/constants.503109fa.js.br +0 -0
- package/dist/web/public/{constants.5b18ed64.js.gz → constants.503109fa.js.gz} +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +139 -10
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
- package/dist/web/public/mobile.0644e3ac.css.gz +0 -0
- package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.ca03853f.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.9da1b71f.js +58 -0
- package/dist/web/public/session-ui.9da1b71f.js.br +0 -0
- package/dist/web/public/session-ui.9da1b71f.js.gz +0 -0
- package/dist/web/public/settings-ui.4acbe789.js +67 -0
- package/dist/web/public/settings-ui.4acbe789.js.br +0 -0
- package/dist/web/public/settings-ui.4acbe789.js.gz +0 -0
- package/dist/web/public/{styles.3d78fd0d.css → styles.546ddae4.css} +1 -1
- package/dist/web/public/styles.546ddae4.css.br +0 -0
- package/dist/web/public/styles.546ddae4.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +372 -3
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +67 -4
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +71 -1
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +155 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +11 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +81 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +9 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +11 -0
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.6890f3d0.js.br +0 -0
- package/dist/web/public/app.6890f3d0.js.gz +0 -0
- package/dist/web/public/constants.5b18ed64.js.br +0 -0
- package/dist/web/public/session-ui.c9094fe9.js +0 -49
- package/dist/web/public/session-ui.c9094fe9.js.br +0 -0
- package/dist/web/public/session-ui.c9094fe9.js.gz +0 -0
- package/dist/web/public/settings-ui.9538a0c1.js +0 -67
- package/dist/web/public/settings-ui.9538a0c1.js.br +0 -0
- package/dist/web/public/settings-ui.9538a0c1.js.gz +0 -0
- package/dist/web/public/styles.3d78fd0d.css.br +0 -0
- package/dist/web/public/styles.3d78fd0d.css.gz +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Docker case export / import: move a container (toolchain + any
|
|
3
|
+
* in-image changes) PLUS its workspace to another machine as one portable
|
|
4
|
+
* `.codeman-container.tgz`, and restore it.
|
|
5
|
+
*
|
|
6
|
+
* A full-image export = `docker commit` the running container to an image ->
|
|
7
|
+
* `docker save` that image -> tar the bind-mounted workspace -> a manifest, all
|
|
8
|
+
* bundled into one gzip tarball. A workspace-only export skips the image (fast,
|
|
9
|
+
* files-only). Import validates the manifest + per-member checksums, extracts the
|
|
10
|
+
* workspace with a path-traversal guard, `docker load`s the image and RE-TAGS it
|
|
11
|
+
* into a quarantined namespace (never overwriting a local tag), and hands the
|
|
12
|
+
* caller enough to recreate a hardened case on the destination.
|
|
13
|
+
*
|
|
14
|
+
* Safety (all from the design critic): pause the container spanning the workspace
|
|
15
|
+
* tar AND the commit so the two artifacts are mutually consistent; a free-space
|
|
16
|
+
* precheck (a full docker graph wedges EVERY session on the host); `docker rmi`
|
|
17
|
+
* the intermediate image in a finally; sealed containers refuse a full-image
|
|
18
|
+
* export (an in-container login would ride the committed layer); import rejects
|
|
19
|
+
* absolute / `..` tar members and checksum mismatches. Bounded by
|
|
20
|
+
* runWithConversionLimit so N exports cannot fork-bomb the host.
|
|
21
|
+
*
|
|
22
|
+
* @module docker-export
|
|
23
|
+
*/
|
|
24
|
+
import type { DockerEngine, SessionDocker } from './types.js';
|
|
25
|
+
/** Refuse to export when the target filesystem has less than this free (a full graph wedges the daemon). */
|
|
26
|
+
export declare const DOCKER_EXPORT_MIN_FREE_BYTES: number;
|
|
27
|
+
/** Manifest schema version (bump on any breaking field change). */
|
|
28
|
+
export declare const DOCKER_EXPORT_SCHEMA = 1;
|
|
29
|
+
export type DockerExportMode = 'full' | 'workspace';
|
|
30
|
+
export interface DockerExportManifest {
|
|
31
|
+
schemaVersion: number;
|
|
32
|
+
caseName: string;
|
|
33
|
+
mode: DockerExportMode;
|
|
34
|
+
engine: DockerEngine;
|
|
35
|
+
image: string;
|
|
36
|
+
containerWorkdir: string;
|
|
37
|
+
network: string;
|
|
38
|
+
createdAt: number;
|
|
39
|
+
codemanVersion: string;
|
|
40
|
+
mountCredentials: boolean;
|
|
41
|
+
/** True when the bundle provably carries no credentials (convenient-mode workspace, or a full image whose creds were bind-mounted and thus never committed). */
|
|
42
|
+
secretFree: boolean;
|
|
43
|
+
/** sha256 of each bundle member that is present. */
|
|
44
|
+
checksums: {
|
|
45
|
+
image?: string;
|
|
46
|
+
workspace?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Raw argv prefix for the engine (NO shell escaping — used with spawn). */
|
|
50
|
+
export declare function dockerArgv(docker: Pick<SessionDocker, 'engine' | 'context' | 'daemonHost'>): string[];
|
|
51
|
+
/** Portable bundle filename for a case export. */
|
|
52
|
+
export declare function exportBundleName(caseName: string, timestamp: number, mode: DockerExportMode): string;
|
|
53
|
+
/** Quarantined image tag for an imported bundle (never overwrites a local tag). */
|
|
54
|
+
export declare function importedImageTag(caseName: string, timestamp: number): string;
|
|
55
|
+
/** Intermediate commit tag for a full-image export (unique per export, rmi'd in finally). */
|
|
56
|
+
export declare function exportImageTag(caseName: string, timestamp: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* Reject a tar member path that would escape the extraction root (absolute path
|
|
59
|
+
* or a `..` component). The import-side traversal guard.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isSafeTarMember(member: string): boolean;
|
|
62
|
+
/** Parse the image id/ref from `docker load` output ("Loaded image: x" / "Loaded image ID: sha256:..."). */
|
|
63
|
+
export declare function parseLoadedImageRef(loadOutput: string): string | null;
|
|
64
|
+
export interface ExportResult {
|
|
65
|
+
bundlePath: string;
|
|
66
|
+
manifest: DockerExportManifest;
|
|
67
|
+
sizeBytes: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Export a docker case to a portable bundle. Bounded by runWithConversionLimit.
|
|
71
|
+
* `full` mode commits + saves the image AND tars the workspace; `workspace` mode
|
|
72
|
+
* tars just the workspace. The container is paused across the artifact capture so
|
|
73
|
+
* image and workspace are mutually consistent.
|
|
74
|
+
*/
|
|
75
|
+
export declare function exportDockerCase(params: {
|
|
76
|
+
docker: SessionDocker;
|
|
77
|
+
caseName: string;
|
|
78
|
+
timestamp: number;
|
|
79
|
+
exportsDir: string;
|
|
80
|
+
mode: DockerExportMode;
|
|
81
|
+
codemanVersion: string;
|
|
82
|
+
}): Promise<ExportResult>;
|
|
83
|
+
export interface ImportResult {
|
|
84
|
+
manifest: DockerExportManifest;
|
|
85
|
+
/** Quarantined image ref the destination case should use (full mode only). */
|
|
86
|
+
importedImage?: string;
|
|
87
|
+
/** Directory the workspace was extracted into. */
|
|
88
|
+
workspacePath: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Import a bundle produced by exportDockerCase: validate the manifest + per-member
|
|
92
|
+
* checksums, extract the workspace (traversal-guarded) into destWorkspace, and, in
|
|
93
|
+
* full mode, `docker load` the image and re-tag it into a quarantined namespace.
|
|
94
|
+
*/
|
|
95
|
+
export declare function importDockerBundle(params: {
|
|
96
|
+
bundlePath: string;
|
|
97
|
+
destWorkspace: string;
|
|
98
|
+
engine: DockerEngine;
|
|
99
|
+
timestamp: number;
|
|
100
|
+
}): Promise<ImportResult>;
|
|
101
|
+
/** List export bundles in the exports dir (newest first), with size + mtime. */
|
|
102
|
+
export declare function listDockerExports(exportsDir: string): Promise<Array<{
|
|
103
|
+
name: string;
|
|
104
|
+
sizeBytes: number;
|
|
105
|
+
mtimeMs: number;
|
|
106
|
+
}>>;
|
|
107
|
+
//# sourceMappingURL=docker-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-export.d.ts","sourceRoot":"","sources":["../src/docker-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Docker case export / import: move a container (toolchain + any
|
|
3
|
+
* in-image changes) PLUS its workspace to another machine as one portable
|
|
4
|
+
* `.codeman-container.tgz`, and restore it.
|
|
5
|
+
*
|
|
6
|
+
* A full-image export = `docker commit` the running container to an image ->
|
|
7
|
+
* `docker save` that image -> tar the bind-mounted workspace -> a manifest, all
|
|
8
|
+
* bundled into one gzip tarball. A workspace-only export skips the image (fast,
|
|
9
|
+
* files-only). Import validates the manifest + per-member checksums, extracts the
|
|
10
|
+
* workspace with a path-traversal guard, `docker load`s the image and RE-TAGS it
|
|
11
|
+
* into a quarantined namespace (never overwriting a local tag), and hands the
|
|
12
|
+
* caller enough to recreate a hardened case on the destination.
|
|
13
|
+
*
|
|
14
|
+
* Safety (all from the design critic): pause the container spanning the workspace
|
|
15
|
+
* tar AND the commit so the two artifacts are mutually consistent; a free-space
|
|
16
|
+
* precheck (a full docker graph wedges EVERY session on the host); `docker rmi`
|
|
17
|
+
* the intermediate image in a finally; sealed containers refuse a full-image
|
|
18
|
+
* export (an in-container login would ride the committed layer); import rejects
|
|
19
|
+
* absolute / `..` tar members and checksum mismatches. Bounded by
|
|
20
|
+
* runWithConversionLimit so N exports cannot fork-bomb the host.
|
|
21
|
+
*
|
|
22
|
+
* @module docker-export
|
|
23
|
+
*/
|
|
24
|
+
import { createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs';
|
|
25
|
+
import fs from 'node:fs/promises';
|
|
26
|
+
import { join, basename } from 'node:path';
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { spawn } from 'node:child_process';
|
|
29
|
+
import { pipeline } from 'node:stream/promises';
|
|
30
|
+
import { runWithConversionLimit } from './document-conversion-limiter.js';
|
|
31
|
+
const IS_TEST_MODE = !!process.env.VITEST;
|
|
32
|
+
/** Refuse to export when the target filesystem has less than this free (a full graph wedges the daemon). */
|
|
33
|
+
export const DOCKER_EXPORT_MIN_FREE_BYTES = 2 * 1024 * 1024 * 1024; // 2 GiB
|
|
34
|
+
/** Manifest schema version (bump on any breaking field change). */
|
|
35
|
+
export const DOCKER_EXPORT_SCHEMA = 1;
|
|
36
|
+
// ========== Pure helpers (unit-tested) ==========
|
|
37
|
+
/** Raw argv prefix for the engine (NO shell escaping — used with spawn). */
|
|
38
|
+
export function dockerArgv(docker) {
|
|
39
|
+
const argv = [docker.engine === 'podman' ? 'podman' : 'docker'];
|
|
40
|
+
if (docker.context)
|
|
41
|
+
argv.push('--context', docker.context);
|
|
42
|
+
if (docker.daemonHost)
|
|
43
|
+
argv.push('-H', docker.daemonHost);
|
|
44
|
+
return argv;
|
|
45
|
+
}
|
|
46
|
+
/** Portable bundle filename for a case export. */
|
|
47
|
+
export function exportBundleName(caseName, timestamp, mode) {
|
|
48
|
+
const suffix = mode === 'workspace' ? 'workspace' : 'container';
|
|
49
|
+
return `${caseName}-${timestamp}.codeman-${suffix}.tgz`;
|
|
50
|
+
}
|
|
51
|
+
/** Quarantined image tag for an imported bundle (never overwrites a local tag). */
|
|
52
|
+
export function importedImageTag(caseName, timestamp) {
|
|
53
|
+
return `codeman/imported-${caseName}:${timestamp}`;
|
|
54
|
+
}
|
|
55
|
+
/** Intermediate commit tag for a full-image export (unique per export, rmi'd in finally). */
|
|
56
|
+
export function exportImageTag(caseName, timestamp) {
|
|
57
|
+
return `codeman/export-${caseName}:${timestamp}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reject a tar member path that would escape the extraction root (absolute path
|
|
61
|
+
* or a `..` component). The import-side traversal guard.
|
|
62
|
+
*/
|
|
63
|
+
export function isSafeTarMember(member) {
|
|
64
|
+
const trimmed = member.trim();
|
|
65
|
+
if (!trimmed || trimmed === './')
|
|
66
|
+
return true;
|
|
67
|
+
if (trimmed.startsWith('/'))
|
|
68
|
+
return false;
|
|
69
|
+
// Normalize separators and check each component.
|
|
70
|
+
return !trimmed.split('/').some((part) => part === '..');
|
|
71
|
+
}
|
|
72
|
+
/** Parse the image id/ref from `docker load` output ("Loaded image: x" / "Loaded image ID: sha256:..."). */
|
|
73
|
+
export function parseLoadedImageRef(loadOutput) {
|
|
74
|
+
const idMatch = loadOutput.match(/Loaded image ID:\s*(sha256:[0-9a-f]+)/i);
|
|
75
|
+
if (idMatch)
|
|
76
|
+
return idMatch[1];
|
|
77
|
+
const refMatch = loadOutput.match(/Loaded image:\s*(\S+)/i);
|
|
78
|
+
if (refMatch)
|
|
79
|
+
return refMatch[1];
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// ========== IO helpers ==========
|
|
83
|
+
function run(cmd, args, opts = {}) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
86
|
+
let stdout = '';
|
|
87
|
+
let stderr = '';
|
|
88
|
+
let timer;
|
|
89
|
+
if (opts.timeout) {
|
|
90
|
+
timer = setTimeout(() => {
|
|
91
|
+
child.kill('SIGKILL');
|
|
92
|
+
reject(new Error(`${cmd} timed out after ${opts.timeout}ms`));
|
|
93
|
+
}, opts.timeout);
|
|
94
|
+
}
|
|
95
|
+
child.stdout.on('data', (d) => (stdout += d));
|
|
96
|
+
child.stderr.on('data', (d) => (stderr += d));
|
|
97
|
+
child.on('error', (err) => {
|
|
98
|
+
if (timer)
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
reject(err);
|
|
101
|
+
});
|
|
102
|
+
child.on('close', (code) => {
|
|
103
|
+
if (timer)
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
if (code === 0)
|
|
106
|
+
resolve({ stdout, stderr });
|
|
107
|
+
else
|
|
108
|
+
reject(new Error(`${cmd} ${args.join(' ')} exited ${code}: ${stderr.trim()}`));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Stream `docker save <tag>` stdout to a raw tar file (no shell, no double-gzip).
|
|
114
|
+
* Uses stream `pipeline` so completion means the write stream is FULLY flushed to
|
|
115
|
+
* disk (a naive child 'close' resolves before the last chunks land, truncating the
|
|
116
|
+
* file — a real bug caught in end-to-end testing), AND waits for a clean exit code.
|
|
117
|
+
*/
|
|
118
|
+
async function saveImageToTar(argv, tag, outPath) {
|
|
119
|
+
const child = spawn(argv[0], [...argv.slice(1), 'save', tag], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
120
|
+
let stderr = '';
|
|
121
|
+
child.stderr.on('data', (d) => (stderr += d));
|
|
122
|
+
const exited = new Promise((resolve, reject) => {
|
|
123
|
+
child.on('error', reject);
|
|
124
|
+
child.on('close', (code) => code === 0 ? resolve() : reject(new Error(`docker save exited ${code}: ${stderr.trim()}`)));
|
|
125
|
+
});
|
|
126
|
+
// pipeline resolves only after the destination has fully flushed.
|
|
127
|
+
await Promise.all([pipeline(child.stdout, createWriteStream(outPath)), exited]);
|
|
128
|
+
}
|
|
129
|
+
async function sha256File(path) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const hash = createHash('sha256');
|
|
132
|
+
const stream = createReadStream(path);
|
|
133
|
+
stream.on('data', (d) => hash.update(d));
|
|
134
|
+
stream.on('error', reject);
|
|
135
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function freeBytes(path) {
|
|
139
|
+
try {
|
|
140
|
+
const stat = await fs.statfs(path);
|
|
141
|
+
return Number(stat.bavail) * Number(stat.bsize);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return Number.POSITIVE_INFINITY; // statfs unsupported — don't block
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function isContainerRunning(argv, container) {
|
|
148
|
+
try {
|
|
149
|
+
const { stdout } = await run(argv[0], [...argv.slice(1), 'inspect', '-f', '{{.State.Running}}', container], {
|
|
150
|
+
timeout: 15_000,
|
|
151
|
+
});
|
|
152
|
+
return stdout.trim() === 'true';
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Export a docker case to a portable bundle. Bounded by runWithConversionLimit.
|
|
160
|
+
* `full` mode commits + saves the image AND tars the workspace; `workspace` mode
|
|
161
|
+
* tars just the workspace. The container is paused across the artifact capture so
|
|
162
|
+
* image and workspace are mutually consistent.
|
|
163
|
+
*/
|
|
164
|
+
export async function exportDockerCase(params) {
|
|
165
|
+
const { docker, caseName, timestamp, exportsDir, mode, codemanVersion } = params;
|
|
166
|
+
if (mode === 'full' && !docker.mountCredentials) {
|
|
167
|
+
throw new Error('full-image export is refused for a sealed (mountCredentials:false) container: an in-container login would ride the committed image layer. Use a workspace-only export.');
|
|
168
|
+
}
|
|
169
|
+
if (IS_TEST_MODE) {
|
|
170
|
+
// No real docker/tar under vitest — return a deterministic stub.
|
|
171
|
+
const manifest = {
|
|
172
|
+
schemaVersion: DOCKER_EXPORT_SCHEMA,
|
|
173
|
+
caseName,
|
|
174
|
+
mode,
|
|
175
|
+
engine: docker.engine,
|
|
176
|
+
image: docker.image,
|
|
177
|
+
containerWorkdir: docker.containerWorkdir,
|
|
178
|
+
network: docker.network,
|
|
179
|
+
createdAt: timestamp,
|
|
180
|
+
codemanVersion,
|
|
181
|
+
mountCredentials: docker.mountCredentials,
|
|
182
|
+
secretFree: true,
|
|
183
|
+
checksums: {},
|
|
184
|
+
};
|
|
185
|
+
return { bundlePath: join(exportsDir, exportBundleName(caseName, timestamp, mode)), manifest, sizeBytes: 0 };
|
|
186
|
+
}
|
|
187
|
+
return runWithConversionLimit(async () => {
|
|
188
|
+
if (!existsSync(exportsDir))
|
|
189
|
+
mkdirSync(exportsDir, { recursive: true });
|
|
190
|
+
const free = await freeBytes(exportsDir);
|
|
191
|
+
if (free < DOCKER_EXPORT_MIN_FREE_BYTES) {
|
|
192
|
+
throw new Error(`not enough free space to export (need >= ${Math.round(DOCKER_EXPORT_MIN_FREE_BYTES / 1e9)}GB, have ${Math.round(free / 1e9)}GB). A full docker graph wedges every session on the host.`);
|
|
193
|
+
}
|
|
194
|
+
const argv = dockerArgv(docker);
|
|
195
|
+
const bundlePath = join(exportsDir, exportBundleName(caseName, timestamp, mode));
|
|
196
|
+
const stageDir = join(exportsDir, `.stage-${caseName}-${timestamp}`);
|
|
197
|
+
mkdirSync(stageDir, { recursive: true });
|
|
198
|
+
const wasRunning = await isContainerRunning(argv, docker.containerName);
|
|
199
|
+
let commitTag;
|
|
200
|
+
try {
|
|
201
|
+
if (wasRunning) {
|
|
202
|
+
await run(argv[0], [...argv.slice(1), 'pause', docker.containerName], { timeout: 30_000 }).catch(() => { });
|
|
203
|
+
}
|
|
204
|
+
const checksums = {};
|
|
205
|
+
if (mode === 'full') {
|
|
206
|
+
commitTag = exportImageTag(caseName, timestamp);
|
|
207
|
+
// Blank instance-specific committed env so the image carries no stale host refs.
|
|
208
|
+
await run(argv[0], [
|
|
209
|
+
...argv.slice(1),
|
|
210
|
+
'commit',
|
|
211
|
+
'-c',
|
|
212
|
+
'ENV CODEMAN_API_URL=',
|
|
213
|
+
'-c',
|
|
214
|
+
'ENV CODEMAN_HOOK_SECRET_FILE=',
|
|
215
|
+
docker.containerName,
|
|
216
|
+
commitTag,
|
|
217
|
+
], { timeout: 300_000 });
|
|
218
|
+
const imageTar = join(stageDir, 'image.tar');
|
|
219
|
+
await saveImageToTar(argv, commitTag, imageTar);
|
|
220
|
+
checksums.image = await sha256File(imageTar);
|
|
221
|
+
}
|
|
222
|
+
const workspaceTar = join(stageDir, 'workspace.tar');
|
|
223
|
+
await run('tar', ['-cf', workspaceTar, '-C', docker.hostWorkspacePath, '.'], { timeout: 300_000 });
|
|
224
|
+
checksums.workspace = await sha256File(workspaceTar);
|
|
225
|
+
const manifest = {
|
|
226
|
+
schemaVersion: DOCKER_EXPORT_SCHEMA,
|
|
227
|
+
caseName,
|
|
228
|
+
mode,
|
|
229
|
+
engine: docker.engine,
|
|
230
|
+
image: docker.image,
|
|
231
|
+
containerWorkdir: docker.containerWorkdir,
|
|
232
|
+
network: docker.network,
|
|
233
|
+
createdAt: timestamp,
|
|
234
|
+
codemanVersion,
|
|
235
|
+
mountCredentials: docker.mountCredentials,
|
|
236
|
+
// Convenient mode keeps creds on bind mounts (never committed), so the bundle is secret-free.
|
|
237
|
+
secretFree: docker.mountCredentials,
|
|
238
|
+
checksums,
|
|
239
|
+
};
|
|
240
|
+
await fs.writeFile(join(stageDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
241
|
+
const members = mode === 'full' ? ['manifest.json', 'image.tar', 'workspace.tar'] : ['manifest.json', 'workspace.tar'];
|
|
242
|
+
await run('tar', ['-czf', bundlePath, '-C', stageDir, ...members], { timeout: 300_000 });
|
|
243
|
+
const stat = await fs.stat(bundlePath);
|
|
244
|
+
return { bundlePath, manifest, sizeBytes: stat.size };
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
// Always remove the intermediate image + stage dir, and unpause.
|
|
248
|
+
if (commitTag) {
|
|
249
|
+
await run(argv[0], [...argv.slice(1), 'rmi', commitTag], { timeout: 60_000 }).catch(() => { });
|
|
250
|
+
}
|
|
251
|
+
await fs.rm(stageDir, { recursive: true, force: true }).catch(() => { });
|
|
252
|
+
if (wasRunning) {
|
|
253
|
+
await run(argv[0], [...argv.slice(1), 'unpause', docker.containerName], { timeout: 30_000 }).catch(() => { });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Import a bundle produced by exportDockerCase: validate the manifest + per-member
|
|
260
|
+
* checksums, extract the workspace (traversal-guarded) into destWorkspace, and, in
|
|
261
|
+
* full mode, `docker load` the image and re-tag it into a quarantined namespace.
|
|
262
|
+
*/
|
|
263
|
+
export async function importDockerBundle(params) {
|
|
264
|
+
const { bundlePath, destWorkspace, engine, timestamp } = params;
|
|
265
|
+
const argv = [engine === 'podman' ? 'podman' : 'docker'];
|
|
266
|
+
if (IS_TEST_MODE) {
|
|
267
|
+
const raw = await fs.readFile(bundlePath, 'utf-8').catch(() => '{}');
|
|
268
|
+
return { manifest: JSON.parse(raw), workspacePath: destWorkspace };
|
|
269
|
+
}
|
|
270
|
+
const stageDir = `${destWorkspace}.import-stage-${timestamp}`;
|
|
271
|
+
mkdirSync(stageDir, { recursive: true });
|
|
272
|
+
try {
|
|
273
|
+
await run('tar', ['-xzf', bundlePath, '-C', stageDir], { timeout: 300_000 });
|
|
274
|
+
const manifestRaw = await fs.readFile(join(stageDir, 'manifest.json'), 'utf-8');
|
|
275
|
+
const manifest = JSON.parse(manifestRaw);
|
|
276
|
+
if (manifest.schemaVersion !== DOCKER_EXPORT_SCHEMA) {
|
|
277
|
+
throw new Error(`unsupported export schema version ${manifest.schemaVersion} (expected ${DOCKER_EXPORT_SCHEMA})`);
|
|
278
|
+
}
|
|
279
|
+
// Integrity: verify checksums before trusting any member.
|
|
280
|
+
const workspaceTar = join(stageDir, 'workspace.tar');
|
|
281
|
+
if (manifest.checksums.workspace) {
|
|
282
|
+
const actual = await sha256File(workspaceTar);
|
|
283
|
+
if (actual !== manifest.checksums.workspace)
|
|
284
|
+
throw new Error('workspace checksum mismatch (corrupt or tampered bundle)');
|
|
285
|
+
}
|
|
286
|
+
// Traversal guard: reject absolute / `..` members before extraction.
|
|
287
|
+
const { stdout: memberList } = await run('tar', ['-tf', workspaceTar], { timeout: 60_000 });
|
|
288
|
+
for (const member of memberList.split('\n').filter(Boolean)) {
|
|
289
|
+
if (!isSafeTarMember(member))
|
|
290
|
+
throw new Error(`unsafe path in workspace archive: ${member}`);
|
|
291
|
+
}
|
|
292
|
+
mkdirSync(destWorkspace, { recursive: true });
|
|
293
|
+
await run('tar', ['--no-same-owner', '-xf', workspaceTar, '-C', destWorkspace], { timeout: 300_000 });
|
|
294
|
+
let importedImage;
|
|
295
|
+
if (manifest.mode === 'full') {
|
|
296
|
+
const imageTar = join(stageDir, 'image.tar');
|
|
297
|
+
if (manifest.checksums.image) {
|
|
298
|
+
const actual = await sha256File(imageTar);
|
|
299
|
+
if (actual !== manifest.checksums.image)
|
|
300
|
+
throw new Error('image checksum mismatch (corrupt or tampered bundle)');
|
|
301
|
+
}
|
|
302
|
+
const { stdout } = await run(argv[0], [...argv.slice(1), 'load', '-i', imageTar], { timeout: 300_000 });
|
|
303
|
+
const loadedRef = parseLoadedImageRef(stdout);
|
|
304
|
+
if (!loadedRef)
|
|
305
|
+
throw new Error('could not determine loaded image ref');
|
|
306
|
+
// Quarantine: re-tag by the loaded ref/id, never trusting the bundle's original tag.
|
|
307
|
+
importedImage = importedImageTag(manifest.caseName, timestamp);
|
|
308
|
+
await run(argv[0], [...argv.slice(1), 'tag', loadedRef, importedImage], { timeout: 60_000 });
|
|
309
|
+
}
|
|
310
|
+
return { manifest, importedImage, workspacePath: destWorkspace };
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
await fs.rm(stageDir, { recursive: true, force: true }).catch(() => { });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** List export bundles in the exports dir (newest first), with size + mtime. */
|
|
317
|
+
export async function listDockerExports(exportsDir) {
|
|
318
|
+
if (!existsSync(exportsDir))
|
|
319
|
+
return [];
|
|
320
|
+
const entries = await fs.readdir(exportsDir).catch(() => []);
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const name of entries) {
|
|
323
|
+
if (!name.endsWith('.tgz'))
|
|
324
|
+
continue;
|
|
325
|
+
try {
|
|
326
|
+
const stat = await fs.stat(join(exportsDir, name));
|
|
327
|
+
out.push({ name: basename(name), sizeBytes: stat.size, mtimeMs: stat.mtimeMs });
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
/* skip */
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=docker-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-export.js","sourceRoot":"","sources":["../src/docker-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Docker cases: storage, pure command-arg builders, and daemon probes.
|
|
3
|
+
*
|
|
4
|
+
* Docker mode is a LOCATION OVERLAY on cases (not a 6th SessionMode), the direct
|
|
5
|
+
* analog of the remote-SSH feature in `remote-hosts.ts`. Instead of a local tmux
|
|
6
|
+
* pane running `ssh host` into a durable remote tmux server, a local tmux pane
|
|
7
|
+
* runs `docker exec -it` into a durable IN-CONTAINER tmux server. The container is
|
|
8
|
+
* scoped to the CASE (`codeman-case-<name>`), so multiple sessions can `docker
|
|
9
|
+
* exec` into the same long-lived container.
|
|
10
|
+
*
|
|
11
|
+
* This module mirrors `remote-hosts.ts`:
|
|
12
|
+
* - JSON storage for hosts (`docker-hosts.json`) and cases (`docker-cases.json`)
|
|
13
|
+
* - `toSessionDocker()` (mirror of `toSessionRemote`)
|
|
14
|
+
* - `buildDockerBaseArgs()` / `buildDockerCreateArgs()` (mirror of `buildSshConnectionArgs`)
|
|
15
|
+
* - `checkDockerAvailable()` / `checkDockerTmuxAvailable()` (mirror of `checkRemoteTmuxAvailable`)
|
|
16
|
+
*
|
|
17
|
+
* The launch/kill command orchestration (`buildDockerLaunchCommand`,
|
|
18
|
+
* `buildDockerKillCommand`, `dockerTmuxSessionName`) lives in `tmux-manager.ts`,
|
|
19
|
+
* mirroring where `buildRemoteLaunchCommand` lives.
|
|
20
|
+
*
|
|
21
|
+
* @module docker-hosts
|
|
22
|
+
*/
|
|
23
|
+
import type { DockerCase, DockerEngine, DockerHost, DockerResourceLimits, SessionDocker, SessionMode } from './types.js';
|
|
24
|
+
/** Locally-built base image (see scripts/build-agent-image.mjs). */
|
|
25
|
+
export declare const DEFAULT_AGENT_IMAGE = "codeman/agent:base";
|
|
26
|
+
/** HOME inside the base image (the `agent` user). Cred mounts + hook-secret land under it. */
|
|
27
|
+
export declare const CONTAINER_HOME = "/home/agent";
|
|
28
|
+
/** Sensible resource defaults (all overridable per host). */
|
|
29
|
+
export declare const DEFAULT_DOCKER_RESOURCES: DockerResourceLimits;
|
|
30
|
+
export declare function dockerHostsPath(configDir: string): string;
|
|
31
|
+
export declare function dockerCasesPath(configDir: string): string;
|
|
32
|
+
export declare function readDockerHosts(configDir: string): Promise<DockerHost[]>;
|
|
33
|
+
export declare function writeDockerHosts(configDir: string, hosts: DockerHost[]): Promise<void>;
|
|
34
|
+
export declare function readDockerCases(configDir: string): Promise<DockerCase[]>;
|
|
35
|
+
export declare function writeDockerCases(configDir: string, cases: DockerCase[]): Promise<void>;
|
|
36
|
+
/** Per-case container name. Mirrors how remote derives a stable name from the case. */
|
|
37
|
+
export declare function dockerContainerName(caseName: string): string;
|
|
38
|
+
/** Default pane command per CLI mode (mirror of defaultRemoteCommandForMode). */
|
|
39
|
+
export declare function defaultDockerCommandForMode(mode: SessionMode): string;
|
|
40
|
+
/** `container:/workdir` display string (mirror of remoteDisplayPath's `user@host:path`). */
|
|
41
|
+
export declare function dockerDisplayPath(docker: Pick<SessionDocker, 'containerName' | 'containerWorkdir'> | {
|
|
42
|
+
container: string;
|
|
43
|
+
path: string;
|
|
44
|
+
}): string;
|
|
45
|
+
/**
|
|
46
|
+
* The host-callback gateway alias is ENGINE-SPECIFIC: Docker exposes the host as
|
|
47
|
+
* `host.docker.internal`, Podman as `host.containers.internal`. Both are added to
|
|
48
|
+
* the host-guard allowlist so a mixed fleet keeps working.
|
|
49
|
+
*/
|
|
50
|
+
export declare function hostGatewayAlias(engine: DockerEngine): string;
|
|
51
|
+
/**
|
|
52
|
+
* Rewrite the server's own `CODEMAN_API_URL` to a container-reachable one by
|
|
53
|
+
* swapping ONLY the hostname for the engine's host-gateway alias, preserving
|
|
54
|
+
* scheme AND port (prod is HTTPS on 3000, so hardcoding http://…:3000 breaks
|
|
55
|
+
* every hook). Falls back to `https://<alias>:3000` when the input is absent or
|
|
56
|
+
* unparseable.
|
|
57
|
+
*/
|
|
58
|
+
export declare function containerApiUrl(processApiUrl: string | undefined, engine: DockerEngine): string;
|
|
59
|
+
/**
|
|
60
|
+
* Stable hash of the drift-relevant `docker create` inputs, stored on the
|
|
61
|
+
* container as the `codeman.confighash` label. On launch, a mismatch between the
|
|
62
|
+
* desired hash and the running container's label triggers the recreate-on-drift
|
|
63
|
+
* prompt (host config edits actually take effect).
|
|
64
|
+
*/
|
|
65
|
+
export declare function dockerConfigHash(docker: Pick<SessionDocker, 'engine' | 'image' | 'containerWorkdir' | 'network' | 'networkName' | 'resources' | 'gpus' | 'mountCredentials' | 'extraCreateArgs'>): string;
|
|
66
|
+
/**
|
|
67
|
+
* Build the flattened per-session Docker metadata from a host profile + a case,
|
|
68
|
+
* resolving every default (mirror of toSessionRemote). The `configHash` is
|
|
69
|
+
* computed last over the resolved values.
|
|
70
|
+
*/
|
|
71
|
+
export declare function toSessionDocker(host: DockerHost, dockerCase: DockerCase): SessionDocker;
|
|
72
|
+
/**
|
|
73
|
+
* POSIX single-quote shell-escaping (end-quote, escaped-quote, restart-quote).
|
|
74
|
+
* Mirror of the helper in remote-hosts.ts / tmux-manager.ts. Every dynamic value
|
|
75
|
+
* interpolated into the outer `bash -c "..."` launch layer is escaped through
|
|
76
|
+
* this so a path with spaces stays a single shell token. Operator-entered fields
|
|
77
|
+
* are ALSO schema-rejected for `$`/backtick (NO_SHELL_META) as defense in depth.
|
|
78
|
+
*/
|
|
79
|
+
export declare function shellescape(str: string): string;
|
|
80
|
+
/** A resolved bind mount (source existence already checked by the caller). */
|
|
81
|
+
export interface DockerMount {
|
|
82
|
+
src: string;
|
|
83
|
+
dst: string;
|
|
84
|
+
readonly?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolved, IO-free context for buildDockerCreateArgs. The caller (tmux-manager)
|
|
88
|
+
* resolves the environment-dependent bits (host uid, existing cred mounts, the
|
|
89
|
+
* derived api url, Desktop detection) so this builder stays pure and unit-testable.
|
|
90
|
+
*/
|
|
91
|
+
export interface DockerCreateContext {
|
|
92
|
+
docker: SessionDocker;
|
|
93
|
+
/** Codeman session id (only the first 8 chars are used, for the codeman.session label). */
|
|
94
|
+
sessionId: string;
|
|
95
|
+
/** CODEMAN_INSTANCE ('' for prod) — scopes the boot reaper so a beta never reaps prod. */
|
|
96
|
+
instance: string;
|
|
97
|
+
/** Pre-resolved uid/userns tokens: ['--user','1000:0'] | ['--userns','keep-id'] | []. */
|
|
98
|
+
userArgs: string[];
|
|
99
|
+
/** Existing host credential bind mounts (convenient mode). Empty in sealed mode. */
|
|
100
|
+
credentialMounts: DockerMount[];
|
|
101
|
+
/** Extra bind mounts (e.g. the read-only hook-secret file). */
|
|
102
|
+
extraMounts: DockerMount[];
|
|
103
|
+
/** Create-time env (NON-secret, committed-safe): HOME, TERM, COLORTERM, CODEMAN_API_URL, CODEMAN_HOOK_SECRET_FILE. */
|
|
104
|
+
envCreate: Record<string, string>;
|
|
105
|
+
/** Whether to add `--add-host <alias>:host-gateway` (skipped on Docker Desktop, where the alias is native). */
|
|
106
|
+
addHostGateway: boolean;
|
|
107
|
+
/** Engine host-gateway alias (host.docker.internal / host.containers.internal). */
|
|
108
|
+
gatewayAlias: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Engine prefix tokens shared by every docker invocation (mirror of
|
|
112
|
+
* buildSshConnectionArgs). Returns e.g. ['docker'] or ['podman','--context','ctx'].
|
|
113
|
+
*/
|
|
114
|
+
export declare function buildDockerBaseArgs(docker: Pick<SessionDocker, 'engine' | 'context' | 'daemonHost'>): string[];
|
|
115
|
+
/**
|
|
116
|
+
* Build the `docker create` token list (from `create` through the `sleep
|
|
117
|
+
* infinity` CMD) for a long-lived, hardened, per-case container. PURE: every
|
|
118
|
+
* dynamic value is shellescaped; the caller joins with spaces into the launch
|
|
119
|
+
* string. Security invariants baked in: --cap-drop ALL, --security-opt
|
|
120
|
+
* no-new-privileges, --pids-limit, --memory==--memory-swap, --init,
|
|
121
|
+
* --pull=never, --restart no, NEVER --privileged, NEVER the docker socket.
|
|
122
|
+
*/
|
|
123
|
+
export declare function buildDockerCreateArgs(ctx: DockerCreateContext): string[];
|
|
124
|
+
/**
|
|
125
|
+
* Resolve which host credential dirs/files EXIST and map them to their container
|
|
126
|
+
* HOME location. Only-existing avoids docker auto-creating root-owned empty dirs
|
|
127
|
+
* in the user's home. `~/.claude` also carries the transcripts (bind-mounted so
|
|
128
|
+
* host watchers + `--resume` see them) and is therefore mounted read-WRITE.
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveCredentialMounts(home?: string): DockerMount[];
|
|
131
|
+
export interface DockerAvailability {
|
|
132
|
+
ok: boolean;
|
|
133
|
+
engine: DockerEngine;
|
|
134
|
+
rootless: boolean;
|
|
135
|
+
isDesktop: boolean;
|
|
136
|
+
cgroupV2: boolean;
|
|
137
|
+
/** Best-effort: are --memory/--cpus/--pids-limit actually enforced on this engine? */
|
|
138
|
+
capsEnforced: boolean;
|
|
139
|
+
error?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Probe the container engine: server up, cgroup version, rootless, Desktop, and
|
|
143
|
+
* whether resource caps are enforceable. Auto-detects docker then podman when no
|
|
144
|
+
* engine is given. No-op canned value under VITEST.
|
|
145
|
+
*/
|
|
146
|
+
export declare function checkDockerAvailable(engine?: DockerEngine): Promise<DockerAvailability>;
|
|
147
|
+
/** Is the base image present locally? (never triggers an auto-pull). */
|
|
148
|
+
export declare function checkDockerImagePresent(engine: DockerEngine, image: string): Promise<boolean>;
|
|
149
|
+
export interface DockerTmuxCheckResult {
|
|
150
|
+
ok: boolean;
|
|
151
|
+
tmuxPath?: string;
|
|
152
|
+
/** Distinguishes "image missing" (build it) from "tmux missing in image" (rebuild it). */
|
|
153
|
+
imageMissing?: boolean;
|
|
154
|
+
error?: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Verify the base image is present AND contains tmux (a HARD prerequisite: the
|
|
158
|
+
* in-container tmux is what makes reconnect durable). Never triggers a pull
|
|
159
|
+
* (`--pull=never`). No-op under VITEST. Mirror of checkRemoteTmuxAvailable.
|
|
160
|
+
*/
|
|
161
|
+
export declare function checkDockerTmuxAvailable(docker: Pick<SessionDocker, 'engine' | 'image'>): Promise<DockerTmuxCheckResult>;
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the host's IP on the default docker bridge (the address a container
|
|
164
|
+
* reaches as `host.docker.internal`), so the server can bind a hooks-only listener
|
|
165
|
+
* there and in-container hooks can call back. Defaults to the conventional
|
|
166
|
+
* 172.17.0.1 when the inspect fails but docker is up; null when docker is absent.
|
|
167
|
+
* No-op canned value under VITEST.
|
|
168
|
+
*/
|
|
169
|
+
export declare function detectDockerBridgeGateway(engine?: DockerEngine): Promise<string | null>;
|
|
170
|
+
/**
|
|
171
|
+
* Instance-scoped boot reaper: `docker rm -f` any MANAGED container that belongs
|
|
172
|
+
* to THIS instance (by the `codeman.instance` label) but whose case is no longer
|
|
173
|
+
* in `docker-cases.json`. The instance scoping is what stops a beta from reaping
|
|
174
|
+
* prod's containers (the cross-instance hazard). No-op under VITEST. Best-effort.
|
|
175
|
+
*/
|
|
176
|
+
export declare function reapOrphanedDockerContainers(configDir: string, instance: string, engine?: DockerEngine): Promise<string[]>;
|
|
177
|
+
/**
|
|
178
|
+
* Read the IN-CONTAINER Claude CLI version (`docker exec <container> claude
|
|
179
|
+
* --version`). Feeds Session.cliVersion for docker sessions (the LOCAL claude
|
|
180
|
+
* would report the wrong version and disable trackpad wheel-forwarding, #154).
|
|
181
|
+
* Returns undefined on any failure. No-op under VITEST.
|
|
182
|
+
*/
|
|
183
|
+
export declare function probeDockerCliVersion(docker: Pick<SessionDocker, 'engine' | 'containerName'>, mode: SessionMode): Promise<string | undefined>;
|
|
184
|
+
//# sourceMappingURL=docker-hosts.d.ts.map
|