From de97ad6b504831dd7b196ebab8eb320833b84c1d Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Tue, 8 Apr 2025 20:24:13 -0700 Subject: [PATCH] feat: Add support for Hetzner Storage Box integration - Introduced new source and destination forms for Hetzner Storage Box, allowing users to configure their storage settings. - Updated configuration generation logic to handle Hetzner, including necessary parameters such as host, port, username, and authentication type. - Enhanced user interface with clear instructions and security notes for Hetzner, improving user experience and security awareness. - Updated existing logic to accommodate Hetzner in rclone configuration generation for both source and destination types. --- components/config_form.templ | 52 ++++--- components/providers/common/common.templ | 2 + .../providers/destination/hetzner.templ | 128 ++++++++++++++++++ components/providers/source/hetzner.templ | 128 ++++++++++++++++++ internal/db/transfer_config_store.go | 4 +- internal/rclone_service/rclone_service.go | 11 ++ 6 files changed, 294 insertions(+), 31 deletions(-) create mode 100644 components/providers/destination/hetzner.templ create mode 100644 components/providers/source/hetzner.templ diff --git a/components/config_form.templ b/components/config_form.templ index 7068c13..14d2957 100644 --- a/components/config_form.templ +++ b/components/config_form.templ @@ -398,6 +398,8 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { sourcePort = 22; } else if (sourceType === 'ftp') { sourcePort = 21; + } else if (sourceType === 'hetzner') { + sourcePort = 23; } } @@ -406,25 +408,32 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { destPort = 22; } else if (destinationType === 'ftp') { destPort = 21; + } else if (destinationType === 'hetzner') { + destPort = 23; } } // Initialize command requirements updateCommandRequirements(); })" - x-effect="if (sourceType === 'sftp' && (sourcePort === 0 || sourcePort === 21)) { + x-effect="if (sourceType === 'sftp' && (sourcePort === 0 || sourcePort === 21 || sourcePort === 23)) { sourcePort = 22; console.log('Updating source port to 22 for SFTP'); - } else if (sourceType === 'ftp' && (sourcePort === 0 || sourcePort === 22)) { + } else if (sourceType === 'ftp' && (sourcePort === 0 || sourcePort === 22 || sourcePort === 23)) { sourcePort = 21; console.log('Updating source port to 21 for FTP'); - }" - x-effect="if (destinationType === 'sftp' && (destPort === 0 || destPort === 21)) { + } else if (sourceType === 'hetzner' && (sourcePort === 0 || sourcePort === 22 || sourcePort === 21)) { + sourcePort = 23; + console.log('Updating source port to 23 for Hetzner'); + } else if (destinationType === 'sftp' && (destPort === 0 || destPort === 21 || destPort === 23)) { destPort = 22; console.log('Updating destination port to 22 for SFTP'); - } else if (destinationType === 'ftp' && (destPort === 0 || destPort === 22)) { + } else if (destinationType === 'ftp' && (destPort === 0 || destPort === 22 || destPort === 23)) { destPort = 21; console.log('Updating destination port to 21 for FTP'); + } else if (destinationType === 'hetzner' && (destPort === 0 || destPort === 22 || destPort === 21)) { + destPort = 23; + console.log('Updating destination port to 23 for Hetzner'); }" > @@ -545,6 +554,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { + + @@ -628,6 +641,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { + + @@ -642,30 +659,7 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { @common.ArchiveOptions() -
- -
-
- -
- -
-

- Directory where files will be saved - Remote path on the server - Path within bucket. Leave empty for bucket root. - Share directory path - Path on remote server - Folder in Google Drive - Album name in Google Photos -

-
+ diff --git a/components/providers/common/common.templ b/components/providers/common/common.templ index a2df517..5323293 100644 --- a/components/providers/common/common.templ +++ b/components/providers/common/common.templ @@ -203,6 +203,7 @@ templ SourceSelection() { + @@ -229,6 +230,7 @@ templ DestinationSelection() { + 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() { +
+ + +
+ +
+
+ +
+ +
+

+ 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) +

+
+ + +
+} \ 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() { +
+ + +
+ +
+
+ +
+ +
+

+ 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) +

+
+ + +
+} \ No newline at end of file diff --git a/internal/db/transfer_config_store.go b/internal/db/transfer_config_store.go index 19834ab..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, @@ -245,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, diff --git a/internal/rclone_service/rclone_service.go b/internal/rclone_service/rclone_service.go index 002fecb..7796936 100644 --- a/internal/rclone_service/rclone_service.go +++ b/internal/rclone_service/rclone_service.go @@ -116,6 +116,17 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta if keyFile != "" { createArgs = append(createArgs, "key_file", keyFile) } + case "hetzner": + createArgs = append(createArgs, "host", host, "user", user, "pass", pass, "key_file", keyFile) + if port != 0 { + createArgs = append(createArgs, "port", fmt.Sprintf("%d", port)) + } + if pass != "" { + createArgs = append(createArgs, "pass", pass) + } + if keyFile != "" { + createArgs = append(createArgs, "key_file", keyFile) + } case "s3": createArgs = append(createArgs, "provider", "AWS", "env_auth", "false") if accessKey != "" {