From 987c5d8baff398d047197fe4ec2260a8e330ea36 Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Mon, 8 Dec 2025 22:11:23 +0800 Subject: [PATCH 1/4] refactor(bootstrap): move booting to bootstrap package --- cmd/admin.go | 13 +- cmd/cancel2FA.go | 5 +- cmd/common.go | 17 --- cmd/crypt.go | 6 +- cmd/server.go | 252 +---------------------------------- cmd/storage.go | 13 +- internal/bootstrap/run.go | 273 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 293 insertions(+), 286 deletions(-) create mode 100644 internal/bootstrap/run.go diff --git a/cmd/admin.go b/cmd/admin.go index 447c49703..5e09959cf 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" + "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/internal/setting" @@ -20,8 +21,8 @@ var AdminCmd = &cobra.Command{ Aliases: []string{"password"}, Short: "Show admin user's info and some operations about admin user's password", Run: func(cmd *cobra.Command, args []string) { - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() admin, err := op.GetAdmin() if err != nil { utils.Log.Errorf("failed get admin user: %+v", err) @@ -61,8 +62,8 @@ var ShowTokenCmd = &cobra.Command{ Use: "token", Short: "Show admin token", Run: func(cmd *cobra.Command, args []string) { - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() token := setting.GetStr(conf.Token) utils.Log.Infof("show admin token from CLI") fmt.Println("Admin token:", token) @@ -70,8 +71,8 @@ var ShowTokenCmd = &cobra.Command{ } func setAdminPassword(pwd string) { - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() admin, err := op.GetAdmin() if err != nil { utils.Log.Errorf("failed get admin user: %+v", err) diff --git a/cmd/cancel2FA.go b/cmd/cancel2FA.go index 809d32e37..3ddd7f838 100644 --- a/cmd/cancel2FA.go +++ b/cmd/cancel2FA.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" + "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/spf13/cobra" @@ -16,8 +17,8 @@ var Cancel2FACmd = &cobra.Command{ Use: "cancel2fa", Short: "Delete 2FA of admin user", Run: func(cmd *cobra.Command, args []string) { - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() admin, err := op.GetAdmin() if err != nil { utils.Log.Errorf("failed to get admin user: %+v", err) diff --git a/cmd/common.go b/cmd/common.go index 6835a0f5c..80a19f963 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -5,27 +5,10 @@ import ( "path/filepath" "strconv" - "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" - "github.com/OpenListTeam/OpenList/v4/internal/bootstrap/data" - "github.com/OpenListTeam/OpenList/v4/internal/db" "github.com/OpenListTeam/OpenList/v4/pkg/utils" log "github.com/sirupsen/logrus" ) -func Init() { - bootstrap.InitConfig() - bootstrap.Log() - bootstrap.InitDB() - data.InitData() - bootstrap.InitStreamLimit() - bootstrap.InitIndex() - bootstrap.InitUpgradePatch() -} - -func Release() { - db.Close() -} - var pid = -1 var pidFile string diff --git a/cmd/crypt.go b/cmd/crypt.go index 10b3352cd..12f51f5cd 100644 --- a/cmd/crypt.go +++ b/cmd/crypt.go @@ -1,19 +1,17 @@ package cmd import ( - log "github.com/sirupsen/logrus" - "io" "os" "path" "path/filepath" "strings" - "github.com/spf13/cobra" - rcCrypt "github.com/rclone/rclone/backend/crypt" "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/obscure" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) // encryption and decryption command format for Crypt driver diff --git a/cmd/server.go b/cmd/server.go index 9f45161d7..468f8ba5d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,34 +1,9 @@ package cmd import ( - "context" - "errors" - "fmt" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "sync" - "syscall" - "time" - "github.com/OpenListTeam/OpenList/v4/cmd/flags" "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" - "github.com/OpenListTeam/OpenList/v4/internal/conf" - "github.com/OpenListTeam/OpenList/v4/internal/fs" - "github.com/OpenListTeam/OpenList/v4/pkg/utils" - "github.com/OpenListTeam/OpenList/v4/server" - "github.com/OpenListTeam/OpenList/v4/server/middlewares" - "github.com/OpenListTeam/sftpd-openlist" - ftpserver "github.com/fclairamb/ftpserverlib" - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" - - "github.com/quic-go/quic-go/http3" ) // ServerCmd represents the server command @@ -38,232 +13,7 @@ var ServerCmd = &cobra.Command{ Long: `Start the server at the specified address the address is defined in config file`, Run: func(cmd *cobra.Command, args []string) { - Init() - if conf.Conf.DelayedStart != 0 { - utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) - time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second) - } - bootstrap.InitOfflineDownloadTools() - bootstrap.LoadStorages() - bootstrap.InitTaskManager() - if !flags.Debug && !flags.Dev { - gin.SetMode(gin.ReleaseMode) - } - r := gin.New() - - // gin log - if conf.Conf.Log.Filter.Enable { - r.Use(middlewares.FilteredLogger()) - } else { - r.Use(gin.LoggerWithWriter(log.StandardLogger().Out)) - } - r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out)) - - server.Init(r) - var httpHandler http.Handler = r - if conf.Conf.Scheme.EnableH2c { - httpHandler = h2c.NewHandler(r, &http2.Server{}) - } - var httpSrv, httpsSrv, unixSrv *http.Server - var quicSrv *http3.Server - if conf.Conf.Scheme.HttpPort != -1 { - httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) - fmt.Printf("start HTTP server @ %s\n", httpBase) - utils.Log.Infof("start HTTP server @ %s", httpBase) - httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler} - go func() { - err := httpSrv.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start http: %s", err.Error()) - } - }() - } - if conf.Conf.Scheme.HttpsPort != -1 { - httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort) - fmt.Printf("start HTTPS server @ %s\n", httpsBase) - utils.Log.Infof("start HTTPS server @ %s", httpsBase) - httpsSrv = &http.Server{Addr: httpsBase, Handler: r} - go func() { - err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start https: %s", err.Error()) - } - }() - if conf.Conf.Scheme.EnableH3 { - fmt.Printf("start HTTP3 (quic) server @ %s\n", httpsBase) - utils.Log.Infof("start HTTP3 (quic) server @ %s", httpsBase) - r.Use(func(c *gin.Context) { - if c.Request.TLS != nil { - port := conf.Conf.Scheme.HttpsPort - c.Header("Alt-Svc", fmt.Sprintf("h3=\":%d\"; ma=86400", port)) - } - c.Next() - }) - quicSrv = &http3.Server{Addr: httpsBase, Handler: r} - go func() { - err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start http3 (quic): %s", err.Error()) - } - }() - } - } - if conf.Conf.Scheme.UnixFile != "" { - fmt.Printf("start unix server @ %s\n", conf.Conf.Scheme.UnixFile) - utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile) - unixSrv = &http.Server{Handler: httpHandler} - go func() { - listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile) - if err != nil { - utils.Log.Fatalf("failed to listen unix: %+v", err) - } - // set socket file permission - mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) - if err != nil { - utils.Log.Errorf("failed to parse socket file permission: %+v", err) - } else { - err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode)) - if err != nil { - utils.Log.Errorf("failed to chmod socket file: %+v", err) - } - } - err = unixSrv.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start unix: %s", err.Error()) - } - }() - } - if conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable { - s3r := gin.New() - s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out)) - server.InitS3(s3r) - s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port) - fmt.Printf("start S3 server @ %s\n", s3Base) - utils.Log.Infof("start S3 server @ %s", s3Base) - go func() { - var err error - if conf.Conf.S3.SSL { - httpsSrv = &http.Server{Addr: s3Base, Handler: s3r} - err = httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - } - if !conf.Conf.S3.SSL { - httpSrv = &http.Server{Addr: s3Base, Handler: s3r} - err = httpSrv.ListenAndServe() - } - if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start s3 server: %s", err.Error()) - } - }() - } - var ftpDriver *server.FtpMainDriver - var ftpServer *ftpserver.FtpServer - if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable { - var err error - ftpDriver, err = server.NewMainDriver() - if err != nil { - utils.Log.Fatalf("failed to start ftp driver: %s", err.Error()) - } else { - fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen) - utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) - go func() { - ftpServer = ftpserver.NewFtpServer(ftpDriver) - err = ftpServer.ListenAndServe() - if err != nil { - utils.Log.Fatalf("problem ftp server listening: %s", err.Error()) - } - }() - } - } - var sftpDriver *server.SftpDriver - var sftpServer *sftpd.SftpServer - if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable { - var err error - sftpDriver, err = server.NewSftpDriver() - if err != nil { - utils.Log.Fatalf("failed to start sftp driver: %s", err.Error()) - } else { - fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen) - utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) - go func() { - sftpServer = sftpd.NewSftpServer(sftpDriver) - err = sftpServer.RunServer() - if err != nil { - utils.Log.Fatalf("problem sftp server listening: %s", err.Error()) - } - }() - } - } - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 1 second. - quit := make(chan os.Signal, 1) - // kill (no param) default send syscanll.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - utils.Log.Println("Shutdown server...") - fs.ArchiveContentUploadTaskManager.RemoveAll() - Release() - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - var wg sync.WaitGroup - if conf.Conf.Scheme.HttpPort != -1 { - wg.Add(1) - go func() { - defer wg.Done() - if err := httpSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTP server shutdown err: ", err) - } - }() - } - if conf.Conf.Scheme.HttpsPort != -1 { - wg.Add(1) - go func() { - defer wg.Done() - if err := httpsSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTPS server shutdown err: ", err) - } - }() - if conf.Conf.Scheme.EnableH3 { - wg.Add(1) - go func() { - defer wg.Done() - if err := quicSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTP3 (quic) server shutdown err: ", err) - } - }() - } - } - if conf.Conf.Scheme.UnixFile != "" { - wg.Add(1) - go func() { - defer wg.Done() - if err := unixSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("Unix server shutdown err: ", err) - } - }() - } - if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil && ftpDriver != nil { - wg.Add(1) - go func() { - defer wg.Done() - ftpDriver.Stop() - if err := ftpServer.Stop(); err != nil { - utils.Log.Fatal("FTP server shutdown err: ", err) - } - }() - } - if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil && sftpDriver != nil { - wg.Add(1) - go func() { - defer wg.Done() - if err := sftpServer.Close(); err != nil { - utils.Log.Fatal("SFTP server shutdown err: ", err) - } - }() - } - wg.Wait() - utils.Log.Println("Server exit") + bootstrap.Run(!flags.Debug && !flags.Dev) }, } diff --git a/cmd/storage.go b/cmd/storage.go index c744dace5..6190feb38 100644 --- a/cmd/storage.go +++ b/cmd/storage.go @@ -8,6 +8,7 @@ import ( "os" "strconv" + "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" "github.com/OpenListTeam/OpenList/v4/internal/db" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/charmbracelet/bubbles/table" @@ -30,8 +31,8 @@ var disableStorageCmd = &cobra.Command{ return fmt.Errorf("mount path is required") } mountPath := args[0] - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() storage, err := db.GetStorageByMountPath(mountPath) if err != nil { return fmt.Errorf("failed to query storage: %+v", err) @@ -69,8 +70,8 @@ var deleteStorageCmd = &cobra.Command{ } } - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() err = db.DeleteStorageById(uint(id)) if err != nil { return fmt.Errorf("failed to delete storage by id: %+v", err) @@ -123,8 +124,8 @@ var listStorageCmd = &cobra.Command{ Use: "list", Short: "List all storages", RunE: func(cmd *cobra.Command, args []string) error { - Init() - defer Release() + bootstrap.Init() + defer bootstrap.Release() storages, _, err := db.GetStorages(1, -1) if err != nil { return fmt.Errorf("failed to query storages: %+v", err) diff --git a/internal/bootstrap/run.go b/internal/bootstrap/run.go new file mode 100644 index 000000000..8b97d9acd --- /dev/null +++ b/internal/bootstrap/run.go @@ -0,0 +1,273 @@ +package bootstrap + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "sync" + "syscall" + "time" + + "github.com/OpenListTeam/OpenList/v4/internal/bootstrap/data" + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/db" + "github.com/OpenListTeam/OpenList/v4/internal/fs" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/OpenListTeam/OpenList/v4/server" + "github.com/OpenListTeam/OpenList/v4/server/middlewares" + "github.com/OpenListTeam/sftpd-openlist" + ftpserver "github.com/fclairamb/ftpserverlib" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "github.com/quic-go/quic-go/http3" + log "github.com/sirupsen/logrus" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +func Init() { + InitConfig() + Log() + InitDB() + data.InitData() + InitStreamLimit() + InitIndex() + InitUpgradePatch() +} + +func Release() { + db.Close() +} + +func Run(isRelease bool) { + Init() + if conf.Conf.DelayedStart != 0 { + utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) + time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second) + } + InitOfflineDownloadTools() + LoadStorages() + InitTaskManager() + if isRelease { + gin.SetMode(gin.ReleaseMode) + } + r := gin.New() + + // gin log + if conf.Conf.Log.Filter.Enable { + r.Use(middlewares.FilteredLogger()) + } else { + r.Use(gin.LoggerWithWriter(log.StandardLogger().Out)) + } + r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out)) + + server.Init(r) + var httpHandler http.Handler = r + if conf.Conf.Scheme.EnableH2c { + httpHandler = h2c.NewHandler(r, &http2.Server{}) + } + var httpSrv, httpsSrv, unixSrv *http.Server + var quicSrv *http3.Server + if conf.Conf.Scheme.HttpPort != -1 { + httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) + fmt.Printf("start HTTP server @ %s\n", httpBase) + utils.Log.Infof("start HTTP server @ %s", httpBase) + httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler} + go func() { + err := httpSrv.ListenAndServe() + if err != nil && !errors.Is(err, http.ErrServerClosed) { + utils.Log.Fatalf("failed to start http: %s", err.Error()) + } + }() + } + if conf.Conf.Scheme.HttpsPort != -1 { + httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort) + fmt.Printf("start HTTPS server @ %s\n", httpsBase) + utils.Log.Infof("start HTTPS server @ %s", httpsBase) + httpsSrv = &http.Server{Addr: httpsBase, Handler: r} + go func() { + err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + utils.Log.Fatalf("failed to start https: %s", err.Error()) + } + }() + if conf.Conf.Scheme.EnableH3 { + fmt.Printf("start HTTP3 (quic) server @ %s\n", httpsBase) + utils.Log.Infof("start HTTP3 (quic) server @ %s", httpsBase) + r.Use(func(c *gin.Context) { + if c.Request.TLS != nil { + port := conf.Conf.Scheme.HttpsPort + c.Header("Alt-Svc", fmt.Sprintf("h3=\":%d\"; ma=86400", port)) + } + c.Next() + }) + quicSrv = &http3.Server{Addr: httpsBase, Handler: r} + go func() { + err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + utils.Log.Fatalf("failed to start http3 (quic): %s", err.Error()) + } + }() + } + } + if conf.Conf.Scheme.UnixFile != "" { + fmt.Printf("start unix server @ %s\n", conf.Conf.Scheme.UnixFile) + utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile) + unixSrv = &http.Server{Handler: httpHandler} + go func() { + listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile) + if err != nil { + utils.Log.Fatalf("failed to listen unix: %+v", err) + } + // set socket file permission + mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) + if err != nil { + utils.Log.Errorf("failed to parse socket file permission: %+v", err) + } else { + err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode)) + if err != nil { + utils.Log.Errorf("failed to chmod socket file: %+v", err) + } + } + err = unixSrv.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + utils.Log.Fatalf("failed to start unix: %s", err.Error()) + } + }() + } + if conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable { + s3r := gin.New() + s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out)) + server.InitS3(s3r) + s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port) + fmt.Printf("start S3 server @ %s\n", s3Base) + utils.Log.Infof("start S3 server @ %s", s3Base) + go func() { + var err error + if conf.Conf.S3.SSL { + httpsSrv = &http.Server{Addr: s3Base, Handler: s3r} + err = httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + } + if !conf.Conf.S3.SSL { + httpSrv = &http.Server{Addr: s3Base, Handler: s3r} + err = httpSrv.ListenAndServe() + } + if err != nil && !errors.Is(err, http.ErrServerClosed) { + utils.Log.Fatalf("failed to start s3 server: %s", err.Error()) + } + }() + } + var ftpDriver *server.FtpMainDriver + var ftpServer *ftpserver.FtpServer + if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable { + var err error + ftpDriver, err = server.NewMainDriver() + if err != nil { + utils.Log.Fatalf("failed to start ftp driver: %s", err.Error()) + } else { + fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen) + utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) + go func() { + ftpServer = ftpserver.NewFtpServer(ftpDriver) + err = ftpServer.ListenAndServe() + if err != nil { + utils.Log.Fatalf("problem ftp server listening: %s", err.Error()) + } + }() + } + } + var sftpDriver *server.SftpDriver + var sftpServer *sftpd.SftpServer + if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable { + var err error + sftpDriver, err = server.NewSftpDriver() + if err != nil { + utils.Log.Fatalf("failed to start sftp driver: %s", err.Error()) + } else { + fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen) + utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) + go func() { + sftpServer = sftpd.NewSftpServer(sftpDriver) + err = sftpServer.RunServer() + if err != nil { + utils.Log.Fatalf("problem sftp server listening: %s", err.Error()) + } + }() + } + } + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 1 second. + quit := make(chan os.Signal, 1) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + utils.Log.Println("Shutdown server...") + fs.ArchiveContentUploadTaskManager.RemoveAll() + Release() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + var wg sync.WaitGroup + if conf.Conf.Scheme.HttpPort != -1 { + wg.Add(1) + go func() { + defer wg.Done() + if err := httpSrv.Shutdown(ctx); err != nil { + utils.Log.Fatal("HTTP server shutdown err: ", err) + } + }() + } + if conf.Conf.Scheme.HttpsPort != -1 { + wg.Add(1) + go func() { + defer wg.Done() + if err := httpsSrv.Shutdown(ctx); err != nil { + utils.Log.Fatal("HTTPS server shutdown err: ", err) + } + }() + if conf.Conf.Scheme.EnableH3 { + wg.Add(1) + go func() { + defer wg.Done() + if err := quicSrv.Shutdown(ctx); err != nil { + utils.Log.Fatal("HTTP3 (quic) server shutdown err: ", err) + } + }() + } + } + if conf.Conf.Scheme.UnixFile != "" { + wg.Add(1) + go func() { + defer wg.Done() + if err := unixSrv.Shutdown(ctx); err != nil { + utils.Log.Fatal("Unix server shutdown err: ", err) + } + }() + } + if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil && ftpDriver != nil { + wg.Add(1) + go func() { + defer wg.Done() + ftpDriver.Stop() + if err := ftpServer.Stop(); err != nil { + utils.Log.Fatal("FTP server shutdown err: ", err) + } + }() + } + if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil && sftpDriver != nil { + wg.Add(1) + go func() { + defer wg.Done() + if err := sftpServer.Close(); err != nil { + utils.Log.Fatal("SFTP server shutdown err: ", err) + } + }() + } + wg.Wait() + utils.Log.Println("Server exit") +} From 23597a69d4e36c154b35d781e82611921ed51b7f Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Mon, 8 Dec 2025 22:35:04 +0800 Subject: [PATCH 2/4] chore(log): reduce level of some callings of `utils.Log.Fatal` --- cmd/server.go | 3 +- .../bootstrap/patch/v3_24_0/hash_password.go | 5 ++- .../bootstrap/patch/v3_32_0/update_authn.go | 5 ++- internal/bootstrap/run.go | 38 ++++++++++--------- server/ftp.go | 4 +- server/sftp/hostkey.go | 12 +++--- 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 468f8ba5d..d3b7ae442 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,7 +1,6 @@ package cmd import ( - "github.com/OpenListTeam/OpenList/v4/cmd/flags" "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" "github.com/spf13/cobra" ) @@ -13,7 +12,7 @@ var ServerCmd = &cobra.Command{ Long: `Start the server at the specified address the address is defined in config file`, Run: func(cmd *cobra.Command, args []string) { - bootstrap.Run(!flags.Debug && !flags.Dev) + bootstrap.Run() }, } diff --git a/internal/bootstrap/patch/v3_24_0/hash_password.go b/internal/bootstrap/patch/v3_24_0/hash_password.go index 881e3d47b..30a296c47 100644 --- a/internal/bootstrap/patch/v3_24_0/hash_password.go +++ b/internal/bootstrap/patch/v3_24_0/hash_password.go @@ -11,7 +11,8 @@ import ( func HashPwdForOldVersion() { users, _, err := op.GetUsers(1, -1) if err != nil { - utils.Log.Fatalf("[hash pwd for old version] failed get users: %v", err) + utils.Log.Errorf("[hash pwd for old version] failed get users: %v", err) + return } for i := range users { user := users[i] @@ -19,7 +20,7 @@ func HashPwdForOldVersion() { user.SetPassword(user.Password) user.Password = "" if err := db.UpdateUser(&user); err != nil { - utils.Log.Fatalf("[hash pwd for old version] failed update user: %v", err) + utils.Log.Errorf("[hash pwd for old version] failed update user: %v", err) } } } diff --git a/internal/bootstrap/patch/v3_32_0/update_authn.go b/internal/bootstrap/patch/v3_32_0/update_authn.go index fea4bbfb1..721c3f582 100644 --- a/internal/bootstrap/patch/v3_32_0/update_authn.go +++ b/internal/bootstrap/patch/v3_32_0/update_authn.go @@ -11,14 +11,15 @@ import ( func UpdateAuthnForOldVersion() { users, _, err := op.GetUsers(1, -1) if err != nil { - utils.Log.Fatalf("[update authn for old version] failed get users: %v", err) + utils.Log.Errorf("[update authn for old version] failed get users: %v", err) + return } for i := range users { user := users[i] if user.Authn == "" { user.Authn = "[]" if err := db.UpdateUser(&user); err != nil { - utils.Log.Fatalf("[update authn for old version] failed update user: %v", err) + utils.Log.Errorf("[update authn for old version] failed update user: %v", err) } } } diff --git a/internal/bootstrap/run.go b/internal/bootstrap/run.go index 8b97d9acd..df8344d5a 100644 --- a/internal/bootstrap/run.go +++ b/internal/bootstrap/run.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/OpenListTeam/OpenList/v4/cmd/flags" "github.com/OpenListTeam/OpenList/v4/internal/bootstrap/data" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/db" @@ -43,7 +44,7 @@ func Release() { db.Close() } -func Run(isRelease bool) { +func Run() { Init() if conf.Conf.DelayedStart != 0 { utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) @@ -52,7 +53,7 @@ func Run(isRelease bool) { InitOfflineDownloadTools() LoadStorages() InitTaskManager() - if isRelease { + if !flags.Debug && !flags.Dev { gin.SetMode(gin.ReleaseMode) } r := gin.New() @@ -80,7 +81,7 @@ func Run(isRelease bool) { go func() { err := httpSrv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start http: %s", err.Error()) + utils.Log.Errorf("failed to start http: %s", err.Error()) } }() } @@ -92,7 +93,7 @@ func Run(isRelease bool) { go func() { err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start https: %s", err.Error()) + utils.Log.Errorf("failed to start https: %s", err.Error()) } }() if conf.Conf.Scheme.EnableH3 { @@ -109,7 +110,7 @@ func Run(isRelease bool) { go func() { err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start http3 (quic): %s", err.Error()) + utils.Log.Errorf("failed to start http3 (quic): %s", err.Error()) } }() } @@ -121,7 +122,8 @@ func Run(isRelease bool) { go func() { listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile) if err != nil { - utils.Log.Fatalf("failed to listen unix: %+v", err) + utils.Log.Errorf("failed to listen unix: %+v", err) + return } // set socket file permission mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) @@ -135,7 +137,7 @@ func Run(isRelease bool) { } err = unixSrv.Serve(listener) if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start unix: %s", err.Error()) + utils.Log.Errorf("failed to start unix: %s", err.Error()) } }() } @@ -157,7 +159,7 @@ func Run(isRelease bool) { err = httpSrv.ListenAndServe() } if err != nil && !errors.Is(err, http.ErrServerClosed) { - utils.Log.Fatalf("failed to start s3 server: %s", err.Error()) + utils.Log.Errorf("failed to start s3 server: %s", err.Error()) } }() } @@ -167,7 +169,7 @@ func Run(isRelease bool) { var err error ftpDriver, err = server.NewMainDriver() if err != nil { - utils.Log.Fatalf("failed to start ftp driver: %s", err.Error()) + utils.Log.Errorf("failed to start ftp driver: %s", err.Error()) } else { fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen) utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) @@ -175,7 +177,7 @@ func Run(isRelease bool) { ftpServer = ftpserver.NewFtpServer(ftpDriver) err = ftpServer.ListenAndServe() if err != nil { - utils.Log.Fatalf("problem ftp server listening: %s", err.Error()) + utils.Log.Errorf("problem ftp server listening: %s", err.Error()) } }() } @@ -186,7 +188,7 @@ func Run(isRelease bool) { var err error sftpDriver, err = server.NewSftpDriver() if err != nil { - utils.Log.Fatalf("failed to start sftp driver: %s", err.Error()) + utils.Log.Errorf("failed to start sftp driver: %s", err.Error()) } else { fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen) utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) @@ -194,7 +196,7 @@ func Run(isRelease bool) { sftpServer = sftpd.NewSftpServer(sftpDriver) err = sftpServer.RunServer() if err != nil { - utils.Log.Fatalf("problem sftp server listening: %s", err.Error()) + utils.Log.Errorf("problem sftp server listening: %s", err.Error()) } }() } @@ -218,7 +220,7 @@ func Run(isRelease bool) { go func() { defer wg.Done() if err := httpSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTP server shutdown err: ", err) + utils.Log.Error("HTTP server shutdown err: ", err) } }() } @@ -227,7 +229,7 @@ func Run(isRelease bool) { go func() { defer wg.Done() if err := httpsSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTPS server shutdown err: ", err) + utils.Log.Error("HTTPS server shutdown err: ", err) } }() if conf.Conf.Scheme.EnableH3 { @@ -235,7 +237,7 @@ func Run(isRelease bool) { go func() { defer wg.Done() if err := quicSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("HTTP3 (quic) server shutdown err: ", err) + utils.Log.Error("HTTP3 (quic) server shutdown err: ", err) } }() } @@ -245,7 +247,7 @@ func Run(isRelease bool) { go func() { defer wg.Done() if err := unixSrv.Shutdown(ctx); err != nil { - utils.Log.Fatal("Unix server shutdown err: ", err) + utils.Log.Error("Unix server shutdown err: ", err) } }() } @@ -255,7 +257,7 @@ func Run(isRelease bool) { defer wg.Done() ftpDriver.Stop() if err := ftpServer.Stop(); err != nil { - utils.Log.Fatal("FTP server shutdown err: ", err) + utils.Log.Error("FTP server shutdown err: ", err) } }() } @@ -264,7 +266,7 @@ func Run(isRelease bool) { go func() { defer wg.Done() if err := sftpServer.Close(); err != nil { - utils.Log.Fatal("SFTP server shutdown err: ", err) + utils.Log.Error("SFTP server shutdown err: ", err) } }() } diff --git a/server/ftp.go b/server/ftp.go index fb7d86c8e..dc42219da 100644 --- a/server/ftp.go +++ b/server/ftp.go @@ -167,7 +167,7 @@ func lookupIP(host string) string { } ips, err := net.LookupIP(host) if err != nil || len(ips) == 0 { - utils.Log.Fatalf("given FTP public host is invalid, and the default value will be used: %v", err) + utils.Log.Errorf("given FTP public host is invalid, and the default value will be used: %v", err) return "" } for _, ip := range ips { @@ -272,7 +272,7 @@ func newPortMapper(str string) ftpserver.PasvPortGetter { break } if err != nil { - utils.Log.Fatalf("failed to convert FTP PASV port mapper %s: %v, the port mapper will be ignored.", mapper, err) + utils.Log.Errorf("failed to convert FTP PASV port mapper %s: %v, the port mapper will be ignored.", mapper, err) return nil } } diff --git a/server/sftp/hostkey.go b/server/sftp/hostkey.go index ffa25c665..6d857666a 100644 --- a/server/sftp/hostkey.go +++ b/server/sftp/hostkey.go @@ -24,7 +24,7 @@ func InitHostKey() { if !utils.Exists(sshPath) { err := utils.CreateNestedDirectory(sshPath) if err != nil { - utils.Log.Fatalf("failed to create ssh directory: %+v", err) + utils.Log.Errorf("failed to create ssh directory: %+v", err) return } } @@ -54,30 +54,30 @@ func LoadOrGenerateRSAHostKey(parentDir string) (ssh.Signer, bool) { _ = os.Remove(publicKeyPath) privateKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { - utils.Log.Fatalf("failed to generate RSA private key: %+v", err) + utils.Log.Errorf("failed to generate RSA private key: %+v", err) return nil, false } publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) if err != nil { - utils.Log.Fatalf("failed to generate RSA public key: %+v", err) + utils.Log.Errorf("failed to generate RSA public key: %+v", err) return nil, false } ret, err := ssh.NewSignerFromKey(privateKey) if err != nil { - utils.Log.Fatalf("failed to generate RSA signer: %+v", err) + utils.Log.Errorf("failed to generate RSA signer: %+v", err) return nil, false } privateBytes := rsaEncodePrivateKey(privateKey) publicBytes := ssh.MarshalAuthorizedKey(publicKey) err = os.WriteFile(privateKeyPath, privateBytes, 0600) if err != nil { - utils.Log.Fatalf("failed to write RSA private key to file: %+v", err) + utils.Log.Errorf("failed to write RSA private key to file: %+v", err) return nil, false } err = os.WriteFile(publicKeyPath, publicBytes, 0644) if err != nil { _ = os.Remove(privateKeyPath) - utils.Log.Fatalf("failed to write RSA public key to file: %+v", err) + utils.Log.Errorf("failed to write RSA public key to file: %+v", err) return nil, false } return ret, true From 53af5f0fcd5f9c70ee12e0f81fd9b1089f7b2e6a Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Tue, 9 Dec 2025 00:16:37 +0800 Subject: [PATCH 3/4] fix(s3): no shutdown after SIGTERM received --- cmd/server.go | 18 +++++- internal/bootstrap/run.go | 123 ++++++++++++++++++++++++++++---------- 2 files changed, 107 insertions(+), 34 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index d3b7ae442..26441f916 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,6 +1,11 @@ package cmd import ( + "os" + "os/signal" + "syscall" + "time" + "github.com/OpenListTeam/OpenList/v4/internal/bootstrap" "github.com/spf13/cobra" ) @@ -12,7 +17,18 @@ var ServerCmd = &cobra.Command{ Long: `Start the server at the specified address the address is defined in config file`, Run: func(cmd *cobra.Command, args []string) { - bootstrap.Run() + bootstrap.Init() + defer bootstrap.Release() + bootstrap.Start() + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 1 second. + quit := make(chan os.Signal, 1) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + bootstrap.Shutdown(1 * time.Second) }, } diff --git a/internal/bootstrap/run.go b/internal/bootstrap/run.go index df8344d5a..9fc36e31d 100644 --- a/internal/bootstrap/run.go +++ b/internal/bootstrap/run.go @@ -6,10 +6,8 @@ import ( "net" "net/http" "os" - "os/signal" "strconv" "sync" - "syscall" "time" "github.com/OpenListTeam/OpenList/v4/cmd/flags" @@ -44,8 +42,48 @@ func Release() { db.Close() } -func Run() { - Init() +var ( + running bool + httpSrv *http.Server + httpRunning bool + httpsSrv *http.Server + httpsRunning bool + unixSrv *http.Server + unixRunning bool + quicSrv *http3.Server + quicRunning bool + s3Srv *http.Server + s3Running bool + ftpDriver *server.FtpMainDriver + ftpServer *ftpserver.FtpServer + ftpRunning bool + sftpDriver *server.SftpDriver + sftpServer *sftpd.SftpServer + sftpRunning bool +) + +// Called by OpenList-Mobile +func IsRunning(t string) bool { + switch t { + case "http": + return httpRunning + case "https": + return httpsRunning + case "unix": + return unixRunning + case "quic": + return quicRunning + case "s3": + return s3Running + case "sftp": + return sftpRunning + case "ftp": + return ftpRunning + } + return running +} + +func Start() { if conf.Conf.DelayedStart != 0 { utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second) @@ -71,15 +109,15 @@ func Run() { if conf.Conf.Scheme.EnableH2c { httpHandler = h2c.NewHandler(r, &http2.Server{}) } - var httpSrv, httpsSrv, unixSrv *http.Server - var quicSrv *http3.Server if conf.Conf.Scheme.HttpPort != -1 { httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) fmt.Printf("start HTTP server @ %s\n", httpBase) utils.Log.Infof("start HTTP server @ %s", httpBase) httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler} go func() { + httpRunning = true err := httpSrv.ListenAndServe() + httpRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { utils.Log.Errorf("failed to start http: %s", err.Error()) } @@ -91,7 +129,9 @@ func Run() { utils.Log.Infof("start HTTPS server @ %s", httpsBase) httpsSrv = &http.Server{Addr: httpsBase, Handler: r} go func() { + httpsRunning = true err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + httpsRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { utils.Log.Errorf("failed to start https: %s", err.Error()) } @@ -108,7 +148,9 @@ func Run() { }) quicSrv = &http3.Server{Addr: httpsBase, Handler: r} go func() { + quicRunning = true err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + quicRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { utils.Log.Errorf("failed to start http3 (quic): %s", err.Error()) } @@ -125,6 +167,7 @@ func Run() { utils.Log.Errorf("failed to listen unix: %+v", err) return } + unixRunning = true // set socket file permission mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) if err != nil { @@ -136,6 +179,7 @@ func Run() { } } err = unixSrv.Serve(listener) + unixRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { utils.Log.Errorf("failed to start unix: %s", err.Error()) } @@ -149,22 +193,21 @@ func Run() { fmt.Printf("start S3 server @ %s\n", s3Base) utils.Log.Infof("start S3 server @ %s", s3Base) go func() { + s3Running = true var err error if conf.Conf.S3.SSL { - httpsSrv = &http.Server{Addr: s3Base, Handler: s3r} - err = httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - } - if !conf.Conf.S3.SSL { - httpSrv = &http.Server{Addr: s3Base, Handler: s3r} - err = httpSrv.ListenAndServe() + s3Srv = &http.Server{Addr: s3Base, Handler: s3r} + err = s3Srv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + } else { + s3Srv = &http.Server{Addr: s3Base, Handler: s3r} + err = s3Srv.ListenAndServe() } + s3Running = false if err != nil && !errors.Is(err, http.ErrServerClosed) { utils.Log.Errorf("failed to start s3 server: %s", err.Error()) } }() } - var ftpDriver *server.FtpMainDriver - var ftpServer *ftpserver.FtpServer if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable { var err error ftpDriver, err = server.NewMainDriver() @@ -182,8 +225,6 @@ func Run() { }() } } - var sftpDriver *server.SftpDriver - var sftpServer *sftpd.SftpServer if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable { var err error sftpDriver, err = server.NewSftpDriver() @@ -201,75 +242,91 @@ func Run() { }() } } - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 1 second. - quit := make(chan os.Signal, 1) - // kill (no param) default send syscanll.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit + running = true +} + +func Shutdown(timeout time.Duration) { utils.Log.Println("Shutdown server...") fs.ArchiveContentUploadTaskManager.RemoveAll() - Release() - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() var wg sync.WaitGroup - if conf.Conf.Scheme.HttpPort != -1 { + if httpSrv != nil && conf.Conf.Scheme.HttpPort != -1 { wg.Add(1) go func() { defer wg.Done() if err := httpSrv.Shutdown(ctx); err != nil { utils.Log.Error("HTTP server shutdown err: ", err) } + httpSrv = nil }() } - if conf.Conf.Scheme.HttpsPort != -1 { + if httpsSrv != nil && conf.Conf.Scheme.HttpsPort != -1 { wg.Add(1) go func() { defer wg.Done() if err := httpsSrv.Shutdown(ctx); err != nil { utils.Log.Error("HTTPS server shutdown err: ", err) } + httpsSrv = nil }() - if conf.Conf.Scheme.EnableH3 { + if quicSrv != nil && conf.Conf.Scheme.EnableH3 { wg.Add(1) go func() { defer wg.Done() if err := quicSrv.Shutdown(ctx); err != nil { utils.Log.Error("HTTP3 (quic) server shutdown err: ", err) } + quicSrv = nil }() } } - if conf.Conf.Scheme.UnixFile != "" { + if unixSrv != nil && conf.Conf.Scheme.UnixFile != "" { wg.Add(1) go func() { defer wg.Done() if err := unixSrv.Shutdown(ctx); err != nil { utils.Log.Error("Unix server shutdown err: ", err) } + unixSrv = nil + }() + } + if s3Srv != nil && conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable { + wg.Add(1) + go func() { + defer wg.Done() + if err := s3Srv.Shutdown(ctx); err != nil { + utils.Log.Error("S3 server shutdown err: ", err) + } + s3Srv = nil }() } - if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil && ftpDriver != nil { + if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil { wg.Add(1) go func() { defer wg.Done() - ftpDriver.Stop() + if ftpDriver != nil { + ftpDriver.Stop() + ftpDriver = nil + } if err := ftpServer.Stop(); err != nil { utils.Log.Error("FTP server shutdown err: ", err) } + ftpServer = nil }() } - if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil && sftpDriver != nil { + if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil { wg.Add(1) go func() { defer wg.Done() if err := sftpServer.Close(); err != nil { utils.Log.Error("SFTP server shutdown err: ", err) } + sftpServer = nil + sftpDriver = nil }() } wg.Wait() utils.Log.Println("Server exit") + running = false } From 2127269a01e62f7e7170e77c82e972e42cfa111a Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Tue, 9 Dec 2025 00:52:05 +0800 Subject: [PATCH 4/4] fix: add handle hook --- internal/bootstrap/run.go | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/internal/bootstrap/run.go b/internal/bootstrap/run.go index 9fc36e31d..6740dba65 100644 --- a/internal/bootstrap/run.go +++ b/internal/bootstrap/run.go @@ -21,6 +21,7 @@ import ( "github.com/OpenListTeam/sftpd-openlist" ftpserver "github.com/fclairamb/ftpserverlib" "github.com/gin-gonic/gin" + "github.com/google/uuid" "github.com/pkg/errors" "github.com/quic-go/quic-go/http3" log "github.com/sirupsen/logrus" @@ -119,7 +120,10 @@ func Start() { err := httpSrv.ListenAndServe() httpRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("http", err) utils.Log.Errorf("failed to start http: %s", err.Error()) + } else { + handleEndpointShutdownHooks("http") } }() } @@ -133,7 +137,10 @@ func Start() { err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) httpsRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("https", err) utils.Log.Errorf("failed to start https: %s", err.Error()) + } else { + handleEndpointShutdownHooks("https") } }() if conf.Conf.Scheme.EnableH3 { @@ -152,7 +159,10 @@ func Start() { err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) quicRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("quic", err) utils.Log.Errorf("failed to start http3 (quic): %s", err.Error()) + } else { + handleEndpointShutdownHooks("quic") } }() } @@ -181,7 +191,10 @@ func Start() { err = unixSrv.Serve(listener) unixRunning = false if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("unix", err) utils.Log.Errorf("failed to start unix: %s", err.Error()) + } else { + handleEndpointShutdownHooks("unix") } }() } @@ -204,7 +217,10 @@ func Start() { } s3Running = false if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("s3", err) utils.Log.Errorf("failed to start s3 server: %s", err.Error()) + } else { + handleEndpointShutdownHooks("s3") } }() } @@ -218,9 +234,14 @@ func Start() { utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) go func() { ftpServer = ftpserver.NewFtpServer(ftpDriver) + ftpRunning = true err = ftpServer.ListenAndServe() + ftpRunning = false if err != nil { + handleEndpointStartFailedHooks("ftp", err) utils.Log.Errorf("problem ftp server listening: %s", err.Error()) + } else { + handleEndpointShutdownHooks("ftp") } }() } @@ -235,9 +256,14 @@ func Start() { utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) go func() { sftpServer = sftpd.NewSftpServer(sftpDriver) + sftpRunning = true err = sftpServer.RunServer() + sftpRunning = false if err != nil { + handleEndpointStartFailedHooks("sftp", err) utils.Log.Errorf("problem sftp server listening: %s", err.Error()) + } else { + handleEndpointShutdownHooks("sftp") } }() } @@ -330,3 +356,49 @@ func Shutdown(timeout time.Duration) { utils.Log.Println("Server exit") running = false } + +type EndpointStartFailedHook func(string, string) + +type EndpointShutdownHook func(string) + +var ( + endpointStartFailedHooks map[string]EndpointStartFailedHook + endpointShutdownHooks map[string]EndpointShutdownHook +) + +func RegisterEndpointStartFailedHook(hook EndpointStartFailedHook) string { + id := uuid.NewString() + endpointStartFailedHooks[id] = hook + return id +} + +func RemoveEndpointStartFailedHook(id string) { + delete(endpointStartFailedHooks, id) +} + +func RegisterEndpointShutdownHook(hook EndpointShutdownHook) string { + id := uuid.NewString() + endpointShutdownHooks[id] = hook + return id +} + +func RemoveEndpointShutdownHook(id string) { + delete(endpointShutdownHooks, id) +} + +func handleEndpointStartFailedHooks(t string, err error) { + for _, hook := range endpointStartFailedHooks { + hook(t, err.Error()) + } +} + +func handleEndpointShutdownHooks(t string) { + for _, hook := range endpointShutdownHooks { + hook(t) + } +} + +func init() { + endpointShutdownHooks = make(map[string]EndpointShutdownHook) + endpointStartFailedHooks = make(map[string]EndpointStartFailedHook) +}