rusmes-cli 0.1.2

Administrative CLI tool for RusMES — user/mailbox/queue management, backup, restore, migration, metrics inspection, and configuration validation
Documentation
//! Initialize a new RusMES installation

use anyhow::{Context, Result};
use std::fs;
use std::path::Path;

/// Initialize RusMES with default configuration
pub fn run(domain: &str) -> Result<()> {
    println!("Initializing RusMES for domain: {}", domain);

    // Create default configuration
    let config = generate_default_config(domain);

    // Write rusmes.toml
    fs::write("rusmes.toml", config).context("Failed to write rusmes.toml")?;
    println!("Created rusmes.toml");

    // Create data directories
    create_directories()?;

    println!("Initialization complete!");
    println!("\nNext steps:");
    println!("  1. Review rusmes.toml and adjust settings");
    println!("  2. Create a user: rusmes user add <email> --password <password>");
    println!("  3. Start the server: rusmes start");

    Ok(())
}

/// Generate default configuration file content
fn generate_default_config(domain: &str) -> String {
    format!(
        r#"# RusMES Configuration
# Generated by: rusmes init

domain = "{domain}"
postmaster = "postmaster@{domain}"

[smtp]
host = "0.0.0.0"
port = 25
tls_port = 587
max_message_size = "50MB"
require_auth = false
enable_starttls = true

[imap]
host = "0.0.0.0"
port = 143
tls_port = 993

[jmap]
host = "0.0.0.0"
port = 8080
base_url = "http://localhost:8080"

[storage]
backend = "filesystem"
path = "./data/mailboxes"

[auth]
backend = "file"
path = "./data/users.db"

[logging]
level = "info"
format = "text"
output = "stdout"

[queue]
initial_delay = "60s"
max_delay = "3600s"
backoff_multiplier = 2.0
max_attempts = 5
worker_threads = 4
batch_size = 100

[security]
relay_networks = ["127.0.0.0/8"]
blocked_ips = []
check_recipient_exists = true
reject_unknown_recipients = true

[domains]
local_domains = ["{domain}"]

[domains.aliases]
"abuse@{domain}" = "postmaster@{domain}"
"webmaster@{domain}" = "postmaster@{domain}"

[metrics]
enabled = true
bind_address = "0.0.0.0:9090"
path = "/metrics"

# Processing chain
[[processors]]
name = "root"
state = "root"

[[processors.mailets]]
matcher = "All"
mailet = "AddHeader"

[processors.mailets.params]
name = "X-RusMES"
value = "1.0"

[[processors]]
name = "transport"
state = "transport"

[[processors.mailets]]
matcher = "RecipientIsLocal"
mailet = "LocalDelivery"

[[processors.mailets]]
matcher = "All"
mailet = "RemoteDelivery"
"#,
        domain = domain
    )
}

/// Create necessary data directories
fn create_directories() -> Result<()> {
    let dirs = ["./data", "./data/mailboxes", "./data/queue", "./data/logs"];

    for dir in &dirs {
        let path = Path::new(dir);
        if !path.exists() {
            fs::create_dir_all(path)
                .with_context(|| format!("Failed to create directory: {}", dir))?;
            println!("Created directory: {}", dir);
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_default_config() {
        let config = generate_default_config("example.com");
        assert!(config.contains("example.com"));
        assert!(config.contains("postmaster@example.com"));
        assert!(config.contains("[smtp]"));
        assert!(config.contains("[imap]"));
        assert!(config.contains("[jmap]"));
    }

    #[test]
    fn test_config_contains_all_sections() {
        let config = generate_default_config("test.org");
        assert!(config.contains("[storage]"));
        assert!(config.contains("[auth]"));
        assert!(config.contains("[logging]"));
        assert!(config.contains("[queue]"));
        assert!(config.contains("[security]"));
        assert!(config.contains("[domains]"));
        assert!(config.contains("[metrics]"));
        assert!(config.contains("[[processors]]"));
    }

    #[test]
    fn test_config_smtp_defaults() {
        let config = generate_default_config("mail.example.com");
        assert!(config.contains("port = 25"));
        assert!(config.contains("tls_port = 587"));
        assert!(config.contains("max_message_size = \"50MB\""));
    }

    #[test]
    fn test_config_imap_defaults() {
        let config = generate_default_config("mail.example.com");
        assert!(config.contains("port = 143"));
        assert!(config.contains("tls_port = 993"));
    }

    #[test]
    fn test_config_aliases() {
        let config = generate_default_config("example.org");
        assert!(config.contains("abuse@example.org"));
        assert!(config.contains("webmaster@example.org"));
    }

    #[test]
    fn test_config_processors() {
        let config = generate_default_config("test.com");
        assert!(config.contains("name = \"root\""));
        assert!(config.contains("state = \"root\""));
        assert!(config.contains("LocalDelivery"));
        assert!(config.contains("RemoteDelivery"));
    }
}