linting and SFTP volume support

This commit is contained in:
Jakub Trávník 2025-12-29 11:16:36 +01:00
parent 5cdec5cdaa
commit e4f66de447
3 changed files with 277 additions and 227 deletions

View file

@ -1,6 +1,6 @@
import crypto from "node:crypto";
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 { repositoriesTable } from "../../db/schema";
import { toMessage } from "../../utils/errors";
@ -74,7 +74,9 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
where: eq(repositoriesTable.shortId, shortId),
});
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 {
shortId = generateShortId();
@ -95,13 +97,13 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
if (repoExists && !config.isExistingRepository) {
throw new ConflictError(
`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) {
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.`,
);
}

View file

@ -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
#### Local

View file

@ -1,225 +1,236 @@
{
"volumes": [
{
"name": "local-volume",
"config": {
"backend": "directory",
"path": "/mydata",
"readOnly": true
}
},
{
"name": "nfs-volume",
"config": {
"backend": "nfs",
"server": "nfs.example.com",
"exportPath": "/data",
"port": 2049,
"version": "4",
"readOnly": false
}
},
{
"name": "smb-volume",
"config": {
"backend": "smb",
"server": "smb.example.com",
"share": "shared",
"username": "user",
"password": "${SMB_PASSWORD}",
"vers": "3.0",
"domain": "WORKGROUP",
"port": 445,
"readOnly": false
}
},
{
"name": "webdav-volume",
"config": {
"backend": "webdav",
"server": "webdav.example.com",
"path": "/remote.php/webdav",
"username": "user",
"password": "${WEBDAV_PASSWORD}",
"port": 80,
"readOnly": false,
"ssl": true
}
}
],
"repositories": [
{
"name": "local-repo",
"config": {
"backend": "local",
"path": "/var/lib/zerobyte/repositories"
},
"compressionMode": "auto"
},
{
"name": "s3-repo",
"config": {
"backend": "s3",
"bucket": "mybucket",
"accessKeyId": "${ACCESS_KEY_ID}",
"secretAccessKey": "${SECRET_ACCESS_KEY}"
},
"compressionMode": "auto"
},
{
"name": "gcs-repo",
"config": {
"backend": "gcs",
"bucket": "mybucket",
"projectId": "my-gcp-project",
"credentialsJson": "${GCS_CREDENTIALS}"
}
},
{
"name": "azure-repo",
"config": {
"backend": "azure",
"container": "mycontainer",
"accountName": "myaccount",
"accountKey": "${AZURE_KEY}"
}
},
{
"name": "rclone-repo",
"config": {
"backend": "rclone",
"remote": "myremote",
"path": "backups/zerobyte"
}
},
{
"name": "webdav-repo",
"config": {
"backend": "webdav",
"server": "webdav.example.com",
"path": "/remote.php/webdav",
"username": "user",
"password": "${WEBDAV_PASSWORD}",
"port": 80,
"ssl": true
}
},
{
"name": "sftp-repo",
"config": {
"backend": "sftp",
"host": "sftp.example.com",
"port": 22,
"user": "sftpuser",
"privateKey": "${SFTP_PRIVATE_KEY}",
"path": "/backups"
}
}
],
"backupSchedules": [
{
"name": "local-volume-local-repo",
"volume": "local-volume",
"repository": "local-repo",
"cronExpression": "0 2 * * *",
"retentionPolicy": { "keepLast": 7, "keepDaily": 7 },
"includePatterns": ["important-folder"],
"excludePatterns": ["*.tmp", "*.log"],
"excludeIfPresent": [".nobackup"],
"oneFileSystem": false,
"enabled": true,
"notifications": ["slack-alerts"],
"mirrors": [
{ "repository": "s3-repo" }
]
}
],
"notificationDestinations": [
{
"name": "slack-alerts",
"config": {
"type": "slack",
"webhookUrl": "${SLACK_WEBHOOK_URL}",
"channel": "#backups",
"username": "zerobyte",
"iconEmoji": ":floppy_disk:"
}
},
{
"name": "email-admin",
"config": {
"type": "email",
"smtpHost": "smtp.example.com",
"smtpPort": 587,
"username": "admin@example.com",
"password": "${EMAIL_PASSWORD}",
"from": "zerobyte@example.com",
"to": ["admin@example.com"],
"useTLS": true
}
},
{
"name": "discord-backups",
"config": {
"type": "discord",
"webhookUrl": "${DISCORD_WEBHOOK_URL}",
"username": "zerobyte",
"avatarUrl": "https://example.com/avatar.png",
"threadId": "1234567890"
}
},
{
"name": "gotify-notify",
"config": {
"type": "gotify",
"serverUrl": "https://gotify.example.com",
"token": "${GOTIFY_TOKEN}",
"path": "/message",
"priority": 5
}
},
{
"name": "ntfy-notify",
"config": {
"type": "ntfy",
"serverUrl": "https://ntfy.example.com",
"topic": "zerobyte-backups",
"priority": "high",
"username": "ntfyuser",
"password": "${NTFY_PASSWORD}"
}
},
{
"name": "pushover-notify",
"config": {
"type": "pushover",
"userKey": "${PUSHOVER_USER_KEY}",
"apiToken": "${PUSHOVER_API_TOKEN}",
"devices": "phone,tablet",
"priority": 1
}
},
{
"name": "telegram-notify",
"config": {
"type": "telegram",
"botToken": "${TELEGRAM_BOT_TOKEN}",
"chatId": "123456789"
}
},
{
"name": "custom-shoutrrr",
"config": {
"type": "custom",
"shoutrrrUrl": "${SHOUTRRR_URL}"
}
}
],
"recoveryKey": "${RECOVERY_KEY}",
"users": [
{
"username": "admin",
"password": "${ADMIN_PASSWORD}"
}
]
"volumes": [
{
"name": "local-volume",
"config": {
"backend": "directory",
"path": "/mydata",
"readOnly": true
}
},
{
"name": "nfs-volume",
"config": {
"backend": "nfs",
"server": "nfs.example.com",
"exportPath": "/data",
"port": 2049,
"version": "4",
"readOnly": false
}
},
{
"name": "smb-volume",
"config": {
"backend": "smb",
"server": "smb.example.com",
"share": "shared",
"username": "user",
"password": "${SMB_PASSWORD}",
"vers": "3.0",
"domain": "WORKGROUP",
"port": 445,
"readOnly": false
}
},
{
"name": "webdav-volume",
"config": {
"backend": "webdav",
"server": "webdav.example.com",
"path": "/remote.php/webdav",
"username": "user",
"password": "${WEBDAV_PASSWORD}",
"port": 80,
"readOnly": false,
"ssl": true
}
},
{
"name": "sftp-volume",
"config": {
"backend": "sftp",
"host": "sftp.example.com",
"port": 22,
"username": "user",
"password": "${SFTP_PASSWORD}",
"path": "/data",
"readOnly": false,
"skipHostKeyCheck": true
}
}
],
"repositories": [
{
"name": "local-repo",
"config": {
"backend": "local",
"path": "/var/lib/zerobyte/repositories"
},
"compressionMode": "auto"
},
{
"name": "s3-repo",
"config": {
"backend": "s3",
"bucket": "mybucket",
"accessKeyId": "${ACCESS_KEY_ID}",
"secretAccessKey": "${SECRET_ACCESS_KEY}"
},
"compressionMode": "auto"
},
{
"name": "gcs-repo",
"config": {
"backend": "gcs",
"bucket": "mybucket",
"projectId": "my-gcp-project",
"credentialsJson": "${GCS_CREDENTIALS}"
}
},
{
"name": "azure-repo",
"config": {
"backend": "azure",
"container": "mycontainer",
"accountName": "myaccount",
"accountKey": "${AZURE_KEY}"
}
},
{
"name": "rclone-repo",
"config": {
"backend": "rclone",
"remote": "myremote",
"path": "backups/zerobyte"
}
},
{
"name": "webdav-repo",
"config": {
"backend": "webdav",
"server": "webdav.example.com",
"path": "/remote.php/webdav",
"username": "user",
"password": "${WEBDAV_PASSWORD}",
"port": 80,
"ssl": true
}
},
{
"name": "sftp-repo",
"config": {
"backend": "sftp",
"host": "sftp.example.com",
"port": 22,
"user": "sftpuser",
"privateKey": "${SFTP_PRIVATE_KEY}",
"path": "/backups"
}
}
],
"backupSchedules": [
{
"name": "local-volume-local-repo",
"volume": "local-volume",
"repository": "local-repo",
"cronExpression": "0 2 * * *",
"retentionPolicy": { "keepLast": 7, "keepDaily": 7 },
"includePatterns": ["important-folder"],
"excludePatterns": ["*.tmp", "*.log"],
"excludeIfPresent": [".nobackup"],
"oneFileSystem": false,
"enabled": true,
"notifications": ["slack-alerts"],
"mirrors": [{ "repository": "s3-repo" }]
}
],
"notificationDestinations": [
{
"name": "slack-alerts",
"config": {
"type": "slack",
"webhookUrl": "${SLACK_WEBHOOK_URL}",
"channel": "#backups",
"username": "zerobyte",
"iconEmoji": ":floppy_disk:"
}
},
{
"name": "email-admin",
"config": {
"type": "email",
"smtpHost": "smtp.example.com",
"smtpPort": 587,
"username": "admin@example.com",
"password": "${EMAIL_PASSWORD}",
"from": "zerobyte@example.com",
"to": ["admin@example.com"],
"useTLS": true
}
},
{
"name": "discord-backups",
"config": {
"type": "discord",
"webhookUrl": "${DISCORD_WEBHOOK_URL}",
"username": "zerobyte",
"avatarUrl": "https://example.com/avatar.png",
"threadId": "1234567890"
}
},
{
"name": "gotify-notify",
"config": {
"type": "gotify",
"serverUrl": "https://gotify.example.com",
"token": "${GOTIFY_TOKEN}",
"path": "/message",
"priority": 5
}
},
{
"name": "ntfy-notify",
"config": {
"type": "ntfy",
"serverUrl": "https://ntfy.example.com",
"topic": "zerobyte-backups",
"priority": "high",
"username": "ntfyuser",
"password": "${NTFY_PASSWORD}"
}
},
{
"name": "pushover-notify",
"config": {
"type": "pushover",
"userKey": "${PUSHOVER_USER_KEY}",
"apiToken": "${PUSHOVER_API_TOKEN}",
"devices": "phone,tablet",
"priority": 1
}
},
{
"name": "telegram-notify",
"config": {
"type": "telegram",
"botToken": "${TELEGRAM_BOT_TOKEN}",
"chatId": "123456789"
}
},
{
"name": "custom-shoutrrr",
"config": {
"type": "custom",
"shoutrrrUrl": "${SHOUTRRR_URL}"
}
}
],
"recoveryKey": "${RECOVERY_KEY}",
"users": [
{
"username": "admin",
"password": "${ADMIN_PASSWORD}"
}
]
}