From 05348e31b0f18733d0de7955a88d12b3874d2aae Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 8 Jan 2026 19:43:40 +0100 Subject: [PATCH] chore: verbose logging for mount commands --- Dockerfile | 5 ++- .../modules/backends/nfs/nfs-backend.ts | 37 ++++++++++++++----- .../modules/backends/utils/backend-utils.ts | 27 +++++++++----- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index c9816cc3..b100b1f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,9 @@ ENV VITE_RESTIC_VERSION=${RESTIC_VERSION} \ VITE_RCLONE_VERSION=${RCLONE_VERSION} \ VITE_SHOUTRRR_VERSION=${SHOUTRRR_VERSION} -RUN apk upgrade --no-cache && \ - apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs tini nfs-utils cifs-utils +RUN apk update --no-cache && \ + apk upgrade --no-cache && \ + apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs tini nfs-utils cifs-utils util-linux ENTRYPOINT ["/sbin/tini", "-s", "--"] diff --git a/app/server/modules/backends/nfs/nfs-backend.ts b/app/server/modules/backends/nfs/nfs-backend.ts index 314b2554..7b19e10f 100644 --- a/app/server/modules/backends/nfs/nfs-backend.ts +++ b/app/server/modules/backends/nfs/nfs-backend.ts @@ -1,5 +1,6 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; +import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes"; import { OPERATION_TIMEOUT } from "../../../core/constants"; import { toMessage } from "../../../utils/errors"; import { logger } from "../../../utils/logger"; @@ -7,19 +8,24 @@ import { getMountForPath } from "../../../utils/mountinfo"; import { withTimeout } from "../../../utils/timeout"; import type { VolumeBackend } from "../backend"; import { executeMount, executeUnmount } from "../utils/backend-utils"; -import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes"; const mount = async (config: BackendConfig, path: string) => { logger.debug(`Mounting volume ${path}...`); if (config.backend !== "nfs") { logger.error("Provided config is not for NFS backend"); - return { status: BACKEND_STATUS.error, error: "Provided config is not for NFS backend" }; + return { + status: BACKEND_STATUS.error, + error: "Provided config is not for NFS backend", + }; } if (os.platform() !== "linux") { logger.error("NFS mounting is only supported on Linux hosts."); - return { status: BACKEND_STATUS.error, error: "NFS mounting is only supported on Linux hosts." }; + return { + status: BACKEND_STATUS.error, + error: "NFS mounting is only supported on Linux hosts.", + }; } const { status } = await checkHealth(path); @@ -28,7 +34,9 @@ const mount = async (config: BackendConfig, path: string) => { } if (status === "error") { - logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`); + logger.debug( + `Trying to unmount any existing mounts at ${path} before mounting...`, + ); await unmount(path); } @@ -36,7 +44,7 @@ const mount = async (config: BackendConfig, path: string) => { await fs.mkdir(path, { recursive: true }); const source = `${config.server}:${config.exportPath}`; - const options = [`vers=${config.version}`, `port=${config.port}`]; + const options = [`vers=${config.version}`, `port=${config.port}`, "nolock"]; if (config.readOnly) { options.push("ro"); } @@ -62,7 +70,10 @@ const mount = async (config: BackendConfig, path: string) => { const unmount = async (path: string) => { if (os.platform() !== "linux") { logger.error("NFS unmounting is only supported on Linux hosts."); - return { status: BACKEND_STATUS.error, error: "NFS unmounting is only supported on Linux hosts." }; + return { + status: BACKEND_STATUS.error, + error: "NFS unmounting is only supported on Linux hosts.", + }; } const run = async () => { @@ -83,7 +94,10 @@ const unmount = async (path: string) => { try { return await withTimeout(run(), OPERATION_TIMEOUT, "NFS unmount"); } catch (err) { - logger.error("Error unmounting NFS volume", { path, error: toMessage(err) }); + logger.error("Error unmounting NFS volume", { + path, + error: toMessage(err), + }); return { status: BACKEND_STATUS.error, error: toMessage(err) }; } }; @@ -103,7 +117,9 @@ const checkHealth = async (path: string) => { } if (!mount.fstype.startsWith("nfs")) { - throw new Error(`Path ${path} is not mounted as NFS (found ${mount.fstype}).`); + throw new Error( + `Path ${path} is not mounted as NFS (found ${mount.fstype}).`, + ); } logger.debug(`NFS volume at ${path} is healthy and mounted.`); @@ -121,7 +137,10 @@ const checkHealth = async (path: string) => { } }; -export const makeNfsBackend = (config: BackendConfig, path: string): VolumeBackend => ({ +export const makeNfsBackend = ( + config: BackendConfig, + path: string, +): VolumeBackend => ({ mount: () => mount(config, path), unmount: () => unmount(path), checkHealth: () => checkHealth(path), diff --git a/app/server/modules/backends/utils/backend-utils.ts b/app/server/modules/backends/utils/backend-utils.ts index e610217d..ea11570c 100644 --- a/app/server/modules/backends/utils/backend-utils.ts +++ b/app/server/modules/backends/utils/backend-utils.ts @@ -1,23 +1,30 @@ import * as fs from "node:fs/promises"; import * as npath from "node:path"; +import { $ } from "bun"; import { toMessage } from "../../../utils/errors"; import { logger } from "../../../utils/logger"; -import { $ } from "bun"; export const executeMount = async (args: string[]): Promise => { - let stderr: string | undefined; + const shouldBeVerbose = process.env.LOG_LEVEL === "debug" || process.env.NODE_ENV !== "production"; + const hasVerboseFlag = args.some((arg) => arg === "-v" || arg.startsWith("-vv")); + const effectiveArgs = shouldBeVerbose && !hasVerboseFlag ? ["-vvv", ...args] : args; - logger.debug(`Executing mount ${args.join(" ")}`); - const result = await $`mount ${args}`.nothrow(); - stderr = result.stderr.toString(); + logger.debug(`Executing mount ${effectiveArgs.join(" ")}`); + const result = await $`mount ${effectiveArgs}`.nothrow(); - if (stderr?.trim()) { - logger.warn(stderr.trim()); + const stdout = result.stdout.toString().trim(); + const stderr = result.stderr.toString().trim(); + + if (result.exitCode === 0) { + logger.debug(stdout); + logger.debug(stderr); + return; } - if (result.exitCode !== 0) { - throw new Error(`Mount command failed with exit code ${result.exitCode}: ${stderr?.trim()}`); - } + logger.warn(stdout); + logger.warn(stderr); + + throw new Error(`Mount command failed with exit code ${result.exitCode}: ${stderr || stdout || "unknown error"}`); }; export const executeUnmount = async (path: string): Promise => {