This commit is contained in:
Tommy Parnell
2025-08-03 16:19:45 -04:00
parent 55ad46094c
commit 44db7d06cb
2 changed files with 99 additions and 211 deletions

0
prepare-email-storage.sh Normal file → Executable file
View File

View File

@@ -2,9 +2,10 @@
# Email Server Setup Script # Email Server Setup Script
# Components: Postfix, Dovecot, Amavis, SpamAssassin, OpenDKIM, PostgreSQL, PostfixAdmin # 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 # Author: Email Server Setup Script
# Date: Sun Aug 3 15:05:28 EDT 2025 # Date: $(date)
set -euo pipefail set -euo pipefail
@@ -24,7 +25,7 @@ DB_USER="postfix"
DB_PASSWORD="" DB_PASSWORD=""
POSTFIXADMIN_PASSWORD="" POSTFIXADMIN_PASSWORD=""
WEBROOT="/var/www/postfixadmin" WEBROOT="/var/www/postfixadmin"
POSTFIXADMIN_VHOST_FILE="/etc/apache2/sites-available/postfixadmin.conf" POSTFIXADMIN_VERSION="postfixadmin-3.3.11.tar.gz"
# Logging # Logging
LOG_FILE="/var/log/email-server-setup.log" LOG_FILE="/var/log/email-server-setup.log"
@@ -61,46 +62,6 @@ check_root() {
fi 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 user input
get_configuration() { get_configuration() {
echo -e "${BLUE}Email Server Configuration${NC}" echo -e "${BLUE}Email Server Configuration${NC}"
@@ -142,33 +103,6 @@ update_system() {
install_packages() { install_packages() {
info "Installing required 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 # Install basic packages
apt install -y \ apt install -y \
curl \ curl \
@@ -177,6 +111,8 @@ install_packages() {
lsb-release \ lsb-release \
software-properties-common \ software-properties-common \
certbot \ certbot \
python3-certbot-nginx \
nginx \
ufw ufw
# Install PostgreSQL # Install PostgreSQL
@@ -217,10 +153,8 @@ install_packages() {
# Install OpenDKIM # Install OpenDKIM
apt install -y opendkim opendkim-tools apt install -y opendkim opendkim-tools
# Install PHP and Apache for PostfixAdmin # Install PHP and extensions for PostfixAdmin
apt install -y \ apt install -y \
apache2 \
php \
php-fpm \ php-fpm \
php-cli \ php-cli \
php-pgsql \ php-pgsql \
@@ -242,13 +176,13 @@ configure_postgresql() {
systemctl enable postgresql systemctl enable postgresql
# Create database and user # Create database and user
sudo -u postgres psql -c "CREATE DATABASE $DB_NAME;" 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';" 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;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;"
# Create tables for Postfix # Create tables for Postfix
sudo -u postgres psql -d "$DB_NAME" << 'EOF' sudo -u postgres psql -d "$DB_NAME" << 'EOF'
CREATE TABLE domains ( CREATE TABLE IF NOT EXISTS domains (
domain varchar(50) NOT NULL, domain varchar(50) NOT NULL,
description varchar(255), description varchar(255),
aliases int NOT NULL default '0', aliases int NOT NULL default '0',
@@ -263,7 +197,7 @@ CREATE TABLE domains (
PRIMARY KEY (domain) PRIMARY KEY (domain)
); );
CREATE TABLE aliases ( CREATE TABLE IF NOT EXISTS aliases (
address varchar(255) NOT NULL, address varchar(255) NOT NULL,
goto text NOT NULL, goto text NOT NULL,
domain varchar(255) NOT NULL, domain varchar(255) NOT NULL,
@@ -273,7 +207,7 @@ CREATE TABLE aliases (
PRIMARY KEY (address) PRIMARY KEY (address)
); );
CREATE TABLE mailbox ( CREATE TABLE IF NOT EXISTS mailbox (
username varchar(255) NOT NULL, username varchar(255) NOT NULL,
password varchar(255) NOT NULL, password varchar(255) NOT NULL,
name varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '',
@@ -287,7 +221,7 @@ CREATE TABLE mailbox (
PRIMARY KEY (username) PRIMARY KEY (username)
); );
CREATE TABLE domain_admins ( CREATE TABLE IF NOT EXISTS domain_admins (
username varchar(255) NOT NULL, username varchar(255) NOT NULL,
domain varchar(255) NOT NULL, domain varchar(255) NOT NULL,
created timestamp NOT NULL default now(), created timestamp NOT NULL default now(),
@@ -295,7 +229,7 @@ CREATE TABLE domain_admins (
PRIMARY KEY (username, domain) PRIMARY KEY (username, domain)
); );
CREATE TABLE admin ( CREATE TABLE IF NOT EXISTS admin (
username varchar(255) NOT NULL, username varchar(255) NOT NULL,
password varchar(255) NOT NULL, password varchar(255) NOT NULL,
created timestamp NOT NULL default now(), created timestamp NOT NULL default now(),
@@ -304,7 +238,7 @@ CREATE TABLE admin (
PRIMARY KEY (username) PRIMARY KEY (username)
); );
CREATE TABLE log ( CREATE TABLE IF NOT EXISTS log (
timestamp timestamp NOT NULL default now(), timestamp timestamp NOT NULL default now(),
username varchar(255) NOT NULL default '', username varchar(255) NOT NULL default '',
domain varchar(255) NOT NULL default '', domain varchar(255) NOT NULL default '',
@@ -312,7 +246,7 @@ CREATE TABLE log (
data text NOT NULL default '' data text NOT NULL default ''
); );
CREATE TABLE vacation ( CREATE TABLE IF NOT EXISTS vacation (
email varchar(255) NOT NULL, email varchar(255) NOT NULL,
subject varchar(255) NOT NULL, subject varchar(255) NOT NULL,
body text NOT NULL, body text NOT NULL,
@@ -323,14 +257,14 @@ CREATE TABLE vacation (
PRIMARY KEY (email) PRIMARY KEY (email)
); );
CREATE TABLE quota ( CREATE TABLE IF NOT EXISTS quota (
username varchar(255) NOT NULL, username varchar(255) NOT NULL,
path varchar(100) NOT NULL, path varchar(100) NOT NULL,
current bigint NOT NULL default 0, current bigint NOT NULL default 0,
PRIMARY KEY (username, path) PRIMARY KEY (username, path)
); );
CREATE TABLE quota2 ( CREATE TABLE IF NOT EXISTS quota2 (
username varchar(100) NOT NULL, username varchar(100) NOT NULL,
bytes bigint NOT NULL default 0, bytes bigint NOT NULL default 0,
messages int 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_domains = pgsql:/etc/postfix/pgsql-virtual-mailbox-domains.cf
virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual-alias-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_minimum_uid = 100
virtual_uid_maps = static:5000 virtual_uid_maps = static:5000
virtual_gid_maps = static:5000 virtual_gid_maps = static:5000
@@ -494,10 +428,10 @@ smtp-amavis unix - - - - 2 smtp
EOF EOF
# Create virtual mailbox directory # Create virtual mailbox directory
mkdir -p /mnt/MainEmail mkdir -p /var/mail/vhosts
groupadd -g 5000 vmail 2>/dev/null || true groupadd -g 5000 vmail 2>/dev/null || true
useradd -g vmail -u 5000 vmail -d /mnt/MainEmail -m 2>/dev/null || true useradd -g vmail -u 5000 vmail -d /var/mail/vhosts -m 2>/dev/null || true
chown -R vmail:vmail /mnt/MainEmail chown -R vmail:vmail /var/mail/vhosts
success "Postfix configured" success "Postfix configured"
} }
@@ -519,7 +453,7 @@ EOF
# 10-mail.conf # 10-mail.conf
cat > /etc/dovecot/conf.d/10-mail.conf << EOF 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 { namespace inbox {
inbox = yes inbox = yes
} }
@@ -547,7 +481,7 @@ passdb {
} }
userdb { userdb {
driver = static driver = static
args = uid=vmail gid=vmail home=/mnt/MainEmail/%d/%n args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
} }
EOF EOF
@@ -762,46 +696,20 @@ EOF
success "Amavis and SpamAssassin configured" success "Amavis and SpamAssassin configured"
} }
# Get Let's Encrypt certificates using the DNS challenge # Install PostfixAdmin
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..." info "Installing PostfixAdmin..."
# Download PostfixAdmin # Download PostfixAdmin
cd /tmp cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.3.11.tar.gz wget -q https://github.com/postfixadmin/postfixadmin/archive/$POSTFIXADMIN_VERSION || error "Failed to download PostfixAdmin"
tar -xzf postfixadmin-3.3.11.tar.gz tar -xzf $POSTFIXADMIN_VERSION
mv postfixadmin-postfixadmin-3.3.11 $WEBROOT mv postfixadmin-postfixadmin-3.3.11 $WEBROOT
# Set permissions # Set permissions
chown -R www-data:www-data $WEBROOT 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 # Create PostfixAdmin configuration
cat > $WEBROOT/config.local.php << EOF cat > $WEBROOT/config.local.php << EOF
@@ -835,70 +743,64 @@ function maildir_name_hook(\$domain, \$user) {
?> ?>
EOF EOF
# Configure Apache virtual host # Configure Nginx virtual host for PostfixAdmin
cat > "$POSTFIXADMIN_VHOST_FILE" << EOF cat > /etc/nginx/sites-available/postfixadmin.conf << EOF
<VirtualHost *:80> server {
ServerName $HOSTNAME listen 80;
Redirect permanent / https://$HOSTNAME/ server_name $HOSTNAME;
</VirtualHost> return 301 https://\$server_name\$request_uri;
}
<VirtualHost *:443> server {
ServerName $HOSTNAME listen 443 ssl http2;
DocumentRoot $WEBROOT server_name $HOSTNAME;
DirectoryIndex index.php index.html
root $WEBROOT/public;
SSLEngine on index index.php index.html index.htm;
SSLCertificateFile /etc/letsencrypt/live/$HOSTNAME/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$HOSTNAME/privkey.pem location / {
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 try_files \$uri \$uri/ /index.php?\$args;
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 }
SSLHonorCipherOrder on
location ~ \.php$ {
<Directory $WEBROOT> include snippets/fastcgi-php.conf;
Options -Indexes fastcgi_pass unix:/var/run/php/php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm.sock;
AllowOverride All fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
Require all granted fastcgi_read_timeout 600s;
</Directory> }
# PHP configuration # SSL configuration, to be updated by Certbot
<FilesMatch "\.php$"> ssl_certificate /etc/letsencrypt/live/$HOSTNAME/fullchain.pem;
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost" ssl_certificate_key /etc/letsencrypt/live/$HOSTNAME/privkey.pem;
</FilesMatch> ssl_trusted_certificate /etc/letsencrypt/live/$HOSTNAME/chain.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ErrorLog \${APACHE_LOG_DIR}/postfixadmin_error.log }
CustomLog \${APACHE_LOG_DIR}/postfixadmin_access.log combined
</VirtualHost>
EOF EOF
# Stop Apache before configuration changes # Enable Nginx site and disable default
if systemctl is-active --quiet apache2; then ln -s /etc/nginx/sites-available/postfixadmin.conf /etc/nginx/sites-enabled/
systemctl stop apache2 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 fi
# Enable required Apache modules # Certbot automatically sets up a cronjob for renewal with the nginx plugin
a2enmod ssl
a2enmod rewrite
a2enmod proxy
a2enmod proxy_fcgi
a2enmod setenvif
# Enable PHP-FPM configuration success "SSL certificates obtained and Nginx configured for HTTPS"
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
@@ -950,28 +852,9 @@ start_services() {
systemctl enable --now clamav-daemon systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam systemctl enable --now clamav-freshclam
systemctl enable --now opendkim 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" success "All services started and enabled"
} }
@@ -995,10 +878,10 @@ display_final_info() {
cat /etc/opendkim/keys/$DOMAIN/mail.txt cat /etc/opendkim/keys/$DOMAIN/mail.txt
echo echo
echo -e "${YELLOW}=== Next Steps ===${NC}" echo -e "${YELLOW}=== Next Steps ===${NC}"
echo "1. Add the DNS records shown above." echo "1. Add the DNS records shown above"
echo "2. Visit https://$HOSTNAME/postfixadmin/setup.php to complete PostfixAdmin setup." echo "2. Visit https://$HOSTNAME/postfixadmin/setup.php to complete PostfixAdmin setup"
echo "3. Create your first domain and mailbox in PostfixAdmin." echo "3. Create your first domain and mailbox in PostfixAdmin"
echo "4. Test email sending and receiving." echo "4. Test email sending and receiving"
echo echo
echo -e "${BLUE}=== Multiple Domain Support ===${NC}" echo -e "${BLUE}=== Multiple Domain Support ===${NC}"
echo "This server supports unlimited virtual domains!" echo "This server supports unlimited virtual domains!"
@@ -1009,6 +892,12 @@ display_final_info() {
echo "3. Configure the domain in PostfixAdmin" echo "3. Configure the domain in PostfixAdmin"
echo "4. Create mailboxes for the new domain" echo "4. Create mailboxes for the new domain"
echo 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 -e "${BLUE}=== Application SMTP Configuration ===${NC}"
echo "Your applications can send email using these settings:" echo "Your applications can send email using these settings:"
echo " Host: $HOSTNAME" echo " Host: $HOSTNAME"
@@ -1020,27 +909,26 @@ display_final_info() {
echo -e "${GREEN}Setup completed successfully!${NC}" echo -e "${GREEN}Setup completed successfully!${NC}"
} }
# Main function to run all setup steps # Main execution
main() { main() {
log "Starting email server setup script" echo -e "${BLUE}Email Server Setup Script${NC}"
echo "========================="
echo
check_root check_root
verify_email_storage
get_configuration get_configuration
update_system update_system
install_packages install_packages
configure_postgresql configure_postgresql
install_postfixadmin
get_ssl_certificates
configure_postfix configure_postfix
configure_dovecot configure_dovecot
configure_opendkim configure_opendkim
configure_amavis configure_amavis
get_ssl_certificates
install_postfixadmin
configure_firewall configure_firewall
start_services start_services
display_final_info display_final_info
log "Email server setup completed successfully"
} }
# Run main function # Run main function