2026-08-12 11:54:39 +02:00
2026-08-12 11:46:18 +02:00
2026-08-12 11:46:18 +02:00
2026-08-12 11:46:18 +02:00
2026-08-12 11:54:39 +02:00
2026-08-12 11:46:18 +02:00

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.

Scan Server Docker


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: true laufen (siehe docker-compose.yml).
S
Description
No description provided
Readme
101 MiB
Languages
TypeScript 60%
Kotlin 18.6%
CSS 18.5%
Java 1.7%
Shell 0.7%
Other 0.5%