Files
ai-agent/internal/diag/diag.go
Christoph K. 905981cd1e zwischenstand
2026-03-20 23:24:56 +01:00

128 lines
3.2 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 alle konfigurierten Accounts prüfen
for _, acc := range config.AllEmailAccounts() {
if acc.Host == "" {
continue
}
imapAddr := fmt.Sprintf("%s:%d", acc.Host, acc.Port)
label := acc.Name
if label == "" {
label = acc.User
}
ok, msg = tcpCheck(imapAddr)
check("IMAP "+label+" ("+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)
}