#!/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" </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 < /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 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 < /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}" < "${NGINX_SITE}" </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}" < "${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 </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 )