Graylog

Stand 2023-06 gibt es noch keine offizielle EOL-Regelung. In der Regel werden die letzten drei dot-Releases unterstützt, also z.B. 5.2, 5.1, 5.0. Dies kann sich je nach Versionsnummer mit Major Releases überschneiden (Beispiel 5.1., 5.0, 4.3). Etwa alle 4 bis 6 Monate erscheint ein neues dot-Release.

Es gibt keine Releases, die als besonders stabil oder instabil gelten. Graylog führt daher auch keine LTS-Versionen. Vor einer intensiven Fehleranalyse wird empfohlen, auf eine neuere Version zu aktualisieren.

Graylog Cloud für Europa wird von AWS in Frankfurt gehostet.

Installation

Bemerkung

Unbedingt die zu installierenden Versionen der abhängigen Software-Pakete beachten: https://go2docs.graylog.org/current/downloading_and_installing_graylog/installing_graylog.html

Graylog kann als Linux-Paket (deb, rpm) oder als Docker-Container (docker-compose, Kubernetes) installiert werden.

Minimum:

  • 2x CPUs

  • 6 GB RAM, besser 8 GB

  • 8 GB Platz auf /

Hier wird die Installation von Graylog 6.0.x beschrieben.

dnf -y install epel-release
dnf -y install dnf-plugin-versionlock

MongoDB 7.x (inkl. Pinning):

  • Installation siehe MongoDB

  • Nach der Installation ZWINGEND ausführen: dnf versionlock add mongodb-org

OpenSearch 2.15.x (inkl. Pinning):

  • Installation siehe OpenSearch

  • Nach der Installation ZWINGEND ausführen: dnf versionlock add opensearch

  • OpenSearch für Graylog konfigurieren:

    /etc/opensearch/opensearch.yml
    # for a minimum unsecured running state, single node
    cluster.name: graylog
    node.name: ${HOSTNAME}
    path.data: /var/lib/opensearch
    path.logs: /var/log/opensearch
    discovery.type: single-node
    network.host: 0.0.0.0
    action.auto_create_index: false
    plugins.security.disabled: true
    indices.query.bool.max_clause_count: 32768
    

Die eigentliche Installation der Graylog Community Edition kann beginnen:

rpm --upgrade --verbose --hash https://packages.graylog2.org/repo/packages/graylog-6.0-repository_latest.rpm
dnf -y install graylog-server

dnf versionlock add graylog-server

Konfiguration - es müssen password_secret und root_password_sha2 generiert und konfiguriert werden.

  • password_secret (Passwort für alle Graylog-Nodes im Cluster): < /dev/urandom tr -dc A-Z-a-z-0-9 | head -c${1:-96};echo;

  • root_password_sha2: echo -n "Enter Password: " && head -1 </dev/stdin | tr -d '\n' | sha256sum | cut -d" " -f1

Dann:

/etc/graylog/server/server.conf
password_secret = 3JJgCxO...
root_username = graylog-admin
root_password_sha2 = 0621e81...
http_bind_address = 0.0.0.0:9000
elasticsearch_hosts = https://127.0.0.1:9200
elasticsearch_version_probe_attempts = 10
mongodb_version_probe_attempts = 10

SELinux:

setsebool -P httpd_can_network_connect=on

dnf -y install policycoreutils-python-utils
semanage port --add --type=mongod_port_t --proto=tcp 27017
systemctl daemon-reload
systemctl enable --now graylog-server.service

tail -f /var/log/opensearch/opensearch.log

Nach der Installation verfügbare Ports:

  • 9000 (tcp): Graylog-GUI und API-Browser per Swagger UI

  • 9200 (tcp): Elasticsearch/OpenSearch

  • 9300 (tcp): Elasticsearch/OpenSearch Node Communication

  • 27017 (tcp): MongoDB

Port 9000 nach aussen öffnen.

Achtung

Wird Graylog das erste mal gestartet, findet sich der Link zum Login inkl. Initial-Passwort in /var/log/graylog-server/server.log.

Konfiguration

  • Neues Index-Set mit alternativen Retention Times / Retention Policies einrichten: System / Indices / Indices > Default Index Set > Edit; als Default Index Set einrichten

  • Neuen Stream erstellen: Streams > Create stream, Remove matches from ‘Default Stream’, neues Index Set verwenden; Manage Rules > Add stream rule > Type: always match - so erhält der neue Stream alle Nachrichten in das neue Default Index set

Config Ex- und Import:

  • Export: System / Inputs > Content Packs: Create a content pack

  • Import: System / Inputs > Content Packs > Select content packs: Import content pack

Update

Vor dem Update sollte man sich die Upgrading und den Changelog anschauen (Achtung: richtige Version im Dropdown oben auswählen).

Generelles Vorgehen:

  1. Graylog stoppen

  2. Repo-File aktualisieren

  3. Graylog Package aktualisieren

  4. Config File prüfen

  5. Graylog starten

Input: Syslog

Soll Graylog Logdaten entgegennehmen, müssen die unter „System > Inputs“ konfiguriert werden.

Für das althergebrachte Syslog Port 1514 (udp) verwenden und lokale Firewall öffnen.

Bemerkung

Graylog kann nicht auf Port 514 hören, das führt zu einem „Permission denied, Failed to bind to: ip-address:514“, da es sich hier um einen privilegierten Port handelt. Daher wie in allen Beispielen Port 1514 verwenden. Um einen Bind auf jede IP-Adresse einzurichten, 0.0.0.0 verwenden, * funktioniert als Angabe nicht.

LDAP

Eine Gruppen-Synchronisation ist in Graylog Open nicht möglich (muss erworben werden). Eine bestimmte Gruppe kann beispielsweise auf folgende Art und Weise aus dem LDAP bezogen werden: (&(&(uid={0})(objectClass=inetOrgPerson))(memberOf=cn=MYGROUP,cn=groups,cn=accounts,dc=example,dc=org)) (in diesem Beispiel heisst die Gruppe MYGROUP).

Nachrichtenformat

Welche Felder sind Standard und/oder Pflicht?

  • message: das einzige Pflichtfeld, muss gesetzt werden; ohne gesetzte „message“ wird Nachricht nicht angenommen.

Felder, welche automatisch gesetzt werden:

  • source (wenn nicht gesetzt, wird die IP des sendenden Hosts eingetragen (nicht die eines möglichen Docker-Containers))

  • timestamp (wenn nicht gesetzt, wird aktueller Timestamp verwendet; überschreibbar mit UNIX-Timestamp; falls falsch überschrieben, wird die Nachricht verworfen)

Message-IDs sind UUIDs/GUIDs: ee1bdfe2-4c23-11e7-9f76-525400ce74cd.

Extractors

Unter System > Inputs > Extractors.

Elasticsearch Index / Indizes

Ein Elasticsearch-„Index“ hat einen Namen, z.B. „graylog_0“.

Default Settings des „Default index set“:

  • Rotation strategy: 20000000

  • Retention strategy: Delete Index

  • Max number of indices: 20

Wer eigene Streams definiert, sollte darauf achten, den Haken „Remove matches from ‚All messages‘ stream“ zu setzen, damit Nachrichten nicht doppelt gespeichert werden.

Theroetisch kann man mit diesen curl-Aufrufen nach einer Änderung die Indices neu bauen (praktisch hat das bei uns nicht funktioniert - auch nach einem einfachen Reboot des Graylog-Servers waren die alten Indizes noch vorhanden):

# if Elastic is in read-only mode, switch it back to normal
curl --request PUT \
    --header "Content-Type: application/json" \
    --data '{"index.blocks.read_only_allow_delete": null}'
    http://elastic.example.com:9200/_all/_settings

# start retention job
curl --request POST http://graylog.example.com:9000/api/system/indices/ranges/rebuild

Indizes aus Elasticsearch auslesen:

curl 'http://elastic.example.com:9200/_cat/indices?v'

Indizes-Statistiken:

curl 'http://elastic.example.com:9200/_stats'

Indizes leeren:

curl --request DELETE 'http://elastic.example.com:9200/_all'

Logging testen

Wichtig

Taucht in einer JSON-Nachricht an einen Syslog-Input der Key „level“ auf, wird die Nachricht von Graylog verworfen, sobald der dazu gehörende Value nicht kompatibel mit Syslog ist. Beispiel: „level“:“FINE“ wird verworfen, „level“:“5“ dagegen akzeptiert.

Syslog per 1514/udp:

logger --server graylog.example.com --udp --port 1514 'Test Syslog 1514/udp on '$(date)

Um GELF zu testen, wird ncat benötigt.

dnf -y install nmap-ncat

GELF per 12201/UDP:

# shortest message possible
echo -e '{"message": "Test GELF 12201/udp on '$(date)'"}' | ncat --wait 1 --udp graylog.example.com 12201

# longer message, should split up into fields
echo -e '{
    "version": "1.1",
    "host": "example.org",
    "short_message": "Test GELF 12201/udp on '$(date)'",
    "full_message": "Backtrace here\nsome stuff\nmore stuff",
    "level": 1,
    "_user_id": 9001,
    "some_info": "foo",
    "some_env_var": "bar"
}' | ncat --wait 1 --udp graylog.example.com 12201

GELF per 12201/TCP:

echo -e '{
    "version": "1.1",
    "host": "example.org",
    "short_message": "Test GELF 12201/tcp on '$(date)'",
    "full_message": "Backtrace here\nsome stuff\nmore stuff",
    "level": 1,
    "_user_id": 9001,
    "some_info": "foo",
    "some_env_var": "bar"
}' | ncat --wait 1 graylog.example.com 12201

Tipp

Graylog bei der Arbeit zuschauen: tail -f /var/log/graylog-server/server.log /var/log/opensearch/*log

Events und Alerts

„Alerts“ bedeutet: Man definiert einen Event (also einen Match auf bestimmte Log-Einträge), welcher eine Notification auslöst.

Beispiel: Sende eine Nachricht nach Rocket.Chat, sobald das Feld „operator_alert“ in einer Nachricht in einem bestimmten Stream vorkommt.

  • Graylog GUI > Alerts > Event Definitions > Create event definition

  • Event Details

    • Title: Operator Alert

  • Filter & Aggregation

    • Search Query: _exists_:operator_alert

    • Streams: Stream auswählen

    • Search within the last: 1 hours

    • Execute search every: 5 seconds

  • Notifications

    • Add notification

      • Title: operator_alert Notification

      • Notification Type: Custom HTTP Notification

      • URL: Rocket.Chat Webhook URL

      • Body Template:

        {
          "channel": "my channel",
          "text": ":service_crit: Graylog ${event_definition_title}: ${foreach backlog message}${message.message}${end}"
        }
        
    • Message Backlog: 1

Suche: Graylog Query Language

Beispiel für eine Suche in zwei Textfeldern und einem Integer-Feld:

NOT requestURI:"/billing/*" AND NOT requestURI:"/calendar/*" AND durationInMs:>500

Hosts ausschliessen:

NOT source:hostA and NOT source:hostB

Das Beispiel

source:host.example.* AND response-time:<30000

liefert auch Ergebnisse mit „response-time“ von 100’000 und mehr? Da drängt sich der Verdacht auf, dass es sich hier um eine Suche nach Strings und nicht nach Integern handelt.

So kann man überprüfen, wie Elasticsearch die Werte speichert - auf dem Log-Server:

curl 'http://localhost:9200/_mapping?pretty'
curl 'http://localhost:9200/_cat/indices?v'
curl 'localhost:9200/_cat/templates?v'

Client-Logging

rsyslog

Soll rsyslog Events an einen Graylog Server weiterleiten, empfiehlt sich das GELF Protokoll, da hier einfach zusätzliche Felder mitgeschickt werden können:

/etc/rsyslog.d/graylog.conf
# send server's FQDN instead of its short hostname
$PreserveFQDN on

template(name="gelf" type="list") {
    constant(value="{\"version\":\"1.1\",")
    constant(value="\"host\":\"")
    property(name="hostname")
    constant(value="\",\"short_message\":\"")
    property(name="msg" format="json")
    constant(value="\",\"timestamp\":")
    property(name="timegenerated" dateformat="unixtimestamp")
    constant(value=",\"level\":\"")
    property(name="syslogseverity")
    constant(value="\"}")
}

# syslog forwarder via UDP
action(type="omfwd" target="graylog.example.com" port="12201" protocol="udp" template="gelf")

Damit ein Client sinnvolle Daten weiterleitet, kann folgende rsyslog-Konfiguration als Basis verwendet werden. So können die Events schon auf dem Client gefiltert werden, und dann per Syslog oder GELF weitergeleitet werden.

/etc/rsyslog.d/graylog.conf
# rsyslog v7 filter conditions:
# contains isequal startswith regex ereregex
# http://www.rsyslog.com/doc/v7-stable/configuration/filters.html
if (
    $msg startswith "GSSAPI client step " or
    $msg startswith "GSSAPI server step " or
    ($programname == "kernel" and $msg startswith "RULE ") or
    ($programname == "systemd" and ($msg startswith "Created slice " or $msg startswith "Removed slice ")) or
    ($programname == "systemd" and ($msg startswith "Starting user-" or $msg startswith "Stopping user-")) or
    ($programname == "systemd" and ($msg startswith "Starting Session " or $msg startswith "Started Session ")) or
    ($programname == "systemd-logind" and ($msg startswith "New Session " or $msg startswith "Removed Session "))
)
then
    # ignore, do not foward
    continue
else
    # Syslog
    *.* @graylog.example.com:1514;RSYSLOG_SyslogProtocol23Format

    # GELF (requires the template from above)
    # action(type="omfwd" target="graylog.example.com" port="12201" protocol="udp" template="gelf")

Beispiel einer Weiterleitung per rsyslog für einen JBoss-Applikationsserver (alternativ WildFly):

/etc/rsyslog.d/jboss.conf
module(load="imfile" PollingInterval="10")

input(
    type="imfile"
    File="/opt/jboss/standalone/log/server.json"
    Tag="jboss-eap"
    StateFile="statefile-to6nnu8DgDug4ImsaaJCpkAQt0zPKb"
)

# just forward, do not log again in /var/log/messages followed by the rules later on in /etc/rsyslog.conf
if $programname == 'jboss-eap' then {
    action(
        type="omfwd"
        Target="graylog.example.com"
        Port="1514"
        Protocol="udp"
    )
    stop
}

Apache

Am besten so: https://www.lisenet.com/2016/send-apache-logs-to-graylog/

  • LogFormat: direkt GELF-kompatibles JSON schreiben

  • CustomLog mit ncat direkt an Graylog pipen

# GELF Log Format
LogFormat "{\n\
  \"version\": \"1.1\",\n\
  \"host\": \"%v\",\n\
  \"short_message\": \"%r\",\n\
  \"timestamp\": %{%s}t,\n\
  \"level\": 6,\n\
  \"_user_agent\": \"%{User-Agent}i\",\n\
  \"_source_ip\": \"%{X-Forwarded-For}i\",\n\
  \"_duration_usec\": %D,\n\
  \"_duration_sec\": %T,\n\
  \"_request_size_byte\": %O,\n\
  \"_http_status_orig\": %s,\n\
  \"_http_status\": %>s,\n\
  \"_http_request_path\": \"%U\",\n\
  \"_http_request\": \"%U%q\",\n\
  \"_http_method\": \"%m\",\n\
  \"_http_referer\": \"%{Referer}i\",\n\
  \"_from_apache\": \"true\"\n\
}" gelf
CustomLog "|/usr/bin/ncat --udp graylog.example.com 12201" gelf

Fortinet

Bei Fortinet kann statt Syslog besser ` das Common Event Format (CEF) <https://go2docs.graylog.org/5-0/getting_in_log_data/ingest_cef.html>`_ verwendet werden.

journalctl per GELF

Hinweis: Nachrichten per TCP müssen mit einem „0“ beendet werden.

Wir empfehlen nailgun/journal2gelf. Zunächst EPEL-Repo aktivieren, dann:

dnf -y install git python-pip gcc python-devel systemd-devel

pip install --user git+https://github.com/systemd/python-systemd.git#egg=systemd
pip install --user gelfclient
git clone https://github.com/nailgun/journal2gelf.git

cd journal2gelf
python setup.py install

journal2gelf --help

Test (geht per GELF an Port 12201/udp):

journal2gelf graylog.example.com:12201
/etc/systemd/system/journal2gelf.service
[Unit]
Description=Journald to GELF (graylog) log relay service

[Service]
ExecStart=/usr/bin/journal2gelf graylog.example.com:12201
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

Java Application Server

Alle Log-Daemons arbeiten nach dem Prinzip „Zeilenumbruch = neue Log-Message“, so auch Graylog. Dieses Verhalten lässt sich nicht ändern.

Um Graylog fit für Java Appserver zu machen, die z.B. bei Stacktraces riesige Meldungen mit Zeilenumbrüchen garniert ausgeben, muss im Java Applikationsserver ein GELF-Appender im nativen, von Entwickler eingesetzten Java Logging Framework eingesetzt werden.

GELF-Appender gibt es einige, und sind wie gesagt von dem abhängig, was ein Entwickler an Log-Mechanismen verwendet. Einige GELF-Appender bauen auf dem GELF Java Client auf: https://github.com/Graylog2/gelfclient

Windows

Soll ein Windows-Server Event-Logs senden, wird ein lokal zu installierender Agent benötigt. Graylog empfiehlt die Kombi aus Graylog Sidecar (ein Daemon) und WinLogBeat 7.x (der eigentliche Client). Alternativ kann man Graylog Sidecar und NXlog einsetzen.

Siehe auch https://go2docs.graylog.org/5-2/getting_in_log_data/ingest_windows_eventlog.html

Auf dem Graylog Server:

  • Es muss im Vorfeld ein API Key erstellt werden: System > Users and Teams

  • Ausserdem muss ein Beats-Input erstellt werden

Installation Sidecar auf dem Windows Host:

Wieder auf dem Graylog Server:

  • Unter System > Sidecars sollte nun das zuvor installiert Sidecar zu sehen sein

  • Rechts auf Show Messages sollten bereits die ersten Log Messages zu sehen sein

Benutzer

Benutzer benötigt Decorator-Edit-Rechte? Sowas geht nur über das REST-API.

Verwendbare Berechtigungen abfragen:

curl --request GET \
    --user admin:linuxfabrik \
'http://graylog.example.com:9000/api/system/permissions?pretty=true'

Ergebnis u.a.:

"decorators" : [ "create", "read", "edit" ]

Neue Rolle erstellen:

curl --request POST \
    --user admin:linuxfabrik \
    --header 'Content-Type:application/json' \
    --data '{"read_only": false,"permissions": ["decorators:create:*", "decorators:read:*", "decorators:edit:*" ],"name": "Full Decorator Permissions","description": "Permission to create, read oder delete decorators."}'
    'http://graylog.example.com:9000/api/roles'

Anschliessend kann die neue Rolle im GUI > Authentication Management ganz unten unter „Roles“ den Benutzern zugeteilt werden.

Berechtigungen für den Benutzer prüfen:

curl --request GET \
    --user admin:linuxfabrik \
    "http://graylog.example.com:9000/api/users/$USERNAME?pretty=true"

Pipelines

Unter System > Configuration > Message Processors Configuration die „Pipeline“ aktivieren und nach der „Message Filter Chain“ setzen, wenn man auf Felder prüfen möchte, die von Extraktoren gesetzt wurden.

Ablauf:

  • Pipeline anlegen

  • Stage anlegen

  • Rule anlegen

  • Rule zur Stage hinzufügen

  • Pipeline zu Stream connecten

Sinnfreies Beispiel für eine Rule mit Kommentaren, Abfrage auf einen Input und Wegwerfen einer Nachricht:

rule "unneeded if container_name contains _app_ or _cep_"
when
    (
     // to_string($message.gl2_source_input) == "5b16a9176e8bf60b6cf8bc6b" and
     has_field("container_name") and
     (contains(to_string($message.container_name), "_app_") or contains(to_string($message.container_name), "_cep_"))
    )
then
    set_field("unneeded_message", true);
    drop_message();
end

Simulation:

GELF-Message: {
    "version": "1.1",
    "host": "example.org",
    "short_message": "Short message",
    "full_message": "Full message",
    "level": 1,
    "container_name": "my_container"
}

Wert nach Long konvertieren:

rule "Convert RT_FLOW to Numeric"
when
    has_field("bytes-from-server" ) && $message.application_name == "RT_FLOW"
then
    let serverre = to_long( $message.`bytes-from-server`);
    let clientre = to_long( $message.`bytes-from-client`);
    set_field("bytes-from-server_conv", serverre );
    set_field("bytes-from-client_conv", clientre );
end

API, API-Browser

Alle Methoden auf einen Blick: http://graylog.example.com:9000/api/api-browser

Authentifizierung entweder mittels Username und Passwort, oder per Access Token (letztere ist aus Sicherheitsgründen die bevorzugte Methode).

Access Token erstellen:

  • Benutzer erstellen, Standard-Rolle „Reader“ notfalls anpassen

  • Token zuweisen

curl --user user:linuxfabrik \
    --header 'Accept: application/json' \
    --request GET \
    'http://graylog.example.com:9000/api/...' | jq

Cluster-Informationen auslesen - mit Username und Passwort:

curl --user admin:linuxfabrik \
    --header 'Accept: application/json' \
    'http://graylog.example.com:9000/api/cluster?pretty=true'

Cluster-Informationen auslesen - mit Access Token (Token als Benutzername nutzen; Passwort ist hier immer „token“):

curl --user 1crgamba51dkdn8jsrltpek90m167p1dmteaa0gr7ehscnb3bpce:token \
    --header 'Accept: application/json' \
    'http://graylog.example.com:9000/api/cluster?pretty=true'

Log-Einträge auf Basis einer Query als CSV exportieren:

# urlencoded query
#q=*
q=syslog_identifier%3Asshd%20AND%20opened%20AND%20NOT%20root
range=300       # in seconds

# comma-separated list of fields (%2C)
curl --user admin:linuxfabrik \
    --header 'Accept:text/csv' \
    'http://graylog.example.com:9000/api/search/universal/relative/export?query=$q&range=$range&fields=timestamp%2Csource%2Cmessage' | gzip -9 > export.csv

Streams und Stream Rules:

  • Stream Rule Type 1: match exactly

  • Stream Rule Type 2: match regular expression

  • Stream Rule Type 3: greater than

  • Stream Rule Type 4: smaller than

  • Stream Rule Type 5: field presence

  • Stream Rule Type 6: contain

  • Stream Rule Type 7: always match

  • Stream Rule Type 8: match input

curl --user user:linuxfabrik \
    --header 'Accept: application/json' \
    --header 'X-Requested-By: cli' \
    --data '{
          "title": "All messages",
          "description": "All messages are routed here",
          "matching_type": "AND",
          "rules": [
            {
              "field": "",
              "type": 7,
              "inverted": false,
              "value": ""
            }
          ],
          "content_pack": null
        }' \
    --request POST \
    'http://graylog.example.com:9000/api/streams' | jq

Troubleshooting

„415 Unsupported Media Type“ bei Verwendung des APIs

The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.

OpenSearchException[OpenSearch exception [type=illegal_argument_exception, reason=mapper [latency] cannot be changed from type [float] to [long]]]

Die Mappings (OpenSearch / Elasticsearch Felder und deren Typ) werden dynamisch anhand der ersten Nachricht im jeweiligen Index bestimmt. Dies geschieht jedes Mal, wenn ein neuer Index begonnen wird, also auch, wenn der Index rotiert wurde. Das heisst, wenn der erste Eintrag latency: 0 ist, nimmt OpenSearch fälschlicherweise an, dass es sich um ein long handelt. Wenn man nun einen float-Wert schickt, wird dieser zwar im Graylog und in OpenSearch als float angezeigt, intern aber trotzdem als long behandelt (siehe https://xeraa.net/blog/2020_elasticsearch-coerce-float-to-integer-or-long/).

Um dieses Problem zu beheben, kann man den Typ für das Feld latency manuell setzen:

# show current mapping
curl 'localhost:9200/lfops-default_4/_mappings?pretty' | grep latency -A1
# "latency" : {
#   "type" : "float"

# create custom mapping, see https://go2docs.graylog.org/5-1/setting_up_graylog/elasticsearch.htm#CustomIndexMappings
cat > lfops-latency-float.json << 'EOF'
{
    "template": "lfops-default_*",
    "order": 10,
    "mappings": {
        "properties": {
            "latency": {
                "type": "float"
            }
        }
    }
}
EOF
curl -X PUT --data @'lfops-latency-float.json' -H 'Content-Type: application/json' 'http://localhost:9200/_template/lfops-latency-float?pretty'

# show current templates
curl -X DELETE 'http://localhost:9200/_template/lfops-latency-float?pretty'

Anschliessend muss im Graylog unter Indices der betroffene Index ausgewählt und über „Maintanance > Rotate active write index“ der aktuelle Index neu generiert werden.

Built on 2024-11-18