diff --git a/scripts/10-provision.sh b/scripts/10-provision.sh index 038357d..fd28ec8 100644 --- a/scripts/10-provision.sh +++ b/scripts/10-provision.sh @@ -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 diff --git a/scripts/40-postfix.sh b/scripts/40-postfix.sh index 99b22e5..0445a2a 100644 --- a/scripts/40-postfix.sh +++ b/scripts/40-postfix.sh @@ -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 diff --git a/scripts/50-dovecot.sh b/scripts/50-dovecot.sh index 4d07b08..0892cfb 100644 --- a/scripts/50-dovecot.sh +++ b/scripts/50-dovecot.sh @@ -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 = > "$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 < /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 \ No newline at end of file diff --git a/scripts/60-rspamd-opendkim.sh b/scripts/60-rspamd-opendkim.sh index 669d47d..e7e5b73 100644 --- a/scripts/60-rspamd-opendkim.sh +++ b/scripts/60-rspamd-opendkim.sh @@ -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 </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 </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)." diff --git a/scripts/61-opendmarc.sh b/scripts/61-opendmarc.sh new file mode 100644 index 0000000..c416cad --- /dev/null +++ b/scripts/61-opendmarc.sh @@ -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." \ No newline at end of file diff --git a/scripts/62-clamav.sh b/scripts/62-clamav.sh new file mode 100644 index 0000000..fd3cb07 --- /dev/null +++ b/scripts/62-clamav.sh @@ -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." \ No newline at end of file diff --git a/scripts/63-fail2ban.sh b/scripts/63-fail2ban.sh new file mode 100644 index 0000000..21ae708 --- /dev/null +++ b/scripts/63-fail2ban.sh @@ -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 +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." \ No newline at end of file diff --git a/scripts/75-le-issue.sh b/scripts/75-le-issue.sh index f87693b..7e89352 100644 --- a/scripts/75-le-issue.sh +++ b/scripts/75-le-issue.sh @@ -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 -# - diff --git a/scripts/80-app.sh b/scripts/80-app.sh index 905e24a..bc4c587 100644 --- a/scripts/80-app.sh +++ b/scripts/80-app.sh @@ -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 diff --git a/scripts/90-services.sh b/scripts/90-services.sh index 85f5098..aa4a054 100644 --- a/scripts/90-services.sh +++ b/scripts/90-services.sh @@ -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; } diff --git a/scripts/95-monit.sh b/scripts/95-monit.sh index 872a4f0..6dca86e 100644 --- a/scripts/95-monit.sh +++ b/scripts/95-monit.sh @@ -40,4 +40,8 @@ chmod 600 /etc/monit/monitrc monit -t && systemctl enable --now monit monit reload || true -log "[✓] Monit konfiguriert und gestartet" \ No newline at end of file +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'" \ No newline at end of file diff --git a/scripts/95-woltguard.sh b/scripts/95-woltguard.sh new file mode 100644 index 0000000..cb41f4d --- /dev/null +++ b/scripts/95-woltguard.sh @@ -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." \ No newline at end of file diff --git a/scripts/98-motd.sh b/scripts/98-motd.sh index c5ca436..40753d8 100644 --- a/scripts/98-motd.sh +++ b/scripts/98-motd.sh @@ -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 \ No newline at end of file +#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 \ No newline at end of file diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 2c43921..3859224 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -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 <>> Running ${STEP}.sh" bash "./${STEP}.sh" -done \ No newline at end of file +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 <>> 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 <>> Running ${STEP}.sh" +## bash "./${STEP}.sh" +##done \ No newline at end of file diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100644 index 0000000..7c78698 --- /dev/null +++ b/scripts/update.sh @@ -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." \ No newline at end of file diff --git a/tools/push.sh b/tools/push.sh new file mode 100644 index 0000000..67456ba --- /dev/null +++ b/tools/push.sh @@ -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." \ No newline at end of file diff --git a/tools/release.sh b/tools/release.sh new file mode 100644 index 0000000..75869cb --- /dev/null +++ b/tools/release.sh @@ -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 [-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 <