Enhance CLI output: UTF-8 console codepage, VT colors, clean status cards

This commit is contained in:
2026-09-07 16:01:50 +02:00
parent a50dc04bb3
commit 681fb0c032
4 changed files with 194 additions and 64 deletions
+53 -33
View File
@@ -2,12 +2,12 @@ use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use tracing::info;
use tracing_subscriber::EnvFilter;
use sanctum::crypto::{derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams};
use sanctum::mount::{format_drive, mount_container, unmount_drive};
use sanctum::storage::Database;
use sanctum::ui;
#[derive(Parser)]
#[command(name = "sanctum")]
@@ -73,10 +73,11 @@ fn handle_init(container_path: &Path) -> Result<()> {
);
}
println!("============================================================");
println!(" Sanctum Container Initialisierung");
println!(" Zieldatei: {}", container_path.display());
println!("============================================================");
println!("┌─────────────────────────────────────────────────────────────┐");
println!(" Sanctum — Neuen verschlüsselten Container erstellen │");
println!("└─────────────────────────────────────────────────────────────┘");
println!(" Zieldatei: {}", container_path.display());
println!();
let password = rpassword::prompt_password("Master-Passwort eingeben: ")
.context("Fehler beim Einlesen des Passworts")?;
@@ -92,20 +93,21 @@ fn handle_init(container_path: &Path) -> Result<()> {
bail!("Die eingegebenen Passwörter stimmen nicht überein!");
}
info!("Generiere KDF-Salt und leite KEK via Argon2id ab ...");
println!();
ui::step(1, 4, "🔑", "Leite KEK via Argon2id ab (M=64MB, T=3, P=4)...");
let salt = generate_salt();
let kdf_params = KdfParams::default();
let kek = derive_kek(&password, &salt, &kdf_params)
.context("KDF-Schlüsselableitung fehlgeschlagen")?;
info!("Erzeuge kryptografisch sicheren DEK via CSPRNG (OsRng) ...");
ui::step(2, 4, "🎲", "Erzeuge kryptografisch sicheren DEK via CSPRNG...");
let dek = generate_dek();
info!("Verschlüssele DEK mit KEK via AES-256-GCM ...");
ui::step(3, 4, "🔒", "Verschlüssele DEK via AES-256-GCM...");
let (wrapped_dek, header_nonce, header_tag) =
wrap_dek(&kek, &dek).context("DEK-Wrapping fehlgeschlagen")?;
info!("Initialisiere SQLite-Containerstruktur mit WAL-Modus ...");
ui::step(4, 4, "📦", "Initialisiere SQLite-Containerstruktur & WAL-Modus...");
let db = Database::open(container_path)
.context("Konnte SQLite-Containerdatei nicht anlegen")?;
@@ -116,31 +118,23 @@ fn handle_init(container_path: &Path) -> Result<()> {
.context("Fehler beim finalen WAL-Checkpoint")?;
println!();
println!("✔ Sanctum-Container erfolgreich initialisiert!");
println!(" Container: {}", container_path.display());
println!(" Format: Version 1 (Magic: SANCTUM\\0)");
println!(" KDF: Argon2id (M=64MB, T=3, P=4)");
println!(" Cipher: AES-256-GCM mit AEAD-Swap-Protection (1-MB Chunks)");
println!("┌─────────────────────────────────────────────────────────────┐");
println!(" ✔ Sanctum-Container erfolgreich initialisiert! │");
println!("└─────────────────────────────────────────────────────────────┘");
println!();
println!(" • Container: {}", container_path.display());
println!(" • Format: Version 1 (Magic: SANCTUM\\0)");
println!(" • KDF: Argon2id (M=64MB, T=3, P=4)");
println!(" • Cipher: AES-256-GCM (1-MB Chunks, AEAD Swap-Schutz)");
println!();
println!(" Befehl zum Einbinden als Netzlaufwerk:");
println!(" {}", ui::cyan(&format!("sanctum mount --path \"{}\" --drive S", container_path.display())));
println!();
println!("Zum Einbinden ausführen:");
println!(
" sanctum mount --path \"{}\" --drive S",
container_path.display()
);
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
// Tracing / Logging initialisieren (Standard-Filter: info)
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("sanctum=info,dav_server=warn,hyper=warn")),
)
.init();
async fn run() -> Result<()> {
let cli = Cli::parse();
match cli.command {
@@ -149,20 +143,46 @@ async fn main() -> Result<()> {
}
Commands::Mount { path, drive, port } => {
let drive_char = parse_drive_letter(&drive)?;
let password = rpassword::prompt_password(format!(
let prompt_text = format!(
"Master-Passwort für Container '{}' eingeben: ",
path.display()
))
.context("Fehler beim Einlesen des Passworts")?;
);
let password = rpassword::prompt_password(prompt_text)
.context("Fehler beim Einlesen des Passworts")?;
mount_container(&path, drive_char, port, &password).await?;
}
Commands::Unmount { drive } => {
let drive_char = parse_drive_letter(&drive)?;
let drive_str = format_drive(drive_char);
print!(" {} Trenne Windows-Netzlaufwerk {} ... ", ui::dim("[-]"), drive_str);
let _ = std::io::Write::flush(&mut std::io::stdout());
unmount_drive(drive_char)?;
println!("✔ Laufwerk {} erfolgreich getrennt.", format_drive(drive_char));
println!("{}", ui::green("OK"));
println!("{} Laufwerk {} erfolgreich getrennt.", ui::green(""), drive_str);
}
}
Ok(())
}
#[tokio::main]
async fn main() {
// Windows Konsole für UTF-8 (Code Page 65001) und VT ANSI Processing konfigurieren
ui::init_console();
// Tracing / Logging initialisieren (Standard-Filter: warn)
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("sanctum=warn,dav_server=warn,hyper=warn")),
)
.init();
if let Err(err) = run().await {
eprintln!();
eprintln!("{} {:#}", ui::red("✖ Fehler:"), err);
std::process::exit(1);
}
}