From 9bd702d60f5377f4dea3cb622d4e1a6faacaad0b Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 6 Jun 2025 17:19:11 +0200 Subject: [PATCH] Add cloud-localds as a required program --- virtweb_backend/src/constants.rs | 15 +++++---- virtweb_backend/src/main.rs | 8 +++-- virtweb_backend/src/utils/file_disks_utils.rs | 32 +++++++++---------- virtweb_backend/src/utils/net_utils.rs | 4 +-- virtweb_docs/SETUP_DEV.md | 4 +-- virtweb_docs/SETUP_PROD.md | 4 +-- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/virtweb_backend/src/constants.rs b/virtweb_backend/src/constants.rs index e69e82c..f1dfe65 100644 --- a/virtweb_backend/src/constants.rs +++ b/virtweb_backend/src/constants.rs @@ -122,19 +122,22 @@ pub const API_TOKEN_DESCRIPTION_MAX_LENGTH: usize = 30; pub const API_TOKEN_RIGHT_PATH_MAX_LENGTH: usize = 255; /// Qemu image program path -pub const QEMU_IMAGE_PROGRAM: &str = "/usr/bin/qemu-img"; +pub const PROGRAM_QEMU_IMAGE: &str = "/usr/bin/qemu-img"; /// IP program path -pub const IP_PROGRAM: &str = "/usr/sbin/ip"; +pub const PROGRAM_IP: &str = "/usr/sbin/ip"; /// Copy program path -pub const COPY_PROGRAM: &str = "/bin/cp"; +pub const PROGRAM_COPY: &str = "/bin/cp"; /// Gzip program path -pub const GZIP_PROGRAM: &str = "/usr/bin/gzip"; +pub const PROGRAM_GZIP: &str = "/usr/bin/gzip"; /// Bash program -pub const BASH_PROGRAM: &str = "/usr/bin/bash"; +pub const PROGRAM_BASH: &str = "/usr/bin/bash"; /// DD program -pub const DD_PROGRAM: &str = "/usr/bin/dd"; +pub const PROGRAM_DD: &str = "/usr/bin/dd"; + +/// cloud-localds program +pub const PROGRAM_CLOUD_LOCALDS: &str = "/usr/bin/cloud-localds"; diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 91a2a7d..c9b509d 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -47,13 +47,17 @@ async fn main() -> std::io::Result<()> { log::debug!("Checking for required programs"); exec_utils::check_program( - constants::QEMU_IMAGE_PROGRAM, + constants::PROGRAM_QEMU_IMAGE, "QEMU disk image utility is required to manipulate QCow2 files!", ); exec_utils::check_program( - constants::IP_PROGRAM, + constants::PROGRAM_IP, "ip is required to access bridges information!", ); + exec_utils::check_program( + constants::PROGRAM_CLOUD_LOCALDS, + "cloud-localds from package cloud-image-utils is required to build cloud-init images!", + ); log::debug!("Create required directory, if missing"); files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap(); diff --git a/virtweb_backend/src/utils/file_disks_utils.rs b/virtweb_backend/src/utils/file_disks_utils.rs index 228f011..db1d40f 100644 --- a/virtweb_backend/src/utils/file_disks_utils.rs +++ b/virtweb_backend/src/utils/file_disks_utils.rs @@ -124,7 +124,7 @@ impl DiskFileInfo { } DiskFileFormat::QCow2 { virtual_size } => { - let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); cmd.arg("create") .arg("-f") .arg("qcow2") @@ -161,7 +161,7 @@ impl DiskFileInfo { let mut cmd = match (self.format, dest_format) { // Decompress QCow2 (DiskFileFormat::CompressedQCow2, DiskFileFormat::QCow2 { .. }) => { - let mut cmd = Command::new(constants::GZIP_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--decompress") .arg("--to-stdout") @@ -172,7 +172,7 @@ impl DiskFileInfo { // Compress QCow2 (DiskFileFormat::QCow2 { .. }, DiskFileFormat::CompressedQCow2) => { - let mut cmd = Command::new(constants::GZIP_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--to-stdout") .arg(&self.file_path) @@ -182,7 +182,7 @@ impl DiskFileInfo { // Convert QCow2 to Raw file (DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => { - let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); cmd.arg("convert") .arg("-f") .arg("qcow2") @@ -201,7 +201,7 @@ impl DiskFileInfo { // Clone a QCow file, using qemu-image instead of cp might improve "sparsification" of // file (DiskFileFormat::QCow2 { .. }, DiskFileFormat::QCow2 { .. }) => { - let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); cmd.arg("convert") .arg("-f") .arg("qcow2") @@ -214,7 +214,7 @@ impl DiskFileInfo { // Convert Raw to QCow2 file (DiskFileFormat::Raw { .. }, DiskFileFormat::QCow2 { .. }) => { - let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); cmd.arg("convert") .arg("-f") .arg("raw") @@ -228,7 +228,7 @@ impl DiskFileInfo { // Render raw file non sparse (DiskFileFormat::Raw { is_sparse: true }, DiskFileFormat::Raw { is_sparse: false }) => { - let mut cmd = Command::new(constants::COPY_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_COPY); cmd.arg("--sparse=never") .arg(&self.file_path) .arg(&temp_file); @@ -237,7 +237,7 @@ impl DiskFileInfo { // Render raw file sparse (DiskFileFormat::Raw { is_sparse: false }, DiskFileFormat::Raw { is_sparse: true }) => { - let mut cmd = Command::new(constants::DD_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_DD); cmd.arg("conv=sparse") .arg(format!("if={}", self.file_path.display())) .arg(format!("of={}", temp_file.display())); @@ -246,7 +246,7 @@ impl DiskFileInfo { // Compress Raw (DiskFileFormat::Raw { .. }, DiskFileFormat::CompressedRaw) => { - let mut cmd = Command::new(constants::GZIP_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--to-stdout") .arg(&self.file_path) @@ -256,7 +256,7 @@ impl DiskFileInfo { // Decompress Raw to not sparse file (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: false }) => { - let mut cmd = Command::new(constants::GZIP_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--decompress") .arg("--to-stdout") @@ -268,12 +268,12 @@ impl DiskFileInfo { // Decompress Raw to sparse file // https://benou.fr/www/ben/decompressing-sparse-files.html (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: true }) => { - let mut cmd = Command::new(constants::BASH_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_BASH); cmd.arg("-c").arg(format!( "{} -d -c {} | {} conv=sparse of={}", - constants::GZIP_PROGRAM, + constants::PROGRAM_GZIP, self.file_path.display(), - constants::DD_PROGRAM, + constants::PROGRAM_DD, temp_file.display() )); cmd @@ -281,7 +281,7 @@ impl DiskFileInfo { // Dumb copy of file (a, b) if a == b => { - let mut cmd = Command::new(constants::COPY_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_COPY); cmd.arg("--sparse=auto") .arg(&self.file_path) .arg(&temp_file); @@ -341,7 +341,7 @@ struct QCowInfoOutput { /// Get QCow2 virtual size fn qcow_virt_size(path: &Path) -> anyhow::Result { // Run qemu-img - let mut cmd = Command::new(constants::QEMU_IMAGE_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); cmd.args([ "info", path.to_str().unwrap_or(""), @@ -353,7 +353,7 @@ fn qcow_virt_size(path: &Path) -> anyhow::Result { if !output.status.success() { anyhow::bail!( "{} info failed, status: {}, stderr: {}", - constants::QEMU_IMAGE_PROGRAM, + constants::PROGRAM_QEMU_IMAGE, output.status, String::from_utf8_lossy(&output.stderr) ); diff --git a/virtweb_backend/src/utils/net_utils.rs b/virtweb_backend/src/utils/net_utils.rs index 8a55d04..d835718 100644 --- a/virtweb_backend/src/utils/net_utils.rs +++ b/virtweb_backend/src/utils/net_utils.rs @@ -145,13 +145,13 @@ struct IPBridgeInfo { /// Get the list of bridge interfaces pub fn bridges_list() -> anyhow::Result> { - let mut cmd = Command::new(constants::IP_PROGRAM); + let mut cmd = Command::new(constants::PROGRAM_IP); cmd.args(["-json", "link", "show", "type", "bridge"]); let output = cmd.output()?; if !output.status.success() { anyhow::bail!( "{} failed, status: {}, stderr: {}", - constants::IP_PROGRAM, + constants::PROGRAM_IP, output.status, String::from_utf8_lossy(&output.stderr) ); diff --git a/virtweb_docs/SETUP_DEV.md b/virtweb_docs/SETUP_DEV.md index b587d05..b337e18 100644 --- a/virtweb_docs/SETUP_DEV.md +++ b/virtweb_docs/SETUP_DEV.md @@ -5,9 +5,9 @@ sudo apt install libvirt-dev ``` -2. Libvirt must also be installed: +2. Libvirt and cloud image utilities must also be installed: ```bash -sudo apt install qemu-kvm libvirt-daemon-system +sudo apt install qemu-kvm libvirt-daemon-system cloud-image-utils ``` 3. Allow the current user to manage VMs: diff --git a/virtweb_docs/SETUP_PROD.md b/virtweb_docs/SETUP_PROD.md index 9e9709e..a72f2b5 100644 --- a/virtweb_docs/SETUP_PROD.md +++ b/virtweb_docs/SETUP_PROD.md @@ -12,10 +12,10 @@ The release file will be available in `virtweb_backend/target/release/virtweb_ba This is the only artifact that must be copied to the server. It is recommended to copy it to the `/usr/local/bin` directory. ## Install requirements -In order to work properly, VirtWeb relies on `libvirt`, `qemu` and `kvm`: +In order to work properly, VirtWeb relies on `libvirt`, `qemu`, `kvm` and `cloud-localds`: ```bash -sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils +sudo apt install qemu-kvm libvirt-daemon-system libvirt0 libvirt-clients libvirt-daemon bridge-utils cloud-image-utils ``` ## Dedicated user