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`
|
# Applies all system changes of `setup-users`
|
||||||
def main [cfg] {
|
def main [cfg] {
|
||||||
echo "Executing up task `setup-users` with config" $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 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 {
|
pub(crate) mod utils;
|
||||||
($($function:ident => $task:ident),+) => {
|
use std::path::PathBuf;
|
||||||
$(
|
|
||||||
#[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
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Installs the system from the given system configuration
|
use distro::{distro_config::DistroConfig, loader::OSConfigLoader};
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
use error::AppResult;
|
||||||
pub async fn install_from_config(&self) -> AppResult<()> {
|
use task::task_executor::TaskExecutor;
|
||||||
let config = self.config.clone().ok_or(AppError::MissingConfig)?;
|
pub use utils::generate_script_files;
|
||||||
self.create_partitions(TaskOperation::Up, config.partitions)
|
pub mod distro;
|
||||||
.await?;
|
pub mod task;
|
||||||
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?;
|
|
||||||
|
|
||||||
if config.enable_zramd {
|
/// Creates a new executor with the given os config for the current distro
|
||||||
self.install_zramd(TaskOperation::Up, ()).await?;
|
#[tracing::instrument(level = "trace")]
|
||||||
}
|
pub async fn create_executor(os_cfg_path: PathBuf) -> AppResult<TaskExecutor> {
|
||||||
if config.enable_timeshift {
|
let distro_config = DistroConfig::load().await?;
|
||||||
self.install_timeshift(TaskOperation::Up, ()).await?;
|
let os_config = OSConfigLoader::new(os_cfg_path, &distro_config)
|
||||||
}
|
.load()
|
||||||
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)
|
|
||||||
.await?;
|
|
||||||
self.install_desktop(TaskOperation::Up, config.desktop)
|
|
||||||
.await?;
|
|
||||||
self.install_extra_packages(TaskOperation::Up, config.extra_packages)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(unakite) = config.unakite {
|
Ok(TaskExecutor::new(os_config, distro_config))
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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