feat: add config for SMB guest mode (#431)
* feat: add config for SMB guest mode Closes #279 * chore: pr feedbacks
This commit is contained in:
parent
f62ae222ca
commit
da5ad5c817
5 changed files with 108 additions and 50 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type Props = {
|
|||
};
|
||||
|
||||
export const SMBForm = ({ form }: Props) => {
|
||||
const guest = form.watch("guest");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
|
|
@ -47,6 +49,33 @@ export const SMBForm = ({ form }: Props) => {
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="guest"
|
||||
defaultValue={false}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Guest Mode</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={field.value ?? false}
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.checked);
|
||||
}}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">Connect as guest (no authentication)</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Connect to the SMB share without credentials. Username and password fields will be disabled.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
|
|
@ -54,7 +83,12 @@ export const SMBForm = ({ form }: Props) => {
|
|||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="admin" value={field.value} onChange={field.onChange} />
|
||||
<Input
|
||||
placeholder="admin"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
disabled={guest}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>Username for SMB authentication.</FormDescription>
|
||||
<FormMessage />
|
||||
|
|
@ -68,7 +102,12 @@ export const SMBForm = ({ form }: Props) => {
|
|||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<SecretInput placeholder="••••••••" value={field.value ?? ""} onChange={field.onChange} />
|
||||
<SecretInput
|
||||
placeholder="••••••••"
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
disabled={guest}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>Password for SMB authentication.</FormDescription>
|
||||
<FormMessage />
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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.`);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ async function encryptSensitiveFields(config: BackendConfig): Promise<BackendCon
|
|||
case "smb":
|
||||
return {
|
||||
...config,
|
||||
password: await cryptoUtils.sealSecret(config.password),
|
||||
password: config.password ? await cryptoUtils.sealSecret(config.password) : undefined,
|
||||
};
|
||||
case "webdav":
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in a new issue