mirror of
https://git.proxmox.com/git/vma-to-pbs
synced 2025-08-06 14:15:29 +00:00
split BackupVmaToPbsArgs into PbsArgs and VmaBackupArgs
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
This commit is contained in:
parent
3c850b39a5
commit
f48dd30f86
17
src/main.rs
17
src/main.rs
@ -6,7 +6,7 @@ use proxmox_time::epoch_i64;
|
|||||||
|
|
||||||
mod vma;
|
mod vma;
|
||||||
mod vma2pbs;
|
mod vma2pbs;
|
||||||
use vma2pbs::{backup_vma_to_pbs, BackupVmaToPbsArgs};
|
use vma2pbs::{vma2pbs, BackupVmaToPbsArgs, PbsArgs, VmaBackupArgs};
|
||||||
|
|
||||||
const CMD_HELP: &str = "\
|
const CMD_HELP: &str = "\
|
||||||
Usage: vma-to-pbs [OPTIONS] --repository <auth_id@host:port:datastore> --vmid <VMID> [vma_file]
|
Usage: vma-to-pbs [OPTIONS] --repository <auth_id@host:port:datastore> --vmid <VMID> [vma_file]
|
||||||
@ -184,12 +184,9 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = BackupVmaToPbsArgs {
|
let pbs_args = PbsArgs {
|
||||||
vma_file_path: vma_file_path.cloned(),
|
|
||||||
pbs_repository,
|
pbs_repository,
|
||||||
namespace,
|
namespace,
|
||||||
backup_id: vmid,
|
|
||||||
backup_time,
|
|
||||||
pbs_password,
|
pbs_password,
|
||||||
keyfile,
|
keyfile,
|
||||||
key_password,
|
key_password,
|
||||||
@ -197,16 +194,24 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
|
|||||||
fingerprint,
|
fingerprint,
|
||||||
compress,
|
compress,
|
||||||
encrypt,
|
encrypt,
|
||||||
|
};
|
||||||
|
|
||||||
|
let vma_args = VmaBackupArgs {
|
||||||
|
vma_file_path: vma_file_path.cloned(),
|
||||||
|
backup_id: vmid,
|
||||||
|
backup_time,
|
||||||
notes,
|
notes,
|
||||||
log_file_path,
|
log_file_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let options = BackupVmaToPbsArgs { pbs_args, vma_args };
|
||||||
|
|
||||||
Ok(options)
|
Ok(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let args = parse_args()?;
|
let args = parse_args()?;
|
||||||
backup_vma_to_pbs(args)?;
|
vma2pbs(args)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
195
src/vma2pbs.rs
195
src/vma2pbs.rs
@ -29,11 +29,13 @@ use crate::vma::VmaReader;
|
|||||||
const VMA_CLUSTER_SIZE: usize = 65536;
|
const VMA_CLUSTER_SIZE: usize = 65536;
|
||||||
|
|
||||||
pub struct BackupVmaToPbsArgs {
|
pub struct BackupVmaToPbsArgs {
|
||||||
pub vma_file_path: Option<OsString>,
|
pub pbs_args: PbsArgs,
|
||||||
|
pub vma_args: VmaBackupArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PbsArgs {
|
||||||
pub pbs_repository: String,
|
pub pbs_repository: String,
|
||||||
pub namespace: Option<String>,
|
pub namespace: Option<String>,
|
||||||
pub backup_id: String,
|
|
||||||
pub backup_time: i64,
|
|
||||||
pub pbs_password: String,
|
pub pbs_password: String,
|
||||||
pub keyfile: Option<String>,
|
pub keyfile: Option<String>,
|
||||||
pub key_password: Option<String>,
|
pub key_password: Option<String>,
|
||||||
@ -41,6 +43,12 @@ pub struct BackupVmaToPbsArgs {
|
|||||||
pub fingerprint: String,
|
pub fingerprint: String,
|
||||||
pub compress: bool,
|
pub compress: bool,
|
||||||
pub encrypt: bool,
|
pub encrypt: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VmaBackupArgs {
|
||||||
|
pub vma_file_path: Option<OsString>,
|
||||||
|
pub backup_id: String,
|
||||||
|
pub backup_time: i64,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub log_file_path: Option<OsString>,
|
pub log_file_path: Option<OsString>,
|
||||||
}
|
}
|
||||||
@ -61,25 +69,23 @@ fn handle_pbs_error(pbs_err: *mut c_char, function_name: &str) -> Result<(), Err
|
|||||||
bail!("{function_name} failed: {pbs_err_str}");
|
bail!("{function_name} failed: {pbs_err_str}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBackupHandle, Error> {
|
fn create_pbs_backup_task(
|
||||||
println!("PBS repository: {}", args.pbs_repository);
|
pbs_args: &PbsArgs,
|
||||||
if let Some(ns) = &args.namespace {
|
backup_args: &VmaBackupArgs,
|
||||||
println!("PBS namespace: {}", ns);
|
) -> Result<*mut ProxmoxBackupHandle, Error> {
|
||||||
}
|
println!(
|
||||||
println!("PBS fingerprint: {}", args.fingerprint);
|
"backup time: {}",
|
||||||
println!("compress: {}", args.compress);
|
epoch_to_rfc3339(backup_args.backup_time)?
|
||||||
println!("encrypt: {}", args.encrypt);
|
);
|
||||||
|
|
||||||
println!("backup time: {}", epoch_to_rfc3339(args.backup_time)?);
|
|
||||||
|
|
||||||
let mut pbs_err: *mut c_char = ptr::null_mut();
|
let mut pbs_err: *mut c_char = ptr::null_mut();
|
||||||
|
|
||||||
let pbs_repository_cstr = CString::new(args.pbs_repository.as_str())?;
|
let pbs_repository_cstr = CString::new(pbs_args.pbs_repository.as_str())?;
|
||||||
let ns_cstr = CString::new(args.namespace.as_deref().unwrap_or(""))?;
|
let ns_cstr = CString::new(pbs_args.namespace.as_deref().unwrap_or(""))?;
|
||||||
let backup_id_cstr = CString::new(args.backup_id.as_str())?;
|
let backup_id_cstr = CString::new(backup_args.backup_id.as_str())?;
|
||||||
let pbs_password_cstr = CString::new(args.pbs_password.as_str())?;
|
let pbs_password_cstr = CString::new(pbs_args.pbs_password.as_str())?;
|
||||||
let fingerprint_cstr = CString::new(args.fingerprint.as_str())?;
|
let fingerprint_cstr = CString::new(pbs_args.fingerprint.as_str())?;
|
||||||
let keyfile_cstr = args
|
let keyfile_cstr = pbs_args
|
||||||
.keyfile
|
.keyfile
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| CString::new(v.as_str()).unwrap());
|
.map(|v| CString::new(v.as_str()).unwrap());
|
||||||
@ -87,7 +93,7 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| v.as_ptr())
|
.map(|v| v.as_ptr())
|
||||||
.unwrap_or(ptr::null());
|
.unwrap_or(ptr::null());
|
||||||
let key_password_cstr = args
|
let key_password_cstr = pbs_args
|
||||||
.key_password
|
.key_password
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| CString::new(v.as_str()).unwrap());
|
.map(|v| CString::new(v.as_str()).unwrap());
|
||||||
@ -95,7 +101,7 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| v.as_ptr())
|
.map(|v| v.as_ptr())
|
||||||
.unwrap_or(ptr::null());
|
.unwrap_or(ptr::null());
|
||||||
let master_keyfile_cstr = args
|
let master_keyfile_cstr = pbs_args
|
||||||
.master_keyfile
|
.master_keyfile
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|v| CString::new(v.as_str()).unwrap());
|
.map(|v| CString::new(v.as_str()).unwrap());
|
||||||
@ -108,14 +114,14 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
|
|||||||
pbs_repository_cstr.as_ptr(),
|
pbs_repository_cstr.as_ptr(),
|
||||||
ns_cstr.as_ptr(),
|
ns_cstr.as_ptr(),
|
||||||
backup_id_cstr.as_ptr(),
|
backup_id_cstr.as_ptr(),
|
||||||
args.backup_time as u64,
|
backup_args.backup_time as u64,
|
||||||
PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE,
|
PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE,
|
||||||
pbs_password_cstr.as_ptr(),
|
pbs_password_cstr.as_ptr(),
|
||||||
keyfile_ptr,
|
keyfile_ptr,
|
||||||
key_password_ptr,
|
key_password_ptr,
|
||||||
master_keyfile_ptr,
|
master_keyfile_ptr,
|
||||||
args.compress,
|
pbs_args.compress,
|
||||||
args.encrypt,
|
pbs_args.encrypt,
|
||||||
fingerprint_cstr.as_ptr(),
|
fingerprint_cstr.as_ptr(),
|
||||||
&mut pbs_err,
|
&mut pbs_err,
|
||||||
);
|
);
|
||||||
@ -361,17 +367,24 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pbs_client_setup(args: &BackupVmaToPbsArgs) -> Result<(HttpClient, String, Value), Error> {
|
fn pbs_client_setup(
|
||||||
let repo: BackupRepository = args.pbs_repository.parse()?;
|
pbs_args: &PbsArgs,
|
||||||
|
backup_args: &VmaBackupArgs,
|
||||||
|
) -> Result<(HttpClient, String, Value), Error> {
|
||||||
|
let repo: BackupRepository = pbs_args.pbs_repository.parse()?;
|
||||||
let options = HttpClientOptions::new_interactive(
|
let options = HttpClientOptions::new_interactive(
|
||||||
Some(args.pbs_password.clone()),
|
Some(pbs_args.pbs_password.clone()),
|
||||||
Some(args.fingerprint.clone()),
|
Some(pbs_args.fingerprint.clone()),
|
||||||
);
|
);
|
||||||
let client = HttpClient::new(repo.host(), repo.port(), repo.auth_id(), options)?;
|
let client = HttpClient::new(repo.host(), repo.port(), repo.auth_id(), options)?;
|
||||||
|
|
||||||
let backup_dir = BackupDir::from((BackupType::Vm, args.backup_id.clone(), args.backup_time));
|
let backup_dir = BackupDir::from((
|
||||||
|
BackupType::Vm,
|
||||||
|
backup_args.backup_id.clone(),
|
||||||
|
backup_args.backup_time,
|
||||||
|
));
|
||||||
|
|
||||||
let namespace = match &args.namespace {
|
let namespace = match &pbs_args.namespace {
|
||||||
Some(namespace) => BackupNamespace::new(namespace)?,
|
Some(namespace) => BackupNamespace::new(namespace)?,
|
||||||
None => BackupNamespace::root(),
|
None => BackupNamespace::root(),
|
||||||
};
|
};
|
||||||
@ -386,45 +399,44 @@ fn pbs_client_setup(args: &BackupVmaToPbsArgs) -> Result<(HttpClient, String, Va
|
|||||||
|
|
||||||
fn upload_log(
|
fn upload_log(
|
||||||
client: &HttpClient,
|
client: &HttpClient,
|
||||||
args: &BackupVmaToPbsArgs,
|
log_file_path: &OsString,
|
||||||
|
pbs_args: &PbsArgs,
|
||||||
store: &str,
|
store: &str,
|
||||||
request_args: Value,
|
request_args: Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if let Some(log_file_path) = &args.log_file_path {
|
let path = format!("api2/json/admin/datastore/{}/upload-backup-log", store);
|
||||||
let path = format!("api2/json/admin/datastore/{}/upload-backup-log", store);
|
let data = std::fs::read(log_file_path)?;
|
||||||
let data = std::fs::read(log_file_path)?;
|
|
||||||
|
|
||||||
let blob = if args.encrypt {
|
let blob = if pbs_args.encrypt {
|
||||||
let crypt_config = match &args.keyfile {
|
let crypt_config = match &pbs_args.keyfile {
|
||||||
None => None,
|
None => None,
|
||||||
Some(keyfile) => {
|
Some(keyfile) => {
|
||||||
let key = std::fs::read(keyfile)?;
|
let key = std::fs::read(keyfile)?;
|
||||||
let (key, _created, _) = decrypt_key(&key, &|| -> Result<Vec<u8>, Error> {
|
let (key, _created, _) = decrypt_key(&key, &|| -> Result<Vec<u8>, Error> {
|
||||||
match &args.key_password {
|
match &pbs_args.key_password {
|
||||||
Some(key_password) => Ok(key_password.clone().into_bytes()),
|
Some(key_password) => Ok(key_password.clone().into_bytes()),
|
||||||
None => bail!("no key password provided"),
|
None => bail!("no key password provided"),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
Some(crypt_config)
|
Some(crypt_config)
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
DataBlob::encode(&data, crypt_config.as_ref(), args.compress)?
|
|
||||||
} else {
|
|
||||||
// fixme: howto sign log?
|
|
||||||
DataBlob::encode(&data, None, args.compress)?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = hyper::Body::from(blob.into_inner());
|
DataBlob::encode(&data, crypt_config.as_ref(), pbs_args.compress)?
|
||||||
|
} else {
|
||||||
|
// fixme: howto sign log?
|
||||||
|
DataBlob::encode(&data, None, pbs_args.compress)?
|
||||||
|
};
|
||||||
|
|
||||||
block_on(async {
|
let body = hyper::Body::from(blob.into_inner());
|
||||||
client
|
|
||||||
.upload("application/octet-stream", body, &path, Some(request_args))
|
block_on(async {
|
||||||
.await
|
client
|
||||||
.unwrap();
|
.upload("application/octet-stream", body, &path, Some(request_args))
|
||||||
});
|
.await
|
||||||
}
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -444,8 +456,32 @@ fn set_notes(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
|
pub fn vma2pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
|
||||||
let vma_file: Box<dyn BufRead> = match &args.vma_file_path {
|
let pbs_args = &args.pbs_args;
|
||||||
|
println!("PBS repository: {}", pbs_args.pbs_repository);
|
||||||
|
if let Some(ns) = &pbs_args.namespace {
|
||||||
|
println!("PBS namespace: {}", ns);
|
||||||
|
}
|
||||||
|
println!("PBS fingerprint: {}", pbs_args.fingerprint);
|
||||||
|
println!("compress: {}", pbs_args.compress);
|
||||||
|
println!("encrypt: {}", pbs_args.encrypt);
|
||||||
|
|
||||||
|
let start_transfer_time = SystemTime::now();
|
||||||
|
|
||||||
|
upload_vma_file(pbs_args, &args.vma_args)?;
|
||||||
|
|
||||||
|
let transfer_duration = SystemTime::now().duration_since(start_transfer_time)?;
|
||||||
|
let total_seconds = transfer_duration.as_secs();
|
||||||
|
let minutes = total_seconds / 60;
|
||||||
|
let seconds = total_seconds % 60;
|
||||||
|
let milliseconds = transfer_duration.as_millis() % 1000;
|
||||||
|
println!("Backup finished within {minutes} minutes, {seconds} seconds and {milliseconds} ms");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_vma_file(pbs_args: &PbsArgs, backup_args: &VmaBackupArgs) -> Result<(), Error> {
|
||||||
|
let vma_file: Box<dyn BufRead> = match &backup_args.vma_file_path {
|
||||||
Some(vma_file_path) => match File::open(vma_file_path) {
|
Some(vma_file_path) => match File::open(vma_file_path) {
|
||||||
Err(why) => return Err(anyhow!("Couldn't open file: {}", why)),
|
Err(why) => return Err(anyhow!("Couldn't open file: {}", why)),
|
||||||
Ok(file) => Box::new(BufReader::new(file)),
|
Ok(file) => Box::new(BufReader::new(file)),
|
||||||
@ -454,7 +490,7 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
|
|||||||
};
|
};
|
||||||
let vma_reader = VmaReader::new(vma_file)?;
|
let vma_reader = VmaReader::new(vma_file)?;
|
||||||
|
|
||||||
let pbs = create_pbs_backup_task(&args)?;
|
let pbs = create_pbs_backup_task(pbs_args, backup_args)?;
|
||||||
|
|
||||||
defer! {
|
defer! {
|
||||||
proxmox_backup_disconnect(pbs);
|
proxmox_backup_disconnect(pbs);
|
||||||
@ -467,10 +503,6 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
|
|||||||
handle_pbs_error(pbs_err, "proxmox_backup_connect")?;
|
handle_pbs_error(pbs_err, "proxmox_backup_connect")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Connected to Proxmox Backup Server");
|
|
||||||
|
|
||||||
let start_transfer_time = SystemTime::now();
|
|
||||||
|
|
||||||
upload_configs(&vma_reader, pbs)?;
|
upload_configs(&vma_reader, pbs)?;
|
||||||
upload_block_devices(vma_reader, pbs)?;
|
upload_block_devices(vma_reader, pbs)?;
|
||||||
|
|
||||||
@ -478,24 +510,23 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
|
|||||||
handle_pbs_error(pbs_err, "proxmox_backup_finish")?;
|
handle_pbs_error(pbs_err, "proxmox_backup_finish")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.notes.is_some() || args.log_file_path.is_some() {
|
if backup_args.notes.is_some() || backup_args.log_file_path.is_some() {
|
||||||
let (client, store, request_args) = pbs_client_setup(&args)?;
|
let (client, store, request_args) = pbs_client_setup(pbs_args, backup_args)?;
|
||||||
|
|
||||||
if args.log_file_path.is_some() {
|
if let Some(log_file_path) = &backup_args.log_file_path {
|
||||||
upload_log(&client, &args, &store, request_args.clone())?;
|
upload_log(
|
||||||
|
&client,
|
||||||
|
log_file_path,
|
||||||
|
pbs_args,
|
||||||
|
&store,
|
||||||
|
request_args.clone(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(notes) = args.notes {
|
if let Some(notes) = &backup_args.notes {
|
||||||
set_notes(&client, ¬es, &store, request_args)?;
|
set_notes(&client, notes, &store, request_args)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let transfer_duration = SystemTime::now().duration_since(start_transfer_time)?;
|
|
||||||
let total_seconds = transfer_duration.as_secs();
|
|
||||||
let minutes = total_seconds / 60;
|
|
||||||
let seconds = total_seconds % 60;
|
|
||||||
let milliseconds = transfer_duration.as_millis() % 1000;
|
|
||||||
println!("Backup finished within {minutes} minutes, {seconds} seconds and {milliseconds} ms");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user