envvault/cli/commands/
init.rs1use std::fs;
4use std::io::{BufRead, BufReader};
5use std::path::Path;
6
7use dialoguer::Confirm;
8
9use crate::cli::env_parser::parse_env_line;
10use crate::cli::output;
11use crate::cli::{load_keyfile, prompt_new_password, Cli};
12use crate::config::Settings;
13use crate::errors::{EnvVaultError, Result};
14use crate::vault::VaultStore;
15
16pub fn execute(cli: &Cli) -> Result<()> {
18 let cwd = std::env::current_dir()?;
19 let vault_dir = cwd.join(&cli.vault_dir);
20 let env = &cli.env;
21 let vault_path = vault_dir.join(format!("{env}.vault"));
22
23 if !vault_dir.exists() {
25 fs::create_dir_all(&vault_dir)?;
26 let dir_display = vault_dir.display();
27 output::info(&format!("Created vault directory: {dir_display}"));
28 }
29
30 if vault_path.exists() {
32 output::tip("Use `envvault set` to add secrets to the existing vault.");
33 return Err(EnvVaultError::VaultAlreadyExists(vault_path));
34 }
35
36 let password = prompt_new_password()?;
38
39 let keyfile = load_keyfile(cli)?;
41 let settings = Settings::load(&cwd)?;
42 let mut store = VaultStore::create(
43 &vault_path,
44 password.as_bytes(),
45 &cli.env,
46 Some(&settings.argon2_params()),
47 keyfile.as_deref(),
48 )?;
49 if keyfile.is_some() {
50 output::info("Vault created with keyfile — you must pass --keyfile on every command.");
51 }
52 output::success(&format!(
53 "Vault created for '{}' environment at {}",
54 cli.env,
55 vault_path.display()
56 ));
57
58 let env_file = cwd.join(".env");
60 if env_file.exists() {
61 let should_import = Confirm::new()
62 .with_prompt("Found .env file. Import secrets from it?")
63 .default(true)
64 .interact()
65 .map_err(|e| {
66 EnvVaultError::CommandFailed(format!("failed to read confirmation: {e}"))
67 })?;
68
69 if should_import {
70 let count = import_env_file(&env_file, &mut store)?;
71 store.save()?;
72 output::success(&format!("Imported {count} secrets from .env"));
73 }
74 }
75
76 crate::cli::gitignore::patch_gitignore(&cwd, &format!("{}/", cli.vault_dir));
78
79 match crate::git::install_hook(&cwd) {
81 Ok(crate::git::InstallResult::Installed) => {
82 output::info("Installed pre-commit hook to detect secret leaks.");
83 }
84 Ok(crate::git::InstallResult::ExistingHookFound) => {
85 output::warning("A pre-commit hook already exists — EnvVault hook was not installed.");
86 }
87 Ok(
88 crate::git::InstallResult::AlreadyInstalled | crate::git::InstallResult::NotAGitRepo,
89 )
90 | Err(_) => {} }
92
93 crate::audit::log_audit(cli, "init", None, Some("vault created"));
95
96 output::tip("Run `envvault set <KEY>` to add a secret.");
98 output::tip("Run `envvault list` to see all secrets.");
99 output::tip("Run `envvault run -- <command>` to inject secrets into a command.");
100
101 Ok(())
102}
103
104fn import_env_file(path: &Path, store: &mut VaultStore) -> Result<usize> {
110 let file = fs::File::open(path)?;
111 let reader = BufReader::new(file);
112 let mut count = 0;
113
114 for line in reader.lines() {
115 let line = line?;
116
117 if let Some((key, value)) = parse_env_line(&line) {
118 store.set_secret(key, value)?;
119 count += 1;
120 }
121 }
122
123 Ok(count)
124}