Merge remote-tracking branch 'origin/main' into config-import-json
This commit is contained in:
commit
5cdec5cdaa
53 changed files with 9579 additions and 7022 deletions
|
|
@ -1,9 +1,7 @@
|
||||||
name: Checks
|
name: Checks
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_call:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
@ -11,8 +9,11 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
checks:
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -24,6 +25,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
uses: "./.github/actions/install-dependencies"
|
uses: "./.github/actions/install-dependencies"
|
||||||
|
|
||||||
|
- name: Run lint
|
||||||
|
shell: bash
|
||||||
|
run: bun run lint:ci
|
||||||
|
|
||||||
- name: Run type checks
|
- name: Run type checks
|
||||||
shell: bash
|
shell: bash
|
||||||
run: bun run tsc
|
run: bun run tsc
|
||||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
|
|
@ -31,9 +31,12 @@ jobs:
|
||||||
echo "release_type=release" >> $GITHUB_OUTPUT
|
echo "release_type=release" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
checks:
|
||||||
|
uses: ./.github/workflows/checks.yml
|
||||||
|
|
||||||
build-images:
|
build-images:
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
needs: [determine-release-type]
|
needs: [determine-release-type, checks]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Zerobyte is a backup automation tool built on top of Restic that provides a web interface for scheduling, managing, and monitoring encrypted backups. It supports multiple volume backends (NFS, SMB, WebDAV, local directories) and repository backends (S3, Azure, GCS, local, and rclone-based storage).
|
Zerobyte is a backup automation tool built on top of Restic that provides a web interface for scheduling, managing, and monitoring encrypted backups. It supports multiple volume backends (NFS, SMB, WebDAV, SFTP, local directories) and repository backends (S3, Azure, GCS, local, and rclone-based storage).
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ The server follows a modular service-oriented architecture:
|
||||||
Each module follows a controller <20> service <20> database pattern:
|
Each module follows a controller <20> service <20> database pattern:
|
||||||
|
|
||||||
- `auth/` - User authentication and session management
|
- `auth/` - User authentication and session management
|
||||||
- `volumes/` - Volume mounting/unmounting (NFS, SMB, WebDAV, directories)
|
- `volumes/` - Volume mounting/unmounting (NFS, SMB, WebDAV, SFTP, directories)
|
||||||
- `repositories/` - Restic repository management (S3, Azure, GCS, local, rclone)
|
- `repositories/` - Restic repository management (S3, Azure, GCS, local, rclone)
|
||||||
- `backups/` - Backup schedule management and execution
|
- `backups/` - Backup schedule management and execution
|
||||||
- `notifications/` - Notification system with multiple providers (Discord, email, Gotify, Ntfy, Slack, Pushover)
|
- `notifications/` - Notification system with multiple providers (Discord, email, Gotify, Ntfy, Slack, Pushover)
|
||||||
|
|
@ -109,7 +109,7 @@ Each module follows a controller <20> service <20> database pattern:
|
||||||
- `lifecycle/` - Application startup/shutdown hooks
|
- `lifecycle/` - Application startup/shutdown hooks
|
||||||
|
|
||||||
**Backends** (`app/server/modules/backends/`):
|
**Backends** (`app/server/modules/backends/`):
|
||||||
Each volume backend (NFS, SMB, WebDAV, directory) implements mounting logic using system tools (mount.nfs, mount.cifs, davfs2).
|
Each volume backend (NFS, SMB, WebDAV, SFTP, directory) implements mounting logic using system tools (mount.nfs, mount.cifs, davfs2, sshfs).
|
||||||
|
|
||||||
**Jobs** (`app/server/jobs/`):
|
**Jobs** (`app/server/jobs/`):
|
||||||
Cron-based background jobs managed by the Scheduler:
|
Cron-based background jobs managed by the Scheduler:
|
||||||
|
|
@ -158,7 +158,7 @@ Routes are organized in feature modules at `app/client/modules/*/routes/`.
|
||||||
|
|
||||||
`app/schemas/` contains ArkType schemas used by both client and server:
|
`app/schemas/` contains ArkType schemas used by both client and server:
|
||||||
|
|
||||||
- Volume configurations (NFS, SMB, WebDAV, directory)
|
- Volume configurations (NFS, SMB, WebDAV, SFTP, directory)
|
||||||
- Repository configurations (S3, Azure, GCS, local, rclone)
|
- Repository configurations (S3, Azure, GCS, local, rclone)
|
||||||
- Restic command output parsing types
|
- Restic command output parsing types
|
||||||
- Backend status types
|
- Backend status types
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ ARG BUN_VERSION="1.3.5"
|
||||||
FROM oven/bun:${BUN_VERSION}-alpine AS base
|
FROM oven/bun:${BUN_VERSION}-alpine AS base
|
||||||
|
|
||||||
RUN apk upgrade --no-cache && \
|
RUN apk upgrade --no-cache && \
|
||||||
apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3
|
apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 sshfs
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ Zerobyte is a backup automation tool that helps you save your data across multip
|
||||||
- **Automated backups** with encryption, compression and retention policies powered by Restic
|
- **Automated backups** with encryption, compression and retention policies powered by Restic
|
||||||
- **Flexible scheduling** For automated backup jobs with fine-grained retention policies
|
- **Flexible scheduling** For automated backup jobs with fine-grained retention policies
|
||||||
- **End-to-end encryption** ensuring your data is always protected
|
- **End-to-end encryption** ensuring your data is always protected
|
||||||
- **Multi-protocol support**: Backup from NFS, SMB, WebDAV, or local directories
|
- **Multi-protocol support**: Backup from NFS, SMB, WebDAV, SFTP, or local directories
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ services:
|
||||||
- ✅ Improved security by reducing container capabilities
|
- ✅ Improved security by reducing container capabilities
|
||||||
- ✅ Support for local directories
|
- ✅ Support for local directories
|
||||||
- ✅ Keep support all repository types (local, S3, GCS, Azure, rclone)
|
- ✅ Keep support all repository types (local, S3, GCS, Azure, rclone)
|
||||||
- ❌ Cannot mount NFS, SMB, or WebDAV shares directly from Zerobyte
|
- ❌ Cannot mount NFS, SMB, WebDAV, or SFTP shares directly from Zerobyte
|
||||||
|
|
||||||
If you need remote mount capabilities, keep the original configuration with `cap_add: SYS_ADMIN` and `devices: /dev/fuse:/dev/fuse`.
|
If you need remote mount capabilities, keep the original configuration with `cap_add: SYS_ADMIN` and `devices: /dev/fuse:/dev/fuse`.
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ If you want Zerobyte to create volumes, repositories, schedules, notification de
|
||||||
|
|
||||||
## Adding your first volume
|
## Adding your first volume
|
||||||
|
|
||||||
Zerobyte supports multiple volume backends including NFS, SMB, WebDAV, and local directories. A volume represents the source data you want to back up and monitor.
|
Zerobyte supports multiple volume backends including NFS, SMB, WebDAV, SFTP, and local directories. A volume represents the source data you want to back up and monitor.
|
||||||
|
|
||||||
To add your first volume, navigate to the "Volumes" section in the web interface and click on "Create volume". Fill in the required details such as volume name, type, and connection settings.
|
To add your first volume, navigate to the "Volumes" section in the web interface and click on "Create volume". Fill in the required details such as volume name, type, and connection settings.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,17 @@ export type ListVolumesResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -196,7 +207,7 @@ export type ListVolumesResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
@ -221,6 +232,17 @@ export type CreateVolumeData = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -270,6 +292,17 @@ export type CreateVolumeResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -297,7 +330,7 @@ export type CreateVolumeResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -322,6 +355,17 @@ export type TestConnectionData = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -424,6 +468,17 @@ export type GetVolumeResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -451,7 +506,7 @@ export type GetVolumeResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -478,6 +533,17 @@ export type UpdateVolumeData = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -536,6 +602,17 @@ export type UpdateVolumeResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -563,7 +640,7 @@ export type UpdateVolumeResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -773,8 +850,10 @@ export type ListRepositoriesResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -850,8 +929,10 @@ export type CreateRepositoryData = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
name: string;
|
name: string;
|
||||||
compressionMode?: 'auto' | 'max' | 'off';
|
compressionMode?: 'auto' | 'max' | 'off';
|
||||||
|
|
@ -989,8 +1070,10 @@ export type GetRepositoryResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1093,8 +1176,10 @@ export type UpdateRepositoryResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1367,8 +1452,10 @@ export type ListBackupSchedulesResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1390,6 +1477,7 @@ export type ListBackupSchedulesResponses = {
|
||||||
keepWithinDuration?: string;
|
keepWithinDuration?: string;
|
||||||
keepYearly?: number;
|
keepYearly?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
shortId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
volume: {
|
volume: {
|
||||||
autoRemount: boolean;
|
autoRemount: boolean;
|
||||||
|
|
@ -1409,6 +1497,17 @@ export type ListBackupSchedulesResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -1436,7 +1535,7 @@ export type ListBackupSchedulesResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
volumeId: number;
|
volumeId: number;
|
||||||
|
|
@ -1500,6 +1599,7 @@ export type CreateBackupScheduleResponses = {
|
||||||
keepWithinDuration?: string;
|
keepWithinDuration?: string;
|
||||||
keepYearly?: number;
|
keepYearly?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
shortId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
volumeId: number;
|
volumeId: number;
|
||||||
};
|
};
|
||||||
|
|
@ -1614,8 +1714,10 @@ export type GetBackupScheduleResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1637,6 +1739,7 @@ export type GetBackupScheduleResponses = {
|
||||||
keepWithinDuration?: string;
|
keepWithinDuration?: string;
|
||||||
keepYearly?: number;
|
keepYearly?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
shortId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
volume: {
|
volume: {
|
||||||
autoRemount: boolean;
|
autoRemount: boolean;
|
||||||
|
|
@ -1656,6 +1759,17 @@ export type GetBackupScheduleResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -1683,7 +1797,7 @@ export type GetBackupScheduleResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
volumeId: number;
|
volumeId: number;
|
||||||
|
|
@ -1748,6 +1862,7 @@ export type UpdateBackupScheduleResponses = {
|
||||||
keepWithinDuration?: string;
|
keepWithinDuration?: string;
|
||||||
keepYearly?: number;
|
keepYearly?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
shortId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
volumeId: number;
|
volumeId: number;
|
||||||
};
|
};
|
||||||
|
|
@ -1842,8 +1957,10 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1865,6 +1982,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||||
keepWithinDuration?: string;
|
keepWithinDuration?: string;
|
||||||
keepYearly?: number;
|
keepYearly?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
shortId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
volume: {
|
volume: {
|
||||||
autoRemount: boolean;
|
autoRemount: boolean;
|
||||||
|
|
@ -1884,6 +2002,17 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||||
path: string;
|
path: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
} | {
|
||||||
|
backend: 'sftp';
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
username: string;
|
||||||
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
|
password?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
} | {
|
} | {
|
||||||
backend: 'smb';
|
backend: 'smb';
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -1911,7 +2040,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
||||||
name: string;
|
name: string;
|
||||||
shortId: string;
|
shortId: string;
|
||||||
status: 'error' | 'mounted' | 'unmounted';
|
status: 'error' | 'mounted' | 'unmounted';
|
||||||
type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
|
type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
volumeId: number;
|
volumeId: number;
|
||||||
|
|
@ -2238,8 +2367,10 @@ export type GetScheduleMirrorsResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -2342,8 +2473,10 @@ export type UpdateScheduleMirrorsResponses = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
user: string;
|
user: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
|
skipHostKeyCheck?: boolean;
|
||||||
customPassword?: string;
|
customPassword?: string;
|
||||||
isExistingRepository?: boolean;
|
isExistingRepository?: boolean;
|
||||||
|
knownHosts?: string;
|
||||||
};
|
};
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{snapshots.map((snapshot) => {
|
{snapshots.map((snapshot) => {
|
||||||
const backupIds = snapshot.tags.map(Number).filter((tag) => !Number.isNaN(tag));
|
const backup = backups.find((b) => snapshot.tags.includes(b.shortId));
|
||||||
const backup = backups.find((b) => backupIds.includes(b.id));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,52 @@
|
||||||
import { Cloud, Folder, Server, Share2 } from "lucide-react";
|
import { Cloud, Folder, Server } from "lucide-react";
|
||||||
import type { BackendType } from "~/schemas/volumes";
|
import type { BackendType } from "~/schemas/volumes";
|
||||||
|
|
||||||
type VolumeIconProps = {
|
type VolumeIconProps = {
|
||||||
backend: BackendType;
|
backend: BackendType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIconAndColor = (backend: BackendType) => {
|
const getIconAndLabel = (backend: BackendType) => {
|
||||||
switch (backend) {
|
switch (backend) {
|
||||||
case "directory":
|
case "directory":
|
||||||
return {
|
return {
|
||||||
icon: Folder,
|
icon: Folder,
|
||||||
color: "text-blue-600 dark:text-blue-400",
|
|
||||||
label: "Directory",
|
label: "Directory",
|
||||||
};
|
};
|
||||||
case "nfs":
|
case "nfs":
|
||||||
return {
|
return {
|
||||||
icon: Server,
|
icon: Server,
|
||||||
color: "text-orange-600 dark:text-orange-400",
|
|
||||||
label: "NFS",
|
label: "NFS",
|
||||||
};
|
};
|
||||||
case "smb":
|
case "smb":
|
||||||
return {
|
return {
|
||||||
icon: Share2,
|
icon: Server,
|
||||||
color: "text-purple-600 dark:text-purple-400",
|
|
||||||
label: "SMB",
|
label: "SMB",
|
||||||
};
|
};
|
||||||
case "webdav":
|
case "webdav":
|
||||||
return {
|
return {
|
||||||
icon: Cloud,
|
icon: Server,
|
||||||
color: "text-green-600 dark:text-green-400",
|
|
||||||
label: "WebDAV",
|
label: "WebDAV",
|
||||||
};
|
};
|
||||||
case "rclone":
|
case "rclone":
|
||||||
return {
|
return {
|
||||||
icon: Cloud,
|
icon: Cloud,
|
||||||
color: "text-cyan-600 dark:text-cyan-400",
|
|
||||||
label: "Rclone",
|
label: "Rclone",
|
||||||
};
|
};
|
||||||
|
case "sftp":
|
||||||
|
return {
|
||||||
|
icon: Server,
|
||||||
|
label: "SFTP",
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
icon: Folder,
|
icon: Folder,
|
||||||
color: "text-gray-600 dark:text-gray-400",
|
|
||||||
label: "Unknown",
|
label: "Unknown",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VolumeIcon = ({ backend }: VolumeIconProps) => {
|
export const VolumeIcon = ({ backend }: VolumeIconProps) => {
|
||||||
const { icon: Icon, label } = getIconAndColor(backend);
|
const { icon: Icon, label } = getIconAndLabel(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`}>
|
||||||
|
|
|
||||||
40
app/client/modules/auth/components/reset-password-dialog.tsx
Normal file
40
app/client/modules/auth/components/reset-password-dialog.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "~/client/components/ui/button";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/client/components/ui/dialog";
|
||||||
|
import { copyToClipboard } from "~/utils/clipboard";
|
||||||
|
|
||||||
|
const RESET_PASSWORD_COMMAND = "docker exec -it zerobyte bun run cli reset-password";
|
||||||
|
|
||||||
|
type ResetPasswordDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResetPasswordDialog = ({ open, onOpenChange }: ResetPasswordDialogProps) => {
|
||||||
|
const handleCopy = async () => {
|
||||||
|
await copyToClipboard(RESET_PASSWORD_COMMAND);
|
||||||
|
toast.success("Command copied to clipboard");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Reset your password</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
To reset your password, run the following command on the server where Zerobyte is installed.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="rounded-md bg-muted p-4 font-mono text-sm break-all">{RESET_PASSWORD_COMMAND}</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
This command will start an interactive session where you can enter a new password for your account.
|
||||||
|
</p>
|
||||||
|
<Button onClick={handleCopy} variant="outline" className="w-full">
|
||||||
|
Copy Command
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -7,13 +7,12 @@ import { useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { AuthLayout } from "~/client/components/auth-layout";
|
import { AuthLayout } from "~/client/components/auth-layout";
|
||||||
import { Button } from "~/client/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/client/components/ui/dialog";
|
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/client/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/client/components/ui/form";
|
||||||
import { Input } from "~/client/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import type { Route } from "./+types/login";
|
import type { Route } from "./+types/login";
|
||||||
import { loginMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
import { loginMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { copyToClipboard } from "~/utils/clipboard";
|
import { ResetPasswordDialog } from "../components/reset-password-dialog";
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
|
|
@ -119,34 +118,3 @@ export default function LoginPage() {
|
||||||
</AuthLayout>
|
</AuthLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RESET_PASSWORD_COMMAND = "docker exec -it zerobyte bun run cli reset-password";
|
|
||||||
|
|
||||||
function ResetPasswordDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
|
|
||||||
const handleCopy = async () => {
|
|
||||||
await copyToClipboard(RESET_PASSWORD_COMMAND);
|
|
||||||
toast.success("Command copied to clipboard");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogContent className="sm:max-w-xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Reset your password</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
To reset your password, run the following command on the server where Zerobyte is installed.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="rounded-md bg-muted p-4 font-mono text-sm break-all">{RESET_PASSWORD_COMMAND}</div>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
This command will start an interactive session where you can enter a new password for your account.
|
|
||||||
</p>
|
|
||||||
<Button onClick={handleCopy} variant="outline" className="w-full">
|
|
||||||
Copy Command
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
||||||
isLoading,
|
isLoading,
|
||||||
failureReason,
|
failureReason,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
...listSnapshotsOptions({ path: { id: schedule.repository.id }, query: { backupId: schedule.id.toString() } }),
|
...listSnapshotsOptions({ path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSchedule = useMutation({
|
const updateSchedule = useMutation({
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const defaultValuesForType = {
|
||||||
azure: { backend: "azure" as const, compressionMode: "auto" as const },
|
azure: { backend: "azure" as const, compressionMode: "auto" as const },
|
||||||
rclone: { backend: "rclone" as const, compressionMode: "auto" as const },
|
rclone: { backend: "rclone" as const, compressionMode: "auto" as const },
|
||||||
rest: { backend: "rest" as const, compressionMode: "auto" as const },
|
rest: { backend: "rest" as const, compressionMode: "auto" as const },
|
||||||
sftp: { backend: "sftp" as const, compressionMode: "auto" as const, port: 22 },
|
sftp: { backend: "sftp" as const, compressionMode: "auto" as const, port: 22, skipHostKeyCheck: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateRepositoryForm = ({
|
export const CreateRepositoryForm = ({
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "../../../../components/ui/form";
|
} from "../../../../components/ui/form";
|
||||||
import { Input } from "../../../../components/ui/input";
|
import { Input } from "../../../../components/ui/input";
|
||||||
import { Textarea } from "../../../../components/ui/textarea";
|
import { Textarea } from "../../../../components/ui/textarea";
|
||||||
|
import { Switch } from "../../../../components/ui/switch";
|
||||||
import type { RepositoryFormValues } from "../create-repository-form";
|
import type { RepositoryFormValues } from "../create-repository-form";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -72,7 +73,7 @@ export const SftpRepositoryForm = ({ form }: Props) => {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Path</FormLabel>
|
<FormLabel>Path</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="backups/ironmount" {...field} />
|
<Input placeholder="backups/zerobyte" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Repository path on the SFTP server. </FormDescription>
|
<FormDescription>Repository path on the SFTP server. </FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
@ -96,6 +97,47 @@ export const SftpRepositoryForm = ({ form }: Props) => {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="skipHostKeyCheck"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Skip Host Key Verification</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Disable SSH host key checking. Useful for servers with dynamic IPs or self-signed keys.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{!form.watch("skipHostKeyCheck") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="knownHosts"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Known Hosts</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ..."
|
||||||
|
className="font-mono text-xs"
|
||||||
|
rows={3}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
The contents of the <code>known_hosts</code> file for this server.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { cn } from "~/client/lib/utils";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
|
||||||
import { RepositoryInfoTabContent } from "../tabs/info";
|
import { RepositoryInfoTabContent } from "../tabs/info";
|
||||||
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
||||||
import { Loader2, Stethoscope, Trash2, X } from "lucide-react";
|
import { Loader2, Stethoscope, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
export const handle = {
|
export const handle = {
|
||||||
breadcrumb: (match: Route.MetaArgs) => [
|
breadcrumb: (match: Route.MetaArgs) => [
|
||||||
|
|
@ -185,13 +185,13 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Are you sure you want to delete the repository <strong>{data.name}</strong>? This will not remove the
|
Are you sure you want to delete the repository <strong>{data.name}</strong>? This will not remove the
|
||||||
actual data from the backend storage, only the repository configuration will be deleted.
|
actual data from the backend storage, only the repository configuration will be deleted.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
All backup schedules associated with this repository will also be removed.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
<X className="h-4 w-4 mr-2" />
|
|
||||||
Cancel
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={handleConfirmDelete}
|
onClick={handleConfirmDelete}
|
||||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,7 @@ export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps
|
||||||
{(value) => {
|
{(value) => {
|
||||||
if (!value.data) return null;
|
if (!value.data) return null;
|
||||||
|
|
||||||
const backupIds = value.data.tags.map(Number).filter((tag) => !Number.isNaN(tag));
|
const backupSchedule = schedules.data?.find((s) => value.data.tags.includes(s.shortId));
|
||||||
const backupSchedule = schedules.data?.find((s) => backupIds.includes(s.id));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||||
);
|
);
|
||||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
|
|
||||||
const isImportedLocal = repository.type === "local" && repository.config.isExistingRepository;
|
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
...updateRepositoryMutation(),
|
...updateRepositoryMutation(),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
@ -77,13 +75,8 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||||
placeholder="Repository name"
|
placeholder="Repository name"
|
||||||
maxLength={32}
|
maxLength={32}
|
||||||
minLength={2}
|
minLength={2}
|
||||||
disabled={isImportedLocal}
|
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">Unique identifier for the repository.</p>
|
||||||
{isImportedLocal
|
|
||||||
? "Imported local repositories cannot be renamed."
|
|
||||||
: "Unique identifier for the repository."}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="compressionMode">Compression mode</Label>
|
<Label htmlFor="compressionMode">Compression mode</Label>
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
|
||||||
if (!searchQuery) return true;
|
if (!searchQuery) return true;
|
||||||
const searchLower = searchQuery.toLowerCase();
|
const searchLower = searchQuery.toLowerCase();
|
||||||
|
|
||||||
const backupIds = snapshot.tags.map(Number).filter((tag) => !Number.isNaN(tag));
|
const backup = schedules.data?.find((b) => snapshot.tags.includes(b.shortId));
|
||||||
const backup = schedules.data?.find((b) => backupIds.includes(b.id));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
snapshot.short_id.toLowerCase().includes(searchLower) ||
|
snapshot.short_id.toLowerCase().includes(searchLower) ||
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { volumeConfigSchemaBase } from "~/schemas/volumes";
|
||||||
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
|
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
||||||
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm } from "./volume-forms";
|
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
|
||||||
|
|
||||||
export const formSchema = type({
|
export const formSchema = type({
|
||||||
name: "2<=string<=32",
|
name: "2<=string<=32",
|
||||||
|
|
@ -46,6 +46,7 @@ const defaultValuesForType = {
|
||||||
smb: { backend: "smb" as const, port: 445, vers: "3.0" as const },
|
smb: { backend: "smb" as const, port: 445, vers: "3.0" as const },
|
||||||
webdav: { backend: "webdav" as const, port: 80, ssl: false, path: "/webdav" },
|
webdav: { backend: "webdav" as const, port: 80, ssl: false, path: "/webdav" },
|
||||||
rclone: { backend: "rclone" as const, path: "/" },
|
rclone: { backend: "rclone" as const, path: "/" },
|
||||||
|
sftp: { backend: "sftp" as const, port: 22, path: "/", skipHostKeyCheck: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading, className }: Props) => {
|
export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading, className }: Props) => {
|
||||||
|
|
@ -96,7 +97,12 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
const handleTestConnection = async () => {
|
const handleTestConnection = async () => {
|
||||||
const formValues = getValues();
|
const formValues = getValues();
|
||||||
|
|
||||||
if (formValues.backend === "nfs" || formValues.backend === "smb" || formValues.backend === "webdav") {
|
if (
|
||||||
|
formValues.backend === "nfs" ||
|
||||||
|
formValues.backend === "smb" ||
|
||||||
|
formValues.backend === "webdav" ||
|
||||||
|
formValues.backend === "sftp"
|
||||||
|
) {
|
||||||
testBackendConnection.mutate({
|
testBackendConnection.mutate({
|
||||||
body: { config: formValues },
|
body: { config: formValues },
|
||||||
});
|
});
|
||||||
|
|
@ -177,6 +183,18 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
<p>Remote mounts require SYS_ADMIN capability</p>
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div>
|
||||||
|
<SelectItem disabled={!capabilities.sysAdmin} value="sftp">
|
||||||
|
SFTP
|
||||||
|
</SelectItem>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
|
||||||
|
<p>Remote mounts require SYS_ADMIN capability</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -204,6 +222,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
{watchedBackend === "webdav" && <WebDAVForm form={form} />}
|
{watchedBackend === "webdav" && <WebDAVForm form={form} />}
|
||||||
{watchedBackend === "smb" && <SMBForm form={form} />}
|
{watchedBackend === "smb" && <SMBForm form={form} />}
|
||||||
{watchedBackend === "rclone" && <RcloneForm form={form} />}
|
{watchedBackend === "rclone" && <RcloneForm form={form} />}
|
||||||
|
{watchedBackend === "sftp" && <SFTPForm form={form} />}
|
||||||
{watchedBackend && watchedBackend !== "directory" && watchedBackend !== "rclone" && (
|
{watchedBackend && watchedBackend !== "directory" && watchedBackend !== "rclone" && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ export { NFSForm } from "./nfs-form";
|
||||||
export { SMBForm } from "./smb-form";
|
export { SMBForm } from "./smb-form";
|
||||||
export { WebDAVForm } from "./webdav-form";
|
export { WebDAVForm } from "./webdav-form";
|
||||||
export { RcloneForm } from "./rclone-form";
|
export { RcloneForm } from "./rclone-form";
|
||||||
|
export { SFTPForm } from "./sftp-form";
|
||||||
|
|
|
||||||
161
app/client/modules/volumes/components/volume-forms/sftp-form.tsx
Normal file
161
app/client/modules/volumes/components/volume-forms/sftp-form.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import type { UseFormReturn } from "react-hook-form";
|
||||||
|
import type { FormValues } from "../create-volume-form";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "../../../../components/ui/form";
|
||||||
|
import { Input } from "../../../../components/ui/input";
|
||||||
|
import { SecretInput } from "../../../../components/ui/secret-input";
|
||||||
|
import { Textarea } from "../../../../components/ui/textarea";
|
||||||
|
import { Switch } from "../../../../components/ui/switch";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
form: UseFormReturn<FormValues>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SFTPForm = ({ form }: Props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="host"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Host</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="example.com" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>SFTP server hostname or IP address.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="port"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Port</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="22"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(parseInt(e.target.value, 10) || undefined)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>SFTP server port (default: 22).</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="root" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Username for SFTP authentication.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Password (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<SecretInput placeholder="••••••••" value={field.value ?? ""} onChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Password for SFTP authentication (optional if using private key).</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="privateKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Private Key (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
||||||
|
className="font-mono text-xs"
|
||||||
|
rows={5}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>SSH private key for authentication (optional if using password).</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="path"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="/backups" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Path to the directory on the SFTP server.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="skipHostKeyCheck"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Skip Host Key Verification</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Disable SSH host key checking. Useful for servers with dynamic IPs or self-signed keys.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{!form.watch("skipHostKeyCheck") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="knownHosts"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Known Hosts</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ..."
|
||||||
|
className="font-mono text-xs"
|
||||||
|
rows={3}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
The contents of the <code>known_hosts</code> file for this server.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
10
app/drizzle/0026_migrate-local-repo-paths.sql
Normal file
10
app/drizzle/0026_migrate-local-repo-paths.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
-- Migrate local imported repository paths to include the repository name
|
||||||
|
-- Previously, the path and name were concatenated at runtime. Now the path should be the full path.
|
||||||
|
|
||||||
|
UPDATE `repositories_table`
|
||||||
|
SET `config` = json_set(`config`, '$.path', rtrim(json_extract(`config`, '$.path'), '/') || '/' || json_extract(`config`, '$.name')),
|
||||||
|
`updated_at` = (unixepoch() * 1000)
|
||||||
|
WHERE `type` = 'local'
|
||||||
|
AND json_extract(`config`, '$.isExistingRepository') = true
|
||||||
|
AND json_extract(`config`, '$.path') IS NOT NULL
|
||||||
|
AND json_extract(`config`, '$.name') IS NOT NULL;
|
||||||
3
app/drizzle/0027_careful_cammi.sql
Normal file
3
app/drizzle/0027_careful_cammi.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE `backup_schedules_table` ADD `short_id` text;--> statement-breakpoint
|
||||||
|
UPDATE `backup_schedules_table` SET `short_id` = lower(hex(randomblob(4))) WHERE `short_id` IS NULL;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `backup_schedules_table_short_id_unique` ON `backup_schedules_table` (`short_id`);
|
||||||
31
app/drizzle/0028_third_amazoness.sql
Normal file
31
app/drizzle/0028_third_amazoness.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||||
|
CREATE TABLE `__new_backup_schedules_table` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`short_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`volume_id` integer NOT NULL,
|
||||||
|
`repository_id` text NOT NULL,
|
||||||
|
`enabled` integer DEFAULT true NOT NULL,
|
||||||
|
`cron_expression` text NOT NULL,
|
||||||
|
`retention_policy` text,
|
||||||
|
`exclude_patterns` text DEFAULT '[]',
|
||||||
|
`exclude_if_present` text DEFAULT '[]',
|
||||||
|
`include_patterns` text DEFAULT '[]',
|
||||||
|
`last_backup_at` integer,
|
||||||
|
`last_backup_status` text,
|
||||||
|
`last_backup_error` text,
|
||||||
|
`next_backup_at` integer,
|
||||||
|
`one_file_system` integer DEFAULT false NOT NULL,
|
||||||
|
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
|
||||||
|
FOREIGN KEY (`volume_id`) REFERENCES `volumes_table`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`repository_id`) REFERENCES `repositories_table`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
INSERT INTO `__new_backup_schedules_table`("id", "short_id", "name", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "exclude_if_present", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "one_file_system", "sort_order", "created_at", "updated_at") SELECT "id", "short_id", "name", "volume_id", "repository_id", "enabled", "cron_expression", "retention_policy", "exclude_patterns", "exclude_if_present", "include_patterns", "last_backup_at", "last_backup_status", "last_backup_error", "next_backup_at", "one_file_system", "sort_order", "created_at", "updated_at" FROM `backup_schedules_table`;--> statement-breakpoint
|
||||||
|
DROP TABLE `backup_schedules_table`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `__new_backup_schedules_table` RENAME TO `backup_schedules_table`;--> statement-breakpoint
|
||||||
|
PRAGMA foreign_keys=ON;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `backup_schedules_table_short_id_unique` ON `backup_schedules_table` (`short_id`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `backup_schedules_table_name_unique` ON `backup_schedules_table` (`name`);
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -204,12 +193,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -217,22 +202,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -352,12 +330,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -365,12 +339,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -438,9 +408,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -537,16 +505,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -594,12 +558,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -660,9 +620,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -760,16 +718,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -204,12 +193,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -217,22 +202,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -356,9 +334,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -367,12 +343,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -380,12 +352,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -453,9 +421,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -552,16 +518,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -609,12 +571,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -675,9 +633,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -775,16 +731,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -204,12 +193,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -217,22 +202,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -364,9 +342,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -375,12 +351,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -388,12 +360,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -461,9 +429,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -560,16 +526,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -617,12 +579,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -683,9 +641,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -783,16 +739,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -212,12 +201,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -225,22 +210,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -372,9 +350,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -383,12 +359,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -396,12 +368,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -469,9 +437,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -568,16 +534,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -625,12 +587,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -691,9 +649,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -791,16 +747,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -212,12 +201,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -225,22 +210,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -380,9 +358,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -391,12 +367,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -404,12 +376,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -477,9 +445,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -576,16 +542,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -633,12 +595,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -699,9 +657,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -799,16 +755,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -212,12 +201,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -225,22 +210,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -388,9 +366,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -399,12 +375,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -412,12 +384,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -485,9 +453,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -584,16 +550,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -641,12 +603,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -707,9 +665,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -807,16 +763,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -120,26 +117,18 @@
|
||||||
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
|
||||||
],
|
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
},
|
},
|
||||||
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
}
|
}
|
||||||
|
|
@ -211,36 +200,25 @@
|
||||||
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
|
||||||
],
|
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
},
|
},
|
||||||
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -388,9 +366,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -398,26 +374,18 @@
|
||||||
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
|
||||||
],
|
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
},
|
},
|
||||||
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
}
|
}
|
||||||
|
|
@ -485,9 +453,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -584,16 +550,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"repositories_table_name_unique": {
|
"repositories_table_name_unique": {
|
||||||
"name": "repositories_table_name_unique",
|
"name": "repositories_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -640,13 +602,9 @@
|
||||||
"sessions_table_user_id_users_table_id_fk": {
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
|
||||||
],
|
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsTo": [
|
"columnsTo": ["id"],
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "no action",
|
"onUpdate": "no action",
|
||||||
"onDelete": "cascade"
|
"onDelete": "cascade"
|
||||||
}
|
}
|
||||||
|
|
@ -707,9 +665,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -807,16 +763,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
"columns": [
|
"columns": ["schedule_id", "repository_id"],
|
||||||
"schedule_id",
|
|
||||||
"repository_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -121,12 +118,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -134,12 +127,8 @@
|
||||||
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedule_mirrors_table",
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -212,12 +201,8 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "backup_schedules_table",
|
"tableTo": "backup_schedules_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["schedule_id"],
|
||||||
"schedule_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -225,22 +210,15 @@
|
||||||
"name": "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",
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
"tableTo": "notification_destinations_table",
|
"tableTo": "notification_destinations_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["destination_id"],
|
||||||
"destination_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
"columns": [
|
"columns": ["schedule_id", "destination_id"],
|
||||||
"schedule_id",
|
|
||||||
"destination_id"
|
|
||||||
],
|
|
||||||
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -388,9 +366,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"backup_schedules_table_name_unique": {
|
"backup_schedules_table_name_unique": {
|
||||||
"name": "backup_schedules_table_name_unique",
|
"name": "backup_schedules_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -399,12 +375,8 @@
|
||||||
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "volumes_table",
|
"tableTo": "volumes_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["volume_id"],
|
||||||
"volume_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -412,12 +384,8 @@
|
||||||
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
"tableFrom": "backup_schedules_table",
|
"tableFrom": "backup_schedules_table",
|
||||||
"tableTo": "repositories_table",
|
"tableTo": "repositories_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["repository_id"],
|
||||||
"repository_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -485,9 +453,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"notification_destinations_table_name_unique": {
|
"notification_destinations_table_name_unique": {
|
||||||
"name": "notification_destinations_table_name_unique",
|
"name": "notification_destinations_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -584,9 +550,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"repositories_table_short_id_unique": {
|
"repositories_table_short_id_unique": {
|
||||||
"name": "repositories_table_short_id_unique",
|
"name": "repositories_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -634,12 +598,8 @@
|
||||||
"name": "sessions_table_user_id_users_table_id_fk",
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
"tableFrom": "sessions_table",
|
"tableFrom": "sessions_table",
|
||||||
"tableTo": "users_table",
|
"tableTo": "users_table",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -700,9 +660,7 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"users_table_username_unique": {
|
"users_table_username_unique": {
|
||||||
"name": "users_table_username_unique",
|
"name": "users_table_username_unique",
|
||||||
"columns": [
|
"columns": ["username"],
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -800,16 +758,12 @@
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"volumes_table_short_id_unique": {
|
"volumes_table_short_id_unique": {
|
||||||
"name": "volumes_table_short_id_unique",
|
"name": "volumes_table_short_id_unique",
|
||||||
"columns": [
|
"columns": ["short_id"],
|
||||||
"short_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"volumes_table_name_unique": {
|
"volumes_table_name_unique": {
|
||||||
"name": "volumes_table_name_unique",
|
"name": "volumes_table_name_unique",
|
||||||
"columns": [
|
"columns": ["name"],
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
786
app/drizzle/meta/0026_snapshot.json
Normal file
786
app/drizzle/meta/0026_snapshot.json
Normal file
|
|
@ -0,0 +1,786 @@
|
||||||
|
{
|
||||||
|
"id": "19421265-4e3a-46b8-9ca1-a01d1e293dbc",
|
||||||
|
"prevId": "ca46a423-51ca-45ae-9470-f82172a67bd3",
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"tables": {
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table": {
|
||||||
|
"name": "backup_schedule_mirrors_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"last_copy_at": {
|
||||||
|
"name": "last_copy_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_status": {
|
||||||
|
"name": "last_copy_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_error": {
|
||||||
|
"name": "last_copy_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
|
"columns": ["schedule_id", "repository_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table": {
|
||||||
|
"name": "backup_schedule_notifications_table",
|
||||||
|
"columns": {
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_id": {
|
||||||
|
"name": "destination_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"notify_on_start": {
|
||||||
|
"name": "notify_on_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_success": {
|
||||||
|
"name": "notify_on_success",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_warning": {
|
||||||
|
"name": "notify_on_warning",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"notify_on_failure": {
|
||||||
|
"name": "notify_on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"columnsFrom": ["destination_id"],
|
||||||
|
"tableTo": "notification_destinations_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
|
"columns": ["schedule_id", "destination_id"],
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedules_table": {
|
||||||
|
"name": "backup_schedules_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"volume_id": {
|
||||||
|
"name": "volume_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"cron_expression": {
|
||||||
|
"name": "cron_expression",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"retention_policy": {
|
||||||
|
"name": "retention_policy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"exclude_patterns": {
|
||||||
|
"name": "exclude_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"exclude_if_present": {
|
||||||
|
"name": "exclude_if_present",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"include_patterns": {
|
||||||
|
"name": "include_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"last_backup_at": {
|
||||||
|
"name": "last_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_status": {
|
||||||
|
"name": "last_backup_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_error": {
|
||||||
|
"name": "last_backup_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"next_backup_at": {
|
||||||
|
"name": "next_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"one_file_system": {
|
||||||
|
"name": "one_file_system",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedules_table_name_unique": {
|
||||||
|
"name": "backup_schedules_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["volume_id"],
|
||||||
|
"tableTo": "volumes_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
},
|
||||||
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_destinations_table": {
|
||||||
|
"name": "notification_destinations_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"notification_destinations_table_name_unique": {
|
||||||
|
"name": "notification_destinations_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories_table": {
|
||||||
|
"name": "repositories_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"compression_mode": {
|
||||||
|
"name": "compression_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'auto'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"last_checked": {
|
||||||
|
"name": "last_checked",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"repositories_table_short_id_unique": {
|
||||||
|
"name": "repositories_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions_table": {
|
||||||
|
"name": "sessions_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
|
"tableFrom": "sessions_table",
|
||||||
|
"columnsFrom": ["user_id"],
|
||||||
|
"tableTo": "users_table",
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onUpdate": "no action",
|
||||||
|
"onDelete": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_table": {
|
||||||
|
"name": "users_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_downloaded_restic_password": {
|
||||||
|
"name": "has_downloaded_restic_password",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_table_username_unique": {
|
||||||
|
"name": "users_table_username_unique",
|
||||||
|
"columns": ["username"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"volumes_table": {
|
||||||
|
"name": "volumes_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unmounted'"
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_health_check": {
|
||||||
|
"name": "last_health_check",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"auto_remount": {
|
||||||
|
"name": "auto_remount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"volumes_table_short_id_unique": {
|
||||||
|
"name": "volumes_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"volumes_table_name_unique": {
|
||||||
|
"name": "volumes_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
798
app/drizzle/meta/0027_snapshot.json
Normal file
798
app/drizzle/meta/0027_snapshot.json
Normal file
|
|
@ -0,0 +1,798 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "94ebc8c3-f11f-48cb-9814-d8d550422d26",
|
||||||
|
"prevId": "19421265-4e3a-46b8-9ca1-a01d1e293dbc",
|
||||||
|
"tables": {
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table": {
|
||||||
|
"name": "backup_schedule_mirrors_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"last_copy_at": {
|
||||||
|
"name": "last_copy_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_status": {
|
||||||
|
"name": "last_copy_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_error": {
|
||||||
|
"name": "last_copy_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
|
"columns": ["schedule_id", "repository_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table": {
|
||||||
|
"name": "backup_schedule_notifications_table",
|
||||||
|
"columns": {
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_id": {
|
||||||
|
"name": "destination_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"notify_on_start": {
|
||||||
|
"name": "notify_on_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_success": {
|
||||||
|
"name": "notify_on_success",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_warning": {
|
||||||
|
"name": "notify_on_warning",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"notify_on_failure": {
|
||||||
|
"name": "notify_on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "notification_destinations_table",
|
||||||
|
"columnsFrom": ["destination_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
|
"columns": ["schedule_id", "destination_id"],
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedules_table": {
|
||||||
|
"name": "backup_schedules_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"volume_id": {
|
||||||
|
"name": "volume_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"cron_expression": {
|
||||||
|
"name": "cron_expression",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"retention_policy": {
|
||||||
|
"name": "retention_policy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"exclude_patterns": {
|
||||||
|
"name": "exclude_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"exclude_if_present": {
|
||||||
|
"name": "exclude_if_present",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"include_patterns": {
|
||||||
|
"name": "include_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"last_backup_at": {
|
||||||
|
"name": "last_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_status": {
|
||||||
|
"name": "last_backup_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_error": {
|
||||||
|
"name": "last_backup_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"next_backup_at": {
|
||||||
|
"name": "next_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"one_file_system": {
|
||||||
|
"name": "one_file_system",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedules_table_short_id_unique": {
|
||||||
|
"name": "backup_schedules_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"backup_schedules_table_name_unique": {
|
||||||
|
"name": "backup_schedules_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "volumes_table",
|
||||||
|
"columnsFrom": ["volume_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_destinations_table": {
|
||||||
|
"name": "notification_destinations_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"notification_destinations_table_name_unique": {
|
||||||
|
"name": "notification_destinations_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories_table": {
|
||||||
|
"name": "repositories_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"compression_mode": {
|
||||||
|
"name": "compression_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'auto'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"last_checked": {
|
||||||
|
"name": "last_checked",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"repositories_table_short_id_unique": {
|
||||||
|
"name": "repositories_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions_table": {
|
||||||
|
"name": "sessions_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
|
"tableFrom": "sessions_table",
|
||||||
|
"tableTo": "users_table",
|
||||||
|
"columnsFrom": ["user_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_table": {
|
||||||
|
"name": "users_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_downloaded_restic_password": {
|
||||||
|
"name": "has_downloaded_restic_password",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_table_username_unique": {
|
||||||
|
"name": "users_table_username_unique",
|
||||||
|
"columns": ["username"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"volumes_table": {
|
||||||
|
"name": "volumes_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unmounted'"
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_health_check": {
|
||||||
|
"name": "last_health_check",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"auto_remount": {
|
||||||
|
"name": "auto_remount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"volumes_table_short_id_unique": {
|
||||||
|
"name": "volumes_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"volumes_table_name_unique": {
|
||||||
|
"name": "volumes_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
798
app/drizzle/meta/0028_snapshot.json
Normal file
798
app/drizzle/meta/0028_snapshot.json
Normal file
|
|
@ -0,0 +1,798 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "2837bed4-34fb-4d16-b331-7b6d483979bc",
|
||||||
|
"prevId": "94ebc8c3-f11f-48cb-9814-d8d550422d26",
|
||||||
|
"tables": {
|
||||||
|
"app_metadata": {
|
||||||
|
"name": "app_metadata",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table": {
|
||||||
|
"name": "backup_schedule_mirrors_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"last_copy_at": {
|
||||||
|
"name": "last_copy_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_status": {
|
||||||
|
"name": "last_copy_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_copy_error": {
|
||||||
|
"name": "last_copy_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
|
||||||
|
"columns": ["schedule_id", "repository_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_mirrors_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table": {
|
||||||
|
"name": "backup_schedule_notifications_table",
|
||||||
|
"columns": {
|
||||||
|
"schedule_id": {
|
||||||
|
"name": "schedule_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_id": {
|
||||||
|
"name": "destination_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"notify_on_start": {
|
||||||
|
"name": "notify_on_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_success": {
|
||||||
|
"name": "notify_on_success",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"notify_on_warning": {
|
||||||
|
"name": "notify_on_warning",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"notify_on_failure": {
|
||||||
|
"name": "notify_on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "backup_schedules_table",
|
||||||
|
"columnsFrom": ["schedule_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
|
||||||
|
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedule_notifications_table",
|
||||||
|
"tableTo": "notification_destinations_table",
|
||||||
|
"columnsFrom": ["destination_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
|
||||||
|
"columns": ["schedule_id", "destination_id"],
|
||||||
|
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"backup_schedules_table": {
|
||||||
|
"name": "backup_schedules_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"volume_id": {
|
||||||
|
"name": "volume_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"cron_expression": {
|
||||||
|
"name": "cron_expression",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"retention_policy": {
|
||||||
|
"name": "retention_policy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"exclude_patterns": {
|
||||||
|
"name": "exclude_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"exclude_if_present": {
|
||||||
|
"name": "exclude_if_present",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"include_patterns": {
|
||||||
|
"name": "include_patterns",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"last_backup_at": {
|
||||||
|
"name": "last_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_status": {
|
||||||
|
"name": "last_backup_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_backup_error": {
|
||||||
|
"name": "last_backup_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"next_backup_at": {
|
||||||
|
"name": "next_backup_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"one_file_system": {
|
||||||
|
"name": "one_file_system",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"backup_schedules_table_short_id_unique": {
|
||||||
|
"name": "backup_schedules_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"backup_schedules_table_name_unique": {
|
||||||
|
"name": "backup_schedules_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"backup_schedules_table_volume_id_volumes_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "volumes_table",
|
||||||
|
"columnsFrom": ["volume_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"backup_schedules_table_repository_id_repositories_table_id_fk": {
|
||||||
|
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
|
||||||
|
"tableFrom": "backup_schedules_table",
|
||||||
|
"tableTo": "repositories_table",
|
||||||
|
"columnsFrom": ["repository_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_destinations_table": {
|
||||||
|
"name": "notification_destinations_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"notification_destinations_table_name_unique": {
|
||||||
|
"name": "notification_destinations_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories_table": {
|
||||||
|
"name": "repositories_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"compression_mode": {
|
||||||
|
"name": "compression_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'auto'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unknown'"
|
||||||
|
},
|
||||||
|
"last_checked": {
|
||||||
|
"name": "last_checked",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"repositories_table_short_id_unique": {
|
||||||
|
"name": "repositories_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions_table": {
|
||||||
|
"name": "sessions_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_table_user_id_users_table_id_fk": {
|
||||||
|
"name": "sessions_table_user_id_users_table_id_fk",
|
||||||
|
"tableFrom": "sessions_table",
|
||||||
|
"tableTo": "users_table",
|
||||||
|
"columnsFrom": ["user_id"],
|
||||||
|
"columnsTo": ["id"],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_table": {
|
||||||
|
"name": "users_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_downloaded_restic_password": {
|
||||||
|
"name": "has_downloaded_restic_password",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_table_username_unique": {
|
||||||
|
"name": "users_table_username_unique",
|
||||||
|
"columns": ["username"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"volumes_table": {
|
||||||
|
"name": "volumes_table",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"short_id": {
|
||||||
|
"name": "short_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'unmounted'"
|
||||||
|
},
|
||||||
|
"last_error": {
|
||||||
|
"name": "last_error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_health_check": {
|
||||||
|
"name": "last_health_check",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch() * 1000)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"auto_remount": {
|
||||||
|
"name": "auto_remount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"volumes_table_short_id_unique": {
|
||||||
|
"name": "volumes_table_short_id_unique",
|
||||||
|
"columns": ["short_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"volumes_table_name_unique": {
|
||||||
|
"name": "volumes_table_name_unique",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -183,6 +183,27 @@
|
||||||
"when": 1766431021321,
|
"when": 1766431021321,
|
||||||
"tag": "0025_remarkable_pete_wisdom",
|
"tag": "0025_remarkable_pete_wisdom",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 26,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1766765013108,
|
||||||
|
"tag": "0026_migrate-local-repo-paths",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 27,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1766778073418,
|
||||||
|
"tag": "0027_careful_cammi",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 28,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1766778162985,
|
||||||
|
"tag": "0028_third_amazoness",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +77,8 @@ export const sftpRepositoryConfigSchema = type({
|
||||||
user: "string",
|
user: "string",
|
||||||
path: "string",
|
path: "string",
|
||||||
privateKey: "string",
|
privateKey: "string",
|
||||||
|
skipHostKeyCheck: "boolean = true",
|
||||||
|
knownHosts: "string?",
|
||||||
}).and(baseRepositoryConfigSchema);
|
}).and(baseRepositoryConfigSchema);
|
||||||
|
|
||||||
export const repositoryConfigSchemaBase = s3RepositoryConfigSchema
|
export const repositoryConfigSchemaBase = s3RepositoryConfigSchema
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const BACKEND_TYPES = {
|
||||||
directory: "directory",
|
directory: "directory",
|
||||||
webdav: "webdav",
|
webdav: "webdav",
|
||||||
rclone: "rclone",
|
rclone: "rclone",
|
||||||
|
sftp: "sftp",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type BackendType = keyof typeof BACKEND_TYPES;
|
export type BackendType = keyof typeof BACKEND_TYPES;
|
||||||
|
|
@ -55,11 +56,25 @@ export const rcloneConfigSchema = type({
|
||||||
readOnly: "boolean?",
|
readOnly: "boolean?",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sftpConfigSchema = type({
|
||||||
|
backend: "'sftp'",
|
||||||
|
host: "string",
|
||||||
|
port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(22),
|
||||||
|
username: "string",
|
||||||
|
password: "string?",
|
||||||
|
privateKey: "string?",
|
||||||
|
path: "string",
|
||||||
|
readOnly: "boolean?",
|
||||||
|
skipHostKeyCheck: "boolean = true",
|
||||||
|
knownHosts: "string?",
|
||||||
|
});
|
||||||
|
|
||||||
export const volumeConfigSchemaBase = nfsConfigSchema
|
export const volumeConfigSchemaBase = nfsConfigSchema
|
||||||
.or(smbConfigSchema)
|
.or(smbConfigSchema)
|
||||||
.or(webdavConfigSchema)
|
.or(webdavConfigSchema)
|
||||||
.or(directoryConfigSchema)
|
.or(directoryConfigSchema)
|
||||||
.or(rcloneConfigSchema);
|
.or(rcloneConfigSchema)
|
||||||
|
.or(sftpConfigSchema);
|
||||||
|
|
||||||
export const volumeConfigSchema = volumeConfigSchemaBase.onUndeclaredKey("delete");
|
export const volumeConfigSchema = volumeConfigSchemaBase.onUndeclaredKey("delete");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ const envSchema = type({
|
||||||
NODE_ENV: type.enumerated("development", "production", "test").default("production"),
|
NODE_ENV: type.enumerated("development", "production", "test").default("production"),
|
||||||
SERVER_IP: 'string = "localhost"',
|
SERVER_IP: 'string = "localhost"',
|
||||||
SERVER_IDLE_TIMEOUT: 'string.integer.parse = "60"',
|
SERVER_IDLE_TIMEOUT: 'string.integer.parse = "60"',
|
||||||
|
RESTIC_HOSTNAME: "string = 'zerobyte'",
|
||||||
}).pipe((s) => ({
|
}).pipe((s) => ({
|
||||||
__prod__: s.NODE_ENV === "production",
|
__prod__: s.NODE_ENV === "production",
|
||||||
environment: s.NODE_ENV,
|
environment: s.NODE_ENV,
|
||||||
serverIp: s.SERVER_IP,
|
serverIp: s.SERVER_IP,
|
||||||
serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
|
serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
|
||||||
|
resticHostname: s.RESTIC_HOSTNAME,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const parseConfig = (env: unknown) => {
|
const parseConfig = (env: unknown) => {
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ export const RESTIC_PASS_FILE = "/var/lib/zerobyte/data/restic.pass";
|
||||||
|
|
||||||
export const DEFAULT_EXCLUDES = [DATABASE_URL, RESTIC_PASS_FILE, REPOSITORY_BASE];
|
export const DEFAULT_EXCLUDES = [DATABASE_URL, RESTIC_PASS_FILE, REPOSITORY_BASE];
|
||||||
|
|
||||||
export const REQUIRED_MIGRATIONS = ["v0.14.0"];
|
export const REQUIRED_MIGRATIONS = ["v0.21.0"];
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ export type RepositoryInsert = typeof repositoriesTable.$inferInsert;
|
||||||
*/
|
*/
|
||||||
export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
export const backupSchedulesTable = sqliteTable("backup_schedules_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(),
|
||||||
volumeId: int("volume_id")
|
volumeId: int("volume_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createHonoServer } from "react-router-hono-server/bun";
|
import { createHonoServer } from "react-router-hono-server/bun";
|
||||||
import { runDbMigrations } from "./db/db";
|
import { runDbMigrations } from "./db/db";
|
||||||
import { startup } from "./modules/lifecycle/startup";
|
import { startup } from "./modules/lifecycle/startup";
|
||||||
import { migrateToShortIds } from "./modules/lifecycle/migration";
|
import { retagSnapshots } from "./modules/lifecycle/migration";
|
||||||
import { logger } from "./utils/logger";
|
import { logger } from "./utils/logger";
|
||||||
import { shutdown } from "./modules/lifecycle/shutdown";
|
import { shutdown } from "./modules/lifecycle/shutdown";
|
||||||
import { REQUIRED_MIGRATIONS } from "./core/constants";
|
import { REQUIRED_MIGRATIONS } from "./core/constants";
|
||||||
|
|
@ -13,7 +13,7 @@ const app = createApp();
|
||||||
|
|
||||||
runDbMigrations();
|
runDbMigrations();
|
||||||
|
|
||||||
await migrateToShortIds();
|
await retagSnapshots();
|
||||||
await validateRequiredMigrations(REQUIRED_MIGRATIONS);
|
await validateRequiredMigrations(REQUIRED_MIGRATIONS);
|
||||||
|
|
||||||
startup();
|
startup();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { makeNfsBackend } from "./nfs/nfs-backend";
|
||||||
import { makeRcloneBackend } from "./rclone/rclone-backend";
|
import { makeRcloneBackend } from "./rclone/rclone-backend";
|
||||||
import { makeSmbBackend } from "./smb/smb-backend";
|
import { makeSmbBackend } from "./smb/smb-backend";
|
||||||
import { makeWebdavBackend } from "./webdav/webdav-backend";
|
import { makeWebdavBackend } from "./webdav/webdav-backend";
|
||||||
|
import { makeSftpBackend } from "./sftp/sftp-backend";
|
||||||
|
|
||||||
type OperationResult = {
|
type OperationResult = {
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
@ -37,5 +38,8 @@ export const createVolumeBackend = (volume: Volume): VolumeBackend => {
|
||||||
case "rclone": {
|
case "rclone": {
|
||||||
return makeRcloneBackend(volume.config, path);
|
return makeRcloneBackend(volume.config, path);
|
||||||
}
|
}
|
||||||
|
case "sftp": {
|
||||||
|
return makeSftpBackend(volume.config, path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
180
app/server/modules/backends/sftp/sftp-backend.ts
Normal file
180
app/server/modules/backends/sftp/sftp-backend.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import * as fs from "node:fs/promises";
|
||||||
|
import * as os from "node:os";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { $ } from "bun";
|
||||||
|
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
||||||
|
import { cryptoUtils } from "../../../utils/crypto";
|
||||||
|
import { toMessage } from "../../../utils/errors";
|
||||||
|
import { logger } from "../../../utils/logger";
|
||||||
|
import { getMountForPath } from "../../../utils/mountinfo";
|
||||||
|
import { withTimeout } from "../../../utils/timeout";
|
||||||
|
import type { VolumeBackend } from "../backend";
|
||||||
|
import { executeUnmount } from "../utils/backend-utils";
|
||||||
|
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
|
||||||
|
|
||||||
|
const SSH_KEYS_DIR = "/var/lib/zerobyte/ssh";
|
||||||
|
|
||||||
|
const getPrivateKeyPath = (mountPath: string) => {
|
||||||
|
const name = path.basename(mountPath);
|
||||||
|
return path.join(SSH_KEYS_DIR, `${name}.key`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKnownHostsPath = (mountPath: string) => {
|
||||||
|
const name = path.basename(mountPath);
|
||||||
|
return path.join(SSH_KEYS_DIR, `${name}.known_hosts`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mount = async (config: BackendConfig, mountPath: string) => {
|
||||||
|
logger.debug(`Mounting SFTP volume ${mountPath}...`);
|
||||||
|
|
||||||
|
if (config.backend !== "sftp") {
|
||||||
|
logger.error("Provided config is not for SFTP backend");
|
||||||
|
return { status: BACKEND_STATUS.error, error: "Provided config is not for SFTP backend" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os.platform() !== "linux") {
|
||||||
|
logger.error("SFTP mounting is only supported on Linux hosts.");
|
||||||
|
return { status: BACKEND_STATUS.error, error: "SFTP mounting is only supported on Linux hosts." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status } = await checkHealth(mountPath);
|
||||||
|
if (status === "mounted") {
|
||||||
|
return { status: BACKEND_STATUS.mounted };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "error") {
|
||||||
|
logger.debug(`Trying to unmount any existing mounts at ${mountPath} before mounting...`);
|
||||||
|
await unmount(mountPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
await fs.mkdir(mountPath, { recursive: true });
|
||||||
|
await fs.mkdir(SSH_KEYS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const { uid, gid } = os.userInfo();
|
||||||
|
const options = [
|
||||||
|
"reconnect",
|
||||||
|
"ServerAliveInterval=15",
|
||||||
|
"ServerAliveCountMax=3",
|
||||||
|
"allow_other",
|
||||||
|
`uid=${uid}`,
|
||||||
|
`gid=${gid}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (config.skipHostKeyCheck || !config.knownHosts) {
|
||||||
|
options.push("StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null");
|
||||||
|
} else if (config.knownHosts) {
|
||||||
|
const knownHostsPath = getKnownHostsPath(mountPath);
|
||||||
|
await fs.writeFile(knownHostsPath, config.knownHosts, { mode: 0o600 });
|
||||||
|
options.push(`UserKnownHostsFile=${knownHostsPath}`, "StrictHostKeyChecking=yes");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.readOnly) {
|
||||||
|
options.push("ro");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.port) {
|
||||||
|
options.push(`port=${config.port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPath = getPrivateKeyPath(mountPath);
|
||||||
|
if (config.privateKey) {
|
||||||
|
const decryptedKey = await cryptoUtils.resolveSecret(config.privateKey);
|
||||||
|
let normalizedKey = decryptedKey.replace(/\r\n/g, "\n");
|
||||||
|
if (!normalizedKey.endsWith("\n")) {
|
||||||
|
normalizedKey += "\n";
|
||||||
|
}
|
||||||
|
await fs.writeFile(keyPath, normalizedKey, { mode: 0o600 });
|
||||||
|
options.push(`IdentityFile=${keyPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = `${config.username}@${config.host}:${config.path || ""}`;
|
||||||
|
const args = [source, mountPath, "-o", options.join(",")];
|
||||||
|
|
||||||
|
logger.debug(`Mounting SFTP volume ${mountPath}...`);
|
||||||
|
|
||||||
|
let result: $.ShellOutput;
|
||||||
|
if (config.password) {
|
||||||
|
const password = await cryptoUtils.resolveSecret(config.password);
|
||||||
|
args.push("-o", "password_stdin");
|
||||||
|
logger.info(`Executing sshfs: echo "******" | sshfs ${args.join(" ")}`);
|
||||||
|
result = await $`echo ${password} | sshfs ${args}`.nothrow();
|
||||||
|
} else {
|
||||||
|
logger.info(`Executing sshfs: sshfs ${args.join(" ")}`);
|
||||||
|
result = await $`sshfs ${args}`.nothrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
const errorMsg = result.stderr.toString() || result.stdout.toString() || "Unknown error";
|
||||||
|
throw new Error(`Failed to mount SFTP volume: ${errorMsg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`SFTP volume at ${mountPath} mounted successfully.`);
|
||||||
|
return { status: BACKEND_STATUS.mounted };
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await withTimeout(run(), OPERATION_TIMEOUT * 2, "SFTP mount");
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = toMessage(error);
|
||||||
|
logger.error("Error mounting SFTP volume", { error: errorMsg });
|
||||||
|
return { status: BACKEND_STATUS.error, error: errorMsg };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unmount = async (mountPath: string) => {
|
||||||
|
if (os.platform() !== "linux") {
|
||||||
|
logger.error("SFTP unmounting is only supported on Linux hosts.");
|
||||||
|
return { status: BACKEND_STATUS.error, error: "SFTP unmounting is only supported on Linux hosts." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const mount = await getMountForPath(mountPath);
|
||||||
|
if (!mount || mount.mountPoint !== mountPath) {
|
||||||
|
logger.debug(`Path ${mountPath} is not a mount point. Skipping unmount.`);
|
||||||
|
} else {
|
||||||
|
await executeUnmount(mountPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPath = getPrivateKeyPath(mountPath);
|
||||||
|
await fs.unlink(keyPath).catch(() => {});
|
||||||
|
|
||||||
|
const knownHostsPath = getKnownHostsPath(mountPath);
|
||||||
|
await fs.unlink(knownHostsPath).catch(() => {});
|
||||||
|
|
||||||
|
await fs.rmdir(mountPath).catch(() => {});
|
||||||
|
|
||||||
|
logger.info(`SFTP volume at ${mountPath} unmounted successfully.`);
|
||||||
|
return { status: BACKEND_STATUS.unmounted };
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await withTimeout(run(), OPERATION_TIMEOUT, "SFTP unmount");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error unmounting SFTP volume", { mountPath, error: toMessage(error) });
|
||||||
|
return { status: BACKEND_STATUS.error, error: toMessage(error) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkHealth = async (mountPath: string) => {
|
||||||
|
const mount = await getMountForPath(mountPath);
|
||||||
|
|
||||||
|
if (!mount || mount.mountPoint !== mountPath) {
|
||||||
|
return { status: BACKEND_STATUS.unmounted };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mount.fstype !== "fuse.sshfs") {
|
||||||
|
return {
|
||||||
|
status: BACKEND_STATUS.error,
|
||||||
|
error: `Invalid filesystem type: ${mount.fstype} (expected fuse.sshfs)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: BACKEND_STATUS.mounted };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeSftpBackend = (config: BackendConfig, mountPath: string): VolumeBackend => ({
|
||||||
|
mount: () => mount(config, mountPath),
|
||||||
|
unmount: () => unmount(mountPath),
|
||||||
|
checkHealth: () => checkHealth(mountPath),
|
||||||
|
});
|
||||||
|
|
@ -39,13 +39,14 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
const password = await cryptoUtils.resolveSecret(config.password);
|
const password = await cryptoUtils.resolveSecret(config.password);
|
||||||
|
|
||||||
const source = `//${config.server}/${config.share}`;
|
const source = `//${config.server}/${config.share}`;
|
||||||
|
const { uid, gid } = os.userInfo();
|
||||||
const options = [
|
const options = [
|
||||||
`user=${config.username}`,
|
`user=${config.username}`,
|
||||||
`pass=${password}`,
|
`pass=${password}`,
|
||||||
`vers=${config.vers}`,
|
`vers=${config.vers}`,
|
||||||
`port=${config.port}`,
|
`port=${config.port}`,
|
||||||
"uid=1000",
|
`uid=${uid}`,
|
||||||
"gid=1000",
|
`gid=${gid}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (config.domain) {
|
if (config.domain) {
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,10 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
const port = config.port !== defaultPort ? `:${config.port}` : "";
|
const port = config.port !== defaultPort ? `:${config.port}` : "";
|
||||||
const source = `${protocol}://${config.server}${port}${config.path}`;
|
const source = `${protocol}://${config.server}${port}${config.path}`;
|
||||||
|
|
||||||
|
const { uid, gid } = os.userInfo();
|
||||||
const options = config.readOnly
|
const options = config.readOnly
|
||||||
? ["uid=1000", "gid=1000", "file_mode=0444", "dir_mode=0555", "ro"]
|
? [`uid=${uid}`, `gid=${gid}`, "file_mode=0444", "dir_mode=0555", "ro"]
|
||||||
: ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"];
|
: [`uid=${uid}`, `gid=${gid}`, "file_mode=0664", "dir_mode=0775"];
|
||||||
|
|
||||||
if (config.username && config.password) {
|
if (config.username && config.password) {
|
||||||
const password = await cryptoUtils.resolveSecret(config.password);
|
const password = await cryptoUtils.resolveSecret(config.password);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export type RetentionPolicy = typeof retentionPolicySchema.infer;
|
||||||
|
|
||||||
const backupScheduleSchema = type({
|
const backupScheduleSchema = type({
|
||||||
id: "number",
|
id: "number",
|
||||||
|
shortId: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
volumeId: "number",
|
volumeId: "number",
|
||||||
repositoryId: "string",
|
repositoryId: "string",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { notificationsService } from "../notifications/notifications.service";
|
||||||
import { repoMutex } from "../../core/repository-mutex";
|
import { repoMutex } from "../../core/repository-mutex";
|
||||||
import { checkMirrorCompatibility, getIncompatibleMirrorError } from "~/server/utils/backend-compatibility";
|
import { checkMirrorCompatibility, getIncompatibleMirrorError } from "~/server/utils/backend-compatibility";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { generateShortId } from "~/server/utils/id";
|
||||||
|
|
||||||
const runningBackups = new Map<number, AbortController>();
|
const runningBackups = new Map<number, AbortController>();
|
||||||
|
|
||||||
|
|
@ -126,6 +127,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
|
||||||
includePatterns: data.includePatterns ?? [],
|
includePatterns: data.includePatterns ?? [],
|
||||||
oneFileSystem: data.oneFileSystem,
|
oneFileSystem: data.oneFileSystem,
|
||||||
nextBackupAt: nextBackupAt,
|
nextBackupAt: nextBackupAt,
|
||||||
|
shortId: generateShortId(),
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|
@ -277,7 +279,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
||||||
oneFileSystem?: boolean;
|
oneFileSystem?: boolean;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
} = {
|
} = {
|
||||||
tags: [schedule.id.toString()],
|
tags: [schedule.shortId],
|
||||||
oneFileSystem: schedule.oneFileSystem,
|
oneFileSystem: schedule.oneFileSystem,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
};
|
};
|
||||||
|
|
@ -476,7 +478,7 @@ const runForget = async (scheduleId: number, repositoryId?: string) => {
|
||||||
logger.info(`running retention policy (forget) for schedule ${scheduleId}`);
|
logger.info(`running retention policy (forget) for schedule ${scheduleId}`);
|
||||||
const releaseLock = await repoMutex.acquireExclusive(repository.id, `forget:${scheduleId}`);
|
const releaseLock = await repoMutex.acquireExclusive(repository.id, `forget:${scheduleId}`);
|
||||||
try {
|
try {
|
||||||
await restic.forget(repository.config, schedule.retentionPolicy, { tag: schedule.id.toString() });
|
await restic.forget(repository.config, schedule.retentionPolicy, { tag: schedule.shortId });
|
||||||
} finally {
|
} finally {
|
||||||
releaseLock();
|
releaseLock();
|
||||||
}
|
}
|
||||||
|
|
@ -570,6 +572,14 @@ const copyToMirrors = async (
|
||||||
sourceRepository: { id: string; config: (typeof repositoriesTable.$inferSelect)["config"] },
|
sourceRepository: { id: string; config: (typeof repositoriesTable.$inferSelect)["config"] },
|
||||||
retentionPolicy: (typeof backupSchedulesTable.$inferSelect)["retentionPolicy"],
|
retentionPolicy: (typeof backupSchedulesTable.$inferSelect)["retentionPolicy"],
|
||||||
) => {
|
) => {
|
||||||
|
const schedule = await db.query.backupSchedulesTable.findFirst({
|
||||||
|
where: eq(backupSchedulesTable.id, scheduleId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!schedule) {
|
||||||
|
throw new NotFoundError("Backup schedule not found");
|
||||||
|
}
|
||||||
|
|
||||||
const mirrors = await db.query.backupScheduleMirrorsTable.findMany({
|
const mirrors = await db.query.backupScheduleMirrorsTable.findMany({
|
||||||
where: eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
|
where: eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
|
||||||
with: { repository: true },
|
with: { repository: true },
|
||||||
|
|
@ -599,7 +609,7 @@ const copyToMirrors = async (
|
||||||
const releaseMirror = await repoMutex.acquireShared(mirror.repository.id, `mirror:${scheduleId}`);
|
const releaseMirror = await repoMutex.acquireShared(mirror.repository.id, `mirror:${scheduleId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await restic.copy(sourceRepository.config, mirror.repository.config, { tag: scheduleId.toString() });
|
await restic.copy(sourceRepository.config, mirror.repository.config, { tag: schedule.shortId });
|
||||||
} finally {
|
} finally {
|
||||||
releaseSource();
|
releaseSource();
|
||||||
releaseMirror();
|
releaseMirror();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import * as fs from "node:fs/promises";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { repositoriesTable } from "../../db/schema";
|
import { repositoriesTable } from "../../db/schema";
|
||||||
import { VOLUME_MOUNT_BASE, REPOSITORY_BASE } from "../../core/constants";
|
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import { hasMigrationCheckpoint, recordMigrationCheckpoint } from "./checkpoint";
|
import { hasMigrationCheckpoint, recordMigrationCheckpoint } from "./checkpoint";
|
||||||
import type { RepositoryConfig } from "~/schemas/restic";
|
import { toMessage } from "~/server/utils/errors";
|
||||||
|
import { safeSpawn } from "~/server/utils/spawn";
|
||||||
|
import { addCommonArgs, buildEnv, buildRepoUrl, cleanupTemporaryKeys } from "~/server/utils/restic";
|
||||||
|
|
||||||
const MIGRATION_VERSION = "v0.14.0";
|
const MIGRATION_VERSION = "v0.21.0";
|
||||||
|
|
||||||
interface MigrationResult {
|
interface MigrationResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -28,19 +27,17 @@ export class MigrationError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const migrateToShortIds = async () => {
|
export const retagSnapshots = async () => {
|
||||||
const alreadyMigrated = await hasMigrationCheckpoint(MIGRATION_VERSION);
|
const alreadyMigrated = await hasMigrationCheckpoint(MIGRATION_VERSION);
|
||||||
if (alreadyMigrated) {
|
if (alreadyMigrated) {
|
||||||
logger.debug(`Migration ${MIGRATION_VERSION} already completed, skipping.`);
|
logger.debug(`Migration ${MIGRATION_VERSION} already completed, skipping.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Starting short ID migration (${MIGRATION_VERSION})...`);
|
logger.info(`Starting snapshots retagging migration (${MIGRATION_VERSION})...`);
|
||||||
|
|
||||||
const volumeResult = await migrateVolumeFolders();
|
const result = await migrateSnapshotsToShortIdTag();
|
||||||
const repoResult = await migrateRepositoryFolders();
|
const allErrors = [...result.errors];
|
||||||
|
|
||||||
const allErrors = [...volumeResult.errors, ...repoResult.errors];
|
|
||||||
|
|
||||||
if (allErrors.length > 0) {
|
if (allErrors.length > 0) {
|
||||||
for (const err of allErrors) {
|
for (const err of allErrors) {
|
||||||
|
|
@ -51,148 +48,45 @@ export const migrateToShortIds = async () => {
|
||||||
|
|
||||||
await recordMigrationCheckpoint(MIGRATION_VERSION);
|
await recordMigrationCheckpoint(MIGRATION_VERSION);
|
||||||
|
|
||||||
logger.info(`Short ID migration (${MIGRATION_VERSION}) complete.`);
|
logger.info(`Snapshots retagging migration (${MIGRATION_VERSION}) complete.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const migrateVolumeFolders = async (): Promise<MigrationResult> => {
|
const migrateSnapshotsToShortIdTag = async (): Promise<MigrationResult> => {
|
||||||
const errors: Array<{ name: string; error: string }> = [];
|
const errors: Array<{ name: string; error: string }> = [];
|
||||||
const volumes = await db.query.volumesTable.findMany({});
|
const backupSchedules = await db.query.backupSchedulesTable.findMany({});
|
||||||
|
|
||||||
for (const volume of volumes) {
|
for (const schedule of backupSchedules) {
|
||||||
if (volume.config.backend === "directory") {
|
const oldTag = schedule.id.toString();
|
||||||
|
const newTag = schedule.shortId;
|
||||||
|
|
||||||
|
const repository = await db.query.repositoriesTable.findFirst({
|
||||||
|
where: eq(repositoriesTable.id, schedule.repositoryId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!repository) {
|
||||||
|
errors.push({ name: `schedule:${schedule.name}`, error: `Associated repository not found` });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldPath = path.join(VOLUME_MOUNT_BASE, volume.name);
|
const repoUrl = buildRepoUrl(repository.config);
|
||||||
const newPath = path.join(VOLUME_MOUNT_BASE, volume.shortId);
|
const env = await buildEnv(repository.config);
|
||||||
|
|
||||||
const oldExists = await pathExists(oldPath);
|
const args = ["--repo", repoUrl, "tag", "--tag", oldTag, "--add", newTag, "--remove", oldTag];
|
||||||
const newExists = await pathExists(newPath);
|
|
||||||
|
|
||||||
if (oldExists && !newExists) {
|
addCommonArgs(args, env);
|
||||||
try {
|
|
||||||
logger.info(`Migrating volume folder: ${oldPath} -> ${newPath}`);
|
logger.info(`Migrating snapshots for schedule '${schedule.name}' from tag '${oldTag}' to '${newTag}'`);
|
||||||
await fs.rename(oldPath, newPath);
|
const res = await safeSpawn({ command: "restic", args, env });
|
||||||
logger.info(`Successfully migrated volume folder for "${volume.name}"`);
|
await cleanupTemporaryKeys(repository.config, env);
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
if (res.exitCode !== 0) {
|
||||||
errors.push({ name: `volume:${volume.name}`, error: errorMessage });
|
logger.error(`Restic tag failed: ${res.stderr}`);
|
||||||
}
|
errors.push({ name: `schedule:${schedule.name}`, error: `Restic tag command failed: ${toMessage(res.stderr)}` });
|
||||||
} else if (oldExists && newExists) {
|
continue;
|
||||||
logger.warn(
|
|
||||||
`Both old (${oldPath}) and new (${newPath}) paths exist for volume "${volume.name}". Manual intervention may be required.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`Migrated snapshots for schedule '${schedule.name}' from tag '${oldTag}' to '${newTag}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: errors.length === 0, errors };
|
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: Date.now(),
|
|
||||||
})
|
|
||||||
.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: Date.now(),
|
|
||||||
})
|
|
||||||
.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: Date.now(),
|
|
||||||
})
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import { eq, or } from "drizzle-orm";
|
import { eq, or } from "drizzle-orm";
|
||||||
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { InternalServerError, NotFoundError } from "http-errors-enhanced";
|
||||||
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";
|
||||||
|
|
@ -186,7 +186,7 @@ const listSnapshots = async (id: string, backupId?: string) => {
|
||||||
let snapshots = [];
|
let snapshots = [];
|
||||||
|
|
||||||
if (backupId) {
|
if (backupId) {
|
||||||
snapshots = await restic.snapshots(repository.config, { tags: [backupId.toString()] });
|
snapshots = await restic.snapshots(repository.config, { tags: [backupId] });
|
||||||
} else {
|
} else {
|
||||||
snapshots = await restic.snapshots(repository.config);
|
snapshots = await restic.snapshots(repository.config);
|
||||||
}
|
}
|
||||||
|
|
@ -421,15 +421,6 @@ const updateRepository = async (id: string, updates: { name?: string; compressio
|
||||||
throw new NotFoundError("Repository not found");
|
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newConfig = repositoryConfigSchema(existing.config);
|
const newConfig = repositoryConfigSchema(existing.config);
|
||||||
if (newConfig instanceof type.errors) {
|
if (newConfig instanceof type.errors) {
|
||||||
throw new InternalServerError("Invalid repository configuration");
|
throw new InternalServerError("Invalid repository configuration");
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ async function encryptSensitiveFields(config: BackendConfig): Promise<BackendCon
|
||||||
...config,
|
...config,
|
||||||
password: config.password ? await cryptoUtils.sealSecret(config.password) : undefined,
|
password: config.password ? await cryptoUtils.sealSecret(config.password) : undefined,
|
||||||
};
|
};
|
||||||
|
case "sftp":
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
password: config.password ? await cryptoUtils.sealSecret(config.password) : undefined,
|
||||||
|
privateKey: config.privateKey ? await cryptoUtils.sealSecret(config.privateKey) : undefined,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
export const generateShortId = (length = 5): string => {
|
export const generateShortId = (length = 8): string => {
|
||||||
const bytesNeeded = Math.ceil((length * 3) / 4);
|
const bytesNeeded = Math.ceil((length * 3) / 4);
|
||||||
return crypto.randomBytes(bytesNeeded).toString("base64url").slice(0, length).toLowerCase();
|
return crypto.randomBytes(bytesNeeded).toString("base64url").slice(0, length);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import os from "node:os";
|
||||||
import { throttle } from "es-toolkit";
|
import { throttle } from "es-toolkit";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { REPOSITORY_BASE, RESTIC_PASS_FILE, DEFAULT_EXCLUDES } from "../core/constants";
|
import { REPOSITORY_BASE, RESTIC_PASS_FILE, DEFAULT_EXCLUDES } from "../core/constants";
|
||||||
|
import { config as appConfig } from "../core/config";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { cryptoUtils } from "./crypto";
|
import { cryptoUtils } from "./crypto";
|
||||||
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
||||||
|
|
@ -71,9 +72,14 @@ const ensurePassfile = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildRepoUrl = (config: RepositoryConfig): string => {
|
export const buildRepoUrl = (config: RepositoryConfig): string => {
|
||||||
switch (config.backend) {
|
switch (config.backend) {
|
||||||
case "local":
|
case "local":
|
||||||
|
if (config.isExistingRepository) {
|
||||||
|
if (!config.path) throw new Error("Path is required for existing local repositories");
|
||||||
|
return config.path;
|
||||||
|
}
|
||||||
|
|
||||||
return config.path ? `${config.path}/${config.name}` : `${REPOSITORY_BASE}/${config.name}`;
|
return config.path ? `${config.path}/${config.name}` : `${REPOSITORY_BASE}/${config.name}`;
|
||||||
case "s3":
|
case "s3":
|
||||||
return `s3:${config.endpoint}/${config.bucket}`;
|
return `s3:${config.endpoint}/${config.bucket}`;
|
||||||
|
|
@ -99,7 +105,7 @@ const buildRepoUrl = (config: RepositoryConfig): string => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildEnv = async (config: RepositoryConfig) => {
|
export const buildEnv = async (config: RepositoryConfig) => {
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
RESTIC_CACHE_DIR: "/var/lib/zerobyte/restic/cache",
|
RESTIC_CACHE_DIR: "/var/lib/zerobyte/restic/cache",
|
||||||
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
||||||
|
|
@ -153,7 +159,7 @@ const buildEnv = async (config: RepositoryConfig) => {
|
||||||
}
|
}
|
||||||
case "sftp": {
|
case "sftp": {
|
||||||
const decryptedKey = await cryptoUtils.resolveSecret(config.privateKey);
|
const decryptedKey = await cryptoUtils.resolveSecret(config.privateKey);
|
||||||
const keyPath = path.join("/tmp", `ironmount-ssh-${crypto.randomBytes(8).toString("hex")}`);
|
const keyPath = path.join("/tmp", `zerobyte-ssh-${crypto.randomBytes(8).toString("hex")}`);
|
||||||
|
|
||||||
let normalizedKey = decryptedKey.replace(/\r\n/g, "\n");
|
let normalizedKey = decryptedKey.replace(/\r\n/g, "\n");
|
||||||
if (!normalizedKey.endsWith("\n")) {
|
if (!normalizedKey.endsWith("\n")) {
|
||||||
|
|
@ -170,10 +176,6 @@ const buildEnv = async (config: RepositoryConfig) => {
|
||||||
env._SFTP_KEY_PATH = keyPath;
|
env._SFTP_KEY_PATH = keyPath;
|
||||||
|
|
||||||
const sshArgs = [
|
const sshArgs = [
|
||||||
"-o",
|
|
||||||
"StrictHostKeyChecking=no",
|
|
||||||
"-o",
|
|
||||||
"UserKnownHostsFile=/dev/null",
|
|
||||||
"-o",
|
"-o",
|
||||||
"LogLevel=VERBOSE",
|
"LogLevel=VERBOSE",
|
||||||
"-o",
|
"-o",
|
||||||
|
|
@ -184,6 +186,15 @@ const buildEnv = async (config: RepositoryConfig) => {
|
||||||
keyPath,
|
keyPath,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (config.skipHostKeyCheck || !config.knownHosts) {
|
||||||
|
sshArgs.push("-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null");
|
||||||
|
} else if (config.knownHosts) {
|
||||||
|
const knownHostsPath = path.join("/tmp", `zerobyte-known-hosts-${crypto.randomBytes(8).toString("hex")}`);
|
||||||
|
await fs.writeFile(knownHostsPath, config.knownHosts, { mode: 0o600 });
|
||||||
|
env._SFTP_KNOWN_HOSTS_PATH = knownHostsPath;
|
||||||
|
sshArgs.push("-o", "StrictHostKeyChecking=yes", "-o", `UserKnownHostsFile=${knownHostsPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.port && config.port !== 22) {
|
if (config.port && config.port !== 22) {
|
||||||
sshArgs.push("-p", String(config.port));
|
sshArgs.push("-p", String(config.port));
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +268,10 @@ const backup = async (
|
||||||
args.push("--one-file-system");
|
args.push("--one-file-system");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appConfig.resticHostname) {
|
||||||
|
args.push("--host", appConfig.resticHostname);
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.tags && options.tags.length > 0) {
|
if (options?.tags && options.tags.length > 0) {
|
||||||
for (const tag of options.tags) {
|
for (const tag of options.tags) {
|
||||||
args.push("--tag", tag);
|
args.push("--tag", tag);
|
||||||
|
|
@ -795,9 +810,14 @@ const copy = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record<string, string>) => {
|
export const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record<string, string>) => {
|
||||||
if (config.backend === "sftp" && env._SFTP_KEY_PATH) {
|
if (config.backend === "sftp") {
|
||||||
|
if (env._SFTP_KEY_PATH) {
|
||||||
await fs.unlink(env._SFTP_KEY_PATH).catch(() => {});
|
await fs.unlink(env._SFTP_KEY_PATH).catch(() => {});
|
||||||
|
}
|
||||||
|
if (env._SFTP_KNOWN_HOSTS_PATH) {
|
||||||
|
await fs.unlink(env._SFTP_KNOWN_HOSTS_PATH).catch(() => {});
|
||||||
|
}
|
||||||
} else if (config.isExistingRepository && config.customPassword && env.RESTIC_PASSWORD_FILE) {
|
} else if (config.isExistingRepository && config.customPassword && env.RESTIC_PASSWORD_FILE) {
|
||||||
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||||
} else if (config.backend === "gcs" && env.GOOGLE_APPLICATION_CREDENTIALS) {
|
} else if (config.backend === "gcs" && env.GOOGLE_APPLICATION_CREDENTIALS) {
|
||||||
|
|
@ -805,7 +825,7 @@ const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record<string
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCommonArgs = (args: string[], env: Record<string, string>) => {
|
export const addCommonArgs = (args: string[], env: Record<string, string>) => {
|
||||||
args.push("--json");
|
args.push("--json");
|
||||||
|
|
||||||
if (env._SFTP_SSH_ARGS) {
|
if (env._SFTP_SSH_ARGS) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export const createTestBackupSchedule = async (overrides: Partial<BackupSchedule
|
||||||
cronExpression: "0 0 * * *",
|
cronExpression: "0 0 * * *",
|
||||||
repositoryId: "repo_123",
|
repositoryId: "repo_123",
|
||||||
volumeId: 1,
|
volumeId: 1,
|
||||||
|
shortId: faker.string.uuid(),
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue