aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/goget/main.go165
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..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)
+ })
+}