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
# 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
<VirtualHost *:80>
ServerName $HOSTNAME
Redirect permanent / https://$HOSTNAME/
</VirtualHost>
# 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;
}
<VirtualHost *:443>
ServerName $HOSTNAME
DocumentRoot $WEBROOT
DirectoryIndex index.php index.html
server {
listen 443 ssl http2;
server_name $HOSTNAME;
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
root $WEBROOT/public;
index index.php index.html index.htm;
<Directory $WEBROOT>
Options -Indexes
AllowOverride All
Require all granted
</Directory>
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
# PHP configuration
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
</FilesMatch>
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;
}
ErrorLog \${APACHE_LOG_DIR}/postfixadmin_error.log
CustomLog \${APACHE_LOG_DIR}/postfixadmin_access.log combined
</VirtualHost>
# 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,27 +852,8 @@ start_services() {
systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam
systemctl enable --now opendkim
# 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
systemctl enable --now nginx
systemctl enable --now php$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;")-fpm
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