Seit dem 3. September 2026 läuft nur noch die Fassung im Cluster
(PostgreSQL). Der alte systemd-Dienst ist angehalten und abgeschaltet — siehe
Umschaltung.
| Adresse | |
|---|---|
| https://31-220-73-209.sslip.io/zeit/ | die gewohnte, auf allen Geräten eingerichtete |
| https://zeit.31-220-73-209.sslip.io/zeit/ | derselbe Dienst über den Cluster-Hostnamen |
Beide führen an dieselbe Stelle. Der Pfad /zeit ist mit Absicht geblieben:
so mussten Lesezeichen, Startsymbole und installierte Apps nicht angefasst
werden.
| Testreihe gegen PostgreSQL | 86 Tests grün |
| Übernommene Zeilen | 0 (die App war noch leer) |
| Datenbank im Cluster | timetracker in der gemeinsamen Instanz, eigene Rolle |
Das Verfahren im Einzelnen: Migration SQLite → PostgreSQL.
Die App ist beim Umzug leer übernommen worden: null Konten, null
Buchungen. Es gibt keinen versteckten Administrator und kein Vorgabekonto.
Wer als Erster ein Konto anlegt, ist einfach der erste Benutzer.
Die Anmeldeseite fragt nach einem Registrier-Code. Er verhindert, dass
sich Fremde ein Konto anlegen — die Adresse ist ja öffentlich erreichbar.
Der Code ist kein persönlicher Einladungscode und läuft nicht ab. Er steht
als Umgebungsvariable im Cluster:
ssh openclaw-server "KUBECONFIG=/root/.kube/config kubectl -n apps get secret timetracker-zugang -o jsonpath='{.data.ZEIT_REGISTRIER_CODE}' | base64 -d; echo"
Derselbe Wert liegt zusätzlich in /opt/timetracker/.env (der Datei des alten
Dienstes) — beide sind identisch, damit beim Umschalten nichts umzustellen war.
Ändern lässt er sich so — auf dem Server, weil dort kubectl liegt:
ssh openclaw-server
export KUBECONFIG=/root/.kube/config
# Den Sitzungsschluessel unveraendert uebernehmen und nur den Code austauschen
ALT=$(kubectl -n apps get secret timetracker-zugang -o jsonpath='{.data.ZEIT_SECRET}' | base64 -d)
kubectl -n apps create secret generic timetracker-zugang --dry-run=client -o yaml --from-literal=ZEIT_SECRET="$ALT" --from-literal=ZEIT_REGISTRIER_CODE="NEUER-CODE" | kubectl apply -f -
kubectl -n apps rollout restart deploy/timetracker
ZEIT_SECRET muss dabei unverändert mitgegeben werden: es ist der
HMAC-Schlüssel der Sitzungs-Cookies. Wird er neu gewürfelt, sind alle
angemeldeten Geräte auf einen Schlag abgemeldet.
Die E-Mail wird nur als Anmeldename benutzt — es wird nichts verschickt.
Die Sitzung hält 180 Tage, das Handy fragt also nicht ständig nach.
Unter Einstellungen:
| Feld | Bedeutung | Vorgabe |
|---|---|---|
| Wochenstunden (Soll) | die eigene vertragliche Arbeitszeit | 35,0 |
| Gemeinsames Ziel | Wochenstunden beider Partner zusammen | 70,0 |
| Arbeitstage | welche Wochentage zählen | Mo–Fr |
| Zielabschlusstag | an welchem Tag die Woche abgerechnet wird | Freitag |
| Pausenpflicht | ArbZG §4 prüfen (30 min ab 6 h, 45 min ab 9 h) | an |
| Saldo-Startdatum | ab wann der Überstundensaldo zählt | leer = ab der ersten Buchung |
Zwei Konten lassen sich zu einem Paar verbinden. Erst dann gibt es das
gemeinsame Wochenziel und die gemeinsame Auswertung.
Der Ablauf, und die Reihenfolge ist wichtig:
Danach sehen beide dieselbe Wochenübersicht.
Zu wissen:
Die beiden Codes sind also zwei verschiedene Dinge:
| Registrier-Code | Einladungscode | |
|---|---|---|
| wofür | überhaupt ein Konto anlegen dürfen | zwei Konten zu einem Paar verbinden |
| woher | aus dem Cluster-Secret (Schritt 1) | in der App, von einem bestehenden Konto |
| Gültigkeit | unbegrenzt, für alle gleich | 14 Tage, einmalig, persönlich |
| liegt in | Umgebungsvariable ZEIT_REGISTRIER_CODE |
Tabelle partner_einladung |
Sobald das erste Konto da ist, sollte es im Offsite-Backup auftauchen:
ssh openclaw-server "head -3 /opt/homelab/.sicherung/zeiterfassung-klartext.csv"
Diese CSV entsteht täglich um 3:15 aus PostgreSQL und liegt anschließend im
privaten GitHub-Repo — lesbar auch ohne diese App. Siehe
Sicherung des Clusters.
| Symptom | Ursache | Abhilfe |
|---|---|---|
| „Der Registrier-Code stimmt nicht" | falscher Code, oder er wurde geändert | Code neu auslesen (Schritt 1) |
| nach mehreren Fehlversuchen keine Antwort mehr | eingebaute Bremse gegen Durchprobieren, je Absender-IP | ein paar Minuten warten |
| alle Geräte plötzlich abgemeldet | ZEIT_SECRET hat sich geändert |
neu anmelden; künftig beim Ändern mitgeben |
| „Das Passwort braucht mindestens 8 Zeichen" | genau das | längeres Passwort |
100 KB Datenbank, sieben Tabellen, keine Volltextsuche, keine externen
Schnittstellen. Am kleinsten Fall wurde das gesamte Muster einmal
durchgespielt: Dockerfile, Registry, Deployment, Ingress, Datenbankmigration,
Rückfallweg. Was dabei gelernt wurde, machte die nächsten drei Apps zur
Fließbandarbeit — die Fallen 1 bis 6 stammen alle von
hier.
ZEIT_SECRET wird aus der .env des laufenden Dienstes übernommen, nicht
neu gewürfelt. Sonst wären beim Umschalten alle Sitzungen auf allen Geräten
ungültig und jeder müsste sich neu anmelden.
benutzer.partner_id zeigt auf dieselbe Tabelle. Person 1 kann nicht auf
Person 2 zeigen, solange es Person 2 nicht gibt — deshalb lässt der
Übernahmelauf die Spalte zunächst leer und trägt sie ganz am Ende nach. Das
macht er generisch: er erkennt Selbstbezüge selbst aus pg_constraint.
Der echte Bestand war leer. Um die Übernahme trotzdem zu beweisen, lief sie
einmal mit einem Prüfbestand im Schnappschuss (nie in der Betriebsdatenbank):
zwei verbundene Konten, fünf Buchungen darunter eine laufende Stoppuhr,
Vorlagen mit Blöcken, Tagesart, offene Einladung. Alle Zeilenzahlen und die
Summe der Arbeitsminuten (1480) stimmten auf beiden Seiten. Danach wurde der
Prüfbestand wieder verworfen.
# Abbild bauen und Etikett in die Manifeste eintragen
ssh openclaw-server 'cd /opt/homelab && ./images/bauen.sh timetracker --setzen'
# Testreihe im Cluster gegen PostgreSQL
ssh openclaw-server 'cd /opt/homelab && ./migration/ausfuehren.sh timetracker test'
# Daten übernehmen (Ziel vorher leeren)
ssh openclaw-server 'cd /opt/homelab && ./migration/ausfuehren.sh timetracker uebernahme --ueberschreiben'
# Protokoll der laufenden Fassung
ssh openclaw-server 'KUBECONFIG=/root/.kube/config \
kubectl -n apps logs -l app.kubernetes.io/name=timetracker --tail=50'
Siehe auch: Arbeitsablauf · Stand