Endstand: Aufnahmen (GB + Dateizahl) mit Balken, Noch frei, Gesamt fuer Aufnahmen. /api/sysstat liefert partPct und totalBytes weiter, die GUI zeigt sie nur nicht mehr an. Doku: Kapitel 07 + Changelog.
177 lines
11 KiB
Markdown
177 lines
11 KiB
Markdown
# 07 — Betrieb
|
||
|
||
> ⚠️ Dieses Repo ist **privat**; hier stehen LAN-Zugangsdaten. Nicht öffentlich stellen.
|
||
|
||
## Zugänge
|
||
|
||
| System | Zugang |
|
||
|---|---|
|
||
| Board SSH | `root@192.168.1.200` (WLAN, primär) / `root@192.168.1.50` (LAN), PW `123456` |
|
||
| Web-GUI | `http://192.168.1.200:3000` |
|
||
| MariaDB (auf dem Board) | DB `callrec`, User `callrec`, PW `callrec123` (kein `sqlite3`/CLI-Client — `mysql` nutzen) |
|
||
| Gitea (diese Doku) | `http://192.168.1.199:3000`, User `sascha` |
|
||
| App-User auf dem Board | `callrec`, Code in `/opt/callrec` |
|
||
|
||
## systemd-Einheiten
|
||
|
||
| Einheit | Scope | Aufgabe |
|
||
|---|---|---|
|
||
| `callrec.service` | system | Node-App (Web-GUI :3000, Aufnahme, Pipeline) |
|
||
| `callrec-btwatch.service` | system | BT-Headset-Auto-Connect (card-aware) |
|
||
| `callrec-perf.service` | system | CPU-Governor `performance` + A76-Pinning |
|
||
| `aime-usb-audio.service` | user | PipeWire-Topologie beim Boot (callmix, Loopbacks, Remap) |
|
||
| `aime-micgate.service` | user | LADSPA-Noise-Gate-Filter-Chain |
|
||
| `aime-volume-headset.service` | user | Windows-Volume → Headset-Sink (UAC2 numid=7) |
|
||
| `aime-headset-keys.service` | root | H390-Inline-Tasten → Lautstärke |
|
||
| `aime-iphone-route.service` | — | HFP-Routing-Watcher für iPhone-Anrufe. **Achtung:** die Unit liegt derzeit nicht unter `/etc/systemd/system/`, nur das Skript `/opt/callrec/aime-iphone-route.sh` ist da (Stand 2026-08-03, war schon vor dem eMMC-Umzug so) |
|
||
| `aime-bootsync.service` | system | spiegelt `/boot` der eMMC auf die SD-Karte (Start + Shutdown), siehe Speicher-Abschnitt |
|
||
| `filter-chain.service` (PipeWire-Standard) | user | **MASKIERT** — würde micgate doppeln |
|
||
| `aime-audio-out` / `aime-volume` | user | ES8323-Ära, **disabled** |
|
||
|
||
## Speicher: System auf der eMMC, Bootdateien auf der SD (seit 2026-08-03)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
ROM["BootROM RK3588S"] --> IDB["idbloader + U-Boot<br/>(eMMC, Sektor 64 / 16384)"]
|
||
IDB --> SCR{"boot.scr + armbianEnv.txt<br/>zuerst gefunden auf:"}
|
||
SCR -->|SD-Karte| KRN["Kernel + initrd<br/>von der SD"]
|
||
KRN -->|"rootdev=UUID=3ba18bc1…"| ROOT["/ = eMMC<br/>mmcblk0p1 „aime-emmc“"]
|
||
ROOT -.->|"aime-bootsync spiegelt /boot"| KRN
|
||
```
|
||
|
||
| Gerät | Größe | Rolle |
|
||
|---|---|---|
|
||
| `mmcblk0p1` (eMMC, `aime-emmc`) | 58 GB, 28 GB belegt | **`/` — System, Datenbank, Aufnahmen, Modelle** |
|
||
| `mmcblk1p1` (SD, `armbi_root`) | 118 GB | `boot.scr`/Kernel/initrd **und** eine vollständige Kopie des alten Systems als Rückfallebene. Nicht eingehängt — die SD wird im Betrieb weder gelesen noch beschrieben. |
|
||
|
||
**Warum die eMMC:** sie ist für Dauerbetrieb gebaut (Verschleiß auslesbar: `life_time 0x01`,
|
||
`pre_eol_info 0x01` = unter 10 % verbraucht), die SD-Karte ist der übliche Ausfallpunkt und stirbt
|
||
meist ohne Vorwarnung. Aime schreibt viel — jede Aufnahme WAV + MP3, dazu Datenbank und Suchindex.
|
||
|
||
**Der Haken, den man kennen muss:** U-Boot liest `boot.scr` und `armbianEnv.txt` von der **SD-Karte**,
|
||
solange sie steckt — nachgewiesen durch einen Neustart, bei dem eine auf die SD-Root zeigende
|
||
`armbianEnv.txt` das alte System hochfuhr. Der Kernel kommt also von der SD, das Root von der eMMC.
|
||
Ohne Gegenmaßnahme würde ein Kernel-Update auseinanderlaufen: neue Module auf der eMMC, alter Kernel
|
||
von der SD.
|
||
|
||
**`aime-bootsync.service`** löst das — spiegelt `/boot` beim Start **und** beim Herunterfahren auf die
|
||
SD (`rsync -a --delete --exclude='*.bak*'`), sodass beide Medien denselben Kernel tragen. Stirbt die
|
||
SD-Karte, findet U-Boot dort nichts mehr und nimmt die eMMC-Bootkette, die vollständig vorhanden ist.
|
||
|
||
Beide `armbianEnv.txt` (eMMC und SD) tragen `rootdev=UUID=3ba18bc1-bb8e-4a34-9c61-18a27f2c7adf`.
|
||
Auf der SD liegt daneben `armbianEnv.txt.bak-vor-emmc-umzug` mit der alten SD-UUID
|
||
`ab30e77b-…` — wer das alte System wieder booten will, kopiert die Datei zurück.
|
||
|
||
**Wichtig bei einem neuen Dateisystem:** `mkfs.ext4` aus e2fsprogs 1.47 setzt `orphan_file` und
|
||
`metadata_csum_seed`. `orphan_present` ist ein Incompat-Merkmal — ein Treiber, der es nicht kennt
|
||
(U-Boots ext4-Leser), verweigert das Lesen. Vor dem ersten Start entfernen:
|
||
|
||
```bash
|
||
tune2fs -O ^orphan_file,^metadata_csum_seed /dev/mmcblk0p1 && e2fsck -fy /dev/mmcblk0p1
|
||
```
|
||
|
||
## Wichtige Pfade auf dem Board
|
||
|
||
| Pfad | Inhalt |
|
||
|---|---|
|
||
| `/opt/callrec/*.js` | server / recorder / llm / diar / spk / db / bt / sysstat |
|
||
| `/opt/callrec/web/` | Vite-Frontend (`npx vite build` nach Änderungen an `src/`) |
|
||
| `/opt/callrec/aime-*.sh` | Setup-/Bridge-/Cap-Skripte |
|
||
| `/opt/callrec/milestones/m1-20260709/` | Meilenstein-1-Snapshot inkl. MANIFEST.md (Rollback) |
|
||
| `/opt/callrec/*.bak-*` | Datei-Backups je Änderung (z. B. `.bak-structsum`, `.bak-anchor`) |
|
||
| `/etc/sudoers.d/callrec-cpucap` | NOPASSWD für `aime-cpucap.sh` (Thermal-Cap) |
|
||
|
||
## Systemanzeige in der Kopfzeile (seit 2026-08-17)
|
||
|
||
Zwischen dem Tab „Bluetooth“ und dem Hell/Dunkel-Schalter stehen zwei Symbole, die den Zustand des
|
||
Boards ohne SSH zeigen:
|
||
|
||
| Symbol | Zeigt an | Klappkarte bei Mauskontakt |
|
||
|---|---|---|
|
||
| Ring + Prozentzahl | Anteil des **Aufnahme-Vorrats**, den die Aufnahmen schon belegen | Aufnahmen (GB + Dateizahl) mit Balken, Noch frei, Gesamt für Aufnahmen |
|
||
| Ventilator | CPU-Last — je höher, desto schneller die Drehung | CPU % (+ Kernzahl), RAM % mit GB, Temperatur mit Zone |
|
||
|
||
**Bezugsgröße der Prozentzahl** (Wunsch des Nutzers, 2026-08-17): nicht die Partition, sondern der Platz,
|
||
den ein *frisch aufgesetztes System dieser Ausbaustufe* für Aufnahmen übrig hätte:
|
||
|
||
```
|
||
100 % = freier Platz + Summe aller Audiodateien unter recordings/
|
||
belegt = Summe aller Audiodateien unter recordings/
|
||
```
|
||
|
||
System, Modelle und `node_modules` zählen also weder oben noch unten mit — die Anzeige beantwortet
|
||
„wie viel von meinem Aufnahme-Vorrat ist weg?“ und nicht „wie voll ist die eMMC?“. Die Klappkarte zeigt
|
||
bewusst nur die drei Zeilen Aufnahmen / Noch frei / Gesamt für Aufnahmen; den `df`-Wert (`partPct`) und
|
||
den Erklärtext zur 100-%-Bezugsgröße hat der Nutzer wieder streichen lassen. `/api/sysstat` liefert
|
||
`partPct` und `totalBytes` weiterhin.
|
||
|
||
**Mitgezählt wird jede Audiodatei unter `recordings/`, nicht nur die MP3s** — zu jeder Aufnahme liegt
|
||
dort auch die WAV-Rohdatei, und die ist rund viermal so groß wie das MP3 (Stand 2026-08-17: 52 MP3 =
|
||
0,39 GB, 51 WAV = 1,57 GB). Ebenso zählen die Aufnahmen im **Papierkorb** mit, denn deren Dateien
|
||
liegen bis zum Ablauf der 30 Tage weiter auf der Platte (Stand 2026-08-17: 16 aktiv, 36 im Papierkorb).
|
||
|
||
**Zeilenumbruch:** `.sys-row` steht auf `white-space: nowrap`, die Karte wächst stattdessen in die
|
||
Breite (`.sys-pop-disk { min-width: 285px }`). Vorher rutschte bei „Gesamt für Aufnahmen 27,5 GB“ die
|
||
Einheit allein in die nächste Zeile und sah aus wie ein fehlender Wert.
|
||
|
||
- Datenquelle: `GET /api/sysstat` (`app/sysstat.js`). `/proc`, `fs.statfsSync` und ein **gepufferter**
|
||
Größenscan über `recordings/` (`readdir` rekursiv + `stat`, TTL 60 s, nur Audio-Endungen) — **kein**
|
||
`df`/`du`/`top`-Prozess, damit die GUI im Sekundentakt fragen darf.
|
||
- CPU-% aus der Differenz zweier `/proc/stat`-Messpunkte. `sysstat.js` misst dafür **selbst** alle 2 s
|
||
(`setInterval(...).unref()`); ohne diesen eigenen Ticker wäre die erste Antwort nach jedem Start 0 %.
|
||
- RAM rechnet mit `MemAvailable`, nicht `MemFree` — sonst stünde das Board wegen Cache dauernd bei 95 %.
|
||
- Temperatur = wärmste der SoC-/Core-/Center-/GPU-/NPU-Zonen; `iwlwifi_1` (WLAN-Karte) fliegt raus, weil
|
||
ihr Wert nichts über die Rechenlast sagt.
|
||
- Frontend: `sysmonMarkup()` / `paintSysmon()` / `tickSysmon()` in `web/src/main.js`, Poll alle 5 s, im
|
||
Hintergrund-Tab (`document.hidden`) ausgesetzt. Ein einziger Ticker für alle `render()`-Durchläufe;
|
||
nach jedem `render()` werden die zuletzt geholten Werte sofort zurückgemalt, weil `render()` die
|
||
Kopfzeile komplett neu schreibt.
|
||
- Drehzahl des Ventilators läuft in **Stufen** (`spinSeconds()`: 7 s bei Leerlauf … 0,35 s ab 90 %). Eine
|
||
stufenlos gesetzte `animation-duration` würde die laufende Drehung bei jedem Poll neu ansetzen — das
|
||
ruckt sichtbar. `prefers-reduced-motion` stoppt die Animation ganz.
|
||
- Icon: vier gebogene Flügel (Pinwheel) im **feststehenden** Gehäusering — nur die Flügelgruppe dreht
|
||
sich, sonst wäre an einem runden Ring keine Drehung zu sehen.
|
||
- Farbe: grün < 75 %, gelb ab 75 %, rot ab 90 %; beim Ventilator zählt der schlimmste Wert aus CPU, RAM
|
||
und Temperatur (Temperatur skaliert 45 °C → 0 %, 90 °C → 100 %).
|
||
|
||
## Deploy-Ablauf (von Windows aus)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
DEV["Datei lokal ändern<br/>(pscp vom Board holen)"] --> BAK["Backup auf Board:<br/>cp X X.bak-<thema>"]
|
||
BAK --> UP["pscp hochladen"]
|
||
UP --> FE{"Frontend<br/>geändert?"}
|
||
FE -- ja --> BUILD["ssh: cd /opt/callrec/web && npx vite build"]
|
||
FE -- nein --> RESTART
|
||
BUILD --> RESTART["systemctl restart callrec"]
|
||
RESTART --> VERIFY["Verifizieren: journalctl -u callrec,<br/>GUI-Test, ggf. reanalyze-llm"]
|
||
VERIFY --> DOC["Doku + Changelog in Gitea aktualisieren"]
|
||
```
|
||
|
||
- Windows-Seite: PuTTY-Tools (`plink -batch -pw …`, `pscp`) — OpenSSH-Keys sind nicht hinterlegt.
|
||
- Node-Syntax vor Upload prüfen: `node --check datei.js`.
|
||
|
||
## Troubleshooting
|
||
|
||
| Symptom | Ursache / Fix |
|
||
|---|---|
|
||
| Zeile hängt auf `transcribing` | Absturz mitten in Diarization → GUI „Neu transkribieren“ (`/retranscribe`) |
|
||
| Summary wirkt leer nach Retranskription | GUI muss `analysis_status` pollen, nicht `status` — Analyse ist fire-and-forget |
|
||
| Board plötzlich aus | Thermik prüfen — Schutzabschaltung; Cap-Skript/Sudoers intakt? (Kap. 01) |
|
||
| Zu viele Sprecher | Audio geclippt? (Gain-Staging Kap. 02) Sonst „Analyse neu“ mit gepinnter Zahl |
|
||
| Teilnehmer ≠ Sprecher | Teilnehmer kommen aus Diarization; Namen im Sprecher-Editor vergeben |
|
||
| Kein far-end bei iPhone-Aufnahme | BT-Gerät `Trusted=yes`? HFP-Nodes erscheinen nur IM Anruf (Kap. 06) |
|
||
| GUI zeigt Quelle ohne Signal | `.monitor`-Falle — Quelle mit `parecord` + `ffmpeg astats` prüfen (Kap. 02) |
|
||
| `pactl load-module module-filter-chain` → „No such entity“ | Filter-Chain ist Core-Modul → Drop-in-Config nutzen (Kap. 02) |
|
||
| rkllm/Whisper `rc=-6` | RAM-Contention — nicht beide NPU-resident; NPU-Lock beachten (Kap. 05) |
|
||
| Analyse „KI Fehler“ bei langen Aufnahmen | Kontext-Überlauf → Chunk-Pfad greift; bei schwachem Ergebnis „Analyse neu“ |
|
||
|
||
## Offene Punkte (Stand 2026-07-16)
|
||
|
||
- Bedingter Takt-Cap (nur > 15 min Aufnahmedauer) — angeboten, nicht gebaut (~1,4× schnellere Kurzclips).
|
||
- Feinabstimmung Mikro/PC-Balance in einem echten Headset-Call.
|
||
- Glossar Phase 2: nächtlicher systemd-Timer + large-v3-Kandidaten-Vorschläge mit Bestätigung (verschoben).
|
||
- iPhone-Pfad: automatischer Aufnahme-Start + voller E2E-Test (Kap. 06).
|
||
- Qwen-1.5B als schnellerer LLM-Kandidat für Kurzclips (verschoben).
|
||
- Aufnahme #41 bleibt dauerhaft geclippt (Referenz für den Gain-Bug).
|