Files
tiptoi-sync/README.md
T
2026-08-12 11:54:39 +02:00

234 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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)
```bash
# 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**):
```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`).