diff --git a/apps/server/src/core/capabilities.ts b/apps/server/src/core/capabilities.ts index 1b87a42..94e36d2 100644 --- a/apps/server/src/core/capabilities.ts +++ b/apps/server/src/core/capabilities.ts @@ -4,6 +4,7 @@ import { logger } from "../utils/logger"; export type SystemCapabilities = { docker: boolean; + hostProc: boolean; }; let capabilitiesPromise: Promise | null = null; @@ -28,6 +29,7 @@ export async function getCapabilities(): Promise { async function detectCapabilities(): Promise { return { docker: await detectDocker(), + hostProc: await detectHostProc(), }; } @@ -53,3 +55,23 @@ async function detectDocker(): Promise { return false; } } + +/** + * Checks if host proc is available by attempting to access /host/proc/1/ns/mnt + * This allows using nsenter to execute mount commands in the host namespace + */ +async function detectHostProc(): Promise { + try { + await fs.access("/host/proc/1/ns/mnt"); + + logger.info("Host proc capability: enabled"); + return true; + } catch (_) { + logger.warn( + "Host proc capability: disabled. " + + "To enable: mount /proc:/host/proc:ro in docker-compose.yml. " + + "Mounts will be executed in container namespace instead of host namespace.", + ); + return false; + } +} diff --git a/apps/server/src/modules/backends/utils/backend-utils.ts b/apps/server/src/modules/backends/utils/backend-utils.ts index abdb98b..3f5197d 100644 --- a/apps/server/src/modules/backends/utils/backend-utils.ts +++ b/apps/server/src/modules/backends/utils/backend-utils.ts @@ -2,24 +2,24 @@ import { execFile as execFileCb } from "node:child_process"; import * as fs from "node:fs/promises"; import * as npath from "node:path"; import { promisify } from "node:util"; +import { getCapabilities } from "../../../core/capabilities"; import { OPERATION_TIMEOUT } from "../../../core/constants"; import { toMessage } from "../../../utils/errors"; import { logger } from "../../../utils/logger"; -import { access, constants } from "node:fs/promises"; const execFile = promisify(execFileCb); export const executeMount = async (args: string[]): Promise => { + const capabilities = await getCapabilities(); let stderr: string | undefined; - try { - await access("/host/proc", constants.F_OK); + if (capabilities.hostProc) { const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "mount", ...args], { timeout: OPERATION_TIMEOUT, maxBuffer: 1024 * 1024, }); stderr = result.stderr; - } catch (_) { + } else { const result = await execFile("mount", args, { timeout: OPERATION_TIMEOUT, maxBuffer: 1024 * 1024, @@ -33,16 +33,16 @@ export const executeMount = async (args: string[]): Promise => { }; export const executeUnmount = async (path: string): Promise => { + const capabilities = await getCapabilities(); let stderr: string | undefined; - try { - await access("/host/proc", constants.F_OK); + if (capabilities.hostProc) { const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "umount", "-l", "-f", path], { timeout: OPERATION_TIMEOUT, maxBuffer: 1024 * 1024, }); stderr = result.stderr; - } catch (_) { + } else { const result = await execFile("umount", ["-l", "-f", path], { timeout: OPERATION_TIMEOUT, maxBuffer: 1024 * 1024,