Update README: remove stale server architecture, add AI-generated disclaimer
README beschrieb noch eine Bun+Elysia-Server-Architektur mit Docker- Deployment, USB-Automount etc., die es in diesem Repo nicht mehr gibt (reines Android/Capacitor-Setup). Ersetzt durch die aktuelle Architektur, Build-/Dev-Anleitung und CI-Hinweis. Zusätzlich Hinweis ganz oben, dass das Projekt größtenteils KI-generiert und noch nicht vollständig reviewed ist.
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
|
> ⚠️ **Hinweis:** Dieses Projekt wurde größtenteils KI-generiert (mit GitHub Copilot). Nicht der gesamte Code wurde bisher manuell reviewed – Vorsicht bei produktivem Einsatz, Beiträge und Reviews sind willkommen.
|
||||||
|
|
||||||
# tiptoi-sync
|
# 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.
|
Android-App zum Suchen, Herunterladen und Synchronisieren von tiptoi-Büchern auf den tiptoi-Stift. Läuft direkt auf dem Smartphone (Capacitor-App) – kein Server, kein selbst-gehostetes Backend nötig.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,43 +15,40 @@ Web-App zum Suchen, Herunterladen und Synchronisieren von tiptoi-Büchern auf de
|
|||||||
- **Freitextsuche** – nach Titel oder Artikelnummer suchen
|
- **Freitextsuche** – nach Titel oder Artikelnummer suchen
|
||||||
- **Download** – GME-Datei + Cover automatisch von Ravensburger herunterladen
|
- **Download** – GME-Datei + Cover automatisch von Ravensburger herunterladen
|
||||||
- **Bibliothek** – alle heruntergeladenen Bücher in der Übersicht
|
- **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-Sync** – tiptoi-Stift verbinden (Storage Access Framework), Bücher direkt aus der App auf den Stift kopieren
|
||||||
- **Stift-Status** – zeigt welche Bücher bereits auf dem Stift sind und welche fehlen
|
- **Stift-Status** – zeigt welche Bücher bereits auf dem Stift sind und welche fehlen
|
||||||
- **PWA** – kann auf dem Homescreen installiert werden (Android/iOS)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
|
Die App läuft komplett nativ auf dem Android-Gerät – Suche, Download und Stift-Zugriff passieren alle direkt im Client, ohne Server-Komponente:
|
||||||
|
|
||||||
```
|
```
|
||||||
Browser / PWA
|
┌───────────────────────────────────────────────┐
|
||||||
│ HTTP
|
│ Android-App (Capacitor + SolidJS) │
|
||||||
▼
|
│ │
|
||||||
┌─────────────────────────────────┐
|
│ client/src/api.ts → Ravensburger-Suche + │
|
||||||
│ Bun + Elysia (Port 3000) │
|
│ Download-Orchestrierung │
|
||||||
│ │
|
│ │
|
||||||
│ /api/search → Ravensburger│
|
│ Native Plugin (Kotlin, TiptoiPlugin.kt): │
|
||||||
│ /api/download → GME + Cover │
|
│ - Download von GME-Datei + Cover │
|
||||||
│ /api/downloads → Bibliothek │
|
│ - Stift-Zugriff via SAF (Storage Access │
|
||||||
│ /api/pen/detect → lsblk │
|
│ Framework, Verzeichnis-Picker) │
|
||||||
│ /api/pen/files → Stift-Inhalt│
|
│ - Auflisten/Kopieren/Löschen von .gme auf │
|
||||||
│ /api/pen/sync → Dateien kop.│
|
│ dem Stift │
|
||||||
│ /api/pen/eject → umount │
|
└───────────────────────────────────────────────┘
|
||||||
│ │
|
|
||||||
│ /* → SolidJS SPA │
|
|
||||||
└─────────────────────────────────┘
|
|
||||||
│ │
|
|
||||||
/data/ USB
|
|
||||||
(GME-Dateien) tiptoi-Stift
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Details zu Modulaufteilung und Konventionen: siehe [`.github/copilot-instructions.md`](.github/copilot-instructions.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
- [Bun](https://bun.sh) ≥ 1.0
|
- [Bun](https://bun.sh) ≥ 1.0
|
||||||
- Node.js (nur für den Client-Build, via Bun verfügbar)
|
- Android SDK + Gradle (für lokale Android-Builds) **oder** Docker/Podman (für den containerisierten APK-Build, siehe unten)
|
||||||
- Docker + Docker Compose (für Deployment)
|
- Android-Gerät oder -Emulator zum Testen
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -61,147 +59,47 @@ git clone <repo>
|
|||||||
cd tiptoi-sync
|
cd tiptoi-sync
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Server + Client parallel starten
|
# Vite Dev Server starten (Web-Ansicht im Browser)
|
||||||
bun run dev
|
bun run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
- Client: `http://localhost:5173` (Vite Dev Server mit HMR)
|
- Web-Ansicht: `http://localhost:5173` (Vite Dev Server mit HMR)
|
||||||
- Server: `http://localhost:3000`
|
|
||||||
|
|
||||||
Der Vite Dev Server proxied `/api/*` automatisch zum Server.
|
Für Live-Reload direkt auf dem Android-Gerät/-Emulator gegen den Vite Dev-Server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run cap:sync
|
||||||
|
DEV_URL=http://<host-ip>:5173 bun run cap:android
|
||||||
|
```
|
||||||
|
|
||||||
|
`client/capacitor.config.ts` liest `DEV_URL` und lädt die Webview dann direkt vom Dev-Server statt vom gebauten `dist/`-Ordner. Alternativ lässt sich das auch zur Laufzeit über das Dev-Menü in der App umschalten (5x auf den Titel in der Kopfzeile tippen).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Deployment
|
## Android-APK bauen
|
||||||
|
|
||||||
### Docker (empfohlen)
|
### Über Docker/Podman (empfohlen, kein lokales Android SDK nötig)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Image bauen
|
./build-apk.sh
|
||||||
docker compose build
|
|
||||||
|
|
||||||
# Starten
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
docker compose logs -f
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Die App ist danach unter `http://<server-ip>:3000` erreichbar.
|
Baut die APK reproduzierbar in einem Container (Bun + Android SDK + Gradle, siehe `Dockerfile.android`) und legt sie unter `apk-output/tiptoi-sync.apk` ab.
|
||||||
|
|
||||||
Heruntergeladene GME-Dateien werden im Verzeichnis `./data/` gespeichert (persistentes Volume).
|
### Lokal mit installiertem Android SDK
|
||||||
|
|
||||||
### Arm64 (Rock Pi, Raspberry Pi) – Cross-Build von x86
|
|
||||||
|
|
||||||
Auf der x86-Entwicklungsmaschine (mit **Podman**):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# QEMU einmalig installieren
|
bun run build # Web-Assets bauen
|
||||||
sudo apt install qemu-user-static # Debian/Ubuntu
|
bun run cap:sync # Assets ins Android-Projekt synchronisieren
|
||||||
sudo dnf install qemu-user-static # Fedora
|
cd client/android
|
||||||
|
./gradlew assembleDebug
|
||||||
# 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)
|
## CI
|
||||||
|
|
||||||
Damit der tiptoi-Stift beim Einstecken automatisch gemountet wird (kein Desktop nötig):
|
Ein Workflow unter [`.github/workflows/ci.yml`](.github/workflows/ci.yml) führt bei jedem Push/PR auf `main` einen Type-Check aus und baut anschließend die APK, die als Artifact bereitgestellt wird. Kompatibel sowohl mit GitHub Actions als auch mit Gitea Actions.
|
||||||
|
|
||||||
### 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. |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -209,25 +107,28 @@ sudo systemctl start usb-mount@sdb.service
|
|||||||
|
|
||||||
```
|
```
|
||||||
tiptoi-sync/
|
tiptoi-sync/
|
||||||
├── client/ # SolidJS PWA (Vite)
|
├── client/ # SolidJS + Capacitor App
|
||||||
│ └── src/
|
│ ├── src/
|
||||||
│ ├── pages/
|
│ │ ├── api.ts # Ravensburger-Suche, Download-/Sync-Orchestrierung
|
||||||
│ │ ├── ScanPage.tsx # Barcode-Scanner + Suche
|
│ │ ├── native/ # TS-Interfaces für native Capacitor-Plugins
|
||||||
│ │ └── DownloadsPage.tsx # Bibliothek + Stift-Sync
|
│ │ ├── pages/
|
||||||
│ └── components/
|
│ │ │ ├── ScanPage.tsx # Barcode-Scanner + Suche
|
||||||
│ └── QrScanner.tsx
|
│ │ │ └── DownloadsPage.tsx # Bibliothek + Stift-Sync
|
||||||
├── server/ # Bun + Elysia API
|
│ │ └── components/
|
||||||
│ └── src/
|
│ └── android/ # Capacitor Android-Projekt (im Repo eingecheckt)
|
||||||
│ └── index.ts
|
│ └── app/src/main/java/com/tiptoisync/app/
|
||||||
├── Dockerfile
|
│ ├── TiptoiPlugin.kt # Stift-Zugriff (SAF), Download, Bibliothek
|
||||||
├── docker-compose.yml
|
│ └── DevPlugin.kt # Live-Reload gegen Vite Dev-Server
|
||||||
└── 99-usb-mount.rules # udev-Regel (Referenz)
|
├── Dockerfile.android # Container-Build für die APK
|
||||||
|
├── build-apk.sh # Wrapper um Dockerfile.android (Podman/Docker)
|
||||||
|
└── .github/
|
||||||
|
├── workflows/ci.yml # Type-Check + APK-Build
|
||||||
|
└── copilot-instructions.md # Architektur- und Konventions-Details
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Hinweise
|
## Hinweise
|
||||||
|
|
||||||
- Der **Stift muss vom Host-System gemountet** werden (udev-Automount, s. o.). Der Docker-Container greift über `rshared`-Volume-Mount darauf zu.
|
- **Kein direkter USB-Massenspeicher-Zugriff nötig** – der Stift wird über das Android Storage Access Framework (SAF) via Verzeichnis-Picker eingebunden, kein Root/Server erforderlich.
|
||||||
- **Android-Direktsync** (Stift per USB-OTG ans Handy) ist technisch nicht möglich – Browser haben keinen Zugriff auf USB-Mass-Storage-Geräte.
|
- Die App ist bewusst **Android-only**: ein Web-/iOS-Backend existiert nicht (mehr).
|
||||||
- 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