mirror of
https://github.com/Portabase/agent.git
synced 2026-10-05 12:41:56 +00:00
feat(storage): sftp provider over rclone rcat
This commit is contained in:
@@ -11,6 +11,7 @@ use providers::google_drive;
|
||||
use providers::local;
|
||||
use providers::rclone;
|
||||
use providers::s3;
|
||||
use providers::sftp;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, info};
|
||||
|
||||
@@ -40,6 +41,7 @@ pub fn get_provider(storage: &DatabaseStorage) -> Option<Box<dyn StorageProvider
|
||||
google_cloud_storage::GoogleCloudStorageProvider {},
|
||||
)),
|
||||
"rclone" => Some(Box::new(rclone::RcloneProvider {})),
|
||||
"sftp" => Some(Box::new(sftp::SftpProvider {})),
|
||||
_ => {
|
||||
error!("Unknown storage provider: {}", storage.provider);
|
||||
None
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod google_drive;
|
||||
pub mod local;
|
||||
pub mod rclone;
|
||||
pub mod s3;
|
||||
pub mod sftp;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
pub mod helpers;
|
||||
pub mod models;
|
||||
|
||||
use crate::core::context::Context;
|
||||
use crate::services::api::models::agent::status::DatabaseStorage;
|
||||
use crate::services::backup::models::{BackupResult, UploadResult};
|
||||
use crate::services::storage::StorageProvider;
|
||||
use crate::services::storage::providers::rclone::helpers::{rcat, remote_target, write_config};
|
||||
use crate::services::storage::providers::sftp::helpers::build_sftp_config;
|
||||
use crate::services::storage::providers::sftp::models::SftpProviderConfig;
|
||||
use crate::utils::common::BackupMethod;
|
||||
use crate::utils::file::{full_file_name, full_file_path};
|
||||
use crate::utils::stream::build_stream;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub struct SftpProvider {}
|
||||
|
||||
fn failed(storage_id: &str, error: impl ToString, total_size: Option<u64>) -> UploadResult {
|
||||
UploadResult {
|
||||
storage_id: storage_id.to_string(),
|
||||
success: false,
|
||||
error: Some(error.to_string()),
|
||||
remote_file_path: None,
|
||||
total_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StorageProvider for SftpProvider {
|
||||
async fn upload(
|
||||
&self,
|
||||
ctx: Arc<Context>,
|
||||
result: BackupResult,
|
||||
_method: BackupMethod,
|
||||
storage: &DatabaseStorage,
|
||||
encrypt: Option<bool>,
|
||||
_backup_storage_id: &str,
|
||||
) -> UploadResult {
|
||||
let storage_id = storage.id.clone();
|
||||
|
||||
let Some(file_path) = result.backup_file else {
|
||||
return failed(&storage_id, "Missing backup file path", None);
|
||||
};
|
||||
|
||||
let total_size = match fs::metadata(&file_path).await {
|
||||
Ok(meta) => meta.len(),
|
||||
Err(e) => {
|
||||
error!("Failed to get file size: {}", e);
|
||||
return failed(&storage_id, e, None);
|
||||
}
|
||||
};
|
||||
|
||||
let config: SftpProviderConfig = match storage.clone().config.try_into() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("sftp config deserialization failed: {}", e);
|
||||
return failed(&storage_id, e, Some(total_size));
|
||||
}
|
||||
};
|
||||
|
||||
// `_key_file` stays in scope so the temp key survives the whole upload.
|
||||
let (config_text, _key_file) = match build_sftp_config(&config) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("sftp config build failed: {}", e);
|
||||
return failed(&storage_id, e, Some(total_size));
|
||||
}
|
||||
};
|
||||
|
||||
let encrypt = encrypt.unwrap_or(false);
|
||||
|
||||
let upload = match build_stream(&file_path, encrypt, &ctx.edge_key.master_key_b64).await {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
error!("Stream build failed: {}", e);
|
||||
return failed(&storage_id, e, Some(total_size));
|
||||
}
|
||||
};
|
||||
|
||||
let file_name = full_file_name(encrypt);
|
||||
let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref());
|
||||
|
||||
let config_file = match write_config(&config_text) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!("sftp config write failed: {}", e);
|
||||
return failed(&storage_id, e, Some(total_size));
|
||||
}
|
||||
};
|
||||
|
||||
let target = remote_target("sftp", &config.remote_path, &remote_file_path);
|
||||
|
||||
info!("Starting sftp (rclone) upload to {}", target);
|
||||
|
||||
match rcat(config_file.path(), &target, upload.stream).await {
|
||||
Ok(()) => {
|
||||
info!("sftp upload successful: {}", remote_file_path);
|
||||
UploadResult {
|
||||
storage_id,
|
||||
success: true,
|
||||
error: None,
|
||||
remote_file_path: Some(remote_file_path),
|
||||
total_size: Some(total_size),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("sftp upload failed: {:?}", e);
|
||||
failed(&storage_id, e, Some(total_size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user