main.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package main
  2. //go:generate go run github.com/google/wire/cmd/wire
  3. import (
  4. "context"
  5. _ "embed"
  6. "errors"
  7. "flag"
  8. "log"
  9. "net/http"
  10. "os"
  11. "os/signal"
  12. "strings"
  13. "syscall"
  14. "time"
  15. _ "github.com/Wei-Shaw/sub2api/ent/runtime"
  16. "github.com/Wei-Shaw/sub2api/internal/config"
  17. "github.com/Wei-Shaw/sub2api/internal/handler"
  18. "github.com/Wei-Shaw/sub2api/internal/pkg/logger"
  19. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  20. "github.com/Wei-Shaw/sub2api/internal/setup"
  21. "github.com/Wei-Shaw/sub2api/internal/web"
  22. "github.com/gin-gonic/gin"
  23. )
  24. //go:embed VERSION
  25. var embeddedVersion string
  26. // Build-time variables (can be set by ldflags)
  27. var (
  28. Version = ""
  29. Commit = "unknown"
  30. Date = "unknown"
  31. BuildType = "source" // "source" for manual builds, "release" for CI builds (set by ldflags)
  32. )
  33. func init() {
  34. // 如果 Version 已通过 ldflags 注入(例如 -X main.Version=...),则不要覆盖。
  35. if strings.TrimSpace(Version) != "" {
  36. return
  37. }
  38. // 默认从 embedded VERSION 文件读取版本号(编译期打包进二进制)。
  39. Version = strings.TrimSpace(embeddedVersion)
  40. if Version == "" {
  41. Version = "0.0.0-dev"
  42. }
  43. }
  44. // initLogger configures the default slog handler based on gin.Mode().
  45. // In non-release mode, Debug level logs are enabled.
  46. func main() {
  47. logger.InitBootstrap()
  48. defer logger.Sync()
  49. // Parse command line flags
  50. setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
  51. showVersion := flag.Bool("version", false, "Show version information")
  52. flag.Parse()
  53. if *showVersion {
  54. log.Printf("Sub2API %s (commit: %s, built: %s)\n", Version, Commit, Date)
  55. return
  56. }
  57. // CLI setup mode
  58. if *setupMode {
  59. if err := setup.RunCLI(); err != nil {
  60. log.Fatalf("Setup failed: %v", err)
  61. }
  62. return
  63. }
  64. // Check if setup is needed
  65. if setup.NeedsSetup() {
  66. // Check if auto-setup is enabled (for Docker deployment)
  67. if setup.AutoSetupEnabled() {
  68. log.Println("Auto setup mode enabled...")
  69. if err := setup.AutoSetupFromEnv(); err != nil {
  70. log.Fatalf("Auto setup failed: %v", err)
  71. }
  72. // Continue to main server after auto-setup
  73. } else {
  74. log.Println("First run detected, starting setup wizard...")
  75. runSetupServer()
  76. return
  77. }
  78. }
  79. // Normal server mode
  80. runMainServer()
  81. }
  82. func runSetupServer() {
  83. r := gin.New()
  84. r.Use(middleware.Recovery())
  85. r.Use(middleware.CORS(config.CORSConfig{}))
  86. r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}, nil))
  87. // Register setup routes
  88. setup.RegisterRoutes(r)
  89. // Serve embedded frontend if available
  90. if web.HasEmbeddedFrontend() {
  91. r.Use(web.ServeEmbeddedFrontend())
  92. }
  93. // Get server address from config.yaml or environment variables (SERVER_HOST, SERVER_PORT)
  94. // This allows users to run setup on a different address if needed
  95. addr := config.GetServerAddress()
  96. log.Printf("Setup wizard available at http://%s", addr)
  97. log.Println("Complete the setup wizard to configure Sub2API")
  98. protocols := new(http.Protocols)
  99. protocols.SetHTTP1(true)
  100. protocols.SetUnencryptedHTTP2(true)
  101. server := &http.Server{
  102. Addr: addr,
  103. Handler: r,
  104. ReadHeaderTimeout: 30 * time.Second,
  105. IdleTimeout: 120 * time.Second,
  106. Protocols: protocols,
  107. }
  108. if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  109. log.Fatalf("Failed to start setup server: %v", err)
  110. }
  111. }
  112. func runMainServer() {
  113. cfg, err := config.LoadForBootstrap()
  114. if err != nil {
  115. log.Fatalf("Failed to load config: %v", err)
  116. }
  117. if err := logger.Init(logger.OptionsFromConfig(cfg.Log)); err != nil {
  118. log.Fatalf("Failed to initialize logger: %v", err)
  119. }
  120. if cfg.RunMode == config.RunModeSimple {
  121. log.Println("⚠️ WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED")
  122. }
  123. buildInfo := handler.BuildInfo{
  124. Version: Version,
  125. BuildType: BuildType,
  126. }
  127. app, err := initializeApplication(buildInfo)
  128. if err != nil {
  129. log.Fatalf("Failed to initialize application: %v", err)
  130. }
  131. defer app.Cleanup()
  132. if app.PluginManager != nil {
  133. if err := app.PluginManager.Start(context.Background()); err != nil {
  134. log.Printf("Plugin manager started in degraded state: %v", err)
  135. }
  136. }
  137. if app.PromptAudit != nil {
  138. if err := app.PromptAudit.Start(context.Background()); err != nil {
  139. // Startup continues so unrelated APIs stay up. Fail-closed (unavailable)
  140. // applies only when a persisted blocking policy was observed; without
  141. // blocking intent, Prompt Audit stays ModeOff so the gateway remains
  142. // usable and administrators can still disable the feature (#4560).
  143. log.Printf("Prompt Audit started in degraded state: %v", err)
  144. }
  145. }
  146. // 启动服务器
  147. go func() {
  148. if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  149. log.Fatalf("Failed to start server: %v", err)
  150. }
  151. }()
  152. log.Printf("Server started on %s", app.Server.Addr)
  153. // 等待中断信号
  154. quit := make(chan os.Signal, 1)
  155. signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  156. <-quit
  157. log.Println("Shutting down server...")
  158. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  159. defer cancel()
  160. if err := app.Server.Shutdown(ctx); err != nil {
  161. log.Printf("Server forced to shutdown: %v", err)
  162. }
  163. log.Println("Server exited")
  164. }