mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
fix(mounts): use bun shell instead of execFile
This commit is contained in:
20
README.md
20
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
|
||||
```
|
||||
|
||||
@@ -6,9 +6,8 @@ await Bun.build({
|
||||
sourcemap: true,
|
||||
minify: {
|
||||
whitespace: true,
|
||||
identifiers: true,
|
||||
identifiers: false,
|
||||
syntax: true,
|
||||
keepNames: true,
|
||||
},
|
||||
external: ["ssh2"],
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { logger } from "../utils/logger";
|
||||
|
||||
export type SystemCapabilities = {
|
||||
docker: boolean;
|
||||
hostProc: boolean;
|
||||
};
|
||||
|
||||
let capabilitiesPromise: Promise<SystemCapabilities> | null = null;
|
||||
@@ -29,7 +28,6 @@ export async function getCapabilities(): Promise<SystemCapabilities> {
|
||||
async function detectCapabilities(): Promise<SystemCapabilities> {
|
||||
return {
|
||||
docker: await detectDocker(),
|
||||
hostProc: await detectHostProc(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,23 +53,3 @@ async function detectDocker(): Promise<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
};
|
||||
|
||||
export const executeUnmount = async (path: string): Promise<void> => {
|
||||
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());
|
||||
|
||||
@@ -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 ?? "{}");
|
||||
|
||||
|
||||
@@ -12,10 +12,19 @@ interface Params {
|
||||
finally?: () => Promise<void> | 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<SpawnResult>((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,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user