From 2b3381bd6d9823fc975f91f313e00871e3f53c4a Mon Sep 17 00:00:00 2001 From: Tyler Cook <10459406+cilki@users.noreply.github.com> Date: Tue, 19 Mar 2024 05:01:45 -0500 Subject: [PATCH] wip: refresh alpine linux --- .../src/foundry/molds/alpine_linux/mod.rs | 210 ++++++++---------- .../foundry/molds/arch_linux/archinstall.rs | 6 +- goldboot/src/foundry/molds/arch_linux/mod.rs | 18 +- goldboot/src/foundry/molds/mod.rs | 11 +- goldboot/src/foundry/options/unix_account.rs | 6 + 5 files changed, 116 insertions(+), 135 deletions(-) diff --git a/goldboot/src/foundry/molds/alpine_linux/mod.rs b/goldboot/src/foundry/molds/alpine_linux/mod.rs index e2af9b8..a378957 100644 --- a/goldboot/src/foundry/molds/alpine_linux/mod.rs +++ b/goldboot/src/foundry/molds/alpine_linux/mod.rs @@ -1,70 +1,62 @@ -use crate::{ - build::BuildWorker, cache::*, provisioners::*, qemu::QemuArgs, sources::*, templates::*, -}; +use anyhow::Result; +use dialoguer::theme::Theme; +use goldboot_image::ImageArch; use serde::{Deserialize, Serialize}; -use std::error::Error; +use std::fmt::Display; use strum::{Display, EnumIter, IntoEnumIterator}; use validator::Validate; -/// Template for Alpine Linux images (https://www.alpinelinux.org). -#[derive(Clone, Serialize, Deserialize, Validate, Debug)] -pub struct AlpineTemplate { - pub edition: AlpineEdition, - pub release: AlpineRelease, +use crate::{ + cli::prompt::{Prompt, PromptNew}, + enter, + foundry::{ + options::{hostname::Hostname, unix_account::RootPassword}, + qemu::{OsCategory, QemuBuilder}, + sources::ImageSource, + Foundry, FoundryWorker, + }, + wait, wait_screen_rect, +}; - pub source: Option, - pub provisioners: Option>, -} +use super::{CastImage, DefaultSource}; -#[derive(Clone, Serialize, Deserialize, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum AlpineSource { - Iso(IsoSource), +/// Produces [Alpine Linux](https://www.alpinelinux.org) images. +#[derive(Clone, Serialize, Deserialize, Validate, Debug, Default)] +pub struct AlpineLinux { + pub edition: AlpineEdition, + #[serde(flatten)] + pub hostname: Hostname, + pub release: AlpineRelease, + pub root_password: RootPassword, } -#[derive(Clone, Serialize, Deserialize, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum AlpineProvisioner { - Ansible(AnsibleProvisioner), - Hostname(HostnameProvisioner), - User(UnixAccountProvisioners), - Partition(PartitionProvisioner), - Executable(ExecutableProvisioner), +impl DefaultSource for AlpineLinux { + fn default_source(&self, _: ImageArch) -> Result { + Ok(ImageSource::Iso { + url: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-standard-3.19.1-x86_64.iso".to_string(), + checksum: Some("sha256:12addd7d4154df1caf5f258b80ad72e7a724d33e75e6c2e6adc1475298d47155".to_string()), + }) + } } -impl Default for AlpineTemplate { - fn default() -> Self { - Self { - edition: AlpineEdition::Standard, - release: AlpineRelease::V3_16, - source: None, - provisioners: None, - } +// TODO proc macro +impl Prompt for AlpineLinux { + fn prompt(&mut self, _foundry: &Foundry, _theme: Box) -> Result<()> { + self.root_password = RootPassword::prompt_new(_foundry, _theme)?; + Ok(()) } } -impl BuildTemplate for AlpineTemplate { - fn build(&self, context: &BuildWorker) -> Result<()> { - let mut qemuargs = QemuArgs::new(&context); - - let iso = self.iso.unwrap_or_else(|| { - fetch_latest_iso(self.edition, self.release, context.config.arch).unwrap() - }); - - qemuargs.drive.push(format!( - "file={},if=virtio,cache=writeback,discard=ignore,format=qcow2", - context.image_path - )); - qemuargs - .drive - .push(format!("file={},media=cdrom", iso.download()?)); - - // Start VM - let mut qemu = qemuargs.start_process()?; +impl CastImage for AlpineLinux { + fn cast(&self, worker: &FoundryWorker) -> Result<()> { + let mut qemu = QemuBuilder::new(&worker, OsCategory::Linux) + .source(&worker.element.source)? + .prepare_ssh()? + .start()?; // Send boot command #[rustfmt::skip] - qemu.vnc.boot_command(vec![ + qemu.vnc.run(vec![ // Initial wait wait!(30), // Root login @@ -92,17 +84,15 @@ iface eth0 inet dhcp wait_screen_rect!("6d7b9fc9229c4f4ae8bc84f0925d8479ccd3e7d2", 668, 0, 1024, 100), // Remount root partition enter!("mount -t ext4 /dev/vda3 /mnt"), - // Configure SSH - enter!("echo 'PermitRootLogin yes' >>/mnt/etc/ssh/sshd_config"), // Reboot into installation enter!("apk add efibootmgr; efibootmgr -n 0003; reboot"), ])?; // Wait for SSH - let mut ssh = qemu.ssh_wait(context.ssh_port, "root", &self.root_password)?; + let mut ssh = qemu.ssh("root")?; // Run provisioners - self.provisioners.run(&mut ssh)?; + // TODO // Shutdown ssh.shutdown("poweroff")?; @@ -111,52 +101,45 @@ iface eth0 inet dhcp } } -impl Prompt for AlpineLinux { - fn prompt( - &mut self, - config: &BuildConfig, - theme: Box, - ) -> Result<()> { - // Prompt edition - { - let editions: Vec = AlpineEdition::iter().collect(); - let edition_index = dialoguer::Select::with_theme(&theme) - .with_prompt("Choose an edition") - .default(0) - .items(&editions) - .interact()?; - - self.edition = editions[edition_index]; - } - - // Prompt mirror list - // TODO - - self.validate()?; - Ok(()) - } -} - -#[derive(Clone, Serialize, Deserialize, Debug, EnumIter, Display)] +#[derive(Clone, Copy, Serialize, Deserialize, Debug, EnumIter, Display, Default)] pub enum AlpineEdition { + #[default] Standard, Extended, RaspberryPi, Xen, } -#[derive(Clone, Serialize, Deserialize, Debug, EnumIter)] +impl PromptNew for AlpineEdition { + fn prompt_new( + foundry: &crate::foundry::Foundry, + theme: Box, + ) -> Result { + let editions: Vec = AlpineEdition::iter().collect(); + let edition_index = dialoguer::Select::with_theme(&*theme) + .with_prompt("Choose an edition") + .default(0) + .items(&editions) + .interact()?; + + Ok(editions[edition_index]) + } +} + +#[derive(Clone, Copy, Serialize, Deserialize, Debug, EnumIter, Default)] pub enum AlpineRelease { + #[default] Edge, + #[serde(rename = "v3.19")] + V3_19, + #[serde(rename = "v3.18")] + V3_18, #[serde(rename = "v3.17")] V3_17, #[serde(rename = "v3.16")] V3_16, #[serde(rename = "v3.15")] V3_15, - #[deprecated] - #[serde(rename = "v3.14")] - V3_14, } impl Display for AlpineRelease { @@ -166,6 +149,9 @@ impl Display for AlpineRelease { "{}", match &self { AlpineRelease::Edge => "Edge", + AlpineRelease::V3_19 => "v3.19", + AlpineRelease::V3_18 => "v3.18", + AlpineRelease::V3_17 => "v3.17", AlpineRelease::V3_16 => "v3.16", AlpineRelease::V3_15 => "v3.15", } @@ -173,29 +159,29 @@ impl Display for AlpineRelease { } } -fn fetch_latest_iso( - edition: AlpineEdition, - release: AlpineRelease, - arch: Architecture, -) -> Result { - let arch = match arch { - Architecture::amd64 => "x86_64", - Architecture::arm64 => "aarch64", - _ => bail!("Unsupported architecture"), - }; - - let edition = match edition { - AlpineEdition::Standard => "standard", - AlpineEdition::Extended => "extended", - AlpineEdition::Xen => "virt", - AlpineEdition::RaspberryPi => "rpi", - }; - - let url = format!("https://dl-cdn.alpinelinux.org/alpine/v3.16/releases/{arch}/alpine-{edition}-3.16.0-{arch}.iso"); - - // Download checksum - let rs = reqwest::blocking::get(format!("{url}.sha256"))?; - let checksum = if rs.status().is_success() { None } else { None }; - - Ok(IsoSource { url, checksum }) -} +// fn fetch_latest_iso( +// edition: AlpineEdition, +// release: AlpineRelease, +// arch: Architecture, +// ) -> Result { +// let arch = match arch { +// Architecture::amd64 => "x86_64", +// Architecture::arm64 => "aarch64", +// _ => bail!("Unsupported architecture"), +// }; + +// let edition = match edition { +// AlpineEdition::Standard => "standard", +// AlpineEdition::Extended => "extended", +// AlpineEdition::Xen => "virt", +// AlpineEdition::RaspberryPi => "rpi", +// }; + +// let url = format!("https://dl-cdn.alpinelinux.org/alpine/v3.16/releases/{arch}/alpine-{edition}-3.16.0-{arch}.iso"); + +// // Download checksum +// let rs = reqwest::blocking::get(format!("{url}.sha256"))?; +// let checksum = if rs.status().is_success() { None } else { None }; + +// Ok(IsoSource { url, checksum }) +// } diff --git a/goldboot/src/foundry/molds/arch_linux/archinstall.rs b/goldboot/src/foundry/molds/arch_linux/archinstall.rs index 1a4f7ae..befec6d 100644 --- a/goldboot/src/foundry/molds/arch_linux/archinstall.rs +++ b/goldboot/src/foundry/molds/arch_linux/archinstall.rs @@ -235,11 +235,7 @@ impl From<&super::ArchLinux> for ArchinstallConfig { wipe: false, }], }, - hostname: value - .hostname - .clone() - .unwrap_or(Hostname::default()) - .to_string(), + hostname: value.hostname.hostname.clone(), kernels: vec!["linux".to_string()], keyboard_layout: "us".to_string(), mirror_region: "Worldwide".to_string(), diff --git a/goldboot/src/foundry/molds/arch_linux/mod.rs b/goldboot/src/foundry/molds/arch_linux/mod.rs index f2de652..bf9ace1 100644 --- a/goldboot/src/foundry/molds/arch_linux/mod.rs +++ b/goldboot/src/foundry/molds/arch_linux/mod.rs @@ -26,28 +26,16 @@ use validator::Validate; mod archinstall; /// This `Mold` produces an [Arch Linux](https://archlinux.org) image. -#[derive(Clone, Serialize, Deserialize, Validate, Debug)] +#[derive(Clone, Serialize, Deserialize, Validate, Debug, Default)] pub struct ArchLinux { #[serde(flatten)] - pub hostname: Option, + pub hostname: Hostname, pub mirrorlist: Option, + #[serde(flatten)] pub packages: Option, pub root_password: RootPassword, } -impl Default for ArchLinux { - fn default() -> Self { - Self { - root_password: RootPassword::Plaintext("root".to_string()), - packages: None, - mirrorlist: None, - hostname: Some(Hostname { - hostname: "ArchLinux".to_string(), - }), - } - } -} - // TODO proc macro impl Prompt for ArchLinux { fn prompt(&mut self, _foundry: &Foundry, _theme: Box) -> Result<()> { diff --git a/goldboot/src/foundry/molds/mod.rs b/goldboot/src/foundry/molds/mod.rs index 0695be8..d40648a 100644 --- a/goldboot/src/foundry/molds/mod.rs +++ b/goldboot/src/foundry/molds/mod.rs @@ -3,9 +3,7 @@ use crate::cli::prompt::Prompt; use crate::foundry::Foundry; use crate::foundry::FoundryWorker; use anyhow::Result; -use arch_linux::ArchLinux; use clap::ValueEnum; -use debian::Debian; use dialoguer::theme::Theme; use enum_dispatch::enum_dispatch; use goldboot_image::ImageArch; @@ -13,6 +11,11 @@ use serde::{Deserialize, Serialize}; use std::{fmt::Display, sync::OnceLock}; use strum::{EnumIter, IntoEnumIterator}; +use alpine_linux::AlpineLinux; +use arch_linux::ArchLinux; +use debian::Debian; + +pub mod alpine_linux; pub mod arch_linux; pub mod debian; @@ -37,7 +40,7 @@ pub trait DefaultSource { #[enum_dispatch] #[derive(Clone, Serialize, Deserialize, Debug, EnumIter)] pub enum ImageMold { - // AlpineLinux, + AlpineLinux, ArchLinux, // Artix, // BedrockLinux, @@ -80,6 +83,7 @@ impl ImageMold { /// Supported system architectures pub fn architectures(&self) -> Vec { match self { + ImageMold::AlpineLinux(_) => vec![ImageArch::Amd64, ImageArch::Arm64], ImageMold::ArchLinux(_) => vec![ImageArch::Amd64], ImageMold::Debian(_) => vec![ImageArch::Amd64, ImageArch::Arm64], } @@ -99,6 +103,7 @@ impl Display for ImageMold { f, "{}", match self { + ImageMold::AlpineLinux(_) => "AlpineLinux", ImageMold::ArchLinux(_) => "ArchLinux", ImageMold::Debian(_) => "Debian", } diff --git a/goldboot/src/foundry/options/unix_account.rs b/goldboot/src/foundry/options/unix_account.rs index 42d396e..8921091 100644 --- a/goldboot/src/foundry/options/unix_account.rs +++ b/goldboot/src/foundry/options/unix_account.rs @@ -60,6 +60,12 @@ pub enum RootPassword { PlaintextEnv(String), } +impl Default for RootPassword { + fn default() -> Self { + Self::Plaintext("root".to_string()) + } +} + impl PromptNew for RootPassword { fn prompt_new(_: &Foundry, theme: Box) -> Result { Ok(RootPassword::Plaintext(