# 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 eine `teams.yaml` (siehe [Teams](#teams--zugriffskeys) unten) und ein `SESSION_SECRET`: ```bash cp teams.yaml.example teams.yaml # teams.yaml mit deinen Teams befüllen echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env ``` Standardwerte lokal: - **Daten-Ordner:** `data/` (jedes Team unter `data//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. ### Teams anlegen 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//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. ### 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, zeigt danach aber zunächst eine Team-Übersicht (`/switch-team`) zum Umschalten. ### 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 `/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//content/index.md` | `/` | | `data//content/setup.md` | `/setup` | | `data//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 `/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](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`/`POST` | `/switch-team` | Team-Umschaltung für den Admin-Zugang | | `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) |