#!/usr/bin/env bash
# Richtet Redis Sentinel auf den drei Datenbankknoten ein.
#
#   redis-sentinel-einrichten.sh pruefen
#   redis-sentinel-einrichten.sh einrichten [--ja]
#   redis-sentinel-einrichten.sh lage
#
# Warum auf den DB-Knoten:
#   - Es sind drei feste Maschinen. Ein Quorum braucht eine ungerade Zahl,
#     die sich nicht aendert.
#   - Nicht auf die Frontends: die kommen und gehen (frontend-aufnehmen.sh).
#     Ein Waechter-Quorum, das mit der Last mitwaechst, entscheidet
#     irgendwann falsch.
#   - Nicht auf die Redis-Maschinen selbst: sonst verliert man einen
#     Waechter genau in dem Moment, in dem man ihn braucht.
#
# Angemeldet wird als ACL-Benutzer "default" - "shopware" hat resetchannels
# und darf kein Pub/Sub, was Sentinel zwingend braucht.
set -uo pipefail

BEFEHL="${1:-pruefen}"
JA="${2:-}"

VERBUND=riccardo            # Name des Masters, wie Sentinel ihn kennt
MASTER_IP=10.0.2.1
REPLIK_IP=10.0.2.2
QUORUM=2
WAECHTER=(10.0.3.1 10.0.3.2 10.0.3.3)

cd "$(dirname "$0")"
source ./lib-knoten.sh
rz_pruefe_schluessel || exit 1

pw_holen() {
    rz_ssh 10.0.1.1 "cd /var/www/riccardo && python3 -c \"
import re
from urllib.parse import urlparse, unquote
m = re.search(r'^REDIS_URL=(.+)\\\$', open('.env.local').read(), re.M)
print(unquote(urlparse(m.group(1).strip()).password or ''))
\""
}

lage_zeigen() {
    PW=$(pw_holen)
    echo "== Redis =="
    ./redis-zugang.sh --lage "$MASTER_IP" "$REPLIK_IP"
    echo "== Waechter =="
    # Die Rohantwort holen und HIER auswerten. Der erste Versuch hat das in
    # awk auf der Gegenseite gemacht - durch drei Ebenen Anfuehrungszeichen
    # (lokale Shell, ssh, awk) kam dort nur noch Bruch an.
    for ip in "${WAECHTER[@]}"; do
        printf '  %-10s ' "$ip"
        rz_ssh "$ip" "systemctl is-active --quiet redis-sentinel && redis-cli -p 26379 --no-auth-warning sentinel master $VERBUND 2>/dev/null" \
          | python3 -c '
import sys
z = [t.strip() for t in sys.stdin]
if len(z) < 2:
    print("Dienst laeuft nicht oder kennt den Verbund nicht"); raise SystemExit
d = dict(zip(z[0::2], z[1::2]))
print("Master %s:%s (%s), %s Replik(en), %s weitere Waechter, Quorum %s" % (
    d.get("ip"), d.get("port"), d.get("flags"),
    d.get("num-slaves"), d.get("num-other-sentinels"), d.get("quorum")))
' || echo "nicht erreichbar"
    done
}

if [ "$BEFEHL" = lage ]; then lage_zeigen; exit 0; fi

echo "== Vorher =="
lage_zeigen

if [ "$BEFEHL" != einrichten ]; then
    echo
    echo "Nur geprueft. Zum Einrichten: $0 einrichten --ja"
    exit 0
fi
[ "$JA" = "--ja" ] || { echo "Mit --ja bestaetigen."; exit 1; }

PW=$(pw_holen)
[ -n "$PW" ] || { echo "Kein Redis-Passwort gefunden."; exit 1; }

# ---------------------------------------------------------------------------
# Der Master braucht selbst masteruser/masterauth, obwohl er gerade Master ist.
# Sonst kann er nach einer Uebernahme nicht als Replik zurueckkommen: Sentinel
# schickt ihm ein REPLICAOF, und er scheitert an der Anmeldung. Das faellt
# erst im Ernstfall auf - also jetzt.
echo "== Master auf den Rueckweg vorbereiten =="
printf '%s\n' "$PW" | rz_ssh_stdin "$MASTER_IP" '
    set -e
    read -r pw || true   # ohne Zeilenumbruch liefert read 1, obwohl es gelesen hat
    sed -i "/^masteruser /d; /^masterauth /d" /etc/redis/redis.conf
    { echo ""; echo "# Fuer den Fall, dass Sentinel diese Maschine zur Replik macht"
      echo "masteruser default"; echo "masterauth $pw"; } >> /etc/redis/redis.conf
    chown redis:redis /etc/redis/redis.conf; chmod 640 /etc/redis/redis.conf
    export REDISCLI_AUTH="$pw"
    redis-cli --user default --no-auth-warning config set masteruser default >/dev/null
    redis-cli --user default --no-auth-warning config set masterauth "$pw" >/dev/null
    echo "   gesetzt"
'

echo "== Waechter einrichten =="
for ip in "${WAECHTER[@]}"; do
    echo "  $ip"
    printf '%s\n' "$PW" | rz_ssh_stdin "$ip" '
        set -e
        read -r pw || true   # ohne Zeilenumbruch liefert read 1, obwohl es gelesen hat
        export DEBIAN_FRONTEND=noninteractive
        if ! command -v redis-sentinel >/dev/null; then
            apt-get update -qq
            apt-get install -y -qq redis-sentinel >/dev/null
        fi
        # Auf einem Datenbankknoten hat ein Redis-SERVER nichts zu suchen -
        # das Paket bringt ihn unter Umstaenden mit.
        systemctl disable -q --now redis-server 2>/dev/null || true

        install -d -o redis -g redis /var/lib/redis-sentinel
        cat > /etc/redis/sentinel.conf <<CONF
# ERZEUGT von dev/deploy/redis-sentinel-einrichten.sh - nicht von Hand
# aendern. Sentinel schreibt allerdings selbst in diese Datei (den aktuellen
# Master, entdeckte Waechter); genau dafuer gehoert sie redis.
port 26379
bind '"$ip"' 127.0.0.1
# Ohne announce-ip meldet sich der Waechter unter Umstaenden mit der
# falschen Adresse - die Maschinen haengen an mehreren Netzen.
sentinel announce-ip '"$ip"'
protected-mode no
daemonize no
pidfile /run/sentinel/redis-sentinel.pid
logfile /var/log/redis/redis-sentinel.log
dir /var/lib/redis-sentinel
sentinel resolve-hostnames no

sentinel monitor '"$VERBUND"' '"$MASTER_IP"' 6379 '"$QUORUM"'
sentinel auth-user '"$VERBUND"' default
sentinel auth-pass '"$VERBUND"' $pw
# 5 s bis "der antwortet nicht mehr". Kuerzer waere nervoes: ein kurzer
# Aussetzer wuerde eine Uebernahme ausloesen, die teurer ist als die Stoerung.
sentinel down-after-milliseconds '"$VERBUND"' 5000
sentinel failover-timeout '"$VERBUND"' 60000
sentinel parallel-syncs '"$VERBUND"' 1
CONF
        chown redis:redis /etc/redis/sentinel.conf
        chmod 640 /etc/redis/sentinel.conf
        systemctl enable -q redis-sentinel
        systemctl restart redis-sentinel
        sleep 2
        systemctl is-active redis-sentinel | sed "s/^/     Dienst: /"
    ' || { echo "     fehlgeschlagen"; exit 1; }
done

echo "== Waechter finden einander (bis zu 30 s) =="
for i in $(seq 1 30); do
    ANDERE=$(printf '%s\n' "$PW" | rz_ssh_stdin "${WAECHTER[0]}" '
        read -r pw || true   # ohne Zeilenumbruch liefert read 1, obwohl es gelesen hat
        redis-cli -p 26379 --no-auth-warning sentinel master '"$VERBUND"' 2>/dev/null \
          | paste - - | awk -F"\t" "\$1==\"num-other-sentinels\"{print \$2}"')
    [ "${ANDERE:-0}" -ge 2 ] 2>/dev/null && { echo "   vollzaehlig nach ${i}s"; break; }
    sleep 1
done

echo "== Nachher =="
lage_zeigen
