108 lines
4.1 KiB
Go
108 lines
4.1 KiB
Go
|
|
package collector
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"forge.lthn.ai/core/cli/pkg/lab"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Services struct {
|
||
|
|
store *lab.Store
|
||
|
|
services []lab.Service
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewServices(s *lab.Store) *Services {
|
||
|
|
return &Services{
|
||
|
|
store: s,
|
||
|
|
services: []lab.Service{
|
||
|
|
// Source Control
|
||
|
|
{Name: "Forgejo (primary)", URL: "https://forge.lthn.io", Category: "Source Control", Machine: "m3-ultra", Icon: "git"},
|
||
|
|
{Name: "Forgejo (dev)", URL: "https://dev.lthn.io", Category: "Source Control", Machine: "snider-linux", Icon: "git"},
|
||
|
|
{Name: "Forgejo (QA)", URL: "https://qa.lthn.io", Category: "Source Control", Machine: "gateway", Icon: "git"},
|
||
|
|
{Name: "Forgejo (devops)", URL: "https://devops.lthn.io", Category: "Source Control", Machine: "gateway", Icon: "git"},
|
||
|
|
{Name: "Forgejo Pages", URL: "https://host-uk.pages.lthn.io", Category: "Source Control", Machine: "snider-linux", Icon: "web"},
|
||
|
|
|
||
|
|
// CI/CD
|
||
|
|
{Name: "Woodpecker CI", URL: "https://ci.lthn.io", Category: "CI/CD", Machine: "snider-linux", Icon: "ci"},
|
||
|
|
|
||
|
|
// Monitoring
|
||
|
|
{Name: "Grafana", URL: "https://grafana.lthn.io", Category: "Monitoring", Machine: "snider-linux", Icon: "chart"},
|
||
|
|
{Name: "Traefik Dashboard", URL: "https://traefik.lthn.io", Category: "Monitoring", Machine: "snider-linux", Icon: "route"},
|
||
|
|
{Name: "Portainer", URL: "https://portainer.lthn.io", Category: "Monitoring", Machine: "snider-linux", Icon: "container"},
|
||
|
|
{Name: "MantisBT", URL: "https://bugs.lthn.io", Category: "Monitoring", Machine: "snider-linux", Icon: "bug"},
|
||
|
|
|
||
|
|
// AI & Models
|
||
|
|
{Name: "Ollama API", URL: "https://ollama.lthn.io", Category: "AI", Machine: "snider-linux", Icon: "ai"},
|
||
|
|
{Name: "AnythingLLM", URL: "https://anythingllm.lthn.io", Category: "AI", Machine: "snider-linux", Icon: "ai"},
|
||
|
|
{Name: "Argilla", URL: "https://argilla.lthn.io", Category: "AI", Machine: "snider-linux", Icon: "data"},
|
||
|
|
{Name: "Lab Helper API", URL: "http://10.69.69.108:9800", Category: "AI", Machine: "m3-ultra", Icon: "api"},
|
||
|
|
{Name: "Lab Dashboard", URL: "https://lab.lthn.io", Category: "AI", Machine: "snider-linux", Icon: "web"},
|
||
|
|
|
||
|
|
// Media & Content
|
||
|
|
{Name: "Jellyfin", URL: "https://media.lthn.io", Category: "Media", Machine: "m3-ultra", Icon: "media"},
|
||
|
|
{Name: "Immich Photos", URL: "https://photos.lthn.io", Category: "Media", Machine: "m3-ultra", Icon: "photo"},
|
||
|
|
|
||
|
|
// Social
|
||
|
|
{Name: "Mastodon", URL: "https://fedi.lthn.io", Category: "Social", Machine: "snider-linux", Icon: "social"},
|
||
|
|
{Name: "Mixpost", URL: "https://social.lthn.io", Category: "Social", Machine: "snider-linux", Icon: "social"},
|
||
|
|
|
||
|
|
// i18n
|
||
|
|
{Name: "Weblate", URL: "https://i18n.lthn.io", Category: "Translation", Machine: "snider-linux", Icon: "i18n"},
|
||
|
|
|
||
|
|
// Infra
|
||
|
|
{Name: "dAppCo.re CDN", URL: "https://dappco.re", Category: "Infrastructure", Machine: "snider-linux", Icon: "cdn"},
|
||
|
|
{Name: "lthn.ai Landing", URL: "https://lthn.ai", Category: "Infrastructure", Machine: "snider-linux", Icon: "web"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Services) Name() string { return "services" }
|
||
|
|
|
||
|
|
func (s *Services) Collect(ctx context.Context) error {
|
||
|
|
client := &http.Client{
|
||
|
|
Timeout: 5 * time.Second,
|
||
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||
|
|
return http.ErrUseLastResponse // don't follow redirects
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := range s.services {
|
||
|
|
s.services[i].Status = checkHealth(ctx, client, s.services[i].URL)
|
||
|
|
}
|
||
|
|
|
||
|
|
result := make([]lab.Service, len(s.services))
|
||
|
|
copy(result, s.services)
|
||
|
|
s.store.SetServices(result)
|
||
|
|
s.store.SetError("services", nil)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func checkHealth(ctx context.Context, client *http.Client, url string) string {
|
||
|
|
// Try HEAD first, fall back to GET if HEAD fails.
|
||
|
|
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
||
|
|
if err != nil {
|
||
|
|
return "unavailable"
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
// Retry with GET (some servers reject HEAD).
|
||
|
|
req2, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||
|
|
if req2 == nil {
|
||
|
|
return "unavailable"
|
||
|
|
}
|
||
|
|
resp, err = client.Do(req2)
|
||
|
|
if err != nil {
|
||
|
|
return "unavailable"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode < 500 {
|
||
|
|
return "ok"
|
||
|
|
}
|
||
|
|
return "unavailable"
|
||
|
|
}
|