package main //go:generate go run github.com/google/wire/cmd/wire import ( "context" _ "embed" "errors" "flag" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" _ "github.com/Wei-Shaw/sub2api/ent/runtime" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/setup" "github.com/Wei-Shaw/sub2api/internal/web" "github.com/gin-gonic/gin" ) //go:embed VERSION var embeddedVersion string // Build-time variables (can be set by ldflags) var ( Version = "" Commit = "unknown" Date = "unknown" BuildType = "source" // "source" for manual builds, "release" for CI builds (set by ldflags) ) func init() { // Read version from embedded VERSION file Version = strings.TrimSpace(embeddedVersion) if Version == "" { Version = "0.0.0-dev" } } func main() { // Parse command line flags setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode") showVersion := flag.Bool("version", false, "Show version information") flag.Parse() if *showVersion { log.Printf("Sub2API %s (commit: %s, built: %s)\n", Version, Commit, Date) return } // CLI setup mode if *setupMode { if err := setup.RunCLI(); err != nil { log.Fatalf("Setup failed: %v", err) } return } // Check if setup is needed if setup.NeedsSetup() { // Check if auto-setup is enabled (for Docker deployment) if setup.AutoSetupEnabled() { log.Println("Auto setup mode enabled...") if err := setup.AutoSetupFromEnv(); err != nil { log.Fatalf("Auto setup failed: %v", err) } // Continue to main server after auto-setup } else { log.Println("First run detected, starting setup wizard...") runSetupServer() return } } // Normal server mode runMainServer() } func runSetupServer() { r := gin.New() r.Use(middleware.Recovery()) r.Use(middleware.CORS()) // Register setup routes setup.RegisterRoutes(r) // Serve embedded frontend if available if web.HasEmbeddedFrontend() { r.Use(web.ServeEmbeddedFrontend()) } // Get server address from config.yaml or environment variables (SERVER_HOST, SERVER_PORT) // This allows users to run setup on a different address if needed addr := config.GetServerAddress() log.Printf("Setup wizard available at http://%s", addr) log.Println("Complete the setup wizard to configure Sub2API") if err := r.Run(addr); err != nil { log.Fatalf("Failed to start setup server: %v", err) } } func runMainServer() { cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load config: %v", err) } if cfg.RunMode == config.RunModeSimple { log.Println("⚠️ WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED") } buildInfo := handler.BuildInfo{ Version: Version, BuildType: BuildType, } app, err := initializeApplication(buildInfo) if err != nil { log.Fatalf("Failed to initialize application: %v", err) } defer app.Cleanup() // 启动服务器 go func() { if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("Failed to start server: %v", err) } }() log.Printf("Server started on %s", app.Server.Addr) // 等待中断信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := app.Server.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } log.Println("Server exited") }