move more api types for the client
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
ba0ccc5991
commit
ea584a7510
|
@ -16,3 +16,4 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
proxmox = { version = "0.11.5", default-features = false, features = [ "api-macro" ] }
|
proxmox = { version = "0.11.5", default-features = false, features = [ "api-macro" ] }
|
||||||
|
|
||||||
pbs-systemd = { path = "../pbs-systemd" }
|
pbs-systemd = { path = "../pbs-systemd" }
|
||||||
|
pbs-tools = { path = "../pbs-tools" }
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::api;
|
||||||
|
|
||||||
|
use pbs_tools::format::{as_fingerprint, bytes_as_fingerprint};
|
||||||
|
|
||||||
|
#[api(default: "encrypt")]
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Defines whether data is encrypted (using an AEAD cipher), only signed, or neither.
|
||||||
|
pub enum CryptMode {
|
||||||
|
/// Don't encrypt.
|
||||||
|
None,
|
||||||
|
/// Encrypt.
|
||||||
|
Encrypt,
|
||||||
|
/// Only sign.
|
||||||
|
SignOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
/// 32-byte fingerprint, usually calculated with SHA256.
|
||||||
|
pub struct Fingerprint {
|
||||||
|
#[serde(with = "bytes_as_fingerprint")]
|
||||||
|
bytes: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fingerprint {
|
||||||
|
pub fn new(bytes: [u8; 32]) -> Self {
|
||||||
|
Self { bytes }
|
||||||
|
}
|
||||||
|
pub fn bytes(&self) -> &[u8; 32] {
|
||||||
|
&self.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display as short key ID
|
||||||
|
impl Display for Fingerprint {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", as_fingerprint(&self.bytes[0..8]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Fingerprint {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Error> {
|
||||||
|
let mut tmp = s.to_string();
|
||||||
|
tmp.retain(|c| c != ':');
|
||||||
|
let bytes = proxmox::tools::hex_to_digest(&tmp)?;
|
||||||
|
Ok(Fingerprint::new(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use proxmox::api::api;
|
use proxmox::api::api;
|
||||||
use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema};
|
use proxmox::api::schema::{ApiStringFormat, EnumEntry, IntegerSchema, Schema, StringSchema};
|
||||||
use proxmox::const_regex;
|
use proxmox::const_regex;
|
||||||
use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
|
use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
|
||||||
|
|
||||||
|
@ -43,6 +43,9 @@ pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN
|
||||||
pub mod upid;
|
pub mod upid;
|
||||||
pub use upid::UPID;
|
pub use upid::UPID;
|
||||||
|
|
||||||
|
mod crypto;
|
||||||
|
pub use crypto::{CryptMode, Fingerprint};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod local_macros {
|
mod local_macros {
|
||||||
|
@ -115,6 +118,26 @@ pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_RE
|
||||||
pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX);
|
pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX);
|
||||||
pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX);
|
pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX);
|
||||||
|
|
||||||
|
pub const BACKUP_ID_SCHEMA: Schema = StringSchema::new("Backup ID.")
|
||||||
|
.format(&BACKUP_ID_FORMAT)
|
||||||
|
.schema();
|
||||||
|
pub const BACKUP_TYPE_SCHEMA: Schema = StringSchema::new("Backup type.")
|
||||||
|
.format(&ApiStringFormat::Enum(&[
|
||||||
|
EnumEntry::new("vm", "Virtual Machine Backup"),
|
||||||
|
EnumEntry::new("ct", "Container Backup"),
|
||||||
|
EnumEntry::new("host", "Host Backup"),
|
||||||
|
]))
|
||||||
|
.schema();
|
||||||
|
pub const BACKUP_TIME_SCHEMA: Schema = IntegerSchema::new("Backup time (Unix epoch.)")
|
||||||
|
.minimum(1_547_797_308)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
|
pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
|
||||||
ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
|
ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
|
||||||
|
|
||||||
|
@ -197,3 +220,207 @@ pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA25
|
||||||
pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
||||||
|
|
||||||
pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX);
|
pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX);
|
||||||
|
|
||||||
|
pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = StringSchema::new("Backup archive name.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
// Complex type definitions
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"filename": {
|
||||||
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
"crypt-mode": {
|
||||||
|
type: CryptMode,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Basic information about archive files inside a backup snapshot.
|
||||||
|
pub struct BackupContent {
|
||||||
|
pub filename: String,
|
||||||
|
/// Info if file is encrypted, signed, or neither.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub crypt_mode: Option<CryptMode>,
|
||||||
|
/// Archive size (from backup manifest).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub size: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
/// Result of a verify operation.
|
||||||
|
pub enum VerifyState {
|
||||||
|
/// Verification was successful
|
||||||
|
Ok,
|
||||||
|
/// Verification reported one or more errors
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
upid: {
|
||||||
|
type: UPID,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: VerifyState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
/// Task properties.
|
||||||
|
pub struct SnapshotVerifyState {
|
||||||
|
/// UPID of the verify task
|
||||||
|
pub upid: UPID,
|
||||||
|
/// State of the verification. Enum.
|
||||||
|
pub state: VerifyState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-time": {
|
||||||
|
schema: BACKUP_TIME_SCHEMA,
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
verification: {
|
||||||
|
type: SnapshotVerifyState,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
fingerprint: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
items: {
|
||||||
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||||
|
},
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
type: Authid,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Basic information about backup snapshot.
|
||||||
|
pub struct SnapshotListItem {
|
||||||
|
pub backup_type: String, // enum
|
||||||
|
pub backup_id: String,
|
||||||
|
pub backup_time: i64,
|
||||||
|
/// The first line from manifest "notes"
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
/// The result of the last run verify task
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub verification: Option<SnapshotVerifyState>,
|
||||||
|
/// Fingerprint of encryption key
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub fingerprint: Option<Fingerprint>,
|
||||||
|
/// List of contained archive files.
|
||||||
|
pub files: Vec<BackupContent>,
|
||||||
|
/// Overall snapshot size (sum of all archive sizes).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub size: Option<u64>,
|
||||||
|
/// The owner of the snapshots group
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub owner: Option<Authid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
"last-backup": {
|
||||||
|
schema: BACKUP_TIME_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-count": {
|
||||||
|
type: Integer,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
items: {
|
||||||
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||||
|
},
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
type: Authid,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Basic information about a backup group.
|
||||||
|
pub struct GroupListItem {
|
||||||
|
pub backup_type: String, // enum
|
||||||
|
pub backup_id: String,
|
||||||
|
pub last_backup: i64,
|
||||||
|
/// Number of contained snapshots
|
||||||
|
pub backup_count: u64,
|
||||||
|
/// List of contained archive files.
|
||||||
|
pub files: Vec<String>,
|
||||||
|
/// The owner of group
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub owner: Option<Authid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
store: {
|
||||||
|
schema: DATASTORE_SCHEMA,
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
optional: true,
|
||||||
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Basic information about a datastore.
|
||||||
|
pub struct DataStoreListItem {
|
||||||
|
pub store: String,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-time": {
|
||||||
|
schema: BACKUP_TIME_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Prune result.
|
||||||
|
pub struct PruneListItem {
|
||||||
|
pub backup_type: String, // enum
|
||||||
|
pub backup_id: String,
|
||||||
|
pub backup_time: i64,
|
||||||
|
/// Keep snapshot
|
||||||
|
pub keep: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -7,19 +7,14 @@
|
||||||
//! encryption](https://en.wikipedia.org/wiki/Authenticated_encryption)
|
//! encryption](https://en.wikipedia.org/wiki/Authenticated_encryption)
|
||||||
//! for a short introduction.
|
//! for a short introduction.
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::pkcs5::pbkdf2_hmac;
|
use openssl::pkcs5::pbkdf2_hmac;
|
||||||
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use proxmox::api::api;
|
pub use pbs_api_types::{CryptMode, Fingerprint};
|
||||||
|
|
||||||
use pbs_tools::format::{as_fingerprint, bytes_as_fingerprint};
|
|
||||||
|
|
||||||
// openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint")
|
// openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint")
|
||||||
/// This constant is used to compute fingerprints.
|
/// This constant is used to compute fingerprints.
|
||||||
|
@ -29,53 +24,6 @@ const FINGERPRINT_INPUT: [u8; 32] = [
|
||||||
97, 64, 127, 19, 76, 114, 93, 223,
|
97, 64, 127, 19, 76, 114, 93, 223,
|
||||||
48, 153, 45, 37, 236, 69, 237, 38,
|
48, 153, 45, 37, 236, 69, 237, 38,
|
||||||
];
|
];
|
||||||
#[api(default: "encrypt")]
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
/// Defines whether data is encrypted (using an AEAD cipher), only signed, or neither.
|
|
||||||
pub enum CryptMode {
|
|
||||||
/// Don't encrypt.
|
|
||||||
None,
|
|
||||||
/// Encrypt.
|
|
||||||
Encrypt,
|
|
||||||
/// Only sign.
|
|
||||||
SignOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
/// 32-byte fingerprint, usually calculated with SHA256.
|
|
||||||
pub struct Fingerprint {
|
|
||||||
#[serde(with = "bytes_as_fingerprint")]
|
|
||||||
bytes: [u8; 32],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fingerprint {
|
|
||||||
pub fn new(bytes: [u8; 32]) -> Self {
|
|
||||||
Self { bytes }
|
|
||||||
}
|
|
||||||
pub fn bytes(&self) -> &[u8; 32] {
|
|
||||||
&self.bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display as short key ID
|
|
||||||
impl Display for Fingerprint {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", as_fingerprint(&self.bytes[0..8]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for Fingerprint {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Error> {
|
|
||||||
let mut tmp = s.to_string();
|
|
||||||
tmp.retain(|c| c != ':');
|
|
||||||
let bytes = proxmox::tools::hex_to_digest(&tmp)?;
|
|
||||||
Ok(Fingerprint::new(bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encryption Configuration with secret key
|
/// Encryption Configuration with secret key
|
||||||
///
|
///
|
||||||
|
|
|
@ -197,6 +197,7 @@ pub mod key_derivation;
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
pub mod prune;
|
pub mod prune;
|
||||||
pub mod read_chunk;
|
pub mod read_chunk;
|
||||||
|
pub mod store_progress;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|
||||||
pub mod dynamic_index;
|
pub mod dynamic_index;
|
||||||
|
@ -216,3 +217,4 @@ pub use key_derivation::{
|
||||||
};
|
};
|
||||||
pub use key_derivation::{Kdf, KeyConfig, KeyDerivationConfig, KeyInfo};
|
pub use key_derivation::{Kdf, KeyConfig, KeyDerivationConfig, KeyInfo};
|
||||||
pub use manifest::BackupManifest;
|
pub use manifest::BackupManifest;
|
||||||
|
pub use store_progress::StoreProgress;
|
||||||
|
|
|
@ -85,10 +85,8 @@ pub enum ArchiveType {
|
||||||
Blob,
|
Blob,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn archive_type<P: AsRef<Path>>(
|
impl ArchiveType {
|
||||||
archive_name: P,
|
pub fn from_path(archive_name: impl AsRef<Path>) -> Result<Self, Error> {
|
||||||
) -> Result<ArchiveType, Error> {
|
|
||||||
|
|
||||||
let archive_name = archive_name.as_ref();
|
let archive_name = archive_name.as_ref();
|
||||||
let archive_type = match archive_name.extension().and_then(|ext| ext.to_str()) {
|
let archive_type = match archive_name.extension().and_then(|ext| ext.to_str()) {
|
||||||
Some("didx") => ArchiveType::DynamicIndex,
|
Some("didx") => ArchiveType::DynamicIndex,
|
||||||
|
@ -97,8 +95,15 @@ pub fn archive_type<P: AsRef<Path>>(
|
||||||
_ => bail!("unknown archive type: {:?}", archive_name),
|
_ => bail!("unknown archive type: {:?}", archive_name),
|
||||||
};
|
};
|
||||||
Ok(archive_type)
|
Ok(archive_type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[deprecated(note = "use ArchivType::from_path instead")] later...
|
||||||
|
pub fn archive_type<P: AsRef<Path>>(
|
||||||
|
archive_name: P,
|
||||||
|
) -> Result<ArchiveType, Error> {
|
||||||
|
ArchiveType::from_path(archive_name)
|
||||||
|
}
|
||||||
|
|
||||||
impl BackupManifest {
|
impl BackupManifest {
|
||||||
|
|
||||||
|
@ -114,7 +119,7 @@ impl BackupManifest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_file(&mut self, filename: String, size: u64, csum: [u8; 32], crypt_mode: CryptMode) -> Result<(), Error> {
|
pub fn add_file(&mut self, filename: String, size: u64, csum: [u8; 32], crypt_mode: CryptMode) -> Result<(), Error> {
|
||||||
let _archive_type = archive_type(&filename)?; // check type
|
let _archive_type = ArchiveType::from_path(&filename)?; // check type
|
||||||
self.files.push(FileInfo { filename, size, csum, crypt_mode });
|
self.files.push(FileInfo { filename, size, csum, crypt_mode });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,7 @@ use proxmox::const_regex;
|
||||||
use pbs_datastore::catalog::CatalogEntryType;
|
use pbs_datastore::catalog::CatalogEntryType;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backup::{
|
backup::DirEntryAttribute,
|
||||||
CryptMode,
|
|
||||||
Fingerprint,
|
|
||||||
DirEntryAttribute,
|
|
||||||
},
|
|
||||||
server::UPID,
|
|
||||||
config::acl::Role,
|
config::acl::Role,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,39 +239,10 @@ pub struct AclListItem {
|
||||||
pub roleid: String,
|
pub roleid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
|
|
||||||
StringSchema::new("Backup archive name.")
|
|
||||||
.format(&PROXMOX_SAFE_ID_FORMAT)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const BACKUP_TYPE_SCHEMA: Schema =
|
|
||||||
StringSchema::new("Backup type.")
|
|
||||||
.format(&ApiStringFormat::Enum(&[
|
|
||||||
EnumEntry::new("vm", "Virtual Machine Backup"),
|
|
||||||
EnumEntry::new("ct", "Container Backup"),
|
|
||||||
EnumEntry::new("host", "Host Backup")]))
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const BACKUP_ID_SCHEMA: Schema =
|
|
||||||
StringSchema::new("Backup ID.")
|
|
||||||
.format(&BACKUP_ID_FORMAT)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const BACKUP_TIME_SCHEMA: Schema =
|
|
||||||
IntegerSchema::new("Backup time (Unix epoch.)")
|
|
||||||
.minimum(1_547_797_308)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
|
pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
|
||||||
.max_length(256)
|
.max_length(256)
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
|
|
||||||
.format(&PROXMOX_SAFE_ID_FORMAT)
|
|
||||||
.min_length(3)
|
|
||||||
.max_length(32)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
|
pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
|
||||||
.format(&DATASTORE_MAP_FORMAT)
|
.format(&DATASTORE_MAP_FORMAT)
|
||||||
.min_length(3)
|
.min_length(3)
|
||||||
|
@ -391,180 +357,6 @@ pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name.")
|
||||||
|
|
||||||
// Complex type definitions
|
// Complex type definitions
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
store: {
|
|
||||||
schema: DATASTORE_SCHEMA,
|
|
||||||
},
|
|
||||||
comment: {
|
|
||||||
optional: true,
|
|
||||||
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
/// Basic information about a datastore.
|
|
||||||
pub struct DataStoreListItem {
|
|
||||||
pub store: String,
|
|
||||||
pub comment: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
"backup-type": {
|
|
||||||
schema: BACKUP_TYPE_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-id": {
|
|
||||||
schema: BACKUP_ID_SCHEMA,
|
|
||||||
},
|
|
||||||
"last-backup": {
|
|
||||||
schema: BACKUP_TIME_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-count": {
|
|
||||||
type: Integer,
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
items: {
|
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
|
||||||
},
|
|
||||||
},
|
|
||||||
owner: {
|
|
||||||
type: Authid,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
/// Basic information about a backup group.
|
|
||||||
pub struct GroupListItem {
|
|
||||||
pub backup_type: String, // enum
|
|
||||||
pub backup_id: String,
|
|
||||||
pub last_backup: i64,
|
|
||||||
/// Number of contained snapshots
|
|
||||||
pub backup_count: u64,
|
|
||||||
/// List of contained archive files.
|
|
||||||
pub files: Vec<String>,
|
|
||||||
/// The owner of group
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub owner: Option<Authid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api()]
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
/// Result of a verify operation.
|
|
||||||
pub enum VerifyState {
|
|
||||||
/// Verification was successful
|
|
||||||
Ok,
|
|
||||||
/// Verification reported one or more errors
|
|
||||||
Failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
upid: {
|
|
||||||
schema: UPID_SCHEMA
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
type: VerifyState
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
/// Task properties.
|
|
||||||
pub struct SnapshotVerifyState {
|
|
||||||
/// UPID of the verify task
|
|
||||||
pub upid: UPID,
|
|
||||||
/// State of the verification. Enum.
|
|
||||||
pub state: VerifyState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
"backup-type": {
|
|
||||||
schema: BACKUP_TYPE_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-id": {
|
|
||||||
schema: BACKUP_ID_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-time": {
|
|
||||||
schema: BACKUP_TIME_SCHEMA,
|
|
||||||
},
|
|
||||||
comment: {
|
|
||||||
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
verification: {
|
|
||||||
type: SnapshotVerifyState,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
fingerprint: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
items: {
|
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
|
||||||
},
|
|
||||||
},
|
|
||||||
owner: {
|
|
||||||
type: Authid,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
/// Basic information about backup snapshot.
|
|
||||||
pub struct SnapshotListItem {
|
|
||||||
pub backup_type: String, // enum
|
|
||||||
pub backup_id: String,
|
|
||||||
pub backup_time: i64,
|
|
||||||
/// The first line from manifest "notes"
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub comment: Option<String>,
|
|
||||||
/// The result of the last run verify task
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub verification: Option<SnapshotVerifyState>,
|
|
||||||
/// Fingerprint of encryption key
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub fingerprint: Option<Fingerprint>,
|
|
||||||
/// List of contained archive files.
|
|
||||||
pub files: Vec<BackupContent>,
|
|
||||||
/// Overall snapshot size (sum of all archive sizes).
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub size: Option<u64>,
|
|
||||||
/// The owner of the snapshots group
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub owner: Option<Authid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
"backup-type": {
|
|
||||||
schema: BACKUP_TYPE_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-id": {
|
|
||||||
schema: BACKUP_ID_SCHEMA,
|
|
||||||
},
|
|
||||||
"backup-time": {
|
|
||||||
schema: BACKUP_TIME_SCHEMA,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
/// Prune result.
|
|
||||||
pub struct PruneListItem {
|
|
||||||
pub backup_type: String, // enum
|
|
||||||
pub backup_id: String,
|
|
||||||
pub backup_time: i64,
|
|
||||||
/// Keep snapshot
|
|
||||||
pub keep: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
|
pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
|
||||||
"Number of daily backups to keep.")
|
"Number of daily backups to keep.")
|
||||||
.minimum(1)
|
.minimum(1)
|
||||||
|
@ -595,30 +387,6 @@ pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema = IntegerSchema::new(
|
||||||
.minimum(1)
|
.minimum(1)
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
#[api(
|
|
||||||
properties: {
|
|
||||||
"filename": {
|
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA,
|
|
||||||
},
|
|
||||||
"crypt-mode": {
|
|
||||||
type: CryptMode,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
/// Basic information about archive files inside a backup snapshot.
|
|
||||||
pub struct BackupContent {
|
|
||||||
pub filename: String,
|
|
||||||
/// Info if file is encrypted, signed, or neither.
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub crypt_mode: Option<CryptMode>,
|
|
||||||
/// Archive size (from backup manifest).
|
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
|
||||||
pub size: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api()]
|
#[api()]
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
/// Storage space usage information.
|
/// Storage space usage information.
|
||||||
|
|
|
@ -77,6 +77,8 @@ pub use pbs_datastore::manifest::*;
|
||||||
pub use pbs_datastore::prune;
|
pub use pbs_datastore::prune;
|
||||||
pub use pbs_datastore::prune::*;
|
pub use pbs_datastore::prune::*;
|
||||||
|
|
||||||
|
pub use pbs_datastore::store_progress::StoreProgress;
|
||||||
|
|
||||||
pub use pbs_datastore::dynamic_index::*;
|
pub use pbs_datastore::dynamic_index::*;
|
||||||
pub use pbs_datastore::fixed_index;
|
pub use pbs_datastore::fixed_index;
|
||||||
pub use pbs_datastore::fixed_index::*;
|
pub use pbs_datastore::fixed_index::*;
|
||||||
|
@ -97,9 +99,6 @@ pub use dynamic_index::*;
|
||||||
mod datastore;
|
mod datastore;
|
||||||
pub use datastore::*;
|
pub use datastore::*;
|
||||||
|
|
||||||
mod store_progress;
|
|
||||||
pub use store_progress::*;
|
|
||||||
|
|
||||||
mod verify;
|
mod verify;
|
||||||
pub use verify::*;
|
pub use verify::*;
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,20 @@ use serde_json::json;
|
||||||
|
|
||||||
use proxmox::api::error::{HttpError, StatusCode};
|
use proxmox::api::error::{HttpError, StatusCode};
|
||||||
|
|
||||||
use pbs_datastore::task_log;
|
use pbs_api_types::{Authid, SnapshotListItem, GroupListItem};
|
||||||
|
use pbs_datastore::{task_log, BackupInfo, BackupDir, BackupGroup, StoreProgress};
|
||||||
|
use pbs_datastore::data_blob::DataBlob;
|
||||||
|
use pbs_datastore::dynamic_index::DynamicIndexReader;
|
||||||
|
use pbs_datastore::fixed_index::FixedIndexReader;
|
||||||
|
use pbs_datastore::index::IndexFile;
|
||||||
|
use pbs_datastore::manifest::{
|
||||||
|
CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME, ArchiveType, BackupManifest, FileInfo, archive_type
|
||||||
|
};
|
||||||
use pbs_tools::sha::sha256;
|
use pbs_tools::sha::sha256;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api2::types::*,
|
backup::DataStore,
|
||||||
backup::*,
|
client::{BackupReader, BackupRepository, HttpClient, HttpClientOptions, RemoteChunkReader},
|
||||||
client::*,
|
|
||||||
server::WorkerTask,
|
server::WorkerTask,
|
||||||
tools::ParallelHandler,
|
tools::ParallelHandler,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue