Files
Wormhole/internal/server/server.go
T
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

121 lines
3.1 KiB
Go

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)
}
}