Merge remote-tracking branch 'origin/main' into config-import-json
This commit is contained in:
commit
883ee49ded
24 changed files with 1897 additions and 47 deletions
18
.github/dependabot.yml
vendored
Normal file
18
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "bun"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
rebase-strategy: 'auto'
|
||||||
|
groups:
|
||||||
|
minor-patch:
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
|
||||||
|
- package-ecosystem: 'github-actions'
|
||||||
|
directory: '/'
|
||||||
|
schedule:
|
||||||
|
interval: 'daily'
|
||||||
|
rebase-strategy: 'auto'
|
||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ export const createSseClient = <TData = unknown>({
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
buffer += value;
|
buffer += value;
|
||||||
|
// Normalize line endings: CRLF -> LF, then CR -> LF
|
||||||
|
buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||||
|
|
||||||
const chunks = buffer.split('\n\n');
|
const chunks = buffer.split('\n\n');
|
||||||
buffer = chunks.pop() ?? '';
|
buffer = chunks.pop() ?? '';
|
||||||
|
|
|
||||||
|
|
@ -1305,6 +1305,7 @@ export type ListBackupSchedulesResponses = {
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
name: string;
|
name: string;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
|
oneFileSystem: boolean;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'max' | 'off' | null;
|
||||||
config: {
|
config: {
|
||||||
|
|
@ -1453,6 +1454,7 @@ export type CreateBackupScheduleData = {
|
||||||
excludeIfPresent?: Array<string>;
|
excludeIfPresent?: Array<string>;
|
||||||
excludePatterns?: Array<string>;
|
excludePatterns?: Array<string>;
|
||||||
includePatterns?: Array<string>;
|
includePatterns?: Array<string>;
|
||||||
|
oneFileSystem?: boolean;
|
||||||
retentionPolicy?: {
|
retentionPolicy?: {
|
||||||
keepDaily?: number;
|
keepDaily?: number;
|
||||||
keepHourly?: number;
|
keepHourly?: number;
|
||||||
|
|
@ -1486,6 +1488,7 @@ export type CreateBackupScheduleResponses = {
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
name: string;
|
name: string;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
|
oneFileSystem: boolean;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
keepDaily?: number;
|
keepDaily?: number;
|
||||||
|
|
@ -1549,6 +1552,7 @@ export type GetBackupScheduleResponses = {
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
name: string;
|
name: string;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
|
oneFileSystem: boolean;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'max' | 'off' | null;
|
||||||
config: {
|
config: {
|
||||||
|
|
@ -1696,6 +1700,7 @@ export type UpdateBackupScheduleData = {
|
||||||
excludePatterns?: Array<string>;
|
excludePatterns?: Array<string>;
|
||||||
includePatterns?: Array<string>;
|
includePatterns?: Array<string>;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
oneFileSystem?: boolean;
|
||||||
retentionPolicy?: {
|
retentionPolicy?: {
|
||||||
keepDaily?: number;
|
keepDaily?: number;
|
||||||
keepHourly?: number;
|
keepHourly?: number;
|
||||||
|
|
@ -1731,6 +1736,7 @@ export type UpdateBackupScheduleResponses = {
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
name: string;
|
name: string;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
|
oneFileSystem: boolean;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
keepDaily?: number;
|
keepDaily?: number;
|
||||||
|
|
@ -1774,6 +1780,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
name: string;
|
name: string;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
|
oneFileSystem: boolean;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'max' | 'off' | null;
|
||||||
config: {
|
config: {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,15 @@ const items = [
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const { state } = useSidebar();
|
const { state } = useSidebar();
|
||||||
|
const displayVersion = APP_VERSION.startsWith("v") || APP_VERSION === "dev" ? APP_VERSION : `v${APP_VERSION}`;
|
||||||
|
const releaseUrl =
|
||||||
|
APP_VERSION === "dev"
|
||||||
|
? "https://github.com/nicotsx/zerobyte"
|
||||||
|
: `https://github.com/nicotsx/zerobyte/releases/tag/${displayVersion}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="inset" collapsible="icon" className="p-0">
|
<Sidebar variant="inset" collapsible="icon" className="p-0">
|
||||||
<SidebarHeader className="bg-card-header border-b border-border/50 hidden md:flex h-[65px] flex-row items-center p-4">
|
<SidebarHeader className="bg-card-header border-b border-border/50 hidden md:flex h-16.25 flex-row items-center p-4">
|
||||||
<Link to="/volumes" className="flex items-center gap-3 font-semibold pl-2">
|
<Link to="/volumes" className="flex items-center gap-3 font-semibold pl-2">
|
||||||
<img src="/images/zerobyte.png" alt="Zerobyte Logo" className={cn("h-8 w-8 shrink-0 object-contain -ml-2")} />
|
<img src="/images/zerobyte.png" alt="Zerobyte Logo" className={cn("h-8 w-8 shrink-0 object-contain -ml-2")} />
|
||||||
<span
|
<span
|
||||||
|
|
@ -93,13 +98,16 @@ export function AppSidebar() {
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter className="p-4 border-r border-t border-border/50">
|
<SidebarFooter className="p-4 border-r border-t border-border/50">
|
||||||
<div
|
<a
|
||||||
className={cn("text-xs text-muted-foreground transition-all duration-200", {
|
href={releaseUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={cn("text-xs text-muted-foreground transition-all duration-200 hover:text-foreground", {
|
||||||
"opacity-0 w-0 overflow-hidden": state === "collapsed",
|
"opacity-0 w-0 overflow-hidden": state === "collapsed",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{APP_VERSION}
|
{displayVersion}
|
||||||
</div>
|
</a>
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||||
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
@ -6,6 +7,7 @@ import { useForm } from "react-hook-form";
|
||||||
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { RepositoryIcon } from "~/client/components/repository-icon";
|
import { RepositoryIcon } from "~/client/components/repository-icon";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
|
import { Checkbox } from "~/client/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -17,6 +19,7 @@ import {
|
||||||
} from "~/client/components/ui/form";
|
} from "~/client/components/ui/form";
|
||||||
import { Input } from "~/client/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Textarea } from "~/client/components/ui/textarea";
|
import { Textarea } from "~/client/components/ui/textarea";
|
||||||
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
||||||
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
||||||
|
|
@ -32,12 +35,14 @@ const internalFormSchema = type({
|
||||||
frequency: "string",
|
frequency: "string",
|
||||||
dailyTime: "string?",
|
dailyTime: "string?",
|
||||||
weeklyDay: "string?",
|
weeklyDay: "string?",
|
||||||
|
monthlyDays: "string[]?",
|
||||||
keepLast: "number?",
|
keepLast: "number?",
|
||||||
keepHourly: "number?",
|
keepHourly: "number?",
|
||||||
keepDaily: "number?",
|
keepDaily: "number?",
|
||||||
keepWeekly: "number?",
|
keepWeekly: "number?",
|
||||||
keepMonthly: "number?",
|
keepMonthly: "number?",
|
||||||
keepYearly: "number?",
|
keepYearly: "number?",
|
||||||
|
oneFileSystem: "boolean?",
|
||||||
});
|
});
|
||||||
const cleanSchema = type.pipe((d) => internalFormSchema(deepClean(d)));
|
const cleanSchema = type.pipe((d) => internalFormSchema(deepClean(d)));
|
||||||
|
|
||||||
|
|
@ -76,15 +81,16 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = schedule.cronExpression.split(" ");
|
const parts = schedule.cronExpression.split(" ");
|
||||||
const [minutePart, hourPart, , , dayOfWeekPart] = parts;
|
const [minutePart, hourPart, dayOfMonthPart, , dayOfWeekPart] = parts;
|
||||||
|
|
||||||
const isHourly = hourPart === "*";
|
const isHourly = hourPart === "*";
|
||||||
const isDaily = !isHourly && dayOfWeekPart === "*";
|
const isMonthly = !isHourly && dayOfMonthPart !== "*" && dayOfWeekPart === "*";
|
||||||
const frequency = isHourly ? "hourly" : isDaily ? "daily" : "weekly";
|
const isDaily = !isHourly && dayOfMonthPart === "*" && dayOfWeekPart === "*";
|
||||||
|
|
||||||
|
const frequency = isHourly ? "hourly" : isMonthly ? "monthly" : isDaily ? "daily" : "weekly";
|
||||||
const dailyTime = isHourly ? undefined : `${hourPart.padStart(2, "0")}:${minutePart.padStart(2, "0")}`;
|
const dailyTime = isHourly ? undefined : `${hourPart.padStart(2, "0")}:${minutePart.padStart(2, "0")}`;
|
||||||
|
|
||||||
const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined;
|
const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined;
|
||||||
|
const monthlyDays = isMonthly ? dayOfMonthPart.split(",") : undefined;
|
||||||
|
|
||||||
const patterns = schedule.includePatterns || [];
|
const patterns = schedule.includePatterns || [];
|
||||||
const isGlobPattern = (p: string) => /[*?[\]]/.test(p);
|
const isGlobPattern = (p: string) => /[*?[\]]/.test(p);
|
||||||
|
|
@ -95,12 +101,14 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
|
||||||
name: schedule.name,
|
name: schedule.name,
|
||||||
repositoryId: schedule.repositoryId,
|
repositoryId: schedule.repositoryId,
|
||||||
frequency,
|
frequency,
|
||||||
|
monthlyDays,
|
||||||
dailyTime,
|
dailyTime,
|
||||||
weeklyDay,
|
weeklyDay,
|
||||||
includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined,
|
includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined,
|
||||||
includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined,
|
includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined,
|
||||||
excludePatternsText: schedule.excludePatterns?.join("\n") || undefined,
|
excludePatternsText: schedule.excludePatterns?.join("\n") || undefined,
|
||||||
excludeIfPresentText: schedule.excludeIfPresent?.join("\n") || undefined,
|
excludeIfPresentText: schedule.excludeIfPresent?.join("\n") || undefined,
|
||||||
|
oneFileSystem: schedule.oneFileSystem ?? false,
|
||||||
...schedule.retentionPolicy,
|
...schedule.retentionPolicy,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -246,6 +254,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
<SelectItem value="hourly">Hourly</SelectItem>
|
<SelectItem value="hourly">Hourly</SelectItem>
|
||||||
<SelectItem value="daily">Daily</SelectItem>
|
<SelectItem value="daily">Daily</SelectItem>
|
||||||
<SelectItem value="weekly">Weekly</SelectItem>
|
<SelectItem value="weekly">Weekly</SelectItem>
|
||||||
|
<SelectItem value="monthly">Specific days</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -299,6 +308,42 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{frequency === "monthly" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="monthlyDays"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="md:col-span-2">
|
||||||
|
<FormLabel>Days of the month</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="grid grid-cols-7 gap-4 w-max">
|
||||||
|
{Array.from({ length: 31 }, (_, i) => {
|
||||||
|
const day = (i + 1).toString();
|
||||||
|
const isSelected = field.value?.includes(day);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
key={day}
|
||||||
|
variant={isSelected ? "primary" : "secondary"}
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
const current = field.value || [];
|
||||||
|
const next = isSelected ? current.filter((d) => d !== day) : [...current, day];
|
||||||
|
field.onChange(next);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Select one or more days when the backup should run.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -416,6 +461,24 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="oneFileSystem"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4 mt-6">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel>Stay on one file system</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Prevent Restic from crossing file system boundaries. This is useful to avoid backing up network
|
||||||
|
mounts or other partitions that might be mounted inside your backup source.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -629,6 +692,10 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div>
|
||||||
|
<p className="text-xs uppercase text-muted-foreground">One file system</p>
|
||||||
|
<p className="font-medium">{formValues.oneFileSystem ? "Enabled" : "Disabled"}</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs uppercase text-muted-foreground">Retention</p>
|
<p className="text-xs uppercase text-muted-foreground">Retention</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,8 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
<div className="md:col-span-2 lg:col-span-4">
|
<div className="md:col-span-2 lg:col-span-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Warning Details</p>
|
<p className="text-xs uppercase text-muted-foreground">Warning Details</p>
|
||||||
<p className="font-mono text-sm text-yellow-600 whitespace-pre-wrap break-all">
|
<p className="font-mono text-sm text-yellow-600 whitespace-pre-wrap break-all">
|
||||||
Last backup completed with warnings. Check your container logs for more details.
|
{schedule.lastBackupError ??
|
||||||
|
"Last backup completed with warnings. Check your container logs for more details."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,12 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
||||||
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
||||||
if (!schedule) return;
|
if (!schedule) return;
|
||||||
|
|
||||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay);
|
const cronExpression = getCronExpression(
|
||||||
|
formValues.frequency,
|
||||||
|
formValues.dailyTime,
|
||||||
|
formValues.weeklyDay,
|
||||||
|
formValues.monthlyDays,
|
||||||
|
);
|
||||||
|
|
||||||
const retentionPolicy: Record<string, number> = {};
|
const retentionPolicy: Record<string, number> = {};
|
||||||
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
||||||
|
|
@ -162,6 +167,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
||||||
includePatterns: formValues.includePatterns,
|
includePatterns: formValues.includePatterns,
|
||||||
excludePatterns: formValues.excludePatterns,
|
excludePatterns: formValues.excludePatterns,
|
||||||
excludeIfPresent: formValues.excludeIfPresent,
|
excludeIfPresent: formValues.excludeIfPresent,
|
||||||
|
oneFileSystem: formValues.oneFileSystem,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -177,6 +183,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
||||||
includePatterns: schedule.includePatterns || [],
|
includePatterns: schedule.includePatterns || [],
|
||||||
excludePatterns: schedule.excludePatterns || [],
|
excludePatterns: schedule.excludePatterns || [],
|
||||||
excludeIfPresent: schedule.excludeIfPresent || [],
|
excludeIfPresent: schedule.excludeIfPresent || [],
|
||||||
|
oneFileSystem: schedule.oneFileSystem,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,12 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
|
||||||
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
||||||
if (!selectedVolumeId) return;
|
if (!selectedVolumeId) return;
|
||||||
|
|
||||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay);
|
const cronExpression = getCronExpression(
|
||||||
|
formValues.frequency,
|
||||||
|
formValues.dailyTime,
|
||||||
|
formValues.weeklyDay,
|
||||||
|
formValues.monthlyDays,
|
||||||
|
);
|
||||||
|
|
||||||
const retentionPolicy: Record<string, number> = {};
|
const retentionPolicy: Record<string, number> = {};
|
||||||
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
||||||
|
|
@ -92,6 +97,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
|
||||||
includePatterns: formValues.includePatterns,
|
includePatterns: formValues.includePatterns,
|
||||||
excludePatterns: formValues.excludePatterns,
|
excludePatterns: formValues.excludePatterns,
|
||||||
excludeIfPresent: formValues.excludeIfPresent,
|
excludeIfPresent: formValues.excludeIfPresent,
|
||||||
|
oneFileSystem: formValues.oneFileSystem,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
app/drizzle/0023_special_thor.sql
Normal file
2
app/drizzle/0023_special_thor.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE `backup_schedules_table` ADD `one_file_system` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
|
UPDATE `backup_schedules_table` SET `one_file_system` = true;
|
||||||
1
app/drizzle/0024_schedules-one-fs.sql
Normal file
1
app/drizzle/0024_schedules-one-fs.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
UPDATE `backup_schedules_table` SET `one_file_system` = 1;
|
||||||
839
app/drizzle/meta/0023_snapshot.json
Normal file
839
app/drizzle/meta/0023_snapshot.json
Normal file
|
|
@ -0,0 +1,839 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "3e3841ca-67a8-493a-a061-9c2a780878ed",
|
||||||
|
"prevId": "11c24867-3186-4578-b8dd-cee4c48a28d1",
|
||||||
|
"tables": {
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table": {
|
||||||
|
"name": "backup_schedule_mirrors_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"last_copy_at": {
|
||||||
|
"name": "last_copy_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_status": {
|
||||||
|
"name": "last_copy_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_error": {
|
||||||
|
"name": "last_copy_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"schedule_id",
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"schedule_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table": {
|
||||||
|
"name": "backup_schedule_notifications_table",
|
||||||
|
"columns": {
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_id": {
|
||||||
|
"name": "destination_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"notify_on_start": {
|
||||||
|
"name": "notify_on_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_success": {
|
||||||
|
"name": "notify_on_success",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_warning": {
|
||||||
|
"name": "notify_on_warning",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"notify_on_failure": {
|
||||||
|
"name": "notify_on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"schedule_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "notification_destinations_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"destination_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"schedule_id",
|
||||||
|
"destination_id"
|
||||||
|
],
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedules_table": {
|
||||||
|
"name": "backup_schedules_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"volume_id": {
|
||||||
|
"name": "volume_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"cron_expression": {
|
||||||
|
"name": "cron_expression",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"retention_policy": {
|
||||||
|
"name": "retention_policy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"exclude_patterns": {
|
||||||
|
"name": "exclude_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"exclude_if_present": {
|
||||||
|
"name": "exclude_if_present",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"include_patterns": {
|
||||||
|
"name": "include_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"last_backup_at": {
|
||||||
|
"name": "last_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_status": {
|
||||||
|
"name": "last_backup_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_error": {
|
||||||
|
"name": "last_backup_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"next_backup_at": {
|
||||||
|
"name": "next_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"one_file_system": {
|
||||||
|
"name": "one_file_system",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedules_table_name_unique": {
|
||||||
|
"name": "backup_schedules_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "volumes_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"volume_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_destinations_table": {
|
||||||
|
"name": "notification_destinations_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"notification_destinations_table_name_unique": {
|
||||||
|
"name": "notification_destinations_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories_table": {
|
||||||
|
"name": "repositories_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"compression_mode": {
|
||||||
|
"name": "compression_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'auto'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"last_checked": {
|
||||||
|
"name": "last_checked",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"repositories_table_short_id_unique": {
|
||||||
|
"name": "repositories_table_short_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"short_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"repositories_table_name_unique": {
|
||||||
|
"name": "repositories_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions_table": {
|
||||||
|
"name": "sessions_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
|
"tableFrom": "sessions_table",
|
||||||
|
"tableTo": "users_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_table": {
|
||||||
|
"name": "users_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_downloaded_restic_password": {
|
||||||
|
"name": "has_downloaded_restic_password",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_table_username_unique": {
|
||||||
|
"name": "users_table_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"volumes_table": {
|
||||||
|
"name": "volumes_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unmounted'"
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_health_check": {
|
||||||
|
"name": "last_health_check",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"auto_remount": {
|
||||||
|
"name": "auto_remount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"volumes_table_short_id_unique": {
|
||||||
|
"name": "volumes_table_short_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"short_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"volumes_table_name_unique": {
|
||||||
|
"name": "volumes_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
839
app/drizzle/meta/0024_snapshot.json
Normal file
839
app/drizzle/meta/0024_snapshot.json
Normal file
|
|
@ -0,0 +1,839 @@
|
||||||
|
{
|
||||||
|
"id": "f19cb32f-2280-42dd-a86a-aba7c0409d9f",
|
||||||
|
"prevId": "3e3841ca-67a8-493a-a061-9c2a780878ed",
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"tables": {
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table": {
|
||||||
|
"name": "backup_schedule_mirrors_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"last_copy_at": {
|
||||||
|
"name": "last_copy_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_status": {
|
||||||
|
"name": "last_copy_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_error": {
|
||||||
|
"name": "last_copy_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"schedule_id",
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"schedule_id"
|
||||||
|
],
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table": {
|
||||||
|
"name": "backup_schedule_notifications_table",
|
||||||
|
"columns": {
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_id": {
|
||||||
|
"name": "destination_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"notify_on_start": {
|
||||||
|
"name": "notify_on_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_success": {
|
||||||
|
"name": "notify_on_success",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_warning": {
|
||||||
|
"name": "notify_on_warning",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"notify_on_failure": {
|
||||||
|
"name": "notify_on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"schedule_id"
|
||||||
|
],
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"destination_id"
|
||||||
|
],
|
||||||
|
"tableTo": "notification_destinations_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"schedule_id",
|
||||||
|
"destination_id"
|
||||||
|
],
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedules_table": {
|
||||||
|
"name": "backup_schedules_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"volume_id": {
|
||||||
|
"name": "volume_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"cron_expression": {
|
||||||
|
"name": "cron_expression",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"retention_policy": {
|
||||||
|
"name": "retention_policy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"exclude_patterns": {
|
||||||
|
"name": "exclude_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"exclude_if_present": {
|
||||||
|
"name": "exclude_if_present",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"include_patterns": {
|
||||||
|
"name": "include_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"last_backup_at": {
|
||||||
|
"name": "last_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_status": {
|
||||||
|
"name": "last_backup_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_error": {
|
||||||
|
"name": "last_backup_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"next_backup_at": {
|
||||||
|
"name": "next_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"one_file_system": {
|
||||||
|
"name": "one_file_system",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedules_table_name_unique": {
|
||||||
|
"name": "backup_schedules_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"volume_id"
|
||||||
|
],
|
||||||
|
"tableTo": "volumes_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"repository_id"
|
||||||
|
],
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_destinations_table": {
|
||||||
|
"name": "notification_destinations_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"notification_destinations_table_name_unique": {
|
||||||
|
"name": "notification_destinations_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories_table": {
|
||||||
|
"name": "repositories_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"compression_mode": {
|
||||||
|
"name": "compression_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'auto'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"last_checked": {
|
||||||
|
"name": "last_checked",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"repositories_table_short_id_unique": {
|
||||||
|
"name": "repositories_table_short_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"short_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"repositories_table_name_unique": {
|
||||||
|
"name": "repositories_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions_table": {
|
||||||
|
"name": "sessions_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
|
"tableFrom": "sessions_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"tableTo": "users_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_table": {
|
||||||
|
"name": "users_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_downloaded_restic_password": {
|
||||||
|
"name": "has_downloaded_restic_password",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_table_username_unique": {
|
||||||
|
"name": "users_table_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"volumes_table": {
|
||||||
|
"name": "volumes_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unmounted'"
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_health_check": {
|
||||||
|
"name": "last_health_check",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"auto_remount": {
|
||||||
|
"name": "auto_remount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"volumes_table_short_id_unique": {
|
||||||
|
"name": "volumes_table_short_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"short_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"volumes_table_name_unique": {
|
||||||
|
"name": "volumes_table_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -162,6 +162,20 @@
|
||||||
"when": 1765794552191,
|
"when": 1765794552191,
|
||||||
"tag": "0022_woozy_shen",
|
"tag": "0022_woozy_shen",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1766320570509,
|
||||||
|
"tag": "0023_special_thor",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1766325504548,
|
||||||
|
"tag": "0024_schedules-one-fs",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +94,7 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
||||||
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress" | "warning">(),
|
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress" | "warning">(),
|
||||||
lastBackupError: text("last_backup_error"),
|
lastBackupError: text("last_backup_error"),
|
||||||
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
||||||
|
oneFileSystem: int("one_file_system", { mode: "boolean" }).notNull().default(false),
|
||||||
sortOrder: int("sort_order", { mode: "number" }).notNull().default(0),
|
sortOrder: int("sort_order", { mode: "number" }).notNull().default(0),
|
||||||
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`),
|
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`),
|
||||||
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`),
|
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`),
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export class CleanupDanglingMountsJob extends Job {
|
||||||
if (!matchingVolume) {
|
if (!matchingVolume) {
|
||||||
const fullPath = path.join(VOLUME_MOUNT_BASE, dir);
|
const fullPath = path.join(VOLUME_MOUNT_BASE, dir);
|
||||||
logger.info(`Found dangling mount directory at ${fullPath}, attempting to remove...`);
|
logger.info(`Found dangling mount directory at ${fullPath}, attempting to remove...`);
|
||||||
await fs.rmdir(fullPath, { recursive: true }).catch((err) => {
|
await fs.rm(fullPath, { recursive: true, force: true }).catch((err) => {
|
||||||
logger.warn(`Failed to remove dangling mount directory ${fullPath}: ${toMessage(err)}`);
|
logger.warn(`Failed to remove dangling mount directory ${fullPath}: ${toMessage(err)}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const backupScheduleSchema = type({
|
||||||
excludePatterns: "string[] | null",
|
excludePatterns: "string[] | null",
|
||||||
excludeIfPresent: "string[] | null",
|
excludeIfPresent: "string[] | null",
|
||||||
includePatterns: "string[] | null",
|
includePatterns: "string[] | null",
|
||||||
|
oneFileSystem: "boolean",
|
||||||
lastBackupAt: "number | null",
|
lastBackupAt: "number | null",
|
||||||
lastBackupStatus: "'success' | 'error' | 'in_progress' | 'warning' | null",
|
lastBackupStatus: "'success' | 'error' | 'in_progress' | 'warning' | null",
|
||||||
lastBackupError: "string | null",
|
lastBackupError: "string | null",
|
||||||
|
|
@ -131,6 +132,7 @@ export const createBackupScheduleBody = type({
|
||||||
excludePatterns: "string[]?",
|
excludePatterns: "string[]?",
|
||||||
excludeIfPresent: "string[]?",
|
excludeIfPresent: "string[]?",
|
||||||
includePatterns: "string[]?",
|
includePatterns: "string[]?",
|
||||||
|
oneFileSystem: "boolean?",
|
||||||
tags: "string[]?",
|
tags: "string[]?",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -168,6 +170,7 @@ export const updateBackupScheduleBody = type({
|
||||||
excludePatterns: "string[]?",
|
excludePatterns: "string[]?",
|
||||||
excludeIfPresent: "string[]?",
|
excludeIfPresent: "string[]?",
|
||||||
includePatterns: "string[]?",
|
includePatterns: "string[]?",
|
||||||
|
oneFileSystem: "boolean?",
|
||||||
tags: "string[]?",
|
tags: "string[]?",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
|
||||||
excludePatterns: data.excludePatterns ?? [],
|
excludePatterns: data.excludePatterns ?? [],
|
||||||
excludeIfPresent: data.excludeIfPresent ?? [],
|
excludeIfPresent: data.excludeIfPresent ?? [],
|
||||||
includePatterns: data.includePatterns ?? [],
|
includePatterns: data.includePatterns ?? [],
|
||||||
|
oneFileSystem: data.oneFileSystem,
|
||||||
nextBackupAt: nextBackupAt,
|
nextBackupAt: nextBackupAt,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
@ -273,9 +274,11 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
||||||
excludeIfPresent?: string[];
|
excludeIfPresent?: string[];
|
||||||
include?: string[];
|
include?: string[];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
oneFileSystem?: boolean;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
} = {
|
} = {
|
||||||
tags: [schedule.id.toString()],
|
tags: [schedule.id.toString()],
|
||||||
|
oneFileSystem: schedule.oneFileSystem,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -428,23 +431,25 @@ const stopBackup = async (scheduleId: number) => {
|
||||||
throw new NotFoundError("Backup schedule not found");
|
throw new NotFoundError("Backup schedule not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const abortController = runningBackups.get(scheduleId);
|
try {
|
||||||
if (!abortController) {
|
const abortController = runningBackups.get(scheduleId);
|
||||||
throw new ConflictError("No backup is currently running for this schedule");
|
if (!abortController) {
|
||||||
|
throw new ConflictError("No backup is currently running for this schedule");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Stopping backup for schedule ${scheduleId}`);
|
||||||
|
|
||||||
|
abortController.abort();
|
||||||
|
} finally {
|
||||||
|
await db
|
||||||
|
.update(backupSchedulesTable)
|
||||||
|
.set({
|
||||||
|
lastBackupStatus: "warning",
|
||||||
|
lastBackupError: "Backup was stopped by user",
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
})
|
||||||
|
.where(eq(backupSchedulesTable.id, scheduleId));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Stopping backup for schedule ${scheduleId}`);
|
|
||||||
|
|
||||||
abortController.abort();
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(backupSchedulesTable)
|
|
||||||
.set({
|
|
||||||
lastBackupStatus: "warning",
|
|
||||||
lastBackupError: "Backup was stopped by user",
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
})
|
|
||||||
.where(eq(backupSchedulesTable.id, scheduleId));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const runForget = async (scheduleId: number, repositoryId?: string) => {
|
const runForget = async (scheduleId: number, repositoryId?: string) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Scheduler } from "../../core/scheduler";
|
import { Scheduler } from "../../core/scheduler";
|
||||||
import { and, eq, or } from "drizzle-orm";
|
import { and, eq, or } from "drizzle-orm";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { volumesTable } from "../../db/schema";
|
import { backupSchedulesTable, volumesTable } from "../../db/schema";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import { restic } from "../../utils/restic";
|
import { restic } from "../../utils/restic";
|
||||||
import { volumeService } from "../volumes/volume.service";
|
import { volumeService } from "../volumes/volume.service";
|
||||||
|
|
@ -71,6 +71,18 @@ export const startup = async () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(backupSchedulesTable)
|
||||||
|
.set({
|
||||||
|
lastBackupStatus: "warning",
|
||||||
|
lastBackupError: "Zerobyte was restarted during the last scheduled backup",
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
})
|
||||||
|
.where(eq(backupSchedulesTable.lastBackupStatus, "in_progress"))
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(`Failed to update stuck backup schedules on startup: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
Scheduler.build(CleanupDanglingMountsJob).schedule("0 * * * *");
|
Scheduler.build(CleanupDanglingMountsJob).schedule("0 * * * *");
|
||||||
Scheduler.build(VolumeHealthCheckJob).schedule("*/30 * * * *");
|
Scheduler.build(VolumeHealthCheckJob).schedule("*/30 * * * *");
|
||||||
Scheduler.build(RepositoryHealthCheckJob).schedule("50 12 * * *");
|
Scheduler.build(RepositoryHealthCheckJob).schedule("50 12 * * *");
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,10 @@ const buildEnv = async (config: RepositoryConfig) => {
|
||||||
"UserKnownHostsFile=/dev/null",
|
"UserKnownHostsFile=/dev/null",
|
||||||
"-o",
|
"-o",
|
||||||
"LogLevel=VERBOSE",
|
"LogLevel=VERBOSE",
|
||||||
|
"-o",
|
||||||
|
"ServerAliveInterval=60",
|
||||||
|
"-o",
|
||||||
|
"ServerAliveCountMax=240",
|
||||||
"-i",
|
"-i",
|
||||||
keyPath,
|
keyPath,
|
||||||
];
|
];
|
||||||
|
|
@ -238,6 +242,7 @@ const backup = async (
|
||||||
excludeIfPresent?: string[];
|
excludeIfPresent?: string[];
|
||||||
include?: string[];
|
include?: string[];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
oneFileSystem?: boolean;
|
||||||
compressionMode?: CompressionMode;
|
compressionMode?: CompressionMode;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
onProgress?: (progress: BackupProgress) => void;
|
onProgress?: (progress: BackupProgress) => void;
|
||||||
|
|
@ -246,14 +251,11 @@ const backup = async (
|
||||||
const repoUrl = buildRepoUrl(config);
|
const repoUrl = buildRepoUrl(config);
|
||||||
const env = await buildEnv(config);
|
const env = await buildEnv(config);
|
||||||
|
|
||||||
const args: string[] = [
|
const args: string[] = ["--repo", repoUrl, "backup", "--compression", options?.compressionMode ?? "auto"];
|
||||||
"--repo",
|
|
||||||
repoUrl,
|
if (options?.oneFileSystem) {
|
||||||
"backup",
|
args.push("--one-file-system");
|
||||||
"--one-file-system",
|
}
|
||||||
"--compression",
|
|
||||||
options?.compressionMode ?? "auto",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (options?.tags && options.tags.length > 0) {
|
if (options?.tags && options.tags.length > 0) {
|
||||||
for (const tag of options.tags) {
|
for (const tag of options.tags) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import { intervalToDuration } from "date-fns";
|
import { intervalToDuration } from "date-fns";
|
||||||
|
|
||||||
export const getCronExpression = (frequency: string, dailyTime?: string, weeklyDay?: string): string => {
|
export const getCronExpression = (
|
||||||
|
frequency: string,
|
||||||
|
dailyTime?: string,
|
||||||
|
weeklyDay?: string,
|
||||||
|
monthlyDays?: string[],
|
||||||
|
): string => {
|
||||||
if (frequency === "hourly") {
|
if (frequency === "hourly") {
|
||||||
return "0 * * * *";
|
return "0 * * * *";
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +20,15 @@ export const getCronExpression = (frequency: string, dailyTime?: string, weeklyD
|
||||||
return `${minutes} ${hours} * * *`;
|
return `${minutes} ${hours} * * *`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frequency === "monthly") {
|
||||||
|
const sortedDays = (monthlyDays || [])
|
||||||
|
.map(Number)
|
||||||
|
.filter((day) => day >= 1 && day <= 31)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
const days = sortedDays.length > 0 ? sortedDays.join(",") : "1";
|
||||||
|
return `${minutes} ${hours} ${days} * *`;
|
||||||
|
}
|
||||||
|
|
||||||
return `${minutes} ${hours} * * ${weeklyDay ?? "0"}`;
|
return `${minutes} ${hours} * * ${weeklyDay ?? "0"}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
10
bun.lock
10
bun.lock
|
|
@ -64,7 +64,7 @@
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/react-query-devtools": "^5.91.1",
|
"@tanstack/react-query-devtools": "^5.91.1",
|
||||||
"@types/bun": "^1.3.4",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^25.0.3",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.6",
|
"vite": "^7.2.6",
|
||||||
"vite-bundle-analyzer": "^1.2.3",
|
"vite-bundle-analyzer": "^1.2.3",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^6.0.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -484,7 +484,7 @@
|
||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
||||||
|
|
||||||
|
|
@ -1068,7 +1068,7 @@
|
||||||
|
|
||||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||||
|
|
||||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
"vite-tsconfig-paths": ["vite-tsconfig-paths@6.0.3", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-7bL7FPX/DSviaZGYUKowWF1AiDVWjMjxNbE8lyaVGDezkedWqfGhlnQ4BZXre0ZN5P4kAgIJfAlgFDVyjrCIyg=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
|
@ -1138,6 +1138,8 @@
|
||||||
|
|
||||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
||||||
|
|
||||||
"c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/react-query-devtools": "^5.91.1",
|
"@tanstack/react-query-devtools": "^5.91.1",
|
||||||
"@types/bun": "^1.3.4",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^25.0.3",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
|
|
@ -92,6 +92,6 @@
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.6",
|
"vite": "^7.2.6",
|
||||||
"vite-bundle-analyzer": "^1.2.3",
|
"vite-bundle-analyzer": "^1.2.3",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^6.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue