Files
scriptos 3ede89322f
Build Test Docker Image / docker-test (pull_request) Successful in 1m26s
Run Tests / test (pull_request) Successful in 4m53s
fix: Überflüssige Leerzeilen am Ende von startup.go entfernt
2026-03-29 23:22:10 +02:00

120 lines
3.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
package main
import (
"context"
"log/slog"
"net/http"
"net/url"
"time"
"git.techniverse.net/scriptos/mailcow-birthday-daemon/pkg/mailcow"
)
const (
// startupBackoffInit ist das anfängliche Warteintervall zwischen
// Erreichbarkeitsprüfungen (2 Sekunden).
startupBackoffInit = 2 * time.Second
// startupBackoffMax begrenzt das maximale Warteintervall auf 30 Sekunden.
startupBackoffMax = 30 * time.Second
// startupRequestTimeout begrenzt einzelne HTTP-Anfragen während der
// Startphase auf 10 Sekunden.
startupRequestTimeout = 10 * time.Second
)
// waitForServices prüft in einer Schleife, ob die Mailcow-API und SOGo
// erreichbar sind. Erst wenn beide Dienste antworten, kehrt die Funktion
// zurück. Bei einem Shutdown-Signal (ctx.Done) wird sofort nil
// zurückgegeben.
func waitForServices(ctx context.Context, httpClient *http.Client, baseURL, apiKey string) error {
slog.Info("checking connectivity to mailcow API and SOGo")
backoff := startupBackoffInit
for {
apiOK := checkMailcowAPI(ctx, httpClient, baseURL, apiKey)
sogoOK := checkSOGo(ctx, httpClient, baseURL)
if apiOK && sogoOK {
slog.Info("all services reachable, proceeding with initialization")
return nil
}
// Klare Meldung, welcher Dienst noch nicht bereit ist.
if !apiOK && !sogoOK {
slog.Warn("mailcow API and SOGo not reachable yet, retrying", "backoff", backoff)
} else if !apiOK {
slog.Warn("mailcow API not reachable yet, retrying", "backoff", backoff)
} else {
slog.Warn("SOGo not reachable yet, retrying", "backoff", backoff)
}
// Auf nächsten Versuch oder Shutdown-Signal warten.
timer := time.NewTimer(backoff)
select {
case <-timer.C:
case <-ctx.Done():
timer.Stop()
slog.Warn("shutdown signal received during startup connectivity check, exiting")
return nil
}
// Exponentielles Backoff verdoppeln, aber nicht über das Maximum.
backoff *= 2
if backoff > startupBackoffMax {
backoff = startupBackoffMax
}
}
}
// checkMailcowAPI führt einen einfachen API-Aufruf (GetMailboxes) durch,
// um zu prüfen, ob die Mailcow-API erreichbar ist und der API-Key gültig
// ist. Bei Erfolg wird true zurückgegeben.
func checkMailcowAPI(ctx context.Context, httpClient *http.Client, baseURL, apiKey string) bool {
reqCtx, cancel := context.WithTimeout(ctx, startupRequestTimeout)
defer cancel()
mc := mailcow.New(httpClient, baseURL, apiKey)
_, err := mc.GetMailboxes(reqCtx)
if err != nil {
slog.Debug("mailcow API check failed", "err", err)
return false
}
slog.Debug("mailcow API is reachable")
return true
}
// checkSOGo prüft die Erreichbarkeit von SOGo über einen HTTP-GET auf den
// SOGo-Basispfad. Jeder HTTP-Statuscode (auch Redirects oder 401) gilt als
// "erreichbar", da SOGo in diesem Fall antwortet. Nur Netzwerkfehler
// (connection refused, timeout) gelten als "nicht erreichbar".
func checkSOGo(ctx context.Context, httpClient *http.Client, baseURL string) bool {
reqCtx, cancel := context.WithTimeout(ctx, startupRequestTimeout)
defer cancel()
sogoURL, err := url.JoinPath(baseURL, "SOGo/")
if err != nil {
slog.Debug("SOGo URL construction failed", "err", err)
return false
}
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, sogoURL, nil)
if err != nil {
slog.Debug("SOGo request creation failed", "err", err)
return false
}
resp, err := httpClient.Do(req)
if err != nil {
slog.Debug("SOGo check failed", "err", err)
return false
}
defer resp.Body.Close()
// SOGo ist erreichbar der konkrete Statuscode ist irrelevant,
// solange eine HTTP-Antwort zurückkommt.
slog.Debug("SOGo is reachable", "status", resp.StatusCode)
return true
}