#!/bin/bash
# Replikation nfs-1.crb2 -> nfs-2.crb2 (Standby fuer die AEP-NFS-Instanz crb2).
#
# Laeuft auf nfs-2 als root, PULL-basiert (nfs-2 holt), analog zum
# Backup-Setup platform/backup-asag auf backup-1.
#
# Ablauf pro Lauf:
#   1. Atomarer rekursiver Snapshot auf nfs-1: zfs snapshot -r nfs@repl-<ts>
#      -> ein gemeinsamer, crash-konsistenter Zeitpunkt ueber alle Datasets.
#   2. Pro Dataset inkrementeller Transfer via syncoid --no-sync-snap
#      (zfs send -c, also komprimierte Records direkt vom Pool).
#   3. Standby-Properties erzwingen (mountpoint wie auf nfs-1, readonly=on).
#   4. Retention: KEEP_SRC Snapshots auf nfs-1, KEEP_DEST auf nfs-2.
#   5. Zeitstempel des letzten erfolgreichen Laufs fuer Zabbix schreiben.
#
# Aufruf: replicate_nfs [dataset ...]   (ohne Argumente: alle aus DATASETS)

set -u

# Configuration is read from /etc/default/aep-nfs-ha (written by configuration
# management); the defaults below match the crb2 pair as built in 2026-08.
CONF=${AEP_NFS_HA_CONF:-/etc/default/aep-nfs-ha}
# shellcheck source=/dev/null
[ -r "$CONF" ] && . "$CONF"
SRC_HOST=${REPL_SOURCE:?REPL_SOURCE (address of the active node, replication interface) must be set in $CONF}
SRC_SSH="root@${SRC_HOST}"
SSHKEY=${REPL_SSHKEY:-/root/.ssh/id_repl_nfs}
POOL=${POOL:-nfs}
DATASETS=${DATASETS:-"pvc-prod pvc-nonprod backup"}
SNAP_PREFIX=${SNAP_PREFIX:-repl}   # -> nfs@repl-YYYY-MM-DD-HHMMSS
KEEP_SRC=${KEEP_SRC:-8}
KEEP_DEST=${KEEP_DEST:-48}
VIP=${VIP:-10.132.0.242}
VIPDEV=${VIPDEV:-eth0}
LOG=/var/log/replicate_nfs.log
LOCK=/run/replicate_nfs.lock
STAMP=/var/lib/misc/replicate_nfs.stamp

exec 9>"$LOCK"
flock -n 9 || { echo "replicate_nfs laeuft bereits, Abbruch." >&2; exit 1; }

log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; }

# ---------------------------------------------------------------------------
# SICHERUNG GEGEN DATENVERLUST NACH FAILOVER
# Ist dieser Knoten aktiv (haelt die Service-VIP oder wurde von keepalived
# promotet), darf NIEMALS repliziert werden: ein inkrementelles zfs recv -F
# wuerde die Live-Daten auf den Stand von nfs-1 zurueckrollen.
# ---------------------------------------------------------------------------
if [ -e /run/replicate_nfs.blocked ]; then
    echo "Knoten ist MASTER (/run/replicate_nfs.blocked), Replikation unterbunden." >&2
    exit 0
fi
if ip -4 addr show dev "$VIPDEV" 2>/dev/null | grep -q "inet ${VIP}/"; then
    echo "Knoten haelt die Service-VIP ${VIP}, Replikation unterbunden." >&2
    exit 0
fi
if svctl isup nfsd >/dev/null 2>&1; then
    echo "nfsd laeuft auf diesem Knoten, Replikation unterbunden." >&2
    exit 0
fi

# -n: stdin nicht verbrauchen, sonst frisst ssh die while-read-Pipes weiter unten
rssh() { ssh -n -i "$SSHKEY" -o BatchMode=yes -o ConnectTimeout=15 "$SRC_SSH" "$@"; }

fail=0
snapname="${SNAP_PREFIX}-$(date +%F-%H%M%S)"
log "=== Lauf beginnt, Snapshot $POOL@$snapname von $SRC_HOST ==="

# Auswahl der zu replizierenden Datasets
sets="$DATASETS"
[ "$#" -gt 0 ] && sets="$*"

# 1. Atomarer rekursiver Snapshot auf der Quelle.
#    -r deckt Pool-Root + alle Kinder in EINER Transaktion ab, damit die
#    Datasets untereinander denselben Stand haben.
if ! rssh "zfs snapshot -r ${POOL}@${snapname}" >>"$LOG" 2>&1; then
    log "FEHLER: Snapshot ${POOL}@${snapname} auf nfs-1 fehlgeschlagen, Abbruch"
    exit 1
fi

for ds in $sets; do
    src="${POOL}/${ds}"
    dst="${POOL}/${ds}"
    log "--- $src -> $dst ---"

    # 2. Inkrementeller Transfer. --no-sync-snap: syncoid legt keine eigenen
    #    Snapshots an, es nutzt unseren atomaren repl-Snapshot.
    #    --sendoptions=c: zfs send -c, komprimierte Records, spart Netz+CPU.
    if ! nice -n 10 ionice -c2 -n7 syncoid \
            --no-sync-snap \
            --no-privilege-elevation \
            --sshkey="$SSHKEY" \
            --sendoptions=c \
            "${SRC_SSH}:${src}" "$dst" >>"$LOG" 2>&1; then
        log "FEHLER $ds: syncoid fehlgeschlagen"
        fail=1
        continue
    fi

    # 3. Standby-Properties erzwingen: Mountpoint wie auf nfs-1, schreibgeschuetzt.
    #    zfs send ohne -p uebertraegt keine Properties, daher lokal setzen.
    want_mp=$(rssh "zfs get -H -o value mountpoint $src" 2>/dev/null)
    if [ -n "$want_mp" ] && [ "$want_mp" != "-" ]; then
        have_mp=$(zfs get -H -o value mountpoint "$dst")
        if [ "$have_mp" != "$want_mp" ]; then
            zfs set mountpoint="$want_mp" "$dst" >>"$LOG" 2>&1 \
                || log "WARN $ds: mountpoint=$want_mp konnte nicht gesetzt werden"
        fi
    fi
    [ "$(zfs get -H -o value readonly "$dst")" = "on" ] \
        || zfs set readonly=on "$dst" >>"$LOG" 2>&1 \
        || log "WARN $ds: readonly=on konnte nicht gesetzt werden"

    # 4a. Retention Quelle
    rssh "zfs list -H -t snapshot -o name -s creation $src" 2>/dev/null \
        | grep "@${SNAP_PREFIX}-" | head -n -"$KEEP_SRC" \
        | while read -r snap; do
            rssh "zfs destroy $snap" >>"$LOG" 2>&1 \
                || log "WARN $ds: Quell-Snapshot $snap nicht loeschbar"
        done

    # 4b. Retention Ziel
    zfs list -H -t snapshot -o name -s creation "$dst" 2>/dev/null \
        | grep "@${SNAP_PREFIX}-" | head -n -"$KEEP_DEST" \
        | while read -r snap; do
            zfs destroy "$snap" >>"$LOG" 2>&1 \
                || log "WARN $ds: Ziel-Snapshot $snap nicht loeschbar"
        done

    log "OK $ds"
done

# 4bis. NFSv4-Client-Tracking mitziehen.
#   nfsd merkt sich in /var/lib/nfs/nfsdcld/main.sqlite, welche Clients State
#   halten. Ohne diese DB scheitert nach einem Failover das Lock-Reclaim in der
#   Grace-Period (NFS4ERR_NO_GRACE). Wir legen sie nur in einem Staging-Pfad ab;
#   scharf geschaltet wird sie erst beim Failover durch keepalived_nfs_notify.
if ! rsync -a --delete \
        -e "ssh -i $SSHKEY -o BatchMode=yes" \
        --exclude='*.lock' --exclude='export-lock' \
        "${SRC_SSH}:/var/lib/nfs/" /var/lib/nfs-from-peer/ >>"$LOG" 2>&1; then
    log "WARN: /var/lib/nfs (nfsdcld-State) konnte nicht gespiegelt werden"
fi

# 4c. Retention fuer den Pool-Root-Snapshot auf beiden Seiten (nur Traeger des
#     rekursiven Snapshots, enthaelt selbst keine Nutzdaten).
rssh "zfs list -H -t snapshot -o name -s creation $POOL" 2>/dev/null \
    | grep "@${SNAP_PREFIX}-" | head -n -"$KEEP_SRC" \
    | while read -r snap; do rssh "zfs destroy $snap" >>"$LOG" 2>&1; done

# 5. Erfolgsstempel fuer Zabbix (nur wenn alles geklappt hat)
if [ "$fail" -eq 0 ]; then
    mkdir -p "$(dirname "$STAMP")"
    date +%s > "$STAMP"
fi

log "=== Lauf beendet, Status $fail ==="
exit "$fail"
