From 368f182ae52f097a30219584984c09a4a8399e42 Mon Sep 17 00:00:00 2001 From: scriptos Date: Tue, 19 Aug 2025 00:12:56 +0200 Subject: [PATCH] initial --- README.md | 30 +++++- dns-watch.v1.sh | 277 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 dns-watch.v1.sh diff --git a/README.md b/README.md index ffe6696..bb1ef93 100644 --- a/README.md +++ b/README.md @@ -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 + ```

@@ -11,5 +35,5 @@ Wichtig: Link für Lizenz anpassen.

-License License | Matrix Matrix | Matrix Mastodon +License License | Matrix Matrix | Matrix Mastodon

\ No newline at end of file diff --git a/dns-watch.v1.sh b/dns-watch.v1.sh new file mode 100644 index 0000000..39fc0a3 --- /dev/null +++ b/dns-watch.v1.sh @@ -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 + 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 <