mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
feat(docker): resource templates, GPU, elastic disk, bridge-hooks listener
- One-click "Run in Docker" gains an expandable settings panel with a Template picker (Small 2G/1 · Medium 4G/2 default · Large 8G/4 · GPU 8G/4/all) plus memory/cpu/gpu/network/image/mount-creds overrides. Any tweak creates a dedicated per-case host; the plain checkbox keeps using the shared `default` host. - GPU passthrough: `gpus` on DockerHost/SessionDocker -> `--gpus <value>` in create args (needs the NVIDIA container toolkit). Elastic disk: no `--storage-opt` cap, so container storage grows as data flows in. - CODEMAN_DOCKER_BRIDGE_HOOKS=1: opt-in second listener on the docker bridge gateway (auto-detected 172.17.0.1, override CODEMAN_DOCKER_BRIDGE_HOST) that serves ONLY the hook endpoints and delegates into the secret-gated pipeline, so in-container hooks fire on a loopback-only server. Non-hook paths -> 403; host-internal, not LAN. Verified live: Large template applies real 8GB/4CPU limits; a secret-authenticated hook POST from inside a container now reaches the handler (was connection-refused); non-hook paths return 403; template UI + GPU field verified via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1865,9 +1865,52 @@
|
||||
<input type="text" id="newCaseDescription" placeholder="A brief description..." autocomplete="off">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="checkbox-row"><input type="checkbox" id="newCaseDocker"> 🐳 Run in an isolated Docker container</label>
|
||||
<span class="form-hint">One click: creates the case folder AND a hardened container with default settings, then starts the session inside it. Requires the base image (<code>node scripts/build-agent-image.mjs</code>).</span>
|
||||
<label class="checkbox-row"><input type="checkbox" id="newCaseDocker" onchange="app.toggleDockerQuickSettings()"> 🐳 Run in an isolated Docker container</label>
|
||||
<span class="form-hint">One checkbox is enough — it creates the case folder AND a hardened container with default settings, then starts the session inside it. Click to expand for optional presets. Requires the base image (<code>node scripts/build-agent-image.mjs</code>).</span>
|
||||
</div>
|
||||
<details class="advanced-options" id="dockerQuickSettings" style="display:none;">
|
||||
<summary>Container settings (optional — sensible defaults)</summary>
|
||||
<div class="advanced-options-content">
|
||||
<div class="form-row">
|
||||
<label>Template</label>
|
||||
<select id="quickDockerTemplate" onchange="app.applyDockerTemplate()">
|
||||
<option value="small">Small — 2 GB RAM, 1 CPU</option>
|
||||
<option value="medium" selected>Medium — 4 GB RAM, 2 CPU (default)</option>
|
||||
<option value="large">Large — 8 GB RAM, 4 CPU</option>
|
||||
<option value="gpu">GPU — 8 GB RAM, 4 CPU, all GPUs</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
<span class="form-hint">Disk is elastic — storage grows automatically as data flows in (no fixed cap).</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Memory</label>
|
||||
<input type="text" id="quickDockerMemory" placeholder="4g" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>CPUs</label>
|
||||
<input type="text" id="quickDockerCpus" placeholder="2" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>GPUs</label>
|
||||
<input type="text" id="quickDockerGpus" placeholder="none (e.g. all, or 1)" autocomplete="off" spellcheck="false">
|
||||
<span class="form-hint">Needs the NVIDIA container toolkit on the host.</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Network</label>
|
||||
<select id="quickDockerNetwork">
|
||||
<option value="bridge">bridge (internet on)</option>
|
||||
<option value="none">none (fully isolated)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Image</label>
|
||||
<input type="text" id="quickDockerImage" placeholder="codeman/agent:base" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="checkbox-row"><input type="checkbox" id="quickDockerMountCreds" checked> Mount host credentials (~/.claude etc.)</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<!-- Link Existing Tab -->
|
||||
<div class="modal-tab-content hidden" id="case-link">
|
||||
|
||||
@@ -1642,16 +1642,19 @@ Object.assign(CodemanApp.prototype, {
|
||||
return;
|
||||
}
|
||||
|
||||
// One-click "Run in Docker": create the case folder AND a container (default
|
||||
// settings), then start a session inside it.
|
||||
// One-click "Run in Docker": create the case folder AND a container, then start
|
||||
// a session inside it. Optional expandable settings override the defaults.
|
||||
const inDocker = document.getElementById('newCaseDocker')?.checked;
|
||||
const endpoint = inDocker ? '/api/cases/docker-quickcreate' : '/api/cases';
|
||||
const payload = inDocker
|
||||
? { name, description, ...this._collectDockerQuickSettings() }
|
||||
: { name, description };
|
||||
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, description })
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
@@ -1678,6 +1681,54 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
// Show/hide the expandable container-settings section under the Docker checkbox.
|
||||
toggleDockerQuickSettings() {
|
||||
const on = document.getElementById('newCaseDocker')?.checked;
|
||||
const el = document.getElementById('dockerQuickSettings');
|
||||
if (el) el.style.display = on ? '' : 'none';
|
||||
},
|
||||
|
||||
// Fill the memory/cpu/gpu fields from a resource template. `medium` clears them so
|
||||
// the server uses its defaults (no per-case host); `custom` leaves them editable.
|
||||
applyDockerTemplate() {
|
||||
const t = document.getElementById('quickDockerTemplate')?.value;
|
||||
const presets = {
|
||||
small: { m: '2g', c: '1', g: '' },
|
||||
medium: { m: '', c: '', g: '' },
|
||||
large: { m: '8g', c: '4', g: '' },
|
||||
gpu: { m: '8g', c: '4', g: 'all' },
|
||||
};
|
||||
const p = presets[t];
|
||||
if (!p) return; // 'custom' — leave fields as-is
|
||||
const set = (id, v) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = v;
|
||||
};
|
||||
set('quickDockerMemory', p.m);
|
||||
set('quickDockerCpus', p.c);
|
||||
set('quickDockerGpus', p.g);
|
||||
},
|
||||
|
||||
// Collect only the non-default docker overrides (empty fields fall back to defaults
|
||||
// server-side; sent as undefined, never null, per the Zod .optional() gotcha).
|
||||
_collectDockerQuickSettings() {
|
||||
const val = (id) => (document.getElementById(id)?.value || '').trim();
|
||||
const o = {};
|
||||
const mem = val('quickDockerMemory');
|
||||
if (mem) o.memory = mem;
|
||||
const cpus = val('quickDockerCpus');
|
||||
if (cpus) o.cpus = cpus;
|
||||
const gpus = val('quickDockerGpus');
|
||||
if (gpus && gpus.toLowerCase() !== 'none') o.gpus = gpus;
|
||||
const net = document.getElementById('quickDockerNetwork')?.value;
|
||||
if (net && net !== 'bridge') o.network = net;
|
||||
const img = val('quickDockerImage');
|
||||
if (img) o.image = img;
|
||||
const mc = document.getElementById('quickDockerMountCreds');
|
||||
if (mc && !mc.checked) o.mountCredentials = false;
|
||||
return o;
|
||||
},
|
||||
|
||||
async linkCase() {
|
||||
const name = document.getElementById('linkCaseName').value.trim();
|
||||
const path = document.getElementById('linkCasePath').value.trim();
|
||||
|
||||
Reference in New Issue
Block a user