Files
esn-support-bash-snippets/legacy/exitrace/exitrace.sh
T
h0tznpl0tzandClaude c5fa15d28d Fix symlink resolution in exitrace
- Make SCRIPT_DIR robust for symlink execution
- Resolves lib/ dependency loading when called via ~/bin symlink
- Maintains compatibility with direct execution

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 14:28:38 +02:00

182 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# exitrace - Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
# Maintainer: ljp
# Modular version - requires lib/ directory
# -----------------------------------------------------------------------------
# Features:
# - Sucht in /var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz) (anpassbar mit $LOGROOT)
# - Zeitfenster: letzte N Stunden/Tage ODER konkretes Datum/Spanne
# - Filter: Absender (-f), Empfänger-Regex (-t), Ziel-Domain (-D), Message-ID (-m), Extra-Regex (-G)
# - Gruppiert Treffer pro Exim-Message-ID und fasst Status zusammen
# - Klassifikation: OK / PARTIAL / DELAY / FAIL / UNKNOWN
# - Optional: MX-Übersicht für -D/--domain mittels --mx
#
# Beispiele:
# exitrace -f sender@example.com -D example.de -H 48
# exitrace -m 1uwRvz-000Fz2-CI
# exitrace -t '.*@example.de' -r 2025-09-09..2025-09-11 --mx
#
# Exitcodes: 0=OK (Treffer evtl. 0), 1=Fehlerhafte Parameter/Umgebung
# -----------------------------------------------------------------------------
set -euo pipefail
# Script directory (symlink-robust)
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
# Load modules
if [[ -f "$SCRIPT_DIR/lib/date_utils.sh" ]]; then
source "$SCRIPT_DIR/lib/date_utils.sh"
else
echo "Fehler: lib/date_utils.sh nicht gefunden in $SCRIPT_DIR" >&2
exit 1
fi
if [[ -f "$SCRIPT_DIR/lib/mx_lookup.sh" ]]; then
source "$SCRIPT_DIR/lib/mx_lookup.sh"
else
echo "Fehler: lib/mx_lookup.sh nicht gefunden in $SCRIPT_DIR" >&2
exit 1
fi
# Global settings
LOGROOT="${LOGROOT:-/var/log/easyname}"
TODAY="$(date +%Y-%m-%d)"
usage() {
cat <<'USAGE'
Usage: exitrace [OPTS]
Zeitfenster (eine Option wählen):
-H, --hours N Letzte N Stunden durchsuchen
-DAYS, --days N Letzte N Tage durchsuchen
-d, --date YYYY-MM-DD Exaktes Datum
-r, --range A..B Datumsbereich (inkl.): YYYY-MM-DD..YYYY-MM-DD
Filter (beliebig kombinierbar):
-f, --from ADDR Envelope-From / "from <...>"
-t, --to REGEX Empfänger (Regex, matcht ' for <rcpt>' oder in '** rcpt')
-D, --domain NAME Ziel-Domain (z. B. example.de)
-m, --msgid ID Exim-Message-ID (z. B. 1uwRvz-000Fz2-CI)
-G, --grep REGEX Zusätzlicher Regex auf Logzeilen
Sonstiges:
--mx Zeigt MX-Einträge (und A/AAAA) für --domain
-n, --dry-run Zeigt nur, welche Logfiles untersucht würden
-h, --help Diese Hilfe
Beispiele:
exitrace -f sender@example.com -D example.de -H 48
exitrace -m 1uwRvz-000Fz2-CI
exitrace -t '.*@example.de' -r 2025-09-09..2025-09-11 --mx
USAGE
}
# --------------------------- Argumente parsen --------------------------------
HOURS=""; DAYS=""; DATE_ONE=""; DATE_RANGE=""
FROM=""; TO_REGEX=""; TARGET_DOMAIN=""; MSGID=""; GREPEXTRA=""
DRYRUN=0; WANT_MX=0
while [[ $# -gt 0 ]]; do
case "$1" in
-H|--hours)
HOURS="${2:-}"
validate_positive_integer "$HOURS" "--hours"
shift 2 ;;
-DAYS|--days)
DAYS="${2:-}"
validate_positive_integer "$DAYS" "--days"
shift 2 ;;
-d|--date)
DATE_ONE="${2:-}"
validate_date "$DATE_ONE" "--date"
shift 2 ;;
-r|--range)
DATE_RANGE="${2:-}"
validate_date_range "$DATE_RANGE"
shift 2 ;;
-f|--from) FROM="${2:-}"; shift 2 ;;
-t|--to) TO_REGEX="${2:-}"; shift 2 ;;
-D|--domain) TARGET_DOMAIN="${2:-}"; shift 2 ;;
-m|--msgid) MSGID="${2:-}"; shift 2 ;;
-G|--grep) GREPEXTRA="${2:-}"; shift 2 ;;
--mx) WANT_MX=1; shift ;;
-n|--dry-run) DRYRUN=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unbekannte Option: $1" >&2; usage; exit 1 ;;
esac
done
# ------------------------- Datums-Liste bauen --------------------------------
declare -a DATES=()
if [[ -n "$HOURS" ]]; then
mapfile -t DATES < <(gen_dates_hours_back "$HOURS")
elif [[ -n "$DAYS" ]]; then
mapfile -t DATES < <(gen_dates_days_back "$DAYS")
elif [[ -n "$DATE_ONE" ]]; then
DATES=("$DATE_ONE")
elif [[ -n "$DATE_RANGE" ]]; then
mapfile -t DATES < <(gen_dates_range "$DATE_RANGE")
else
DATES=("$TODAY")
fi
declare -a FILES=()
for d in "${DATES[@]}"; do
while read -r f; do FILES+=("$f"); done < <(files_for_date "$d" || true)
done
if (( DRYRUN )); then
echo "Würde durchsuchen:"
printf ' %s\n' "${FILES[@]}"
exit 0
fi
if [[ "${#FILES[@]}" -eq 0 ]]; then
echo "Keine Logfiles gefunden unter ${LOGROOT}/YYYY/MM/DATE.log(.gz) fürs gewählte Zeitfenster." >&2
exit 0
fi
# --------------------------- Vorfilter bauen ---------------------------------
declare -a PREF_GREP=()
[[ -n "$MSGID" ]] && PREF_GREP+=( "$MSGID" )
[[ -n "$FROM" ]] && PREF_GREP+=( "$FROM" )
[[ -n "$TARGET_DOMAIN" ]] && PREF_GREP+=( "$TARGET_DOMAIN" )
[[ -n "$GREPEXTRA" ]] && PREF_GREP+=( "$GREPEXTRA" )
# ------------------------------- Reader --------------------------------------
cat_files() {
# .log direkt lesen, .gz via zcat
local f
for f in "${FILES[@]}"; do
if [[ "$f" == *.gz ]]; then
zcat -- "$f"
else
cat -- "$f"
fi
done
}
grep_chain() {
if [[ "${#PREF_GREP[@]}" -eq 0 ]]; then
cat_files
else
# Mehrere -F -e Muster
cat_files | grep -F -e "${PREF_GREP[0]}" $(for s in "${PREF_GREP[@]:1}"; do printf ' -e %q' "$s"; done)
fi
}
# ------------------------------- MX Block ------------------------------------
if (( WANT_MX )) && [[ -n "$TARGET_DOMAIN" ]]; then
show_mx_records "$TARGET_DOMAIN"
fi
# ---------------------------- Auswertung (awk) --------------------------------
if [[ ! -f "$SCRIPT_DIR/lib/log_analyzer.awk" ]]; then
echo "Fehler: lib/log_analyzer.awk nicht gefunden in $SCRIPT_DIR" >&2
exit 1
fi
grep_chain | awk -v TO_RE="${TO_REGEX:-}" -v WANTDOM="${TARGET_DOMAIN:-}" -v MSGIDREQ="${MSGID:-}" -v FROMREQ="${FROM:-}" -v EXTRA="${GREPEXTRA:-}" -f "$SCRIPT_DIR/lib/log_analyzer.awk"
# Built with the help of Claude Code