924 lines
26 KiB
Bash
Executable File
924 lines
26 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
|
|
}
|
|
|
|
# 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; then
|
|
warning "Nginx is running and will be stopped to allow Apache to run."
|
|
systemctl stop nginx
|
|
systemctl disable nginx
|
|
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 = /var/mail/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
|
|
|
|
# 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 /var/mail/vhosts
|
|
groupadd -g 5000 vmail 2>/dev/null || true
|
|
useradd -g vmail -u 5000 vmail -d /var/mail/vhosts -m 2>/dev/null || true
|
|
chown -R vmail:vmail /var/mail/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:/var/mail/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=/var/mail/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..."
|
|
|
|
# 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/public
|
|
|
|
SSLEngine on
|
|
SSLCertificateFile /etc/letsencrypt/live/$HOSTNAME/fullchain.pem
|
|
SSLCertificateKeyFile /etc/letsencrypt/live/$HOSTNAME/privkey.pem
|
|
|
|
<Directory $WEBROOT/public>
|
|
Options -Indexes
|
|
AllowOverride All
|
|
Require all granted
|
|
</Directory>
|
|
|
|
ErrorLog \${APACHE_LOG_DIR}/postfixadmin_error.log
|
|
CustomLog \${APACHE_LOG_DIR}/postfixadmin_access.log combined
|
|
</VirtualHost>
|
|
EOF
|
|
|
|
# Enable Apache modules and site
|
|
a2enmod ssl
|
|
a2enmod rewrite
|
|
a2ensite postfixadmin
|
|
a2dissite 000-default
|
|
|
|
# Check Apache config before reloading
|
|
if ! apachectl configtest &> /dev/null; 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
|
|
info "Starting Apache2..."
|
|
systemctl enable --now apache2
|
|
if ! systemctl is-active --quiet apache2; then
|
|
error "Apache2 failed to start. Check 'journalctl -xeu apache2' for details."
|
|
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}"
|
|
} # <-- THIS IS THE MISSING BRACE
|
|
|
|
# Run main function
|
|
main "$@" |