Skip to content

Commit

Permalink
Quickget-rs - Reach functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
lj3954 committed Jun 2, 2024
1 parent 72edb5a commit a0c7157
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 24 deletions.
18 changes: 15 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 33 additions & 1 deletion quickemu/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,37 @@ pub struct ConfigFile {
pub ssh_port: u16,
pub usb_devices: Option<Vec<String>>,
}

impl Default for ConfigFile {
fn default() -> Self {
Self {
guest_os: Default::default(),
arch: Default::default(),
boot_type: Default::default(),
cpu_cores: None,
display: Default::default(),
disk_images: Default::default(),
accelerated: default_accel(),
image_files: None,
network: Default::default(),
port_forwards: None,
public_dir: None,
ram: None,
tpm: false,
keyboard: Default::default(),
keyboard_layout: None,
monitor: Default::default(),
serial: Default::default(),
soundcard: Default::default(),
mouse: None,
resolution: Default::default(),
usb_controller: None,
spice_port: default_spice_port(),
ssh_port: default_ssh_port(),
usb_devices: None,
}
}
}
#[cfg(target_os = "macos")]
fn default_accel() -> bool {
false
Expand Down Expand Up @@ -122,9 +153,10 @@ pub enum Access {
}

#[allow(non_camel_case_types)]
#[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
#[derive(clap::ValueEnum, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub enum Arch {
#[default]
#[clap(rename_all = "verbatim")]
x86_64,
aarch64,
riscv64,
Expand Down
8 changes: 6 additions & 2 deletions quickget/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ edition = "2021"
anyhow = "1.0.86"
dirs = "5.0.1"
quickget_ci = { path = "../quickget_ci" }
reqwest = { version = "0.12.4", features = ["blocking"] }
reqwest = "0.12.4"
serde_json = "1.0.117"
toml = "0.8.13"
zstd = "0.13.1"
quick_fetcher = "0.1.0"
quick_fetcher = "0.2.2"
tokio = { version = "1.38.0", features = ["macros"] }
env_logger = "0.11.3"
log = "0.4.21"
clap = { version = "4.5.4", features = ["derive"] }
clap-verbosity-flag = "2.2.0"
itertools = "0.13.0"
which = "6.0.1"
176 changes: 176 additions & 0 deletions quickget/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use anyhow::{anyhow, Result};
use quick_fetcher::{Checksum, Download, Downloader};
use quickget_ci::{Config, ConfigFile, Disk, DiskImage, Image, Source};
use std::{
fs::File,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
use which::which;

pub trait CreateConfig {
async fn create_config(remote: Config, os: String) -> Result<(ConfigFile, String)>;
}

impl CreateConfig for ConfigFile {
async fn create_config(remote: Config, os: String) -> Result<(ConfigFile, String)> {
let vm_path = format!(
"{os}{}{}-{}",
remote.release.map(|r| "-".to_string() + &r).unwrap_or_default(),
remote.edition.map(|e| "-".to_string() + &e).unwrap_or_default(),
remote.arch
);
let vm_dir = PathBuf::from(&vm_path);
std::fs::create_dir(&vm_dir).map_err(|e| anyhow!("Failed to create directory: {}", e))?;
let vm_dir = vm_dir
.canonicalize()
.map_err(|e| anyhow!("Failed to canonicalize directory: {}", e))?;
let mut images = Vec::new();
let mut downloads = Vec::new();
if let Some(iso) = remote.iso {
let (iso_paths, iso_downloads) = convert_sources(iso, &vm_dir, vm_path.clone() + ".iso")?;
images.extend(iso_paths.into_iter().map(Image::Iso));
downloads.extend(iso_downloads);
}
if let Some(img) = remote.img {
let (img_paths, img_downloads) = convert_sources(img, &vm_dir, vm_path.clone() + ".img")?;
images.extend(img_paths.into_iter().map(Image::Img));
downloads.extend(img_downloads);
}
if let Some(floppy) = remote.floppy {
let (floppy_paths, floppy_downloads) = convert_sources(floppy, &vm_dir, vm_path.clone() + "-floppy.img")?;
images.extend(floppy_paths.into_iter().map(Image::Floppy));
downloads.extend(floppy_downloads);
}
if let Some(fixed_iso) = remote.fixed_iso {
let (fixed_iso_paths, fixed_iso_downloads) = convert_sources(fixed_iso, &vm_dir, vm_path.clone() + "-cdrom.iso")?;
images.extend(fixed_iso_paths.into_iter().map(Image::FixedIso));
downloads.extend(fixed_iso_downloads);
}
let disk_images = if let Some(disks) = remote.disk_images {
let (disk_images, disk_downloads) = handle_disks(disks, &vm_dir)?;
downloads.extend(disk_downloads);
disk_images
} else {
Vec::new()
};

let downloader = Downloader::new(downloads).with_progress(Default::default());
log::debug!("Starting downloads");
downloader.start_downloads().await?;
Ok((
ConfigFile {
guest_os: remote.guest_os,
arch: remote.arch,
image_files: Some(images),
disk_images,
..Default::default()
},
vm_path,
))
}
}

fn convert_sources(sources: Vec<Source>, vm_dir: &Path, default_filename: String) -> Result<(Vec<PathBuf>, Vec<Download>)> {
let mut paths = Vec::new();
let mut downloads = Vec::new();
for source in sources {
match source {
Source::FileName(file) => {
let path = vm_dir.join(file);
paths.push(path);
}
Source::Web(web) => {
let url = reqwest::Url::parse(&web.url)?;
let filename = web.file_name.unwrap_or_else(|| {
url.path_segments()
.and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name.into()) })
.unwrap_or(default_filename.clone())
});
let path = vm_dir.join(&filename);
log::debug!("Path: {:?}", path);
let mut dl = Download::new_from_url(url)
.with_output_dir(vm_dir.to_path_buf())
.with_filename(filename);
if let Some(checksum) = web.checksum {
dl = dl.with_checksum(Checksum::new(checksum)?);
}
downloads.push(dl);
paths.push(path);
}
Source::Custom => todo!(),
}
}
Ok((paths, downloads))
}

fn convert_source(source: Source, vm_dir: &Path, default_filename: String) -> Result<(PathBuf, Option<Download>)> {
match source {
Source::FileName(file) => {
let path = vm_dir.join(file);
Ok((path, None))
}
Source::Web(web) => {
let url = reqwest::Url::parse(&web.url)?;
let filename = web.file_name.unwrap_or_else(|| {
url.path_segments()
.and_then(|segments| segments.last())
.and_then(|name| if name.is_empty() { None } else { Some(name.into()) })
.unwrap_or(default_filename.clone())
});
let path = vm_dir.join(&filename);
log::debug!("Path: {:?}", path);
let mut dl = Download::new_from_url(url)
.with_output_dir(vm_dir.to_path_buf())
.with_filename(filename);
if let Some(checksum) = web.checksum {
dl = dl.with_checksum(Checksum::new(checksum)?);
}
Ok((path, Some(dl)))
}
Source::Custom => todo!(),
}
}

fn handle_disks(disks: Vec<Disk>, vm_dir: &Path) -> Result<(Vec<DiskImage>, Vec<Download>)> {
let mut downloads = Vec::new();
let disk_images = disks
.into_iter()
.map(|disk| {
let (path, dl) = convert_source(disk.source, vm_dir, "custom_disk.qcow2".into())?;
if let Some(dl) = dl {
downloads.push(dl);
}
Ok(DiskImage {
path,
size: disk.size,
preallocation: Default::default(),
format: Some(disk.format),
})
})
.collect::<Result<Vec<DiskImage>>>()?;
Ok((disk_images, downloads))
}

pub fn find_quickemu() -> Option<String> {
which("quickemu-rs")
.ok()
.or_else(|| {
let path = std::env::current_exe().ok()?.with_file_name("quickemu-rs");
if path.exists() {
Some(path)
} else {
None
}
})
.map(|q| format!("#!{} --vm\n\n", q.to_string_lossy()))
}

pub fn set_executable(config: &File) -> bool {
let executable = PermissionsExt::from_mode(0o755);
config
.set_permissions(executable)
.map_err(|e| log::warn!("Failed to set permissions on config file: {e}"))
.is_ok()
}
Loading

0 comments on commit a0c7157

Please sign in to comment.