diff --git a/prepare-email-storage.sh b/prepare-email-storage.sh
old mode 100644
new mode 100755
diff --git a/setup-email-server.sh b/setup-email-server.sh
index 9db4258..6a3ac98 100755
--- a/setup-email-server.sh
+++ b/setup-email-server.sh
@@ -2,9 +2,10 @@
# Email Server Setup Script
# Components: Postfix, Dovecot, Amavis, SpamAssassin, OpenDKIM, PostgreSQL, PostfixAdmin
-# SSL: Let's Encrypt (DNS-01 Challenge)
+# SSL: Let's Encrypt
+# Web Server: Nginx
# Author: Email Server Setup Script
-# Date: Sun Aug 3 15:05:28 EDT 2025
+# Date: $(date)
set -euo pipefail
@@ -24,7 +25,7 @@ DB_USER="postfix"
DB_PASSWORD=""
POSTFIXADMIN_PASSWORD=""
WEBROOT="/var/www/postfixadmin"
-POSTFIXADMIN_VHOST_FILE="/etc/apache2/sites-available/postfixadmin.conf"
+POSTFIXADMIN_VERSION="postfixadmin-3.3.11.tar.gz"
# Logging
LOG_FILE="/var/log/email-server-setup.log"
@@ -61,46 +62,6 @@ check_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}"
@@ -142,33 +103,6 @@ update_system() {
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 \
@@ -177,6 +111,8 @@ install_packages() {
lsb-release \
software-properties-common \
certbot \
+ python3-certbot-nginx \
+ nginx \
ufw
# Install PostgreSQL
@@ -217,10 +153,8 @@ install_packages() {
# Install OpenDKIM
apt install -y opendkim opendkim-tools
- # Install PHP and Apache for PostfixAdmin
+ # Install PHP and extensions for PostfixAdmin
apt install -y \
- apache2 \
- php \
php-fpm \
php-cli \
php-pgsql \
@@ -242,13 +176,13 @@ configure_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 "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 domains (
+CREATE TABLE IF NOT EXISTS domains (
domain varchar(50) NOT NULL,
description varchar(255),
aliases int NOT NULL default '0',
@@ -263,7 +197,7 @@ CREATE TABLE domains (
PRIMARY KEY (domain)
);
-CREATE TABLE aliases (
+CREATE TABLE IF NOT EXISTS aliases (
address varchar(255) NOT NULL,
goto text NOT NULL,
domain varchar(255) NOT NULL,
@@ -273,7 +207,7 @@ CREATE TABLE aliases (
PRIMARY KEY (address)
);
-CREATE TABLE mailbox (
+CREATE TABLE IF NOT EXISTS mailbox (
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
name varchar(255) NOT NULL default '',
@@ -287,7 +221,7 @@ CREATE TABLE mailbox (
PRIMARY KEY (username)
);
-CREATE TABLE domain_admins (
+CREATE TABLE IF NOT EXISTS domain_admins (
username varchar(255) NOT NULL,
domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
@@ -295,7 +229,7 @@ CREATE TABLE domain_admins (
PRIMARY KEY (username, domain)
);
-CREATE TABLE admin (
+CREATE TABLE IF NOT EXISTS admin (
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
created timestamp NOT NULL default now(),
@@ -304,7 +238,7 @@ CREATE TABLE admin (
PRIMARY KEY (username)
);
-CREATE TABLE log (
+CREATE TABLE IF NOT EXISTS log (
timestamp timestamp NOT NULL default now(),
username varchar(255) NOT NULL default '',
domain varchar(255) NOT NULL default '',
@@ -312,7 +246,7 @@ CREATE TABLE log (
data text NOT NULL default ''
);
-CREATE TABLE vacation (
+CREATE TABLE IF NOT EXISTS vacation (
email varchar(255) NOT NULL,
subject varchar(255) NOT NULL,
body text NOT NULL,
@@ -323,14 +257,14 @@ CREATE TABLE vacation (
PRIMARY KEY (email)
);
-CREATE TABLE quota (
+CREATE TABLE IF NOT EXISTS quota (
username varchar(255) NOT NULL,
path varchar(100) NOT NULL,
current bigint NOT NULL default 0,
PRIMARY KEY (username, path)
);
-CREATE TABLE quota2 (
+CREATE TABLE IF NOT EXISTS quota2 (
username varchar(100) NOT NULL,
bytes bigint NOT NULL default 0,
messages int NOT NULL default 0,
@@ -370,7 +304,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 = /mnt/MainEmail
+virtual_mailbox_base = /var/mail/vhosts
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
@@ -494,10 +428,10 @@ smtp-amavis unix - - - - 2 smtp
EOF
# Create virtual mailbox directory
- mkdir -p /mnt/MainEmail
+ mkdir -p /var/mail/vhosts
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
+ 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"
}
@@ -519,7 +453,7 @@ EOF
# 10-mail.conf
cat > /etc/dovecot/conf.d/10-mail.conf << EOF
-mail_location = maildir:/mnt/MainEmail/%d/%n
+mail_location = maildir:/var/mail/vhosts/%d/%n
namespace inbox {
inbox = yes
}
@@ -547,7 +481,7 @@ passdb {
}
userdb {
driver = static
- args = uid=vmail gid=vmail home=/mnt/MainEmail/%d/%n
+ args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
EOF
@@ -762,46 +696,20 @@ EOF
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
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
+ 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
- chmod -R 755 $WEBROOT
+ find $WEBROOT -type d -exec chmod 755 {} \;
+ find $WEBROOT -type f -exec chmod 644 {} \;
# Create PostfixAdmin configuration
cat > $WEBROOT/config.local.php << EOF
@@ -835,70 +743,64 @@ function maildir_name_hook(\$domain, \$user) {
?>
EOF
- # Configure Apache virtual host
- cat > "$POSTFIXADMIN_VHOST_FILE" << EOF
-
- ServerName $HOSTNAME
- Redirect permanent / https://$HOSTNAME/
-
+ # 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;
+}
-
- 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
-
-
- Options -Indexes
- AllowOverride All
- Require all granted
-
-
- # PHP configuration
-
- SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
-
-
- ErrorLog \${APACHE_LOG_DIR}/postfixadmin_error.log
- CustomLog \${APACHE_LOG_DIR}/postfixadmin_access.log combined
-
+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
- # Stop Apache before configuration changes
- if systemctl is-active --quiet apache2; then
- systemctl stop apache2
+ # 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
- # Enable required Apache modules
- a2enmod ssl
- a2enmod rewrite
- a2enmod proxy
- a2enmod proxy_fcgi
- a2enmod setenvif
+ # Certbot automatically sets up a cronjob for renewal with the nginx plugin
- # 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"
+ success "SSL certificates obtained and Nginx configured for HTTPS"
}
# Configure firewall
@@ -950,28 +852,9 @@ start_services() {
systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam
systemctl enable --now opendkim
+ systemctl enable --now nginx
+ systemctl enable --now php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm
- # 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"
}
@@ -995,10 +878,10 @@ display_final_info() {
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 "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!"
@@ -1009,6 +892,12 @@ display_final_info() {
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"
@@ -1020,27 +909,26 @@ display_final_info() {
echo -e "${GREEN}Setup completed successfully!${NC}"
}
-# Main function to run all setup steps
+# Main execution
main() {
- log "Starting email server setup script"
+ echo -e "${BLUE}Email Server Setup Script${NC}"
+ echo "========================="
+ echo
check_root
- verify_email_storage
get_configuration
update_system
install_packages
configure_postgresql
+ install_postfixadmin
+ get_ssl_certificates
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