This commit is contained in:
Christoph K.
2026-03-20 07:08:00 +01:00
parent 8163f906cc
commit b1a576f61e
9 changed files with 851 additions and 17 deletions

120
internal/diag/diag.go Normal file
View File

@@ -0,0 +1,120 @@
// diag Start-Diagnose: prüft Erreichbarkeit aller externen Dienste
package diag
import (
"fmt"
"log/slog"
"net"
"net/http"
"strings"
"time"
"my-brain-importer/internal/config"
)
// Result ist das Ergebnis einer einzelnen Prüfung.
type Result struct {
Name string
OK bool
Message string
}
// RunAll führt alle Verbindungs-Checks durch und gibt eine Zusammenfassung zurück.
// warnings = Dienste die konfiguriert aber nicht erreichbar sind.
func RunAll() (results []Result, allOK bool) {
allOK = true
cfg := config.Cfg
check := func(name string, ok bool, msg string) {
results = append(results, Result{Name: name, OK: ok, Message: msg})
if !ok {
allOK = false
}
}
// Qdrant
qdrantAddr := fmt.Sprintf("%s:%s", cfg.Qdrant.Host, cfg.Qdrant.Port)
ok, msg := tcpCheck(qdrantAddr)
check("Qdrant ("+qdrantAddr+")", ok, msg)
// LocalAI Chat
if cfg.Chat.URL != "" {
ok, msg = httpCheck(cfg.Chat.URL)
check("LocalAI Chat ("+cfg.Chat.URL+")", ok, msg)
}
// LocalAI Embedding (nur prüfen wenn andere URL als Chat)
if cfg.Embedding.URL != "" && cfg.Embedding.URL != cfg.Chat.URL {
ok, msg = httpCheck(cfg.Embedding.URL)
check("LocalAI Embedding ("+cfg.Embedding.URL+")", ok, msg)
}
// IMAP
if cfg.Email.Host != "" {
imapAddr := fmt.Sprintf("%s:%d", cfg.Email.Host, cfg.Email.Port)
ok, msg = tcpCheck(imapAddr)
check("IMAP ("+imapAddr+")", ok, msg)
}
return results, allOK
}
// Log gibt die Ergebnisse über slog aus.
func Log(results []Result) {
for _, r := range results {
if r.OK {
slog.Info("Dienst-Check", "dienst", r.Name, "status", "OK", "info", r.Message)
} else {
slog.Warn("Dienst-Check", "dienst", r.Name, "status", "FEHLER", "info", r.Message)
}
}
}
// Format gibt eine menschenlesbare Zusammenfassung zurück (für Discord/stdout).
func Format(results []Result, allOK bool) string {
var sb strings.Builder
sb.WriteString("🔍 **Start-Diagnose:**\n")
for _, r := range results {
icon := "✅"
if !r.OK {
icon = "❌"
}
fmt.Fprintf(&sb, "%s %s %s\n", icon, r.Name, r.Message)
}
if allOK {
sb.WriteString("\n✅ Alle Dienste erreichbar.")
} else {
sb.WriteString("\n⚠ Einige Dienste sind nicht erreichbar — Bot läuft, aber Funktionen könnten fehlen.")
}
return sb.String()
}
func tcpCheck(addr string) (bool, string) {
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
return false, "nicht erreichbar: " + err.Error()
}
conn.Close()
return true, "TCP OK"
}
func httpCheck(baseURL string) (bool, string) {
// Normalisiere URL: entferne trailing /v1 etc., hänge /v1/models an
url := strings.TrimRight(baseURL, "/")
if !strings.HasSuffix(url, "/models") {
// Gehe zur Basis-URL zurück und frage /v1/models
if idx := strings.LastIndex(url, "/v1"); idx >= 0 {
url = url[:idx]
}
url += "/v1/models"
}
client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(url)
if err != nil {
return false, "nicht erreichbar: " + err.Error()
}
resp.Body.Close()
return resp.StatusCode == http.StatusOK,
fmt.Sprintf("HTTP %d", resp.StatusCode)
}