go-wiki/README.md

592 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Wiki
Ein simples, datei-zentriertes Wiki. Markdown-Dateien sind die Datenbank — kein CMS, keine Datenbank, kein Build-Schritt.
---
## Lokal starten
```bash
go run main.go
```
Der Server läuft dann auf [http://localhost:8080](http://localhost:8080).
Vor dem ersten Start brauchst du nur ein `SESSION_SECRET`:
```bash
echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env
```
Für **mehrere Teams** legst du vorher selbst eine `teams.yaml` an (siehe
[Teams](#teams--zugriffskeys) unten). Für den **Ein-Personen-Betrieb** brauchst
du das nicht: fehlt `teams.yaml` beim Start, wird automatisch eine mit einem
einzelnen Standard-Team samt zufällig erzeugtem Zugriffskey angelegt — der Key
steht dann im Server-Log:
```bash
go run main.go
# ...
# teams.yaml nicht gefunden — Standard-Team "default" angelegt (Datei: teams.yaml)
# Zugriffs-Key zum Einloggen: <zufälliger key>
```
Diesen Key zum Einloggen unter `/login` verwenden. Weitere Teams können danach
jederzeit von Hand in `teams.yaml` ergänzt werden (siehe unten) — der Server
überschreibt eine einmal angelegte `teams.yaml` nie automatisch.
Standardwerte lokal:
- **Daten-Ordner:** `data/` (jedes Team unter `data/<team-id>/content/`)
- **Admin-Token:** `dev-token`
- **Port:** `8080`
---
## Teams & Zugriffskeys
Das Wiki ist Multi-Tenant: jedes Team hat einen eigenen Zugriffskey und sieht nur
seine eigenen Markdown-Dateien — im Web, über die REST-API/`deploy.sh` und über MCP.
Ein globaler Admin-Token sieht alle Teams.
### Ein-Personen-Betrieb ("Single User")
Es gibt keinen eigenen team-losen Modus — aber auch keinen Zusatzaufwand: fehlt
`teams.yaml` beim Serverstart, wird automatisch eine mit genau einem Standard-Team
(`default`) und zufällig erzeugtem Key angelegt (siehe [Lokal
starten](#lokal-starten) oben). Damit läuft der Server sofort ohne manuelle
Konfiguration, verhält sich intern aber weiterhin wie Multi-Tenant mit einem Team.
Der Admin-Login (`ADMIN_TOKEN`) landet immer zuerst auf der [Team-Verwaltung](#admin-webinterface)
(`/admin/teams`) — auch wenn nur ein Team existiert. Von dort führt ein Klick
auf "Öffnen" direkt ins Wiki dieses einen Teams.
### Teams anlegen (mehrere Teams / Verwaltung)
Teams werden in `teams.yaml` konfiguriert (Vorlage: `teams.yaml.example`,
niemals committen — steht in `.gitignore`):
```yaml
teams:
- id: alpha
name: "Team Alpha"
key: "hier-sicheren-key-eintragen"
- id: beta
name: "Team Beta"
key: "hier-sicheren-key-eintragen"
```
`id` darf nur aus `a-z`, `0-9`, `-` und `_` bestehen (wird 1:1 als Ordnername
verwendet) und ist reserviert für `admin`. Für jedes Team wird beim Serverstart
automatisch `data/<id>/content/` angelegt — dort landen die Markdown-Dateien
dieses Teams.
Änderungen an `teams.yaml` (neuer Key, neues Team, umbenanntes Team, entferntes
Team) werden alle paar Sekunden automatisch übernommen — **kein Neustart nötig**.
Ein entferntes Team verschwindet nur aus der Registry, seine Dateien auf der
Platte bleiben unangetastet.
#### Admin-Webinterface
Statt `teams.yaml` von Hand zu bearbeiten, kannst du Teams auch unter
**`/admin/teams`** verwalten (nur mit dem globalen Admin-Zugang erreichbar,
Link auch in der Sidebar unter "Teams verwalten"). Der Admin-Login landet
immer direkt hier — die Team-Verwaltung ist zugleich die Team-Übersicht:
- **Anlegen** — ID + optionaler Anzeigename, der Zugriffskey wird automatisch
zufällig erzeugt und einmalig angezeigt (danach nicht mehr abrufbar — am
besten sofort kopieren/weitergeben).
- **Öffnen** — schaltet den Admin in dieses Team um und zeigt dessen Wiki unter
`/` (genau wie das frühere `/switch-team`, nur direkt aus der Tabelle statt
einer eigenen Auswahlseite). Solange ein Team aktiv ist, zeigt die
Team-Verwaltung oben einen "Zurück zu &lt;Team&gt;"-Link, in der Sidebar des
Wikis führt "Teams verwalten" jederzeit wieder zurück.
- **Umbenennen** — ändert nur den Anzeigenamen, ID und Key bleiben gleich.
- **Key neu erzeugen** — macht den bisherigen Key sofort ungültig, der neue
wird einmalig angezeigt.
- **Löschen** — entfernt das Team aus `teams.yaml`; die Markdown-Dateien unter
`data/<id>/content/` bleiben erhalten. Das letzte verbleibende Team lässt
sich nicht löschen (eine leere `teams.yaml` lässt den Server nicht mehr
starten).
Jede Änderung schreibt sofort `teams.yaml` neu und wirkt ohne Serverneustart —
technisch identisch zum manuellen Bearbeiten, nur ohne Editor. Alle Aktionen
landen zusätzlich im [Audit-Log](#audit-log) (`team_created`, `team_renamed`,
`team_key_regenerated`, `team_deleted`).
### Web-Zugriff
Beim Aufruf des Wikis im Browser wird zuerst der Zugriffskey abgefragt (`/login`).
Nach erfolgreichem Login sieht der Besucher nur die Seiten seines Teams — Navigation,
Suche und alles andere sind vollständig getrennt von anderen Teams. Der globale
`ADMIN_TOKEN` funktioniert ebenfalls als Login, landet danach aber zunächst auf der
[Team-Verwaltung](#admin-webinterface) (`/admin/teams`) statt direkt im Wiki eines
Teams.
### REST-API / `deploy.sh`
Der Bearer-Key im `Authorization`-Header bestimmt automatisch das Ziel-Team — ein
Team-Key kann nur in seinem eigenen Ordner schreiben/löschen. Mit dem `ADMIN_TOKEN`
muss zusätzlich das Zielteam über den Parameter `team` angegeben werden (Formfeld
bei Upload, Query-Parameter bei Delete). Details siehe Abschnitt
[Deployment](#3-seite-auf-den-server-deployen).
### MCP
Genau wie bei der REST-API bestimmt der mitgeschickte Key das Team. Beim
`ADMIN_TOKEN` ist zusätzlich der Header `X-Wiki-Team` erforderlich.
### Audit-Log
Upload, Delete, Login (Erfolg/Fehlschlag) und Webhook-Sync werden als JSON Lines
in `<DATA_ROOT>/audit.log` protokolliert — eine Zeile pro Ereignis, mit Team,
Aktion, Pfad, Quelle (`web`/`api`/`mcp`/`webhook`) und Zeitstempel. Direkt mit
`tail -f`/`jq` auswertbar, oder im Browser unter `/admin/audit` (nur mit
`ADMIN_TOKEN` erreichbar).
Da Zugriffskeys pro Team und nicht pro Person vergeben werden, zeigt das Log
"Team Alpha hat X gelöscht", aber nicht welche Person im Team das war.
---
## Binary bauen (ohne Docker)
Go kann ein einzelnes, vollständig selbst-enthaltenes Binary für jedes Betriebssystem bauen — von jedem Rechner aus, ohne dass das Zielsystem Go installiert haben muss.
Templates (`templates/`) und CSS (`static/`) sind per `//go:embed` direkt ins Binary eingebettet. Das Binary ist komplett eigenständig — es braucht keine externen Dateien.
### Für das aktuelle System (schnellster Weg)
```bash
go build -o wiki .
./wiki
```
### Für andere Systeme (Cross-Compilation)
Go verwendet zwei Umgebungsvariablen um das Zielsystem festzulegen:
- `GOOS` — Betriebssystem (`linux`, `darwin`, `windows`)
- `GOARCH` — Prozessorarchitektur (`amd64`, `arm64`)
```bash
# Linux — 64-bit Intel/AMD (z.B. eigener Server, VPS)
GOOS=linux GOARCH=amd64 go build -o wiki-linux .
# Linux — ARM (z.B. Raspberry Pi)
GOOS=linux GOARCH=arm64 go build -o wiki-linux-arm64 .
# Windows — 64-bit
GOOS=windows GOARCH=amd64 go build -o wiki-windows.exe .
# macOS — Apple Silicon (M1/M2/M3)
GOOS=darwin GOARCH=arm64 go build -o wiki-macos-arm64 .
# macOS — Intel
GOOS=darwin GOARCH=amd64 go build -o wiki-macos-amd64 .
```
### Binary betreiben
Das Binary braucht keine externen Dateien außer `teams.yaml`. Der Daten-Ordner
wird beim ersten Start automatisch angelegt falls er nicht existiert.
```
irgendwo/
wiki ← das Binary — mehr braucht es nicht
teams.yaml ← Team-Konfiguration (siehe teams.yaml.example)
.env ← optional
```
Starten:
```bash
# Mit .env-Datei
./wiki
# Oder mit Umgebungsvariablen direkt
ADMIN_TOKEN=mein-token SESSION_SECRET=$(openssl rand -hex 32) ./wiki
```
---
## Neue Seite anlegen
Markdown-Datei mit Frontmatter erstellen:
```markdown
---
title: Meine Seite
tags: [beispiel]
---
# Überschrift
Inhalt hier...
```
**Dateiname bestimmt die URL:**
| Datei | URL (innerhalb des jeweiligen Teams) |
|---|---|
| `data/<team>/content/index.md` | `/` |
| `data/<team>/content/setup.md` | `/setup` |
| `data/<team>/content/projekte/notizen.md` | `/projekte/notizen` |
Unterordner einfach anlegen — die Navigation baut sich automatisch als aufklappbare Baumstruktur daraus auf.
### Alle Frontmatter-Felder
| Feld | Typ | Bedeutung |
|---|---|---|
| `title` | String | Anzeigename in Navigation und Browser-Tab |
| `tags` | Liste | Schlagwörter, z.B. `[projekt, intern]` |
| `password` | String | Seite mit Passwort schützen |
| `hidden` | Boolean | Seite aus Navigation ausblenden (URL bleibt erreichbar) |
### Seite mit Passwortschutz
```markdown
---
title: Interne Notizen
password: mein-passwort
---
Nur für mich.
```
Besucher sehen ein Passwort-Formular. Nach erfolgreicher Eingabe wird ein Cookie gesetzt — beim nächsten Besuch direkt zugänglich.
### Seite aus Navigation ausblenden
```markdown
---
title: Versteckte Seite
hidden: true
---
Nicht in der Sidebar, aber über die URL erreichbar.
```
---
## Deployment (Docker)
### 1. Image bauen
```bash
docker build -t wiki .
```
### 2. Container starten
```bash
docker run -d \
--name wiki \
-p 80:8080 \
-e ADMIN_TOKEN=dein-geheimer-token \
-e SESSION_SECRET=dein-signier-secret \
-v /data:/data \
-v /pfad/zu/teams.yaml:/teams.yaml \
wiki
```
| Option | Bedeutung |
|---|---|
| `-p 80:8080` | Port 80 auf dem Host → Port 8080 im Container |
| `-e ADMIN_TOKEN=...` | Globaler Admin-Zugang (sieht alle Teams) |
| `-e SESSION_SECRET=...` | Signiert die Web-Session-Cookies — Pflichtfeld |
| `-v /data:/data` | Daten-Ordner (ein Unterordner pro Team) auf dem Host einbinden |
| `-v .../teams.yaml:/teams.yaml` | Team-Konfiguration einbinden |
Der Ordner `/data` muss auf dem Server existieren:
```bash
mkdir -p /data
```
### 3. Seite auf den Server deployen
```bash
# Team-Key (oder Admin-Token, siehe unten) und Server-URL setzen
export WIKI_TOKEN=team-zugriffskey
export WIKI_SERVER=https://wiki.example.com
# Datei hochladen (Pfad ist relativ zum Team-Ordner)
./deploy.sh lokale-datei.md /ziel/pfad.md
```
Beispiele:
```bash
./deploy.sh index.md /index.md
./deploy.sh notizen.md /projekte/notizen.md
```
Mit dem globalen Admin-Token statt einem Team-Key muss zusätzlich `WIKI_TEAM`
gesetzt sein:
```bash
WIKI_TOKEN=dein-admin-token WIKI_TEAM=alpha ./deploy.sh notizen.md /projekte/notizen.md
```
Die Seite ist sofort erreichbar — kein Neustart nötig.
### Seite löschen
```bash
# Mit Team-Key:
curl -X DELETE \
-H "Authorization: Bearer team-zugriffskey" \
"https://wiki.example.com/api/delete?path=/projekte/notizen.md"
# Mit Admin-Token (Team explizit angeben):
curl -X DELETE \
-H "Authorization: Bearer dein-admin-token" \
"https://wiki.example.com/api/delete?path=/projekte/notizen.md&team=alpha"
```
---
## MCP-Integration (KI-Tools)
Das Wiki stellt einen [MCP](https://modelcontextprotocol.io)-Endpoint bereit. KI-Tools wie **Cline**, **Opencode** oder **Claude Desktop** können darüber direkt auf das Wiki zugreifen — Seiten lesen, suchen, erstellen und löschen.
### Verbindung einrichten
Lokal: `http://localhost:8080/mcp` — auf dem Server: `https://wiki.example.com/mcp`
Der `headers`-Block sorgt dafür dass der Key bei jeder Verbindung automatisch mitgeschickt wird — kein manuelles Eingeben nötig. Der Key bestimmt automatisch das Team — normalerweise reicht `Authorization` allein:
```json
{
"mcpServers": {
"wiki": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer team-zugriffskey"
}
}
}
}
```
Genau dieses JSON funktioniert unverändert für **Cline** (`.vscode/mcp.json`),
**Opencode** (`~/.config/opencode/config.json`, dort unter `"mcp"` statt
`"mcpServers"` und mit zusätzlichem `"type": "remote"`) und **Claude Desktop**
(`~/Library/Application Support/Claude/claude_desktop_config.json`).
Mit dem globalen `ADMIN_TOKEN` statt einem Team-Key muss zusätzlich der Header
`X-Wiki-Team` gesetzt werden:
```json
{
"mcpServers": {
"wiki": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer dein-admin-token",
"X-Wiki-Team": "alpha"
}
}
}
}
```
Im Produktionsbetrieb `http://localhost:8080` durch `https://wiki.example.com` ersetzen.
### Resources (Daten lesen)
MCP-Resources sind Dokumente die das KI-Tool direkt in seinen Kontext laden kann.
| Resource URI | Beschreibung |
|---|---|
| `wiki://pages` | Alle Seiten als JSON (Titel, URL, Tags) |
| `wiki://page/{+pfad}` | Eine Seite als Markdown, z.B. `wiki://page/projekte/notizen` |
### Tools (Aktionen ausführen)
| Tool | Beschreibung |
|---|---|
| `search_pages` | Volltextsuche mit BM25-Ranking — Titel-Treffer zuerst, dann Body |
| `upload_page` | Neue Seite erstellen oder bestehende überschreiben — aktualisiert `_index.md` automatisch |
| `delete_page` | Eine Seite löschen |
| `rebuild_index` | `_index.md` manuell neu generieren — nach Bulk-Uploads sinnvoll |
### Wiki-Index für KI-Navigation
Nach dem ersten `upload_page`-Aufruf wird automatisch eine `_index.md` erzeugt — eine nach Tags gegliederte Übersicht aller Seiten. Das KI-Tool kann diese Resource lesen um sich schnell zu orientieren, bevor es gezielt mit `search_pages` sucht:
```
"Lies wiki://page/_index um einen Überblick zu bekommen, dann such nach 'nginx'"
```
### Beispiel-Prompts
```
"Was steht in meinem Wiki über Projekt X?"
"Suche nach allen Seiten die das Wort 'Docker' enthalten."
"Erstelle eine neue Wiki-Seite /projekte/ideen.md mit einer Liste meiner Ideen."
"Fasse alle Seiten im Ordner /projekte zusammen."
"Aktualisiere die Seite /setup mit den neuen Docker-Befehlen."
```
---
## Git-Synchronisation (Webhook)
Mehrere Nutzer können Wiki-Seiten über ein gemeinsames Git-Repository bearbeiten. Bei jedem Push wird der Server automatisch per Webhook benachrichtigt und zieht die Änderungen rein.
### 1. Daten-Ordner als Git-Repository einrichten
Alle Teams liegen in einem gemeinsamen Repository (ein Ordner pro Team, siehe
[Teams & Zugriffskeys](#teams--zugriffskeys)). Einmalig auf dem Server:
```bash
cd /data
git init
git remote add origin https://github.com/dein-user/wiki-docs.git
git pull origin main
```
### 2. WEBHOOK_SECRET setzen
Das Secret muss identisch sein mit dem was du beim Git-Anbieter einträgst:
```bash
# In .env (lokal) oder als Docker-Umgebungsvariable
WEBHOOK_SECRET=dein-geheimes-webhook-secret
```
### 3. Webhook beim Git-Anbieter einrichten
**GitHub** — Repository → Settings → Webhooks → Add webhook:
```
Payload URL: https://wiki.example.com/api/webhook
Content type: application/json
Secret: dein-geheimes-webhook-secret
Events: Just the push event
```
**GitLab** — Repository → Settings → Webhooks:
```
URL: https://wiki.example.com/api/webhook
Secret token: dein-geheimes-webhook-secret
Trigger: Push events
```
**Bitbucket** — Repository → Repository settings → Webhooks → Add webhook:
```
URL: https://wiki.example.com/api/webhook
Secret: dein-geheimes-webhook-secret
Triggers: Repository push
```
### Ablauf nach einem Push
```
git push → GitHub/GitLab/Bitbucket → POST /api/webhook
Signatur prüfen
git pull (im Daten-Wurzelverzeichnis)
Cache jedes Teams neu aufbauen
Seiten sofort live
```
Der Webhook erkennt den Anbieter automatisch anhand des Request-Headers — kein separater Endpoint pro Anbieter nötig.
---
## Lokal mit .env arbeiten
Statt Umgebungsvariablen einzeln zu setzen kannst du eine `.env`-Datei verwenden:
```bash
cp .env.example .env
# .env mit deinen Werten befüllen
```
```bash
# .env
ADMIN_TOKEN=mein-token
SESSION_SECRET=ein-langer-zufaelliger-string
WEBHOOK_SECRET=mein-webhook-secret
```
Der Server lädt `.env` automatisch beim Start. Echte Umgebungsvariablen (z.B. aus `docker run -e`) haben immer Vorrang über `.env`.
> **Wichtig:** `.env` enthält Secrets und ist in `.gitignore` ausgeschlossen — niemals committen.
---
## Konfiguration (Umgebungsvariablen)
| Variable | Standard | Bedeutung |
|---|---|---|
| `ADMIN_TOKEN` | `dev-token` | Globaler Admin-Zugang — sieht/verwaltet alle Teams |
| `SESSION_SECRET` | — | Pflichtfeld. Signiert die Web-Session-Cookies (HMAC) |
| `TEAMS_CONFIG` | `teams.yaml` | Pfad zur Team-Konfiguration |
| `DATA_ROOT` | `data` | Wurzelverzeichnis, unter dem jedes Team seinen `<id>/content`-Ordner bekommt |
| `WEBHOOK_SECRET` | — | Optional. Secret für den Git-Webhook — fehlt er, ist `/api/webhook` deaktiviert |
| `PORT` | `8080` | Port auf dem der Server lauscht |
---
## Suche
Das Wiki hat eine eingebaute Volltextsuche auf Basis von **BM25** (der Algorithmus hinter Elasticsearch & Co.).
### Web-Suche
Das Suchfeld befindet sich in der Sidebar — jede Seite. Suche über `/search?q=suchbegriff`.
Ergebnisse sind nach Relevanz sortiert: Treffer im **Titel** werden höher gewichtet als Treffer im Body, Treffer in **Tags** stärker als normaler Fließtext.
### Wie der Index aufgebaut ist
Der Suchindex wird beim Serverstart einmalig aus allen `.md`-Dateien aufgebaut und komplett im Arbeitsspeicher gehalten. Bei jedem `upload_page` (MCP oder REST-API) wird der Index sofort aktualisiert — kein Neustart nötig.
---
## Skalierbarkeit
Der BM25-Index liegt vollständig im RAM. Die folgende Tabelle zeigt grobe Richtwerte für typische Wiki-Seiten (~500 Wörter, ~300 unique Tokens):
| Dokumente | RAM-Verbrauch | Startzeit |
|---|---|---|
| 100 | ~12 MB | <0,1 s |
| 500 | ~510 MB | <0,5 s |
| 1.000 | ~2040 MB | ~1 s |
| 5.000 | ~150250 MB | ~5 s |
| 10.000+ | >500 MB | >10 s |
**Faustregel:** Bis ~2.000 Seiten ohne Einschränkungen, bis ~5.000 noch vertretbar. Für größere Datenmengen wäre ein persistenter Index auf Disk (z.B. [Bleve](https://github.com/blevesearch/bleve)) oder ein dedizierter Suchdienst (z.B. [Meilisearch](https://www.meilisearch.com/)) der nächste Schritt.
**Bekannte Grenzen der aktuellen Implementierung:**
- Kein Fuzzy-Matching — Tippfehler werden nicht korrigiert
- Kein Disk-Persistence — Index wird bei jedem Neustart neu aufgebaut
- Keine Pagination — maximal 20 Suchergebnisse
---
## Übersicht der HTTP-Endpoints
| Methode | Pfad | Beschreibung |
|---|---|---|
| `GET` | `/:pfad` | Wiki-Seite anzeigen (Team-Session erforderlich) |
| `GET` | `/search` | Volltextsuche (`?q=suchbegriff`, Team-Session erforderlich) |
| `GET` | `/static/*` | CSS und statische Dateien |
| `GET`/`POST` | `/login` | Team-Key eingeben, Session-Cookie setzen |
| `GET`/`POST` | `/logout` | Session-Cookie löschen |
| `GET` | `/admin/teams` | Teams verwalten — Übersicht, Anlegen-Formular (Admin-Zugang erforderlich) |
| `POST` | `/admin/teams/create` | Team anlegen (Admin-Zugang erforderlich) |
| `POST` | `/admin/teams/rename` | Team umbenennen (Admin-Zugang erforderlich) |
| `POST` | `/admin/teams/regenerate-key` | Zugriffskey eines Teams neu erzeugen (Admin-Zugang erforderlich) |
| `POST` | `/admin/teams/delete` | Team löschen (Admin-Zugang erforderlich) |
| `GET` | `/admin/audit` | Audit-Log ansehen (Admin-Zugang erforderlich) |
| `POST` | `/switch-team` | Admin schaltet in ein Team um (ausgelöst von "Öffnen" in `/admin/teams`) |
| `POST` | `/auth/:pfad` | Seiten-Passwort absenden, Cookie setzen |
| `POST` | `/api/upload` | Datei hochladen (Team-Key oder Admin-Token erforderlich) |
| `DELETE` | `/api/delete` | Datei löschen (Team-Key oder Admin-Token erforderlich) |
| `POST` | `/mcp` | MCP Nachrichten empfangen (Team-Key oder Admin-Token erforderlich) |
| `POST` | `/api/webhook` | Git-Webhook empfangen — GitHub, GitLab, Bitbucket (Signatur erforderlich) |