From 2aa90ec44d04ad5249a283cbecf0ad60e32e50ef Mon Sep 17 00:00:00 2001
From: Nicolas Meienberger
Date: Sun, 9 Nov 2025 14:09:49 +0100
Subject: [PATCH] feat: exclude patterns
---
apps/client/app/components/ui/textarea.tsx | 18 +++
.../components/create-schedule-form.tsx | 109 ++++++++++++++++--
apps/server/src/utils/restic.ts | 2 +-
3 files changed, 120 insertions(+), 9 deletions(-)
create mode 100644 apps/client/app/components/ui/textarea.tsx
diff --git a/apps/client/app/components/ui/textarea.tsx b/apps/client/app/components/ui/textarea.tsx
new file mode 100644
index 0000000..e5aa0b1
--- /dev/null
+++ b/apps/client/app/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react";
+
+import { cn } from "~/lib/utils";
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export { Textarea };
diff --git a/apps/client/app/modules/backups/components/create-schedule-form.tsx b/apps/client/app/modules/backups/components/create-schedule-form.tsx
index e72262f..ad3efee 100644
--- a/apps/client/app/modules/backups/components/create-schedule-form.tsx
+++ b/apps/client/app/modules/backups/components/create-schedule-form.tsx
@@ -9,13 +9,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/com
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
+import { Textarea } from "~/components/ui/textarea";
import { VolumeFileBrowser } from "~/components/volume-file-browser";
import type { BackupSchedule, Volume } from "~/lib/types";
import { deepClean } from "~/utils/object";
-const formSchema = type({
+const internalFormSchema = type({
repositoryId: "string",
- excludePatterns: "string[]?",
+ excludePatternsText: "string?",
includePatterns: "string[]?",
frequency: "string",
dailyTime: "string?",
@@ -27,7 +28,7 @@ const formSchema = type({
keepMonthly: "number?",
keepYearly: "number?",
});
-const cleanSchema = type.pipe((d) => formSchema(deepClean(d)));
+const cleanSchema = type.pipe((d) => internalFormSchema(deepClean(d)));
export const weeklyDays = [
{ label: "Monday", value: "1" },
@@ -39,7 +40,11 @@ export const weeklyDays = [
{ label: "Sunday", value: "0" },
];
-export type BackupScheduleFormValues = typeof formSchema.infer;
+type InternalFormValues = typeof internalFormSchema.infer;
+
+export type BackupScheduleFormValues = Omit & {
+ excludePatterns?: string[];
+};
type Props = {
volume: Volume;
@@ -50,7 +55,7 @@ type Props = {
formId: string;
};
-const backupScheduleToFormValues = (schedule?: BackupSchedule): BackupScheduleFormValues | undefined => {
+const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValues | undefined => {
if (!schedule) {
return undefined;
}
@@ -72,16 +77,36 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): BackupScheduleFo
dailyTime,
weeklyDay,
includePatterns: schedule.includePatterns || undefined,
+ excludePatternsText: schedule.excludePatterns?.join("\n") || undefined,
...schedule.retentionPolicy,
};
};
export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: Props) => {
- const form = useForm({
- resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
+ const form = useForm({
+ resolver: arktypeResolver(cleanSchema as unknown as typeof internalFormSchema),
defaultValues: backupScheduleToFormValues(initialValues),
});
+ const handleSubmit = useCallback(
+ (data: InternalFormValues) => {
+ // Convert excludePatternsText string to excludePatterns array
+ const { excludePatternsText, ...rest } = data;
+ const excludePatterns = excludePatternsText
+ ? excludePatternsText
+ .split("\n")
+ .map((p) => p.trim())
+ .filter(Boolean)
+ : undefined;
+
+ onSubmit({
+ ...rest,
+ excludePatterns,
+ });
+ },
+ [onSubmit],
+ );
+
const { data: repositoriesData } = useQuery({
...listRepositoriesOptions(),
});
@@ -102,7 +127,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
return (
+ {formValues.includePatterns && formValues.includePatterns.length > 0 && (
+
+
Include paths
+
+ {formValues.includePatterns.map((path) => (
+
+ {path}
+
+ ))}
+
+
+ )}
+ {formValues.excludePatternsText && (
+
+
Exclude patterns
+
+ {formValues.excludePatternsText
+ .split("\n")
+ .filter(Boolean)
+ .map((pattern) => (
+
+ {pattern.trim()}
+
+ ))}
+
+
+ )}
Retention
diff --git a/apps/server/src/utils/restic.ts b/apps/server/src/utils/restic.ts
index bccc325..96e2382 100644
--- a/apps/server/src/utils/restic.ts
+++ b/apps/server/src/utils/restic.ts
@@ -82,7 +82,7 @@ const buildRepoUrl = (config: RepositoryConfig): string => {
const buildEnv = async (config: RepositoryConfig) => {
const env: Record = {
- RESTIC_CACHE_DIR: "/tmp/restic-cache",
+ RESTIC_CACHE_DIR: "/var/lib/ironmount/restic/cache",
RESTIC_PASSWORD_FILE: RESTIC_PASS_FILE,
};