feat: host/proc optional capability

This commit is contained in:
Nicolas Meienberger
2025-11-08 10:26:50 +01:00
parent 9d27bffc21
commit 42497be4b5
2 changed files with 29 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import { logger } from "../utils/logger";
export type SystemCapabilities = { export type SystemCapabilities = {
docker: boolean; docker: boolean;
hostProc: boolean;
}; };
let capabilitiesPromise: Promise<SystemCapabilities> | null = null; let capabilitiesPromise: Promise<SystemCapabilities> | null = null;
@@ -28,6 +29,7 @@ export async function getCapabilities(): Promise<SystemCapabilities> {
async function detectCapabilities(): Promise<SystemCapabilities> { async function detectCapabilities(): Promise<SystemCapabilities> {
return { return {
docker: await detectDocker(), docker: await detectDocker(),
hostProc: await detectHostProc(),
}; };
} }
@@ -53,3 +55,23 @@ async function detectDocker(): Promise<boolean> {
return false; 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<boolean> {
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;
}
}

View File

@@ -2,24 +2,24 @@ import { execFile as execFileCb } from "node:child_process";
import * as fs from "node:fs/promises"; import * as fs from "node:fs/promises";
import * as npath from "node:path"; import * as npath from "node:path";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { getCapabilities } from "../../../core/capabilities";
import { OPERATION_TIMEOUT } from "../../../core/constants"; import { OPERATION_TIMEOUT } from "../../../core/constants";
import { toMessage } from "../../../utils/errors"; import { toMessage } from "../../../utils/errors";
import { logger } from "../../../utils/logger"; import { logger } from "../../../utils/logger";
import { access, constants } from "node:fs/promises";
const execFile = promisify(execFileCb); const execFile = promisify(execFileCb);
export const executeMount = async (args: string[]): Promise<void> => { export const executeMount = async (args: string[]): Promise<void> => {
const capabilities = await getCapabilities();
let stderr: string | undefined; let stderr: string | undefined;
try { if (capabilities.hostProc) {
await access("/host/proc", constants.F_OK);
const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "mount", ...args], { const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "mount", ...args], {
timeout: OPERATION_TIMEOUT, timeout: OPERATION_TIMEOUT,
maxBuffer: 1024 * 1024, maxBuffer: 1024 * 1024,
}); });
stderr = result.stderr; stderr = result.stderr;
} catch (_) { } else {
const result = await execFile("mount", args, { const result = await execFile("mount", args, {
timeout: OPERATION_TIMEOUT, timeout: OPERATION_TIMEOUT,
maxBuffer: 1024 * 1024, maxBuffer: 1024 * 1024,
@@ -33,16 +33,16 @@ export const executeMount = async (args: string[]): Promise<void> => {
}; };
export const executeUnmount = async (path: string): Promise<void> => { export const executeUnmount = async (path: string): Promise<void> => {
const capabilities = await getCapabilities();
let stderr: string | undefined; let stderr: string | undefined;
try { if (capabilities.hostProc) {
await access("/host/proc", constants.F_OK);
const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "umount", "-l", "-f", path], { const result = await execFile("nsenter", ["--mount=/host/proc/1/ns/mnt", "umount", "-l", "-f", path], {
timeout: OPERATION_TIMEOUT, timeout: OPERATION_TIMEOUT,
maxBuffer: 1024 * 1024, maxBuffer: 1024 * 1024,
}); });
stderr = result.stderr; stderr = result.stderr;
} catch (_) { } else {
const result = await execFile("umount", ["-l", "-f", path], { const result = await execFile("umount", ["-l", "-f", path], {
timeout: OPERATION_TIMEOUT, timeout: OPERATION_TIMEOUT,
maxBuffer: 1024 * 1024, maxBuffer: 1024 * 1024,