mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: custom local repository path
This commit is contained in:
@@ -741,12 +741,21 @@ export type ListRepositoriesResponses = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
createdAt: number;
|
||||
id: string;
|
||||
@@ -754,7 +763,7 @@ export type ListRepositoriesResponses = {
|
||||
lastError: string | null;
|
||||
name: string;
|
||||
status: 'error' | 'healthy' | 'unknown' | null;
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3';
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3';
|
||||
updatedAt: number;
|
||||
}>;
|
||||
};
|
||||
@@ -799,12 +808,21 @@ export type CreateRepositoryData = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
name: string;
|
||||
compressionMode?: 'auto' | 'better' | 'fastest' | 'max' | 'off';
|
||||
@@ -919,12 +937,21 @@ export type GetRepositoryResponses = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
createdAt: number;
|
||||
id: string;
|
||||
@@ -932,7 +959,7 @@ export type GetRepositoryResponses = {
|
||||
lastError: string | null;
|
||||
name: string;
|
||||
status: 'error' | 'healthy' | 'unknown' | null;
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3';
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3';
|
||||
updatedAt: number;
|
||||
};
|
||||
};
|
||||
@@ -1166,12 +1193,21 @@ export type ListBackupSchedulesResponses = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
createdAt: number;
|
||||
id: string;
|
||||
@@ -1179,7 +1215,7 @@ export type ListBackupSchedulesResponses = {
|
||||
lastError: string | null;
|
||||
name: string;
|
||||
status: 'error' | 'healthy' | 'unknown' | null;
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3';
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3';
|
||||
updatedAt: number;
|
||||
};
|
||||
repositoryId: string;
|
||||
@@ -1379,12 +1415,21 @@ export type GetBackupScheduleResponses = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
createdAt: number;
|
||||
id: string;
|
||||
@@ -1392,7 +1437,7 @@ export type GetBackupScheduleResponses = {
|
||||
lastError: string | null;
|
||||
name: string;
|
||||
status: 'error' | 'healthy' | 'unknown' | null;
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3';
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3';
|
||||
updatedAt: number;
|
||||
};
|
||||
repositoryId: string;
|
||||
@@ -1573,12 +1618,21 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||
name: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
path?: string;
|
||||
} | {
|
||||
backend: 'rclone';
|
||||
path: string;
|
||||
remote: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
} | {
|
||||
backend: 'rest';
|
||||
url: string;
|
||||
customPassword?: string;
|
||||
isExistingRepository?: boolean;
|
||||
password?: string;
|
||||
path?: string;
|
||||
username?: string;
|
||||
};
|
||||
createdAt: number;
|
||||
id: string;
|
||||
@@ -1586,7 +1640,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||
lastError: string | null;
|
||||
name: string;
|
||||
status: 'error' | 'healthy' | 'unknown' | null;
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3';
|
||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3';
|
||||
updatedAt: number;
|
||||
};
|
||||
repositoryId: string;
|
||||
|
||||
@@ -10,12 +10,23 @@ import { Input } from "./ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Alert, AlertDescription } from "./ui/alert";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { ExternalLink, AlertTriangle } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||
import { COMPRESSION_MODES, repositoryConfigSchema } from "~/schemas/restic";
|
||||
import { listRcloneRemotesOptions } from "../api-client/@tanstack/react-query.gen";
|
||||
import { Checkbox } from "./ui/checkbox";
|
||||
import { DirectoryBrowser } from "./directory-browser";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
|
||||
export const formSchema = type({
|
||||
name: "2<=string<=32",
|
||||
@@ -67,6 +78,8 @@ export const CreateRepositoryForm = ({
|
||||
const watchedIsExistingRepository = watch("isExistingRepository");
|
||||
|
||||
const [passwordMode, setPasswordMode] = useState<"default" | "custom">("default");
|
||||
const [showPathBrowser, setShowPathBrowser] = useState(false);
|
||||
const [showPathWarning, setShowPathWarning] = useState(false);
|
||||
|
||||
const { capabilities } = useSystemInfo();
|
||||
|
||||
@@ -247,6 +260,87 @@ export const CreateRepositoryForm = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{watchedBackend === "local" && (
|
||||
<>
|
||||
<FormItem>
|
||||
<FormLabel>Repository Directory</FormLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 text-sm font-mono bg-muted px-3 py-2 rounded-md border">
|
||||
{form.watch("path") || "/var/lib/ironmount/repositories"}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setShowPathWarning(true)}
|
||||
size="sm"
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
</div>
|
||||
<FormDescription>
|
||||
The directory where the repository will be stored.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
|
||||
<AlertDialog open={showPathWarning} onOpenChange={setShowPathWarning}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-500" />
|
||||
Important: Host Mount Required
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="space-y-3">
|
||||
<p>
|
||||
When selecting a custom path, ensure it is mounted from the host machine into the
|
||||
container.
|
||||
</p>
|
||||
<p className="font-medium">
|
||||
If the path is not a host mount, you will lose your repository data when the container
|
||||
restarts.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The default path <code className="bg-muted px-1 rounded">/var/lib/ironmount/repositories</code> is
|
||||
already mounted from the host and is safe to use.
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
setShowPathBrowser(true);
|
||||
setShowPathWarning(false);
|
||||
}}
|
||||
>
|
||||
I Understand, Continue
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog open={showPathBrowser} onOpenChange={setShowPathBrowser}>
|
||||
<AlertDialogContent className="max-w-2xl">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Select Repository Directory</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Choose a directory from the filesystem to store the repository.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="py-4">
|
||||
<DirectoryBrowser
|
||||
onSelectPath={(path) => form.setValue("path", path)}
|
||||
selectedPath={form.watch("path") || "/var/lib/ironmount/repositories"}
|
||||
/>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => setShowPathBrowser(false)}>Done</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)}
|
||||
|
||||
{watchedBackend === "s3" && (
|
||||
<>
|
||||
<FormField
|
||||
|
||||
@@ -37,6 +37,7 @@ export const r2RepositoryConfigSchema = type({
|
||||
export const localRepositoryConfigSchema = type({
|
||||
backend: "'local'",
|
||||
name: "string",
|
||||
path: "string?",
|
||||
}).and(baseRepositoryConfigSchema);
|
||||
|
||||
export const gcsRepositoryConfigSchema = type({
|
||||
|
||||
@@ -71,7 +71,7 @@ const ensurePassfile = async () => {
|
||||
const buildRepoUrl = (config: RepositoryConfig): string => {
|
||||
switch (config.backend) {
|
||||
case "local":
|
||||
return `${REPOSITORY_BASE}/${config.name}`;
|
||||
return `${config.path}/${config.name}` || `${REPOSITORY_BASE}/${config.name}`;
|
||||
case "s3":
|
||||
return `s3:${config.endpoint}/${config.bucket}`;
|
||||
case "r2": {
|
||||
|
||||
Reference in New Issue
Block a user