package main import ( "context" "errors" "log" "net/http" "os" "os/signal" "path/filepath" "syscall" "scout/internal/access" "scout/internal/config" "scout/internal/httpapi" "scout/internal/notify" "scout/internal/registry" "scout/internal/users" ) func main() { logger := log.Default() cfg, err := config.Load() if err != nil { logger.Fatal(err) } store, err := registry.New(cfg.DataDir) if err != nil { logger.Fatal(err) } projectStore, err := registry.NewProjectRegistry(cfg.DataDir) if err != nil { logger.Fatal(err) } accessStore, err := access.New(filepath.Join(cfg.DataDir, "project-access.json")) if err != nil { logger.Fatal(err) } userStore, err := users.New(filepath.Join(cfg.DataDir, "users.json")) if err != nil { logger.Fatal(err) } if cfg.AdminEmail != "" { if _, err := userStore.EnsureAdmin(cfg.AdminEmail, cfg.AdminName, cfg.AdminPassword); err != nil { logger.Fatal(err) } } api := &httpapi.Server{ Registry: store, Projects: projectStore, Access: accessStore, DeveloperToken: cfg.DeveloperToken, AllowLegacyToken: cfg.AllowLegacyToken, MaxUploadBytes: cfg.MaxUploadBytes, Users: userStore, Mailer: notify.NewFromEnv(logger), SessionDuration: cfg.SessionDuration, AllowedOrigins: cfg.AllowedOrigins, AllowedHosts: cfg.AllowedHosts, ForceHTTPS: cfg.ForceHTTPS, TrustProxy: cfg.TrustProxy, RateLimitPerMinute: cfg.RateLimitPerMinute, Logger: logger, } server := &http.Server{ Addr: cfg.Address, Handler: api.Handler(), ReadHeaderTimeout: cfg.ReadHeaderTimeout, ReadTimeout: cfg.ReadTimeout, WriteTimeout: cfg.WriteTimeout, IdleTimeout: cfg.IdleTimeout, } logger.Printf("Scout 包服务端正在监听 %s,数据目录为 %s,运行环境为 %s", cfg.Address, cfg.DataDir, cfg.Environment) if cfg.DeveloperToken == "" || !cfg.AllowLegacyToken { logger.Printf("兼容开发者令牌未启用,上传需要开发者账户或管理员账户") } if os.Getenv("SCOUT_SMTP_HOST") == "" { logger.Printf("未配置 SMTP,邮箱通知将只记录日志") } serverErrors := make(chan error, 1) go func() { serverErrors <- server.ListenAndServe() }() shutdownSignal := make(chan os.Signal, 1) signal.Notify(shutdownSignal, os.Interrupt, syscall.SIGTERM) select { case err := <-serverErrors: if !errors.Is(err, http.ErrServerClosed) { logger.Fatal(err) } case signal := <-shutdownSignal: logger.Printf("收到 %s,开始优雅停止服务", signal) ctx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout) defer cancel() if err := server.Shutdown(ctx); err != nil { logger.Printf("优雅停止失败,强制关闭服务: %v", err) _ = server.Close() } } }