From ff4c1404a6e4c9f381e618a4f27538aa3641c3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Tr=C3=A1vn=C3=ADk?= Date: Wed, 3 Dec 2025 17:31:44 +0100 Subject: [PATCH 01/15] feat: add icons to buttons and alerts for improved UI clarity --- .../components/create-repository-form.tsx | 19 ++++++++++++++++--- app/client/components/create-volume-form.tsx | 5 ++++- app/client/components/restore-form.tsx | 4 +++- app/client/components/snapshots-table.tsx | 8 ++++++-- .../schedule-notifications-config.tsx | 5 ++++- .../backups/components/schedule-summary.tsx | 18 ++++++++++++++---- .../components/snapshot-file-browser.tsx | 4 +++- .../modules/backups/routes/backup-details.tsx | 9 ++++++++- .../modules/backups/routes/create-backup.tsx | 3 ++- .../routes/create-notification.tsx | 4 +++- .../routes/notification-details.tsx | 14 +++++++++++--- .../repositories/routes/create-repository.tsx | 4 +++- .../routes/repository-details.tsx | 19 +++++++++++++++---- app/client/modules/repositories/tabs/info.tsx | 12 ++++++++++-- .../modules/repositories/tabs/snapshots.tsx | 3 ++- .../modules/settings/routes/settings.tsx | 5 ++++- .../volumes/components/healthchecks-card.tsx | 3 ++- .../modules/volumes/routes/create-volume.tsx | 4 +++- .../modules/volumes/routes/volume-details.tsx | 10 +++++++++- app/client/modules/volumes/tabs/info.tsx | 11 +++++++++-- 20 files changed, 131 insertions(+), 33 deletions(-) diff --git a/app/client/components/create-repository-form.tsx b/app/client/components/create-repository-form.tsx index 255dd7a..98827f6 100644 --- a/app/client/components/create-repository-form.tsx +++ b/app/client/components/create-repository-form.tsx @@ -2,6 +2,7 @@ import { arktypeResolver } from "@hookform/resolvers/arktype"; import { type } from "arktype"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import { Check, Pencil, Save, X } from "lucide-react"; import { cn, slugify } from "~/client/lib/utils"; import { deepClean } from "~/utils/object"; import { Button } from "./ui/button"; @@ -267,6 +268,7 @@ export const CreateRepositoryForm = ({ {form.watch("path") || "/var/lib/zerobyte/repositories"} @@ -292,13 +294,17 @@ export const CreateRepositoryForm = ({ - Cancel + + + Cancel + { setShowPathBrowser(true); setShowPathWarning(false); }} > + I Understand, Continue @@ -320,8 +326,14 @@ export const CreateRepositoryForm = ({ /> - Cancel - setShowPathBrowser(false)}>Done + + + Cancel + + setShowPathBrowser(false)}> + + Done + @@ -775,6 +787,7 @@ export const CreateRepositoryForm = ({ {mode === "update" && ( )} diff --git a/app/client/components/create-volume-form.tsx b/app/client/components/create-volume-form.tsx index a4fa692..a497526 100644 --- a/app/client/components/create-volume-form.tsx +++ b/app/client/components/create-volume-form.tsx @@ -1,7 +1,7 @@ import { arktypeResolver } from "@hookform/resolvers/arktype"; import { useMutation } from "@tanstack/react-query"; import { type } from "arktype"; -import { CheckCircle, Loader2, XCircle } from "lucide-react"; +import { CheckCircle, Loader2, Pencil, Plug, Save, XCircle } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { cn, slugify } from "~/client/lib/utils"; @@ -152,6 +152,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
{field.value}
@@ -561,6 +562,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for {!testBackendConnection.isPending && testMessage && !testMessage.success && ( )} + {!testBackendConnection.isPending && !testMessage && } {testBackendConnection.isPending ? "Testing..." : testMessage @@ -584,6 +586,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} {mode === "update" && ( )} diff --git a/app/client/components/restore-form.tsx b/app/client/components/restore-form.tsx index 4e4f779..b76721e 100644 --- a/app/client/components/restore-form.tsx +++ b/app/client/components/restore-form.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router"; import { toast } from "sonner"; -import { ChevronDown, FileIcon, FolderOpen, RotateCcw } from "lucide-react"; +import { ChevronDown, FileIcon, FolderOpen, RotateCcw, X } from "lucide-react"; import { Button } from "~/client/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Checkbox } from "~/client/components/ui/checkbox"; @@ -161,9 +161,11 @@ export function RestoreForm({ snapshot, repositoryName, snapshotId, returnPath }
@@ -254,9 +255,11 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations }: Props) {hasChanges && (
diff --git a/app/client/modules/backups/components/schedule-summary.tsx b/app/client/modules/backups/components/schedule-summary.tsx index 4422c77..426285f 100644 --- a/app/client/modules/backups/components/schedule-summary.tsx +++ b/app/client/modules/backups/components/schedule-summary.tsx @@ -1,4 +1,4 @@ -import { Eraser, Pencil, Play, Square, Trash2 } from "lucide-react"; +import { Check, Eraser, Pencil, Play, Square, Trash2, X } from "lucide-react"; import { useMemo, useState } from "react"; import { OnOff } from "~/client/components/onoff"; import { Button } from "~/client/components/ui/button"; @@ -199,11 +199,15 @@ export const ScheduleSummary = (props: Props) => {
- Cancel + + + Cancel + + Delete schedule
@@ -220,8 +224,14 @@ export const ScheduleSummary = (props: Props) => {
- Cancel - Run cleanup + + + Cancel + + + + Run cleanup +
diff --git a/app/client/modules/backups/components/snapshot-file-browser.tsx b/app/client/modules/backups/components/snapshot-file-browser.tsx index 8ace50b..94f9555 100644 --- a/app/client/modules/backups/components/snapshot-file-browser.tsx +++ b/app/client/modules/backups/components/snapshot-file-browser.tsx @@ -1,6 +1,6 @@ import { useCallback } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { FileIcon } from "lucide-react"; +import { FileIcon, RotateCcw, Trash2 } from "lucide-react"; import { Link } from "react-router"; import { FileTree } from "~/client/components/file-tree"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; @@ -98,6 +98,7 @@ export const SnapshotFileBrowser = (props: Props) => { } className={buttonVariants({ variant: "primary", size: "sm" })} > + Restore {onDeleteSnapshot && ( @@ -108,6 +109,7 @@ export const SnapshotFileBrowser = (props: Props) => { disabled={isDeletingSnapshot} loading={isDeletingSnapshot} > + {isDeletingSnapshot ? "Deleting..." : "Delete Snapshot"} )} diff --git a/app/client/modules/backups/routes/backup-details.tsx b/app/client/modules/backups/routes/backup-details.tsx index 727e939..4318599 100644 --- a/app/client/modules/backups/routes/backup-details.tsx +++ b/app/client/modules/backups/routes/backup-details.tsx @@ -2,6 +2,7 @@ import { useId, useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { redirect, useNavigate } from "react-router"; import { toast } from "sonner"; +import { Save, Trash2, X } from "lucide-react"; import { Button } from "~/client/components/ui/button"; import { AlertDialog, @@ -201,9 +202,11 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
@@ -254,12 +257,16 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon - Cancel + + + Cancel + + Delete snapshot diff --git a/app/client/modules/backups/routes/create-backup.tsx b/app/client/modules/backups/routes/create-backup.tsx index f982086..f19d7b9 100644 --- a/app/client/modules/backups/routes/create-backup.tsx +++ b/app/client/modules/backups/routes/create-backup.tsx @@ -1,6 +1,6 @@ import { useId, useState } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { Database, HardDrive } from "lucide-react"; +import { Database, HardDrive, Plus } from "lucide-react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import { @@ -160,6 +160,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
diff --git a/app/client/modules/notifications/routes/create-notification.tsx b/app/client/modules/notifications/routes/create-notification.tsx index 7990d65..1e206d9 100644 --- a/app/client/modules/notifications/routes/create-notification.tsx +++ b/app/client/modules/notifications/routes/create-notification.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { Bell } from "lucide-react"; +import { Bell, Plus, X } from "lucide-react"; import { useId } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -65,9 +65,11 @@ export default function CreateNotification() {
diff --git a/app/client/modules/notifications/routes/notification-details.tsx b/app/client/modules/notifications/routes/notification-details.tsx index 082f475..c667536 100644 --- a/app/client/modules/notifications/routes/notification-details.tsx +++ b/app/client/modules/notifications/routes/notification-details.tsx @@ -24,7 +24,7 @@ import { getNotificationDestination } from "~/client/api-client/sdk.gen"; import type { Route } from "./+types/notification-details"; import { cn } from "~/client/lib/utils"; import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card"; -import { Bell, TestTube2 } from "lucide-react"; +import { Bell, Save, TestTube2, Trash2, X } from "lucide-react"; import { Alert, AlertDescription } from "~/client/components/ui/alert"; import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form"; @@ -147,6 +147,7 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP variant="destructive" loading={deleteDestination.isPending} > + Delete @@ -174,6 +175,7 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP
@@ -190,8 +192,14 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP - Cancel - Delete + + + Cancel + + + + Delete + diff --git a/app/client/modules/repositories/routes/create-repository.tsx b/app/client/modules/repositories/routes/create-repository.tsx index 288e4ea..b4df970 100644 --- a/app/client/modules/repositories/routes/create-repository.tsx +++ b/app/client/modules/repositories/routes/create-repository.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { Database } from "lucide-react"; +import { Database, Plus, X } from "lucide-react"; import { useId } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -76,9 +76,11 @@ export default function CreateRepository() { />
diff --git a/app/client/modules/repositories/routes/repository-details.tsx b/app/client/modules/repositories/routes/repository-details.tsx index 6acdbdd..cd1f233 100644 --- a/app/client/modules/repositories/routes/repository-details.tsx +++ b/app/client/modules/repositories/routes/repository-details.tsx @@ -25,7 +25,7 @@ import { cn } from "~/client/lib/utils"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { RepositoryInfoTabContent } from "../tabs/info"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; -import { Loader2 } from "lucide-react"; +import { Loader2, Stethoscope, Trash2, X } from "lucide-react"; export const handle = { breadcrumb: (match: Route.MetaArgs) => [ @@ -152,10 +152,14 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro Running Doctor... ) : ( - "Run Doctor" + <> + + Run Doctor + )} @@ -184,11 +188,15 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro
- Cancel + + + Cancel + + Delete repository
@@ -230,7 +238,10 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro )}
- +
diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx index 585a5a1..0f63c19 100644 --- a/app/client/modules/repositories/tabs/info.tsx +++ b/app/client/modules/repositories/tabs/info.tsx @@ -2,6 +2,7 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { toast } from "sonner"; import { useNavigate } from "react-router"; +import { Check, Save, X } from "lucide-react"; import { Card } from "~/client/components/ui/card"; import { Button } from "~/client/components/ui/button"; import { Input } from "~/client/components/ui/input"; @@ -146,6 +147,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
@@ -159,8 +161,14 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => { Are you sure you want to update the repository settings? - Cancel - Update + + + Cancel + + + + Update + diff --git a/app/client/modules/repositories/tabs/snapshots.tsx b/app/client/modules/repositories/tabs/snapshots.tsx index c0a26ff..17c430c 100644 --- a/app/client/modules/repositories/tabs/snapshots.tsx +++ b/app/client/modules/repositories/tabs/snapshots.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { Database } from "lucide-react"; +import { Database, X } from "lucide-react"; import { useState } from "react"; import { listSnapshotsOptions } from "~/client/api-client/@tanstack/react-query.gen"; import { SnapshotsTable } from "~/client/components/snapshots-table"; @@ -124,6 +124,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {

No snapshots match your search.

diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx index a539712..7bbd0df 100644 --- a/app/client/modules/settings/routes/settings.tsx +++ b/app/client/modules/settings/routes/settings.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { Download, KeyRound, User } from "lucide-react"; +import { Download, KeyRound, User, X } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -195,6 +195,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { /> @@ -252,9 +253,11 @@ export default function Settings({ loaderData }: Route.ComponentProps) { setDownloadPassword(""); }} > + Cancel diff --git a/app/client/modules/volumes/components/healthchecks-card.tsx b/app/client/modules/volumes/components/healthchecks-card.tsx index 49c2e19..198efc4 100644 --- a/app/client/modules/volumes/components/healthchecks-card.tsx +++ b/app/client/modules/volumes/components/healthchecks-card.tsx @@ -1,6 +1,6 @@ import { useMutation } from "@tanstack/react-query"; import { formatDistanceToNow } from "date-fns"; -import { HeartIcon } from "lucide-react"; +import { Activity, HeartIcon } from "lucide-react"; import { toast } from "sonner"; import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { OnOff } from "~/client/components/onoff"; @@ -80,6 +80,7 @@ export const HealthchecksCard = ({ volume }: Props) => { loading={healthcheck.isPending} onClick={() => healthcheck.mutate({ path: { name: volume.name } })} > + Run Health Check diff --git a/app/client/modules/volumes/routes/create-volume.tsx b/app/client/modules/volumes/routes/create-volume.tsx index 15218ac..545cb03 100644 --- a/app/client/modules/volumes/routes/create-volume.tsx +++ b/app/client/modules/volumes/routes/create-volume.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { HardDrive } from "lucide-react"; +import { HardDrive, Plus, X } from "lucide-react"; import { useId } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -70,9 +70,11 @@ export default function CreateVolume() {
diff --git a/app/client/modules/volumes/routes/volume-details.tsx b/app/client/modules/volumes/routes/volume-details.tsx index f6802ab..1df9304 100644 --- a/app/client/modules/volumes/routes/volume-details.tsx +++ b/app/client/modules/volumes/routes/volume-details.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { useNavigate, useParams, useSearchParams } from "react-router"; import { toast } from "sonner"; import { useState } from "react"; +import { CircleStop, Play, Trash2, X } from "lucide-react"; import { StatusDot } from "~/client/components/status-dot"; import { Button } from "~/client/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; @@ -148,6 +149,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) { loading={mountVol.isPending} className={cn({ hidden: volume.status === "mounted" })} > + Mount @@ -200,11 +204,15 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
- Cancel + + + Cancel + + Delete volume
diff --git a/app/client/modules/volumes/tabs/info.tsx b/app/client/modules/volumes/tabs/info.tsx index 2b21307..5e0d5fe 100644 --- a/app/client/modules/volumes/tabs/info.tsx +++ b/app/client/modules/volumes/tabs/info.tsx @@ -2,6 +2,7 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; +import { Check, X } from "lucide-react"; import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form"; import { AlertDialog, @@ -93,8 +94,14 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => { - Cancel - Update + + + Cancel + + + + Update + From da8e9c4ada3def88b1a90f1d3ecf8cb39ddd60ce Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 6 Dec 2025 09:39:10 +0100 Subject: [PATCH 02/15] ci: fix docker cache args on wrong step --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16e8d9a..dadfa1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,8 +62,6 @@ jobs: type=semver,pattern={{major}}.{{minor}}.{{patch}},prefix=v,enable=${{ needs.determine-release-type.outputs.release_type == 'release' }} flavor: | latest=${{ needs.determine-release-type.outputs.release_type == 'release' }} - cache-from: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache - cache-to: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache,mode=max - name: Build and push images uses: docker/build-push-action@v6 @@ -76,6 +74,8 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | APP_VERSION=${{ needs.determine-release-type.outputs.tagname }} + cache-from: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache + cache-to: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache,mode=max publish-release: runs-on: ubuntu-latest From 2df1fa53a09767af3ba7d8a311a153f871e6f02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Tr=C3=A1vn=C3=ADk?= Date: Thu, 4 Dec 2025 00:04:26 +0100 Subject: [PATCH 03/15] feat: implement encryption for sensitive fields in volume backends --- .../modules/backends/smb/smb-backend.ts | 5 +++- .../modules/backends/webdav/webdav-backend.ts | 4 ++- app/server/modules/volumes/volume.service.ts | 25 +++++++++++++++++-- app/server/utils/crypto.ts | 11 +++++++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/server/modules/backends/smb/smb-backend.ts b/app/server/modules/backends/smb/smb-backend.ts index cdc112a..774e10f 100644 --- a/app/server/modules/backends/smb/smb-backend.ts +++ b/app/server/modules/backends/smb/smb-backend.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import { OPERATION_TIMEOUT } from "../../../core/constants"; +import { cryptoUtils } from "../../../utils/crypto"; import { toMessage } from "../../../utils/errors"; import { logger } from "../../../utils/logger"; import { getMountForPath } from "../../../utils/mountinfo"; @@ -33,10 +34,12 @@ const mount = async (config: BackendConfig, path: string) => { const run = async () => { await fs.mkdir(path, { recursive: true }); + const password = await cryptoUtils.decrypt(config.password); + const source = `//${config.server}/${config.share}`; const options = [ `user=${config.username}`, - `pass=${config.password}`, + `pass=${password}`, `vers=${config.vers}`, `port=${config.port}`, "uid=1000", diff --git a/app/server/modules/backends/webdav/webdav-backend.ts b/app/server/modules/backends/webdav/webdav-backend.ts index 1e9b72f..2467736 100644 --- a/app/server/modules/backends/webdav/webdav-backend.ts +++ b/app/server/modules/backends/webdav/webdav-backend.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import { promisify } from "node:util"; import { OPERATION_TIMEOUT } from "../../../core/constants"; +import { cryptoUtils } from "../../../utils/crypto"; import { toMessage } from "../../../utils/errors"; import { logger } from "../../../utils/logger"; import { getMountForPath } from "../../../utils/mountinfo"; @@ -49,8 +50,9 @@ const mount = async (config: BackendConfig, path: string) => { : ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"]; if (config.username && config.password) { + const password = await cryptoUtils.decrypt(config.password); const secretsFile = "/etc/davfs2/secrets"; - const secretsContent = `${source} ${config.username} ${config.password}\n`; + const secretsContent = `${source} ${config.username} ${password}\n`; await fs.appendFile(secretsFile, secretsContent, { mode: 0o600 }); } diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index 48dea19..3f415c0 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -8,6 +8,7 @@ import slugify from "slugify"; import { getCapabilities, parseDockerHost } from "../../core/capabilities"; import { db } from "../../db/db"; import { volumesTable } from "../../db/schema"; +import { cryptoUtils } from "../../utils/crypto"; import { toMessage } from "../../utils/errors"; import { generateShortId } from "../../utils/id"; import { getStatFs, type StatFs } from "../../utils/mountinfo"; @@ -19,6 +20,23 @@ import { logger } from "../../utils/logger"; import { serverEvents } from "../../core/events"; import type { BackendConfig } from "~/schemas/volumes"; +async function encryptSensitiveFields(config: BackendConfig): Promise { + switch (config.backend) { + case "smb": + return { + ...config, + password: await cryptoUtils.encrypt(config.password), + }; + case "webdav": + return { + ...config, + password: config.password ? await cryptoUtils.encrypt(config.password) : undefined, + }; + default: + return config; + } +} + const listVolumes = async () => { const volumes = await db.query.volumesTable.findMany({}); @@ -37,13 +55,14 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => { } const shortId = generateShortId(); + const encryptedConfig = await encryptSensitiveFields(backendConfig); const [created] = await db .insert(volumesTable) .values({ shortId, name: slug, - config: backendConfig, + config: encryptedConfig, type: backendConfig.backend, }) .returning(); @@ -175,11 +194,13 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => { await backend.unmount(); } + const encryptedConfig = volumeData.config ? await encryptSensitiveFields(volumeData.config) : undefined; + const [updated] = await db .update(volumesTable) .set({ name: newName, - config: volumeData.config, + config: encryptedConfig, type: volumeData.config?.backend, autoRemount: volumeData.autoRemount, updatedAt: Date.now(), diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index 651bebe..5394cef 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -5,6 +5,10 @@ const algorithm = "aes-256-gcm" as const; const keyLength = 32; const encryptionPrefix = "encv1"; +const isEncrypted = (val?: string): boolean => { + return typeof val === "string" && val.startsWith(encryptionPrefix); +}; + /** * Given a string, encrypts it using a randomly generated salt */ @@ -13,7 +17,7 @@ const encrypt = async (data: string) => { return data; } - if (data.startsWith(encryptionPrefix)) { + if (isEncrypted(data)) { return data; } @@ -34,6 +38,10 @@ const encrypt = async (data: string) => { * Given an encrypted string, decrypts it using the salt stored in the string */ const decrypt = async (encryptedData: string) => { + if (!isEncrypted(encryptedData)) { + return encryptedData; + } + const secret = await Bun.file(RESTIC_PASS_FILE).text(); const parts = encryptedData.split(":").slice(1); // Remove prefix @@ -58,4 +66,5 @@ const decrypt = async (encryptedData: string) => { export const cryptoUtils = { encrypt, decrypt, + isEncrypted, }; From 1fe026a76f444d95afebcb42298cb5cb60ab4302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Tr=C3=A1vn=C3=ADk?= Date: Thu, 4 Dec 2025 08:33:55 +0100 Subject: [PATCH 04/15] avoid logging secrets in smb backend --- app/server/modules/backends/smb/smb-backend.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/server/modules/backends/smb/smb-backend.ts b/app/server/modules/backends/smb/smb-backend.ts index 774e10f..9c38e0f 100644 --- a/app/server/modules/backends/smb/smb-backend.ts +++ b/app/server/modules/backends/smb/smb-backend.ts @@ -37,9 +37,8 @@ const mount = async (config: BackendConfig, path: string) => { const password = await cryptoUtils.decrypt(config.password); const source = `//${config.server}/${config.share}`; - const options = [ + const baseOptions = [ `user=${config.username}`, - `pass=${password}`, `vers=${config.vers}`, `port=${config.port}`, "uid=1000", @@ -47,18 +46,22 @@ const mount = async (config: BackendConfig, path: string) => { ]; if (config.domain) { - options.push(`domain=${config.domain}`); + baseOptions.push(`domain=${config.domain}`); } if (config.readOnly) { - options.push("ro"); + baseOptions.push("ro"); } - const args = ["-t", "cifs", "-o", options.join(","), source, path]; + const baseArgs = ["-t", "cifs", "-o"]; logger.debug(`Mounting SMB volume ${path}...`); - logger.info(`Executing mount: mount ${args.join(" ")}`); + const safeOptions = [...baseOptions, "pass=***"]; + const safeArgs = [...baseArgs, safeOptions.join(","), source, path]; + logger.info(`Executing mount: mount ${safeArgs.join(" ")}`); + const options = [...baseOptions, `pass=${password}`]; + const args = [...baseArgs, options.join(","), source, path]; await executeMount(args); logger.info(`SMB volume at ${path} mounted successfully.`); From fc482e97295154d975528ac79a01222d31779aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Tr=C3=A1vn=C3=ADk?= Date: Thu, 4 Dec 2025 08:39:51 +0100 Subject: [PATCH 05/15] crypto.Utils comments updated to reflect behaviour --- app/server/utils/crypto.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index 5394cef..7acac22 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -5,12 +5,16 @@ const algorithm = "aes-256-gcm" as const; const keyLength = 32; const encryptionPrefix = "encv1"; +/** + * Checks if a given string is encrypted by looking for the encryption prefix. + */ const isEncrypted = (val?: string): boolean => { return typeof val === "string" && val.startsWith(encryptionPrefix); }; /** - * Given a string, encrypts it using a randomly generated salt + * Given a string, encrypts it using a randomly generated salt. + * Returns the input unchanged if it's empty or already encrypted. */ const encrypt = async (data: string) => { if (!data) { @@ -35,7 +39,8 @@ const encrypt = async (data: string) => { }; /** - * Given an encrypted string, decrypts it using the salt stored in the string + * Given an encrypted string, decrypts it using the salt stored in the string. + * Returns the input unchanged if it's not encrypted (for backward compatibility). */ const decrypt = async (encryptedData: string) => { if (!isEncrypted(encryptedData)) { From 780fdae63e6e9c8da09eeed77aca4adedfad8b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Tr=C3=A1vn=C3=ADk?= Date: Thu, 4 Dec 2025 08:43:17 +0100 Subject: [PATCH 06/15] cryptoUtils.decrypt alligned with encrypt in regards of handling extra space in password file --- app/server/utils/crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index 7acac22..b4e338e 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -47,7 +47,7 @@ const decrypt = async (encryptedData: string) => { return encryptedData; } - const secret = await Bun.file(RESTIC_PASS_FILE).text(); + const secret = (await Bun.file(RESTIC_PASS_FILE).text()).trim(); const parts = encryptedData.split(":").slice(1); // Remove prefix const saltHex = parts.shift() as string; From 91201533750cf30c6c533d2c085dcc355701af46 Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:52:47 +0100 Subject: [PATCH 07/15] feat: mirror repositories (#95) * feat: mirror repositories feat: mirror backup repositories * chore: pr feedbacks --- .../api-client/@tanstack/react-query.gen.ts | 6 +- app/client/api-client/sdk.gen.ts | 34 + app/drizzle/0018_bizarre_zzzax.sql | 139 ++ app/drizzle/0019_heavy_shen.sql | 1 + app/drizzle/meta/0018_snapshot.json | 1530 ++++++++--------- app/drizzle/meta/0019_snapshot.json | 21 +- app/drizzle/meta/_journal.json | 15 +- app/server/db/schema.ts | 2 +- app/server/utils/restic.ts | 6 - 9 files changed, 924 insertions(+), 830 deletions(-) create mode 100644 app/drizzle/0018_bizarre_zzzax.sql create mode 100644 app/drizzle/0019_heavy_shen.sql diff --git a/app/client/api-client/@tanstack/react-query.gen.ts b/app/client/api-client/@tanstack/react-query.gen.ts index a4ac2a3..90ae121 100644 --- a/app/client/api-client/@tanstack/react-query.gen.ts +++ b/app/client/api-client/@tanstack/react-query.gen.ts @@ -753,7 +753,7 @@ export const updateScheduleNotificationsMutation = (options?: Partial) => createQueryKey('getScheduleMirrors', options); +export const getScheduleMirrorsQueryKey = (options: Options) => createQueryKey("getScheduleMirrors", options); /** * Get mirror repository assignments for a backup schedule @@ -788,7 +788,7 @@ export const updateScheduleMirrorsMutation = (options?: Partial) => createQueryKey('getMirrorCompatibility', options); +export const getMirrorCompatibilityQueryKey = (options: Options) => createQueryKey("getMirrorCompatibility", options); /** * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository @@ -806,7 +806,7 @@ export const getMirrorCompatibilityOptions = (options: Options) => createQueryKey('listNotificationDestinations', options); +export const listNotificationDestinationsQueryKey = (options?: Options) => createQueryKey("listNotificationDestinations", options); /** * List all notification destinations diff --git a/app/client/api-client/sdk.gen.ts b/app/client/api-client/sdk.gen.ts index c27c269..ea45a32 100644 --- a/app/client/api-client/sdk.gen.ts +++ b/app/client/api-client/sdk.gen.ts @@ -329,6 +329,40 @@ export const updateScheduleMirrors = (opti */ export const getMirrorCompatibility = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{scheduleId}/mirrors/compatibility', ...options }); +/** + * Get mirror repository assignments for a backup schedule + */ +export const getScheduleMirrors = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v1/backups/{scheduleId}/mirrors', + ...options + }); +}; + +/** + * Update mirror repository assignments for a backup schedule + */ +export const updateScheduleMirrors = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v1/backups/{scheduleId}/mirrors', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository + */ +export const getMirrorCompatibility = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v1/backups/{scheduleId}/mirrors/compatibility', + ...options + }); +}; + /** * List all notification destinations */ diff --git a/app/drizzle/0018_bizarre_zzzax.sql b/app/drizzle/0018_bizarre_zzzax.sql new file mode 100644 index 0000000..0b72b21 --- /dev/null +++ b/app/drizzle/0018_bizarre_zzzax.sql @@ -0,0 +1,139 @@ +DROP TABLE IF EXISTS `backup_schedule_mirrors_table`;--> statement-breakpoint +CREATE TABLE `backup_schedule_mirrors_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `schedule_id` integer NOT NULL, + `repository_id` text NOT NULL, + `enabled` integer DEFAULT true NOT NULL, + `last_copy_at` integer, + `last_copy_status` text, + `last_copy_error` text, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + FOREIGN KEY (`schedule_id`) REFERENCES `backup_schedules_table`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`repository_id`) REFERENCES `repositories_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_app_metadata` ( + `key` text PRIMARY KEY NOT NULL, + `value` text NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_app_metadata`("key", "value", "created_at", "updated_at") SELECT "key", "value", "created_at", "updated_at" FROM `app_metadata`;--> statement-breakpoint +DROP TABLE `app_metadata`;--> statement-breakpoint +ALTER TABLE `__new_app_metadata` RENAME TO `app_metadata`;--> statement-breakpoint +CREATE TABLE `__new_backup_schedule_notifications_table` ( + `schedule_id` integer NOT NULL, + `destination_id` integer NOT NULL, + `notify_on_start` integer DEFAULT false NOT NULL, + `notify_on_success` integer DEFAULT false NOT NULL, + `notify_on_failure` integer DEFAULT true NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + PRIMARY KEY(`schedule_id`, `destination_id`), + FOREIGN KEY (`schedule_id`) REFERENCES `backup_schedules_table`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`destination_id`) REFERENCES `notification_destinations_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_backup_schedule_notifications_table`("schedule_id", "destination_id", "notify_on_start", "notify_on_success", "notify_on_failure", "created_at") SELECT "schedule_id", "destination_id", "notify_on_start", "notify_on_success", "notify_on_failure", "created_at" FROM `backup_schedule_notifications_table`;--> statement-breakpoint +DROP TABLE `backup_schedule_notifications_table`;--> statement-breakpoint +ALTER TABLE `__new_backup_schedule_notifications_table` RENAME TO `backup_schedule_notifications_table`;--> statement-breakpoint +CREATE TABLE `__new_backup_schedules_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `volume_id` integer NOT NULL, + `repository_id` text NOT NULL, + `enabled` integer DEFAULT true NOT NULL, + `cron_expression` text NOT NULL, + `retention_policy` text, + `exclude_patterns` text DEFAULT '[]', + `include_patterns` text DEFAULT '[]', + `last_backup_at` integer, + `last_backup_status` text, + `last_backup_error` text, + `next_backup_at` integer, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + FOREIGN KEY (`volume_id`) REFERENCES `volumes_table`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`repository_id`) REFERENCES `repositories_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_backup_schedules_table`("id", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "created_at", "updated_at") SELECT "id", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "created_at", "updated_at" FROM `backup_schedules_table`;--> statement-breakpoint +DROP TABLE `backup_schedules_table`;--> statement-breakpoint +ALTER TABLE `__new_backup_schedules_table` RENAME TO `backup_schedules_table`;--> statement-breakpoint +CREATE TABLE `__new_notification_destinations_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `enabled` integer DEFAULT true NOT NULL, + `type` text NOT NULL, + `config` text NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_notification_destinations_table`("id", "name", "enabled", "type", "config", "created_at", "updated_at") SELECT "id", "name", "enabled", "type", "config", "created_at", "updated_at" FROM `notification_destinations_table`;--> statement-breakpoint +DROP TABLE `notification_destinations_table`;--> statement-breakpoint +ALTER TABLE `__new_notification_destinations_table` RENAME TO `notification_destinations_table`;--> statement-breakpoint +CREATE UNIQUE INDEX `notification_destinations_table_name_unique` ON `notification_destinations_table` (`name`);--> statement-breakpoint +CREATE TABLE `__new_repositories_table` ( + `id` text PRIMARY KEY NOT NULL, + `short_id` text NOT NULL, + `name` text NOT NULL, + `type` text NOT NULL, + `config` text NOT NULL, + `compression_mode` text DEFAULT 'auto', + `status` text DEFAULT 'unknown', + `last_checked` integer, + `last_error` text, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_repositories_table`("id", "short_id", "name", "type", "config", "compression_mode", "status", "last_checked", "last_error", "created_at", "updated_at") SELECT "id", "short_id", "name", "type", "config", "compression_mode", "status", "last_checked", "last_error", "created_at", "updated_at" FROM `repositories_table`;--> statement-breakpoint +DROP TABLE `repositories_table`;--> statement-breakpoint +ALTER TABLE `__new_repositories_table` RENAME TO `repositories_table`;--> statement-breakpoint +CREATE UNIQUE INDEX `repositories_table_short_id_unique` ON `repositories_table` (`short_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `repositories_table_name_unique` ON `repositories_table` (`name`);--> statement-breakpoint +CREATE TABLE `__new_sessions_table` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` integer NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_sessions_table`("id", "user_id", "expires_at", "created_at") SELECT "id", "user_id", "expires_at", "created_at" FROM `sessions_table`;--> statement-breakpoint +DROP TABLE `sessions_table`;--> statement-breakpoint +ALTER TABLE `__new_sessions_table` RENAME TO `sessions_table`;--> statement-breakpoint +CREATE TABLE `__new_users_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `username` text NOT NULL, + `password_hash` text NOT NULL, + `has_downloaded_restic_password` integer DEFAULT false NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_users_table`("id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at") SELECT "id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at" FROM `users_table`;--> statement-breakpoint +DROP TABLE `users_table`;--> statement-breakpoint +ALTER TABLE `__new_users_table` RENAME TO `users_table`;--> statement-breakpoint +CREATE UNIQUE INDEX `users_table_username_unique` ON `users_table` (`username`);--> statement-breakpoint +CREATE TABLE `__new_volumes_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `short_id` text NOT NULL, + `name` text NOT NULL, + `type` text NOT NULL, + `status` text DEFAULT 'unmounted' NOT NULL, + `last_error` text, + `last_health_check` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `config` text NOT NULL, + `auto_remount` integer DEFAULT true NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_volumes_table`("id", "short_id", "name", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount") SELECT "id", "short_id", "name", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount" FROM `volumes_table`;--> statement-breakpoint +DROP TABLE `volumes_table`;--> statement-breakpoint +ALTER TABLE `__new_volumes_table` RENAME TO `volumes_table`;--> statement-breakpoint +CREATE UNIQUE INDEX `volumes_table_short_id_unique` ON `volumes_table` (`short_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `volumes_table_name_unique` ON `volumes_table` (`name`); +PRAGMA foreign_keys=ON;--> statement-breakpoint diff --git a/app/drizzle/0019_heavy_shen.sql b/app/drizzle/0019_heavy_shen.sql new file mode 100644 index 0000000..98b747b --- /dev/null +++ b/app/drizzle/0019_heavy_shen.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX `backup_schedule_mirrors_table_schedule_id_repository_id_unique` ON `backup_schedule_mirrors_table` (`schedule_id`,`repository_id`); \ No newline at end of file diff --git a/app/drizzle/meta/0018_snapshot.json b/app/drizzle/meta/0018_snapshot.json index 27420d2..dcc5188 100644 --- a/app/drizzle/meta/0018_snapshot.json +++ b/app/drizzle/meta/0018_snapshot.json @@ -1,792 +1,740 @@ { - "version": "6", - "dialect": "sqlite", - "id": "d5a60aea-4490-423e-8725-6ace87a76c9b", - "prevId": "d0bfd316-b8f5-459b-ab17-0ce679479321", - "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_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 - }, - "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": "'[]'" - }, - "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 - }, - "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": { - "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": {} - } -} \ No newline at end of file + "version": "6", + "dialect": "sqlite", + "id": "121ef03c-eb5a-4b97-b2f1-4add6adfb080", + "prevId": "d0bfd316-b8f5-459b-ab17-0ce679479321", + "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": {}, + "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_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 + }, + "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": "'[]'" + }, + "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 + }, + "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": { + "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": {} + } +} diff --git a/app/drizzle/meta/0019_snapshot.json b/app/drizzle/meta/0019_snapshot.json index 3f2d178..ff1f037 100644 --- a/app/drizzle/meta/0019_snapshot.json +++ b/app/drizzle/meta/0019_snapshot.json @@ -1,8 +1,8 @@ { "version": "6", "dialect": "sqlite", - "id": "b5b3acff-51d7-45ae-b9d2-4b07a6286fc3", - "prevId": "d5a60aea-4490-423e-8725-6ace87a76c9b", + "id": "dedfb246-68e7-4590-af52-6476eb2999d1", + "prevId": "121ef03c-eb5a-4b97-b2f1-4add6adfb080", "tables": { "app_metadata": { "name": "app_metadata", @@ -249,13 +249,6 @@ "notNull": true, "autoincrement": true }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "volume_id": { "name": "volume_id", "type": "integer", @@ -353,15 +346,7 @@ "default": "(unixepoch() * 1000)" } }, - "indexes": { - "backup_schedules_table_name_unique": { - "name": "backup_schedules_table_name_unique", - "columns": [ - "name" - ], - "isUnique": true - } - }, + "indexes": {}, "foreignKeys": { "backup_schedules_table_volume_id_volumes_table_id_fk": { "name": "backup_schedules_table_volume_id_volumes_table_id_fk", diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index b3edcb3..4ae3a51 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -131,22 +131,15 @@ { "idx": 18, "version": "6", - "when": 1764794371040, - "tag": "0018_breezy_invaders", + "when": 1764619898949, + "tag": "0018_bizarre_zzzax", "breakpoints": true }, { "idx": 19, "version": "6", - "when": 1764839917446, - "tag": "0019_secret_nomad", - "breakpoints": true - }, - { - "idx": 20, - "version": "6", - "when": 1764847918249, - "tag": "0020_even_dexter_bennett", + "when": 1764790151212, + "tag": "0019_heavy_shen", "breakpoints": true } ] diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index a316ad3..37ae488 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -1,5 +1,5 @@ import { relations, sql } from "drizzle-orm"; -import { int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core"; +import { int, integer, sqliteTable, text, primaryKey, uniqueIndex, unique } from "drizzle-orm/sqlite-core"; import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus } from "~/schemas/restic"; import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes"; import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications"; diff --git a/app/server/utils/restic.ts b/app/server/utils/restic.ts index ab6743f..c1559dc 100644 --- a/app/server/utils/restic.ts +++ b/app/server/utils/restic.ts @@ -281,12 +281,6 @@ const backup = async ( } } - if (options?.excludeIfPresent && options.excludeIfPresent.length > 0) { - for (const filename of options.excludeIfPresent) { - args.push("--exclude-if-present", filename); - } - } - addCommonArgs(args, env); const logData = throttle((data: string) => { From b155f82575062d76043414abee0e4df548f04794 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Wed, 3 Dec 2025 20:55:38 +0100 Subject: [PATCH 08/15] chore: update dependencies --- app/server/db/schema.ts | 2 +- bun.lock | 10 ++++++---- package.json | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index 37ae488..a316ad3 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -1,5 +1,5 @@ import { relations, sql } from "drizzle-orm"; -import { int, integer, sqliteTable, text, primaryKey, uniqueIndex, unique } from "drizzle-orm/sqlite-core"; +import { int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core"; import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus } from "~/schemas/restic"; import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes"; import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications"; diff --git a/bun.lock b/bun.lock index 66bf1d8..dd2432a 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "es-toolkit": "^1.42.0", - "hono": "4.10.5", + "hono": "^4.10.7", "hono-openapi": "^1.1.1", "http-errors-enhanced": "^4.0.2", "isbot": "^5.1.32", @@ -41,7 +41,7 @@ "node-cron": "^4.2.1", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-hook-form": "^7.68.0", + "react-hook-form": "^7.67.0", "react-router": "^7.10.0", "react-router-hono-server": "^2.22.0", "recharts": "3.5.1", @@ -770,7 +770,7 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="], + "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], @@ -952,7 +952,7 @@ "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], - "react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="], + "react-hook-form": ["react-hook-form@7.67.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ=="], "react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="], @@ -1216,6 +1216,8 @@ "react-router-hono-server/@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], + "react-router-hono-server/hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="], + "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], diff --git a/package.json b/package.json index 4ea70bd..a1f54ed 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "es-toolkit": "^1.42.0", - "hono": "4.10.5", + "hono": "^4.10.7", "hono-openapi": "^1.1.1", "http-errors-enhanced": "^4.0.2", "isbot": "^5.1.32", @@ -54,7 +54,7 @@ "node-cron": "^4.2.1", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-hook-form": "^7.68.0", + "react-hook-form": "^7.67.0", "react-router": "^7.10.0", "react-router-hono-server": "^2.22.0", "recharts": "3.5.1", From 2660d9100275d9a6dd5dd0f894330a87e52af45f Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Wed, 3 Dec 2025 21:20:50 +0100 Subject: [PATCH 09/15] fix: broken migration --- app/drizzle/0018_bizarre_zzzax.sql | 139 --- app/drizzle/0019_heavy_shen.sql | 1 - app/drizzle/meta/0018_snapshot.json | 1530 ++++++++++++++------------- app/drizzle/meta/0019_snapshot.json | 792 -------------- app/drizzle/meta/_journal.json | 11 +- 5 files changed, 793 insertions(+), 1680 deletions(-) delete mode 100644 app/drizzle/0018_bizarre_zzzax.sql delete mode 100644 app/drizzle/0019_heavy_shen.sql delete mode 100644 app/drizzle/meta/0019_snapshot.json diff --git a/app/drizzle/0018_bizarre_zzzax.sql b/app/drizzle/0018_bizarre_zzzax.sql deleted file mode 100644 index 0b72b21..0000000 --- a/app/drizzle/0018_bizarre_zzzax.sql +++ /dev/null @@ -1,139 +0,0 @@ -DROP TABLE IF EXISTS `backup_schedule_mirrors_table`;--> statement-breakpoint -CREATE TABLE `backup_schedule_mirrors_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `schedule_id` integer NOT NULL, - `repository_id` text NOT NULL, - `enabled` integer DEFAULT true NOT NULL, - `last_copy_at` integer, - `last_copy_status` text, - `last_copy_error` text, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - FOREIGN KEY (`schedule_id`) REFERENCES `backup_schedules_table`(`id`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (`repository_id`) REFERENCES `repositories_table`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -PRAGMA foreign_keys=OFF;--> statement-breakpoint -CREATE TABLE `__new_app_metadata` ( - `key` text PRIMARY KEY NOT NULL, - `value` text NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL -); ---> statement-breakpoint -INSERT INTO `__new_app_metadata`("key", "value", "created_at", "updated_at") SELECT "key", "value", "created_at", "updated_at" FROM `app_metadata`;--> statement-breakpoint -DROP TABLE `app_metadata`;--> statement-breakpoint -ALTER TABLE `__new_app_metadata` RENAME TO `app_metadata`;--> statement-breakpoint -CREATE TABLE `__new_backup_schedule_notifications_table` ( - `schedule_id` integer NOT NULL, - `destination_id` integer NOT NULL, - `notify_on_start` integer DEFAULT false NOT NULL, - `notify_on_success` integer DEFAULT false NOT NULL, - `notify_on_failure` integer DEFAULT true NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - PRIMARY KEY(`schedule_id`, `destination_id`), - FOREIGN KEY (`schedule_id`) REFERENCES `backup_schedules_table`(`id`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (`destination_id`) REFERENCES `notification_destinations_table`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO `__new_backup_schedule_notifications_table`("schedule_id", "destination_id", "notify_on_start", "notify_on_success", "notify_on_failure", "created_at") SELECT "schedule_id", "destination_id", "notify_on_start", "notify_on_success", "notify_on_failure", "created_at" FROM `backup_schedule_notifications_table`;--> statement-breakpoint -DROP TABLE `backup_schedule_notifications_table`;--> statement-breakpoint -ALTER TABLE `__new_backup_schedule_notifications_table` RENAME TO `backup_schedule_notifications_table`;--> statement-breakpoint -CREATE TABLE `__new_backup_schedules_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `volume_id` integer NOT NULL, - `repository_id` text NOT NULL, - `enabled` integer DEFAULT true NOT NULL, - `cron_expression` text NOT NULL, - `retention_policy` text, - `exclude_patterns` text DEFAULT '[]', - `include_patterns` text DEFAULT '[]', - `last_backup_at` integer, - `last_backup_status` text, - `last_backup_error` text, - `next_backup_at` integer, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - FOREIGN KEY (`volume_id`) REFERENCES `volumes_table`(`id`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (`repository_id`) REFERENCES `repositories_table`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO `__new_backup_schedules_table`("id", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "created_at", "updated_at") SELECT "id", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "created_at", "updated_at" FROM `backup_schedules_table`;--> statement-breakpoint -DROP TABLE `backup_schedules_table`;--> statement-breakpoint -ALTER TABLE `__new_backup_schedules_table` RENAME TO `backup_schedules_table`;--> statement-breakpoint -CREATE TABLE `__new_notification_destinations_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `name` text NOT NULL, - `enabled` integer DEFAULT true NOT NULL, - `type` text NOT NULL, - `config` text NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL -); ---> statement-breakpoint -INSERT INTO `__new_notification_destinations_table`("id", "name", "enabled", "type", "config", "created_at", "updated_at") SELECT "id", "name", "enabled", "type", "config", "created_at", "updated_at" FROM `notification_destinations_table`;--> statement-breakpoint -DROP TABLE `notification_destinations_table`;--> statement-breakpoint -ALTER TABLE `__new_notification_destinations_table` RENAME TO `notification_destinations_table`;--> statement-breakpoint -CREATE UNIQUE INDEX `notification_destinations_table_name_unique` ON `notification_destinations_table` (`name`);--> statement-breakpoint -CREATE TABLE `__new_repositories_table` ( - `id` text PRIMARY KEY NOT NULL, - `short_id` text NOT NULL, - `name` text NOT NULL, - `type` text NOT NULL, - `config` text NOT NULL, - `compression_mode` text DEFAULT 'auto', - `status` text DEFAULT 'unknown', - `last_checked` integer, - `last_error` text, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL -); ---> statement-breakpoint -INSERT INTO `__new_repositories_table`("id", "short_id", "name", "type", "config", "compression_mode", "status", "last_checked", "last_error", "created_at", "updated_at") SELECT "id", "short_id", "name", "type", "config", "compression_mode", "status", "last_checked", "last_error", "created_at", "updated_at" FROM `repositories_table`;--> statement-breakpoint -DROP TABLE `repositories_table`;--> statement-breakpoint -ALTER TABLE `__new_repositories_table` RENAME TO `repositories_table`;--> statement-breakpoint -CREATE UNIQUE INDEX `repositories_table_short_id_unique` ON `repositories_table` (`short_id`);--> statement-breakpoint -CREATE UNIQUE INDEX `repositories_table_name_unique` ON `repositories_table` (`name`);--> statement-breakpoint -CREATE TABLE `__new_sessions_table` ( - `id` text PRIMARY KEY NOT NULL, - `user_id` integer NOT NULL, - `expires_at` integer NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO `__new_sessions_table`("id", "user_id", "expires_at", "created_at") SELECT "id", "user_id", "expires_at", "created_at" FROM `sessions_table`;--> statement-breakpoint -DROP TABLE `sessions_table`;--> statement-breakpoint -ALTER TABLE `__new_sessions_table` RENAME TO `sessions_table`;--> statement-breakpoint -CREATE TABLE `__new_users_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `username` text NOT NULL, - `password_hash` text NOT NULL, - `has_downloaded_restic_password` integer DEFAULT false NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL -); ---> statement-breakpoint -INSERT INTO `__new_users_table`("id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at") SELECT "id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at" FROM `users_table`;--> statement-breakpoint -DROP TABLE `users_table`;--> statement-breakpoint -ALTER TABLE `__new_users_table` RENAME TO `users_table`;--> statement-breakpoint -CREATE UNIQUE INDEX `users_table_username_unique` ON `users_table` (`username`);--> statement-breakpoint -CREATE TABLE `__new_volumes_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `short_id` text NOT NULL, - `name` text NOT NULL, - `type` text NOT NULL, - `status` text DEFAULT 'unmounted' NOT NULL, - `last_error` text, - `last_health_check` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, - `config` text NOT NULL, - `auto_remount` integer DEFAULT true NOT NULL -); ---> statement-breakpoint -INSERT INTO `__new_volumes_table`("id", "short_id", "name", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount") SELECT "id", "short_id", "name", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount" FROM `volumes_table`;--> statement-breakpoint -DROP TABLE `volumes_table`;--> statement-breakpoint -ALTER TABLE `__new_volumes_table` RENAME TO `volumes_table`;--> statement-breakpoint -CREATE UNIQUE INDEX `volumes_table_short_id_unique` ON `volumes_table` (`short_id`);--> statement-breakpoint -CREATE UNIQUE INDEX `volumes_table_name_unique` ON `volumes_table` (`name`); -PRAGMA foreign_keys=ON;--> statement-breakpoint diff --git a/app/drizzle/0019_heavy_shen.sql b/app/drizzle/0019_heavy_shen.sql deleted file mode 100644 index 98b747b..0000000 --- a/app/drizzle/0019_heavy_shen.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE UNIQUE INDEX `backup_schedule_mirrors_table_schedule_id_repository_id_unique` ON `backup_schedule_mirrors_table` (`schedule_id`,`repository_id`); \ No newline at end of file diff --git a/app/drizzle/meta/0018_snapshot.json b/app/drizzle/meta/0018_snapshot.json index dcc5188..27420d2 100644 --- a/app/drizzle/meta/0018_snapshot.json +++ b/app/drizzle/meta/0018_snapshot.json @@ -1,740 +1,792 @@ { - "version": "6", - "dialect": "sqlite", - "id": "121ef03c-eb5a-4b97-b2f1-4add6adfb080", - "prevId": "d0bfd316-b8f5-459b-ab17-0ce679479321", - "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": {}, - "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_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 - }, - "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": "'[]'" - }, - "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 - }, - "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": { - "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": {} - } -} + "version": "6", + "dialect": "sqlite", + "id": "d5a60aea-4490-423e-8725-6ace87a76c9b", + "prevId": "d0bfd316-b8f5-459b-ab17-0ce679479321", + "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_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 + }, + "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": "'[]'" + }, + "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 + }, + "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": { + "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": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/0019_snapshot.json b/app/drizzle/meta/0019_snapshot.json deleted file mode 100644 index ff1f037..0000000 --- a/app/drizzle/meta/0019_snapshot.json +++ /dev/null @@ -1,792 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "dedfb246-68e7-4590-af52-6476eb2999d1", - "prevId": "121ef03c-eb5a-4b97-b2f1-4add6adfb080", - "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_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 - }, - "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": "'[]'" - }, - "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 - }, - "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": { - "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": {} - } -} \ No newline at end of file diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index 4ae3a51..46fb2dd 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -131,15 +131,8 @@ { "idx": 18, "version": "6", - "when": 1764619898949, - "tag": "0018_bizarre_zzzax", - "breakpoints": true - }, - { - "idx": 19, - "version": "6", - "when": 1764790151212, - "tag": "0019_heavy_shen", + "when": 1764794371040, + "tag": "0018_breezy_invaders", "breakpoints": true } ] From d542318e2c7eeb500e7a06b5c4af90166118e5e2 Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:31:00 +0100 Subject: [PATCH 10/15] feat: naming backup schedules (#103) * docs: add agents instructions * feat: naming backup schedules * fix: wrong table for filtering --- .../api-client/@tanstack/react-query.gen.ts | 6 +- app/client/api-client/sdk.gen.ts | 34 - app/client/modules/backups/routes/backups.tsx | 2 +- app/drizzle/meta/0019_snapshot.json | 807 ++++++++++++++++++ app/drizzle/meta/_journal.json | 7 + bun.lock | 10 +- package.json | 4 +- 7 files changed, 824 insertions(+), 46 deletions(-) create mode 100644 app/drizzle/meta/0019_snapshot.json diff --git a/app/client/api-client/@tanstack/react-query.gen.ts b/app/client/api-client/@tanstack/react-query.gen.ts index 90ae121..a4ac2a3 100644 --- a/app/client/api-client/@tanstack/react-query.gen.ts +++ b/app/client/api-client/@tanstack/react-query.gen.ts @@ -753,7 +753,7 @@ export const updateScheduleNotificationsMutation = (options?: Partial) => createQueryKey("getScheduleMirrors", options); +export const getScheduleMirrorsQueryKey = (options: Options) => createQueryKey('getScheduleMirrors', options); /** * Get mirror repository assignments for a backup schedule @@ -788,7 +788,7 @@ export const updateScheduleMirrorsMutation = (options?: Partial) => createQueryKey("getMirrorCompatibility", options); +export const getMirrorCompatibilityQueryKey = (options: Options) => createQueryKey('getMirrorCompatibility', options); /** * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository @@ -806,7 +806,7 @@ export const getMirrorCompatibilityOptions = (options: Options) => createQueryKey("listNotificationDestinations", options); +export const listNotificationDestinationsQueryKey = (options?: Options) => createQueryKey('listNotificationDestinations', options); /** * List all notification destinations diff --git a/app/client/api-client/sdk.gen.ts b/app/client/api-client/sdk.gen.ts index ea45a32..c27c269 100644 --- a/app/client/api-client/sdk.gen.ts +++ b/app/client/api-client/sdk.gen.ts @@ -329,40 +329,6 @@ export const updateScheduleMirrors = (opti */ export const getMirrorCompatibility = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{scheduleId}/mirrors/compatibility', ...options }); -/** - * Get mirror repository assignments for a backup schedule - */ -export const getScheduleMirrors = (options: Options) => { - return (options.client ?? client).get({ - url: '/api/v1/backups/{scheduleId}/mirrors', - ...options - }); -}; - -/** - * Update mirror repository assignments for a backup schedule - */ -export const updateScheduleMirrors = (options: Options) => { - return (options.client ?? client).put({ - url: '/api/v1/backups/{scheduleId}/mirrors', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } - }); -}; - -/** - * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository - */ -export const getMirrorCompatibility = (options: Options) => { - return (options.client ?? client).get({ - url: '/api/v1/backups/{scheduleId}/mirrors/compatibility', - ...options - }); -}; - /** * List all notification destinations */ diff --git a/app/client/modules/backups/routes/backups.tsx b/app/client/modules/backups/routes/backups.tsx index 651c86f..b6ac602 100644 --- a/app/client/modules/backups/routes/backups.tsx +++ b/app/client/modules/backups/routes/backups.tsx @@ -79,7 +79,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) { isInProgress={schedule.lastBackupStatus === "in_progress"} /> - + {schedule.volume.name} diff --git a/app/drizzle/meta/0019_snapshot.json b/app/drizzle/meta/0019_snapshot.json new file mode 100644 index 0000000..3f2d178 --- /dev/null +++ b/app/drizzle/meta/0019_snapshot.json @@ -0,0 +1,807 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "b5b3acff-51d7-45ae-b9d2-4b07a6286fc3", + "prevId": "d5a60aea-4490-423e-8725-6ace87a76c9b", + "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_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": "'[]'" + }, + "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 + }, + "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": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index 46fb2dd..36145bb 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -134,6 +134,13 @@ "when": 1764794371040, "tag": "0018_breezy_invaders", "breakpoints": true + }, + { + "idx": 19, + "version": "6", + "when": 1764839917446, + "tag": "0019_secret_nomad", + "breakpoints": true } ] } \ No newline at end of file diff --git a/bun.lock b/bun.lock index dd2432a..66bf1d8 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "es-toolkit": "^1.42.0", - "hono": "^4.10.7", + "hono": "4.10.5", "hono-openapi": "^1.1.1", "http-errors-enhanced": "^4.0.2", "isbot": "^5.1.32", @@ -41,7 +41,7 @@ "node-cron": "^4.2.1", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-hook-form": "^7.67.0", + "react-hook-form": "^7.68.0", "react-router": "^7.10.0", "react-router-hono-server": "^2.22.0", "recharts": "3.5.1", @@ -770,7 +770,7 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], + "hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], @@ -952,7 +952,7 @@ "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], - "react-hook-form": ["react-hook-form@7.67.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ=="], + "react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="], "react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="], @@ -1216,8 +1216,6 @@ "react-router-hono-server/@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], - "react-router-hono-server/hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="], - "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], diff --git a/package.json b/package.json index a1f54ed..4ea70bd 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "es-toolkit": "^1.42.0", - "hono": "^4.10.7", + "hono": "4.10.5", "hono-openapi": "^1.1.1", "http-errors-enhanced": "^4.0.2", "isbot": "^5.1.32", @@ -54,7 +54,7 @@ "node-cron": "^4.2.1", "react": "^19.2.1", "react-dom": "^19.2.1", - "react-hook-form": "^7.67.0", + "react-hook-form": "^7.68.0", "react-router": "^7.10.0", "react-router-hono-server": "^2.22.0", "recharts": "3.5.1", From f232fc07c122787c3944ae24ad881345dbd72852 Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:44:34 +0100 Subject: [PATCH 11/15] feat: custom include patterns (#104) * feat: add custom include patterns * feat: add exclude-if-present option --- app/drizzle/meta/_journal.json | 7 +++++++ app/server/utils/restic.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index 36145bb..b3edcb3 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -141,6 +141,13 @@ "when": 1764839917446, "tag": "0019_secret_nomad", "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1764847918249, + "tag": "0020_even_dexter_bennett", + "breakpoints": true } ] } \ No newline at end of file diff --git a/app/server/utils/restic.ts b/app/server/utils/restic.ts index c1559dc..ab6743f 100644 --- a/app/server/utils/restic.ts +++ b/app/server/utils/restic.ts @@ -281,6 +281,12 @@ const backup = async ( } } + if (options?.excludeIfPresent && options.excludeIfPresent.length > 0) { + for (const filename of options.excludeIfPresent) { + args.push("--exclude-if-present", filename); + } + } + addCommonArgs(args, env); const logData = throttle((data: string) => { From b8ae10b316db92bbd42dba866f9553261eef46e6 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 4 Dec 2025 18:54:02 +0100 Subject: [PATCH 12/15] fix: volume data not refreshing when changing selection --- app/client/modules/backups/routes/backups.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/modules/backups/routes/backups.tsx b/app/client/modules/backups/routes/backups.tsx index b6ac602..651c86f 100644 --- a/app/client/modules/backups/routes/backups.tsx +++ b/app/client/modules/backups/routes/backups.tsx @@ -79,7 +79,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) { isInProgress={schedule.lastBackupStatus === "in_progress"} /> - + {schedule.volume.name} From 51ed47c30f1fc2367516f0af0523b4fdff40ce2b Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 6 Dec 2025 10:06:03 +0100 Subject: [PATCH 13/15] refactor: no need to print safe args as it's already sanitized --- app/server/modules/backends/smb/smb-backend.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/server/modules/backends/smb/smb-backend.ts b/app/server/modules/backends/smb/smb-backend.ts index 9c38e0f..774e10f 100644 --- a/app/server/modules/backends/smb/smb-backend.ts +++ b/app/server/modules/backends/smb/smb-backend.ts @@ -37,8 +37,9 @@ const mount = async (config: BackendConfig, path: string) => { const password = await cryptoUtils.decrypt(config.password); const source = `//${config.server}/${config.share}`; - const baseOptions = [ + const options = [ `user=${config.username}`, + `pass=${password}`, `vers=${config.vers}`, `port=${config.port}`, "uid=1000", @@ -46,22 +47,18 @@ const mount = async (config: BackendConfig, path: string) => { ]; if (config.domain) { - baseOptions.push(`domain=${config.domain}`); + options.push(`domain=${config.domain}`); } if (config.readOnly) { - baseOptions.push("ro"); + options.push("ro"); } - const baseArgs = ["-t", "cifs", "-o"]; + const args = ["-t", "cifs", "-o", options.join(","), source, path]; logger.debug(`Mounting SMB volume ${path}...`); - const safeOptions = [...baseOptions, "pass=***"]; - const safeArgs = [...baseArgs, safeOptions.join(","), source, path]; - logger.info(`Executing mount: mount ${safeArgs.join(" ")}`); + logger.info(`Executing mount: mount ${args.join(" ")}`); - const options = [...baseOptions, `pass=${password}`]; - const args = [...baseArgs, options.join(","), source, path]; await executeMount(args); logger.info(`SMB volume at ${path} mounted successfully.`); From c42380b26b24d902ab86eb062153136024d60f60 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 6 Dec 2025 10:08:01 +0100 Subject: [PATCH 14/15] refactor: remove trim on password The password should be taken as-is. It could potentially contain a space --- app/server/utils/crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index b4e338e..3b47591 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -25,7 +25,7 @@ const encrypt = async (data: string) => { return data; } - const secret = (await Bun.file(RESTIC_PASS_FILE).text()).trim(); + const secret = await Bun.file(RESTIC_PASS_FILE).text(); const salt = crypto.randomBytes(16); const key = crypto.pbkdf2Sync(secret, salt, 100000, keyLength, "sha256"); From fdb84374a00f9c3e5527786926563cd48e63c695 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 6 Dec 2025 11:13:26 +0100 Subject: [PATCH 15/15] refactor: remove icons where it was making the context worse --- app/client/components/create-repository-form.tsx | 8 ++------ app/client/components/restore-form.tsx | 3 +-- app/client/components/snapshots-table.tsx | 8 ++------ .../components/schedule-notifications-config.tsx | 5 +---- .../modules/backups/components/schedule-summary.tsx | 6 +----- .../backups/components/snapshot-file-browser.tsx | 2 +- .../modules/backups/routes/backup-details.tsx | 8 ++------ .../notifications/routes/create-notification.tsx | 3 +-- .../repositories/routes/create-repository.tsx | 5 ++--- .../modules/repositories/routes/repositories.tsx | 2 +- .../repositories/routes/repository-details.tsx | 11 ++++------- app/client/modules/repositories/tabs/info.tsx | 11 ++++------- app/client/modules/volumes/routes/create-volume.tsx | 3 +-- .../modules/volumes/routes/volume-details.tsx | 13 ++++--------- app/client/modules/volumes/tabs/info.tsx | 9 +++------ biome.json | 3 ++- package.json | 2 +- 17 files changed, 33 insertions(+), 69 deletions(-) diff --git a/app/client/components/create-repository-form.tsx b/app/client/components/create-repository-form.tsx index 98827f6..ad013db 100644 --- a/app/client/components/create-repository-form.tsx +++ b/app/client/components/create-repository-form.tsx @@ -280,7 +280,7 @@ export const CreateRepositoryForm = ({ - Important: Host Mount Required + Important: Host mount required

When selecting a custom path, ensure it is mounted from the host machine into the container.

@@ -294,17 +294,13 @@ export const CreateRepositoryForm = ({
- - - Cancel - + Cancel { setShowPathBrowser(true); setShowPathWarning(false); }} > - I Understand, Continue diff --git a/app/client/components/restore-form.tsx b/app/client/components/restore-form.tsx index b76721e..1891da8 100644 --- a/app/client/components/restore-form.tsx +++ b/app/client/components/restore-form.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router"; import { toast } from "sonner"; -import { ChevronDown, FileIcon, FolderOpen, RotateCcw, X } from "lucide-react"; +import { ChevronDown, FileIcon, FolderOpen, RotateCcw } from "lucide-react"; import { Button } from "~/client/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Checkbox } from "~/client/components/ui/checkbox"; @@ -161,7 +161,6 @@ export function RestoreForm({ snapshot, repositoryName, snapshotId, returnPath }
@@ -255,11 +254,9 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations }: Props) {hasChanges && (
diff --git a/app/client/modules/backups/components/schedule-summary.tsx b/app/client/modules/backups/components/schedule-summary.tsx index 09902f0..8bdd3b9 100644 --- a/app/client/modules/backups/components/schedule-summary.tsx +++ b/app/client/modules/backups/components/schedule-summary.tsx @@ -207,15 +207,11 @@ export const ScheduleSummary = (props: Props) => {
- - - Cancel - + Cancel - Delete schedule
diff --git a/app/client/modules/backups/components/snapshot-file-browser.tsx b/app/client/modules/backups/components/snapshot-file-browser.tsx index 94f9555..40804f1 100644 --- a/app/client/modules/backups/components/snapshot-file-browser.tsx +++ b/app/client/modules/backups/components/snapshot-file-browser.tsx @@ -98,7 +98,7 @@ export const SnapshotFileBrowser = (props: Props) => { } className={buttonVariants({ variant: "primary", size: "sm" })} > - + Restore {onDeleteSnapshot && ( diff --git a/app/client/modules/backups/routes/backup-details.tsx b/app/client/modules/backups/routes/backup-details.tsx index b7d518f..4804c4b 100644 --- a/app/client/modules/backups/routes/backup-details.tsx +++ b/app/client/modules/backups/routes/backup-details.tsx @@ -2,7 +2,7 @@ import { useId, useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { redirect, useNavigate } from "react-router"; import { toast } from "sonner"; -import { Save, Trash2, X } from "lucide-react"; +import { Save, X } from "lucide-react"; import { Button } from "~/client/components/ui/button"; import { AlertDialog, @@ -269,16 +269,12 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon - - - Cancel - + Cancel - Delete snapshot diff --git a/app/client/modules/notifications/routes/create-notification.tsx b/app/client/modules/notifications/routes/create-notification.tsx index 1e206d9..72268a6 100644 --- a/app/client/modules/notifications/routes/create-notification.tsx +++ b/app/client/modules/notifications/routes/create-notification.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { Bell, Plus, X } from "lucide-react"; +import { Bell, Plus } from "lucide-react"; import { useId } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -65,7 +65,6 @@ export default function CreateNotification() {
diff --git a/app/client/modules/repositories/routes/repositories.tsx b/app/client/modules/repositories/routes/repositories.tsx index df58493..89e05d2 100644 --- a/app/client/modules/repositories/routes/repositories.tsx +++ b/app/client/modules/repositories/routes/repositories.tsx @@ -72,7 +72,7 @@ export default function Repositories({ loaderData }: Route.ComponentProps) { button={ } /> diff --git a/app/client/modules/repositories/routes/repository-details.tsx b/app/client/modules/repositories/routes/repository-details.tsx index cd1f233..be3e7c9 100644 --- a/app/client/modules/repositories/routes/repository-details.tsx +++ b/app/client/modules/repositories/routes/repository-details.tsx @@ -149,12 +149,12 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro {doctorMutation.isPending ? ( <> - Running Doctor... + Running doctor... ) : ( <> - Run Doctor + Run doctor )} @@ -206,7 +206,7 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro - Doctor Results + Doctor results Repository doctor operation completed @@ -238,10 +238,7 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro )}
- +
diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx index 0f63c19..bd198ea 100644 --- a/app/client/modules/repositories/tabs/info.tsx +++ b/app/client/modules/repositories/tabs/info.tsx @@ -2,7 +2,7 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { toast } from "sonner"; import { useNavigate } from "react-router"; -import { Check, Save, X } from "lucide-react"; +import { Check, Save } from "lucide-react"; import { Card } from "~/client/components/ui/card"; import { Button } from "~/client/components/ui/button"; import { Input } from "~/client/components/ui/input"; @@ -157,16 +157,13 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => { - Update Repository + Update repository Are you sure you want to update the repository settings? - - - Cancel - + Cancel - + Update diff --git a/app/client/modules/volumes/routes/create-volume.tsx b/app/client/modules/volumes/routes/create-volume.tsx index 545cb03..35e8bbb 100644 --- a/app/client/modules/volumes/routes/create-volume.tsx +++ b/app/client/modules/volumes/routes/create-volume.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { HardDrive, Plus, X } from "lucide-react"; +import { HardDrive, Plus } from "lucide-react"; import { useId } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; @@ -70,7 +70,6 @@ export default function CreateVolume() {
@@ -204,15 +203,11 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
- - - Cancel - + Cancel - Delete volume
diff --git a/app/client/modules/volumes/tabs/info.tsx b/app/client/modules/volumes/tabs/info.tsx index 5e0d5fe..d311bcf 100644 --- a/app/client/modules/volumes/tabs/info.tsx +++ b/app/client/modules/volumes/tabs/info.tsx @@ -2,7 +2,7 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; -import { Check, X } from "lucide-react"; +import { Check } from "lucide-react"; import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form"; import { AlertDialog, @@ -94,12 +94,9 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => { - - - Cancel - + Cancel - + Update diff --git a/biome.json b/biome.json index 68a6cf1..928a49a 100644 --- a/biome.json +++ b/biome.json @@ -1,8 +1,9 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.5/schema.json", + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "vcs": { "enabled": true, "clientKind": "git", + "defaultBranch": "origin/main", "useIgnoreFile": true }, "files": { diff --git a/package.json b/package.json index 4ea70bd..a787257 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "bun ./dist/server/index.js", "tsc": "react-router typegen && tsc", "lint": "biome check .", - "lint:ci": "biome check . --ci", + "lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched", "start:dev": "docker compose down && docker compose up --build zerobyte-dev", "start:prod": "docker compose down && docker compose up --build zerobyte-prod", "gen:api-client": "openapi-ts",