diff --git a/backend/app/api/main.go b/backend/app/api/main.go index 2a553723..9472e339 100644 --- a/backend/app/api/main.go +++ b/backend/app/api/main.go @@ -6,8 +6,10 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/pressly/goose/v3" + "github.com/sysadminsmedia/homebox/backend/internal/sys/analytics" "net/http" "strings" + "time" "github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/httpkit/graceful" @@ -98,7 +100,6 @@ func run(cfg *config.Config) error { // ========================================================================= // Initialize Database & Repos - setupStorageDir(cfg) if strings.ToLower(cfg.Database.Driver) == "postgres" { @@ -196,5 +197,28 @@ func run(cfg *config.Config) error { // Start Reoccurring Tasks registerRecurringTasks(app, cfg, runner) + // Send analytics if enabled at around midnight UTC + if cfg.Options.AllowAnalytics { + analyticsTime := time.Second + runner.AddPlugin(NewTask("send-analytics", analyticsTime, func(ctx context.Context) { + for { + now := time.Now().UTC() + nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) + dur := time.Until(nextMidnight) + analyticsTime = dur + select { + case <-ctx.Done(): + return + case <-time.After(dur): + log.Debug().Msg("running send analytics") + err := analytics.Send(version, build()) + if err != nil { + log.Error().Err(err).Msg("failed to send analytics") + } + } + } + })) + } + return runner.Start(context.Background()) } diff --git a/backend/internal/sys/analytics/analytics.go b/backend/internal/sys/analytics/analytics.go index 8b88d348..6337eb45 100644 --- a/backend/internal/sys/analytics/analytics.go +++ b/backend/internal/sys/analytics/analytics.go @@ -11,6 +11,8 @@ import ( "github.com/rs/zerolog/log" ) +var startTime = time.Now() + type Data struct { Domain string `json:"domain"` Name string `json:"name"` @@ -18,7 +20,7 @@ type Data struct { Props map[string]interface{} `json:"props"` } -func Send(version, buildInfo string) { +func Send(version, buildInfo string) error { hostData, _ := host.Info() analytics := Data{ Domain: "homebox.software", @@ -32,22 +34,23 @@ func Send(version, buildInfo string) { "platform_version": hostData.PlatformVersion, "kernel_arch": hostData.KernelArch, "virt_type": hostData.VirtualizationSystem, + "uptime_min": time.Since(startTime).Minutes(), }, } jsonBody, err := json.Marshal(analytics) if err != nil { log.Error().Err(err).Msg("failed to marshal analytics data") - return + return err } bodyReader := bytes.NewReader(jsonBody) req, err := http.NewRequest("POST", "https://a.sysadmins.zone/api/event", bodyReader) if err != nil { log.Error().Err(err).Msg("failed to create analytics request") - return + return err } req.Header.Set("Content-Type", "application/json") - req.Header.Set("User-Agent", "Homebox/"+version+"/"+buildInfo+" (https://homebox.software)") + req.Header.Set("User-Agent", "Homebox/"+version+"/(https://homebox.software)") client := &http.Client{ Timeout: 10 * time.Second, @@ -56,7 +59,7 @@ func Send(version, buildInfo string) { res, err := client.Do(req) if err != nil { log.Error().Err(err).Msg("failed to send analytics request") - return + return err } defer func() { @@ -65,4 +68,5 @@ func Send(version, buildInfo string) { log.Error().Err(err).Msg("failed to close response body") } }() + return nil }