Docs: close the gaps an audit turned up

Audited README, BEDIENUNGSANLEITUNG and CLAUDE.md against what the repo and the
running stack actually do. Findings, all fixed:

- CLAUDE.md claimed the repo holds "only deployment config (docker-compose.yml,
  .env, .gitignore)". It has not been that for a while: services/ is our own
  source (TTS/STT servers + systemd units), scripts/ is tooling, and
  prompts/podcast/ + config/content_core.yaml are overlays bind-mounted over the
  image. Those overlays are the fragile part, so the section now says so and
  points at smoke_test.sh.
- CLAUDE.md still described the image as a moving tag and argued against patching
  app code "because it would rot against pull_policy: always" — both obsolete
  since the digest pin.
- README listed neither yt-dlp (needed by add_video_source.py) nor smoke_test.sh
  in the verification step, and section 2.3 still told you to `docker compose pull`
  as if the tag moved.
- BEDIENUNGSANLEITUNG had nothing at all about updates — the very thing a user has
  to do periodically. New section 8 covers check_updates / update_stack / smoke_test
  and, importantly, why updating is not automatic (DB migration is one-way without
  a backup; the local adaptations can break silently without crashing).
- Renumbered the ad-hoc "3a. Updates" in README into a real section 4, and the
  BEDIENUNGSANLEITUNG sections after the new 8 accordingly.

Verified mechanically: every scripts//services//config//prompts/ path named in the
docs exists, and all internal and cross-document anchors resolve (0 dead links —
one was already broken before this change: README pointed at
BEDIENUNGSANLEITUNG.md#stimmklonung instead of #5-stimmklonung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-07-11 14:17:06 +02:00
commit 09c6d910a7
3 changed files with 82 additions and 16 deletions

View file

@ -15,9 +15,10 @@ siehe [`CLAUDE.md`](CLAUDE.md).
5. [Stimmklonung](#5-stimmklonung) 5. [Stimmklonung](#5-stimmklonung)
6. [Modelle und Provider verwalten](#6-modelle-und-provider-verwalten) 6. [Modelle und Provider verwalten](#6-modelle-und-provider-verwalten)
7. [Dienste starten/stoppen](#7-dienste-startenstoppen) 7. [Dienste starten/stoppen](#7-dienste-startenstoppen)
8. [Suche über Chunks (Search/Ask)](#8-suche-über-chunks-searchask) 8. [Updates einspielen](#8-updates-einspielen)
9. [Die API in eigenen Programmen nutzen](#9-die-api-in-eigenen-programmen-nutzen) 9. [Suche über Chunks (Search/Ask)](#9-suche-über-chunks-searchask)
10. [Troubleshooting](#10-troubleshooting) 10. [Die API in eigenen Programmen nutzen](#10-die-api-in-eigenen-programmen-nutzen)
11. [Troubleshooting](#11-troubleshooting)
--- ---
@ -256,7 +257,54 @@ ollama ps # aktuell geladene Modelle + GPU/CPU-Verteilung
--- ---
## 8. Suche über Chunks (Search/Ask) ## 8. Updates einspielen
Open Notebook läuft hier auf einer **festgenagelten Version** (Digest-Pin in
`docker-compose.yml`). Weder ein Start noch ein Reboot tauscht die Anwendung unbemerkt aus — Sie
bekommen immer genau den Stand, der zuletzt geprüft wurde.
**Beim Sitzungsstart** (ändert nichts, dauert Sekunden):
```bash
./scripts/check_updates.sh
```
Ausgabe entweder „Du laeufst auf dem aktuellen Release. Nichts zu tun." oder ein Hinweis auf ein
neues Image.
**Update einspielen:**
```bash
./scripts/update_stack.sh
```
Das Skript sichert zuerst Datenbank und Notebook-Daten nach `backups/<Zeitstempel>/`, stellt dann
auf das neue Image um, startet den Stack und fährt einen **Rauchtest** (API, Sprachausgabe,
Spracherkennung, YouTube-Import, alle lokalen Anpassungen). Schlägt der fehl, wird **automatisch
zurückgerollt** — Daten und Konfiguration landen wieder auf dem alten Stand.
Läuft alles, committen Sie den neuen Stand:
```bash
git add docker-compose.yml && git commit -m "Update auf <Version>"
```
**Warum nicht einfach immer die neueste Version?** Ein Update verändert das Datenbankschema, und
eine ältere Version kann eine bereits migrierte Datenbank in der Regel nicht mehr lesen — ohne
Sicherung wäre ein misslungenes Update also eine Einbahnstraße. Außerdem hängen die Anpassungen
dieser Installation (Podcast-Vorlagen, YouTube-Sprachen, TTS-Einstellungen) an Interna der
Anwendung und können durch ein Update **still** kaputtgehen, ohne dass etwas abstürzt. Genau
deshalb der Rauchtest.
Ihn können Sie auch jederzeit einzeln laufen lassen, etwa wenn sich etwas seltsam verhält:
```bash
./scripts/smoke_test.sh
```
---
## 9. Suche über Chunks (Search/Ask)
Jedes Dokument wird beim Hochladen automatisch in feste, überlappende Textabschnitte Jedes Dokument wird beim Hochladen automatisch in feste, überlappende Textabschnitte
("Chunks", ~400 Tokens) zerlegt und eingebettet — **nicht** kapitelweise, sondern rein ("Chunks", ~400 Tokens) zerlegt und eingebettet — **nicht** kapitelweise, sondern rein
@ -300,7 +348,7 @@ weil nur die relevanten Chunks statt des ganzen Dokuments verarbeitet werden.
--- ---
## 9. Die API in eigenen Programmen nutzen ## 10. Die API in eigenen Programmen nutzen
Die REST-API ist eine ganz normale JSON-über-HTTP-API **ohne Authentifizierung** — kein API-Key Die REST-API ist eine ganz normale JSON-über-HTTP-API **ohne Authentifizierung** — kein API-Key
nötig, da nur `127.0.0.1` gebunden (jeder lokale Prozess auf diesem Rechner hat Zugriff, aber nötig, da nur `127.0.0.1` gebunden (jeder lokale Prozess auf diesem Rechner hat Zugriff, aber
@ -361,7 +409,7 @@ Spezifisches nötig, kein SDK erforderlich.
--- ---
## 10. Troubleshooting ## 11. Troubleshooting
### „Chat gibt keine Antwort" (leere Antwort, kein Fehler) ### „Chat gibt keine Antwort" (leere Antwort, kein Fehler)

View file

@ -4,7 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this repo is ## What this repo is
This is a local Docker Compose deployment of [Open Notebook](https://github.com/lfnovo/open-notebook) (upstream image `lfnovo/open_notebook`), not a clone of its source. There is no application source code here — only deployment config (`docker-compose.yml`, `.env`, `.gitignore`). Application bugs/features belong upstream; this repo only concerns itself with how the stack is run locally. This is a local Docker Compose deployment of [Open Notebook](https://github.com/lfnovo/open-notebook) (upstream image `lfnovo/open_notebook`), not a clone of its source. The application's own code is **not** here and its bugs/features belong upstream — this repo only concerns itself with how the stack is run locally.
What *is* here, beyond `docker-compose.yml`/`.env`:
- `services/` — our own source: the TTS/STT wrapper servers (Open Notebook has no local TTS/STT provider) plus their systemd units.
- `scripts/` — setup, maintenance and update tooling (see Commands below).
- `prompts/podcast/`, `config/content_core.yaml`**overlays** bind-mounted over files inside the image, to fix upstream behaviour we can't configure otherwise (podcast language/reliability, YouTube caption languages). These are the fragile part: they can silently rot when the image is updated, which is what `scripts/smoke_test.sh` exists to catch.
## Commands ## Commands
@ -71,7 +77,7 @@ check.
Two services, defined in `docker-compose.yml`: Two services, defined in `docker-compose.yml`:
- **surrealdb** (`surrealdb/surrealdb:v2`) — the database, RocksDB-backed, bound to `127.0.0.1:8000` only (local debugging access, e.g. `surreal sql`). Credentials come from `SURREAL_USER`/`SURREAL_PASSWORD` (default `root`/`root` if unset in `.env`) and are shared with the `open_notebook` service so they stay in sync. - **surrealdb** (`surrealdb/surrealdb:v2`) — the database, RocksDB-backed, bound to `127.0.0.1:8000` only (local debugging access, e.g. `surreal sql`). Credentials come from `SURREAL_USER`/`SURREAL_PASSWORD` (default `root`/`root` if unset in `.env`) and are shared with the `open_notebook` service so they stay in sync.
- **open_notebook** (`lfnovo/open_notebook:v1-latest`) — bundles the web UI (port 8502) and REST API (port 5055) in one image, both bound to `127.0.0.1` only. Connects to `surrealdb` over the internal compose network (`ws://surrealdb:8000/rpc`). - **open_notebook** (`lfnovo/open_notebook:v1-latest`, digest-pinned — see *Image versioning*) — bundles the web UI (port 8502) and REST API (port 5055) in one image, both bound to `127.0.0.1` only. Connects to `surrealdb` over the internal compose network (`ws://surrealdb:8000/rpc`).
Data persists in `./surreal_data` and `./notebook_data` (bind mounts, gitignored). Data persists in `./surreal_data` and `./notebook_data` (bind mounts, gitignored).
@ -130,8 +136,9 @@ dialogue segment), `outline.json` and `transcript.json`, is never touched, and n
Failed and `/retry`-replaced runs leak a directory too (the retry writes a *new* episode row with a new Failed and `/retry`-replaced runs leak a directory too (the retry writes a *new* episode row with a new
uuid dir; the old one loses its DB entry and just stays). Upstream this would be roughly uuid dir; the old one loses its DB entry and just stays). Upstream this would be roughly
`shutil.rmtree(audio_path.parent.parent)` in that handler. We deliberately do **not** patch it here by `shutil.rmtree(audio_path.parent.parent)` in that handler. We deliberately do **not** patch it here by
bind-mounting a modified router — that would fork app logic into a deployment repo and silently rot bind-mounting a modified router — that would fork app logic into a deployment repo and rot against
against `pull_policy: always`. Instead: `scripts/prune_podcast_data.py` (dry-run by default, `--yes` to image updates (the same trap the existing overlays already carry). Instead:
`scripts/prune_podcast_data.py` (dry-run by default, `--yes` to
apply) reconciles the episode list from the API against the directories on disk and removes orphans plus apply) reconciles the episode list from the API against the directories on disk and removes orphans plus
the `clips/` of completed episodes. Two guards matter — it aborts if any job is `running`/`pending` the `clips/` of completed episodes. Two guards matter — it aborts if any job is `running`/`pending`
(a running job has no `audio_file` yet, so its directory is indistinguishable from an orphan) and it (a running job has no `audio_file` yet, so its directory is indistinguishable from an orphan) and it

View file

@ -49,6 +49,8 @@ Der `open_notebook`-Container erreicht Ollama und die TTS/STT-Wrapper über
in Schritt 2) in Schritt 2)
- **`ufw`** (falls als Firewall aktiv) — Standardrichtlinie „deny incoming“ wird vorausgesetzt - **`ufw`** (falls als Firewall aktiv) — Standardrichtlinie „deny incoming“ wird vorausgesetzt
- **`openssl`**, **`ffmpeg`**, **`curl`**, **`python3`** - **`openssl`**, **`ffmpeg`**, **`curl`**, **`python3`**
- **`yt-dlp`** — nur für `scripts/add_video_source.py` (Videos ohne Untertitel); der übrige Stack
läuft auch ohne
--- ---
@ -80,11 +82,14 @@ chmod 600 .env
### 2.3 Container starten ### 2.3 Container starten
```bash ```bash
docker compose pull
docker compose up -d docker compose up -d
docker compose ps # beide Container sollten "Up" sein docker compose ps # beide Container sollten "Up" sein
``` ```
Die Images sind in `docker-compose.yml` auf einen **Digest gepinnt** (`pull_policy: missing`) —
`up -d` lädt sie beim ersten Mal und danach nie wieder unbemerkt eine andere Version. Aktualisiert
wird bewusst, siehe [Abschnitt 4](#4-updates).
Web-UI: `http://localhost:8502` · REST-API: `http://localhost:5055` (beide nur `127.0.0.1`). Web-UI: `http://localhost:8502` · REST-API: `http://localhost:5055` (beide nur `127.0.0.1`).
### 2.4 Firewall öffnen für TTS/STT ### 2.4 Firewall öffnen für TTS/STT
@ -123,7 +128,7 @@ journalctl --user -u open-notebook-tts -f
Für Stimmklonung mindestens eine Referenz-WAV in `services/voices/default.wav` ablegen (z. B. Für Stimmklonung mindestens eine Referenz-WAV in `services/voices/default.wav` ablegen (z. B.
Symlink auf eine eigene Sprachaufnahme, 1030s, WAV) — siehe Symlink auf eine eigene Sprachaufnahme, 1030s, WAV) — siehe
[`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md#stimmklonung). [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md#5-stimmklonung).
### 2.6 KI-Provider und Modelle registrieren ### 2.6 KI-Provider und Modelle registrieren
@ -161,6 +166,12 @@ curl -s http://127.0.0.1:8901/health # Chatterbox TTS
curl -s http://127.0.0.1:8902/health # faster-whisper STT curl -s http://127.0.0.1:8902/health # faster-whisper STT
``` ```
Gründlicher — prüft API, TTS, STT, YouTube-Extraktion und alle lokalen Anpassungen auf einmal:
```bash
./scripts/smoke_test.sh
```
UI öffnen (`http://localhost:8502`), ein Notebook anlegen, eine Quelle hinzufügen und chatten — UI öffnen (`http://localhost:8502`), ein Notebook anlegen, eine Quelle hinzufügen und chatten —
siehe [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md) für die weitere Bedienung. siehe [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md) für die weitere Bedienung.
@ -194,7 +205,7 @@ Ersteinrichtung bzw. nach Änderungen an den Unit-Dateien nötig.
--- ---
## 3a. Updates ## 4. Updates
Die Images sind **auf einen Digest gepinnt** — es läuft immer genau der Stand, der im Repo steht. Die Images sind **auf einen Digest gepinnt** — es läuft immer genau der Stand, der im Repo steht.
Weder ein `docker compose up -d` noch ein Reboot tauscht die Anwendung unbemerkt aus. Weder ein `docker compose up -d` noch ein Reboot tauscht die Anwendung unbemerkt aus.
@ -223,7 +234,7 @@ typischerweise Wochen voraus, aber unveröffentlicht. Wer den will, müsste selb
--- ---
## 4. Verzeichnisstruktur ## 5. Verzeichnisstruktur
``` ```
open-notebook/ open-notebook/
@ -256,7 +267,7 @@ open-notebook/
--- ---
## 5. Bekannte Stolpersteine ## 6. Bekannte Stolpersteine
Kurzreferenz — Details jeweils in [`CLAUDE.md`](CLAUDE.md): Kurzreferenz — Details jeweils in [`CLAUDE.md`](CLAUDE.md):
@ -297,4 +308,4 @@ Kurzreferenz — Details jeweils in [`CLAUDE.md`](CLAUDE.md):
Vorlagen enthalten zusätzlich `/no_think`, damit `qwen3.5:27b` bei langem Quellinhalt nicht Vorlagen enthalten zusätzlich `/no_think`, damit `qwen3.5:27b` bei langem Quellinhalt nicht
durch überlange Reasoning-Blöcke ungültiges JSON liefert. Änderst du die Vorlagen, danach durch überlange Reasoning-Blöcke ungültiges JSON liefert. Änderst du die Vorlagen, danach
`docker compose up -d --force-recreate open_notebook`. Details: `docker compose up -d --force-recreate open_notebook`. Details:
[`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md#10-troubleshooting) und [`CLAUDE.md`](CLAUDE.md). [`BEDIENUNGSANLEITUNG.md`](BEDIENUNGSANLEITUNG.md#11-troubleshooting) und [`CLAUDE.md`](CLAUDE.md).