Compare commits
1 commit
main
...
v0.22.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05348e31b0 |
3 changed files with 48 additions and 21 deletions
|
|
@ -10,8 +10,9 @@ ENV VITE_RESTIC_VERSION=${RESTIC_VERSION} \
|
||||||
VITE_RCLONE_VERSION=${RCLONE_VERSION} \
|
VITE_RCLONE_VERSION=${RCLONE_VERSION} \
|
||||||
VITE_SHOUTRRR_VERSION=${SHOUTRRR_VERSION}
|
VITE_SHOUTRRR_VERSION=${SHOUTRRR_VERSION}
|
||||||
|
|
||||||
RUN apk upgrade --no-cache && \
|
RUN apk update --no-cache && \
|
||||||
apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs tini nfs-utils cifs-utils
|
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", "--"]
|
ENTRYPOINT ["/sbin/tini", "-s", "--"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
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 { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
|
||||||
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";
|
||||||
|
|
@ -7,19 +8,24 @@ import { getMountForPath } from "../../../utils/mountinfo";
|
||||||
import { withTimeout } from "../../../utils/timeout";
|
import { withTimeout } from "../../../utils/timeout";
|
||||||
import type { VolumeBackend } from "../backend";
|
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";
|
|
||||||
|
|
||||||
const mount = async (config: BackendConfig, path: string) => {
|
const mount = async (config: BackendConfig, path: string) => {
|
||||||
logger.debug(`Mounting volume ${path}...`);
|
logger.debug(`Mounting volume ${path}...`);
|
||||||
|
|
||||||
if (config.backend !== "nfs") {
|
if (config.backend !== "nfs") {
|
||||||
logger.error("Provided config is not for NFS backend");
|
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") {
|
if (os.platform() !== "linux") {
|
||||||
logger.error("NFS mounting is only supported on Linux hosts.");
|
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);
|
const { status } = await checkHealth(path);
|
||||||
|
|
@ -28,7 +34,9 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === "error") {
|
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);
|
await unmount(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +44,7 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
await fs.mkdir(path, { recursive: true });
|
await fs.mkdir(path, { recursive: true });
|
||||||
|
|
||||||
const source = `${config.server}:${config.exportPath}`;
|
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) {
|
if (config.readOnly) {
|
||||||
options.push("ro");
|
options.push("ro");
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +70,10 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
const unmount = async (path: string) => {
|
const unmount = async (path: string) => {
|
||||||
if (os.platform() !== "linux") {
|
if (os.platform() !== "linux") {
|
||||||
logger.error("NFS unmounting is only supported on Linux hosts.");
|
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 () => {
|
const run = async () => {
|
||||||
|
|
@ -83,7 +94,10 @@ const unmount = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
return await withTimeout(run(), OPERATION_TIMEOUT, "NFS unmount");
|
return await withTimeout(run(), OPERATION_TIMEOUT, "NFS unmount");
|
||||||
} catch (err) {
|
} 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) };
|
return { status: BACKEND_STATUS.error, error: toMessage(err) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -103,7 +117,9 @@ const checkHealth = async (path: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mount.fstype.startsWith("nfs")) {
|
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.`);
|
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),
|
mount: () => mount(config, path),
|
||||||
unmount: () => unmount(path),
|
unmount: () => unmount(path),
|
||||||
checkHealth: () => checkHealth(path),
|
checkHealth: () => checkHealth(path),
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
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 { $ } from "bun";
|
||||||
import { toMessage } from "../../../utils/errors";
|
import { toMessage } from "../../../utils/errors";
|
||||||
import { logger } from "../../../utils/logger";
|
import { logger } from "../../../utils/logger";
|
||||||
import { $ } from "bun";
|
|
||||||
|
|
||||||
export const executeMount = async (args: string[]): Promise<void> => {
|
export const executeMount = async (args: string[]): Promise<void> => {
|
||||||
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(" ")}`);
|
logger.debug(`Executing mount ${effectiveArgs.join(" ")}`);
|
||||||
const result = await $`mount ${args}`.nothrow();
|
const result = await $`mount ${effectiveArgs}`.nothrow();
|
||||||
stderr = result.stderr.toString();
|
|
||||||
|
|
||||||
if (stderr?.trim()) {
|
const stdout = result.stdout.toString().trim();
|
||||||
logger.warn(stderr.trim());
|
const stderr = result.stderr.toString().trim();
|
||||||
|
|
||||||
|
if (result.exitCode === 0) {
|
||||||
|
logger.debug(stdout);
|
||||||
|
logger.debug(stderr);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
logger.warn(stdout);
|
||||||
throw new Error(`Mount command failed with exit code ${result.exitCode}: ${stderr?.trim()}`);
|
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<void> => {
|
export const executeUnmount = async (path: string): Promise<void> => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue