Dovecot Systax Problem

main
boksbc 2025-10-20 21:18:02 +02:00
parent 06263461a3
commit 4b9a142712
17 changed files with 1648 additions and 555 deletions

View File

@ -12,11 +12,10 @@ apt-get update -y
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 \
ca-certificates rsyslog sudo openssl monit acl netcat-openbsd
mariadb-server mariadb-client redis-server rspamd opendkim opendkim-tools opendmarc clamav \
clamav-daemon 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 monit acl netcat-openbsd
# <<< Apache konsequent entfernen >>>
systemctl disable --now apache2 >/dev/null 2>&1 || true

View File

@ -35,6 +35,29 @@ fi
/usr/sbin/postconf -e "smtp_tls_security_level = may"
/usr/sbin/postconf -e "smtp_tls_loglevel = 1"
# ++ HÄRTUNG: DH-Parameter + ECDHE bevorzugen ++
DH_FILE="/etc/ssl/private/dhparams.pem"
if [[ ! -s "$DH_FILE" ]]; then
openssl dhparam -out "$DH_FILE" 4096
chmod 600 "$DH_FILE"
chown root:root "$DH_FILE"
fi
/usr/sbin/postconf -e "smtpd_tls_dh1024_param_file = ${DH_FILE}"
/usr/sbin/postconf -e "smtpd_tls_eecdh_grade = strong"
/usr/sbin/postconf -e "tls_preempt_cipherlist = yes"
# Nur moderne TLS-Versionen (auch für ausgehendes SMTP)
# (überschreibt die älteren Zeilen oben)
/usr/sbin/postconf -e "smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
/usr/sbin/postconf -e "smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
/usr/sbin/postconf -e "smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
# Hohe Cipher, alte raus
/usr/sbin/postconf -e "smtpd_tls_ciphers = high"
/usr/sbin/postconf -e "smtp_tls_ciphers = high"
/usr/sbin/postconf -e "smtpd_tls_exclude_ciphers = aNULL,eNULL,MD5,RC4,DES,3DES"
/usr/sbin/postconf -e "smtp_tls_exclude_ciphers = aNULL,eNULL,MD5,RC4,DES,3DES"
# --- SMTP Sicherheit ----------------------------------------------------------
/usr/sbin/postconf -e "disable_vrfy_command = yes"
/usr/sbin/postconf -e "smtpd_helo_required = yes"
@ -106,4 +129,4 @@ chmod 640 /etc/postfix/sql/mysql-virtual-alias-maps.cf
/usr/sbin/postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"
# --- Dienst aktivieren & neu laden --------------------------------------------
systemctl enable postfix >/dev/null 2>&1 || true
#systemctl enable postfix >/dev/null 2>&1 || true

View File

@ -168,6 +168,10 @@ else
fi
grep -q '^ssl_min_protocol' "$DOVECOT_SSL_CONF" || echo "ssl_min_protocol = TLSv1.2" >> "$DOVECOT_SSL_CONF"
# Starke Cipher + DH-Params für DHE-Fallback
grep -q '^ssl_prefer_server_ciphers' "$DOVECOT_SSL_CONF" || echo "ssl_prefer_server_ciphers = yes" >> "$DOVECOT_SSL_CONF"
grep -q '^ssl_dh' "$DOVECOT_SSL_CONF" || echo "ssl_dh = </etc/ssl/private/dhparams.pem" >> "$DOVECOT_SSL_CONF"
# Postfix-Socket-Verzeichnis sicherstellen
mkdir -p /var/spool/postfix/private
chown root:root /var/spool/postfix
@ -176,124 +180,4 @@ chown postfix:postfix /var/spool/postfix/private
chmod 0755 /var/spool/postfix/private
# Nur aktivieren Start/Reload später
systemctl enable dovecot >/dev/null 2>&1 || true
##!/usr/bin/env bash
#set -euo pipefail
#source ./lib.sh
#
#MAIL_SSL_DIR="/etc/ssl/mail"
#MAIL_CERT="${MAIL_SSL_DIR}/fullchain.pem"
#MAIL_KEY="${MAIL_SSL_DIR}/privkey.pem"
#
#log "Dovecot konfigurieren …"
#
## Hauptdatei
#cat > /etc/dovecot/dovecot.conf <<'CONF'
#!include_try /etc/dovecot/conf.d/*.conf
#CONF
#
## Mail-Location & Namespace
#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
#first_valid_uid = 109
#last_valid_uid = 109
#CONF
#
## Auth
#cat > /etc/dovecot/conf.d/10-auth.conf <<'CONF'
#disable_plaintext_auth = yes
#auth_mechanisms = plain login
#!include_try auth-sql.conf.ext
#CONF
#
## SQL-Anbindung
#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
#
## Auth-SQL Einbindung
#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=mail home=/var/mail/vhosts/%d/%n
#}
#CONF
#chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext
#chmod 640 /etc/dovecot/conf.d/auth-sql.conf.ext
#
## Master-Services (LMTP + AUTH + Listener)
#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
#
## SSL stabile Mail-Pfade
#DOVECOT_SSL_CONF="/etc/dovecot/conf.d/10-ssl.conf"
#grep -q '^ssl\s*=' "$DOVECOT_SSL_CONF" 2>/dev/null || echo "ssl = required" >> "$DOVECOT_SSL_CONF"
#if grep -q '^\s*ssl_cert\s*=' "$DOVECOT_SSL_CONF"; then
# sed -i "s|^\s*ssl_cert\s*=.*|ssl_cert = <${MAIL_CERT}|" "$DOVECOT_SSL_CONF"
#else
# echo "ssl_cert = <${MAIL_CERT}" >> "$DOVECOT_SSL_CONF"
#fi
#if grep -q '^\s*ssl_key\s*=' "$DOVECOT_SSL_CONF"; then
# sed -i "s|^\s*ssl_key\s*=.*|ssl_key = <${MAIL_KEY}|" "$DOVECOT_SSL_CONF"
#else
# echo "ssl_key = <${MAIL_KEY}" >> "$DOVECOT_SSL_CONF"
#fi
#
## Postfix-Socket-Verzeichnis sicherstellen
#mkdir -p /var/spool/postfix/private
#chown root:root /var/spool/postfix
#chmod 0755 /var/spool/postfix
#chown postfix:postfix /var/spool/postfix/private
#chmod 0755 /var/spool/postfix/private
#
## Nur aktivieren Start/Reload erst nach App/DB in 90-services.sh
#systemctl enable dovecot >/dev/null 2>&1 || true

View File

@ -21,7 +21,7 @@ RSPAMD_CONTROLLER_PASSWORD="${RSPAMD_CONTROLLER_PASSWORD:-admin}"
# ──────────────────────────────────────────────────────────────
# Rspamd (Controller + Milter)
# ──────────────────────────────────────────────────────────────
install -d -m 0755 /etc/rspamd/local.d
install -d -m 0750 /etc/rspamd/local.d
if command -v rspamadm >/dev/null 2>&1; then
RSPAMD_HASH="$(rspamadm pw -p "${RSPAMD_CONTROLLER_PASSWORD}")"
@ -30,32 +30,94 @@ else
fi
cat >/etc/rspamd/local.d/worker-controller.inc <<CONF
password = "${RSPAMD_HASH}";
bind_socket = "127.0.0.1:11334";
worker "controller" {
bind_socket = "127.0.0.1:11334";
password = "${RSPAMD_HASH}";
}
CONF
cat >/etc/rspamd/local.d/worker-normal.inc <<'CONF'
bind_socket = "127.0.0.1:11332";
#cat >/etc/rspamd/local.d/worker-normal.inc <<'CONF'
#worker "normal" {
# bind_socket = "127.0.0.1:11333";
#}
#CONF
cat >/etc/rspamd/local.d/worker-proxy.inc <<'CONF'
worker "proxy" {
bind_socket = "127.0.0.1:11333";
milter = yes;
timeout = 120s;
upstream "local" {
default = yes;
self_scan = yes;
}
}
CONF
cat >/etc/rspamd/local.d/milter_headers.conf <<'CONF'
use = ["authentication-results"];
header = "Authentication-Results";
CONF
# ──────────────────────────────────────────────────────────────
# Rspamd Redis-Konfiguration
# ──────────────────────────────────────────────────────────────
log "Rspamd Redis konfigurieren …"
: "${REDIS_PASS:=}"
cat >/etc/rspamd/local.d/redis.conf <<CONF
servers = "127.0.0.1:6379";
${REDIS_PASS:+password = "${REDIS_PASS}";}
db = 0;
CONF
# Eigentümer und Rechte setzen
chown root:_rspamd /etc/rspamd/local.d /etc/rspamd/local.d/redis.conf
chmod 750 /etc/rspamd/local.d
chmod 640 /etc/rspamd/local.d/redis.conf
# Testweise prüfen, ob Redis erreichbar ist (nicht kritisch)
if command -v redis-cli >/dev/null 2>&1; then
if [[ -n "${REDIS_PASS}" ]]; then
if redis-cli -h 127.0.0.1 -p 6379 -a "${REDIS_PASS}" ping >/dev/null 2>&1; then
log "[✓] Redis erreichbar und Passwort akzeptiert."
else
log "[!] Warnung: Redis antwortet nicht oder Passwort falsch."
fi
else
if redis-cli -h 127.0.0.1 -p 6379 ping >/dev/null 2>&1; then
log "[✓] Redis erreichbar (ohne Passwort)."
else
log "[!] Warnung: Redis antwortet nicht."
fi
fi
fi
systemctl enable --now rspamd || true
# ──────────────────────────────────────────────────────────────
# OpenDKIM nur wenn DKIM_ENABLE=1
# ──────────────────────────────────────────────────────────────
if [[ "${DKIM_ENABLE}" != "1" ]]; then
log "DKIM_ENABLE=0 → OpenDKIM wird übersprungen."
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332"
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
systemctl reload postfix || true
/usr/sbin/postconf -e "milter_default_action = accept"
/usr/sbin/postconf -e "milter_protocol = 6"
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11333"
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11333"
exit 0
fi
#if [[ "${DKIM_ENABLE}" != "1" ]]; then
# log "DKIM_ENABLE=0 → OpenDKIM wird übersprungen."
# /usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332"
# /usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
# systemctl reload postfix || true
# exit 0
#fi
install -d -m 0755 /etc/opendkim
install -d -m 0750 /etc/opendkim/keys
chown -R opendkim:opendkim /etc/opendkim
@ -241,10 +303,23 @@ visudo -cf /etc/sudoers.d/mailwolt-dkim >/dev/null
# ── Dienst + Postfix-Milter aktivieren ─────────────────────────
systemctl daemon-reload
systemctl enable --now opendkim || true
systemctl enable opendkim || true
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
systemctl reload postfix || true
if command -v /usr/local/sbin/mw-apply-milters >/dev/null 2>&1; then
/usr/local/sbin/mw-apply-milters
else
# Fallback: nur Rspamd + OpenDKIM, Port 11333
/usr/sbin/postconf -e "milter_default_action = accept"
/usr/sbin/postconf -e "milter_protocol = 6"
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11333, inet:127.0.0.1:8891"
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11333, inet:127.0.0.1:8891"
systemctl reload postfix || true
fi
chgrp _rspamd /etc/rspamd/local.d/*.inc /etc/rspamd/local.d/*.conf || true
chmod 0640 /etc/rspamd/local.d/*.inc /etc/rspamd/local.d/*.conf || true
#/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
#/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
log "[✓] Rspamd + OpenDKIM eingerichtet (läuft; signiert, sobald Keys vorhanden sind)."

65
scripts/61-opendmarc.sh Normal file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
source ./lib.sh
log "OpenDMARC installieren/konfigurieren …"
# Flags laden
set +u
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
set -u
OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
# Paket sicherstellen
if ! dpkg -s opendmarc >/dev/null 2>&1; then
apt-get update -qq
apt-get install -y opendmarc
fi
# Config-Verzeichnisse
install -d -m 0755 /etc/opendmarc
install -d -m 0755 /run/opendmarc
# IgnoreHosts
cat >/etc/opendmarc/ignore.hosts <<'EOF'
127.0.0.1
::1
localhost
EOF
chmod 0644 /etc/opendmarc/ignore.hosts
# Hauptkonfiguration
cat >/etc/opendmarc.conf <<'EOF'
AuthservID mailwolt
TrustedAuthservIDs mailwolt
IgnoreHosts /etc/opendmarc/ignore.hosts
Syslog true
SoftwareHeader true
Socket local:/run/opendmarc/opendmarc.sock
RejectFailures false
EOF
chmod 0644 /etc/opendmarc.conf
# systemd Drop-in für RuntimeDirectory (robust nach Reboot)
install -d -m 0755 /etc/systemd/system/opendmarc.service.d
cat >/etc/systemd/system/opendmarc.service.d/override.conf <<'EOF'
[Service]
RuntimeDirectory=opendmarc
RuntimeDirectoryMode=0755
EOF
systemctl daemon-reload
# Dienst nach Flag
if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
systemctl enable --now opendmarc
else
systemctl disable --now opendmarc || true
fi
# Postfix-Milter-Kette konsistent setzen (Rspamd + OpenDKIM + optional OpenDMARC)
if command -v /usr/local/sbin/mw-apply-milters >/dev/null 2>&1; then
/usr/local/sbin/mw-apply-milters
fi
log "[✓] OpenDMARC (ENABLE=${OPENDMARC_ENABLE}) bereit."

59
scripts/62-clamav.sh Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
source ./lib.sh
log "ClamAV (clamav-daemon) installieren/konfigurieren …"
# Flags laden
set +u
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
set -u
CLAMAV_ENABLE="${CLAMAV_ENABLE:-0}"
# Pakete
if ! dpkg -s clamav-daemon >/dev/null 2>&1; then
apt-get update -qq
apt-get install -y clamav clamav-daemon
fi
# Signaturen aktualisieren (erst Freshclam starten)
systemctl stop clamav-freshclam 2>/dev/null || true
freshclam || true
systemctl start clamav-freshclam || true
# clamd LocalSocket setzen
sed -i 's|^#\?LocalSocket .*|LocalSocket /run/clamav/clamd.ctl|' /etc/clamav/clamd.conf || true
install -d -m 0755 /run/clamav
chown clamav:clamav /run/clamav
# Dienst nach Flag
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
systemctl enable --now clamav-daemon
else
systemctl disable --now clamav-daemon || true
fi
# Rspamd-Integration (nur wenn aktiv)
AV_CONF="/etc/rspamd/local.d/antivirus.conf"
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
cat >"$AV_CONF" <<'EOF'
clamav {
symbol = "CLAM_VIRUS";
type = "clamav";
servers = "/run/clamav/clamd.ctl";
scan_mime_parts = true;
scan_text_mime = true;
max_size = 50mb;
log_clean = false;
action = "reject";
}
EOF
chown root:_rspamd "$AV_CONF" || true
chmod 0640 "$AV_CONF" || true
systemctl reload rspamd || systemctl restart rspamd
else
rm -f "$AV_CONF" || true
systemctl reload rspamd || true
fi
log "[✓] ClamAV (ENABLE=${CLAMAV_ENABLE}) konfiguriert."

69
scripts/63-fail2ban.sh Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
source ./lib.sh
log "Fail2Ban installieren/konfigurieren …"
# Flags laden
set +u
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
set -u
FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
# Paket
if ! dpkg -s fail2ban >/dev/null 2>&1; then
apt-get update -qq
apt-get install -y fail2ban
fi
install -d -m 0755 /etc/fail2ban/jail.d
# Basis-Jails (praxisnah)
cat >/etc/fail2ban/jail.d/mailwolt.conf <<'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = auto
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
[postfix]
enabled = true
logpath = /var/log/mail.log
port = smtp,ssmtp,submission,465
[dovecot]
enabled = true
logpath = /var/log/mail.log
port = pop3,pop3s,imap,imaps,submission,465,587,993
# Optional: Rspamd-Controller-Auth (nur wenn Passwort/Basic-Auth genutzt wird)
[rspamd-controller]
enabled = true
port = 11334
filter = rspamd
logpath = /var/log/rspamd/rspamd.log
maxretry = 5
EOF
# einfacher Filter für Rspamd-Controller
if [ ! -f /etc/fail2ban/filter.d/rspamd.conf ]; then
cat >/etc/fail2ban/filter.d/rspamd.conf <<'EOF'
[Definition]
failregex = .*Authentication failed for user.* from <HOST>
ignoreregex =
EOF
fi
# Dienst nach Flag
if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
systemctl enable --now fail2ban
else
systemctl disable --now fail2ban || true
fi
log "[✓] Fail2Ban (ENABLE=${FAIL2BAN_ENABLE}) bereit."

View File

@ -63,11 +63,6 @@ if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
issue "${WEBMAIL_HOST:-}"
issue "${MAIL_HOSTNAME:-}"
# Falls der Deploy-Wrapper gerade erst installiert wurde:
# if [[ -x /usr/local/sbin/mw-deploy.sh ]]; then
# /usr/local/sbin/mw-deploy.sh || true
# fi
# Nginx nur neu laden, wenn aktiv
if systemctl is-active --quiet nginx; then
systemctl reload nginx || true
@ -75,238 +70,3 @@ if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
else
echo "[i] BASE_DOMAIN=example.com LE wird übersprungen."
fi
##!/usr/bin/env bash
#set -euo pipefail
#source ./lib.sh
#
#ACME_WEBROOT="/var/www/letsencrypt"
#install -d -m 0755 "${ACME_WEBROOT}/.well-known/acme-challenge"
#
#CERTBOT_EXTRA=()
#LE_STAGING="${LE_STAGING:-0}" # 1 = Let's Encrypt Staging aktivieren
#[[ "$LE_STAGING" = "1" ]] && CERTBOT_EXTRA+=(--test-cert)
#
#resolve_ok() {
# local host="$1"
# local pats=()
# [[ -n "${SERVER_PUBLIC_IPV4:-}" ]] && pats+=("${SERVER_PUBLIC_IPV4//./\\.}")
# [[ -n "${SERVER_PUBLIC_IPV6:-}" ]] && pats+=("${SERVER_PUBLIC_IPV6//:/\\:}")
# # Wenn gar nichts bekannt ist, lieber nicht blockieren:
# [[ ${#pats[@]} -eq 0 ]] && return 0
# getent ahosts "$host" | awk '{print $1}' | sort -u \
# | grep -Eq "^($(IFS='|'; echo "${pats[*]}"))$"
#}
#
#probe_http() {
# local host="$1"
# echo test > "${ACME_WEBROOT}/.well-known/acme-challenge/_probe"
# curl -fsS --max-time 5 -4 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null \
# || curl -fsS --max-time 5 -6 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null
#}
#
#issue() {
# local host="$1"
# echo "[i] Versuche LE für ${host} …"
# resolve_ok "$host" || { echo "[!] DNS zeigt (noch) nicht hierher skip ${host}"; return 0; }
#
# if ! probe_http "$host"; then
# echo "[!] ACME-HTTP-Check für ${host} fehlgeschlagen (Port 80/IPv6/Firewall/Nginx prüfen)."
# fi
#
# # MX: Key beibehalten (TLSA 3 1 1 bleibt stabil)
# EXTRA_ARGS=()
# [[ "$host" == "$MAIL_HOSTNAME" ]] && EXTRA_ARGS+=(--reuse-key)
#
# certbot certonly --agree-tos -m "${LE_EMAIL:-admin@${BASE_DOMAIN}}" \
# --non-interactive --webroot -w "$ACME_WEBROOT" -d "$host" \
# "${EXTRA_ARGS[@]}" "${CERTBOT_EXTRA[@]}" || true
#}
#
#if [[ "$BASE_DOMAIN" != "example.com" ]]; then
# issue "$UI_HOST"
# issue "$WEBMAIL_HOST"
# issue "$MAIL_HOSTNAME"
#
#run-parts /etc/letsencrypt/renewal-hooks/deploy || true
#systemctl reload nginx || true
#
## # TLSA direkt einmal schreiben (Hook macht es bei Renewals sowieso)
## MX_CERT="/etc/letsencrypt/live/${MAIL_HOSTNAME}/fullchain.pem"
## if [[ -s "$MX_CERT" ]]; then
## HASH="$(openssl x509 -in "$MX_CERT" -noout -pubkey \
## | openssl pkey -pubin -outform DER \
## | openssl dgst -sha256 | sed 's/^.*= //')"
## TLSA_LINE="_25._tcp.${MAIL_HOSTNAME}. IN TLSA 3 1 1 ${HASH}"
## install -d -m 0755 /etc/mailwolt/dns
## echo "${TLSA_LINE}" > "/etc/mailwolt/dns/${MAIL_HOSTNAME}.tlsa.txt"
## echo "[TLSA] ${TLSA_LINE}"
## fi
#else
# echo "[i] BASE_DOMAIN=example.com LE wird übersprungen."
#fi
##!/usr/bin/env bash
#set -euo pipefail
#source ./lib.sh
#
#ACME_WEBROOT="/var/www/letsencrypt"
#install -d -m 0755 "${ACME_WEBROOT}/.well-known/acme-challenge"
#
## Let's Encrypt: Staging optional (für Tests)
#CERTBOT_EXTRA=()
#LE_STAGING="${LE_STAGING:-0}"
#[[ "$LE_STAGING" = "1" ]] && CERTBOT_EXTRA+=(--test-cert)
#
## Einheitliche LE-E-Mail mit Fallback
#LE_MAIL="${LE_EMAIL:-admin@${BASE_DOMAIN}}"
#
## DNS zeigt auf diese Kiste?
#resolve_ok() {
# local host="$1"
# local pats=()
# [[ -n "${SERVER_PUBLIC_IPV4:-}" ]] && pats+=("${SERVER_PUBLIC_IPV4//./\\.}")
# [[ -n "${SERVER_PUBLIC_IPV6:-}" ]] && pats+=("${SERVER_PUBLIC_IPV6//:/\\:}")
# [[ ${#pats[@]} -eq 0 ]] && return 0
# getent ahosts "$host" | awk '{print $1}' | sort -u \
# | grep -Eq "^($(IFS='|'; echo "${pats[*]}"))$"
#}
#
## HTTP-01 erreichbar?
#probe_http() {
# local host="$1"
# echo test > "${ACME_WEBROOT}/.well-known/acme-challenge/_probe"
# curl -fsS --max-time 5 -4 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null \
# || curl -fsS --max-time 5 -6 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null
#}
#
## Ein Zertifikat ausstellen
#issue() {
# local host="$1"
# [[ -z "$host" ]] && return 0
#
# echo "[i] Versuche LE für ${host} …"
#
# if ! resolve_ok "$host"; then
# echo "[!] DNS zeigt (noch) nicht hierher überspringe: ${host}"
# return 0
# fi
#
# if ! probe_http "$host"; then
# echo "[!] ACME-HTTP-Check für ${host} fehlgeschlagen (Port 80/IPv6/Firewall/Nginx prüfen)."
# fi
#
# EXTRA_ARGS=()
# # MX: Key wiederverwenden → stabiler TLSA-Hash (3 1 1)
# [[ "${host}" == "${MAIL_HOSTNAME}" ]] && EXTRA_ARGS+=(--reuse-key)
#
#certbot certonly \
# --agree-tos -m "${LE_MAIL}" --non-interactive \
# --webroot -w "${ACME_WEBROOT}" -d "${host}" \
# "${EXTRA_ARGS[@]}" "${CERTBOT_EXTRA[@]}" || true
#}
#
## ------------------- Hauptlauf -------------------
#if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
# issue "${UI_HOST:-}"
# issue "${WEBMAIL_HOST:-}"
# issue "${MAIL_HOSTNAME:-}"
#
# # Falls Deploy-Hook erst JETZT angelegt wurde: einmal manuell ausführen
# if [[ -x /usr/local/sbin/mw-deploy.sh ]]; then
# /usr/local/sbin/mw-deploy.sh || true
# fi
#
# # Nginx nur neu laden, wenn aktiv
# if systemctl is-active --quiet nginx; then
# systemctl reload nginx || true
# fi
#else
# echo "[i] BASE_DOMAIN=example.com LE wird übersprungen."
#fi
##!/usr/bin/env bash
#set -euo pipefail
#source ./lib.sh
#
#ACME_WEBROOT="/var/www/letsencrypt"
#install -d -m 0755 "${ACME_WEBROOT}/.well-known/acme-challenge"
#
## Let's Encrypt: Staging optional aktivieren (keine echten Zertifikate)
#CERTBOT_EXTRA=()
#LE_STAGING="${LE_STAGING:-0}" # 1 = Staging
#[[ "$LE_STAGING" = "1" ]] && CERTBOT_EXTRA+=(--test-cert)
#
## Einheitliche LE-E-Mail mit Fallback
#LE_MAIL="${LE_EMAIL:-admin@${BASE_DOMAIN}}"
#
## DNS-Auflösung gegen unsere bekannte(n) IP(s) prüfen (nur als Warnsignal)
#resolve_ok() {
# local host="$1"
# local pats=()
# [[ -n "${SERVER_PUBLIC_IPV4:-}" ]] && pats+=("${SERVER_PUBLIC_IPV4//./\\.}")
# [[ -n "${SERVER_PUBLIC_IPV6:-}" ]] && pats+=("${SERVER_PUBLIC_IPV6//:/\\:}")
# [[ ${#pats[@]} -eq 0 ]] && return 0
# getent ahosts "$host" | awk '{print $1}' | sort -u \
# | grep -Eq "^($(IFS='|'; echo "${pats[*]}"))$"
#}
#
## HTTP-01 Erreichbarkeit schnell antesten (IPv4/IPv6)
#probe_http() {
# local host="$1"
# echo test > "${ACME_WEBROOT}/.well-known/acme-challenge/_probe"
# curl -fsS --max-time 5 -4 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null \
# || curl -fsS --max-time 5 -6 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null
#}
#
## Ein Zertifikat für einen Host ausstellen
#issue() {
# local host="$1"
# [[ -z "$host" ]] && return 0
#
# echo "[i] Versuche LE für ${host} …"
#
# if ! resolve_ok "$host"; then
# echo "[!] DNS zeigt (noch) nicht hierher überspringe: ${host}"
# return 0
# fi
#
# if ! probe_http "$host"; then
# echo "[!] ACME-HTTP-Check für ${host} fehlgeschlagen (Port 80/IPv6/Firewall/Nginx prüfen)."
# # wir versuchen es trotzdem Certbot meldet sich, falls es scheitert
# fi
#
# # Für MX den Key wiederverwenden (stabiler TLSA-Hash 3 1 1)
# EXTRA_ARGS=()
# [[ "${host}" == "${MAIL_HOSTNAME}" ]] && EXTRA_ARGS+=(--reuse-key)
#
# certbot certonly \
# --agree-tos -m "${LE_MAIL}" --non-interactive \
# --webroot -w "${ACME_WEBROOT}" -d "${host}" \
# --deploy-hook /usr/local/sbin/mw-deploy.sh \
# "${EXTRA_ARGS[@]}" "${CERTBOT_EXTRA[@]}" || true
#}
#
## -----------------------------------------------------------------------------
## Hauptlauf
## -----------------------------------------------------------------------------
#if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
# issue "${UI_HOST:-}"
# issue "${WEBMAIL_HOST:-}"
# issue "${MAIL_HOSTNAME:-}"
#
# # Der Deploy-Hook hat Symlinks bereits gesetzt und nginx ggf. neu geladen.
# # Optional trotzdem manuell ausführen (harmlos, hilft bei exotischen Setups):
# if [[ -d /etc/letsencrypt/renewal-hooks/deploy ]]; then
# run-parts /etc/letsencrypt/renewal-hooks/deploy || true
# fi
# if systemctl is-active --quiet nginx; then
# systemctl reload nginx || true
# fi
#else
# echo "[i] BASE_DOMAIN=example.com LE-Ausstellung wird übersprungen."
#fi
#

View File

@ -236,7 +236,7 @@ if [[ "${DKIM_ENABLE}" = "1" && -n "${SYSMAIL_DOMAIN}" ]]; then
rm -f "$TMP_TXT" || true
# 3) OpenDKIM neu laden
systemctl reload opendkim || systemctl restart opendkim || true
touch /run/mailwolt.need-opendkim-reload || true
else
log "DKIM übersprungen (DKIM_ENABLE=${DKIM_ENABLE}, SYSMAIL_DOMAIN='${SYSMAIL_DOMAIN}')."
fi
@ -262,5 +262,5 @@ chown -R "$APP_USER":"$APP_GROUP" "$APP_DIR"
chmod -R u=rwX,g=rwX,o=rX "$APP_DIR"
install -d -m 0775 -o "$APP_USER" -g "$APP_GROUP" "$APP_DIR/storage" "$APP_DIR/bootstrap/cache"
relink_and_reload
systemctl restart php*-fpm || true
#relink_and_reload
#systemctl restart php*-fpm || true

View File

@ -94,14 +94,28 @@ fi
systemctl enable --now ${APP_USER}-schedule
systemctl enable --now ${APP_USER}-queue
# Webstack
systemctl reload nginx || true
systemctl restart php*-fpm || true
# Mail-Dienste starten
systemctl enable --now rspamd opendkim postfix dovecot || true
# Mail-Dienste JETZT starten (damit 25/465/587 offen sind)
systemctl enable --now rspamd opendkim || true
systemctl enable --now postfix
systemctl enable --now dovecot
# PHP-FPM: Unit erkennen, enable + (re)load
enable_and_touch_php_fpm() {
for u in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm php7.4-fpm php-fpm; do
if systemctl list-unit-files | grep -q "^${u}\.service"; then
systemctl enable --now "$u" || true
systemctl reload "$u" || systemctl restart "$u" || true
echo "[i] PHP-FPM unit: $u"
return 0
fi
done
echo "[!] Keine passende php-fpm Unit gefunden."
}
enable_and_touch_php_fpm
# Falls in 80-app.sh DKIM installiert wurde: jetzt einmal reloaden
if [[ -e /run/mailwolt.need-opendkim-reload ]]; then
systemctl reload opendkim || true
rm -f /run/mailwolt.need-opendkim-reload || true
fi
# Falls DB-Migration schon durch: einmal reload
db_ready(){ mysql -u"${DB_USER}" -p"${DB_PASS}" -h 127.0.0.1 -D "${DB_NAME}" -e "SHOW TABLES LIKE 'migrations'\G" >/dev/null 2>&1; }

View File

@ -40,4 +40,8 @@ chmod 600 /etc/monit/monitrc
monit -t && systemctl enable --now monit
monit reload || true
log "[✓] Monit konfiguriert und gestartet"
log "[✓] Monit konfiguriert und gestartet"
# ── mailwolt-update ins System kopieren ─────────────────────────────
install -m 0750 -o root -g root scripts/update.sh /usr/local/sbin/mailwolt-update
log "[✓] mailwolt-update installiert → ausführbar via 'sudo mailwolt-update'"

175
scripts/95-woltguard.sh Normal file
View File

@ -0,0 +1,175 @@
#!/usr/bin/env bash
set -euo pipefail
source ./lib.sh
log "WoltGuard (Monit + Self-Heal) einrichten …"
set +u
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
set -u
CLAMAV_ENABLE="${CLAMAV_ENABLE:-0}"
OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-0}"
FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
# Pakete sicherstellen
command -v monit >/dev/null || { apt-get update -qq; apt-get install -y monit; }
systemctl enable --now monit
# Helper-Skripte
install -d -m 0755 /usr/local/sbin
cat >/usr/local/sbin/mw-redis-ping.sh <<'EOSH'
#!/usr/bin/env bash
set -euo pipefail
PASS=""
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env || true
if command -v redis-cli >/dev/null 2>&1; then
[[ -n "${REDIS_PASS:-}" ]] \
&& redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ping | grep -q PONG \
|| redis-cli -h 127.0.0.1 -p 6379 ping | grep -q PONG
else
exit 1
fi
EOSH
chmod 0755 /usr/local/sbin/mw-redis-ping.sh
cat >/usr/local/sbin/mw-rspamd-heal.sh <<'EOSH'
#!/usr/bin/env bash
set -euo pipefail
install -d -m 0755 -o _rspamd -g _rspamd /run/rspamd || true
[ -S /var/lib/rspamd/rspamd.sock ] && rm -f /var/lib/rspamd/rspamd.sock || true
systemctl restart rspamd
EOSH
chmod 0755 /usr/local/sbin/mw-rspamd-heal.sh
# WoltGuard Wrapper + Unit
cat >/usr/local/bin/woltguard <<'EOSH'
#!/usr/bin/env bash
set -euo pipefail
case "${1:-status}" in
start) systemctl enable --now monit ;;
stop) systemctl stop monit ;;
status) monit summary || systemctl status monit || true ;;
heal) monit reload || true; sleep 1; monit restart all || true ;;
monitor) monit monitor all || true ;;
unmonitor) monit unmonitor all || true ;;
*) echo "Usage: woltguard {start|stop|status|heal|monitor|unmonitor}"; exit 2;;
esac
EOSH
chmod 0755 /usr/local/bin/woltguard
cat >/etc/systemd/system/woltguard.service <<'EOF'
[Unit]
Description=WoltGuard Self-Healing Monitor for MailWolt
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/woltguard start
ExecStop=/usr/local/bin/woltguard stop
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now woltguard
# Monit Basis + include
sed -i 's/^set daemon .*/set daemon 30/' /etc/monit/monitrc || true
grep -q 'include /etc/monit/conf.d/*' /etc/monit/monitrc || echo 'include /etc/monit/conf.d/*' >>/etc/monit/monitrc
install -d -m 0755 /etc/monit/conf.d
# Checks
cat >/etc/monit/conf.d/postfix.conf <<'EOF'
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 tcpssl then restart
if failed port 587 type tcp then restart
if 5 restarts within 5 cycles then alert
EOF
cat >/etc/monit/conf.d/dovecot.conf <<'EOF'
check process dovecot with pidfile /run/dovecot/master.pid
start program = "/bin/systemctl start dovecot"
stop program = "/bin/systemctl stop dovecot"
if failed port 993 type tcpssl for 2 cycles then restart
if failed port 24 protocol lmtp for 2 cycles then restart
if 5 restarts within 5 cycles then alert
EOF
cat >/etc/monit/conf.d/nginx.conf <<'EOF'
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 tcpssl then restart
if 5 restarts within 5 cycles then alert
EOF
cat >/etc/monit/conf.d/redis.conf <<'EOF'
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 for 2 cycles then restart
if failed program "/usr/local/sbin/mw-redis-ping.sh" for 2 cycles then restart
if 5 restarts within 5 cycles then alert
EOF
cat >/etc/monit/conf.d/rspamd.conf <<'EOF'
check process rspamd with pidfile /run/rspamd/rspamd.pid
start program = "/bin/systemctl start rspamd"
stop program = "/bin/systemctl stop rspamd"
if failed port 11333 for 2 cycles then exec "/usr/local/sbin/mw-rspamd-heal.sh"
if failed port 11334 for 2 cycles then exec "/usr/local/sbin/mw-rspamd-heal.sh"
if 5 restarts within 5 cycles then alert
EOF
cat >/etc/monit/conf.d/opendkim.conf <<'EOF'
check process opendkim with pidfile /run/opendkim/opendkim.pid
start program = "/bin/systemctl start opendkim"
stop program = "/bin/systemctl stop opendkim"
if does not exist file "/run/opendkim/opendkim.pid" then restart
if 5 restarts within 5 cycles then alert
EOF
# optional: OpenDMARC
if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
cat >/etc/monit/conf.d/opendmarc.conf <<'EOF'
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 5 cycles then alert
EOF
else
rm -f /etc/monit/conf.d/opendmarc.conf || true
fi
# optional: ClamAV
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
cat >/etc/monit/conf.d/clamav.conf <<'EOF'
check process clamd with pidfile /run/clamav/clamd.pid
start program = "/bin/systemctl start clamav-daemon"
stop program = "/bin/systemctl stop clamav-daemon"
if failed unixsocket /run/clamav/clamd.ctl then restart
if 5 restarts within 5 cycles then alert
EOF
else
rm -f /etc/monit/conf.d/clamav.conf || true
fi
# optional: Fail2Ban
if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
cat >/etc/monit/conf.d/fail2ban.conf <<'EOF'
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 5 cycles then alert
EOF
else
rm -f /etc/monit/conf.d/fail2ban.conf || true
fi
monit -t
systemctl reload monit || systemctl restart monit
woltguard status || true
log "[✓] WoltGuard aktiv."

View File

@ -4,130 +4,134 @@ source ./lib.sh
log "MOTD installieren …"
install -d /usr/local/bin
cat >/usr/local/bin/mw-motd <<'SH'
#!/usr/bin/env bash
# bewusst KEIN "set -e"; MOTD soll nie hart abbrechen
# MOTD MailWolt
# bewusst KEIN "set -e" und KEIN pipefail; MOTD darf nie hart abbrechen
set -u
# Farben
NC="\033[0m"; CY="\033[1;36m"; GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"; GY="\033[0;90m"; WH="\033[1;37m"
# ---------- Farben ----------
NC="\033[0m"; WH="\033[1;37m"; CY="\033[1;36m"; GY="\033[0;90m"
GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"
# Installer-Variablen (optional)
UI_HOST=""; WEBMAIL_HOST=""; MAIL_HOSTNAME=""; LE_EMAIL=""; PROXY_MODE=""; NPM_IP=""
# ---------- Breite / Zentrierung ----------
W=110
term_cols=$(tput cols 2>/dev/null || echo $W)
[ "$term_cols" -gt "$W" ] && pad=$(( (term_cols - W)/2 )) || pad=0
sp(){ [ "$1" -gt 0 ] && printf "%${1}s" " " || true; }
center() { local s="$1"; local n=$(( (W - ${#s})/2 )); sp $((pad+n)); printf "%s\n" "$s"; }
rule(){ sp "$pad"; printf "%0.s=" $(seq 1 "$W"); printf "\n"; }
title(){ sp "$pad"; local t="$1"; local lf=$(( (W - ${#t} - 2)/2 )); local rf=$(( W - ${#t} - 2 - lf )); \
printf "%s" "$(printf '─%.0s' $(seq 1 $lf))"; printf " %s " "$t"; printf "%s\n" "$(printf '─%.0s' $(seq 1 $rf))"; }
kv(){ sp "$pad"; printf "%-12s: %s\n" "$1" "$2"; }
# ---------- Installer-/App-Variablen ----------
UI_HOST=""; WEBMAIL_HOST=""; MAIL_HOSTNAME=""
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env || true
# Aus .env (falls vorhanden)
if [ -r /var/www/mailwolt/.env ]; then
LE_EMAIL="${LE_EMAIL:-$(grep -E '^LE_EMAIL=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^LE_EMAIL=//')}"
PROXY_MODE="${PROXY_MODE:-$(grep -E '^PROXY_MODE=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^PROXY_MODE=//')}"
NPM_IP="${NPM_IP:-$(grep -E '^NPM_IP=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^NPM_IP=//')}"
fi
# Header
printf "${CY}"
cat <<'ASCII'
:::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
+:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
+:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+
+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
#+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
### ### ### ### ########### ########## ### ### ######## ########## ###
ASCII
printf "${NC}\n"
# ---------- Systemdaten ----------
now="$(date '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo '-')"
upt="$(uptime -p 2>/dev/null || echo '-')"
cores="$(nproc 2>/dev/null || echo 1)"
load_raw="$(awk '{printf "%s / %s / %s",$1,$2,$3}' /proc/loadavg 2>/dev/null || echo '0.00 / 0.00 / 0.00')"
load1="$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo 0)"
# Safe-Helfer (niemals Script killen)
grab() { eval "$1" 2>/dev/null || true; }
line() { printf "${GY}%-7s:${NC} %s\n" "$1" "$2"; }
# RAM/SWAP
mem_total="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
mem_avail="$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
mem_used=$(( mem_total - mem_avail ))
swap_total="$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
swap_free="$(awk '/SwapFree/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
swap_used=$(( swap_total - swap_free ))
# Systemdaten
now="$(date '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo -n '-')"
fqdn="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo -n '-')"
ip_int="$(hostname -I 2>/dev/null | awk '{print $1}' 2>/dev/null || true)"
ip_ext="$(curl -fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
upt="$(uptime -p 2>/dev/null || echo -n '-')"
cores="$(nproc 2>/dev/null || echo -n '-')"
load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null || echo -n '-')"
# RAM/SWAP (MiB)
mem_total="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '-')"
mem_avail="$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '0')"
mem_used=$(( ${mem_total:-0}-${mem_avail:-0} ))
swap_total="$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '-')"
swap_free="$(awk '/SwapFree/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '0')"
swap_used=$(( ${swap_total:-0}-${swap_free:-0} ))
pct(){ local u="$1" t="$2"; [ "$t" -gt 0 ] || { echo 0; return; }; awk -v u="$u" -v t="$t" 'BEGIN{printf "%d",(u*100)/t}' ; }
ram_pct=$(pct "$mem_used" "$mem_total")
swap_pct=$(pct "$swap_used" "$swap_total")
# Disks
disk_line(){ df -hP "$1" 2>/dev/null | awk 'NR==2{printf "%s/%s (%s used)", $3,$2,$5}'; }
disk_root="$(disk_line /)"
disk_var="$(disk_line /var)"
df_line(){ df -hP "$1" 2>/dev/null | awk 'NR==2{printf "%s / %s (%s)",$3,$2,$5}'; }
df_pct(){ df -P "$1" 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5+0}'; }
disk_root="$(df_line /)"; pct_root="$(df_pct /)"
disk_var="$(df_line /var 2>/dev/null)"; [ -n "$disk_var" ] || disk_var="-"
pct_var="$(df_pct /var 2>/dev/null)"; [ -n "$pct_var" ] || pct_var=0
svc_state(){
local unit="$1"
if systemctl is-active --quiet "$unit"; then
printf "${GR}OK${NC}"
else
printf "${RD}FAIL${NC}"
# IPs (int/ext)
ipv4_int="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i!~/:/){print $i;exit}}')"
ipv6_int="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i~/:/){print $i;exit}}')"
ipv4_ext="$(curl -4fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
ipv6_ext="$(curl -6fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
# ---------- Status-Farben ----------
mark(){ # value thresholdY thresholdR
local v="$1" y="$2" r="$3"
if [ "$v" -ge "$r" ]; then printf "${RD}[HIGH]${NC}"
elif [ "$v" -ge "$y" ]; then printf "${YE}[WARN]${NC}"
else printf "${GR}[OK]${NC}"
fi
}
# Load/CPU-Schwellen (pro Core)
load_pct=$(awk -v l="$load1" -v c="$cores" 'BEGIN{if(c<1)c=1; printf "%d", (l/c)*100}')
m_load="$(mark "$load_pct" 70 100)"
m_ram="$(mark "$ram_pct" 75 90)"
m_swap="$(mark "$swap_pct" 10 50)"
m_root="$(mark "$pct_root" 75 90)"
m_var="$(mark "$pct_var" 75 90)"
# Ausgabe
printf "${CY}Information as of:${NC} ${YE}%s${NC}\n" "$now"
line "FQDN" "$fqdn"
if [ -n "$ip_ext" ]; then
printf "${GY}%-7s:${NC} %s ${GY}(ext:${NC} %s${GY})${NC}\n" "IP" "${ip_int:--}" "$ip_ext"
else
line "IP" "${ip_int:--}"
fi
line "Uptime" "$upt"
printf "${GY}%-7s:${NC} %s cores, load %s (1/5/15)\n" "CPU" "$cores" "$load"
printf "${GY}%-7s:${NC} %s MiB used / %s MiB total\n" "RAM" "$mem_used" "$mem_total"
printf "${GY}%-7s:${NC} %s MiB used / %s MiB total\n" "SWAP" "$swap_used" "$swap_total"
line "Disk /" "${disk_root:-'-'}"
line "Disk/var" "${disk_var:-'-'}"
# ---------- Header ----------
rule
center ""
center ":::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::"
center ":+:+:+ :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: "
center ":+: +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ "
center "+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+ "
center "+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+ "
center "#+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+# "
center "### ### ### ### ########### ########## ### ### ######## ########## ### "
center ""
rule
# App/Installer Infos
[ -n "${LE_EMAIL:-}" ] && line "LE Mail" "$LE_EMAIL"
[ -n "${UI_HOST:-}" ] && line "UI" "$UI_HOST"
[ -n "${WEBMAIL_HOST:-}" ] && line "Webmail" "$WEBMAIL_HOST"
[ -n "${MAIL_HOSTNAME:-}" ]&& line "MX" "$MAIL_HOSTNAME"
if [ -n "${PROXY_MODE:-}" ]; then
if [ "$PROXY_MODE" = "1" ]; then
line "Proxy" "ja (NPM: ${NPM_IP:-unbekannt})"
elif [ "$PROXY_MODE" = "dev" ]; then
line "Proxy" "Entwicklungsmodus"
else
line "Proxy" "nein"
fi
fi
# ---------- System ----------
kv "Date / Time" "${YE}${now}${NC}"
sp "$pad"; printf "%-12s: int %-40s ext %s\n" "IPv4" "${ipv4_int:--}" "${ipv4_ext:--}"
sp "$pad"; printf "%-12s: int %-40s ext %s\n" "IPv6" "${ipv6_int:--}" "${ipv6_ext:--}"
kv "Uptime" "$upt"
sp "$pad"; printf "%-12s: %s cores, load %s %b\n" "CPU" "$cores" "$load_raw" "$m_load"
sp "$pad"; printf "%-12s: %s MiB / %s MiB (%d%%) %b %-5s %s MiB / %s MiB (%d%%) %b\n" \
"RAM" "$mem_used" "$mem_total" "$ram_pct" "$m_ram" "SWAP:" "$swap_used" "$swap_total" "$swap_pct" "$m_swap"
sp "$pad"; printf "%-12s: / %s %b %-5s %s %b\n" \
"Disk" "$disk_root" "$m_root" "/var:" "$disk_var" "$m_var"
echo
# Services
printf "${WH}\nServices:${NC}\n"
printf " nginx … %b\n" "$(svc_state nginx)"
printf " mariadb … %b\n" "$(svc_state mariadb)"
printf " redis-server … %b\n" "$(svc_state redis-server)"
printf " postfix … %b\n" "$(svc_state postfix)"
printf " dovecot … %b\n" "$(svc_state dovecot)"
printf " mailwolt-ws … %b\n" "$(svc_state mailwolt-ws)"
printf " mailwolt-queue … %b\n" "$(svc_state mailwolt-queue)"
printf " mailwolt-schedule … %b\n" "$(svc_state mailwolt-schedule)"
# ---------- Domains ----------
title "Domains"
[ -n "${UI_HOST:-}" ] && kv "UI" "${UI_HOST}"
[ -n "${WEBMAIL_HOST:-}" ] && kv "Webmail" "${WEBMAIL_HOST}"
[ -n "${MAIL_HOSTNAME:-}" ]&& kv "MX" "${MAIL_HOSTNAME}"
echo
# Zertifikatskurzinfo (nur wenn vorhanden)
show_cert_exp(){
local name="$1" path="$2"
if [ -r "$path" ]; then
local exp
exp="$(openssl x509 -in "$path" -noout -enddate 2>/dev/null | sed 's/notAfter=//')"
[ -n "$exp" ] && printf "${GY}%s cert:${NC} expires %s\n" "$name" "$exp"
fi
}
show_cert_exp "UI" "/etc/ssl/ui/fullchain.pem"
show_cert_exp "Webmail" "/etc/ssl/webmail/fullchain.pem"
show_cert_exp "MX" "/etc/ssl/mail/fullchain.pem"
# ---------- Services (4 Spalten, bündig) ----------
title "Services"
svc_state(){ systemctl is-active --quiet "$1" && printf "${GR}[OK]${NC}" || printf "${RD}[FAIL]${NC}"; }
SVC=( nginx mariadb redis-server postfix dovecot rspamd opendkim opendmarc clamav-daemon fail2ban mailwolt-ws mailwolt-queue mailwolt-schedule )
i=0; line=""
for s in "${SVC[@]}"; do
st="$(svc_state "$s")"
seg="$(printf "%-18s %-7s" "$s" "$st")"
line="$line$seg"
i=$((i+1))
if [ $((i%4)) -eq 0 ]; then sp "$pad"; echo "$line"; line=""; else line="$line "; fi
done
[ -n "$line" ] && { sp "$pad"; echo "$line"; }
echo
exit 0
SH
chmod 755 /usr/local/bin/mw-motd
# update-motd Hook
if [[ -d /etc/update-motd.d ]]; then
cat >/etc/update-motd.d/10-mailwolt <<'SH'
#!/usr/bin/env bash
@ -136,47 +140,21 @@ 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
else
# Fallback für Systeme ohne dynamic MOTD
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
log "[✓] MOTD installiert."
#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 '?')"
#load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null)"
#svc(){ 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}(ext:${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}Cores :${NC} %s\n" "$cores"
#printf "${GY}Load :${NC} %s (1/5/15)\n" "${load:-?}"
#printf "${GY}Svc :${NC} postfix: $(svc postfix) dovecot: $(svc dovecot) nginx: $(svc nginx) mariadb: $(svc mariadb) redis: $(svc redis)\n"
#SH
#chmod +x /usr/local/bin/mw-motd
#source ./lib.sh
#
#log "MOTD installieren …"
#install -d /usr/local/bin
#cat >/usr/local/bin/mw-motd <<'SH'
##!/usr/bin/env bash
## bewusst KEIN "set -e"; MOTD soll nie hart abbrechen
@ -198,6 +176,9 @@ fi
## Header
#printf "${CY}"
#cat <<'ASCII'
#
#==========================================================================================
#
#:::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
#+:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
#+:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
@ -205,6 +186,9 @@ fi
#+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
##+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
#### ### ### ### ########### ########## ### ### ######## ########## ###
#
#==========================================================================================
#
#ASCII
#printf "${NC}\n"
#
@ -299,4 +283,177 @@ fi
#
#exit 0
#SH
#chmod 755 /usr/local/bin/mw-motd
#chmod 755 /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
#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
#
#
#
##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 '?')"
##load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null)"
##svc(){ 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}(ext:${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}Cores :${NC} %s\n" "$cores"
##printf "${GY}Load :${NC} %s (1/5/15)\n" "${load:-?}"
##printf "${GY}Svc :${NC} postfix: $(svc postfix) dovecot: $(svc dovecot) nginx: $(svc nginx) mariadb: $(svc mariadb) redis: $(svc redis)\n"
##SH
##chmod +x /usr/local/bin/mw-motd
#
##cat >/usr/local/bin/mw-motd <<'SH'
###!/usr/bin/env bash
### bewusst KEIN "set -e"; MOTD soll nie hart abbrechen
##set -u
##
### Farben
##NC="\033[0m"; CY="\033[1;36m"; GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"; GY="\033[0;90m"; WH="\033[1;37m"
##
### Installer-Variablen (optional)
##UI_HOST=""; WEBMAIL_HOST=""; MAIL_HOSTNAME=""; LE_EMAIL=""; PROXY_MODE=""; NPM_IP=""
##[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env || true
### Aus .env (falls vorhanden)
##if [ -r /var/www/mailwolt/.env ]; then
## LE_EMAIL="${LE_EMAIL:-$(grep -E '^LE_EMAIL=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^LE_EMAIL=//')}"
## PROXY_MODE="${PROXY_MODE:-$(grep -E '^PROXY_MODE=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^PROXY_MODE=//')}"
## NPM_IP="${NPM_IP:-$(grep -E '^NPM_IP=' /var/www/mailwolt/.env 2>/dev/null | sed 's/^NPM_IP=//')}"
##fi
##
### Header
##printf "${CY}"
##cat <<'ASCII'
##:::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
##+:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
##+:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
##+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+
##+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
###+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
##### ### ### ### ########### ########## ### ### ######## ########## ###
##ASCII
##printf "${NC}\n"
##
### Safe-Helfer (niemals Script killen)
##grab() { eval "$1" 2>/dev/null || true; }
##line() { printf "${GY}%-7s:${NC} %s\n" "$1" "$2"; }
##
### Systemdaten
##now="$(date '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo -n '-')"
##fqdn="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo -n '-')"
##ip_int="$(hostname -I 2>/dev/null | awk '{print $1}' 2>/dev/null || true)"
##ip_ext="$(curl -fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
##upt="$(uptime -p 2>/dev/null || echo -n '-')"
##cores="$(nproc 2>/dev/null || echo -n '-')"
##load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null || echo -n '-')"
##
### RAM/SWAP (MiB)
##mem_total="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '-')"
##mem_avail="$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '0')"
##mem_used=$(( ${mem_total:-0}-${mem_avail:-0} ))
##swap_total="$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '-')"
##swap_free="$(awk '/SwapFree/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo -n '0')"
##swap_used=$(( ${swap_total:-0}-${swap_free:-0} ))
##
### Disks
##disk_line(){ df -hP "$1" 2>/dev/null | awk 'NR==2{printf "%s/%s (%s used)", $3,$2,$5}'; }
##disk_root="$(disk_line /)"
##disk_var="$(disk_line /var)"
##
##svc_state(){
## local unit="$1"
## if systemctl is-active --quiet "$unit"; then
## printf "${GR}OK${NC}"
## else
## printf "${RD}FAIL${NC}"
## fi
##}
##
### Ausgabe
##printf "${CY}Information as of:${NC} ${YE}%s${NC}\n" "$now"
##line "FQDN" "$fqdn"
##if [ -n "$ip_ext" ]; then
## printf "${GY}%-7s:${NC} %s ${GY}(ext:${NC} %s${GY})${NC}\n" "IP" "${ip_int:--}" "$ip_ext"
##else
## line "IP" "${ip_int:--}"
##fi
##line "Uptime" "$upt"
##printf "${GY}%-7s:${NC} %s cores, load %s (1/5/15)\n" "CPU" "$cores" "$load"
##printf "${GY}%-7s:${NC} %s MiB used / %s MiB total\n" "RAM" "$mem_used" "$mem_total"
##printf "${GY}%-7s:${NC} %s MiB used / %s MiB total\n" "SWAP" "$swap_used" "$swap_total"
##line "Disk /" "${disk_root:-'-'}"
##line "Disk/var" "${disk_var:-'-'}"
##
### App/Installer Infos
##[ -n "${LE_EMAIL:-}" ] && line "LE Mail" "$LE_EMAIL"
##[ -n "${UI_HOST:-}" ] && line "UI" "$UI_HOST"
##[ -n "${WEBMAIL_HOST:-}" ] && line "Webmail" "$WEBMAIL_HOST"
##[ -n "${MAIL_HOSTNAME:-}" ]&& line "MX" "$MAIL_HOSTNAME"
##if [ -n "${PROXY_MODE:-}" ]; then
## if [ "$PROXY_MODE" = "1" ]; then
## line "Proxy" "ja (NPM: ${NPM_IP:-unbekannt})"
## elif [ "$PROXY_MODE" = "dev" ]; then
## line "Proxy" "Entwicklungsmodus"
## else
## line "Proxy" "nein"
## fi
##fi
##
### Services
##printf "${WH}\nServices:${NC}\n"
##printf " nginx … %b\n" "$(svc_state nginx)"
##printf " mariadb … %b\n" "$(svc_state mariadb)"
##printf " redis-server … %b\n" "$(svc_state redis-server)"
##printf " postfix … %b\n" "$(svc_state postfix)"
##printf " dovecot … %b\n" "$(svc_state dovecot)"
##printf " mailwolt-ws … %b\n" "$(svc_state mailwolt-ws)"
##printf " mailwolt-queue … %b\n" "$(svc_state mailwolt-queue)"
##printf " mailwolt-schedule … %b\n" "$(svc_state mailwolt-schedule)"
##
### Zertifikatskurzinfo (nur wenn vorhanden)
##show_cert_exp(){
## local name="$1" path="$2"
## if [ -r "$path" ]; then
## local exp
## exp="$(openssl x509 -in "$path" -noout -enddate 2>/dev/null | sed 's/notAfter=//')"
## [ -n "$exp" ] && printf "${GY}%s cert:${NC} expires %s\n" "$name" "$exp"
## fi
##}
##show_cert_exp "UI" "/etc/ssl/ui/fullchain.pem"
##show_cert_exp "Webmail" "/etc/ssl/webmail/fullchain.pem"
##show_cert_exp "MX" "/etc/ssl/mail/fullchain.pem"
##
##exit 0
##SH
##chmod 755 /usr/local/bin/mw-motd

View File

@ -1,11 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
# --- Flags / Modi ---
# ──────────────────────────────────────────────────────────────
# MailWolt Interaktiver Bootstrap (whiptail + Fallback)
# ──────────────────────────────────────────────────────────────
DEV_MODE=0
PROXY_MODE=0
NPM_IP=""
while [[ $# -gt 0 ]]; do
case "$1" in
-dev) DEV_MODE=1 ;;
@ -15,12 +17,11 @@ while [[ $# -gt 0 ]]; do
done
APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
export DB_PASS REDIS_PASS
cd "$(dirname "$0")"
@ -28,7 +29,7 @@ source ./lib.sh
require_root
header
# ── Defaults ────────────────────────────────────────────────────────────────
# ── Defaults ──────────────────────────────────────────────────
APP_NAME="${APP_NAME:-MailWolt}"
APP_USER="${APP_USER:-mailwolt}"
APP_GROUP="${APP_GROUP:-www-data}"
@ -50,49 +51,109 @@ DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-}${NC}"
# ── FQDNs abfragen ───────────────────────────────────────────────────────────
read -r -p "Mailserver FQDN (MX, z.B. mx.domain.tld) [Enter=${MTA_SUB}.${BASE_DOMAIN}]: " MTA_FQDN
read -r -p "UI / Admin-Panel FQDN (z.B. ui.domain.tld) [Enter=${UI_SUB}.${BASE_DOMAIN}]: " UI_FQDN
read -r -p "Webmail FQDN (z.B. webmail.domain.tld) [Enter=${WEBMAIL_SUB}.${BASE_DOMAIN}]: " WEBMAIL_FQDN
# ── Helpers ───────────────────────────────────────────────────
have_whiptail(){ command -v whiptail >/dev/null 2>&1; }
valid_fqdn(){
[[ "$1" =~ ^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,}$ ]]
}
ask_tty_domain(){
local label="$1" example="$2" def="$3" outvar="$4" inp
echo -e "${CYAN}${label}${NC}"
echo -e " z.B. ${YELLOW}${example}${NC}"
echo -e " Default: ${GREY}${def}${NC}"
read -r -p " Eingabe (Enter=Default): " inp || true
inp="${inp:-$def}"
if ! valid_fqdn "$inp"; then
echo -e "${YELLOW}[!] Ungültiger FQDN, nehme Default: ${def}${NC}"
inp="$def"
fi
eval "$outvar='$inp'"
}
# Defaults, wenn Enter gedrückt
MTA_FQDN="${MTA_FQDN:-${MTA_SUB}.${BASE_DOMAIN}}"
UI_FQDN="${UI_FQDN:-${UI_SUB}.${BASE_DOMAIN}}"
WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_SUB}.${BASE_DOMAIN}}"
# ── Interaktive Eingaben (whiptail oder Fallback) ─────────────
MTA_DEFAULT="${MTA_SUB}.${BASE_DOMAIN}"
UI_DEFAULT="${UI_SUB}.${BASE_DOMAIN}"
WEBMAIL_DEFAULT="${WEBMAIL_SUB}.${BASE_DOMAIN}"
CLAMAV_ENABLE=1
OPENDMARC_ENABLE=1
FAIL2BAN_ENABLE=1
if have_whiptail; then
TITLE="MailWolt Setup"
MTA_FQDN="$(whiptail --title "$TITLE" --inputbox "Mailserver-FQDN (MX)\nBeispiel: mx.domain.tld" 11 70 "$MTA_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
valid_fqdn "$MTA_FQDN" || MTA_FQDN="$MTA_DEFAULT"
UI_FQDN="$(whiptail --title "$TITLE" --inputbox "UI / Admin-Panel FQDN\nBeispiel: ui.domain.tld" 11 70 "$UI_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
valid_fqdn "$UI_FQDN" || UI_FQDN="$UI_DEFAULT"
WEBMAIL_FQDN="$(whiptail --title "$TITLE" --inputbox "Webmail FQDN\nBeispiel: webmail.domain.tld" 11 70 "$WEBMAIL_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
valid_fqdn "$WEBMAIL_FQDN" || WEBMAIL_FQDN="$WEBMAIL_DEFAULT"
CHOICES="$(whiptail --title "$TITLE" --checklist "Optionale Dienste aktivieren" 15 70 6 \
"ClamAV" "Virenscan (clamd/clamav-daemon)" ON \
"OpenDMARC" "DMARC-Auswertung" ON \
"Fail2Ban" "Brute-Force-Schutz" ON \
3>&1 1>&2 2>&3)" || true
CLAMAV_ENABLE=0; [[ "$CHOICES" == *"ClamAV"* ]] && CLAMAV_ENABLE=1
OPENDMARC_ENABLE=0; [[ "$CHOICES" == *"OpenDMARC"* ]] && OPENDMARC_ENABLE=1
FAIL2BAN_ENABLE=0; [[ "$CHOICES" == *"Fail2Ban"* ]] && FAIL2BAN_ENABLE=1
whiptail --title "$TITLE" --msgbox "Zusammenfassung:
MX : $MTA_FQDN
UI : $UI_FQDN
Webmail : $WEBMAIL_FQDN
ClamAV : $([[ $CLAMAV_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
OpenDMARC : $([[ $OPENDMARC_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
Fail2Ban : $([[ $FAIL2BAN_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
" 16 70
else
echo -e "${GREY}[i] whiptail nicht gefunden nutze TTY-Prompts.${NC}\n"
ask_tty_domain "Mailserver-FQDN (MX)" "mx.domain.tld" "$MTA_DEFAULT" MTA_FQDN
ask_tty_domain "UI / Admin-Panel FQDN" "ui.domain.tld" "$UI_DEFAULT" UI_FQDN
ask_tty_domain "Webmail FQDN" "webmail.domain.tld" "$WEBMAIL_DEFAULT" WEBMAIL_FQDN
read -r -p "ClamAV aktivieren? (1/0, Enter=1): " CLAMAV_ENABLE; CLAMAV_ENABLE="${CLAMAV_ENABLE:-1}"
read -r -p "OpenDMARC aktivieren? (1/0, Enter=1): " OPENDMARC_ENABLE; OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
read -r -p "Fail2Ban aktivieren? (1/0, Enter=1): " FAIL2BAN_ENABLE; FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
fi
# ── Defaults/Kompatibilität ──────────────────────────────────
MTA_FQDN="${MTA_FQDN:-${MTA_DEFAULT}}"
UI_FQDN="${UI_FQDN:-${UI_DEFAULT}}"
WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_DEFAULT}}"
DKIM_ENABLE="${DKIM_ENABLE:-1}"
DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
DKIM_GENERATE="${DKIM_GENERATE:-1}"
# BASE_DOMAIN und Sub-Labels aus MTA/UI/WEBMAIL ableiten (robust)
if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
MTA_SUB="${BASH_REMATCH[1]}"
BASE_DOMAIN="${BASH_REMATCH[2]}"
fi
if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
UI_SUB="${BASH_REMATCH[1]}"
fi
if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
WEBMAIL_SUB="${BASH_REMATCH[1]}"
fi
# BASE_DOMAIN/Subs aus FQDNs ableiten
if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then MTA_SUB="${BASH_REMATCH[1]}"; BASE_DOMAIN="${BASH_REMATCH[2]}"; fi
if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then UI_SUB="${BASH_REMATCH[1]}"; fi
if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then WEBMAIL_SUB="${BASH_REMATCH[1]}"; fi
SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
# Kanonische Host-Variablen (NIE wieder zusammenbauen nimm die FQDNs)
MAIL_HOSTNAME="${MTA_FQDN}"
UI_HOST="${UI_FQDN}"
WEBMAIL_HOST="${WEBMAIL_FQDN}"
# Zeitzone/Locale sinnvoll setzen (könntest du auch noch abfragen)
APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
# ── Variablen exportieren ───────────────────────────────────────────────────
# ── Export & persist ─────────────────────────────────────────
export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
export DB_NAME DB_USER
export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
export CLAMAV_ENABLE OPENDMARC_ENABLE FAIL2BAN_ENABLE
install -d -m 0755 /etc/mailwolt
cat >/etc/mailwolt/installer.env <<EOF
@ -116,15 +177,350 @@ DB_HOST=127.0.0.1
DB_NAME=${DB_NAME}
DB_USER=${DB_USER}
DB_PASS=${DB_PASS}
REDIS_PASS=${REDIS_PASS}
SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
APP_ENV=${APP_ENV}
EOF
# ── Sequenz ────────────────────────────────────────────────────────────────
for STEP in 10-provision 20-ssl 21-le-deploy-hook 22-dkim-helper 30-db 40-postfix 50-dovecot 60-rspamd-opendkim 70-nginx 75-le-issue 80-app 90-services 95-monit 98-motd 99-summary
CLAMAV_ENABLE=${CLAMAV_ENABLE}
OPENDMARC_ENABLE=${OPENDMARC_ENABLE}
FAIL2BAN_ENABLE=${FAIL2BAN_ENABLE}
EOF
chmod 600 /etc/mailwolt/installer.env
# ── Installer-Sequenz ────────────────────────────────────────
for STEP in \
10-provision \
20-ssl 21-le-deploy-hook 22-dkim-helper \
30-db 40-postfix 50-dovecot \
60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban \
70-nginx 75-le-issue 80-app 90-services 95-woltguard 98-motd 99-summary
do
log ">>> Running ${STEP}.sh"
bash "./${STEP}.sh"
done
done
##!/usr/bin/env bash
#set -euo pipefail
#
## --- Flags / Modi ---
#DEV_MODE=0
#PROXY_MODE=0
#NPM_IP=""
#
#while [[ $# -gt 0 ]]; do
# case "$1" in
# -dev) DEV_MODE=1 ;;
# -proxy) PROXY_MODE=1; NPM_IP="${2:-}"; shift ;;
# esac
# shift
#done
#
#APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
#APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
#export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
#
#DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
#REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
#
#export DB_PASS REDIS_PASS
#
#cd "$(dirname "$0")"
#source ./lib.sh
#require_root
#header
#
## ── Defaults ────────────────────────────────────────────────────────────────
#APP_NAME="${APP_NAME:-MailWolt}"
#APP_USER="${APP_USER:-mailwolt}"
#APP_GROUP="${APP_GROUP:-www-data}"
#APP_USER_PREFIX="${APP_USER_PREFIX:-mw}"
#APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
#
#BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
#UI_SUB="${UI_SUB:-ui}"
#WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}"
#MTA_SUB="${MTA_SUB:-mx}"
#
#DB_NAME="${DB_NAME:-${APP_USER}}"
#DB_USER="${DB_USER:-${APP_USER}}"
#
#SERVER_PUBLIC_IPV4="$(detect_ip)"
#SERVER_PUBLIC_IPV6="$(detect_ipv6)"
#DEFAULT_TZ="$(detect_timezone)"
#DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
#
#echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-}${NC}"
#
## ── Schöne, farbige Abfragen ────────────────────────────────────────────────
#echo -e "${CYAN}"
#echo "──────────────────────────────────────────────"
#echo -e " 📧 MailWolt Setup Domain Konfiguration"
#echo "──────────────────────────────────────────────"
#echo -e "${NC}"
#
#MTA_DEFAULT="${MTA_SUB}.${BASE_DOMAIN}"
#UI_DEFAULT="${UI_SUB}.${BASE_DOMAIN}"
#WEBMAIL_DEFAULT="${WEBMAIL_SUB}.${BASE_DOMAIN}"
#
#ask_domain() {
# local __outvar="$1" label="$2" example="$3" defval="$4" input=""
# echo -e "${GREEN}[?]${NC} ${label}"
# echo -e " z.B. ${YELLOW}${example}${NC}"
# echo -e " Default: ${CYAN}${defval}${NC}"
# echo -ne " → Eingabe: ${CYAN}"
# read -r input
# echo -e "${NC}"
# if [[ -z "$input" ]]; then
# eval "$__outvar='$defval'"
# else
# eval "$__outvar='$input'"
# fi
#}
#
#ask_toggle() {
# local __outvar="$1" label="$2" defval="${3:-1}" input=""
# echo -ne "${GREEN}[?]${NC} ${label} (${CYAN}1${NC}=Ja / ${YELLOW}0${NC}=Nein) [Enter=${defval}]: "
# read -r input
# input="${input:-$defval}"
# case "$input" in
# 1|0) ;;
# *) echo -e "${YELLOW}Ungültig, nehme Default=${defval}.${NC}"; input="$defval" ;;
# esac
# eval "$__outvar='$input'"
#}
#
#ask_domain "MTA_FQDN" "Mailserver-FQDN (MX)" "mx.domain.tld" "$MTA_DEFAULT"
#ask_domain "UI_FQDN" "UI / Admin-Panel" "ui.domain.tld" "$UI_DEFAULT"
#ask_domain "WEBMAIL_FQDN" "Webmail-FQDN" "webmail.domain.tld" "$WEBMAIL_DEFAULT"
#
#echo -e "${CYAN}"
#echo "──────────────────────────────────────────────"
#echo -e " 🛡 Optionale Dienste"
#echo "──────────────────────────────────────────────"
#echo -e "${NC}"
#
#ask_toggle "CLAMAV_ENABLE" "ClamAV Virenscan aktivieren?" 1
#ask_toggle "OPENDMARC_ENABLE" "OpenDMARC auswerten?" 1
#ask_toggle "FAIL2BAN_ENABLE" "Fail2Ban aktivieren?" 1
#echo
#
## Defaults, wenn Enter gedrückt (Abwärtskompatibilität)
#MTA_FQDN="${MTA_FQDN:-${MTA_SUB}.${BASE_DOMAIN}}"
#UI_FQDN="${UI_FQDN:-${UI_SUB}.${BASE_DOMAIN}}"
#WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_SUB}.${BASE_DOMAIN}}"
#DKIM_ENABLE="${DKIM_ENABLE:-1}"
#DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
#DKIM_GENERATE="${DKIM_GENERATE:-1}"
#
## BASE_DOMAIN und Sub-Labels aus MTA/UI/WEBMAIL ableiten (robust)
#if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
# MTA_SUB="${BASH_REMATCH[1]}"
# BASE_DOMAIN="${BASH_REMATCH[2]}"
#fi
#if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
# UI_SUB="${BASH_REMATCH[1]}"
#fi
#if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
# WEBMAIL_SUB="${BASH_REMATCH[1]}"
#fi
#
#SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
#SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
## Kanonische Host-Variablen (NIE wieder zusammenbauen nimm die FQDNs)
#MAIL_HOSTNAME="${MTA_FQDN}"
#UI_HOST="${UI_FQDN}"
#WEBMAIL_HOST="${WEBMAIL_FQDN}"
#
## Zeitzone/Locale sinnvoll setzen
#APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
#APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
#
## ── Variablen exportieren ───────────────────────────────────────────────────
#export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
#export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
#export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
#export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
#export DB_NAME DB_USER
#export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
#export CLAMAV_ENABLE OPENDMARC_ENABLE FAIL2BAN_ENABLE
#
#install -d -m 0755 /etc/mailwolt
#cat >/etc/mailwolt/installer.env <<EOF
#BASE_DOMAIN=${BASE_DOMAIN}
#MTA_SUB=${MTA_SUB}
#UI_SUB=${UI_SUB}
#WEBMAIL_SUB=${WEBMAIL_SUB}
#
#MAIL_HOSTNAME=${MAIL_HOSTNAME}
#UI_HOST=${UI_HOST}
#WEBMAIL_HOST=${WEBMAIL_HOST}
#
#SYSMAIL_SUB=${SYSMAIL_SUB}
#SYSMAIL_DOMAIN=${SYSMAIL_DOMAIN}
#
#DKIM_ENABLE=${DKIM_ENABLE}
#DKIM_SELECTOR=${DKIM_SELECTOR}
#DKIM_GENERATE=${DKIM_GENERATE}
#
#DB_HOST=127.0.0.1
#DB_NAME=${DB_NAME}
#DB_USER=${DB_USER}
#DB_PASS=${DB_PASS}
#REDIS_PASS=${REDIS_PASS}
#
#SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
#SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
#APP_ENV=${APP_ENV}
#
#CLAMAV_ENABLE=${CLAMAV_ENABLE}
#OPENDMARC_ENABLE=${OPENDMARC_ENABLE}
#FAIL2BAN_ENABLE=${FAIL2BAN_ENABLE}
#EOF
#
#chmod 600 /etc/mailwolt/installer.env
#
## ── Sequenz ────────────────────────────────────────────────────────────────
#for STEP in 10-provision 20-ssl 21-le-deploy-hook 22-dkim-helper 30-db 40-postfix 50-dovecot 60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban 70-nginx 75-le-issue 80-app 90-services 95-woltguard 98-motd 99-summary
#do
# log ">>> Running ${STEP}.sh"
# bash "./${STEP}.sh"
#done
###!/usr/bin/env bash
##set -euo pipefail
##
### --- Flags / Modi ---
##DEV_MODE=0
##PROXY_MODE=0
##NPM_IP=""
##
##while [[ $# -gt 0 ]]; do
## case "$1" in
## -dev) DEV_MODE=1 ;;
## -proxy) PROXY_MODE=1; NPM_IP="${2:-}"; shift ;;
## esac
## shift
##done
##
##APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
##APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
##export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
##
##DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
##REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
##
##export DB_PASS REDIS_PASS
##
##cd "$(dirname "$0")"
##source ./lib.sh
##require_root
##header
##
### ── Defaults ────────────────────────────────────────────────────────────────
##APP_NAME="${APP_NAME:-MailWolt}"
##APP_USER="${APP_USER:-mailwolt}"
##APP_GROUP="${APP_GROUP:-www-data}"
##APP_USER_PREFIX="${APP_USER_PREFIX:-mw}"
##APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
##
##BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
##UI_SUB="${UI_SUB:-ui}"
##WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}"
##MTA_SUB="${MTA_SUB:-mx}"
##
##DB_NAME="${DB_NAME:-${APP_USER}}"
##DB_USER="${DB_USER:-${APP_USER}}"
##
##SERVER_PUBLIC_IPV4="$(detect_ip)"
##SERVER_PUBLIC_IPV6="$(detect_ipv6)"
##DEFAULT_TZ="$(detect_timezone)"
##DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
##
##echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-}${NC}"
##
### ── FQDNs abfragen ───────────────────────────────────────────────────────────
##read -r -p "Mailserver FQDN (MX, z.B. mx.domain.tld) [Enter=${MTA_SUB}.${BASE_DOMAIN}]: " MTA_FQDN
##read -r -p "UI / Admin-Panel FQDN (z.B. ui.domain.tld) [Enter=${UI_SUB}.${BASE_DOMAIN}]: " UI_FQDN
##read -r -p "Webmail FQDN (z.B. webmail.domain.tld) [Enter=${WEBMAIL_SUB}.${BASE_DOMAIN}]: " WEBMAIL_FQDN
##
### Defaults, wenn Enter gedrückt
##MTA_FQDN="${MTA_FQDN:-${MTA_SUB}.${BASE_DOMAIN}}"
##UI_FQDN="${UI_FQDN:-${UI_SUB}.${BASE_DOMAIN}}"
##WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_SUB}.${BASE_DOMAIN}}"
##DKIM_ENABLE="${DKIM_ENABLE:-1}"
##DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
##DKIM_GENERATE="${DKIM_GENERATE:-1}"
##
### BASE_DOMAIN und Sub-Labels aus MTA/UI/WEBMAIL ableiten (robust)
##if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
## MTA_SUB="${BASH_REMATCH[1]}"
## BASE_DOMAIN="${BASH_REMATCH[2]}"
##fi
##if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
## UI_SUB="${BASH_REMATCH[1]}"
##fi
##if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
## WEBMAIL_SUB="${BASH_REMATCH[1]}"
##fi
##
##SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
##SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
### Kanonische Host-Variablen (NIE wieder zusammenbauen nimm die FQDNs)
##MAIL_HOSTNAME="${MTA_FQDN}"
##UI_HOST="${UI_FQDN}"
##WEBMAIL_HOST="${WEBMAIL_FQDN}"
##
### Zeitzone/Locale sinnvoll setzen (könntest du auch noch abfragen)
##APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
##APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
##
### ── Variablen exportieren ───────────────────────────────────────────────────
##export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
##export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
##export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
##export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
##export DB_NAME DB_USER
##export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
##
##install -d -m 0755 /etc/mailwolt
##cat >/etc/mailwolt/installer.env <<EOF
##BASE_DOMAIN=${BASE_DOMAIN}
##MTA_SUB=${MTA_SUB}
##UI_SUB=${UI_SUB}
##WEBMAIL_SUB=${WEBMAIL_SUB}
##
##MAIL_HOSTNAME=${MAIL_HOSTNAME}
##UI_HOST=${UI_HOST}
##WEBMAIL_HOST=${WEBMAIL_HOST}
##
##SYSMAIL_SUB=${SYSMAIL_SUB}
##SYSMAIL_DOMAIN=${SYSMAIL_DOMAIN}
##
##DKIM_ENABLE=${DKIM_ENABLE}
##DKIM_SELECTOR=${DKIM_SELECTOR}
##DKIM_GENERATE=${DKIM_GENERATE}
##
##DB_HOST=127.0.0.1
##DB_NAME=${DB_NAME}
##DB_USER=${DB_USER}
##DB_PASS=${DB_PASS}
##REDIS_PASS=${REDIS_PASS}
##
##SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
##SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
##APP_ENV=${APP_ENV}
##
##CLAMAV_ENABLE=1
##OPENDMARC_ENABLE=1
##FAIL2BAN_ENABLE=1
##EOF
##
##chmod 600 /etc/mailwolt/installer.env
##
### ── Sequenz ────────────────────────────────────────────────────────────────
##for STEP in 10-provision 20-ssl 21-le-deploy-hook 22-dkim-helper 30-db 40-postfix 50-dovecot 60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban 70-nginx 75-le-issue 80-app 90-services 95-woltguard 98-motd 99-summary
##do
## log ">>> Running ${STEP}.sh"
## bash "./${STEP}.sh"
##done

300
scripts/update.sh Normal file
View File

@ -0,0 +1,300 @@
#!/usr/bin/env bash
set -euo pipefail
# -------- Konfiguration --------
APP_USER="mailwolt"
APP_DIR="/var/www/mailwolt"
BRANCH="${BRANCH:-main}" # nur relevant bei UPDATE_MODE=branch
MODE="${UPDATE_MODE:-tags}" # tags | branch
# -------- Helper --------
as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; }
restart_if_exists(){ local u="$1"; systemctl list-unit-files | grep -q "^${u}\.service" && systemctl restart "$u" || true; }
reload_if_active(){ local u="$1"; systemctl is-active --quiet "$u" && systemctl reload "$u" || true; }
restart_php_fpm(){
for u in php8.3-fpm php8.2-fpm php8.1-fpm php-fpm; do
if systemctl list-unit-files | grep -q "^${u}\.service"; then
systemctl restart "$u"
return 0
fi
done
}
# -------- Guards --------
[[ "$(id -u)" -eq 0 ]] || { echo "[!] Bitte als root ausführen"; exit 1; }
[[ -d "$APP_DIR/.git" ]] || { echo "[!] $APP_DIR scheint kein Git-Repo zu sein"; exit 1; }
echo "[i] Prüfe Repository …"
OLD_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
NEW_REV="$OLD_REV"
if [[ "$MODE" = "tags" ]]; then
# Auf neuesten Release-Tag wechseln (semantisch sortiert)
LATEST_TAG="$(as_app "cd ${APP_DIR} && git fetch --tags --quiet origin && git tag --list | sort -V | tail -n1")"
if [[ -z "$LATEST_TAG" ]]; then
echo "[!] Keine Tags gefunden falle auf origin/${BRANCH} zurück"
as_app "cd ${APP_DIR} && git fetch --quiet origin ${BRANCH} && git checkout -q ${BRANCH} && git pull --ff-only origin ${BRANCH}"
else
TARGET_REV="$(as_app "cd ${APP_DIR} && git rev-list -n1 ${LATEST_TAG}")"
if [[ "$TARGET_REV" = "$OLD_REV" ]]; then
echo "[✓] Bereits auf neuestem Release (${LATEST_TAG}) nichts zu tun."
exit 0
fi
echo "[i] Checkout auf Release ${LATEST_TAG} (${TARGET_REV:0:7}) …"
as_app "cd ${APP_DIR} && git checkout -q ${LATEST_TAG}"
fi
NEW_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
else
# Rolling: branch folgen
as_app "cd ${APP_DIR} && git fetch --quiet origin ${BRANCH}"
BEHIND="$(as_app "cd ${APP_DIR} && git rev-list --count HEAD..origin/${BRANCH} || echo 0")"
if [[ "$BEHIND" -eq 0 ]]; then
echo "[✓] Branch origin/${BRANCH} ist bereits aktuell nichts zu tun."
exit 0
fi
echo "[i] Es gibt ${BEHIND} neue Commit(s) ziehe Änderungen …"
as_app "cd ${APP_DIR} && git checkout -q ${BRANCH} && git pull --ff-only origin ${BRANCH}"
NEW_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
fi
# -------- Änderungstypen ermitteln --------
CHANGED_FILES="$(as_app "cd ${APP_DIR} && git diff --name-only ${OLD_REV}..${NEW_REV}")"
NEED_COMPOSER=0
NEED_MIGRATIONS=0
NEED_FRONTEND=0
NEED_PHP_RESTART=0
echo "$CHANGED_FILES" | grep -qE '(^|/)composer\.(json|lock)$' && NEED_COMPOSER=1
echo "$CHANGED_FILES" | grep -qE '^database/migrations/' && NEED_MIGRATIONS=1
echo "$CHANGED_FILES" | grep -qE '^(package(-lock)?\.json|vite\.config|resources/|public/.*\.(js|css))' && NEED_FRONTEND=1
echo "$CHANGED_FILES" | grep -qE '^(app/|routes/|config/|resources/views/)' && NEED_PHP_RESTART=1
echo "[i] Zusammenfassung:"
echo " Composer : $([[ $NEED_COMPOSER -eq 1 ]] && echo JA || echo nein)"
echo " Migrations : $([[ $NEED_MIGRATIONS -eq 1 ]] && echo JA || echo nein)"
echo " Frontend : $([[ $NEED_FRONTEND -eq 1 ]] && echo JA || echo nein)"
echo " PHP restart : $([[ $NEED_PHP_RESTART -eq 1 ]] && echo JA || echo nein)"
# Wenn gar nichts relevantes geändert wurde → sauber beenden
if [[ $NEED_COMPOSER -eq 0 && $NEED_MIGRATIONS -eq 0 && $NEED_FRONTEND -eq 0 && $NEED_PHP_RESTART -eq 0 ]]; then
echo "[✓] Code-Stand aktualisiert, aber keine Build/Runtime-Änderungen keine Neustarts nötig."
# Build-Info trotzdem aktualisieren
INST_VER="$(as_app "cd ${APP_DIR} && (cat VERSION 2>/dev/null || echo dev)")"
printf "version=%s\nrev=%s\nupdated=%s\n" "$INST_VER" "$NEW_REV" "$(date -Is)" > /etc/mailwolt/build.info || true
exit 0
fi
# -------- Gezielter Build/Deploy --------
if [[ $NEED_COMPOSER -eq 1 ]]; then
echo "[i] Composer …"
as_app "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --optimize-autoloader"
fi
if [[ $NEED_MIGRATIONS -eq 1 ]]; then
echo "[i] DB-Migrationen …"
as_app "cd ${APP_DIR} && php artisan migrate --force"
fi
if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
echo "[i] Cache/Optimierungen …"
as_app "cd ${APP_DIR} && php artisan config:cache && php artisan route:cache || true"
as_app "cd ${APP_DIR} && php artisan queue:restart || true"
as_app "cd ${APP_DIR} && php artisan optimize:clear || true"
fi
if [[ $NEED_FRONTEND -eq 1 ]]; then
echo "[i] Frontend build …"
as_app "cd ${APP_DIR} && (npm ci --no-audit --no-fund || npm install)"
as_app "cd ${APP_DIR} && npm run build"
fi
# -------- Dienste nur wenn nötig --------
echo "[i] Dienste neu laden/neustarten (gezielt) …"
if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
restart_php_fpm
restart_if_exists "${APP_USER}-queue"
restart_if_exists "${APP_USER}-schedule"
restart_if_exists "${APP_USER}-ws"
fi
if [[ $NEED_FRONTEND -eq 1 || $NEED_PHP_RESTART -eq 1 ]]; then
reload_if_active nginx
fi
# -------- Build-Info ablegen --------
INST_VER="$(as_app "cd ${APP_DIR} && (cat VERSION 2>/dev/null || echo dev)")"
printf "version=%s\nrev=%s\nupdated=%s\n" "$INST_VER" "$NEW_REV" "$(date -Is)" > /etc/mailwolt/build.info || true
echo "[✓] Update abgeschlossen: ${OLD_REV:0:7}${NEW_REV:0:7} (Version: ${INST_VER})"
##!/usr/bin/env bash
#set -euo pipefail
#
#APP_USER="mailwolt"
#APP_DIR="/var/www/mailwolt"
#BRANCH="${BRANCH:-main}" # bei Bedarf anpassen
#
#as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; }
#
#restart_if_exists(){ local u="$1"; systemctl list-unit-files | grep -q "^${u}\.service" && systemctl restart "$u" || true; }
#reload_if_active(){ local u="$1"; systemctl is-active --quiet "$u" && systemctl reload "$u" || true; }
#
#restart_php_fpm(){
# for u in php8.3-fpm php8.2-fpm php8.1-fpm php-fpm; do
# systemctl list-unit-files | grep -q "^${u}\.service" && { systemctl restart "$u"; return 0; }
# done
# return 0
#}
#
#[[ "$(id -u)" -eq 0 ]] || { echo "[!] Bitte als root ausführen"; exit 1; }
#
##!/usr/bin/env bash
#set -euo pipefail
#APP_USER="mailwolt"
#APP_DIR="/var/www/mailwolt"
#MODE="${UPDATE_MODE:-tags}"
#
#as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; }
#
## --- Ab hier wie gehabt ---
#echo "[i] Prüfe Repository …"
#if [[ "$MODE" = "tags" ]]; then
# LATEST_TAG="$(as_app "cd ${APP_DIR} && git fetch --tags --quiet origin && git tag --list | sort -V | tail -n1")"
# if [[ -n "$LATEST_TAG" ]]; then
# OLD_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
# echo "[i] Checkout auf neuesten Release: ${LATEST_TAG}"
# as_app "cd ${APP_DIR} && git checkout -q ${LATEST_TAG}"
# NEW_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
# else
# echo "[!] Keine Tags gefunden, falle auf origin/main zurück"
# as_app "cd ${APP_DIR} && git pull --ff-only origin main"
# fi
#else
# as_app "cd ${APP_DIR} && git fetch --quiet origin main && git pull --ff-only origin main"
#fi
#
#echo "[i] Prüfe Repository …"
#OLD_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
#as_app "cd ${APP_DIR} && git fetch --tags --quiet origin ${BRANCH}"
#BEHIND="$(as_app "cd ${APP_DIR} && git rev-list --count HEAD..origin/${BRANCH} || echo 0")"
#
#if [[ "${BEHIND}" -eq 0 ]]; then
# echo "[✓] Bereits aktuell nichts zu tun."
# exit 0
#fi
#
#echo "[i] Es gibt ${BEHIND} neue Commit(s) ziehe Änderungen …"
#as_app "cd ${APP_DIR} && git pull --ff-only origin ${BRANCH}"
#NEW_REV="$(as_app "cd ${APP_DIR} && git rev-parse HEAD")"
#
## Welche Bereiche haben sich geändert?
#CHANGED_FILES="$(as_app "cd ${APP_DIR} && git diff --name-only ${OLD_REV}..${NEW_REV}")"
#
#NEED_COMPOSER=0
#NEED_MIGRATIONS=0
#NEED_FRONTEND=0
#NEED_PHP_RESTART=0
#
#if echo "$CHANGED_FILES" | grep -qE '^(composer\.json|composer\.lock)'; then
# NEED_COMPOSER=1
#fi
#if echo "$CHANGED_FILES" | grep -qE '^database/migrations/'; then
# NEED_MIGRATIONS=1
#fi
#if echo "$CHANGED_FILES" | grep -qE '^(package(-lock)?\.json|vite\.config\.|resources/|public/.*\.(js|css))'; then
# NEED_FRONTEND=1
#fi
## PHP-Code/Views/Config geändert?
#if echo "$CHANGED_FILES" | grep -qE '^(app/|routes/|config/|resources/views/)'; then
# NEED_PHP_RESTART=1
#fi
#
#echo "[i] Zusammenfassung:"
#echo " Composer : $([[ $NEED_COMPOSER -eq 1 ]] && echo JA || echo nein)"
#echo " Migrations : $([[ $NEED_MIGRATIONS -eq 1 ]] && echo JA || echo nein)"
#echo " Frontend : $([[ $NEED_FRONTEND -eq 1 ]] && echo JA || echo nein)"
#echo " PHP restart : $([[ $NEED_PHP_RESTART -eq 1 ]] && echo JA || echo nein)"
#
#echo "[i] Führe Build/Deploy (gezielt) aus …"
#if [[ $NEED_COMPOSER -eq 1 ]]; then
# as_app "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --optimize-autoloader"
#fi
#
#if [[ $NEED_MIGRATIONS -eq 1 ]]; then
# as_app "cd ${APP_DIR} && php artisan migrate --force"
#fi
#
## Cache & Queue nur wenn relevant
#if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
# as_app "cd ${APP_DIR} && php artisan config:cache && php artisan route:cache || true"
# as_app "cd ${APP_DIR} && php artisan queue:restart || true"
# as_app "cd ${APP_DIR} && php artisan optimize:clear || true"
#fi
#
#if [[ $NEED_FRONTEND -eq 1 ]]; then
# as_app "cd ${APP_DIR} && (npm ci --no-audit --no-fund || npm install)"
# as_app "cd ${APP_DIR} && npm run build"
#fi
#
## Dienste nur neu laden/starten wenn nötig
#ANY_RUNTIME_CHANGE=0
#[[ $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 || $NEED_FRONTEND -eq 1 || $NEED_PHP_RESTART -eq 1 ]] && ANY_RUNTIME_CHANGE=1
#
#if [[ $ANY_RUNTIME_CHANGE -eq 1 ]]; then
# echo "[i] Dienste neu laden/neustarten (gezielt) …"
# if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
# restart_php_fpm
# restart_if_exists "${APP_USER}-queue"
# restart_if_exists "${APP_USER}-schedule"
# restart_if_exists "${APP_USER}-ws"
# fi
# # Webserver nur bei Assets/Views/Config
# if [[ $NEED_FRONTEND -eq 1 || $NEED_PHP_RESTART -eq 1 ]]; then
# reload_if_active nginx
# fi
#else
# echo "[i] Nichts zum Updaten."
#fi
#
#echo "[✓] Update auf ${NEW_REV:0:7} abgeschlossen."
##!/usr/bin/env bash
#set -euo pipefail
#
#APP_USER="mailwolt"
#APP_DIR="/var/www/mailwolt"
#
#as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; }
#
#restart_if_exists(){ local u="$1"; systemctl list-unit-files | grep -q "^${u}\.service" && systemctl restart "$u" || true; }
#reload_if_active(){ local u="$1"; systemctl is-active --quiet "$u" && systemctl reload "$u" || true; }
#
#restart_php_fpm(){
# for u in php8.3-fpm php8.2-fpm php8.1-fpm php-fpm; do
# systemctl list-unit-files | grep -q "^${u}\.service" && { systemctl restart "$u"; return 0; }
# done
# return 0
#}
#
#[[ "$(id -u)" -eq 0 ]] || { echo "[!] Bitte als root ausführen"; exit 1; }
#
#echo "[i] Code-Update & Build als ${APP_USER} …"
#as_app "cd ${APP_DIR} && git pull --ff-only"
#as_app "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --optimize-autoloader"
#as_app "cd ${APP_DIR} && php artisan migrate --force"
#as_app "cd ${APP_DIR} && php artisan config:cache && php artisan route:cache || true && php artisan queue:restart || true && php artisan optimize:clear"
#as_app "cd ${APP_DIR} && (npm ci --no-audit --no-fund || npm install)"
#as_app "cd ${APP_DIR} && npm run build"
#
#echo "[i] Dienste neu laden/neustarten …"
#restart_php_fpm
#restart_if_exists "${APP_USER}-queue"
#restart_if_exists "${APP_USER}-schedule"
#restart_if_exists "${APP_USER}-ws"
#reload_if_active nginx
#reload_if_active opendkim
#reload_if_active postfix
#reload_if_active dovecot
#reload_if_active rspamd
#
#echo "[✓] Update abgeschlossen."

33
tools/push.sh Normal file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
MSG=""
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
REMOTE="${REMOTE:-origin}"
usage(){ echo "Usage: $0 -m \"commit message\" [-b branch]"; exit 1; }
while getopts ":m:b:" opt; do
case "$opt" in
m) MSG="$OPTARG" ;;
b) BRANCH="$OPTARG" ;;
*) usage ;;
esac
done
shift $((OPTIND-1))
[[ -n "$MSG" ]] || usage
git rev-parse --git-dir >/dev/null 2>&1 || { echo "[x] kein Git-Repo"; exit 1; }
echo "[i] Add/Commit → $BRANCH"
git add -A
# Kein Commit, wenn nichts geändert ist
if ! git diff --cached --quiet; then
git commit -m "$MSG"
else
echo "[i] Nichts zu committen (Index leer) pushe nur."
fi
echo "[i] Push → $REMOTE/$BRANCH"
git push "$REMOTE" "$BRANCH"
echo "[✓] Done."

80
tools/release.sh Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE="${REMOTE:-origin}"
BRANCH="${BRANCH:-main}"
ALLOW_DIRTY=0
VERSION=""
MSG=""
usage(){
echo "Usage: $0 <X.Y.Z> [-m \"release notes\"] [-b branch] [--allow-dirty]"
exit 1
}
# Args
[[ $# -ge 1 ]] || usage
VERSION="$1"; shift || true
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "[x] Ungültige Version: $VERSION (erwarte SemVer X.Y.Z)"; exit 1; }
while [[ $# -gt 0 ]]; do
case "$1" in
-m) MSG="$2"; shift 2 ;;
-b) BRANCH="$2"; shift 2 ;;
--allow-dirty) ALLOW_DIRTY=1; shift ;;
*) usage ;;
esac
done
git rev-parse --git-dir >/dev/null 2>&1 || { echo "[x] kein Git-Repo"; exit 1; }
# Clean tree?
if [[ $ALLOW_DIRTY -eq 0 ]]; then
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "[x] Arbeitsbaum nicht sauber. Committe oder nutze --allow-dirty."
exit 1
fi
fi
# Aktuellen Stand holen
git fetch --quiet "$REMOTE" --tags
# Branch check-out/sync
git checkout -q "$BRANCH"
git pull --ff-only "$REMOTE" "$BRANCH"
TAG="v${VERSION}"
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "[x] Tag ${TAG} existiert bereits."
exit 1
fi
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
if [[ -n "$LAST_TAG" ]]; then
CHANGELOG="$(git log --pretty=format:'- %s (%h)' "${LAST_TAG}..HEAD" || true)"
else
CHANGELOG="$(git log --pretty=format:'- %s (%h)' || true)"
fi
[[ -n "$CHANGELOG" ]] || CHANGELOG="- initial release content"
# VERSION bumpen
echo -n "$VERSION" > VERSION
git add VERSION
git commit -m "chore(release): v${VERSION}"
# Tag-Message bauen
if [[ -z "$MSG" ]]; then
read -r -d '' MSG <<EOF || true
MailWolt v${VERSION}
Changes since ${LAST_TAG:-repo start}:
${CHANGELOG}
EOF
fi
git tag -a "$TAG" -m "$MSG"
# Push commit + tag
git push "$REMOTE" "$BRANCH"
git push "$REMOTE" "$TAG"
echo "[✓] Release ${TAG} erstellt & gepusht."