loki 77da7b74bb
Docker Image / test (push) Canceled after 0s
Docker Image / image (push) Canceled after 0s
Abmelde-Icon neben Hell/Dunkel im App-Kopf
2026-10-11 19:47:48 +02:00
2026-10-04 19:59:44 +02:00
2026-10-05 20:42:44 +02:00

MyTime

MyTime ist eine persönliche Web-App zur Urlaubsplanung und Überstunden-Erfassung. Sie läuft als einzelner Docker-Dienst mit SQLite und ist für den Zugriff im Heimnetz gedacht. Es gibt keine Anmeldung: Port niemals ins öffentliche Internet weiterleiten.

Schnellstart (fertiges Image)

Voraussetzungen: Docker Engine, Docker Compose Plugin und Zugriff auf die private Gitea-Container-Registry. Einmalig anmelden; Docker fragt interaktiv nach dem Token mit Paket-Leserecht:

docker login gitea.lokcal.de -u <gitea-benutzer>

Dann im geklonten Repository:

cp .env.example .env
docker compose pull
docker compose up -d

compose.yaml verwendet standardmäßig das fertige Image gitea.lokcal.de/loki/mytime:latest und baut nichts lokal. Der Host-Port kommt aus .env (APP_PORT=3100 in der Vorlage); erreichbar ist die App dann unter http://<server-ip>:3100. Der Status lässt sich mit curl http://127.0.0.1:3100/api/health prüfen.

Lokal entwickeln (Image selbst bauen)

Für Änderungen am Quellcode die separate Dev-Datei verwenden:

cp .env.example .env
docker compose -f compose.dev.yaml up --build -d

compose.dev.yaml baut aus dem lokalen Verzeichnis. Daten und Sicherungen bleiben in beiden Varianten in ./data und ./backups erhalten.

Beim ersten Start in Einstellungen → Arbeitszeit die Sollstunden für alle sieben Wochentage eintragen. Freie Tage erhalten 0; der Knopf „40-h-Woche übernehmen“ füllt Mo–Fr mit 8 Stunden. Danach „Speichern“.

Erste Einrichtung

Öffne Einstellungen → Arbeitszeit. Trage Sollstunden für Montag bis Sonntag ein; 0 bedeutet arbeitsfrei. „40-h-Woche übernehmen“ füllt Montag bis Freitag mit je 8 Stunden und Samstag/Sonntag mit 0. Einzelne Tage kannst du anschließend anpassen. Die angezeigte Wochensumme dient zur Kontrolle. Speichern.

Unter Einstellungen → Urlaub & Bundesland stellst du den Jahresanspruch, den Übertrag und das Bundesland ein. Standard-Bundesland ist Hamburg. Änderungen am Urlaubsanspruch gelten für das im Hauptbereich ausgewählte Jahr; das Bundesland gilt app-weit.

Bedienung

Die Hauptnavigation enthält Übersicht, Zeiterfassung, Feiertage, Verlauf und Einstellungen. Der Hell-/Dunkelmodus wird über das Sonne-/Mond-Symbol im oberen MyTime-Balken umgeschaltet und pro Gerät gespeichert.

Arbeitszeit erfassen

  1. In Zeiterfassung das Datum, Arbeitsbeginn, Arbeitsende und Pause eintragen. Die Pause ist standardmäßig 30 Minuten; 0 ist erlaubt.
  2. Speichern. Positive Zeit über dem Soll des jeweiligen Wochentags wird automatisch gutgeschrieben. Minderstunden werden nicht automatisch abgezogen.
  3. Manuelle Überstunden oder Abbau buchst du in Übersicht. Abbau ist nur möglich, wenn der Überstundenstand dadurch nicht negativ wird; eine Notiz ist optional.
  4. Krankheitstage meldest du ebenfalls in Zeiterfassung: Datum wählen, ganzen oder halben Tag sowie optional eine Notiz eintragen und „Krank melden“. Nur geplante Arbeitstage (keine Wochenenden/Feiertage) sind meldbar. Eine bestehende Meldung lässt sich dort auch wieder löschen.

Urlaub buchen und stornieren

  • In Übersicht Start- und Enddatum sowie ganzen oder halben Tag wählen und buchen. Wochenenden, Tage ohne Sollstunden und Feiertage im ausgewählten Bundesland zählen nicht als Urlaubstage.
  • Auf dem Desktop kannst du den Zeitraum in der Jahrestabelle ziehen. Auf einem Touch-Gerät einen Kalendertag etwa eine halbe Sekunde gedrückt halten, loslassen und den Endtag antippen. Ein normaler Tipp öffnet das Tagesfenster.
  • Das Tagesfenster zeigt Buchungen und Details des Tages. Dort kannst du Urlaub buchen oder eine bestehende Buchung mit Sicherheitsabfrage stornieren.
  • Unter Verlauf → Urlaubsbuchungen findest du Buchungen aller Jahre. Häkchen auswählen und mehrere Buchungen gemeinsam stornieren; der CSV-Export lädt die Liste herunter.
  • Stornierte Einträge landen im Papierkorb. Dort kannst du sie wiederherstellen oder nach einer zweiten Bestätigung endgültig löschen.
  • Die Buchung ist auf den verfügbaren Rest beschränkt: Jahresanspruch + Übertrag − bereits verbrauchte Urlaubstage. Bei einem Zeitraum über den Jahreswechsel wird jedes Jahr einzeln geprüft.
  • Der Übertrag eines Jahres läuft am 31.03. des Folgejahres ab. Kurz vorher und danach zeigt die Übersicht einen Hinweis; Salden und Buchungen bleiben unverändert.

Hauptbereiche

  • Übersicht: Salden, Buchungsformulare und Jahrestabelle. Die Jahrestabelle ist gedreht aufgebaut: Tage als Zeilen, Monate als Spalten. Urlaub ist grün, Krankheit violett, Feiertage rot; die Legende erklärt die Markierungen. Beim Ziehen bleibt der gewählte Zeitraum markiert und kann im Popup direkt gebucht werden.
  • Feiertage → Feiertage: Feiertage des ausgewählten Bundeslands, monatsweise aufgelistet. Ein Feiertag lässt sich anklicken, um zu seinem Datum in der Übersicht zu springen.
  • Feiertage → Brückentage: gespeicherte Empfehlungen von ferien-feiertage.de, nach Datum sortiert und nach Monaten gruppiert. Monatsübergreifende Tage sind in beiden Mini-Kalendern markiert. „Übernehmen“ bucht eine Empfehlung nach Bestätigung direkt; bereits gebuchte oder nicht mehr verfügbare Vorschläge werden ausgeblendet.
  • Verlauf → Jahresübersicht: zwölf Mini-Kalender und Monatssummen. Der Jahresumschalter gilt nur hier und springt beim Verlassen des Tabs wieder auf das App-Jahr zurück. Export als PDF.
  • Verlauf → Überstundenverlauf: automatische und manuelle Buchungen aller Jahre, als CSV exportierbar.
  • Verlauf → Urlaubsbuchungen: Monatsverteilung als Balkendiagramm mit Gesamt- und Durchschnittswerten, darunter die Buchungen aller Jahre mit Mehrfachauswahl zum Stornieren; als CSV exportierbar.
  • Verlauf → Jahresvergleich: Anspruch, Verbrauch, Resturlaub, Krankheitstage sowie automatische, manuelle und gesamte Überstunden für Vorjahr und App-Jahr.
  • Einstellungen → Sicherung: Sicherungsstände ansehen und über „Jetzt sichern“ sofort eine Sicherung erstellen.

Installation auf einem Server

MyTime kann auf dem Server selbst gebaut werden oder als fertiges Image aus der Gitea-Registry laufen.

Empfohlen: fertiges Image

Voraussetzungen:

  • Docker Engine und Docker Compose Plugin
  • Git-Zugriff auf das private Gitea-Repository
  • Leseberechtigung für gitea.lokcal.de/loki/mytime in der Container-Registry

Einmalig am Server an der Registry anmelden (Token mit Paket-Leserecht verwenden; nicht in eine Shell-History oder Datei schreiben):

docker login gitea.lokcal.de -u <gitea-benutzer>

Repository klonen, Port konfigurieren und Image starten:

git clone https://gitea.lokcal.de/loki/mytime.git
cd mytime
cp .env.example .env
# APP_PORT bei Bedarf in .env anpassen, z. B. auf 3100
docker compose pull
docker compose up -d
curl http://127.0.0.1:3100/api/health

Auf einem neuen Server ohne Portkonflikt kann APP_PORT in .env auf 3000 gesetzt werden. Datenbank und Backups liegen in ./data und ./backups neben der Compose-Datei.

Aktualisieren:

cd mytime
git pull
docker compose pull
docker compose up -d --force-recreate
curl http://127.0.0.1:3100/api/health

Image-Build in Gitea Actions

.gitea/workflows/docker.yml führt bei Pushes auf main Tests, Skript-Tests, TypeScript-Prüfung und Produktions-Build aus. Nur wenn alles erfolgreich ist, wird das Image als latest und mit Commit-SHA in die Registry gepusht. Ein Gitea-Actions-Runner und die Container-Registry müssen aktiviert sein. Der Workflow verwendet ACTION_BOT als Registry-Token; alternativ können REGISTRY_USER und REGISTRY_TOKEN als Repository-Secrets gesetzt werden. Der Token braucht Paket-Schreibrecht.

Zum lokalen Bauen und Entwickeln siehe compose.dev.yaml im Schnellstart.

Brückentage-Daten aktualisieren

Die App enthält Scraping-Daten für alle 16 Bundesländer. Sie werden beim normalen Betrieb nicht aus dem Internet geladen; die App funktioniert damit offline. Auf einem Rechner mit Internetzugang im Repository ausführen:

npm ci
npm run scrape:bridge
npm test -- --run
npm run test:scripts
npx tsc --noEmit

Standardmäßig lädt das Skript das laufende und das nächste Jahr und erhält bereits gespeicherte Jahre. Optional lassen sich Jahre und Bundesländer angeben:

npm run scrape:bridge -- --years=2028,2029,2030 --states=HH,BY

Danach Änderungen committen und nach Gitea pushen. Die Action baut ein aktualisiertes Image. Für nicht gespeicherte Jahre zeigt die App einen Hinweis statt lokal berechneter Empfehlungen.

Datensicherung und Wiederherstellung

Die App erstellt wöchentlich automatisch eine konsistente SQLite-Sicherung und behält höchstens fünf Dateien in ./backups. In Einstellungen → Sicherung kann zusätzlich jederzeit eine Sicherung manuell erstellt und der Sicherungsstand kontrolliert werden. Nicht mehr benötigte Sicherungen lassen sich dort nach einer Bestätigung einzeln löschen.

Sicherung zurückspielen

  1. Im Verzeichnis mit der Compose-Datei den Dienst stoppen:

    docker compose stop app
    

    Bei lokaler Entwicklung stattdessen docker compose -f compose.dev.yaml stop app verwenden.

  2. WAL-Seitendateien der aktuellen Datenbank entfernen, dann Sicherung einspielen:

    rm -f data/app.sqlite3-wal data/app.sqlite3-shm
    cp backups/<backup-datei>.sqlite3 data/app.sqlite3
    
  3. Dienst wieder starten:

    docker compose start app
    

    Bei lokaler Entwicklung stattdessen docker compose -f compose.dev.yaml start app verwenden.

  4. http://<server-ip>:<APP_PORT>/api/health prüfen und anschließend Salden und Buchungen in der App kontrollieren.

Wichtig: Die App verwendet SQLite im WAL-Modus. Deshalb vor dem Restore immer stoppen und die alten -wal- und -shm-Dateien löschen; sonst können alte Transaktionen mit der eingespielten Sicherung vermischt werden.

Sicherheit und Daten

  • MyTime hat standardmäßig keine Anmeldung (AUTH_MODE=off). Nur im Heimnetz betreiben, keinen Router-Port ins Internet weiterleiten und den Port in der Firewall auf das LAN begrenzen. Wer die optionale Anmeldung einschaltet (siehe unten), betreibt den Dienst trotzdem nur im Heimnetz.
  • Der Container startet und läuft als Non-Root-User node (UID:GID 1000:1000); compose.yaml und compose.dev.yaml erzwingen das zusätzlich per user: "1000:1000". Einmalige Migration bestehender Daten: sudo chown -R 1000:1000 data backups.
  • Datenbank: ./data/app.sqlite3; Sicherungen: ./backups/.
  • Daten und Sicherungen sind in .gitignore ausgeschlossen und werden nicht mit dem Quellcode veröffentlicht.

Anmeldung (optional)

Standardmäßig ist die Anmeldung ausgeschaltet und die App verhält sich wie bisher. Mit AUTH_MODE=password oder AUTH_MODE=sso stehen alle Seiten und API-Routen nur noch nach Anmeldung zur Verfügung (Ausnahmen: /api/auth/* und der Docker-Healthcheck /api/health).

  1. .env.example nach .env kopieren und anpassen:

    Variable Bedeutung
    AUTH_MODE off (Standard), password oder sso.
    BETTER_AUTH_SECRET Pflicht bei aktiver Anmeldung, mindestens 32 Zeichen (openssl rand -base64 32).
    BETTER_AUTH_URL Pflicht bei aktiver Anmeldung, z. B. http://<server-ip>:3100.
    OIDC_DISCOVERY_URL Nur sso: Discovery-URL des Anbieters, z. B. https://auth.lan/application/o/mytime/.well-known/openid-configuration.
    OIDC_CLIENT_ID / OIDC_CLIENT_SECRET Nur sso: Client-Daten aus dem Anbieter (Authentik, Keycloak, Zitadel …).
  2. Dienst neu starten (docker compose up -d bzw. docker compose -f compose.dev.yaml up -d).

  3. Erste Anmeldung (password): Die leere Datenbank verlangt beim ersten Aufruf „Erste Anmeldung" – Name, E-Mail und Passwort anlegen. Danach ist die Registrierung geschlossen; es bleibt bei diesem einen Benutzer.

  4. SSO (sso): Auf der Anmeldeseite erscheint „Mit SSO anmelden". Die App leitet zum OIDC-Anbieter weiter und legt beim ersten Login das Benutzerkonto automatisch an.

Hinweise:

  • HTTP im Heimnetz genügt; sichere Cookies sind bewusst nicht erzwungen (useSecureCookies aus). Trotzdem gilt: kein Internet-Expose, LAN-Begrenzung bleibt.
  • Abmelden steht im Kopf neben dem Theme-Schalter. Eine abgelaufene Sitzung führt zurück zur Anmeldung statt zur Fehlerwand.
  • Sitzungs-, Benutzer- und Kontodaten liegen als Tabellen session, user, account, verification in derselben app.sqlite3 und werden mitgesichert.

Entwicklung und Tests

Voraussetzungen: Node.js 22 und npm.

npm ci
npm run dev
npm test -- --run
npm run test:scripts
npx tsc --noEmit
npm run build
S
Description
Persönliche Urlaubsplanung und Überstunden-Erfassung (MyTime)
Readme
1.2 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 95.1%
CSS 3.4%
JavaScript 1.2%
HTML 0.2%
Dockerfile 0.1%