880 lines
26 KiB
Bash
Executable File
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 "$@" |