* [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots
@ 2021-09-23 11:37 Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 01/13] pbs-datastore: add protection info to BackupInfo Dominik Csapak
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
add the means to 'protect' a snapshot against pruning and removal by
adding a file '.protected' in the snapshot folder
in the v2, i opted against syncing the protected flag for two reasons:
* it gives the admin the choice on each datastore if he wants to keep
the snapshot or not(if we sync it, we would have to sync it everytime?)
* it is much simpler to implement
changes from v1:
* rebase on master
* return the PruneMark enum instead of two booleans in compute_prune_info
* lock the snapshot when setting/removig the flag
* don't bail if the file does not exist on removal
* add a 'protected show' command to the client
* add a docs section to explain the protected flag
changes from rfc:
* added gui parts
* added tests
* fixed a bug (that the test uncovered)
* add pbs client command
* prevent removal (also during sync)
Dominik Csapak (13):
pbs-datastore: add protection info to BackupInfo
pbs-datastore: skip protected backups in pruning
add protected info of snapshots to api and task logs
tests/prune: add tests for protecteded backups
backup/datastore: prevent protected snapshots to be removed
pull_store/group: dont try remove locally protected snapshots
api2: datastore/delete_group: throw error for partially removed group
api2/admin/datastore: add get/set_protection
proxmox-backup-client: add 'protected' commands
ui: PruneInputPanel: add keepReason 'protected' for protected backups
ui: add protected icon to snapshots
fix #3602: ui: datastore/Content: add action to set protection status
docs: add info about protection flag to client docs
docs/backup-client.rst | 19 +++++
pbs-api-types/src/datastore.rs | 2 +
pbs-datastore/src/backup_info.rs | 20 ++++-
pbs-datastore/src/prune.rs | 44 ++++++++--
proxmox-backup-client/src/snapshot.rs | 113 ++++++++++++++++++++++++++
src/api2/admin/datastore.rs | 111 +++++++++++++++++++++++--
src/backup/datastore.rs | 61 +++++++++++---
src/server/prune_job.rs | 6 +-
src/server/pull.rs | 19 ++++-
tests/prune.rs | 44 +++++++++-
www/datastore/Content.js | 75 +++++++++++++++++
www/datastore/Prune.js | 4 +
12 files changed, 481 insertions(+), 37 deletions(-)
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 01/13] pbs-datastore: add protection info to BackupInfo
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 02/13] pbs-datastore: skip protected backups in pruning Dominik Csapak
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
and add necessary helper functions (protected_file/is_protected)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
pbs-datastore/src/backup_info.rs | 20 ++++++++++++++++++--
tests/prune.rs | 2 +-
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/pbs-datastore/src/backup_info.rs b/pbs-datastore/src/backup_info.rs
index f77098ee..032fad8b 100644
--- a/pbs-datastore/src/backup_info.rs
+++ b/pbs-datastore/src/backup_info.rs
@@ -92,7 +92,9 @@ impl BackupGroup {
BackupDir::with_rfc3339(&self.backup_type, &self.backup_id, backup_time)?;
let files = list_backup_files(l2_fd, backup_time)?;
- list.push(BackupInfo { backup_dir, files });
+ let protected = backup_dir.is_protected(base_path.to_owned());
+
+ list.push(BackupInfo { backup_dir, files, protected });
Ok(())
},
@@ -253,6 +255,17 @@ impl BackupDir {
relative_path
}
+ pub fn protected_file(&self, mut path: PathBuf) -> PathBuf {
+ path.push(self.relative_path());
+ path.push(".protected");
+ path
+ }
+
+ pub fn is_protected(&self, base_path: PathBuf) -> bool {
+ let path = self.protected_file(base_path);
+ path.exists()
+ }
+
pub fn backup_time_to_string(backup_time: i64) -> Result<String, Error> {
// fixme: can this fail? (avoid unwrap)
proxmox::tools::time::epoch_to_rfc3339_utc(backup_time)
@@ -293,6 +306,8 @@ pub struct BackupInfo {
pub backup_dir: BackupDir,
/// List of data files
pub files: Vec<String>,
+ /// Protection Status
+ pub protected: bool,
}
impl BackupInfo {
@@ -301,8 +316,9 @@ impl BackupInfo {
path.push(backup_dir.relative_path());
let files = list_backup_files(libc::AT_FDCWD, &path)?;
+ let protected = backup_dir.is_protected(base_path.to_owned());
- Ok(BackupInfo { backup_dir, files })
+ Ok(BackupInfo { backup_dir, files, protected })
}
/// Finds the latest backup inside a backup group
diff --git a/tests/prune.rs b/tests/prune.rs
index 35b40ad7..a733ff82 100644
--- a/tests/prune.rs
+++ b/tests/prune.rs
@@ -41,7 +41,7 @@ fn create_info(
files.push(String::from(MANIFEST_BLOB_NAME));
}
- BackupInfo { backup_dir, files }
+ BackupInfo { backup_dir, files, protected: false }
}
#[test]
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 02/13] pbs-datastore: skip protected backups in pruning
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 01/13] pbs-datastore: add protection info to BackupInfo Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 03/13] add protected info of snapshots to api and task logs Dominik Csapak
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
as a separate keep reason so it will not be calculated for the other reasons
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
pbs-datastore/src/prune.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/pbs-datastore/src/prune.rs b/pbs-datastore/src/prune.rs
index 3b4cf4f2..1eb4208f 100644
--- a/pbs-datastore/src/prune.rs
+++ b/pbs-datastore/src/prune.rs
@@ -7,7 +7,7 @@ use pbs_api_types::PruneOptions;
use super::BackupInfo;
-enum PruneMark { Keep, KeepPartial, Remove }
+enum PruneMark { Protected, Keep, KeepPartial, Remove }
fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
mark: &mut HashMap<PathBuf, PruneMark>,
@@ -30,6 +30,10 @@ fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
for info in list {
let backup_id = info.backup_dir.relative_path();
if mark.get(&backup_id).is_some() { continue; }
+ if info.protected {
+ mark.insert(backup_id, PruneMark::Protected);
+ continue;
+ }
let sel_id: String = select_id(&info)?;
if already_included.contains(&sel_id) { continue; }
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 03/13] add protected info of snapshots to api and task logs
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 01/13] pbs-datastore: add protection info to BackupInfo Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 02/13] pbs-datastore: skip protected backups in pruning Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 04/13] tests/prune: add tests for protecteded backups Dominik Csapak
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
adds the info that a snapshot is protected to:
* snapshot list
* manual pruning (also dry-run)
* prune jobs
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
pbs-api-types/src/datastore.rs | 2 ++
pbs-datastore/src/prune.rs | 40 +++++++++++++++++++++++++++-------
src/api2/admin/datastore.rs | 15 ++++++++-----
src/server/prune_job.rs | 6 ++---
tests/prune.rs | 4 ++--
5 files changed, 49 insertions(+), 18 deletions(-)
diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs
index 75f82ea4..31b4d483 100644
--- a/pbs-api-types/src/datastore.rs
+++ b/pbs-api-types/src/datastore.rs
@@ -393,6 +393,8 @@ pub struct SnapshotListItem {
/// The owner of the snapshots group
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<Authid>,
+ /// Protection from prunes
+ pub protected: bool,
}
#[api(
diff --git a/pbs-datastore/src/prune.rs b/pbs-datastore/src/prune.rs
index 1eb4208f..7614999c 100644
--- a/pbs-datastore/src/prune.rs
+++ b/pbs-datastore/src/prune.rs
@@ -7,7 +7,30 @@ use pbs_api_types::PruneOptions;
use super::BackupInfo;
-enum PruneMark { Protected, Keep, KeepPartial, Remove }
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum PruneMark { Protected, Keep, KeepPartial, Remove }
+
+impl PruneMark {
+ pub fn keep(&self) -> bool {
+ *self != PruneMark::Remove
+ }
+
+ pub fn protected(&self) -> bool {
+ *self == PruneMark::Protected
+ }
+}
+
+impl std::fmt::Display for PruneMark {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let txt = match self {
+ PruneMark::Protected => "protected",
+ PruneMark::Keep => "keep",
+ PruneMark::KeepPartial => "keep-partial",
+ PruneMark::Remove => "remove",
+ };
+ write!(f, "{}", txt)
+ }
+}
fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
mark: &mut HashMap<PathBuf, PruneMark>,
@@ -125,7 +148,7 @@ pub fn cli_options_string(options: &PruneOptions) -> String {
pub fn compute_prune_info(
mut list: Vec<BackupInfo>,
options: &PruneOptions,
-) -> Result<Vec<(BackupInfo, bool)>, Error> {
+) -> Result<Vec<(BackupInfo, PruneMark)>, Error> {
let mut mark = HashMap::new();
@@ -173,15 +196,16 @@ pub fn compute_prune_info(
})?;
}
- let prune_info: Vec<(BackupInfo, bool)> = list.into_iter()
+ let prune_info: Vec<(BackupInfo, PruneMark)> = list.into_iter()
.map(|info| {
let backup_id = info.backup_dir.relative_path();
- let keep = match mark.get(&backup_id) {
- Some(PruneMark::Keep) => true,
- Some(PruneMark::KeepPartial) => true,
- _ => false,
+ let mark = if info.protected {
+ PruneMark::Protected
+ } else {
+ *mark.get(&backup_id).unwrap_or(&PruneMark::Remove)
};
- (info, keep)
+
+ (info, mark)
})
.collect();
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 0b14dfbf..7390a62f 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -439,6 +439,7 @@ pub fn list_snapshots (
let backup_type = group.backup_type().to_string();
let backup_id = group.backup_id().to_string();
let backup_time = info.backup_dir.backup_time();
+ let protected = info.backup_dir.is_protected(base_path.clone());
match get_all_snapshot_files(&datastore, &info) {
Ok((manifest, files)) => {
@@ -477,6 +478,7 @@ pub fn list_snapshots (
files,
size,
owner,
+ protected,
}
},
Err(err) => {
@@ -501,6 +503,7 @@ pub fn list_snapshots (
files,
size: None,
owner,
+ protected,
}
},
}
@@ -844,8 +847,8 @@ pub fn prune(
let keep_all = !pbs_datastore::prune::keeps_something(&prune_options);
if dry_run {
- for (info, mut keep) in prune_info {
- if keep_all { keep = true; }
+ for (info, mark) in prune_info {
+ let keep = keep_all || mark.keep();
let backup_time = info.backup_dir.backup_time();
let group = info.backup_dir.group();
@@ -855,6 +858,7 @@ pub fn prune(
"backup-id": group.backup_id(),
"backup-time": backup_time,
"keep": keep,
+ "protected": mark.protected(),
}));
}
return Ok(json!(prune_result));
@@ -872,8 +876,8 @@ pub fn prune(
store, backup_type, backup_id));
}
- for (info, mut keep) in prune_info {
- if keep_all { keep = true; }
+ for (info, mark) in prune_info {
+ let keep = keep_all || mark.keep();
let backup_time = info.backup_dir.backup_time();
let timestamp = info.backup_dir.backup_time_string();
@@ -885,7 +889,7 @@ pub fn prune(
group.backup_type(),
group.backup_id(),
timestamp,
- if keep { "keep" } else { "remove" },
+ mark,
);
worker.log(msg);
@@ -895,6 +899,7 @@ pub fn prune(
"backup-id": group.backup_id(),
"backup-time": backup_time,
"keep": keep,
+ "protected": mark.protected(),
}));
if !(dry_run || keep) {
diff --git a/src/server/prune_job.rs b/src/server/prune_job.rs
index f298a74c..42c806a1 100644
--- a/src/server/prune_job.rs
+++ b/src/server/prune_job.rs
@@ -65,12 +65,12 @@ pub fn prune_datastore(
group.backup_id()
);
- for (info, mut keep) in prune_info {
- if keep_all { keep = true; }
+ for (info, mark) in prune_info {
+ let keep = keep_all || mark.keep();
task_log!(
worker,
"{} {}/{}/{}",
- if keep { "keep" } else { "remove" },
+ mark,
group.backup_type(),
group.backup_id(),
info.backup_dir.backup_time_string()
diff --git a/tests/prune.rs b/tests/prune.rs
index a733ff82..14ecc887 100644
--- a/tests/prune.rs
+++ b/tests/prune.rs
@@ -18,8 +18,8 @@ fn get_prune_list(
prune_info
.iter()
- .filter_map(|(info, keep)| {
- if *keep != return_kept {
+ .filter_map(|(info, mark)| {
+ if mark.keep() != return_kept {
None
} else {
Some(info.backup_dir.relative_path())
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 04/13] tests/prune: add tests for protecteded backups
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (2 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 03/13] add protected info of snapshots to api and task logs Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 05/13] backup/datastore: prevent protected snapshots to be removed Dominik Csapak
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
tests/prune.rs | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tests/prune.rs b/tests/prune.rs
index 14ecc887..24bd10cd 100644
--- a/tests/prune.rs
+++ b/tests/prune.rs
@@ -44,6 +44,44 @@ fn create_info(
BackupInfo { backup_dir, files, protected: false }
}
+fn create_info_protected(
+ snapshot: &str,
+ partial: bool,
+) -> BackupInfo {
+ let mut info = create_info(snapshot, partial);
+ info.protected = true;
+ info
+}
+
+#[test]
+fn test_prune_protected() -> Result<(), Error> {
+ let mut orig_list = Vec::new();
+
+ orig_list.push(create_info_protected("host/elsa/2019-11-15T09:39:15Z", false));
+ orig_list.push(create_info("host/elsa/2019-11-15T10:39:15Z", false));
+ orig_list.push(create_info("host/elsa/2019-11-15T10:49:15Z", false));
+ orig_list.push(create_info_protected("host/elsa/2019-11-15T10:59:15Z", false));
+
+ eprintln!("{:?}", orig_list);
+
+ let mut options = PruneOptions::default();
+ options.keep_last = Some(1);
+ let remove_list = get_prune_list(orig_list.clone(), false, &options);
+ let expect: Vec<PathBuf> = vec![
+ PathBuf::from("host/elsa/2019-11-15T10:39:15Z"),
+ ];
+ assert_eq!(remove_list, expect);
+
+ let mut options = PruneOptions::default();
+ options.keep_hourly = Some(1);
+ let remove_list = get_prune_list(orig_list.clone(), false, &options);
+ let expect: Vec<PathBuf> = vec![
+ PathBuf::from("host/elsa/2019-11-15T10:39:15Z"),
+ ];
+ assert_eq!(remove_list, expect);
+ Ok(())
+}
+
#[test]
fn test_prune_hourly() -> Result<(), Error> {
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 05/13] backup/datastore: prevent protected snapshots to be removed
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (3 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 04/13] tests/prune: add tests for protecteded backups Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 06/13] pull_store/group: dont try remove locally protected snapshots Dominik Csapak
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
by throwing an error for remove_backup_dir, and skipping for
remove_backup_group
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/backup/datastore.rs | 37 +++++++++++++++++++++++++------------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs
index df8d46b6..a791807f 100644
--- a/src/backup/datastore.rs
+++ b/src/backup/datastore.rs
@@ -267,8 +267,9 @@ impl DataStore {
full_path
}
- /// Remove a complete backup group including all snapshots
- pub fn remove_backup_group(&self, backup_group: &BackupGroup) -> Result<(), Error> {
+ /// Remove a complete backup group including all snapshots, returns true
+ /// if all snapshots were removed, and false if some were protected
+ pub fn remove_backup_group(&self, backup_group: &BackupGroup) -> Result<bool, Error> {
let full_path = self.group_path(backup_group);
@@ -276,22 +277,30 @@ impl DataStore {
log::info!("removing backup group {:?}", full_path);
+ let mut removed_all = true;
+
// remove all individual backup dirs first to ensure nothing is using them
for snap in backup_group.list_backups(&self.base_path())? {
+ if snap.backup_dir.is_protected(self.base_path()) {
+ removed_all = false;
+ continue;
+ }
self.remove_backup_dir(&snap.backup_dir, false)?;
}
- // no snapshots left, we can now safely remove the empty folder
- std::fs::remove_dir_all(&full_path)
- .map_err(|err| {
- format_err!(
- "removing backup group directory {:?} failed - {}",
- full_path,
- err,
- )
- })?;
+ if removed_all {
+ // no snapshots left, we can now safely remove the empty folder
+ std::fs::remove_dir_all(&full_path)
+ .map_err(|err| {
+ format_err!(
+ "removing backup group directory {:?} failed - {}",
+ full_path,
+ err,
+ )
+ })?;
+ }
- Ok(())
+ Ok(removed_all)
}
/// Remove a backup directory including all content
@@ -305,6 +314,10 @@ impl DataStore {
_manifest_guard = self.lock_manifest(backup_dir)?;
}
+ if backup_dir.is_protected(self.base_path()) {
+ bail!("cannot remove protected snapshot");
+ }
+
log::info!("removing backup snapshot {:?}", full_path);
std::fs::remove_dir_all(&full_path)
.map_err(|err| {
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 06/13] pull_store/group: dont try remove locally protected snapshots
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (4 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 05/13] backup/datastore: prevent protected snapshots to be removed Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 07/13] api2: datastore/delete_group: throw error for partially removed group Dominik Csapak
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
and log if a vanished groups could not be completely deleted if it
contains protected snapshots
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/server/pull.rs | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/server/pull.rs b/src/server/pull.rs
index 5214a218..910ba03b 100644
--- a/src/server/pull.rs
+++ b/src/server/pull.rs
@@ -609,6 +609,13 @@ pub async fn pull_group(
if remote_snapshots.contains(&backup_time) {
continue;
}
+ if info.backup_dir.is_protected(tgt_store.base_path()) {
+ worker.log(format!(
+ "don't delete vanished snapshot {:?} (protected)",
+ info.backup_dir.relative_path()
+ ));
+ continue;
+ }
worker.log(format!(
"delete vanished snapshot {:?}",
info.backup_dir.relative_path()
@@ -722,9 +729,15 @@ pub async fn pull_store(
local_group.backup_type(),
local_group.backup_id()
));
- if let Err(err) = tgt_store.remove_backup_group(&local_group) {
- worker.log(err.to_string());
- errors = true;
+ match tgt_store.remove_backup_group(&local_group) {
+ Ok(true) => {},
+ Ok(false) => {
+ task_log!(worker, "kept some protected snapshots of group '{}'", local_group);
+ },
+ Err(err) => {
+ task_log!(worker, "{}", err);
+ errors = true;
+ }
}
}
Ok(())
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 07/13] api2: datastore/delete_group: throw error for partially removed group
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (5 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 06/13] pull_store/group: dont try remove locally protected snapshots Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 08/13] api2/admin/datastore: add get/set_protection Dominik Csapak
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
when a group could not be completely removed due to protected snapshot,
throw an error
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/api2/admin/datastore.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 7390a62f..04320f9c 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -270,7 +270,9 @@ pub fn delete_group(
check_priv_or_backup_owner(&datastore, &group, &auth_id, PRIV_DATASTORE_MODIFY)?;
- datastore.remove_backup_group(&group)?;
+ if !datastore.remove_backup_group(&group)? {
+ bail!("did not delete whole group because of protected snapthots");
+ }
Ok(Value::Null)
}
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 08/13] api2/admin/datastore: add get/set_protection
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (6 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 07/13] api2: datastore/delete_group: throw error for partially removed group Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 09/13] proxmox-backup-client: add 'protected' commands Dominik Csapak
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
for gettin/setting the protected flag for snapshots (akin to notes)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/api2/admin/datastore.rs | 92 +++++++++++++++++++++++++++++++++++++
src/backup/datastore.rs | 24 ++++++++++
2 files changed, 116 insertions(+)
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 04320f9c..a92efc02 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -1748,6 +1748,92 @@ pub fn set_notes(
Ok(())
}
+#[api(
+ input: {
+ properties: {
+ store: {
+ schema: DATASTORE_SCHEMA,
+ },
+ "backup-type": {
+ schema: BACKUP_TYPE_SCHEMA,
+ },
+ "backup-id": {
+ schema: BACKUP_ID_SCHEMA,
+ },
+ "backup-time": {
+ schema: BACKUP_TIME_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
+ },
+)]
+/// Query protection for a specific backup
+pub fn get_protection(
+ store: String,
+ backup_type: String,
+ backup_id: String,
+ backup_time: i64,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<bool, Error> {
+ let datastore = DataStore::lookup_datastore(&store)?;
+
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_AUDIT)?;
+
+ let protected_path = backup_dir.protected_file(datastore.base_path());
+
+ Ok(protected_path.exists())
+}
+
+#[api(
+ input: {
+ properties: {
+ store: {
+ schema: DATASTORE_SCHEMA,
+ },
+ "backup-type": {
+ schema: BACKUP_TYPE_SCHEMA,
+ },
+ "backup-id": {
+ schema: BACKUP_ID_SCHEMA,
+ },
+ "backup-time": {
+ schema: BACKUP_TIME_SCHEMA,
+ },
+ protected: {
+ description: "Enable/disable protection.",
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["datastore", "{store}"],
+ PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
+ true),
+ },
+)]
+/// En- or disable protection for a specific backup
+pub fn set_protection(
+ store: String,
+ backup_type: String,
+ backup_id: String,
+ backup_time: i64,
+ protected: bool,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let datastore = DataStore::lookup_datastore(&store)?;
+
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_MODIFY)?;
+
+ datastore.update_protection(&backup_dir, protected)
+}
+
#[api(
input: {
properties: {
@@ -1896,6 +1982,12 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[
.get(&API_METHOD_GET_NOTES)
.put(&API_METHOD_SET_NOTES)
),
+ (
+ "protected",
+ &Router::new()
+ .get(&API_METHOD_GET_PROTECTION)
+ .put(&API_METHOD_SET_PROTECTION)
+ ),
(
"prune",
&Router::new()
diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs
index a791807f..2ab0e289 100644
--- a/src/backup/datastore.rs
+++ b/src/backup/datastore.rs
@@ -860,6 +860,30 @@ impl DataStore {
Ok(())
}
+ /// Updates the protection status of the specified snapshot.
+ pub fn update_protection(
+ &self,
+ backup_dir: &BackupDir,
+ protection: bool
+ ) -> Result<(), Error> {
+ let full_path = self.snapshot_path(backup_dir);
+
+ let _guard = lock_dir_noblock(&full_path, "snapshot", "possibly running or in use")?;
+
+ let protected_path = backup_dir.protected_file(self.base_path());
+ if protection {
+ std::fs::File::create(protected_path)
+ .map_err(|err| format_err!("could not create protection file: {}", err))?;
+ } else if let Err(err) = std::fs::remove_file(protected_path) {
+ // ignore error for non-existing file
+ if err.kind() != std::io::ErrorKind::NotFound {
+ bail!("could not remove protection file: {}", err);
+ }
+ }
+
+ Ok(())
+ }
+
pub fn verify_new(&self) -> bool {
self.verify_new
}
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 09/13] proxmox-backup-client: add 'protected' commands
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (7 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 08/13] api2/admin/datastore: add get/set_protection Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 10/13] ui: PruneInputPanel: add keepReason 'protected' for protected backups Dominik Csapak
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
includes 'update' and 'show' similar to the notes commands
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
proxmox-backup-client/src/snapshot.rs | 113 ++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
diff --git a/proxmox-backup-client/src/snapshot.rs b/proxmox-backup-client/src/snapshot.rs
index 4465f69a..37db5579 100644
--- a/proxmox-backup-client/src/snapshot.rs
+++ b/proxmox-backup-client/src/snapshot.rs
@@ -359,6 +359,118 @@ async fn update_notes(param: Value) -> Result<Value, Error> {
Ok(Value::Null)
}
+#[api(
+ input: {
+ properties: {
+ repository: {
+ schema: REPO_URL_SCHEMA,
+ optional: true,
+ },
+ snapshot: {
+ type: String,
+ description: "Snapshot path.",
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// Show protection status of the specified snapshot
+async fn show_protection(param: Value) -> Result<Value, Error> {
+ let repo = extract_repository_from_value(¶m)?;
+ let path = required_string_param(¶m, "snapshot")?;
+
+ let snapshot: BackupDir = path.parse()?;
+ let client = connect(&repo)?;
+
+ let path = format!("api2/json/admin/datastore/{}/protected", repo.store());
+
+ let args = json!({
+ "backup-type": snapshot.group().backup_type(),
+ "backup-id": snapshot.group().backup_id(),
+ "backup-time": snapshot.backup_time(),
+ });
+
+ let output_format = get_output_format(¶m);
+
+ let mut result = client.get(&path, Some(args)).await?;
+
+ let protected = result["data"].take();
+
+ if output_format == "text" {
+ if let Some(protected) = protected.as_bool() {
+ println!("{}", protected);
+ }
+ } else {
+ format_and_print_result(
+ &json!({
+ "protected": protected,
+ }),
+ &output_format,
+ );
+ }
+
+ Ok(Value::Null)
+}
+
+#[api(
+ input: {
+ properties: {
+ repository: {
+ schema: REPO_URL_SCHEMA,
+ optional: true,
+ },
+ snapshot: {
+ type: String,
+ description: "Snapshot path.",
+ },
+ protected: {
+ type: bool,
+ description: "The protection status.",
+ },
+ }
+ }
+)]
+/// Update Protection Status of a snapshot
+async fn update_protection(protected: bool, param: Value) -> Result<Value, Error> {
+ let repo = extract_repository_from_value(¶m)?;
+ let path = required_string_param(¶m, "snapshot")?;
+
+ let snapshot: BackupDir = path.parse()?;
+ let mut client = connect(&repo)?;
+
+ let path = format!("api2/json/admin/datastore/{}/protected", repo.store());
+
+ let args = json!({
+ "backup-type": snapshot.group().backup_type(),
+ "backup-id": snapshot.group().backup_id(),
+ "backup-time": snapshot.backup_time(),
+ "protected": protected,
+ });
+
+ client.put(&path, Some(args)).await?;
+
+ Ok(Value::Null)
+}
+
+fn protected_cli() -> CliCommandMap {
+ CliCommandMap::new()
+ .insert(
+ "show",
+ CliCommand::new(&API_METHOD_SHOW_PROTECTION)
+ .arg_param(&["snapshot"])
+ .completion_cb("snapshot", complete_backup_snapshot),
+ )
+ .insert(
+ "update",
+ CliCommand::new(&API_METHOD_UPDATE_PROTECTION)
+ .arg_param(&["snapshot", "protected"])
+ .completion_cb("snapshot", complete_backup_snapshot),
+ )
+}
+
fn notes_cli() -> CliCommandMap {
CliCommandMap::new()
.insert(
@@ -378,6 +490,7 @@ fn notes_cli() -> CliCommandMap {
pub fn snapshot_mgtm_cli() -> CliCommandMap {
CliCommandMap::new()
.insert("notes", notes_cli())
+ .insert("protected", protected_cli())
.insert(
"list",
CliCommand::new(&API_METHOD_LIST_SNAPSHOTS)
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 10/13] ui: PruneInputPanel: add keepReason 'protected' for protected backups
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (8 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 09/13] proxmox-backup-client: add 'protected' commands Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 11/13] ui: add protected icon to snapshots Dominik Csapak
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/datastore/Prune.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/www/datastore/Prune.js b/www/datastore/Prune.js
index 42ea21bf..e4db4b6c 100644
--- a/www/datastore/Prune.js
+++ b/www/datastore/Prune.js
@@ -103,6 +103,10 @@ Ext.define('PBS.Datastore.PruneInputPanel', {
let rule = nextRule();
for (let backup of backups) {
if (backup.keep) {
+ if (backup.protected) {
+ backup.keepReason = 'protected';
+ continue;
+ }
counter[rule]++;
if (rule !== 'keep-all') {
backup.keepReason = rule + ': ' + counter[rule];
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 11/13] ui: add protected icon to snapshots
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (9 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 10/13] ui: PruneInputPanel: add keepReason 'protected' for protected backups Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 12/13] fix #3602: ui: datastore/Content: add action to set protection status Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 13/13] docs: add info about protection flag to client docs Dominik Csapak
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
if they are protected
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/datastore/Content.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/www/datastore/Content.js b/www/datastore/Content.js
index 57693785..fbc3e12f 100644
--- a/www/datastore/Content.js
+++ b/www/datastore/Content.js
@@ -640,6 +640,13 @@ Ext.define('PBS.DataStoreContent', {
xtype: 'treecolumn',
header: gettext("Backup Group"),
dataIndex: 'text',
+ renderer: (value, meta, record) => {
+ let protect = "";
+ if (record.data.protected) {
+ protect = ` <i class="fa fa-shield"></i>`;
+ }
+ return value + protect;
+ },
flex: 1,
},
{
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 12/13] fix #3602: ui: datastore/Content: add action to set protection status
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (10 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 11/13] ui: add protected icon to snapshots Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 13/13] docs: add info about protection flag to client docs Dominik Csapak
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/datastore/Content.js | 68 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/www/datastore/Content.js b/www/datastore/Content.js
index fbc3e12f..6bb29c2a 100644
--- a/www/datastore/Content.js
+++ b/www/datastore/Content.js
@@ -491,6 +491,68 @@ Ext.define('PBS.DataStoreContent', {
});
},
+ onProtectionChange: function(view, rI, cI, item, e, rec) {
+ let me = this;
+ view = this.getView();
+
+ if (!(rec && rec.data)) return;
+ let data = rec.data;
+ if (!view.datastore) return;
+
+ let type = data["backup-type"];
+ let id = data["backup-id"];
+ let time = (data["backup-time"].getTime()/1000).toFixed(0);
+
+ let params = {
+ 'backup-type': type,
+ 'backup-id': id,
+ 'backup-time': time,
+ };
+
+ let url = `/api2/extjs/admin/datastore/${view.datastore}/protected`;
+
+ Ext.create('Proxmox.window.Edit', {
+ subject: gettext('Protection') + ` - ${data.text}`,
+ width: 400,
+
+ method: 'PUT',
+ autoShow: true,
+ isCreate: false,
+ autoLoad: true,
+
+ loadUrl: `${url}?${Ext.Object.toQueryString(params)}`,
+ url,
+
+ items: [
+ {
+ xtype: 'hidden',
+ name: 'backup-type',
+ value: type,
+ },
+ {
+ xtype: 'hidden',
+ name: 'backup-id',
+ value: id,
+ },
+ {
+ xtype: 'hidden',
+ name: 'backup-time',
+ value: time,
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Protected'),
+ uncheckedValue: 0,
+ name: 'protected',
+ value: data.protected,
+ },
+ ],
+ listeners: {
+ destroy: () => me.reload(),
+ },
+ });
+ },
+
onForget: function(view, rI, cI, item, e, rec) {
let me = this;
view = this.getView();
@@ -716,6 +778,12 @@ Ext.define('PBS.DataStoreContent', {
getClass: (v, m, rec) => rec.parentNode.id ==='root' ? 'fa fa-scissors' : 'pmx-hidden',
isActionDisabled: (v, r, c, i, rec) => rec.parentNode.id !=='root',
},
+ {
+ handler: 'onProtectionChange',
+ getTip: (v, m, rec) => Ext.String.format(gettext("Change protection of '{0}'"), v),
+ getClass: (v, m, rec) => !rec.data.leaf && rec.parentNode.id !== 'root' ? 'fa fa-shield' : 'pmx-hidden',
+ isActionDisabled: (v, r, c, i, rec) => !!rec.data.leaf || rec.parentNode.id === 'root',
+ },
{
handler: 'onForget',
getTip: (v, m, rec) => rec.parentNode.id !=='root'
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 13/13] docs: add info about protection flag to client docs
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
` (11 preceding siblings ...)
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 12/13] fix #3602: ui: datastore/Content: add action to set protection status Dominik Csapak
@ 2021-09-23 11:37 ` Dominik Csapak
12 siblings, 0 replies; 14+ messages in thread
From: Dominik Csapak @ 2021-09-23 11:37 UTC (permalink / raw)
To: pbs-devel
and mention that sync/pull does not sync the protected flag
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
docs/backup-client.rst | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/docs/backup-client.rst b/docs/backup-client.rst
index 02dae100..f18651ef 100644
--- a/docs/backup-client.rst
+++ b/docs/backup-client.rst
@@ -652,6 +652,25 @@ shows the list of existing snapshots and what actions prune would take.
in the chunk-store. The chunk-store still contains the data blocks. To free
space you need to perform :ref:`client_garbage-collection`.
+It is also possible to protect single snapshots from being pruned or deleted:
+
+.. code-block:: console
+
+ # proxmox-backup-client snapshot protected update <snapshot> true
+
+This will set the protected flag on the snapshot and prevent pruning or manual
+deletion of this snapshot untilt he flag is removed again with:
+
+.. code-block:: console
+
+ # proxmox-backup-client snapshot protected update <snapshot> false
+
+When a group is with a protected snapshot is deleted, only the non-protected
+ones are removed and the group will remain.
+
+.. note:: This flag will not be synced when using pull or sync jobs. If you
+ want to protect a synced snapshot, you have to manually to this again on
+ the target :ref:`backup server.
.. _client_garbage-collection:
--
2.30.2
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2021-09-23 11:38 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-23 11:37 [pbs-devel] [PATCH proxmox-backup v2 00/13] add 'protected' setting for snapshots Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 01/13] pbs-datastore: add protection info to BackupInfo Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 02/13] pbs-datastore: skip protected backups in pruning Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 03/13] add protected info of snapshots to api and task logs Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 04/13] tests/prune: add tests for protecteded backups Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 05/13] backup/datastore: prevent protected snapshots to be removed Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 06/13] pull_store/group: dont try remove locally protected snapshots Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 07/13] api2: datastore/delete_group: throw error for partially removed group Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 08/13] api2/admin/datastore: add get/set_protection Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 09/13] proxmox-backup-client: add 'protected' commands Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 10/13] ui: PruneInputPanel: add keepReason 'protected' for protected backups Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 11/13] ui: add protected icon to snapshots Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 12/13] fix #3602: ui: datastore/Content: add action to set protection status Dominik Csapak
2021-09-23 11:37 ` [pbs-devel] [PATCH proxmox-backup v2 13/13] docs: add info about protection flag to client docs Dominik Csapak
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox