diff --git a/src/cli.rs b/src/cli.rs index 502058a..2553b5b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -100,13 +100,28 @@ pub async fn cli( args_vec.push(new_arg); position_count += 1; } + for given_arg in &args { + if given_arg.contains("=") { + for optional_arg in toolcommand.optional_args.clone() { + let arg_vec: Vec<&str> = given_arg.split("=").collect(); + if arg_vec[0].to_string() == optional_arg { + println!("arg found {}", optional_arg); + let mut new_arg = ToolArgument::default(); + new_arg.name = optional_arg; + new_arg.string = Some(arg_vec[1].trim().to_string()); + new_arg.optional = true; + args_vec.push(new_arg); + } + } + } + } } if ready { for arg in &mut args_vec { let iargs = args.clone(); if arg.user_supplied { arg.string = Some(iargs[arg.position.unwrap()].clone()); - } else { + } else if !arg.optional { match arg.name.as_str() { "projects" => arg.projects = Some(projects.clone()), "upcoming_files" => { @@ -117,11 +132,23 @@ pub async fn cli( arg.path = Some(PathBuf::from(settings["upcoming_notes"].clone())) } + "files" => { + arg.path = + Some(PathBuf::from(settings["current_files"].clone())) + } + "notes" => { + arg.path = + Some(PathBuf::from(settings["current_notes"].clone())) + } + "tools" => { + arg.path = Some(PathBuf::from(settings["tools"].clone())) + } "template" => { arg.string = Some(String::from(settings["templatebox"].clone())) } "config" => arg.path = Some(config_path.clone()), + "terminal" => arg.string = Some(settings["terminal"].clone()), _ => print_error( &format!("unkown arg requested! {}", arg.name), None, diff --git a/src/commands.rs b/src/commands.rs index 99dfe41..ce9cbd6 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,10 +1,10 @@ +use crate::get_user_input; use crate::lib::Destination; use crate::lib::Message; use crate::lib::Project; use crate::lib::Table; use crate::print_error; use crate::print_success; -use crate::save_project; use std::fs::create_dir; use std::fs::create_dir_all; use std::fs::{File, OpenOptions, ReadDir, read_to_string}; @@ -20,6 +20,7 @@ pub struct ToolCommand { pub help: String, pub req_args: Vec, pub user_args: Vec, + pub optional_args: Vec, pub args: Option>, pub func: fn(Option>) -> String, } @@ -31,6 +32,7 @@ impl ToolCommand { help, req_args: Vec::new(), user_args: Vec::new(), + optional_args: Vec::new(), args: None, func, } @@ -40,9 +42,13 @@ impl ToolCommand { if self.req_args.len() > 0 { if self.args.is_none() { return String::from("Error: no arguments given, but arguments are required!"); - } else if self.args.clone().unwrap().len() != self.req_args.len() + self.user_args.len() - { - return String::from("Error: the wrong number of args were supplied!"); + } else { + let min_args = self.req_args.len() + self.user_args.len(); + let max_args = min_args + self.optional_args.len(); + let args_provided = self.args.clone().unwrap().len(); + if args_provided > max_args || args_provided < min_args { + return String::from("Error: the wrong number of args were supplied!"); + } } } return (self.func)(self.args.clone()); @@ -53,6 +59,7 @@ impl ToolCommand { pub struct ToolArgument { pub name: String, pub user_supplied: bool, + pub optional: bool, pub position: Option, pub path: Option, pub string: Option, @@ -86,6 +93,31 @@ pub fn build_tools() -> Vec { ]; createproject.user_args = vec![String::from("name")]; tool_commands.push(createproject); + let mut promoteproject = ToolCommand::new( + "promote_project".to_string(), + "promotes a project from upcoming to current, and sets up the distrobox. +Optional arguments: home=/path/to/distrobox/home name=project_name_to_promote" + .to_string(), + promote_project, + ); + promoteproject.req_args = vec![ + String::from("projects"), + String::from("tools"), + String::from("files"), + String::from("notes"), + String::from("template"), + ]; + promoteproject.optional_args = vec![String::from("home"), String::from("name")]; + tool_commands.push(promoteproject); + let mut removeproject = ToolCommand::new( + "remove_project".to_string(), + "removes a project from the tool, deletes the files and notes, and deletes the distrobox." + .to_string(), + remove_project, + ); + removeproject.optional_args = vec!["project".to_string()]; + removeproject.req_args = vec!["projects".to_string()]; + tool_commands.push(removeproject); return tool_commands; } @@ -111,7 +143,6 @@ pub fn list_projects(args: Option>) -> String { } pub fn new_project(args: Option>) -> String { - println!("reached the new_project function"); let given_args = args.unwrap(); let mut config_path = PathBuf::new(); let mut name = String::new(); @@ -180,8 +211,96 @@ pub fn new_project(args: Option>) -> String { new_project.notes = notes_path; new_project.current = false; new_project.boxname = format!("{}_{}", template_box, name); - save_project(&new_project, &project_path); + new_project.config = project_path; + println!("{}", new_project.config.display()); + new_project.save_project(); print_success("folder structure and config file created successfully!"); println!("setting up default notes..."); return new_project.generate_default_notes(&config_path); } + +pub fn promote_project(args: Option>) -> String { + let mut project = String::new(); + let mut projects = Vec::new(); + let mut files = PathBuf::new(); + let mut notes = PathBuf::new(); + let mut tools = PathBuf::new(); + let mut template = String::new(); + let mut home = None; + let mut given_args = Vec::new(); + if args.is_some() { + given_args = args.unwrap(); + } + for arg in given_args { + match arg.name.as_str() { + "name" => project = arg.string.clone().unwrap(), + "projects" => projects = arg.projects.clone().unwrap(), + "files" => files = arg.path.unwrap(), + "notes" => notes = arg.path.unwrap(), + "template" => template = arg.string.unwrap(), + "home" => home = arg.path, + "tools" => tools = arg.path.unwrap(), + _ => {} + } + } + let project_set = project.len() > 1; + if !project_set { + println!("{} : {}", project, project.len()); + println!("{}", project.len() > 1); + let mut current_index = 0; + for existing_project in &projects { + println!("{}.) {}", current_index, existing_project.name); + current_index += 1; + } + let response: usize = get_user_input("which project would you like to promote?") + .parse() + .unwrap(); + if response >= projects.len() { + return format!("error invalid project selection!"); + } + project = projects[response].name.clone(); + } + for mut existing_project in projects { + if existing_project.name == project { + let promote_res = existing_project.promote_project( + &files, + ¬es, + template.clone(), + &tools, + home.clone(), + ); + if !promote_res.to_lowercase().contains("success") { + return format!("Error promoting project!\n{}", promote_res); + } + } + } + return String::from("Success!"); +} + +pub fn remove_project(args: Option>) -> String { + let mut project = Project::default(); + let mut projects = Vec::new(); + if args.is_some() { + let given_args = args.unwrap(); + for arg in given_args { + match arg.name.as_str() { + "project" => project = arg.project.unwrap(), + "projects" => projects = arg.projects.unwrap(), + _ => {} + } + } + } + if !project.name.len() > 1 { + let mut current_index = 0; + for project in &projects { + println!("{}.) {}", current_index, project.name); + current_index += 1; + } + let response: usize = get_user_input("project to remove?").parse().unwrap(); + if response >= projects.len() { + return format!("error invalid project selection!"); + } + project = projects[response].clone() + } + return project.remove_project(); +} diff --git a/src/install.rs b/src/install.rs index 844ecbd..3a71fdd 100644 --- a/src/install.rs +++ b/src/install.rs @@ -145,6 +145,13 @@ pub fn install(config: &PathBuf) -> bool { settings.insert("current_notes".to_string(), get_user_input("full path to where you want your current project's notes stored example: /home/pyro/notes/current")); settings.insert("upcoming_files".to_string(),get_user_input("full path to where you want your upcoming project's files stored example: /home/pyro/projects/upcoming")); settings.insert("upcoming_notes".to_string(), get_user_input("full path to where you want your upcoming project's notes stored exmple: /home/pyro/notes/upcoming")); + settings.insert( + "tools".to_string(), + get_user_input( + "full path to where you store your custom tools (like those from github)?", + ), + ); + settings.insert("terminal".to_string(), get_user_input("command used to launch your terminal while executing a command (for exmaple konsole in kde is konsole -e)?")); print_success("sweet, we have all we need, writing config file..."); let out_file_res = File::create_new(&config_file); if out_file_res.is_err() { diff --git a/src/lib.rs b/src/lib.rs index f61fc0e..8658c73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,9 @@ +use colored::Colorize; use std::{ - fs::{copy, create_dir_all}, + fs::{File, OpenOptions, copy, create_dir_all, remove_dir_all, remove_file}, + io::Write, path::PathBuf, + process::Command, }; use walkdir::WalkDir; @@ -136,6 +139,7 @@ pub struct Project { pub notes: PathBuf, pub current: bool, pub boxname: String, + pub config: PathBuf, } impl Project { @@ -176,4 +180,241 @@ impl Project { } return String::from("Success!"); } + + pub fn promote_project( + &mut self, + files: &PathBuf, + notes: &PathBuf, + template: String, + tools: &PathBuf, + home: Option, + ) -> String { + let mut new_files_path = files.clone(); + let mut new_notes_path = notes.clone(); + new_files_path.push(&self.name); + new_notes_path.push(&self.name); + let files_create_res = create_dir_all(&new_files_path); + if files_create_res.is_err() { + return (format!( + "Error creating new files folder!\n{}", + files_create_res.err().unwrap() + )); + } + let notes_create_res = create_dir_all(&new_notes_path); + if notes_create_res.is_err() { + return (format!( + "Error creating new notes folder!\n{}", + notes_create_res.err().unwrap() + )); + } + files_create_res.unwrap(); + notes_create_res.unwrap(); + let file_walkdir = WalkDir::new(&self.files); + let notes_walkdir = WalkDir::new(&self.notes); + for res in file_walkdir { + if res.is_ok() { + let entry = res.unwrap(); + if entry.file_type().is_file() { + let mut temp_path = new_files_path.clone(); + temp_path.push(entry.file_name()); + let copy_res = copy(entry.path(), temp_path); + if copy_res.is_ok() { + copy_res.unwrap(); + } + } + } + } + for res in notes_walkdir { + if res.is_ok() { + let entry = res.unwrap(); + if entry.file_type().is_file() { + let mut temp_path = new_notes_path.clone(); + temp_path.push(entry.file_name()); + let copy_res = copy(entry.path(), temp_path); + if copy_res.is_ok() { + copy_res.unwrap(); + } + } + } + } + let old_file_remove_res = remove_dir_all(&self.files); + if old_file_remove_res.is_err() { + println!( + "{}", + "Error removing upcoming files directory, manual clean up required!".red() + ); + } else { + old_file_remove_res.unwrap(); + } + let old_note_remove_res = remove_dir_all(&self.notes); + if old_note_remove_res.is_err() { + println!( + "{}", + "Error removing upcoming notes directory, manual cleanup required!".red(), + ); + } else { + old_note_remove_res.unwrap(); + } + + self.current = true; + self.files = new_files_path; + self.notes = new_notes_path; + self.save_project(); + let distrobox_res = self.create_distrobox(template, tools, home); + if !distrobox_res.to_lowercase().contains("Success") { + return format!( + "Error creating distrobox!\n{}\n\nThe project was still promoted, but the distrobox has not been created!", + distrobox_res + ); + } + return String::from("Success!"); + } + + pub fn save_project(&self) -> String { + if self.config.exists() { + let remove_res = remove_file(&self.config); + if remove_res.is_err() { + return format!("Error removing old config!\n{}", remove_res.err().unwrap()); + } + remove_res.unwrap(); + } + let conf_open_create_res = File::create(&self.config); + if conf_open_create_res.is_err() { + return format!( + "Error creating new config file!\n{}", + conf_open_create_res.err().unwrap() + ); + } + let mut conf_file = conf_open_create_res.unwrap(); + let mut config_string = format!( + "name|{}\nfiles|{}\nnotes|{}\nboxname|{}\nconfig|{}\n", + self.name, + self.files.display(), + self.notes.display(), + self.boxname, + self.config.display(), + ); + if self.current { + config_string.push_str("stage|current"); + } else { + config_string.push_str("stage|upcoming"); + } + write!(conf_file, "{}", config_string).unwrap(); + return String::from("Success!"); + } + + pub fn remove_project(&self) -> String { + let files_remove_res = remove_dir_all(&self.files); + if files_remove_res.is_err() { + return format!( + "Error removing files directory!\n{}", + files_remove_res.err().unwrap() + ); + } + let notes_remove_res = remove_dir_all(&self.notes); + if notes_remove_res.is_err() { + return format!( + "Error removing notes directory!\n{}", + notes_remove_res.err().unwrap() + ); + } + let config_remove_res = remove_file(&self.config); + if config_remove_res.is_err() { + return format!( + "Error removing config file!\n{}", + config_remove_res.err().unwrap() + ); + } + let db_remove_res = Command::new("distrobox") + .arg("rm") + .arg("--root") + .arg(&self.boxname) + .status(); + if db_remove_res.is_err() { + return format!( + "Error deleting distrobox!\n{}", + db_remove_res.err().unwrap() + ); + } + db_remove_res.unwrap(); + return String::from("Success!"); + } + + pub fn create_distrobox( + &self, + template: String, + tools: &PathBuf, + home: Option, + ) -> String { + println!("stopping project distrobox and template distrobox."); + println!("ignore any errors about the distrobox not existing."); + let pdb_stop_res = Command::new("distrobox") + .arg("stop") + .arg("--root") + .arg(&self.boxname) + .status(); + if pdb_stop_res.is_err() { + return format!( + "Error stopping project distrobox!\n{}", + pdb_stop_res.err().unwrap() + ); + } + let tdb_stop_res = Command::new("distrobox") + .arg("stop") + .arg("--root") + .arg(&template) + .status(); + if tdb_stop_res.is_err() { + return format!( + "Error stopping template box!\n{}", + tdb_stop_res.err().unwrap() + ); + } + pdb_stop_res.unwrap(); + tdb_stop_res.unwrap(); + let db_remove_res = Command::new("distrobox") + .arg("rm") + .arg("--root") + .arg(&self.boxname) + .status(); + if db_remove_res.is_err() { + return format!( + "Error removing distrobox!\n{}", + db_remove_res.err().unwrap() + ); + } + db_remove_res.unwrap(); + let db_create_res = Command::new("distrobox") + .arg("create") + .arg("--root") + .arg("--clone") + .arg(template) + .arg("--init") + .arg("--volume") + .arg(format!("{}:/pentest:rw", &self.files.display())) + .arg(format!("{}:/tools:rw", tools.display())) + .arg("--name") + .arg(&self.boxname) + .status(); + if db_create_res.is_err() { + return format!( + "Error creating distrobox!\n{}", + db_create_res.err().unwrap() + ); + } + db_create_res.unwrap(); + println!("{}", "distrobox created!".green()); + println!("starting it up to do some setup stuff."); + let start_res = Command::new("distrobox") + .arg("enter") + .arg("--root") + .arg(&self.boxname) + .arg("--") + .arg("exit") + .status(); + if start_res.is_err() { + return format!("Error starting distrobox!\n{}", start_res.err().unwrap()); + } + return String::from("Success!"); + } } diff --git a/src/main.rs b/src/main.rs index 50c9620..9a99cd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,6 +122,7 @@ pub fn load_projects(path: &PathBuf, display: bool) -> Vec { new_project.notes = PathBuf::from(line_vec[1]); } "boxname" => new_project.boxname = String::from(line_vec[1]), + "config" => new_project.config = PathBuf::from(line_vec[1]), _ => { print_error( "unknown setting discoverd in project config file!", @@ -152,33 +153,6 @@ pub fn load_projects(path: &PathBuf, display: bool) -> Vec { return projects; } -pub fn save_project(project: &lib::Project, config_path: &PathBuf) { - let mut conf_open_options = OpenOptions::new(); - if config_path.exists() { - conf_open_options.append(true); - } else { - conf_open_options.create(true); - } - let conf_open_create_res = conf_open_options.open(config_path); - if conf_open_create_res.is_err() { - print_error( - "error opening project config path!", - Some(format!("{}", conf_open_create_res.err().unwrap())), - ); - return; - } - let mut conf_file = conf_open_create_res.unwrap(); - let config_string = format!( - "name|{}\nstage|upcoming\nfiles|{}\nnotes|{}\nboxname|{}", - project.name, - project.files.display(), - project.notes.display(), - project.boxname - ); - write!(conf_file, "{}", config_string).unwrap(); - print_success("project saved!"); -} - pub fn load_settings(config_path: &PathBuf, display: bool) -> HashMap { let mut settings = HashMap::new(); let conf_read_res = read_to_string(&config_path);