// 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) }