diff --git a/cmd/cmds_add.go b/cmd/cmds_add.go index eebbe00c..1a6f4ba2 100644 --- a/cmd/cmds_add.go +++ b/cmd/cmds_add.go @@ -1,22 +1,20 @@ package cmd import ( + "strings" + "github.com/spf13/cobra" ) func init() { cmdsCmd.AddCommand(cmdsAddCmd) - cmdsAddCmd.Flags().StringP("command", "c", "", "command to add") - cmdsAddCmd.Flags().StringP("event", "e", "", "corresponding event") - cmdsAddCmd.MarkFlagRequired("command") - cmdsAddCmd.MarkFlagRequired("event") } var cmdsAddCmd = &cobra.Command{ - Use: "add", + Use: "add ", Short: "Add a command to run on a specific event", Long: `Add a command to run on a specific event.`, - Args: cobra.NoArgs, + Args: cobra.MinimumNArgs(2), Run: func(cmd *cobra.Command, args []string) { db := getDB() defer db.Close() @@ -24,10 +22,9 @@ var cmdsAddCmd = &cobra.Command{ s, err := st.Settings.Get() checkErr(err) - evt := mustGetString(cmd, "event") - command := mustGetString(cmd, "command") + command := strings.Join(args[1:], " ") - s.Commands[evt] = append(s.Commands[evt], command) + s.Commands[args[0]] = append(s.Commands[args[0]], command) err = st.Settings.Save(s) checkErr(err) printEvents(s.Commands) diff --git a/cmd/cmds_rm.go b/cmd/cmds_rm.go index 2c19727f..002bdb6a 100644 --- a/cmd/cmds_rm.go +++ b/cmd/cmds_rm.go @@ -1,34 +1,49 @@ package cmd import ( + "strconv" + "github.com/spf13/cobra" ) func init() { cmdsCmd.AddCommand(cmdsRmCmd) - cmdsRmCmd.Flags().StringP("event", "e", "", "corresponding event") - cmdsRmCmd.Flags().UintP("index", "i", 0, "command index") - cmdsRmCmd.MarkFlagRequired("event") - cmdsRmCmd.MarkFlagRequired("index") } var cmdsRmCmd = &cobra.Command{ - Use: "rm", + Use: "rm [index_end]", Short: "Removes a command from an event hooker", Long: `Removes a command from an event hooker.`, - Args: cobra.NoArgs, + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { + return err + } + + for _, arg := range args[1:] { + if _, err := strconv.Atoi(arg); err != nil { + return err + } + } + + return nil + }, Run: func(cmd *cobra.Command, args []string) { db := getDB() defer db.Close() st := getStorage(db) s, err := st.Settings.Get() checkErr(err) + evt := args[0] - evt := mustGetString(cmd, "event") - i, err := cmd.Flags().GetUint("index") + i, err := strconv.Atoi(args[1]) checkErr(err) + f := i + if len(args) == 3 { + f, err = strconv.Atoi(args[2]) + checkErr(err) + } - s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][i+1:]...) + s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...) err = st.Settings.Save(s) checkErr(err) printEvents(s.Commands) diff --git a/cmd/config.go b/cmd/config.go index bca203c5..4415805e 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -31,7 +31,6 @@ var configCmd = &cobra.Command{ func addConfigFlags(cmd *cobra.Command) { addUserFlags(cmd) - cmd.Flags().StringP("baseURL", "b", "/", "base url of this installation") cmd.Flags().BoolP("signup", "s", false, "allow users to signup") cmd.Flags().String("shell", "", "shell command to which other commands should be appended") @@ -91,7 +90,6 @@ func getAuthentication(cmd *cobra.Command) (settings.AuthMethod, auth.Auther) { func printSettings(s *settings.Settings, auther auth.Auther) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "\nBase URL:\t%s\n", s.BaseURL) fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup) fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod) fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " ")) diff --git a/cmd/config_init.go b/cmd/config_init.go index 6a75bd1e..64adc137 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -43,7 +43,6 @@ override the options.`, st := getStorage(db) s := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit - BaseURL: mustGetString(cmd, "baseURL"), Signup: mustGetBool(cmd, "signup"), Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "), AuthMethod: authMethod, diff --git a/cmd/config_set.go b/cmd/config_set.go index b5e7b510..ea0f0159 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -30,8 +30,6 @@ you want to change.`, hasAuth := false cmd.Flags().Visit(func(flag *pflag.Flag) { switch flag.Name { - case "baseURL": - s.BaseURL = mustGetString(cmd, flag.Name) case "signup": s.Signup = mustGetBool(cmd, flag.Name) case "auth.method": diff --git a/cmd/root.go b/cmd/root.go index 9670d414..905079b7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "crypto/tls" + "errors" "io/ioutil" "log" "net" @@ -27,10 +28,12 @@ var ( ) func init() { + cobra.OnInitialize(initConfig) + f := rootCmd.Flags() pf := rootCmd.PersistentFlags() - f.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')") + pf.StringVarP(&cfgFile, "config", "c", "", "config file path") vaddP(pf, "database", "d", "./filebrowser.db", "path to the database") vaddP(f, "address", "a", "127.0.0.1", "address to listen on") vaddP(f, "log", "l", "stdout", "log output") @@ -38,6 +41,9 @@ func init() { vaddP(f, "cert", "t", "", "tls certificate") vaddP(f, "key", "k", "", "tls key") vaddP(f, "scope", "s", ".", "scope to prepend to a user's scope when it is relative") + vaddP(f, "baseurl", "b", "", "base url") + vadd(f, "username", "admin", "username for the first user when using quick config") + vadd(f, "password", "admin", "password for the first user when using quick config") if err := v.BindPFlags(f); err != nil { panic(err) @@ -52,17 +58,43 @@ var rootCmd = &cobra.Command{ Use: "filebrowser", Short: "A stylish web-based file browser", Long: `File Browser CLI lets you create the database to use with File Browser, -manage your user and all the configurations without accessing the +manage your users and all the configurations without acessing the web interface. + +If you've never run File Browser, you'll need to have a database for +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. -If you've never run File Browser, you will need to create the database. -See 'filebrowser help config init' for more information.`, +For this specific command, all the flags you have available (except +"config" for the configuration file), can be given either through +environment variables or configuration files. + +If you don't set "config", it will look for a configuration file called +.filebrowser.{json, toml, yaml, yml} in the following directories: + +- ./ +- $HOME/ +- /etc/filebrowser/ + +The precedence of the configuration values are as follows: + +- flag +- environment variable +- configuration file +- defaults + +The environment variables are prefixed by "FB_" followed by the option +name in caps. So to set "database" via an env variable, you should +set FB_DATABASE equals to the path. + +Also, if the database path doesn't exist, File Browser will enter into +the quick setup mode and a new database will be bootstraped and a new +user created with the credentials from options "username" and "password".`, Run: serveAndListen, } func serveAndListen(cmd *cobra.Command, args []string) { - initConfig() - switch logMethod := v.GetString("log"); logMethod { case "stdout": log.SetOutput(os.Stdout) @@ -97,6 +129,12 @@ func serveAndListen(cmd *cobra.Command, args []string) { checkErr(err) settings, err := st.Settings.Get() checkErr(err) + + // Despite Base URL and Scope being "server" type of + // variables, we persist them to the database because + // they are needed during the execution and not only + // to start up the server. + settings.BaseURL = v.GetString("baseurl") settings.Scope = scope err = st.Settings.Save(settings) checkErr(err) @@ -130,7 +168,7 @@ func quickSetup(cmd *cobra.Command) { set := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit - BaseURL: "", + BaseURL: v.GetString("baseurl"), Signup: false, AuthMethod: auth.MethodJSONAuth, Defaults: settings.UserDefaults{ @@ -157,11 +195,17 @@ func quickSetup(cmd *cobra.Command) { err = st.Auth.Save(&auth.JSONAuth{}) checkErr(err) - password, err := users.HashPwd("admin") + username := v.GetString("username") + password := v.GetString("password") + if username == "" || password == "" { + checkErr(errors.New("username and password cannot be empty during quick setup")) + } + + password, err = users.HashPwd(password) checkErr(err) user := &users.User{ - Username: "admin", + Username: username, Password: password, LockPassword: false, } @@ -173,7 +217,6 @@ func quickSetup(cmd *cobra.Command) { checkErr(err) } -// initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile == "" { home, err := homedir.Dir() @@ -194,8 +237,7 @@ func initConfig() { if _, ok := err.(v.ConfigParseError); ok { panic(err) } - log.Println("No config file provided") - } else { - log.Println("Using config file:", v.ConfigFileUsed()) + // TODO: log.Println("No config file provided") } + // else TODO: log.Println("Using config file:", v.ConfigFileUsed()) } diff --git a/cmd/rule_rm.go b/cmd/rule_rm.go index 8b1dda1a..264fdb19 100644 --- a/cmd/rule_rm.go +++ b/cmd/rule_rm.go @@ -1,6 +1,8 @@ package cmd import ( + "strconv" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/users" @@ -14,21 +16,39 @@ func init() { } var rulesRmCommand = &cobra.Command{ - Use: "rm", + Use: "rm [index_end]", Short: "Remove a global rule or user rule", Long: `Remove a global rule or user rule.`, - Args: cobra.NoArgs, + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { + return err + } + + for _, arg := range args { + if _, err := strconv.Atoi(arg); err != nil { + return err + } + } + + return nil + }, Run: func(cmd *cobra.Command, args []string) { - index := mustGetUint(cmd, "index") + i, err := strconv.Atoi(args[0]) + checkErr(err) + f := i + if len(args) == 2 { + f, err = strconv.Atoi(args[1]) + checkErr(err) + } user := func(u *users.User, st *storage.Storage) { - u.Rules = append(u.Rules[:index], u.Rules[index+1:]...) + u.Rules = append(u.Rules[:i], u.Rules[f+1:]...) err := st.Users.Save(u) checkErr(err) } global := func(s *settings.Settings, st *storage.Storage) { - s.Rules = append(s.Rules[:index], s.Rules[index+1:]...) + s.Rules = append(s.Rules[:i], s.Rules[f+1:]...) err := st.Settings.Save(s) checkErr(err) } diff --git a/cmd/rules.go b/cmd/rules.go index 2a7d219d..7a651e53 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -83,9 +83,17 @@ func printRules(rules []rules.Rule, id interface{}) { for id, rule := range rules { fmt.Printf("(%d) ", id) if rule.Regex { - fmt.Printf("Allow: %t\tRegex: %s\n", rule.Allow, rule.Regexp.Raw) + if rule.Allow { + fmt.Printf("Allow Regex: \t%s\n", rule.Regexp.Raw) + } else { + fmt.Printf("Disallow Regex: \t%s\n", rule.Regexp.Raw) + } } else { - fmt.Printf("Allow: %t\tPath: %s\n", rule.Allow, rule.Path) + if rule.Allow { + fmt.Printf("Allow Path: \t%s\n", rule.Path) + } else { + fmt.Printf("Disallow Path: \t%s\n", rule.Path) + } } } } diff --git a/cmd/rules_add.go b/cmd/rules_add.go index 8e982a95..2b25b1e8 100644 --- a/cmd/rules_add.go +++ b/cmd/rules_add.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "regexp" "github.com/filebrowser/filebrowser/v2/rules" @@ -13,41 +12,33 @@ import ( func init() { rulesCmd.AddCommand(rulesAddCmd) - rulesAddCmd.Flags().BoolP("allow", "a", false, "allow rule instead of disallow") - rulesAddCmd.Flags().StringP("path", "p", "", "path to which the rule applies") - rulesAddCmd.Flags().StringP("regex", "r", "", "regex to which the rule applies") + rulesAddCmd.Flags().BoolP("allow", "a", false, "indicates this is an allow rule") + rulesAddCmd.Flags().BoolP("regex", "r", false, "indicates this is a regex rule") } var rulesAddCmd = &cobra.Command{ - Use: "add", + Use: "add ", Short: "Add a global rule or user rule", - Long: `Add a global rule or user rule. You must -set either path or regex.`, - Args: cobra.NoArgs, + Long: `Add a global rule or user rule.`, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { allow := mustGetBool(cmd, "allow") - path := mustGetString(cmd, "path") - regex := mustGetString(cmd, "regex") + regex := mustGetBool(cmd, "regex") + exp := args[0] - if path == "" && regex == "" { - panic(errors.New("you must set either --path or --regex flags")) - } - - if path != "" && regex != "" { - panic(errors.New("you can't set --path and --regex flags at the same time")) - } - - if regex != "" { - regexp.MustCompile(regex) + if regex { + regexp.MustCompile(exp) } rule := rules.Rule{ Allow: allow, - Path: path, - Regex: regex != "", - Regexp: &rules.Regexp{ - Raw: regex, - }, + Regex: regex, + } + + if regex { + rule.Regexp = &rules.Regexp{Raw: exp} + } else { + rule.Path = exp } user := func(u *users.User, st *storage.Storage) { diff --git a/cmd/users.go b/cmd/users.go index 3afdba86..a69199e9 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strconv" "text/tabwriter" "github.com/filebrowser/filebrowser/v2/settings" @@ -53,15 +54,12 @@ func printUsers(users []*users.User) { w.Flush() } -func usernameOrIDRequired(cmd *cobra.Command, args []string) error { - username, _ := cmd.Flags().GetString("username") - id, _ := cmd.Flags().GetUint("id") - - if username == "" && id == 0 { - return errors.New("'username' of 'id' flag required") +func parseUsernameOrID(arg string) (string, uint) { + id, err := strconv.ParseUint(arg, 10, 0) + if err != nil { + return arg, 0 } - - return nil + return "", uint(id) } func addUserFlags(cmd *cobra.Command) { diff --git a/cmd/users_new.go b/cmd/users_add.go similarity index 55% rename from cmd/users_new.go rename to cmd/users_add.go index 47480d65..06906d66 100644 --- a/cmd/users_new.go +++ b/cmd/users_add.go @@ -6,20 +6,15 @@ import ( ) func init() { - usersCmd.AddCommand(usersNewCmd) - - addUserFlags(usersNewCmd) - usersNewCmd.Flags().StringP("username", "u", "", "new users's username") - usersNewCmd.Flags().StringP("password", "p", "", "new user's password") - usersNewCmd.MarkFlagRequired("username") - usersNewCmd.MarkFlagRequired("password") + usersCmd.AddCommand(usersAddCmd) + addUserFlags(usersAddCmd) } -var usersNewCmd = &cobra.Command{ - Use: "new", +var usersAddCmd = &cobra.Command{ + Use: "add ", Short: "Create a new user", Long: `Create a new user and add it to the database.`, - Args: cobra.NoArgs, + Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { db := getDB() defer db.Close() @@ -29,12 +24,11 @@ var usersNewCmd = &cobra.Command{ checkErr(err) getUserDefaults(cmd, &s.Defaults, false) - password, _ := cmd.Flags().GetString("password") - password, err = users.HashPwd(password) + password, err := users.HashPwd(args[1]) checkErr(err) user := &users.User{ - Username: mustGetString(cmd, "username"), + Username: args[0], Password: password, LockPassword: mustGetBool(cmd, "lockPassword"), } diff --git a/cmd/users_find.go b/cmd/users_find.go index 126c7354..070959c6 100644 --- a/cmd/users_find.go +++ b/cmd/users_find.go @@ -8,15 +8,13 @@ import ( func init() { usersCmd.AddCommand(usersFindCmd) usersCmd.AddCommand(usersLsCmd) - usersFindCmd.Flags().StringP("username", "u", "", "username to find") - usersFindCmd.Flags().UintP("id", "i", 0, "id to find") } var usersFindCmd = &cobra.Command{ - Use: "find", + Use: "find ", Short: "Find a user by username or id", Long: `Find a user by username or id. If no flag is set, all users will be printed.`, - Args: cobra.NoArgs, + Args: cobra.ExactArgs(1), Run: findUsers, } @@ -32,28 +30,25 @@ var findUsers = func(cmd *cobra.Command, args []string) { defer db.Close() st := getStorage(db) - settings, err := st.Settings.Get() - checkErr(err) + var ( + list []*users.User + user *users.User + err error + ) - username, _ := cmd.Flags().GetString("username") - id, _ := cmd.Flags().GetUint("id") + if len(args) == 1 { + username, id := parseUsernameOrID(args[0]) + if username != "" { + user, err = st.Users.Get("", username) + } else { + user, err = st.Users.Get("", id) + } - var list []*users.User - var user *users.User - - if username != "" { - user, err = st.Users.Get(settings.Scope, username) - } else if id != 0 { - user, err = st.Users.Get(settings.Scope, id) - } else { - list, err = st.Users.Gets(settings.Scope) - } - - checkErr(err) - - if user != nil { list = []*users.User{user} + } else { + list, err = st.Users.Gets("") } + checkErr(err) printUsers(list) } diff --git a/cmd/users_rm.go b/cmd/users_rm.go index 5f1b5eb6..e942c62b 100644 --- a/cmd/users_rm.go +++ b/cmd/users_rm.go @@ -8,23 +8,19 @@ import ( func init() { usersCmd.AddCommand(usersRmCmd) - usersRmCmd.Flags().StringP("username", "u", "", "username to delete") - usersRmCmd.Flags().UintP("id", "i", 0, "id to delete") } var usersRmCmd = &cobra.Command{ - Use: "rm", + Use: "rm ", Short: "Delete a user by username or id", Long: `Delete a user by username or id`, - Args: usernameOrIDRequired, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db := getDB() defer db.Close() st := getStorage(db) - username, _ := cmd.Flags().GetString("username") - id, _ := cmd.Flags().GetUint("id") - + username, id := parseUsernameOrID(args[0]) var err error if username != "" { diff --git a/cmd/users_update.go b/cmd/users_update.go index f0aa27ba..fb6f65a5 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -9,18 +9,17 @@ import ( func init() { usersCmd.AddCommand(usersUpdateCmd) - usersUpdateCmd.Flags().UintP("id", "i", 0, "id of the user") - usersUpdateCmd.Flags().StringP("username", "u", "", "user to change or new username if flag 'id' is set") usersUpdateCmd.Flags().StringP("password", "p", "", "new password") + usersUpdateCmd.Flags().StringP("username", "u", "", "new username") addUserFlags(usersUpdateCmd) } var usersUpdateCmd = &cobra.Command{ - Use: "update", + Use: "update ", Short: "Updates an existing user", Long: `Updates an existing user. Set the flags for the options you want to change.`, - Args: usernameOrIDRequired, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db := getDB() defer db.Close() @@ -29,9 +28,9 @@ options you want to change.`, set, err := st.Settings.Get() checkErr(err) - id, _ := cmd.Flags().GetUint("id") - username := mustGetString(cmd, "username") + username, id := parseUsernameOrID(args[0]) password := mustGetString(cmd, "password") + newUsername := mustGetString(cmd, "username") var user *users.User @@ -60,8 +59,8 @@ options you want to change.`, user.Sorting = defaults.Sorting user.LockPassword = mustGetBool(cmd, "lockPassword") - if user.Username != username && username != "" { - user.Username = username + if newUsername != "" { + user.Username = newUsername } if password != "" { diff --git a/cmd/utils.go b/cmd/utils.go index fe0ee06d..b5f84915 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -55,12 +55,6 @@ func mustGetBool(cmd *cobra.Command, flag string) bool { return b } -func mustGetInt(cmd *cobra.Command, flag string) int { - b, err := cmd.Flags().GetInt(flag) - checkErr(err) - return b -} - func mustGetUint(cmd *cobra.Command, flag string) uint { b, err := cmd.Flags().GetUint(flag) checkErr(err)