190 lines
7.7 KiB
Bash
190 lines
7.7 KiB
Bash
#!/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
|
||
}
|