From da5ad5c8175c2d2938c7861cc378c4ac5c1bd38b Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:16:31 +0100 Subject: [PATCH] feat: add config for SMB guest mode (#431) * feat: add config for SMB guest mode Closes #279 * chore: pr feedbacks --- app/client/api-client/types.gen.ts | 50 +++++++++------- .../components/volume-forms/smb-form.tsx | 43 +++++++++++++- app/schemas/volumes.ts | 5 +- .../modules/backends/smb/smb-backend.ts | 58 +++++++++++-------- app/server/modules/volumes/volume.service.ts | 2 +- 5 files changed, 108 insertions(+), 50 deletions(-) diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 6b706223..73c55985 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -97,14 +97,15 @@ export type ListVolumesResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -166,14 +167,15 @@ export type CreateVolumeData = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -232,14 +234,15 @@ export type CreateVolumeResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -301,14 +304,15 @@ export type TestConnectionData = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -420,14 +424,15 @@ export type GetVolumeResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -491,14 +496,15 @@ export type UpdateVolumeData = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -566,14 +572,15 @@ export type UpdateVolumeResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -2103,14 +2110,15 @@ export type ListBackupSchedulesResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -2485,14 +2493,15 @@ export type GetBackupScheduleResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; @@ -2848,14 +2857,15 @@ export type GetBackupScheduleForVolumeResponses = { } | { backend: "smb"; - password: string; server: string; share: string; - username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0" | "auto"; port?: number; domain?: string; + guest?: boolean; + password?: string; readOnly?: boolean; + username?: string; } | { backend: "webdav"; diff --git a/app/client/modules/volumes/components/volume-forms/smb-form.tsx b/app/client/modules/volumes/components/volume-forms/smb-form.tsx index b46fcef3..3c9f0167 100644 --- a/app/client/modules/volumes/components/volume-forms/smb-form.tsx +++ b/app/client/modules/volumes/components/volume-forms/smb-form.tsx @@ -17,6 +17,8 @@ type Props = { }; export const SMBForm = ({ form }: Props) => { + const guest = form.watch("guest"); + return ( <> { )} /> + ( + + Guest Mode + +
+ { + field.onChange(e.target.checked); + }} + className="rounded border-gray-300" + /> + Connect as guest (no authentication) +
+
+ + Connect to the SMB share without credentials. Username and password fields will be disabled. + + +
+ )} + /> { Username - + Username for SMB authentication. @@ -68,7 +102,12 @@ export const SMBForm = ({ form }: Props) => { Password - + Password for SMB authentication. diff --git a/app/schemas/volumes.ts b/app/schemas/volumes.ts index 5fab2c51..dd57e713 100644 --- a/app/schemas/volumes.ts +++ b/app/schemas/volumes.ts @@ -24,8 +24,9 @@ export const smbConfigSchema = type({ backend: "'smb'", server: "string", share: "string", - username: "string", - password: "string", + username: "string?", + password: "string?", + guest: "boolean?", vers: type("'1.0' | '2.0' | '2.1' | '3.0' | 'auto'").default("auto"), domain: "string?", port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(445), diff --git a/app/server/modules/backends/smb/smb-backend.ts b/app/server/modules/backends/smb/smb-backend.ts index 9d4ef48a..661e31a5 100644 --- a/app/server/modules/backends/smb/smb-backend.ts +++ b/app/server/modules/backends/smb/smb-backend.ts @@ -36,36 +36,44 @@ const mount = async (config: BackendConfig, path: string) => { const run = async () => { await fs.mkdir(path, { recursive: true }); - const password = await cryptoUtils.resolveSecret(config.password); - const source = `//${config.server}/${config.share}`; const { uid, gid } = os.userInfo(); + + const options = [`port=${config.port}`, `uid=${uid}`, `gid=${gid}`]; + const credentialsDir = await fs.mkdtemp(`${os.tmpdir()}/zerobyte-smb-`); const credentialsPath = `${credentialsDir}/credentials`; - const credentialsLines = [`username=${config.username}`, `password=${password}`]; - - if (config.domain) { - credentialsLines.push(`domain=${config.domain}`); - } - - await fs.writeFile(credentialsPath, credentialsLines.join("\n"), { mode: 0o600 }); - - const options = [`credentials=${credentialsPath}`, `port=${config.port}`, `uid=${uid}`, `gid=${gid}`]; - - if (config.vers && config.vers !== "auto") { - options.push(`vers=${config.vers}`); - } - - if (config.readOnly) { - options.push("ro"); - } - - const args = ["-t", "cifs", "-o", options.join(","), source, path]; - - logger.debug(`Mounting SMB volume ${path}...`); - logger.info(`Executing mount: mount ${args.join(" ")}`); try { + const credentialsLines: string[] = []; + + if (config.guest) { + credentialsLines.push("username=guest", "password="); + } else { + const password = await cryptoUtils.resolveSecret(config.password ?? ""); + credentialsLines.push(`username=${config.username ?? ""}`, `password=${password}`); + } + + if (config.domain) { + credentialsLines.push(`domain=${config.domain}`); + } + + await fs.writeFile(credentialsPath, credentialsLines.join("\n"), { mode: 0o600 }); + options.push(`credentials=${credentialsPath}`); + + if (config.vers && config.vers !== "auto") { + options.push(`vers=${config.vers}`); + } + + if (config.readOnly) { + options.push("ro"); + } + + const args = ["-t", "cifs", "-o", options.join(","), source, path]; + + logger.debug(`Mounting SMB volume ${path}...`); + logger.info(`Executing mount: mount ${args.join(" ")}`); + try { await executeMount(args); } catch (error) { @@ -74,7 +82,7 @@ const mount = async (config: BackendConfig, path: string) => { } } finally { await fs.rm(credentialsPath, { force: true }); - await fs.rmdir(credentialsDir).catch(() => {}); + await fs.rm(credentialsDir, { force: true, recursive: true }); } logger.info(`SMB volume at ${path} mounted successfully.`); diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index f150c9f5..ef869683 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -24,7 +24,7 @@ async function encryptSensitiveFields(config: BackendConfig): Promise