From 42134a48499b658d77eaa01785a4a70e36fb0dc4 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sun, 6 Jan 2019 13:21:31 +0000
Subject: [PATCH] feat: cleanup cli

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

Former-commit-id: 2923c251335301f361098890bf67d47cd58c0f86 [formerly 277653e21c4b9077ea3b83c149f0c5b2982b694f] [formerly 7481557b6f47c8de6499f3ee7339ea8d29216700 [formerly 999c69de5c96a3ea42c22b88e3ae016f0cfd6f52]]
Former-commit-id: 65eaacb4aee11f84c9f97ca2834def045921202a [formerly 7c9260a1fe142258dd0ac02c4710b03badd66d76]
Former-commit-id: ff3f3d7189b2e8cc2f00bef581cba108780e4552
---
 cmd/cmds_add.go                    | 15 +++----
 cmd/cmds_rm.go                     | 33 +++++++++++----
 cmd/config.go                      |  2 -
 cmd/config_init.go                 |  1 -
 cmd/config_set.go                  |  2 -
 cmd/root.go                        | 68 ++++++++++++++++++++++++------
 cmd/rule_rm.go                     | 30 ++++++++++---
 cmd/rules.go                       | 12 +++++-
 cmd/rules_add.go                   | 41 +++++++-----------
 cmd/users.go                       | 14 +++---
 cmd/{users_new.go => users_add.go} | 20 +++------
 cmd/users_find.go                  | 39 ++++++++---------
 cmd/users_rm.go                    | 10 ++---
 cmd/users_update.go                | 15 +++----
 cmd/utils.go                       |  6 ---
 15 files changed, 176 insertions(+), 132 deletions(-)
 rename cmd/{users_new.go => users_add.go} (55%)

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 <event> <command>",
 	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 <event> <index> [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> [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 <path|expression>",
 	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 <username> <password>",
 	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 <id|username>",
 	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 <id|username>",
 	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 <id|username>",
 	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)