diff --git a/goldboot-image/src/lib.rs b/goldboot-image/src/lib.rs index 1774545..83ddf77 100644 --- a/goldboot-image/src/lib.rs +++ b/goldboot-image/src/lib.rs @@ -37,6 +37,20 @@ pub enum ImageArch { S390x, } +impl ImageArch { + pub fn as_github_string(&self) -> String { + match self { + ImageArch::Amd64 => "x86_64", + ImageArch::Arm64 => todo!(), + ImageArch::I386 => todo!(), + ImageArch::Mips => todo!(), + ImageArch::Mips64 => todo!(), + ImageArch::S390x => todo!(), + } + .to_string() + } +} + impl Default for ImageArch { fn default() -> Self { match std::env::consts::ARCH { @@ -387,11 +401,11 @@ impl ImageHandle { let path = path.as_ref(); let mut file = File::open(path)?; - debug!("Opening image from: {}", path.display()); + debug!(path = ?path, "Opening image"); // Read primary header (always plaintext) let primary_header: PrimaryHeader = file.read_be()?; - trace!("Read: {:?}", &primary_header); + debug!(primary_header = ?primary_header, "Primary header"); // Get image ID let id = if let Some(stem) = path.file_stem() { @@ -753,9 +767,9 @@ impl ImageHandle { let mut cluster: Cluster = cluster_table.read_be()?; trace!( - "Read cluster of size {} from offset {}", - cluster.size, - entry.cluster_offset + cluster_size = cluster.size, + cluster_offset = entry.cluster_offset, + "Read cluster", ); // Reverse encryption @@ -777,7 +791,7 @@ impl ImageHandle { // Write the cluster to the block dest.seek(SeekFrom::Start(entry.block_offset))?; - dest.write_all(&cluster.data)?; + // dest.write_all(&cluster.data)?; } progress( diff --git a/goldboot/src/cli/cmd/image.rs b/goldboot/src/cli/cmd/image.rs index a9d6f3f..1c2cdf5 100644 --- a/goldboot/src/cli/cmd/image.rs +++ b/goldboot/src/cli/cmd/image.rs @@ -9,7 +9,7 @@ pub fn run(cmd: super::Commands) -> ExitCode { match cmd { super::Commands::Image { command } => match &command { super::ImageCommands::List {} => { - let images = ImageLibrary::load().unwrap(); + let images = ImageLibrary::find_all().unwrap(); println!("Image Name Image Size Build Date Image ID Description"); for image in images { diff --git a/goldboot/src/cli/cmd/liveusb.rs b/goldboot/src/cli/cmd/liveusb.rs index 5b4b03c..62994b0 100644 --- a/goldboot/src/cli/cmd/liveusb.rs +++ b/goldboot/src/cli/cmd/liveusb.rs @@ -22,9 +22,20 @@ pub fn run(cmd: super::Commands) -> ExitCode { return ExitCode::FAILURE; } + // Load from library or download + let mut image_handles = match ImageLibrary::find_by_name("Test") { + Ok(image_handles) => image_handles, + Err(_) => return ExitCode::FAILURE, + }; + + // TODO prompt password + if image_handles[0].load(None).is_err() { + return ExitCode::FAILURE; + } + if !confirm { if !Confirm::with_theme(&theme) - .with_prompt("Do you want to continue?") + .with_prompt(format!("Do you want to overwrite: {}?", dest)) .interact() .unwrap() { @@ -32,7 +43,13 @@ pub fn run(cmd: super::Commands) -> ExitCode { } } - ExitCode::SUCCESS + match image_handles[0].write(dest, ProgressBar::Write.new_empty()) { + Err(err) => { + error!(error = %err, "Failed to write image"); + ExitCode::FAILURE + } + _ => ExitCode::SUCCESS, + } } _ => panic!(), } diff --git a/goldboot/src/foundry/molds/goldboot/mod.rs b/goldboot/src/foundry/molds/goldboot/mod.rs index 7110bf8..c068415 100644 --- a/goldboot/src/foundry/molds/goldboot/mod.rs +++ b/goldboot/src/foundry/molds/goldboot/mod.rs @@ -2,7 +2,12 @@ use anyhow::{bail, Result}; use dialoguer::theme::Theme; use goldboot_image::ImageArch; use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader}; +use serde_json::{Map, Value}; +use std::{ + collections::HashMap, + io::{BufRead, BufReader}, +}; +use tracing::debug; use validator::Validate; use crate::{ @@ -54,6 +59,10 @@ impl CastImage for Goldboot { let mut qemu = QemuBuilder::new(&worker, OsCategory::Linux) .vga("cirrus") .source(&worker.element.source)? + .drive_files(HashMap::from([( + "goldboot".to_string(), + get_latest_release(OsCategory::Linux, worker.arch)?, + )]))? .start()?; // Start HTTP @@ -77,7 +86,8 @@ impl CastImage for Goldboot { enter!("root"), enter!("r00tme"), // Install goldboot - enter!(format!("curl -o /usr/bin/goldboot https://github.com/fossable/goldboot/releases/download/goldboot-v0.0.7/goldboot_{}-unknown-linux-gnu", worker.arch)), + enter!("mount /dev/vdb /mnt"), + enter!("cp /mnt/goldboot /usr/bin/goldboot"), enter!("chmod +x /usr/bin/goldboot"), // Skip getty login enter!("sed -i 's|ExecStart=.*$|ExecStart=/usr/bin/goldboot|' /usr/lib/systemd/system/getty@.service"), @@ -89,3 +99,80 @@ impl CastImage for Goldboot { Ok(()) } } + +/// Download the latest goldboot release. +fn get_latest_release(os: OsCategory, arch: ImageArch) -> Result> { + // List releases + let releases: Vec = reqwest::blocking::Client::new() + .get("https://api.github.com/repos/fossable/goldboot/releases") + .header("Accept", "application/vnd.github+json") + .header("X-GitHub-Api-Version", "2022-11-28") + .header("User-Agent", "goldboot") + .send()? + .json()?; + debug!(count = releases.len(), "Total releases"); + + // Match the major and minor versions against what we're currently running + let mut releases: Vec> = releases + .into_iter() + .filter_map(|r| match r { + Value::Object(release) => match release.get("tag_name") { + Some(Value::String(name)) => { + if name.starts_with(&format!( + "goldboot-v{}.{}.", + crate::built_info::PKG_VERSION_MAJOR, + crate::built_info::PKG_VERSION_MINOR + )) { + Some(release) + } else { + None + } + } + _ => None, + }, + _ => None, + }) + .collect(); + + debug!(count = releases.len(), "Matched releases"); + + // Sort by patch version + releases.sort_by_key(|release| match release.get("tag_name") { + Some(Value::String(name)) => name.split(".").last().unwrap().parse::().unwrap(), + _ => todo!(), + }); + + // Find asset for the given arch + let asset = match releases.last().unwrap().get("assets") { + Some(Value::Array(assets)) => assets + .iter() + .filter_map(|a| match a { + Value::Object(asset) => match asset.get("name") { + Some(Value::String(name)) => { + if name.contains(&arch.as_github_string()) + && name.contains(&os.as_github_string()) + { + Some(asset) + } else { + None + } + } + _ => None, + }, + _ => None, + }) + .last(), + _ => None, + }; + + // Download the asset + if let Some(asset) = asset { + debug!(asset = ?asset, "Found asset for download"); + match asset.get("browser_download_url") { + Some(Value::String(url)) => Ok(reqwest::blocking::get(url)?.bytes()?.into()), + _ => todo!(), + } + } else { + bail!("No release asset found for OS/Arch combination"); + } +} diff --git a/goldboot/src/foundry/qemu.rs b/goldboot/src/foundry/qemu.rs index 9ed307b..017e057 100644 --- a/goldboot/src/foundry/qemu.rs +++ b/goldboot/src/foundry/qemu.rs @@ -25,8 +25,21 @@ pub enum OsCategory { Windows, } +impl OsCategory { + /// Convert to String representation for use in a Github release asset + pub fn as_github_string(&self) -> String { + match self { + OsCategory::Darwin => "apple", + OsCategory::Linux => "linux", + OsCategory::Windows => "windows", + } + .to_string() + } +} + /// Supported VM hardware acceleration. pub enum Accel { + /// "Kernel VM" which requires Intel VT or AMD-V Kvm, /// Basically means no acceleration Tcg, @@ -363,7 +376,7 @@ impl QemuBuilder { // Add a buffer of extra space let mut fs_size: u64 = files.values().map(|c| c.len() as u64).sum(); - fs_size += 32000; + fs_size += 320000; debug!( fs_path = ?fs_path, @@ -391,6 +404,7 @@ impl QemuBuilder { let root_dir = fs.root_dir(); for (path, content) in &files { + debug!(path = ?path, size = content.len(), "Copying file to temporary filesystem"); let mut file = root_dir.create_file(path)?; file.write_all(content)?; } diff --git a/goldboot/src/gui/select_image.rs b/goldboot/src/gui/select_image.rs index 1482ce6..9d9b51d 100644 --- a/goldboot/src/gui/select_image.rs +++ b/goldboot/src/gui/select_image.rs @@ -43,7 +43,7 @@ pub fn init(window: &'static gtk::ApplicationWindow) { let mut images = Vec::new(); - for image in ImageLibrary::load().unwrap() { + for image in ImageLibrary::find_all().unwrap() { images.push(image.id.clone()); image_box.append(&create_image_row(&image)); } diff --git a/goldboot/src/library.rs b/goldboot/src/library.rs index 6e2d6b0..7c59120 100644 --- a/goldboot/src/library.rs +++ b/goldboot/src/library.rs @@ -10,7 +10,7 @@ use std::{ fs::File, path::{Path, PathBuf}, }; -use tracing::{debug, info}; +use tracing::{debug, error, info}; /// Represents the local image library. /// @@ -119,7 +119,7 @@ impl ImageLibrary { /// Find images in the library by ID. pub fn find_by_id(image_id: &str) -> Result { - Ok(Self::load()? + Ok(Self::find_all()? .into_iter() .find(|image| image.id == image_id || image.id[0..12] == image_id[0..12]) .ok_or_else(|| anyhow!("Image not found"))?) @@ -127,14 +127,14 @@ impl ImageLibrary { /// Find images in the library by name. pub fn find_by_name(image_name: &str) -> Result> { - Ok(Self::load()? + Ok(Self::find_all()? .into_iter() .filter(|image| image.primary_header.name() == image_name) .collect()) } - /// Load images present in the local image library. - pub fn load() -> Result> { + /// Find all images present in the local image library. + pub fn find_all() -> Result> { let mut images = Vec::new(); for p in Self::open().directory.read_dir()? { @@ -142,10 +142,7 @@ impl ImageLibrary { if let Some(ext) = path.extension() { if ext == "gb" { - match ImageHandle::open(&path) { - Ok(image) => images.push(image), - Err(error) => debug!("Failed to load image: {:?}", error), - } + images.push(ImageHandle::open(&path)?); } } }