This commit is contained in:
Tommy Parnell
2025-08-03 16:31:47 -04:00
parent eb201d9865
commit b1c0fcf1f1

View File

@@ -69,29 +69,29 @@ check_root() {
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"
}
@@ -105,7 +105,7 @@ update_system() {
# Install required packages
install_packages() {
info "Installing required packages..."
# Install basic packages
apt install -y \
curl \
@@ -117,10 +117,10 @@ install_packages() {
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'"
@@ -128,7 +128,7 @@ install_packages() {
postfix \
postfix-pgsql \
postfix-policyd-spf-python
# Install Dovecot
apt install -y \
dovecot-core \
@@ -136,7 +136,7 @@ install_packages() {
dovecot-pop3d \
dovecot-lmtpd \
dovecot-pgsql
# Install Amavis and SpamAssassin
apt install -y \
amavisd-new \
@@ -144,7 +144,7 @@ install_packages() {
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
@@ -152,10 +152,10 @@ install_packages() {
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 \
@@ -166,23 +166,23 @@ install_packages() {
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;" || warning "Database already exists, skipping."
sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';" || warning "User already exists, skipping."
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 IF NOT EXISTS domains (
@@ -274,25 +274,124 @@ CREATE TABLE IF NOT EXISTS quota2 (
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"
}
# ---
# 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
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
# Set permissions
chown -R www-data:www-data $WEBROOT
find $WEBROOT -type d -exec chmod 755 {} \;
find $WEBROOT -type f -exec chmod 644 {} \;
# 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 Nginx virtual host with a basic HTTP block
cat > /etc/nginx/sites-available/postfixadmin.conf << EOF
server {
listen 80;
server_name $HOSTNAME;
root $WEBROOT/public;
index index.php index.html index.htm;
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;
}
}
EOF
# Enable Nginx site and disable default
ln -s /etc/nginx/sites-available/postfixadmin.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
success "PostfixAdmin installed and Nginx configured for HTTP"
}
# 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
@@ -307,7 +406,7 @@ mydestination = localhost
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 # <-- CHANGED: Mailbox base is now the volume path
virtual_mailbox_base = $MAIL_VOLUME_PATH/vhosts
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
@@ -377,75 +476,23 @@ 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 and set permissions
mkdir -p $MAIL_VOLUME_PATH/vhosts # <-- CHANGED: Directory is now created on the volume
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 # <-- CHANGED: Permissions set on the volume
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
@@ -456,7 +503,7 @@ EOF
# 10-mail.conf
cat > /etc/dovecot/conf.d/10-mail.conf << EOF
mail_location = maildir:$MAIL_VOLUME_PATH/vhosts/%d/%n # <-- CHANGED: Mail location is now the volume path
mail_location = maildir:$MAIL_VOLUME_PATH/vhosts/%d/%n
namespace inbox {
inbox = yes
}
@@ -484,7 +531,7 @@ passdb {
}
userdb {
driver = static
args = uid=vmail gid=vmail home=$MAIL_VOLUME_PATH/vhosts/%d/%n # <-- CHANGED: User home directory is now on the volume
args = uid=vmail gid=vmail home=$MAIL_VOLUME_PATH/vhosts/%d/%n
}
EOF
@@ -558,28 +605,28 @@ 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
@@ -597,10 +644,10 @@ 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
@@ -610,7 +657,7 @@ $DOMAIN
EOF
success "OpenDKIM configured"
# Display DNS record
info "Add this TXT record to your DNS:"
echo "mail._domainkey.$DOMAIN"
@@ -620,10 +667,10 @@ EOF
# 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;
@@ -695,131 +742,24 @@ EOF
# Add amavis user to clamav group
usermod -a -G clamav amavis
success "Amavis and SpamAssassin configured"
}
# Install PostfixAdmin
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
# Set permissions
chown -R www-data:www-data $WEBROOT
find $WEBROOT -type d -exec chmod 755 {} \;
find $WEBROOT -type f -exec chmod 644 {} \;
# 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 Nginx virtual host for PostfixAdmin
cat > /etc/nginx/sites-available/postfixadmin.conf << EOF
server {
listen 80;
server_name $HOSTNAME;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name $HOSTNAME;
root $WEBROOT/public;
index index.php index.html index.htm;
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;
}
# SSL configuration, to be updated by Certbot
ssl_certificate /etc/letsencrypt/live/$HOSTNAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$HOSTNAME/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/$HOSTNAME/chain.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
}
EOF
# Enable Nginx site and disable default
ln -s /etc/nginx/sites-available/postfixadmin.conf /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
# Reload Nginx to apply changes
systemctl reload nginx
success "PostfixAdmin installed and Nginx configured"
}
# Get Let's Encrypt certificates
get_ssl_certificates() {
info "Obtaining Let's Encrypt certificates..."
# Use certbot with the nginx plugin
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"
}
# 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
@@ -828,24 +768,24 @@ configure_firewall() {
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
@@ -857,7 +797,7 @@ start_services() {
systemctl enable --now opendkim
systemctl enable --now nginx
systemctl enable --now php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm
success "All services started and enabled"
}
@@ -869,7 +809,7 @@ display_final_info() {
echo "Primary Domain: $DOMAIN"
echo "Hostname: $HOSTNAME"
echo "Admin Email: $ADMIN_EMAIL"
echo "Mailbox Location: $MAIL_VOLUME_PATH/vhosts" # <-- CHANGED: Display the new path
echo "Mailbox Location: $MAIL_VOLUME_PATH/vhosts"
echo "PostfixAdmin URL: https://$HOSTNAME/postfixadmin/"
echo
echo -e "${YELLOW}=== DNS Records to Add for $DOMAIN ===${NC}"
@@ -918,20 +858,20 @@ main() {
echo -e "${BLUE}Email Server Setup Script${NC}"
echo "========================="
echo
check_root
get_configuration
update_system
install_packages
configure_postgresql
install_postfixadmin
get_ssl_certificates
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
start_services
display_final_info
}