mirror of
https://github.com/Portabase/agent.git
synced 2026-09-11 22:11:52 +00:00
Compare commits
16 Commits
1.1.2-rc.2
..
1.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
| e62de95827 | |||
| fc3612fa3f | |||
| 18fe211ad1 | |||
| 74ada01232 | |||
| 4fef360c06 | |||
| 2647941008 | |||
| b4bdc89888 | |||
| 829e5bade3 | |||
| fc9a4ef8fb | |||
| 3c5734ed4e | |||
| 4b77896ecb | |||
| 40c4a4e2bb | |||
| 8a05b93ff3 | |||
| c186932a79 | |||
| bf74173cac | |||
| 3dcebf4fcc |
+2
-2
@@ -22,5 +22,5 @@ keywords:
|
||||
- self-hosted
|
||||
- portabase
|
||||
license: Apache-2.0
|
||||
version: 1.1.2-rc.2
|
||||
date-released: "2026-02-16"
|
||||
version: 1.1.5
|
||||
date-released: "2026-02-19"
|
||||
Generated
+1
-1
@@ -2851,7 +2851,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portabase-agent"
|
||||
version = "1.1.2-rc.1"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "portabase-agent"
|
||||
version = "1.1.2-rc.2"
|
||||
version = "1.1.5"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ services:
|
||||
APP_ENV: development
|
||||
LOG: debug
|
||||
TZ: "Europe/Paris"
|
||||
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiOWZjYjhmNjUtNTcyMC00N2M3LThkNWItYWFlMmVjOTc2OWU1IiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
|
||||
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiZjg4Y2E0MDMtNDgwOS00NGM4LTlkZjItY2VkNWYwYzhkNTM2IiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
|
||||
#POOLING: 1
|
||||
#DATABASES_CONFIG_FILE: "config.toml"
|
||||
extra_hosts:
|
||||
|
||||
@@ -88,13 +88,10 @@ impl BackupService {
|
||||
|
||||
if result.status == "failed" {
|
||||
error!("Backup failed early for {}", result.generated_id);
|
||||
|
||||
let service = BackupService { ctx: ctx.clone() };
|
||||
|
||||
let _ = service
|
||||
.send_result(result, vec![], &backup_id)
|
||||
.await;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -280,8 +277,12 @@ impl BackupService {
|
||||
} else {
|
||||
"failed"
|
||||
};
|
||||
info!("Storage {} uploaded to remote path {:?}", storage_id, upload_result.remote_file_path);
|
||||
|
||||
if status != "success" {
|
||||
return upload_result;
|
||||
}
|
||||
|
||||
info!("Storage {} uploaded to remote path {:?}", storage_id, upload_result.remote_file_path);
|
||||
|
||||
let (remote_path, total_size) = match (
|
||||
&upload_result.remote_file_path,
|
||||
|
||||
@@ -58,13 +58,7 @@ impl StorageProvider for S3Provider {
|
||||
|
||||
let encrypt = encrypt.unwrap_or(false);
|
||||
|
||||
let upload = match build_stream(
|
||||
&file_path,
|
||||
encrypt,
|
||||
&ctx.edge_key.master_key_b64,
|
||||
)
|
||||
.await
|
||||
{
|
||||
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);
|
||||
@@ -101,15 +95,20 @@ impl StorageProvider for S3Provider {
|
||||
|
||||
let region = Region::new(config.region.clone().unwrap_or("us-east-1".to_string()));
|
||||
|
||||
let scheme = if config.ssl { "https" } else { "http" };
|
||||
|
||||
let endpoint = match config.port {
|
||||
Some(port) => format!("{scheme}://{}:{port}", config.end_point_url),
|
||||
None => format!("{scheme}://{}", config.end_point_url),
|
||||
};
|
||||
|
||||
info!("S3 endpoint to {}", &endpoint);
|
||||
|
||||
let sdk_config = s3::config::Builder::new()
|
||||
.credentials_provider(credentials)
|
||||
.region(region)
|
||||
.force_path_style(true)
|
||||
.endpoint_url(format!(
|
||||
"{}://{}",
|
||||
if config.ssl { "https" } else { "http" },
|
||||
config.end_point_url
|
||||
))
|
||||
.endpoint_url(endpoint)
|
||||
.behavior_version(BehaviorVersion::latest())
|
||||
.build();
|
||||
|
||||
@@ -282,8 +281,6 @@ impl StorageProvider for S3Provider {
|
||||
"Successfully completed multipart upload: {}",
|
||||
remote_file_path
|
||||
);
|
||||
|
||||
|
||||
UploadResult {
|
||||
storage_id: storage.id.clone(),
|
||||
success: true,
|
||||
|
||||
@@ -8,4 +8,5 @@ pub struct S3ProviderConfig {
|
||||
pub end_point_url: String,
|
||||
pub ssl: bool,
|
||||
pub region: Option<String>,
|
||||
pub port: Option<String>,
|
||||
}
|
||||
|
||||
+116
-24
@@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use log::info;
|
||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||
use tracing::{error, info};
|
||||
|
||||
const PATCH_CHUNK_SIZE: usize = 1 * 1024 * 1024;
|
||||
|
||||
@@ -18,39 +18,62 @@ where
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
info!("File size: {}", total_size);
|
||||
info!("Endpoint URL: {}", tus_endpoint);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Tus-Resumable", HeaderValue::from_static("1.0.0"));
|
||||
headers.insert("Upload-Defer-Length", HeaderValue::from_static("1"));
|
||||
let mut create_headers = HeaderMap::new();
|
||||
create_headers.insert("Tus-Resumable", HeaderValue::from_static("1.0.0"));
|
||||
create_headers.insert("Upload-Defer-Length", HeaderValue::from_static("1"));
|
||||
|
||||
let resp = client
|
||||
.post(tus_endpoint)
|
||||
.headers(headers.clone())
|
||||
.headers(create_headers.clone())
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
.context("Failed to send POST to create TUS upload")?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("Failed to create upload: {}", resp.status());
|
||||
let status = resp.status();
|
||||
let headers = resp.headers().clone();
|
||||
let body = resp
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "<failed to read body>".into());
|
||||
|
||||
error!(
|
||||
"TUS creation failed | status={} | headers={:?} | body={}",
|
||||
status, headers, body
|
||||
);
|
||||
|
||||
anyhow::bail!(
|
||||
"Failed to create upload.\nStatus: {}\nHeaders: {:?}\nBody: {}",
|
||||
status,
|
||||
headers,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
let upload_url = resp
|
||||
.headers()
|
||||
.get("Location")
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing Location header"))?
|
||||
.to_str()?
|
||||
.context("TUS creation response missing Location header")?
|
||||
.to_str()
|
||||
.context("Invalid Location header value")?
|
||||
.to_string();
|
||||
|
||||
let mut stream = Box::pin(
|
||||
encrypted_stream.map(|r| r.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))),
|
||||
);
|
||||
|
||||
let mut stream = Box::pin(encrypted_stream);
|
||||
let mut offset: u64 = 0;
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
let chunk = chunk.context("Stream produced IO error")?;
|
||||
|
||||
for sub_chunk in chunk.chunks(PATCH_CHUNK_SIZE) {
|
||||
let mut patch_headers = extra_headers.clone();
|
||||
patch_headers.insert("Tus-Resumable", HeaderValue::from_static("1.0.0"));
|
||||
patch_headers.insert("Upload-Offset", HeaderValue::from_str(&offset.to_string())?);
|
||||
patch_headers.insert(
|
||||
"Upload-Offset",
|
||||
HeaderValue::from_str(&offset.to_string())
|
||||
.context("Invalid offset header value")?,
|
||||
);
|
||||
patch_headers.insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/offset+octet-stream"),
|
||||
@@ -61,24 +84,69 @@ where
|
||||
.headers(patch_headers)
|
||||
.body(sub_chunk.to_vec())
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
.with_context(|| format!("PATCH request failed at offset {}", offset))?;
|
||||
|
||||
if !patch_resp.status().is_success() {
|
||||
let status = patch_resp.status();
|
||||
let headers = patch_resp.headers().clone();
|
||||
let body = patch_resp
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "<failed to read body>".into());
|
||||
|
||||
error!(
|
||||
"TUS PATCH failure | offset={} | status={} | body={}",
|
||||
offset, status, body
|
||||
);
|
||||
|
||||
anyhow::bail!(
|
||||
"Chunk upload failed at offset {}: {}",
|
||||
"Chunk upload failed.\n\
|
||||
URL: {}\n\
|
||||
Offset: {}\n\
|
||||
Status: {}\n\
|
||||
Headers: {:?}\n\
|
||||
Body: {}",
|
||||
upload_url,
|
||||
offset,
|
||||
patch_resp.status()
|
||||
status,
|
||||
headers,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(server_offset) = patch_resp.headers().get("Upload-Offset") {
|
||||
let server_offset = server_offset
|
||||
.to_str()
|
||||
.context("Invalid Upload-Offset header")?
|
||||
.parse::<u64>()
|
||||
.context("Failed to parse Upload-Offset header")?;
|
||||
|
||||
let expected = offset + sub_chunk.len() as u64;
|
||||
|
||||
if server_offset != expected {
|
||||
anyhow::bail!(
|
||||
"Offset mismatch detected.\nLocal expected: {}\nServer returned: {}",
|
||||
expected,
|
||||
server_offset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
offset += sub_chunk.len() as u64;
|
||||
// info!("Progress: {}/{}", offset, total_size);
|
||||
}
|
||||
}
|
||||
|
||||
let mut finalize_headers = extra_headers.clone();
|
||||
finalize_headers.insert("Tus-Resumable", HeaderValue::from_static("1.0.0"));
|
||||
finalize_headers.insert("Upload-Offset", HeaderValue::from_str(&offset.to_string())?);
|
||||
finalize_headers.insert("Upload-Length", HeaderValue::from_str(&offset.to_string())?);
|
||||
finalize_headers.insert(
|
||||
"Upload-Offset",
|
||||
HeaderValue::from_str(&offset.to_string()).context("Invalid finalize offset header")?,
|
||||
);
|
||||
finalize_headers.insert(
|
||||
"Upload-Length",
|
||||
HeaderValue::from_str(&offset.to_string()).context("Invalid finalize length header")?,
|
||||
);
|
||||
finalize_headers.insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/offset+octet-stream"),
|
||||
@@ -88,11 +156,35 @@ where
|
||||
.patch(&upload_url)
|
||||
.headers(finalize_headers)
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
.context("Finalize PATCH request failed")?;
|
||||
|
||||
if !finalize_resp.status().is_success() {
|
||||
anyhow::bail!("Failed to finalize upload");
|
||||
let status = finalize_resp.status();
|
||||
let body = finalize_resp
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "<failed to read body>".into());
|
||||
|
||||
error!(
|
||||
"TUS finalize failure | offset={} | status={} | body={}",
|
||||
offset, status, body
|
||||
);
|
||||
|
||||
anyhow::bail!(
|
||||
"Finalize upload failed.\n\
|
||||
URL: {}\n\
|
||||
Final offset: {}\n\
|
||||
Status: {}\n\
|
||||
Body: {}",
|
||||
upload_url,
|
||||
offset,
|
||||
status,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
info!("Upload completed successfully. Final size: {}", offset);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user