aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/goget/main.go165
-rw-r--r--go.mod13
-rw-r--r--go.sum20
3 files changed, 198 insertions, 0 deletions
diff --git a/cmd/goget/main.go b/cmd/goget/main.go
new file mode 100644
index 0000000..ae8b3a6
--- /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: ":8080",
+ 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)
+ })
+}
diff --git a/go.mod b/go.mod
index b3d61b2..bf80943 100644
--- a/go.mod
+++ b/go.mod
@@ -3,3 +3,16 @@ module go.fcuny.net/x
go 1.24.5
require golang.org/x/text v0.28.0
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/prometheus/client_golang v1.23.0 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.65.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ golang.org/x/mod v0.27.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+)
diff --git a/go.sum b/go.sum
index 433ec67..12023c2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,22 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=