refactor: reduce startup mounting noise (#177)

- Skip mount early if volume is not in error or neither mounted
- Error healthchecks early if volume is not mounted
This commit is contained in:
Nico 2025-12-18 22:19:21 +01:00 committed by GitHub
parent a17da2562f
commit fd5193b5e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 97 additions and 66 deletions

View file

@ -27,8 +27,10 @@ const mount = async (config: BackendConfig, path: string) => {
return { status: BACKEND_STATUS.mounted }; return { status: BACKEND_STATUS.mounted };
} }
logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`); if (status === "error") {
await unmount(path); logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`);
await unmount(path);
}
const run = async () => { const run = async () => {
await fs.mkdir(path, { recursive: true }); await fs.mkdir(path, { recursive: true });
@ -64,16 +66,15 @@ const unmount = async (path: string) => {
} }
const run = async () => { const run = async () => {
try { const mount = await getMountForPath(path);
await fs.access(path); if (!mount || mount.mountPoint !== path) {
} catch { logger.debug(`Path ${path} is not a mount point. Skipping unmount.`);
logger.warn(`Path ${path} does not exist. Skipping unmount.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
} }
await executeUnmount(path); await executeUnmount(path);
await fs.rmdir(path); await fs.rmdir(path).catch(() => {});
logger.info(`NFS volume at ${path} unmounted successfully.`); logger.info(`NFS volume at ${path} unmounted successfully.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
@ -89,13 +90,20 @@ const unmount = async (path: string) => {
const checkHealth = async (path: string) => { const checkHealth = async (path: string) => {
const run = async () => { const run = async () => {
logger.debug(`Checking health of NFS volume at ${path}...`); try {
await fs.access(path); await fs.access(path);
} catch {
throw new Error("Volume is not mounted");
}
const mount = await getMountForPath(path); const mount = await getMountForPath(path);
if (!mount || !mount.fstype.startsWith("nfs")) { if (!mount || mount.mountPoint !== path) {
throw new Error(`Path ${path} is not mounted as NFS.`); throw new Error("Volume is not mounted");
}
if (!mount.fstype.startsWith("nfs")) {
throw new Error(`Path ${path} is not mounted as NFS (found ${mount.fstype}).`);
} }
logger.debug(`NFS volume at ${path} is healthy and mounted.`); logger.debug(`NFS volume at ${path} is healthy and mounted.`);
@ -105,8 +113,11 @@ const checkHealth = async (path: string) => {
try { try {
return await withTimeout(run(), OPERATION_TIMEOUT, "NFS health check"); return await withTimeout(run(), OPERATION_TIMEOUT, "NFS health check");
} catch (error) { } catch (error) {
logger.error("NFS volume health check failed:", toMessage(error)); const message = toMessage(error);
return { status: BACKEND_STATUS.error, error: toMessage(error) }; if (message !== "Volume is not mounted") {
logger.error("NFS volume health check failed:", message);
}
return { status: BACKEND_STATUS.error, error: message };
} }
}; };

View file

@ -28,8 +28,10 @@ const mount = async (config: BackendConfig, path: string) => {
return { status: BACKEND_STATUS.mounted }; return { status: BACKEND_STATUS.mounted };
} }
logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`); if (status === "error") {
await unmount(path); logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`);
await unmount(path);
}
const run = async () => { const run = async () => {
await fs.mkdir(path, { recursive: true }); await fs.mkdir(path, { recursive: true });
@ -76,15 +78,14 @@ const unmount = async (path: string) => {
} }
const run = async () => { const run = async () => {
try { const mount = await getMountForPath(path);
await fs.access(path); if (!mount || mount.mountPoint !== path) {
} catch (e) { logger.debug(`Path ${path} is not a mount point. Skipping unmount.`);
logger.warn(`Path ${path} does not exist. Skipping unmount.`, e);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
} }
await executeUnmount(path); await executeUnmount(path);
await fs.rmdir(path); await fs.rmdir(path).catch(() => {});
logger.info(`Rclone volume at ${path} unmounted successfully.`); logger.info(`Rclone volume at ${path} unmounted successfully.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
@ -100,13 +101,20 @@ const unmount = async (path: string) => {
const checkHealth = async (path: string) => { const checkHealth = async (path: string) => {
const run = async () => { const run = async () => {
logger.debug(`Checking health of rclone volume at ${path}...`); try {
await fs.access(path); await fs.access(path);
} catch {
throw new Error("Volume is not mounted");
}
const mount = await getMountForPath(path); const mount = await getMountForPath(path);
if (!mount || mount.fstype !== "fuse.rclone") { if (!mount || mount.mountPoint !== path) {
throw new Error(`Path ${path} is not mounted as rclone.`); throw new Error("Volume is not mounted");
}
if (!mount.fstype.includes("rclone")) {
throw new Error(`Path ${path} is not mounted as rclone (found ${mount.fstype}).`);
} }
logger.debug(`Rclone volume at ${path} is healthy and mounted.`); logger.debug(`Rclone volume at ${path} is healthy and mounted.`);
@ -116,8 +124,11 @@ const checkHealth = async (path: string) => {
try { try {
return await withTimeout(run(), OPERATION_TIMEOUT, "Rclone health check"); return await withTimeout(run(), OPERATION_TIMEOUT, "Rclone health check");
} catch (error) { } catch (error) {
logger.error("Rclone volume health check failed:", toMessage(error)); const message = toMessage(error);
return { status: BACKEND_STATUS.error, error: toMessage(error) }; if (message !== "Volume is not mounted") {
logger.error("Rclone volume health check failed:", message);
}
return { status: BACKEND_STATUS.error, error: message };
} }
}; };

View file

@ -28,8 +28,10 @@ const mount = async (config: BackendConfig, path: string) => {
return { status: BACKEND_STATUS.mounted }; return { status: BACKEND_STATUS.mounted };
} }
logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`); if (status === "error") {
await unmount(path); logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`);
await unmount(path);
}
const run = async () => { const run = async () => {
await fs.mkdir(path, { recursive: true }); await fs.mkdir(path, { recursive: true });
@ -80,16 +82,15 @@ const unmount = async (path: string) => {
} }
const run = async () => { const run = async () => {
try { const mount = await getMountForPath(path);
await fs.access(path); if (!mount || mount.mountPoint !== path) {
} catch { logger.debug(`Path ${path} is not a mount point. Skipping unmount.`);
logger.warn(`Path ${path} does not exist. Skipping unmount.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
} }
await executeUnmount(path); await executeUnmount(path);
await fs.rmdir(path); await fs.rmdir(path).catch(() => {});
logger.info(`SMB volume at ${path} unmounted successfully.`); logger.info(`SMB volume at ${path} unmounted successfully.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
@ -105,13 +106,20 @@ const unmount = async (path: string) => {
const checkHealth = async (path: string) => { const checkHealth = async (path: string) => {
const run = async () => { const run = async () => {
logger.debug(`Checking health of SMB volume at ${path}...`); try {
await fs.access(path); await fs.access(path);
} catch {
throw new Error("Volume is not mounted");
}
const mount = await getMountForPath(path); const mount = await getMountForPath(path);
if (!mount || mount.fstype !== "cifs") { if (!mount || mount.mountPoint !== path) {
throw new Error(`Path ${path} is not mounted as CIFS/SMB.`); throw new Error("Volume is not mounted");
}
if (mount.fstype !== "cifs") {
throw new Error(`Path ${path} is not mounted as CIFS/SMB (found ${mount.fstype}).`);
} }
logger.debug(`SMB volume at ${path} is healthy and mounted.`); logger.debug(`SMB volume at ${path} is healthy and mounted.`);
@ -121,8 +129,11 @@ const checkHealth = async (path: string) => {
try { try {
return await withTimeout(run(), OPERATION_TIMEOUT, "SMB health check"); return await withTimeout(run(), OPERATION_TIMEOUT, "SMB health check");
} catch (error) { } catch (error) {
logger.error("SMB volume health check failed:", toMessage(error)); const message = toMessage(error);
return { status: BACKEND_STATUS.error, error: toMessage(error) }; if (message !== "Volume is not mounted") {
logger.error("SMB volume health check failed:", message);
}
return { status: BACKEND_STATUS.error, error: message };
} }
}; };

View file

@ -1,7 +1,5 @@
import { execFile as execFileCb } from "node:child_process";
import * as fs from "node:fs/promises"; import * as fs from "node:fs/promises";
import * as os from "node:os"; import * as os from "node:os";
import { promisify } from "node:util";
import { OPERATION_TIMEOUT } from "../../../core/constants"; import { OPERATION_TIMEOUT } from "../../../core/constants";
import { cryptoUtils } from "../../../utils/crypto"; import { cryptoUtils } from "../../../utils/crypto";
import { toMessage } from "../../../utils/errors"; import { toMessage } from "../../../utils/errors";
@ -12,8 +10,6 @@ import type { VolumeBackend } from "../backend";
import { executeMount, executeUnmount } from "../utils/backend-utils"; import { executeMount, executeUnmount } from "../utils/backend-utils";
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes"; import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
const execFile = promisify(execFileCb);
const mount = async (config: BackendConfig, path: string) => { const mount = async (config: BackendConfig, path: string) => {
logger.debug(`Mounting WebDAV volume ${path}...`); logger.debug(`Mounting WebDAV volume ${path}...`);
@ -32,8 +28,10 @@ const mount = async (config: BackendConfig, path: string) => {
return { status: BACKEND_STATUS.mounted }; return { status: BACKEND_STATUS.mounted };
} }
logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`); if (status === "error") {
await unmount(path); logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`);
await unmount(path);
}
const run = async () => { const run = async () => {
await fs.mkdir(path, { recursive: true }).catch((err) => { await fs.mkdir(path, { recursive: true }).catch((err) => {
@ -58,18 +56,9 @@ const mount = async (config: BackendConfig, path: string) => {
logger.debug(`Mounting WebDAV volume ${path}...`); logger.debug(`Mounting WebDAV volume ${path}...`);
const args = ["-t", "davfs", source, path]; const args = ["-t", "davfs", "-o", options.join(","), source, path];
await executeMount(args); await executeMount(args);
const { stderr } = await execFile("mount", ["-t", "davfs", "-o", options.join(","), source, path], {
timeout: OPERATION_TIMEOUT,
maxBuffer: 1024 * 1024,
});
if (stderr?.trim()) {
logger.warn(stderr.trim());
}
logger.info(`WebDAV volume at ${path} mounted successfully.`); logger.info(`WebDAV volume at ${path} mounted successfully.`);
return { status: BACKEND_STATUS.mounted }; return { status: BACKEND_STATUS.mounted };
}; };
@ -113,16 +102,15 @@ const unmount = async (path: string) => {
} }
const run = async () => { const run = async () => {
try { const mount = await getMountForPath(path);
await fs.access(path); if (!mount || mount.mountPoint !== path) {
} catch (e) { logger.debug(`Path ${path} is not a mount point. Skipping unmount.`);
logger.warn(`Path ${path} does not exist. Skipping unmount.`, e);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
} }
await executeUnmount(path); await executeUnmount(path);
await fs.rmdir(path); await fs.rmdir(path).catch(() => {});
logger.info(`WebDAV volume at ${path} unmounted successfully.`); logger.info(`WebDAV volume at ${path} unmounted successfully.`);
return { status: BACKEND_STATUS.unmounted }; return { status: BACKEND_STATUS.unmounted };
@ -138,13 +126,20 @@ const unmount = async (path: string) => {
const checkHealth = async (path: string) => { const checkHealth = async (path: string) => {
const run = async () => { const run = async () => {
logger.debug(`Checking health of WebDAV volume at ${path}...`); try {
await fs.access(path); await fs.access(path);
} catch {
throw new Error("Volume is not mounted");
}
const mount = await getMountForPath(path); const mount = await getMountForPath(path);
if (!mount || mount.fstype !== "fuse") { if (!mount || mount.mountPoint !== path) {
throw new Error(`Path ${path} is not mounted as WebDAV.`); throw new Error("Volume is not mounted");
}
if (mount.fstype !== "fuse" && mount.fstype !== "davfs") {
throw new Error(`Path ${path} is not mounted as WebDAV (found ${mount.fstype}).`);
} }
logger.debug(`WebDAV volume at ${path} is healthy and mounted.`); logger.debug(`WebDAV volume at ${path} is healthy and mounted.`);
@ -154,8 +149,11 @@ const checkHealth = async (path: string) => {
try { try {
return await withTimeout(run(), OPERATION_TIMEOUT, "WebDAV health check"); return await withTimeout(run(), OPERATION_TIMEOUT, "WebDAV health check");
} catch (error) { } catch (error) {
logger.error("WebDAV volume health check failed:", toMessage(error)); const message = toMessage(error);
return { status: BACKEND_STATUS.error, error: toMessage(error) }; if (message !== "Volume is not mounted") {
logger.error("WebDAV volume health check failed:", message);
}
return { status: BACKEND_STATUS.error, error: message };
} }
}; };