This commit is contained in:
scriptos 2025-08-19 00:12:56 +02:00
parent 438ad74707
commit 368f182ae5
2 changed files with 304 additions and 3 deletions

View File

@ -1,9 +1,33 @@
# template_repository # DNS-Watch
Ein Bash-Skript zur Überwachung von DNS-Einträgen (A/AAAA-Records) für definierte Hosts.
Bei Änderungen werden Benachrichtigungen per **E-Mail** und/oder **ntfy** ausgelöst.
## Features
Wichtig: Link für Lizenz anpassen. - Überwachung beliebiger Hosts und Subdomains
- Unterstützung für **A**- und **AAAA**-Records
- Speicherung des letzten Zustands zur Erkennung von Änderungen
- Benachrichtigung:
- **Mail** (konfigurierbar)
- **ntfy** mit **Bearer Token Auth**
- Logging & Lockfile (verhindert parallele Läufe)
- Konfigurierbar über Variablen im Skript
## Voraussetzungen
- `bash` (>= 4.0)
- `dig` (meist im Paket `dnsutils` oder `bind9-dnsutils`)
- `curl` (für ntfy)
- `mail`-Binary (z. B. via `mailutils` oder `msmtp-mta`) nur falls Mail genutzt wird
## Installation
1. Skript ins System legen:
```bash
sudo cp dns-watch.sh /usr/local/bin/dns-watch.sh
sudo chmod +x /usr/local/bin/dns-watch.sh
```
<p align="center"> <p align="center">
@ -11,5 +35,5 @@ Wichtig: Link für Lizenz anpassen.
</p> </p>
<p align="center"> <p align="center">
<img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./template_repository/src/branch/main/LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Matrix" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a> <img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./dns-watch/src/branch/main/LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Matrix" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a>
</p> </p>

277
dns-watch.v1.sh Normal file
View File

@ -0,0 +1,277 @@
#!/usr/bin/env bash
# Script Name: dns-watch.v1.sh
# Beschreibung: Überwacht A/AAAA DNS-Records für definierte Hosts und meldet Änderungen per Mail und/oder ntfy.
# Autor: Patrick Asmus
# Web: https://www.cleveradmin.de
# Git-Reposit.: https://git.techniverse.net/scriptos/dns-watch
# Version: 1.0
# Datum: 18.08.2025
# Modifikation: Initial
#####################################################
set -euo pipefail
#####################################
# KONFIG #
#####################################
# Liste der zu überwachenden Hosts
HOSTS=(
"domain.com"
"sub.domain.com"
)
# Welche Record-Typen prüfen?
RECORD_TYPES=("A" "AAAA")
# Optional: Eigener DNS-Resolver (leer = System-Resolver)
# Beispielformat: "1.1.1.1" oder "9.9.9.9"
DNS_RESOLVER="1.1.1.1"
# Zustandsablage (wird automatisch erstellt)
STATE_DIR="./states"
# Logdatei (optional; leer lassen, wenn nur stdout)
LOG_FILE="/var/log/dns-watch.log"
# --- Benachrichtigungen ---
# Mail
MAIL_ENABLED=false
MAIL_TO=""
MAIL_FROM=""
MAIL_SUBJECT_PREFIX="[DNS-Watch]"
MAIL_BIN="${MAIL_BIN:-/usr/bin/mail}" # /usr/bin/mail (bsd-mailx / mailutils / msmtp-mta)
# ntfy
NTFY_ENABLED=true
NTFY_SERVER="https://ntfy.sh"
NTFY_TOPIC="dns-watch"
NTFY_TOKEN=""
NTFY_TITLE_PREFIX="[DNS-Watch]"
NTFY_PRIORITY="default" # options: min|low|default|high|max
NTFY_TAGS="satellite" # Komma-getrennt, z. B. "satellite,dns,warning"
# Lockfile gegen Parallelstarts
LOCK_FILE="/tmp/dns-watch.lock"
#####################################
# HILFSFUNKTIONEN #
#####################################
log() {
local ts
ts="$(date '+%Y-%m-%d %H:%M:%S')"
local line="[$ts] $*"
if [[ -n "${LOG_FILE}" ]]; then
echo "${line}" >> "${LOG_FILE}"
else
echo "${line}"
fi
}
with_lock() {
exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
log "Bereits laufende Instanz erkannt. Beende."
exit 0
fi
}
ensure_dirs() {
mkdir -p "${STATE_DIR}"
if [[ -n "${LOG_FILE}" ]]; then
mkdir -p "$(dirname "${LOG_FILE}")"
touch "${LOG_FILE}"
fi
}
# DNS-Abfrage für Host/Typ, gibt sortierte, eindeutige Liste (zeilenweise) zurück.
resolve_records() {
local host="$1"
local rtype="$2"
local dig_args=("+short" "${host}" "${rtype}")
if [[ -n "${DNS_RESOLVER}" ]]; then
dig_args=("@${DNS_RESOLVER}" "${host}" "${rtype}" "+short")
fi
# Ausführen, IPv4/IPv6-Adressen filtern, sortieren, eindeutige Einträge
local out
if ! out="$(dig "${dig_args[@]}" 2>/dev/null | sed 's/\s\+$//' | grep -E '^[0-9a-fA-F:\.]+$' || true)"; then
out=""
fi
if [[ -z "${out}" ]]; then
# Als Platzhalter "EMPTY" speichern, damit Änderungen erkennbar sind
echo "EMPTY"
else
# Sortierte, eindeutige Liste
echo "${out}" | sort -u
fi
}
# Zustandspfad für Host/Typ
state_file_path() {
local host="$1"
local rtype="$2"
local safe_host
safe_host="$(echo -n "${host}" | tr '/:@' '___')"
echo "${STATE_DIR}/${safe_host}__${rtype}.state"
}
# Vergleicht Alt/Neu; gibt 0 zurück, wenn identisch
compare_sets() {
local old_file="$1"
local tmp_new="$2"
# Falls kein Altzustand existiert, als Unterschied werten
if [[ ! -s "${old_file}" ]]; then
return 1
fi
# diff -q gibt 0 zurück, wenn gleich; sonst != 0
if diff -q "${old_file}" "${tmp_new}" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
#####################################
# BENACHRICHTIGUNGEN #
#####################################
notify_mail() {
# usage: notify_mail "Betreff" "Nachrichtentext"
local subject="$1"
local body="$2"
if [[ "${MAIL_ENABLED}" != "true" ]]; then
return 0
fi
if [[ ! -x "${MAIL_BIN}" ]]; then
log "WARN: ${MAIL_BIN} nicht gefunden/ausführbar, Mailbenachrichtigung übersprungen."
return 0
fi
# Versuch über -a "From:" Header (bei mailutils/bsd-mailx/msmtp-mta häufig verfügbar)
{
echo -e "${body}"
} | "${MAIL_BIN}" -a "From: ${MAIL_FROM}" -s "${MAIL_SUBJECT_PREFIX} ${subject}" "${MAIL_TO}" || {
log "WARN: Mailversand fehlgeschlagen."
}
}
notify_ntfy() {
# usage: notify_ntfy "Titel" "Body" "priority" "tags"
local title="$1"
local body="$2"
local prio="${3:-${NTFY_PRIORITY}}"
local tags="${4:-${NTFY_TAGS}}"
if [[ "${NTFY_ENABLED}" != "true" ]]; then
return 0
fi
if [[ -z "${NTFY_SERVER}" || -z "${NTFY_TOPIC}" ]]; then
log "WARN: ntfy SERVER/TOPIC nicht gesetzt, ntfy-Notify übersprungen."
return 0
fi
local url="${NTFY_SERVER%/}/${NTFY_TOPIC}"
# Headers:
# Title: Betreffzeile
# Priority: min|low|default|high|max
# Tags: emoji/labels (kommagetrennt)
# Authorization: Bearer <token>
local auth_header=()
if [[ -n "${NTFY_TOKEN}" && "${NTFY_TOKEN}" != "PLACE_YOUR_NTFY_API_TOKEN_HERE" ]]; then
auth_header=(-H "Authorization: Bearer ${NTFY_TOKEN}")
fi
curl -sS -X POST \
-H "Title: ${NTFY_TITLE_PREFIX} ${title}" \
-H "Priority: ${prio}" \
-H "Tags: ${tags}" \
"${auth_header[@]}" \
--data-binary "${body}" \
"${url}" >/dev/null || {
log "WARN: ntfy-POST fehlgeschlagen."
}
}
#####################################
# LOGIK #
#####################################
process_host_type() {
local host="$1"
local rtype="$2"
local new_records
new_records="$(resolve_records "${host}" "${rtype}")"
# Temporäre Datei für neuen Zustand
local tmp_new
tmp_new="$(mktemp)"
printf "%s\n" "${new_records}" > "${tmp_new}"
local state_file
state_file="$(state_file_path "${host}" "${rtype}")"
if compare_sets "${state_file}" "${tmp_new}"; then
# Keine Änderung
rm -f "${tmp_new}"
return 0
fi
# Änderung erkannt
local old="(kein vorheriger Zustand)"
if [[ -s "${state_file}" ]]; then
old="$(cat "${state_file}")"
fi
local now
now="$(cat "${tmp_new}")"
# State aktualisieren
mv -f "${tmp_new}" "${state_file}"
# Meldung bauen
local subject="Änderung: ${host} ${rtype}"
local body
body=$(cat <<EOF
DNS-Änderung erkannt
Host: ${host}
Typ: ${rtype}
Zeit: $(date -Iseconds)
Alt:
${old}
Neu:
${now}
Resolver: ${DNS_RESOLVER:-system-default}
EOF
)
log "Änderung erkannt für ${host} ${rtype}. Sende Benachrichtigungen."
notify_mail "${subject}" "${body}"
notify_ntfy "${subject}" "${body}" "${NTFY_PRIORITY}" "${NTFY_TAGS}"
}
main() {
with_lock
ensure_dirs
for host in "${HOSTS[@]}"; do
for rt in "${RECORD_TYPES[@]}"; do
process_host_type "${host}" "${rt}"
done
done
}
main "$@"