diff options
Diffstat (limited to 'cmd/goget/main.go')
| -rw-r--r-- | cmd/goget/main.go | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/cmd/goget/main.go b/cmd/goget/main.go new file mode 100644 index 0000000..3f17448 --- /dev/null +++ b/cmd/goget/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "slices" + "strings" + "syscall" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/mod/semver" +) + +const ( + forgeDomain = "code.fcuny.net" + forgeUser = "fcuny" + goPkgDomain = "go.fcuny.net" +) + +var ( + goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "goget_requests_total", + Help: "go get requests processed, by repository name.", + }, []string{"name"}) + + modules = []string{"x"} +) + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + metricsMux := http.NewServeMux() + metricsMux.Handle("/metrics", promhttp.Handler()) + metricsServer := &http.Server{ + Addr: ":9091", Handler: metricsMux, + ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, + } + + go func() { + log.Println("Starting metrics server on :9091") + if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Metrics server error: %v", err) + } + }() + + s := &http.Server{ + Addr: ":8070", + Handler: handler(), + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 1 * time.Minute, + } + + go func() { + log.Println("Starting main server on :8080") + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Main server error: %v", err) + } + }() + + <-ctx.Done() + log.Println("Shutdown signal received, starting graceful shutdown...") + + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer shutdownCancel() + + var shutdownErr error + + log.Println("Shutting down main server...") + if err := s.Shutdown(shutdownCtx); err != nil { + log.Printf("Main server shutdown error: %v", err) + shutdownErr = err + } + + log.Println("Shutting down metrics server...") + if err := metricsServer.Shutdown(shutdownCtx); err != nil { + log.Printf("Metrics server shutdown error: %v", err) + if shutdownErr == nil { + shutdownErr = err + } + } + + if shutdownErr != nil { + log.Printf("Shutdown completed with errors") + os.Exit(1) + } + + log.Println("Shutdown completed successfully") +} + +func handler() http.Handler { + mux := http.NewServeMux() + + mux.Handle(goPkgDomain+"/{name}", siteHandler(modules)) + mux.Handle(goPkgDomain+"/{name}/", siteHandler(modules)) + + goGetMux := http.NewServeMux() + for _, name := range modules { + module := goPkgDomain + "/" + name + goGetMux.Handle( + module+"/", + goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name), + ) + } + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + status := http.StatusMethodNotAllowed + http.Error(rw, http.StatusText(status), status) + return + } + + if r.URL.Query().Get("go-get") == "1" { + goGetMux.ServeHTTP(rw, r) + return + } + mux.ServeHTTP(rw, r) + }) +} + +func goImportHandler(module, repo string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + goGetReqs.WithLabelValues(module).Inc() + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + if _, err := fmt.Fprintf(w, `<head><meta name="go-import" content="%s git %s">`, module, repo); err != nil { + log.Printf("Error writing response: %v", err) + } + }) +} + +func siteHandler(names []string) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + name, version, hasV := strings.Cut(name, "@") + if !hasV { + name, _, _ = strings.Cut(name, ".") + } + if hasV && !semver.IsValid(version) || !slices.Contains(names, name) { + http.NotFound(rw, r) + return + } + u := &url.URL{ + Scheme: "https", + Host: forgeDomain, + Path: forgeUser + r.URL.Path, + } + if !hasV { + path, symbol, hasSymbol := strings.Cut(r.URL.Path, ".") + if hasSymbol { + u.Path = forgeUser + "/" + path + u.Fragment = symbol + } + } + http.Redirect(rw, r, u.String(), http.StatusFound) + }) +} |
