mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
Compare commits
9 Commits
v0.13.1
...
4328607cc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4328607cc1 | ||
|
|
bedd325a60 | ||
|
|
b26a062648 | ||
|
|
d190d9c8cd | ||
|
|
f8363a6c71 | ||
|
|
59b2b53837 | ||
|
|
e99487eed9 | ||
|
|
8d4e5d2d4e | ||
|
|
daea3e64e4 |
22
README.md
22
README.md
@@ -18,6 +18,10 @@
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Zerobyte is still in version 0.x.x and is subject to major changes from version to version. I am developing the core features and collecting feedbacks. Expect bugs! Please open issues or feature requests
|
> Zerobyte is still in version 0.x.x and is subject to major changes from version to version. I am developing the core features and collecting feedbacks. Expect bugs! Please open issues or feature requests
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.buymeacoffee.com/nicotsx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Intro
|
## Intro
|
||||||
|
|
||||||
Zerobyte is a backup automation tool that helps you save your data across multiple storage backends. Built on top of Restic, it provides an modern web interface to schedule, manage, and monitor encrypted backups of your remote storage.
|
Zerobyte is a backup automation tool that helps you save your data across multiple storage backends. Built on top of Restic, it provides an modern web interface to schedule, manage, and monitor encrypted backups of your remote storage.
|
||||||
@@ -45,6 +49,8 @@ services:
|
|||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
devices:
|
devices:
|
||||||
- /dev/fuse:/dev/fuse
|
- /dev/fuse:/dev/fuse
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris # Set your timezone here
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /var/lib/zerobyte:/var/lib/zerobyte
|
- /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
@@ -81,6 +87,8 @@ services:
|
|||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
devices:
|
devices:
|
||||||
- /dev/fuse:/dev/fuse
|
- /dev/fuse:/dev/fuse
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /var/lib/zerobyte:/var/lib/zerobyte
|
- /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
@@ -147,6 +155,8 @@ Zerobyte can use [rclone](https://rclone.org/) to support 40+ cloud storage prov
|
|||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
devices:
|
devices:
|
||||||
- /dev/fuse:/dev/fuse
|
- /dev/fuse:/dev/fuse
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /var/lib/zerobyte:/var/lib/zerobyte
|
- /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
@@ -202,6 +212,8 @@ services:
|
|||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
devices:
|
devices:
|
||||||
- /dev/fuse:/dev/fuse
|
- /dev/fuse:/dev/fuse
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- - /var/lib/zerobyte:/var/lib/zerobyte
|
- - /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
@@ -233,6 +245,8 @@ services:
|
|||||||
- "4096:4096"
|
- "4096:4096"
|
||||||
devices:
|
devices:
|
||||||
- /dev/fuse:/dev/fuse
|
- /dev/fuse:/dev/fuse
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- - /var/lib/zerobyte:/var/lib/zerobyte
|
- - /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
@@ -251,7 +265,7 @@ docker compose up -d
|
|||||||
Your Zerobyte volumes will now be available as Docker volumes that you can mount into other containers using the `--volume` flag:
|
Your Zerobyte volumes will now be available as Docker volumes that you can mount into other containers using the `--volume` flag:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -v im-nfs:/path/in/container nginx:latest
|
docker run -v zb-abc12:/path/in/container nginx:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or using Docker Compose:
|
Or using Docker Compose:
|
||||||
@@ -261,13 +275,13 @@ services:
|
|||||||
myservice:
|
myservice:
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
volumes:
|
volumes:
|
||||||
- im-nfs:/path/in/container
|
- zb-abc12:/path/in/container
|
||||||
volumes:
|
volumes:
|
||||||
im-nfs:
|
zb-abc12:
|
||||||
external: true
|
external: true
|
||||||
```
|
```
|
||||||
|
|
||||||
The volume name format is `im-<volume-name>` where `<volume-name>` is the name you assigned to the volume in Zerobyte. You can verify that the volume is available by running:
|
The volume name format is `zb-<short-id>` where `<short-id>` is the unique identifier shown on the volume's Docker tab in Zerobyte. This short ID remains stable even if you rename the volume. You can verify that the volume is available by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker volume ls
|
docker volume ls
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { type DefaultError, queryOptions, type UseMutationOptions } from '@tanstack/react-query';
|
import { type DefaultError, queryOptions, type UseMutationOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { client } from '../client.gen';
|
import { client } from '../client.gen';
|
||||||
import { browseFilesystem, changePassword, createBackupSchedule, createNotificationDestination, createRepository, createVolume, deleteBackupSchedule, deleteNotificationDestination, deleteRepository, deleteSnapshot, deleteVolume, doctorRepository, downloadResticPassword, getBackupSchedule, getBackupScheduleForVolume, getContainersUsingVolume, getMe, getNotificationDestination, getRepository, getScheduleNotifications, getSnapshotDetails, getStatus, getSystemInfo, getVolume, healthCheckVolume, listBackupSchedules, listFiles, listNotificationDestinations, listRcloneRemotes, listRepositories, listSnapshotFiles, listSnapshots, listVolumes, login, logout, mountVolume, type Options, register, restoreSnapshot, runBackupNow, runForget, stopBackup, testConnection, testNotificationDestination, unmountVolume, updateBackupSchedule, updateNotificationDestination, updateScheduleNotifications, updateVolume } from '../sdk.gen';
|
import { browseFilesystem, changePassword, createBackupSchedule, createNotificationDestination, createRepository, createVolume, deleteBackupSchedule, deleteNotificationDestination, deleteRepository, deleteSnapshot, deleteVolume, doctorRepository, downloadResticPassword, getBackupSchedule, getBackupScheduleForVolume, getContainersUsingVolume, getMe, getNotificationDestination, getRepository, getScheduleNotifications, getSnapshotDetails, getStatus, getSystemInfo, getVolume, healthCheckVolume, listBackupSchedules, listFiles, listNotificationDestinations, listRcloneRemotes, listRepositories, listSnapshotFiles, listSnapshots, listVolumes, login, logout, mountVolume, type Options, register, restoreSnapshot, runBackupNow, runForget, stopBackup, testConnection, testNotificationDestination, unmountVolume, updateBackupSchedule, updateNotificationDestination, updateRepository, updateScheduleNotifications, updateVolume } from '../sdk.gen';
|
||||||
import type { BrowseFilesystemData, BrowseFilesystemResponse, ChangePasswordData, ChangePasswordResponse, CreateBackupScheduleData, CreateBackupScheduleResponse, CreateNotificationDestinationData, CreateNotificationDestinationResponse, CreateRepositoryData, CreateRepositoryResponse, CreateVolumeData, CreateVolumeResponse, DeleteBackupScheduleData, DeleteBackupScheduleResponse, DeleteNotificationDestinationData, DeleteNotificationDestinationResponse, DeleteRepositoryData, DeleteRepositoryResponse, DeleteSnapshotData, DeleteSnapshotResponse, DeleteVolumeData, DeleteVolumeResponse, DoctorRepositoryData, DoctorRepositoryResponse, DownloadResticPasswordData, DownloadResticPasswordResponse, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponse, GetBackupScheduleResponse, GetContainersUsingVolumeData, GetContainersUsingVolumeResponse, GetMeData, GetMeResponse, GetNotificationDestinationData, GetNotificationDestinationResponse, GetRepositoryData, GetRepositoryResponse, GetScheduleNotificationsData, GetScheduleNotificationsResponse, GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetStatusData, GetStatusResponse, GetSystemInfoData, GetSystemInfoResponse, GetVolumeData, GetVolumeResponse, HealthCheckVolumeData, HealthCheckVolumeResponse, ListBackupSchedulesData, ListBackupSchedulesResponse, ListFilesData, ListFilesResponse, ListNotificationDestinationsData, ListNotificationDestinationsResponse, ListRcloneRemotesData, ListRcloneRemotesResponse, ListRepositoriesData, ListRepositoriesResponse, ListSnapshotFilesData, ListSnapshotFilesResponse, ListSnapshotsData, ListSnapshotsResponse, ListVolumesData, ListVolumesResponse, LoginData, LoginResponse, LogoutData, LogoutResponse, MountVolumeData, MountVolumeResponse, RegisterData, RegisterResponse, RestoreSnapshotData, RestoreSnapshotResponse, RunBackupNowData, RunBackupNowResponse, RunForgetData, RunForgetResponse, StopBackupData, StopBackupResponse, TestConnectionData, TestConnectionResponse, TestNotificationDestinationData, TestNotificationDestinationResponse, UnmountVolumeData, UnmountVolumeResponse, UpdateBackupScheduleData, UpdateBackupScheduleResponse, UpdateNotificationDestinationData, UpdateNotificationDestinationResponse, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponse, UpdateVolumeData, UpdateVolumeResponse } from '../types.gen';
|
import type { BrowseFilesystemData, BrowseFilesystemResponse, ChangePasswordData, ChangePasswordResponse, CreateBackupScheduleData, CreateBackupScheduleResponse, CreateNotificationDestinationData, CreateNotificationDestinationResponse, CreateRepositoryData, CreateRepositoryResponse, CreateVolumeData, CreateVolumeResponse, DeleteBackupScheduleData, DeleteBackupScheduleResponse, DeleteNotificationDestinationData, DeleteNotificationDestinationResponse, DeleteRepositoryData, DeleteRepositoryResponse, DeleteSnapshotData, DeleteSnapshotResponse, DeleteVolumeData, DeleteVolumeResponse, DoctorRepositoryData, DoctorRepositoryResponse, DownloadResticPasswordData, DownloadResticPasswordResponse, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponse, GetBackupScheduleResponse, GetContainersUsingVolumeData, GetContainersUsingVolumeResponse, GetMeData, GetMeResponse, GetNotificationDestinationData, GetNotificationDestinationResponse, GetRepositoryData, GetRepositoryResponse, GetScheduleNotificationsData, GetScheduleNotificationsResponse, GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetStatusData, GetStatusResponse, GetSystemInfoData, GetSystemInfoResponse, GetVolumeData, GetVolumeResponse, HealthCheckVolumeData, HealthCheckVolumeResponse, ListBackupSchedulesData, ListBackupSchedulesResponse, ListFilesData, ListFilesResponse, ListNotificationDestinationsData, ListNotificationDestinationsResponse, ListRcloneRemotesData, ListRcloneRemotesResponse, ListRepositoriesData, ListRepositoriesResponse, ListSnapshotFilesData, ListSnapshotFilesResponse, ListSnapshotsData, ListSnapshotsResponse, ListVolumesData, ListVolumesResponse, LoginData, LoginResponse, LogoutData, LogoutResponse, MountVolumeData, MountVolumeResponse, RegisterData, RegisterResponse, RestoreSnapshotData, RestoreSnapshotResponse, RunBackupNowData, RunBackupNowResponse, RunForgetData, RunForgetResponse, StopBackupData, StopBackupResponse, TestConnectionData, TestConnectionResponse, TestNotificationDestinationData, TestNotificationDestinationResponse, UnmountVolumeData, UnmountVolumeResponse, UpdateBackupScheduleData, UpdateBackupScheduleResponse, UpdateNotificationDestinationData, UpdateNotificationDestinationResponse, UpdateRepositoryData, UpdateRepositoryResponse, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponse, UpdateVolumeData, UpdateVolumeResponse } from '../types.gen';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new user
|
* Register a new user
|
||||||
@@ -442,6 +442,23 @@ export const getRepositoryOptions = (options: Options<GetRepositoryData>) => que
|
|||||||
queryKey: getRepositoryQueryKey(options)
|
queryKey: getRepositoryQueryKey(options)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a repository's name or settings
|
||||||
|
*/
|
||||||
|
export const updateRepositoryMutation = (options?: Partial<Options<UpdateRepositoryData>>): UseMutationOptions<UpdateRepositoryResponse, DefaultError, Options<UpdateRepositoryData>> => {
|
||||||
|
const mutationOptions: UseMutationOptions<UpdateRepositoryResponse, DefaultError, Options<UpdateRepositoryData>> = {
|
||||||
|
mutationFn: async (fnOptions) => {
|
||||||
|
const { data } = await updateRepository({
|
||||||
|
...options,
|
||||||
|
...fnOptions,
|
||||||
|
throwOnError: true
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return mutationOptions;
|
||||||
|
};
|
||||||
|
|
||||||
export const listSnapshotsQueryKey = (options: Options<ListSnapshotsData>) => createQueryKey("listSnapshots", options);
|
export const listSnapshotsQueryKey = (options: Options<ListSnapshotsData>) => createQueryKey("listSnapshots", options);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { Client, Options as Options2, TDataShape } from './client';
|
import type { Client, Options as Options2, TDataShape } from './client';
|
||||||
import { client } from './client.gen';
|
import { client } from './client.gen';
|
||||||
import type { BrowseFilesystemData, BrowseFilesystemResponses, ChangePasswordData, ChangePasswordResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteVolumeData, DeleteVolumeResponses, DoctorRepositoryData, DoctorRepositoryResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetContainersUsingVolumeData, GetContainersUsingVolumeErrors, GetContainersUsingVolumeResponses, GetMeData, GetMeResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetRepositoryData, GetRepositoryResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, LoginData, LoginResponses, LogoutData, LogoutResponses, MountVolumeData, MountVolumeResponses, RegisterData, RegisterResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, StopBackupData, StopBackupErrors, StopBackupResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen';
|
import type { BrowseFilesystemData, BrowseFilesystemResponses, ChangePasswordData, ChangePasswordResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteVolumeData, DeleteVolumeResponses, DoctorRepositoryData, DoctorRepositoryResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetContainersUsingVolumeData, GetContainersUsingVolumeErrors, GetContainersUsingVolumeResponses, GetMeData, GetMeResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetRepositoryData, GetRepositoryResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, LoginData, LoginResponses, LogoutData, LogoutResponses, MountVolumeData, MountVolumeResponses, RegisterData, RegisterResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, StopBackupData, StopBackupErrors, StopBackupResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
||||||
/**
|
/**
|
||||||
@@ -276,6 +276,20 @@ export const getRepository = <ThrowOnError extends boolean = false>(options: Opt
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a repository's name or settings
|
||||||
|
*/
|
||||||
|
export const updateRepository = <ThrowOnError extends boolean = false>(options: Options<UpdateRepositoryData, ThrowOnError>) => {
|
||||||
|
return (options.client ?? client).patch<UpdateRepositoryResponses, UpdateRepositoryErrors, ThrowOnError>({
|
||||||
|
url: '/api/v1/repositories/{name}',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all snapshots in a repository
|
* List all snapshots in a repository
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export type ListVolumesResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -279,6 +280,7 @@ export type CreateVolumeResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -422,6 +424,7 @@ export type GetVolumeResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -465,6 +468,7 @@ export type UpdateVolumeData = {
|
|||||||
ssl?: boolean;
|
ssl?: boolean;
|
||||||
username?: string;
|
username?: string;
|
||||||
};
|
};
|
||||||
|
name?: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -522,6 +526,7 @@ export type UpdateVolumeResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -771,6 +776,7 @@ export type ListRepositoriesResponses = {
|
|||||||
lastChecked: number | null;
|
lastChecked: number | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'healthy' | 'unknown' | null;
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -985,6 +991,7 @@ export type GetRepositoryResponses = {
|
|||||||
lastChecked: number | null;
|
lastChecked: number | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'healthy' | 'unknown' | null;
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -993,6 +1000,110 @@ export type GetRepositoryResponses = {
|
|||||||
|
|
||||||
export type GetRepositoryResponse = GetRepositoryResponses[keyof GetRepositoryResponses];
|
export type GetRepositoryResponse = GetRepositoryResponses[keyof GetRepositoryResponses];
|
||||||
|
|
||||||
|
export type UpdateRepositoryData = {
|
||||||
|
body?: {
|
||||||
|
compressionMode?: 'auto' | 'better' | 'fastest' | 'max' | 'off';
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
path: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
query?: never;
|
||||||
|
url: '/api/v1/repositories/{name}';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateRepositoryErrors = {
|
||||||
|
/**
|
||||||
|
* Repository not found
|
||||||
|
*/
|
||||||
|
404: unknown;
|
||||||
|
/**
|
||||||
|
* Repository with this name already exists
|
||||||
|
*/
|
||||||
|
409: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateRepositoryResponses = {
|
||||||
|
/**
|
||||||
|
* Repository updated successfully
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
||||||
|
config: {
|
||||||
|
accessKeyId: string;
|
||||||
|
backend: 'r2';
|
||||||
|
bucket: string;
|
||||||
|
endpoint: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
} | {
|
||||||
|
accessKeyId: string;
|
||||||
|
backend: 's3';
|
||||||
|
bucket: string;
|
||||||
|
endpoint: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
} | {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: 'azure';
|
||||||
|
container: string;
|
||||||
|
customPassword?: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'gcs';
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'local';
|
||||||
|
name: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
path?: string;
|
||||||
|
} | {
|
||||||
|
backend: 'rclone';
|
||||||
|
path: string;
|
||||||
|
remote: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'rest';
|
||||||
|
url: string;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
password?: string;
|
||||||
|
path?: string;
|
||||||
|
username?: string;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
privateKey: string;
|
||||||
|
user: string;
|
||||||
|
port?: number;
|
||||||
|
customPassword?: string;
|
||||||
|
isExistingRepository?: boolean;
|
||||||
|
};
|
||||||
|
createdAt: number;
|
||||||
|
id: string;
|
||||||
|
lastChecked: number | null;
|
||||||
|
lastError: string | null;
|
||||||
|
name: string;
|
||||||
|
shortId: string;
|
||||||
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
|
updatedAt: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateRepositoryResponse = UpdateRepositoryResponses[keyof UpdateRepositoryResponses];
|
||||||
|
|
||||||
export type ListSnapshotsData = {
|
export type ListSnapshotsData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
@@ -1181,7 +1292,7 @@ export type ListBackupSchedulesResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
||||||
@@ -1251,6 +1362,7 @@ export type ListBackupSchedulesResponses = {
|
|||||||
lastChecked: number | null;
|
lastChecked: number | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'healthy' | 'unknown' | null;
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1304,6 +1416,7 @@ export type ListBackupSchedulesResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1351,7 +1464,7 @@ export type CreateBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
@@ -1412,7 +1525,7 @@ export type GetBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
||||||
@@ -1482,6 +1595,7 @@ export type GetBackupScheduleResponses = {
|
|||||||
lastChecked: number | null;
|
lastChecked: number | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'healthy' | 'unknown' | null;
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1535,6 +1649,7 @@ export type GetBackupScheduleResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1583,7 +1698,7 @@ export type UpdateBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
@@ -1624,7 +1739,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: 'error' | 'in_progress' | 'success' | null;
|
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
|
||||||
@@ -1694,6 +1809,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
lastChecked: number | null;
|
lastChecked: number | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'healthy' | 'unknown' | null;
|
status: 'error' | 'healthy' | 'unknown' | null;
|
||||||
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1747,6 +1863,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
lastHealthCheck: number;
|
lastHealthCheck: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
@@ -1859,13 +1976,15 @@ export type GetScheduleNotificationsResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -1873,6 +1992,7 @@ export type GetScheduleNotificationsResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -1940,13 +2060,15 @@ export type UpdateScheduleNotificationsResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -1954,6 +2076,7 @@ export type UpdateScheduleNotificationsResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2010,13 +2133,15 @@ export type ListNotificationDestinationsResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2024,6 +2149,7 @@ export type ListNotificationDestinationsResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2064,13 +2190,15 @@ export type CreateNotificationDestinationData = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2078,6 +2206,7 @@ export type CreateNotificationDestinationData = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2117,13 +2246,15 @@ export type CreateNotificationDestinationResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2131,6 +2262,7 @@ export type CreateNotificationDestinationResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2217,13 +2349,15 @@ export type GetNotificationDestinationResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2231,6 +2365,7 @@ export type GetNotificationDestinationResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2271,13 +2406,15 @@ export type UpdateNotificationDestinationData = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2285,6 +2422,7 @@ export type UpdateNotificationDestinationData = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
@@ -2334,13 +2472,15 @@ export type UpdateNotificationDestinationResponses = {
|
|||||||
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
priority: 'default' | 'high' | 'low' | 'max' | 'min';
|
||||||
topic: string;
|
topic: string;
|
||||||
type: 'ntfy';
|
type: 'ntfy';
|
||||||
|
password?: string;
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
token?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
priority: number;
|
priority: number;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
type: 'gotify';
|
type: 'gotify';
|
||||||
|
path?: string;
|
||||||
} | {
|
} | {
|
||||||
shoutrrrUrl: string;
|
shoutrrrUrl: string;
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
@@ -2348,6 +2488,7 @@ export type UpdateNotificationDestinationResponses = {
|
|||||||
type: 'discord';
|
type: 'discord';
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
threadId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} | {
|
} | {
|
||||||
type: 'slack';
|
type: 'slack';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { BackendType } from "~/schemas/volumes";
|
|||||||
|
|
||||||
type VolumeIconProps = {
|
type VolumeIconProps = {
|
||||||
backend: BackendType;
|
backend: BackendType;
|
||||||
size?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIconAndColor = (backend: BackendType) => {
|
const getIconAndColor = (backend: BackendType) => {
|
||||||
@@ -41,12 +40,12 @@ const getIconAndColor = (backend: BackendType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VolumeIcon = ({ backend, size = 10 }: VolumeIconProps) => {
|
export const VolumeIcon = ({ backend }: VolumeIconProps) => {
|
||||||
const { icon: Icon, label } = getIconAndColor(backend);
|
const { icon: Icon, label } = getIconAndColor(backend);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`flex items-center gap-2 rounded-md px-2 py-1`}>
|
<span className={`flex items-center gap-2 rounded-md px-2 py-1`}>
|
||||||
<Icon size={size} />
|
<Icon className="h-4 w-4" />
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -164,10 +164,20 @@ export const ScheduleSummary = (props: Props) => {
|
|||||||
{schedule.lastBackupStatus === "success" && "✓ Success"}
|
{schedule.lastBackupStatus === "success" && "✓ Success"}
|
||||||
{schedule.lastBackupStatus === "error" && "✗ Error"}
|
{schedule.lastBackupStatus === "error" && "✗ Error"}
|
||||||
{schedule.lastBackupStatus === "in_progress" && "⟳ in progress..."}
|
{schedule.lastBackupStatus === "in_progress" && "⟳ in progress..."}
|
||||||
|
{schedule.lastBackupStatus === "warning" && "! Warning"}
|
||||||
{!schedule.lastBackupStatus && "—"}
|
{!schedule.lastBackupStatus && "—"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{schedule.lastBackupStatus === "warning" && (
|
||||||
|
<div className="md:col-span-2 lg:col-span-4">
|
||||||
|
<p className="text-xs uppercase text-muted-foreground">Warning Details</p>
|
||||||
|
<p className="font-mono text-sm text-yellow-600 whitespace-pre-wrap break-all">
|
||||||
|
Last backup completed with warnings. Check your container logs for more details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{schedule.lastBackupError && (
|
{schedule.lastBackupError && (
|
||||||
<div className="md:col-span-2 lg:col-span-4">
|
<div className="md:col-span-2 lg:col-span-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Error Details</p>
|
<p className="text-xs uppercase text-muted-foreground">Error Details</p>
|
||||||
|
|||||||
@@ -370,6 +370,22 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="threadId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Thread ID (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
ID of the thread to post messages in. Leave empty to post in the main channel.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -423,6 +439,20 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="path"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Path (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="/custom/path" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Custom path on the Gotify server, if applicable.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -458,14 +488,28 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="token"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Access Token (Optional)</FormLabel>
|
<FormLabel>Username (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="username" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Username for server authentication, if required.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Password (Optional)</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} type="password" placeholder="••••••••" />
|
<Input {...field} type="password" placeholder="••••••••" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Required if the topic is protected.</FormDescription>
|
<FormDescription>Password for server authentication, if required.</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
|||||||
|
|
||||||
{volume.status[0].toUpperCase() + volume.status.slice(1)}
|
{volume.status[0].toUpperCase() + volume.status.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
<VolumeIcon size={14} backend={volume?.config.backend} />
|
<VolumeIcon backend={volume?.config.backend} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
services: {
|
services: {
|
||||||
nginx: {
|
nginx: {
|
||||||
image: "nginx:latest",
|
image: "nginx:latest",
|
||||||
volumes: [`im-${volume.name}:/path/in/container`],
|
volumes: [`zb-${volume.shortId}:/path/in/container`],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumes: {
|
volumes: {
|
||||||
[`im-${volume.name}`]: {
|
[`zb-${volume.shortId}`]: {
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dockerRunCommand = `docker run -v im-${volume.name}:/path/in/container nginx:latest`;
|
const dockerRunCommand = `docker run -v zb-${volume.shortId}:/path/in/container nginx:latest`;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: containersData,
|
data: containersData,
|
||||||
|
|||||||
7
app/drizzle/0012_add_short_ids.sql
Normal file
7
app/drizzle/0012_add_short_ids.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE `repositories_table` ADD `short_id` text;--> statement-breakpoint
|
||||||
|
UPDATE `repositories_table` SET `short_id` = lower(hex(randomblob(3))) WHERE `short_id` IS NULL;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `repositories_table_short_id_unique` ON `repositories_table` (`short_id`);--> statement-breakpoint
|
||||||
|
|
||||||
|
ALTER TABLE `volumes_table` ADD `short_id` text;--> statement-breakpoint
|
||||||
|
UPDATE `volumes_table` SET `short_id` = lower(hex(randomblob(3))) WHERE `short_id` IS NULL;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `volumes_table_short_id_unique` ON `volumes_table` (`short_id`);
|
||||||
6
app/drizzle/0013_elite_sprite.sql
Normal file
6
app/drizzle/0013_elite_sprite.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE `app_metadata` (
|
||||||
|
`key` text PRIMARY KEY NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||||
|
);
|
||||||
40
app/drizzle/0014_wild_echo.sql
Normal file
40
app/drizzle/0014_wild_echo.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||||
|
CREATE TABLE `__new_repositories_table` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`short_id` text,
|
||||||
|
`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()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) 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
|
||||||
|
PRAGMA foreign_keys=ON;--> 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_volumes_table` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`short_id` text,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`type` text NOT NULL,
|
||||||
|
`status` text DEFAULT 'unmounted' NOT NULL,
|
||||||
|
`last_error` text,
|
||||||
|
`last_health_check` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) 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`);
|
||||||
40
app/drizzle/0015_jazzy_sersi.sql
Normal file
40
app/drizzle/0015_jazzy_sersi.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
PRAGMA foreign_keys=OFF;--> 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()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) 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
|
||||||
|
PRAGMA foreign_keys=ON;--> 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_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()) NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) 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`);
|
||||||
648
app/drizzle/meta/0012_snapshot.json
Normal file
648
app/drizzle/meta/0012_snapshot.json
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "bbca8451-3894-4556-9824-c309b5105628",
|
||||||
|
"prevId": "67552135-fa49-478f-9333-107d3dbd7610",
|
||||||
|
"tables": {
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
654
app/drizzle/meta/0013_snapshot.json
Normal file
654
app/drizzle/meta/0013_snapshot.json
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "794bddf6-1978-46e4-88d5-051d76cfa2f6",
|
||||||
|
"prevId": "bbca8451-3894-4556-9824-c309b5105628",
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
688
app/drizzle/meta/0014_snapshot.json
Normal file
688
app/drizzle/meta/0014_snapshot.json
Normal file
@@ -0,0 +1,688 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "05309ea5-8ef2-4d63-b3d2-9842b2b4111b",
|
||||||
|
"prevId": "794bddf6-1978-46e4-88d5-051d76cfa2f6",
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": false,
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": false,
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
688
app/drizzle/meta/0015_snapshot.json
Normal file
688
app/drizzle/meta/0015_snapshot.json
Normal file
@@ -0,0 +1,688 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "e52fe10a-3f36-4b21-abef-c15990d28363",
|
||||||
|
"prevId": "05309ea5-8ef2-4d63-b3d2-9842b2b4111b",
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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())"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,34 @@
|
|||||||
"when": 1763644043601,
|
"when": 1763644043601,
|
||||||
"tag": "0011_familiar_stone_men",
|
"tag": "0011_familiar_stone_men",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1764100562084,
|
||||||
|
"tag": "0012_add_short_ids",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1764182159797,
|
||||||
|
"tag": "0013_elite_sprite",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1764182405089,
|
||||||
|
"tag": "0014_wild_echo",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1764182465287,
|
||||||
|
"tag": "0015_jazzy_sersi",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -36,12 +36,14 @@ export const discordNotificationConfigSchema = type({
|
|||||||
webhookUrl: "string",
|
webhookUrl: "string",
|
||||||
username: "string?",
|
username: "string?",
|
||||||
avatarUrl: "string?",
|
avatarUrl: "string?",
|
||||||
|
threadId: "string?",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gotifyNotificationConfigSchema = type({
|
export const gotifyNotificationConfigSchema = type({
|
||||||
type: "'gotify'",
|
type: "'gotify'",
|
||||||
serverUrl: "string",
|
serverUrl: "string",
|
||||||
token: "string",
|
token: "string",
|
||||||
|
path: "string?",
|
||||||
priority: "0 <= number <= 10",
|
priority: "0 <= number <= 10",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,8 +51,9 @@ export const ntfyNotificationConfigSchema = type({
|
|||||||
type: "'ntfy'",
|
type: "'ntfy'",
|
||||||
serverUrl: "string?",
|
serverUrl: "string?",
|
||||||
topic: "string",
|
topic: "string",
|
||||||
token: "string?",
|
|
||||||
priority: "'max' | 'high' | 'default' | 'low' | 'min'",
|
priority: "'max' | 'high' | 'default' | 'low' | 'min'",
|
||||||
|
username: "string?",
|
||||||
|
password: "string?",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pushoverNotificationConfigSchema = type({
|
export const pushoverNotificationConfigSchema = type({
|
||||||
@@ -80,6 +83,7 @@ export const NOTIFICATION_EVENTS = {
|
|||||||
start: "start",
|
start: "start",
|
||||||
success: "success",
|
success: "success",
|
||||||
failure: "failure",
|
failure: "failure",
|
||||||
|
warning: "warning",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type NotificationEvent = keyof typeof NOTIFICATION_EVENTS;
|
export type NotificationEvent = keyof typeof NOTIFICATION_EVENTS;
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ export const REPOSITORY_BASE = "/var/lib/zerobyte/repositories";
|
|||||||
export const DATABASE_URL = "/var/lib/zerobyte/data/ironmount.db";
|
export const DATABASE_URL = "/var/lib/zerobyte/data/ironmount.db";
|
||||||
export const RESTIC_PASS_FILE = "/var/lib/zerobyte/data/restic.pass";
|
export const RESTIC_PASS_FILE = "/var/lib/zerobyte/data/restic.pass";
|
||||||
export const SOCKET_PATH = "/run/docker/plugins/zerobyte.sock";
|
export const SOCKET_PATH = "/run/docker/plugins/zerobyte.sock";
|
||||||
|
|
||||||
|
export const REQUIRED_MIGRATIONS = ["v0.14.0"];
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface ServerEvents {
|
|||||||
scheduleId: number;
|
scheduleId: number;
|
||||||
volumeName: string;
|
volumeName: string;
|
||||||
repositoryName: string;
|
repositoryName: string;
|
||||||
status: "success" | "error" | "stopped";
|
status: "success" | "error" | "stopped" | "warning";
|
||||||
}) => void;
|
}) => void;
|
||||||
"volume:mounted": (data: { volumeName: string }) => void;
|
"volume:mounted": (data: { volumeName: string }) => void;
|
||||||
"volume:unmounted": (data: { volumeName: string }) => void;
|
"volume:unmounted": (data: { volumeName: string }) => void;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import fs from "node:fs/promises";
|
|||||||
await fs.mkdir(path.dirname(DATABASE_URL), { recursive: true });
|
await fs.mkdir(path.dirname(DATABASE_URL), { recursive: true });
|
||||||
|
|
||||||
const sqlite = new Database(DATABASE_URL);
|
const sqlite = new Database(DATABASE_URL);
|
||||||
sqlite.run("PRAGMA foreign_keys = ON;");
|
|
||||||
|
|
||||||
export const db = drizzle({ client: sqlite, schema });
|
export const db = drizzle({ client: sqlite, schema });
|
||||||
|
|
||||||
export const runDbMigrations = () => {
|
export const runDbMigrations = () => {
|
||||||
@@ -23,4 +21,6 @@ export const runDbMigrations = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
migrate(db, { migrationsFolder });
|
migrate(db, { migrationsFolder });
|
||||||
|
|
||||||
|
sqlite.run("PRAGMA foreign_keys = ON;");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { NotificationType, notificationConfigSchema } from "~/schemas/notif
|
|||||||
*/
|
*/
|
||||||
export const volumesTable = sqliteTable("volumes_table", {
|
export const volumesTable = sqliteTable("volumes_table", {
|
||||||
id: int().primaryKey({ autoIncrement: true }),
|
id: int().primaryKey({ autoIncrement: true }),
|
||||||
|
shortId: text("short_id").notNull().unique(),
|
||||||
name: text().notNull().unique(),
|
name: text().notNull().unique(),
|
||||||
type: text().$type<BackendType>().notNull(),
|
type: text().$type<BackendType>().notNull(),
|
||||||
status: text().$type<BackendStatus>().notNull().default("unmounted"),
|
status: text().$type<BackendStatus>().notNull().default("unmounted"),
|
||||||
@@ -48,6 +49,7 @@ export type Session = typeof sessionsTable.$inferSelect;
|
|||||||
*/
|
*/
|
||||||
export const repositoriesTable = sqliteTable("repositories_table", {
|
export const repositoriesTable = sqliteTable("repositories_table", {
|
||||||
id: text().primaryKey(),
|
id: text().primaryKey(),
|
||||||
|
shortId: text("short_id").notNull().unique(),
|
||||||
name: text().notNull().unique(),
|
name: text().notNull().unique(),
|
||||||
type: text().$type<RepositoryBackend>().notNull(),
|
type: text().$type<RepositoryBackend>().notNull(),
|
||||||
config: text("config", { mode: "json" }).$type<typeof repositoryConfigSchema.inferOut>().notNull(),
|
config: text("config", { mode: "json" }).$type<typeof repositoryConfigSchema.inferOut>().notNull(),
|
||||||
@@ -85,7 +87,7 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
|||||||
excludePatterns: text("exclude_patterns", { mode: "json" }).$type<string[]>().default([]),
|
excludePatterns: text("exclude_patterns", { mode: "json" }).$type<string[]>().default([]),
|
||||||
includePatterns: text("include_patterns", { mode: "json" }).$type<string[]>().default([]),
|
includePatterns: text("include_patterns", { mode: "json" }).$type<string[]>().default([]),
|
||||||
lastBackupAt: int("last_backup_at", { mode: "number" }),
|
lastBackupAt: int("last_backup_at", { mode: "number" }),
|
||||||
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress">(),
|
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress" | "warning">(),
|
||||||
lastBackupError: text("last_backup_error"),
|
lastBackupError: text("last_backup_error"),
|
||||||
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
||||||
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
||||||
@@ -151,3 +153,15 @@ export const backupScheduleNotificationRelations = relations(backupScheduleNotif
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
export type BackupScheduleNotification = typeof backupScheduleNotificationsTable.$inferSelect;
|
export type BackupScheduleNotification = typeof backupScheduleNotificationsTable.$inferSelect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App Metadata Table
|
||||||
|
* Used for storing key-value pairs like migration checkpoints
|
||||||
|
*/
|
||||||
|
export const appMetadataTable = sqliteTable("app_metadata", {
|
||||||
|
key: text().primaryKey(),
|
||||||
|
value: text().notNull(),
|
||||||
|
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
||||||
|
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
||||||
|
});
|
||||||
|
export type AppMetadata = typeof appMetadataTable.$inferSelect;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { authController } from "./modules/auth/auth.controller";
|
|||||||
import { requireAuth } from "./modules/auth/auth.middleware";
|
import { requireAuth } from "./modules/auth/auth.middleware";
|
||||||
import { driverController } from "./modules/driver/driver.controller";
|
import { driverController } from "./modules/driver/driver.controller";
|
||||||
import { startup } from "./modules/lifecycle/startup";
|
import { startup } from "./modules/lifecycle/startup";
|
||||||
|
import { migrateToShortIds } from "./modules/lifecycle/migration";
|
||||||
import { repositoriesController } from "./modules/repositories/repositories.controller";
|
import { repositoriesController } from "./modules/repositories/repositories.controller";
|
||||||
import { systemController } from "./modules/system/system.controller";
|
import { systemController } from "./modules/system/system.controller";
|
||||||
import { volumeController } from "./modules/volumes/volume.controller";
|
import { volumeController } from "./modules/volumes/volume.controller";
|
||||||
@@ -19,7 +20,8 @@ import { notificationsController } from "./modules/notifications/notifications.c
|
|||||||
import { handleServiceError } from "./utils/errors";
|
import { handleServiceError } from "./utils/errors";
|
||||||
import { logger } from "./utils/logger";
|
import { logger } from "./utils/logger";
|
||||||
import { shutdown } from "./modules/lifecycle/shutdown";
|
import { shutdown } from "./modules/lifecycle/shutdown";
|
||||||
import { SOCKET_PATH } from "./core/constants";
|
import { REQUIRED_MIGRATIONS, SOCKET_PATH } from "./core/constants";
|
||||||
|
import { validateRequiredMigrations } from "./modules/lifecycle/checkpoint";
|
||||||
|
|
||||||
export const generalDescriptor = (app: Hono) =>
|
export const generalDescriptor = (app: Hono) =>
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
@@ -68,6 +70,9 @@ app.onError((err, c) => {
|
|||||||
|
|
||||||
runDbMigrations();
|
runDbMigrations();
|
||||||
|
|
||||||
|
await migrateToShortIds();
|
||||||
|
await validateRequiredMigrations(REQUIRED_MIGRATIONS);
|
||||||
|
|
||||||
const { docker } = await getCapabilities();
|
const { docker } = await getCapabilities();
|
||||||
|
|
||||||
if (docker) {
|
if (docker) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const backupScheduleSchema = type({
|
|||||||
excludePatterns: "string[] | null",
|
excludePatterns: "string[] | null",
|
||||||
includePatterns: "string[] | null",
|
includePatterns: "string[] | null",
|
||||||
lastBackupAt: "number | null",
|
lastBackupAt: "number | null",
|
||||||
lastBackupStatus: "'success' | 'error' | 'in_progress' | null",
|
lastBackupStatus: "'success' | 'error' | 'in_progress' | 'warning' | null",
|
||||||
lastBackupError: "string | null",
|
lastBackupError: "string | null",
|
||||||
nextBackupAt: "number | null",
|
nextBackupAt: "number | null",
|
||||||
createdAt: "number",
|
createdAt: "number",
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
|||||||
backupOptions.include = schedule.includePatterns;
|
backupOptions.include = schedule.includePatterns;
|
||||||
}
|
}
|
||||||
|
|
||||||
await restic.backup(repository.config, volumePath, {
|
const { exitCode } = await restic.backup(repository.config, volumePath, {
|
||||||
...backupOptions,
|
...backupOptions,
|
||||||
compressionMode: repository.compressionMode ?? "auto",
|
compressionMode: repository.compressionMode ?? "auto",
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
@@ -258,24 +258,28 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
|||||||
.update(backupSchedulesTable)
|
.update(backupSchedulesTable)
|
||||||
.set({
|
.set({
|
||||||
lastBackupAt: Date.now(),
|
lastBackupAt: Date.now(),
|
||||||
lastBackupStatus: "success",
|
lastBackupStatus: exitCode === 0 ? "success" : "warning",
|
||||||
lastBackupError: null,
|
lastBackupError: null,
|
||||||
nextBackupAt: nextBackupAt,
|
nextBackupAt: nextBackupAt,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
})
|
})
|
||||||
.where(eq(backupSchedulesTable.id, scheduleId));
|
.where(eq(backupSchedulesTable.id, scheduleId));
|
||||||
|
|
||||||
logger.info(`Backup completed successfully for volume ${volume.name} to repository ${repository.name}`);
|
if (exitCode !== 0) {
|
||||||
|
logger.warn(`Backup completed with warnings for volume ${volume.name} to repository ${repository.name}`);
|
||||||
|
} else {
|
||||||
|
logger.info(`Backup completed successfully for volume ${volume.name} to repository ${repository.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
serverEvents.emit("backup:completed", {
|
serverEvents.emit("backup:completed", {
|
||||||
scheduleId,
|
scheduleId,
|
||||||
volumeName: volume.name,
|
volumeName: volume.name,
|
||||||
repositoryName: repository.name,
|
repositoryName: repository.name,
|
||||||
status: "success",
|
status: exitCode === 0 ? "success" : "warning",
|
||||||
});
|
});
|
||||||
|
|
||||||
notificationsService
|
notificationsService
|
||||||
.sendBackupNotification(scheduleId, "success", {
|
.sendBackupNotification(scheduleId, exitCode === 0 ? "success" : "warning", {
|
||||||
volumeName: volume.name,
|
volumeName: volume.name,
|
||||||
repositoryName: repository.name,
|
repositoryName: repository.name,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { volumeService } from "../volumes/volume.service";
|
import { volumeService } from "../volumes/volume.service";
|
||||||
import { getVolumePath } from "../volumes/helpers";
|
import { getVolumePath } from "../volumes/helpers";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../../db/db";
|
||||||
|
import { volumesTable } from "../../db/schema";
|
||||||
|
|
||||||
export const driverController = new Hono()
|
export const driverController = new Hono()
|
||||||
.post("/VolumeDriver.Capabilities", (c) => {
|
.post("/VolumeDriver.Capabilities", (c) => {
|
||||||
@@ -30,10 +33,18 @@ export const driverController = new Hono()
|
|||||||
return c.json({ Err: "Volume name is required" }, 400);
|
return c.json({ Err: "Volume name is required" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const volumeName = body.Name.replace(/^zb-/, "");
|
const shortId = body.Name.replace(/^zb-/, "");
|
||||||
|
|
||||||
|
const volume = await db.query.volumesTable.findFirst({
|
||||||
|
where: eq(volumesTable.shortId, shortId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!volume) {
|
||||||
|
return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
Mountpoint: getVolumePath(volumeName),
|
Mountpoint: getVolumePath(volume),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.post("/VolumeDriver.Unmount", (c) => {
|
.post("/VolumeDriver.Unmount", (c) => {
|
||||||
@@ -48,7 +59,15 @@ export const driverController = new Hono()
|
|||||||
return c.json({ Err: "Volume name is required" }, 400);
|
return c.json({ Err: "Volume name is required" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { volume } = await volumeService.getVolume(body.Name.replace(/^zb-/, ""));
|
const shortId = body.Name.replace(/^zb-/, "");
|
||||||
|
|
||||||
|
const volume = await db.query.volumesTable.findFirst({
|
||||||
|
where: eq(volumesTable.shortId, shortId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!volume) {
|
||||||
|
return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
Mountpoint: getVolumePath(volume),
|
Mountpoint: getVolumePath(volume),
|
||||||
@@ -61,11 +80,19 @@ export const driverController = new Hono()
|
|||||||
return c.json({ Err: "Volume name is required" }, 400);
|
return c.json({ Err: "Volume name is required" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { volume } = await volumeService.getVolume(body.Name.replace(/^zb-/, ""));
|
const shortId = body.Name.replace(/^zb-/, "");
|
||||||
|
|
||||||
|
const volume = await db.query.volumesTable.findFirst({
|
||||||
|
where: eq(volumesTable.shortId, shortId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!volume) {
|
||||||
|
return c.json({ Err: `Volume with shortId ${shortId} not found` }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
Volume: {
|
Volume: {
|
||||||
Name: `zb-${volume.name}`,
|
Name: `zb-${volume.shortId}`,
|
||||||
Mountpoint: getVolumePath(volume),
|
Mountpoint: getVolumePath(volume),
|
||||||
Status: {},
|
Status: {},
|
||||||
},
|
},
|
||||||
@@ -76,7 +103,7 @@ export const driverController = new Hono()
|
|||||||
const volumes = await volumeService.listVolumes();
|
const volumes = await volumeService.listVolumes();
|
||||||
|
|
||||||
const res = volumes.map((volume) => ({
|
const res = volumes.map((volume) => ({
|
||||||
Name: `zb-${volume.name}`,
|
Name: `zb-${volume.shortId}`,
|
||||||
Mountpoint: getVolumePath(volume),
|
Mountpoint: getVolumePath(volume),
|
||||||
Status: {},
|
Status: {},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const eventsController = new Hono().get("/", (c) => {
|
|||||||
scheduleId: number;
|
scheduleId: number;
|
||||||
volumeName: string;
|
volumeName: string;
|
||||||
repositoryName: string;
|
repositoryName: string;
|
||||||
status: "success" | "error" | "stopped";
|
status: "success" | "error" | "stopped" | "warning";
|
||||||
}) => {
|
}) => {
|
||||||
stream.writeSSE({
|
stream.writeSSE({
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
|
|||||||
89
app/server/modules/lifecycle/checkpoint.ts
Normal file
89
app/server/modules/lifecycle/checkpoint.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../../db/db";
|
||||||
|
import { appMetadataTable, usersTable } from "../../db/schema";
|
||||||
|
import { logger } from "../../utils/logger";
|
||||||
|
import { REQUIRED_MIGRATIONS } from "~/server/core/constants";
|
||||||
|
|
||||||
|
const MIGRATION_KEY_PREFIX = "migration:";
|
||||||
|
|
||||||
|
export const recordMigrationCheckpoint = async (version: string): Promise<void> => {
|
||||||
|
const key = `${MIGRATION_KEY_PREFIX}${version}`;
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.insert(appMetadataTable)
|
||||||
|
.values({
|
||||||
|
key,
|
||||||
|
value: JSON.stringify({ completedAt: new Date().toISOString() }),
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: appMetadataTable.key,
|
||||||
|
set: {
|
||||||
|
value: JSON.stringify({ completedAt: new Date().toISOString() }),
|
||||||
|
updatedAt: now,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Recorded migration checkpoint for ${version}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasMigrationCheckpoint = async (version: string): Promise<boolean> => {
|
||||||
|
const key = `${MIGRATION_KEY_PREFIX}${version}`;
|
||||||
|
const result = await db.query.appMetadataTable.findFirst({
|
||||||
|
where: eq(appMetadataTable.key, key),
|
||||||
|
});
|
||||||
|
return result !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateRequiredMigrations = async (requiredVersions: string[]): Promise<void> => {
|
||||||
|
const userCount = await db.select({ count: sql<number>`count(*)` }).from(usersTable);
|
||||||
|
const isFreshInstall = userCount[0]?.count === 0;
|
||||||
|
|
||||||
|
if (isFreshInstall) {
|
||||||
|
logger.info("Fresh installation detected, skipping migration checkpoint validation.");
|
||||||
|
|
||||||
|
for (const version of requiredVersions) {
|
||||||
|
const hasCheckpoint = await hasMigrationCheckpoint(version);
|
||||||
|
if (!hasCheckpoint) {
|
||||||
|
await recordMigrationCheckpoint(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const version of requiredVersions) {
|
||||||
|
const hasCheckpoint = await hasMigrationCheckpoint(version);
|
||||||
|
if (!hasCheckpoint) {
|
||||||
|
logger.error(`
|
||||||
|
================================================================================
|
||||||
|
MIGRATION ERROR: Required migration ${version} has not been run.
|
||||||
|
|
||||||
|
You are attempting to start a version of Zerobyte that requires migration
|
||||||
|
checkpoints from previous versions. This typically happens when you skip
|
||||||
|
versions during an upgrade.
|
||||||
|
|
||||||
|
To fix this:
|
||||||
|
1. First upgrade to version ${version} and run the application once
|
||||||
|
2. Validate that everything is still working correctly
|
||||||
|
3. Then upgrade to the current version
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMigrationCheckpoints = async (): Promise<{ version: string; completedAt: string }[]> => {
|
||||||
|
const results = await db.query.appMetadataTable.findMany({
|
||||||
|
where: (table, { like }) => like(table.key, `${MIGRATION_KEY_PREFIX}%`),
|
||||||
|
});
|
||||||
|
|
||||||
|
return results.map((r) => ({
|
||||||
|
version: r.key.replace(MIGRATION_KEY_PREFIX, ""),
|
||||||
|
completedAt: JSON.parse(r.value).completedAt,
|
||||||
|
}));
|
||||||
|
};
|
||||||
198
app/server/modules/lifecycle/migration.ts
Normal file
198
app/server/modules/lifecycle/migration.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import * as fs from "node:fs/promises";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../../db/db";
|
||||||
|
import { repositoriesTable } from "../../db/schema";
|
||||||
|
import { VOLUME_MOUNT_BASE, REPOSITORY_BASE } from "../../core/constants";
|
||||||
|
import { logger } from "../../utils/logger";
|
||||||
|
import { hasMigrationCheckpoint, recordMigrationCheckpoint } from "./checkpoint";
|
||||||
|
import type { RepositoryConfig } from "~/schemas/restic";
|
||||||
|
|
||||||
|
const MIGRATION_VERSION = "v0.14.0";
|
||||||
|
|
||||||
|
interface MigrationResult {
|
||||||
|
success: boolean;
|
||||||
|
errors: Array<{ name: string; error: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MigrationError extends Error {
|
||||||
|
version: string;
|
||||||
|
failedItems: Array<{ name: string; error: string }>;
|
||||||
|
|
||||||
|
constructor(version: string, failedItems: Array<{ name: string; error: string }>) {
|
||||||
|
const itemNames = failedItems.map((e) => e.name).join(", ");
|
||||||
|
super(`Migration ${version} failed for: ${itemNames}`);
|
||||||
|
this.version = version;
|
||||||
|
this.failedItems = failedItems;
|
||||||
|
this.name = "MigrationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const migrateToShortIds = async () => {
|
||||||
|
const alreadyMigrated = await hasMigrationCheckpoint(MIGRATION_VERSION);
|
||||||
|
if (alreadyMigrated) {
|
||||||
|
logger.debug(`Migration ${MIGRATION_VERSION} already completed, skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Starting short ID migration (${MIGRATION_VERSION})...`);
|
||||||
|
|
||||||
|
const volumeResult = await migrateVolumeFolders();
|
||||||
|
const repoResult = await migrateRepositoryFolders();
|
||||||
|
|
||||||
|
const allErrors = [...volumeResult.errors, ...repoResult.errors];
|
||||||
|
|
||||||
|
if (allErrors.length > 0) {
|
||||||
|
for (const err of allErrors) {
|
||||||
|
logger.error(`Migration failure - ${err.name}: ${err.error}`);
|
||||||
|
}
|
||||||
|
throw new MigrationError(MIGRATION_VERSION, allErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
await recordMigrationCheckpoint(MIGRATION_VERSION);
|
||||||
|
|
||||||
|
logger.info(`Short ID migration (${MIGRATION_VERSION}) complete.`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrateVolumeFolders = async (): Promise<MigrationResult> => {
|
||||||
|
const errors: Array<{ name: string; error: string }> = [];
|
||||||
|
const volumes = await db.query.volumesTable.findMany({});
|
||||||
|
|
||||||
|
for (const volume of volumes) {
|
||||||
|
if (volume.config.backend === "directory") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldPath = path.join(VOLUME_MOUNT_BASE, volume.name);
|
||||||
|
const newPath = path.join(VOLUME_MOUNT_BASE, volume.shortId);
|
||||||
|
|
||||||
|
const oldExists = await pathExists(oldPath);
|
||||||
|
const newExists = await pathExists(newPath);
|
||||||
|
|
||||||
|
if (oldExists && !newExists) {
|
||||||
|
try {
|
||||||
|
logger.info(`Migrating volume folder: ${oldPath} -> ${newPath}`);
|
||||||
|
await fs.rename(oldPath, newPath);
|
||||||
|
logger.info(`Successfully migrated volume folder for "${volume.name}"`);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
errors.push({ name: `volume:${volume.name}`, error: errorMessage });
|
||||||
|
}
|
||||||
|
} else if (oldExists && newExists) {
|
||||||
|
logger.warn(
|
||||||
|
`Both old (${oldPath}) and new (${newPath}) paths exist for volume "${volume.name}". Manual intervention may be required.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: errors.length === 0, errors };
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrateRepositoryFolders = async (): Promise<MigrationResult> => {
|
||||||
|
const errors: Array<{ name: string; error: string }> = [];
|
||||||
|
const repositories = await db.query.repositoriesTable.findMany({});
|
||||||
|
|
||||||
|
for (const repo of repositories) {
|
||||||
|
if (repo.config.backend !== "local") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = repo.config as Extract<RepositoryConfig, { backend: "local" }>;
|
||||||
|
|
||||||
|
if (config.isExistingRepository) {
|
||||||
|
logger.debug(`Skipping imported repository "${repo.name}" - folder path is user-defined`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.name === repo.shortId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePath = config.path || REPOSITORY_BASE;
|
||||||
|
const oldPath = path.join(basePath, config.name);
|
||||||
|
const newPath = path.join(basePath, repo.shortId);
|
||||||
|
|
||||||
|
const oldExists = await pathExists(oldPath);
|
||||||
|
const newExists = await pathExists(newPath);
|
||||||
|
|
||||||
|
if (oldExists && !newExists) {
|
||||||
|
try {
|
||||||
|
logger.info(`Migrating repository folder: ${oldPath} -> ${newPath}`);
|
||||||
|
await fs.rename(oldPath, newPath);
|
||||||
|
|
||||||
|
const updatedConfig: RepositoryConfig = {
|
||||||
|
...config,
|
||||||
|
name: repo.shortId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(repositoriesTable)
|
||||||
|
.set({
|
||||||
|
config: updatedConfig,
|
||||||
|
updatedAt: Math.floor(Date.now() / 1000),
|
||||||
|
})
|
||||||
|
.where(eq(repositoriesTable.id, repo.id));
|
||||||
|
|
||||||
|
logger.info(`Successfully migrated repository folder and config for "${repo.name}"`);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
errors.push({ name: `repository:${repo.name}`, error: errorMessage });
|
||||||
|
}
|
||||||
|
} else if (oldExists && newExists) {
|
||||||
|
logger.warn(
|
||||||
|
`Both old (${oldPath}) and new (${newPath}) paths exist for repository "${repo.name}". Manual intervention may be required.`,
|
||||||
|
);
|
||||||
|
} else if (!oldExists && !newExists) {
|
||||||
|
try {
|
||||||
|
logger.info(`Updating config.name for repository "${repo.name}" (no folder exists yet)`);
|
||||||
|
|
||||||
|
const updatedConfig: RepositoryConfig = {
|
||||||
|
...config,
|
||||||
|
name: repo.shortId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(repositoriesTable)
|
||||||
|
.set({
|
||||||
|
config: updatedConfig,
|
||||||
|
updatedAt: Math.floor(Date.now() / 1000),
|
||||||
|
})
|
||||||
|
.where(eq(repositoriesTable.id, repo.id));
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
errors.push({ name: `repository:${repo.name}`, error: errorMessage });
|
||||||
|
}
|
||||||
|
} else if (newExists && !oldExists && config.name !== repo.shortId) {
|
||||||
|
try {
|
||||||
|
logger.info(`Folder already at new path, updating config.name for repository "${repo.name}"`);
|
||||||
|
|
||||||
|
const updatedConfig: RepositoryConfig = {
|
||||||
|
...config,
|
||||||
|
name: repo.shortId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(repositoriesTable)
|
||||||
|
.set({
|
||||||
|
config: updatedConfig,
|
||||||
|
updatedAt: Math.floor(Date.now() / 1000),
|
||||||
|
})
|
||||||
|
.where(eq(repositoriesTable.id, repo.id));
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
errors.push({ name: `repository:${repo.name}`, error: errorMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: errors.length === 0, errors };
|
||||||
|
};
|
||||||
|
|
||||||
|
const pathExists = async (p: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await fs.access(p);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -17,7 +17,10 @@ export function buildDiscordShoutrrrUrl(config: Extract<NotificationConfig, { ty
|
|||||||
params.append("username", config.username);
|
params.append("username", config.username);
|
||||||
}
|
}
|
||||||
if (config.avatarUrl) {
|
if (config.avatarUrl) {
|
||||||
params.append("avatar_url", config.avatarUrl);
|
params.append("avatarurl", config.avatarUrl);
|
||||||
|
}
|
||||||
|
if (config.threadId) {
|
||||||
|
params.append("thread_id", config.threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.toString()) {
|
if (params.toString()) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { NotificationConfig } from "~/schemas/notifications";
|
import type { NotificationConfig } from "~/schemas/notifications";
|
||||||
|
|
||||||
export function buildEmailShoutrrrUrl(config: Extract<NotificationConfig, { type: "email" }>): string {
|
export function buildEmailShoutrrrUrl(config: Extract<NotificationConfig, { type: "email" }>): string {
|
||||||
const protocol = config.useTLS ? "smtps" : "smtp";
|
|
||||||
const auth = `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}`;
|
const auth = `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}`;
|
||||||
const host = `${config.smtpHost}:${config.smtpPort}`;
|
const host = `${config.smtpHost}:${config.smtpPort}`;
|
||||||
const toRecipients = config.to.map((email) => encodeURIComponent(email)).join(",");
|
const toRecipients = config.to.map((email) => encodeURIComponent(email)).join(",");
|
||||||
|
const useStartTLS = config.useTLS ? "yes" : "no";
|
||||||
|
|
||||||
return `${protocol}://${auth}@${host}/?from=${encodeURIComponent(config.from)}&to=${toRecipients}`;
|
return `smtp://${auth}@${host}/?from=${encodeURIComponent(config.from)}&to=${toRecipients}&starttls=${useStartTLS}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ export function buildGotifyShoutrrrUrl(config: Extract<NotificationConfig, { typ
|
|||||||
const url = new URL(config.serverUrl);
|
const url = new URL(config.serverUrl);
|
||||||
const hostname = url.hostname;
|
const hostname = url.hostname;
|
||||||
const port = url.port ? `:${url.port}` : "";
|
const port = url.port ? `:${url.port}` : "";
|
||||||
|
const path = config.path ? `/${config.path.replace(/^\/+|\/+$/g, "")}` : "";
|
||||||
|
|
||||||
let shoutrrrUrl = `gotify://${hostname}${port}/${config.token}`;
|
let shoutrrrUrl = `gotify://${hostname}${port}${path}/${config.token}`;
|
||||||
|
|
||||||
if (config.priority !== undefined) {
|
if (config.priority !== undefined) {
|
||||||
shoutrrrUrl += `?priority=${config.priority}`;
|
shoutrrrUrl += `?priority=${config.priority}`;
|
||||||
|
|||||||
@@ -3,19 +3,26 @@ import type { NotificationConfig } from "~/schemas/notifications";
|
|||||||
export function buildNtfyShoutrrrUrl(config: Extract<NotificationConfig, { type: "ntfy" }>): string {
|
export function buildNtfyShoutrrrUrl(config: Extract<NotificationConfig, { type: "ntfy" }>): string {
|
||||||
let shoutrrrUrl: string;
|
let shoutrrrUrl: string;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
const auth =
|
||||||
|
config.username && config.password
|
||||||
|
? `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@`
|
||||||
|
: "";
|
||||||
|
|
||||||
if (config.serverUrl) {
|
if (config.serverUrl) {
|
||||||
const url = new URL(config.serverUrl);
|
const url = new URL(config.serverUrl);
|
||||||
const hostname = url.hostname;
|
const hostname = url.hostname;
|
||||||
const port = url.port ? `:${url.port}` : "";
|
const port = url.port ? `:${url.port}` : "";
|
||||||
shoutrrrUrl = `ntfy://${hostname}${port}/${config.topic}`;
|
const scheme = url.protocol === "https:" ? "https" : "http";
|
||||||
|
|
||||||
|
params.append("scheme", scheme);
|
||||||
|
|
||||||
|
shoutrrrUrl = `ntfy://${auth}${hostname}${port}/${config.topic}`;
|
||||||
} else {
|
} else {
|
||||||
shoutrrrUrl = `ntfy://ntfy.sh/${config.topic}`;
|
shoutrrrUrl = `ntfy://${auth}ntfy.sh/${config.topic}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
if (config.token) {
|
|
||||||
params.append("token", config.token);
|
|
||||||
}
|
|
||||||
if (config.priority) {
|
if (config.priority) {
|
||||||
params.append("priority", config.priority);
|
params.append("priority", config.priority);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { NotificationConfig } from "~/schemas/notifications";
|
import type { NotificationConfig } from "~/schemas/notifications";
|
||||||
|
|
||||||
export function buildPushoverShoutrrrUrl(
|
export function buildPushoverShoutrrrUrl(config: Extract<NotificationConfig, { type: "pushover" }>): string {
|
||||||
config: Extract<NotificationConfig, { type: "pushover" }>,
|
|
||||||
): string {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (config.devices) {
|
if (config.devices) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function buildSlackShoutrrrUrl(config: Extract<NotificationConfig, { type
|
|||||||
params.append("username", config.username);
|
params.append("username", config.username);
|
||||||
}
|
}
|
||||||
if (config.iconEmoji) {
|
if (config.iconEmoji) {
|
||||||
params.append("icon", config.iconEmoji);
|
params.append("icon_emoji", config.iconEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.toString()) {
|
if (params.toString()) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
@@ -58,7 +58,7 @@ async function encryptSensitiveFields(config: NotificationConfig): Promise<Notif
|
|||||||
case "ntfy":
|
case "ntfy":
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
token: config.token ? await cryptoUtils.encrypt(config.token) : undefined,
|
password: config.password ? await cryptoUtils.encrypt(config.password) : undefined,
|
||||||
};
|
};
|
||||||
case "pushover":
|
case "pushover":
|
||||||
return {
|
return {
|
||||||
@@ -100,7 +100,7 @@ async function decryptSensitiveFields(config: NotificationConfig): Promise<Notif
|
|||||||
case "ntfy":
|
case "ntfy":
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
token: config.token ? await cryptoUtils.decrypt(config.token) : undefined,
|
password: config.password ? await cryptoUtils.decrypt(config.password) : undefined,
|
||||||
};
|
};
|
||||||
case "pushover":
|
case "pushover":
|
||||||
return {
|
return {
|
||||||
@@ -164,10 +164,10 @@ const updateDestination = async (
|
|||||||
const slug = slugify(updates.name, { lower: true, strict: true });
|
const slug = slugify(updates.name, { lower: true, strict: true });
|
||||||
|
|
||||||
const conflict = await db.query.notificationDestinationsTable.findFirst({
|
const conflict = await db.query.notificationDestinationsTable.findFirst({
|
||||||
where: and(eq(notificationDestinationsTable.name, slug), eq(notificationDestinationsTable.id, id)),
|
where: and(eq(notificationDestinationsTable.name, slug), ne(notificationDestinationsTable.id, id)),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (conflict && conflict.id !== id) {
|
if (conflict) {
|
||||||
throw new ConflictError("Notification destination with this name already exists");
|
throw new ConflictError("Notification destination with this name already exists");
|
||||||
}
|
}
|
||||||
updateData.name = slug;
|
updateData.name = slug;
|
||||||
@@ -291,6 +291,7 @@ const sendBackupNotification = async (
|
|||||||
case "success":
|
case "success":
|
||||||
return assignment.notifyOnSuccess;
|
return assignment.notifyOnSuccess;
|
||||||
case "failure":
|
case "failure":
|
||||||
|
case "warning":
|
||||||
return assignment.notifyOnFailure;
|
return assignment.notifyOnFailure;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -367,7 +368,7 @@ function buildNotificationMessage(
|
|||||||
|
|
||||||
case "success":
|
case "success":
|
||||||
return {
|
return {
|
||||||
title: "✅ Backup Completed Successfully",
|
title: "✅ Backup Completed successfully",
|
||||||
body: [
|
body: [
|
||||||
`Volume: ${context.volumeName}`,
|
`Volume: ${context.volumeName}`,
|
||||||
`Repository: ${context.repositoryName}`,
|
`Repository: ${context.repositoryName}`,
|
||||||
@@ -381,9 +382,26 @@ function buildNotificationMessage(
|
|||||||
.join("\n"),
|
.join("\n"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case "warning":
|
||||||
|
return {
|
||||||
|
title: "! Backup completed with warnings",
|
||||||
|
body: [
|
||||||
|
`Volume: ${context.volumeName}`,
|
||||||
|
`Repository: ${context.repositoryName}`,
|
||||||
|
context.duration ? `Duration: ${Math.round(context.duration / 1000)}s` : null,
|
||||||
|
context.filesProcessed !== undefined ? `Files: ${context.filesProcessed}` : null,
|
||||||
|
context.bytesProcessed ? `Size: ${context.bytesProcessed}` : null,
|
||||||
|
context.snapshotId ? `Snapshot: ${context.snapshotId}` : null,
|
||||||
|
context.error ? `Warning: ${context.error}` : null,
|
||||||
|
`Time: ${date} - ${time}`,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n"),
|
||||||
|
};
|
||||||
|
|
||||||
case "failure":
|
case "failure":
|
||||||
return {
|
return {
|
||||||
title: "❌ Backup Failed",
|
title: "❌ Backup failed",
|
||||||
body: [
|
body: [
|
||||||
`Volume: ${context.volumeName}`,
|
`Volume: ${context.volumeName}`,
|
||||||
`Repository: ${context.repositoryName}`,
|
`Repository: ${context.repositoryName}`,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import {
|
|||||||
listSnapshotsFilters,
|
listSnapshotsFilters,
|
||||||
restoreSnapshotBody,
|
restoreSnapshotBody,
|
||||||
restoreSnapshotDto,
|
restoreSnapshotDto,
|
||||||
|
updateRepositoryBody,
|
||||||
|
updateRepositoryDto,
|
||||||
type DeleteRepositoryDto,
|
type DeleteRepositoryDto,
|
||||||
type DeleteSnapshotDto,
|
type DeleteSnapshotDto,
|
||||||
type DoctorRepositoryDto,
|
type DoctorRepositoryDto,
|
||||||
@@ -25,6 +27,7 @@ import {
|
|||||||
type ListSnapshotFilesDto,
|
type ListSnapshotFilesDto,
|
||||||
type ListSnapshotsDto,
|
type ListSnapshotsDto,
|
||||||
type RestoreSnapshotDto,
|
type RestoreSnapshotDto,
|
||||||
|
type UpdateRepositoryDto,
|
||||||
} from "./repositories.dto";
|
} from "./repositories.dto";
|
||||||
import { repositoriesService } from "./repositories.service";
|
import { repositoriesService } from "./repositories.service";
|
||||||
import { getRcloneRemoteInfo, listRcloneRemotes } from "../../utils/rclone";
|
import { getRcloneRemoteInfo, listRcloneRemotes } from "../../utils/rclone";
|
||||||
@@ -152,4 +155,12 @@ export const repositoriesController = new Hono()
|
|||||||
await repositoriesService.deleteSnapshot(name, snapshotId);
|
await repositoriesService.deleteSnapshot(name, snapshotId);
|
||||||
|
|
||||||
return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200);
|
return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200);
|
||||||
|
})
|
||||||
|
.patch("/:name", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => {
|
||||||
|
const { name } = c.req.param();
|
||||||
|
const body = c.req.valid("json");
|
||||||
|
|
||||||
|
const res = await repositoriesService.updateRepository(name, body);
|
||||||
|
|
||||||
|
return c.json<UpdateRepositoryDto>(res.repository, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { COMPRESSION_MODES, REPOSITORY_BACKENDS, REPOSITORY_STATUS, repositoryCo
|
|||||||
|
|
||||||
export const repositorySchema = type({
|
export const repositorySchema = type({
|
||||||
id: "string",
|
id: "string",
|
||||||
|
shortId: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
type: type.valueOf(REPOSITORY_BACKENDS),
|
type: type.valueOf(REPOSITORY_BACKENDS),
|
||||||
config: repositoryConfigSchema,
|
config: repositoryConfigSchema,
|
||||||
@@ -123,6 +124,41 @@ export const deleteRepositoryDto = describeRoute({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a repository
|
||||||
|
*/
|
||||||
|
export const updateRepositoryBody = type({
|
||||||
|
name: "string?",
|
||||||
|
compressionMode: type.valueOf(COMPRESSION_MODES).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateRepositoryBody = typeof updateRepositoryBody.infer;
|
||||||
|
|
||||||
|
export const updateRepositoryResponse = repositorySchema;
|
||||||
|
export type UpdateRepositoryDto = typeof updateRepositoryResponse.infer;
|
||||||
|
|
||||||
|
export const updateRepositoryDto = describeRoute({
|
||||||
|
description: "Update a repository's name or settings",
|
||||||
|
tags: ["Repositories"],
|
||||||
|
operationId: "updateRepository",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Repository updated successfully",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(updateRepositoryResponse),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
description: "Repository not found",
|
||||||
|
},
|
||||||
|
409: {
|
||||||
|
description: "Repository with this name already exists",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List snapshots in a repository
|
* List snapshots in a repository
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import { eq } from "drizzle-orm";
|
import { and, eq, ne } from "drizzle-orm";
|
||||||
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { repositoriesTable } from "../../db/schema";
|
import { repositoriesTable } from "../../db/schema";
|
||||||
import { toMessage } from "../../utils/errors";
|
import { toMessage } from "../../utils/errors";
|
||||||
|
import { generateShortId } from "../../utils/id";
|
||||||
import { restic } from "../../utils/restic";
|
import { restic } from "../../utils/restic";
|
||||||
import { cryptoUtils } from "../../utils/crypto";
|
import { cryptoUtils } from "../../utils/crypto";
|
||||||
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
||||||
@@ -61,13 +62,20 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
|
const shortId = generateShortId();
|
||||||
|
|
||||||
const encryptedConfig = await encryptConfig(config);
|
let processedConfig = config;
|
||||||
|
if (config.backend === "local") {
|
||||||
|
processedConfig = { ...config, name: shortId };
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedConfig = await encryptConfig(processedConfig);
|
||||||
|
|
||||||
const [created] = await db
|
const [created] = await db
|
||||||
.insert(repositoriesTable)
|
.insert(repositoriesTable)
|
||||||
.values({
|
.values({
|
||||||
id,
|
id,
|
||||||
|
shortId,
|
||||||
name: slug,
|
name: slug,
|
||||||
type: config.backend,
|
type: config.backend,
|
||||||
config: encryptedConfig,
|
config: encryptedConfig,
|
||||||
@@ -350,11 +358,62 @@ const deleteSnapshot = async (name: string, snapshotId: string) => {
|
|||||||
await restic.deleteSnapshot(repository.config, snapshotId);
|
await restic.deleteSnapshot(repository.config, snapshotId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateRepository = async (name: string, updates: { name?: string; compressionMode?: CompressionMode }) => {
|
||||||
|
const existing = await db.query.repositoriesTable.findFirst({
|
||||||
|
where: eq(repositoriesTable.name, name),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundError("Repository not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
updates.name !== undefined &&
|
||||||
|
updates.name !== existing.name &&
|
||||||
|
existing.config.backend === "local" &&
|
||||||
|
existing.config.isExistingRepository
|
||||||
|
) {
|
||||||
|
throw new ConflictError("Cannot rename an imported local repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
let newName = existing.name;
|
||||||
|
if (updates.name !== undefined && updates.name !== existing.name) {
|
||||||
|
const newSlug = slugify(updates.name, { lower: true, strict: true });
|
||||||
|
|
||||||
|
const conflict = await db.query.repositoriesTable.findFirst({
|
||||||
|
where: and(eq(repositoriesTable.name, newSlug), ne(repositoriesTable.id, existing.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (conflict) {
|
||||||
|
throw new ConflictError("A repository with this name already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
newName = newSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updated] = await db
|
||||||
|
.update(repositoriesTable)
|
||||||
|
.set({
|
||||||
|
name: newName,
|
||||||
|
compressionMode: updates.compressionMode ?? existing.compressionMode,
|
||||||
|
updatedAt: Math.floor(Date.now() / 1000),
|
||||||
|
})
|
||||||
|
.where(eq(repositoriesTable.id, existing.id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
throw new InternalServerError("Failed to update repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { repository: updated };
|
||||||
|
};
|
||||||
|
|
||||||
export const repositoriesService = {
|
export const repositoriesService = {
|
||||||
listRepositories,
|
listRepositories,
|
||||||
createRepository,
|
createRepository,
|
||||||
getRepository,
|
getRepository,
|
||||||
deleteRepository,
|
deleteRepository,
|
||||||
|
updateRepository,
|
||||||
listSnapshots,
|
listSnapshots,
|
||||||
listSnapshotFiles,
|
listSnapshotFiles,
|
||||||
restoreSnapshot,
|
restoreSnapshot,
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ export const getVolumePath = (volume: Volume) => {
|
|||||||
return volume.config.path;
|
return volume.config.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${VOLUME_MOUNT_BASE}/${volume.name}/_data`;
|
return `${VOLUME_MOUNT_BASE}/${volume.shortId}/_data`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { BACKEND_STATUS, BACKEND_TYPES, volumeConfigSchema } from "~/schemas/vol
|
|||||||
|
|
||||||
export const volumeSchema = type({
|
export const volumeSchema = type({
|
||||||
id: "number",
|
id: "number",
|
||||||
|
shortId: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
type: type.valueOf(BACKEND_TYPES),
|
type: type.valueOf(BACKEND_TYPES),
|
||||||
status: type.valueOf(BACKEND_STATUS),
|
status: type.valueOf(BACKEND_STATUS),
|
||||||
@@ -128,6 +129,7 @@ export const getVolumeDto = describeRoute({
|
|||||||
* Update a volume
|
* Update a volume
|
||||||
*/
|
*/
|
||||||
export const updateVolumeBody = type({
|
export const updateVolumeBody = type({
|
||||||
|
name: "string?",
|
||||||
autoRemount: "boolean?",
|
autoRemount: "boolean?",
|
||||||
config: volumeConfigSchema.optional(),
|
config: volumeConfigSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import * as fs from "node:fs/promises";
|
|||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import { eq } from "drizzle-orm";
|
import { and, eq, ne } from "drizzle-orm";
|
||||||
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
import { getCapabilities } from "../../core/capabilities";
|
import { getCapabilities } from "../../core/capabilities";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { volumesTable } from "../../db/schema";
|
import { volumesTable } from "../../db/schema";
|
||||||
import { toMessage } from "../../utils/errors";
|
import { toMessage } from "../../utils/errors";
|
||||||
|
import { generateShortId } from "../../utils/id";
|
||||||
import { getStatFs, type StatFs } from "../../utils/mountinfo";
|
import { getStatFs, type StatFs } from "../../utils/mountinfo";
|
||||||
import { withTimeout } from "../../utils/timeout";
|
import { withTimeout } from "../../utils/timeout";
|
||||||
import { createVolumeBackend } from "../backends/backend";
|
import { createVolumeBackend } from "../backends/backend";
|
||||||
@@ -35,9 +36,12 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
|
|||||||
throw new ConflictError("Volume already exists");
|
throw new ConflictError("Volume already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shortId = generateShortId();
|
||||||
|
|
||||||
const [created] = await db
|
const [created] = await db
|
||||||
.insert(volumesTable)
|
.insert(volumesTable)
|
||||||
.values({
|
.values({
|
||||||
|
shortId,
|
||||||
name: slug,
|
name: slug,
|
||||||
config: backendConfig,
|
config: backendConfig,
|
||||||
type: backendConfig.backend,
|
type: backendConfig.backend,
|
||||||
@@ -147,6 +151,21 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
|
|||||||
throw new NotFoundError("Volume not found");
|
throw new NotFoundError("Volume not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newName = existing.name;
|
||||||
|
if (volumeData.name !== undefined && volumeData.name !== existing.name) {
|
||||||
|
const newSlug = slugify(volumeData.name, { lower: true, strict: true });
|
||||||
|
|
||||||
|
const conflict = await db.query.volumesTable.findFirst({
|
||||||
|
where: and(eq(volumesTable.name, newSlug), ne(volumesTable.id, existing.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (conflict) {
|
||||||
|
throw new ConflictError("A volume with this name already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
newName = newSlug;
|
||||||
|
}
|
||||||
|
|
||||||
const configChanged =
|
const configChanged =
|
||||||
JSON.stringify(existing.config) !== JSON.stringify(volumeData.config) && volumeData.config !== undefined;
|
JSON.stringify(existing.config) !== JSON.stringify(volumeData.config) && volumeData.config !== undefined;
|
||||||
|
|
||||||
@@ -159,12 +178,13 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
|
|||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(volumesTable)
|
.update(volumesTable)
|
||||||
.set({
|
.set({
|
||||||
|
name: newName,
|
||||||
config: volumeData.config,
|
config: volumeData.config,
|
||||||
type: volumeData.config?.backend,
|
type: volumeData.config?.backend,
|
||||||
autoRemount: volumeData.autoRemount,
|
autoRemount: volumeData.autoRemount,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
})
|
})
|
||||||
.where(eq(volumesTable.name, name))
|
.where(eq(volumesTable.id, existing.id))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
@@ -177,9 +197,9 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
|
|||||||
await db
|
await db
|
||||||
.update(volumesTable)
|
.update(volumesTable)
|
||||||
.set({ status, lastError: error ?? null, lastHealthCheck: Date.now() })
|
.set({ status, lastError: error ?? null, lastHealthCheck: Date.now() })
|
||||||
.where(eq(volumesTable.name, name));
|
.where(eq(volumesTable.id, existing.id));
|
||||||
|
|
||||||
serverEvents.emit("volume:updated", { volumeName: name });
|
serverEvents.emit("volume:updated", { volumeName: updated.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { volume: updated };
|
return { volume: updated };
|
||||||
@@ -190,6 +210,7 @@ const testConnection = async (backendConfig: BackendConfig) => {
|
|||||||
|
|
||||||
const mockVolume = {
|
const mockVolume = {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
shortId: "test",
|
||||||
name: "test-connection",
|
name: "test-connection",
|
||||||
path: tempDir,
|
path: tempDir,
|
||||||
config: backendConfig,
|
config: backendConfig,
|
||||||
@@ -264,7 +285,7 @@ const getContainersUsingVolume = async (name: string) => {
|
|||||||
const container = docker.getContainer(info.Id);
|
const container = docker.getContainer(info.Id);
|
||||||
const inspect = await container.inspect();
|
const inspect = await container.inspect();
|
||||||
const mounts = inspect.Mounts || [];
|
const mounts = inspect.Mounts || [];
|
||||||
const usesVolume = mounts.some((mount) => mount.Type === "volume" && mount.Name === `im-${volume.name}`);
|
const usesVolume = mounts.some((mount) => mount.Type === "volume" && mount.Name === `zb-${volume.shortId}`);
|
||||||
if (usesVolume) {
|
if (usesVolume) {
|
||||||
usingContainers.push({
|
usingContainers.push({
|
||||||
id: inspect.Id,
|
id: inspect.Id,
|
||||||
|
|||||||
@@ -17,3 +17,25 @@ export const toMessage = (err: unknown): string => {
|
|||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
return sanitizeSensitiveData(message);
|
return sanitizeSensitiveData(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resticErrorCodes: Record<number, string> = {
|
||||||
|
1: "Command failed: An error occurred while executing the command.",
|
||||||
|
2: "Go runtime error: A runtime error occurred in the Go program.",
|
||||||
|
3: "Backup could not read all files: Some files could not be read during backup.",
|
||||||
|
10: "Repository not found: The specified repository could not be found.",
|
||||||
|
11: "Failed to lock repository: Unable to acquire a lock on the repository. Try to run doctor on the repository.",
|
||||||
|
12: "Wrong repository password: The provided password for the repository is incorrect.",
|
||||||
|
130: "Backup interrupted: The backup process was interrupted.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ResticError extends Error {
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
constructor(code: number, stderr: string) {
|
||||||
|
const message = resticErrorCodes[code] || `Unknown restic error with code ${code}`;
|
||||||
|
super(`${message}\n${stderr}`);
|
||||||
|
|
||||||
|
this.code = code;
|
||||||
|
this.name = "ResticError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
app/server/utils/id.ts
Normal file
6
app/server/utils/id.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
export const generateShortId = (length = 5): string => {
|
||||||
|
const bytesNeeded = Math.ceil((length * 3) / 4);
|
||||||
|
return crypto.randomBytes(bytesNeeded).toString("base64url").slice(0, length);
|
||||||
|
};
|
||||||
@@ -10,6 +10,7 @@ import { cryptoUtils } from "./crypto";
|
|||||||
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
||||||
import { safeSpawn } from "./spawn";
|
import { safeSpawn } from "./spawn";
|
||||||
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
||||||
|
import { ResticError } from "./errors";
|
||||||
|
|
||||||
const backupOutputSchema = type({
|
const backupOutputSchema = type({
|
||||||
message_type: "'summary'",
|
message_type: "'summary'",
|
||||||
@@ -313,34 +314,41 @@ const backup = async (
|
|||||||
streamProgress(data);
|
streamProgress(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStderr: (error) => {
|
|
||||||
logger.error(error.trim());
|
|
||||||
},
|
|
||||||
finally: async () => {
|
finally: async () => {
|
||||||
includeFile && (await fs.unlink(includeFile).catch(() => {}));
|
includeFile && (await fs.unlink(includeFile).catch(() => {}));
|
||||||
await cleanupTemporaryKeys(config, env);
|
await cleanupTemporaryKeys(config, env);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode === 3) {
|
||||||
logger.error(`Restic backup failed: ${res.stderr}`);
|
logger.error(`Restic backup encountered read errors: ${res.stderr.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.exitCode !== 0 && res.exitCode !== 3) {
|
||||||
|
logger.error(`Restic backup failed: ${res.stderr.toString()}`);
|
||||||
logger.error(`Command executed: restic ${args.join(" ")}`);
|
logger.error(`Command executed: restic ${args.join(" ")}`);
|
||||||
|
|
||||||
throw new Error(`Restic backup failed: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastLine = stdout.trim();
|
const lastLine = stdout.trim();
|
||||||
const resSummary = JSON.parse(lastLine ?? "{}");
|
let summaryLine = "";
|
||||||
|
try {
|
||||||
|
const resSummary = JSON.parse(lastLine ?? "{}");
|
||||||
|
summaryLine = resSummary;
|
||||||
|
} catch (_) {
|
||||||
|
logger.warn("Failed to parse restic backup output JSON summary.", lastLine);
|
||||||
|
summaryLine = "{}";
|
||||||
|
}
|
||||||
|
|
||||||
const result = backupOutputSchema(resSummary);
|
const result = backupOutputSchema(summaryLine);
|
||||||
|
|
||||||
if (result instanceof type.errors) {
|
if (result instanceof type.errors) {
|
||||||
logger.error(`Restic backup output validation failed: ${result}`);
|
logger.error(`Restic backup output validation failed: ${result}`);
|
||||||
|
return { result: null, exitCode: res.exitCode };
|
||||||
throw new Error(`Restic backup output validation failed: ${result}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return { result, exitCode: res.exitCode };
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreOutputSchema = type({
|
const restoreOutputSchema = type({
|
||||||
@@ -404,7 +412,7 @@ const restore = async (
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic restore failed: ${res.stderr}`);
|
logger.error(`Restic restore failed: ${res.stderr}`);
|
||||||
throw new Error(`Restic restore failed: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdout = res.text();
|
const stdout = res.text();
|
||||||
@@ -517,7 +525,7 @@ const forget = async (config: RepositoryConfig, options: RetentionPolicy, extra:
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic forget failed: ${res.stderr}`);
|
logger.error(`Restic forget failed: ${res.stderr}`);
|
||||||
throw new Error(`Restic forget failed: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -535,7 +543,7 @@ const deleteSnapshot = async (config: RepositoryConfig, snapshotId: string) => {
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic snapshot deletion failed: ${res.stderr}`);
|
logger.error(`Restic snapshot deletion failed: ${res.stderr}`);
|
||||||
throw new Error(`Failed to delete snapshot: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -585,7 +593,7 @@ const ls = async (config: RepositoryConfig, snapshotId: string, path?: string) =
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic ls failed: ${res.stderr}`);
|
logger.error(`Restic ls failed: ${res.stderr}`);
|
||||||
throw new Error(`Restic ls failed: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The output is a stream of JSON objects, first is snapshot info, rest are file/dir nodes
|
// The output is a stream of JSON objects, first is snapshot info, rest are file/dir nodes
|
||||||
@@ -636,7 +644,7 @@ const unlock = async (config: RepositoryConfig) => {
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic unlock failed: ${res.stderr}`);
|
logger.error(`Restic unlock failed: ${res.stderr}`);
|
||||||
throw new Error(`Restic unlock failed: ${res.stderr}`);
|
throw new ResticError(res.exitCode, res.stderr.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Restic unlock succeeded for repository: ${repoUrl}`);
|
logger.info(`Restic unlock succeeded for repository: ${repoUrl}`);
|
||||||
@@ -697,7 +705,7 @@ const repairIndex = async (config: RepositoryConfig) => {
|
|||||||
|
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
logger.error(`Restic repair index failed: ${stderr}`);
|
logger.error(`Restic repair index failed: ${stderr}`);
|
||||||
throw new Error(`Restic repair index failed: ${stderr}`);
|
throw new ResticError(res.exitCode, stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Restic repair index completed for repository: ${repoUrl}`);
|
logger.info(`Restic repair index completed for repository: ${repoUrl}`);
|
||||||
|
|||||||
@@ -41,9 +41,8 @@ export const safeSpawn = (params: Params) => {
|
|||||||
child.stderr.on("data", (data) => {
|
child.stderr.on("data", (data) => {
|
||||||
if (callbacks.onStderr) {
|
if (callbacks.onStderr) {
|
||||||
callbacks.onStderr(data.toString());
|
callbacks.onStderr(data.toString());
|
||||||
} else {
|
|
||||||
stderrData += data.toString();
|
|
||||||
}
|
}
|
||||||
|
stderrData += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on("error", async (error) => {
|
child.on("error", async (error) => {
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ services:
|
|||||||
|
|
||||||
- ./app:/app/app
|
- ./app:/app/app
|
||||||
- ~/.config/rclone:/root/.config/rclone
|
- ~/.config/rclone:/root/.config/rclone
|
||||||
- /var/lib/zerobyte:/var/lib/zerobyte:rshared
|
# - /run/docker/plugins:/run/docker/plugins
|
||||||
- /run/docker/plugins:/run/docker/plugins
|
# - /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
|
|
||||||
zerobyte-prod:
|
zerobyte-prod:
|
||||||
build:
|
build:
|
||||||
|
|||||||
Reference in New Issue
Block a user