From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 600E91FF0E7 for ; Thu, 13 Aug 2026 19:11:27 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 8AEC521AF5; Thu, 13 Aug 2026 19:11:14 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup 20/28] sync: push: allow to set retention timestamp for synced snapshots Date: Thu, 13 Aug 2026 19:09:54 +0200 Message-ID: <20260813171002.809441-21-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260813171002.809441-1-c.ebner@proxmox.com> References: <20260813171002.809441-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1786641017337 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.199 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: PJUF2P3LCKEUYXIOCZ6ZJTY7VT7EC2HJ X-Message-ID-Hash: PJUF2P3LCKEUYXIOCZ6ZJTY7VT7EC2HJ X-MailFrom: c.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Allow the push sync job to present the retention timestamp when pushing the snapshot to the remote, if the remote supports it. The timestamp is calculated based on the start time of the sync job by push parameter construction. Signed-off-by: Christian Ebner --- src/api2/push.rs | 8 +++++++- src/server/push.rs | 19 ++++++++++++++++++- src/server/sync.rs | 1 + 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/api2/push.rs b/src/api2/push.rs index 16bcdf85b..e74bf78c3 100644 --- a/src/api2/push.rs +++ b/src/api2/push.rs @@ -5,7 +5,7 @@ use pbs_api_types::{ Authid, BackupNamespace, CRYPT_KEY_ID_SCHEMA, DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA, GroupFilter, NS_MAX_DEPTH_REDUCED_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_READ, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_PRUNE, REMOTE_ID_SCHEMA, - REMOVE_VANISHED_BACKUPS_SCHEMA, RateLimitConfig, SYNC_ENCRYPTED_ONLY_SCHEMA, + REMOVE_VANISHED_BACKUPS_SCHEMA, RateLimitConfig, RetentionTimespan, SYNC_ENCRYPTED_ONLY_SCHEMA, SYNC_VERIFIED_ONLY_SCHEMA, SYNC_WORKER_THREADS_SCHEMA, TRANSFER_LAST_SCHEMA, }; use proxmox_rest_server::WorkerTask; @@ -116,6 +116,10 @@ fn check_push_privs( schema: CRYPT_KEY_ID_SCHEMA, optional: true, }, + "retention-timespan": { + type: RetentionTimespan, + optional: true, + }, }, }, access: { @@ -143,6 +147,7 @@ async fn push( transfer_last: Option, worker_threads: Option, encryption_key: Option, + retention_timespan: Option, rpcenv: &mut dyn RpcEnvironment, ) -> Result { let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -176,6 +181,7 @@ async fn push( transfer_last, worker_threads, encryption_key, + retention_timespan, ) .await?; diff --git a/src/server/push.rs b/src/server/push.rs index 83e4ea22d..617b2ba9b 100644 --- a/src/server/push.rs +++ b/src/server/push.rs @@ -17,7 +17,7 @@ use pbs_api_types::{ GroupListItem, MANIFEST_BLOB_NAME, NamespaceListItem, Operation, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_READ, PRIV_REMOTE_DATASTORE_APPEND, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_MODIFY, PRIV_REMOTE_DATASTORE_PRUNE, RateLimitConfig, Remote, - SnapshotListItem, print_store_and_ns, + RetentionTimespan, SnapshotListItem, print_store_and_ns, }; use pbs_client::{ BackupRepository, BackupStats, BackupWriter, BackupWriterOptions, HttpClient, IndexType, @@ -98,6 +98,9 @@ pub(crate) struct PushParameters { /// Encryption key to use for pushing unencrypted backup snapshots. Does not affect /// already encrypted snapshots. crypt_config: Option<(String, Arc)>, + /// Retention timestamp to be set for newly pushed snapshots on sync target, + /// overwrites existing retentions if present on source manifest. + retain_until: Option, } impl PushParameters { @@ -119,6 +122,7 @@ impl PushParameters { transfer_last: Option, worker_threads: Option, active_encryption_key: Option, + retention_timespan: Option, ) -> Result { if let Some(max_depth) = max_depth { ns.check_max_depth(max_depth)?; @@ -161,6 +165,17 @@ impl PushParameters { bail!("Unsupported remote api version, minimum v2.2 required"); } + let retain_until = retention_timespan + .map(|timespan| { + if api_version < ApiVersion::new(4, 2, 6) { + bail!( + "Unsupported 'retain-until' by remote api version, minimum v4.2.6 required" + ); + } + timespan.to_timestamp_from_systemtime() + }) + .transpose()?; + let supports_prune_delete_stats = api_version >= ApiVersion::new(3, 2, 11); let target = PushTarget { @@ -192,6 +207,7 @@ impl PushParameters { transfer_last, worker_threads, crypt_config, + retain_until, }) } @@ -1113,6 +1129,7 @@ pub(crate) async fn push_snapshot( debug: false, benchmark: false, no_cache: false, + retain_until: params.retain_until, }, ) .await diff --git a/src/server/sync.rs b/src/server/sync.rs index 11f30d318..7b9510be2 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -752,6 +752,7 @@ pub fn do_sync_job( sync_job.transfer_last, sync_job.worker_threads, sync_job.active_encryption_key, + sync_job.retention_timespan, ) .await?; push_store(push_params).await? -- 2.47.3