Add config loading and task executor initialization
parent
3f85c87202
commit
618c1ddf9f
@ -1,4 +1,4 @@
|
|||||||
pub mod config;
|
pub mod distro_config;
|
||||||
mod os_config;
|
mod os_config;
|
||||||
|
|
||||||
pub use os_config::*;
|
pub use os_config::*;
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use tokio::fs;
|
||||||
|
use valico::json_schema::Scope;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
distro::distro_config::DistroConfig,
|
||||||
|
error::{AppError, AppResult},
|
||||||
|
utils::CFG_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::OSConfig;
|
||||||
|
|
||||||
|
pub struct OSConfigLoader<'a> {
|
||||||
|
distro_cfg: &'a DistroConfig,
|
||||||
|
cfg_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OSConfigLoader<'a> {
|
||||||
|
pub fn new(path: PathBuf, distro_cfg: &'a DistroConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
distro_cfg,
|
||||||
|
cfg_path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load(&self) -> AppResult<OSConfig> {
|
||||||
|
let schema = self.load_extension_schema().await?;
|
||||||
|
let os_config = OSConfig::load(&self.cfg_path).await?;
|
||||||
|
Self::validate_config(schema, &os_config)?;
|
||||||
|
|
||||||
|
Ok(os_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_extension_schema(&self) -> AppResult<serde_json::Value> {
|
||||||
|
let schema_path = self
|
||||||
|
.distro_cfg
|
||||||
|
.config
|
||||||
|
.schema
|
||||||
|
.as_ref()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| CFG_PATH.join("config.schema.json"));
|
||||||
|
let contents = fs::read_to_string(schema_path).await?;
|
||||||
|
let schema = serde_json::from_str(&contents)?;
|
||||||
|
|
||||||
|
Ok(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_config(schema: serde_json::Value, config: &OSConfig) -> AppResult<()> {
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
let schema = scope.compile_and_return(schema, true)?;
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
for (key, value) in config.extended.iter() {
|
||||||
|
let result = schema.validate_in(value, key);
|
||||||
|
|
||||||
|
for error in result.errors {
|
||||||
|
tracing::error!(
|
||||||
|
"ConfigError: {} ({}) at {}",
|
||||||
|
error.get_title(),
|
||||||
|
error.get_code(),
|
||||||
|
error.get_path(),
|
||||||
|
);
|
||||||
|
errors.push(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let msg = errors
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| format!("{} ({}) at {}", e.get_title(), e.get_code(), e.get_path()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
Err(AppError::InvalidConfig(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,128 +1,21 @@
|
|||||||
use distro::OSConfig;
|
|
||||||
use error::{AppError, AppResult};
|
|
||||||
use scripting::{
|
|
||||||
loader::ScriptLoader,
|
|
||||||
script::{NuScript, Script},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub(crate) mod scripting;
|
|
||||||
|
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use distro::{distro_config::DistroConfig, loader::OSConfigLoader};
|
||||||
|
use error::AppResult;
|
||||||
|
use task::task_executor::TaskExecutor;
|
||||||
pub use utils::generate_script_files;
|
pub use utils::generate_script_files;
|
||||||
pub mod distro;
|
pub mod distro;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
/// Creates a new executor with the given os config for the current distro
|
||||||
pub enum TaskOperation {
|
pub async fn create_executor(os_cfg_path: PathBuf) -> AppResult<TaskExecutor> {
|
||||||
Up,
|
let distro_config = DistroConfig::load().await?;
|
||||||
Down,
|
let os_config = OSConfigLoader::new(os_cfg_path, &distro_config)
|
||||||
}
|
.load()
|
||||||
|
|
||||||
pub struct TaskExecutor {
|
|
||||||
config: Option<OSConfig>,
|
|
||||||
loader: ScriptLoader,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! task_executors {
|
|
||||||
($($function:ident => $task:ident),+) => {
|
|
||||||
$(
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
|
||||||
pub async fn $function(&self, operation: TaskOperation, cfg: <$task as crate::tasks::Task>::Config) -> AppResult<()> {
|
|
||||||
match operation {
|
|
||||||
TaskOperation::Up => self.execute_task::<<$task as crate::tasks::Task>::UpScript>(cfg).await,
|
|
||||||
TaskOperation::Down => self.execute_task::<<$task as crate::tasks::Task>::DownScript>(cfg).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskExecutor {
|
|
||||||
/// Creates a new task executor with a given config
|
|
||||||
pub fn with_config(config: OSConfig) -> Self {
|
|
||||||
Self {
|
|
||||||
config: Some(config),
|
|
||||||
loader: ScriptLoader::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task_executors!(
|
|
||||||
setup_users => SetupUsersTask,
|
|
||||||
configure_network => ConfigureNetworkTask,
|
|
||||||
configure_unakite => ConfigureUnakiteTask,
|
|
||||||
create_partitions => CreatePartitionsTask,
|
|
||||||
install_base => InstallBaseTask,
|
|
||||||
install_bootloader => InstallBootloaderTask,
|
|
||||||
install_desktop => InstallDesktopTask,
|
|
||||||
install_extra_packages => InstallExtraPackagesTask,
|
|
||||||
install_flatpak => InstallFlatpakTask,
|
|
||||||
install_kernels => InstallKernelsTask,
|
|
||||||
install_timeshift => InstallTimeshiftTask,
|
|
||||||
install_zramd => InstallZRamDTask,
|
|
||||||
setup_root_user => SetupRootUserTask,
|
|
||||||
configure_locale => ConfigureLocaleTask
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Installs the system from the given system configuration
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
|
||||||
pub async fn install_from_config(&self) -> AppResult<()> {
|
|
||||||
let config = self.config.clone().ok_or(AppError::MissingConfig)?;
|
|
||||||
let base_config = config.base;
|
|
||||||
|
|
||||||
self.create_partitions(TaskOperation::Up, base_config.partitions)
|
|
||||||
.await?;
|
|
||||||
self.install_base(TaskOperation::Up, ()).await?;
|
|
||||||
self.install_kernels(TaskOperation::Up, base_config.kernels)
|
|
||||||
.await?;
|
|
||||||
self.install_bootloader(TaskOperation::Up, base_config.bootloader)
|
|
||||||
.await?;
|
|
||||||
self.configure_locale(TaskOperation::Up, base_config.locale)
|
|
||||||
.await?;
|
|
||||||
self.configure_network(TaskOperation::Up, base_config.network)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if base_config.enable_zramd {
|
|
||||||
self.install_zramd(TaskOperation::Up, ()).await?;
|
|
||||||
}
|
|
||||||
if base_config.enable_timeshift {
|
|
||||||
self.install_timeshift(TaskOperation::Up, ()).await?;
|
|
||||||
}
|
|
||||||
if base_config.enable_flatpak {
|
|
||||||
self.install_flatpak(TaskOperation::Up, ()).await?;
|
|
||||||
}
|
|
||||||
self.setup_users(TaskOperation::Up, base_config.users)
|
|
||||||
.await?;
|
|
||||||
self.setup_root_user(TaskOperation::Up, base_config.root_user)
|
|
||||||
.await?;
|
.await?;
|
||||||
self.install_desktop(TaskOperation::Up, base_config.desktop)
|
|
||||||
.await?;
|
|
||||||
self.install_extra_packages(TaskOperation::Up, base_config.extra_packages)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(unakite) = base_config.unakite {
|
|
||||||
self.configure_unakite(TaskOperation::Up, unakite).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute_task<S: Script>(&self, args: S::Args) -> AppResult<()> {
|
|
||||||
let script = self.loader.load::<S>()?;
|
|
||||||
self.execute(script, args.clone()).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
Ok(TaskExecutor::new(os_config, distro_config))
|
||||||
async fn execute<S: Script>(&self, mut script: NuScript<S>, args: S::Args) -> AppResult<()> {
|
|
||||||
if let Some(cfg) = self.config.as_ref() {
|
|
||||||
script.set_global_var("TRM_CONFIG", cfg.to_owned())
|
|
||||||
} else {
|
|
||||||
script.set_global_var("TRM_CONFIG", OSConfig::empty())
|
|
||||||
}
|
|
||||||
.set_global_var("TRM_VERSION", env!("CARGO_PKG_VERSION"))
|
|
||||||
.execute(args)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::error::{AppError, AppResult};
|
|
||||||
|
|
||||||
use super::script::{NuScript, Script};
|
|
||||||
|
|
||||||
/// A loader for nu script files
|
|
||||||
pub struct ScriptLoader {
|
|
||||||
script_dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScriptLoader {
|
|
||||||
/// Creates a new script loader with the default config dir
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
script_dir: crate::utils::CFG_PATH.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads the given script file
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
pub fn load<S: Script>(&self) -> AppResult<NuScript<S>> {
|
|
||||||
let script_path = self.script_dir.join(S::name());
|
|
||||||
|
|
||||||
if !script_path.exists() {
|
|
||||||
Err(AppError::ScriptNotFound(script_path))
|
|
||||||
} else {
|
|
||||||
Ok(NuScript::new(script_path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod loader;
|
|
||||||
pub mod script;
|
|
@ -1,84 +0,0 @@
|
|||||||
use core::fmt;
|
|
||||||
use std::{collections::HashMap, marker::PhantomData, path::PathBuf};
|
|
||||||
|
|
||||||
use embed_nu::{
|
|
||||||
rusty_value::RustyValue, Argument, CommandGroupConfig, ContextBuilder, IntoArgument, IntoValue,
|
|
||||||
RawValue, Value,
|
|
||||||
};
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::error::{AppError, AppResult};
|
|
||||||
|
|
||||||
/// A trait implemented for a given nu script type to
|
|
||||||
/// associate arguments
|
|
||||||
pub trait Script {
|
|
||||||
type Args: ScriptArgs + fmt::Debug + Clone;
|
|
||||||
|
|
||||||
/// Returns the (expected) name of the script file
|
|
||||||
/// This function is used by the loader to load the associated file
|
|
||||||
/// The name needs to include the file extension
|
|
||||||
fn name() -> &'static str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Script arguments that can be collected in a Vec to
|
|
||||||
/// be passed to the script
|
|
||||||
pub trait ScriptArgs: RustyValue {
|
|
||||||
fn get_args(self) -> Vec<Argument>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: RustyValue> ScriptArgs for T {
|
|
||||||
fn get_args(self) -> Vec<Argument> {
|
|
||||||
match self.into_value() {
|
|
||||||
Value::List { vals, .. } => vals
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| RawValue(v).into_argument())
|
|
||||||
.collect(),
|
|
||||||
val => vec![RawValue(val).into_argument()],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A nu script instance that can be executed
|
|
||||||
pub struct NuScript<S: Script> {
|
|
||||||
path: PathBuf,
|
|
||||||
vars: HashMap<String, Value>,
|
|
||||||
__phantom: PhantomData<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Script> NuScript<S> {
|
|
||||||
pub(crate) fn new(path: PathBuf) -> Self {
|
|
||||||
Self {
|
|
||||||
path,
|
|
||||||
vars: HashMap::new(),
|
|
||||||
__phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a global variable
|
|
||||||
pub fn set_global_var<S1: ToString, V: IntoValue>(&mut self, key: S1, value: V) -> &mut Self {
|
|
||||||
self.vars.insert(key.to_string(), value.into_value());
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes the script with the given args
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
|
||||||
pub async fn execute(&self, args: S::Args) -> AppResult<()> {
|
|
||||||
let mut ctx = ContextBuilder::default()
|
|
||||||
.with_command_groups(CommandGroupConfig::default().all_groups(true))?
|
|
||||||
.add_script(self.read_file().await?)?
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
if ctx.has_fn("main") {
|
|
||||||
let pipeline = ctx.call_fn("main", args.get_args())?;
|
|
||||||
ctx.print_pipeline(pipeline)?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(AppError::MissingMain(self.path.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_file(&self) -> AppResult<String> {
|
|
||||||
fs::read_to_string(&self.path).await.map_err(AppError::from)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue