package web import ( "encoding/json" "net/http" "net/netip" "time" "github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/buildinfo" "github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/diagnostics" "github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/model" ) // Only this allowlisted report is downloadable, never raw Docker logs, stored // error messages, audit messages, configuration, credentials or session data. func (s *Server) downloadDiagnostics(w http.ResponseWriter, r *http.Request, _ *model.Session) { settings, err := s.store.GetSettings(r.Context()) if err != nil { s.serverError(w, err) return } runs, err := s.store.RecentRuns(r.Context(), 100) if err != nil { s.serverError(w, err) return } type run struct { ID int64 `json:"id"` StartedAt time.Time `json:"started_at"` Status string `json:"status"` } report := struct { Format int `json:"format"` Version string `json:"version"` ExportedAt time.Time `json:"exported_at"` Network *diagnostics.Network `json:"current_network"` UpdaterEnabled bool `json:"updater_enabled"` IPv6Enabled bool `json:"ipv6_enabled"` IntervalSeconds int `json:"interval_seconds"` LastIPv4 string `json:"last_ipv4,omitempty"` LastIPv6 string `json:"last_ipv6,omitempty"` Runs []run `json:"recent_runs"` Events []diagnostics.Event `json:"events"` }{ Format: 1, Version: buildinfo.Version, ExportedAt: time.Now().UTC(), Network: diagnostics.CurrentNetwork(), UpdaterEnabled: settings.UpdaterEnabled, IPv6Enabled: settings.IPv6Enabled, IntervalSeconds: settings.UpdateIntervalSeconds, Events: diagnostics.Snapshot(), } if ip, err := netip.ParseAddr(settings.LastIPv4); err == nil && ip.Zone() == "" { report.LastIPv4 = ip.String() } if ip, err := netip.ParseAddr(settings.LastIPv6); err == nil && ip.Zone() == "" { report.LastIPv6 = ip.String() } for _, item := range runs { status := "unknown" switch item.Status { case "running", "ok", "error", "partial": status = item.Status } report.Runs = append(report.Runs, run{item.ID, item.StartedAt, status}) } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Disposition", `attachment; filename="domain-control-diagnostics.json"`) w.Header().Set("Cache-Control", "no-store") w.Header().Set("X-Content-Type-Options", "nosniff") encoder := json.NewEncoder(w) encoder.SetIndent("", " ") _ = encoder.Encode(report) }