package cmd import ( "context" "errors" "fmt" "net" "net/http" "os" "os/signal" "strconv" "sync" "syscall" "time" "github.com/alist-org/alist/v3/cmd/flags" "github.com/alist-org/alist/v3/internal/bootstrap" "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/pkg/utils" "github.com/alist-org/alist/v3/server" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // ServerCmd represents the server command var ServerCmd = &cobra.Command{ Use: "server", Short: "Start the server at the specified address", 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() r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out)) server.Init(r) var httpSrv, httpsSrv, unixSrv *http.Server if conf.Conf.Scheme.HttpPort != -1 { httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) utils.Log.Infof("start HTTP server @ %s", httpBase) httpSrv = &http.Server{Addr: httpBase, Handler: r} 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) 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.UnixFile != "" { utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile) unixSrv = &http.Server{Handler: r} 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) 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()) } }() } // 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...") 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.UnixFile != "" { wg.Add(1) go func() { defer wg.Done() if err := unixSrv.Shutdown(ctx); err != nil { utils.Log.Fatal("Unix server shutdown err: ", err) } }() } wg.Wait() utils.Log.Println("Server exit") }, } func init() { RootCmd.AddCommand(ServerCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: // serverCmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: // serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // OutAlistInit 暴露用于外部启动server的函数 func OutAlistInit() { var ( cmd *cobra.Command args []string ) ServerCmd.Run(cmd, args) }