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:
2026-08-26 08:53:14 +02:00
co-authored by Claude Opus 5
parent ec1658ba85
commit acb840bd0a
5 changed files with 72 additions and 8 deletions
+3 -3
View File
@@ -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)
+9 -1
View File
@@ -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
View File
@@ -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) |
+55
View File
@@ -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
+2 -1
View File
@@ -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