20 KiB
Wiki
Ein simples, datei-zentriertes Wiki. Markdown-Dateien sind die Datenbank — kein CMS, keine Datenbank, kein Build-Schritt.
Lokal starten
go run main.go
Der Server läuft dann auf http://localhost:8080.
Vor dem ersten Start brauchst du nur ein SESSION_SECRET:
echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env
Für mehrere Teams legst du vorher selbst eine teams.yaml an (siehe
Teams 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:
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 unterdata/<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 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/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):
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 <Team>"-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 unterdata/<id>/content/bleiben erhalten. Das letzte verbleibende Team lässt sich nicht löschen (eine leereteams.yamllä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 (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/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.
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)
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)
# 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:
# 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:
---
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
---
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
---
title: Versteckte Seite
hidden: true
---
Nicht in der Sidebar, aber über die URL erreichbar.
Deployment (Docker)
1. Image bauen
docker build -t wiki .
2. Container starten
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:
mkdir -p /data
3. Seite auf den Server deployen
# 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:
./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:
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
# 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-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:
{
"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:
{
"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). Einmalig auf dem Server:
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:
# 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:
cp .env.example .env
# .env mit deinen Werten befüllen
# .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:
.enventhält Secrets und ist in.gitignoreausgeschlossen — 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 | ~1–2 MB | <0,1 s |
| 500 | ~5–10 MB | <0,5 s |
| 1.000 | ~20–40 MB | ~1 s |
| 5.000 | ~150–250 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) oder ein dedizierter Suchdienst (z.B. Meilisearch) 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) |