From 0cf14847ce6bbeda8af4704dcb75126a8216b8bd Mon Sep 17 00:00:00 2001 From: mae Date: Mon, 9 Mar 2026 04:09:18 -0500 Subject: [PATCH] cmd/pkgserver: add status endpoint --- cmd/pkgserver/main.go | 117 +++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/cmd/pkgserver/main.go b/cmd/pkgserver/main.go index de9734f..8ae00c9 100644 --- a/cmd/pkgserver/main.go +++ b/cmd/pkgserver/main.go @@ -1,17 +1,20 @@ package main import ( + "bytes" "cmp" "context" "embed" "fmt" + "io" "log" "net/http" "os" "os/signal" + "path" "slices" + "strings" "syscall" - "unique" "hakurei.app/command" "hakurei.app/container/check" @@ -57,68 +60,112 @@ func serveStaticContent(w http.ResponseWriter, r *http.Request) { } } -func serveAPI(pi *PackageIndex) func(w http.ResponseWriter, r *http.Request) { +func serveAPI(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {} } +func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if index == nil { + http.Error(w, "index is nil", http.StatusInternalServerError) + return + } + base := path.Base(r.URL.Path) + name := strings.TrimSuffix(base, ".log") + p, ok := rosa.ResolveName(name) + if !ok { + http.NotFound(w, r) + return + } + m := rosa.GetMetadata(p) + pk, ok := index.names[m.Name] + if !ok { + http.NotFound(w, r) + return + } + if len(pk.status) > 0 { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.WriteHeader(http.StatusOK) + _, err := io.Copy(w, bytes.NewReader(pk.status)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + http.NotFound(w, r) + } + } +} + type SortOrders int const ( - DeclarationDescending SortOrders = iota - DeclarationAscending + DeclarationAscending SortOrders = iota + DeclarationDescending NameAscending NameDescending limitSortOrders ) -type PackageIndex [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry +type PackageIndex struct { + sorts [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry + names map[string]*PackageIndexEntry +} type PackageIndexEntry struct { - id unique.Handle[pkg.ID] - name string - description string - website string - version string + Name string `json:"name"` + Description string `json:"description"` + Website string `json:"website"` + Version string `json:"version"` status []byte } -func createPackageIndex(cache *pkg.Cache, report *rosa.Report) *PackageIndex { - var index PackageIndex - var work []PackageIndexEntry +func createPackageIndex(cache *pkg.Cache, report *rosa.Report) (_ *PackageIndex, err error) { + index := new(PackageIndex) + index.names = make(map[string]*PackageIndexEntry, rosa.PresetUnexportedStart) + work := make([]PackageIndexEntry, rosa.PresetUnexportedStart) + defer report.HandleAccess(&err)() for p := range rosa.PresetUnexportedStart { m := rosa.GetMetadata(p) v := rosa.Std.Version(p) a := rosa.Std.Load(p) id := cache.Ident(a) - status, n := report.ArtifactOf(id) - work[p] = PackageIndexEntry{ - id: id, - name: m.Name, - description: m.Description, - website: m.Website, - version: v, - status: status[:n], + st, n := report.ArtifactOf(id) + var status []byte + if n < 1 { + status = nil + } else { + status = st } + log.Printf("Processing package %s...\n", m.Name) + entry := PackageIndexEntry{ + Name: m.Name, + Description: m.Description, + Website: m.Website, + Version: v, + status: status, + } + work[p] = entry + index.names[m.Name] = &entry } for i, p := range work { - index[DeclarationAscending][i] = &p + index.sorts[DeclarationAscending][i] = &p } slices.Reverse(work) for i, p := range work { - index[DeclarationDescending][i] = &p + index.sorts[DeclarationDescending][i] = &p } slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int { - return cmp.Compare(a.name, b.name) + return cmp.Compare(a.Name, b.Name) }) for i, p := range work { - index[NameAscending][i] = &p + index.sorts[NameAscending][i] = &p } slices.Reverse(work) for i, p := range work { - index[NameDescending][i] = &p + index.sorts[NameDescending][i] = &p } - - return &index + return index, err } func main() { log.SetFlags(0) @@ -126,7 +173,7 @@ func main() { var ( flagBaseDir string - flagPort uint16 + flagPort int ) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) @@ -139,6 +186,7 @@ func main() { if err != nil { return err } + log.Println("baseDir:", baseDir) cache, err := pkg.Open(ctx, msg, 0, baseDir) if err != nil { return err @@ -147,12 +195,19 @@ func main() { if err != nil { return err } - defer report.HandleAccess(&err)() - index := createPackageIndex(cache, report) + log.Println("reportPath:", reportPath) + log.Println("indexing packages...") + index, err := createPackageIndex(cache, report) + if err != nil { + return err + } + log.Println("created package index") http.HandleFunc("GET /{$}", serveWebUI) http.HandleFunc("GET /favicon.ico", serveStaticContent) http.HandleFunc("GET /static/", serveStaticContent) http.HandleFunc("GET /api/", serveAPI(index)) + http.HandleFunc("GET /api/status/", serveStatus(index)) + log.Println("listening on", flagPort) err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil) if err != nil { return err