mntn 3.2.2

A Rust-based command-line tool for dotfiles management with profiles.
Documentation
use crate::encryption::{
    create_temp_path, encrypt_file, resolve_encryption_password, write_entries_tar,
};
use crate::registry::encrypted::EncryptedRegistry;
use crate::utils::{
    display::{green, yellow},
    paths::{ENCRYPTED_BUNDLE_FILE, get_encrypted_registry_path},
};
use age::secrecy::SecretString;
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;

pub fn backup_encrypted_configs(
    encrypted_backup_path: &Path,
    ask_password: bool,
) -> Result<(u32, u32)> {
    let password = resolve_encryption_password(ask_password, true)
        .context("Prompt for encryption password before encrypted backup")?;

    backup_encrypted_configs_with_password(encrypted_backup_path, &password)
}

fn backup_encrypted_configs_with_password(
    encrypted_backup_path: &Path,
    password: &SecretString,
) -> Result<(u32, u32)> {
    let registry_path = get_encrypted_registry_path();
    let encrypted_registry = EncryptedRegistry::load_or_create(&registry_path)
        .with_context(|| format!("Load encrypted registry: {}", registry_path.display()))?;

    let enabled_entries: Vec<_> = encrypted_registry.get_enabled_entries().collect();

    if enabled_entries.is_empty() {
        println!("No encrypted configuration files found to backup");
        return Ok((0, 0));
    }

    println!("   Encrypted configs: {} entries", enabled_entries.len());

    let mut skipped: u32 = 0;
    let mut to_archive: Vec<(String, std::path::PathBuf)> = Vec::new();

    for (id, entry) in enabled_entries {
        if !entry.target_path.exists() {
            skipped += 1;
            println!(
                "{}",
                yellow(&format!(
                    "     skipped missing target {} ({})",
                    entry.target_path.display(),
                    id
                ))
            );
            continue;
        }

        if entry.target_path.is_dir() {
            skipped += 1;
            println!(
                "{}",
                yellow(&format!(
                    "     skipped directory {} ({})",
                    entry.target_path.display(),
                    id
                ))
            );
            continue;
        }

        to_archive.push((entry.source_path.clone(), entry.target_path.clone()));
    }

    to_archive.sort_by(|a, b| a.0.cmp(&b.0));

    let bundle_destination = encrypted_backup_path.join(ENCRYPTED_BUNDLE_FILE);

    if to_archive.is_empty() {
        let _ = fs::remove_file(&bundle_destination);
        return Ok((0, skipped));
    }

    let tar_refs: Vec<(&str, &Path)> = to_archive
        .iter()
        .map(|(source, target)| (source.as_str(), target.as_path()))
        .collect();

    let tar_temp = create_temp_path("enc-bundle-tar").context("Create temporary tar path")?;

    let backup_result: Result<()> = (|| {
        write_entries_tar(&tar_temp, &tar_refs)?;
        encrypt_file(&tar_temp, &bundle_destination, password).context("Encrypt config bundle")?;
        Ok(())
    })();

    let _ = fs::remove_file(&tar_temp);

    backup_result?;

    let succeeded = to_archive.len();
    for (source_path, _) in &to_archive {
        println!("     {} {}", green(""), source_path);
    }

    Ok((succeeded as u32, skipped))
}