| "use strict"; |
| var __importDefault = (this && this.__importDefault) || function (mod) { |
| return (mod && mod.__esModule) ? mod : { "default": mod }; |
| }; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.dockerManager = exports.DockerManager = exports.pendingProvisioning = exports.provisioningBus = exports.dockerWorkspaces = exports.nativeProcesses = void 0; |
| exports.isNativeWorkspaceRunning = isNativeWorkspaceRunning; |
| exports.isWorkspaceRunning = isWorkspaceRunning; |
| exports.getWorkspaceStatus = getWorkspaceStatus; |
| exports.isDockerAvailable = isDockerAvailable; |
| exports.stopNativeWorkspace = stopNativeWorkspace; |
| exports.getNativeWorkspacePort = getNativeWorkspacePort; |
| exports.getWorkspacePort = getWorkspacePort; |
| exports.getAndroidPort = getAndroidPort; |
| exports.prewarmWorkspace = prewarmWorkspace; |
| exports.startWorkspaceContainer = startWorkspaceContainer; |
| exports.reconnectRunningWorkspaces = reconnectRunningWorkspaces; |
| exports.stopWorkspaceContainer = stopWorkspaceContainer; |
| const fs_1 = __importDefault(require("fs")); |
| const child_process_1 = require("child_process"); |
| const path_1 = __importDefault(require("path")); |
| const events_1 = require("events"); |
| const idx_engine_1 = require("../idx/idx-engine"); |
| const builder_1 = require("./builder"); |
| const client_1 = require("./client"); |
| const storage_1 = require("../hf/storage"); |
| const isolation_1 = require("../fs/isolation"); |
| const env_config_1 = require("../env-config"); |
| |
| |
| |
| |
| exports.nativeProcesses = new Map(); |
| exports.dockerWorkspaces = new Map(); |
| |
| |
| |
| class ProvisioningBus extends events_1.EventEmitter { |
| } |
| exports.provisioningBus = new ProvisioningBus(); |
| |
| |
| |
| exports.pendingProvisioning = new Map(); |
| const SHORT_WORKSPACE_ID_LENGTH = 8; |
| const RUNTIME_ROOT_DIR_NAME = '.codeverse-runtime'; |
| const DOCKER_CODE_SERVER_PORT = 8080; |
| const WORKSPACE_CONTAINER_NAME_PREFIX = 'codeverse-workspace-'; |
| const WORKSPACE_RUNTIME_LABEL = 'codeverse.runtime'; |
| const WORKSPACE_RUNTIME_LABEL_VALUE = 'workspace'; |
| const WORKSPACE_ID_LABEL = 'codeverse.workspace.id'; |
| function getWorkspaceRootPath() { |
| return env_config_1.ENV_CONFIG.WORKSPACE_ROOT; |
| } |
| function getRuntimeRootPath() { |
| return path_1.default.join( getWorkspaceRootPath(), RUNTIME_ROOT_DIR_NAME); |
| } |
| function getShortWorkspaceId(id) { |
| return id.slice(0, SHORT_WORKSPACE_ID_LENGTH); |
| } |
| function isPathWithinParent(parentPath, targetPath) { |
| const normalizedParent = path_1.default.resolve(parentPath); |
| const normalizedTarget = path_1.default.resolve(targetPath); |
| return normalizedTarget === normalizedParent || normalizedTarget.startsWith(`${normalizedParent}${path_1.default.sep}`); |
| } |
| function getNativeWorkspaceEntry(id) { |
| const directEntry = exports.nativeProcesses.get(id); |
| if (directEntry) { |
| return directEntry; |
| } |
| const prefixedKey = Array.from(exports.nativeProcesses.keys()).find((key) => id.startsWith(key)); |
| return prefixedKey ? exports.nativeProcesses.get(prefixedKey) : undefined; |
| } |
| function getDockerWorkspaceEntry(id) { |
| return exports.dockerWorkspaces.get(id); |
| } |
| function getWorkspaceRuntimeEntry(id) { |
| var _a; |
| return (_a = getNativeWorkspaceEntry(id)) !== null && _a !== void 0 ? _a : getDockerWorkspaceEntry(id); |
| } |
| function getWorkspaceContainerName(id) { |
| return `${WORKSPACE_CONTAINER_NAME_PREFIX}${id}`; |
| } |
| function getWorkspaceContainerLabels(config) { |
| return { |
| [WORKSPACE_RUNTIME_LABEL]: WORKSPACE_RUNTIME_LABEL_VALUE, |
| [WORKSPACE_ID_LABEL]: config.id, |
| 'codeverse.workspace.project': config.projectName, |
| 'codeverse.workspace.user': config.userId, |
| }; |
| } |
| function getDockerBindSourcePath(hostPath) { |
| return path_1.default.resolve(hostPath); |
| } |
| function hasCustomDockerPackages(config) { |
| var _a, _b, _c, _d; |
| return Boolean(((_b = (_a = config.packages) === null || _a === void 0 ? void 0 : _a.apt) === null || _b === void 0 ? void 0 : _b.length) |
| || ((_d = (_c = config.packages) === null || _c === void 0 ? void 0 : _c.npm) === null || _d === void 0 ? void 0 : _d.length)); |
| } |
| function getWorkspaceEnvironment(config) { |
| var _a; |
| return Object.entries((_a = config.env) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => `${key}=${value}`); |
| } |
| async function ensureDockerImageAvailable(docker, imageName, log) { |
| try { |
| await docker.getImage(imageName).inspect(); |
| return; |
| } |
| catch (_a) { |
| log(`Pulling Docker image '${imageName}'...`); |
| } |
| await new Promise((resolve, reject) => { |
| docker.pull(imageName, (error, stream) => { |
| if (error || !stream) { |
| reject(error !== null && error !== void 0 ? error : new Error(`Failed to pull Docker image '${imageName}'`)); |
| return; |
| } |
| docker.modem.followProgress(stream, (progressError) => { |
| if (progressError) { |
| reject(progressError); |
| return; |
| } |
| resolve(); |
| }, (event) => { |
| if (event.status) { |
| const progressLabel = event.id ? `${event.id}: ${event.status}` : event.status; |
| log(`[DOCKER:PULL] ${progressLabel}`); |
| } |
| }); |
| }); |
| }); |
| } |
| function getPublishedPortForContainerInfo(info) { |
| var _a, _b, _c; |
| const bindings = (_b = (_a = info.NetworkSettings) === null || _a === void 0 ? void 0 : _a.Ports) === null || _b === void 0 ? void 0 : _b[`${DOCKER_CODE_SERVER_PORT}/tcp`]; |
| const hostPort = (_c = bindings === null || bindings === void 0 ? void 0 : bindings[0]) === null || _c === void 0 ? void 0 : _c.HostPort; |
| if (!hostPort) { |
| return undefined; |
| } |
| const parsedPort = Number.parseInt(hostPort, 10); |
| return Number.isFinite(parsedPort) ? parsedPort : undefined; |
| } |
| function getPublishedPortForListEntry(containerInfo) { |
| var _a; |
| const publishedPort = (_a = containerInfo.Ports.find((portInfo) => portInfo.PrivatePort === DOCKER_CODE_SERVER_PORT && portInfo.PublicPort)) === null || _a === void 0 ? void 0 : _a.PublicPort; |
| return publishedPort && Number.isFinite(publishedPort) ? publishedPort : undefined; |
| } |
| async function resolveWorkspaceRuntimePaths(config) { |
| const shortWorkspaceId = getShortWorkspaceId(config.id); |
| const runtimeRootPath = getRuntimeRootPath(); |
| const projectPath = await (0, isolation_1.resolveSafeProjectPath)(config.userId, config.projectName); |
| return { |
| fullWorkspaceId: config.id, |
| shortWorkspaceId, |
| projectPath, |
| runtimeRootPath, |
| runtimeWorkspacePath: path_1.default.join( runtimeRootPath, shortWorkspaceId), |
| userDataPath: path_1.default.join( runtimeRootPath, `${shortWorkspaceId}-userdata`), |
| metadataPath: path_1.default.join( runtimeRootPath, `${shortWorkspaceId}.id`), |
| npmCachePath: path_1.default.join( runtimeRootPath, 'npm-cache'), |
| }; |
| } |
| function ensureRuntimeWorkspacePath(paths, log) { |
| if (!fs_1.default.existsSync(paths.projectPath)) { |
| fs_1.default.mkdirSync(paths.projectPath, { recursive: true }); |
| } |
| fs_1.default.mkdirSync(paths.runtimeRootPath, { recursive: true }); |
| fs_1.default.mkdirSync(paths.userDataPath, { recursive: true }); |
| fs_1.default.mkdirSync(paths.npmCachePath, { recursive: true }); |
| if (fs_1.default.existsSync(paths.runtimeWorkspacePath)) { |
| try { |
| const existingTargetPath = fs_1.default.realpathSync(paths.runtimeWorkspacePath); |
| if (path_1.default.resolve(existingTargetPath) === path_1.default.resolve(paths.projectPath)) { |
| fs_1.default.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); |
| return paths.runtimeWorkspacePath; |
| } |
| } |
| catch (_a) { |
| |
| } |
| if (!isPathWithinParent(paths.runtimeRootPath, paths.runtimeWorkspacePath)) { |
| throw new Error(`Unsafe runtime workspace path: ${paths.runtimeWorkspacePath}`); |
| } |
| fs_1.default.rmSync(paths.runtimeWorkspacePath, { recursive: true, force: true }); |
| } |
| try { |
| fs_1.default.symlinkSync(paths.projectPath, paths.runtimeWorkspacePath, process.platform === 'win32' ? 'junction' : 'dir'); |
| fs_1.default.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); |
| log === null || log === void 0 ? void 0 : log(`Bound runtime alias ${paths.shortWorkspaceId} -> ${paths.projectPath}`); |
| return paths.runtimeWorkspacePath; |
| } |
| catch (error) { |
| const errorMessage = error instanceof Error ? error.message : String(error); |
| fs_1.default.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); |
| log === null || log === void 0 ? void 0 : log(`[WARN] Runtime alias creation failed. Falling back to direct project path: ${errorMessage}`); |
| return paths.projectPath; |
| } |
| } |
| |
| |
| |
| |
| function isNativeWorkspaceRunning(id) { |
| if (exports.pendingProvisioning.has(id)) |
| return false; |
| return getNativeWorkspaceEntry(id) !== undefined; |
| } |
| function isWorkspaceRunning(id) { |
| if (exports.pendingProvisioning.has(id)) |
| return false; |
| return getWorkspaceRuntimeEntry(id) !== undefined; |
| } |
| |
| |
| |
| function getWorkspaceStatus(id) { |
| if (exports.pendingProvisioning.has(id)) |
| return "provisioning"; |
| if (getWorkspaceRuntimeEntry(id)) |
| return "ready"; |
| return "offline"; |
| } |
| |
| |
| |
| const delay = (ms) => new Promise(res => setTimeout(res, ms)); |
| |
| |
| |
| function findAvailablePort() { |
| const occupiedPorts = [ |
| ...Array.from(exports.nativeProcesses.values()).map((entry) => entry.port), |
| ...Array.from(exports.dockerWorkspaces.values()).map((entry) => entry.port), |
| ]; |
| let port = Math.floor(Math.random() * (9000 - 8100) + 8100); |
| while (occupiedPorts.includes(port)) { |
| port = Math.floor(Math.random() * (9000 - 8100) + 8100); |
| } |
| return port; |
| } |
| function resolveExecutableOnPath(candidates) { |
| const locatorCommand = process.platform === 'win32' ? 'where' : 'which'; |
| for (const candidate of candidates) { |
| try { |
| const output = (0, child_process_1.execFileSync)(locatorCommand, [candidate], { |
| encoding: 'utf8', |
| stdio: ['ignore', 'pipe', 'ignore'], |
| }); |
| const resolvedPath = output |
| .split(/\r?\n/) |
| .map((line) => line.trim()) |
| .find(Boolean); |
| if (resolvedPath) { |
| return resolvedPath; |
| } |
| } |
| catch (_a) { |
| |
| } |
| } |
| return null; |
| } |
| function resolveLocalToolNodeBinary() { |
| var _a; |
| if (env_config_1.ENV_CONFIG.CODE_SERVER_NODE_BIN && fs_1.default.existsSync(env_config_1.ENV_CONFIG.CODE_SERVER_NODE_BIN)) { |
| return env_config_1.ENV_CONFIG.CODE_SERVER_NODE_BIN; |
| } |
| const localToolsRoot = path_1.default.join(process.cwd(), '.codeverse-tools'); |
| if (!fs_1.default.existsSync(localToolsRoot)) { |
| return null; |
| } |
| const directNodePath = path_1.default.join(localToolsRoot, 'node', 'node.exe'); |
| if (fs_1.default.existsSync(directNodePath)) { |
| return directNodePath; |
| } |
| const nodeDirs = fs_1.default.readdirSync(localToolsRoot, { withFileTypes: true }) |
| .filter((entry) => entry.isDirectory() && /^node-v22\..*-win-x64$/i.test(entry.name)) |
| .map((entry) => path_1.default.join(localToolsRoot, entry.name, 'node.exe')); |
| return (_a = nodeDirs.find((candidate) => fs_1.default.existsSync(candidate))) !== null && _a !== void 0 ? _a : null; |
| } |
| function resolveLocalCodeServerEntry() { |
| if (env_config_1.ENV_CONFIG.CODE_SERVER_ENTRY && fs_1.default.existsSync(env_config_1.ENV_CONFIG.CODE_SERVER_ENTRY)) { |
| return env_config_1.ENV_CONFIG.CODE_SERVER_ENTRY; |
| } |
| const localEntryPath = path_1.default.join(process.cwd(), '.codeverse-tools', 'code-server', 'node_modules', 'code-server', 'out', 'node', 'entry.js'); |
| return fs_1.default.existsSync(localEntryPath) ? localEntryPath : null; |
| } |
| function getCurrentNodeMajorVersion() { |
| const [majorVersion] = process.versions.node.split('.'); |
| return Number.parseInt(majorVersion !== null && majorVersion !== void 0 ? majorVersion : '0', 10); |
| } |
| function resolveCodeServerLaunch() { |
| const overrideBinary = process.env.CODE_SERVER_BIN; |
| if (overrideBinary) { |
| const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(overrideBinary); |
| return { command: overrideBinary, args: [], label: overrideBinary, usesNpx: false, useShell }; |
| } |
| if (process.platform === 'win32') { |
| const localToolNodeBinary = resolveLocalToolNodeBinary(); |
| const localCodeServerEntry = resolveLocalCodeServerEntry(); |
| if (localToolNodeBinary && localCodeServerEntry) { |
| return { |
| command: localToolNodeBinary, |
| args: [localCodeServerEntry], |
| label: 'local code-server toolchain', |
| usesNpx: false, |
| useShell: false |
| }; |
| } |
| const codeServerBinary = resolveExecutableOnPath(['code-server.exe', 'code-server']); |
| if (codeServerBinary) { |
| return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false }; |
| } |
| if (getCurrentNodeMajorVersion() !== 22) { |
| throw new Error('CODE_SERVER_WINDOWS_REQUIRES_NODE_22_OR_LOCAL_TOOLCHAIN'); |
| } |
| const npxCliPath = path_1.default.join(path_1.default.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js'); |
| if (fs_1.default.existsSync(npxCliPath)) { |
| return { command: process.execPath, args: [npxCliPath, '--yes', 'code-server'], label: 'node npx-cli.js code-server', usesNpx: true, useShell: false }; |
| } |
| throw new Error('CODE_SERVER_BIN_NOT_FOUND'); |
| } |
| const codeServerBinary = resolveExecutableOnPath(['code-server']); |
| if (codeServerBinary) { |
| return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false }; |
| } |
| const npxBinary = resolveExecutableOnPath(['npx']); |
| if (npxBinary) { |
| return { command: npxBinary, args: ['--yes', 'code-server'], label: 'npx code-server', usesNpx: true, useShell: false }; |
| } |
| throw new Error('CODE_SERVER_BIN_NOT_FOUND'); |
| } |
| |
| |
| |
| async function isDockerAvailable() { |
| var _a; |
| if (process.env.SIMULATE_HF === 'true') { |
| return { available: false, reason: "Hugging Face Simulation Mode (Artificial Sandbox)" }; |
| } |
| if (process.env.SPACE_ID) { |
| return { available: false, reason: "Hugging Face Space (Native Sandboxed)" }; |
| } |
| const availability = await (0, client_1.probeDockerAvailability)(env_config_1.ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); |
| return availability.available |
| ? { available: true } |
| : { available: false, reason: (_a = availability.reason) !== null && _a !== void 0 ? _a : "Docker daemon unreachable" }; |
| } |
| |
| |
| |
| async function stopNativeWorkspace(id) { |
| const entry = exports.nativeProcesses.get(id); |
| if (entry) { |
| try { |
| entry.process.kill(); |
| exports.nativeProcesses.delete(id); |
| return true; |
| } |
| catch (e) { |
| console.error(`[MANAGER] Failed to kill code-server ${id}:`, e); |
| exports.nativeProcesses.delete(id); |
| } |
| } |
| return false; |
| } |
| |
| |
| |
| |
| function getNativeWorkspacePort(id) { |
| var _a; |
| if (exports.pendingProvisioning.has(id)) { |
| return undefined; |
| } |
| return (_a = getNativeWorkspaceEntry(id)) === null || _a === void 0 ? void 0 : _a.port; |
| } |
| function getWorkspacePort(id) { |
| var _a; |
| if (exports.pendingProvisioning.has(id)) { |
| return undefined; |
| } |
| return (_a = getWorkspaceRuntimeEntry(id)) === null || _a === void 0 ? void 0 : _a.port; |
| } |
| |
| |
| |
| function getAndroidPort() { |
| return 6080; |
| } |
| |
| |
| |
| async function prewarmWorkspace(config) { |
| const runtimePaths = await resolveWorkspaceRuntimePaths(config); |
| const workspacePath = ensureRuntimeWorkspacePath(runtimePaths); |
| const idxConfig = idx_engine_1.IdxEngine.getIdxConfig(workspacePath); |
| if (idxConfig) { |
| |
| idx_engine_1.IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => { |
| exports.provisioningBus.emit(`log:${config.id}`, `[HYDRATE] ${msg}`); |
| }); |
| } |
| } |
| async function waitForWorkspaceHeartbeat({ port, log, getFailureReason }) { |
| let attempts = 0; |
| while (attempts < 60) { |
| const failureReason = await (getFailureReason === null || getFailureReason === void 0 ? void 0 : getFailureReason()); |
| if (failureReason) { |
| log(`[FATAL] IDE bootstrap aborted before handshake: ${failureReason}`); |
| return failureReason; |
| } |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 2000); |
| try { |
| const response = await fetch(`http://127.0.0.1:${port}`, { signal: controller.signal }); |
| if (response.ok) { |
| log(`Handshake verified. Studio Engine Online.`); |
| return null; |
| } |
| } |
| catch (_a) { |
| if (attempts % 5 === 0) |
| log(`[INFO] Scanning for IDE heartbeat... (Attempt ${attempts}/60)`); |
| if (attempts === 15) { |
| const bootstrapHint = env_config_1.ENV_CONFIG.IDX_NIX_SYNC_ENABLED |
| ? 'Nix evaluation in progress. Cold boot detected.' |
| : 'IDE bootstrap still in progress.'; |
| log(`[INFO] ${bootstrapHint}`); |
| } |
| if (attempts === 45) |
| log(`[WARN] Handshake threshold approaching. IDE core high load.`); |
| } |
| finally { |
| clearTimeout(timeoutId); |
| } |
| await delay(1000); |
| attempts++; |
| } |
| log(`[FATAL] Handshake timeout on 127.0.0.1:${port}.`); |
| return "IDE_HANDSHAKE_TIMEOUT"; |
| } |
| async function resolveExistingWorkspaceContainer(docker, workspaceId) { |
| var _a; |
| const containerName = getWorkspaceContainerName(workspaceId); |
| const containers = await docker.listContainers({ |
| all: true, |
| filters: { |
| name: [containerName], |
| }, |
| }); |
| const exactMatch = containers.find((container) => container.Names.some((name) => name === `/${containerName}`)); |
| return (_a = exactMatch !== null && exactMatch !== void 0 ? exactMatch : containers[0]) !== null && _a !== void 0 ? _a : null; |
| } |
| function registerDockerWorkspaceRuntime(workspaceId, containerInfo, socketPath) { |
| const publishedPort = getPublishedPortForContainerInfo(containerInfo); |
| if (!publishedPort) { |
| return null; |
| } |
| const containerEntry = { |
| kind: 'docker', |
| containerId: containerInfo.Id, |
| containerName: containerInfo.Name.replace(/^\//, ''), |
| imageName: containerInfo.Config.Image, |
| port: publishedPort, |
| socketPath, |
| }; |
| exports.dockerWorkspaces.set(workspaceId, containerEntry); |
| return containerEntry; |
| } |
| async function startDockerWorkspace(config, runtimePaths, workspacePath, log) { |
| const dockerResolution = await (0, client_1.resolveDockerClient)(env_config_1.ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); |
| if (!dockerResolution) { |
| return null; |
| } |
| const { docker, socketPath } = dockerResolution; |
| const workspaceConfig = await (0, builder_1.loadWorkspaceConfig)(workspacePath); |
| const containerName = getWorkspaceContainerName(config.id); |
| const existingContainerInfo = await resolveExistingWorkspaceContainer(docker, config.id); |
| if (existingContainerInfo) { |
| const existingContainer = docker.getContainer(existingContainerInfo.Id); |
| const existingInspect = await existingContainer.inspect(); |
| if (!existingInspect.State.Running) { |
| log(`Restarting existing Docker workspace container '${containerName}'...`); |
| await existingContainer.start(); |
| } |
| else { |
| log(`Reusing active Docker workspace container '${containerName}'.`); |
| } |
| const runningInspect = await existingContainer.inspect(); |
| const runningEntry = registerDockerWorkspaceRuntime(config.id, runningInspect, socketPath); |
| if (!runningEntry) { |
| return { success: false, error: 'DOCKER_PORT_BINDING_MISSING' }; |
| } |
| const heartbeatError = await waitForWorkspaceHeartbeat({ |
| port: runningEntry.port, |
| log, |
| getFailureReason: async () => { |
| const info = await existingContainer.inspect(); |
| return info.State.Running ? null : `DOCKER_CONTAINER_EXITED_${info.State.ExitCode}`; |
| }, |
| }); |
| if (heartbeatError) { |
| exports.dockerWorkspaces.delete(config.id); |
| return { success: false, error: heartbeatError }; |
| } |
| return { |
| success: true, |
| containerId: runningEntry.containerId, |
| port: runningEntry.port, |
| runtime: 'docker', |
| }; |
| } |
| let imageName = env_config_1.ENV_CONFIG.DOCKER_WORKSPACE_BASE_IMAGE; |
| if (hasCustomDockerPackages(workspaceConfig)) { |
| const buildResult = await (0, builder_1.buildWorkspaceImage)(config.id, workspacePath, log, docker); |
| imageName = buildResult.imageName; |
| } |
| await ensureDockerImageAvailable(docker, imageName, log); |
| const port = findAvailablePort(); |
| const workspaceContainer = await docker.createContainer({ |
| name: containerName, |
| Image: imageName, |
| WorkingDir: '/home/coder/project', |
| Env: [ |
| 'HOME=/home/coder', |
| 'SHELL=/bin/bash', |
| ...getWorkspaceEnvironment(workspaceConfig), |
| ], |
| Labels: getWorkspaceContainerLabels(config), |
| ExposedPorts: { |
| [`${DOCKER_CODE_SERVER_PORT}/tcp`]: {}, |
| }, |
| HostConfig: { |
| Binds: [ |
| `${getDockerBindSourcePath(workspacePath)}:/home/coder/project`, |
| `${getDockerBindSourcePath(runtimePaths.userDataPath)}:/home/coder/.local/share/code-server`, |
| ], |
| PortBindings: { |
| [`${DOCKER_CODE_SERVER_PORT}/tcp`]: [{ HostIp: '127.0.0.1', HostPort: `${port}` }], |
| }, |
| }, |
| Cmd: [ |
| '--auth', 'none', |
| '--bind-addr', `0.0.0.0:${DOCKER_CODE_SERVER_PORT}`, |
| '--disable-telemetry', |
| '--disable-update-check', |
| '--user-data-dir', '/home/coder/.local/share/code-server', |
| '/home/coder/project', |
| ], |
| }); |
| await workspaceContainer.start(); |
| log(`Docker workspace container '${containerName}' started from image '${imageName}'.`); |
| const createdInspect = await workspaceContainer.inspect(); |
| const createdEntry = registerDockerWorkspaceRuntime(config.id, createdInspect, socketPath); |
| if (!createdEntry) { |
| return { success: false, error: 'DOCKER_PORT_BINDING_MISSING' }; |
| } |
| const heartbeatError = await waitForWorkspaceHeartbeat({ |
| port: createdEntry.port, |
| log, |
| getFailureReason: async () => { |
| const info = await workspaceContainer.inspect(); |
| return info.State.Running ? null : `DOCKER_CONTAINER_EXITED_${info.State.ExitCode}`; |
| }, |
| }); |
| if (heartbeatError) { |
| try { |
| await workspaceContainer.remove({ force: true }); |
| } |
| catch (_a) { |
| |
| } |
| exports.dockerWorkspaces.delete(config.id); |
| return { success: false, error: heartbeatError }; |
| } |
| return { |
| success: true, |
| containerId: createdEntry.containerId, |
| port: createdEntry.port, |
| runtime: 'docker', |
| }; |
| } |
| |
| |
| |
| async function performProvisioning(config) { |
| var _a, _b; |
| const log = (msg) => { |
| if (config.onLog) |
| config.onLog(`[IDX:ENGINE] ${msg}`); |
| exports.provisioningBus.emit(`log:${config.id}`, msg); |
| }; |
| try { |
| log(`Provisioning hermetic environment for '${config.projectName}'...`); |
| |
| try { |
| await storage_1.HFStorage.syncFromDataset((msg) => log(msg)); |
| } |
| catch (e) { |
| log(`[WARN] Persistent profile restoration failed: ${e instanceof Error ? e.message : String(e)}. Proceeding with clean environment.`); |
| } |
| |
| const runtimePaths = await resolveWorkspaceRuntimePaths(config); |
| const workspacePath = ensureRuntimeWorkspacePath(runtimePaths, log); |
| const userDataPath = runtimePaths.userDataPath; |
| |
| const idxConfig = idx_engine_1.IdxEngine.getIdxConfig(workspacePath); |
| log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`); |
| await idx_engine_1.IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => log(msg)); |
| const flagPath = path_1.default.join( workspacePath, '.idx-created'); |
| if (!fs_1.default.existsSync(flagPath)) { |
| if (idxConfig.onCreate) { |
| log(`Executing onCreate lifecycle hook...`); |
| await idx_engine_1.IdxEngine.runHook(workspacePath, 'onCreate', idxConfig.onCreate, (msg) => log(msg)); |
| } |
| fs_1.default.writeFileSync(flagPath, new Date().toISOString()); |
| } |
| const shouldPreferDockerRuntime = env_config_1.ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'docker' |
| || (env_config_1.ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'auto' && process.platform === 'win32'); |
| if (shouldPreferDockerRuntime) { |
| log(`Evaluating Docker runtime for workspace boot...`); |
| const dockerResult = await startDockerWorkspace(config, runtimePaths, workspacePath, log); |
| if (dockerResult) { |
| if (dockerResult.success && idxConfig.onStart) { |
| log(`Executing background onStart lifecycle hooks...`); |
| idx_engine_1.IdxEngine.runHook(workspacePath, 'onStart', idxConfig.onStart, (msg) => log(msg), true); |
| } |
| const finalDockerResult = { |
| ...dockerResult, |
| androidPort: config.withAndroidEmulator ? 6080 : undefined, |
| }; |
| if (dockerResult.success) { |
| exports.provisioningBus.emit(`ready:${config.id}`, finalDockerResult); |
| } |
| else { |
| exports.provisioningBus.emit(`error:${config.id}`, finalDockerResult); |
| } |
| return finalDockerResult; |
| } |
| if (env_config_1.ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'docker') { |
| const dockerUnavailableResult = { |
| success: false, |
| error: 'DOCKER_DAEMON_UNREACHABLE', |
| runtime: 'docker', |
| }; |
| exports.provisioningBus.emit(`error:${config.id}`, dockerUnavailableResult); |
| return dockerUnavailableResult; |
| } |
| log(`[INFO] Docker runtime unavailable. Falling back to native IDE process.`); |
| } |
| |
| const codeServerLaunch = resolveCodeServerLaunch(); |
| const port = findAvailablePort(); |
| const shellCommand = codeServerLaunch.command; |
| const args = codeServerLaunch.args; |
| const spawnCwd = (() => { |
| try { |
| return fs_1.default.realpathSync(workspacePath); |
| } |
| catch (_a) { |
| return workspacePath; |
| } |
| })(); |
| const baseArgs = [ |
| '--auth', 'none', |
| '--bind-addr', `127.0.0.1:${port}`, |
| '--user-data-dir', userDataPath, |
| '--disable-telemetry', |
| '--disable-update-check', |
| workspacePath, |
| ]; |
| const spawnEnv = { |
| ...process.env, |
| HOME: workspacePath, |
| npm_config_cache: runtimePaths.npmCachePath, |
| npm_config_update_notifier: 'false', |
| }; |
| delete spawnEnv.PORT; |
| delete spawnEnv.SERVER_PORT; |
| const launchArgs = [...args, ...baseArgs]; |
| log(`IDE launch prepared. Binary: ${shellCommand} | cwd: ${spawnCwd} | target: ${workspacePath}`); |
| let child; |
| try { |
| child = (0, child_process_1.spawn)(shellCommand, launchArgs, { |
| env: spawnEnv, |
| cwd: spawnCwd, |
| shell: false, |
| }); |
| } |
| catch (error) { |
| const errorMessage = error instanceof Error ? error.message : String(error); |
| throw new Error(`IDE_SPAWN_CONFIGURATION_REJECTED: ${errorMessage}`); |
| } |
| log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`); |
| let childExited = false; |
| let childFailureReason = null; |
| child.on('error', (err) => { |
| childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`; |
| log(`[FATAL] IDE binary failure: ${err.message}`); |
| }); |
| (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => { |
| const out = data.toString().trim(); |
| if (out.includes('listening on')) |
| log(`[IDX:UP] ${out}`); |
| else if (out.length > 0) |
| log(`[IDE:CORE] ${out}`); |
| }); |
| (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => { |
| const err = data.toString().trim(); |
| if (err.length > 0) |
| log(`[IDE:ERR] ${err}`); |
| }); |
| child.on('close', (code, signal) => { |
| childExited = true; |
| if (code !== 0 || signal) { |
| childFailureReason = childFailureReason !== null && childFailureReason !== void 0 ? childFailureReason : `IDE_PROCESS_EXIT_${code !== null && code !== void 0 ? code : 'unknown'}${signal ? `_${signal}` : ''}`; |
| } |
| log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`); |
| exports.nativeProcesses.delete(config.id); |
| }); |
| exports.nativeProcesses.set(config.id, { kind: 'native', pid: child.pid, port, process: child }); |
| const handshakeError = await waitForWorkspaceHeartbeat({ |
| port, |
| log, |
| getFailureReason: async () => { |
| if (!childExited) { |
| return null; |
| } |
| const rawFailureMessage = childFailureReason !== null && childFailureReason !== void 0 ? childFailureReason : 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE'; |
| return codeServerLaunch.usesNpx |
| ? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.` |
| : rawFailureMessage; |
| }, |
| }); |
| if (handshakeError) { |
| const entry = exports.nativeProcesses.get(config.id); |
| if (entry) { |
| entry.process.kill(); |
| exports.nativeProcesses.delete(config.id); |
| } |
| const errResult = { success: false, error: handshakeError }; |
| exports.provisioningBus.emit(`error:${config.id}`, errResult); |
| return errResult; |
| } |
| if (idxConfig.onStart) { |
| log(`Executing background onStart lifecycle hooks...`); |
| idx_engine_1.IdxEngine.runHook(workspacePath, 'onStart', idxConfig.onStart, (msg) => log(msg), true); |
| } |
| const finalResult = { |
| success: true, |
| containerId: `native-${config.id}`, |
| androidPort: config.withAndroidEmulator ? 6080 : undefined, |
| port, |
| runtime: 'native', |
| }; |
| exports.provisioningBus.emit(`ready:${config.id}`, finalResult); |
| return finalResult; |
| } |
| catch (e) { |
| const error = e instanceof Error ? e.message : String(e); |
| log(`[FATAL] Provisioning pipeline collapsed: ${error}`); |
| exports.nativeProcesses.delete(config.id); |
| exports.dockerWorkspaces.delete(config.id); |
| const errResult = { success: false, error: `PROVISIONING_FAILED: ${error}` }; |
| exports.provisioningBus.emit(`error:${config.id}`, errResult); |
| return errResult; |
| } |
| } |
| |
| |
| |
| async function startWorkspaceContainer(config) { |
| const pendingWorkspace = exports.pendingProvisioning.get(config.id); |
| if (pendingWorkspace) { |
| return await pendingWorkspace; |
| } |
| const existingRuntime = getWorkspaceRuntimeEntry(config.id); |
| if (existingRuntime) { |
| return { |
| success: true, |
| containerId: existingRuntime.kind === 'docker' ? existingRuntime.containerId : `native-${config.id}`, |
| port: existingRuntime.port, |
| runtime: existingRuntime.kind, |
| }; |
| } |
| let pending = exports.pendingProvisioning.get(config.id); |
| if (!pending) { |
| pending = performProvisioning(config).finally(() => { |
| exports.pendingProvisioning.delete(config.id); |
| }); |
| exports.pendingProvisioning.set(config.id, pending); |
| } |
| return await pending; |
| } |
| |
| |
| |
| |
| async function reconnectRunningWorkspaces() { |
| var _a, _b, _c, _d, _e; |
| const workspaceRoot = getWorkspaceRootPath(); |
| const runtimeRootPath = getRuntimeRootPath(); |
| console.log(`[BOOT] Probing filesystem segment: ${workspaceRoot} for existing sessions...`); |
| try { |
| const dockerResolution = await (0, client_1.resolveDockerClient)(env_config_1.ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); |
| if (dockerResolution) { |
| const runningContainers = await dockerResolution.docker.listContainers({ |
| filters: { |
| label: [`${WORKSPACE_RUNTIME_LABEL}=${WORKSPACE_RUNTIME_LABEL_VALUE}`], |
| }, |
| }); |
| for (const containerInfo of runningContainers) { |
| const workspaceId = (_a = containerInfo.Labels) === null || _a === void 0 ? void 0 : _a[WORKSPACE_ID_LABEL]; |
| const port = getPublishedPortForListEntry(containerInfo); |
| if (!workspaceId || !port) { |
| continue; |
| } |
| exports.dockerWorkspaces.set(workspaceId, { |
| kind: 'docker', |
| containerId: containerInfo.Id, |
| containerName: (_c = (_b = containerInfo.Names[0]) === null || _b === void 0 ? void 0 : _b.replace(/^\//, '')) !== null && _c !== void 0 ? _c : getWorkspaceContainerName(workspaceId), |
| imageName: containerInfo.Image, |
| port, |
| socketPath: dockerResolution.socketPath, |
| }); |
| console.log(`[RECONNECT] Restored Docker workspace ${workspaceId} on port ${port}.`); |
| } |
| } |
| } |
| catch (error) { |
| console.warn(`[RECONNECT:WARN] Docker workspace restore failed: ${error instanceof Error ? error.message : String(error)}`); |
| } |
| try { |
| |
| |
| const psCmd = process.platform === 'win32' ? 'tasklist' : "ps aux | grep code-server | grep -v grep"; |
| const output = (0, child_process_1.execSync)(psCmd).toString(); |
| const lines = output.split('\n'); |
| for (const line of lines) { |
| |
| const bindMatch = line.match(/--bind-addr 127\.0\.0\.1:(\d+)/); |
| const userDataMatch = line.match(/\.codeverse-runtime[\/\\]([a-zA-Z0-9]{8})-userdata/); |
| const runtimePathMatch = line.match(/\.codeverse-runtime[\/\\]([a-zA-Z0-9]{8})(?:\s|$)/); |
| const legacyPathMatch = line.match(/[ /](?:w|workspaces)[\/\\]([a-zA-Z0-9]{8})/); |
| if (bindMatch) { |
| const shortId = (_e = (_d = userDataMatch === null || userDataMatch === void 0 ? void 0 : userDataMatch[1]) !== null && _d !== void 0 ? _d : runtimePathMatch === null || runtimePathMatch === void 0 ? void 0 : runtimePathMatch[1]) !== null && _e !== void 0 ? _e : legacyPathMatch === null || legacyPathMatch === void 0 ? void 0 : legacyPathMatch[1]; |
| if (!shortId) { |
| continue; |
| } |
| const port = parseInt(bindMatch[1], 10); |
| const metadataPath = path_1.default.join(runtimeRootPath, `${shortId}.id`); |
| const legacyIdFile = path_1.default.join(workspaceRoot, shortId, '.codeverse-id'); |
| let foundFullId = ""; |
| if (fs_1.default.existsSync(metadataPath)) { |
| foundFullId = fs_1.default.readFileSync(metadataPath, 'utf-8').trim(); |
| } |
| else if (fs_1.default.existsSync(legacyIdFile)) { |
| foundFullId = fs_1.default.readFileSync(legacyIdFile, 'utf-8').trim(); |
| } |
| else { |
| |
| foundFullId = shortId; |
| console.warn(`[RECONNECT:WARN] No .codeverse-id for session ${shortId}. Using prefix mapping.`); |
| } |
| if (foundFullId && !exports.nativeProcesses.has(foundFullId)) { |
| |
| const psParts = line.trim().split(/\s+/); |
| const pid = parseInt(psParts[1]); |
| console.log(`[RECONNECT] Identified active IDE ${foundFullId} (PID: ${pid}) on port ${port}. Restoration complete.`); |
| exports.nativeProcesses.set(foundFullId, { |
| kind: 'native', |
| pid, |
| port, |
| process: { |
| pid, |
| kill: () => { |
| try { |
| process.kill(pid, 'SIGKILL'); |
| return true; |
| } |
| catch (_a) { |
| try { |
| (0, child_process_1.execSync)(`fuser -k ${port}/tcp`); |
| } |
| catch (_b) { } |
| return true; |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| } |
| catch (_f) { |
| |
| } |
| } |
| |
| |
| |
| function startEngineWatchdog() { |
| setInterval(async () => { |
| for (const [id, entry] of exports.nativeProcesses.entries()) { |
| try { |
| |
| try { |
| process.kill(entry.pid, 0); |
| } |
| catch (_a) { |
| console.log(`[WATCHDOG] Process ${entry.pid} for ${id} is missing. Pruning.`); |
| exports.nativeProcesses.delete(id); |
| continue; |
| } |
| |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 2000); |
| try { |
| const res = await fetch(`http://127.0.0.1:${entry.port}`, { signal: controller.signal }); |
| if (!res.ok) |
| throw new Error('Unhealthy'); |
| } |
| catch (_b) { |
| console.warn(`[WATCHDOG] IDE ${id} (Port ${entry.port}) is non-responsive.`); |
| |
| } |
| finally { |
| clearTimeout(timeoutId); |
| } |
| } |
| catch (e) { |
| console.error(`[WATCHDOG:ERR] ${e}`); |
| } |
| } |
| }, 60000); |
| } |
| startEngineWatchdog(); |
| async function stopDockerWorkspace(id) { |
| var _a; |
| const dockerEntry = exports.dockerWorkspaces.get(id); |
| const docker = dockerEntry |
| ? (0, client_1.createDockerClient)(dockerEntry.socketPath) |
| : (_a = (await (0, client_1.resolveDockerClient)(env_config_1.ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS))) === null || _a === void 0 ? void 0 : _a.docker; |
| if (!docker) { |
| exports.dockerWorkspaces.delete(id); |
| return false; |
| } |
| const existingContainerInfo = await resolveExistingWorkspaceContainer(docker, id); |
| if (!existingContainerInfo) { |
| exports.dockerWorkspaces.delete(id); |
| return false; |
| } |
| const container = docker.getContainer(existingContainerInfo.Id); |
| try { |
| const inspectInfo = await container.inspect(); |
| if (inspectInfo.State.Running) { |
| await container.stop({ t: 10 }); |
| } |
| } |
| catch (_b) { |
| |
| } |
| try { |
| await container.remove({ force: true }); |
| } |
| catch (_c) { |
| |
| } |
| exports.dockerWorkspaces.delete(id); |
| return true; |
| } |
| |
| |
| |
| async function stopWorkspaceContainer(id) { |
| const nativeStopped = await stopNativeWorkspace(id); |
| if (nativeStopped) { |
| return { success: true }; |
| } |
| const dockerStopped = await stopDockerWorkspace(id); |
| return { success: dockerStopped }; |
| } |
| |
| |
| |
| class DockerManager { |
| async getContainerStatus(id) { |
| if (isWorkspaceRunning(id)) |
| return "running"; |
| return "stopped"; |
| } |
| async stopContainer(id) { |
| return (await stopWorkspaceContainer(id)).success; |
| } |
| async startWorkspace(config) { |
| const result = await startWorkspaceContainer(config); |
| return result.success; |
| } |
| } |
| exports.DockerManager = DockerManager; |
| exports.dockerManager = new DockerManager(); |
|
|