998 lines
34 KiB
Bash
998 lines
34 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
##############################################
|
||
# MailWolt #
|
||
# Bootstrap Installer v1.0 #
|
||
##############################################
|
||
|
||
# ===== Branding & Pfade (einmal ändern, überall wirksam) =====
|
||
APP_NAME="${APP_NAME:-MailWolt}"
|
||
|
||
APP_ENV="production"
|
||
APP_DEBUG="false"
|
||
|
||
APP_USER="${APP_USER:-mailwolt}"
|
||
APP_GROUP="${APP_GROUP:-www-data}"
|
||
|
||
# Projektverzeichnis
|
||
APP_DIR="/var/www/${APP_USER}"
|
||
|
||
# Konfigbasis (statt /etc/falcomail -> /etc/${APP_USER})
|
||
CONF_BASE="/etc/${APP_USER}"
|
||
CERT_DIR="${CONF_BASE}/ssl"
|
||
CERT="${CERT_DIR}/cert.pem"
|
||
KEY="${CERT_DIR}/key.pem"
|
||
|
||
# Nginx vHost
|
||
NGINX_SITE="/etc/nginx/sites-available/${APP_USER}.conf"
|
||
NGINX_SITE_LINK="/etc/nginx/sites-enabled/${APP_USER}.conf"
|
||
|
||
# DB
|
||
DB_NAME="${DB_NAME:-${APP_USER}}"
|
||
DB_USER="${DB_USER:-${APP_USER}}"
|
||
# DB_PASS muss vorher generiert oder gesetzt werden
|
||
|
||
# Git (Platzhalter; später ersetzen)
|
||
GIT_REPO="${GIT_REPO:-https://git.nexlab.at/boban/mailwolt.git}"
|
||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||
|
||
# Node Setup (deb = apt Pakete; nodesource = NodeSource LTS)
|
||
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="──────────────────────────────────────────────────────────────────────────────"
|
||
|
||
# ===== Install-Log =====
|
||
LOG_FILE="/var/log/mailwolt-install.log"
|
||
> "$LOG_FILE"
|
||
|
||
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
|
||
}
|
||
|
||
|
||
footer_ok() {
|
||
local ip="$1"
|
||
local app_name="${2:-$APP_NAME}"
|
||
local app_dir="${3:-$APP_DIR}"
|
||
local cert_dir="${4:-$CERT_DIR}"
|
||
|
||
echo
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo -e "${GREEN} ✔ ${app_name} Installation erfolgreich abgeschlossen${NC}"
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo -e ""
|
||
echo -e " ${CYAN}➜ Setup-Wizard jetzt öffnen:${NC}"
|
||
echo -e " ${CYAN}http://${ip}/setup${NC}"
|
||
echo -e ""
|
||
echo -e " Laravel Root: ${GREY}${app_dir}${NC}"
|
||
echo -e " Mail-TLS Cert: ${GREY}${cert_dir}/{cert.pem,key.pem}${NC} (Postfix/Dovecot)"
|
||
echo -e " Postfix/Dovecot: ${GREY}25, 465, 587, 110, 995, 143, 993${NC}"
|
||
echo -e "${GREEN}${BAR}${NC}"
|
||
echo
|
||
}
|
||
|
||
_STEP_T=0
|
||
_SPIN_PID=
|
||
|
||
_spin_bg() {
|
||
local chars='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||
local n=${#chars} i=0
|
||
while true; do
|
||
printf "\r \033[0;90m${chars:$i:1}\033[0m"
|
||
i=$(( (i + 1) % n ))
|
||
sleep 0.1
|
||
done
|
||
}
|
||
start_spin() { _spin_bg & _SPIN_PID=$!; }
|
||
stop_spin() {
|
||
[[ -n "${_SPIN_PID:-}" ]] || return 0
|
||
kill "$_SPIN_PID" 2>/dev/null || true
|
||
wait "$_SPIN_PID" 2>/dev/null || true
|
||
_SPIN_PID=
|
||
printf "\r\033[K"
|
||
}
|
||
trap 'stop_spin' EXIT INT TERM
|
||
|
||
step() {
|
||
local msg="$1" dur="${2:-}"
|
||
[ -n "$dur" ] && dur=" ${GREY}(~${dur})${NC}" || dur=""
|
||
printf "\n${CYAN} ▸${NC} %-42s%b\n" "$msg" "$dur"
|
||
_STEP_T=$SECONDS
|
||
}
|
||
ok() {
|
||
stop_spin
|
||
local t=$(( SECONDS - _STEP_T ))
|
||
[ $t -gt 1 ] && printf " ${GREEN}✔${NC} ${GREY}%ds${NC}\n" $t || printf " ${GREEN}✔${NC}\n"
|
||
}
|
||
warn() { stop_spin; printf " ${YELLOW}⚠${NC} %s\n" "$*"; }
|
||
err() { stop_spin; printf " ${RED}✗${NC} %s\n" "$*"; }
|
||
|
||
quietly() {
|
||
start_spin
|
||
if ! "$@" >> "$LOG_FILE" 2>&1; then
|
||
stop_spin
|
||
printf " ${RED}✗${NC} Fehlgeschlagen. Letzte Log-Zeilen:\n\n"
|
||
tail -20 "$LOG_FILE" | sed 's/^/ /'
|
||
printf "\n ${GREY}Vollständiges Log: %s${NC}\n\n" "$LOG_FILE"
|
||
exit 1
|
||
fi
|
||
stop_spin
|
||
}
|
||
try_quiet() { "$@" >> "$LOG_FILE" 2>&1 || true; }
|
||
log() { :; }
|
||
require_root() { [ "$(id -u)" -eq 0 ] || { err "Bitte als root ausführen."; exit 1; }; }
|
||
|
||
# ===== IP ermitteln =====
|
||
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"
|
||
}
|
||
|
||
# ===== Secrets =====
|
||
gen() { head -c 512 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c "${1:-28}" || true; }
|
||
pw() { gen 28; }
|
||
short() { gen 16; }
|
||
|
||
# ===== Argument-Parsing =====
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-dev) APP_ENV="local"; APP_DEBUG="true" ;;
|
||
-stag|-staging) APP_ENV="staging"; APP_DEBUG="false" ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# ===== Start =====
|
||
require_root
|
||
header
|
||
|
||
SERVER_IP="$(detect_ip)"
|
||
APP_PW="${APP_PW:-$(pw)}"
|
||
MAIL_HOSTNAME="${MAIL_HOSTNAME:-"bootstrap.local"}" # Wizard setzt später FQDN
|
||
TZ="${TZ:-""}" # leer; Wizard setzt final
|
||
|
||
echo -e "\n ${GREY}Server-IP: ${SERVER_IP} Log: ${LOG_FILE}${NC}"
|
||
[ -n "$TZ" ] && { ln -fs "/usr/share/zoneinfo/${TZ}" /etc/localtime || true; }
|
||
|
||
step "Paketquellen aktualisieren" "10 Sek"
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
quietly apt-get update -y
|
||
ok
|
||
|
||
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
|
||
|
||
step "Pakete installieren" "2–5 Min"
|
||
quietly 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 php-sqlite3 unzip curl acl \
|
||
composer \
|
||
certbot python3-certbot-nginx \
|
||
fail2ban \
|
||
ca-certificates rsyslog sudo openssl netcat-openbsd monit git
|
||
ok
|
||
|
||
step "Benutzer & Verzeichnisse anlegen" "5 Sek"
|
||
mkdir -p ${CERT_DIR} /etc/postfix /etc/dovecot/conf.d /etc/rspamd/local.d /var/mail/vhosts
|
||
quietly bash -c "id vmail >/dev/null 2>&1 || adduser --system --group --home /var/mail vmail"
|
||
chown -R vmail:vmail /var/mail
|
||
quietly bash -c "id '$APP_USER' >/dev/null 2>&1 || adduser --disabled-password --gecos '' '$APP_USER'"
|
||
quietly usermod -a -G www-data "$APP_USER"
|
||
ok
|
||
|
||
# ===== Self-signed TLS (SAN = IP) =====
|
||
CERT="${CERT_DIR}/cert.pem"
|
||
KEY="${CERT_DIR}/key.pem"
|
||
OSSL_CFG="${CERT_DIR}/openssl.cnf"
|
||
|
||
if [ ! -s "$CERT" ] || [ ! -s "$KEY" ]; then
|
||
step "Self-Signed Zertifikat erstellen" "5 Sek"
|
||
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
|
||
quietly openssl req -x509 -newkey rsa:2048 -days 825 -nodes \
|
||
-keyout "$KEY" -out "$CERT" -config "$OSSL_CFG"
|
||
chmod 600 "$KEY"; chmod 644 "$CERT"
|
||
ok
|
||
fi
|
||
|
||
# ===== MariaDB vorbereiten =====
|
||
step "Datenbank einrichten" "10 Sek"
|
||
quietly systemctl enable --now mariadb
|
||
DB_PASS="$(pw)"
|
||
quietly 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' IDENTIFIED BY '${DB_PASS}';
|
||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
|
||
FLUSH PRIVILEGES;
|
||
SQL
|
||
ok
|
||
|
||
# ===== Postfix konfigurieren (25/465/587) =====
|
||
step "Mailserver konfigurieren (Postfix / Dovecot / Rspamd)" "15 Sek"
|
||
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 "smtp_dns_support_level = disabled"
|
||
|
||
postconf -e "smtpd_tls_cert_file = ${CERT}"
|
||
postconf -e "smtpd_tls_key_file = ${KEY}"
|
||
postconf -e "smtpd_tls_security_level = may"
|
||
postconf -e "smtpd_tls_auth_only = yes"
|
||
postconf -e "smtp_tls_security_level = may"
|
||
|
||
# Rspamd + OpenDKIM als Milter (accept)
|
||
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"
|
||
|
||
# Auth via Dovecot
|
||
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"
|
||
|
||
# Master-Services
|
||
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_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_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"
|
||
|
||
# --- Postfix SQL-Maps (Platzhalter) ---
|
||
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 WHERE email = '%s' AND 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 WHERE source = '%s' AND 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
|
||
|
||
try_quiet systemctl enable --now postfix
|
||
|
||
# ===== Dovecot konfigurieren (IMAP/POP3 + SSL) =====
|
||
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
|
||
|
||
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
|
||
|
||
try_quiet systemctl enable --now dovecot
|
||
|
||
# ===== Rspamd & OpenDKIM =====
|
||
cat > /etc/rspamd/local.d/worker-controller.inc <<'CONF'
|
||
password = "admin";
|
||
bind_socket = "127.0.0.1:11334";
|
||
CONF
|
||
try_quiet systemctl enable --now rspamd
|
||
|
||
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 später im Wizard
|
||
CONF
|
||
try_quiet systemctl enable --now opendkim
|
||
try_quiet systemctl enable --now redis-server
|
||
ok
|
||
|
||
# ===== Nginx: Laravel vHost (80/443) =====
|
||
step "Webserver konfigurieren (Nginx)" "5 Sek"
|
||
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default || true
|
||
|
||
PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
|
||
PHP_FPM_SOCK="/run/php/php${PHPV}-fpm.sock"
|
||
[ -S "$PHP_FPM_SOCK" ] || PHP_FPM_SOCK="/run/php/php-fpm.sock"
|
||
|
||
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;
|
||
|
||
location ^~ /.well-known/acme-challenge/ {
|
||
root /var/www/letsencrypt;
|
||
try_files \$uri =404;
|
||
}
|
||
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
|
||
ln -sf ${NGINX_SITE} ${NGINX_SITE_LINK}
|
||
try_quiet nginx -t
|
||
try_quiet systemctl enable --now nginx
|
||
ok
|
||
|
||
step "Projekt herunterladen (Git)" "15 Sek"
|
||
mkdir -p "$(dirname "$APP_DIR")"
|
||
chown "$APP_USER":$APP_GROUP "$(dirname "$APP_DIR")"
|
||
|
||
if [ ! -d "${APP_DIR}/.git" ]; then
|
||
rm -rf "${APP_DIR}"
|
||
quietly sudo -u "$APP_USER" -H bash -lc "git clone --depth=1 -b ${GIT_BRANCH} ${GIT_REPO} ${APP_DIR}"
|
||
else
|
||
quietly sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && git fetch --depth=1 origin ${GIT_BRANCH} && git checkout ${GIT_BRANCH} && git pull --ff-only"
|
||
fi
|
||
ok
|
||
|
||
# ===== .env erstellen und befüllen =====
|
||
APP_URL="http://${SERVER_IP}"
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && cp -n .env.example .env || true"
|
||
|
||
sed -i "s|^APP_NAME=.*|APP_NAME=${APP_NAME}|g" "${APP_DIR}/.env"
|
||
sed -i "s|^APP_URL=.*|APP_URL=${APP_URL}|g" "${APP_DIR}/.env"
|
||
sed -i "s|^APP_ENV=.*|APP_ENV=${APP_ENV}|g" "${APP_DIR}/.env"
|
||
sed -i "s|^APP_DEBUG=.*|APP_DEBUG=${APP_DEBUG}|g" "${APP_DIR}/.env"
|
||
|
||
sed -i "s|^DB_CONNECTION=.*|DB_CONNECTION=mysql|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*DB_HOST=.*|DB_HOST=127.0.0.1|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*DB_PORT=.*|DB_PORT=3306|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*DB_DATABASE=.*|DB_DATABASE=${DB_NAME}|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*DB_USERNAME=.*|DB_USERNAME=${DB_USER}|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*DB_PASSWORD=.*|DB_PASSWORD=${DB_PASS}|g" "${APP_DIR}/.env"
|
||
|
||
sed -i "s|^CACHE_DRIVER=.*|CACHE_DRIVER=redis|g" "${APP_DIR}/.env"
|
||
sed -i -E "s|^[#[:space:]]*CACHE_PREFIX=.*|CACHE_PREFIX=${APP_USER}|g" "${APP_DIR}/.env"
|
||
|
||
sed -i "s|^SESSION_DRIVER=.*|SESSION_DRIVER=redis|g" "${APP_DIR}/.env"
|
||
sed -i "s|^REDIS_HOST=.*|REDIS_HOST=127.0.0.1|g" "${APP_DIR}/.env"
|
||
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=null|g" "${APP_DIR}/.env"
|
||
sed -i "s|^REDIS_PORT=.*|REDIS_PORT=6379|g" "${APP_DIR}/.env"
|
||
|
||
REVERB_APP_ID="$(short)"
|
||
REVERB_APP_KEY="$(short)"
|
||
REVERB_APP_SECRET="$(short)"
|
||
grep -q '^REVERB_APP_ID=' "${APP_DIR}/.env" \
|
||
|| printf '\nBROADCAST_CONNECTION=reverb\nREVERB_APP_ID=%s\nREVERB_APP_KEY=%s\nREVERB_APP_SECRET=%s\nREVERB_HOST=127.0.0.1\nREVERB_PORT=8080\nREVERB_SCHEME=http\nVITE_REVERB_APP_KEY=%s\nVITE_REVERB_HOST=%s\nVITE_REVERB_PORT=8080\nVITE_REVERB_SCHEME=http\n' \
|
||
"$REVERB_APP_ID" "$REVERB_APP_KEY" "$REVERB_APP_SECRET" "$REVERB_APP_KEY" "$SERVER_IP" >> "${APP_DIR}/.env"
|
||
|
||
# Bootstrap-Admin für den ersten Login
|
||
BOOTSTRAP_USER="${APP_USER}"
|
||
BOOTSTRAP_EMAIL="${APP_USER}@localhost"
|
||
BOOTSTRAP_PASS="$(openssl rand -base64 18 | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 12)"
|
||
BOOTSTRAP_HASH="$(php -r 'echo password_hash($argv[1], PASSWORD_BCRYPT);' "$BOOTSTRAP_PASS")"
|
||
|
||
grep -q '^SETUP_PHASE=' "${APP_DIR}/.env" \
|
||
|| echo "SETUP_PHASE=bootstrap" >> "${APP_DIR}/.env"
|
||
sed -i "s|^SETUP_PHASE=.*|SETUP_PHASE=bootstrap|g" "${APP_DIR}/.env"
|
||
|
||
grep -q '^BOOTSTRAP_ADMIN_USER=' "${APP_DIR}/.env" \
|
||
|| echo "BOOTSTRAP_ADMIN_USER=${BOOTSTRAP_USER}" >> "${APP_DIR}/.env"
|
||
sed -i "s|^BOOTSTRAP_ADMIN_USER=.*|BOOTSTRAP_ADMIN_USER=${BOOTSTRAP_USER}|g" "${APP_DIR}/.env"
|
||
|
||
grep -q '^BOOTSTRAP_ADMIN_EMAIL=' "${APP_DIR}/.env" \
|
||
|| echo "BOOTSTRAP_ADMIN_EMAIL=${BOOTSTRAP_EMAIL}" >> "${APP_DIR}/.env"
|
||
sed -i "s|^BOOTSTRAP_ADMIN_EMAIL=.*|BOOTSTRAP_ADMIN_EMAIL=${BOOTSTRAP_EMAIL}|g" "${APP_DIR}/.env"
|
||
|
||
grep -q '^BOOTSTRAP_ADMIN_PASSWORD_HASH=' "${APP_DIR}/.env" \
|
||
|| echo "BOOTSTRAP_ADMIN_PASSWORD_HASH=${BOOTSTRAP_HASH}" >> "${APP_DIR}/.env"
|
||
sed -i "s|^BOOTSTRAP_ADMIN_PASSWORD_HASH=.*|BOOTSTRAP_ADMIN_PASSWORD_HASH=${BOOTSTRAP_HASH}|g" "${APP_DIR}/.env"
|
||
|
||
step "PHP-Abhängigkeiten installieren (Composer)" "1–2 Min"
|
||
quietly sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-dev --optimize-autoloader --no-interaction"
|
||
ok
|
||
|
||
step "Datenbank migrieren" "15 Sek"
|
||
quietly sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan key:generate --force"
|
||
quietly sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan migrate --force"
|
||
try_quiet sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan storage:link --force"
|
||
try_quiet sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan config:cache"
|
||
try_quiet sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan route:cache"
|
||
try_quiet sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan view:cache"
|
||
ok
|
||
|
||
if [ -f "${APP_DIR}/package.json" ]; then
|
||
step "Frontend bauen (npm)" "1–3 Min"
|
||
# Node/npm falls noch nicht installiert
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
if [ "$NODE_SETUP" = "nodesource" ]; then
|
||
quietly bash -c "curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh && bash /tmp/nodesource_setup.sh && rm -f /tmp/nodesource_setup.sh"
|
||
quietly apt-get install -y nodejs
|
||
else
|
||
quietly apt-get install -y nodejs npm
|
||
fi
|
||
fi
|
||
quietly sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm ci --no-audit --no-fund || npm install"
|
||
if ! sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm run build" >> "$LOG_FILE" 2>&1; then
|
||
warn "npm run build fehlgeschlagen — manuell nachholen: cd ${APP_DIR} && npm run build"
|
||
else
|
||
ok
|
||
fi
|
||
fi
|
||
|
||
mkdir -p /var/lib/mailwolt/wizard
|
||
chown www-data:www-data /var/lib/mailwolt/wizard
|
||
chmod 775 /var/lib/mailwolt/wizard
|
||
|
||
step "Hilfsskripte & Konfiguration installieren" "5 Sek"
|
||
cat > /usr/local/sbin/mailwolt-apply-domains <<'HELPER'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
UI_HOST=""; WEBMAIL_HOST=""; MAIL_HOST=""; SSL_AUTO=0
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--ui-host) UI_HOST="$2"; shift 2 ;;
|
||
--webmail-host) WEBMAIL_HOST="$2"; shift 2 ;;
|
||
--mail-host) MAIL_HOST="$2"; shift 2 ;;
|
||
--ssl-auto) SSL_AUTO="$2"; shift 2 ;;
|
||
*) shift ;;
|
||
esac
|
||
done
|
||
|
||
PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
|
||
PHP_FPM_SOCK="/run/php/php${PHPV}-fpm.sock"
|
||
[ -S "$PHP_FPM_SOCK" ] || PHP_FPM_SOCK="/run/php/php-fpm.sock"
|
||
|
||
APP_DIR="/var/www/mailwolt"
|
||
NGINX_SITE="/etc/nginx/sites-available/mailwolt.conf"
|
||
ACME_ROOT="/var/www/letsencrypt"
|
||
mkdir -p "${ACME_ROOT}/.well-known/acme-challenge"
|
||
|
||
# --- Phase 1: HTTP-only Vhosts mit ACME-Challenge ---
|
||
cat > "${NGINX_SITE}" <<CONF
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name ${UI_HOST} ${WEBMAIL_HOST};
|
||
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
|
||
location /.well-known/acme-challenge/ {
|
||
root ${ACME_ROOT};
|
||
try_files \$uri =404;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
CONF
|
||
|
||
nginx -t && systemctl reload nginx
|
||
|
||
# --- Phase 2: Let's Encrypt Zertifikate holen ---
|
||
# Prüfen ob Server globales IPv6 hat (nötig wenn AAAA-Records existieren)
|
||
has_global_ipv6() {
|
||
ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'
|
||
}
|
||
|
||
cert_needs_action() {
|
||
local domain="$1"
|
||
local cert="/etc/letsencrypt/live/${domain}/fullchain.pem"
|
||
[ ! -f "${cert}" ] && return 0
|
||
# 0 = gültig für >10 Tage → überspringen; 1 = läuft ab → erneuern
|
||
openssl x509 -checkend 864000 -noout -in "${cert}" 2>/dev/null && return 1
|
||
return 0
|
||
}
|
||
|
||
certbot_safe() {
|
||
local domain="$1"
|
||
local has_aaaa
|
||
has_aaaa=$(dig +short AAAA "${domain}" 2>/dev/null | head -1)
|
||
if [ -n "${has_aaaa}" ] && ! has_global_ipv6; then
|
||
echo "[!] ${domain}: AAAA-Record vorhanden aber kein IPv6 auf diesem Server — Let's Encrypt würde fehlschlagen. Self-signed wird verwendet." >&2
|
||
return 1
|
||
fi
|
||
certbot certonly --webroot \
|
||
-w "${ACME_ROOT}" \
|
||
-d "${domain}" \
|
||
--non-interactive --agree-tos \
|
||
--email "webmaster@${domain}" \
|
||
--no-eff-email
|
||
}
|
||
|
||
if [ "${SSL_AUTO}" = "1" ]; then
|
||
for DOMAIN in "${UI_HOST}" "${WEBMAIL_HOST}"; do
|
||
[ -z "${DOMAIN}" ] && continue
|
||
if cert_needs_action "${DOMAIN}"; then
|
||
certbot_safe "${DOMAIN}" || true
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# --- Phase 3: Finale Vhosts ---
|
||
# Nur HTTPS wenn LE-Cert tatsächlich vorhanden, sonst HTTP-only (kein self-signed Fallback)
|
||
UI_HAS_CERT=0
|
||
WM_HAS_CERT=0
|
||
[ -f "/etc/letsencrypt/live/${UI_HOST}/fullchain.pem" ] && UI_HAS_CERT=1
|
||
[ -f "/etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem" ] && WM_HAS_CERT=1
|
||
(
|
||
|
||
if [ "${UI_HAS_CERT}" = "1" ] || [ "${WM_HAS_CERT}" = "1" ]; then
|
||
# Mindestens ein Cert vorhanden → HTTP-Redirect Block
|
||
cat <<CONF
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name ${UI_HOST} ${WEBMAIL_HOST};
|
||
|
||
location /.well-known/acme-challenge/ {
|
||
root ${ACME_ROOT};
|
||
try_files \$uri =404;
|
||
}
|
||
location / { return 301 https://\$host\$request_uri; }
|
||
}
|
||
CONF
|
||
else
|
||
# Kein Cert → HTTP-only, App läuft auf Port 80 weiter
|
||
cat <<CONF
|
||
server {
|
||
listen 80 default_server;
|
||
listen [::]:80 default_server;
|
||
server_name _;
|
||
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
|
||
location /.well-known/acme-challenge/ {
|
||
root ${ACME_ROOT};
|
||
try_files \$uri =404;
|
||
}
|
||
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
|
||
|
||
if [ -n "${UI_HOST}" ] && [ "${UI_HAS_CERT}" = "1" ]; then
|
||
cat <<CONF
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
listen [::]:443 ssl;
|
||
http2 on;
|
||
server_name ${UI_HOST};
|
||
|
||
ssl_certificate /etc/letsencrypt/live/${UI_HOST}/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/${UI_HOST}/privkey.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
|
||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||
location ~ \.php\$ {
|
||
include snippets/fastcgi-php.conf;
|
||
fastcgi_param HTTPS on;
|
||
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
|
||
|
||
if [ -n "${WEBMAIL_HOST}" ] && [ "${WM_HAS_CERT}" = "1" ]; then
|
||
cat <<CONF
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
listen [::]:443 ssl;
|
||
http2 on;
|
||
server_name ${WEBMAIL_HOST};
|
||
|
||
ssl_certificate /etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/${WEBMAIL_HOST}/privkey.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
|
||
root ${APP_DIR}/public;
|
||
index index.php index.html;
|
||
|
||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||
location ~ \.php\$ {
|
||
include snippets/fastcgi-php.conf;
|
||
fastcgi_param HTTPS on;
|
||
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
|
||
) > "${NGINX_SITE}"
|
||
|
||
# State-Dateien VOR dem nginx-Switch schreiben:
|
||
# Browser-Poll (alle 2s) liest done=1 → Polling stoppt → "Zum Login" erscheint.
|
||
# Danach 6s sleep → nginx switchet auf HTTPS → User klickt Link → funktioniert.
|
||
STATE_DIR="/var/lib/mailwolt/wizard"
|
||
if [ -d "${STATE_DIR}" ]; then
|
||
for k in ui mail webmail; do
|
||
[ -f "${STATE_DIR}/${k}" ] && printf "done" > "${STATE_DIR}/${k}"
|
||
done
|
||
if [ "${UI_HAS_CERT}" = "1" ] || [ "${WM_HAS_CERT}" = "1" ]; then
|
||
printf "1" > "${STATE_DIR}/done"
|
||
else
|
||
printf "0" > "${STATE_DIR}/done"
|
||
fi
|
||
sleep 6
|
||
fi
|
||
|
||
nginx -t && systemctl reload nginx
|
||
HELPER
|
||
chmod 755 /usr/local/sbin/mailwolt-apply-domains
|
||
|
||
# ===== mailwolt-update installieren =====
|
||
install -m 755 "${APP_DIR}/update.sh" /usr/local/sbin/mailwolt-update
|
||
|
||
# ===== Sudoers für www-data (helper + update) =====
|
||
cat > /etc/sudoers.d/mailwolt-certbot <<'SUDOERS'
|
||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-apply-domains
|
||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update
|
||
www-data ALL=(root) NOPASSWD: /usr/bin/certbot
|
||
SUDOERS
|
||
chmod 440 /etc/sudoers.d/mailwolt-certbot
|
||
|
||
# git safe.directory damit spätere pulls als root möglich sind
|
||
git config --global --add safe.directory "${APP_DIR}" || true
|
||
|
||
# ===== Version-Datei schreiben =====
|
||
mkdir -p /var/lib/mailwolt
|
||
GIT_TAG="$(sudo -u "$APP_USER" -H bash -lc "git -C ${APP_DIR} ls-remote --tags --sort=-v:refname origin 'v*' 2>/dev/null | grep -v '\^{}' | head -1 | sed 's|.*refs/tags/||'")"
|
||
if [ -n "$GIT_TAG" ]; then
|
||
echo "${GIT_TAG#v}" > /var/lib/mailwolt/version
|
||
echo "$GIT_TAG" > /var/lib/mailwolt/version_raw
|
||
else
|
||
warn "Kein Git-Tag gefunden — Version-Datei wird nicht geschrieben"
|
||
fi
|
||
ok
|
||
|
||
step "Berechtigungen setzen & Dienste starten" "10 Sek"
|
||
if ! id -u "$APP_USER" >/dev/null 2>&1; then
|
||
quietly adduser --disabled-password --gecos "" "$APP_USER"
|
||
fi
|
||
echo "${APP_USER}:${APP_PW}" | quietly chpasswd
|
||
quietly usermod -a -G "$APP_GROUP" "$APP_USER"
|
||
|
||
chown -R "$APP_USER":"$APP_GROUP" "$APP_DIR"
|
||
find "$APP_DIR" -type d -exec chmod 775 {} \; 2>>"$LOG_FILE"
|
||
find "$APP_DIR" -type f -exec chmod 664 {} \; 2>>"$LOG_FILE"
|
||
chmod -R 775 "$APP_DIR"/storage "$APP_DIR"/bootstrap/cache
|
||
if command -v setfacl >/dev/null 2>&1; then
|
||
try_quiet setfacl -R -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache"
|
||
try_quiet setfacl -dR -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache"
|
||
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
|
||
try_quiet sudo -u "$APP_USER" -H bash -lc "npm config set umask 0002"
|
||
|
||
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"
|
||
try_quiet systemctl restart php${PHPV}-fpm
|
||
fi
|
||
|
||
IDE_USER="${SUDO_USER:-}"
|
||
if [ -n "$IDE_USER" ] && id "$IDE_USER" >/dev/null 2>&1 && command -v setfacl >/dev/null 2>&1; then
|
||
try_quiet usermod -a -G "$APP_GROUP" "$IDE_USER"
|
||
try_quiet setfacl -R -m u:${IDE_USER}:rwX "$APP_DIR"
|
||
try_quiet setfacl -dR -m u:${IDE_USER}:rwX "$APP_DIR"
|
||
fi
|
||
|
||
try_quiet systemctl reload nginx
|
||
try_quiet systemctl restart php*-fpm
|
||
ok
|
||
|
||
# ===== Monit =====
|
||
cat > /etc/monit/monitrc <<'EOF'
|
||
set daemon 60
|
||
set logfile syslog facility log_daemon
|
||
# set mailserver + set alert werden im Wizard gesetzt
|
||
|
||
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 host 127.0.0.1 port 25 protocol smtp for 3 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process dovecot with pidfile /run/dovecot/master.pid
|
||
start program = "/bin/systemctl start dovecot"
|
||
stop program = "/bin/systemctl stop dovecot"
|
||
if failed host 127.0.0.1 port 143 type tcp for 3 cycles then restart
|
||
if failed host 127.0.0.1 port 993 type tcpssl for 3 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process mariadb matching "mysqld"
|
||
start program = "/bin/systemctl start mariadb"
|
||
stop program = "/bin/systemctl stop mariadb"
|
||
if failed host 127.0.0.1 port 3306 type tcp for 2 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process redis with pidfile /run/redis/redis-server.pid
|
||
start program = "/bin/systemctl start redis-server"
|
||
stop program = "/bin/systemctl stop redis-server"
|
||
if failed host 127.0.0.1 port 6379 type tcp for 2 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process rspamd matching "rspamd: main process"
|
||
start program = "/bin/systemctl start rspamd" with timeout 60 seconds
|
||
stop program = "/bin/systemctl stop rspamd"
|
||
if failed host 127.0.0.1 port 11332 type tcp for 3 cycles then restart
|
||
if failed host 127.0.0.1 port 11334 type tcp for 3 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||
start program = "/bin/systemctl start opendkim"
|
||
stop program = "/bin/systemctl stop opendkim"
|
||
if failed host 127.0.0.1 port 8891 type tcp for 2 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process opendmarc with pidfile /run/opendmarc/opendmarc.pid
|
||
start program = "/bin/systemctl start opendmarc"
|
||
stop program = "/bin/systemctl stop opendmarc"
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process nginx with pidfile /run/nginx.pid
|
||
start program = "/bin/systemctl start nginx"
|
||
stop program = "/bin/systemctl stop nginx"
|
||
if failed host 127.0.0.1 port 80 type tcp for 2 cycles then restart
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||
start program = "/bin/systemctl start fail2ban"
|
||
stop program = "/bin/systemctl stop fail2ban"
|
||
if 5 restarts within 10 cycles then alert
|
||
|
||
check process clamav matching "clamd"
|
||
start program = "/bin/systemctl start clamav-daemon"
|
||
stop program = "/bin/systemctl stop clamav-daemon"
|
||
if failed unixsocket /run/clamav/clamd.ctl for 3 cycles then restart
|
||
if 5 restarts within 10 cycles then unmonitor
|
||
EOF
|
||
chmod 600 /etc/monit/monitrc
|
||
monit -t || { warn "Monit-Config ungültig — prüfe /etc/monit/monitrc"; }
|
||
try_quiet systemctl enable --now monit
|
||
|
||
# ===== Smoke-Test =====
|
||
step "Dienste prüfen (Port-Check)"
|
||
_sok=0; _sfail=0
|
||
|
||
smoke_smtp() {
|
||
local port="$1" label="$2"
|
||
local out
|
||
out=$(printf "EHLO localhost\r\nQUIT\r\n" | timeout 5s nc -w3 127.0.0.1 "$port" 2>/dev/null || true)
|
||
if echo "$out" | grep -q '^220'; then
|
||
printf " ${GREEN}✔${NC} %-5s %s\n" "$port" "$label"; (( _sok++ )) || true
|
||
else
|
||
printf " ${YELLOW}⚠${NC} %-5s %s — nicht erreichbar\n" "$port" "$label"; (( _sfail++ )) || true
|
||
fi
|
||
}
|
||
smoke_tls() {
|
||
local port="$1" label="$2" extra="${3:-}"
|
||
local out
|
||
out=$(timeout 5s openssl s_client $extra -connect 127.0.0.1:"$port" -brief -quiet </dev/null 2>&1 || true)
|
||
if echo "$out" | grep -qiE '(CONNECTED|depth|Verify|^220|\+OK|OK)'; then
|
||
printf " ${GREEN}✔${NC} %-5s %s\n" "$port" "$label"; (( _sok++ )) || true
|
||
else
|
||
printf " ${YELLOW}⚠${NC} %-5s %s — nicht erreichbar\n" "$port" "$label"; (( _sfail++ )) || true
|
||
fi
|
||
}
|
||
smoke_imap() {
|
||
local port="$1" label="$2"
|
||
local out
|
||
out=$(printf ". CAPABILITY\r\n. LOGOUT\r\n" | timeout 5s nc -w3 127.0.0.1 "$port" 2>/dev/null || true)
|
||
if echo "$out" | grep -qi 'CAPABILITY'; then
|
||
printf " ${GREEN}✔${NC} %-5s %s\n" "$port" "$label"; (( _sok++ )) || true
|
||
else
|
||
printf " ${YELLOW}⚠${NC} %-5s %s — nicht erreichbar\n" "$port" "$label"; (( _sfail++ )) || true
|
||
fi
|
||
}
|
||
smoke_pop3() {
|
||
local port="$1" label="$2"
|
||
local out
|
||
out=$(printf "QUIT\r\n" | timeout 5s nc -w3 127.0.0.1 "$port" 2>/dev/null || true)
|
||
if echo "$out" | grep -qi '^\+OK'; then
|
||
printf " ${GREEN}✔${NC} %-5s %s\n" "$port" "$label"; (( _sok++ )) || true
|
||
else
|
||
printf " ${YELLOW}⚠${NC} %-5s %s — nicht erreichbar\n" "$port" "$label"; (( _sfail++ )) || true
|
||
fi
|
||
}
|
||
|
||
smoke_smtp 25 "SMTP"
|
||
smoke_tls 465 "SMTPS" ""
|
||
smoke_tls 587 "Submission" "-starttls smtp"
|
||
smoke_imap 143 "IMAP"
|
||
smoke_tls 993 "IMAPS" ""
|
||
smoke_pop3 110 "POP3"
|
||
smoke_tls 995 "POP3S" ""
|
||
|
||
printf "\n ${GREY}%d/%d Dienste erreichbar${NC}\n" "$_sok" "$(( _sok + _sfail ))"
|
||
|
||
echo
|
||
echo -e " ${GREY}Bootstrap-Login (nur für ERSTEN Login & Wizard):${NC}"
|
||
echo -e " ${CYAN}User: ${NC}${BOOTSTRAP_USER}"
|
||
echo -e " ${CYAN}Passwort: ${NC}${BOOTSTRAP_PASS}"
|
||
echo -e " ${GREY}Log: ${LOG_FILE}${NC}"
|
||
echo
|
||
|
||
footer_ok "$SERVER_IP"
|