package server import ( "context" "flag" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "github.com/wormhole/wormhole/internal/api" "github.com/wormhole/wormhole/internal/auth" "github.com/wormhole/wormhole/internal/bridge" "github.com/wormhole/wormhole/internal/cache" "github.com/wormhole/wormhole/internal/config" "github.com/wormhole/wormhole/internal/metrics" "github.com/wormhole/wormhole/internal/proxy" "github.com/wormhole/wormhole/internal/security" "github.com/wormhole/wormhole/internal/store" webdist "github.com/wormhole/wormhole/web" ) func Run(args []string) { fs := flag.NewFlagSet("server", flag.ExitOnError) fs.Parse(args) st, err := store.Open(config.DefaultDBPath) if err != nil { log.Fatalf("open store: %v", err) } cfg, err := st.LoadConfig() if err != nil { log.Fatalf("load settings: %v", err) } log.Printf("[server] settings loaded (%s)", cfg.Summary()) c := cache.NewManager() if err := c.ReloadAll(st); err != nil { log.Fatalf("reload cache: %v", err) } authSvc := auth.NewService(&cfg.Auth) resAuth := auth.NewResourceAuth(c, st, &cfg.Auth) sec := security.NewService(c, st) b := bridge.New(c, st) mc := metrics.NewCollector(st, c, b) pm := proxy.NewManager(c, st, b, sec, resAuth, mc) if err := b.Start(cfg.Server.BridgeAddr, cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile); err != nil { log.Fatalf("bridge start: %v", err) } mc.Start(cfg.Metrics.FlushInterval) if err := pm.StartAll(); err != nil { log.Printf("start tunnels: %v", err) } if err := pm.StartHTTPServer(cfg.Server.HTTPProxyPort, cfg.Server.HTTPSProxyPort, cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile); err != nil { log.Printf("start http proxy: %v", err) } apiServer := api.NewServer(cfg, st, c, authSvc, resAuth, sec, b, pm, mc) router := apiServer.Router() router.NoRoute(serveSPA()) srv := &http.Server{ Addr: cfg.Server.HTTPAddr, Handler: router, } go func() { log.Printf("[server] http api on %s", cfg.Server.HTTPAddr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("http server: %v", err) } }() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig log.Println("[server] shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() mc.Stop() if err := pm.Shutdown(ctx); err != nil { log.Printf("[server] proxy shutdown: %v", err) } _ = b.Shutdown() if err := srv.Shutdown(ctx); err != nil { log.Printf("[server] api shutdown: %v", err) } log.Println("[server] stopped") } func serveSPA() gin.HandlerFunc { distFS, err := fs.Sub(webdist.Dist, "dist") if err != nil { return func(c *gin.Context) { c.String(http.StatusOK, "Wormhole API running. Build web dist for UI.") } } fileServer := http.FileServer(http.FS(distFS)) return func(c *gin.Context) { path := c.Request.URL.Path if path == "/" { path = "/index.html" } if f, err := distFS.Open(path[1:]); err == nil { _ = f.Close() fileServer.ServeHTTP(c.Writer, c.Request) return } c.Request.URL.Path = "/" fileServer.ServeHTTP(c.Writer, c.Request) } }