Add exitrace tool and improve documentation
- Add exitrace: Enhanced Exim log analyzer with status classification - Add Maintainer: ljp to all ljp-owned scripts - Update READMEs: Use installer-compatible command examples - Update main and legacy READMEs to include exitrace tool
This commit is contained in:
@@ -45,6 +45,9 @@ Optimierte Bash-Konfigurationen für verschiedene Support-Umgebungen:
|
||||
- [`pincMails`](./legacy/pincMails)
|
||||
Sucht nach versendeten PINC Mails einer bestimmten Kundennummer und kopiert diese in einen lokalen Unterordner
|
||||
|
||||
- [`exitrace`](./legacy/exitrace)
|
||||
Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele mit Status-Klassifikation (OK/PARTIAL/DELAY/FAIL)
|
||||
|
||||
## WH20 Snippets
|
||||
|
||||
- [`plesk`](./wh20/plesk)
|
||||
@@ -116,7 +119,7 @@ cd /pfad/zu/esn-support-tools
|
||||
|
||||
**Verfügbare Kategorien:**
|
||||
- **Bash-Profile**: .bashrc Optimierungen und modulare Konfiguration
|
||||
- **Legacy Tools**: checkms, go, pincMails für Legacy-Infrastruktur
|
||||
- **Legacy Tools**: checkms, go, pincMails, exitrace für Legacy-Infrastruktur
|
||||
- **WH20 Tools**: plesk für WH20-Infrastruktur
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh - Installationsscript für easyname Support Bash-Snippets
|
||||
# Maintainer: ljp
|
||||
# Modular erweiterbar für neue Tools und Kategorien
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -36,6 +36,11 @@ Sucht nach versendeten PINC Mails einer bestimmten Kundennummer und kopiert dies
|
||||
|
||||
- [Dokumentation & Beispiele](./pincMails/README.md)
|
||||
|
||||
### [`exitrace`](./exitrace)
|
||||
Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele. Durchsucht und analysiert Exim-Logs nach Message-IDs und gruppiert alle Ereignisse einer Mail.
|
||||
|
||||
- [Dokumentation & Beispiele](./exitrace/README.md)
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
+13
-14
@@ -24,11 +24,10 @@ Neben der Auswertung kann das Skript auch die Konfiguration (Fehlercodes, SSH-Us
|
||||
|
||||
## Installation
|
||||
|
||||
1. Repository klonen:
|
||||
1. Installation über Installer:
|
||||
```bash
|
||||
git clone <gitea-url>/esn-support-bash-snippets.git
|
||||
cd esn-support-bash-snippets/legacy/checkms
|
||||
chmod +x checkms
|
||||
# Im Repository-Hauptverzeichnis
|
||||
./install.sh
|
||||
```
|
||||
|
||||
2. Beim ersten Start erstellt `checkms` automatisch die Konfiguration unter:
|
||||
@@ -46,21 +45,21 @@ Neben der Auswertung kann das Skript auch die Konfiguration (Fehlercodes, SSH-Us
|
||||
|
||||
### Standardanalyse
|
||||
```bash
|
||||
./checkms
|
||||
checkms
|
||||
```
|
||||
→ analysiert das heutige Logfile komplett.
|
||||
|
||||
### Tageslog
|
||||
```bash
|
||||
./checkms 2025-09-08
|
||||
checkms 2025-09-08
|
||||
```
|
||||
→ analysiert das Logfile vom 08.09.2025
|
||||
(automatisch `.log` oder `.log.gz`).
|
||||
|
||||
### Zeitfenster
|
||||
```bash
|
||||
./checkms --minutes 30 # letzte 30 Minuten
|
||||
./checkms --hours 2 # letzte 2 Stunden
|
||||
checkms --minutes 30 # letzte 30 Minuten
|
||||
checkms --hours 2 # letzte 2 Stunden
|
||||
```
|
||||
|
||||
---
|
||||
@@ -69,7 +68,7 @@ Neben der Auswertung kann das Skript auch die Konfiguration (Fehlercodes, SSH-Us
|
||||
|
||||
### Aktuelle Config ansehen
|
||||
```bash
|
||||
./checkms --show-config
|
||||
checkms --show-config
|
||||
```
|
||||
|
||||
Beispielausgabe:
|
||||
@@ -96,18 +95,18 @@ ID CODE
|
||||
|
||||
### SSH-User ändern
|
||||
```bash
|
||||
./checkms --set-user alice --show-config
|
||||
checkms --set-user alice --show-config
|
||||
```
|
||||
|
||||
### Code hinzufügen
|
||||
```bash
|
||||
./checkms --add-code S77714
|
||||
./checkms --add-code 'S77[0-9]{3}' --show-config
|
||||
checkms --add-code S77714
|
||||
checkms --add-code 'S77[0-9]{3}' --show-config
|
||||
```
|
||||
|
||||
### Code löschen
|
||||
```bash
|
||||
./checkms --del-code 2 --show-config
|
||||
checkms --del-code 2 --show-config
|
||||
```
|
||||
→ löscht den Code mit ID `2` (siehe `--show-config`).
|
||||
|
||||
@@ -116,7 +115,7 @@ ID CODE
|
||||
## Optionen
|
||||
|
||||
```
|
||||
Usage: ./checkms [OPTIONS] [YYYY-MM-DD]
|
||||
Usage: checkms [OPTIONS] [YYYY-MM-DD]
|
||||
|
||||
Analyse von MS-Fehlercodes im Exim-Log und Pflege der Codes.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# checkms - Analyse von MS-Fehlercodes im Exim-Log + Code-Management
|
||||
# Maintainer: ljp
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# exitrace
|
||||
|
||||
Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele:
|
||||
Durchsucht Exim-Logs nach Message-IDs, gruppiert alle Ereignisse und klassifiziert den Delivery-Status
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Sucht in `/var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz)` (konfigurierbar via `$LOGROOT`)
|
||||
- Zeitfenster: letzte N Stunden/Tage ODER konkretes Datum/Datumsbereich
|
||||
- Filter: Absender, Empfänger (Regex), Domain, Message-ID, zusätzliche Regex
|
||||
- Gruppiert alle Treffer pro Exim-Message-ID und fasst Status zusammen
|
||||
- **Status-Klassifikation**: OK / PARTIAL / DELAY / FAIL / UNKNOWN
|
||||
- **MX-Records**: Optional DNS-Abfragen für Ziel-Domain (--mx)
|
||||
- Markiert häufige Fehler (z. B. "all hosts for 'domain' have been failing")
|
||||
- Sortierte chronologische Ausgabe pro Message-ID
|
||||
- **Komprimierte Logs**: Automatischer Support für .gz-Archive
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
1. Repository klonen oder Skript kopieren:
|
||||
```bash
|
||||
git clone <gitea-url>/esn-support-bash-snippets.git
|
||||
cd esn-support-bash-snippets/legacy/exitrace
|
||||
chmod +x exitrace.sh
|
||||
```
|
||||
|
||||
2. Abhängigkeiten:
|
||||
- Standard Unix-Tools (awk, grep, date, zcat)
|
||||
- `dig` (optional, für --mx Feature)
|
||||
- Lesezugriff auf `/var/log/easyname/` (oder custom `$LOGROOT`)
|
||||
|
||||
---
|
||||
|
||||
## Nutzung
|
||||
|
||||
### Standardaufruf
|
||||
```bash
|
||||
exitrace
|
||||
```
|
||||
(durchsucht heutiges Log ohne Filter)
|
||||
|
||||
### Beispiele
|
||||
```bash
|
||||
# Mail-Trace einer konkreten Message-ID
|
||||
exitrace -m 1uwRvz-000Fz2-CI
|
||||
|
||||
# Alle Mails von bestimmtem Absender in den letzten 48 Stunden
|
||||
exitrace -f sender@example.com -H 48
|
||||
|
||||
# Empfänger-Domain in Zeitspanne
|
||||
exitrace -D example.de -r 2025-09-09..2025-09-11
|
||||
|
||||
# Regex-Filter auf Empfänger + konkretes Datum
|
||||
exitrace -t '.*@example\.de' -d 2025-09-10
|
||||
|
||||
# Kombinierte Filter: Absender + Domain + Zeitfenster + MX-Info
|
||||
exitrace -f sender@example.com -D example.de -H 48 --mx
|
||||
|
||||
# Dry-Run: zeigt nur, welche Logfiles durchsucht würden
|
||||
exitrace -n -H 24
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optionen
|
||||
|
||||
```
|
||||
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 nach " for <rcpt>" oder in "** ...")
|
||||
-D, --domain NAME Ziel-Domain (z. B. example.de), prüft auf Zustellungen/Fehler dorthin
|
||||
-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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispielausgabe
|
||||
|
||||
```
|
||||
[OK] ID=1uwRvz-000Fz2-CI | First=2025-09-10 14:32:15 | From=sender@example.com | Rcpts=recipient@example.de | Delivered=1 Deferred=0 Failed=0
|
||||
------------------------------------------------------------------------
|
||||
ID: 1uwRvz-000Fz2-CI
|
||||
First: 2025-09-10 14:32:15
|
||||
Last: 2025-09-10 14:32:47
|
||||
From: sender@example.com
|
||||
Rcpts: recipient@example.de
|
||||
Status: OK (Delivered=1, Deferred=0, Failed=0)
|
||||
Log:
|
||||
2025-09-10 14:32:15 | <= sender@example.com H=mail.example.com [10.0.0.1] P=esmtp S=1234
|
||||
2025-09-10 14:32:47 | => recipient@example.de <recipient@example.de> R=dnslookup T=remote_smtp H=mx.example.de [10.0.0.2]
|
||||
------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
**Status-Klassifikation:**
|
||||
- `OK`: Alle Mails erfolgreich zugestellt
|
||||
- `PARTIAL`: Teilweise zugestellt, aber auch Fehler/Delays
|
||||
- `DELAY`: Noch in Warteschlange (Deferred)
|
||||
- `FAIL`: Permanent fehlgeschlagen
|
||||
- `UNKNOWN`: Status unklar
|
||||
|
||||
Bei MX-Abfrage (--mx) wird zusätzlich angezeigt:
|
||||
```
|
||||
== MX für example.de ==
|
||||
10 mx1.example.de.
|
||||
20 mx2.example.de.
|
||||
-- A/AAAA der MX:
|
||||
mx1.example.de. | 192.0.2.1,192.0.2.2
|
||||
mx2.example.de. | 192.0.2.3
|
||||
========================================================================
|
||||
```
|
||||
|
||||
Bei Delivery-Stalls:
|
||||
```
|
||||
Note: DELIVERY STALL erkannt: 'all hosts for domain failing (retry/backoff)'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup-Hinweise
|
||||
|
||||
Nach Installation über `./install.sh` ist das Script direkt verfügbar:
|
||||
```bash
|
||||
exitrace -m 1uwRvz-000Fz2-CI
|
||||
```
|
||||
|
||||
Das Installationsscript erstellt automatisch einen Symlink in `~/bin` (ohne `.sh` Extension).
|
||||
|
||||
Umgebungsvariable für abweichenden Logpfad:
|
||||
```bash
|
||||
export LOGROOT="/var/log/custom"
|
||||
```
|
||||
|
||||
Logger-spezifisch ggf. in `~/.profile` ergänzen:
|
||||
```bash
|
||||
cd /var/log/easyname/$(date +%Y)/$(date +%m)/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Das Skript analysiert Exim-Logs im Format `/var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz)`
|
||||
- **Komprimierte Archive**: Automatische Erkennung und Verarbeitung von .gz-Dateien
|
||||
- **Status-Logik**: Basiert auf SMTP-Response-Codes und typischen Exim-Nachrichten
|
||||
- Bei größeren Zeiträumen kann die Verarbeitung länger dauern
|
||||
- Regex-Filter (-t, -G) sollten sorgfältig formuliert werden, um false positives zu vermeiden
|
||||
- Die Ausgabe ist nach chronologischem Erstkontakt der Message-ID sortiert
|
||||
- **MX-Feature**: Benötigt `dig` für DNS-Abfragen
|
||||
- Exit-Codes: 0 = OK (auch bei 0 Treffern), 1 = Fehlerhafte Parameter/Umgebung
|
||||
|
||||
Built with the help of Claude Code
|
||||
Executable
+368
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env bash
|
||||
# exitrace - Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
|
||||
# Maintainer: ljp
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
LOGROOT="${LOGROOT:-/var/log/easyname}"
|
||||
DATE_FMT="%Y-%m-%d"
|
||||
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:-}"; shift 2 ;;
|
||||
-DAYS|--days) DAYS="${2:-}"; shift 2 ;;
|
||||
-d|--date) DATE_ONE="${2:-}"; shift 2 ;;
|
||||
-r|--range) DATE_RANGE="${2:-}"; 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 --------------------------------
|
||||
files_for_date() {
|
||||
local d="$1" y="${d:0:4}" m="${d:5:2}"
|
||||
local p="${LOGROOT}/${y}/${m}/${d}.log"
|
||||
if [[ -f "$p" ]]; then
|
||||
printf "%s\n" "$p"
|
||||
fi
|
||||
if [[ -f "${p}.gz" ]]; then
|
||||
printf "%s\n" "${p}.gz"
|
||||
fi
|
||||
}
|
||||
|
||||
gen_dates_hours_back() {
|
||||
local hrs="$1" start_ts end_ts cur
|
||||
end_ts="$(date +%s)"
|
||||
start_ts="$(date -d "-${hrs} hours" +%s)"
|
||||
cur="$(date -d "@$start_ts" +${DATE_FMT})"
|
||||
while :; do
|
||||
echo "$cur"
|
||||
[[ "$(date -d "$cur +1 day" +%s)" -le "$end_ts" ]] || break
|
||||
cur="$(date -d "$cur +1 day" +${DATE_FMT})"
|
||||
done
|
||||
}
|
||||
|
||||
gen_dates_days_back() {
|
||||
local days="$1" i
|
||||
for ((i=0;i<=days;i++)); do
|
||||
date -d "-$i day" +${DATE_FMT}
|
||||
done
|
||||
}
|
||||
|
||||
gen_dates_range() {
|
||||
local a="${1%%..*}" b="${1##*..}" cur="$a"
|
||||
while :; do
|
||||
echo "$cur"
|
||||
[[ "$cur" == "$b" ]] && break
|
||||
cur="$(date -d "$cur +1 day" +${DATE_FMT})"
|
||||
done
|
||||
}
|
||||
|
||||
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
|
||||
if command -v dig >/dev/null 2>&1; then
|
||||
echo "== MX für ${TARGET_DOMAIN} =="
|
||||
dig +short MX "$TARGET_DOMAIN" | sort -n || true
|
||||
echo "-- A/AAAA der MX:"
|
||||
while read -r mx; do
|
||||
host="${mx#* }"
|
||||
printf "%-35s | " "$host"
|
||||
dig +short A "$host" | paste -sd, - || true
|
||||
printf "%-35s | " ""
|
||||
dig +short AAAA "$host" | paste -sd, - || true
|
||||
done < <(dig +short MX "$TARGET_DOMAIN" | sort -n)
|
||||
echo "========================================================================"
|
||||
else
|
||||
echo "(Hinweis) dig nicht gefunden, MX-Ausgabe übersprungen."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------- Auswertung (awk) --------------------------------
|
||||
grep_chain | awk -v TO_RE="${TO_REGEX:-}" -v WANTDOM="${TARGET_DOMAIN:-}" -v MSGIDREQ="${MSGID:-}" -v FROMREQ="${FROM:-}" -v EXTRA="${GREPEXTRA:-}" '
|
||||
function trim(s){ sub(/^[ \t\r\n]+/, "", s); sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function add_rcpt(id, rc){ if (!(rc in rcpts[id])) rcpts[id][rc]=1 }
|
||||
function incr(a, k){ a[k]++; return a[k] }
|
||||
|
||||
BEGIN { FS=" " }
|
||||
|
||||
{
|
||||
raw=$0
|
||||
|
||||
# Zeit + ID detektieren
|
||||
match(raw, /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [A-Za-z0-9-]+/, t)
|
||||
if (!t[0]) next
|
||||
split(t[0], p, " ")
|
||||
ts = p[1] " " p[2]
|
||||
sub(/^[0-9-: ]+ /, "", raw_after) # restline
|
||||
msgid = $1
|
||||
if (!msgid) next
|
||||
|
||||
if (MSGIDREQ != "" && msgid != MSGIDREQ) next
|
||||
|
||||
# Init Times
|
||||
if (!(msgid in first_ts) || ts < first_ts[msgid]) first_ts[msgid]=ts
|
||||
if (!(msgid in last_ts) || ts > last_ts[msgid]) last_ts[msgid]=ts
|
||||
|
||||
# Sender extrahieren
|
||||
if (index($0, "<=") == 1 || $2 == "<=") {
|
||||
if (match($0, / from <[^>]+>/, m)) {
|
||||
s=m[0]; gsub(/.* from </, "", s); gsub(/>.*/, "", s)
|
||||
from=trim(s)
|
||||
} else if (match($0, /<= [^ ]+@[^ ]+/, m2)) {
|
||||
split(m2[0], a, " "); from=trim(a[2])
|
||||
} else { from="" }
|
||||
if (from != "") {
|
||||
from_addr[msgid]=from
|
||||
if (FROMREQ != "" && from != FROMREQ) from_mismatch[msgid]=1
|
||||
}
|
||||
}
|
||||
|
||||
# Empfänger extrahieren
|
||||
rcpt=""
|
||||
if (match($0, / for <[^>]+>/, r)) {
|
||||
rcpt=r[0]; gsub(/ for </, "", rcpt); gsub(/>/, "", rcpt); rcpt=trim(rcpt)
|
||||
} else if (match($0, /\*\* [^ ]+@[^ ]+/, r2)) {
|
||||
split(r2[0], b, " "); rcpt=trim(b[2])
|
||||
}
|
||||
if (rcpt != "") add_rcpt(msgid, rcpt)
|
||||
|
||||
# Klassify line types
|
||||
is_delivery = index($0, " => ")>0
|
||||
is_fail = index($0, " ** ")>0
|
||||
is_remote = (index($0, " T=remote_smtp")>0 || index($0, " T=remote_smtp_dkim")>0)
|
||||
|
||||
# Success detection: C="250 ..." oder "250 " in C=...
|
||||
is_success = 0
|
||||
if (is_delivery && is_remote) {
|
||||
if (match($0, /C="[^"]*250[^"]*"/)) is_success=1
|
||||
}
|
||||
|
||||
# Deferred detection
|
||||
# typische Phrasen: "retry time not reached", "temporarily", "Connection timed out", "have been failing"
|
||||
is_defer = 0
|
||||
if (is_remote && (index($0, "retry time not reached")>0 || index($0, "temporar")>0 || index($0, "timed out")>0 || match($0, /all hosts for '\''[^']+'\'' have been failing/))) {
|
||||
is_defer=1
|
||||
}
|
||||
|
||||
# Fail detection (permanent)
|
||||
# Zeilen mit "** rcpt ..." sind i. d. R. permanent; weitere Marker: "Unrouteable address", "rejected", "mailbox full" kann defer sein -> belassen
|
||||
is_perm_fail = 0
|
||||
if (is_fail) {
|
||||
is_perm_fail=1
|
||||
} else if (match($0, /(Unrouteable address|no such user|User unknown|550 )/)) {
|
||||
is_perm_fail=1
|
||||
}
|
||||
|
||||
# Domainfilter (weicher): wir verwerfen die ID nicht, zeigen aber non-matching Zeilen trotzdem mit
|
||||
if (WANTDOM != "") {
|
||||
# keine harte Filterung, nur spätere Auswertung nutzt WANTDOM, falls nötig
|
||||
;
|
||||
}
|
||||
|
||||
# Extra Regex? (nur für Zeilenfilterung, ID bleibt)
|
||||
if (EXTRA != "" && $0 !~ EXTRA) {
|
||||
; # wir speichern die Zeile dennoch im Voll-Log, sonst verliert man Kontext
|
||||
}
|
||||
|
||||
# Counters
|
||||
if (is_success) deliveries[msgid]++
|
||||
if (is_defer) defers[msgid]++
|
||||
if (is_perm_fail) fails[msgid]++
|
||||
|
||||
# „have been failing …" Marker
|
||||
if (match($0, /all hosts for '\''[^']+'\'' have been failing/)) {
|
||||
fail_marker[msgid]=1
|
||||
}
|
||||
|
||||
# Full log sammeln
|
||||
status[msgid]=(status[msgid] ? status[msgid] "\n" : "") ts " | " $0
|
||||
}
|
||||
|
||||
END {
|
||||
# IDs einsammeln & sortieren
|
||||
n=0
|
||||
for (id in first_ts){ ids[++n]=id }
|
||||
for (i=2;i<=n;i++){
|
||||
key=ids[i]; j=i-1
|
||||
while (j>=1 && first_ts[ids[j]] > first_ts[key]) { ids[j+1]=ids[j]; j-- }
|
||||
ids[j+1]=key
|
||||
}
|
||||
|
||||
if (n==0){
|
||||
print "Keine passenden Einträge im gewählten Zeitfenster gefunden."
|
||||
exit 0
|
||||
}
|
||||
|
||||
sep="------------------------------------------------------------------------"
|
||||
for (k=1;k<=n;k++){
|
||||
id=ids[k]
|
||||
|
||||
# FROM Nachfilter
|
||||
if (FROMREQ != "" && from_mismatch[id]==1) {
|
||||
if (from_addr[id] != FROMREQ && from_addr[id] != "") continue
|
||||
}
|
||||
|
||||
d=(id in deliveries ? deliveries[id] : 0)
|
||||
e=(id in defers ? defers[id] : 0)
|
||||
f=(id in fails ? fails[id] : 0)
|
||||
|
||||
# Status-Logik:
|
||||
# - d>0 && f==0 && e==0 -> OK
|
||||
# - d>0 && (f>0 || e>0) -> PARTIAL
|
||||
# - d==0 && f>0 -> FAIL
|
||||
# - d==0 && f==0 && e>0 -> DELAY
|
||||
# - sonst -> UNKNOWN
|
||||
status_txt="UNKNOWN"
|
||||
if (d>0 && f==0 && e==0) status_txt="OK"
|
||||
else if (d>0 && (f>0 || e>0)) status_txt="PARTIAL"
|
||||
else if (d==0 && f>0) status_txt="FAIL"
|
||||
else if (d==0 && f==0 && e>0) status_txt="DELAY"
|
||||
|
||||
# Summary-Zeile
|
||||
# Rcpts sammeln
|
||||
out_rcpts=""
|
||||
if (id in rcpts){
|
||||
for (r in rcpts[id]) {
|
||||
out_rcpts = (out_rcpts=="" ? r : out_rcpts ", " r)
|
||||
}
|
||||
}
|
||||
printf "[%s] ID=%s | First=%s | From=%s | Rcpts=%s | Delivered=%d Deferred=%d Failed=%d\n",
|
||||
status_txt, id, first_ts[id], (from_addr[id] ? from_addr[id] : "(?)"), (out_rcpts=="" ? "(?)" : out_rcpts), d, e, f
|
||||
|
||||
# Marker-Hinweis
|
||||
if (fail_marker[id]) {
|
||||
printf " Note: DELIVERY STALL erkannt: '\''all hosts for domain failing (retry/backoff)'\''\n"
|
||||
}
|
||||
|
||||
# Detailblock
|
||||
print sep
|
||||
printf "ID: %s\n", id
|
||||
printf "First: %s\n", first_ts[id]
|
||||
printf "Last: %s\n", last_ts[id]
|
||||
printf "From: %s\n", (from_addr[id] ? from_addr[id] : "(unbekannt)")
|
||||
printf "Rcpts: %s\n", (out_rcpts=="" ? "(unbekannt)" : out_rcpts)
|
||||
printf "Status: %s (Delivered=%d, Deferred=%d, Failed=%d)\n", status_txt, d, e, f
|
||||
print "Log:"
|
||||
print status[id]
|
||||
print sep
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
# Built with the help of Claude Code
|
||||
+4
-7
@@ -15,16 +15,13 @@ Bash-Skript für schnelle SSH-Verbindungen zu easyname-Servern mit Kerberos-Auth
|
||||
export LUSER="deinusername"
|
||||
```
|
||||
|
||||
2. **Skript ausführbar machen**:
|
||||
2. **Installation über Installer**:
|
||||
```bash
|
||||
chmod +x go.sh
|
||||
# Im Repository-Hauptverzeichnis
|
||||
./install.sh
|
||||
```
|
||||
|
||||
3. **Optional: In PATH hinzufügen** oder Alias erstellen:
|
||||
```bash
|
||||
# Alias in ~/.bashrc
|
||||
alias go="/pfad/zu/go.sh"
|
||||
```
|
||||
Das Installationsscript erstellt automatisch einen Symlink in `~/bin`, sodass `go` direkt verfügbar ist.
|
||||
|
||||
## Verwendung
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# go - SSH-Shortcuts für easyname Server
|
||||
# Maintainer: ljp
|
||||
set -euo pipefail
|
||||
|
||||
LUSER="${LUSER:?Bitte LUSER setzen!}"
|
||||
|
||||
Reference in New Issue
Block a user