# 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](https://img.shields.io/badge/SolidJS-PWA-blue) ![Server](https://img.shields.io/badge/Bun-Elysia-orange) ![Docker](https://img.shields.io/badge/Docker-arm64-green) --- ## 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](https://bun.sh) ≥ 1.0 - Node.js (nur für den Client-Build, via Bun verfügbar) - Docker + Docker Compose (für Deployment) --- ## Entwicklung (lokal) ```bash git clone 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) ```bash # Image bauen docker compose build # Starten docker compose up -d # Logs docker compose logs -f ``` Die App ist danach unter `http://: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**): ```bash # 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 ```bash 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 ```bash 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 ```bash 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 ```bash sudo udevadm control --reload-rules sudo systemctl daemon-reload ``` ### Testen ```bash # 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`).