added the logic to create a new distrobox for the project on project

promotion.

also added project promotion

also added project removal
This commit is contained in:
pyro57000
2025-11-15 03:10:36 -06:00
parent 8ff38acbf2
commit 5053113dc0
5 changed files with 403 additions and 35 deletions

View File

@@ -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,

View File

@@ -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<String>,
pub user_args: Vec<String>,
pub optional_args: Vec<String>,
pub args: Option<Vec<ToolArgument>>,
pub func: fn(Option<Vec<ToolArgument>>) -> 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<usize>,
pub path: Option<PathBuf>,
pub string: Option<String>,
@@ -86,6 +93,31 @@ pub fn build_tools() -> Vec<ToolCommand> {
];
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<Vec<ToolArgument>>) -> String {
}
pub fn new_project(args: Option<Vec<ToolArgument>>) -> 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<Vec<ToolArgument>>) -> 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<Vec<ToolArgument>>) -> 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,
&notes,
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<Vec<ToolArgument>>) -> 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();
}

View File

@@ -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() {

View File

@@ -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<PathBuf>,
) -> 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<PathBuf>,
) -> 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!");
}
}

View File

@@ -122,6 +122,7 @@ pub fn load_projects(path: &PathBuf, display: bool) -> Vec<lib::Project> {
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<lib::Project> {
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<String, String> {
let mut settings = HashMap::new();
let conf_read_res = read_to_string(&config_path);