Compare commits
No commits in common. "main" and "v1.0.0-rc1" have entirely different histories.
main
...
v1.0.0-rc1
9 changed files with 13 additions and 532 deletions
98
README.md
98
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/README.md](docs/README.md)** - Documentation overview and quick start
|
||||||
- **[docs/ANALYSIS.md](docs/ANALYSIS.md)** - Technical analysis and current status
|
- **[docs/ANALYSIS.md](docs/ANALYSIS.md)** - Technical analysis and current status
|
||||||
- **[docs/IMPLEMENTATION_COMPARISON.md](docs/IMPLEMENTATION_COMPARISON.md)** - Go vs Rust comparison
|
- **[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/FOLDER_PATTERNS.md](docs/FOLDER_PATTERNS.md)** - Folder filtering guide
|
||||||
- **[docs/couchdb-schemas.md](docs/couchdb-schemas.md)** - Database schema documentation
|
- **[docs/couchdb-schemas.md](docs/couchdb-schemas.md)** - Database schema documentation
|
||||||
- **[docs/TODO.md](docs/TODO.md)** - Development roadmap and future plans
|
- **[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:
|
Options:
|
||||||
-c, --config FILE Path to configuration file
|
-c, --config FILE Path to configuration file
|
||||||
-m, --max-messages N Limit messages processed per mailbox per run (0 = unlimited)
|
-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
|
-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_<NAME>_USER` | Override IMAP username for source `<NAME>` | `user@gmail.com` |
|
|
||||||
| `MAIL2COUCH_IMAP_<NAME>_PASSWORD` | Override IMAP password for source `<NAME>` | `app_password` |
|
|
||||||
|
|
||||||
**Name Normalization**: The `<NAME>` 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
|
### Folder Pattern Examples
|
||||||
|
|
||||||
| Pattern | Description | Matches |
|
| 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
|
### Advanced Multi-Account Configuration
|
||||||
Complex setup with multiple accounts, filtering, and different sync modes:
|
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
|
## Production Deployment
|
||||||
|
|
||||||
### Security Considerations
|
### Security Considerations
|
||||||
- **Use Environment Variables**: Store sensitive credentials in environment variables instead of configuration files
|
- Use app passwords instead of account passwords
|
||||||
- **Use App Passwords**: Use app passwords instead of account passwords for IMAP authentication
|
- Store configuration files with restricted permissions (600)
|
||||||
- **File Permissions**: Store configuration files with restricted permissions (600)
|
- Use HTTPS for CouchDB connections in production
|
||||||
- **Secure Connections**: Use HTTPS for CouchDB connections in production
|
- Consider encrypting sensitive configuration data
|
||||||
- **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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring and Maintenance
|
### Monitoring and Maintenance
|
||||||
- Review sync metadata documents for sync health
|
- Review sync metadata documents for sync health
|
||||||
|
|
|
||||||
|
|
@ -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_<NAME>_USER` | Override IMAP username for source `<NAME>` | `user@gmail.com` |
|
|
||||||
| `MAIL2COUCH_IMAP_<NAME>_PASSWORD` | Override IMAP password for source `<NAME>` | `app_specific_password` |
|
|
||||||
|
|
||||||
## Name Normalization
|
|
||||||
|
|
||||||
The `<NAME>` 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
|
|
||||||
|
|
@ -138,14 +138,11 @@ Both implementations have achieved **production readiness** with comprehensive t
|
||||||
- ✅ **SystemD Integration**: Automated scheduling support
|
- ✅ **SystemD Integration**: Automated scheduling support
|
||||||
- ✅ **Build System**: Unified justfile for both implementations
|
- ✅ **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**
|
### **Future Enhancement Priorities**
|
||||||
1. **Go Concurrency**: Optional parallel processing
|
1. **Security**: Environment variable credential support
|
||||||
2. **Progress Indicators**: Real-time progress reporting
|
2. **Go Concurrency**: Optional parallel processing
|
||||||
3. **Interactive Setup**: Guided configuration wizard
|
3. **Progress Indicators**: Real-time progress reporting
|
||||||
|
4. **Interactive Setup**: Guided configuration wizard
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ This directory contains comprehensive documentation for the mail2couch project,
|
||||||
- **[TODO.md](TODO.md)** - Development roadmap and outstanding tasks
|
- **[TODO.md](TODO.md)** - Development roadmap and outstanding tasks
|
||||||
|
|
||||||
### Configuration & Setup
|
### 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
|
- **[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
|
- **[test-config-comparison.md](test-config-comparison.md)** - Configuration examples and testing scenarios
|
||||||
|
|
||||||
|
|
|
||||||
11
docs/TODO.md
11
docs/TODO.md
|
|
@ -34,14 +34,11 @@ This document outlines the development roadmap for mail2couch, with both Go and
|
||||||
|
|
||||||
## 🚧 Current Development Priorities
|
## 🚧 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
|
### 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
|
### Medium Priority
|
||||||
2. **🚀 Go Implementation Concurrency**
|
2. **🚀 Go Implementation Concurrency**
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"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
|
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_<NAME>_USER: Override IMAP username for source named <NAME>
|
|
||||||
// - MAIL2COUCH_IMAP_<NAME>_PASSWORD: Override IMAP password for source named <NAME>
|
|
||||||
//
|
|
||||||
// The <NAME> 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
|
// IsSyncMode returns true if the mail source is in sync mode
|
||||||
func (ms *MailSource) IsSyncMode() bool {
|
func (ms *MailSource) IsSyncMode() bool {
|
||||||
return ms.Mode == "sync"
|
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, "Usage: %s [OPTIONS]\n\n", os.Args[0])
|
||||||
fmt.Fprintf(os.Stderr, "Options:\n")
|
fmt.Fprintf(os.Stderr, "Options:\n")
|
||||||
pflag.PrintDefaults()
|
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_<NAME>_USER Override IMAP username for source <NAME>\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " MAIL2COUCH_IMAP_<NAME>_PASSWORD Override IMAP password for source <NAME>\n\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " Where <NAME> 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)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,38 +212,6 @@ func FindConfigFile(args *CommandLineArgs) (string, error) {
|
||||||
return "", fmt.Errorf("no configuration file found. Searched locations: %v", candidates)
|
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
|
// LoadConfigWithDiscovery loads configuration using automatic file discovery
|
||||||
func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) {
|
func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) {
|
||||||
configPath, err := FindConfigFile(args)
|
configPath, err := FindConfigFile(args)
|
||||||
|
|
@ -338,14 +228,5 @@ func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) {
|
||||||
if args.DryRun {
|
if args.DryRun {
|
||||||
fmt.Printf("DRY-RUN MODE: No changes will be made to CouchDB\n")
|
fmt.Printf("DRY-RUN MODE: No changes will be made to CouchDB\n")
|
||||||
}
|
}
|
||||||
|
return LoadConfig(configPath)
|
||||||
config, err := LoadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show which environment variable overrides are active
|
|
||||||
showEnvironmentOverrides(config)
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,6 @@ pub fn parse_command_line() -> CommandLineArgs {
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about("Email backup utility for CouchDB")
|
.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.")
|
.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_<NAME>_USER Override IMAP username for source <NAME>
|
|
||||||
MAIL2COUCH_IMAP_<NAME>_PASSWORD Override IMAP password for source <NAME>
|
|
||||||
|
|
||||||
Where <NAME> 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")
|
.arg(Arg::new("config")
|
||||||
.short('c')
|
.short('c')
|
||||||
.long("config")
|
.long("config")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
//! file discovery, matching the behavior of the Go implementation.
|
//! file discovery, matching the behavior of the Go implementation.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -132,9 +131,6 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply environment variable overrides for sensitive credentials
|
|
||||||
config.apply_environment_overrides();
|
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,109 +186,6 @@ impl Config {
|
||||||
let config = Self::load_from_path(config_path.to_str().unwrap())?;
|
let config = Self::load_from_path(config_path.to_str().unwrap())?;
|
||||||
Ok((config, config_path))
|
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_<NAME>_USER: Override IMAP username for source named <NAME>
|
|
||||||
/// - MAIL2COUCH_IMAP_<NAME>_PASSWORD: Override IMAP password for source named <NAME>
|
|
||||||
///
|
|
||||||
/// The <NAME> 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::<Vec<_>>()
|
|
||||||
.join("_");
|
|
||||||
|
|
||||||
cleaned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,6 @@ fn print_config_summary(config: &mail2couch::config::Config) {
|
||||||
info!(" CouchDB: {}", config.couch_db.url);
|
info!(" CouchDB: {}", config.couch_db.url);
|
||||||
info!(" Mail sources: {}", config.mail_sources.len());
|
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() {
|
for (i, source) in config.mail_sources.iter().enumerate() {
|
||||||
let status = if source.enabled {
|
let status = if source.enabled {
|
||||||
"enabled"
|
"enabled"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue