diff --git a/Cargo.lock b/Cargo.lock index 008b46c..c398993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,16 @@ dependencies = [ "zip", ] +[[package]] +name = "cargo_toml" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd63e5969cd55839b1ff80d8694e7571667befbaee13bbc2fe7aabc7d2cc104c" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "cc" version = "1.0.73" @@ -360,24 +370,37 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "4.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "0a1af219c3e254a8b4649d6ddaef886b2015089f35f2ac5e1db31410c0566ab8" dependencies = [ "atty", "bitflags", + "clap_derive", "clap_lex", - "indexmap", + "once_cell", "strsim", "termcolor", - "textwrap", +] + +[[package]] +name = "clap_derive" +version = "4.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd114ae53ce5a0670f43d2f169c1cd26c69b4896b0c121900cf1e4d06d67316c" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -3281,6 +3304,7 @@ dependencies = [ name = "tourmaline" version = "0.1.0" dependencies = [ + "cargo_toml", "clap", "color-eyre", "dotenv", diff --git a/Cargo.toml b/Cargo.toml index 74ffdab..7a23853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "tourmaline" version = "0.1.0" edition = "2021" +[package.metadata] +codename = "Walter White" + [lib] name = "tourmaline" @@ -13,7 +16,7 @@ path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "3.2.22" +clap = { version = "4.0.7", features = ["derive"] } color-eyre = "0.6.2" dotenv = "0.15.0" lazy_static = "1.4.0" @@ -29,3 +32,7 @@ thiserror = "1.0.35" tokio = { version = "1.21.1", features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] } tracing = "0.1.36" tracing-subscriber = "0.3.15" + +[build-dependencies] +cargo_toml = "0.12.3" +serde = { version = "1.0.144", features = ["derive"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..74bf55a --- /dev/null +++ b/build.rs @@ -0,0 +1,20 @@ +use serde::Deserialize; +use std::path::PathBuf; + +use cargo_toml::Manifest; + +#[derive(Clone, Debug, Deserialize)] +struct Metadata { + codename: String, +} + +fn main() { + let manifest = Manifest::::from_path_with_metadata(PathBuf::from("Cargo.toml")) + .expect("Failed to read manifest (Cargo.toml)"); + + if let Some(package) = manifest.package { + if let Some(metadata) = package.metadata { + println!("cargo:rustc-env=TOURMALINE_CODENAME={}", metadata.codename); + } + } +} diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..cf7e84a --- /dev/null +++ b/src/args.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; + +use clap::Parser; +use clap::Subcommand; + +const VERSION: &str = concat!( + env!("CARGO_PKG_VERSION"), + " (", + env!("TOURMALINE_CODENAME"), + ")", +); + +#[derive(Debug, Clone, Parser)] +#[clap(bin_name = "trm", name = "Tourmaline", version=VERSION, about= env!("CARGO_PKG_DESCRIPTION"), infer_subcommands = true)] +pub struct Args { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum Command { + /// Installs the system from the given config + #[command()] + InstallFromConfig(InstallFromConfigArgs), + + /// Generates empty script files for the installation + #[command()] + GenerateScripts(GenerateScriptsArgs), +} + +#[derive(Debug, Clone, Parser)] +pub struct InstallFromConfigArgs { + /// The path to the json config file + #[arg()] + pub path: PathBuf, +} + +#[derive(Debug, Clone, Parser)] +pub struct GenerateScriptsArgs { + /// The path to the folder where the scripts should be generated in + #[arg()] + pub path: PathBuf, +} diff --git a/src/lib.rs b/src/lib.rs index ebf61f5..cb32cd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,14 @@ pub struct TaskExecutor { } 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(), + } + } + tasks!( setup_users => SetupUsersScript, configure_network => ConfigureNetworkScript, diff --git a/src/main.rs b/src/main.rs index 05608f5..5cbea09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,45 @@ -use tourmaline::{ - tasks::{User, UsersConfig}, - TaskExecutor, -}; +use args::{Args, Command, GenerateScriptsArgs, InstallFromConfigArgs}; +use clap::Parser; +use miette::{Context, IntoDiagnostic}; +use tokio::{fs::OpenOptions, io::AsyncReadExt}; +use tourmaline::{config::Config, error::AppResult, generate_script_files, TaskExecutor}; + +mod args; #[tokio::main(flavor = "current_thread")] -async fn main() -> miette::Result<()> { +async fn main() { color_eyre::install().unwrap(); dotenv::dotenv().unwrap(); - let executor = TaskExecutor::default(); - let user_cfg = UsersConfig { - users: vec![ - User { - name: String::from("test"), - password: String::from("password"), - sudoer: false, - shell: String::from("/bin/zsh"), - }, - User { - name: String::from("test2"), - password: String::from("superpassword"), - sudoer: true, - shell: String::from("/bin/nu"), - }, - ], - }; - executor.setup_users(&user_cfg).await?; + let args = Args::parse(); + + match args.command { + Command::InstallFromConfig(args) => install_from_config(args).await, + Command::GenerateScripts(args) => generate_scripts(args).await, + } + .unwrap(); +} + +async fn install_from_config(args: InstallFromConfigArgs) -> AppResult<()> { + let mut file = OpenOptions::new() + .read(true) + .open(args.path) + .await + .into_diagnostic() + .context("Could not read file")?; + let mut cfg_contents = String::new(); + file.read_to_string(&mut cfg_contents) + .await + .into_diagnostic() + .context("Could not read file")?; + let config: Config = serde_json::from_str(&cfg_contents) + .into_diagnostic() + .context("Could not parse config as JSON")?; + + TaskExecutor::with_config(config) + .install_from_config() + .await +} - Ok(()) +async fn generate_scripts(args: GenerateScriptsArgs) -> AppResult<()> { + generate_script_files(args.path).await } diff --git a/src/utils.rs b/src/utils.rs index 4cd7a9a..3e1d44b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,6 +16,16 @@ lazy_static::lazy_static! { } pub async fn generate_script_files>(output: P) -> AppResult<()> { + let script_path = output.as_ref().join("scripts"); + let hook_path = output.as_ref().join("hooks"); + + if !script_path.exists() { + fs::create_dir_all(&script_path).await?; + } + if !hook_path.exists() { + fs::create_dir_all(&hook_path).await?; + } + let tasks = all_tasks(); for task in tasks {