Web-GUI auf Port 80 (ohne Portnummer aufrufbar)
Die Unit startet Node als User=callrec (uid 1000) und konnte darum keinen privilegierten Port binden - daher bisher :3000. Mit AmbientCapabilities=CAP_NET_BIND_SERVICE bindet der unprivilegierte Prozess jetzt Port 80 direkt, ohne Reverse-Proxy (nginx haette 20-40 MB RAM gekostet, die neben dem residenten rkllm nicht frei sind). Kein Code-Eingriff noetig: server.js liest process.env.PORT, das Frontend nutzt relative API-Pfade. :3000 ist bewusst tot, alte Lesezeichen muessen angepasst werden. Dazu: Doku-IPs auf aime.local/192.168.1.48 korrigiert (192.168.1.200 war veraltet) und die Geisteranzeige-Diagnose vom 26.08. in Kapitel 03 + Changelog festgehalten (verwaister Browser-Reiter zaehlt ewig weiter, fmt() ohne Stunden). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,7 +36,7 @@ flowchart LR
|
|||||||
|
|
||||||
subgraph R1["Youyeetoo R1 (RK3588S, Armbian, lüfterlos)"]
|
subgraph R1["Youyeetoo R1 (RK3588S, Armbian, lüfterlos)"]
|
||||||
PW["PipeWire<br/>Audio-Routing<br/>(callmix + micgate)"]
|
PW["PipeWire<br/>Audio-Routing<br/>(callmix + micgate)"]
|
||||||
APP["Aime Web-App<br/>Node.js :3000"]
|
APP["Aime Web-App<br/>Node.js :80"]
|
||||||
WH["Whisper large-v3-turbo<br/>Encoder auf NPU"]
|
WH["Whisper large-v3-turbo<br/>Encoder auf NPU"]
|
||||||
DIAR["Diarization<br/>sherpa-onnx + TitaNet"]
|
DIAR["Diarization<br/>sherpa-onnx + TitaNet"]
|
||||||
LLM["Gemma-3-4B<br/>rkllm auf NPU"]
|
LLM["Gemma-3-4B<br/>rkllm auf NPU"]
|
||||||
@@ -53,8 +53,8 @@ flowchart LR
|
|||||||
|
|
||||||
## Quick Facts
|
## Quick Facts
|
||||||
|
|
||||||
- **Board:** `192.168.1.200` (WLAN, primär) / `192.168.1.50` (LAN), Login `root`
|
- **Board:** `aime.local` per mDNS — aktuell `192.168.1.48` (WLAN, DHCP) / `192.168.1.50` (LAN, statisch, nur bei gestecktem Kabel), Login `root`
|
||||||
- **Web-GUI:** `http://192.168.1.200:3000` — Aufnahme, Transkript, Sprecher-Editor, Glossar
|
- **Web-GUI:** `http://aime.local` — Port **80**, also ohne Portnummer im Browser. Aufnahme, Transkript, Sprecher-Editor, Glossar
|
||||||
- **App-Verzeichnis:** `/opt/callrec` (Node.js, systemd `callrec.service`)
|
- **App-Verzeichnis:** `/opt/callrec` (Node.js, systemd `callrec.service`)
|
||||||
- **Harte Anforderungen:** immer offline (kein Cloud-LLM), akustische Sprecher-ID, kein aktiver Lüfter
|
- **Harte Anforderungen:** immer offline (kein Cloud-LLM), akustische Sprecher-ID, kein aktiver Lüfter
|
||||||
- Details und alle Zugangsdaten: [07 – Betrieb](docs/07-betrieb.md)
|
- Details und alle Zugangsdaten: [07 – Betrieb](docs/07-betrieb.md)
|
||||||
|
|||||||
@@ -225,7 +225,15 @@ flowchart TB
|
|||||||
Start-Knopf (●), der Stop-Knopf ist also unerreichbar, während `parecord` weiterschreibt. Ein Druck auf ●
|
Start-Knopf (●), der Stop-Knopf ist also unerreichbar, während `parecord` weiterschreibt. Ein Druck auf ●
|
||||||
läuft in `recorder.js` gegen `if (current) throw new Error('already recording')` — es gibt keinen
|
läuft in `recorder.js` gegen `if (current) throw new Error('already recording')` — es gibt keinen
|
||||||
Datenverlust, aber auch keinen Weg mehr, die Aufnahme aus der GUI zu beenden. Notausgang ohne GUI bleibt
|
Datenverlust, aber auch keinen Weg mehr, die Aufnahme aus der GUI zu beenden. Notausgang ohne GUI bleibt
|
||||||
`curl -X POST localhost:3000/api/record/stop` (speichert regulär und startet die Transkription).
|
`curl -X POST localhost/api/record/stop` (speichert regulär und startet die Transkription).
|
||||||
|
|
||||||
|
🕳️ **Bekannte Lücke in die andere Richtung (Stand 2026-08-26, nicht gefixt):** der Abgleich passiert
|
||||||
|
**nur beim Laden**. Während „Aufnahme läuft" pollt die GUI `/api/health` nicht mehr, und die Uhr rechnet
|
||||||
|
rein lokal (`Date.now() - state.recStart`). Wird der Stop von einem anderen Gerät oder Tab ausgelöst,
|
||||||
|
zählt der verwaiste Reiter für immer weiter — am 26.08. stand so über Nacht eine „laufende" Aufnahme mit
|
||||||
|
`1280:34` im Kopfband, obwohl der Server seit dem Vortag nichts aufnahm. Verschärfend: `fmt()` hat kein
|
||||||
|
Stundenfeld, die Minuten laufen also unbegrenzt hoch und lesen sich wie eine reale Dauer. Vorschlag:
|
||||||
|
`recording` am bestehenden 5-s-`sysmon`-Tick mitliefern und `fmt()` um Stunden erweitern.
|
||||||
|
|
||||||
## API-Endpunkte (Auszug)
|
## API-Endpunkte (Auszug)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
| System | Zugang |
|
| System | Zugang |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Board SSH | `root@192.168.1.200` (WLAN, primär) / `root@192.168.1.50` (LAN), PW `123456` |
|
| Board SSH | `root@aime.local` — aktuell `192.168.1.48` (WLAN, DHCP) / `192.168.1.50` (LAN, statisch, nur bei gestecktem Kabel), PW `123456` |
|
||||||
| Web-GUI | `http://192.168.1.200:3000` |
|
| Web-GUI | `http://aime.local` bzw. `http://192.168.1.48` — **Port 80 seit 2026-08-26**, keine Portnummer mehr nötig |
|
||||||
| MariaDB (auf dem Board) | DB `callrec`, User `callrec`, PW `callrec123` (kein `sqlite3`/CLI-Client — `mysql` nutzen) |
|
| 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` |
|
| Gitea (diese Doku) | `http://192.168.1.199:3000`, User `sascha` |
|
||||||
| App-User auf dem Board | `callrec`, Code in `/opt/callrec` |
|
| App-User auf dem Board | `callrec`, Code in `/opt/callrec` |
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
| Einheit | Scope | Aufgabe |
|
| Einheit | Scope | Aufgabe |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `callrec.service` | system | Node-App (Web-GUI :3000, Aufnahme, Pipeline) |
|
| `callrec.service` | system | Node-App (Web-GUI **:80**, Aufnahme, Pipeline) — bindet den privilegierten Port als `User=callrec` über `AmbientCapabilities=CAP_NET_BIND_SERVICE` |
|
||||||
| `callrec-btwatch.service` | system | BT-Headset-Auto-Connect (card-aware) |
|
| `callrec-btwatch.service` | system | BT-Headset-Auto-Connect (card-aware) |
|
||||||
| `callrec-perf.service` | system | CPU-Governor `performance` + A76-Pinning |
|
| `callrec-perf.service` | system | CPU-Governor `performance` + A76-Pinning |
|
||||||
| `aime-usb-audio.service` | user | PipeWire-Topologie beim Boot (callmix, Loopbacks, Remap) |
|
| `aime-usb-audio.service` | user | PipeWire-Topologie beim Boot (callmix, Loopbacks, Remap) |
|
||||||
|
|||||||
@@ -3,6 +3,61 @@
|
|||||||
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
||||||
Was, Warum, betroffene Dateien/Backups).
|
Was, Warum, betroffene Dateien/Backups).
|
||||||
|
|
||||||
|
## 2026-08-26 — Web-GUI auf Port 80 (Aufruf ohne Portnummer)
|
||||||
|
|
||||||
|
**Wunsch:** „Kann man es nicht über Port 80 laufen lassen? Dann müsste man nur die IP bzw. Aime in den
|
||||||
|
Browser eingeben ohne die Portnummer."
|
||||||
|
|
||||||
|
**Warum es vorher 3000 war:** die Unit startet Node als `User=callrec` (uid 1000). Ports unter 1024 sind
|
||||||
|
privilegiert (`net.ipv4.ip_unprivileged_port_start = 1024`), ein unprivilegierter Prozess darf sie nicht
|
||||||
|
binden. 3000 war also reine Bequemlichkeit, kein technischer Zwang. Port 80 war frei — kein nginx/Apache
|
||||||
|
auf dem Board (`ss -tlnp`: nur 22, 3000, 3306, 53/5355).
|
||||||
|
|
||||||
|
**Was geändert wurde**
|
||||||
|
|
||||||
|
- `/etc/systemd/system/callrec.service`: `Environment=PORT=80` statt `3000`, dazu
|
||||||
|
**`AmbientCapabilities=CAP_NET_BIND_SERVICE`** — damit darf der `callrec`-Prozess den privilegierten
|
||||||
|
Port binden, ohne als root zu laufen. Kein Reverse-Proxy: nginx hätte 20–40 MB RAM gekostet, die neben
|
||||||
|
dem residenten rkllm (~6,5 GB von 7,74 GB) nicht frei sind (siehe OOM-Historie in Kapitel 05).
|
||||||
|
- Kein Code-Eingriff nötig: `server.js:941` liest `process.env.PORT`, und das Frontend spricht die API
|
||||||
|
ausschließlich über relative Pfade (`/api/...`) an. Einziger `:3000`-Rest im Code ist der Dev-Proxy in
|
||||||
|
`web/vite.config.js` (betrifft nur `npm run dev`).
|
||||||
|
|
||||||
|
**Gegenprobe:** `callrec listening on :80`, `ss -tlnp` zeigt `0.0.0.0:80 node`, vom PC aus liefern
|
||||||
|
`http://192.168.1.48/` und `http://aime.local/` je **200**, `/api/health` antwortet. `:3000` ist bewusst
|
||||||
|
tot (Entscheidung des Nutzers: keine Parallel-Weiterleitung) — **alte Lesezeichen mit `:3000` müssen
|
||||||
|
angepasst werden.**
|
||||||
|
|
||||||
|
**Nebenbefund (nicht gefixt):** die Doku führte das Board noch unter `192.168.1.200`; aktuell hat es per
|
||||||
|
DHCP `192.168.1.48` (WLAN), LAN-Kabel steckt nicht. README und Kapitel 07 nennen jetzt `aime.local`
|
||||||
|
als stabilen Weg.
|
||||||
|
|
||||||
|
**Dateien/Backups:** `/etc/systemd/system/callrec.service.bak-port3000` auf dem Board.
|
||||||
|
`systemctl daemon-reload` + `restart callrec`; vorher `analysis_status='pending'` geprüft (leer) und
|
||||||
|
`/api/health` auf `recording:false` (Regel: kein Neustart während einer Analyse).
|
||||||
|
|
||||||
|
## 2026-08-26 — Diagnose: „Aufnahme läuft seit tausenden Minuten" war eine Geisteranzeige
|
||||||
|
|
||||||
|
**Meldung:** Morgens stand in der GUI eine laufende Aufnahme mit vierstelliger Minutenzahl.
|
||||||
|
|
||||||
|
**Befund: es lief nichts.** `/api/health` → `recording:false`, kein `arecord`/`ffmpeg`-Prozess, letzter
|
||||||
|
Journal-Eintrag von `callrec` war 25.08. 10:00:53, neueste Datei in `recordings/` war
|
||||||
|
`rec_1787648427247.wav` vom 25.08. 09:21, neueste DB-Zeile #123 (25.08., 21,5 min, `done/done`). Kein
|
||||||
|
Datenverlust, kein Plattenverbrauch.
|
||||||
|
|
||||||
|
**Ursache, zwei Teile in `web/src/main.js`:**
|
||||||
|
|
||||||
|
1. Die Aufnahmeuhr läuft rein im Browser (`setInterval` auf `Date.now() - state.recStart`, Zeile ~304).
|
||||||
|
Der Serverzustand wird **nur einmal beim Laden** geprüft (`rehydrateRecording()`, Zeile ~1951);
|
||||||
|
während „Aufnahme läuft" pollt die GUI `/api/health` nie und es gibt keinen Abgleich zwischen Tabs.
|
||||||
|
Ein Reiter, der den Stop nicht selbst ausgelöst hat (zweites Gerät, zweiter Tab), zählt darum ewig
|
||||||
|
weiter.
|
||||||
|
2. `fmt()` (Zeile 33) kennt nur `MM:SS`, keine Stunden — 21 h Geisterlauf erscheinen als `1280:34` und
|
||||||
|
lesen sich wie „1280 Minuten".
|
||||||
|
|
||||||
|
**Offener Vorschlag (vom Nutzer noch nicht beauftragt):** `recording` auf dem bestehenden 5-s-`sysmon`-
|
||||||
|
Tick mitliefern, damit ein verwaister Reiter sich selbst aufräumt, und `fmt()` um Stunden erweitern.
|
||||||
|
|
||||||
## 2026-08-20 — Stop-Knopf reagiert sofort (MP3-Encode in den Hintergrund)
|
## 2026-08-20 — Stop-Knopf reagiert sofort (MP3-Encode in den Hintergrund)
|
||||||
|
|
||||||
**Meldung:** „Wenn ich eine laufende Aufnahme stoppen will, passiert auf der GUI nichts. Ich muss den
|
**Meldung:** „Wenn ich eine laufende Aufnahme stoppen will, passiert auf der GUI nichts. Ich muss den
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ WorkingDirectory=/opt/callrec
|
|||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
Environment=XDG_RUNTIME_DIR=/run/user/1000
|
Environment=XDG_RUNTIME_DIR=/run/user/1000
|
||||||
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
||||||
Environment=PORT=3000
|
Environment=PORT=80
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
ExecStart=/usr/bin/node /opt/callrec/server.js
|
ExecStart=/usr/bin/node /opt/callrec/server.js
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
|
|||||||
Reference in New Issue
Block a user