linting and SFTP volume support
This commit is contained in:
parent
5cdec5cdaa
commit
e4f66de447
3 changed files with 277 additions and 227 deletions
|
|
@ -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 { InternalServerError, NotFoundError } from "http-errors-enhanced";
|
import { ConflictError, 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";
|
||||||
|
|
@ -74,7 +74,9 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
|
||||||
where: eq(repositoriesTable.shortId, shortId),
|
where: eq(repositoriesTable.shortId, shortId),
|
||||||
});
|
});
|
||||||
if (existingByShortId) {
|
if (existingByShortId) {
|
||||||
throw new ConflictError(`A repository with shortId '${shortId}' already exists. The shortId is used as the subdirectory name for local repositories.`);
|
throw new ConflictError(
|
||||||
|
`A repository with shortId '${shortId}' already exists. The shortId is used as the subdirectory name for local repositories.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shortId = generateShortId();
|
shortId = generateShortId();
|
||||||
|
|
@ -95,13 +97,13 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
|
||||||
if (repoExists && !config.isExistingRepository) {
|
if (repoExists && !config.isExistingRepository) {
|
||||||
throw new ConflictError(
|
throw new ConflictError(
|
||||||
`A restic repository already exists at this location. ` +
|
`A restic repository already exists at this location. ` +
|
||||||
`If you want to use the existing repository, set "isExistingRepository": true in the config.`
|
`If you want to use the existing repository, set "isExistingRepository": true in the config.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!repoExists && config.isExistingRepository) {
|
if (!repoExists && config.isExistingRepository) {
|
||||||
throw new InternalServerError(
|
throw new InternalServerError(
|
||||||
`Cannot access existing repository. Verify the path/credentials are correct and the repository exists.`
|
`Cannot access existing repository. Verify the path/credentials are correct and the repository exists.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,43 @@ This example is intended to be the primary, copy/paste-friendly reference for co
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### SFTP
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "sftp-volume",
|
||||||
|
"config": {
|
||||||
|
"backend": "sftp",
|
||||||
|
"host": "sftp.example.com",
|
||||||
|
"port": 22,
|
||||||
|
"username": "user",
|
||||||
|
"password": "${SFTP_PASSWORD}",
|
||||||
|
"path": "/data",
|
||||||
|
"readOnly": false,
|
||||||
|
"skipHostKeyCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For key-based authentication:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "sftp-volume-key",
|
||||||
|
"config": {
|
||||||
|
"backend": "sftp",
|
||||||
|
"host": "sftp.example.com",
|
||||||
|
"port": 22,
|
||||||
|
"username": "user",
|
||||||
|
"privateKey": "${SFTP_PRIVATE_KEY}",
|
||||||
|
"path": "/data",
|
||||||
|
"readOnly": false,
|
||||||
|
"skipHostKeyCheck": false,
|
||||||
|
"knownHosts": "sftp.example.com ssh-ed25519 AAAA..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Repository types
|
### Repository types
|
||||||
|
|
||||||
#### Local
|
#### Local
|
||||||
|
|
|
||||||
|
|
@ -1,225 +1,236 @@
|
||||||
{
|
{
|
||||||
"volumes": [
|
"volumes": [
|
||||||
{
|
{
|
||||||
"name": "local-volume",
|
"name": "local-volume",
|
||||||
"config": {
|
"config": {
|
||||||
"backend": "directory",
|
"backend": "directory",
|
||||||
"path": "/mydata",
|
"path": "/mydata",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nfs-volume",
|
"name": "nfs-volume",
|
||||||
"config": {
|
"config": {
|
||||||
"backend": "nfs",
|
"backend": "nfs",
|
||||||
"server": "nfs.example.com",
|
"server": "nfs.example.com",
|
||||||
"exportPath": "/data",
|
"exportPath": "/data",
|
||||||
"port": 2049,
|
"port": 2049,
|
||||||
"version": "4",
|
"version": "4",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "smb-volume",
|
"name": "smb-volume",
|
||||||
"config": {
|
"config": {
|
||||||
"backend": "smb",
|
"backend": "smb",
|
||||||
"server": "smb.example.com",
|
"server": "smb.example.com",
|
||||||
"share": "shared",
|
"share": "shared",
|
||||||
"username": "user",
|
"username": "user",
|
||||||
"password": "${SMB_PASSWORD}",
|
"password": "${SMB_PASSWORD}",
|
||||||
"vers": "3.0",
|
"vers": "3.0",
|
||||||
"domain": "WORKGROUP",
|
"domain": "WORKGROUP",
|
||||||
"port": 445,
|
"port": 445,
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webdav-volume",
|
"name": "webdav-volume",
|
||||||
"config": {
|
"config": {
|
||||||
"backend": "webdav",
|
"backend": "webdav",
|
||||||
"server": "webdav.example.com",
|
"server": "webdav.example.com",
|
||||||
"path": "/remote.php/webdav",
|
"path": "/remote.php/webdav",
|
||||||
"username": "user",
|
"username": "user",
|
||||||
"password": "${WEBDAV_PASSWORD}",
|
"password": "${WEBDAV_PASSWORD}",
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
"ssl": true
|
"ssl": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"repositories": [
|
"name": "sftp-volume",
|
||||||
{
|
"config": {
|
||||||
"name": "local-repo",
|
"backend": "sftp",
|
||||||
"config": {
|
"host": "sftp.example.com",
|
||||||
"backend": "local",
|
"port": 22,
|
||||||
"path": "/var/lib/zerobyte/repositories"
|
"username": "user",
|
||||||
},
|
"password": "${SFTP_PASSWORD}",
|
||||||
"compressionMode": "auto"
|
"path": "/data",
|
||||||
},
|
"readOnly": false,
|
||||||
{
|
"skipHostKeyCheck": true
|
||||||
"name": "s3-repo",
|
}
|
||||||
"config": {
|
}
|
||||||
"backend": "s3",
|
],
|
||||||
"bucket": "mybucket",
|
"repositories": [
|
||||||
"accessKeyId": "${ACCESS_KEY_ID}",
|
{
|
||||||
"secretAccessKey": "${SECRET_ACCESS_KEY}"
|
"name": "local-repo",
|
||||||
},
|
"config": {
|
||||||
"compressionMode": "auto"
|
"backend": "local",
|
||||||
},
|
"path": "/var/lib/zerobyte/repositories"
|
||||||
{
|
},
|
||||||
"name": "gcs-repo",
|
"compressionMode": "auto"
|
||||||
"config": {
|
},
|
||||||
"backend": "gcs",
|
{
|
||||||
"bucket": "mybucket",
|
"name": "s3-repo",
|
||||||
"projectId": "my-gcp-project",
|
"config": {
|
||||||
"credentialsJson": "${GCS_CREDENTIALS}"
|
"backend": "s3",
|
||||||
}
|
"bucket": "mybucket",
|
||||||
},
|
"accessKeyId": "${ACCESS_KEY_ID}",
|
||||||
{
|
"secretAccessKey": "${SECRET_ACCESS_KEY}"
|
||||||
"name": "azure-repo",
|
},
|
||||||
"config": {
|
"compressionMode": "auto"
|
||||||
"backend": "azure",
|
},
|
||||||
"container": "mycontainer",
|
{
|
||||||
"accountName": "myaccount",
|
"name": "gcs-repo",
|
||||||
"accountKey": "${AZURE_KEY}"
|
"config": {
|
||||||
}
|
"backend": "gcs",
|
||||||
},
|
"bucket": "mybucket",
|
||||||
{
|
"projectId": "my-gcp-project",
|
||||||
"name": "rclone-repo",
|
"credentialsJson": "${GCS_CREDENTIALS}"
|
||||||
"config": {
|
}
|
||||||
"backend": "rclone",
|
},
|
||||||
"remote": "myremote",
|
{
|
||||||
"path": "backups/zerobyte"
|
"name": "azure-repo",
|
||||||
}
|
"config": {
|
||||||
},
|
"backend": "azure",
|
||||||
{
|
"container": "mycontainer",
|
||||||
"name": "webdav-repo",
|
"accountName": "myaccount",
|
||||||
"config": {
|
"accountKey": "${AZURE_KEY}"
|
||||||
"backend": "webdav",
|
}
|
||||||
"server": "webdav.example.com",
|
},
|
||||||
"path": "/remote.php/webdav",
|
{
|
||||||
"username": "user",
|
"name": "rclone-repo",
|
||||||
"password": "${WEBDAV_PASSWORD}",
|
"config": {
|
||||||
"port": 80,
|
"backend": "rclone",
|
||||||
"ssl": true
|
"remote": "myremote",
|
||||||
}
|
"path": "backups/zerobyte"
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"name": "sftp-repo",
|
{
|
||||||
"config": {
|
"name": "webdav-repo",
|
||||||
"backend": "sftp",
|
"config": {
|
||||||
"host": "sftp.example.com",
|
"backend": "webdav",
|
||||||
"port": 22,
|
"server": "webdav.example.com",
|
||||||
"user": "sftpuser",
|
"path": "/remote.php/webdav",
|
||||||
"privateKey": "${SFTP_PRIVATE_KEY}",
|
"username": "user",
|
||||||
"path": "/backups"
|
"password": "${WEBDAV_PASSWORD}",
|
||||||
}
|
"port": 80,
|
||||||
}
|
"ssl": true
|
||||||
],
|
}
|
||||||
"backupSchedules": [
|
},
|
||||||
{
|
{
|
||||||
"name": "local-volume-local-repo",
|
"name": "sftp-repo",
|
||||||
"volume": "local-volume",
|
"config": {
|
||||||
"repository": "local-repo",
|
"backend": "sftp",
|
||||||
"cronExpression": "0 2 * * *",
|
"host": "sftp.example.com",
|
||||||
"retentionPolicy": { "keepLast": 7, "keepDaily": 7 },
|
"port": 22,
|
||||||
"includePatterns": ["important-folder"],
|
"user": "sftpuser",
|
||||||
"excludePatterns": ["*.tmp", "*.log"],
|
"privateKey": "${SFTP_PRIVATE_KEY}",
|
||||||
"excludeIfPresent": [".nobackup"],
|
"path": "/backups"
|
||||||
"oneFileSystem": false,
|
}
|
||||||
"enabled": true,
|
}
|
||||||
"notifications": ["slack-alerts"],
|
],
|
||||||
"mirrors": [
|
"backupSchedules": [
|
||||||
{ "repository": "s3-repo" }
|
{
|
||||||
]
|
"name": "local-volume-local-repo",
|
||||||
}
|
"volume": "local-volume",
|
||||||
],
|
"repository": "local-repo",
|
||||||
"notificationDestinations": [
|
"cronExpression": "0 2 * * *",
|
||||||
{
|
"retentionPolicy": { "keepLast": 7, "keepDaily": 7 },
|
||||||
"name": "slack-alerts",
|
"includePatterns": ["important-folder"],
|
||||||
"config": {
|
"excludePatterns": ["*.tmp", "*.log"],
|
||||||
"type": "slack",
|
"excludeIfPresent": [".nobackup"],
|
||||||
"webhookUrl": "${SLACK_WEBHOOK_URL}",
|
"oneFileSystem": false,
|
||||||
"channel": "#backups",
|
"enabled": true,
|
||||||
"username": "zerobyte",
|
"notifications": ["slack-alerts"],
|
||||||
"iconEmoji": ":floppy_disk:"
|
"mirrors": [{ "repository": "s3-repo" }]
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"notificationDestinations": [
|
||||||
"name": "email-admin",
|
{
|
||||||
"config": {
|
"name": "slack-alerts",
|
||||||
"type": "email",
|
"config": {
|
||||||
"smtpHost": "smtp.example.com",
|
"type": "slack",
|
||||||
"smtpPort": 587,
|
"webhookUrl": "${SLACK_WEBHOOK_URL}",
|
||||||
"username": "admin@example.com",
|
"channel": "#backups",
|
||||||
"password": "${EMAIL_PASSWORD}",
|
"username": "zerobyte",
|
||||||
"from": "zerobyte@example.com",
|
"iconEmoji": ":floppy_disk:"
|
||||||
"to": ["admin@example.com"],
|
}
|
||||||
"useTLS": true
|
},
|
||||||
}
|
{
|
||||||
},
|
"name": "email-admin",
|
||||||
{
|
"config": {
|
||||||
"name": "discord-backups",
|
"type": "email",
|
||||||
"config": {
|
"smtpHost": "smtp.example.com",
|
||||||
"type": "discord",
|
"smtpPort": 587,
|
||||||
"webhookUrl": "${DISCORD_WEBHOOK_URL}",
|
"username": "admin@example.com",
|
||||||
"username": "zerobyte",
|
"password": "${EMAIL_PASSWORD}",
|
||||||
"avatarUrl": "https://example.com/avatar.png",
|
"from": "zerobyte@example.com",
|
||||||
"threadId": "1234567890"
|
"to": ["admin@example.com"],
|
||||||
}
|
"useTLS": true
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"name": "gotify-notify",
|
{
|
||||||
"config": {
|
"name": "discord-backups",
|
||||||
"type": "gotify",
|
"config": {
|
||||||
"serverUrl": "https://gotify.example.com",
|
"type": "discord",
|
||||||
"token": "${GOTIFY_TOKEN}",
|
"webhookUrl": "${DISCORD_WEBHOOK_URL}",
|
||||||
"path": "/message",
|
"username": "zerobyte",
|
||||||
"priority": 5
|
"avatarUrl": "https://example.com/avatar.png",
|
||||||
}
|
"threadId": "1234567890"
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"name": "ntfy-notify",
|
{
|
||||||
"config": {
|
"name": "gotify-notify",
|
||||||
"type": "ntfy",
|
"config": {
|
||||||
"serverUrl": "https://ntfy.example.com",
|
"type": "gotify",
|
||||||
"topic": "zerobyte-backups",
|
"serverUrl": "https://gotify.example.com",
|
||||||
"priority": "high",
|
"token": "${GOTIFY_TOKEN}",
|
||||||
"username": "ntfyuser",
|
"path": "/message",
|
||||||
"password": "${NTFY_PASSWORD}"
|
"priority": 5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pushover-notify",
|
"name": "ntfy-notify",
|
||||||
"config": {
|
"config": {
|
||||||
"type": "pushover",
|
"type": "ntfy",
|
||||||
"userKey": "${PUSHOVER_USER_KEY}",
|
"serverUrl": "https://ntfy.example.com",
|
||||||
"apiToken": "${PUSHOVER_API_TOKEN}",
|
"topic": "zerobyte-backups",
|
||||||
"devices": "phone,tablet",
|
"priority": "high",
|
||||||
"priority": 1
|
"username": "ntfyuser",
|
||||||
}
|
"password": "${NTFY_PASSWORD}"
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"name": "telegram-notify",
|
{
|
||||||
"config": {
|
"name": "pushover-notify",
|
||||||
"type": "telegram",
|
"config": {
|
||||||
"botToken": "${TELEGRAM_BOT_TOKEN}",
|
"type": "pushover",
|
||||||
"chatId": "123456789"
|
"userKey": "${PUSHOVER_USER_KEY}",
|
||||||
}
|
"apiToken": "${PUSHOVER_API_TOKEN}",
|
||||||
},
|
"devices": "phone,tablet",
|
||||||
{
|
"priority": 1
|
||||||
"name": "custom-shoutrrr",
|
}
|
||||||
"config": {
|
},
|
||||||
"type": "custom",
|
{
|
||||||
"shoutrrrUrl": "${SHOUTRRR_URL}"
|
"name": "telegram-notify",
|
||||||
}
|
"config": {
|
||||||
}
|
"type": "telegram",
|
||||||
],
|
"botToken": "${TELEGRAM_BOT_TOKEN}",
|
||||||
"recoveryKey": "${RECOVERY_KEY}",
|
"chatId": "123456789"
|
||||||
"users": [
|
}
|
||||||
{
|
},
|
||||||
"username": "admin",
|
{
|
||||||
"password": "${ADMIN_PASSWORD}"
|
"name": "custom-shoutrrr",
|
||||||
}
|
"config": {
|
||||||
]
|
"type": "custom",
|
||||||
|
"shoutrrrUrl": "${SHOUTRRR_URL}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recoveryKey": "${RECOVERY_KEY}",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "${ADMIN_PASSWORD}"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue