Update projektplan and TODO with share analysis results

- projektplan: status section (server delivered, driver done), share
  analysis results in §2.1, customer checklist answers in §2.2, storage
  resolved in §2.3, new §2.6 on scoping the corpus, updated risks
- scripts/list-shares.ps1: enumerate SMB shares incl. paths, permissions
  and DFS namespaces on the file server
- TODO.md: restructured into blocking/server/done

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 14:10:14 +02:00
co-authored by Claude Opus 4.8
parent e7ef29c5d5
commit be6efa7b34
3 changed files with 243 additions and 38 deletions
@@ -1,11 +1,26 @@
# Projektplan — Setup phy-z-srv-gpu01 (LLM-Server mit SMB-Wissensbasis)
Date: 2026-07-10
Status: Plan — Server-Lieferung in einigen Monaten erwartet
Date: 2026-07-10 (Stand-Update: 2026-09-03)
Status: **In Umsetzung** — Server geliefert, Basis-Setup + GPU-Treiber fertig
Basiert auf: `20260706-hardware-assessment.md`, `20260706-software-assessment.md`, `20260707-deep-dive.md`
---
## Stand 2026-09-03 (Kurzfassung — Details in den jeweiligen Abschnitten)
**Erledigt:** Server geliefert und im Rack; Basis-Setup (Ubuntu 24.04.4, Nutzer,
Netz, RAID/Filesystem — s. `SETUP.md` im Repo-Root); GPU-Treiber via Ansible-Rolle
`nvidia_gpu` (595er-Branch, **open** Kernel-Module — Blackwell läuft nicht mit den
proprietären Modulen, s. `manuals/20260714-nvidia-driver-install.md`); Docker
installiert. Share-Analyse gelaufen (§2.1).
**Als Nächstes:** CIFS-Mounts + Container-Stack (Rollen `cifs_mounts`, `llm_stack`)
— entspricht Phasen 35 in §3.
**Kritischer offener Punkt:** Der Bestand ist rund **12× größer** als die in §1
gesetzte Schwelle. Ohne Eingrenzung des Korpus (Kundengespräch) ist Phase 5 nicht
sinnvoll umsetzbar — s. §2.1 und §2.6.
## 0. Rahmenbedingungen (fixiert)
| Punkt | Entscheidung |
@@ -49,6 +64,14 @@ Deep Dive §4.7). Ausschlaggebend, Stand 2026-07-10 verifiziert:
Onyx (mit Eigenbau-Sync) oder Eigenbau-Pipeline (Deep Dive §4.5) neu bewerten.
- Datei-genaue NTFS-ACLs werden doch Anforderung → Neubewertung (Deep Dive §4.4/4.5).
**Status 2026-09-03:** Trigger 1 ist **eingetreten** (1,26 Mio. Office-Dateien,
§2.1). Die Produktentscheidung bleibt dennoch bei Open WebUI — aber **nur unter
der Bedingung**, dass der Korpus auf einen kuratierten Teilbestand eingegrenzt
wird (§2.6). Ein ungefilterter Vollindex über 1,26 Mio. Dokumente ist mit Open
WebUI nicht sinnvoll zu betreiben (und liefert auch inhaltlich schlechte
Antworten — RAG über einen kompletten, ungeordneten Fileserver retourniert
überwiegend Rauschen).
Jira später: für *Indizierung* von Jira-Inhalten hat Onyx zwar native
Konnektoren, Open WebUI deckt das aber über Tools/MCP (Atlassian-MCP) bzw.
API-Workloads direkt gegen vLLM ab — kein Entscheidungskriterium für heute.
@@ -70,28 +93,54 @@ GPU-Server nötig). Zu erheben:
Ergebnis bestimmt: Korpus-Umfang (→ §1-Trigger), OCR-Pipeline ja/nein,
Index-Größe (→ Storage-Check §2.3), Erst-Indexierungsdauer.
### 2.2 Mit dem Kunden zu klären (Checkliste)
**Ergebnis (Lauf 2026-07-14, Skript `scripts/share-analysis.ps1` auf `D:` des
Fileservers Z-FILESERVER):**
- [ ] AD/LDAP: Bind-Service-Account, Basis-DN, Gruppe „LLM-Nutzer"
- [ ] **SMB-Service-Account read-only** für die Mounts (kein Nutzer-Account)
- [ ] Welche Shares/Unterordner genau in den Index? Ausschlussliste (HR o. ä.)
- [ ] DNS-Name (z. B. `ki.phytron.local`) + TLS (internes CA-Zertifikat?)
- [ ] Internetzugang des Servers (Modell-/Container-Downloads bei Installation;
danach einschränkbar)
- [ ] Protokollierung/Datenschutz: werden Chats gespeichert? Betriebsrat/DSGVO
früh einbinden
- [ ] Update-/Wartungsfenster und wer den Betrieb nach Übergabe verantwortet
- [ ] „Andere Workloads" konkretisieren (Jira: Inhalte durchsuchen vs.
Aktionen ausführen?) → bestimmt MIG-Layout später
| Kennzahl | Wert | Konsequenz |
|---|---|---|
| Gesamt | **3.322.175 Dateien, 2.224 GB** | Scan lief über `D:` gesamt, nicht je Share → Obergrenze, enthält evtl. nicht freigegebene Daten |
| `office` (extrahierbar) | **1.258.642 Dateien, 587 GB** | **12× über der §1-Schwelle** → Eingrenzung zwingend (§2.6) |
| `other` (unklassifiziert) | 1.575.752 Dateien, 934 GB | größter Block, **noch unidentifiziert**`D-file-types.csv` auswerten |
| Bilder / CAD / Archive / Medien | 403k / 60k / 12,6k / 6,2k Dateien | nicht extrahierbar → Erwartungsmanagement |
| PDFs | **924.076**, davon ~**18 % ohne Textlayer** (Stichprobe 200) | ≈ **166.000 Scan-PDFs → OCR**; eigener Zeit-/GPU-Aufwand, konkurriert mit vLLM |
| Änderungsrate | 94 % älter als 1 Jahr; nur ~158k in den letzten 365 Tagen | Sync-Frequenz unkritisch; **Alter ≠ Irrelevanz** (Datenblätter, Normen) → Filter primär ordner-/inhaltsbasiert, Aktualität nur sekundär |
| Duplikat-/Altversions-Muster | 33.179 Dateien | Filterregeln |
| Pfade > 240 Zeichen | 25.169 | Extraktions-Toolchain gezielt testen |
| Nicht-ASCII-Dateinamen | 126.111 | UTF-8 durchgängig (CIFS-Mount-Optionen → Extraktion → Index) |
### 2.3 Storage-Check (⚠️ vor Bestellung ggf. nachsteuern)
**Noch auszuwerten** (CSVs liegen auf dem Fileserver, noch nicht gesichtet):
`D-toplevel-folders.csv` (Größe/Anzahl je Top-Level-Ordner — die Grundlage für
die Include-/Exclude-Liste) und `D-file-types.csv` (Auflösung der `other`-Kategorie).
2× 960 GB NVMe, vermutlich RAID1 → **~960 GB nutzbar** für OS + Docker +
Modelle (~100 GB) + Vektor-DB + Extraktions-Cache. Strategie: **kein lokaler
Spiegel der Shares** — nur Index (extrahierter Text + Embeddings, erfahrungsgemäß
wenige % des Rohvolumens). Nach der Share-Analyse gegenrechnen; wenn eng:
zwei weitere NVMe nachordern (Slots frei) — das ist **vor** Lieferung am
billigsten zu ändern.
### 2.2 Mit dem Kunden zu klären (Checkliste) — Stand 2026-09-03
- [x] AD/LDAP: AD-Gruppe **`llm_users`** wird angelegt. *Offen:* Bind-Service-Account
und Basis-DN (werden für die LDAP-Konfiguration in Open WebUI gebraucht)
- [x] **SMB-Service-Account read-only** wird separat angelegt (kein Nutzer-Account)
- [ ] **Welche Shares/Unterordner in den Index?** — offen, wird besprochen;
**wichtigster Punkt**, s. §2.1/§2.6
- [x] DNS-Name **`chat.phytron.local`** (vorerst); **kein TLS/keine lokalen
Zertifikate** zu Beginn → reines HTTP im LAN. *Hinweis:* AD-Passwörter gehen
damit im Klartext über das Netz; für später ein internes CA-Zertifikat
einplanen (Reverse Proxy ist vorbereitet, nur Zertifikat fehlt)
- [x] Internetzugang: **unbeschränkt**, keine Einschränkung nötig
- [ ] Protokollierung/Datenschutz: **noch zu besprechen** (Chat-Logs =
Verhaltensdaten; Betriebsrat/DSGVO) — vor dem Rollout klären, nicht vor der
technischen Umsetzung
- [x] Betrieb/Wartung/Updates: **wir** (Softbox)
- [x] „Andere Workloads": **Jira vorerst nicht relevant** → MIG bleibt aus, die
volle GPU geht an den LLM-Stack
### 2.3 Storage-Check — ✅ erledigt, unkritisch
Ist-Stand auf dem Server: **876 GB nutzbar** (`/`, LVM), davon 818 GB frei.
Gegenrechnung mit §2.1: selbst ein Vollindex des `office`-Bestands
(1,26 Mio. Dokumente → grob 150250 GB inkl. extrahiertem Text, Embeddings und
Index-Overhead) plus Modelle (~100 GB) passt. Strategie bleibt: **kein lokaler
Spiegel der Shares**, nur Index. **Keine zusätzlichen NVMe nötig.**
Der begrenzende Faktor ist damit *nicht* der Plattenplatz, sondern Retrieval-Qualität
und Indexgröße (§2.6).
### 2.4 Ansible vorbereiten (im Repo, testbar ohne GPU)
@@ -116,6 +165,32 @@ Monaten neu sichten (§ Wiedereinstieg). Kleines deutsches Eval-Set (2030
Fragen mit erwarteten Antworten aus dem Bestand) schon jetzt mit dem Kunden
sammeln — das ist später das Abnahmekriterium.
### 2.6 Korpus eingrenzen (neu, wichtigster offener Punkt)
Die Share-Analyse (§2.1) hat 1,26 Mio. potenziell extrahierbare Dokumente
ergeben. „Alles indizieren" ist weder technisch sinnvoll noch inhaltlich
wünschenswert. Vorschlag für das Kundengespräch:
1. **Ordner statt Alter als Filter.** Aus `D-toplevel-folders.csv` gemeinsam mit
dem Kunden die Ordner markieren, die tatsächlich Wissensbasis sind
(Doku, Normen, Datenblätter, Handbücher, Projektdoku) — Rest bleibt außen vor.
Reiner Aktualitätsfilter wäre falsch: Datenblätter und Normen sind über Jahre
gültig.
2. **Ausschlusslisten** für Duplikate/Altversionen (33k Treffer), Archiv- und
Backup-Ordner, nicht extrahierbare Formate (CAD, Bilder, Medien).
3. **Zielgröße nennen.** Als Orientierung: eine gut funktionierende
Wissensbasis liegt eher im Bereich **10.000100.000 Dokumente**. Das ist
kein hartes Limit, aber der Bereich, in dem Retrieval-Qualität und
Indexierungsdauer beherrschbar bleiben.
4. **Iterativ starten.** Phase 5 zuerst mit *einem* gut abgegrenzten Share/Ordner,
Qualität am Eval-Set (§2.5) messen, dann erweitern. Das erspart einen
tagelangen Fehlversuch über den Gesamtbestand.
**Erwartungsmanagement gegenüber dem Kunden:** Die ursprüngliche Anforderung
lautete sinngemäß „unsere Dokumente auslesen". Realistisch ist „ein kuratierter
Teil unserer Dokumente, dafür mit guten Antworten". Das früh sagen — nicht erst,
wenn der Vollindex schlechte Treffer liefert.
## 3. Phasen ab Server-Lieferung
| Phase | Inhalt | Ergebnis/Abnahme |
@@ -133,9 +208,11 @@ sammeln — das ist später das Abnahmekriterium.
| Risiko | Umgang |
|---|---|
| Storage zu klein für Index (§2.3) | Share-Analyse → ggf. NVMe nachordern **vor** Lieferung |
| Viele gescannte PDFs → OCR-Aufwand & -Qualität | Analyse-Stichprobe; OCR-Phase einplanen; Erwartungen dämpfen |
| SM120/Treiber-Kinderkrankheiten (Deep Dive §1.6) | Treiber ≥ 580, NVIDIA-vLLM-Container, Burn-in in Phase 2, HPE-Support |
| ~~Storage zu klein für Index~~ | ✅ erledigt — 876 GB nutzbar, reicht (§2.3) |
| **Korpus 12× über der Planungsschwelle (§2.1)** | **Eingrenzung auf kuratierten Teilbestand (§2.6); ohne Kundenentscheid keine Phase 5** |
| ~166.000 Scan-PDFs → OCR-Aufwand & -Qualität | eigener Arbeitsblock, GPU-Konkurrenz zu vLLM einplanen; Erwartungen dämpfen |
| ~~SM120/Treiber-Kinderkrankheiten~~ | ✅ erledigt — Treiber 595 **open** läuft (proprietäre Module scheitern an `RmInitAdapter`) |
| Kein TLS zu Beginn (§2.2) | AD-Passwörter im Klartext im LAN; internes CA-Zertifikat nachrüsten, Reverse Proxy vorbereiten |
| Software-Stand veraltet bis Lieferung | Versionen **erst bei Installation pinnen**; Wiedereinstiegs-Checkliste |
| Erst-Indexierung dauert Tage | Teilbestand zuerst; Sync nachts; Dauer vorab abschätzen |
| RAM-Konkurrenz Extraktion/OCR vs. vLLM | vLLM hat feste VRAM-Quote; Indexer-Jobs drosseln (nice/cgroups); 128 GB reichen für OWUI-Stack |
@@ -144,12 +221,12 @@ sammeln — das ist später das Abnahmekriterium.
## 5. Wiedereinstiegs-Checkliste (wenn der Server da ist)
1. Dieses Dokument + Deep Dive lesen; offene Punkte aus §2.2 abhaken
2. Ergebnis der Share-Analyse vorliegen? (sonst zuerst!)
3. Neu sichten & **dann erst pinnen**: vLLM-Version (RTX-PRO-6000-Support),
Open WebUI + oikb Release Notes, NVIDIA-Treiber, Modell-Shortlist §2.5
4. Storage-Entscheidung §2.3 umgesetzt?
5. Dann Phasenplan §3 von oben abarbeiten; jede Phase = Ansible-Commit + ggf.
1. [x] Dieses Dokument + Deep Dive lesen; offene Punkte aus §2.2 abhaken
2. [x] Ergebnis der Share-Analyse vorliegen? → §2.1
3. [ ] Neu sichten & **dann erst pinnen**: vLLM-Version (RTX-PRO-6000-/SM120-Support),
Open WebUI + oikb Release Notes, Modell-Shortlist §2.5 — NVIDIA-Treiber ✅ (595-open)
4. [x] Storage-Entscheidung §2.3 → unkritisch, nichts nachzubestellen
5. [ ] Dann Phasenplan §3 von oben abarbeiten; jede Phase = Ansible-Commit + ggf.
Runbook unter `manuals/`
## 6. Bewusst offen gelassen (kein Jetzt-Entscheid nötig)
@@ -0,0 +1,100 @@
<#
.SYNOPSIS
Lists all SMB shares of a Windows file server incl. local path, size and
permissions — the basis for the include/exclude decision (projektplan §2.6).
.DESCRIPTION
Run ON the file server (or against a remote one via -ComputerName). Reports
per share: name, local path, description, share-level permissions, NTFS
groups, plus optional size/file count. Admin shares (C$, ADMIN$, IPC$) are
skipped unless -IncludeAdminShares is given.
Purely read-only. Requires local admin for Get-SmbShare (part of Windows
Server, no extra module needed).
.EXAMPLE
.\list-shares.ps1
.EXAMPLE
# remote, and with size per share (slow on large shares)
.\list-shares.ps1 -ComputerName Z-FILESERVER -WithSize
#>
param(
[string]$ComputerName = $env:COMPUTERNAME,
[switch]$IncludeAdminShares,
[switch]$WithSize,
[string]$OutDir = (Join-Path (Get-Location) "share-list")
)
$ErrorActionPreference = 'Continue'
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
Write-Host "=== SMB shares on $ComputerName ===" -ForegroundColor Cyan
$shares = Get-SmbShare -CimSession $ComputerName -ErrorAction Stop
if (-not $IncludeAdminShares) {
$shares = $shares | Where-Object { -not $_.Name.EndsWith('$') }
}
$result = foreach ($sh in $shares) {
Write-Host (" {0,-25} {1}" -f $sh.Name, $sh.Path)
# share-level permissions
$sharePerms = try {
(Get-SmbShareAccess -Name $sh.Name -CimSession $ComputerName -ErrorAction Stop |
ForEach-Object { "$($_.AccountName)=$($_.AccessRight)" }) -join '; '
} catch { 'n/a' }
# NTFS permissions (top level only) — who actually has access
$ntfsPerms = try {
$uncPath = "\\$ComputerName\$($sh.Name)"
((Get-Acl -LiteralPath $uncPath -ErrorAction Stop).Access |
ForEach-Object { "$($_.IdentityReference)=$($_.FileSystemRights)" } |
Select-Object -Unique | Select-Object -First 10) -join '; '
} catch { 'n/a' }
$sizeGB = $null; $fileCount = $null
if ($WithSize) {
Write-Host " measuring size ..." -ForegroundColor DarkGray
try {
$m = Get-ChildItem -LiteralPath "\\$ComputerName\$($sh.Name)" -Recurse -File -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$sizeGB = [Math]::Round($m.Sum / 1GB, 2)
$fileCount = $m.Count
} catch { }
}
[PSCustomObject]@{
Name = $sh.Name
Path = $sh.Path
Description = $sh.Description
SharePermissions = $sharePerms
NtfsPermissions = $ntfsPerms
SizeGB = $sizeGB
FileCount = $fileCount
}
}
$csv = Join-Path $OutDir "smb-shares.csv"
$result | Export-Csv $csv -NoTypeInformation -Encoding UTF8
$result | Format-Table Name, Path, SizeGB, FileCount -AutoSize
# DFS namespaces (if used) — often the path users actually see
Write-Host "`n=== DFS namespaces ===" -ForegroundColor Cyan
try {
$roots = Get-DfsnRoot -ErrorAction Stop
if ($roots) {
$dfs = foreach ($r in $roots) {
Get-DfsnFolder -Path "$($r.Path)\*" -ErrorAction SilentlyContinue | ForEach-Object {
$t = Get-DfsnFolderTarget -Path $_.Path -ErrorAction SilentlyContinue
[PSCustomObject]@{ DfsPath = $_.Path; Targets = ($t.TargetPath -join '; ') }
}
}
$dfs | Export-Csv (Join-Path $OutDir "dfs-namespaces.csv") -NoTypeInformation -Encoding UTF8
$dfs | Format-Table -AutoSize
} else { Write-Host " none" }
} catch {
Write-Host " no DFS role / not available on this host"
}
Write-Host "`nResults in: $OutDir" -ForegroundColor Green