jbergner 2d384046dd
All checks were successful
release-tag / release-image (push) Successful in 2m1s
gitignore-fix
2026-08-14 17:51:57 +02:00
2026-08-14 17:44:49 +02:00
2026-08-14 17:51:57 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:51:57 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:49:31 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:42:54 +02:00
2026-08-14 17:42:54 +02:00

Pocketwatch Go

Ein schlanker, self-hosted Zeiterfasser in Go funktional an winnicodes/pocketwatch angelehnt, aber ohne React, Node.js, PHP oder Nginx im Anwendungs-Stack.

Die Anwendung besteht aus einem Go-Binary, eingebettetem HTML/CSS/Vanilla-JavaScript und SQLite über modernc.org/sqlite (pure Go, kein CGO notwendig).

Enthalten

  • Start-/Stop-Timer mit Live-Anzeige
  • Laufender Timer überlebt Reloads und Gerätewechsel
  • Kunde + Tätigkeit, Kunden-Autocomplete nach letzter Nutzung
  • Tages- und Wochensumme
  • Verlauf gruppiert nach Tagen
  • Suche über Kunde und Tätigkeit
  • Zeitraumfilter: Tag, Woche, Monat, Jahr, alle
  • Zeitraum vor/zurück schalten
  • Einträge nachtragen, bearbeiten und löschen
  • Rundung 160 Minuten, normal oder immer aufwärts
  • CSV-Export (Semikolon + UTF-8 BOM für Excel)
  • PDF-Export ohne PDF-Framework
  • Export wahlweise für aktuelle Ansicht oder freien Datumsbereich, auf-/absteigend und kompakt
  • Responsive Desktop-/Mobile-Oberfläche
  • Mehrbenutzerbetrieb mit strikt getrennten Daten
  • Admin-/Benutzerrollen
  • Benutzer anlegen, deaktivieren und Passwörter zurücksetzen
  • Eigenes Passwort ändern
  • Keine öffentliche Registrierung nach der Ersteinrichtung
  • Sessions in SQLite, HttpOnly-Cookie, CSRF-Token
  • Login-Rate-Limit
  • CSP und weitere Security-Header
  • SQLite WAL, Foreign Keys, Busy Timeout
  • Healthcheck unter /healthz
  • Docker/Compose, unprivilegierter Runtime-Benutzer, alle Linux-Capabilities entfernt

Abhängigkeiten

Zur Laufzeit gibt es nur eine externe Go-Abhängigkeit:

modernc.org/sqlite

Alles andere verwendet die Go-Standardbibliothek. Das Frontend hat keine npm-/Node-Abhängigkeiten und keinen separaten Build-Schritt.

Schnellstart mit Docker Compose

docker compose up -d --build

Danach:

http://localhost:8080

Beim ersten Aufruf erscheint die Ersteinrichtung. Der erste Account wird Administrator.

Die Daten liegen im Docker-Volume pocketwatch-data in /data/pocketwatch.db. Das Image enthält außerdem einen Docker-Healthcheck gegen /healthz.

Bei einem Host-Bind-Mount statt eines Named Volumes muss das Zielverzeichnis für UID/GID 10001 schreibbar sein.

Lokal ohne Docker

Voraussetzungen:

  • Go 1.26+

Dann:

go mod tidy
DATA_DIR=./data APP_ADDR=:8080 go run ./cmd/pocketwatch

Oder:

make run

Konfiguration

Variable Default Bedeutung
APP_ADDR :8080 Listen-Adresse des HTTP-Servers
DATA_DIR /data Verzeichnis für pocketwatch.db
COOKIE_SECURE false Auf true setzen, wenn die App ausschließlich über HTTPS erreichbar ist

Hinter Reverse Proxy / HTTPS

Wenn z. B. Caddy, Traefik oder nginx TLS terminiert:

environment:
  COOKIE_SECURE: "true"

Die App setzt selbst keine CORS-Header. API und UI sind als Same-Origin-Anwendung gedacht.

Datenmodell

SQLite enthält vier Kernbereiche:

  • users Accounts, Rollen, Aktivstatus
  • sessions gehashte Session-Tokens + CSRF-Token
  • entries Zeiteinträge, immer mit user_id
  • user_settings persönliche Rundungs-, Export- und Anzeigeeinstellungen

Ein partieller Unique-Index stellt sicher, dass pro Benutzer höchstens ein laufender Timer existiert.

Zeitpunkte werden als Unix-Millisekunden gespeichert. Die Web-Oberfläche verwendet die lokale Browser-Zeitzone; Exporte verwenden die persönliche IANA-Zeitzone, z. B. Europe/Berlin.

Sicherheit

Passwörter

Passwörter werden mit PBKDF2-HMAC-SHA256, zufälligem Salt und 310.000 Iterationen gespeichert. Die Implementierung nutzt crypto/pbkdf2, crypto/sha256 und crypto/rand aus der Go-Standardbibliothek von Go 1.26.

Sessions

  • 256-Bit zufällige Session-Tokens
  • nur SHA-256-Digest des Session-Tokens in SQLite
  • HttpOnly-Cookie
  • SameSite=Lax
  • optional Secure
  • serverseitiges Ablaufdatum
  • Sessions werden beim Deaktivieren eines Benutzers oder Passwort-Reset invalidiert

CSRF

Schreibende API-Aufrufe benötigen zusätzlich ein zufälliges, sitzungsgebundenes X-CSRF-Token.

Mandantentrennung

Jede SQL-Operation auf Zeiten enthält die user_id aus der authentifizierten Session. IDs aus einem anderen Benutzerkonto reichen daher nicht aus, um fremde Einträge zu lesen oder zu verändern.

Backup

Wegen WAL sollte die Datenbank nicht blind während Schreibzugriffen als einzelne Datei kopiert werden. Der einfachste konsistente Weg bei Docker Compose:

docker compose stop pocketwatch

docker run --rm \
  -v pocketwatch-go_pocketwatch-data:/data:ro \
  -v "$PWD:/backup" \
  alpine:3.22 \
  tar czf /backup/pocketwatch-backup.tgz -C /data .

docker compose start pocketwatch

Der konkrete Volume-Name kann je nach Compose-Projektname abweichen (docker volume ls).

Projektstruktur

pocketwatch-go/
├── cmd/pocketwatch/main.go
├── internal/app/
│   ├── auth.go
│   ├── db.go
│   ├── export.go
│   ├── server.go
│   ├── *_test.go
│   └── web/
│       ├── index.html
│       ├── login.html
│       ├── app.css
│       ├── app.js
│       └── login.js
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── go.mod

Tests

go test ./...

Enthalten sind u. a. Tests für PBKDF2, Rundungslogik und den minimalen PDF-Writer.

Bewusste Unterschiede zum ursprünglichen Pocketwatch

Diese Implementierung übernimmt das Produktkonzept und die wichtigsten Bedienabläufe, ist aber technisch ein Neuaufbau:

  • SQLite statt JSON-Dateien
  • Go statt PHP
  • Vanilla JS statt React/Vite/Tailwind
  • kein Node.js im Build oder Betrieb
  • Login und Mehrbenutzerbetrieb
  • Adminverwaltung
  • serverseitige Datenisolation
  • CSRF- und Session-Schutz
  • Healthcheck und Security-Header

Die Oberfläche orientiert sich am dunklen, kompakten Amber-Design des Originals, ist aber kein 1:1 kopierter Frontend-Quellcode. Sie ist derzeit bewusst deutschsprachig; das Datenmodell hält die Spracheinstellung bereits für eine spätere vollständige Lokalisierung vor.

Inspiration

Inspiriert von winnicodes/pocketwatch, das als minimalistischer self-hosted Zeiterfasser unter MIT veröffentlicht ist.

Description
No description provided
Readme MIT 112 KiB
Languages
Go 50.7%
JavaScript 22%
CSS 14.1%
HTML 12.4%
Dockerfile 0.6%
Other 0.2%