use std::fs;
use crate::{
config::Config,
error::{Error, Result},
store, ui,
};
pub fn run_encrypt(paths: &[String]) -> Result<()> {
let repo_root = store::require_repo_root()?;
let config_path = store::config_path(&repo_root);
let mut config = Config::load(&config_path)?;
let password = ui::password("Vault password");
let master_key = crate::crypto::vault::unlock_vault(&password)?;
let mut encrypted_count = 0usize;
let mut errors = 0usize;
for query in paths {
let entry = config.find_entry_mut(query);
match entry {
Some(entry) if entry.encrypted => {
ui::warning(&format!("`{}` is already encrypted", entry.source));
}
Some(entry) => {
let source_path = repo_root.join(&entry.source);
if entry.directory {
ui::error(&format!(
"`{}` is a directory; cannot encrypt entire directories",
entry.source
));
errors += 1;
continue;
}
if !source_path.exists() {
ui::error(&format!(
"source `{}` not found in repo",
source_path.display()
));
errors += 1;
continue;
}
let content =
fs::read(&source_path).map_err(|e| Error::io(&source_path, "read", e))?;
let encrypted = crate::crypto::encrypt_with_key(&content, &master_key)?;
let enc_path = repo_root.join(format!("{}.enc", entry.source));
crate::fs::atomic_write(&enc_path, &encrypted)?;
fs::remove_file(&source_path)
.map_err(|e| Error::io(&source_path, "remove plaintext", e))?;
entry.encrypted = true;
ui::success(&format!("encrypted `{}`", entry.source));
encrypted_count += 1;
}
None => {
ui::error(&format!("`{query}` is not tracked"));
errors += 1;
}
}
}
config.save()?;
ui::summary(encrypted_count, 0, errors);
Ok(())
}
pub fn run_decrypt(paths: &[String]) -> Result<()> {
let repo_root = store::require_repo_root()?;
let config_path = store::config_path(&repo_root);
let mut config = Config::load(&config_path)?;
let password = ui::password("Vault password");
let master_key = crate::crypto::vault::unlock_vault(&password)?;
let mut decrypted_count = 0usize;
let mut errors = 0usize;
for query in paths {
let entry = config.find_entry_mut(query);
match entry {
Some(entry) if !entry.encrypted => {
ui::warning(&format!("`{}` is not encrypted", entry.source));
}
Some(entry) => {
if entry.directory {
ui::error(&format!(
"`{}` is a directory; cannot decrypt entire directories",
entry.source
));
errors += 1;
continue;
}
let enc_path = repo_root.join(format!("{}.enc", entry.source));
if !enc_path.exists() {
ui::error(&format!(
"encrypted source `{}` not found",
enc_path.display()
));
errors += 1;
continue;
}
let encrypted =
fs::read(&enc_path).map_err(|e| Error::io(&enc_path, "read encrypted", e))?;
let plaintext = crate::crypto::decrypt_with_key(&encrypted, &master_key)?;
let source_path = repo_root.join(&entry.source);
crate::fs::atomic_write(&source_path, &plaintext)?;
fs::remove_file(&enc_path)
.map_err(|e| Error::io(&enc_path, "remove encrypted", e))?;
entry.encrypted = false;
ui::success(&format!("decrypted `{}`", entry.source));
decrypted_count += 1;
}
None => {
ui::error(&format!("`{query}` is not tracked"));
errors += 1;
}
}
}
config.save()?;
ui::summary(decrypted_count, 0, errors);
Ok(())
}