mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
315 lines
9.4 KiB
Bash
315 lines
9.4 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Hardened mail server bootstrap for Debian/Ubuntu or Arch/CachyOS hosts.
|
|
# Installs and configures: Postfix, Dovecot (IMAP/LMTP), OpenDKIM, TLS, fail2ban.
|
|
#
|
|
# Usage (run as root on the remote host):
|
|
# sudo bash scripts/bootstrap_dag_mail_server.sh \
|
|
# --mail-domain example.com \
|
|
# --mail-host mail.example.com \
|
|
# --admin-email admin@example.com
|
|
|
|
# Detect OS to choose appropriate package manager
|
|
detect_os() {
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
if [[ "$ID" == "arch" || "$ID" == "cachyos" ]]; then
|
|
echo "arch"
|
|
elif [[ "$ID_LIKE" == *"arch"* ]]; then
|
|
echo "arch"
|
|
else
|
|
echo "debian"
|
|
fi
|
|
else
|
|
# Fallback: check for apt vs pacman
|
|
if command -v pacman &>/dev/null; then
|
|
echo "arch"
|
|
else
|
|
echo "debian"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
OS_TYPE=$(detect_os)
|
|
|
|
install_packages() {
|
|
if [ "$OS_TYPE" = "arch" ]; then
|
|
pacman -Syu --noconfirm
|
|
pacman -S --noconfirm \
|
|
postfix dovecot \
|
|
opendkim \
|
|
certbot \
|
|
fail2ban ufw
|
|
else
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -y
|
|
apt-get install -y \
|
|
postfix postfix-pcre dovecot-core dovecot-imapd dovecot-lmtpd \
|
|
opendkim opendkim-tools \
|
|
certbot python3-certbot \
|
|
fail2ban ufw
|
|
fi
|
|
}
|
|
|
|
MAIL_DOMAIN=""
|
|
MAIL_HOST=""
|
|
ADMIN_EMAIL=""
|
|
MAIL_USER="vmail"
|
|
MAIL_UID="5000"
|
|
MAIL_GID="5000"
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--mail-domain)
|
|
MAIL_DOMAIN="$2"; shift 2 ;;
|
|
--mail-host)
|
|
MAIL_HOST="$2"; shift 2 ;;
|
|
--admin-email)
|
|
ADMIN_EMAIL="$2"; shift 2 ;;
|
|
--mail-user)
|
|
MAIL_USER="$2"; shift 2 ;;
|
|
--help)
|
|
sed -n '1,40p' "$0"
|
|
exit 0 ;;
|
|
*)
|
|
echo "Unknown arg: $1" >&2
|
|
exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$MAIL_DOMAIN" ] || [ -z "$MAIL_HOST" ] || [ -z "$ADMIN_EMAIL" ]; then
|
|
echo "Missing required args. Need --mail-domain, --mail-host, --admin-email" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Run as root (sudo)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[1/9] Detecting OS and installing packages..."
|
|
install_packages
|
|
|
|
echo "[2/9] Hostname and service accounts..."
|
|
hostnamectl set-hostname "$MAIL_HOST"
|
|
if ! getent group "$MAIL_USER" >/dev/null; then
|
|
groupadd -g "$MAIL_GID" "$MAIL_USER"
|
|
fi
|
|
if ! id "$MAIL_USER" >/dev/null 2>&1; then
|
|
useradd -g "$MAIL_USER" -u "$MAIL_UID" "$MAIL_USER" -d /var/mail/vhosts -m -s /usr/sbin/nologin
|
|
fi
|
|
mkdir -p /var/mail/vhosts
|
|
chown -R "$MAIL_USER":"$MAIL_USER" /var/mail/vhosts
|
|
chmod 770 /var/mail/vhosts
|
|
|
|
echo "[3/9] Postfix base config..."
|
|
postconf -e "myhostname = $MAIL_HOST"
|
|
postconf -e "mydomain = $MAIL_DOMAIN"
|
|
postconf -e "myorigin = $MAIL_DOMAIN"
|
|
postconf -e "mydestination = localhost"
|
|
postconf -e "inet_interfaces = all"
|
|
postconf -e "inet_protocols = all"
|
|
postconf -e "smtpd_banner = $MAIL_HOST ESMTP"
|
|
postconf -e "smtp_tls_security_level = may"
|
|
postconf -e "smtpd_tls_security_level = may"
|
|
postconf -e "smtpd_tls_auth_only = yes"
|
|
postconf -e "smtpd_tls_loglevel = 1"
|
|
postconf -e "smtpd_tls_received_header = yes"
|
|
postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"
|
|
postconf -e "virtual_mailbox_domains = $MAIL_DOMAIN"
|
|
postconf -e "virtual_mailbox_base = /var/mail/vhosts"
|
|
postconf -e "virtual_mailbox_maps = hash:/etc/postfix/vmailbox"
|
|
postconf -e "virtual_minimum_uid = $MAIL_UID"
|
|
postconf -e "virtual_uid_maps = static:$MAIL_UID"
|
|
postconf -e "virtual_gid_maps = static:$MAIL_GID"
|
|
postconf -e "smtpd_sasl_type = dovecot"
|
|
postconf -e "smtpd_sasl_path = private/auth"
|
|
postconf -e "smtpd_sasl_auth_enable = yes"
|
|
postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination"
|
|
postconf -e "milter_default_action = accept"
|
|
postconf -e "milter_protocol = 6"
|
|
postconf -e "smtpd_milters = inet:127.0.0.1:8891"
|
|
postconf -e "non_smtpd_milters = inet:127.0.0.1:8891"
|
|
|
|
echo "[4/9] Postfix submission/smtps ports..."
|
|
if ! grep -q '^submission inet' /etc/postfix/master.cf; then
|
|
cat >> /etc/postfix/master.cf <<'EOF'
|
|
submission inet n - y - - smtpd
|
|
-o syslog_name=postfix/submission
|
|
-o smtpd_tls_security_level=encrypt
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
|
|
smtps inet n - y - - smtpd
|
|
-o syslog_name=postfix/smtps
|
|
-o smtpd_tls_wrappermode=yes
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
EOF
|
|
fi
|
|
|
|
echo "[5/9] Dovecot config..."
|
|
mkdir -p /etc/dovecot/conf.d
|
|
cat > /etc/dovecot/conf.d/10-mail.conf <<EOF
|
|
mail_location = maildir:/var/mail/vhosts/%d/%n
|
|
first_valid_uid = $MAIL_UID
|
|
last_valid_uid = $MAIL_UID
|
|
mail_uid = $MAIL_UID
|
|
mail_gid = $MAIL_GID
|
|
EOF
|
|
|
|
cat > /etc/dovecot/conf.d/10-auth.conf <<'EOF'
|
|
disable_plaintext_auth = yes
|
|
auth_mechanisms = plain login
|
|
!include auth-passwdfile.conf.ext
|
|
EOF
|
|
|
|
cat > /etc/dovecot/conf.d/auth-passwdfile.conf.ext <<'EOF'
|
|
passdb {
|
|
driver = passwd-file
|
|
args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/users
|
|
}
|
|
userdb {
|
|
driver = static
|
|
args = uid=5000 gid=5000 home=/var/mail/vhosts/%d/%n
|
|
}
|
|
EOF
|
|
|
|
mkdir -p /etc/dovecot
|
|
if [ ! -f /etc/dovecot/users ]; then
|
|
touch /etc/dovecot/users
|
|
chmod 640 /etc/dovecot/users
|
|
fi
|
|
chown root:dovecot /etc/dovecot/users || true
|
|
|
|
cat > /etc/dovecot/conf.d/10-master.conf <<'EOF'
|
|
service lmtp {
|
|
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
|
mode = 0600
|
|
user = postfix
|
|
group = postfix
|
|
}
|
|
}
|
|
service auth {
|
|
unix_listener /var/spool/postfix/private/auth {
|
|
mode = 0660
|
|
user = postfix
|
|
group = postfix
|
|
}
|
|
}
|
|
EOF
|
|
|
|
echo "[6/9] OpenDKIM config and key generation..."
|
|
mkdir -p /etc/opendkim/keys/$MAIL_DOMAIN
|
|
if [ ! -f "/etc/opendkim/keys/$MAIL_DOMAIN/default.private" ]; then
|
|
opendkim-genkey -D "/etc/opendkim/keys/$MAIL_DOMAIN" -d "$MAIL_DOMAIN" -s default || echo "WARNING: opendkim-genkey may have had issues"
|
|
fi
|
|
chown -R opendkim:opendkim /etc/opendkim/keys || true
|
|
chmod 750 /etc/opendkim/keys/$MAIL_DOMAIN || true
|
|
chmod 640 /etc/opendkim/keys/$MAIL_DOMAIN/default.private || true
|
|
|
|
# Ensure opendkim can read its config and keys
|
|
mkdir -p /etc/opendkim || true
|
|
chown -R opendkim:opendkim /etc/opendkim || true
|
|
chmod 755 /etc/opendkim || true
|
|
|
|
cat > /etc/opendkim.conf <<EOF
|
|
Syslog yes
|
|
UMask 002
|
|
Canonicalization relaxed/simple
|
|
Mode sv
|
|
SubDomains no
|
|
AutoRestart yes
|
|
AutoRestartRate 10/1h
|
|
Background yes
|
|
DNSTimeout 5
|
|
SignatureAlgorithm rsa-sha256
|
|
Socket inet:8891@127.0.0.1
|
|
UserID opendkim:opendkim
|
|
KeyTable /etc/opendkim/key.table
|
|
SigningTable refile:/etc/opendkim/signing.table
|
|
ExternalIgnoreList /etc/opendkim/trusted.hosts
|
|
InternalHosts /etc/opendkim/trusted.hosts
|
|
EOF
|
|
|
|
cat > /etc/opendkim/key.table <<EOF
|
|
default._domainkey.$MAIL_DOMAIN $MAIL_DOMAIN:default:/etc/opendkim/keys/$MAIL_DOMAIN/default.private
|
|
EOF
|
|
|
|
cat > /etc/opendkim/signing.table <<EOF
|
|
*@$MAIL_DOMAIN default._domainkey.$MAIL_DOMAIN
|
|
EOF
|
|
|
|
cat > /etc/opendkim/trusted.hosts <<EOF
|
|
127.0.0.1
|
|
localhost
|
|
$MAIL_HOST
|
|
EOF
|
|
|
|
echo "[7/9] TLS certificate (Let's Encrypt standalone or self-signed)..."
|
|
ufw allow 80/tcp || true
|
|
|
|
# Try Let's Encrypt for publicly resolvable domains
|
|
if ! certbot certonly --standalone --agree-tos --non-interactive -m "$ADMIN_EMAIL" -d "$MAIL_HOST" 2>/dev/null; then
|
|
echo "WARNING: Let's Encrypt failed (domain may be Tailscale-only). Generating self-signed cert..."
|
|
mkdir -p /etc/letsencrypt/live/"$MAIL_HOST"
|
|
openssl req -x509 -newkey rsa:2048 -keyout /etc/letsencrypt/live/"$MAIL_HOST"/privkey.pem \
|
|
-out /etc/letsencrypt/live/"$MAIL_HOST"/fullchain.pem -days 365 -nodes \
|
|
-subj "/C=US/ST=State/L=City/O=Org/CN=$MAIL_HOST" 2>/dev/null || {
|
|
echo "ERROR: Could not create TLS certificate"
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/$MAIL_HOST/fullchain.pem"
|
|
postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/$MAIL_HOST/privkey.pem"
|
|
cat > /etc/dovecot/conf.d/10-ssl.conf <<EOF
|
|
ssl = required
|
|
ssl_cert = </etc/letsencrypt/live/$MAIL_HOST/fullchain.pem
|
|
ssl_key = </etc/letsencrypt/live/$MAIL_HOST/privkey.pem
|
|
EOF
|
|
|
|
echo "[8/9] Firewall + fail2ban..."
|
|
ufw allow 22/tcp || true
|
|
ufw allow 25/tcp || true
|
|
ufw allow 587/tcp || true
|
|
ufw allow 465/tcp || true
|
|
ufw allow 993/tcp || true
|
|
ufw --force enable
|
|
|
|
cat > /etc/fail2ban/jail.d/mail.conf <<'EOF'
|
|
[postfix]
|
|
enabled = true
|
|
port = smtp,ssmtp,submission
|
|
logpath = /var/log/mail.log
|
|
|
|
[dovecot]
|
|
enabled = true
|
|
port = pop3,pop3s,imap,imaps,submission,465,sieve
|
|
logpath = /var/log/mail.log
|
|
EOF
|
|
|
|
echo "[9/9] Enable services..."
|
|
systemctl enable opendkim postfix dovecot fail2ban || true
|
|
systemctl restart opendkim || echo "WARNING: opendkim restart failed (may need configuration)"
|
|
systemctl restart postfix || true
|
|
systemctl restart dovecot || true
|
|
systemctl restart fail2ban || true
|
|
|
|
echo ""
|
|
echo "Bootstrap complete."
|
|
echo ""
|
|
echo "Create mailbox credentials (example):"
|
|
echo " doveadm pw -s SHA512-CRYPT"
|
|
echo " # then add: user@$MAIL_DOMAIN:{SHA512-CRYPT}HASH" \
|
|
"to /etc/dovecot/users and restart dovecot"
|
|
echo ""
|
|
echo "DNS records required:"
|
|
echo " A $MAIL_HOST -> <server-ip>"
|
|
echo " MX $MAIL_DOMAIN -> 10 $MAIL_HOST."
|
|
echo " SPF $MAIL_DOMAIN -> v=spf1 mx -all"
|
|
echo " DKIM default._domainkey.$MAIL_DOMAIN -> value from /etc/opendkim/keys/$MAIL_DOMAIN/default.txt"
|
|
echo " DMARC _dmarc.$MAIL_DOMAIN -> v=DMARC1; p=quarantine; rua=mailto:postmaster@$MAIL_DOMAIN"
|
|
echo ""
|
|
echo "PTR (reverse DNS) should map server IP -> $MAIL_HOST (set at your VPS provider)."
|