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