128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
// 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)
|
||
}
|