Add README
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
# 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](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`).
|
||||
Reference in New Issue
Block a user