diff --git a/README.md b/README.md index 092379c..8267464 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ Comprehensive documentation is available in the [`docs/`](docs/) directory: - **[docs/README.md](docs/README.md)** - Documentation overview and quick start - **[docs/ANALYSIS.md](docs/ANALYSIS.md)** - Technical analysis and current status - **[docs/IMPLEMENTATION_COMPARISON.md](docs/IMPLEMENTATION_COMPARISON.md)** - Go vs Rust comparison -- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Environment variable credential overrides - **[docs/FOLDER_PATTERNS.md](docs/FOLDER_PATTERNS.md)** - Folder filtering guide - **[docs/couchdb-schemas.md](docs/couchdb-schemas.md)** - Database schema documentation - **[docs/TODO.md](docs/TODO.md)** - Development roadmap and future plans @@ -140,50 +139,9 @@ mail2couch automatically searches for configuration files in this order: Options: -c, --config FILE Path to configuration file -m, --max-messages N Limit messages processed per mailbox per run (0 = unlimited) - -n, --dry-run Show what would be done without making changes -h, --help Show help message ``` -### Environment Variables - -mail2couch supports environment variable overrides for sensitive credentials, allowing you to keep passwords out of configuration files: - -| Environment Variable | Purpose | Example | -|---------------------|---------|---------| -| `MAIL2COUCH_COUCHDB_USER` | Override CouchDB username | `admin` | -| `MAIL2COUCH_COUCHDB_PASSWORD` | Override CouchDB password | `secure_password` | -| `MAIL2COUCH_IMAP__USER` | Override IMAP username for source `` | `user@gmail.com` | -| `MAIL2COUCH_IMAP__PASSWORD` | Override IMAP password for source `` | `app_password` | - -**Name Normalization**: The `` part is the mail source name from your configuration converted to uppercase with non-alphanumeric characters replaced by underscores. - -**Examples**: -- Source name `"Personal Gmail"` → `MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD` -- Source name `"Work Email"` → `MAIL2COUCH_IMAP_WORK_EMAIL_USER` -- Source name `"Self-Hosted Mail"` → `MAIL2COUCH_IMAP_SELF_HOSTED_MAIL_PASSWORD` - -**Usage Examples**: -```bash -# Override CouchDB credentials -MAIL2COUCH_COUCHDB_PASSWORD=secret ./mail2couch - -# Override IMAP password for a specific account -MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD=app-password ./mail2couch - -# Use with systemd service (in service file) -Environment="MAIL2COUCH_COUCHDB_PASSWORD=secret" -Environment="MAIL2COUCH_IMAP_WORK_EMAIL_PASSWORD=app-pass" - -# Combine with other options -MAIL2COUCH_COUCHDB_USER=backup_user ./mail2couch --dry-run --max-messages 100 -``` - -**Security Benefits**: -- ✅ Keep sensitive credentials out of configuration files -- ✅ Use different credentials per environment (dev/staging/prod) -- ✅ Integrate with CI/CD systems and secret management -- ✅ Maintain backward compatibility with existing configurations - ### Folder Pattern Examples | Pattern | Description | Matches | @@ -319,47 +277,6 @@ Basic setup for a single Gmail account: } ``` -### Secure Configuration with Environment Variables -Configuration using placeholder values with sensitive credentials provided via environment variables: - -```json -{ - "couchDb": { - "url": "http://localhost:5984", - "user": "placeholder", - "password": "placeholder" - }, - "mailSources": [ - { - "name": "Personal Gmail", - "enabled": true, - "protocol": "imap", - "host": "imap.gmail.com", - "port": 993, - "user": "placeholder", - "password": "placeholder", - "mode": "archive", - "folderFilter": { - "include": ["INBOX", "Sent"], - "exclude": [] - } - } - ] -} -``` - -Run with environment variables: -```bash -# Set credentials via environment variables -export MAIL2COUCH_COUCHDB_USER="admin" -export MAIL2COUCH_COUCHDB_PASSWORD="secure_couchdb_password" -export MAIL2COUCH_IMAP_PERSONAL_GMAIL_USER="your-email@gmail.com" -export MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD="your-app-password" - -# Run mail2couch (credentials will override config placeholders) -./mail2couch -``` - ### Advanced Multi-Account Configuration Complex setup with multiple accounts, filtering, and different sync modes: @@ -474,17 +391,10 @@ Complex setup with multiple accounts, filtering, and different sync modes: ## Production Deployment ### Security Considerations -- **Use Environment Variables**: Store sensitive credentials in environment variables instead of configuration files -- **Use App Passwords**: Use app passwords instead of account passwords for IMAP authentication -- **File Permissions**: Store configuration files with restricted permissions (600) -- **Secure Connections**: Use HTTPS for CouchDB connections in production -- **SystemD Integration**: Use environment variables in systemd service files: - ```ini - [Service] - Environment="MAIL2COUCH_COUCHDB_PASSWORD=secure_password" - Environment="MAIL2COUCH_IMAP_GMAIL_PASSWORD=app_password" - ExecStart=/usr/local/bin/mail2couch - ``` +- Use app passwords instead of account passwords +- Store configuration files with restricted permissions (600) +- Use HTTPS for CouchDB connections in production +- Consider encrypting sensitive configuration data ### Monitoring and Maintenance - Review sync metadata documents for sync health diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md deleted file mode 100644 index 256eb57..0000000 --- a/docs/ENVIRONMENT_VARIABLES.md +++ /dev/null @@ -1,176 +0,0 @@ -# Environment Variables Reference - -mail2couch supports environment variable overrides for sensitive credentials, allowing you to keep passwords and usernames out of configuration files while maintaining security and flexibility. - -## Overview - -Environment variables take precedence over values specified in configuration files, enabling: -- ✅ Secure credential management without plaintext passwords in config files -- ✅ Different credentials per environment (development, staging, production) -- ✅ Integration with CI/CD systems and secret management tools -- ✅ SystemD service configuration with secure credential injection - -## Supported Environment Variables - -| Variable | Purpose | Example Value | -|----------|---------|---------------| -| `MAIL2COUCH_COUCHDB_USER` | Override CouchDB username | `admin` | -| `MAIL2COUCH_COUCHDB_PASSWORD` | Override CouchDB password | `secure_password123` | -| `MAIL2COUCH_IMAP__USER` | Override IMAP username for source `` | `user@gmail.com` | -| `MAIL2COUCH_IMAP__PASSWORD` | Override IMAP password for source `` | `app_specific_password` | - -## Name Normalization - -The `` portion of IMAP environment variables corresponds to your mail source name from the configuration file, normalized according to these rules: - -1. **Convert to uppercase**: `Personal Gmail` → `PERSONAL GMAIL` -2. **Replace non-alphanumeric characters with underscores**: `PERSONAL GMAIL` → `PERSONAL_GMAIL` -3. **Clean up consecutive underscores**: `WORK__EMAIL` → `WORK_EMAIL` - -### Examples - -| Configuration Source Name | Environment Variable Prefix | -|---------------------------|------------------------------| -| `"Personal Gmail"` | `MAIL2COUCH_IMAP_PERSONAL_GMAIL_` | -| `"Work Email"` | `MAIL2COUCH_IMAP_WORK_EMAIL_` | -| `"Self-Hosted Mail"` | `MAIL2COUCH_IMAP_SELF_HOSTED_MAIL_` | -| `"Company.com Account"` | `MAIL2COUCH_IMAP_COMPANY_COM_ACCOUNT_` | - -## Usage Examples - -### Basic Override -```bash -# Override CouchDB password -export MAIL2COUCH_COUCHDB_PASSWORD="secure_password" -./mail2couch - -# Override specific IMAP account credentials -export MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD="app_password_123" -./mail2couch --config my-config.json -``` - -### Multiple Account Setup -```bash -# Set credentials for multiple accounts -export MAIL2COUCH_COUCHDB_USER="backup_user" -export MAIL2COUCH_COUCHDB_PASSWORD="backup_password" -export MAIL2COUCH_IMAP_WORK_EMAIL_USER="work@company.com" -export MAIL2COUCH_IMAP_WORK_EMAIL_PASSWORD="work_app_password" -export MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD="gmail_app_password" - -./mail2couch --max-messages 1000 -``` - -### SystemD Service Configuration -```ini -[Unit] -Description=Mail2Couch Email Backup -After=network.target - -[Service] -Type=oneshot -User=mail2couch -Environment="MAIL2COUCH_COUCHDB_PASSWORD=secure_password" -Environment="MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD=gmail_app_pass" -Environment="MAIL2COUCH_IMAP_WORK_EMAIL_PASSWORD=work_app_pass" -ExecStart=/usr/local/bin/mail2couch -WorkingDirectory=/home/mail2couch - -[Install] -WantedBy=multi-user.target -``` - -### Docker/Container Usage -```bash -# Docker run with environment variables -docker run -e MAIL2COUCH_COUCHDB_PASSWORD=secret \ - -e MAIL2COUCH_IMAP_GMAIL_PASSWORD=app_pass \ - -v /path/to/config.json:/config.json \ - mail2couch --config /config.json - -# Docker Compose -version: '3.8' -services: - mail2couch: - image: mail2couch:latest - environment: - - MAIL2COUCH_COUCHDB_PASSWORD=${COUCHDB_PASSWORD} - - MAIL2COUCH_IMAP_GMAIL_PASSWORD=${GMAIL_APP_PASSWORD} - volumes: - - ./config.json:/config.json -``` - -### CI/CD Integration -```yaml -# GitHub Actions example -- name: Run Mail2Couch Backup - run: ./mail2couch --dry-run - env: - MAIL2COUCH_COUCHDB_PASSWORD: ${{ secrets.COUCHDB_PASSWORD }} - MAIL2COUCH_IMAP_GMAIL_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }} -``` - -## Configuration File Template - -When using environment variables, you can use placeholder values in your configuration file: - -```json -{ - "couchDb": { - "url": "https://couchdb.example.com:5984", - "user": "placeholder_will_be_overridden", - "password": "placeholder_will_be_overridden" - }, - "mailSources": [ - { - "name": "Personal Gmail", - "enabled": true, - "protocol": "imap", - "host": "imap.gmail.com", - "port": 993, - "user": "placeholder_will_be_overridden", - "password": "placeholder_will_be_overridden", - "mode": "archive" - } - ] -} -``` - -## Verification - -mail2couch will show which environment variable overrides are active during startup: - -``` -Using configuration file: config.json -Active environment variable overrides: MAIL2COUCH_COUCHDB_PASSWORD, MAIL2COUCH_IMAP_PERSONAL_GMAIL_PASSWORD -``` - -This helps you verify that your environment variables are being detected and applied correctly. - -## Security Best Practices - -1. **Never commit real credentials to version control** - Use placeholder values in config files -2. **Use app-specific passwords** - For Gmail, Outlook, and other providers that support them -3. **Restrict environment variable access** - Ensure only authorized users/processes can read the environment variables -4. **Rotate credentials regularly** - Update both app passwords and environment variable values periodically -5. **Use secret management systems** - In production, integrate with tools like HashiCorp Vault, AWS Secrets Manager, etc. - -## Troubleshooting - -### Environment Variables Not Working -- Check variable names match exactly (case-sensitive) -- Verify the source name normalization (see examples above) -- Ensure variables are exported in your shell session -- Check for typos in variable names - -### SystemD Service Issues -- Verify Environment lines in service file are correct -- Check service status: `systemctl status mail2couch` -- View logs: `journalctl -u mail2couch -f` -- Test manually first: `sudo -u mail2couch mail2couch --dry-run` - -### Verification Steps -1. Run with `--dry-run` to test without making changes -2. Check startup output for "Active environment variable overrides" -3. Verify authentication errors are resolved -4. Test with minimal configuration first \ No newline at end of file diff --git a/docs/IMPLEMENTATION_COMPARISON.md b/docs/IMPLEMENTATION_COMPARISON.md index b416fa1..9dbc6f0 100644 --- a/docs/IMPLEMENTATION_COMPARISON.md +++ b/docs/IMPLEMENTATION_COMPARISON.md @@ -138,14 +138,11 @@ Both implementations have achieved **production readiness** with comprehensive t - ✅ **SystemD Integration**: Automated scheduling support - ✅ **Build System**: Unified justfile for both implementations -### **Recently Completed Enhancements** -1. **✅ Security**: Environment variable credential support - both implementations support full credential override via environment variables -2. **✅ Documentation**: Comprehensive help text and README documentation for all security features - ### **Future Enhancement Priorities** -1. **Go Concurrency**: Optional parallel processing -2. **Progress Indicators**: Real-time progress reporting -3. **Interactive Setup**: Guided configuration wizard +1. **Security**: Environment variable credential support +2. **Go Concurrency**: Optional parallel processing +3. **Progress Indicators**: Real-time progress reporting +4. **Interactive Setup**: Guided configuration wizard ## Conclusion diff --git a/docs/README.md b/docs/README.md index cb587b0..ba9647b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,6 @@ This directory contains comprehensive documentation for the mail2couch project, - **[TODO.md](TODO.md)** - Development roadmap and outstanding tasks ### Configuration & Setup -- **[ENVIRONMENT_VARIABLES.md](ENVIRONMENT_VARIABLES.md)** - Complete guide to environment variable credential overrides - **[FOLDER_PATTERNS.md](FOLDER_PATTERNS.md)** - Guide to folder filtering patterns and wildcards - **[test-config-comparison.md](test-config-comparison.md)** - Configuration examples and testing scenarios diff --git a/docs/TODO.md b/docs/TODO.md index a16b50a..1df605f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -34,14 +34,11 @@ This document outlines the development roadmap for mail2couch, with both Go and ## 🚧 Current Development Priorities -### Recently Completed ✅ -1. **🔐 Enhanced Security Model** - - ✅ Environment variable credential support (`MAIL2COUCH_IMAP_PASSWORD`, etc.) - - ✅ Eliminate plaintext passwords from configuration files - - ✅ Comprehensive documentation and help integration - - ❌ System keyring integration for credential storage (future enhancement) - ### High Priority +1. **🔐 Enhanced Security Model** + - Environment variable credential support (`MAIL2COUCH_IMAP_PASSWORD`, etc.) + - Eliminate plaintext passwords from configuration files + - System keyring integration for credential storage ### Medium Priority 2. **🚀 Go Implementation Concurrency** diff --git a/go/config/config.go b/go/config/config.go index c3649e9..b7bf6ca 100644 --- a/go/config/config.go +++ b/go/config/config.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/spf13/pflag" ) @@ -76,71 +75,9 @@ func LoadConfig(path string) (*Config, error) { } } - // Apply environment variable overrides for sensitive credentials - applyEnvironmentOverrides(&config) - return &config, nil } -// applyEnvironmentOverrides applies environment variable overrides for sensitive credentials -// This allows users to keep credentials out of config files while maintaining security -// -// Environment variable patterns: -// - MAIL2COUCH_COUCHDB_USER: Override CouchDB username -// - MAIL2COUCH_COUCHDB_PASSWORD: Override CouchDB password -// - MAIL2COUCH_IMAP__USER: Override IMAP username for source named -// - MAIL2COUCH_IMAP__PASSWORD: Override IMAP password for source named -// -// The part is the mail source name converted to uppercase with non-alphanumeric characters replaced with underscores -func applyEnvironmentOverrides(config *Config) { - // CouchDB credential overrides - if user := os.Getenv("MAIL2COUCH_COUCHDB_USER"); user != "" { - config.CouchDb.User = user - } - if password := os.Getenv("MAIL2COUCH_COUCHDB_PASSWORD"); password != "" { - config.CouchDb.Password = password - } - - // IMAP credential overrides for each mail source - for i := range config.MailSources { - source := &config.MailSources[i] - - // Convert source name to environment variable suffix - // Replace non-alphanumeric characters with underscores and uppercase - envSuffix := normalizeNameForEnvVar(source.Name) - - userEnvVar := fmt.Sprintf("MAIL2COUCH_IMAP_%s_USER", envSuffix) - if user := os.Getenv(userEnvVar); user != "" { - source.User = user - } - - passwordEnvVar := fmt.Sprintf("MAIL2COUCH_IMAP_%s_PASSWORD", envSuffix) - if password := os.Getenv(passwordEnvVar); password != "" { - source.Password = password - } - } -} - -// normalizeNameForEnvVar converts a source name to a valid environment variable suffix -// Example: "Personal Gmail" -> "PERSONAL_GMAIL" -func normalizeNameForEnvVar(name string) string { - result := strings.ToUpper(name) - - // Replace non-alphanumeric characters with underscores - var normalized strings.Builder - for _, char := range result { - if (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') { - normalized.WriteRune(char) - } else { - normalized.WriteRune('_') - } - } - - // Clean up multiple consecutive underscores and trim - cleaned := strings.Trim(strings.ReplaceAll(normalized.String(), "__", "_"), "_") - return cleaned -} - // IsSyncMode returns true if the mail source is in sync mode func (ms *MailSource) IsSyncMode() bool { return ms.Mode == "sync" @@ -179,21 +116,6 @@ func ParseCommandLine() *CommandLineArgs { fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Options:\n") pflag.PrintDefaults() - fmt.Fprintf(os.Stderr, "\nEnvironment Variables:\n") - fmt.Fprintf(os.Stderr, " Override sensitive credentials to avoid storing them in config files:\n\n") - fmt.Fprintf(os.Stderr, " MAIL2COUCH_COUCHDB_USER Override CouchDB username\n") - fmt.Fprintf(os.Stderr, " MAIL2COUCH_COUCHDB_PASSWORD Override CouchDB password\n") - fmt.Fprintf(os.Stderr, " MAIL2COUCH_IMAP__USER Override IMAP username for source \n") - fmt.Fprintf(os.Stderr, " MAIL2COUCH_IMAP__PASSWORD Override IMAP password for source \n\n") - fmt.Fprintf(os.Stderr, " Where is the mail source name from config.json converted to\n") - fmt.Fprintf(os.Stderr, " uppercase with non-alphanumeric characters replaced by underscores.\n") - fmt.Fprintf(os.Stderr, " Example: \"Personal Gmail\" -> \"PERSONAL_GMAIL\"\n\n") - fmt.Fprintf(os.Stderr, "Examples:\n") - fmt.Fprintf(os.Stderr, " %s --config /path/to/config.json\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " %s --max-messages 100 --dry-run\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " MAIL2COUCH_COUCHDB_PASSWORD=secret %s\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " MAIL2COUCH_IMAP_WORK_EMAIL_PASSWORD=app-pass %s\n", os.Args[0]) - os.Stderr.Sync() os.Exit(0) } @@ -290,38 +212,6 @@ func FindConfigFile(args *CommandLineArgs) (string, error) { return "", fmt.Errorf("no configuration file found. Searched locations: %v", candidates) } -// showEnvironmentOverrides displays which environment variable overrides are active -func showEnvironmentOverrides(config *Config) { - var overrides []string - - // Check CouchDB overrides - if os.Getenv("MAIL2COUCH_COUCHDB_USER") != "" { - overrides = append(overrides, "MAIL2COUCH_COUCHDB_USER") - } - if os.Getenv("MAIL2COUCH_COUCHDB_PASSWORD") != "" { - overrides = append(overrides, "MAIL2COUCH_COUCHDB_PASSWORD") - } - - // Check IMAP overrides for each source - for _, source := range config.MailSources { - envSuffix := normalizeNameForEnvVar(source.Name) - - userEnvVar := fmt.Sprintf("MAIL2COUCH_IMAP_%s_USER", envSuffix) - if os.Getenv(userEnvVar) != "" { - overrides = append(overrides, userEnvVar) - } - - passwordEnvVar := fmt.Sprintf("MAIL2COUCH_IMAP_%s_PASSWORD", envSuffix) - if os.Getenv(passwordEnvVar) != "" { - overrides = append(overrides, passwordEnvVar) - } - } - - if len(overrides) > 0 { - fmt.Printf("Active environment variable overrides: %s\n", strings.Join(overrides, ", ")) - } -} - // LoadConfigWithDiscovery loads configuration using automatic file discovery func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) { configPath, err := FindConfigFile(args) @@ -338,14 +228,5 @@ func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) { if args.DryRun { fmt.Printf("DRY-RUN MODE: No changes will be made to CouchDB\n") } - - config, err := LoadConfig(configPath) - if err != nil { - return nil, err - } - - // Show which environment variable overrides are active - showEnvironmentOverrides(config) - - return config, nil + return LoadConfig(configPath) } diff --git a/rust/src/cli.rs b/rust/src/cli.rs index a572722..fea0afa 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -15,23 +15,6 @@ pub fn parse_command_line() -> CommandLineArgs { .version(env!("CARGO_PKG_VERSION")) .about("Email backup utility for CouchDB") .long_about("A powerful email backup utility that synchronizes mail from IMAP accounts to CouchDB databases with intelligent incremental sync, comprehensive filtering, and native attachment support.") - .after_help(r#"Environment Variables: - Override sensitive credentials to avoid storing them in config files: - - MAIL2COUCH_COUCHDB_USER Override CouchDB username - MAIL2COUCH_COUCHDB_PASSWORD Override CouchDB password - MAIL2COUCH_IMAP__USER Override IMAP username for source - MAIL2COUCH_IMAP__PASSWORD Override IMAP password for source - - Where is the mail source name from config.json converted to - uppercase with non-alphanumeric characters replaced by underscores. - Example: "Personal Gmail" -> "PERSONAL_GMAIL" - -Examples: - mail2couch --config /path/to/config.json - mail2couch --max-messages 100 --dry-run - MAIL2COUCH_COUCHDB_PASSWORD=secret mail2couch - MAIL2COUCH_IMAP_WORK_EMAIL_PASSWORD=app-pass mail2couch"#) .arg(Arg::new("config") .short('c') .long("config") diff --git a/rust/src/config.rs b/rust/src/config.rs index b8ea2d5..49d243a 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -4,7 +4,6 @@ //! file discovery, matching the behavior of the Go implementation. use serde::{Deserialize, Serialize}; -use std::env; use std::fs; use std::path::PathBuf; use thiserror::Error; @@ -132,9 +131,6 @@ impl Config { } } - // Apply environment variable overrides for sensitive credentials - config.apply_environment_overrides(); - Ok(config) } @@ -190,109 +186,6 @@ impl Config { let config = Self::load_from_path(config_path.to_str().unwrap())?; Ok((config, config_path)) } - - /// Apply environment variable overrides for sensitive credentials - /// This allows users to keep credentials out of config files while maintaining security - /// - /// Environment variable patterns: - /// - MAIL2COUCH_COUCHDB_USER: Override CouchDB username - /// - MAIL2COUCH_COUCHDB_PASSWORD: Override CouchDB password - /// - MAIL2COUCH_IMAP__USER: Override IMAP username for source named - /// - MAIL2COUCH_IMAP__PASSWORD: Override IMAP password for source named - /// - /// The part is the mail source name converted to uppercase with non-alphanumeric characters replaced with underscores - fn apply_environment_overrides(&mut self) { - // CouchDB credential overrides - if let Ok(user) = env::var("MAIL2COUCH_COUCHDB_USER") { - if !user.is_empty() { - self.couch_db.user = user; - } - } - if let Ok(password) = env::var("MAIL2COUCH_COUCHDB_PASSWORD") { - if !password.is_empty() { - self.couch_db.password = password; - } - } - - // IMAP credential overrides for each mail source - for source in &mut self.mail_sources { - // Convert source name to environment variable suffix - let env_suffix = normalize_name_for_env_var(&source.name); - - let user_env_var = format!("MAIL2COUCH_IMAP_{}_USER", env_suffix); - if let Ok(user) = env::var(&user_env_var) { - if !user.is_empty() { - source.user = user; - } - } - - let password_env_var = format!("MAIL2COUCH_IMAP_{}_PASSWORD", env_suffix); - if let Ok(password) = env::var(&password_env_var) { - if !password.is_empty() { - source.password = password; - } - } - } - } - - /// Show which environment variable overrides are active - pub fn show_environment_overrides(&self) { - let mut overrides = Vec::new(); - - // Check CouchDB overrides - if env::var("MAIL2COUCH_COUCHDB_USER").is_ok() { - overrides.push("MAIL2COUCH_COUCHDB_USER"); - } - if env::var("MAIL2COUCH_COUCHDB_PASSWORD").is_ok() { - overrides.push("MAIL2COUCH_COUCHDB_PASSWORD"); - } - - // Check IMAP overrides for each source - for source in &self.mail_sources { - let env_suffix = normalize_name_for_env_var(&source.name); - - let user_env_var = format!("MAIL2COUCH_IMAP_{}_USER", env_suffix); - if env::var(&user_env_var).is_ok() { - overrides.push(Box::leak(user_env_var.into_boxed_str())); - } - - let password_env_var = format!("MAIL2COUCH_IMAP_{}_PASSWORD", env_suffix); - if env::var(&password_env_var).is_ok() { - overrides.push(Box::leak(password_env_var.into_boxed_str())); - } - } - - if !overrides.is_empty() { - println!("Active environment variable overrides: {}", overrides.join(", ")); - } - } -} - -/// Normalize a source name to a valid environment variable suffix -/// Example: "Personal Gmail" -> "PERSONAL_GMAIL" -fn normalize_name_for_env_var(name: &str) -> String { - let upper_name = name.to_uppercase(); - - // Replace non-alphanumeric characters with underscores - let normalized: String = upper_name - .chars() - .map(|c| { - if c.is_ascii_alphanumeric() { - c - } else { - '_' - } - }) - .collect(); - - // Clean up multiple consecutive underscores and trim - let cleaned = normalized - .split('_') - .filter(|s| !s.is_empty()) - .collect::>() - .join("_"); - - cleaned } #[cfg(test)] diff --git a/rust/src/main.rs b/rust/src/main.rs index 37038f6..8d4b2c6 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -62,9 +62,6 @@ fn print_config_summary(config: &mail2couch::config::Config) { info!(" CouchDB: {}", config.couch_db.url); info!(" Mail sources: {}", config.mail_sources.len()); - // Show which environment variable overrides are active - config.show_environment_overrides(); - for (i, source) in config.mail_sources.iter().enumerate() { let status = if source.enabled { "enabled"