b768ff29c9f410b74f35c231b870c1a7a4d54996
tiptoi-sync
Web-App zum Suchen, Herunterladen und Synchronisieren von tiptoi-Büchern auf den tiptoi-Stift. Läuft als selbst-gehosteter Server (z. B. auf einem Raspberry Pi oder Rock Pi) und ist über den Browser erreichbar – auch vom Smartphone aus.
Features
- Barcode-Scanner – Buch per Kamera-Scan (QR/EAN) suchen
- Freitextsuche – nach Titel oder Artikelnummer suchen
- Download – GME-Datei + Cover automatisch von Ravensburger herunterladen
- Bibliothek – alle heruntergeladenen Bücher in der Übersicht
- Stift-Sync – tiptoi-Stift per USB anschließen, Bücher direkt aus der App auf den Stift kopieren
- Stift-Status – zeigt welche Bücher bereits auf dem Stift sind und welche fehlen
- PWA – kann auf dem Homescreen installiert werden (Android/iOS)
Architektur
Browser / PWA
│ HTTP
▼
┌─────────────────────────────────┐
│ Bun + Elysia (Port 3000) │
│ │
│ /api/search → Ravensburger│
│ /api/download → GME + Cover │
│ /api/downloads → Bibliothek │
│ /api/pen/detect → lsblk │
│ /api/pen/files → Stift-Inhalt│
│ /api/pen/sync → Dateien kop.│
│ /api/pen/eject → umount │
│ │
│ /* → SolidJS SPA │
└─────────────────────────────────┘
│ │
/data/ USB
(GME-Dateien) tiptoi-Stift
Voraussetzungen
- Bun ≥ 1.0
- Node.js (nur für den Client-Build, via Bun verfügbar)
- Docker + Docker Compose (für Deployment)
Entwicklung (lokal)
git clone <repo>
cd tiptoi-sync
bun install
# Server + Client parallel starten
bun run dev
- Client:
http://localhost:5173(Vite Dev Server mit HMR) - Server:
http://localhost:3000
Der Vite Dev Server proxied /api/* automatisch zum Server.
Deployment
Docker (empfohlen)
# Image bauen
docker compose build
# Starten
docker compose up -d
# Logs
docker compose logs -f
Die App ist danach unter http://<server-ip>:3000 erreichbar.
Heruntergeladene GME-Dateien werden im Verzeichnis ./data/ gespeichert (persistentes Volume).
Arm64 (Rock Pi, Raspberry Pi) – Cross-Build von x86
Auf der x86-Entwicklungsmaschine (mit Podman):
# QEMU einmalig installieren
sudo apt install qemu-user-static # Debian/Ubuntu
sudo dnf install qemu-user-static # Fedora
# Image bauen und direkt auf den Server laden
podman build --platform linux/arm64 -t tiptoi-sync:latest . \
&& podman save --format docker-archive tiptoi-sync:latest \
| ssh user@rockpi 'docker load'
# docker-compose.yml auf den Server kopieren und starten
scp docker-compose.yml user@rockpi:~/tiptoi-sync/
ssh user@rockpi 'mkdir -p ~/tiptoi-sync/data && cd ~/tiptoi-sync && docker compose up -d'
USB-Automount (Headless Debian/Armbian)
Damit der tiptoi-Stift beim Einstecken automatisch gemountet wird (kein Desktop nötig):
1. Mount-Scripts installieren
sudo tee /usr/local/bin/usb-mount.sh > /dev/null << 'EOF'
#!/bin/bash
set -euo pipefail
DEV="$1"
DEVPATH="/dev/$DEV"
FSTYPE=$(blkid -o value -s TYPE "$DEVPATH" 2>/dev/null || true)
[ -z "$FSTYPE" ] && exit 0
LABEL=$(blkid -o value -s LABEL "$DEVPATH" 2>/dev/null | tr ' /\\' '___' || true)
UUID=$(blkid -o value -s UUID "$DEVPATH" 2>/dev/null || echo "$DEV")
NAME="${LABEL:-$UUID}"
MOUNTPOINT="/media/$NAME"
mkdir -p "$MOUNTPOINT"
mount "$DEVPATH" "$MOUNTPOINT"
logger -t usb-mount "Gemountet: $DEVPATH → $MOUNTPOINT ($FSTYPE)"
EOF
sudo chmod +x /usr/local/bin/usb-mount.sh
sudo tee /usr/local/bin/usb-umount.sh > /dev/null << 'EOF'
#!/bin/bash
set -euo pipefail
DEV="$1"
MOUNTPOINT=$(findmnt -n -o TARGET "/dev/$DEV" 2>/dev/null || true)
if [ -n "$MOUNTPOINT" ]; then
umount "$MOUNTPOINT"
rmdir "$MOUNTPOINT" 2>/dev/null || true
logger -t usb-mount "Ausgeworfen: /dev/$DEV ($MOUNTPOINT)"
fi
EOF
sudo chmod +x /usr/local/bin/usb-umount.sh
2. Systemd-Service-Template
sudo tee /etc/systemd/system/usb-mount@.service > /dev/null << 'EOF'
[Unit]
Description=USB-Mount %i
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/usb-mount.sh %i
ExecStop=/usr/local/bin/usb-umount.sh %i
EOF
3. udev-Regel
sudo tee /etc/udev/rules.d/99-usb-mount.rules > /dev/null << 'EOF'
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_TYPE}!="", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-mount@%k.service"
ACTION=="remove", SUBSYSTEMS=="usb", SUBSYSTEM=="block", \
TAG+="systemd", RUN+="/bin/systemctl stop --no-block usb-mount@%k.service"
EOF
4. Aktivieren
sudo udevadm control --reload-rules
sudo systemctl daemon-reload
Testen
# Stift einstecken, dann:
journalctl -t usb-mount -f
# Manuell testen (Gerätname aus lsblk)
sudo systemctl start usb-mount@sdb.service
Umgebungsvariablen
| Variable | Standard | Beschreibung |
|---|---|---|
DOWNLOAD_DIR |
./downloads |
Verzeichnis für GME-Dateien und Cover |
STATIC_DIR |
(leer) | Pfad zum gebauten Client (client/dist). Wenn gesetzt, liefert der Server die SPA aus. |
Projektstruktur
tiptoi-sync/
├── client/ # SolidJS PWA (Vite)
│ └── src/
│ ├── pages/
│ │ ├── ScanPage.tsx # Barcode-Scanner + Suche
│ │ └── DownloadsPage.tsx # Bibliothek + Stift-Sync
│ └── components/
│ └── QrScanner.tsx
├── server/ # Bun + Elysia API
│ └── src/
│ └── index.ts
├── Dockerfile
├── docker-compose.yml
└── 99-usb-mount.rules # udev-Regel (Referenz)
Hinweise
- Der Stift muss vom Host-System gemountet werden (udev-Automount, s. o.). Der Docker-Container greift über
rshared-Volume-Mount darauf zu. - Android-Direktsync (Stift per USB-OTG ans Handy) ist technisch nicht möglich – Browser haben keinen Zugriff auf USB-Mass-Storage-Geräte.
- Zum sicheren Auswerfen aus der Web-Oberfläche muss der Container mit
privileged: truelaufen (siehedocker-compose.yml).
Languages
TypeScript
60%
Kotlin
18.6%
CSS
18.5%
Java
1.7%
Shell
0.7%
Other
0.5%