#!/bin/bash # mailhog_manager.sh - Comprehensive MailHog server management utility # Usage: ./mailhog_manager.sh [COMMAND] [OPTIONS] set -e # Default configuration DEFAULT_SMTP_PORT=1025 DEFAULT_UI_PORT=8025 DEFAULT_API_PORT=8025 DEFAULT_HOSTNAME="mailhog.local" DEFAULT_STORAGE="memory" PID_FILE="/tmp/mailhog.pid" LOG_FILE="/tmp/mailhog.log" CONFIG_FILE="$HOME/.mailhog_config" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Print colored output print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_header() { echo -e "${CYAN}=== $1 ===${NC}" } # Show usage information show_usage() { cat << EOF MailHog Server Manager Usage: $0 COMMAND [OPTIONS] COMMANDS: start Start MailHog server stop Stop MailHog server restart Restart MailHog server status Show MailHog server status logs Show MailHog server logs config Show current configuration test Test MailHog functionality cleanup Clean up MailHog data install Install MailHog (if not installed) update Update MailHog to latest version START OPTIONS: -p, --smtp-port PORT SMTP port (default: $DEFAULT_SMTP_PORT) -u, --ui-port PORT UI/API port (default: $DEFAULT_UI_PORT) -h, --hostname HOST Hostname (default: $DEFAULT_HOSTNAME) -s, --storage TYPE Storage type: memory, mongodb, maildir (default: $DEFAULT_STORAGE) --mongo-uri URI MongoDB connection URI (for MongoDB storage) --mongo-db DB MongoDB database name (default: mailhog) --mongo-coll COLL MongoDB collection name (default: messages) --maildir-path PATH Maildir storage path --auth-file FILE Authentication file path --outgoing-smtp FILE Outgoing SMTP configuration file --cors-origin ORIGIN CORS origin --invite-jim Enable Jim network simulation -d, --daemon Run in daemon mode -v, --verbose Enable verbose logging CONFIGURATION: --save Save current configuration as default --load Load saved configuration --reset Reset configuration to defaults TESTING OPTIONS: --send-email Send test email --check-connection Check SMTP connection --check-api Check API connection --cleanup-test Clean up test emails EXAMPLES: # Start MailHog with default settings $0 start # Start with custom ports and MongoDB storage $0 start --smtp-port 1026 --ui-port 8026 --storage mongodb --mongo-uri mongodb://localhost:27017 # Start in daemon mode with authentication $0 start --auth-file /path/to/auth.txt --daemon # Check server status $0 status # Send test email $0 test --send-email --to test@recipient.local # View logs $0 logs # Save current configuration $0 config --save INSTALLATION: # Install MailHog (Linux/macOS) $0 install # Update to latest version $0 update EOF } # Load configuration from file load_config() { if [[ -f "$CONFIG_FILE" ]]; then print_info "Loading configuration from $CONFIG_FILE" # Source the configuration file source "$CONFIG_FILE" fi } # Save current configuration save_config() { cat > "$CONFIG_FILE" << EOF # MailHog Configuration SMTP_PORT="$SMTP_PORT" UI_PORT="$UI_PORT" API_PORT="$API_PORT" HOSTNAME="$HOSTNAME" STORAGE="$STORAGE" MONGO_URI="$MONGO_URI" MONGO_DB="$MONGO_DB" MONGO_COLL="$MONGO_COLL" MAILDIR_PATH="$MAILDIR_PATH" AUTH_FILE="$AUTH_FILE" OUTGOING_SMTP="$OUTGOING_SMTP" CORS_ORIGIN="$CORS_ORIGIN" INVITE_JIM="$INVITE_JIM" EOF print_success "Configuration saved to $CONFIG_FILE" } # Reset configuration to defaults reset_config() { SMTP_PORT="$DEFAULT_SMTP_PORT" UI_PORT="$DEFAULT_UI_PORT" API_PORT="$DEFAULT_API_PORT" HOSTNAME="$DEFAULT_HOSTNAME" STORAGE="$DEFAULT_STORAGE" MONGO_URI="" MONGO_DB="mailhog" MONGO_COLL="messages" MAILDIR_PATH="" AUTH_FILE="" OUTGOING_SMTP="" CORS_ORIGIN="" INVITE_JIM="" print_success "Configuration reset to defaults" } # Check if MailHog is installed check_mailhog_installed() { if command -v mailhog >/dev/null 2>&1; then return 0 else return 1 fi } # Install MailHog install_mailhog() { print_header "Installing MailHog" if check_mailhog_installed; then print_warning "MailHog is already installed" return 0 fi local os=$(uname -s | tr '[:upper:]' '[:lower:]') local arch=$(uname -m) # Determine architecture case "$arch" in x86_64) arch="amd64" ;; aarch64|arm64) arch="arm64" ;; armv7l) arch="arm7" ;; *) print_error "Unsupported architecture: $arch" return 1 ;; esac # Get latest version local latest_version=$(curl -s "https://api.github.com/repos/mailhog/MailHog/releases/latest" | grep -o '"tag_name": "[^"]*' | cut -d'"' -f2 | sed 's/v//') if [[ -z "$latest_version" ]]; then print_error "Failed to fetch latest MailHog version" return 1 fi local download_url="https://github.com/mailhog/MailHog/releases/download/v${latest_version}/MailHog_${os}_${arch}.zip" print_info "Downloading MailHog v${latest_version} for ${os}/${arch}..." # Download and extract local temp_dir=$(mktemp -d) cd "$temp_dir" if curl -L -o mailhog.zip "$download_url"; then unzip mailhog.zip chmod +x MailHog # Move to binary directory if [[ -w "/usr/local/bin" ]]; then sudo mv MailHog /usr/local/bin/ elif [[ -w "$HOME/.local/bin" ]]; then mkdir -p "$HOME/.local/bin" mv MailHog "$HOME/.local/bin/" # Add to PATH if not already there if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then echo 'export PATH="$PATH:$HOME/.local/bin"' >> "$HOME/.bashrc" export PATH="$PATH:$HOME/.local/bin" fi else print_error "No writable binary directory found. Please install manually." cd / rm -rf "$temp_dir" return 1 fi cd / rm -rf "$temp_dir" print_success "MailHog v${latest_version} installed successfully" return 0 else print_error "Failed to download MailHog" cd / rm -rf "$temp_dir" return 1 fi } # Update MailHog update_mailhog() { print_header "Updating MailHog" if ! check_mailhog_installed; then print_warning "MailHog not installed. Installing..." install_mailhog return $? fi # Stop MailHog if running if is_mailhog_running; then stop_mailhog fi # Install latest version install_mailhog } # Check if MailHog is running is_mailhog_running() { if [[ -f "$PID_FILE" ]]; then local pid=$(cat "$PID_FILE") if ps -p "$pid" > /dev/null 2>&1; then return 0 else # PID file exists but process is not running rm -f "$PID_FILE" return 1 fi fi # Check by looking for mailhog process if pgrep -f "mailhog" > /dev/null; then return 0 fi return 1 } # Build MailHog command with current configuration build_mailhog_command() { local cmd="mailhog" # Basic configuration cmd+=" -hostname $HOSTNAME" cmd+=" -smtp-bind-addr 0.0.0.0:$SMTP_PORT" cmd+=" -ui-bind-addr 0.0.0.0:$UI_PORT" cmd+=" -api-bind-addr 0.0.0.0:$API_PORT" # Storage configuration case "$STORAGE" in "mongodb") if [[ -n "$MONGO_URI" ]]; then cmd+=" -storage mongodb -mongo-uri $MONGO_URI" else cmd+=" -storage mongodb -mongo-uri 127.0.0.1:27017" fi if [[ -n "$MONGO_DB" ]]; then cmd+=" -mongo-db $MONGO_DB" fi if [[ -n "$MONGO_COLL" ]]; then cmd+=" -mongo-coll $MONGO_COLL" fi ;; "maildir") if [[ -n "$MAILDIR_PATH" ]]; then cmd+=" -storage maildir -maildir-path $MAILDIR_PATH" else cmd+=" -storage maildir -maildir-path ./maildir" fi ;; *) cmd+=" -storage memory" ;; esac # Optional configurations if [[ -n "$AUTH_FILE" ]]; then cmd+=" -auth-file $AUTH_FILE" fi if [[ -n "$OUTGOING_SMTP" ]]; then cmd+=" -outgoing-smtp $OUTGOING_SMTP" fi if [[ -n "$CORS_ORIGIN" ]]; then cmd+=" -cors-origin \"$CORS_ORIGIN\"" fi if [[ "$INVITE_JIM" == "true" ]]; then cmd+=" -invite-jim" fi echo "$cmd" } # Start MailHog start_mailhog() { print_header "Starting MailHog" if is_mailhog_running; then print_warning "MailHog is already running" return 0 fi if ! check_mailhog_installed; then print_error "MailHog is not installed. Run '$0 install' first." return 1 fi # Check if ports are available if netstat -tlnp 2>/dev/null | grep -q ":$SMTP_PORT "; then print_error "Port $SMTP_PORT is already in use" return 1 fi if netstat -tlnp 2>/dev/null | grep -q ":$UI_PORT "; then print_error "Port $UI_PORT is already in use" return 1 fi local mailhog_cmd=$(build_mailhog_command) print_info "Starting MailHog with the following command:" print_info "$mailhog_cmd" if [[ "$DAEMON_MODE" == true ]]; then # Start in daemon mode nohup $mailhog_cmd > "$LOG_FILE" 2>&1 & local pid=$! echo "$pid" > "$PID_FILE" print_success "MailHog started in daemon mode (PID: $pid)" print_info "Logs are being written to: $LOG_FILE" else # Start in foreground $mailhog_cmd 2>&1 | tee "$LOG_FILE" & local pid=$! echo "$pid" > "$PID_FILE" print_success "MailHog started (PID: $pid)" fi # Wait a moment and check if it started successfully sleep 2 if is_mailhog_running; then print_success "MailHog started successfully" print_info "SMTP server: localhost:$SMTP_PORT" print_info "Web UI: http://localhost:$UI_PORT" print_info "API: http://localhost:$API_PORT/api/v1" else print_error "MailHog failed to start" return 1 fi } # Stop MailHog stop_mailhog() { print_header "Stopping MailHog" if ! is_mailhog_running; then print_warning "MailHog is not running" return 0 fi local pid="" if [[ -f "$PID_FILE" ]]; then pid=$(cat "$PID_FILE") else # Try to find PID by process name pid=$(pgrep -f "mailhog" | head -1) fi if [[ -n "$pid" ]]; then print_info "Stopping MailHog (PID: $pid)" kill "$pid" # Wait for process to stop local count=0 while ps -p "$pid" > /dev/null 2>&1 && [[ $count -lt 10 ]]; do sleep 1 ((count++)) done # Force kill if still running if ps -p "$pid" > /dev/null 2>&1; then print_warning "Force killing MailHog process" kill -9 "$pid" fi rm -f "$PID_FILE" print_success "MailHog stopped" else print_warning "Could not find MailHog process to stop" fi } # Restart MailHog restart_mailhog() { print_header "Restarting MailHog" stop_mailhog sleep 2 start_mailhog } # Show MailHog status show_status() { print_header "MailHog Status" if is_mailhog_running; then print_success "MailHog is running" # Show process information if [[ -f "$PID_FILE" ]]; then local pid=$(cat "$PID_FILE") print_info "PID: $pid" print_info "Command: $(ps -p "$pid" -o cmd=)" fi # Show configuration echo "" print_info "Configuration:" print_info " SMTP Port: $SMTP_PORT" print_info " UI Port: $UI_PORT" print_info " API Port: $API_PORT" print_info " Hostname: $HOSTNAME" print_info " Storage: $STORAGE" # Test API connection echo "" print_info "Testing API connection..." if curl -s --max-time 3 "http://localhost:$API_PORT/api/v1/status" > /dev/null 2>&1; then print_success "API is accessible" local messages=$(curl -s "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null | jq -r '.total // 0' 2>/dev/null || echo "unknown") print_info "Messages stored: $messages" else print_error "API is not accessible" fi # Show URLs echo "" print_info "Access URLs:" print_info " Web UI: http://localhost:$UI_PORT" print_info " API: http://localhost:$API_PORT/api/v1" print_info " SMTP: localhost:$SMTP_PORT" else print_error "MailHog is not running" fi } # Show logs show_logs() { print_header "MailHog Logs" if [[ -f "$LOG_FILE" ]]; then tail -f "$LOG_FILE" else print_warning "Log file not found: $LOG_FILE" fi } # Show configuration show_config() { print_header "Current Configuration" echo "SMTP Port: $SMTP_PORT" echo "UI Port: $UI_PORT" echo "API Port: $API_PORT" echo "Hostname: $HOSTNAME" echo "Storage: $STORAGE" if [[ "$STORAGE" == "mongodb" ]]; then echo "MongoDB URI: ${MONGO_URI:-"127.0.0.1:27017"}" echo "MongoDB DB: $MONGO_DB" echo "MongoDB Collection: $MONGO_COLL" fi if [[ "$STORAGE" == "maildir" ]]; then echo "Maildir Path: ${MAILDIR_PATH:-"./maildir"}" fi echo "Auth File: ${AUTH_FILE:-"none"}" echo "Outgoing SMTP: ${OUTGOING_SMTP:-"none"}" echo "CORS Origin: ${CORS_ORIGIN:-"none"}" echo "Jim Simulation: ${INVITE_JIM:-"false"}" echo "" echo "MailHog Command:" echo "$(build_mailhog_command)" } # Test MailHog functionality test_mailhog() { print_header "Testing MailHog" if ! is_mailhog_running; then print_error "MailHog is not running" return 1 fi # Test API connection if [[ "$CHECK_API" == true ]]; then print_info "Testing API connection..." local response=$(curl -s "http://localhost:$API_PORT/api/v1/status" 2>/dev/null) if [[ $? -eq 0 ]]; then print_success "API connection successful" echo "$response" | jq . 2>/dev/null || echo "$response" else print_error "API connection failed" fi fi # Test SMTP connection if [[ "$CHECK_CONNECTION" == true ]]; then print_info "Testing SMTP connection..." if echo "" | nc -w 3 localhost "$SMTP_PORT" 2>/dev/null | grep -q "220"; then print_success "SMTP connection successful" else print_error "SMTP connection failed" fi fi # Send test email if [[ "$SEND_EMAIL" == true ]]; then print_info "Sending test email..." local test_to="${TEST_TO:-test@recipient.local}" local test_from="${TEST_FROM:-test@sender.local}" local test_subject="MailHog Test Email $(date +%s)" local test_body="This is a test email sent at $(date)" # Send email using netcat ( echo "EHLO localhost" echo "MAIL FROM:<$test_from>" echo "RCPT TO:<$test_to>" echo "DATA" echo "From: $test_from" echo "To: $test_to" echo "Subject: $test_subject" echo "Date: $(date -R)" echo "" echo "$test_body" echo "." echo "QUIT" ) | nc localhost "$SMTP_PORT" 2>/dev/null if [[ $? -eq 0 ]]; then print_success "Test email sent to $test_to" # Wait and check if received sleep 2 local messages=$(curl -s "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null | jq -r '.total // 0' 2>/dev/null) if [[ "$messages" -gt 0 ]]; then print_success "Test email received by MailHog ($messages total messages)" else print_warning "Test email not found in MailHog (may need more time)" fi else print_error "Failed to send test email" fi fi # Clean up test emails if [[ "$CLEANUP_TEST" == true ]]; then print_info "Cleaning up test emails..." local response=$(curl -s -X DELETE "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null) if [[ $? -eq 0 ]]; then print_success "Test emails cleaned up" else print_warning "Failed to clean up test emails" fi fi } # Clean up MailHog data cleanup_mailhog() { print_header "Cleaning up MailHog" if is_mailhog_running; then print_info "Clearing all messages..." local response=$(curl -s -X DELETE "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null) if [[ $? -eq 0 ]]; then print_success "All messages cleared" else print_warning "Failed to clear messages" fi else print_warning "MailHog is not running" fi # Clean up temporary files print_info "Cleaning up temporary files..." rm -f "$PID_FILE" "$LOG_FILE" # Optional: Clean up maildir if used if [[ "$STORAGE" == "maildir" ]] && [[ -n "$MAILDIR_PATH" ]]; then if [[ -d "$MAILDIR_PATH" ]]; then print_info "Cleaning up Maildir storage..." rm -rf "$MAILDIR_PATH"/{cur,new,tmp}/* print_success "Maildir storage cleaned" fi fi print_success "Cleanup completed" } # Main function main() { # Load default configuration load_config # Set defaults if not loaded SMTP_PORT="${SMTP_PORT:-$DEFAULT_SMTP_PORT}" UI_PORT="${UI_PORT:-$DEFAULT_UI_PORT}" API_PORT="${API_PORT:-$DEFAULT_API_PORT}" HOSTNAME="${HOSTNAME:-$DEFAULT_HOSTNAME}" STORAGE="${STORAGE:-$DEFAULT_STORAGE}" MONGO_DB="${MONGO_DB:-mailhog}" MONGO_COLL="${MONGO_COLL:-messages}" DAEMON_MODE=false # Parse command line arguments case "${1:-}" in "start") shift while [[ $# -gt 0 ]]; do case $1 in -p|--smtp-port) SMTP_PORT="$2" shift 2 ;; -u|--ui-port) UI_PORT="$2" API_PORT="$2" shift 2 ;; -h|--hostname) HOSTNAME="$2" shift 2 ;; -s|--storage) STORAGE="$2" shift 2 ;; --mongo-uri) MONGO_URI="$2" shift 2 ;; --mongo-db) MONGO_DB="$2" shift 2 ;; --mongo-coll) MONGO_COLL="$2" shift 2 ;; --maildir-path) MAILDIR_PATH="$2" shift 2 ;; --auth-file) AUTH_FILE="$2" shift 2 ;; --outgoing-smtp) OUTGOING_SMTP="$2" shift 2 ;; --cors-origin) CORS_ORIGIN="$2" shift 2 ;; --invite-jim) INVITE_JIM="true" shift ;; -d|--daemon) DAEMON_MODE=true shift ;; -v|--verbose) VERBOSE=true shift ;; --save) save_config exit 0 ;; *) print_error "Unknown option: $1" show_usage exit 1 ;; esac done start_mailhog ;; "stop") stop_mailhog ;; "restart") restart_mailhog ;; "status") show_status ;; "logs") show_logs ;; "config") case "${2:-}" in "--save") save_config ;; "--load") load_config print_success "Configuration loaded" ;; "--reset") reset_config ;; *) show_config ;; esac ;; "test") shift while [[ $# -gt 0 ]]; do case $1 in --send-email) SEND_EMAIL=true shift ;; --check-connection) CHECK_CONNECTION=true shift ;; --check-api) CHECK_API=true shift ;; --cleanup-test) CLEANUP_TEST=true shift ;; --to) TEST_TO="$2" shift 2 ;; --from) TEST_FROM="$2" shift 2 ;; *) print_error "Unknown test option: $1" show_usage exit 1 ;; esac done # If no specific test options, run all tests if [[ -z "$SEND_EMAIL" && -z "$CHECK_CONNECTION" && -z "$CHECK_API" && -z "$CLEANUP_TEST" ]]; then SEND_EMAIL=true CHECK_CONNECTION=true CHECK_API=true CLEANUP_TEST=true fi test_mailhog ;; "cleanup") cleanup_mailhog ;; "install") install_mailhog ;; "update") update_mailhog ;; "help"|"-h"|"--help") show_usage ;; *) print_error "Unknown command: ${1:-}" show_usage exit 1 ;; esac } # Run main function main "$@"