Files
EmailHostingPlatform/setup-email-server.sh
Tommy Parnell 372203e599 ok try again
2025-08-03 16:04:35 -04:00

1047 lines
30 KiB
Bash
Executable File

#!/bin/bash
# Email Server Setup Script
# Components: Postfix, Dovecot, Amavis, SpamAssassin, OpenDKIM, PostgreSQL, PostfixAdmin
# SSL: Let's Encrypt (DNS-01 Challenge)
# Author: Email Server Setup Script
# Date: Sun Aug 3 15:05:28 EDT 2025
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_VHOST_FILE="/etc/apache2/sites-available/postfixadmin.conf"
# 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
}
verify_email_storage() {
info "Verifying email storage at /mnt/MainEmail..."
# Check if directory exists
if [[ ! -d "/mnt/MainEmail" ]]; then
error "Email storage directory /mnt/MainEmail does not exist. Please run prepare-email-storage.sh first."
fi
# Check if vmail user exists
if ! getent passwd vmail >/dev/null; then
error "vmail user does not exist. Please run prepare-email-storage.sh first."
fi
# Check ownership
OWNER=$(stat -c '%U:%G' /mnt/MainEmail)
if [[ "$OWNER" != "vmail:vmail" ]]; then
warning "Incorrect ownership on /mnt/MainEmail. Expected vmail:vmail, got $OWNER"
info "Fixing ownership..."
chown vmail:vmail /mnt/MainEmail
fi
# Check write permissions
if ! sudo -u vmail test -w /mnt/MainEmail; then
error "vmail user cannot write to /mnt/MainEmail. Please run prepare-email-storage.sh first."
fi
# Check available space
AVAILABLE_SPACE=$(df --output=avail /mnt/MainEmail | tail -n1)
AVAILABLE_GB=$((AVAILABLE_SPACE / 1024 / 1024))
if [[ $AVAILABLE_GB -lt 5 ]]; then
warning "Low disk space: only ${AVAILABLE_GB}GB available in /mnt/MainEmail"
else
info "Email storage ready: ${AVAILABLE_GB}GB available"
fi
success "Email storage verification completed"
}
# 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..."
# Check for and stop Nginx if it's running to avoid port conflicts with Apache
if systemctl is-active --quiet nginx 2>/dev/null; then
warning "Nginx is running and will be stopped to allow Apache to run."
systemctl stop nginx
systemctl disable nginx
elif command -v nginx &> /dev/null; then
warning "Nginx is installed but not running as a service."
# Check if nginx is running manually and kill it
if pgrep nginx > /dev/null; then
warning "Found nginx processes, killing them..."
pkill nginx
fi
fi
# Also check if anything is listening on port 80 or 443
if ss -tlnp | grep -q ":80 "; then
warning "Something is already listening on port 80:"
ss -tlnp | grep ":80 "
echo "You may need to stop this service manually before Apache can start."
fi
if ss -tlnp | grep -q ":443 "; then
warning "Something is already listening on port 443:"
ss -tlnp | grep ":443 "
echo "You may need to stop this service manually before Apache can start."
fi
# Install basic packages
apt install -y \
curl \
wget \
gnupg \
lsb-release \
software-properties-common \
certbot \
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 Apache for PostfixAdmin
apt install -y \
apache2 \
php \
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
sudo -u postgres psql -c "CREATE DATABASE $DB_NAME;"
sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;"
# Create tables for Postfix
sudo -u postgres psql -d "$DB_NAME" << 'EOF'
CREATE TABLE domains (
domain varchar(50) NOT NULL,
description varchar(255),
aliases int NOT NULL default '0',
mailboxes int NOT NULL default '0',
maxquota bigint NOT NULL default '0',
quota bigint NOT NULL default '0',
transport varchar(255) default NULL,
backupmx boolean NOT NULL default '0',
created timestamp NOT NULL default now(),
modified timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (domain)
);
CREATE TABLE aliases (
address varchar(255) NOT NULL,
goto text NOT NULL,
domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
modified timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (address)
);
CREATE TABLE mailbox (
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
name varchar(255) NOT NULL default '',
maildir varchar(255) NOT NULL default '',
quota bigint NOT NULL default '0',
local_part varchar(255) NOT NULL,
domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
modified timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (username)
);
CREATE TABLE domain_admins (
username varchar(255) NOT NULL,
domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (username, domain)
);
CREATE TABLE admin (
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
modified timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (username)
);
CREATE TABLE log (
timestamp timestamp NOT NULL default now(),
username varchar(255) NOT NULL default '',
domain varchar(255) NOT NULL default '',
action varchar(255) NOT NULL default '',
data text NOT NULL default ''
);
CREATE TABLE vacation (
email varchar(255) NOT NULL,
subject varchar(255) NOT NULL,
body text NOT NULL,
cache text NOT NULL default '',
domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
active boolean NOT NULL default '1',
PRIMARY KEY (email)
);
CREATE TABLE quota (
username varchar(255) NOT NULL,
path varchar(100) NOT NULL,
current bigint NOT NULL default 0,
PRIMARY KEY (username, path)
);
CREATE TABLE quota2 (
username varchar(100) NOT NULL,
bytes bigint NOT NULL default 0,
messages int NOT NULL default 0,
PRIMARY KEY (username)
);
EOF
# Grant permissions
sudo -u postgres psql -d "$DB_NAME" -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO $DB_USER;"
sudo -u postgres psql -d "$DB_NAME" -c "GRANT INSERT, UPDATE, DELETE ON aliases, mailbox, domain_admins, domains TO $DB_USER;"
sudo -u postgres psql -d "$DB_NAME" -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $DB_USER;"
# Insert the primary domain
sudo -u postgres psql -d "$DB_NAME" -c "INSERT INTO domains (domain, description, active) VALUES ('$DOMAIN', 'Primary domain configured during setup', '1') ON CONFLICT (domain) DO NOTHING;"
success "PostgreSQL configured"
}
# 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 = /mnt/MainEmail
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
# Configure master.cf for submission and smtps
cat >> /etc/postfix/master.cf << EOF
# Submission port 587
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
# SMTPS port 465
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
# Amavis
smtp-amavis unix - - - - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - - - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
EOF
# Create virtual mailbox directory
mkdir -p /mnt/MainEmail
groupadd -g 5000 vmail 2>/dev/null || true
useradd -g vmail -u 5000 vmail -d /mnt/MainEmail -m 2>/dev/null || true
chown -R vmail:vmail /mnt/MainEmail
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:/mnt/MainEmail/%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=/mnt/MainEmail/%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..."
# Update ClamAV signatures
freshclam
# 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"
}
# Get Let's Encrypt certificates using the DNS challenge
get_ssl_certificates() {
info "Obtaining Let's Encrypt certificates..."
if [[ ! -f "/etc/letsencrypt/live/$HOSTNAME/fullchain.pem" ]]; then
warning "SSL certificates for $HOSTNAME were not found."
echo -e "${YELLOW}This script will use the DNS challenge method with Certbot, which does not require opening port 80.${NC}"
echo -e "${YELLOW}You will need to manually update your DNS records during this process.${NC}"
echo
echo -e "${BLUE}1. Install the appropriate Certbot DNS plugin for your DNS provider (e.g., 'certbot-dns-cloudflare' for Cloudflare).${NC}"
echo -e "${BLUE}2. Create the necessary API credentials file for Certbot to access your DNS provider's API.${NC}"
echo -e "${BLUE}3. Run the following command (with your plugin and credentials) to obtain your certificate:${NC}"
echo -e "${GREEN} certbot certonly --dns-PLUGIN --dns-PLUGIN-credentials /path/to/credentials.ini -d \"$HOSTNAME\" -d \"www.$HOSTNAME\" --agree-tos --email \"$ADMIN_EMAIL\"${NC}"
echo -e "${YELLOW}After running the command and successfully obtaining the certificates, press ENTER to continue this script.${NC}"
read -p ""
fi
if [[ ! -f "/etc/letsencrypt/live/$HOSTNAME/fullchain.pem" ]]; then
error "Failed to find SSL certificate. Please obtain it manually and re-run this script."
fi
# Set up auto-renewal
(crontab -l 2>/dev/null; echo "0 12 * * * /usr/bin/certbot renew --quiet") | crontab -
success "SSL certificates obtained"
}
# Install PostfixAdmin and configure Apache
install_postfixadmin() {
info "Installing PostfixAdmin..."
# Download PostfixAdmin
cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.3.11.tar.gz
tar -xzf postfixadmin-3.3.11.tar.gz
mv postfixadmin-postfixadmin-3.3.11 $WEBROOT
# Set permissions
chown -R www-data:www-data $WEBROOT
chmod -R 755 $WEBROOT
# Create PostfixAdmin configuration
cat > $WEBROOT/config.local.php << EOF
<?php
\$CONF['configured'] = true;
\$CONF['setup_password'] = '$(php -r "echo password_hash('$POSTFIXADMIN_PASSWORD', PASSWORD_DEFAULT);")';
\$CONF['postfix_admin_url'] = 'https://$HOSTNAME/postfixadmin';
\$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';
\$CONF['encrypt'] = 'sha512.crypt';
\$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
\$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';
function maildir_name_hook(\$domain, \$user) {
return \$domain . '/' . \$user . '/';
}
?>
EOF
# Configure Apache virtual host
cat > "$POSTFIXADMIN_VHOST_FILE" << EOF
<VirtualHost *:80>
ServerName $HOSTNAME
Redirect permanent / https://$HOSTNAME/
</VirtualHost>
<VirtualHost *:443>
ServerName $HOSTNAME
DocumentRoot $WEBROOT
DirectoryIndex index.php index.html
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/$HOSTNAME/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$HOSTNAME/privkey.pem
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder on
<Directory $WEBROOT>
Options -Indexes
AllowOverride All
Require all granted
</Directory>
# PHP configuration
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
</FilesMatch>
ErrorLog \${APACHE_LOG_DIR}/postfixadmin_error.log
CustomLog \${APACHE_LOG_DIR}/postfixadmin_access.log combined
</VirtualHost>
EOF
# Stop Apache before configuration changes
if systemctl is-active --quiet apache2; then
systemctl stop apache2
fi
# Enable required Apache modules
a2enmod ssl
a2enmod rewrite
a2enmod proxy
a2enmod proxy_fcgi
a2enmod setenvif
# Enable PHP-FPM configuration
a2enconf php*-fpm
# Enable PostfixAdmin site and disable default
a2ensite postfixadmin
a2dissite 000-default
# Ensure PHP-FPM is running
systemctl enable php*-fpm
systemctl start php*-fpm
# Check Apache config before starting
if ! apachectl configtest; then
error "Apache configuration test failed. Please check $POSTFIXADMIN_VHOST_FILE"
fi
success "PostfixAdmin installed and Apache configured"
}
# Configure firewall
configure_firewall() {
info "Configuring firewall..."
# Reset UFW
ufw --force reset
# Default policies
ufw default deny incoming
ufw default allow outgoing
# Allow SSH
ufw allow ssh
# Allow email ports
ufw allow 25/tcp # SMTP
ufw allow 587/tcp # Submission
ufw allow 465/tcp # SMTPS
ufw allow 110/tcp # POP3
ufw allow 995/tcp # POP3S
ufw allow 143/tcp # IMAP
ufw allow 993/tcp # IMAPS
# Allow web ports
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
# Enable UFW
ufw --force enable
success "Firewall configured"
}
# Start and enable services
start_services() {
info "Starting and enabling services..."
# Reload systemd
systemctl daemon-reload
# Start and enable services
systemctl enable --now postgresql
systemctl enable --now postfix
systemctl enable --now dovecot
systemctl enable --now amavis
systemctl enable --now spamassassin
systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam
systemctl enable --now opendkim
# Start Apache2 and verify
info "Starting Apache2..."
systemctl enable apache2
systemctl restart apache2
# Wait a moment for Apache to start
sleep 2
if ! systemctl is-active --quiet apache2; then
error "Apache2 failed to start. Check 'journalctl -xeu apache2' for details."
fi
# Verify Apache is listening on both ports
if ! ss -tlnp | grep -q ":80 "; then
warning "Apache is not listening on port 80"
fi
if ! ss -tlnp | grep -q ":443 "; then
warning "Apache is not listening on port 443"
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 "PostfixAdmin URL: https://$HOSTNAME/postfixadmin/"
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/postfixadmin/setup.php to complete PostfixAdmin setup."
echo "3. Create your first domain and mailbox in PostfixAdmin."
echo "4. 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 -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 function to run all setup steps
main() {
log "Starting email server setup script"
check_root
verify_email_storage
get_configuration
update_system
install_packages
configure_postgresql
configure_postfix
configure_dovecot
configure_opendkim
configure_amavis
get_ssl_certificates
install_postfixadmin
configure_firewall
start_services
display_final_info
log "Email server setup completed successfully"
}
# Run main function
main "$@"