Files
EmailHostingPlatform/setup-email-server.sh
Tommy Parnell 86fdc0b3ba stop for now
2025-08-04 04:16:00 -04:00

880 lines
26 KiB
Bash
Executable File

#!/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
<?php
\$CONF['configured'] = false; // Allow setup wizard to run
\$CONF['setup_password'] = '$(php -r "echo password_hash('$POSTFIXADMIN_PASSWORD', PASSWORD_DEFAULT);")';
\$CONF['postfix_admin_url'] = 'https://$HOSTNAME';
\$CONF['postfix_admin_path'] = '$WEBROOT';
\$CONF['default_language'] = 'en';
\$CONF['database_type'] = 'pgsql';
\$CONF['database_host'] = 'localhost';
\$CONF['database_user'] = '$DB_USER';
\$CONF['database_password'] = '$DB_PASSWORD';
\$CONF['database_name'] = '$DB_NAME';
\$CONF['admin_email'] = '$ADMIN_EMAIL';
// Use PHP-based encryption instead of dovecot command for compatibility
\$CONF['encrypt'] = 'php_crypt:SHA512';
\$CONF['page_size'] = '10';
\$CONF['default_aliases'] = array (
'abuse' => '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/letsencrypt/live/$HOSTNAME/fullchain.pem
ssl_key = </etc/letsencrypt/live/$HOSTNAME/privkey.pem
ssl_protocols = !SSLv2 !SSLv3 !TLSv1 !TLSv1.1
ssl_cipher_list = ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384
ssl_prefer_server_ciphers = yes
ssl_dh = </etc/dovecot/dh.pem
EOF
# Generate DH parameters
openssl dhparam -out /etc/dovecot/dh.pem 2048
# Set permissions
chmod 600 /etc/dovecot/dovecot-sql.conf.ext
chown vmail:dovecot /etc/dovecot/dovecot-sql.conf.ext
success "Dovecot configured"
}
# Configure OpenDKIM
configure_opendkim() {
info "Configuring OpenDKIM..."
# Create directories
mkdir -p /etc/opendkim/keys/$DOMAIN
# Generate DKIM key
opendkim-genkey -t -s mail -d $DOMAIN -D /etc/opendkim/keys/$DOMAIN
# Set permissions
chown -R opendkim:opendkim /etc/opendkim
chmod 600 /etc/opendkim/keys/$DOMAIN/mail.private
# OpenDKIM configuration
cat > /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 "$@"