import Docker from 'dockerode';
import { config } from '../../config.js';
import { resolveWorkspacePath, SandboxViolationError } from '../../lib/sandbox.js';
import { logger } from '../../lib/logger.js';
class DockerManager {
    docker;
    containers = new Map();
    daemonReachable = null;
    constructor() {
        this.docker = new Docker();
    }
    async ensureReachable() {
        if (this.daemonReachable === true)
            return;
        try {
            await this.docker.ping();
            this.daemonReachable = true;
        }
        catch (error) {
            this.daemonReachable = false;
            throw new Error('Docker daemon is not reachable. Make sure Docker is running and the current user can access it. ' +
                `Original error: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    getDocker() {
        return this.docker;
    }
    track(containerId, image, name, ports) {
        if (this.containers.size >= config.maxConcurrentContainers) {
            throw new Error(`Refusing to start another container: already at the max of ${config.maxConcurrentContainers} concurrent containers.`);
        }
        const shortId = containerId.substring(0, 12);
        const managed = {
            id: shortId,
            image,
            name,
            startedAt: new Date().toISOString(),
            ports,
        };
        this.containers.set(shortId, managed);
        return managed;
    }
    getTracked(id) {
        return this.containers.get(id) ?? this.containers.get(id.substring(0, 12));
    }
    listTracked() {
        return Array.from(this.containers.values());
    }
    untrack(id) {
        this.containers.delete(id.substring(0, 12));
    }
    async cleanupAll() {
        for (const [id, managed] of this.containers.entries()) {
            try {
                const container = this.docker.getContainer(id);
                await container.stop({ t: 5 }).catch(() => null);
                await container.remove({ force: true }).catch(() => null);
            }
            catch (error) {
                logger.error(`failed to cleanup container ${managed.name} (${id})`, {
                    error: error instanceof Error ? error.message : error,
                });
            }
            finally {
                this.containers.delete(id);
            }
        }
    }
}
export const dockerManager = new DockerManager();
/**
 * Validates that a volume mount specification keeps the source path inside the
 * workspace root. Rejects Docker socket mounts, host-relative escapes, etc.
 */
export function validateVolumeMount(spec) {
    const parts = spec.split(':');
    if (parts.length < 2) {
        throw new Error(`Invalid volume mount "${spec}": expected format "source:target[:mode]".`);
    }
    const source = parts[0] ?? '';
    const target = parts[1] ?? '';
    const mode = parts[2] ?? 'rw';
    if (!source || !target) {
        throw new Error(`Invalid volume mount "${spec}": source and target are required.`);
    }
    const normalizedSource = source.toLowerCase().replaceAll('/', '\\');
    if (normalizedSource.startsWith('\\\\.\\pipe\\docker_engine') ||
        normalizedSource.startsWith('\\.\\pipe\\docker_engine') ||
        source.includes('/var/run/docker.sock')) {
        throw new SandboxViolationError(`Refusing to mount the Docker socket: "${source}".`);
    }
    const absoluteSource = resolveWorkspacePath(config.workspaceRoot, source);
    return { source: absoluteSource, target, mode };
}
export function isAllowListedImage(image) {
    const base = (image.split(':')[0] ?? '').split('/').pop()?.toLowerCase() ?? '';
    return config.dockerAllowedBaseImages.some((allowed) => base === allowed || base.startsWith(allowed));
}
export function isLocallyBuiltImage(image) {
    // Heuristic: an image built by this server via docker_build_image won't have
    // a registry prefix. We treat "my-app" or "web-dev-mcp" as local; anything
    // with a registry host (contains '.' or '/') is not.
    return !/[/.]/.test(image);
}
//# sourceMappingURL=dockerManager.js.map