use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME; use crate::app_config::AppConfig; use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN}; use crate::controllers::{HttpResult, LibVirtReq}; use crate::extractors::local_auth_extractor::LocalAuthEnabled; use crate::libvirt_rest_structures::hypervisor::HypervisorInfo; use crate::libvirt_rest_structures::net::NetworkInfo; use crate::libvirt_rest_structures::nw_filter::NetworkFilter; use crate::libvirt_rest_structures::vm::VMInfo; use crate::nat::nat_hook; use crate::utils::net_utils; use crate::utils::time_utils::{format_date, time}; use crate::{api_tokens, constants}; use actix_files::NamedFile; use actix_web::{HttpRequest, HttpResponse, Responder}; use serde::Serialize; use std::fs::File; use std::io::Write; use sysinfo::{Components, Disks, Networks, System}; use zip::ZipWriter; use zip::write::SimpleFileOptions; #[derive(serde::Serialize)] struct StaticConfig { auth_disabled: bool, 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], constraints: ServerConstraints, } #[derive(serde::Serialize)] struct LenConstraints { min: usize, max: usize, } #[derive(serde::Serialize)] struct SLenConstraints { min: i64, max: i64, } #[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, group_id_size: LenConstraints, memory_size: LenConstraints, disk_name_size: LenConstraints, disk_size: LenConstraints, disk_image_name_size: LenConstraints, net_name_size: LenConstraints, net_title_size: LenConstraints, net_nat_comment_size: LenConstraints, dhcp_reservation_host_name: LenConstraints, nwfilter_name_size: LenConstraints, nwfilter_comment_size: LenConstraints, nwfilter_priority: SLenConstraints, nwfilter_selectors_count: LenConstraints, api_token_name_size: LenConstraints, api_token_description_size: LenConstraints, api_token_right_path_size: LenConstraints, } pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { HttpResponse::Ok().json(StaticConfig { auth_disabled: AppConfig::get().unsecure_disable_auth, 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.as_bytes(), disk_image_max_size: constants::DISK_IMAGE_MAX_SIZE.as_bytes(), vnc_token_duration: VNC_TOKEN_LIFETIME, vm_name_size: LenConstraints { min: 2, max: 50 }, vm_title_size: LenConstraints { min: 0, max: 50 }, group_id_size: LenConstraints { min: 3, max: 50 }, memory_size: LenConstraints { min: constants::MIN_VM_MEMORY.as_bytes(), max: constants::MAX_VM_MEMORY.as_bytes(), }, disk_name_size: LenConstraints { min: DISK_NAME_MIN_LEN, max: DISK_NAME_MAX_LEN, }, disk_size: LenConstraints { min: DISK_SIZE_MIN.as_bytes(), max: DISK_SIZE_MAX.as_bytes(), }, disk_image_name_size: LenConstraints { min: 5, max: 220 }, net_name_size: LenConstraints { min: 2, max: 50 }, net_title_size: LenConstraints { min: 0, max: 50 }, net_nat_comment_size: LenConstraints { min: 0, max: constants::NET_NAT_COMMENT_MAX_SIZE, }, dhcp_reservation_host_name: LenConstraints { min: 2, max: 250 }, nwfilter_name_size: LenConstraints { min: 2, max: 250 }, nwfilter_comment_size: LenConstraints { min: 0, max: 256 }, nwfilter_priority: SLenConstraints { min: -1000, max: 1000, }, nwfilter_selectors_count: LenConstraints { min: 0, max: 1 }, api_token_name_size: LenConstraints { min: constants::API_TOKEN_NAME_MIN_LENGTH, max: constants::API_TOKEN_NAME_MAX_LENGTH, }, api_token_description_size: LenConstraints { min: constants::API_TOKEN_DESCRIPTION_MIN_LENGTH, max: constants::API_TOKEN_DESCRIPTION_MAX_LENGTH, }, api_token_right_path_size: LenConstraints { min: 0, max: constants::API_TOKEN_RIGHT_PATH_MAX_LENGTH, }, }, }) } #[derive(serde::Serialize)] struct ServerInfo { hypervisor: HypervisorInfo, system: System, components: Components, disks: Disks, networks: Networks, } pub async fn server_info(client: LibVirtReq) -> HttpResult { let mut system = System::new(); system.refresh_all(); let mut components = Components::new(); components.refresh(true); let mut disks = Disks::new(); disks.refresh(true); let mut networks = Networks::new(); networks.refresh(true); Ok(HttpResponse::Ok().json(ServerInfo { hypervisor: client.get_info().await?, system, components, disks, networks, })) } #[derive(serde::Serialize)] struct NetworkHookStatus { installed: bool, content: String, path: &'static str, } pub async fn network_hook_status() -> HttpResult { Ok(HttpResponse::Ok().json(NetworkHookStatus { installed: nat_hook::is_installed()?, content: nat_hook::hook_content()?, path: constants::NAT_HOOK_PATH, })) } pub async fn number_vcpus() -> HttpResult { let mut system = System::new(); system.refresh_cpu_all(); let number_cpus = system.cpus().len(); assert_ne!(number_cpus, 0, "Got invlid number of CPU!"); let mut possible_numbers = vec![1]; if number_cpus > 1 { for i in 0..(number_cpus / 2) { possible_numbers.push(2 + 2 * i); } } Ok(HttpResponse::Ok().json(possible_numbers)) } pub async fn networks_list() -> HttpResult { Ok(HttpResponse::Ok().json(net_utils::net_list())) } pub async fn bridges_list() -> HttpResult { Ok(HttpResponse::Ok().json(net_utils::bridges_list()?)) } /// Add JSON file to ZIP fn zip_json( zip: &mut ZipWriter, dir: &str, content: &Vec, file_name: F, ) -> anyhow::Result<()> where F: Fn(&E) -> String, { for entry in content { let file_encoded = serde_json::to_string(&entry)?; let options = SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Deflated) .unix_permissions(0o750); zip.start_file(format!("{dir}/{}.json", file_name(entry)), options)?; zip.write_all(file_encoded.as_bytes())?; } Ok(()) } /// Export all configuration elements at once pub async fn export_all_configs(req: HttpRequest, client: LibVirtReq) -> HttpResult { // Perform extractions let vms = client .get_full_domains_list() .await? .into_iter() .map(VMInfo::from_domain) .collect::, _>>()?; let networks = client .get_full_networks_list() .await? .into_iter() .map(NetworkInfo::from_xml) .collect::, _>>()?; let nw_filters = client .get_full_network_filters_list() .await? .into_iter() .map(NetworkFilter::lib2rest) .collect::, _>>()?; let tokens = api_tokens::full_list().await?; // Create ZIP file let dest_dir = tempfile::tempdir_in(&AppConfig::get().temp_dir)?; let zip_path = dest_dir.path().join("export.zip"); let file = File::create(&zip_path)?; let mut zip = ZipWriter::new(file); // Encode entities to JSON zip_json(&mut zip, "vms", &vms, |v| v.name.to_string())?; zip_json(&mut zip, "networks", &networks, |v| v.name.0.to_string())?; zip_json( &mut zip, "nw_filters", &nw_filters, |v| match constants::BUILTIN_NETWORK_FILTER_RULES.contains(&v.name.0.as_str()) { true => format!("builtin/{}", v.name.0), false => v.name.0.to_string(), }, )?; zip_json(&mut zip, "tokens", &tokens, |v| v.id.0.to_string())?; // Finalize ZIP and return response zip.finish()?; let file = File::open(zip_path)?; let file = NamedFile::from_file( file, format!( "export_{}.zip", format_date(time() as i64).unwrap().replace('/', "-") ), )?; Ok(file.into_response(&req)) }