Merge branch 'config-extension' into 'main'
Rewrite to allow for dynamic config extension and task definitions See merge request crystal/software/tourmaline!3chrooting
commit
29a12c5e5e
@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://getcryst.al/config.schema.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enable_flatpak": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_timeshift": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_zramd": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"unakite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"root": {
|
||||
"type": "string"
|
||||
},
|
||||
"old_root": {
|
||||
"type": "string"
|
||||
},
|
||||
"efidir": {
|
||||
"type": "string"
|
||||
},
|
||||
"bootdev": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"root",
|
||||
"old_root",
|
||||
"efidir",
|
||||
"bootdev"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enable_flatpak",
|
||||
"enable_timeshift",
|
||||
"enable_zramd",
|
||||
"unakite"
|
||||
]
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
[distro]
|
||||
name = "Crystal Linux"
|
||||
website = "https://getcryst.al"
|
||||
|
||||
[config]
|
||||
schema = "config.schema.json"
|
||||
|
||||
[tasks]
|
||||
|
||||
[tasks.install-flatpak]
|
||||
config_key = "enable_flatpak"
|
||||
skip_on_false = true
|
||||
|
||||
[tasks.install-timeshift]
|
||||
config_key = "enable_timeshift"
|
||||
skip_on_false = true
|
||||
|
||||
[tasks.install-zramd]
|
||||
config_key = "enable_zramd"
|
||||
skip_on_false = true
|
||||
order = 10
|
||||
|
||||
[tasks.configure-unakite]
|
||||
config_key = "unakite"
|
||||
order = 0
|
@ -1,4 +1,5 @@
|
||||
# Applies all system changes of `setup-users`
|
||||
def main [cfg] {
|
||||
echo "Executing up task `setup-users` with config" $cfg
|
||||
echo $TRM_CONFIG
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use embed_nu::rusty_value::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::tasks::{
|
||||
BootloaderConfig, BootloaderPreset, DesktopConfig, ExtraPackages, Kernel, KernelConfig,
|
||||
LocaleConfig, NetworkConfig, Partitions, PartitionsConfig, RootUserConfig, UnakiteConfig,
|
||||
UsersConfig,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct Config {
|
||||
pub locale: LocaleConfig,
|
||||
pub network: NetworkConfig,
|
||||
pub partitions: PartitionsConfig,
|
||||
pub bootloader: BootloaderConfig,
|
||||
pub kernels: KernelConfig,
|
||||
pub desktop: DesktopConfig,
|
||||
pub users: UsersConfig,
|
||||
pub root_user: RootUserConfig,
|
||||
pub unakite: Option<UnakiteConfig>,
|
||||
pub extra_packages: ExtraPackages,
|
||||
pub enable_timeshift: bool,
|
||||
pub enable_flatpak: bool,
|
||||
pub enable_zramd: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
locale: LocaleConfig {
|
||||
locale: Vec::new(),
|
||||
keymap: String::new(),
|
||||
timezone: String::new(),
|
||||
},
|
||||
network: NetworkConfig {
|
||||
hostname: String::new(),
|
||||
ipv6_loopback: false,
|
||||
},
|
||||
partitions: PartitionsConfig {
|
||||
device: PathBuf::new(),
|
||||
efi_partition: false,
|
||||
partitions: Partitions::Auto,
|
||||
},
|
||||
bootloader: BootloaderConfig {
|
||||
preset: BootloaderPreset::GrubEfi,
|
||||
location: PathBuf::new(),
|
||||
},
|
||||
kernels: KernelConfig {
|
||||
default: Kernel(String::new()),
|
||||
additional: Vec::new(),
|
||||
},
|
||||
desktop: DesktopConfig::KdePlasma,
|
||||
users: UsersConfig { users: Vec::new() },
|
||||
root_user: RootUserConfig {
|
||||
password: String::new(),
|
||||
},
|
||||
unakite: None,
|
||||
extra_packages: Vec::new(),
|
||||
enable_timeshift: false,
|
||||
enable_flatpak: false,
|
||||
enable_zramd: false,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{error::DistroConfigError, utils::CFG_PATH};
|
||||
|
||||
/// The config file of a distro that defines
|
||||
/// how that distro should be installed
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct DistroConfig {
|
||||
/// 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_key: Option<String>,
|
||||
|
||||
/// If the task should be skipped if the
|
||||
/// config value of that task is null
|
||||
#[serde(default)]
|
||||
pub skip_on_false: bool,
|
||||
|
||||
/// The execution order of this task
|
||||
/// Note that custom tasks always get executed after
|
||||
/// the base tasks
|
||||
/// If not set this value defaults to [usize::MAX]
|
||||
#[serde(default = "default_order")]
|
||||
pub order: usize,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn default_order() -> usize {
|
||||
usize::MAX
|
||||
}
|
||||
|
||||
impl DistroConfig {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn load() -> Result<Self, DistroConfigError> {
|
||||
let path = CFG_PATH.join("distro.toml");
|
||||
let contents = fs::read_to_string(path).await?;
|
||||
let cfg = toml::from_str::<Self>(&contents)?;
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
pub mod distro_config;
|
||||
mod os_config;
|
||||
|
||||
pub use os_config::*;
|
@ -0,0 +1,162 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use embed_nu::rusty_value::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// The base configuration of the operating system
|
||||
/// This config alone should provide all required configuraiton
|
||||
/// values to create a base distro installation while still being
|
||||
/// distro agnostic.
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct BaseConfig {
|
||||
pub locale: LocaleConfig,
|
||||
pub network: NetworkConfig,
|
||||
pub partitions: PartitionsConfig,
|
||||
pub bootloader: BootloaderConfig,
|
||||
pub kernels: KernelConfig,
|
||||
pub desktop: DesktopConfig,
|
||||
pub users: UsersConfig,
|
||||
pub root_user: RootUserConfig,
|
||||
pub extra_packages: ExtraPackages,
|
||||
}
|
||||
|
||||
impl BaseConfig {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
locale: LocaleConfig {
|
||||
locale: Vec::new(),
|
||||
keymap: String::new(),
|
||||
timezone: String::new(),
|
||||
},
|
||||
network: NetworkConfig {
|
||||
hostname: String::new(),
|
||||
ipv6_loopback: false,
|
||||
},
|
||||
partitions: PartitionsConfig {
|
||||
device: PathBuf::new(),
|
||||
efi_partition: false,
|
||||
partitions: Partitions::Auto,
|
||||
},
|
||||
bootloader: BootloaderConfig {
|
||||
preset: BootloaderPreset::GrubEfi,
|
||||
location: PathBuf::new(),
|
||||
},
|
||||
kernels: KernelConfig {
|
||||
default: Kernel(String::new()),
|
||||
additional: Vec::new(),
|
||||
},
|
||||
desktop: DesktopConfig::KdePlasma,
|
||||
users: UsersConfig(Vec::new()),
|
||||
root_user: RootUserConfig {
|
||||
password: String::new(),
|
||||
},
|
||||
extra_packages: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, RustyValue, Debug)]
|
||||
pub struct LocaleConfig {
|
||||
pub locale: Vec<String>,
|
||||
pub keymap: String,
|
||||
pub timezone: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct NetworkConfig {
|
||||
pub hostname: String,
|
||||
pub ipv6_loopback: bool,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct BootloaderConfig {
|
||||
pub preset: BootloaderPreset,
|
||||
pub location: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub enum BootloaderPreset {
|
||||
GrubEfi,
|
||||
Legacy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub enum DesktopConfig {
|
||||
Onyx,
|
||||
KdePlasma,
|
||||
Mate,
|
||||
Gnome,
|
||||
Cinnamon,
|
||||
Xfce,
|
||||
Budgie,
|
||||
Enlightenment,
|
||||
Lxqt,
|
||||
Sway,
|
||||
I3Gaps,
|
||||
HerbstluftWM,
|
||||
AwesomeWM,
|
||||
Bspwm,
|
||||
}
|
||||
|
||||
pub type ExtraPackages = Vec<String>;
|
||||
|
||||
#[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);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct RootUserConfig {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct UsersConfig(Vec<User>);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, RustyValue)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub password: String,
|
||||
pub sudoer: bool,
|
||||
pub shell: String,
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tokio::fs;
|
||||
use valico::json_schema::Scope;
|
||||
|
||||
use crate::{
|
||||
distro::distro_config::DistroConfig,
|
||||
error::{AppResult, OSConfigError, SchemaError},
|
||||
utils::CFG_PATH,
|
||||
};
|
||||
|
||||
use super::OSConfig;
|
||||
|
||||
#[derive(Debug)]
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
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)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn load_extension_schema(&self) -> Result<serde_json::Value, SchemaError> {
|
||||
let schema_path = self
|
||||
.distro_cfg
|
||||
.config
|
||||
.schema
|
||||
.as_ref()
|
||||
.map(|p| CFG_PATH.join(p))
|
||||
.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)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn validate_config(schema: serde_json::Value, config: &OSConfig) -> AppResult<()> {
|
||||
let mut scope = Scope::new();
|
||||
let schema = scope
|
||||
.compile_and_return(schema, true)
|
||||
.map_err(SchemaError::ParseSchema)?;
|
||||
let ext_value = serde_json::Value::Object(config.extended.clone().into_iter().collect());
|
||||
|
||||
let result = schema.validate(&ext_value);
|
||||
|
||||
if result.is_valid() {
|
||||
tracing::debug!("Config is valid");
|
||||
Ok(())
|
||||
} else {
|
||||
let msg = result
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|e| format!("{} > {}", e.get_path(), e.get_title()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
tracing::error!("Config is invalid");
|
||||
|
||||
Err(OSConfigError::Validation(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
mod base_config;
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
pub use base_config::BaseConfig;
|
||||
pub mod loader;
|
||||
use embed_nu::{
|
||||
rusty_value::{
|
||||
Fields, Float, HashablePrimitive, HashableValue, Integer, Primitive, RustyValue, Struct,
|
||||
Value,
|
||||
},
|
||||
RustyIntoValue,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::error::{AppResult, OSConfigError};
|
||||
|
||||
/// Represents the full configuration of the OS including extensions defined
|
||||
/// by the distro
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct OSConfig {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseConfig,
|
||||
#[serde(flatten)]
|
||||
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.clone().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(|| OSConfigError::MissingConfigKey(key.as_ref().to_owned()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl RustyValue for OSConfig {
|
||||
fn into_rusty_value(self) -> embed_nu::rusty_value::Value {
|
||||
let base = self.base.into_rusty_value();
|
||||
|
||||
let mut fields = if let Value::Struct(Struct { fields, .. }) = base {
|
||||
if let Fields::Named(named) = fields {
|
||||
named
|
||||
} else {
|
||||
panic!("Base fields don't have a name?!");
|
||||
}
|
||||
} else {
|
||||
panic!("Base is not a struct?!");
|
||||
};
|
||||
let ext = self.extended.into_iter().map(ext_field_to_rusty_value);
|
||||
fields.extend(ext);
|
||||
|
||||
Value::Struct(Struct {
|
||||
name: String::from("Config"),
|
||||
fields: Fields::Named(fields),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OSConfig {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
base: BaseConfig::empty(),
|
||||
extended: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ext_field_to_rusty_value(
|
||||
entry: (String, serde_json::Value),
|
||||
) -> (String, embed_nu::rusty_value::Value) {
|
||||
(entry.0, json_to_rusty_value(entry.1))
|
||||
}
|
||||
|
||||
fn json_to_rusty_value(val: serde_json::Value) -> embed_nu::rusty_value::Value {
|
||||
match val {
|
||||
serde_json::Value::Null => Value::None,
|
||||
serde_json::Value::Bool(b) => Value::Primitive(Primitive::Bool(b)),
|
||||
serde_json::Value::Number(num) => Value::Primitive(if num.is_i64() {
|
||||
Primitive::Integer(Integer::I64(num.as_i64().unwrap()))
|
||||
} else if num.is_u64() {
|
||||
Primitive::Integer(Integer::U64(num.as_u64().unwrap()))
|
||||
} else {
|
||||
Primitive::Float(Float::F64(num.as_f64().unwrap()))
|
||||
}),
|
||||
serde_json::Value::String(s) => Value::Primitive(Primitive::String(s)),
|
||||
serde_json::Value::Array(a) => {
|
||||
Value::List(a.into_iter().map(json_to_rusty_value).collect())
|
||||
}
|
||||
serde_json::Value::Object(o) => {
|
||||
let vals = o
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
HashableValue::Primitive(HashablePrimitive::String(k)),
|
||||
json_to_rusty_value(v),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
Value::Map(vals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OSConfig {
|
||||
pub(crate) async fn load(path: &Path) -> Result<Self, OSConfigError> {
|
||||
let contents = fs::read_to_string(path).await?;
|
||||
let cfg = serde_json::from_str::<Self>(&contents)?;
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
}
|
@ -1,134 +1,22 @@
|
||||
use config::Config;
|
||||
use error::{AppError, AppResult};
|
||||
use scripting::{
|
||||
loader::ScriptLoader,
|
||||
script::{NuScript, Script},
|
||||
};
|
||||
use tasks::*;
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub(crate) mod scripting;
|
||||
pub mod tasks;
|
||||
pub(crate) mod utils;
|
||||
pub use utils::generate_script_files;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub enum TaskOperation {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
pub struct TaskExecutor {
|
||||
config: Option<Config>,
|
||||
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: Config) -> 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
|
||||
);
|
||||
pub(crate) mod utils;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 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)?;
|
||||
self.create_partitions(TaskOperation::Up, config.partitions)
|
||||
.await?;
|
||||
self.install_base(TaskOperation::Up, ()).await?;
|
||||
self.install_kernels(TaskOperation::Up, config.kernels)
|
||||
.await?;
|
||||
self.install_bootloader(TaskOperation::Up, config.bootloader)
|
||||
.await?;
|
||||
self.configure_locale(TaskOperation::Up, config.locale)
|
||||
.await?;
|
||||
self.configure_network(TaskOperation::Up, config.network)
|
||||
.await?;
|
||||
use distro::{distro_config::DistroConfig, loader::OSConfigLoader};
|
||||
use error::AppResult;
|
||||
use task::task_executor::TaskExecutor;
|
||||
pub use utils::generate_script_files;
|
||||
pub mod distro;
|
||||
pub mod task;
|
||||
|
||||
if config.enable_zramd {
|
||||
self.install_zramd(TaskOperation::Up, ()).await?;
|
||||
}
|
||||
if config.enable_timeshift {
|
||||
self.install_timeshift(TaskOperation::Up, ()).await?;
|
||||
}
|
||||
if config.enable_flatpak {
|
||||
self.install_flatpak(TaskOperation::Up, ()).await?;
|
||||
}
|
||||
self.setup_users(TaskOperation::Up, config.users).await?;
|
||||
self.setup_root_user(TaskOperation::Up, config.root_user)
|
||||
/// Creates a new executor with the given os config for the current distro
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub async fn create_executor(os_cfg_path: PathBuf) -> AppResult<TaskExecutor> {
|
||||
let distro_config = DistroConfig::load().await?;
|
||||
let os_config = OSConfigLoader::new(os_cfg_path, &distro_config)
|
||||
.load()
|
||||
.await?;
|
||||
self.install_desktop(TaskOperation::Up, config.desktop)
|
||||
.await?;
|
||||
self.install_extra_packages(TaskOperation::Up, config.extra_packages)
|
||||
.await?;
|
||||
|
||||
if let Some(unakite) = 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]
|
||||
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", Config::empty())
|
||||
}
|
||||
.set_global_var("TRM_VERSION", env!("CARGO_PKG_VERSION"))
|
||||
.execute(args)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TaskExecutor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
loader: ScriptLoader::new(),
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
Ok(TaskExecutor::new(os_config, distro_config))
|
||||
}
|
||||
|
@ -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::SCRIPT_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,83 +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") {
|
||||
ctx.call_fn("main", args.get_args())?;
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{distro::OSConfig, error::AppResult};
|
||||
|
||||
use super::{exec_builder::ExecBuilder, TaskTrait};
|
||||
use embed_nu::IntoValue;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BaseTask {
|
||||
ConfigureLocale,
|
||||
ConfigureNetwork,
|
||||
CreatePartitions,
|
||||
InstallBase,
|
||||
InstallBootloader,
|
||||
InstallDesktop,
|
||||
InstallKernels,
|
||||
InstallExtraPackages,
|
||||
SetupRootUser,
|
||||
SetupUsers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BaseTaskKeydata {
|
||||
pub task_name: &'static str,
|
||||
pub config_key: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl BaseTask {
|
||||
pub fn key_data(&self) -> BaseTaskKeydata {
|
||||
match self {
|
||||
BaseTask::ConfigureLocale => BaseTaskKeydata {
|
||||
task_name: "configure-locale",
|
||||
config_key: Some("locale"),
|
||||
},
|
||||
BaseTask::ConfigureNetwork => BaseTaskKeydata {
|
||||
task_name: "configure-network",
|
||||
config_key: Some("network"),
|
||||
},
|
||||
BaseTask::CreatePartitions => BaseTaskKeydata {
|
||||
task_name: "create-partitions",
|
||||
config_key: Some("partitions"),
|
||||
},
|
||||
BaseTask::InstallBase => BaseTaskKeydata {
|
||||
task_name: "install-base",
|
||||
config_key: None,
|
||||
},
|
||||
BaseTask::InstallBootloader => BaseTaskKeydata {
|
||||
task_name: "install-bootloader",
|
||||
config_key: Some("bootloader"),
|
||||
},
|
||||
BaseTask::InstallDesktop => BaseTaskKeydata {
|
||||
task_name: "install-desktop",
|
||||
config_key: Some("desktop"),
|
||||
},
|
||||
BaseTask::InstallExtraPackages => BaseTaskKeydata {
|
||||
task_name: "install-extra-packages",
|
||||
config_key: Some("extra_packages"),
|
||||
},
|
||||
BaseTask::SetupRootUser => BaseTaskKeydata {
|
||||
task_name: "setup-root-user",
|
||||
config_key: Some("root_user"),
|
||||
},
|
||||
BaseTask::SetupUsers => BaseTaskKeydata {
|
||||
task_name: "setup-users",
|
||||
config_key: Some("users"),
|
||||
},
|
||||
BaseTask::InstallKernels => BaseTaskKeydata {
|
||||
task_name: "install-kernels",
|
||||
config_key: Some("kernels"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrait for BaseTask {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn up(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
let key_data = self.key_data();
|
||||
let script = PathBuf::from(key_data.task_name).join("up.nu");
|
||||
|
||||
let task_config = if let Some(key) = key_data.config_key {
|
||||
config.get_nu_value(key)?
|
||||
} else {
|
||||
Option::<()>::None.into_value()
|
||||
};
|
||||
|
||||
Ok(Some(ExecBuilder {
|
||||
script,
|
||||
os_config: config.to_owned(),
|
||||
task_config,
|
||||
}))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn down(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
let key_data = self.key_data();
|
||||
let script = PathBuf::from(key_data.task_name).join("down.nu");
|
||||
let task_config = if let Some(key) = key_data.config_key {
|
||||
config.get_nu_value(key)?
|
||||
} else {
|
||||
Option::<()>::None.into_value()
|
||||
};
|
||||
|
||||
Ok(Some(ExecBuilder {
|
||||
script,
|
||||
os_config: config.to_owned(),
|
||||
task_config,
|
||||
}))
|
||||
}
|
||||
|
||||
fn order(&self) -> usize {
|
||||
match self {
|
||||
BaseTask::CreatePartitions => 10,
|
||||
BaseTask::InstallBase => 20,
|
||||
BaseTask::InstallKernels => 30,
|
||||
BaseTask::InstallBootloader => 40,
|
||||
BaseTask::ConfigureLocale => 50,
|
||||
BaseTask::ConfigureNetwork => 60,
|
||||
BaseTask::SetupRootUser => 70,
|
||||
BaseTask::SetupUsers => 80,
|
||||
BaseTask::InstallDesktop => 90,
|
||||
BaseTask::InstallExtraPackages => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ALL_BASE_TASKS: Vec<BaseTask> = get_all_base_tasks();
|
||||
}
|
||||
|
||||
fn get_all_base_tasks() -> Vec<BaseTask> {
|
||||
vec![
|
||||
BaseTask::ConfigureLocale,
|
||||
BaseTask::ConfigureNetwork,
|
||||
BaseTask::CreatePartitions,
|
||||
BaseTask::InstallBase,
|
||||
BaseTask::InstallBootloader,
|
||||
BaseTask::InstallDesktop,
|
||||
BaseTask::InstallExtraPackages,
|
||||
BaseTask::SetupRootUser,
|
||||
BaseTask::SetupUsers,
|
||||
BaseTask::InstallKernels,
|
||||
]
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use embed_nu::IntoValue;
|
||||
|
||||
use crate::{distro::OSConfig, error::AppResult};
|
||||
|
||||
use super::{exec_builder::ExecBuilder, TaskTrait};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomTask {
|
||||
config_key: Option<String>,
|
||||
up_script: PathBuf,
|
||||
down_script: PathBuf,
|
||||
skip_on_false: bool,
|
||||
order: usize,
|
||||
}
|
||||
|
||||
impl CustomTask {
|
||||
pub fn new(
|
||||
name: String,
|
||||
config_key: Option<String>,
|
||||
skip_on_false: bool,
|
||||
order: usize,
|
||||
) -> Self {
|
||||
let base_path = PathBuf::from(name);
|
||||
Self {
|
||||
config_key,
|
||||
up_script: base_path.join("up.nu"),
|
||||
down_script: base_path.join("down.nu"),
|
||||
skip_on_false,
|
||||
order,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrait for CustomTask {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn up(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
let task_config = if let Some(key) = self.config_key.as_ref() {
|
||||
config.get_nu_value(key)?
|
||||
} else {
|
||||
Option::<()>::None.into_value()
|
||||
};
|
||||
|
||||
if self.skip_on_false && self.config_key.is_some() && config_is_falsy(&task_config) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(ExecBuilder {
|
||||
script: self.up_script.to_owned(),
|
||||
os_config: config.to_owned(),
|
||||
task_config,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn down(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
let task_config = if let Some(key) = self.config_key.as_ref() {
|
||||
config.get_nu_value(key)?
|
||||
} else {
|
||||
Option::<()>::None.into_value()
|
||||
};
|
||||
|
||||
if self.skip_on_false && self.config_key.is_some() && config_is_falsy(&task_config) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(ExecBuilder {
|
||||
script: self.down_script.to_owned(),
|
||||
os_config: config.to_owned(),
|
||||
task_config,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn order(&self) -> usize {
|
||||
self.order
|
||||
}
|
||||
}
|
||||
|
||||
fn config_is_falsy(config: &embed_nu::Value) -> bool {
|
||||
if config.is_nothing() {
|
||||
return true;
|
||||
} else if let Ok(b) = config.as_bool() {
|
||||
return !b;
|
||||
}
|
||||
false
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use embed_nu::{Argument, CommandGroupConfig, Context, ValueIntoExpression};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{distro::OSConfig, error::ScriptError, utils::CFG_PATH};
|
||||
|
||||
pub struct ExecBuilder {
|
||||
pub script: PathBuf,
|
||||
pub os_config: OSConfig,
|
||||
pub task_config: embed_nu::Value,
|
||||
}
|
||||
|
||||
impl ExecBuilder {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn exec(self) -> Result<(), ScriptError> {
|
||||
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(ScriptError::MissingMain(self.script))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn get_script_contents(&self) -> Result<String, ScriptError> {
|
||||
let path = CFG_PATH.join(&self.script);
|
||||
|
||||
if path.exists() {
|
||||
fs::read_to_string(path).await.map_err(ScriptError::from)
|
||||
} else {
|
||||
Err(ScriptError::ScriptNotFound(path))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::{distro::OSConfig, error::AppResult};
|
||||
|
||||
use self::{base_task::BaseTask, custom_task::CustomTask, exec_builder::ExecBuilder};
|
||||
pub mod base_task;
|
||||
pub mod custom_task;
|
||||
pub mod exec_builder;
|
||||
pub mod task_executor;
|
||||
|
||||
pub trait TaskTrait {
|
||||
fn up(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>>;
|
||||
fn down(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>>;
|
||||
/// Used to decide the execution order
|
||||
/// smaller values mean the task get's executed earlier
|
||||
fn order(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Task {
|
||||
Base(BaseTask),
|
||||
Custom(CustomTask),
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn is_custom(&self) -> bool {
|
||||
match self {
|
||||
Task::Base(_) => false,
|
||||
Task::Custom(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_base(&self) -> bool {
|
||||
!self.is_custom()
|
||||
}
|
||||
|
||||
pub fn compare(&self, other: &Self) -> Ordering {
|
||||
if self.is_base() && other.is_custom() {
|
||||
Ordering::Less
|
||||
} else if self.is_custom() && other.is_base() || self.order() > other.order() {
|
||||
Ordering::Greater
|
||||
} else if self.order() < other.order() {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrait for Task {
|
||||
#[inline]
|
||||
fn up(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
match self {
|
||||
Task::Base(b) => b.up(config),
|
||||
Task::Custom(c) => c.up(config),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn down(&self, config: &OSConfig) -> AppResult<Option<ExecBuilder>> {
|
||||
match self {
|
||||
Task::Base(b) => b.down(config),
|
||||
Task::Custom(c) => c.down(config),
|
||||
}
|
||||
}
|
||||
|
||||
fn order(&self) -> usize {
|
||||
match self {
|
||||
Task::Base(b) => b.order(),
|
||||
Task::Custom(c) => c.order(),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
use crate::{
|
||||
distro::{distro_config::DistroConfig, OSConfig},
|
||||
error::AppResult,
|
||||
};
|
||||
|
||||
use super::{base_task::ALL_BASE_TASKS, custom_task::CustomTask, Task, TaskTrait};
|
||||
|
||||
pub struct TaskExecutor {
|
||||
distro_config: DistroConfig,
|
||||
os_config: OSConfig,
|
||||
tasks: Vec<Task>,
|
||||
}
|
||||
|
||||
impl TaskExecutor {
|
||||
/// Creates a new task executor with the given OSConfig and Distro Config
|
||||
pub fn new(os_config: OSConfig, distro_config: DistroConfig) -> Self {
|
||||
Self {
|
||||
distro_config,
|
||||
os_config,
|
||||
tasks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds all base tasks to the executor
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub fn with_base_tasks(&mut self) -> &mut Self {
|
||||
let mut base_tasks = (*ALL_BASE_TASKS)
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Task::Base)
|
||||
.collect::<Vec<_>>();
|
||||
self.tasks.append(&mut base_tasks);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds all custom tasks to the executor
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub fn with_custom_tasks(&mut self) -> &mut Self {
|
||||
let mut custom_tasks = self
|
||||
.distro_config
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|(name, task)| {
|
||||
CustomTask::new(
|
||||
name.to_owned(),
|
||||
task.config_key.to_owned(),
|
||||
task.skip_on_false,
|
||||
task.order,
|
||||
)
|
||||
})
|
||||
.map(Task::Custom)
|
||||
.collect::<Vec<_>>();
|
||||
self.tasks.append(&mut custom_tasks);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Executes all tasks
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn execute(&mut self) -> AppResult<()> {
|
||||
self.tasks.sort_by(Task::compare);
|
||||
|
||||
for task in &self.tasks {
|
||||
if let Some(up_task) = task.up(&self.os_config)? {
|
||||
up_task.exec().await?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -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 {
|
||||
"up.nu"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct [<$task DownScript>];
|
||||
|
||||
impl $crate::scripting::script::Script for [<$task DownScript>] {
|
||||
type Args = $argtype;
|
||||
|
||||
#[inline]
|
||||
fn name() -> &'static str {
|
||||
"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…
Reference in New Issue