909 lines
35 KiB
Bash
Executable File
909 lines
35 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
(
|
||
export HISTFILE=
|
||
set +o history
|
||
set -euo pipefail
|
||
|
||
##############################################
|
||
# MailWolt #
|
||
# Bootstrap Installer v1.0 #
|
||
##############################################
|
||
|
||
# ===== CLI-Flags (-dev / -stag) =====
|
||
APP_ENV="${APP_ENV:-production}"
|
||
APP_DEBUG="${APP_DEBUG:-false}"
|
||
|
||
DEV_MODE=0
|
||
STAG_MODE=0
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-dev)
|
||
DEV_MODE=1
|
||
APP_ENV="local"
|
||
APP_DEBUG="true"
|
||
;;
|
||
-stag|-staging)
|
||
STAG_MODE=1
|
||
APP_ENV="staging"
|
||
APP_DEBUG="false"
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# ===== Branding & Pfade =====
|
||
APP_NAME="${APP_NAME:-MailWolt}"
|
||
|
||
APP_USER="${APP_USER:-mailwolt}"
|
||
APP_GROUP="${APP_GROUP:-www-data}"
|
||
|
||
APP_DIR="/var/www/${APP_USER}"
|
||
|
||
ADMIN_USER="${APP_USER}"
|
||
ADMIN_EMAIL="admin@localhost"
|
||
ADMIN_PASS="ChangeMe"
|
||
|
||
CONF_BASE="/etc/${APP_USER}"
|
||
CERT_DIR="${CONF_BASE}/ssl"
|
||
CERT="${CERT_DIR}/cert.pem"
|
||
KEY="${CERT_DIR}/key.pem"
|
||
|
||
NGINX_SITE="/etc/nginx/sites-available/${APP_USER}.conf"
|
||
NGINX_SITE_LINK="/etc/nginx/sites-enabled/${APP_USER}.conf"
|
||
|
||
DB_NAME="${DB_NAME:-${APP_USER}}"
|
||
DB_USER="${DB_USER:-${APP_USER}}"
|
||
DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
|
||
|
||
GIT_REPO="${GIT_REPO:-http://10.10.20.81:3000/boban/mailwolt.git}"
|
||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||
|
||
NODE_SETUP="${NODE_SETUP:-deb}"
|
||
|
||
# ===== Styling =====
|
||
GREEN="\033[1;32m"; YELLOW="\033[1;33m"; RED="\033[1;31m"; CYAN="\033[1;36m"; GREY="\033[0;90m"; NC="\033[0m"
|
||
BAR="──────────────────────────────────────────────────────────────────────────────"
|
||
|
||
header() {
|
||
echo -e "${CYAN}${BAR}${NC}"
|
||
echo -e "${CYAN} 888b d888 d8b 888 888 888 888 888 ${NC}"
|
||
echo -e "${CYAN} 8888b d8888 Y8P 888 888 o 888 888 888 ${NC}"
|
||
echo -e "${CYAN} 88888b.d88888 888 888 d8b 888 888 888 ${NC}"
|
||
echo -e "${CYAN} 888Y88888P888 8888b. 888 888 888 d888b 888 .d88b. 888 888888 ${NC}"
|
||
echo -e "${CYAN} 888 Y888P 888 '88b 888 888 888d88888b888 d88''88b 888 888 ${NC}"
|
||
echo -e "${CYAN} 888 Y8P 888 .d888888 888 888 88888P Y88888 888 888 888 888 ${NC}"
|
||
echo -e "${CYAN} 888 ' 888 888 888 888 888 8888P Y8888 Y88..88P 888 Y88b. ${NC}"
|
||
echo -e "${CYAN} 888 888 'Y888888 888 888 888P Y888 'Y88P' 888 'Y888 ${NC}"
|
||
echo -e "${CYAN}${BAR}${NC}"
|
||
echo
|
||
}
|
||
|
||
print_bootstrap_summary() {
|
||
local ip="$1"
|
||
local admin_user="$2"
|
||
local admin_pass="$3"
|
||
local GREEN="\033[1;32m"
|
||
local CYAN="\033[1;36m"
|
||
local GREY="\033[0;90m"
|
||
local YELLOW="\033[1;33m"
|
||
local RED="\033[1;31m"
|
||
local NC="\033[0m"
|
||
local BAR="${BAR:-──────────────────────────────────────────────────────────────────────────────}"
|
||
local scheme="http"
|
||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then scheme="https"; fi
|
||
|
||
echo
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo -e "${GREEN} ✔ ${APP_NAME} Bootstrap erfolgreich abgeschlossen${NC}"
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo -e " Bootstrap-Login (nur für ERSTEN Login & Wizard):"
|
||
echo -e " User: ${YELLOW}${admin_user}${NC}"
|
||
echo -e " Passwort: ${RED}${admin_pass}${NC}"
|
||
echo
|
||
echo -e " Aufruf: ${CYAN}${scheme}://${ip}${NC}"
|
||
echo -e " Laravel Root: ${GREY}${APP_DIR}${NC}"
|
||
echo -e " Nginx Site: ${GREY}${NGINX_SITE}${NC}"
|
||
echo -e " Self-signed Cert: ${GREY}${CERT_DIR}/{cert.pem,key.pem}${NC}"
|
||
echo -e " Postfix/Dovecot Ports aktiv: ${GREY}25, 465, 587, 110, 995, 143, 993${NC}"
|
||
echo -e " Rspamd/OpenDKIM: ${GREY}aktiv (DKIM-Keys später im Wizard)${NC}"
|
||
echo -e " Monit (Watchdog): ${GREY}installiert, NICHT aktiviert${NC}"
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo
|
||
}
|
||
|
||
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||
err() { echo -e "${RED}[x]${NC} $*"; }
|
||
require_root() { [ "$(id -u)" -eq 0 ] || { err "Bitte als root ausführen."; exit 1; }; }
|
||
|
||
detect_ip() {
|
||
local ip
|
||
ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')" || true
|
||
[[ -n "${ip:-}" ]] || ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||
[[ -n "${ip:-}" ]] || { err "Konnte Server-IP nicht ermitteln."; exit 1; }
|
||
echo "$ip"
|
||
}
|
||
|
||
gen() { head -c 512 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c "${1:-28}" || true; }
|
||
pw() { gen 28; }
|
||
short() { gen 16; }
|
||
|
||
# ===== Start =====
|
||
require_root
|
||
header
|
||
|
||
SERVER_IP="$(detect_ip)"
|
||
MAIL_HOSTNAME="${MAIL_HOSTNAME:-"bootstrap.local"}"
|
||
TZ="${TZ:-""}"
|
||
|
||
echo -e "${GREY}Server-IP erkannt: ${SERVER_IP}${NC}"
|
||
[ -n "$TZ" ] && { ln -fs "/usr/share/zoneinfo/${TZ}" /etc/localtime || true; }
|
||
|
||
log "Paketquellen aktualisieren…"
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -y
|
||
|
||
# ---- MariaDB-Workaround ----
|
||
log "MariaDB-Workaround vorbereiten…"
|
||
mkdir -p /etc/mysql /etc/mysql/mariadb.conf.d
|
||
[ -f /etc/mysql/mariadb.cnf ] || echo '!include /etc/mysql/mariadb.conf.d/*.cnf' > /etc/mysql/mariadb.cnf
|
||
|
||
# ---- Basis-Pakete installieren ----
|
||
log "Pakete installieren… (dies kann einige Minuten dauern)"
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get -y -o Dpkg::Options::="--force-confdef" \
|
||
-o Dpkg::Options::="--force-confold" install \
|
||
postfix postfix-mysql \
|
||
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql \
|
||
mariadb-server mariadb-client \
|
||
redis-server \
|
||
rspamd \
|
||
opendkim opendkim-tools \
|
||
nginx \
|
||
php php-fpm php-cli php-mbstring php-xml php-curl php-zip php-mysql php-redis php-gd unzip curl \
|
||
composer git \
|
||
certbot python3-certbot-nginx \
|
||
fail2ban \
|
||
ca-certificates rsyslog sudo openssl netcat-openbsd monit acl
|
||
|
||
NGINX_HTTP2_SUPPORTED=0
|
||
if nginx -V 2>&1 | grep -q http_v2; then
|
||
NGINX_HTTP2_SUPPORTED=1
|
||
log "Nginx: HTTP/2-Unterstützung vorhanden ✅"
|
||
else
|
||
warn "Nginx: HTTP/2-Modul nicht gefunden – wechsle auf 'nginx-full'…"
|
||
apt-get install -y nginx-full || true
|
||
systemctl restart nginx || true
|
||
if nginx -V 2>&1 | grep -q http_v2; then
|
||
NGINX_HTTP2_SUPPORTED=1
|
||
log "Nginx: HTTP/2 jetzt verfügbar ✅"
|
||
else
|
||
warn "HTTP/2 weiterhin nicht verfügbar (verwende SSL ohne http2)."
|
||
fi
|
||
fi
|
||
|
||
if [ "$NGINX_HTTP2_SUPPORTED" = "1" ]; then
|
||
NGINX_HTTP2_SUFFIX=" http2"
|
||
else
|
||
NGINX_HTTP2_SUFFIX=""
|
||
fi
|
||
|
||
# ===== Verzeichnisse / User =====
|
||
log "Verzeichnisse und Benutzer anlegen…"
|
||
mkdir -p "${CERT_DIR}" /etc/postfix/sql /etc/dovecot/conf.d /etc/rspamd/local.d /var/mail/vhosts
|
||
id vmail >/dev/null 2>&1 || adduser --system --group --home /var/mail vmail
|
||
chown -R vmail:vmail /var/mail
|
||
|
||
id "$APP_USER" >/dev/null 2>&1 || adduser --disabled-password --gecos "" "$APP_USER"
|
||
usermod -a -G "$APP_GROUP" "$APP_USER"
|
||
|
||
# ===== Self-signed TLS (SAN = IP) =====
|
||
OSSL_CFG="${CERT_DIR}/openssl.cnf"
|
||
if [ ! -s "$CERT" ] || [ ! -s "$KEY" ]; then
|
||
log "Erzeuge Self-Signed TLS Zertifikat (SAN=IP:${SERVER_IP})…"
|
||
install -d -m 0750 -o root -g "${APP_USER}" "${CERT_DIR}"
|
||
cat > "$OSSL_CFG" <<CFG
|
||
[req]
|
||
default_bits = 2048
|
||
prompt = no
|
||
default_md = sha256
|
||
req_extensions = req_ext
|
||
distinguished_name = dn
|
||
|
||
[dn]
|
||
CN = ${SERVER_IP}
|
||
O = ${APP_NAME}
|
||
C = DE
|
||
|
||
[req_ext]
|
||
subjectAltName = @alt_names
|
||
|
||
[alt_names]
|
||
IP.1 = ${SERVER_IP}
|
||
CFG
|
||
openssl req -x509 -newkey rsa:2048 -days 825 -nodes \
|
||
-keyout "$KEY" -out "$CERT" -config "$OSSL_CFG"
|
||
chown root:"${APP_USER}" "$KEY" "$CERT"
|
||
chmod 640 "$KEY" "$CERT"
|
||
chmod 750 "${CERT_DIR}"
|
||
fi
|
||
|
||
DEV_USER="${SUDO_USER:-$USER}"
|
||
if command -v setfacl >/dev/null 2>&1; then
|
||
setfacl -m u:${DEV_USER}:x "${CONF_BASE}" "${CERT_DIR}" || true
|
||
setfacl -m u:${DEV_USER}:r "$CERT" "$KEY" || true
|
||
fi
|
||
|
||
# ===== MariaDB =====
|
||
log "MariaDB vorbereiten…"
|
||
systemctl enable --now mariadb
|
||
mysql -uroot <<SQL
|
||
CREATE DATABASE IF NOT EXISTS ${DB_NAME}
|
||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost';
|
||
CREATE USER IF NOT EXISTS '${DB_USER}'@'127.0.0.1';
|
||
ALTER USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
|
||
ALTER USER '${DB_USER}'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
|
||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
|
||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'127.0.0.1';
|
||
FLUSH PRIVILEGES;
|
||
SQL
|
||
|
||
# ===== Postfix =====
|
||
postconf -e "myhostname = ${MAIL_HOSTNAME}"
|
||
postconf -e "myorigin = \$myhostname"
|
||
postconf -e "mydestination = "
|
||
postconf -e "inet_interfaces = all"
|
||
postconf -e "inet_protocols = ipv4"
|
||
postconf -e "smtpd_banner = \$myhostname ESMTP"
|
||
postconf -e "smtpd_tls_cert_file = ${CERT}"
|
||
postconf -e "smtpd_tls_key_file = ${KEY}"
|
||
postconf -e "smtpd_tls_security_level = may"
|
||
postconf -e "smtp_tls_security_level = may"
|
||
postconf -e "smtpd_tls_received_header = yes"
|
||
postconf -e "disable_vrfy_command = yes"
|
||
postconf -e "smtpd_helo_required = yes"
|
||
postconf -e "milter_default_action = accept"
|
||
postconf -e "milter_protocol = 6"
|
||
postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||
postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||
postconf -e "smtpd_sasl_type = dovecot"
|
||
postconf -e "smtpd_sasl_path = private/auth"
|
||
postconf -e "smtpd_sasl_auth_enable = yes"
|
||
postconf -e "smtpd_sasl_security_options = noanonymous"
|
||
postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"
|
||
postconf -e "smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination"
|
||
postconf -M "smtp/inet=smtp inet n - n - - smtpd -o smtpd_peername_lookup=no -o smtpd_timeout=30s"
|
||
postconf -M "submission/inet=submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_peername_lookup=no -o smtpd_tls_security_level=encrypt -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||
postconf -M "smtps/inet=smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_peername_lookup=no -o smtpd_tls_wrappermode=yes -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||
postconf -M "pickup/unix=pickup unix n - y 60 1 pickup"
|
||
postconf -M "cleanup/unix=cleanup unix n - y - 0 cleanup"
|
||
postconf -M "qmgr/unix=qmgr unix n - n 300 1 qmgr"
|
||
|
||
install -d -o root -g postfix -m 750 /etc/postfix/sql
|
||
install -o root -g postfix -m 640 /dev/null /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||
cat > /etc/postfix/sql/mysql-virtual-mailbox-maps.cf <<CONF
|
||
hosts = 127.0.0.1
|
||
user = ${DB_USER}
|
||
password = ${DB_PASS}
|
||
dbname = ${DB_NAME}
|
||
# query = SELECT 1 FROM mail_users u JOIN domains d ON d.id = u.domain_id WHERE u.email = '%s' AND u.is_active = 1 AND d.is_active = 1 LIMIT 1;
|
||
CONF
|
||
chown root:postfix /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||
chmod 640 /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||
|
||
install -o root -g postfix -m 640 /dev/null /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||
cat > /etc/postfix/sql/mysql-virtual-alias-maps.cf <<CONF
|
||
hosts = 127.0.0.1
|
||
user = ${DB_USER}
|
||
password = ${DB_PASS}
|
||
dbname = ${DB_NAME}
|
||
# query = SELECT destination FROM mail_aliases a JOIN domains d ON d.id = a.domain_id WHERE a.source = '%s' AND a.is_active = 1 AND d.is_active = 1 LIMIT 1;
|
||
CONF
|
||
chown root:postfix /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||
chmod 640 /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||
|
||
systemctl restart postfix
|
||
systemctl enable --now postfix
|
||
|
||
# ===== Dovecot =====
|
||
log "Dovecot konfigurieren…"
|
||
cat > /etc/dovecot/dovecot.conf <<'CONF'
|
||
!include_try /etc/dovecot/conf.d/*.conf
|
||
CONF
|
||
|
||
cat > /etc/dovecot/conf.d/10-mail.conf <<'CONF'
|
||
protocols = imap pop3 lmtp
|
||
mail_location = maildir:/var/mail/vhosts/%d/%n
|
||
namespace inbox { inbox = yes }
|
||
mail_privileged_group = mail
|
||
CONF
|
||
|
||
cat > /etc/dovecot/conf.d/10-auth.conf <<'CONF'
|
||
disable_plaintext_auth = yes
|
||
auth_mechanisms = plain login
|
||
!include_try auth-sql.conf.ext
|
||
CONF
|
||
|
||
cat > /etc/dovecot/dovecot-sql.conf.ext <<CONF
|
||
driver = mysql
|
||
connect = host=127.0.0.1 dbname=${DB_NAME} user=${DB_USER} password=${DB_PASS}
|
||
default_pass_scheme = BLF-CRYPT
|
||
# password_query = SELECT email AS user, password_hash AS password FROM mail_users WHERE email = '%u' AND is_active = 1 LIMIT 1;
|
||
CONF
|
||
chown root:dovecot /etc/dovecot/dovecot-sql.conf.ext
|
||
chmod 640 /etc/dovecot/dovecot-sql.conf.ext
|
||
|
||
cat > /etc/dovecot/conf.d/auth-sql.conf.ext <<'CONF'
|
||
passdb { driver = sql args = /etc/dovecot/dovecot-sql.conf.ext }
|
||
userdb { driver = static args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n }
|
||
CONF
|
||
sudo chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext
|
||
sudo chmod 640 /etc/dovecot/conf.d/auth-sql.conf.ext
|
||
|
||
cat > /etc/dovecot/conf.d/10-master.conf <<'CONF'
|
||
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 }
|
||
}
|
||
service imap-login {
|
||
inet_listener imap { port = 143 }
|
||
inet_listener imaps { port = 993 ssl = yes }
|
||
}
|
||
service pop3-login {
|
||
inet_listener pop3 { port = 110 }
|
||
inet_listener pop3s { port = 995 ssl = yes }
|
||
}
|
||
CONF
|
||
|
||
cat > /etc/dovecot/conf.d/10-ssl.conf <<CONF
|
||
ssl = required
|
||
ssl_cert = <${CERT}
|
||
ssl_key = <${KEY}
|
||
CONF
|
||
|
||
sudo mkdir -p /var/spool/postfix/private
|
||
sudo chown postfix:postfix /var/spool/postfix /var/spool/postfix/private
|
||
sudo chmod 0755 /var/spool/postfix /var/spool/postfix/private
|
||
|
||
sudo systemctl restart dovecot
|
||
sudo systemctl restart postfix
|
||
systemctl enable --now dovecot
|
||
|
||
# ===== Rspamd & OpenDKIM =====
|
||
log "Rspamd + OpenDKIM aktivieren…"
|
||
cat > /etc/rspamd/local.d/worker-controller.inc <<'CONF'
|
||
password = "admin";
|
||
bind_socket = "127.0.0.1:11334";
|
||
CONF
|
||
systemctl enable --now rspamd || true
|
||
|
||
cat > /etc/opendkim.conf <<'CONF'
|
||
Syslog yes
|
||
UMask 002
|
||
Mode sv
|
||
Socket inet:8891@127.0.0.1
|
||
Canonicalization relaxed/simple
|
||
On-BadSignature accept
|
||
On-Default accept
|
||
On-KeyNotFound accept
|
||
On-NoSignature accept
|
||
LogWhy yes
|
||
OversignHeaders From
|
||
# KeyTable / SigningTable werden nach dem Wizard gesetzt
|
||
CONF
|
||
systemctl enable --now opendkim || true
|
||
|
||
# ===== Redis =====
|
||
log "Redis absichern (Passwort setzen & nur localhost)…"
|
||
REDIS_CONF="/etc/redis/redis.conf"
|
||
REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
|
||
sed -i 's/^\s*#\?\s*bind .*/bind 127.0.0.1/' "$REDIS_CONF"
|
||
sed -i 's/^\s*#\?\s*protected-mode .*/protected-mode yes/' "$REDIS_CONF"
|
||
if grep -qE '^\s*#?\s*requirepass ' "$REDIS_CONF"; then
|
||
sed -i "s/^\s*#\?\s*requirepass .*/requirepass ${REDIS_PASS}/" "$REDIS_CONF"
|
||
else
|
||
printf "\nrequirepass %s\n" "${REDIS_PASS}" >> "$REDIS_CONF"
|
||
fi
|
||
systemctl enable --now redis-server
|
||
systemctl restart redis-server
|
||
|
||
# ===== Nginx =====
|
||
log "Nginx konfigurieren…"
|
||
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default || true
|
||
|
||
detect_php_fpm_sock() {
|
||
for v in 8.3 8.2 8.1 8.0 7.4; do s="/run/php/php${v}-fpm.sock"; [ -S "$s" ] && { echo "$s"; return; }; done
|
||
[ -S "/run/php/php-fpm.sock" ] && { echo "/run/php/php-fpm.sock"; return; }
|
||
echo "127.0.0.1:9000"
|
||
}
|
||
PHP_FPM_SOCK="$(detect_php_fpm_sock)"
|
||
install -d -m 0755 /var/www/letsencrypt
|
||
|
||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then
|
||
cat > "${NGINX_SITE}" <<CONF
|
||
server {
|
||
listen 80 default_server;
|
||
listen [::]:80 default_server;
|
||
server_name _;
|
||
location ^~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; allow all; }
|
||
return 301 https://\$host\$request_uri;
|
||
}
|
||
server {
|
||
listen 443 ssl${NGINX_HTTP2_SUFFIX};
|
||
listen [::]:443 ssl${NGINX_HTTP2_SUFFIX};
|
||
server_name _;
|
||
ssl_certificate ${CERT};
|
||
ssl_certificate_key ${KEY};
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
access_log /var/log/nginx/${APP_USER}_ssl_access.log;
|
||
error_log /var/log/nginx/${APP_USER}_ssl_error.log;
|
||
client_max_body_size 25m;
|
||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||
location ~ \.php\$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:${PHP_FPM_SOCK}; }
|
||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||
location /ws {
|
||
proxy_pass http://127.0.0.1:8080;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade \$http_upgrade;
|
||
proxy_set_header Connection "Upgrade";
|
||
proxy_set_header Host \$host;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
}
|
||
}
|
||
CONF
|
||
else
|
||
cat > "${NGINX_SITE}" <<CONF
|
||
server {
|
||
listen 80 default_server;
|
||
listen [::]:80 default_server;
|
||
server_name _;
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
access_log /var/log/nginx/${APP_USER}_access.log;
|
||
error_log /var/log/nginx/${APP_USER}_error.log;
|
||
client_max_body_size 25m;
|
||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||
location ~ \.php\$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:${PHP_FPM_SOCK}; }
|
||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||
}
|
||
CONF
|
||
fi
|
||
|
||
ln -sf "${NGINX_SITE}" "${NGINX_SITE_LINK}"
|
||
if nginx -t; then
|
||
systemctl enable --now nginx
|
||
systemctl reload nginx || true
|
||
else
|
||
echo "[x] Nginx-Konfiguration fehlerhaft – bitte /var/log/nginx/* prüfen."
|
||
exit 1
|
||
fi
|
||
|
||
# ===== Laravel Projekt =====
|
||
log "Laravel bereitstellen…"
|
||
mkdir -p "$(dirname "$APP_DIR")"
|
||
chown -R "$APP_USER":"$APP_GROUP" "$(dirname "$APP_DIR")"
|
||
|
||
log "Git Repo vorbereiten…"
|
||
if [ "${GIT_REPO}" = "https://example.com/your-repo-placeholder.git" ]; then
|
||
if [ ! -d "${APP_DIR}" ] || [ -z "$(ls -A "$APP_DIR" 2>/dev/null || true)" ]; then
|
||
sudo -u "$APP_USER" -H bash -lc "cd /var/www && COMPOSER_ALLOW_SUPERUSER=0 composer create-project laravel/laravel ${APP_USER} --no-interaction"
|
||
fi
|
||
else
|
||
if [ ! -d "${APP_DIR}/.git" ]; then
|
||
sudo -u "$APP_USER" -H bash -lc "git clone --depth=1 -b ${GIT_BRANCH} ${GIT_REPO} ${APP_DIR}"
|
||
else
|
||
sudo -u "$APP_USER" -H bash -lc "
|
||
set -e
|
||
cd ${APP_DIR}
|
||
git checkout ${GIT_BRANCH} 2>/dev/null || git checkout -B ${GIT_BRANCH}
|
||
git fetch --depth=1 origin ${GIT_BRANCH}
|
||
if git merge-base --is-ancestor HEAD origin/${GIT_BRANCH}; then
|
||
git pull --ff-only
|
||
else
|
||
echo '[i] Non-fast-forward erkannt – setze hart auf origin/${GIT_BRANCH}.' >&2
|
||
git reset --hard origin/${GIT_BRANCH}
|
||
git clean -fd
|
||
fi
|
||
"
|
||
fi
|
||
if [ -f "${APP_DIR}/composer.json" ]; then
|
||
if [ "${DEV_MODE}" = "1" ]; then
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist"
|
||
else
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --no-dev"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# ===== Node / Frontend =====
|
||
if [ -f "${APP_DIR}/package.json" ]; then
|
||
log "Node/NPM installieren…"
|
||
if command -v node >/dev/null 2>&1; then
|
||
NODE_MAJ=$(node -v | sed 's/^v//' | cut -d. -f1)
|
||
NODE_MIN=$(node -v | sed 's/^v//' | cut -d. -f2)
|
||
if [ "$NODE_MAJ" -lt 20 ] || { [ "$NODE_MAJ" -eq 20 ] && [ "$NODE_MIN" -lt 19 ]; }; then
|
||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||
apt-get install -y nodejs
|
||
fi
|
||
else
|
||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||
apt-get install -y nodejs
|
||
fi
|
||
|
||
# .env anlegen & APP_KEY
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && cp -n .env.example .env || true"
|
||
if ! grep -q '^APP_KEY=' "${APP_DIR}/.env"; then echo "APP_KEY=" >> "${APP_DIR}/.env"; fi
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan key:generate --force || true"
|
||
fi
|
||
|
||
# ===== .env füllen =====
|
||
ENV_FILE="${APP_DIR}/.env"
|
||
upsert_env () {
|
||
local key="$1" val="$2"
|
||
local esc_key esc_val
|
||
esc_key="$(printf '%s' "$key" | sed -e 's/[.[\*^$(){}+?|/]/\\&/g')"
|
||
esc_val="$(printf '%s' "$val" | sed -e 's/[&/]/\\&/g')"
|
||
if grep -qE "^[#[:space:]]*${esc_key}=" "$ENV_FILE"; then
|
||
sed -Ei "s|^[#[:space:]]*${esc_key}=.*|${key}=${esc_val}|g" "$ENV_FILE"
|
||
else
|
||
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE"
|
||
fi
|
||
}
|
||
|
||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then
|
||
upsert_env APP_URL "\"https://\${APP_HOST}\""
|
||
else
|
||
upsert_env APP_URL "\"http://\${APP_HOST}\""
|
||
fi
|
||
|
||
upsert_env APP_HOST "${SERVER_IP}"
|
||
upsert_env APP_ADMIN_USER "${ADMIN_USER}"
|
||
upsert_env APP_ADMIN_EMAIL "${ADMIN_EMAIL}"
|
||
upsert_env APP_ADMIN_PASS "${ADMIN_PASS}"
|
||
upsert_env APP_NAME "${APP_NAME}"
|
||
upsert_env APP_ENV "${APP_ENV}"
|
||
upsert_env APP_DEBUG "${APP_DEBUG}"
|
||
|
||
upsert_env DB_CONNECTION "mysql"
|
||
upsert_env DB_HOST "127.0.0.1"
|
||
upsert_env DB_PORT "3306"
|
||
upsert_env DB_DATABASE "${DB_NAME}"
|
||
upsert_env DB_USERNAME "${DB_USER}"
|
||
upsert_env DB_PASSWORD "${DB_PASS}"
|
||
|
||
# -------- WICHTIG: Cache-Store auf REDIS setzen (verhindert DB-Tabellenfehler) --------
|
||
upsert_env CACHE_SETTINGS_STORE "redis"
|
||
upsert_env CACHE_STORE "redis" # <- HIER geändert (vorher: database)
|
||
upsert_env CACHE_DRIVER "redis"
|
||
upsert_env CACHE_PREFIX "${APP_USER}_cache"
|
||
|
||
upsert_env SESSION_DRIVER "redis"
|
||
upsert_env REDIS_CLIENT "phpredis"
|
||
upsert_env REDIS_HOST "127.0.0.1"
|
||
upsert_env REDIS_PORT "6379"
|
||
upsert_env REDIS_PASSWORD "${REDIS_PASS}"
|
||
upsert_env REDIS_DB "0"
|
||
upsert_env REDIS_CACHE_DB "1"
|
||
upsert_env REDIS_CACHE_CONNECTION "cache"
|
||
upsert_env REDIS_CACHE_LOCK_CONNECTION "default"
|
||
|
||
# Reverb / Vite
|
||
upsert_env BROADCAST_DRIVER "reverb"
|
||
upsert_env QUEUE_CONNECTION "redis"
|
||
upsert_env REVERB_APP_ID "${APP_USER}"
|
||
upsert_env REVERB_APP_KEY "${APP_USER}-yhp47tbt1aebhr1fgvgj"
|
||
upsert_env REVERB_APP_SECRET "${APP_USER}-ulrdt9agwzkqwqsunbnb"
|
||
upsert_env REVERB_HOST "127.0.0.1"
|
||
upsert_env REVERB_PORT "443"
|
||
upsert_env REVERB_SCHEME "https"
|
||
upsert_env REVERB_PATH "/ws"
|
||
upsert_env VITE_REVERB_APP_KEY "\${REVERB_APP_KEY}"
|
||
upsert_env VITE_REVERB_PORT "\${REVERB_PORT}"
|
||
upsert_env VITE_REVERB_SCHEME "\${REVERB_SCHEME}"
|
||
upsert_env VITE_REVERB_PATH "\${REVERB_PATH}"
|
||
|
||
if [ "${DEV_MODE}" = "1" ]; then
|
||
sed -i '/^# --- MailWolt DEV/,/^# --- \/MailWolt DEV/d' "${ENV_FILE}"
|
||
cat >> "${ENV_FILE}" <<CONF
|
||
# --- MailWolt DEV ---
|
||
VITE_DEV_HOST=127.0.0.1
|
||
VITE_DEV_PORT=5173
|
||
VITE_HMR_PROTOCOL=wss
|
||
VITE_HMR_CLIENT_PORT=443
|
||
VITE_HMR_HOST=${APP_HOST}
|
||
VITE_DEV_ORIGIN=${APP_URL}
|
||
# --- /MailWolt DEV ---
|
||
CONF
|
||
cat > "${APP_DIR}/vite.config.js" <<'JS'
|
||
import { defineConfig, loadEnv } from 'vite'
|
||
import laravel from 'laravel-vite-plugin'
|
||
import tailwindcss from '@tailwindcss/vite'
|
||
export default ({ mode }) => {
|
||
const env = loadEnv(mode, process.cwd(), '')
|
||
const host = env.VITE_DEV_HOST || '127.0.0.1'
|
||
const port = Number(env.VITE_DEV_PORT || 5173)
|
||
const origin = env.VITE_DEV_ORIGIN || env.APP_URL || 'https://localhost'
|
||
const hmrHost = env.VITE_HMR_HOST || (new URL(origin)).hostname
|
||
return defineConfig({
|
||
plugins: [laravel({ input: ['resources/css/app.css','resources/js/app.js'], refresh: true }), tailwindcss()],
|
||
server: { host, port, https:false, strictPort:true,
|
||
hmr:{ protocol: env.VITE_HMR_PROTOCOL || 'wss', host: hmrHost, clientPort:Number(env.VITE_HMR_CLIENT_PORT||443) },
|
||
origin
|
||
}
|
||
})
|
||
}
|
||
JS
|
||
chown "${APP_USER}:${APP_GROUP}" "${APP_DIR}/vite.config.js"
|
||
fi
|
||
|
||
# ===== Frontend Build =====
|
||
if [ -f "${APP_DIR}/package.json" ]; then
|
||
log "Frontend Build…"
|
||
if [ -f "${APP_DIR}/package-lock.json" ] || [ -f "${APP_DIR}/npm-shrinkwrap.json" ]; then
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm ci --no-audit --no-fund || npm install"
|
||
else
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm install"
|
||
fi
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm run build || true"
|
||
rm -f ${APP_DIR}/bootstrap/cache/*.php
|
||
fi
|
||
|
||
# ===== Rechte / PHP-FPM =====
|
||
APP_PW="${APP_PW:-changeme}"
|
||
if ! id -u "$APP_USER" >/dev/null 2>&1; then
|
||
adduser --disabled-password --gecos "" "$APP_USER"
|
||
echo "${APP_USER}:${APP_PW}" | chpasswd
|
||
fi
|
||
usermod -a -G "$APP_GROUP" "$APP_USER"
|
||
|
||
# Sichert, dass alle nötigen Ordner existieren (idempotent)
|
||
install -d -m 0775 "${APP_DIR}/storage" \
|
||
"${APP_DIR}/storage/framework" \
|
||
"${APP_DIR}/storage/framework/cache" \
|
||
"${APP_DIR}/storage/framework/cache/data" \
|
||
"${APP_DIR}/storage/framework/sessions" \
|
||
"${APP_DIR}/storage/framework/views" \
|
||
"${APP_DIR}/bootstrap/cache"
|
||
|
||
# Besitz & Rechte
|
||
chown -R "$APP_USER":"$APP_GROUP" "$APP_DIR"
|
||
find "$APP_DIR" -type d -exec chmod 775 {} \;
|
||
find "$APP_DIR" -type f -exec chmod 664 {} \;
|
||
|
||
[ -f "$APP_DIR/artisan" ] && chmod 755 "$APP_DIR/artisan"
|
||
[ -d "$APP_DIR/vendor/bin" ] && chmod -R 755 "$APP_DIR/vendor/bin"
|
||
[ -d "$APP_DIR/node_modules/.bin" ] && chmod -R 755 "$APP_DIR/node_modules/.bin"
|
||
[ -f "$APP_DIR/node_modules/vite/bin/vite.js" ] && chmod 755 "$APP_DIR/node_modules/vite/bin/vite.js"
|
||
find "$APP_DIR" -type f -name "*.sh" -exec chmod 755 {} \;
|
||
|
||
chmod -R 775 "$APP_DIR"/storage "$APP_DIR"/bootstrap/cache
|
||
|
||
if command -v setfacl >/dev/null 2>&1; then
|
||
setfacl -R -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||
setfacl -dR -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||
fi
|
||
|
||
grep -q 'umask 002' /home/${APP_USER}/.profile 2>/dev/null || echo 'umask 002' >> /home/${APP_USER}/.profile
|
||
grep -q 'umask 002' /home/${APP_USER}/.bashrc 2>/dev/null || echo 'umask 002' >> /home/${APP_USER}/.bashrc
|
||
sudo -u "$APP_USER" -H bash -lc "npm config set umask 0002" >/dev/null 2>&1 || true
|
||
|
||
PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
|
||
FPM_POOL="/etc/php/${PHPV}/fpm/pool.d/www.conf"
|
||
if [ -f "$FPM_POOL" ]; then
|
||
sed -i 's/^;*listen\.owner.*/listen.owner = www-data/' "$FPM_POOL"
|
||
sed -i 's/^;*listen\.group.*/listen.group = www-data/' "$FPM_POOL"
|
||
sed -i 's/^;*listen\.mode.*/listen.mode = 0660/' "$FPM_POOL"
|
||
systemctl restart php${PHPV}-fpm || true
|
||
fi
|
||
|
||
IDE_USER="${SUDO_USER:-}"
|
||
if [ -n "$IDE_USER" ] && id "$IDE_USER" >/dev/null 2>&1 && command -v setfacl >/dev/null 2>&1; then
|
||
usermod -a -G "$APP_GROUP" "$IDE_USER" || true
|
||
setfacl -R -m u:${IDE_USER}:rwX "$APP_DIR"
|
||
setfacl -dR -m u:${IDE_USER}:rwX "$APP_DIR"
|
||
echo -e "${GREEN}[i]${NC} Benutzer '${IDE_USER}' wurde für Schreibzugriff freigeschaltet (ACL + Gruppe ${APP_GROUP})."
|
||
fi
|
||
|
||
# Webstack neu laden
|
||
systemctl reload nginx || true
|
||
systemctl restart php*-fpm || true
|
||
|
||
# ===== Reverb systemd =====
|
||
cat > /etc/systemd/system/mailwolt-ws.service <<EOF
|
||
[Unit]
|
||
Description=MailWolt WebSocket Backend
|
||
After=network.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
Environment=NODE_ENV=production WS_PORT=8080
|
||
User=${APP_USER}
|
||
Group=${APP_GROUP}
|
||
WorkingDirectory=${APP_DIR}
|
||
ExecStart=/usr/bin/php artisan reverb:start --host=127.0.0.1 --port=8080 --no-interaction
|
||
Restart=always
|
||
RestartSec=2
|
||
StandardOutput=append:/var/log/mailwolt-ws.log
|
||
StandardError=append:/var/log/mailwolt-ws.log
|
||
KillSignal=SIGINT
|
||
TimeoutStopSec=15
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
chown root:root /etc/systemd/system/mailwolt-ws.service
|
||
chmod 644 /etc/systemd/system/mailwolt-ws.service
|
||
|
||
touch /var/log/mailwolt-ws.log
|
||
chown ${APP_USER}:${APP_GROUP} /var/log/mailwolt-ws.log
|
||
chmod 664 /var/log/mailwolt-ws.log
|
||
|
||
install -d -m 775 -o "${APP_USER}" -g "${APP_GROUP}" \
|
||
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache"
|
||
|
||
# ---- Laravel-Caches als APP_USER aufräumen (funktioniert jetzt ohne DB-Cache-Tabelle) ----
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear && php artisan config:cache"
|
||
|
||
# HINWEIS: Wenn du unbedingt DATABASE als Cache-Store willst, dann vor obiger Zeile:
|
||
# sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan cache:table && php artisan migrate --force"
|
||
|
||
systemctl daemon-reload
|
||
if sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan list --no-ansi 2>/dev/null | grep -qE '(^| )reverb:start( |$)'"; then
|
||
systemctl enable --now mailwolt-ws
|
||
else
|
||
systemctl disable --now mailwolt-ws >/dev/null 2>&1 || true
|
||
fi
|
||
|
||
# ===== Monit =====
|
||
log "Monit konfigurieren & starten…"
|
||
cat > /etc/monit/monitrc <<'EOF'
|
||
set daemon 60
|
||
set logfile syslog facility log_daemon
|
||
|
||
check process postfix with pidfile /var/spool/postfix/pid/master.pid
|
||
start program = "/bin/systemctl start postfix"
|
||
stop program = "/bin/systemctl stop postfix"
|
||
if failed port 25 protocol smtp then restart
|
||
if failed port 465 type tcp ssl then restart
|
||
if failed port 587 type tcp then restart
|
||
|
||
check process dovecot with pidfile /var/run/dovecot/master.pid
|
||
start program = "/bin/systemctl start dovecot"
|
||
stop program = "/bin/systemctl stop dovecot"
|
||
if failed port 143 type tcp then restart
|
||
if failed port 993 type tcp ssl then restart
|
||
if failed port 110 type tcp then restart
|
||
if failed port 995 type tcp ssl then restart
|
||
|
||
check process mariadb with pidfile /var/run/mysqld/mysqld.pid
|
||
start program = "/bin/systemctl start mariadb"
|
||
stop program = "/bin/systemctl stop mariadb"
|
||
if failed port 3306 type tcp then restart
|
||
|
||
check process redis-server with pidfile /run/redis/redis-server.pid
|
||
start program = "/bin/systemctl start redis-server"
|
||
stop program = "/bin/systemctl stop redis-server"
|
||
if failed port 6379 type tcp then restart
|
||
|
||
check process rspamd with pidfile /run/rspamd/rspamd.pid
|
||
start program = "/bin/systemctl start rspamd"
|
||
stop program = "/bin/systemctl stop rspamd"
|
||
if failed port 11332 type tcp then restart
|
||
|
||
check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||
start program = "/bin/systemctl start opendkim"
|
||
stop program = "/bin/systemctl stop opendkim"
|
||
if failed port 8891 type tcp then restart
|
||
|
||
check process nginx with pidfile /run/nginx.pid
|
||
start program = "/bin/systemctl start nginx"
|
||
stop program = "/bin/systemctl stop nginx"
|
||
if failed port 80 type tcp then restart
|
||
if failed port 443 type tcp ssl then restart
|
||
|
||
check process mailwolt-ws matching "reverb:start"
|
||
start program = "/bin/systemctl start mailwolt-ws"
|
||
stop program = "/bin/systemctl stop mailwolt-ws"
|
||
if failed host 127.0.0.1 port 8080 type tcp for 2 cycles then restart
|
||
if 5 restarts within 5 cycles then timeout
|
||
EOF
|
||
chmod 600 /etc/monit/monitrc
|
||
monit -t && systemctl enable --now monit
|
||
monit reload
|
||
monit summary || true
|
||
|
||
# ===== Healthchecks =====
|
||
GREEN="\033[1;32m"; RED="\033[1;31m"; GREY="\033[0;90m"; NC="\033[0m"
|
||
ok(){ echo -e " [${GREEN}OK${NC}]"; }
|
||
fail(){ echo -e " [${RED}FAIL${NC}]"; }
|
||
|
||
echo "[+] Quick-Healthchecks…"
|
||
printf " • MariaDB … " ; mysqladmin ping --silent >/dev/null 2>&1 && ok || fail
|
||
printf " • Redis … " ; if command -v redis-cli >/dev/null 2>&1; then
|
||
if [ -n "${REDIS_PASS:-}" ] && [ "${REDIS_PASS}" != "null" ]; then
|
||
redis-cli -a "${REDIS_PASS}" ping 2>/dev/null | grep -q PONG && ok || fail
|
||
else
|
||
redis-cli ping 2>/dev/null | grep -q PONG && ok || fail
|
||
fi
|
||
else fail; fi
|
||
printf " • PHP-FPM … " ; if [[ "$PHP_FPM_SOCK" == 127.0.0.1:9000 ]]; then ss -ltn | grep -q ":9000 " && ok || fail; else [ -S "$PHP_FPM_SOCK" ] && ok || fail; fi
|
||
printf " • App … " ; if command -v curl >/dev/null 2>&1; then
|
||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then curl -skI "https://127.0.0.1" >/dev/null 2>&1 && ok || fail; else curl -sI "http://127.0.0.1" >/dev/null 2>&1 && ok || fail; fi
|
||
else echo -e " ${GREY}(curl fehlt)${NC}"; fi
|
||
check_port(){ local label="$1" cmd="$2"; printf " • %-5s … " "$label"; timeout 8s bash -lc "$cmd" >/dev/null 2>&1 && ok || fail; }
|
||
check_port "25" 'printf "QUIT\r\n" | nc -w 3 127.0.0.1 25'
|
||
check_port "465" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:465 -quiet -ign_eof'
|
||
check_port "587" 'printf "EHLO x\r\nSTARTTLS\r\nQUIT\r\n" | openssl s_client -starttls smtp -connect 127.0.0.1:587 -quiet -ign_eof'
|
||
check_port "110" 'printf "QUIT\r\n" | nc -w 3 127.0.0.1 110'
|
||
check_port "995" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:995 -quiet -ign_eof'
|
||
check_port "143" 'printf ". LOGOUT\r\n" | nc -w 3 127.0.0.1 143'
|
||
check_port "993" 'printf ". LOGOUT\r\n" | openssl s_client -connect 127.0.0.1:993 -quiet -ign_eof'
|
||
|
||
print_bootstrap_summary "$SERVER_IP" "$ADMIN_USER" "$ADMIN_PASS"
|
||
|
||
# ===== MOTD =====
|
||
install -d /usr/local/bin
|
||
cat >/usr/local/bin/mw-motd <<'SH'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
NC="\033[0m"; CY="\033[1;36m"; GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"; GY="\033[0;90m"
|
||
printf "\033[1;36m"
|
||
cat <<'ASCII'
|
||
:::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
|
||
+:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||
+:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||
+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+
|
||
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
|
||
#+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
|
||
### ### ### ### ########### ########## ### ### ######## ########## ###
|
||
ASCII
|
||
printf "\033[0m\n"
|
||
now="$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||
fqdn="$(hostname -f 2>/dev/null || hostname)"
|
||
ip_int="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||
ip_ext=""; command -v curl >/dev/null 2>&1 && ip_ext="$(curl -s --max-time 1 https://ifconfig.me || true)"
|
||
upt="$(uptime -p 2>/dev/null || true)"
|
||
cores="$(nproc 2>/dev/null || echo -n '?')"
|
||
mhz="$(LC_ALL=C lscpu 2>/dev/null | awk -F: '/MHz/{gsub(/ /,"",$2); printf("%.0f MHz",$2); exit}')"
|
||
[ -z "$mhz" ] && mhz="$(awk -F: '/cpu MHz/{printf("%.0f MHz",$2); exit}' /proc/cpuinfo 2>/dev/null)"
|
||
load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null)"
|
||
mem_total="$(awk '/MemTotal/{printf "%.2f GB",$2/1024/1024}' /proc/meminfo)"
|
||
mem_free="$(awk '/MemAvailable/{printf "%.2f GB",$2/1024/1024}' /proc/meminfo)"
|
||
svc_status(){ systemctl is-active --quiet "$1" && echo -e "${GR}OK${NC}" || echo -e "${RD}FAIL${NC}"; }
|
||
printf "${CY}Information as of:${NC} ${YE}%s${NC}\n" "$now"
|
||
printf "${GY}FQDN :${NC} %s\n" "$fqdn"
|
||
if [ -n "$ip_ext" ]; then printf "${GY}IP :${NC} %s ${GY}(external:${NC} %s${GY})${NC}\n" "${ip_int:-?}" "$ip_ext"; else printf "${GY}IP :${NC} %s\n" "${ip_int:-?}"; fi
|
||
printf "${GY}Uptime :${NC} %s\n" "${upt:-?}"
|
||
printf "${GY}Core(s) :${NC} %s core(s) at ${CY}%s${NC}\n" "$cores" "${mhz:-?}"
|
||
printf "${GY}Load :${NC} %s (1 / 5 / 15)\n" "${load:-?}"
|
||
printf "${GY}Memory :${NC} ${RD}%s${NC} ${GY}(free)${NC} / ${CY}%s${NC} ${GY}(total)${NC}\n" "${mem_free:-?}" "${mem_total:-?}"
|
||
echo
|
||
printf "${GY}Services :${NC} postfix: $(svc_status postfix) dovecot: $(svc_status dovecot) nginx: $(svc_status nginx) mariadb: $(svc_status mariadb) redis: $(svc_status redis)\n"
|
||
SH
|
||
chmod +x /usr/local/bin/mw-motd
|
||
|
||
if [ -d /etc/update-motd.d ]; then
|
||
cat >/etc/update-motd.d/10-mailwolt <<'SH'
|
||
#!/usr/bin/env bash
|
||
/usr/local/bin/mw-motd
|
||
SH
|
||
chmod +x /etc/update-motd.d/10-mailwolt
|
||
[ -f /etc/update-motd.d/50-motd-news ] && chmod -x /etc/update-motd.d/50-motd-news || true
|
||
[ -f /etc/update-motd.d/80-livepatch ] && chmod -x /etc/update-motd.d/80-livepatch || true
|
||
else
|
||
cat >/etc/profile.d/10-mailwolt-motd.sh <<'SH'
|
||
case "$-" in *i*) /usr/local/bin/mw-motd ;; esac
|
||
SH
|
||
fi
|
||
: > /etc/motd 2>/dev/null || true
|
||
|
||
) |