Can upload disk images on the server
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		@@ -250,6 +250,11 @@ impl AppConfig {
 | 
			
		||||
        self.storage_path().join("iso")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get disk images storage directory
 | 
			
		||||
    pub fn disk_images_storage_path(&self) -> PathBuf {
 | 
			
		||||
        self.storage_path().join("disk_images")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get VM vnc sockets directory
 | 
			
		||||
    pub fn vnc_sockets_path(&self) -> PathBuf {
 | 
			
		||||
        self.storage_path().join("vnc")
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,13 @@ pub const ALLOWED_ISO_MIME_TYPES: [&str; 4] = [
 | 
			
		||||
/// ISO max size
 | 
			
		||||
pub const ISO_MAX_SIZE: usize = 10 * 1000 * 1000 * 1000;
 | 
			
		||||
 | 
			
		||||
/// Allowed uploaded disk images formats
 | 
			
		||||
pub const ALLOWED_DISK_IMAGES_MIME_TYPES: [&str; 2] =
 | 
			
		||||
    ["application/x-qemu-disk", "application/gzip"];
 | 
			
		||||
 | 
			
		||||
/// Disk image max size
 | 
			
		||||
pub const DISK_IMAGE_MAX_SIZE: usize = 10 * 1000 * 1000 * 1000 * 1000;
 | 
			
		||||
 | 
			
		||||
/// Min VM memory size (MB)
 | 
			
		||||
pub const MIN_VM_MEMORY: usize = 100;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								virtweb_backend/src/controllers/disk_images_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								virtweb_backend/src/controllers/disk_images_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::controllers::HttpResult;
 | 
			
		||||
use crate::utils::files_utils;
 | 
			
		||||
use actix_multipart::form::MultipartForm;
 | 
			
		||||
use actix_multipart::form::tempfile::TempFile;
 | 
			
		||||
use actix_web::HttpResponse;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, MultipartForm)]
 | 
			
		||||
pub struct UploadDiskImageForm {
 | 
			
		||||
    #[multipart(rename = "file")]
 | 
			
		||||
    files: Vec<TempFile>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Upload disk image file
 | 
			
		||||
pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>) -> HttpResult {
 | 
			
		||||
    if form.files.is_empty() {
 | 
			
		||||
        log::error!("Missing uploaded disk file!");
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Missing file!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let file = form.files.remove(0);
 | 
			
		||||
 | 
			
		||||
    // Check uploaded file size
 | 
			
		||||
    if file.size > constants::DISK_IMAGE_MAX_SIZE {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Disk image max size exceeded!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check file mime type
 | 
			
		||||
    if let Some(mime_type) = file.content_type {
 | 
			
		||||
        if !constants::ALLOWED_DISK_IMAGES_MIME_TYPES.contains(&mime_type.as_ref()) {
 | 
			
		||||
            return Ok(HttpResponse::BadRequest().json(format!(
 | 
			
		||||
                "Unsupported file type for disk upload: {}",
 | 
			
		||||
                mime_type
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Extract and check file name
 | 
			
		||||
    let Some(file_name) = file.file_name else {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Missing file name of uploaded file!"));
 | 
			
		||||
    };
 | 
			
		||||
    if !files_utils::check_file_name(&file_name) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid uploaded file name!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if a file with the same name already exists
 | 
			
		||||
    let dest_path = AppConfig::get().disk_images_storage_path().join(file_name);
 | 
			
		||||
    if dest_path.is_file() {
 | 
			
		||||
        return Ok(HttpResponse::Conflict().json("A file with the same name already exists!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Copy the file to the destination
 | 
			
		||||
    file.file.persist(dest_path)?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json("Successfully uploaded disk image!"))
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter};
 | 
			
		||||
 | 
			
		||||
pub mod api_tokens_controller;
 | 
			
		||||
pub mod auth_controller;
 | 
			
		||||
pub mod disk_images_controller;
 | 
			
		||||
pub mod groups_controller;
 | 
			
		||||
pub mod iso_controller;
 | 
			
		||||
pub mod network_controller;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ struct StaticConfig {
 | 
			
		||||
    local_auth_enabled: bool,
 | 
			
		||||
    oidc_auth_enabled: bool,
 | 
			
		||||
    iso_mimetypes: &'static [&'static str],
 | 
			
		||||
    disk_images_mimetypes: &'static [&'static str],
 | 
			
		||||
    net_mac_prefix: &'static str,
 | 
			
		||||
    builtin_nwfilter_rules: &'static [&'static str],
 | 
			
		||||
    nwfilter_chains: &'static [&'static str],
 | 
			
		||||
@@ -37,6 +38,7 @@ struct SLenConstraints {
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
struct ServerConstraints {
 | 
			
		||||
    iso_max_size: usize,
 | 
			
		||||
    disk_image_max_size: usize,
 | 
			
		||||
    vnc_token_duration: u64,
 | 
			
		||||
    vm_name_size: LenConstraints,
 | 
			
		||||
    vm_title_size: LenConstraints,
 | 
			
		||||
@@ -63,11 +65,13 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
 | 
			
		||||
        local_auth_enabled: *local_auth,
 | 
			
		||||
        oidc_auth_enabled: !AppConfig::get().disable_oidc,
 | 
			
		||||
        iso_mimetypes: &constants::ALLOWED_ISO_MIME_TYPES,
 | 
			
		||||
        disk_images_mimetypes: &constants::ALLOWED_DISK_IMAGES_MIME_TYPES,
 | 
			
		||||
        net_mac_prefix: constants::NET_MAC_ADDR_PREFIX,
 | 
			
		||||
        builtin_nwfilter_rules: &constants::BUILTIN_NETWORK_FILTER_RULES,
 | 
			
		||||
        nwfilter_chains: &constants::NETWORK_CHAINS,
 | 
			
		||||
        constraints: ServerConstraints {
 | 
			
		||||
            iso_max_size: constants::ISO_MAX_SIZE,
 | 
			
		||||
            disk_image_max_size: constants::DISK_IMAGE_MAX_SIZE,
 | 
			
		||||
 | 
			
		||||
            vnc_token_duration: VNC_TOKEN_LIFETIME,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use std::cmp::max;
 | 
			
		||||
use actix::Actor;
 | 
			
		||||
use actix_cors::Cors;
 | 
			
		||||
use actix_identity::IdentityMiddleware;
 | 
			
		||||
@@ -22,8 +23,9 @@ use virtweb_backend::constants::{
 | 
			
		||||
    MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
 | 
			
		||||
};
 | 
			
		||||
use virtweb_backend::controllers::{
 | 
			
		||||
    api_tokens_controller, auth_controller, groups_controller, iso_controller, network_controller,
 | 
			
		||||
    nwfilter_controller, server_controller, static_controller, vm_controller,
 | 
			
		||||
    api_tokens_controller, auth_controller, disk_images_controller, groups_controller,
 | 
			
		||||
    iso_controller, network_controller, nwfilter_controller, server_controller, static_controller,
 | 
			
		||||
    vm_controller,
 | 
			
		||||
};
 | 
			
		||||
use virtweb_backend::libvirt_client::LibVirtClient;
 | 
			
		||||
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
 | 
			
		||||
@@ -55,6 +57,7 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
 | 
			
		||||
    log::debug!("Create required directory, if missing");
 | 
			
		||||
    files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap();
 | 
			
		||||
    files_utils::create_directory_if_missing(AppConfig::get().disk_images_storage_path()).unwrap();
 | 
			
		||||
    files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap();
 | 
			
		||||
    files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap();
 | 
			
		||||
    files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap();
 | 
			
		||||
@@ -118,7 +121,7 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
            }))
 | 
			
		||||
            .app_data(conn.clone())
 | 
			
		||||
            // Uploaded files
 | 
			
		||||
            .app_data(MultipartFormConfig::default().total_limit(constants::ISO_MAX_SIZE))
 | 
			
		||||
            .app_data(MultipartFormConfig::default().total_limit(max(constants::DISK_IMAGE_MAX_SIZE,constants::ISO_MAX_SIZE)))
 | 
			
		||||
            .app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
 | 
			
		||||
            // Server controller
 | 
			
		||||
            .route(
 | 
			
		||||
@@ -329,6 +332,11 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/nwfilter/{uid}",
 | 
			
		||||
                web::delete().to(nwfilter_controller::delete),
 | 
			
		||||
            )
 | 
			
		||||
            // Disk images library
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/disk_images/upload",
 | 
			
		||||
                web::post().to(disk_images_controller::upload),
 | 
			
		||||
            )
 | 
			
		||||
            // API tokens controller
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/token/create",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user