#!/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" < /etc/postfix/sql/mysql-virtual-mailbox-maps.cf < /etc/postfix/sql/mysql-virtual-alias-maps.cf < /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 < /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 < /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} <> "${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}" </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 < "${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 &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"