Nextcloud synchronisierte nicht. Gemerkt hat es niemand.

Am Dienstagmorgen ist mir aufgefallen, dass das Nextcloud-Symbol in der Menüleiste meines Macs fehlte. Ich habe den Client gestartet. Nichts passierte. Zweiter Versuch, dritter Versuch, jedes Mal das Gleiche: kurz ein Prozess, dann war er wieder weg.

Das Unangenehme daran: Ein Sync-Client arbeitet im Hintergrund. Fällt er aus, verschwindet ein kleines Symbol, sonst ändert sich nichts. Man merkt es erst, wenn auf einem anderen Gerät eine Datei fehlt. Wie lange mein Client schon nicht mehr lief, kann ich nicht sicher sagen. Crash-Reports gab es nur von diesem Morgen. Das Update auf die betroffene Version hatte sich aber schon am 30. Oktober eingespielt, und eines der Sync-Journale wurde seitdem nicht mehr verändert.

Hier also der Weg von der Diagnose über die Reparatur bis zu einem kleinen Wächter, der mich künftig nach wenigen Minuten warnt. Alle Befehle laufen im Terminal von macOS.

Läuft Nextcloud überhaupt?

Die erste Frage ist schnell beantwortet. pgrep sucht nach laufenden Prozessen. -f durchsucht die ganze Befehlszeile, -l zeigt den Namen mit an, -i ignoriert Groß- und Kleinschreibung:

pgrep -fl -i nextcloud

Es liefen nur FinderSyncExt und FinderSyncBroker, zwei Hilfsprozesse für die Finder-Integration. Der Hauptprozess Nextcloud fehlte. Installiert war der Client in Version 35.0.1, und er stand auch in den Anmeldeobjekten. Am Autostart lag es also nicht.

Die Crash-Reports

Stürzt ein Programm unter macOS ab, legt das System einen Bericht in ~/Library/Logs/DiagnosticReports ab. Die Tilde steht dabei für dein Home-Verzeichnis. ls -lt zeigt die neuesten Dateien zuerst, grep filtert auf Nextcloud:

ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i nextcloud

Bei mir lagen dort drei Berichte, genau zu den Zeiten meiner Startversuche. Die Dateien enden auf .ips und bestehen aus einer Kopfzeile und einem großen JSON-Block. Das folgende Python-Skript liest beides ein und gibt nur das Wichtige aus: App-Version, Art der Ausnahme und die obersten Einträge der Aufrufkette im abgestürzten Thread. Den Dateinamen ersetzt du durch einen deiner Berichte:

python3 - ~/Library/Logs/DiagnosticReports/Nextcloud-<datum>.ips <<'EOF'
import json, sys
raw = open(sys.argv[1]).read().split("\n", 1)
hdr, body = json.loads(raw[0]), json.loads(raw[1])
print("app:", hdr.get("app_name"), hdr.get("app_version"), "| os:", hdr.get("os_version"))
print("exception:", body.get("exception"))
print("asi:", body.get("asi"))
th = body["threads"][body["faultingThread"]]
imgs = body["usedImages"]
for i, fr in enumerate(th["frames"][:18]):
    im = imgs[fr["imageIndex"]] if "imageIndex" in fr else {}
    print(i, im.get("name"), fr.get("symbol"))
EOF

Die Ausnahme lautete EXC_CRASH (SIGABRT) mit dem Zusatz abort() called. Das Programm hat sich also selbst beendet, weil es in einen Zustand geraten war, in dem es nicht weiterarbeiten wollte. Aufschlussreicher war die Aufrufkette, hier in der Reihenfolge, in der die Funktionen aufgerufen wurden:

FolderMan::setupFolders
→ Folder::startVfs
→ SyncJournalDb::open
→ checkConnect
→ updateDatabaseStructure
→ updateMetadataTableStructure
→ SqlQuery::prepare
→ QMessageLogger::fatal
→ abort

Übersetzt: Beim Start öffnet der Client die Datenbank eines Sync-Ordners und will deren Tabellenstruktur aktualisieren. Dabei scheitert ein SQL-Befehl, und der Client bricht hart ab.

Die Fehlermeldung im Systemlog

macOS sammelt die Meldungen aller Programme im Unified Log. log show liest es aus. Der Befehl unten zeigt die letzten 15 Minuten, gefiltert auf den Prozess Nextcloud und auf Meldungen, die „sql” oder „fatal” enthalten:

log show --last 15m --style compact \
  --predicate 'process == "Nextcloud" AND (eventMessage CONTAINS[c] "sql" OR eventMessage CONTAINS[c] "fatal")'

Bei jedem Start stand dort dieselbe Zeile:

(QtCore) ENFORCE: "allow_failure" in file .../src/common/ownsql.cpp, line 255 with message: SQLITE Prepare error

Die eigentliche SQL-Fehlermeldung schreibt der Client in sein eigenes Log. Das liegt aber im Container der App-Sandbox unter ~/Library/Containers/com.nextcloud.desktopclient/Data, und den sperrt macOS für andere Programme. Das Terminal bekommt dort nur Operation not permitted zu sehen. Ich musste die Ursache also auf anderem Weg einkreisen.

Welcher Ordner ist es?

Der Nextcloud-Client merkt sich für jeden Sync-Ordner in einer kleinen SQLite-Datenbank, welche Dateien er schon kennt. Diese Datenbank heißt Journal. Sie liegt versteckt direkt im Sync-Ordner und trägt einen Namen nach dem Muster .sync_<hash>.db:

ls -la ~/<sync-ordner>/.sync_*.db*

Ich synchronisiere zwei Ordner. Das Journal des einen wurde bei jedem Absturz neu geschrieben, das des anderen blieb unverändert. Damit war klar, welcher Ordner den Absturz auslöst.

Bevor du so ein Journal untersuchst: Arbeite nie am Original. Der Client darf nicht laufen, und du kopierst die Datei vorher an einen anderen Ort. Das Original habe ich während der ganzen Untersuchung nicht angefasst.

mkdir -p /tmp/nc-journal-test
cp -p ~/<sync-ordner>/.sync_<hash>.db /tmp/nc-journal-test/journal.db
sqlite3 /tmp/nc-journal-test/journal.db "PRAGMA integrity_check;"

Die Integritätsprüfung brach mit unknown function: parent_hash() ab. Das sah zuerst nach einer beschädigten Datei aus, ist aber harmlos. Nextcloud registriert beim Öffnen eine eigene SQL-Funktion mit diesem Namen, und der normale sqlite3-Befehl kennt sie nicht. Lesende Abfragen, die diese Funktion nicht brauchen, klappen trotzdem. In älteren Forenbeiträgen taucht dieselbe Meldung als Ursache für ganz andere Abstürze auf. Hier ist sie nur ein Nebengeräusch.

Die Versionstabelle zeigte, dass das Journal zuletzt von Version 35.0.1 beschrieben worden war:

sqlite3 /tmp/nc-journal-test/journal.db "SELECT * FROM version;"

Die Spur: doppelte Inodes

Mit der Fehlermeldung und der Versionsnummer war die Suche im Nextcloud-Repository auf GitHub kurz. Mehrere Issues beschreiben genau diesen Ablauf, für macOS, Linux und Windows. Version 35.0.1 legt beim Start einen neuen Index im Journal an:

CREATE UNIQUE INDEX IF NOT EXISTS metadata_unique_inode ON metadata(inode);

Jede Datei hat auf der Festplatte eine Nummer, die Inode. „Unique” bedeutet, dass jede Inode-Nummer im Journal nur einmal vorkommen darf. Ob das bei mir verletzt war, verrät eine Abfrage an der Kopie:

sqlite3 /tmp/nc-journal-test/journal.db "
  SELECT inode, path FROM metadata
  WHERE inode IN (SELECT inode FROM metadata GROUP BY inode HAVING count(*) > 1)
  ORDER BY inode;"

Im Journal des unauffälligen Ordners gab es keine Treffer. Im Journal des abstürzenden Ordners standen drei Inode-Nummern, jede in zwei Zeilen. Jeweils ein Pfad lag in einem Ordner mit Plakat-PDFs, der andere im Medienordner eines Videoprojekts mit der Endung .cmproj.

Hardlinks

Zwei Pfade, eine Inode: Das riecht nach Hardlinks. Ein Hardlink ist ein zweiter Name für dieselbe Datei. Auf der Platte liegt sie nur einmal, im Dateisystem taucht sie an zwei Stellen auf. APFS kann das, Linux-Dateisysteme auch. Prüfen lässt sich das mit find, das alle Pfade mit einer bestimmten Inode-Nummer sucht, und stat, das die Zahl der Links ausgibt:

find ~/<sync-ordner> -inum <inode> -exec stat -f 'links=%l  %N' {} \;

Für alle drei Inodes kamen zwei Pfade mit links=2 zurück. Echte Hardlinks also. Das Videoprogramm legt importierte Medien offenbar als Hardlink statt als Kopie in sein Projekt. Die Dateiendung spricht für Camtasia, geprüft habe ich das nicht.

Die Ursache in einfachen Worten

Damit ergibt sich folgendes Bild:

  1. Version 35.0.1 will beim Start einen eindeutigen Index auf die Inode-Nummer anlegen.
  2. In meinem Journal stehen drei Dateien doppelt, weil sie als Hardlink unter zwei Pfaden liegen.
  3. Der Index lässt sich deshalb nicht anlegen. SQLite meldet UNIQUE constraint failed: metadata.inode.
  4. Der Client fängt diesen Fehler nicht sauber ab. Laut den Issues bleibt die Datenbankverbindung danach unbrauchbar. Eine Absicherung im Code greift und beendet das Programm mit abort().
  5. Am Journal ändert sich dabei nichts. Beim nächsten Start passiert deshalb exakt dasselbe, und der Client kommt nie bis zur Oberfläche. Darum fehlte auch das Symbol in der Menüleiste.

Die SQL-Meldung aus Punkt 3 und das Verhalten aus Punkt 4 habe ich aus den Issues übernommen, denn das Client-Log konnte ich wegen der Sandbox nicht lesen. Einen Gegenversuch habe ich auch nicht gemacht, also die Duplikate unter 35.0.1 zu entfernen und neu zu starten. Journalbefund, Systemlog und die Beschreibungen in den Issues passen aber so genau zusammen, dass ich an der Ursache wenig Zweifel habe.

Dazu kommt das automatische Update. Es hat 35.0.1 ungefragt installiert. Nextcloud hat die Version inzwischen vom Update-Server genommen, das bestätigt ein Kommentar eines Maintainers vom 2. November. Nur kann sich ein Client, der beim Start abstürzt, nicht mehr selbst aktualisieren.

Die Reparatur: Update auf 35.0.3

Der übliche Rat bei Journal-Problemen lautet: Journal löschen. Das hätte vermutlich geholfen. Der Client hätte dann aber den gesamten Ordner neu abgleichen müssen. Das dauert und kann Konfliktkopien erzeugen. Weil der Fehler im Programm lag und meine Daten in Ordnung waren, wollte ich das vermeiden.

In den Issues wird auch ein Downgrade auf 35.0.0 empfohlen. Seit dem 2. November gibt es aber Version 35.0.2, die den Index wieder zurücknimmt, und seit dem 5. November Version 35.0.3 mit weiteren Build-Anpassungen. Ich habe mich für 35.0.3 entschieden.

Das Installationspaket liegt in den GitHub-Releases von Nextcloud. curl lädt es herunter, shasum berechnet die SHA-256-Prüfsumme. Die vergleichst du mit der Angabe auf der Release-Seite:

curl -sL -o /tmp/Nextcloud-35.0.3.pkg \
  https://github.com/nextcloud-releases/desktop/releases/download/v35.0.3/Nextcloud-35.0.3.pkg

shasum -a 256 /tmp/Nextcloud-35.0.3.pkg
# erwartet (laut GitHub): 8d555eb26caf2868d815ad5ba336ac58619a0dad77c8c8a5bbff5412804710f8

pkgutil --check-signature /tmp/Nextcloud-35.0.3.pkg

pkgutil --check-signature zeigt, wer das Paket signiert hat. Bei mir stand dort „Developer ID Installer: Nextcloud GmbH (NKUJUXUJ3B)”, dazu der Hinweis, dass Apple das Paket notarisiert hat. Prüfsumme und Signatur passten.

Installiert habe ich im Terminal. sudo fragt dabei nach deinem Administratorpasswort. Ein Doppelklick auf das Paket im Finder führt zum selben Ergebnis.

sudo installer -pkg /tmp/Nextcloud-35.0.3.pkg -target /

Danach Client starten, 15 Sekunden warten und nachsehen, ob der Prozess läuft und ein neuer Crash-Report entstanden ist:

open -a Nextcloud
sleep 15
pgrep -fl "Nextcloud.app/Contents/MacOS/Nextcloud"
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i nextcloud | head -n 1

Der Hauptprozess lief, auch 20 Sekunden später noch, und einen neuen Crash-Report gab es nicht. Beide Journale waren nun gesperrt, sqlite3 meldete „database is locked”. Das ist in diesem Fall ein gutes Zeichen, denn der Client hatte sie geöffnet. Konfiguration und Journale blieben unangetastet, ein Neuabgleich war nicht nötig. Die Journal-Kopien und das Installationspaket habe ich anschließend gelöscht.

Zwei Dinge solltest du nach so einem Ausfall noch prüfen. Ob der Abgleich durch ist, zeigt das Menü des Nextcloud-Symbols mit „Aktuell” oder „Synchronisiere …”. Und falls in der Ausfallzeit Dateien lokal und auf dem Server geändert wurden, legt Nextcloud Konfliktkopien an. Die erkennst du am Wort „Konflikt” im Dateinamen.

Warum ich einen Wächter wollte

Der Fehler selbst war noch am selben Morgen behoben. Was mich mehr beschäftigt hat: Ohne den zufälligen Blick auf die Menüleiste wäre mir das womöglich noch tagelang nicht aufgefallen. Der Client meldet sich nicht, wenn er nicht läuft. Woher auch.

Drei Wege bieten sich an. Du kannst automatische Updates abschalten und neue Versionen bewusst etwas später einspielen. Du kannst in der Nextcloud-Weboberfläche unter Persönliche Einstellungen, Sicherheit, Geräte & Sitzungen nachsehen, wann sich ein Client zuletzt gemeldet hat. Oder du lässt ein kleines Skript regelmäßig prüfen, ob der Client läuft, und sonst eine Benachrichtigung zeigen. Ich habe mich für das Skript entschieden und werfe zusätzlich ab und zu einen Blick in die Geräteliste.

Ein paar Entscheidungen standen vorher fest:

  • Kein automatischer Neustart. Ein Skript, das Nextcloud bei Bedarf neu startet, hätte den Client in meinem Fall im Fünf-Minuten-Takt abstürzen lassen. Der Fehler wäre verdeckt geblieben, dafür hätte ich Berge von Crash-Reports gesammelt. Der Wächter soll nur auffallen.
  • Kein Fehlalarm beim Anmelden. Nextcloud braucht nach dem Login ein paar Sekunden. Gewarnt wird erst, wenn der Prozess bei zwei Prüfungen in Folge fehlt.
  • Keine Dauerbeschallung. Nach der ersten Warnung gibt es nur noch etwa einmal pro Stunde eine Erinnerung.
  • Ein Pausenschalter. Wer Nextcloud absichtlich beendet, legt eine Datei an und hat Ruhe.
  • Hinweis auf Abstürze. Liegt ein frischer Crash-Report vor, sagt die Meldung „abgestürzt” statt nur „läuft nicht”.
  • Nur Bordmittel. sh, pgrep, osascript, find und launchd. Keine Zusatzsoftware, kein Netzwerkzugriff.

Das Skript

Das Skript speicherst du als ~/bin/nc-watch.sh. Den Ordner ~/bin legt die Installation weiter unten an. Die Meldungstexte sind absichtlich ohne Umlaute geschrieben, weil die Zeichenkodierung unter launchd nicht zuverlässig gesetzt ist.

#!/bin/sh
# ----------------------------------------------------------------------------
# nc-watch.sh  -  Nextcloud-Waechter
#
# Warnt per macOS-Benachrichtigung, wenn der Nextcloud-Desktop-Client nicht
# laeuft (z. B. weil er beim Start abstuerzt oder beendet wurde).
#
# Wird alle 5 Minuten per launchd gestartet (local.nc-watch).
# Der Waechter startet Nextcloud ABSICHTLICH NICHT neu: Bei einem Absturz beim
# Start wuerde das nur eine Endlosschleife erzeugen. Er soll nur auffallen.
#
# Verhalten:
#   - Nextcloud laeuft            -> still, Zaehler wird zurueckgesetzt
#   - Nextcloud laeuft nicht      -> Zaehler +1; ab THRESHOLD Pruefungen in Folge
#                                    (Standard 2 = ca. 5-10 Minuten) erste Warnung,
#                                    danach alle REPEAT Pruefungen (Standard 12 =
#                                    ca. 1 Stunde) eine Erinnerung
#   - Pausendatei vorhanden       -> still (z. B. wenn Nextcloud absichtlich aus ist)
#   - Neuer Crash-Report vorhanden-> Warntext nennt den Absturz
#
# Optionen / Umgebungsvariablen (zum Testen):
#   --test                     sofort eine Test-Benachrichtigung senden
#   NC_WATCH_PROCESS=Name      anderen Prozessnamen pruefen (Test: GibtEsNicht)
#   NC_WATCH_THRESHOLD=2       Fehlpruefungen bis zur ersten Warnung
#   NC_WATCH_REPEAT=12         Erinnerung alle N Pruefungen
# ----------------------------------------------------------------------------

PROC="${NC_WATCH_PROCESS:-Nextcloud}"
THRESHOLD="${NC_WATCH_THRESHOLD:-2}"
REPEAT="${NC_WATCH_REPEAT:-12}"
CRASH_WINDOW_MIN="${NC_WATCH_CRASH_WINDOW_MIN:-120}"

STATE_DIR="$HOME/Library/Application Support/nc-watch"
STATE="$STATE_DIR/misses"          # Zaehler aufeinanderfolgender Fehlpruefungen
LAST="$STATE_DIR/last_check"       # Zeitstempel der letzten Pruefung (Lebenszeichen)
PAUSE="$STATE_DIR/pause"           # wenn vorhanden: keine Warnungen
LOG="$HOME/Library/Logs/nc-watch.log"
CRASH_DIR="$HOME/Library/Logs/DiagnosticReports"

mkdir -p "$STATE_DIR"

log() {
    printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG"
}

# Log klein halten (ab ca. 200 KB auf die letzten 200 Zeilen kuerzen)
if [ -f "$LOG" ] && [ "$(stat -f%z "$LOG" 2>/dev/null || echo 0)" -gt 200000 ]; then
    tail -n 200 "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG"
fi

notify() {
    # $1 = Text. Keine Anfuehrungszeichen im Text verwenden.
    # Schlaegt die Anzeige fehl (z. B. keine GUI-Sitzung), steht das im Log.
    if ! err=$(osascript -e "display notification \"$1\" with title \"Nextcloud-Waechter\" sound name \"Basso\"" 2>&1); then
        log "FEHLER: Benachrichtigung konnte nicht angezeigt werden: $err"
    fi
}

# --- Testmodus ---------------------------------------------------------------
if [ "$1" = "--test" ]; then
    notify "Testmeldung: Wenn du das siehst, funktionieren die Warnungen."
    log "Testbenachrichtigung gesendet"
    echo "Testbenachrichtigung gesendet."
    exit 0
fi

date '+%Y-%m-%d %H:%M:%S' > "$LAST"

# --- Pause -------------------------------------------------------------------
if [ -f "$PAUSE" ]; then
    exit 0
fi

# --- Prozess pruefen ---------------------------------------------------------
if pgrep -x "$PROC" >/dev/null 2>&1; then
    if [ -f "$STATE" ]; then
        log "OK: $PROC laeuft wieder (nach $(cat "$STATE") Fehlpruefungen)"
        rm -f "$STATE"
    fi
    exit 0
fi

# --- Prozess laeuft nicht ------------------------------------------------------
n=$(( $(cat "$STATE" 2>/dev/null || echo 0) + 1 ))
echo "$n" > "$STATE"
log "WARNUNG: $PROC laeuft nicht (Fehlpruefung $n)"

if [ "$n" -ge "$THRESHOLD" ] && [ $(( (n - THRESHOLD) % REPEAT )) -eq 0 ]; then
    crash=$(find "$CRASH_DIR" -maxdepth 1 -name 'Nextcloud-*.ips' -mmin "-$CRASH_WINDOW_MIN" 2>/dev/null | head -n 1)
    if [ -n "$crash" ]; then
        msg="Nextcloud ist abgestuerzt (neuer Crash-Report). Dateien werden NICHT synchronisiert!"
    else
        msg="Nextcloud laeuft nicht. Dateien werden NICHT synchronisiert!"
    fi
    notify "$msg"
    log "Benachrichtigung gesendet: $msg"
fi

exit 0

Die Logik in Kurzform: Bei jedem Lauf schreibt das Skript einen Zeitstempel als Lebenszeichen. Existiert die Pausendatei, endet es still. pgrep -x Nextcloud prüft, ob ein Prozess mit genau diesem Namen läuft. Wenn ja, löscht das Skript einen eventuell vorhandenen Zähler. Wenn nein, steigt der Zähler. Gewarnt wird, sobald der Zähler die Schwelle erreicht hat und der Abstand zur Schwelle durch die Wiederholungszahl teilbar ist. Mit den Standardwerten 2 und 12 sind das die Prüfungen 2, 14, 26, 38 und so weiter. Bei einem Takt von fünf Minuten heißt das: erste Warnung nach fünf bis zehn Minuten, danach stündlich.

Der LaunchAgent

Auf dem Mac startet launchd Programme zeitgesteuert, also das, was unter Linux oft Cron erledigt. Ein LaunchAgent läuft in deiner Benutzersitzung, und nur dort kann er Benachrichtigungen anzeigen. Die Konfiguration ist eine XML-Datei, eine sogenannte plist. Speichere sie als ~/Library/LaunchAgents/local.nc-watch.plist und ersetze DEINNAME durch deinen Benutzernamen, denn launchd löst die Tilde nicht auf:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>local.nc-watch</string>

    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>/Users/DEINNAME/bin/nc-watch.sh</string>
    </array>

    <!-- alle 300 Sekunden (5 Minuten) pruefen -->
    <key>StartInterval</key>
    <integer>300</integer>

    <!-- zusaetzlich sofort beim Laden (Anmeldung / bootstrap) -->
    <key>RunAtLoad</key>
    <true/>

    <key>StandardOutPath</key>
    <string>/Users/DEINNAME/Library/Logs/nc-watch.out</string>
    <key>StandardErrorPath</key>
    <string>/Users/DEINNAME/Library/Logs/nc-watch.err</string>

    <key>ProcessType</key>
    <string>Background</string>
</dict>
</plist>

StartInterval startet das Skript alle 300 Sekunden, RunAtLoad zusätzlich sofort beim Laden und bei jeder Anmeldung. ProcessType Background erlaubt macOS, den Job mit niedriger Priorität laufen zu lassen. Ein KeepAlive fehlt bewusst, denn das Skript läuft kurz und beendet sich wieder.

Installation

Die folgenden Befehle legen die Ordner an, machen das Skript ausführbar, prüfen Skript und plist auf Syntaxfehler und laden den Job in deine Benutzersitzung. id -u liefert dabei deine Benutzernummer:

# 1. Ordner anlegen
mkdir -p ~/bin ~/Library/LaunchAgents "$HOME/Library/Application Support/nc-watch"

# 2. Skript und plist wie oben speichern, dann Skript ausführbar machen
chmod 755 ~/bin/nc-watch.sh

# 3. Syntax und plist prüfen
sh -n ~/bin/nc-watch.sh && echo "Skript OK"
plutil -lint ~/Library/LaunchAgents/local.nc-watch.plist

# 4. Job laden (in die Benutzersitzung)
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.nc-watch.plist

# 5. Status prüfen
launchctl print gui/$(id -u)/local.nc-watch | grep -E "state =|run interval|runs =|last exit code"

Bei mir meldete der letzte Befehl run interval = 300 seconds und last exit code = 0. Ab dem nächsten Login lädt macOS den LaunchAgent automatisch.

Testen, ohne Nextcloud zu beenden

Um den Wächter zu testen, musst du Nextcloud nicht beenden. Es reicht, das Skript einen Prozess überwachen zu lassen, den es nicht gibt. Zuerst die Testbenachrichtigung:

sh ~/bin/nc-watch.sh --test

Dann ein simulierter Ausfall in drei Läufen:

NC_WATCH_PROCESS=GibtEsNicht sh ~/bin/nc-watch.sh   # Lauf 1: Zähler = 1, keine Warnung
NC_WATCH_PROCESS=GibtEsNicht sh ~/bin/nc-watch.sh   # Lauf 2: Zähler = 2, Warnung erscheint
NC_WATCH_PROCESS=GibtEsNicht sh ~/bin/nc-watch.sh   # Lauf 3: Zähler = 3, keine neue Warnung

Beim zweiten Lauf erschien die Warnung oben rechts auf dem Bildschirm:

Dass dort „abgestürzt” steht, liegt daran, dass an diesem Morgen tatsächlich Crash-Reports vorlagen. Genau diese Meldung hätte ich am Dienstag gern früher gesehen.

Ein normaler Lauf ohne Variable setzt den Zähler wieder zurück, im Log steht dann „OK: Nextcloud laeuft wieder”. Den Pausenschalter testest du, indem du die Pausendatei anlegst und den simulierten Lauf wiederholst. Der Zähler bleibt dann stehen, eine Meldung kommt nicht. Zusätzlich habe ich die Warnung einmal über einen kurzlebigen zweiten LaunchAgent im echten launchd-Kontext ausgelöst. Im Log stand danach kein Fehler.

Das Log meiner Tests sah so aus:

2026-11-10 09:08:21 Testbenachrichtigung gesendet
2026-11-10 09:08:24 WARNUNG: GibtEsNicht laeuft nicht (Fehlpruefung 1)
2026-11-10 09:08:25 WARNUNG: GibtEsNicht laeuft nicht (Fehlpruefung 2)
2026-11-10 09:08:26 Benachrichtigung gesendet: Nextcloud ist abgestuerzt (neuer Crash-Report). Dateien werden NICHT synchronisiert!
2026-11-10 09:08:29 WARNUNG: GibtEsNicht laeuft nicht (Fehlpruefung 3)
2026-11-10 09:08:29 OK: Nextcloud laeuft wieder (nach 3 Fehlpruefungen)

Noch ein Hinweis, falls du selbst einen Screenshot machst: Nimm nie den ganzen Bildschirm. Bei mir war ein Passwortmanager offen. Ich habe nur die Meldung ausgeschnitten und die Vollbildaufnahme sofort gelöscht.

Im Alltag

Wenn du Nextcloud absichtlich beendest, pausierst du die Warnungen mit dem ersten Befehl. Der zweite schaltet sie wieder ein:

touch "$HOME/Library/Application Support/nc-watch/pause"
rm "$HOME/Library/Application Support/nc-watch/pause"

Log und Lebenszeichen schaust du dir so an. Der Zeitstempel in last_check sollte höchstens etwa fünf Minuten alt sein, solange der Mac läuft:

cat ~/Library/Logs/nc-watch.log
cat "$HOME/Library/Application Support/nc-watch/last_check"

Für ein anderes Prüfintervall passt du StartInterval in der plist an und lädst den Job mit launchctl bootout gui/$(id -u)/local.nc-watch und anschließendem launchctl bootstrap neu. Schwelle und Erinnerungsabstand stehen als Standardwerte oben im Skript.

Deinstallieren geht so:

launchctl bootout gui/$(id -u)/local.nc-watch
rm ~/Library/LaunchAgents/local.nc-watch.plist ~/bin/nc-watch.sh
rm -r "$HOME/Library/Application Support/nc-watch"
rm -f ~/Library/Logs/nc-watch.*

Was der Wächter nicht kann

Er prüft nur, ob der Prozess läuft. Hängt der Client, etwa wegen eines Netzproblems, läuft der Prozess weiter und es kommt keine Warnung. Dafür bleibt der Blick in die Geräteliste auf dem Server.

Schläft der Mac, prüft auch der Wächter nicht. Fokus-Modi wie „Nicht stören” können die Meldung unterdrücken, dann steht sie nur in der Mitteilungszentrale. Banner verschwinden außerdem nach ein paar Sekunden. Wer das nicht will, stellt in den Systemeinstellungen unter Mitteilungen den Stil für den Absender von „Banner” auf „Hinweise”. Die bleiben stehen, bis man sie schließt. Hat macOS Mitteilungen für den Absender ganz abgeschaltet, erscheint gar nichts, und das Skript kann das nicht erkennen. Deshalb nach der Einrichtung einmal den Test mit --test laufen lassen. Wie sich Fokus-Modi und abgeschaltete Mitteilungen genau auswirken, habe ich selbst nicht getestet.

Ein fehlerhaftes Update verhindert der Wächter natürlich auch nicht. Er merkt nur, dass der Client danach nicht mehr läuft. Immerhin innerhalb weniger Minuten statt nach Tagen.

Was ich mitnehme

Die schnellste Spur waren die Crash-Reports und das Systemlog. Damit war die Ursache in wenigen Minuten eingegrenzt. Das Journal habe ich nur als Kopie angefasst, und den naheliegenden Rat, es einfach zu löschen, habe ich nicht befolgt. Die Ursache zu klären hat mir einen kompletten Neuabgleich erspart.

Automatische Updates bleiben bequem. Bei einem Werkzeug, an dem alle meine Dateien hängen, lasse ich neue Versionen künftig aber lieber ein paar Tage liegen. Dieser Fehler stand schon einen Tag nach dem Release in den Issues.

Kurz gesagt: Verlass dich nicht darauf, dass es schon synchronisiert. Lass dir sagen, wenn es das nicht mehr tut.

Links

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert