use crate::encryption::{
create_temp_path, decrypt_file, get_encrypted_path, load_tar_member_map,
set_private_file_permissions,
};
use crate::profiles::ActiveProfile;
use crate::registry::encrypted::{EncryptedRegistry, EncryptedRegistryEntry};
use crate::utils::{
display::{green, red, short_component, yellow},
paths::get_encrypted_registry_path,
};
use age::secrecy::SecretString;
use std::collections::HashMap;
use std::fs;
pub fn restore_encrypted_configs(profile: &ActiveProfile, password: &SecretString) -> (u32, u32) {
let encrypted_registry_path = get_encrypted_registry_path();
let encrypted_registry = match EncryptedRegistry::load_or_create(&encrypted_registry_path) {
Ok(registry) => registry,
Err(e) => {
eprintln!(
"Failed to load encrypted registry, skipping encrypted restore: {}",
e
);
return (0, 0);
}
};
let enabled_entries: Vec<_> = encrypted_registry
.get_enabled_entries()
.map(|(id, e)| (id.clone(), e.clone()))
.collect();
if enabled_entries.is_empty() {
println!("No encrypted configuration files found to restore");
return (0, 0);
}
println!(" Encrypted configs: {} entries", enabled_entries.len());
if let Some(bundle) = profile.resolve_encrypted_bundle()
&& bundle.path.is_file()
{
let tar_temp = match create_temp_path("enc-restore-tar") {
Ok(p) => p,
Err(e) => {
eprintln!(
"Could not create temp file for bundle restore: {}, using per-file backups",
e
);
return restore_encrypted_legacy(profile, password, enabled_entries);
}
};
match decrypt_file(&bundle.path, &tar_temp, password) {
Ok(()) => match load_tar_member_map(&tar_temp) {
Ok(members) => {
let _ = fs::remove_file(&tar_temp);
return restore_from_bundle_members(&enabled_entries, &members);
}
Err(e) => {
eprintln!(
"Could not read encrypted bundle archive: {}, trying per-file backups",
e
);
let _ = fs::remove_file(&tar_temp);
}
},
Err(e) => {
eprintln!(
"Could not decrypt {} ({}), trying per-file backups",
bundle.path.display(),
e
);
let _ = fs::remove_file(&tar_temp);
}
}
}
restore_encrypted_legacy(profile, password, enabled_entries)
}
fn restore_from_bundle_members(
enabled_entries: &[(String, EncryptedRegistryEntry)],
members: &HashMap<String, Vec<u8>>,
) -> (u32, u32) {
let mut restored_count = 0;
let mut skipped_count = 0;
for (id, entry) in enabled_entries {
let target_path = &entry.target_path;
let target_label = short_component(target_path);
match members.get(&entry.source_path) {
Some(contents) => {
if let Some(parent) = target_path.parent()
&& let Err(e) = fs::create_dir_all(parent)
{
eprintln!(
"{}",
red(&format!(
"Failed to create directory {}: {}",
target_label, e
))
);
skipped_count += 1;
continue;
}
match fs::write(target_path, contents) {
Ok(()) => {
if let Err(e) = set_private_file_permissions(target_path) {
eprintln!(
"{}",
red(&format!(
"Failed to set permissions on {}: {}",
target_label, e
))
);
}
restored_count += 1;
println!(" {} {}", green("✔"), entry.source_path);
}
Err(e) => {
eprintln!(
"{}",
red(&format!(
"Failed to write {} ({}): {}",
entry.source_path, target_label, e
))
);
skipped_count += 1;
}
}
}
None => {
println!(
"{}",
yellow(&format!(
" skipped {} ({}): not in encrypted bundle",
entry.source_path, id
))
);
skipped_count += 1;
}
}
}
(restored_count, skipped_count)
}
fn restore_encrypted_legacy(
profile: &ActiveProfile,
password: &SecretString,
enabled_entries: Vec<(String, EncryptedRegistryEntry)>,
) -> (u32, u32) {
let mut restored_count = 0;
let mut skipped_count = 0;
for (id, entry) in enabled_entries {
let target_path = &entry.target_path;
let target_label = short_component(target_path);
let encrypted_path = get_encrypted_path(&entry.source_path);
match profile.resolve_encrypted_source(&encrypted_path) {
Some(resolved) => {
if let Some(parent) = target_path.parent()
&& let Err(e) = fs::create_dir_all(parent)
{
eprintln!(
"{}",
red(&format!(
"Failed to create directory {}: {}",
target_label, e
))
);
skipped_count += 1;
continue;
}
match decrypt_file(&resolved.path, target_path, password) {
Ok(()) => {
restored_count += 1;
println!(" {} {}", green("✔"), entry.source_path);
}
Err(e) => {
eprintln!(
"{}",
red(&format!(
"Failed to decrypt {} ({}): {}",
entry.source_path, target_label, e
))
);
skipped_count += 1;
}
}
}
None => {
println!(
"{}",
yellow(&format!(
" skipped {} ({}): no encrypted backup in any layer",
entry.source_path, id
))
);
skipped_count += 1;
}
}
}
(restored_count, skipped_count)
}