diff --git a/README.md b/README.md index ea0d4af..49df0e5 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Ironmount allows you to easily restore your data from backups. To restore data, Ironmount is capable of propagating mounted volumes from within the container to the host system. This is particularly useful when you want to access the mounted data directly from the host to use it with other applications or services. -In order to enable this feature, you need to run Ironmount with privileged mode and mount /proc from the host. Here is an example of how to set this up in your `docker-compose.yml` file: +In order to enable this feature, you need to change your bind mount `/var/lib/ironmount` to use the `:rshared` flag. Here is an example of how to set this up in your `docker-compose.yml` file: ```diff services: @@ -132,16 +132,13 @@ services: image: ghcr.io/nicotsx/ironmount:v0.6 container_name: ironmount restart: unless-stopped -- cap_add: -- - SYS_ADMIN -+ privileged: true ports: - "4096:4096" devices: - /dev/fuse:/dev/fuse volumes: - - /var/lib/ironmount:/var/lib/ironmount -+ - /proc:/host/proc +- - /var/lib/ironmount:/var/lib/ironmount ++ - /var/lib/ironmount:/var/lib/ironmount:rshared ``` Restart the Ironmount container to apply the changes: @@ -155,7 +152,7 @@ docker compose up -d Ironmount can also be used as a Docker volume plugin, allowing you to mount your volumes directly into other Docker containers. This enables seamless integration with your containerized applications. -In order to enable this feature, you need to run Ironmount with privileged mode and mount several items from the host. Here is an example of how to set this up in your `docker-compose.yml` file: +In order to enable this feature, you need to run Ironmount with several items shared from the host. Here is an example of how to set this up in your `docker-compose.yml` file: ```diff services: @@ -163,16 +160,15 @@ services: image: ghcr.io/nicotsx/ironmount:v0.6 container_name: ironmount restart: unless-stopped -- cap_add: -- - SYS_ADMIN -+ privileged: true + cap_add: + - SYS_ADMIN ports: - "4096:4096" devices: - /dev/fuse:/dev/fuse volumes: - - /var/lib/ironmount:/var/lib/ironmount -+ - /proc:/host/proc +- - /var/lib/ironmount:/var/lib/ironmount ++ - /var/lib/ironmount:/var/lib/ironmount:rshared + - /run/docker/plugins:/run/docker/plugins + - /var/run/docker.sock:/var/run/docker.sock ``` diff --git a/apps/server/build.ts b/apps/server/build.ts index 5d22973..c19f64d 100644 --- a/apps/server/build.ts +++ b/apps/server/build.ts @@ -6,9 +6,8 @@ await Bun.build({ sourcemap: true, minify: { whitespace: true, - identifiers: true, + identifiers: false, syntax: true, - keepNames: true, }, external: ["ssh2"], }); diff --git a/apps/server/src/core/capabilities.ts b/apps/server/src/core/capabilities.ts index 94e36d2..1b87a42 100644 --- a/apps/server/src/core/capabilities.ts +++ b/apps/server/src/core/capabilities.ts @@ -4,7 +4,6 @@ import { logger } from "../utils/logger"; export type SystemCapabilities = { docker: boolean; - hostProc: boolean; }; let capabilitiesPromise: Promise | null = null; @@ -29,7 +28,6 @@ export async function getCapabilities(): Promise { async function detectCapabilities(): Promise { return { docker: await detectDocker(), - hostProc: await detectHostProc(), }; } @@ -55,23 +53,3 @@ 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 3f5197d..3faeff6 100644 --- a/apps/server/src/modules/backends/utils/backend-utils.ts +++ b/apps/server/src/modules/backends/utils/backend-utils.ts @@ -1,31 +1,14 @@ -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"; - -const execFile = promisify(execFileCb); +import { $ } from "bun"; export const executeMount = async (args: string[]): Promise => { - const capabilities = await getCapabilities(); let stderr: string | undefined; - 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; - } else { - const result = await execFile("mount", args, { - timeout: OPERATION_TIMEOUT, - maxBuffer: 1024 * 1024, - }); - stderr = result.stderr; - } + const result = await $`mount ${args}`.nothrow(); + stderr = result.stderr.toString(); if (stderr?.trim()) { logger.warn(stderr.trim()); @@ -33,22 +16,10 @@ export const executeMount = async (args: string[]): Promise => { }; export const executeUnmount = async (path: string): Promise => { - const capabilities = await getCapabilities(); let stderr: string | undefined; - 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; - } else { - const result = await execFile("umount", ["-l", "-f", path], { - timeout: OPERATION_TIMEOUT, - maxBuffer: 1024 * 1024, - }); - stderr = result.stderr; - } + const result = await $`umount -l -f ${path}`.nothrow(); + stderr = result.stderr.toString(); if (stderr?.trim()) { logger.warn(stderr.trim()); diff --git a/apps/server/src/utils/restic.ts b/apps/server/src/utils/restic.ts index 96e2382..175641a 100644 --- a/apps/server/src/utils/restic.ts +++ b/apps/server/src/utils/restic.ts @@ -189,7 +189,7 @@ const backup = async ( let stdout = ""; - await safeSpawn({ + const res = await safeSpawn({ command: "restic", args, env, @@ -210,6 +210,11 @@ const backup = async ( }, }); + if (res.exitCode !== 0) { + logger.error(`Restic backup failed: ${res.stderr}`); + throw new Error(`Restic backup failed: ${res.stderr}`); + } + const lastLine = stdout.trim(); const resSummary = JSON.parse(lastLine ?? "{}"); diff --git a/apps/server/src/utils/spawn.ts b/apps/server/src/utils/spawn.ts index 023ec95..fd465aa 100644 --- a/apps/server/src/utils/spawn.ts +++ b/apps/server/src/utils/spawn.ts @@ -12,10 +12,19 @@ interface Params { finally?: () => Promise | void; } +type SpawnResult = { + exitCode: number; + stdout: string; + stderr: string; +}; + export const safeSpawn = (params: Params) => { const { command, args, env = {}, signal, ...callbacks } = params; - return new Promise((resolve, reject) => { + return new Promise((resolve) => { + let stdoutData = ""; + let stderrData = ""; + const child = spawn(command, args, { env: { ...process.env, ...env }, signal: signal, @@ -24,12 +33,16 @@ export const safeSpawn = (params: Params) => { child.stdout.on("data", (data) => { if (callbacks.onStdout) { callbacks.onStdout(data.toString()); + } else { + stdoutData += data.toString(); } }); child.stderr.on("data", (data) => { if (callbacks.onStderr) { callbacks.onStderr(data.toString()); + } else { + stderrData += data.toString(); } }); @@ -40,7 +53,12 @@ export const safeSpawn = (params: Params) => { if (callbacks.finally) { await callbacks.finally(); } - reject(error); + + resolve({ + exitCode: -1, + stdout: stdoutData, + stderr: stderrData, + }); }); child.on("close", async (code) => { @@ -50,7 +68,12 @@ export const safeSpawn = (params: Params) => { if (callbacks.finally) { await callbacks.finally(); } - resolve(code); + + resolve({ + exitCode: code === null ? -1 : code, + stdout: stdoutData, + stderr: stderrData, + }); }); }); }; diff --git a/docker-compose.yml b/docker-compose.yml index 94fc6f8..d7b100d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,4 +34,6 @@ services: ports: - "4096:4096" volumes: - - /var/lib/ironmount/:/var/lib/ironmount/ + - /var/lib/ironmount:/var/lib/ironmount:rshared + - /run/docker/plugins:/run/docker/plugins + - /var/run/docker.sock:/var/run/docker.sock