Merge branch 'feature/deploy-system'
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy.sh - Einfaches Deployment-Tool für esn-support-bash-snippets
|
||||
# Maintainer: ljp
|
||||
#
|
||||
# Erkennt installierte Scripts in ~/bin und ermöglicht einfache Auswahl
|
||||
set -euo pipefail
|
||||
|
||||
# Prüfe LUSER Environment
|
||||
LUSER="${LUSER:?Bitte LUSER setzen! (siehe go-Script)}"
|
||||
|
||||
# Script directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BIN_DIR="$HOME/bin"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: deploy.sh
|
||||
|
||||
EINFACHES DEPLOYMENT:
|
||||
- Erkennt automatisch installierte Scripts in ~/bin
|
||||
- Einfache Auswahl mit bash select
|
||||
- Einfache Server-Auswahl (web, mx, logger, support)
|
||||
- Optional: bashrc deployment
|
||||
|
||||
FEATURES:
|
||||
- Auto-Detection von Scripts in ~/bin
|
||||
- Symlink-basiertes Deployment
|
||||
- PATH-Setup für target servers
|
||||
- Dry-run verfügbar
|
||||
|
||||
ENVIRONMENT:
|
||||
LUSER Required: Username für SSH-Verbindungen
|
||||
USAGE
|
||||
}
|
||||
|
||||
# Einfache Script-Detection
|
||||
get_deployable_scripts() {
|
||||
local scripts=()
|
||||
|
||||
[[ ! -d "$BIN_DIR" ]] && { echo "Warnung: $BIN_DIR existiert nicht" >&2; return 0; }
|
||||
|
||||
# Finde alle Symlinks die auf unser Repository zeigen
|
||||
for symlink in "$BIN_DIR"/*; do
|
||||
[[ -L "$symlink" ]] || continue
|
||||
local target=$(readlink -f "$symlink" 2>/dev/null || continue)
|
||||
[[ "$target" == "$SCRIPT_DIR"* ]] || continue
|
||||
scripts+=("$(basename "$symlink")")
|
||||
done
|
||||
|
||||
printf '%s\n' "${scripts[@]}" | sort
|
||||
}
|
||||
|
||||
# Einfache Server-Auswahl (nur Target zurückgeben)
|
||||
select_server() {
|
||||
local servers=("web" "mx" "logger" "support")
|
||||
local server
|
||||
|
||||
select server in "${servers[@]}"; do
|
||||
[[ -n "$server" ]] || { echo "Ungültige Auswahl" >&2; continue; }
|
||||
|
||||
case "$server" in
|
||||
web|mx)
|
||||
echo -n "Server-Nummer eingeben: " >&2
|
||||
read -r number
|
||||
[[ "$number" =~ ^[0-9]+$ ]] || { echo "Ungültige Nummer" >&2; continue; }
|
||||
echo "${LUSER}@${server}${number}.in.easyname.com"
|
||||
return 0
|
||||
;;
|
||||
logger)
|
||||
echo "${LUSER}@logger.in.nessus.at"
|
||||
return 0
|
||||
;;
|
||||
support)
|
||||
echo "${LUSER}@support.in.easyname.com"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Einfaches Script-Deployment
|
||||
deploy_script() {
|
||||
local script_name="$1"
|
||||
local ssh_target="$2"
|
||||
local dry_run="$3"
|
||||
|
||||
# Folge Symlink zum Original
|
||||
local symlink_path="$BIN_DIR/$script_name"
|
||||
[[ -L "$symlink_path" ]] || { echo "Fehler: $script_name ist kein Symlink" >&2; return 1; }
|
||||
|
||||
local original_script=$(readlink -f "$symlink_path")
|
||||
local script_dir=$(dirname "$original_script")
|
||||
|
||||
echo "=== Deploying $script_name to $ssh_target ==="
|
||||
|
||||
if (( dry_run )); then
|
||||
echo " [DRY-RUN] Würde deployment ausführen:"
|
||||
echo " Script: $original_script -> ~/$script_name"
|
||||
echo " Symlink: ~/bin/$script_name -> ~/$script_name"
|
||||
[[ -d "$script_dir/lib" ]] && echo " Lib: $script_dir/lib/ -> ~/lib_$script_name/"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Deployment ausführen - ordentlich in ~/scripts/
|
||||
ssh "$ssh_target" "mkdir -p ~/scripts ~/bin"
|
||||
scp "$original_script" "$ssh_target:scripts/$script_name"
|
||||
ssh "$ssh_target" "chmod +x ~/scripts/$script_name && ln -sf ~/scripts/$script_name ~/bin/$script_name"
|
||||
|
||||
# Lib-Dependencies falls vorhanden
|
||||
if [[ -d "$script_dir/lib" ]]; then
|
||||
echo " Deploying lib dependencies..."
|
||||
scp -r "$script_dir/lib" "$ssh_target:scripts/lib_$script_name"
|
||||
ssh "$ssh_target" "sed -i 's|\$SCRIPT_DIR/lib/|~/scripts/lib_$script_name/|g' ~/scripts/$script_name"
|
||||
fi
|
||||
|
||||
echo " ✓ $script_name deployed successfully"
|
||||
}
|
||||
|
||||
# Einfaches bashrc-Deployment
|
||||
deploy_bashrc() {
|
||||
local ssh_target="$1"
|
||||
local dry_run="$2"
|
||||
|
||||
echo "=== Deploying bashrc to $ssh_target ==="
|
||||
|
||||
if (( dry_run )); then
|
||||
echo " [DRY-RUN] Würde default bashrc mit PATH-Setup erstellen"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Erstelle modulare .bashrc + config files
|
||||
ssh "$ssh_target" 'cat > .bashrc << "EOF"
|
||||
# .bashrc - Optimierte Bash-Konfiguration für easyname Support
|
||||
|
||||
##### History #####
|
||||
HISTCONTROL=ignoredups:erasedups
|
||||
HISTSIZE=50000
|
||||
HISTFILESIZE=100000
|
||||
HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
|
||||
shopt -s histappend
|
||||
shopt -s cmdhist
|
||||
|
||||
##### Prompt #####
|
||||
if [[ "$TERM" != "dumb" ]]; then
|
||||
PS1="\[\e[01;32m\]\u@\h\[\e[00m\]:\[\e[01;34m\]\w\[\e[00m\]\\$ "
|
||||
fi
|
||||
|
||||
##### Modular configs #####
|
||||
[[ -f ~/.bash_aliases ]] && source ~/.bash_aliases
|
||||
[[ -f ~/.bash_env ]] && source ~/.bash_env
|
||||
[[ -f ~/.bash_paths ]] && source ~/.bash_paths
|
||||
|
||||
##### Standard settings #####
|
||||
if [[ -f /etc/bash_completion ]]; then
|
||||
source /etc/bash_completion
|
||||
fi
|
||||
|
||||
if [[ -x /usr/bin/dircolors ]]; then
|
||||
eval "$(dircolors -b)"
|
||||
fi
|
||||
|
||||
# Built with the help of Claude Code
|
||||
EOF'
|
||||
|
||||
# Erstelle modulare Config-Files mit PATH-Check
|
||||
ssh "$ssh_target" 'echo "# Add your custom aliases here" > .bash_aliases'
|
||||
ssh "$ssh_target" 'echo "# Add your PATH extensions here" > .bash_paths'
|
||||
|
||||
# Prüfe ob ~/bin bereits in PATH ist, wenn nicht -> zu .bash_env hinzufügen
|
||||
ssh "$ssh_target" 'if [[ ":$PATH:" != *":$HOME/bin:"* ]]; then
|
||||
echo "# Add ~/bin to PATH for deployed scripts" > .bash_env
|
||||
echo "export PATH=\"\$HOME/bin:\$PATH\"" >> .bash_env
|
||||
else
|
||||
echo "# Add your environment variables here" > .bash_env
|
||||
fi'
|
||||
|
||||
echo " ✓ bashrc deployed successfully"
|
||||
}
|
||||
|
||||
# MAIN - Vereinfachte Ausführung
|
||||
main() {
|
||||
# Argument parsing
|
||||
local dry_run=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--dry-run) dry_run=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unbekannte Option: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "=== easyname Support Scripts Deployment ==="
|
||||
echo
|
||||
|
||||
# Script-Auswahl
|
||||
local -a available_scripts
|
||||
mapfile -t available_scripts < <(get_deployable_scripts)
|
||||
|
||||
[[ "${#available_scripts[@]}" -eq 0 ]] && {
|
||||
echo "Keine deployable Scripts in $BIN_DIR gefunden" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Verfügbare Scripts:"
|
||||
local script
|
||||
select script in "${available_scripts[@]}" "ALL SCRIPTS"; do
|
||||
[[ -n "$script" ]] && break
|
||||
echo "Ungültige Auswahl"
|
||||
done
|
||||
|
||||
# Server-Auswahl
|
||||
echo "=== Server-Auswahl ==="
|
||||
local ssh_target
|
||||
ssh_target=$(select_server)
|
||||
|
||||
# Optionen
|
||||
echo -n "Bashrc auch deployen? [y/N]: "
|
||||
read -r deploy_bashrc_choice
|
||||
local deploy_bashrc=0
|
||||
[[ "$deploy_bashrc_choice" =~ ^[yY]$ ]] && deploy_bashrc=1
|
||||
|
||||
# Bestätigung
|
||||
echo
|
||||
echo "=== Deployment-Zusammenfassung ==="
|
||||
echo "Target: $ssh_target"
|
||||
echo "Scripts: $script"
|
||||
[[ "$deploy_bashrc" -eq 1 ]] && echo "Bashrc: Ja"
|
||||
[[ "$dry_run" -eq 1 ]] && echo "Modus: DRY-RUN"
|
||||
echo
|
||||
echo -n "Deployment starten? [y/N]: "
|
||||
read -r confirm
|
||||
[[ "$confirm" =~ ^[yY]$ ]] || { echo "Abgebrochen."; exit 0; }
|
||||
|
||||
# Deployment ausführen
|
||||
echo
|
||||
echo "=== Deployment wird ausgeführt ==="
|
||||
|
||||
# Bashrc deployment
|
||||
(( deploy_bashrc )) && deploy_bashrc "$ssh_target" "$dry_run"
|
||||
|
||||
# Script deployment
|
||||
if [[ "$script" == "ALL SCRIPTS" ]]; then
|
||||
for s in "${available_scripts[@]}"; do
|
||||
deploy_script "$s" "$ssh_target" "$dry_run"
|
||||
done
|
||||
else
|
||||
deploy_script "$script" "$ssh_target" "$dry_run"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Deployment abgeschlossen ==="
|
||||
[[ "$dry_run" -eq 1 ]] && echo "(DRY-RUN - nichts wurde tatsächlich deployed)"
|
||||
}
|
||||
|
||||
# Script starten
|
||||
main "$@"
|
||||
|
||||
# Built with the help of Claude Code
|
||||
Regular → Executable
+154
-60
@@ -1,37 +1,57 @@
|
||||
# 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
|
||||
**Remote Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele**
|
||||
|
||||
Führt Exim-Log-Analyse remote auf `logger.in.nessus.at` aus, wo sich die Exim-Logs befinden.
|
||||
Durchsucht 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
|
||||
- **Remote-Ausführung**: Automatische SSH-Verbindung zu `logger.in.nessus.at`
|
||||
- **Zeitfenster**: Letzte N Stunden/Tage ODER konkretes Datum/Datumsbereich
|
||||
- **Filter**: Absender, Empfänger (Regex), Domain, Message-ID, zusätzliche Regex
|
||||
- **Gruppierung**: Alle Treffer pro Exim-Message-ID mit Status-Zusammenfassung
|
||||
- **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
|
||||
- **MX-Records**: Optional DNS-Abfragen für Ziel-Domain (--mx)
|
||||
- **Failure-Detection**: Markiert häufige Fehler (z.B. "all hosts failing")
|
||||
- **Chronologische Ausgabe**: Sortiert nach Message-ID Erstkontakt
|
||||
- **Komprimierte Logs**: Automatischer Support für .gz-Archive
|
||||
- **Smart Connection**: Verwendet `go logger` oder `ssh ${LUSER}@logger.in.nessus.at`
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
Via Installer (empfohlen):
|
||||
```bash
|
||||
cd ~/.dev/esn-support-bash-snippets
|
||||
./install.sh
|
||||
# Dann: 3) Legacy Tools auswählen und exitrace installieren
|
||||
```
|
||||
|
||||
2. Abhängigkeiten:
|
||||
- Standard Unix-Tools (awk, grep, date, zcat)
|
||||
- `dig` (optional, für --mx Feature)
|
||||
- Lesezugriff auf `/var/log/easyname/` (oder custom `$LOGROOT`)
|
||||
Manuelle Installation:
|
||||
```bash
|
||||
git clone <repo-url>/esn-support-bash-snippets.git ~/.dev/esn-support-bash-snippets
|
||||
cd ~/.dev/esn-support-bash-snippets/legacy/exitrace
|
||||
chmod +x exitrace.sh
|
||||
ln -sf ~/.dev/esn-support-bash-snippets/legacy/exitrace/exitrace.sh ~/bin/exitrace
|
||||
```
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- **LUSER Environment Variable** ODER installiertes `go` Tool
|
||||
- **SSH-Zugang** zu `logger.in.nessus.at`
|
||||
- Standard Unix-Tools (awk, grep, date, ssh/scp)
|
||||
- `dig` (optional, für --mx Feature)
|
||||
|
||||
**Setup:**
|
||||
```bash
|
||||
export LUSER="dein-username"
|
||||
# Oder in ~/.bash_env für permanente Konfiguration
|
||||
echo 'export LUSER="dein-username"' >> ~/.bash_env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -44,24 +64,43 @@ exitrace
|
||||
(durchsucht heutiges Log ohne Filter)
|
||||
|
||||
### Beispiele
|
||||
|
||||
**Häufige Use-Cases:**
|
||||
```bash
|
||||
# Mail-Trace einer konkreten Message-ID
|
||||
exitrace -m 1uwRvz-000Fz2-CI
|
||||
# Mail-Trace für spezifischen Absender zu t-online.de (letzte 48h)
|
||||
exitrace -FROM franziskus@adjuva.info -DOMAIN t-online.de -H 48
|
||||
|
||||
# Alle Mails von bestimmtem Absender in den letzten 48 Stunden
|
||||
exitrace -f sender@example.com -H 48
|
||||
# Alle Mails an bestimmte Domain in Zeitspanne
|
||||
exitrace -DOMAIN gmx.de -r 2025-09-10..2025-09-11
|
||||
|
||||
# Empfänger-Domain in Zeitspanne
|
||||
exitrace -D example.de -r 2025-09-09..2025-09-11
|
||||
# Konkrete Message-ID verfolgen
|
||||
exitrace -MSGID 1uwRvz-000Fz2-CI
|
||||
|
||||
# Regex-Filter auf Empfänger + konkretes Datum
|
||||
exitrace -t '.*@example\.de' -d 2025-09-10
|
||||
# Regex-Filter auf Empfänger + MX-Records anzeigen
|
||||
exitrace -TO '.*@gmail\.com' -d 2025-09-11 --mx
|
||||
|
||||
# Kombinierte Filter: Absender + Domain + Zeitfenster + MX-Info
|
||||
exitrace -f sender@example.com -D example.de -H 48 --mx
|
||||
# Kombinierte Filter: Absender + Domain + Zeitfenster
|
||||
exitrace -FROM noreply@company.com -DOMAIN outlook.com -H 24
|
||||
```
|
||||
|
||||
# Dry-Run: zeigt nur, welche Logfiles durchsucht würden
|
||||
**Debugging & Analyse:**
|
||||
```bash
|
||||
# Dry-Run: zeigt nur welche Logfiles durchsucht würden
|
||||
exitrace -n -H 24
|
||||
|
||||
# Alle Mails von Domain in letzten 7 Tagen
|
||||
exitrace -FROM '.*@problematic-domain\.com' -DAYS 7
|
||||
|
||||
# Zusätzlicher Regex-Filter
|
||||
exitrace -DOMAIN example.com -GREP "550.*blocked" -H 12
|
||||
```
|
||||
|
||||
**Backwards Compatibility:**
|
||||
```bash
|
||||
# Alte Parameter funktionieren weiterhin
|
||||
exitrace -f sender@domain.com -D example.de -H 48
|
||||
exitrace -m 1uwRvz-000Fz2-CI
|
||||
exitrace -t '.*@gmail\.com' --mx
|
||||
```
|
||||
|
||||
---
|
||||
@@ -78,34 +117,55 @@ Zeitfenster (eine Option wählen):
|
||||
-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
|
||||
-FROM, --from ADDR Envelope-From / "from <...>" (alias: -f)
|
||||
-TO, --to REGEX Empfänger (Regex, matcht nach " for <rcpt>" oder in "** ...") (alias: -t)
|
||||
-DOMAIN, --domain NAME Ziel-Domain (z. B. example.de), prüft auf Zustellungen/Fehler dorthin (alias: -D)
|
||||
-MSGID, --msgid ID Exim-Message-ID (z. B. 1uwRvz-000Fz2-CI) (alias: -m)
|
||||
-GREP, --grep REGEX Zusätzlicher Regex auf Logzeilen (alias: -G)
|
||||
|
||||
Sonstiges:
|
||||
--mx Zeigt MX-Einträge (und A/AAAA) für --domain
|
||||
-n, --dry-run Zeigt nur, welche Logfiles untersucht würden
|
||||
-n, --dry-run Zeigt nur, welche Logfiles untersucht würden
|
||||
-h, --help Diese Hilfe
|
||||
|
||||
Remote Execution:
|
||||
Wird automatisch remote auf logger.in.nessus.at ausgeführt.
|
||||
Verwendet 'go logger' oder 'ssh ${LUSER}@logger.in.nessus.at'.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispielausgabe
|
||||
|
||||
### Erfolgreiche Zustellung
|
||||
```
|
||||
[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
|
||||
[OK] ID=1v2K8X-0004Zm-BC | First=2025-09-11 08:32:15 | From=franziskus@adjuva.info | Rcpts=user@t-online.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
|
||||
ID: 1v2K8X-0004Zm-BC
|
||||
First: 2025-09-11 08:32:15
|
||||
Last: 2025-09-11 08:32:47
|
||||
From: franziskus@adjuva.info
|
||||
Rcpts: user@t-online.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]
|
||||
2025-09-11 08:32:15 | <= franziskus@adjuva.info H=mail.adjuva.info [10.0.0.1] P=esmtp S=2156
|
||||
2025-09-11 08:32:47 | => user@t-online.de <user@t-online.de> R=dnslookup T=remote_smtp H=mx01.t-online.de [217.5.206.120]
|
||||
------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
### Fehlgeschlagene Zustellung
|
||||
```
|
||||
[FAIL] ID=1v2L9Y-0005Ap-DE | First=2025-09-11 09:15:32 | From=franziskus@adjuva.info | Rcpts=blocked@t-online.de | Delivered=0 Deferred=0 Failed=1
|
||||
------------------------------------------------------------------------
|
||||
ID: 1v2L9Y-0005Ap-DE
|
||||
First: 2025-09-11 09:15:32
|
||||
Last: 2025-09-11 09:15:55
|
||||
From: franziskus@adjuva.info
|
||||
Rcpts: blocked@t-online.de
|
||||
Status: FAIL (Delivered=0, Deferred=0, Failed=1)
|
||||
Log:
|
||||
2025-09-11 09:15:32 | <= franziskus@adjuva.info H=mail.adjuva.info [10.0.0.1] P=esmtp S=1847
|
||||
2025-09-11 09:15:55 | ** blocked@t-online.de <blocked@t-online.de> R=dnslookup T=remote_smtp H=mx01.t-online.de [217.5.206.120]: SMTP error from remote mail server after RCPT TO:<blocked@t-online.de>: 550 requested action not taken: mailbox unavailable
|
||||
------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
@@ -138,32 +198,66 @@ Bei Delivery-Stalls:
|
||||
|
||||
Nach Installation über `./install.sh` ist das Script direkt verfügbar:
|
||||
```bash
|
||||
exitrace -m 1uwRvz-000Fz2-CI
|
||||
exitrace -FROM franziskus@adjuva.info -DOMAIN t-online.de -H 48
|
||||
```
|
||||
|
||||
Das Installationsscript erstellt automatisch einen Symlink in `~/bin` (ohne `.sh` Extension).
|
||||
|
||||
Umgebungsvariable für abweichenden Logpfad:
|
||||
**LUSER Environment Variable setzen (wichtig!):**
|
||||
```bash
|
||||
export LOGROOT="/var/log/custom"
|
||||
# Temporär
|
||||
export LUSER="dein-username"
|
||||
|
||||
# Permanent in ~/.bash_env (empfohlen)
|
||||
echo 'export LUSER="dein-username"' >> ~/.bash_env
|
||||
source ~/.bash_env
|
||||
```
|
||||
|
||||
Logger-spezifisch ggf. in `~/.profile` ergänzen:
|
||||
**Verbindungstest:**
|
||||
```bash
|
||||
cd /var/log/easyname/$(date +%Y)/$(date +%m)/
|
||||
# Test ob go-Tool verfügbar
|
||||
go logger
|
||||
|
||||
# Test ob SSH funktioniert
|
||||
ssh ${LUSER}@logger.in.nessus.at "hostname"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
## Troubleshooting
|
||||
|
||||
- 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
|
||||
### Häufige Probleme
|
||||
|
||||
**"LUSER Variable nicht gesetzt"**
|
||||
```bash
|
||||
export LUSER="dein-username"
|
||||
```
|
||||
|
||||
**"Permission denied" oder SSH-Fehler**
|
||||
- Kerberos-Login: `kinit ${LUSER}@NESSUS.AT`
|
||||
- SSH-Keys prüfen oder `go` Tool installieren
|
||||
|
||||
**"Keine Logfiles gefunden"**
|
||||
- Zeitfenster zu eng: `-H 48` oder `-DAYS 3` probieren
|
||||
- Dry-run verwenden: `exitrace -n -H 24`
|
||||
|
||||
**AWK-Errors**
|
||||
- Regex-Syntax prüfen bei `-t` und `-G` Optionen
|
||||
- Sonderzeichen escapen: `.*@example\.com`
|
||||
|
||||
### Performance-Tipps
|
||||
|
||||
- **Kleine Zeitfenster**: `-H 24` statt `-DAYS 7`
|
||||
- **Spezifische Filter**: `-f` oder `-m` sind schneller als `-t`
|
||||
- **Dry-Run nutzen**: `-n` zeigt Suchbereich ohne Ausführung
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **Remote-Ausführung**: Komplettes Script wird via SSH Heredoc übertragen
|
||||
- **Log-Format**: `/var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz)` auf logger
|
||||
- **Status-Logik**: Basiert auf SMTP-Response-Codes und Exim-Nachrichten
|
||||
- **Komprimierte Archive**: Automatische .gz-Verarbeitung via `zcat`
|
||||
- **Chronologische Sortierung**: Nach Message-ID Erstkontakt
|
||||
- **Exit-Codes**: 0 = OK (auch bei 0 Treffern), 1 = Parameter-/Verbindungsfehler
|
||||
|
||||
Built with the help of Claude Code
|
||||
+242
-92
@@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# exitrace - Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
|
||||
# exitrace - Remote Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
|
||||
# Maintainer: ljp
|
||||
# Führt Exim-Log-Analyse remote auf logger.in.nessus.at aus
|
||||
# -----------------------------------------------------------------------------
|
||||
# Features:
|
||||
# - Sucht in /var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz) (anpassbar mit $LOGROOT)
|
||||
# - Remote-Ausführung auf logger.in.nessus.at (wo die Exim-Logs liegen)
|
||||
# - Sucht in /var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz) auf logger
|
||||
# - 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
|
||||
@@ -20,10 +22,6 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
LOGROOT="${LOGROOT:-/var/log/easyname}"
|
||||
DATE_FMT="%Y-%m-%d"
|
||||
TODAY="$(date +%Y-%m-%d)"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: exitrace [OPTS]
|
||||
@@ -35,11 +33,11 @@ Zeitfenster (eine Option wählen):
|
||||
-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
|
||||
-FROM, --from ADDR Envelope-From / "from <...>"
|
||||
-TO, --to REGEX Empfänger (Regex, matcht ' for <rcpt>' oder in '** rcpt')
|
||||
-DOMAIN, --domain NAME Ziel-Domain (z. B. example.de)
|
||||
-MSGID, --msgid ID Exim-Message-ID (z. B. 1uwRvz-000Fz2-CI)
|
||||
-GREP, --grep REGEX Zusätzlicher Regex auf Logzeilen
|
||||
|
||||
Sonstiges:
|
||||
--mx Zeigt MX-Einträge (und A/AAAA) für --domain
|
||||
@@ -47,49 +45,173 @@ Sonstiges:
|
||||
-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
|
||||
exitrace -FROM sender@example.com -DOMAIN example.de -H 48
|
||||
exitrace -MSGID 1uwRvz-000Fz2-CI
|
||||
exitrace -TO '.*@example.de' -r 2025-09-09..2025-09-11 --mx
|
||||
|
||||
Note: Wird automatisch remote auf logger.in.nessus.at ausgeführt.
|
||||
USAGE
|
||||
}
|
||||
|
||||
# Validate functions (inline for remote execution)
|
||||
validate_positive_integer() {
|
||||
local value="$1"
|
||||
local param="$2"
|
||||
if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "Fehler: $param benötigt positive Ganzzahl, erhalten: $value" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_date() {
|
||||
local date_str="$1"
|
||||
local param="$2"
|
||||
if ! date -d "$date_str" >/dev/null 2>&1; then
|
||||
echo "Fehler: $param ungültiges Datum: $date_str" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$date_str" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
||||
echo "Fehler: $param Format muss YYYY-MM-DD sein, erhalten: $date_str" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_date_range() {
|
||||
local range="$1"
|
||||
if [[ ! "$range" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2})\.\.([0-9]{4}-[0-9]{2}-[0-9]{2})$ ]]; then
|
||||
echo "Fehler: --range Format muss YYYY-MM-DD..YYYY-MM-DD sein, erhalten: $range" >&2
|
||||
exit 1
|
||||
fi
|
||||
local start_date="${BASH_REMATCH[1]}"
|
||||
local end_date="${BASH_REMATCH[2]}"
|
||||
validate_date "$start_date" "--range start"
|
||||
validate_date "$end_date" "--range end"
|
||||
}
|
||||
|
||||
# --------------------------- Argumente parsen --------------------------------
|
||||
HOURS=""; DAYS=""; DATE_ONE=""; DATE_RANGE=""
|
||||
FROM=""; TO_REGEX=""; TARGET_DOMAIN=""; MSGID=""; GREPEXTRA=""
|
||||
DRYRUN=0; WANT_MX=0
|
||||
|
||||
# Alle Argumente für Remote-Weiterleitung sammeln
|
||||
REMOTE_ARGS=()
|
||||
|
||||
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 ;;
|
||||
-H|--hours)
|
||||
HOURS="${2:-}"
|
||||
validate_positive_integer "$HOURS" "--hours"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-DAYS|--days)
|
||||
DAYS="${2:-}"
|
||||
validate_positive_integer "$DAYS" "--days"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-d|--date)
|
||||
DATE_ONE="${2:-}"
|
||||
validate_date "$DATE_ONE" "--date"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-r|--range)
|
||||
DATE_RANGE="${2:-}"
|
||||
validate_date_range "$DATE_RANGE"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-FROM|--from|-f)
|
||||
FROM="${2:-}"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-TO|--to|-t)
|
||||
TO_REGEX="${2:-}"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-DOMAIN|--domain|-D)
|
||||
TARGET_DOMAIN="${2:-}"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-MSGID|--msgid|-m)
|
||||
MSGID="${2:-}"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
-GREP|--grep|-G)
|
||||
GREPEXTRA="${2:-}"
|
||||
REMOTE_ARGS+=("$1" "$2")
|
||||
shift 2 ;;
|
||||
--mx)
|
||||
WANT_MX=1
|
||||
REMOTE_ARGS+=("$1")
|
||||
shift ;;
|
||||
-n|--dry-run)
|
||||
DRYRUN=1
|
||||
REMOTE_ARGS+=("$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"
|
||||
# --- Remote-Kommando (wie checkms) ---
|
||||
LUSER="${LUSER:-}"
|
||||
if [[ -z "$LUSER" ]] && type go >/dev/null 2>&1; then
|
||||
REMOTE="go logger"
|
||||
elif [[ -n "$LUSER" ]]; then
|
||||
REMOTE="ssh ${LUSER}@logger.in.nessus.at"
|
||||
else
|
||||
echo "Fehler: LUSER Variable nicht gesetzt und 'go' Tool nicht verfügbar" >&2
|
||||
echo "Setze LUSER oder installiere das 'go' Tool für automatische Logger-Verbindung" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Remote Ausführung ---
|
||||
$REMOTE bash -s "${REMOTE_ARGS[@]}" <<'REMOTE_SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
# Remote exitrace execution on logger.in.nessus.at
|
||||
set -euo pipefail
|
||||
|
||||
# Date/validation functions
|
||||
validate_positive_integer() {
|
||||
local value="$1"
|
||||
local param="$2"
|
||||
if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "Fehler: $param benötigt positive Ganzzahl, erhalten: $value" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_date() {
|
||||
local date_str="$1"
|
||||
local param="$2"
|
||||
if ! date -d "$date_str" >/dev/null 2>&1; then
|
||||
echo "Fehler: $param ungültiges Datum: $date_str" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$date_str" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
||||
echo "Fehler: $param Format muss YYYY-MM-DD sein, erhalten: $date_str" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_date_range() {
|
||||
local range="$1"
|
||||
if [[ ! "$range" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2})\.\.([0-9]{4}-[0-9]{2}-[0-9]{2})$ ]]; then
|
||||
echo "Fehler: --range Format muss YYYY-MM-DD..YYYY-MM-DD sein, erhalten: $range" >&2
|
||||
exit 1
|
||||
fi
|
||||
local start_date="${BASH_REMATCH[1]}"
|
||||
local end_date="${BASH_REMATCH[2]}"
|
||||
validate_date "$start_date" "--range start"
|
||||
validate_date "$end_date" "--range end"
|
||||
}
|
||||
|
||||
# Date generation functions
|
||||
gen_dates_hours_back() {
|
||||
local hrs="$1" start_ts end_ts cur
|
||||
local DATE_FMT="%Y-%m-%d"
|
||||
end_ts="$(date +%s)"
|
||||
start_ts="$(date -d "-${hrs} hours" +%s)"
|
||||
cur="$(date -d "@$start_ts" +${DATE_FMT})"
|
||||
@@ -103,7 +225,7 @@ gen_dates_hours_back() {
|
||||
gen_dates_days_back() {
|
||||
local days="$1" i
|
||||
for ((i=0;i<=days;i++)); do
|
||||
date -d "-$i day" +${DATE_FMT}
|
||||
date -d "-$i day" +%Y-%m-%d
|
||||
done
|
||||
}
|
||||
|
||||
@@ -112,10 +234,68 @@ gen_dates_range() {
|
||||
while :; do
|
||||
echo "$cur"
|
||||
[[ "$cur" == "$b" ]] && break
|
||||
cur="$(date -d "$cur +1 day" +${DATE_FMT})"
|
||||
cur="$(date -d "$cur +1 day" +%Y-%m-%d)"
|
||||
done
|
||||
}
|
||||
|
||||
files_for_date() {
|
||||
local d="$1" y="${d:0:4}" m="${d:5:2}"
|
||||
local p="/var/log/easyname/${y}/${m}/${d}.log"
|
||||
if [[ -f "$p" ]]; then
|
||||
printf "%s\n" "$p"
|
||||
fi
|
||||
if [[ -f "${p}.gz" ]]; then
|
||||
printf "%s\n" "${p}.gz"
|
||||
fi
|
||||
}
|
||||
|
||||
# MX lookup function
|
||||
show_mx_records() {
|
||||
local domain="$1"
|
||||
if command -v dig >/dev/null 2>&1; then
|
||||
echo "== MX für ${domain} =="
|
||||
dig +short MX "$domain" | sort -n || true
|
||||
echo "-- A/AAAA der MX:"
|
||||
while read -r mx; do
|
||||
local 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 "$domain" | sort -n)
|
||||
echo "========================================================================"
|
||||
else
|
||||
echo "(Hinweis) dig nicht gefunden, MX-Ausgabe übersprungen."
|
||||
fi
|
||||
}
|
||||
|
||||
# Global settings
|
||||
LOGROOT="${LOGROOT:-/var/log/easyname}"
|
||||
TODAY="$(date +%Y-%m-%d)"
|
||||
|
||||
# Parse remote arguments
|
||||
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 ;;
|
||||
-FROM|--from|-f) FROM="${2:-}"; shift 2 ;;
|
||||
-TO|--to|-t) TO_REGEX="${2:-}"; shift 2 ;;
|
||||
-DOMAIN|--domain|-D) TARGET_DOMAIN="${2:-}"; shift 2 ;;
|
||||
-MSGID|--msgid|-m) MSGID="${2:-}"; shift 2 ;;
|
||||
-GREP|--grep|-G) GREPEXTRA="${2:-}"; shift 2 ;;
|
||||
--mx) WANT_MX=1; shift ;;
|
||||
-n|--dry-run) DRYRUN=1; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Generate date list
|
||||
declare -a DATES=()
|
||||
if [[ -n "$HOURS" ]]; then
|
||||
mapfile -t DATES < <(gen_dates_hours_back "$HOURS")
|
||||
@@ -129,6 +309,7 @@ else
|
||||
DATES=("$TODAY")
|
||||
fi
|
||||
|
||||
# Collect files
|
||||
declare -a FILES=()
|
||||
for d in "${DATES[@]}"; do
|
||||
while read -r f; do FILES+=("$f"); done < <(files_for_date "$d" || true)
|
||||
@@ -145,16 +326,15 @@ if [[ "${#FILES[@]}" -eq 0 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --------------------------- Vorfilter bauen ---------------------------------
|
||||
# Build grep filters
|
||||
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 --------------------------------------
|
||||
# File reader
|
||||
cat_files() {
|
||||
# .log direkt lesen, .gz via zcat
|
||||
local f
|
||||
for f in "${FILES[@]}"; do
|
||||
if [[ "$f" == *.gz ]]; then
|
||||
@@ -165,39 +345,27 @@ cat_files() {
|
||||
done
|
||||
}
|
||||
|
||||
# Grep chain
|
||||
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 ------------------------------------
|
||||
# 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
|
||||
show_mx_records "$TARGET_DOMAIN"
|
||||
fi
|
||||
|
||||
# ---------------------------- Auswertung (awk) --------------------------------
|
||||
# AWK Analysis
|
||||
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] }
|
||||
function add_rcpt(id, rc){
|
||||
if (!(id in rcpts)) rcpts[id][""] = ""
|
||||
if (!(rc in rcpts[id])) rcpts[id][rc]=1
|
||||
}
|
||||
|
||||
BEGIN { FS=" " }
|
||||
|
||||
@@ -209,7 +377,6 @@ BEGIN { FS=" " }
|
||||
if (!t[0]) next
|
||||
split(t[0], p, " ")
|
||||
ts = p[1] " " p[2]
|
||||
sub(/^[0-9-: ]+ /, "", raw_after) # restline
|
||||
msgid = $1
|
||||
if (!msgid) next
|
||||
|
||||
@@ -247,21 +414,19 @@ BEGIN { FS=" " }
|
||||
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=...
|
||||
# Success detection
|
||||
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/))) {
|
||||
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
|
||||
# Fail detection
|
||||
is_perm_fail = 0
|
||||
if (is_fail) {
|
||||
is_perm_fail=1
|
||||
@@ -269,24 +434,13 @@ BEGIN { FS=" " }
|
||||
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
|
||||
if (match($0, /all hosts for.*have been failing/)) {
|
||||
fail_marker[msgid]=1
|
||||
}
|
||||
|
||||
@@ -295,7 +449,7 @@ BEGIN { FS=" " }
|
||||
}
|
||||
|
||||
END {
|
||||
# IDs einsammeln & sortieren
|
||||
# Sort IDs
|
||||
n=0
|
||||
for (id in first_ts){ ids[++n]=id }
|
||||
for (i=2;i<=n;i++){
|
||||
@@ -313,7 +467,7 @@ END {
|
||||
for (k=1;k<=n;k++){
|
||||
id=ids[k]
|
||||
|
||||
# FROM Nachfilter
|
||||
# FROM filter
|
||||
if (FROMREQ != "" && from_mismatch[id]==1) {
|
||||
if (from_addr[id] != FROMREQ && from_addr[id] != "") continue
|
||||
}
|
||||
@@ -322,35 +476,30 @@ END {
|
||||
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 logic
|
||||
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)
|
||||
if (r != "") {
|
||||
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]
|
||||
@@ -364,5 +513,6 @@ END {
|
||||
}
|
||||
}
|
||||
'
|
||||
REMOTE_SCRIPT
|
||||
|
||||
# Built with the help of Claude Code
|
||||
Regular → Executable
Reference in New Issue
Block a user