#!/bin/bash
################################################################################
# Script Name: postfix-relay-install.sh
# Version: 1.0
# Description: Automated Postfix satellite relay installation — configures
#              Postfix as a relay-only MTA with SASL auth, TLS, sender
#              rewriting, and firewall lockdown
#
# Author:  Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
#
# Usage:
#   sudo ./postfix-relay-install.sh --relay smtp.example.com --port 587
#   sudo ./postfix-relay-install.sh --relay smtp.example.com --user user --pass pass
#   sudo ./postfix-relay-install.sh --dry-run
#
################################################################################

set -euo pipefail

# ============================================================================
# DEFAULTS
# ============================================================================

RELAY_HOST=""
RELAY_PORT="587"
SASL_USER=""
SASL_PASS=""
FROM_ADDRESS=""
HOSTNAME_OVERRIDE=""
ROOT_ALIAS=""
TLS_MODE="may"
NO_FIREWALL=false
DRY_RUN=false

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

log_info()  { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
log_step()  { echo -e "${CYAN}[STEP]${NC} $*"; }

show_usage() {
    cat <<EOF
Usage: $0 [OPTIONS]

Installs and configures Postfix as a satellite relay (outbound only).

OPTIONS:
    --relay HOST        Relay host FQDN or IP (required)
    --port PORT         Relay port: 25, 465, 587 (default: 587)
    --user USER         SASL authentication username
    --pass PASS         SASL authentication password
    --from ADDRESS      Rewrite all sender addresses to this
    --hostname NAME     Set Postfix myhostname
    --root-alias ADDR   Forward root mail to this address
    --tls MODE          TLS mode: may, required, wrapper (default: may)
    --no-firewall       Skip firewall configuration
    --dry-run           Show what would be done
    -h, --help          Show this help

EXAMPLES:
    sudo $0 --relay smtp.example.com --port 587
    sudo $0 --relay smtp.example.com --user alerts@example.com --pass AppPass
    sudo $0 --relay smtp.example.com --from noreply@example.com --tls required

EOF
    exit 0
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help) show_usage ;;
            --relay) RELAY_HOST="$2"; shift 2 ;;
            --port) RELAY_PORT="$2"; shift 2 ;;
            --user) SASL_USER="$2"; shift 2 ;;
            --pass) SASL_PASS="$2"; shift 2 ;;
            --from) FROM_ADDRESS="$2"; shift 2 ;;
            --hostname) HOSTNAME_OVERRIDE="$2"; shift 2 ;;
            --root-alias) ROOT_ALIAS="$2"; shift 2 ;;
            --tls) TLS_MODE="$2"; shift 2 ;;
            --no-firewall) NO_FIREWALL=true; shift ;;
            --dry-run) DRY_RUN=true; shift ;;
            *) log_error "Unknown option: $1"; exit 1 ;;
        esac
    done

    if [ -z "$RELAY_HOST" ]; then
        log_error "--relay is required"
        echo ""
        show_usage
    fi
}

check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        log_error "This script must be run as root"
        exit 1
    fi
}

# ============================================================================
# OS DETECTION
# ============================================================================

detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        OS_FAMILY=""
        case "$ID" in
            debian|ubuntu) OS_FAMILY="debian" ;;
            rhel|centos|rocky|almalinux|fedora) OS_FAMILY="rhel" ;;
            *)
                if [ -n "${ID_LIKE:-}" ]; then
                    case "$ID_LIKE" in
                        *debian*|*ubuntu*) OS_FAMILY="debian" ;;
                        *rhel*|*centos*|*fedora*) OS_FAMILY="rhel" ;;
                    esac
                fi
                ;;
        esac

        if [ -z "$OS_FAMILY" ]; then
            log_error "Unsupported OS: $ID"
            exit 1
        fi

        log_info "Detected OS: $PRETTY_NAME (family: $OS_FAMILY)"
    else
        log_error "Cannot detect OS"
        exit 1
    fi
}

# ============================================================================
# INSTALLATION
# ============================================================================

remove_conflicting_mta() {
    log_step "Removing conflicting MTAs..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would remove sendmail/exim if present"
        return
    fi

    case "$OS_FAMILY" in
        debian)
            apt-get remove -y -qq exim4 exim4-base exim4-daemon-light sendmail 2>/dev/null || true
            ;;
        rhel)
            dnf remove -y -q sendmail 2>/dev/null || true
            ;;
    esac
}

install_postfix() {
    log_step "Installing Postfix..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would install postfix and dependencies"
        return
    fi

    case "$OS_FAMILY" in
        debian)
            DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
                postfix libsasl2-modules mailutils ca-certificates >/dev/null 2>&1
            ;;
        rhel)
            dnf install -y -q \
                postfix cyrus-sasl-plain mailx ca-certificates >/dev/null 2>&1
            ;;
    esac

    log_info "Postfix installed"
}

# ============================================================================
# CONFIGURATION
# ============================================================================

configure_relay() {
    log_step "Configuring relay host: [$RELAY_HOST]:$RELAY_PORT"

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would configure main.cf"
        return
    fi

    local my_hostname
    my_hostname="${HOSTNAME_OVERRIDE:-$(hostname -f 2>/dev/null || hostname)}"
    local my_domain
    my_domain=$(echo "$my_hostname" | sed 's/^[^.]*\.//')

    # Build main.cf
    cat > /etc/postfix/main.cf <<EOF
# Postfix Satellite Relay Configuration
# Generated by postfix-relay-install.sh v1.0

# Identity
myhostname = $my_hostname
mydomain = $my_domain
myorigin = \$mydomain

# Relay configuration
relayhost = [$RELAY_HOST]:$RELAY_PORT

# Listen only on loopback — relay only, no inbound mail
inet_interfaces = loopback-only
inet_protocols = all
mydestination = \$myhostname, localhost.\$mydomain, localhost

# SASL authentication
smtp_sasl_auth_enable = $([ -n "$SASL_USER" ] && echo "yes" || echo "no")
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous

# TLS configuration
smtp_use_tls = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_security_level = $TLS_MODE
smtp_tls_loglevel = 1

# Misc
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mailbox_size_limit = 0
recipient_delimiter = +
compatibility_level = 2

# Queue settings
maximal_queue_lifetime = 1d
bounce_queue_lifetime = 1d
EOF

    # Handle TLS wrapper mode (port 465)
    if [ "$TLS_MODE" = "wrapper" ]; then
        postconf -e "smtp_tls_wrappermode = yes"
        postconf -e "smtp_tls_security_level = encrypt"
    fi

    # Fix CA cert path for RHEL
    if [ "$OS_FAMILY" = "rhel" ]; then
        postconf -e "smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt"
    fi

    log_info "main.cf configured"
}

configure_sasl() {
    if [ -z "$SASL_USER" ]; then
        return
    fi

    log_step "Configuring SASL authentication..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would create sasl_passwd"
        return
    fi

    echo "[$RELAY_HOST]:$RELAY_PORT $SASL_USER:$SASL_PASS" > /etc/postfix/sasl_passwd
    postmap /etc/postfix/sasl_passwd
    chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db

    log_info "SASL credentials configured"
}

configure_sender_rewrite() {
    if [ -z "$FROM_ADDRESS" ]; then
        return
    fi

    log_step "Configuring sender rewriting: $FROM_ADDRESS"

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would create sender_canonical"
        return
    fi

    echo "/.+/ $FROM_ADDRESS" > /etc/postfix/sender_canonical_regexp
    postconf -e "sender_canonical_maps = regexp:/etc/postfix/sender_canonical_regexp"

    log_info "Sender rewriting configured"
}

configure_aliases() {
    if [ -z "$ROOT_ALIAS" ]; then
        return
    fi

    log_step "Configuring root alias: $ROOT_ALIAS"

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would set root alias"
        return
    fi

    if grep -q "^root:" /etc/aliases 2>/dev/null; then
        sed -i "s/^root:.*$/root: $ROOT_ALIAS/" /etc/aliases
    else
        echo "root: $ROOT_ALIAS" >> /etc/aliases
    fi

    newaliases
    log_info "Root alias set"
}

configure_firewall() {
    if [ "$NO_FIREWALL" = true ]; then
        return
    fi

    log_step "Configuring firewall (blocking inbound SMTP)..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would block inbound port 25"
        return
    fi

    # UFW
    if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "active"; then
        ufw deny in 25/tcp >/dev/null 2>&1 || true
        log_info "UFW: blocked inbound port 25"
    fi

    # firewalld
    if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
        firewall-cmd --permanent --remove-service=smtp >/dev/null 2>&1 || true
        firewall-cmd --reload >/dev/null 2>&1 || true
        log_info "firewalld: removed smtp service"
    fi
}

# ============================================================================
# SERVICE MANAGEMENT
# ============================================================================

start_postfix() {
    log_step "Starting Postfix..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would enable and start postfix"
        return
    fi

    systemctl enable postfix >/dev/null 2>&1
    systemctl restart postfix

    if systemctl is-active --quiet postfix; then
        log_info "Postfix is running"
    else
        log_error "Postfix failed to start"
        journalctl -u postfix --no-pager -n 10
        exit 1
    fi
}

send_test() {
    log_step "Sending test email..."

    if [ "$DRY_RUN" = true ]; then
        log_info "[DRY RUN] Would send test email"
        return
    fi

    local test_to="${ROOT_ALIAS:-root}"
    echo "Test email from $(hostname) via Postfix relay through $RELAY_HOST" | \
        mail -s "Postfix Relay Test — $(hostname) — $(date '+%Y-%m-%d %H:%M')" "$test_to" 2>/dev/null || true

    log_info "Test email queued (check $test_to inbox)"
}

# ============================================================================
# VERIFICATION
# ============================================================================

verify() {
    log_step "Verifying configuration..."
    echo ""

    if systemctl is-active --quiet postfix; then
        log_info "✓ Postfix service: running"
    else
        log_error "✗ Postfix service: not running"
    fi

    log_info "Relay host: $(postconf -h relayhost 2>/dev/null)"
    log_info "TLS level: $(postconf -h smtp_tls_security_level 2>/dev/null)"
    log_info "SASL auth: $(postconf -h smtp_sasl_auth_enable 2>/dev/null)"
    log_info "Interfaces: $(postconf -h inet_interfaces 2>/dev/null)"

    echo ""
    log_info "Mail queue:"
    mailq 2>/dev/null | tail -5

    echo ""
    log_info "Installation complete"
    echo ""
    log_info "Useful commands:"
    echo "  mailq                              — view mail queue"
    echo "  postqueue -f                       — flush mail queue"
    echo "  tail -f /var/log/mail.log          — watch mail log (Debian)"
    echo "  tail -f /var/log/maillog           — watch mail log (RHEL)"
    echo "  echo test | mail -s test root      — send test email"
}

# ============================================================================
# MAIN
# ============================================================================

main() {
    parse_args "$@"
    check_root
    detect_os

    echo ""
    log_info "=== Postfix Relay Install Script v1.0 ==="
    echo ""

    if [ "$DRY_RUN" = true ]; then
        log_warn "DRY RUN MODE — no changes will be made"
        echo ""
    fi

    remove_conflicting_mta
    install_postfix
    configure_relay
    configure_sasl
    configure_sender_rewrite
    configure_aliases
    configure_firewall
    start_postfix
    send_test

    if [ "$DRY_RUN" != true ]; then
        verify
    fi
}

main "$@"
