Skip to content

Commit

Permalink
wip: refresh alpine linux
Browse files Browse the repository at this point in the history
  • Loading branch information
cilki committed Mar 19, 2024
1 parent 1e91a4b commit 2b3381b
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 135 deletions.
210 changes: 98 additions & 112 deletions goldboot/src/foundry/molds/alpine_linux/mod.rs
Original file line number Diff line number Diff line change
@@ -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<AlpineSource>,
pub provisioners: Option<Vec<AlpineProvisioner>>,
}
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<ImageSource> {
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<dyn Theme>) -> 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
Expand Down Expand Up @@ -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")?;
Expand All @@ -111,52 +101,45 @@ iface eth0 inet dhcp
}
}

impl Prompt for AlpineLinux {
fn prompt(
&mut self,
config: &BuildConfig,
theme: Box<dyn dialoguer::theme::Theme>,
) -> Result<()> {
// Prompt edition
{
let editions: Vec<AlpineEdition> = 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<dyn dialoguer::theme::Theme>,
) -> Result<Self> {
let editions: Vec<AlpineEdition> = 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 {
Expand All @@ -166,36 +149,39 @@ 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",
}
)
}
}

fn fetch_latest_iso(
edition: AlpineEdition,
release: AlpineRelease,
arch: Architecture,
) -> Result<IsoSource> {
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<IsoSource> {
// 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 })
// }
6 changes: 1 addition & 5 deletions goldboot/src/foundry/molds/arch_linux/archinstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
18 changes: 3 additions & 15 deletions goldboot/src/foundry/molds/arch_linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hostname>,
pub hostname: Hostname,
pub mirrorlist: Option<ArchLinuxMirrorlist>,
#[serde(flatten)]
pub packages: Option<ArchLinuxPackages>,
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<dyn Theme>) -> Result<()> {
Expand Down
11 changes: 8 additions & 3 deletions goldboot/src/foundry/molds/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ 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;
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;

Expand All @@ -37,7 +40,7 @@ pub trait DefaultSource {
#[enum_dispatch]
#[derive(Clone, Serialize, Deserialize, Debug, EnumIter)]
pub enum ImageMold {
// AlpineLinux,
AlpineLinux,
ArchLinux,
// Artix,
// BedrockLinux,
Expand Down Expand Up @@ -80,6 +83,7 @@ impl ImageMold {
/// Supported system architectures
pub fn architectures(&self) -> Vec<ImageArch> {
match self {
ImageMold::AlpineLinux(_) => vec![ImageArch::Amd64, ImageArch::Arm64],
ImageMold::ArchLinux(_) => vec![ImageArch::Amd64],
ImageMold::Debian(_) => vec![ImageArch::Amd64, ImageArch::Arm64],
}
Expand All @@ -99,6 +103,7 @@ impl Display for ImageMold {
f,
"{}",
match self {
ImageMold::AlpineLinux(_) => "AlpineLinux",
ImageMold::ArchLinux(_) => "ArchLinux",
ImageMold::Debian(_) => "Debian",
}
Expand Down
6 changes: 6 additions & 0 deletions goldboot/src/foundry/options/unix_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Theme>) -> Result<Self> {
Ok(RootPassword::Plaintext(
Expand Down

0 comments on commit 2b3381b

Please sign in to comment.