simple markdown wiki, written in go.
Find a file
2026-07-27 23:10:49 +02:00
handler Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
middleware initial commit 2026-07-14 20:46:19 +02:00
render initial commit 2026-07-14 20:46:19 +02:00
static Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
templates Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
.dockerignore initial commit 2026-07-14 20:46:19 +02:00
.env.example initial commit 2026-07-14 20:46:19 +02:00
.gitignore initial commit 2026-07-14 20:46:19 +02:00
deploy.sh initial commit 2026-07-14 20:46:19 +02:00
Dockerfile initial commit 2026-07-14 20:46:19 +02:00
go.mod initial commit 2026-07-14 20:46:19 +02:00
go.sum initial commit 2026-07-14 20:46:19 +02:00
main.go Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
README.md Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
REPOMAP.md Erweiterung Admin Interface für bessere Teamverwaltung 2026-07-27 23:10:49 +02:00
teams.yaml.example initial commit 2026-07-14 20:46:19 +02:00

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 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 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 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 (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: .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) 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)