diff --git a/README.md b/README.md index fb91515..fd2c9fc 100644 --- a/README.md +++ b/README.md @@ -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 -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. -![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) +![App](https://img.shields.io/badge/SolidJS-Capacitor-blue) +![Platform](https://img.shields.io/badge/Android-native-green) --- @@ -14,43 +15,40 @@ Web-App zum Suchen, Herunterladen und Synchronisieren von tiptoi-Büchern auf de - **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-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 -- **PWA** – kann auf dem Homescreen installiert werden (Android/iOS) --- ## 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 - ▼ -┌─────────────────────────────────┐ -│ 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 +┌───────────────────────────────────────────────┐ +│ Android-App (Capacitor + SolidJS) │ +│ │ +│ client/src/api.ts → Ravensburger-Suche + │ +│ Download-Orchestrierung │ +│ │ +│ Native Plugin (Kotlin, TiptoiPlugin.kt): │ +│ - Download von GME-Datei + Cover │ +│ - Stift-Zugriff via SAF (Storage Access │ +│ Framework, Verzeichnis-Picker) │ +│ - Auflisten/Kopieren/Löschen von .gme auf │ +│ dem Stift │ +└───────────────────────────────────────────────┘ ``` +Details zu Modulaufteilung und Konventionen: siehe [`.github/copilot-instructions.md`](.github/copilot-instructions.md). + --- ## 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) +- Android SDK + Gradle (für lokale Android-Builds) **oder** Docker/Podman (für den containerisierten APK-Build, siehe unten) +- Android-Gerät oder -Emulator zum Testen --- @@ -61,147 +59,47 @@ git clone cd tiptoi-sync bun install -# Server + Client parallel starten +# Vite Dev Server starten (Web-Ansicht im Browser) bun run dev ``` -- Client: `http://localhost:5173` (Vite Dev Server mit HMR) -- Server: `http://localhost:3000` +- Web-Ansicht: `http://localhost:5173` (Vite Dev Server mit HMR) -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://: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 -# Image bauen -docker compose build - -# Starten -docker compose up -d - -# Logs -docker compose logs -f +./build-apk.sh ``` -Die App ist danach unter `http://: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). - -### Arm64 (Rock Pi, Raspberry Pi) – Cross-Build von x86 - -Auf der x86-Entwicklungsmaschine (mit **Podman**): +### Lokal mit installiertem Android SDK ```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' +bun run build # Web-Assets bauen +bun run cap:sync # Assets ins Android-Projekt synchronisieren +cd client/android +./gradlew assembleDebug ``` --- -## USB-Automount (Headless Debian/Armbian) +## CI -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. | +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. --- @@ -209,25 +107,28 @@ sudo systemctl start usb-mount@sdb.service ``` 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) +├── client/ # SolidJS + Capacitor App +│ ├── src/ +│ │ ├── api.ts # Ravensburger-Suche, Download-/Sync-Orchestrierung +│ │ ├── native/ # TS-Interfaces für native Capacitor-Plugins +│ │ ├── pages/ +│ │ │ ├── ScanPage.tsx # Barcode-Scanner + Suche +│ │ │ └── DownloadsPage.tsx # Bibliothek + Stift-Sync +│ │ └── components/ +│ └── android/ # Capacitor Android-Projekt (im Repo eingecheckt) +│ └── app/src/main/java/com/tiptoisync/app/ +│ ├── TiptoiPlugin.kt # Stift-Zugriff (SAF), Download, Bibliothek +│ └── DevPlugin.kt # Live-Reload gegen Vite Dev-Server +├── 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 -- 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`). +- **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. +- Die App ist bewusst **Android-only**: ein Web-/iOS-Backend existiert nicht (mehr).