#!/bin/bash # Email Server Setup Script # Components: Postfix, Dovecot, Amavis, SpamAssassin, OpenDKIM, PostgreSQL, PostfixAdmin # SSL: Let's Encrypt # Web Server: Nginx # Author: Email Server Setup Script # Date: $(date) set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration variables DOMAIN="" HOSTNAME="" ADMIN_EMAIL="" DB_NAME="postfix" DB_USER="postfix" DB_PASSWORD="" POSTFIXADMIN_PASSWORD="" WEBROOT="/var/www/postfixadmin" POSTFIXADMIN_VERSION="postfixadmin-3.3.11.tar.gz" # New: Define the mail volume path MAIL_VOLUME_PATH="/mnt/email" # Logging LOG_FILE="/var/log/email-server-setup.log" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } error() { echo -e "${RED}ERROR: $1${NC}" >&2 log "ERROR: $1" exit 1 } info() { echo -e "${BLUE}INFO: $1${NC}" log "INFO: $1" } success() { echo -e "${GREEN}SUCCESS: $1${NC}" log "SUCCESS: $1" } warning() { echo -e "${YELLOW}WARNING: $1${NC}" log "WARNING: $1" } # Check if running as root check_root() { if [[ $EUID -ne 0 ]]; then error "This script must be run as root" fi } # Get user input get_configuration() { echo -e "${BLUE}Email Server Configuration${NC}" echo "=================================" while [[ -z "$DOMAIN" ]]; do read -p "Enter your domain name (e.g., example.com): " DOMAIN done while [[ -z "$HOSTNAME" ]]; do read -p "Enter your hostname (e.g., mail.example.com): " HOSTNAME done while [[ -z "$ADMIN_EMAIL" ]]; do read -p "Enter admin email address: " ADMIN_EMAIL done while [[ -z "$DB_PASSWORD" ]]; do read -s -p "Enter PostgreSQL password for postfix user: " DB_PASSWORD echo done while [[ -z "$POSTFIXADMIN_PASSWORD" ]]; do read -s -p "Enter PostfixAdmin setup password: " POSTFIXADMIN_PASSWORD echo done info "Configuration completed" } # Update system update_system() { info "Updating system packages..." apt update && apt upgrade -y success "System updated" } # Install required packages install_packages() { info "Installing required packages..." # Install basic packages apt install -y \ curl \ wget \ gnupg \ lsb-release \ software-properties-common \ certbot \ python3-certbot-nginx \ nginx \ ufw # Install PostgreSQL apt install -y postgresql postgresql-contrib # Install Postfix and related packages debconf-set-selections <<< "postfix postfix/mailname string $HOSTNAME" debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'" apt install -y \ postfix \ postfix-pgsql \ postfix-policyd-spf-python # Install Dovecot apt install -y \ dovecot-core \ dovecot-imapd \ dovecot-pop3d \ dovecot-lmtpd \ dovecot-pgsql # Install Amavis and SpamAssassin apt install -y \ amavisd-new \ spamassassin \ clamav \ clamav-daemon \ clamav-freshclam # Try to install clamav-unofficial-sigs if available if apt-cache show clamav-unofficial-sigs >/dev/null 2>&1; then apt install -y clamav-unofficial-sigs info "Installed clamav-unofficial-sigs" else warning "clamav-unofficial-sigs package not available, skipping (this is normal on newer systems)" fi # Install OpenDKIM apt install -y opendkim opendkim-tools # Install PHP and extensions for PostfixAdmin apt install -y \ php-fpm \ php-cli \ php-pgsql \ php-mbstring \ php-xml \ php-curl \ php-zip \ php-gd success "All packages installed" } # Configure PostgreSQL configure_postgresql() { info "Configuring PostgreSQL..." # Start PostgreSQL systemctl start postgresql systemctl enable postgresql # Create database and user with full permissions for PostfixAdmin setup sudo -u postgres psql -c "CREATE DATABASE $DB_NAME;" || warning "Database already exists, skipping." sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';" || warning "User already exists, skipping." # Grant comprehensive permissions needed for PostfixAdmin setup wizard sudo -u postgres psql -d "$DB_NAME" << EOF -- Grant all privileges on database GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER; -- Grant schema permissions GRANT ALL PRIVILEGES ON SCHEMA public TO $DB_USER; GRANT CREATE ON SCHEMA public TO $DB_USER; -- Allow user to create tables, sequences, etc. ALTER USER $DB_USER CREATEDB; -- Set user as owner of public schema to allow full table management ALTER SCHEMA public OWNER TO $DB_USER; -- Grant default privileges for future objects ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO $DB_USER; EOF success "PostgreSQL configured for PostfixAdmin setup wizard" info "Database tables will be created by PostfixAdmin setup wizard" } # --- # NOTE: The order of web server setup has been corrected here. # Nginx is now configured with a basic HTTP virtual host first, allowing Certbot to run successfully. # --- # Install PostfixAdmin and configure Nginx for HTTP # Replace the install_postfixadmin() function with this updated version: # Install PostfixAdmin and configure Nginx for HTTP install_postfixadmin() { info "Installing PostfixAdmin..." # Download PostfixAdmin cd /tmp wget -q https://github.com/postfixadmin/postfixadmin/archive/$POSTFIXADMIN_VERSION || error "Failed to download PostfixAdmin" tar -xzf $POSTFIXADMIN_VERSION mv postfixadmin-postfixadmin-3.3.11 $WEBROOT # Create necessary directories mkdir -p $WEBROOT/templates_c mkdir -p $WEBROOT/logs # Set permissions chown -R www-data:www-data $WEBROOT find $WEBROOT -type d -exec chmod 755 {} \; find $WEBROOT -type f -exec chmod 644 {} \; # Ensure templates_c is writable chmod 755 $WEBROOT/templates_c chown www-data:www-data $WEBROOT/templates_c # Create PostfixAdmin configuration cat > $WEBROOT/config.local.php << EOF 'abuse@change-this-to-your.domain.tld', 'hostmaster' => 'hostmaster@change-this-to-your.domain.tld', 'postmaster' => 'postmaster@change-this-to-your.domain.tld', 'webmaster' => 'webmaster@change-this-to-your.domain.tld' ); \$CONF['domain_path'] = 'YES'; \$CONF['domain_in_mailbox'] = 'YES'; \$CONF['maildir_name_hook'] = 'maildir_name_hook'; \$CONF['templates_c'] = '$WEBROOT/templates_c'; function maildir_name_hook(\$domain, \$user) { return \$domain . '/' . \$user . '/'; } ?> EOF # Configure Nginx virtual host with proper public directory cat > /etc/nginx/sites-available/postfixadmin.conf << EOF server { listen 80; server_name $HOSTNAME; root $WEBROOT/public; index index.php index.html index.htm; # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; location / { try_files \$uri \$uri/ /index.php?\$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm.sock; fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; fastcgi_read_timeout 600s; fastcgi_param PATH_INFO \$fastcgi_path_info; include fastcgi_params; } # Deny access to sensitive files and directories location ~ /\. { deny all; } # Deny access to config and other sensitive directories outside public location ~ ^/(config|logs|templates_c|scripts|DOCUMENTS|INSTALL|upgrade)/ { deny all; } # Allow access to public assets location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } } EOF # Enable Nginx site and disable default ln -sf /etc/nginx/sites-available/postfixadmin.conf /etc/nginx/sites-enabled/ rm -f /etc/nginx/sites-enabled/default # Test write permissions if sudo -u www-data touch $WEBROOT/templates_c/test_write 2>/dev/null; then rm -f $WEBROOT/templates_c/test_write success "PostfixAdmin write permissions verified" else error "PostfixAdmin templates_c directory is not writable by www-data" fi # Verify public directory exists if [[ -d "$WEBROOT/public" ]]; then success "PostfixAdmin public directory found" else warning "PostfixAdmin public directory not found - this may cause 404 errors" info "Directory contents of $WEBROOT:" ls -la "$WEBROOT" || true fi success "PostfixAdmin installed and Nginx configured for HTTP with public directory" } # Get Let's Encrypt certificates get_ssl_certificates() { info "Obtaining Let's Encrypt certificates..." # Use certbot with the nginx plugin. It will automatically update the Nginx config for HTTPS. certbot --nginx -d $HOSTNAME --email $ADMIN_EMAIL --agree-tos --non-interactive if [[ ! -f "/etc/letsencrypt/live/$HOSTNAME/fullchain.pem" ]]; then error "Failed to obtain SSL certificate" fi # Certbot automatically sets up a cronjob for renewal with the nginx plugin success "SSL certificates obtained and Nginx configured for HTTPS" } # --- # The rest of the script is largely unchanged, but the main function has been reordered. # --- # Configure Postfix configure_postfix() { info "Configuring Postfix..." # Backup original configuration cp /etc/postfix/main.cf /etc/postfix/main.cf.backup # Create main.cf cat > /etc/postfix/main.cf << EOF # Basic configuration myhostname = $HOSTNAME mydomain = $DOMAIN myorigin = \$mydomain inet_interfaces = all inet_protocols = ipv4 mydestination = localhost # Virtual domains virtual_mailbox_domains = pgsql:/etc/postfix/pgsql-virtual-mailbox-domains.cf virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual-alias-maps.cf virtual_mailbox_base = $MAIL_VOLUME_PATH/vhosts virtual_minimum_uid = 100 virtual_uid_maps = static:5000 virtual_gid_maps = static:5000 # SSL/TLS configuration smtpd_tls_cert_file = /etc/letsencrypt/live/$HOSTNAME/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/$HOSTNAME/privkey.pem smtpd_tls_security_level = may smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_security_level = may smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 # SASL authentication smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes # Restrictions smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_pipelining, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_unknown_recipient_domain, reject_non_fqdn_recipient smtpd_data_restrictions = reject_unauth_pipelining # Content filter (Amavis) content_filter = smtp-amavis:[127.0.0.1]:10024 # Milters (OpenDKIM) milter_protocol = 2 milter_default_action = accept smtpd_milters = inet:localhost:8891 non_smtpd_milters = inet:localhost:8891 # Miscellaneous message_size_limit = 52428800 mailbox_size_limit = 0 biff = no append_dot_mydomain = no readme_directory = no compatibility_level = 2 EOF # Create PostgreSQL lookup files cat > /etc/postfix/pgsql-virtual-mailbox-domains.cf << EOF user = $DB_USER password = $DB_PASSWORD hosts = localhost dbname = $DB_NAME query = SELECT 1 FROM domains WHERE domain='%s' AND active='1' EOF cat > /etc/postfix/pgsql-virtual-mailbox-maps.cf << EOF user = $DB_USER password = $DB_PASSWORD hosts = localhost dbname = $DB_NAME query = SELECT 1 FROM mailbox WHERE username='%s' AND active='1' EOF cat > /etc/postfix/pgsql-virtual-alias-maps.cf << EOF user = $DB_USER password = $DB_PASSWORD hosts = localhost dbname = $DB_NAME query = SELECT goto FROM aliases WHERE address='%s' AND active='1' EOF # Set permissions chmod 640 /etc/postfix/pgsql-*.cf chown root:postfix /etc/postfix/pgsql-*.cf # Create virtual mailbox directory and set permissions mkdir -p $MAIL_VOLUME_PATH/vhosts groupadd -g 5000 vmail 2>/dev/null || true useradd -g vmail -u 5000 vmail -d $MAIL_VOLUME_PATH/vhosts -m 2>/dev/null || true chown -R vmail:vmail $MAIL_VOLUME_PATH/vhosts success "Postfix configured" } # Configure Dovecot configure_dovecot() { info "Configuring Dovecot..." # Backup configuration cp -r /etc/dovecot /etc/dovecot.backup # Main configuration cat > /etc/dovecot/dovecot.conf << EOF !include_try /usr/share/dovecot/protocols.d/*.protocol protocols = imap pop3 lmtp !include conf.d/*.conf !include_try local.conf EOF # 10-mail.conf cat > /etc/dovecot/conf.d/10-mail.conf << EOF mail_location = maildir:$MAIL_VOLUME_PATH/vhosts/%d/%n namespace inbox { inbox = yes } mail_privileged_group = mail mail_uid = vmail mail_gid = vmail first_valid_uid = 5000 last_valid_uid = 5000 first_valid_gid = 5000 last_valid_gid = 5000 EOF # 10-auth.conf cat > /etc/dovecot/conf.d/10-auth.conf << EOF disable_plaintext_auth = yes auth_mechanisms = plain login !include auth-sql.conf.ext EOF # auth-sql.conf.ext cat > /etc/dovecot/conf.d/auth-sql.conf.ext << EOF passdb { driver = sql args = /etc/dovecot/dovecot-sql.conf.ext } userdb { driver = static args = uid=vmail gid=vmail home=$MAIL_VOLUME_PATH/vhosts/%d/%n } EOF # dovecot-sql.conf.ext cat > /etc/dovecot/dovecot-sql.conf.ext << EOF driver = pgsql connect = host=localhost dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD default_pass_scheme = SHA512-CRYPT password_query = SELECT username as user, password FROM mailbox WHERE username='%u' AND active='1' EOF # 10-master.conf cat > /etc/dovecot/conf.d/10-master.conf << EOF service imap-login { inet_listener imap { port = 143 } inet_listener imaps { port = 993 ssl = yes } } service pop3-login { inet_listener pop3 { port = 110 } inet_listener pop3s { port = 995 ssl = yes } } service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } service auth { unix_listener /var/spool/postfix/private/auth { mode = 0666 user = postfix group = postfix } unix_listener auth-userdb { mode = 0600 user = vmail group = vmail } user = dovecot } service auth-worker { user = vmail } EOF # 10-ssl.conf cat > /etc/dovecot/conf.d/10-ssl.conf << EOF ssl = required ssl_cert = /etc/opendkim.conf << EOF Syslog yes UMask 007 Socket inet:8891@localhost PidFile /var/run/opendkim/opendkim.pid OversignHeaders From TrustAnchorFile /usr/share/dns/root.key UserID opendkim KeyTable /etc/opendkim/key.table SigningTable /etc/opendkim/signing.table ExternalIgnoreList /etc/opendkim/trusted.hosts InternalHosts /etc/opendkim/trusted.hosts EOF # Key table echo "mail._domainkey.$DOMAIN $DOMAIN:mail:/etc/opendkim/keys/$DOMAIN/mail.private" > /etc/opendkim/key.table # Signing table echo "*@$DOMAIN mail._domainkey.$DOMAIN" > /etc/opendkim/signing.table # Trusted hosts cat > /etc/opendkim/trusted.hosts << EOF 127.0.0.1 localhost $HOSTNAME $DOMAIN EOF success "OpenDKIM configured" # Display DNS record info "Add this TXT record to your DNS:" echo "mail._domainkey.$DOMAIN" cat /etc/opendkim/keys/$DOMAIN/mail.txt } # Configure Amavis and SpamAssassin configure_amavis() { info "Configuring Amavis and SpamAssassin..." # Stop freshclam service temporarily to avoid lock conflicts systemctl stop clamav-freshclam 2>/dev/null || true # Wait a moment for the service to fully stop sleep 2 # Update ClamAV signatures info "Updating ClamAV virus signatures..." if ! freshclam --quiet; then warning "Failed to update ClamAV signatures manually. The automatic updater will handle this." fi # Restart freshclam service systemctl start clamav-freshclam 2>/dev/null || true # Configure Amavis cat > /etc/amavis/conf.d/15-content_filter_mode << 'EOF' use strict; @bypass_virus_checks_maps = ( \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); @bypass_spam_checks_maps = ( \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re); 1; # ensure a defined return EOF cat > /etc/amavis/conf.d/20-debian_defaults << EOF use strict; \$MYHOME = '/var/lib/amavis'; \$mydomain = '$DOMAIN'; \$myhostname = '$HOSTNAME'; \$max_servers = 3; \$daemon_user = 'amavis'; \$daemon_group = 'amavis'; \$inet_socket_port = 10024; \$policy_bank{'MYNETS'} = { originating => 1, os_fingerprint_method => undef, }; \$interface_policy{'10024'} = 'ORIGINATING'; \$policy_bank{'ORIGINATING'} = { originating => 1, allow_disclaimers => 1, virus_admin_maps => ["virusalert\\\@\$mydomain"], spam_admin_maps => ["virusalert\\\@\$mydomain"], warnbadhsender => 1, forward_method => 'smtp:[127.0.0.1]:10025', smtpd_discard_ehlo_keywords => ['8BITMIME'], bypass_banned_checks_maps => [1], terminate_dsn_on_notify_success => 0, }; \$sa_tag_level_deflt = 2.0; \$sa_tag2_level_deflt = 6.0; \$sa_kill_level_deflt = 6.9; \$sa_dsn_cutoff_level = 10; \$penpals_bonus_score = 8; \$penpals_threshold_high = \$sa_kill_level_deflt; \$bounce_killer_score = 100; \$virus_admin = "postmaster\\\@\$mydomain"; \$mailfrom_notify_admin = "postmaster\\\@\$mydomain"; \$mailfrom_notify_recip = "postmaster\\\@\$mydomain"; \$mailfrom_notify_spamadmin = "postmaster\\\@\$mydomain"; \$mailfrom_to_quarantine = ''; \@lookup_sql_dsn = ( ['DBI:Pg:database=$DB_NAME;host=127.0.0.1;port=5432', '$DB_USER', '$DB_PASSWORD'] ); \$enable_db = 1; \$enable_global_cache = 1; \$inet_socket_bind = '127.0.0.1'; @inet_acl = qw(127.0.0.1); 1; EOF # Configure SpamAssassin cat > /etc/spamassassin/local.cf << EOF rewrite_header Subject *****SPAM***** report_safe 0 required_score 5.0 use_bayes 1 use_bayes_rules 1 bayes_auto_learn 1 skip_rbl_checks 0 use_razor2 1 use_dcc 1 use_pyzor 1 EOF # Add amavis user to clamav group usermod -a -G clamav amavis success "Amavis and SpamAssassin configured" } # Start and enable services start_services() { info "Starting and enabling services..." # Reload systemd systemctl daemon-reload # Start and enable core services first systemctl enable --now postgresql systemctl enable --now nginx systemctl enable --now php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm # Start ClamAV services with better error handling info "Starting ClamAV services..." # Start clamav-freshclam first (for signature updates) if systemctl list-unit-files --full | grep -q "clamav-freshclam.service"; then systemctl enable clamav-freshclam systemctl start clamav-freshclam # Wait for freshclam to complete initial signature update info "Waiting for ClamAV signature update to complete..." for i in {1..30}; do if systemctl is-active --quiet clamav-freshclam && [[ -f /var/lib/clamav/main.cvd || -f /var/lib/clamav/main.cld ]]; then break fi sleep 2 done else warning "ClamAV freshclam service file not found. Skipping." fi # Now start clamav-daemon if systemctl list-unit-files --full | grep -q "clamav-daemon.service"; then systemctl enable clamav-daemon systemctl start clamav-daemon # Wait for daemon to start sleep 3 if systemctl is-active --quiet clamav-daemon; then success "ClamAV daemon started successfully" else warning "ClamAV daemon failed to start. Check logs with: journalctl -u clamav-daemon" fi else warning "ClamAV daemon service file not found (expected 'clamav-daemon.service'). Skipping." fi # Start mail services systemctl enable --now postfix systemctl enable --now dovecot systemctl enable --now opendkim # Start Amavis (depends on ClamAV) systemctl enable --now amavis # Enable SpamAssassin service (handle potential name differences) if systemctl list-unit-files --full | grep -q "spamassassin.service"; then systemctl enable --now spamassassin elif systemctl list-unit-files --full | grep -q "spamd.service"; then systemctl enable --now spamd else warning "SpamAssassin service file not found (expected 'spamassassin.service' or 'spamd.service'). Skipping." fi success "All services started and enabled" } # Display final information display_final_info() { success "Email server setup completed!" echo echo -e "${BLUE}=== Setup Summary ===${NC}" echo "Primary Domain: $DOMAIN" echo "Hostname: $HOSTNAME" echo "Admin Email: $ADMIN_EMAIL" echo "Mailbox Location: $MAIL_VOLUME_PATH/vhosts" echo "PostfixAdmin URL: https://$HOSTNAME/" echo echo -e "${YELLOW}=== DNS Records to Add for $DOMAIN ===${NC}" echo "A $HOSTNAME [Your server IP]" echo "MX $DOMAIN $HOSTNAME" echo "TXT $DOMAIN \"v=spf1 mx ~all\"" echo "TXT _dmarc.$DOMAIN \"v=DMARC1; p=none; rua=mailto:dmarc@$DOMAIN\"" echo echo -e "${YELLOW}=== DKIM DNS Record for $DOMAIN ===${NC}" cat /etc/opendkim/keys/$DOMAIN/mail.txt echo echo -e "${YELLOW}=== Next Steps ===${NC}" echo "1. Add the DNS records shown above" echo "2. Visit https://$HOSTNAME/setup.php to complete PostfixAdmin setup" echo "3. After completing the setup wizard, run ONE of these scripts:" echo " • ./toggle-postfixadmin-configured.sh (quick toggle configured = true)" echo " • ./finalize-postfixadmin-config.sh (complete reconfiguration)" echo "4. Create your first domain and mailbox in PostfixAdmin" echo "5. Test email sending and receiving" echo echo -e "${BLUE}=== Multiple Domain Support ===${NC}" echo "This server supports unlimited virtual domains!" echo echo "To add additional domains:" echo "1. Run: ./add-domain.sh newdomain.com" echo "2. Add the provided DNS records" echo "3. Configure the domain in PostfixAdmin" echo "4. Create mailboxes for the new domain" echo echo "Each domain can have its own:" echo " • Independent mailboxes and aliases" echo " • DKIM signing for authentication" echo " • Custom quotas and limits" echo " • Application SMTP access" echo echo -e "${BLUE}=== Application SMTP Configuration ===${NC}" echo "Your applications can send email using these settings:" echo " Host: $HOSTNAME" echo " Port: 587 (STARTTLS) or 465 (SSL/TLS)" echo " Username: mailbox@yourdomain.com (full email address)" echo " Password: [mailbox password from PostfixAdmin]" echo " Security: STARTTLS or SSL/TLS" echo echo -e "${GREEN}Setup completed successfully!${NC}" } # Main execution main() { echo -e "${BLUE}Email Server Setup Script${NC}" echo "=========================" echo check_root get_configuration update_system install_packages configure_postgresql install_postfixadmin start_services # <-- Nginx is started here with a basic HTTP config get_ssl_certificates # <-- Certbot is now run after Nginx is active configure_postfix configure_dovecot configure_opendkim configure_amavis # configure_firewall display_final_info } # Run main function main "$@"