forked from security/hakurei
175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"embed"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"slices"
|
|
"syscall"
|
|
"unique"
|
|
|
|
"hakurei.app/command"
|
|
"hakurei.app/container/check"
|
|
"hakurei.app/internal/pkg"
|
|
"hakurei.app/internal/rosa"
|
|
"hakurei.app/message"
|
|
)
|
|
|
|
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts"
|
|
//go:embed ui/*
|
|
var content embed.FS
|
|
|
|
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Printf("serveWebUI: %s\n", r.URL.Path)
|
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Expires", "0")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("X-XSS-Protection", "1")
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
|
|
http.ServeFileFS(w, r, content, "ui/index.html")
|
|
}
|
|
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Printf("serveStaticContent: %s\n", r.URL.Path)
|
|
switch r.URL.Path {
|
|
case "/static/style.css":
|
|
darkTheme := r.CookiesNamed("dark_theme")
|
|
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
|
|
http.ServeFileFS(w, r, content, "ui/static/dark.css")
|
|
} else {
|
|
http.ServeFileFS(w, r, content, "ui/static/light.css")
|
|
}
|
|
break
|
|
case "/favicon.ico":
|
|
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
|
break
|
|
case "/static/index.js":
|
|
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
|
break
|
|
default:
|
|
http.NotFound(w, r)
|
|
|
|
}
|
|
}
|
|
func serveAPI(pi *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {}
|
|
}
|
|
|
|
type SortOrders int
|
|
|
|
const (
|
|
DeclarationDescending SortOrders = iota
|
|
DeclarationAscending
|
|
NameAscending
|
|
NameDescending
|
|
limitSortOrders
|
|
)
|
|
|
|
type PackageIndex [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry
|
|
|
|
type PackageIndexEntry struct {
|
|
id unique.Handle[pkg.ID]
|
|
name string
|
|
description string
|
|
website string
|
|
version string
|
|
status []byte
|
|
}
|
|
|
|
func createPackageIndex(cache *pkg.Cache, report *rosa.Report) *PackageIndex {
|
|
var index PackageIndex
|
|
var work []PackageIndexEntry
|
|
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],
|
|
}
|
|
}
|
|
for i, p := range work {
|
|
index[DeclarationAscending][i] = &p
|
|
}
|
|
slices.Reverse(work)
|
|
for i, p := range work {
|
|
index[DeclarationDescending][i] = &p
|
|
}
|
|
slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int {
|
|
return cmp.Compare(a.name, b.name)
|
|
})
|
|
for i, p := range work {
|
|
index[NameAscending][i] = &p
|
|
}
|
|
slices.Reverse(work)
|
|
for i, p := range work {
|
|
index[NameDescending][i] = &p
|
|
}
|
|
|
|
return &index
|
|
}
|
|
func main() {
|
|
log.SetFlags(0)
|
|
log.SetPrefix("pkgserver: ")
|
|
|
|
var (
|
|
flagBaseDir string
|
|
flagPort uint16
|
|
)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
|
defer stop()
|
|
msg := message.New(log.Default())
|
|
|
|
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
|
reportPath := args[0]
|
|
baseDir, err := check.NewAbs(flagBaseDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cache, err := pkg.Open(ctx, msg, 0, baseDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
report, err := rosa.OpenReport(reportPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer report.HandleAccess(&err)()
|
|
index := createPackageIndex(cache, report)
|
|
http.HandleFunc("GET /{$}", serveWebUI)
|
|
http.HandleFunc("GET /favicon.ico", serveStaticContent)
|
|
http.HandleFunc("GET /static/", serveStaticContent)
|
|
http.HandleFunc("GET /api/", serveAPI(index))
|
|
err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}).Flag(
|
|
&flagBaseDir,
|
|
"b", command.StringFlag(""),
|
|
"base directory for cache",
|
|
).Flag(
|
|
&flagPort,
|
|
"p", command.IntFlag(8067),
|
|
"http listen port",
|
|
)
|
|
c.MustParse(os.Args[1:], func(e error) {
|
|
log.Fatal(e)
|
|
})
|
|
|
|
}
|