diff --git a/cmd/config.go b/cmd/config.go index 4a2f8d14..73d4faa9 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -30,6 +30,7 @@ var configCmd = &cobra.Command{ func addConfigFlags(flags *pflag.FlagSet) { addServerFlags(flags) addUserFlags(flags) + flags.BoolP("signup", "s", false, "allow users to signup") flags.Bool("hideLoginButton", false, "hide login button from public pages") flags.Bool("createUserDir", false, "generate user's home directory automatically") @@ -47,7 +48,6 @@ func addConfigFlags(flags *pflag.FlagSet) { flags.String("branding.name", "", "replace 'File Browser' by this name") flags.String("branding.theme", "", "set the theme") flags.String("branding.color", "", "set the theme color") - flags.String("branding.files", "", "path to directory with images and custom styles") flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links") flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph") @@ -204,6 +204,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength) fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod) fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " ")) + fmt.Fprintln(w, "\nBranding:") fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name) fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files) @@ -211,6 +212,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage) fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color) fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme) + fmt.Fprintln(w, "\nServer:") fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log) fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port) @@ -221,9 +223,14 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert) fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey) fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec) + fmt.Fprintf(w, "\tThumbnails Enabled:\t%t\n", ser.EnableThumbnails) + fmt.Fprintf(w, "\tResize Preview:\t%t\n", ser.ResizePreview) + fmt.Fprintf(w, "\tType Detection by Header:\t%t\n", ser.TypeDetectionByHeader) + fmt.Fprintln(w, "\nTUS:") fmt.Fprintf(w, "\tChunk size:\t%d\n", set.Tus.ChunkSize) fmt.Fprintf(w, "\tRetry count:\t%d\n", set.Tus.RetryCount) + fmt.Fprintln(w, "\nDefaults:") fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope) fmt.Fprintf(w, "\tHideDotfiles:\t%t\n", set.Defaults.HideDotfiles) @@ -234,9 +241,11 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode) fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme) + fmt.Fprintf(w, "\tSorting:\n") fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc) + fmt.Fprintf(w, "\tPermissions:\n") fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin) fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute) @@ -246,6 +255,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete) fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share) fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download) + w.Flush() b, err := json.MarshalIndent(auther, "", " ") diff --git a/cmd/config_import.go b/cmd/config_import.go index 7763517d..63d394d7 100644 --- a/cmd/config_import.go +++ b/cmd/config_import.go @@ -37,7 +37,7 @@ The path must be for a json or yaml file.`, RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { var key []byte var err error - if d.hadDB { + if d.databaseExisted { settings, settingErr := d.store.Settings.Get() if settingErr != nil { return settingErr @@ -104,7 +104,7 @@ The path must be for a json or yaml file.`, } return printSettings(file.Server, file.Settings, auther) - }, pythonConfig{allowNoDB: true}), + }, pythonConfig{allowsNoDatabase: true}), } func getAuther(sample auth.Auther, data interface{}) (interface{}, error) { diff --git a/cmd/config_init.go b/cmd/config_init.go index ad079d19..21cacb33 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -176,5 +176,5 @@ Now add your first user via 'filebrowser users add' and then you just need to call the main command to boot up the server. `) return printSettings(ser, s, auther) - }, pythonConfig{noDB: true}), + }, pythonConfig{expectsNoDatabase: true}), } diff --git a/cmd/config_set.go b/cmd/config_set.go index e8dd2fa5..74fae9ea 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -37,28 +37,47 @@ you want to change. Other options will remain unchanged.`, } switch flag.Name { - case "baseURL": - ser.BaseURL, err = flags.GetString(flag.Name) - case "root": - ser.Root, err = flags.GetString(flag.Name) - case "socket": - ser.Socket, err = flags.GetString(flag.Name) + // Server flags from [addServerFlags] + case "address": + ser.Address, err = flags.GetString(flag.Name) + case "log": + ser.Log, err = flags.GetString(flag.Name) + case "port": + ser.Port, err = flags.GetString(flag.Name) case "cert": ser.TLSCert, err = flags.GetString(flag.Name) case "key": ser.TLSKey, err = flags.GetString(flag.Name) - case "address": - ser.Address, err = flags.GetString(flag.Name) - case "port": - ser.Port, err = flags.GetString(flag.Name) - case "log": - ser.Log, err = flags.GetString(flag.Name) - case "hideLoginButton": - set.HideLoginButton, err = flags.GetBool(flag.Name) + case "root": + ser.Root, err = flags.GetString(flag.Name) + case "socket": + ser.Socket, err = flags.GetString(flag.Name) + case "baseURL": + ser.BaseURL, err = flags.GetString(flag.Name) + case "tokenExpirationTime": + ser.TokenExpirationTime, err = flags.GetString(flag.Name) + case "disableThumbnails": + ser.EnableThumbnails, err = flags.GetBool(flag.Name) + ser.EnableThumbnails = !ser.EnableThumbnails + case "disablePreviewResize": + ser.ResizePreview, err = flags.GetBool(flag.Name) + ser.ResizePreview = !ser.ResizePreview + case "disableExec": + ser.EnableExec, err = flags.GetBool(flag.Name) + ser.EnableExec = !ser.EnableExec + case "disableTypeDetectionByHeader": + ser.TypeDetectionByHeader, err = flags.GetBool(flag.Name) + ser.TypeDetectionByHeader = !ser.TypeDetectionByHeader + + // Settings flags from [addConfigFlags] case "signup": set.Signup, err = flags.GetBool(flag.Name) - case "auth.method": - hasAuth = true + case "hideLoginButton": + set.HideLoginButton, err = flags.GetBool(flag.Name) + case "createUserDir": + set.CreateUserDir, err = flags.GetBool(flag.Name) + case "minimumPasswordLength": + set.MinimumPasswordLength, err = flags.GetUint(flag.Name) case "shell": var shell string shell, err = flags.GetString(flag.Name) @@ -66,22 +85,20 @@ you want to change. Other options will remain unchanged.`, return } set.Shell = convertCmdStrToCmdArray(shell) - case "createUserDir": - set.CreateUserDir, err = flags.GetBool(flag.Name) - case "minimumPasswordLength": - set.MinimumPasswordLength, err = flags.GetUint(flag.Name) + case "auth.method": + hasAuth = true case "branding.name": set.Branding.Name, err = flags.GetString(flag.Name) - case "branding.color": - set.Branding.Color, err = flags.GetString(flag.Name) case "branding.theme": set.Branding.Theme, err = flags.GetString(flag.Name) + case "branding.color": + set.Branding.Color, err = flags.GetString(flag.Name) + case "branding.files": + set.Branding.Files, err = flags.GetString(flag.Name) case "branding.disableExternal": set.Branding.DisableExternal, err = flags.GetBool(flag.Name) case "branding.disableUsedPercentage": set.Branding.DisableUsedPercentage, err = flags.GetBool(flag.Name) - case "branding.files": - set.Branding.Files, err = flags.GetString(flag.Name) case "fileMode": set.FileMode, err = getAndParseFileMode(flags, flag.Name) case "dirMode": diff --git a/cmd/root.go b/cmd/root.go index 8b39cb68..e3c2aafa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -69,24 +69,30 @@ func migrateFlagNames(f *pflag.FlagSet, name string) pflag.NormalizedName { func init() { rootCmd.SilenceUsage = true + rootCmd.SetGlobalNormalizationFunc(migrateFlagNames) + cobra.MousetrapHelpText = "" rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") - flags := rootCmd.Flags() + // Flags available across the whole program persistent := rootCmd.PersistentFlags() - persistent.StringP("config", "c", "", "config file path") persistent.StringP("database", "d", "./filebrowser.db", "database path") + + // Runtime flags for the root command + flags := rootCmd.Flags() flags.Bool("noauth", false, "use the noauth auther when using quick setup") flags.String("username", "admin", "username for the first user when using quick setup") flags.String("password", "", "hashed password for the first user when using quick setup") - + flags.Uint32("socketPerm", 0666, "unix socket file permissions") + flags.String("cacheDir", "", "file cache directory (disabled if empty)") + flags.Int("imageProcessors", 4, "image processors count") addServerFlags(flags) - - rootCmd.SetGlobalNormalizationFunc(migrateFlagNames) } +// addServerFlags adds server related flags to the given FlagSet. These flags are available +// in both the root command, config set and config init commands. func addServerFlags(flags *pflag.FlagSet) { flags.StringP("address", "a", "127.0.0.1", "address to listen on") flags.StringP("log", "l", "stdout", "log output") @@ -95,11 +101,8 @@ func addServerFlags(flags *pflag.FlagSet) { flags.StringP("key", "k", "", "tls key") flags.StringP("root", "r", ".", "root to prepend to relative paths") flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)") - flags.Uint32("socketPerm", 0666, "unix socket file permissions") flags.StringP("baseURL", "b", "", "base url") - flags.String("cacheDir", "", "file cache directory (disabled if empty)") flags.String("tokenExpirationTime", "2h", "user session timeout") - flags.Int("imageProcessors", 4, "image processors count") flags.Bool("disableThumbnails", false, "disable image thumbnails") flags.Bool("disablePreviewResize", false, "disable resize of image previews") flags.Bool("disableExec", true, "disables Command Runner feature") @@ -118,10 +121,11 @@ it. Don't worry: you don't need to setup a separate database server. We're using Bolt DB which is a single file database and all managed by ourselves. -All flags are available as environmental variables, except for "--config", -which specifies the configuration file to use. The environment variables -are prefixed by "FB_" followed by the flag name in UPPER_SNAKE_CASE. For -example, the flag "--disablePreviewResize" is as FB_DISABLE_PREVIEW_RESIZE. +For this command, all flags are available as environmental variables, +except for "--config", which specifies the configuration file to use. +The environment variables are prefixed by "FB_" followed by the flag name in +UPPER_SNAKE_CASE. For example, the flag "--disablePreviewResize" is available +as FB_DISABLE_PREVIEW_RESIZE. If "--config" is not specified, File Browser will look for a configuration file named .filebrowser.{json, toml, yaml, yml} in the following directories: @@ -142,8 +146,8 @@ Also, if the database path doesn't exist, File Browser will enter into the quick setup mode and a new database will be bootstrapped and a new user created with the credentials from options "username" and "password".`, RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { - if !d.hadDB { - err := quickSetup(d.viper, *d) + if !d.databaseExisted { + err := quickSetup(*d) if err != nil { return err } @@ -257,7 +261,7 @@ user created with the credentials from options "username" and "password".`, log.Println("Graceful shutdown complete.") return nil - }, pythonConfig{allowNoDB: true}), + }, pythonConfig{allowsNoDatabase: true}), } func getServerSettings(v *viper.Viper, st *storage.Storage) (*settings.Server, error) { @@ -356,7 +360,7 @@ func setupLog(logMethod string) { } } -func quickSetup(v *viper.Viper, d pythonData) error { +func quickSetup(d pythonData) error { log.Println("Performing quick setup") set := &settings.Settings{ @@ -370,7 +374,7 @@ func quickSetup(v *viper.Viper, d pythonData) error { Scope: ".", Locale: "en", SingleClick: false, - AceEditorTheme: v.GetString("defaults.aceEditorTheme"), + AceEditorTheme: d.viper.GetString("defaults.aceEditorTheme"), Perm: users.Permissions{ Admin: false, Execute: true, @@ -394,7 +398,7 @@ func quickSetup(v *viper.Viper, d pythonData) error { } var err error - if _, noauth := vGetStringIsSet(v, "noauth"); noauth { + if _, noauth := vGetStringIsSet(d.viper, "noauth"); noauth { set.AuthMethod = auth.MethodNoAuth err = d.store.Auth.Save(&auth.NoAuth{}) } else { @@ -411,13 +415,13 @@ func quickSetup(v *viper.Viper, d pythonData) error { } ser := &settings.Server{ - BaseURL: v.GetString("baseURL"), - Port: v.GetString("port"), - Log: v.GetString("log"), - TLSKey: v.GetString("key"), - TLSCert: v.GetString("cert"), - Address: v.GetString("address"), - Root: v.GetString("root"), + BaseURL: d.viper.GetString("baseURL"), + Port: d.viper.GetString("port"), + Log: d.viper.GetString("log"), + TLSKey: d.viper.GetString("key"), + TLSCert: d.viper.GetString("cert"), + Address: d.viper.GetString("address"), + Root: d.viper.GetString("root"), } err = d.store.Settings.SaveServer(ser) @@ -425,8 +429,8 @@ func quickSetup(v *viper.Viper, d pythonData) error { return err } - username := v.GetString("username") - password := v.GetString("password") + username := d.viper.GetString("username") + password := d.viper.GetString("password") if password == "" { var pwd string diff --git a/cmd/utils.go b/cmd/utils.go index e486c9d5..a136db10 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -135,14 +135,14 @@ type cobraFunc func(cmd *cobra.Command, args []string) error type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error type pythonConfig struct { - noDB bool - allowNoDB bool + expectsNoDatabase bool + allowsNoDatabase bool } type pythonData struct { - hadDB bool - viper *viper.Viper - store *storage.Storage + databaseExisted bool + viper *viper.Viper + store *storage.Storage } func python(fn pythonFunc, cfg pythonConfig) cobraFunc { @@ -152,9 +152,16 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { return err } - data := &pythonData{hadDB: true, viper: v} + data := &pythonData{databaseExisted: true} path := v.GetString("database") + // Only make the viper instance available to the root command (filebrowser). + // This is to make sure that we don't make the mistake of using it somewhere + // else. + if cmd.Name() == "filebrowser" { + data.viper = v + } + absPath, err := filepath.Abs(path) if err != nil { return err @@ -163,16 +170,16 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { exists, err := dbExists(path) if err != nil { return err - } else if exists && cfg.noDB { + } else if exists && cfg.expectsNoDatabase { log.Fatal(absPath + " already exists") - } else if !exists && !cfg.noDB && !cfg.allowNoDB { + } else if !exists && !cfg.expectsNoDatabase && !cfg.allowsNoDatabase { log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.") - } else if !exists && !cfg.noDB { + } else if !exists && !cfg.expectsNoDatabase { log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db")) } log.Println("Using database: " + absPath) - data.hadDB = exists + data.databaseExisted = exists db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil)) if err != nil {