0d84b07f68
CI / docker (push) Successful in 1m58s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
3.1 KiB
Go
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)
|
|
}
|
|
}
|