From ed188483a10631880a29fcf89832fb9a794f1a0a Mon Sep 17 00:00:00 2001 From: Humble Penguin Date: Tue, 4 Apr 2023 00:53:17 +0500 Subject: [PATCH] Fix security vulnerability, add --no-pretty-print argument to envio list command, and update update-checking process --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 15 +- completions/_envio | 1 - completions/envio.bash | 2 +- docs/envio-profile-loading-update.md | 27 +++ docs/usage.md | 22 +- src/bin/envio/cli.rs | 5 +- src/bin/envio/commands.rs | 73 ++++++- src/bin/envio/main.rs | 132 +++++++---- src/lib.rs | 315 ++++++++++++++++----------- 11 files changed, 399 insertions(+), 197 deletions(-) create mode 100644 docs/envio-profile-loading-update.md diff --git a/Cargo.lock b/Cargo.lock index ddf5c3a..2f4fa12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,7 +398,7 @@ dependencies = [ [[package]] name = "envio" -version = "0.3.0" +version = "0.4.0" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index f6047eb..9a76966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "envio" -version = "0.3.0" +version = "0.4.0" rust-version = "1.64.0" description = "Envio is a command-line tool that simplifies the management of environment variables across multiple profiles. It allows users to easily switch between different configurations and apply them to their current environment. Envio also encrypts sensitive environment variable values to ensure secure storage and transmission" edition = "2021" diff --git a/README.md b/README.md index 478f43e..6dad170 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![CICD](https://github.com/humblepenguinn/envio/actions/workflows/CICD.yml/badge.svg)](https://github.com/humblepenguinn/envio/workflows/CICD.yml) [![Version info](https://img.shields.io/crates/v/envio.svg)](https://crates.io/crates/envio) +

@@ -20,13 +21,14 @@ --- -Demo +Demo ## About `envio` is an open source CLI tool that helps make managing environment variables a breeze. With `envio`, users can create encrypted profiles that contain a collection of environment variables associated with a specific project or use case. `envio` ensures security and simplifies the development process by allowing users to easily switch between profiles as needed and load them in their current terminal session for immediate use. Some key features of `envio` include: + - `Encrypted` profiles which can only be decrypted using a `key` - Load profiles into your `terminal sessions` - `Persistent` environment variables that are available in `future sessions` @@ -36,6 +38,7 @@ Some key features of `envio` include: - `Exporting` profiles to a file ## Profiles + In `envio`, a profile is a collection of environment variables that are associated with a specific project, application, or use case. Users can create multiple profiles, each with their own set of environment variables, and easily switch between them as needed. For example, a developer might create a profile for a web development project that includes environment variables for the database connection, API keys, and other project-specific settings. They could then switch to a different profile for a mobile app project that requires a different set of environment variables. @@ -43,12 +46,15 @@ For example, a developer might create a profile for a web development project th The benefit of using profiles is that users can easily manage and switch between different sets of environment variables without having to manually set and unset them every time they switch tasks. Additionally, in `envio`, profiles are encrypted, so users can rest assured that their sensitive environment variables are secure and require a key to access them. ## Installation + You can install `envio` through a few methods ### Releases + You can head over to the [releases page](https://github.com/humblepenguinn/envio/releases/latest) and download the official `envio` binaries from there for your target operating system. `Windows MSI installers` are also available ### Cargo Repository + You can install `envio` through the Cargo repository using the following command: ```sh @@ -56,15 +62,17 @@ $ cargo install envio ``` ### Source -Go [here](./docs/build_from_source.md) to see how +Go [here](./docs/build_from_source.md) to see how More methods of installation will be added in the future! ## Usage + Go [here](./docs/usage.md) to see how to use the tool ## Development + In addition to the command-line tool, `envio` can also be used as a library in Rust programs to manage environment variables. To use `envio` in your program, add it as a dependency in your Cargo.toml file: Please note that the envio library is not stable right now and can be subjected to many changes! @@ -76,6 +84,7 @@ envio = "0.1.0" Then, in your Rust code, you can use the `envio` crate to read and write environment variables Here's a simple example: + ```rust // In this example we get the profile passed as an argument to the program // and then print the environment variables in that profile @@ -130,7 +139,9 @@ Currently, `envio` is only available as a Rust library ## Contributing + Contributions to `envio` are always welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md) for more information. ## License + This project is licensed under the [MIT](LICENSE-MIT) License and the [Apache](LICENSE-APACHE) License diff --git a/completions/_envio b/completions/_envio index a8d4e00..7704f31 100644 --- a/completions/_envio +++ b/completions/_envio @@ -51,7 +51,6 @@ _arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \ '-h[Print help]' \ '--help[Print help]' \ -'*::args:' \ && ret=0 ;; (launch) diff --git a/completions/envio.bash b/completions/envio.bash index 306b259..2543664 100644 --- a/completions/envio.bash +++ b/completions/envio.bash @@ -399,7 +399,7 @@ _envio() { return 0 ;; envio__unload) - opts="-h --help [ARGS]..." + opts="-h --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/docs/envio-profile-loading-update.md b/docs/envio-profile-loading-update.md new file mode 100644 index 0000000..58543ff --- /dev/null +++ b/docs/envio-profile-loading-update.md @@ -0,0 +1,27 @@ +# Important Security Notice for Envio Users + +### Security Vulnerability in Versions Before 0.4.0 + +A security vulnerability has recently been discovered in versions prior to `0.4.0`. Before version `0.4.0`, users could load their profile using the `envio load ` command and load the environment variables from their profile persistently. However, the way this was achieved was risky and insecure. + +Specifically, the `envio load` command would take in the user key, decrypt the contents in the profile, take the environment variables, and then write them to the `setenv.sh` file, which would be sourced from the user's shell. Since the environment variables were written in plain text in `setenv.sh`, it became vulnerable to potential security breaches. + +While it was not completely unsecure because users still had to pass in their key before loading in the environment variables from their profile and only then would the environment variables get written to and exported from the `setenv.sh` script, Nevertheless it was not a good approach and is not recommended. + +### Updated Approach in Versions After 0.4.0 + +Now, with the update in version `0.4.0`, `envio` has implemented an updated approach. Whenever users use the `envio load` command, it creates a `setenv.sh` script (as before) that, whenever the users load their shell, asks for the user's key. If the key is correct, it decrypts the envs in the profile, writes them to a temporary file, sources the temporary file, and then deletes it. This approach ensures that the user's environment variables remain secure and are not exposed in plain text. + +With this new approach, users can still load their profiles as before using the `envio load` command, but now whenever they open their shell, they need to enter their key to access their environment variables. + +Users can also still use the `envio unload` command to unload the profile from their terminal sessions, but they do not need to pass the `profile name` as a argument anymore + +### Future Improvements in Envio + +`envio` is committed to continuously improving the way it loads environment variables until it reaches a certain level of satisfaction. Until version 1.0.0, `envio` will keep working on improving the way it loads environment variables, and users can expect changes in the approach. However, after version 1.0.0, `envio` will stabilize the approach, and users can expect fewer changes that won't be breaking. + +We strongly encourage all users to upgrade to version 0.4.0 and take necessary measures to ensure their environment variables are secure. + +### User Feedback + +We would love to hear your thoughts and feedback on the new approach we use to load environment variables. If you have any suggestions or improvements, please don't hesitate to reach out to us at `humblepenguinofficial@gmail.com` diff --git a/docs/usage.md b/docs/usage.md index 9e139f2..389ea3a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -79,6 +79,8 @@ You can use the `envio load ` command to load the profile and make $ envio load myprofile ``` +On `Windows`, users just need to reload their shell and they can start using their environment variables as before. However, on `Unix-based` operating systems, a new approach has been implemented to load the environment variables securely. Whenever users open their shell, envio now asks users for the key used for the profile that was loaded. They have to type in the key to access their environment variables, which are then stored in a temporary file and sourced in the current session. This ensures that the environment variables are loaded securely and are not accessible to anyone without the correct key. + Now, ```sh $ echo $DATABASE_URL @@ -86,10 +88,16 @@ $ echo $DATABASE_URL ## Unloading a Profile -To unload a profile from the current session, run the command: +On `Windows`, to unload a profile from the current session, run the command: ```sh $ envio unload myprofile ``` + +On `Unix-based` operating systems, to unload a profile from the current session, run the `envio unload` command without any arguments: +```sh +$ envio unload +``` + ## Launching a Program with a Profile The `envio launch` command allows you to run a program using a specific profile. This is useful when you need to switch between different sets of environment variables for different projects or environments. @@ -152,3 +160,15 @@ To list all the environment variables in a profile, users can run the following ```sh $ envio list ``` + +Users can also view their profiles and environment variables in a profile without any visual formatting using the `-no-pretty-print` argument + +To list all existing profiles: +```sh +$ envio list profiles -- --no-pretty-print +``` + +To list all the environment variables in a profile: +```sh +$ envio list -- --no-pretty-print +``` diff --git a/src/bin/envio/cli.rs b/src/bin/envio/cli.rs index 3e14ddf..3050af0 100644 --- a/src/bin/envio/cli.rs +++ b/src/bin/envio/cli.rs @@ -1,6 +1,5 @@ use clap::Args; use clap::Parser; -//use crate::commands::Command; #[derive(Parser)] /* @@ -35,6 +34,10 @@ pub enum Command { Add(CommandArgs), #[clap(name = "load", about = "Load a profile in the current session")] Load(CommandArgs), + #[cfg(target_family = "unix")] + #[clap(name = "unload", about = "Unload a profile from the current session")] + Unload, + #[cfg(target_family = "windows")] #[clap(name = "unload", about = "Unload a profile from the current session")] Unload(CommandArgs), #[clap(name = "launch", about = "Launch a program with a profile")] diff --git a/src/bin/envio/commands.rs b/src/bin/envio/commands.rs index d093d2d..4cbabdf 100644 --- a/src/bin/envio/commands.rs +++ b/src/bin/envio/commands.rs @@ -9,7 +9,7 @@ use envio::crypto::encrypt; use envio::utils::get_configdir; use envio::{ self, check_profile, create_profile, delete_profile, download_profile, get_profile, - import_profile, list_profiles, + import_profile, list_profiles, load_profile, unload_profile, }; use crate::cli::Command; @@ -49,6 +49,22 @@ impl Command { return; } + let profile_name; + + if command_args.args.len() == 1 { + profile_name = command_args.args[0].clone(); + } else if command_args.args.len() == 2 { + profile_name = command_args.args[1].clone(); + } else { + println!("{}: Invalid number of arguments", "Error".red()); + return; + } + + if check_profile(profile_name.to_string()) { + println!("{}: Profile already exists", "Error".red()); + return; + } + let prompt = Password::new("Enter your encryption key:") .with_display_toggle_enabled() .with_display_mode(PasswordDisplayMode::Masked) @@ -68,13 +84,12 @@ impl Command { }; if command_args.args.len() == 1 { - create_profile(command_args.args[0].clone(), None, &user_key); + create_profile(profile_name, None, &user_key); } else if command_args.args.len() == 2 { if !Path::new(&command_args.args[0]).exists() { println!("{}: File does not exist", "Error".red()); return; } - let profile_name = command_args.args[1].clone(); let mut file = std::fs::OpenOptions::new() .read(true) @@ -191,15 +206,30 @@ impl Command { } let profile_name = command_args.args[0].clone(); - let profile = if let Some(p) = get_profile(profile_name, &get_userkey()) { - p - } else { - return; - }; - profile.load_profile(); + #[cfg(target_family = "unix")] + { + load_profile(&profile_name); + } + + #[cfg(target_family = "windows")] + { + let profile = if let Some(p) = get_profile(profile_name, &get_userkey()) { + p + } else { + return; + }; + + load_profile(profile); + } } + #[cfg(target_family = "unix")] + Command::Unload => { + unload_profile(); + } + + #[cfg(target_family = "windows")] Command::Unload(command_args) => { if command_args.args.is_empty() { println!("{}: Invalid number of arguments", "Error".red()); @@ -207,13 +237,14 @@ impl Command { } let profile_name = command_args.args[0].clone(); + let profile = if let Some(p) = get_profile(profile_name, &get_userkey()) { p } else { return; }; - profile.unload_profile(); + unload_profile(profile); } Command::Launch(command_args) => { @@ -275,9 +306,27 @@ impl Command { } let profile_name = command_args.args[0].clone(); - + if command_args.args.len() == 2 { + if command_args.args[1] == "--no-pretty-print" { + if profile_name == "profiles" { + list_profiles(true) + } else { + let profile = if let Some(p) = get_profile(profile_name, &get_userkey()) + { + p + } else { + return; + }; + + for (key, value) in profile.envs.iter() { + println!("{}={}", key, value); + } + } + } + return; + } if profile_name == "profiles" { - list_profiles() + list_profiles(false) } else { let profile = if let Some(p) = get_profile(profile_name, &get_userkey()) { p diff --git a/src/bin/envio/main.rs b/src/bin/envio/main.rs index ca1b167..e4f19d6 100644 --- a/src/bin/envio/main.rs +++ b/src/bin/envio/main.rs @@ -1,12 +1,16 @@ mod cli; mod commands; +#[cfg(target_family = "unix")] +use std::io::Write; + +use std::io::{BufRead, BufReader}; use std::path::Path; +use std::process::Command; use clap::Parser; use colored::Colorize; use semver::Version; -use tokio::runtime::Builder; use envio::utils; @@ -17,64 +21,61 @@ use cli::Cli; @return Option */ -async fn get_latest_version() -> Option { - let url = "https://api.github.com/repos/humblepenguinn/envio/releases/latest"; - let client = reqwest::Client::new(); - let res = if let Ok(val) = client.get(url).header("User-Agent", "envio").send().await { - val - } else { +fn get_latest_version() -> Option { + if Command::new("git").arg("--version").output().is_err() { + println!("{}: Git is not installed", "Error".red()); return None; - }; - - match res.status() { - reqwest::StatusCode::OK => { - let body = if let Ok(val) = res.text().await { - val - } else { - return None; - }; - - if body.contains("tag_name") { - let mut tag_name = body.split("tag_name").collect::>()[1] - .split('\"') - .collect::>()[2]; - - tag_name = tag_name.trim_start_matches('v'); - let latest_version = if let Ok(val) = Version::parse(tag_name) { - val - } else { - return None; - }; - - return Some(latest_version); - } + } - None + let owner = "humblepenguinn"; + let repo = "envio"; + let output = Command::new("git") + .arg("ls-remote") + .arg(format!("https://github.com/{}/{}.git", owner, repo)) + .output() + .unwrap(); + let reader = BufReader::new(output.stdout.as_slice()); + let mut latest_tag = None; + + for line in reader.lines().filter_map(|x| x.ok()) { + let parts: Vec<_> = line.split('\t').collect(); + if parts.len() != 2 { + continue; } + let (ref_name, _) = (parts[1], parts[0]); + if ref_name.starts_with("refs/tags/") { + let tag = ref_name.trim_start_matches("refs/tags/").to_owned(); + latest_tag = + latest_tag.map_or(Some(tag.clone()), |latest| Some(std::cmp::max(latest, tag))); + } + } - _ => None, + if let Some(mut tag) = latest_tag { + tag = tag.trim_start_matches('v').to_string(); + if let Ok(version) = Version::parse(&tag) { + return Some(version); + } } + + None } fn main() { - let rt = if let Ok(val) = Builder::new_current_thread().enable_all().build() { - val - } else { - println!("{}: Failed to create runtime", "Error".red()); - return; - }; - - let latest_version = if let Some(val) = rt.block_on(get_latest_version()) { + let latest_version = if let Some(val) = get_latest_version() { val } else { println!("{}: Failed to get latest version", "Error".red()); - return; + println!( + "{}: You can still use envio but won't be notified about new versions!", + "Warning".yellow() + ); + Version::parse("0.0.0").unwrap() }; let current_version = if let Ok(val) = Version::parse(env!("BUILD_VERSION")) { val } else { - println!("{}: Failed to parse version", "Error".red()); + println!("{}: Failed to parse current version", "Error".red()); return; }; @@ -88,10 +89,7 @@ fn main() { } if !Path::new(&utils::get_configdir()).exists() { - println!( - "{}", - "Config directory does not exist\nCreating config directory".bold() - ); + println!("{}", "Creating config directory".bold()); if let Err(e) = std::fs::create_dir(utils::get_configdir()) { println!("{}: {}", "Error".red(), e); std::process::exit(1); @@ -103,6 +101,46 @@ fn main() { } } + #[cfg(target_family = "unix")] + { + if !Path::new(&utils::get_configdir().join("setenv.sh")).exists() { + println!("{}", "Creating shellscript".bold()); + if let Err(e) = std::fs::write(utils::get_configdir().join("setenv.sh"), "") { + println!("{}: {}", "Error".red(), e); + std::process::exit(1); + } + + let mut file = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open( + utils::get_homedir().to_str().unwrap().to_owned() + + &format!("/{}", envio::get_shell_config()), + ) + .unwrap(); + + let buffer = if envio::get_shell_config().contains("fish") { + println!( + "To use the shellscript properly you need to install the {}(https://github.com/edc/bass) plugin for fish", + "bass".bold() + ); + format!( + "# envio DO NOT MODIFY\n bass source {}", + &utils::get_configdir().join("setenv.sh").to_str().unwrap() + ) + } else { + format!( + "#envio DO NOT MODIFY\n source {}", + &utils::get_configdir().join("setenv.sh").to_str().unwrap() + ) + }; + + if let Err(e) = writeln!(file, "{}", buffer) { + println!("{}: {}", "Error".red(), e); + } + } + } + let args = Cli::parse(); args.command.run(); } diff --git a/src/lib.rs b/src/lib.rs index 1a71fe7..742ada1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,15 +34,17 @@ use std::{ collections::HashMap, io::{Read, Write}, path::{Path, PathBuf}, - process::Command, }; +#[cfg(target_family = "windows")] +use std::process::Command; + use colored::Colorize; use comfy_table::{Attribute, Cell, Table}; use crate::{ crypto::{decrypt, encrypt}, - utils::{download_file, get_configdir, get_cwd, get_homedir}, + utils::{download_file, get_configdir, get_cwd}, }; /* @@ -80,122 +82,6 @@ impl Profile { } } - /* - * Load the environment variables of the profile into the current session - */ - pub fn load_profile(&self) { - if cfg!(windows) { - for (env, value) in &self.envs { - Command::new("setx") - .arg(env) - .arg(value) - .spawn() - .expect("setx command failed"); - } - - println!("Reload your shell to apply changes"); - return; - } - - // Create a shell script that will be used to load the environment variables - // Unix specific code - #[cfg(any(target_family = "unix"))] - { - let configdir = get_configdir(); - let shellscript_path = configdir.join("setenv.sh"); - let mut shell_config = ""; - - let current_shell = get_shell(); - - if current_shell.contains("bash") { - shell_config = ".bashrc"; - } else if current_shell.contains("zsh") { - shell_config = ".zshrc"; - } else if current_shell.contains("fish") { - shell_config = ".config/fish/config.fish" - } - - if !shellscript_path.exists() { - println!("Creating shell script"); - create_shellscript(); - - if !shell_config.is_empty() { - let mut file = std::fs::OpenOptions::new() - .write(true) - .append(true) - .open( - get_homedir().to_str().unwrap().to_owned() - + &format!("/{}", shell_config), - ) - .unwrap(); - - let buffer = format!("# envio \nsource {}", shellscript_path.to_str().unwrap()); - if let Err(e) = writeln!(file, "{}", buffer) { - println!("{}: {}", "Error".red(), e); - } - } else { - println!("The current shell is not supported. Please add the following line to your shell config file: source {}", shellscript_path.to_str().unwrap()); - } - } - - let mut file = std::fs::OpenOptions::new() - .write(true) - .append(false) - .open(&shellscript_path) - .unwrap(); - - let mut buffer = String::from(""); - - for (env, value) in &self.envs { - buffer = buffer + &format!("export {}={}\n", env, value); - } - - if let Err(e) = writeln!(file, "{}", buffer) { - println!("{}: {}", "Error".red(), e); - } - - println!( - "Reload your shell to apply changes or run `source {}`", - format_args!("~/{}", shell_config) - ); - } - } - - /* - * Unload the environment variables of the profile from the current session - */ - pub fn unload_profile(&self) { - if cfg!(windows) { - for env in self.envs.keys() { - Command::new("REG") - .arg("delete") - .arg("HKCU\\Environment") - .arg("/F") - .arg("/V") - .arg(format!("\"{}\"", env)) - .arg("") - .spawn() - .expect("setx command failed"); - } - } - - // Unix specific code - #[cfg(any(target_family = "unix"))] - { - let file = std::fs::OpenOptions::new() - .write(true) - .append(false) - .open(get_configdir().join("setenv.sh")) - .unwrap(); - - if let Err(e) = file.set_len(0) { - println!("{}: {}", "Error".red(), e); - } - } - - println!("Reload your shell to apply changes"); - } - /* * Add a new environment variable to the profile @@ -424,7 +310,7 @@ pub fn delete_profile(name: String) { /* * List all the profiles */ -pub fn list_profiles() { +pub fn list_profiles(raw: bool) { let configdir = get_configdir(); let profile_dir = configdir.join("profiles"); @@ -434,7 +320,6 @@ pub fn list_profiles() { } let mut profiles = Vec::new(); - for entry in std::fs::read_dir(profile_dir).unwrap() { let entry = entry.unwrap(); let path = entry.path(); @@ -443,6 +328,13 @@ pub fn list_profiles() { profiles.push(profile_name); } + if raw { + for profile in profiles { + println!("{}", profile); + } + return; + } + let mut table = Table::new(); table.set_header(vec![Cell::new("Profiles").add_attribute(Attribute::Bold)]); @@ -602,32 +494,195 @@ pub fn get_profile(profile_name: String, key: &str) -> Option { // Unix specific code // Creates a shell script that can be sourced to set the environment variables #[cfg(any(target_family = "unix"))] -pub fn create_shellscript() { +pub fn create_shellscript(profile: &str) { let configdir = get_configdir(); let shellscript_path = configdir.join("setenv.sh"); - let mut file = if let Err(e) = std::fs::File::create(&shellscript_path) { - println!("{}: {}", "Error".red(), e); - return; + let mut file = if let Ok(e) = std::fs::OpenOptions::new() + .write(true) + .append(false) + .open(shellscript_path) + { + e } else { - std::fs::File::create(&shellscript_path).unwrap() + println!("{}: Could not open file", "Error".red()); + return; }; - let shellscript = ""; + let shellscript = format!( + r#"#!/bin/bash +# This script was generated by envio and should not be modified! + + +TMP_FILE=$(mktemp) + +set +e +ENV_VARS=$(envio list {} -- --no-pretty-print) +EXIT_CODE=$? +SHELL_NAME=$(basename "$SHELL") + + +if [ ! -f "$TMP_FILE" ]; then + echo "Failed to create temp file" + exit 1 +fi + +if [ $EXIT_CODE -ne 0 ]; then + echo "Failed to load environment variables" + rm "$TMP_FILE" +else + case "$SHELL_NAME" in + bash | zsh) + echo "$ENV_VARS" | while IFS= read -r line; do + echo "export $line" >> "$TMP_FILE" + done + ;; + fish) + echo "$ENV_VARS" | while IFS= read -r line; do + echo "set -gX $line" >> "$TMP_FILE" + done + ;; + *) + echo "Unsupported shell: $SHELL_NAME" + exit 1 + ;; + esac + + if [ ! -s "$TMP_FILE" ]; then + echo "Temp file is empty" + exit 1 + fi + + source "$TMP_FILE" + source "$TMP_FILE" &> /dev/null + + if [ $? -ne 0 ]; then + echo "Failed to load environment variables from temp file" + exit 1 + fi + + rm "$TMP_FILE" +fi +"#, + profile + ); if let Err(e) = file.write_all(shellscript.as_bytes()) { println!("{}: {}", "Error".red(), e); } + + if let Err(e) = file.flush() { + println!("{}: {}", "Error".red(), e); + } + + if let Err(e) = file.sync_all() { + println!("{}: {}", "Error".red(), e); + } } // Unix specific code // Returns the shell that is being used // @return String #[cfg(any(target_family = "unix"))] -pub fn get_shell() -> String { +pub fn get_shell_config() -> String { // Gets your default shell - let shell = std::env::var("SHELL").unwrap(); - let shell = shell.split('/').collect::>(); + // This is used to determine which shell config file to edit + let shell_env_value = if let Ok(e) = std::env::var("SHELL") { + e + } else { + println!("{}: Could not get shell", "Error".red()); + std::process::exit(1); + }; + + let shell_as_vec = shell_env_value.split('/').collect::>(); + let shell = shell_as_vec[shell_as_vec.len() - 1]; + + let mut shell_config = ""; + if shell.contains("bash") { + shell_config = ".bashrc"; + } else if shell.contains("zsh") { + shell_config = ".zshrc"; + } else if shell.contains("fish") { + shell_config = ".config/fish/config.fish" + } - shell[shell.len() - 1].to_string() + shell_config.to_string() +} + +/* + * Load the environment variables of the profile into the current session + */ +#[cfg(any(target_family = "unix"))] +pub fn load_profile(profile_name: &str) { + if !check_profile(profile_name.to_string()) { + println!("{}: Profile does not exist", "Error".red()); + return; + } + + let shell_config = get_shell_config(); + + create_shellscript(profile_name); + + if !shell_config.is_empty() { + println!( + "Reload your shell to apply changes or run `source {}`", + format_args!("~/{}", shell_config) + ); + } else { + println!("Reload your shell to apply changes"); + } +} + +/* + * Windows specific code +*/ + +#[cfg(target_family = "windows")] +pub fn load_profile(profile: Profile) { + for (env, value) in &profile.envs { + Command::new("setx") + .arg(env) + .arg(value) + .spawn() + .expect("setx command failed"); + } + + println!("Reload your shell to apply changes"); +} + +/* + * Unload the environment variables of the profile from the current session + */ +#[cfg(any(target_family = "unix"))] +pub fn unload_profile() { + let file = std::fs::OpenOptions::new() + .write(true) + .append(false) + .open(get_configdir().join("setenv.sh")) + .unwrap(); + + if let Err(e) = file.set_len(0) { + println!("{}: {}", "Error".red(), e); + } + + println!("Reload your shell to apply changes"); +} + +/* + * Windows specific code +*/ +#[cfg(target_family = "windows")] +pub fn unload_profile(profile: Profile) { + for env in profile.envs.keys() { + Command::new("REG") + .arg("delete") + .arg("HKCU\\Environment") + .arg("/F") + .arg("/V") + .arg(format!("\"{}\"", env)) + .arg("") + .spawn() + .expect("setx command failed"); + } + println!("Reload your shell to apply changes"); }