mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
Merge pull request #80 from StarFleetCPTN/development
Bug Fixes + New Providers
This commit is contained in:
@@ -44,6 +44,9 @@ GoMFT is a web-based managed file transfer application built with Go, leveraging
|
||||
- SFTP
|
||||
- FTP
|
||||
- SMB/CIFS shares
|
||||
- Hetzner Storage Box
|
||||
- Backblaze B2
|
||||
- Wasabi
|
||||
- Local filesystem
|
||||
- And more via rclone
|
||||
- **Webhook Notifications**: Receive real-time notifications of job events:
|
||||
@@ -525,6 +528,10 @@ The following fields have been added to the `users` table:
|
||||
- Local filesystem
|
||||
- Amazon S3
|
||||
- MinIO (S3-compatible storage)
|
||||
- NextCloud
|
||||
- Backblaze B2
|
||||
- Wasabi
|
||||
- Hetzner Storage Box
|
||||
- SFTP
|
||||
- FTP
|
||||
- SMB/CIFS shares
|
||||
|
||||
@@ -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');
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -509,15 +518,23 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<template x-if="sourceType === 'ftp'">
|
||||
@source.FTPSourceForm()
|
||||
</template>
|
||||
|
||||
|
||||
<template x-if="sourceType === 's3'">
|
||||
@source.S3SourceForm()
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'b2'">
|
||||
@source.B2SourceForm()
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'wasabi'">
|
||||
@source.WasabiSourceForm()
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'minio'">
|
||||
@source.MinIOSourceForm()
|
||||
</template>
|
||||
|
||||
|
||||
<template x-if="sourceType === 'smb'">
|
||||
@source.SMBSourceForm()
|
||||
</template>
|
||||
@@ -525,7 +542,7 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<template x-if="sourceType === 'webdav'">
|
||||
@source.WebDAVSourceForm()
|
||||
</template>
|
||||
|
||||
|
||||
<template x-if="sourceType === 'nextcloud'">
|
||||
@source.NextCloudSourceForm()
|
||||
</template>
|
||||
@@ -533,10 +550,14 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<template x-if="sourceType === 'gdrive'">
|
||||
@source.GoogleDriveSourceForm()
|
||||
</template>
|
||||
|
||||
|
||||
<template x-if="sourceType === 'gphotos'">
|
||||
@source.GooglePhotosSourceForm()
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'hetzner'">
|
||||
@source.HetznerSourceForm()
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- File Pattern Section -->
|
||||
@@ -589,6 +610,14 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
@destination.S3DestinationForm()
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'b2'">
|
||||
@destination.B2DestinationForm()
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'wasabi'">
|
||||
@destination.WasabiDestinationForm()
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'minio'">
|
||||
@destination.MinIODestinationForm()
|
||||
</template>
|
||||
@@ -612,6 +641,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<template x-if="destinationType === 'gphotos'">
|
||||
@destination.GooglePhotosDestinationForm()
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'hetzner'">
|
||||
@destination.HetznerDestinationForm()
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Options Section -->
|
||||
@@ -627,6 +660,8 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
|
||||
@@ -195,12 +195,15 @@ templ SourceSelection() {
|
||||
<option value="sftp">SFTP</option>
|
||||
<option value="ftp">FTP</option>
|
||||
<option value="s3">S3</option>
|
||||
<option value="b2">Backblaze B2</option>
|
||||
<option value="wasabi">Wasabi</option>
|
||||
<option value="minio">MinIO</option>
|
||||
<option value="smb">SMB</option>
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="gdrive">Google Drive (BETA)</option>
|
||||
<option value="gphotos">Google Photos (BETA)</option>
|
||||
<option value="hetzner">Hetzner Storage Box</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,12 +222,15 @@ templ DestinationSelection() {
|
||||
<option value="sftp">SFTP</option>
|
||||
<option value="ftp">FTP</option>
|
||||
<option value="s3">S3</option>
|
||||
<option value="b2">Backblaze B2</option>
|
||||
<option value="wasabi">Wasabi</option>
|
||||
<option value="minio">MinIO</option>
|
||||
<option value="smb">SMB</option>
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="gdrive">Google Drive (BETA)</option>
|
||||
<option value="gphotos">Google Photos (BETA)</option>
|
||||
<option value="hetzner">Hetzner Storage Box</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package destination
|
||||
|
||||
templ B2DestinationForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Backblaze B2 details below. You'll need your account ID, application key, and bucket information.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_bucket" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Bucket Name</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destBucket" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="my-b2-bucket" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Name of your Backblaze B2 bucket (case-sensitive)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_access_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Account ID</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destAccessKey" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="0123456789abcdef0123456789" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Backblaze B2 Account ID
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_secret_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Application Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destSecretKey" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="K0123456789abcdef0123456789abcdef0123456789" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Backblaze B2 Application Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_endpoint" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Endpoint (Optional)</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destEndpoint"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="s3.us-west-000.backblazeb2.com" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Custom endpoint URL (only needed for non-standard regions or private endpoints)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="destination_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Path in Bucket</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="destination_path" name="destination_path" x-model="destinationPath"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="path/to/files/" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package destination
|
||||
|
||||
templ HetznerDestinationForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Hetzner Storage Box details below. You'll need your server details, username, and password.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_host" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Storage Box Host</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_host" name="dest_host" x-model="destHost" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="uXXXXXX.your-storagebox.de" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box hostname (e.g., uXXXXXX.your-storagebox.de)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_port" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Port</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-plug text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="number" id="dest_port" name="dest_port" x-model="destPort" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="23" min="1" max="65535" value="23" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Connection port (default: 23)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_auth_type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Authentication Type</label>
|
||||
<div class="flex">
|
||||
<div class="flex items-center me-4">
|
||||
<input id="dest_auth_password" type="radio" value="password" name="dest_auth_type" x-model="destAuthType"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="dest_auth_password" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
||||
</div>
|
||||
<div class="flex items-center me-4">
|
||||
<input id="dest_auth_key" type="radio" value="key" name="dest_auth_type" x-model="destAuthType"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="dest_auth_key" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">SSH Key</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_user" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="uXXXXXX" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box username (typically matches your Storage Box number)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-show="destAuthType === 'password'" class="mb-6">
|
||||
<label for="dest_password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="dest_password" name="dest_password" x-model="destPassword" x-bind:required="requiresDestination && destAuthType === 'password'"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="Your password" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box password
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-show="destAuthType === 'key'" class="mb-6">
|
||||
<label for="dest_key_file" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">SSH Key File</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_key_file" name="dest_key_file" x-model="destKeyFile" x-bind:required="requiresDestination && destAuthType === 'key'"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="/path/to/id_rsa" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path to your SSH private key file (must be readable by the application)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="destination_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Remote Path</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="destination_path" name="destination_path" x-model="destinationPath" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="/backups" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path on your Storage Box (e.g., /backups, /path/to/files)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">For increased security, consider using SSH key authentication instead of password authentication when possible.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package destination
|
||||
|
||||
templ WasabiDestinationForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Wasabi Cloud Storage details below. You'll need your access key, secret key, region, and bucket information.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_region" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Wasabi Region</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_region" name="dest_region" x-model="destRegion" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="us-east-1" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Wasabi region where your bucket is located (e.g., us-east-1, us-west-1, eu-central-1)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_bucket" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Bucket Name</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destBucket" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="my-wasabi-bucket" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Name of your Wasabi bucket (case-sensitive)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_access_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Access Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destAccessKey" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="ABCDEFGHIJKLMNOPQRST" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Wasabi Access Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_secret_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Secret Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destSecretKey" x-bind:required="requiresDestination"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="abcdefghijklmnopqrstuvwxyz1234567890ABCD" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Wasabi Secret Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="dest_endpoint" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Endpoint (Optional)</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destEndpoint"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="s3.wasabisys.com" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Wasabi endpoint URL (usually s3.wasabisys.com or region-specific endpoints)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="destination_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Path in Bucket</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="destination_path" name="destination_path" x-model="destinationPath"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="path/to/files/" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package source
|
||||
|
||||
templ B2SourceForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Backblaze B2 details below. You'll need your account ID, application key, and bucket information.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_bucket" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Bucket Name</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_bucket" name="source_bucket" x-model="sourceBucket" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="my-b2-bucket" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Name of your Backblaze B2 bucket (case-sensitive)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_access_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Account ID</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_access_key" name="source_access_key" x-model="sourceAccessKey" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="0123456789abcdef0123456789" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Backblaze B2 Account ID
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_secret_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Application Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="source_secret_key" name="source_secret_key" x-model="sourceSecretKey" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="K0123456789abcdef0123456789abcdef0123456789" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Backblaze B2 Application Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_endpoint" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Endpoint (Optional)</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_endpoint" name="source_endpoint" x-model="sourceEndpoint"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="s3.us-west-000.backblazeb2.com" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Custom endpoint URL (only needed for non-standard regions or private endpoints)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Path in Bucket</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_path" name="source_path" x-model="sourcePath"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="path/to/files/" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package source
|
||||
|
||||
templ HetznerSourceForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Hetzner Storage Box details below. You'll need your server details, username, and password.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_host" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Storage Box Host</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_host" name="source_host" x-model="sourceHost" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="uXXXXXX.your-storagebox.de" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box hostname (e.g., uXXXXXX.your-storagebox.de)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_port" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Port</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-plug text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="number" id="source_port" name="source_port" x-model="sourcePort" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="23" min="1" max="65535" value="23" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Connection port (default: 23)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_auth_type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Authentication Type</label>
|
||||
<div class="flex">
|
||||
<div class="flex items-center me-4">
|
||||
<input id="source_auth_password" type="radio" value="password" name="source_auth_type" x-model="sourceAuthType"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="source_auth_password" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
||||
</div>
|
||||
<div class="flex items-center me-4">
|
||||
<input id="source_auth_key" type="radio" value="key" name="source_auth_type" x-model="sourceAuthType"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="source_auth_key" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">SSH Key</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_user" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_user" name="source_user" x-model="sourceUser" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="uXXXXXX" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box username (typically matches your Storage Box number)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-show="sourceAuthType === 'password'" class="mb-6">
|
||||
<label for="source_password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="source_password" name="source_password" x-model="sourcePassword" x-bind:required="sourceAuthType === 'password'"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="Your password" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Hetzner Storage Box password
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-show="sourceAuthType === 'key'" class="mb-6">
|
||||
<label for="source_key_file" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">SSH Key File</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_key_file" name="source_key_file" x-model="sourceKeyFile" x-bind:required="sourceAuthType === 'key'"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="/path/to/id_rsa" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path to your SSH private key file (must be readable by the application)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Remote Path</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_path" name="source_path" x-model="sourcePath" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="/backups" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path on your Storage Box (e.g., /backups, /path/to/files)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">For increased security, consider using SSH key authentication instead of password authentication when possible.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package source
|
||||
|
||||
templ WasabiSourceForm() {
|
||||
<div class="space-y-6 mt-4">
|
||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
<span>Configure your Wasabi Cloud Storage details below. You'll need your access key, secret key, region, and bucket information.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_region" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Wasabi Region</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_region" name="source_region" x-model="sourceRegion" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="us-east-1" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Wasabi region where your bucket is located (e.g., us-east-1, us-west-1, eu-central-1)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_bucket" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Bucket Name</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_bucket" name="source_bucket" x-model="sourceBucket" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="my-wasabi-bucket" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Name of your Wasabi bucket (case-sensitive)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_access_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Access Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_access_key" name="source_access_key" x-model="sourceAccessKey" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="ABCDEFGHIJKLMNOPQRST" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Wasabi Access Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_secret_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Secret Key</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="password" id="source_secret_key" name="source_secret_key" x-model="sourceSecretKey" required
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="abcdefghijklmnopqrstuvwxyz1234567890ABCD" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Your Wasabi Secret Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_endpoint" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Endpoint (Optional)</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_endpoint" name="source_endpoint" x-model="sourceEndpoint"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="s3.wasabisys.com" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Wasabi endpoint URL (usually s3.wasabisys.com or region-specific endpoints)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="source_path" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Path in Bucket</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||
<i class="fas fa-folder-open text-gray-400 dark:text-gray-500"></i>
|
||||
</div>
|
||||
<input type="text" id="source_path" name="source_path" x-model="sourcePath"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="path/to/files/" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Path prefix within the bucket (e.g., "backups/"). Leave empty to access the entire bucket.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||
<div class="flex">
|
||||
<i class="fas fa-shield-alt mr-2 flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3 class="font-medium">Security Note</h3>
|
||||
<p class="mt-1">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
+56
-19
@@ -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
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
},
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
},
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user