package cmd import ( "crypto/tls" "flag" "fmt" "io/fs" "log" "net" "net/http" "os" "os/signal" "strconv" "syscall" "embed" "github.com/spf13/pflag" "github.com/spf13/cobra" "github.com/gtsteffaniak/filebrowser/auth" "github.com/gtsteffaniak/filebrowser/diskcache" "github.com/gtsteffaniak/filebrowser/files" fbhttp "github.com/gtsteffaniak/filebrowser/http" "github.com/gtsteffaniak/filebrowser/img" "github.com/gtsteffaniak/filebrowser/settings" "github.com/gtsteffaniak/filebrowser/users" "github.com/gtsteffaniak/filebrowser/version" ) //go:embed dist/* var assets embed.FS var nonEmbededFS = os.Getenv("FILEBROWSER_NO_EMBEDED") == "true" type dirFS struct { http.Dir } func (d dirFS) Open(name string) (fs.File, error) { return d.Dir.Open(name) } func init() { // Define a flag for the config option (-c or --config) configFlag := pflag.StringP("config", "c", "filebrowser.yaml", "Path to the config file") // Bind the flags to the pflag command line parser pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() log.Printf("Initializing FileBrowser Quantum (%v) with config file: %v \n", version.Version, *configFlag) log.Println("Embeded Frontend:", !nonEmbededFS) settings.Initialize(*configFlag) } var rootCmd = &cobra.Command{ Use: "filebrowser", Run: python(func(cmd *cobra.Command, args []string, d pythonData) { serverConfig := settings.Config.Server if !d.hadDB { quickSetup(d) } if serverConfig.NumImageProcessors < 1 { log.Fatal("Image resize workers count could not be < 1") } imgSvc := img.New(serverConfig.NumImageProcessors) cacheDir := "/tmp" var fileCache diskcache.Interface // Use file cache if cacheDir is specified if cacheDir != "" { var err error fileCache, err = diskcache.NewFileCache(cacheDir) if err != nil { log.Fatalf("failed to create file cache: %v", err) } } else { // No-op cache if no cacheDir is specified fileCache = diskcache.NewNoOp() } // initialize indexing and schedule indexing ever n minutes (default 5) go files.InitializeIndex(serverConfig.IndexingInterval, serverConfig.Indexing) _, err := os.Stat(serverConfig.Root) checkErr(fmt.Sprint("cmd os.Stat ", serverConfig.Root), err) var listener net.Listener address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port) switch { case serverConfig.Socket != "": listener, err = net.Listen("unix", serverConfig.Socket) checkErr("net.Listen", err) socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet checkErr("cmd.Flags().GetUint32", err) err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm)) checkErr("os.Chmod", err) case serverConfig.TLSKey != "" && serverConfig.TLSCert != "": cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet checkErr("tls.LoadX509KeyPair", err) listener, err = tls.Listen("tcp", address, &tls.Config{ MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cer}}, ) checkErr("tls.Listen", err) default: listener, err = net.Listen("tcp", address) checkErr("net.Listen", err) } sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) go cleanupHandler(listener, sigc) if !nonEmbededFS { assetsFs, err := fs.Sub(assets, "dist") if err != nil { log.Fatal("Could not embed frontend. Does backend/cmd/dist exist? Must be built and exist first") } handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs) checkErr("fbhttp.NewHandler", err) defer listener.Close() log.Println("Listening on", listener.Addr().String()) //nolint: gosec if err := http.Serve(listener, handler); err != nil { log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err) } } else { assetsFs := dirFS{Dir: http.Dir("frontend/dist")} handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs) checkErr("fbhttp.NewHandler", err) defer listener.Close() log.Println("Listening on", listener.Addr().String()) //nolint: gosec if err := http.Serve(listener, handler); err != nil { log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err) } } }, pythonConfig{allowNoDB: true}), } func StartFilebrowser() { if err := rootCmd.Execute(); err != nil { log.Fatal("Error starting filebrowser:", err) } } func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer sig := <-c log.Printf("Caught signal %s: shutting down.", sig) listener.Close() os.Exit(0) } func quickSetup(d pythonData) { settings.Config.Auth.Key = generateKey() if settings.Config.Auth.Method == "noauth" { err := d.store.Auth.Save(&auth.NoAuth{}) checkErr("d.store.Auth.Save", err) } else { settings.Config.Auth.Method = "password" err := d.store.Auth.Save(&auth.JSONAuth{}) checkErr("d.store.Auth.Save", err) } err := d.store.Settings.Save(&settings.Config) checkErr("d.store.Settings.Save", err) err = d.store.Settings.SaveServer(&settings.Config.Server) checkErr("d.store.Settings.SaveServer", err) user := users.ApplyDefaults(users.User{}) user.Username = settings.Config.Auth.AdminUsername user.Password = settings.Config.Auth.AdminPassword user.Perm.Admin = true user.Scope = "./" user.DarkMode = true user.ViewMode = "normal" user.LockPassword = false user.Perm = settings.Permissions{ Create: true, Rename: true, Modify: true, Delete: true, Share: true, Download: true, Admin: true, } err = d.store.Users.Save(&user) checkErr("d.store.Users.Save", err) }