+
+ Configure your Backblaze B2 details below. You'll need your account ID, application key, and bucket information.
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of your Backblaze B2 bucket (case-sensitive)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Backblaze B2 Account ID
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Backblaze B2 Application Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Custom endpoint URL (only needed for non-standard regions or private endpoints)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
+
+
+
+
+
+
+
+
Security Note
+
It's recommended to create an application key with restricted permissions for this configuration. The application key should only have access to the specific B2 bucket and operations needed.
+
+
+
+
+}
\ No newline at end of file
diff --git a/components/providers/destination/hetzner.templ b/components/providers/destination/hetzner.templ
new file mode 100644
index 0000000..fd26b9d
--- /dev/null
+++ b/components/providers/destination/hetzner.templ
@@ -0,0 +1,128 @@
+package destination
+
+templ HetznerDestinationForm() {
+
+
+
+
+ Configure your Hetzner Storage Box details below. You'll need your server details, username, and password.
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box hostname (e.g., uXXXXXX.your-storagebox.de)
+
+
+
+
+
+
+
+
+
+
+
+
+ Connection port (default: 23)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box username (typically matches your Storage Box number)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box password
+
+
+
+
+
+
+
+
+
+
+
+
+ Path to your SSH private key file (must be readable by the application)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path on your Storage Box (e.g., /backups, /path/to/files)
+
+
+
+
+
+
+
+
Security Note
+
For increased security, consider using SSH key authentication instead of password authentication when possible.
+
+
+
+
+}
\ No newline at end of file
diff --git a/components/providers/destination/wasabi.templ b/components/providers/destination/wasabi.templ
new file mode 100644
index 0000000..6cabf92
--- /dev/null
+++ b/components/providers/destination/wasabi.templ
@@ -0,0 +1,112 @@
+package destination
+
+templ WasabiDestinationForm() {
+
+
+
+
+ Configure your Wasabi Cloud Storage details below. You'll need your access key, secret key, region, and bucket information.
+
+
+
+
+
+
+
+
+
+
+
+
+ Wasabi region where your bucket is located (e.g., us-east-1, us-west-1, eu-central-1)
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of your Wasabi bucket (case-sensitive)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Wasabi Access Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Wasabi Secret Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Wasabi endpoint URL (usually s3.wasabisys.com or region-specific endpoints)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
+
+
+
+
+
+
+
+
Security Note
+
It's recommended to use an IAM user with restricted permissions for this configuration. The IAM user should only have access to the specific Wasabi bucket and operations needed.
+
+
+
+
+}
\ No newline at end of file
diff --git a/components/providers/source/b2.templ b/components/providers/source/b2.templ
new file mode 100644
index 0000000..a75f327
--- /dev/null
+++ b/components/providers/source/b2.templ
@@ -0,0 +1,97 @@
+package source
+
+templ B2SourceForm() {
+
+
+
+
+ Configure your Backblaze B2 details below. You'll need your account ID, application key, and bucket information.
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of your Backblaze B2 bucket (case-sensitive)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Backblaze B2 Account ID
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Backblaze B2 Application Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Custom endpoint URL (only needed for non-standard regions or private endpoints)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
+
+
+
+
+
+
+
+
Security Note
+
It's recommended to create an application key with restricted permissions for this configuration. The application key should only have access to the specific B2 bucket and operations needed.
+
+
+
+
+}
\ No newline at end of file
diff --git a/components/providers/source/hetzner.templ b/components/providers/source/hetzner.templ
new file mode 100644
index 0000000..554229a
--- /dev/null
+++ b/components/providers/source/hetzner.templ
@@ -0,0 +1,128 @@
+package source
+
+templ HetznerSourceForm() {
+
+
+
+
+ Configure your Hetzner Storage Box details below. You'll need your server details, username, and password.
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box hostname (e.g., uXXXXXX.your-storagebox.de)
+
+
+
+
+
+
+
+
+
+
+
+
+ Connection port (default: 23)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box username (typically matches your Storage Box number)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Hetzner Storage Box password
+
+
+
+
+
+
+
+
+
+
+
+
+ Path to your SSH private key file (must be readable by the application)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path on your Storage Box (e.g., /backups, /path/to/files)
+
+
+
+
+
+
+
+
Security Note
+
For increased security, consider using SSH key authentication instead of password authentication when possible.
+
+
+
+
+}
\ No newline at end of file
diff --git a/components/providers/source/wasabi.templ b/components/providers/source/wasabi.templ
new file mode 100644
index 0000000..84292f1
--- /dev/null
+++ b/components/providers/source/wasabi.templ
@@ -0,0 +1,112 @@
+package source
+
+templ WasabiSourceForm() {
+
+
+
+
+ Configure your Wasabi Cloud Storage details below. You'll need your access key, secret key, region, and bucket information.
+
+
+
+
+
+
+
+
+
+
+
+
+ Wasabi region where your bucket is located (e.g., us-east-1, us-west-1, eu-central-1)
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of your Wasabi bucket (case-sensitive)
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Wasabi Access Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Wasabi Secret Key
+
+
+
+
+
+
+
+
+
+
+
+
+ Wasabi endpoint URL (usually s3.wasabisys.com or region-specific endpoints)
+
+
+
+
+
+
+
+
+
+
+
+
+ Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
+
+
+
+
+
+
+
+
Security Note
+
It's recommended to use an IAM user with restricted permissions for this configuration. The IAM user should only have access to the specific Wasabi bucket and operations needed.
+
+
+
+
+}
\ No newline at end of file
diff --git a/entrypoint.sh b/entrypoint.sh
index bf2f734..9d14d0e 100644
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -26,12 +26,22 @@ if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
deluser ${USERNAME} > /dev/null 2>&1 || true
delgroup ${USERNAME} > /dev/null 2>&1 || true
- # Add group with the specified GID
- echo "Adding group ${USERNAME} with GID ${PGID}"
- if ! addgroup -g ${PGID} ${USERNAME}; then
- echo "⚠️ Failed to add group ${USERNAME} with GID ${PGID}."
- # Exiting because user creation will likely fail
- exit 1
+ # Check if a group with the target GID already exists
+ EXISTING_GROUP=$(getent group ${PGID} | cut -d: -f1 || echo "")
+
+ if [ -n "${EXISTING_GROUP}" ]; then
+ echo "Group with GID ${PGID} already exists as '${EXISTING_GROUP}', will use this group"
+ # Set USERNAME_GROUP to the existing group name
+ USERNAME_GROUP="${EXISTING_GROUP}"
+ else
+ # Add group with the specified GID
+ echo "Adding group ${USERNAME} with GID ${PGID}"
+ if ! addgroup -g ${PGID} ${USERNAME}; then
+ echo "⚠️ Failed to add group ${USERNAME} with GID ${PGID}."
+ # Exiting because user creation will likely fail
+ exit 1
+ fi
+ USERNAME_GROUP="${USERNAME}"
fi
# Add user with the specified UID and GID
@@ -39,16 +49,16 @@ if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
# Use -h /app for home directory (consistent with expectations)
# Use -s /bin/sh for shell
# Use -D for no password (system user)
- echo "Adding user ${USERNAME} with UID ${PUID}"
- if ! adduser -u ${PUID} -G ${USERNAME} -h /app -s /bin/sh -D ${USERNAME}; then
- echo "⚠️ Failed to add user ${USERNAME} with UID ${PUID} and group ${USERNAME}."
+ echo "Adding user ${USERNAME} with UID ${PUID} and group ${USERNAME_GROUP}"
+ if ! adduser -u ${PUID} -G ${USERNAME_GROUP} -h /app -s /bin/sh -D ${USERNAME}; then
+ echo "⚠️ Failed to add user ${USERNAME} with UID ${PUID} and group ${USERNAME_GROUP}."
# Exiting because the application cannot run as the correct user
exit 1
fi
# Verify the change
FINAL_UID=$(id -u ${USERNAME} 2>/dev/null || echo "error")
- FINAL_GID=$(getent group ${USERNAME} | cut -d: -f3 2>/dev/null || echo "error")
+ FINAL_GID=$(id -g ${USERNAME} 2>/dev/null || echo "error")
if [ "${FINAL_UID}" = "${PUID}" ] && [ "${FINAL_GID}" = "${PGID}" ]; then
echo "✅ Successfully updated UID/GID to ${PUID}:${PGID}"
@@ -79,13 +89,40 @@ if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
groupdel ${USERNAME} 2>/dev/null || true
fi
- # Recreate group and user in the correct order
- echo "Creating group ${USERNAME} with GID ${PGID}"
- groupadd -g ${PGID} ${USERNAME} 2>/dev/null || groupadd ${USERNAME} 2>/dev/null || true
+ # Check if a group with the target GID already exists
+ EXISTING_GROUP=$(getent group ${PGID} | cut -d: -f1 || echo "")
- echo "Creating user ${USERNAME} with UID ${PUID}"
- useradd -u ${PUID} -g ${USERNAME} -s /bin/sh ${USERNAME} 2>/dev/null ||
- useradd -g ${USERNAME} -s /bin/sh ${USERNAME} 2>/dev/null || true
+ if [ -n "${EXISTING_GROUP}" ]; then
+ echo "Group with GID ${PGID} already exists as '${EXISTING_GROUP}', will use this group"
+ # Set USERNAME_GROUP to the existing group name
+ USERNAME_GROUP="${EXISTING_GROUP}"
+ else
+ # Recreate group with the specified GID
+ echo "Creating group ${USERNAME} with GID ${PGID}"
+ groupadd -g ${PGID} ${USERNAME} 2>/dev/null || groupadd ${USERNAME} 2>/dev/null || true
+ USERNAME_GROUP="${USERNAME}"
+ fi
+
+ # Check if a user with the target UID already exists
+ EXISTING_USER=$(getent passwd ${PUID} | cut -d: -f1 || echo "")
+
+ if [ -n "${EXISTING_USER}" ] && [ "${EXISTING_USER}" != "${USERNAME}" ]; then
+ echo "⚠️ Warning: User with UID ${PUID} already exists as '${EXISTING_USER}'. Using a different username may cause issues."
+ fi
+
+ echo "Creating user ${USERNAME} with UID ${PUID} and group ${USERNAME_GROUP}"
+ useradd -u ${PUID} -g ${USERNAME_GROUP} -s /bin/sh ${USERNAME} 2>/dev/null ||
+ useradd -g ${USERNAME_GROUP} -s /bin/sh ${USERNAME} 2>/dev/null || true
+
+ # Verify the change
+ FINAL_UID=$(id -u ${USERNAME} 2>/dev/null || echo "error")
+ FINAL_GID=$(id -g ${USERNAME} 2>/dev/null || echo "error")
+
+ if [ "${FINAL_UID}" = "${PUID}" ] && [ "${FINAL_GID}" = "${PGID}" ]; then
+ echo "✅ Successfully updated UID/GID to ${PUID}:${PGID}"
+ else
+ echo "⚠️ Warning: Verification failed. Target: ${PUID}:${PGID}, Actual: ${FINAL_UID}:${FINAL_GID}"
+ fi
} || {
echo "⚠️ Warning: Failed to update UID/GID, continuing with built-in user"
}
@@ -93,17 +130,17 @@ if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
# Fix ownership of app directories
echo "Setting ownership of app directories"
- chown -R ${USERNAME}:${USERNAME} /app/data /app/backups || echo "⚠️ Warning: Failed to change ownership"
+ chown -R ${USERNAME}:${USERNAME_GROUP:-${USERNAME}} /app/data /app/backups || echo "⚠️ Warning: Failed to change ownership"
# Ensure .env file exists and has correct permissions
if [ -f /app/.env ]; then
echo "Found .env file, setting permissions..."
- chown ${USERNAME}:${USERNAME} /app/.env || echo "⚠️ Warning: Failed to change .env ownership"
+ chown ${USERNAME}:${USERNAME_GROUP:-${USERNAME}} /app/.env || echo "⚠️ Warning: Failed to change .env ownership"
chmod 644 /app/.env || echo "⚠️ Warning: Failed to change .env permissions"
else
echo "No .env file found, creating empty one..."
touch /app/.env
- chown ${USERNAME}:${USERNAME} /app/.env || echo "⚠️ Warning: Failed to change .env ownership"
+ chown ${USERNAME}:${USERNAME_GROUP:-${USERNAME}} /app/.env || echo "⚠️ Warning: Failed to change .env ownership"
chmod 644 /app/.env || echo "⚠️ Warning: Failed to change .env permissions"
fi
diff --git a/internal/db/migrations/011a_recover_transfer_configs_rename.go b/internal/db/migrations/012a_recover_transfer_configs_rename.go
similarity index 91%
rename from internal/db/migrations/011a_recover_transfer_configs_rename.go
rename to internal/db/migrations/012a_recover_transfer_configs_rename.go
index cd6dcf3..8cabb16 100644
--- a/internal/db/migrations/011a_recover_transfer_configs_rename.go
+++ b/internal/db/migrations/012a_recover_transfer_configs_rename.go
@@ -12,9 +12,9 @@ import (
// table might have been left renamed as _transfer_configs_old.
func RecoverTransferConfigsRename() *gormigrate.Migration {
return &gormigrate.Migration{
- ID: "011a_recover_transfer_configs_rename",
+ ID: "012a_recover_transfer_configs_rename",
Migrate: func(tx *gorm.DB) error {
- fmt.Println("Running migration 011a: Checking for transfer_configs rename recovery...")
+ fmt.Println("Running migration 012a: Checking for transfer_configs rename recovery...")
var oldTableExists int
tx.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='_transfer_configs_old'").Scan(&oldTableExists)
@@ -40,7 +40,7 @@ func RecoverTransferConfigsRename() *gormigrate.Migration {
},
Rollback: func(tx *gorm.DB) error {
// Rollback doesn't make sense for a recovery step.
- fmt.Println("Rollback for migration 011a_recover_transfer_configs_rename is not applicable.")
+ fmt.Println("Rollback for migration 012a_recover_transfer_configs_rename is not applicable.")
return nil
},
}
diff --git a/internal/db/migrations/011b_recover_notification_services_rename.go b/internal/db/migrations/012b_recover_notification_services_rename.go
similarity index 90%
rename from internal/db/migrations/011b_recover_notification_services_rename.go
rename to internal/db/migrations/012b_recover_notification_services_rename.go
index 5966725..15dfdeb 100644
--- a/internal/db/migrations/011b_recover_notification_services_rename.go
+++ b/internal/db/migrations/012b_recover_notification_services_rename.go
@@ -12,9 +12,9 @@ import (
// table might have been left renamed as _notification_services_old.
func RecoverNotificationServicesRename() *gormigrate.Migration {
return &gormigrate.Migration{
- ID: "011b_recover_notification_services_rename",
+ ID: "012b_recover_notification_services_rename",
Migrate: func(tx *gorm.DB) error {
- fmt.Println("Running migration 011b: Checking for notification_services rename recovery...")
+ fmt.Println("Running migration 012b: Checking for notification_services rename recovery...")
var oldTableExists int
tx.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='_notification_services_old'").Scan(&oldTableExists)
@@ -38,7 +38,7 @@ func RecoverNotificationServicesRename() *gormigrate.Migration {
},
Rollback: func(tx *gorm.DB) error {
// Rollback doesn't make sense for a recovery step.
- fmt.Println("Rollback for migration 011b_recover_notification_services_rename is not applicable.")
+ fmt.Println("Rollback for migration 012b_recover_notification_services_rename is not applicable.")
return nil
},
}
diff --git a/internal/db/migrations/011c_recover_auth_providers_rename.go b/internal/db/migrations/012c_recover_auth_providers_rename.go
similarity index 90%
rename from internal/db/migrations/011c_recover_auth_providers_rename.go
rename to internal/db/migrations/012c_recover_auth_providers_rename.go
index 510ba97..071fb8f 100644
--- a/internal/db/migrations/011c_recover_auth_providers_rename.go
+++ b/internal/db/migrations/012c_recover_auth_providers_rename.go
@@ -12,9 +12,9 @@ import (
// table might have been left renamed as _auth_providers_old.
func RecoverAuthProvidersRename() *gormigrate.Migration {
return &gormigrate.Migration{
- ID: "011c_recover_auth_providers_rename",
+ ID: "012c_recover_auth_providers_rename",
Migrate: func(tx *gorm.DB) error {
- fmt.Println("Running migration 011c: Checking for auth_providers rename recovery...")
+ fmt.Println("Running migration 012c: Checking for auth_providers rename recovery...")
var oldTableExists int
tx.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='_auth_providers_old'").Scan(&oldTableExists)
@@ -38,7 +38,7 @@ func RecoverAuthProvidersRename() *gormigrate.Migration {
},
Rollback: func(tx *gorm.DB) error {
// Rollback doesn't make sense for a recovery step.
- fmt.Println("Rollback for migration 011c_recover_auth_providers_rename is not applicable.")
+ fmt.Println("Rollback for migration 012c_recover_auth_providers_rename is not applicable.")
return nil
},
}
diff --git a/internal/db/migrations/migrations.go b/internal/db/migrations/migrations.go
index 5367941..4df11e8 100644
--- a/internal/db/migrations/migrations.go
+++ b/internal/db/migrations/migrations.go
@@ -22,10 +22,10 @@ func GetMigrations(db *gorm.DB) *gormigrate.Gormigrate {
AddRcloneTables(), // 009
AddRcloneCommandToConfig(), // 010
AddAuthProviders(), // 011
- RecoverTransferConfigsRename(), // 011a
- RecoverNotificationServicesRename(), // 011b
- RecoverAuthProvidersRename(), // 011c
AlterBooleanDefaults(), // 012
+ RecoverTransferConfigsRename(), // 012a
+ RecoverNotificationServicesRename(), // 012b
+ RecoverAuthProvidersRename(), // 012c
CleanupInvalidBooleans(), // 013
)
diff --git a/internal/db/transfer_config_store.go b/internal/db/transfer_config_store.go
index 3712782..08ee310 100644
--- a/internal/db/transfer_config_store.go
+++ b/internal/db/transfer_config_store.go
@@ -91,7 +91,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
sourceName := fmt.Sprintf("source_%d", config.ID)
// Generate rclone config using rclone CLI for source
switch config.SourceType {
- case "sftp":
+ case "sftp", "hetzner":
args := []string{
"config", "create", sourceName, "sftp",
"host", config.SourceHost,
@@ -130,6 +130,43 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create source config (s3): %v\nOutput: %s", err, output)
}
+ case "wasabi":
+ args := []string{
+ "config", "create", sourceName, "s3",
+ "provider", "Wasabi",
+ "env_auth", "false",
+ "access_key_id", config.SourceAccessKey,
+ "secret_access_key", config.SourceSecretKey,
+ "region", config.SourceRegion,
+ "--non-interactive",
+ "--config", configPath,
+ "--log-level", "ERROR",
+ }
+ endpoint := config.SourceEndpoint
+ if endpoint == "" {
+ endpoint = "s3.wasabisys.com"
+ }
+ args = append(args, "endpoint", endpoint)
+ cmd := exec.Command(rclonePath, args...)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to create source config (wasabi): %v\nOutput: %s", err, output)
+ }
+ case "b2":
+ args := []string{
+ "config", "create", sourceName, "b2",
+ "account", config.SourceAccessKey, // B2 Account ID
+ "key", config.SourceSecretKey, // B2 Application Key
+ "--non-interactive",
+ "--config", configPath,
+ "--log-level", "ERROR",
+ }
+ if config.SourceEndpoint != "" {
+ args = append(args, "endpoint", config.SourceEndpoint)
+ }
+ cmd := exec.Command(rclonePath, args...)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to create source config (b2): %v\nOutput: %s", err, output)
+ }
case "minio":
args := []string{
"config", "create", sourceName, "s3",
@@ -208,7 +245,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
destName := fmt.Sprintf("dest_%d", config.ID)
// Generate rclone config using rclone CLI for destination
switch config.DestinationType {
- case "sftp":
+ case "sftp", "hetzner":
args := []string{
"config", "create", destName, "sftp",
"host", config.DestHost,
@@ -247,6 +284,43 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create destination config (s3): %v\nOutput: %s", err, output)
}
+ case "wasabi":
+ args := []string{
+ "config", "create", destName, "s3",
+ "provider", "Wasabi",
+ "env_auth", "false",
+ "access_key_id", config.DestAccessKey,
+ "secret_access_key", config.DestSecretKey,
+ "region", config.DestRegion,
+ "--non-interactive",
+ "--config", configPath,
+ "--log-level", "ERROR",
+ }
+ endpoint := config.DestEndpoint
+ if endpoint == "" {
+ endpoint = "s3.wasabisys.com"
+ }
+ args = append(args, "endpoint", endpoint)
+ cmd := exec.Command(rclonePath, args...)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to create destination config (wasabi): %v\nOutput: %s", err, output)
+ }
+ case "b2":
+ args := []string{
+ "config", "create", destName, "b2",
+ "account", config.DestAccessKey, // B2 Account ID
+ "key", config.DestSecretKey, // B2 Application Key
+ "--non-interactive",
+ "--config", configPath,
+ "--log-level", "ERROR",
+ }
+ if config.DestEndpoint != "" {
+ args = append(args, "endpoint", config.DestEndpoint)
+ }
+ cmd := exec.Command(rclonePath, args...)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to create destination config (b2): %v\nOutput: %s", err, output)
+ }
case "minio":
args := []string{
"config", "create", destName, "s3",
diff --git a/internal/rclone_service/rclone_service.go b/internal/rclone_service/rclone_service.go
index e527a01..ed956ea 100644
--- a/internal/rclone_service/rclone_service.go
+++ b/internal/rclone_service/rclone_service.go
@@ -90,8 +90,20 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
rclonePath = "rclone"
}
+ primaryProvider := provider
+
+ // Convert hetzner to sftp
+ if provider == "hetzner" {
+ primaryProvider = "sftp"
+ }
+
+ // S3 compatible providers
+ if provider == "minio" || provider == "wasabi" {
+ primaryProvider = "s3"
+ }
+
createArgs := []string{
- "config", "create", remoteName, provider,
+ "config", "create", remoteName, primaryProvider,
"--config", tempConfigPath,
"--non-interactive",
"--log-level", "DEBUG",
@@ -105,7 +117,7 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
var createCmd *exec.Cmd
switch provider {
- case "sftp":
+ case "sftp", "hetzner":
createArgs = append(createArgs, "host", host, "user", user)
if port != 0 {
createArgs = append(createArgs, "port", fmt.Sprintf("%d", port))
@@ -130,6 +142,20 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
if endpoint != "" {
createArgs = append(createArgs, "endpoint", endpoint)
}
+ case "wasabi":
+ createArgs = append(createArgs, "provider", "Wasabi", "env_auth", "false")
+ if accessKey != "" {
+ createArgs = append(createArgs, "access_key_id", accessKey)
+ }
+ if secretKey != "" {
+ createArgs = append(createArgs, "secret_access_key", secretKey)
+ }
+ if region != "" {
+ createArgs = append(createArgs, "region", region)
+ }
+ if endpoint != "" {
+ createArgs = append(createArgs, "endpoint", endpoint)
+ }
case "minio":
createArgs = append(createArgs, "provider", "Minio", "env_auth", "false")
if accessKey != "" {
@@ -138,6 +164,20 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
if secretKey != "" {
createArgs = append(createArgs, "secret_access_key", secretKey)
}
+ if region != "" {
+ createArgs = append(createArgs, "region", region)
+ }
+ if endpoint != "" {
+ createArgs = append(createArgs, "endpoint", endpoint)
+ }
+ case "b2":
+ createArgs = append(createArgs, "provider", "B2", "env_auth", "false")
+ if accessKey != "" {
+ createArgs = append(createArgs, "account", accessKey)
+ }
+ if secretKey != "" {
+ createArgs = append(createArgs, "key", secretKey)
+ }
if endpoint != "" {
createArgs = append(createArgs, "endpoint", endpoint)
}