mailwolt-installer/scripts/lib.sh

190 lines
7.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env bash
set -euo pipefail
if [ -f /etc/mailwolt/installer.env ]; then
set -a
. /etc/mailwolt/installer.env
set +a
fi
# ── Styling ────────────────────────────────────────────────────────────────
GREEN="$(printf '\033[1;32m')"; YELLOW="$(printf '\033[1;33m')"
RED="$(printf '\033[1;31m')"; CYAN="$(printf '\033[1;36m')"
GREY="$(printf '\033[0;90m')"; NC="$(printf '\033[0m')"
BAR="──────────────────────────────────────────────────────────────────────────────"
log(){ echo -e "${GREEN}[+]${NC} $*"; }
warn(){ echo -e "${YELLOW}[!]${NC} $*"; }
err(){ echo -e "${RED}[x]${NC} $*"; }
die(){ err "$*"; exit 1; }
require_root(){ [[ "$(id -u)" -eq 0 ]] || die "Bitte als root ausführen."; }
# --- Defaults, nur wenn noch nicht gesetzt ---------------------------------
: "${APP_USER:=mailwolt}"
: "${APP_GROUP:=www-data}"
: "${APP_DIR:=/var/www/${APP_USER}}"
: "${APP_NAME:=MailWolt}"
: "${BASE_DOMAIN:=example.com}"
: "${UI_SUB:=ui}"
: "${WEBMAIL_SUB:=webmail}"
: "${MTA_SUB:=mx}"
# DB / Redis (werden später durch .env überschrieben)
: "${DB_NAME:=${APP_USER}}"
: "${DB_USER:=${APP_USER}}"
: "${DB_PASS:=}"
: "${REDIS_PASS:=}"
# Stabile Zert-Pfade (UI/WEBMAIL/MX → symlinked via 20-ssl.sh)
: "${MAIL_SSL_DIR:=/etc/ssl/mail}"
: "${UI_SSL_DIR:=/etc/ssl/ui}"
: "${WEBMAIL_SSL_DIR:=/etc/ssl/webmail}"
: "${UI_CERT:=${UI_SSL_DIR}/fullchain.pem}"
: "${UI_KEY:=${UI_SSL_DIR}/privkey.pem}"
# Optional: E-Mail für LE
: "${LE_EMAIL:=admin@${BASE_DOMAIN}}"
load_env_file(){
local f="$1"
[[ -f "$f" ]] || return 0
while IFS='=' read -r k v; do
[[ "$k" =~ ^[A-Z0-9_]+$ ]] || continue
export "$k=$v"
done < <(grep -E '^[A-Z0-9_]+=' "$f")
}
header(){ echo -e "${CYAN}${BAR}${NC}
${CYAN} :::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
${CYAN} +:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
${CYAN} +:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
${CYAN} +#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+
${CYAN} +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
${CYAN} #+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
${CYAN} ### ### ### ### ########### ########## ### ### ######## ########## ###
${CYAN} ${CYAN}${BAR}${NC}\n"; }
#header(){ echo -e "${CYAN}${BAR}${NC}
#${CYAN} 888b d888 d8b 888 888 888 888 888
#${CYAN} 8888b d8888 Y8P 888 888 o 888 888 888
#${CYAN} 88888b.d88888 888 888 d8b 888 888 888
#${CYAN} 888Y88888P888 8888b. 888 888 888 d888b 888 .d88b. 888 888888
#${CYAN} 888 Y888P 888 '88b 888 888 888d88888b888 d88''88b 888 888
#${CYAN} 888 Y8P 888 .d888888 888 888 88888P Y88888 888 888 888 888
#${CYAN} 888 ' 888 888 888 888 888 8888P Y8888 Y88..88P 888 Y88b.
#${CYAN} 888 888 'Y888888 888 888 888P Y888 'Y88P' 888 'Y888
#${CYAN}${BAR}${NC}\n"; }
detect_ip(){
local ip
ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')" || true
[[ -n "${ip:-}" ]] || ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
[[ -n "${ip:-}" ]] || die "Konnte Server-IP nicht ermitteln."
echo "$ip"
}
detect_ipv4() {
local ext=""
if command -v curl >/dev/null 2>&1; then
ext="$(curl -fsS --max-time 2 https://ifconfig.me 2>/dev/null || true)"
[[ "$ext" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || ext=""
fi
echo "$ext"
}
detect_ipv6(){
local ip6
ip6="$(ip -6 addr show scope global 2>/dev/null | awk '/inet6/{print $2}' | cut -d/ -f1 | head -n1)" || true
[[ -n "${ip6:-}" ]] || ip6="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i ~ /:/){print $i; exit}}')" || true
echo "${ip6:-}"
}
detect_timezone(){
local tz
if command -v timedatectl >/dev/null 2>&1; then
tz="$(timedatectl show -p Timezone --value 2>/dev/null | tr -d '[:space:]')" || true
[[ -n "${tz:-}" && "$tz" == */* ]] && { echo "$tz"; return; }
fi
[[ -r /etc/timezone ]] && { tz="$(sed -n '1p' /etc/timezone | tr -d '[:space:]')" || true; [[ "$tz" == */* ]] && { echo "$tz"; return; }; }
if [[ -L /etc/localtime ]]; then
tz="$(readlink -f /etc/localtime 2>/dev/null || true)"; tz="${tz#/usr/share/zoneinfo/}"
[[ "$tz" == */* ]] && { echo "$tz"; return; }
fi
if command -v curl >/dev/null 2>&1; then
tz="$(curl -fsSL --max-time 3 https://ipapi.co/timezone 2>/dev/null || true)"; [[ "$tz" == */* ]] && { echo "$tz"; return; }
fi
echo "UTC"
}
guess_locale_from_tz(){ case "${1:-UTC}" in
Europe/Berlin|Europe/Vienna|Europe/Zurich|Europe/Luxembourg|Europe/Brussels|Europe/Amsterdam) echo "de";;
*) echo "en";; esac; }
resolve_ok(){ local host="$1"; getent ahosts "$host" | awk '{print $1}' | sort -u | grep -q -F "${SERVER_PUBLIC_IPV4:-}" ; }
join_host(){ local sub="$1" base="$2"; [[ -z "$sub" ]] && echo "$base" || echo "$sub.$base"; }
# dns_preflight HOST [HOST2 ...]
# Prüft: A-Record → SERVER_PUBLIC_IPV4, MX (nur wenn HOST == MAIL_HOSTNAME), PTR.
# Gibt strukturierte Zeilen aus: OK|WARN|FAIL <host> <check> <detail>
# Rückgabe 0 = alles OK; 1 = mind. ein FAIL.
dns_preflight(){
local overall=0
local server_ip="${SERVER_PUBLIC_IPV4:-}"
_dns_line(){ local level="$1" host="$2" check="$3" detail="$4"
case "$level" in
OK) echo -e "${GREEN}[DNS OK ]${NC} ${host} ${GREY}${check}${NC}${detail}" ;;
WARN) echo -e "${YELLOW}[DNS WARN]${NC} ${host} ${GREY}${check}${NC}${detail}" ;;
FAIL) echo -e "${RED}[DNS FAIL]${NC} ${host} ${GREY}${check}${NC}${detail}"; overall=1 ;;
esac
}
for host in "$@"; do
[[ -z "$host" || "$host" == "example.com" ]] && continue
# A-Record
local a_ip
a_ip="$(dig +short A "$host" @1.1.1.1 2>/dev/null | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1)"
if [[ -z "$a_ip" ]]; then
_dns_line FAIL "$host" "A-Record" "kein Eintrag gefunden"
elif [[ -n "$server_ip" && "$a_ip" != "$server_ip" ]]; then
_dns_line FAIL "$host" "A-Record" "${a_ip}${server_ip} (Server-IP)"
else
_dns_line OK "$host" "A-Record" "${a_ip}"
fi
# MX (nur für MAIL_HOSTNAME)
if [[ "$host" == "${MAIL_HOSTNAME:-}" ]]; then
local mx
mx="$(dig +short MX "$host" @1.1.1.1 2>/dev/null | awk '{print $2}' | head -n1)"
if [[ -z "$mx" ]]; then
_dns_line WARN "$host" "MX-Record" "kein Eintrag ausgehende Mail ggf. eingeschränkt"
else
_dns_line OK "$host" "MX-Record" "${mx}"
fi
fi
# PTR (nur wenn IP bekannt)
if [[ -n "$server_ip" && -n "$a_ip" && "$a_ip" == "$server_ip" ]]; then
local ptr
ptr="$(dig +short -x "$server_ip" @1.1.1.1 2>/dev/null | head -n1 | sed 's/\.$//')"
if [[ -z "$ptr" ]]; then
_dns_line WARN "$host" "PTR-Record" "kein Reverse-DNS kann Spam-Score erhöhen"
elif [[ "$ptr" != "$host" && "$ptr" != "${MAIL_HOSTNAME:-}" ]]; then
_dns_line WARN "$host" "PTR-Record" "${ptr} (zeigt nicht auf ${host})"
else
_dns_line OK "$host" "PTR-Record" "${ptr}"
fi
fi
done
return $overall
}
upsert_env(){ # upsert in $ENV_FILE
local k="$1" v="$2" ek ev
ek="$(printf '%s' "$k" | sed -e 's/[.[\*^$(){}+?|/]/\\&/g')"
ev="$(printf '%s' "$v" | sed -e 's/[&/]/\\&/g')"
if grep -qE "^[#[:space:]]*${ek}=" "$ENV_FILE" 2>/dev/null; then
sed -Ei "s|^[#[:space:]]*${ek}=.*|${k}=${ev}|g" "$ENV_FILE"
else
printf '%s=%s\n' "$k" "$v" >> "$ENV_FILE"
fi
}