[BROKEN!] Refactor tasks to allow dynamic loading and executing

config-extension
trivernis 2 years ago
parent 23728d05ff
commit a8426cb4f9
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

4
Cargo.lock generated

@ -767,9 +767,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "embed-nu" name = "embed-nu"
version = "0.3.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4291b7a0c6d69db167faca6bede4c16a07dff35fead4152b913d0f9bc9bcfb23" checksum = "9fd30e83a59e9eeb1931ae5b43d34aeea424d9c1bd88bb79e65bdec1e0676642"
dependencies = [ dependencies = [
"nu-command", "nu-command",
"nu-engine", "nu-engine",

@ -19,7 +19,7 @@ path = "src/main.rs"
clap = { version = "4.0.17", features = ["derive"] } clap = { version = "4.0.17", features = ["derive"] }
color-eyre = "0.6.2" color-eyre = "0.6.2"
dotenv = "0.15.0" dotenv = "0.15.0"
embed-nu = "0.3.1" embed-nu = "0.3.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
paste = "1.0.9" paste = "1.0.9"
rusty-value = { version = "0.6.0", features = ["derive", "json"] } rusty-value = { version = "0.6.0", features = ["derive", "json"] }

@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://getcryst.al/config.schema.json",
"properties": {
"enable_flatpak": {
"type": "boolean"
},
"enable_timeshift": {
"type": "boolean"
},
"enable_zramd": {
"type": "boolean"
}
}
}

@ -0,0 +1,17 @@
[distro]
name = "Crystal Linux"
website = "https://getcryst.al"
[config]
schema = "config.schema.json"
[tasks]
[tasks.enable_flatpak]
config_field = "enable_flatpak"
[tasks.enable_timeshift]
config_field = "enable_timeshift"
[tasks.enable_zramd]
config_field = "enable_zramd"

@ -0,0 +1,44 @@
use std::{collections::HashMap, path::PathBuf};
use serde::Deserialize;
/// The config file of a distro that defines
/// how that distro should be installed
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
/// Metadata about the distro
pub distro: DistroMetadata,
/// Configuration related to the Operating system
/// setup configuration
pub config: OSConfigMetadata,
/// Additional distro specific tasks
pub tasks: HashMap<String, TaskConfig>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct DistroMetadata {
/// The name of the distro
pub name: String,
/// The website of the distro
pub website: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct OSConfigMetadata {
/// The path of the config schema file
pub schema: Option<PathBuf>,
}
/// The configuration of a single task
#[derive(Clone, Debug, Deserialize)]
pub struct TaskConfig {
/// The name of the config field
pub config_field: String,
/// If the task should be skipped if the
/// config value of that task is null
pub skip_on_null_config: bool,
}

@ -1,3 +1,4 @@
mod config; pub mod config;
mod os_config;
pub use config::*; pub use os_config::*;

@ -3,12 +3,6 @@ use std::path::PathBuf;
use embed_nu::rusty_value::*; use embed_nu::rusty_value::*;
use serde::Deserialize; use serde::Deserialize;
use crate::tasks::{
BootloaderConfig, BootloaderPreset, DesktopConfig, ExtraPackages, Kernel, KernelConfig,
LocaleConfig, NetworkConfig, Partitions, PartitionsConfig, RootUserConfig, UnakiteConfig,
UsersConfig,
};
/// The base configuration of the operating system /// The base configuration of the operating system
/// This config alone should provide all required configuraiton /// This config alone should provide all required configuraiton
/// values to create a base distro installation while still being /// values to create a base distro installation while still being

@ -2,11 +2,17 @@ mod base_config;
use std::collections::HashMap; use std::collections::HashMap;
pub use base_config::BaseConfig; pub use base_config::BaseConfig;
use embed_nu::rusty_value::{ use embed_nu::{
Fields, Float, HashablePrimitive, HashableValue, Integer, Primitive, RustyValue, Struct, Value, rusty_value::{
Fields, Float, HashablePrimitive, HashableValue, Integer, Primitive, RustyValue, Struct,
Value,
},
RustyIntoValue,
}; };
use serde::Deserialize; use serde::Deserialize;
use crate::error::{AppError, AppResult};
/// Represents the full configuration of the OS including extensions defined /// Represents the full configuration of the OS including extensions defined
/// by the distro /// by the distro
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@ -17,6 +23,25 @@ pub struct OSConfig {
pub extended: HashMap<String, serde_json::Value>, pub extended: HashMap<String, serde_json::Value>,
} }
impl OSConfig {
pub fn get_nu_value<K: AsRef<str>>(&self, key: K) -> AppResult<embed_nu::Value> {
let value = self.into_rusty_value();
let mut fields = if let Value::Struct(Struct { fields, .. }) = value {
if let Fields::Named(named) = fields {
named
} else {
panic!("OSConfig fields don't have a name?!");
}
} else {
panic!("OSConfig is not a struct?!");
};
fields
.remove(key.as_ref())
.map(|v| v.into_value())
.ok_or_else(|| AppError::MissingConfigKey(key.as_ref().to_owned()))
}
}
impl RustyValue for OSConfig { impl RustyValue for OSConfig {
fn into_rusty_value(self) -> embed_nu::rusty_value::Value { fn into_rusty_value(self) -> embed_nu::rusty_value::Value {
let base = self.base.into_rusty_value(); let base = self.base.into_rusty_value();

@ -21,6 +21,9 @@ pub enum AppError {
#[error("Missing config")] #[error("Missing config")]
MissingConfig, MissingConfig,
#[error("Missing config key {0}")]
MissingConfigKey(String),
#[error("IO Error: {0}")] #[error("IO Error: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),

@ -12,6 +12,7 @@ pub mod tasks;
pub(crate) mod utils; pub(crate) mod utils;
pub use utils::generate_script_files; pub use utils::generate_script_files;
pub mod distro; pub mod distro;
pub mod task;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum TaskOperation { pub enum TaskOperation {
@ -126,12 +127,3 @@ impl TaskExecutor {
.await .await
} }
} }
impl Default for TaskExecutor {
fn default() -> Self {
Self {
loader: ScriptLoader::new(),
config: None,
}
}
}

@ -0,0 +1,20 @@
use super::{BaseTask, Task};
use lazy_static::lazy_static;
lazy_static! {
pub static ref ALL_BASE_TASKS: Vec<Task> = get_all_base_tasks();
}
fn get_all_base_tasks() -> Vec<Task> {
vec![
Task::Base(BaseTask::ConfigureLocale),
Task::Base(BaseTask::ConfigureNetwork),
Task::Base(BaseTask::CreatePartitions),
Task::Base(BaseTask::InstallBase),
Task::Base(BaseTask::InstallBootloader),
Task::Base(BaseTask::InstallDesktop),
Task::Base(BaseTask::InstallExtraPackages),
Task::Base(BaseTask::SetupRootUser),
Task::Base(BaseTask::SetupUsers),
]
}

@ -0,0 +1,43 @@
use std::path::PathBuf;
use embed_nu::{Argument, CommandGroupConfig, Context, ValueIntoExpression};
use tokio::fs;
use crate::{
distro::OSConfig,
error::{AppError, AppResult},
};
pub struct ExecBuilder {
pub script: PathBuf,
pub os_config: OSConfig,
pub task_config: embed_nu::Value,
}
impl ExecBuilder {
pub async fn exec(self) -> AppResult<()> {
let script_contents = self.get_script_contents().await?;
let mut ctx = Context::builder()
.with_command_groups(CommandGroupConfig::default().all_groups(true))?
.add_var("TRM_CONFIG", self.os_config)?
.add_script(script_contents)?
.build()?;
if ctx.has_fn("main") {
let pipeline = ctx.call_fn(
"main",
vec![Argument::Positional(self.task_config.into_expression())],
)?;
ctx.print_pipeline_stderr(pipeline)?;
Ok(())
} else {
Err(AppError::MissingMain(self.script))
}
}
async fn get_script_contents(&self) -> AppResult<String> {
let contents = fs::read_to_string(&self.script).await?;
Ok(contents)
}
}

@ -0,0 +1,145 @@
use std::path::PathBuf;
use embed_nu::IntoValue;
use crate::{distro::OSConfig, error::AppResult};
use self::exec_builder::ExecBuilder;
pub mod base_tasks;
pub mod exec_builder;
pub mod task_executor;
pub trait TaskTrait {
fn up(&self, config: &OSConfig) -> AppResult<ExecBuilder>;
fn down(&self, config: &OSConfig) -> AppResult<ExecBuilder>;
}
#[derive(Clone, Debug)]
pub enum Task {
Base(BaseTask),
Custom(CustomTask),
}
impl TaskTrait for Task {
#[inline]
fn up(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
match self {
Task::Base(b) => b.up(config),
Task::Custom(c) => c.up(config),
}
}
#[inline]
fn down(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
match self {
Task::Base(b) => b.down(config),
Task::Custom(c) => c.down(config),
}
}
}
#[derive(Clone, Debug)]
pub enum BaseTask {
ConfigureLocale,
ConfigureNetwork,
CreatePartitions,
InstallBase,
InstallBootloader,
InstallDesktop,
InstallExtraPackages,
SetupRootUser,
SetupUsers,
}
impl BaseTask {
fn config_key(&self) -> Option<&'static str> {
let field = match self {
BaseTask::ConfigureLocale => "locale",
BaseTask::ConfigureNetwork => "network",
BaseTask::CreatePartitions => "partitions",
BaseTask::InstallBootloader => "bootloader",
BaseTask::InstallDesktop => "desktop",
BaseTask::InstallExtraPackages => "extra_packages",
BaseTask::SetupRootUser => "root_user",
BaseTask::SetupUsers => "users",
_ => return None,
};
Some(field)
}
fn task_name(&self) -> &'static str {
match self {
BaseTask::ConfigureLocale => "configure-locale",
BaseTask::ConfigureNetwork => "configure-network",
BaseTask::CreatePartitions => "create-partitions",
BaseTask::InstallBase => "install-base",
BaseTask::InstallBootloader => "install-bootloader",
BaseTask::InstallDesktop => "install-desktop",
BaseTask::InstallExtraPackages => "install-extra-packages",
BaseTask::SetupRootUser => "setup-root-user",
BaseTask::SetupUsers => "setup-users",
}
}
}
impl TaskTrait for BaseTask {
fn up(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
let script = PathBuf::from(self.task_name()).join("up.nu");
let task_config = if let Some(key) = self.config_key() {
config.get_nu_value(key)?
} else {
Option::<()>::None.into_value()
};
Ok(ExecBuilder {
script,
os_config: config.to_owned(),
task_config,
})
}
fn down(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
let script = PathBuf::from(self.task_name()).join("down.nu");
let task_config = if let Some(key) = self.config_key() {
config.get_nu_value(key)?
} else {
Option::<()>::None.into_value()
};
Ok(ExecBuilder {
script,
os_config: config.to_owned(),
task_config,
})
}
}
#[derive(Clone, Debug)]
pub struct CustomTask {
config_key: String,
up_script: PathBuf,
down_script: PathBuf,
}
impl TaskTrait for CustomTask {
fn up(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
let task_config = config.get_nu_value(&self.config_key)?;
Ok(ExecBuilder {
script: self.up_script.to_owned(),
os_config: config.to_owned(),
task_config,
})
}
fn down(&self, config: &OSConfig) -> AppResult<ExecBuilder> {
let task_config = config.get_nu_value(&self.config_key)?;
Ok(ExecBuilder {
script: self.down_script.to_owned(),
os_config: config.to_owned(),
task_config,
})
}
}

@ -0,0 +1,24 @@
use crate::distro::OSConfig;
use super::{base_tasks::ALL_BASE_TASKS, Task};
pub struct TaskExecutor {
config: OSConfig,
tasks: Vec<Task>,
}
impl TaskExecutor {
pub fn new(config: OSConfig) -> Self {
Self {
config,
tasks: Vec::new(),
}
}
pub fn with_base_tasks(&mut self) -> &mut Self {
let mut base_tasks = (*ALL_BASE_TASKS).clone();
self.tasks.append(&mut base_tasks);
self
}
}

@ -1,16 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(ConfigureLocale {
name = "configure-locale"
args = LocaleConfig
});
#[derive(Clone, Deserialize, RustyValue, Debug)]
pub struct LocaleConfig {
pub locale: Vec<String>,
pub keymap: String,
pub timezone: String,
}

@ -1,15 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(ConfigureNetwork {
name = "configure-network"
args = NetworkConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct NetworkConfig {
pub hostname: String,
pub ipv6_loopback: bool,
}

@ -1,19 +0,0 @@
use std::path::PathBuf;
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(ConfigureUnakite {
name = "configure-unakite"
args = UnakiteConfig
});
#[derive(Clone, Debug, RustyValue, Deserialize)]
pub struct UnakiteConfig {
pub root: PathBuf,
pub old_root: PathBuf,
pub efidir: PathBuf,
pub bootdev: PathBuf,
}

@ -1,47 +0,0 @@
use std::path::PathBuf;
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(CreatePartitions {
name = "create-partitions"
args = PartitionsConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct PartitionsConfig {
pub device: PathBuf,
pub efi_partition: bool,
pub partitions: Partitions,
}
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub enum Partitions {
Auto,
Manual(Vec<Partition>),
}
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct Partition {
pub mountpoint: PathBuf,
pub blockdevice: PathBuf,
pub filesystem: Option<FileSystem>,
}
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub enum FileSystem {
VFAT,
BFS,
CramFS,
Ext2,
Ext3,
Ext4,
FAT,
MSDOS,
XFS,
BTRFS,
Minix,
F2FS,
}

@ -1,8 +0,0 @@
use crate::task;
task!(InstallBase {
name = "install-base"
args = InstallBaseArgs
});
type InstallBaseArgs = ();

@ -1,23 +0,0 @@
use std::path::PathBuf;
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(InstallBootloader {
name = "install-bootloader"
args = BootloaderConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct BootloaderConfig {
pub preset: BootloaderPreset,
pub location: PathBuf,
}
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub enum BootloaderPreset {
GrubEfi,
Legacy,
}

@ -1,27 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(InstallDesktop {
name = "install-desktop"
args = DesktopConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub enum DesktopConfig {
Onyx,
KdePlasma,
Mate,
Gnome,
Cinnamon,
Xfce,
Budgie,
Enlightenment,
Lxqt,
Sway,
I3Gaps,
HerbstluftWM,
AwesomeWM,
BSPWM,
}

@ -1,8 +0,0 @@
use crate::task;
task!(InstallExtraPackages {
name = "install-extra-packages"
args = ExtraPackages
});
pub type ExtraPackages = Vec<String>;

@ -1,8 +0,0 @@
use crate::task;
task!(InstallFlatpak {
name = "install-flatpak"
args = FlatpakConfig
});
pub type FlatpakConfig = ();

@ -1,18 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(InstallKernels {
name = "install-kernels"
args = KernelConfig
});
#[derive(Clone, Debug, RustyValue, Deserialize)]
pub struct KernelConfig {
pub default: Kernel,
pub additional: Vec<Kernel>,
}
#[derive(Clone, Debug, RustyValue, Deserialize)]
pub struct Kernel(pub String);

@ -1,8 +0,0 @@
use crate::task;
task!(InstallTimeshift {
name = "install-timeshift"
args = TimeshiftConfig
});
pub type TimeshiftConfig = ();

@ -1,8 +0,0 @@
use crate::task;
task!(InstallZRamD {
name = "install-zramd"
args = ZRamDConfig
});
pub type ZRamDConfig = ();

@ -1,118 +0,0 @@
mod configure_locale;
mod configure_network;
mod configure_unakite;
mod create_partitions;
mod install_base;
mod install_bootloader;
mod install_desktop;
mod install_extra_packages;
mod install_flatpak;
mod install_kernels;
mod install_timeshift;
mod install_zramd;
mod setup_root_user;
mod setup_users;
use std::{fmt, path::PathBuf};
pub use configure_locale::*;
pub use configure_network::*;
pub use configure_unakite::*;
pub use create_partitions::*;
pub use install_base::*;
pub use install_bootloader::*;
pub use install_desktop::*;
pub use install_extra_packages::*;
pub use install_flatpak::*;
pub use install_kernels::*;
pub use install_timeshift::*;
pub use install_zramd::*;
pub use setup_root_user::*;
pub use setup_users::*;
use crate::scripting::script::{Script, ScriptArgs};
pub trait Task {
type Config: ScriptArgs + fmt::Debug + Clone;
type UpScript: Script<Args = Self::Config>;
type DownScript: Script<Args = Self::Config>;
fn name() -> &'static str;
}
/// Defines a script
/// This macro doesn't accept a file extension for the script name
/// as it is reused for the hook name
#[macro_export]
macro_rules! task {
($task:ident {
name = $name:literal
args = $argtype:ident
}) => {
paste::item! {
pub struct [<$task Task>];
impl $crate::tasks::Task for [<$task Task>] {
type Config = $argtype;
type UpScript = [<$task UpScript>];
type DownScript = [<$task DownScript>];
#[inline]
fn name() -> &'static str {
$name
}
}
pub struct [<$task UpScript>];
impl $crate::scripting::script::Script for [<$task UpScript>] {
type Args = $argtype;
#[inline]
fn name() -> &'static str {
concat!($name, "/up.nu")
}
}
pub struct [<$task DownScript>];
impl $crate::scripting::script::Script for [<$task DownScript>] {
type Args = $argtype;
#[inline]
fn name() -> &'static str {
concat!($name, "/down.nu")
}
}
}
};
}
pub(crate) fn all_tasks() -> Vec<(&'static str, PathBuf, PathBuf)> {
macro_rules! task_scripts {
($task:ident) => {{
let base = PathBuf::from($task::name());
(
$task::name(),
base.join(<$task as Task>::UpScript::name()),
base.join(<$task as Task>::DownScript::name()),
)
}};
}
vec![
task_scripts!(ConfigureLocaleTask),
task_scripts!(ConfigureNetworkTask),
task_scripts!(ConfigureUnakiteTask),
task_scripts!(CreatePartitionsTask),
task_scripts!(InstallBaseTask),
task_scripts!(InstallBootloaderTask),
task_scripts!(InstallDesktopTask),
task_scripts!(InstallExtraPackagesTask),
task_scripts!(InstallFlatpakTask),
task_scripts!(InstallKernelsTask),
task_scripts!(InstallTimeshiftTask),
task_scripts!(InstallZRamDTask),
task_scripts!(SetupRootUserTask),
task_scripts!(SetupUsersTask),
]
}

@ -1,14 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(SetupRootUser {
name = "setup-root-user"
args = RootUserConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct RootUserConfig {
pub password: String,
}

@ -1,22 +0,0 @@
use embed_nu::rusty_value::*;
use serde::Deserialize;
use crate::task;
task!(SetupUsers {
name = "setup-users"
args = UsersConfig
});
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct UsersConfig {
pub users: Vec<User>,
}
#[derive(Clone, Debug, Deserialize, RustyValue)]
pub struct User {
pub name: String,
pub password: String,
pub sudoer: bool,
pub shell: String,
}
Loading…
Cancel
Save