From 1582b8b2cd1c62fa93e60ca9b4e740e940b02e84 Mon Sep 17 00:00:00 2001 From: Jagadam Dinesh Reddy Date: Tue, 22 Jul 2025 11:55:21 +0530 Subject: [PATCH] feat: better error handling for sys kill signals --- cmd/cmd.go | 10 +-- cmd/cmds_add.go | 11 ++- cmd/cmds_ls.go | 12 ++- cmd/cmds_rm.go | 19 +++-- cmd/config.go | 193 +++++++++++++++++++++++++++---------------- cmd/config_cat.go | 16 ++-- cmd/config_export.go | 19 +++-- cmd/config_import.go | 64 ++++++++++---- cmd/config_init.go | 163 +++++++++++++++++++++++++++++------- cmd/config_set.go | 88 +++++++++++++------- cmd/docs.go | 37 ++++++--- cmd/hash.go | 7 +- cmd/root.go | 122 ++++++++++++++++++++------- cmd/rule_rm.go | 22 ++--- cmd/rules.go | 55 ++++++++---- cmd/rules_add.go | 24 +++--- cmd/rules_ls.go | 4 +- cmd/upgrade.go | 19 +++-- cmd/users.go | 57 +++++++------ cmd/users_add.go | 39 +++++++-- cmd/users_export.go | 11 ++- cmd/users_find.go | 11 ++- cmd/users_import.go | 63 +++++++++----- cmd/users_rm.go | 7 +- cmd/users_update.go | 39 +++++++-- cmd/utils.go | 64 ++++++++------ errors/errors.go | 54 ++++++++++++ main.go | 7 +- 28 files changed, 876 insertions(+), 361 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 18f52337..2dc02107 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,12 +1,6 @@ package cmd -import ( - "log" -) - // Execute executes the commands. -func Execute() { - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } +func Execute() error { + return rootCmd.Execute() } diff --git a/cmd/cmds_add.go b/cmd/cmds_add.go index 63571ba6..a4d17061 100644 --- a/cmd/cmds_add.go +++ b/cmd/cmds_add.go @@ -15,13 +15,18 @@ var cmdsAddCmd = &cobra.Command{ Short: "Add a command to run on a specific event", Long: `Add a command to run on a specific event.`, Args: cobra.MinimumNArgs(2), - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } command := strings.Join(args[1:], " ") s.Commands[args[0]] = append(s.Commands[args[0]], command) err = d.store.Settings.Save(s) - checkErr(err) + if err != nil { + return err + } printEvents(s.Commands) + return nil }, pythonConfig{}), } diff --git a/cmd/cmds_ls.go b/cmd/cmds_ls.go index 6d19c846..fa901a56 100644 --- a/cmd/cmds_ls.go +++ b/cmd/cmds_ls.go @@ -14,10 +14,15 @@ var cmdsLsCmd = &cobra.Command{ Short: "List all commands for each event", Long: `List all commands for each event.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { s, err := d.store.Settings.Get() - checkErr(err) - evt := mustGetString(cmd.Flags(), "event") + if err != nil { + return err + } + evt, err := getString(cmd.Flags(), "event") + if err != nil { + return err + } if evt == "" { printEvents(s.Commands) @@ -27,5 +32,6 @@ var cmdsLsCmd = &cobra.Command{ show["after_"+evt] = s.Commands["after_"+evt] printEvents(show) } + return nil }, pythonConfig{}), } diff --git a/cmd/cmds_rm.go b/cmd/cmds_rm.go index 7f187f7f..34089388 100644 --- a/cmd/cmds_rm.go +++ b/cmd/cmds_rm.go @@ -35,22 +35,31 @@ including 'index_end'.`, return nil }, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } evt := args[0] i, err := strconv.Atoi(args[1]) - checkErr(err) + if err != nil { + return err + } f := i if len(args) == 3 { f, err = strconv.Atoi(args[2]) - checkErr(err) + if err != nil { + return err + } } s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...) err = d.store.Settings.Save(s) - checkErr(err) + if err != nil { + return err + } printEvents(s.Commands) + return nil }, pythonConfig{}), } diff --git a/cmd/config.go b/cmd/config.go index 43e07730..5ce54ad9 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -55,9 +55,12 @@ func addConfigFlags(flags *pflag.FlagSet) { flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "Mode bits that new directories are created with") } -//nolint:gocyclo -func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) { - method := settings.AuthMethod(mustGetString(flags, "auth.method")) +func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) { + methodStr, err := getString(flags, "auth.method") + if err != nil { + return "", nil, err + } + method := settings.AuthMethod(methodStr) var defaultAuther map[string]interface{} if len(defaults) > 0 { @@ -68,83 +71,124 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings. method = def.AuthMethod case auth.Auther: ms, err := json.Marshal(def) - checkErr(err) + if err != nil { + return "", nil, err + } err = json.Unmarshal(ms, &defaultAuther) - checkErr(err) + if err != nil { + return "", nil, err + } } } } } - var auther auth.Auther - if method == auth.MethodProxyAuth { - header := mustGetString(flags, "auth.header") - - if header == "" { - header = defaultAuther["header"].(string) - } - - if header == "" { - checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'")) - } - - auther = &auth.ProxyAuth{Header: header} - } - - if method == auth.MethodNoAuth { - auther = &auth.NoAuth{} - } - - if method == auth.MethodJSONAuth { - jsonAuth := &auth.JSONAuth{} - host := mustGetString(flags, "recaptcha.host") - key := mustGetString(flags, "recaptcha.key") - secret := mustGetString(flags, "recaptcha.secret") - - if key == "" { - if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { - key = kmap["key"].(string) - } - } - - if secret == "" { - if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { - secret = smap["secret"].(string) - } - } - - if key != "" && secret != "" { - jsonAuth.ReCaptcha = &auth.ReCaptcha{ - Host: host, - Key: key, - Secret: secret, - } - } - auther = jsonAuth - } - - if method == auth.MethodHookAuth { - command := mustGetString(flags, "auth.command") - - if command == "" { - command = defaultAuther["command"].(string) - } - - if command == "" { - checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'")) - } - - auther = &auth.HookAuth{Command: command} - } - - if auther == nil { - panic(errors.ErrInvalidAuthMethod) - } - - return method, auther + return method, defaultAuther, nil } -func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) { +func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) { + header, err := getString(flags, "auth.header") + if err != nil { + return nil, err + } + + if header == "" { + header = defaultAuther["header"].(string) + } + + if header == "" { + return nil, nerrors.New("you must set the flag 'auth.header' for method 'proxy'") + } + + return &auth.ProxyAuth{Header: header}, nil +} + +func getNoAuth() auth.Auther { + return &auth.NoAuth{} +} + +func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) { + jsonAuth := &auth.JSONAuth{} + host, err := getString(flags, "recaptcha.host") + if err != nil { + return nil, err + } + key, err := getString(flags, "recaptcha.key") + if err != nil { + return nil, err + } + secret, err := getString(flags, "recaptcha.secret") + if err != nil { + return nil, err + } + + if key == "" { + if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { + key = kmap["key"].(string) + } + } + + if secret == "" { + if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { + secret = smap["secret"].(string) + } + } + + if key != "" && secret != "" { + jsonAuth.ReCaptcha = &auth.ReCaptcha{ + Host: host, + Key: key, + Secret: secret, + } + } + return jsonAuth, nil +} + +func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) { + command, err := getString(flags, "auth.command") + if err != nil { + return nil, err + } + + if command == "" { + command = defaultAuther["command"].(string) + } + + if command == "" { + return nil, nerrors.New("you must set the flag 'auth.command' for method 'hook'") + } + + return &auth.HookAuth{Command: command}, nil +} + +func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther, error) { + method, defaultAuther, err := getAuthMethod(flags, defaults...) + if err != nil { + return "", nil, err + } + + var auther auth.Auther + switch method { + case auth.MethodProxyAuth: + auther, err = getProxyAuth(flags, defaultAuther) + case auth.MethodNoAuth: + auther = getNoAuth() + case auth.MethodJSONAuth: + auther, err = getJSONAuth(flags, defaultAuther) + case auth.MethodHookAuth: + auther, err = getHookAuth(flags, defaultAuther) + default: + return "", nil, errors.ErrInvalidAuthMethod + } + + if err != nil { + return "", nil, err + } + + return method, auther, nil +} + +func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup) @@ -192,6 +236,9 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut w.Flush() b, err := json.MarshalIndent(auther, "", " ") - checkErr(err) + if err != nil { + return err + } fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b)) + return nil } diff --git a/cmd/config_cat.go b/cmd/config_cat.go index 8aaf05c3..39b1f664 100644 --- a/cmd/config_cat.go +++ b/cmd/config_cat.go @@ -13,13 +13,19 @@ var configCatCmd = &cobra.Command{ Short: "Prints the configuration", Long: `Prints the configuration.`, Args: cobra.NoArgs, - Run: python(func(_ *cobra.Command, _ []string, d pythonData) { + RunE: python(func(_ *cobra.Command, _ []string, d *pythonData) error { set, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } ser, err := d.store.Settings.GetServer() - checkErr(err) + if err != nil { + return err + } auther, err := d.store.Auth.Get(set.AuthMethod) - checkErr(err) - printSettings(ser, set, auther) + if err != nil { + return err + } + return printSettings(ser, set, auther) }, pythonConfig{}), } diff --git a/cmd/config_export.go b/cmd/config_export.go index 6472bbe6..9877fb63 100644 --- a/cmd/config_export.go +++ b/cmd/config_export.go @@ -15,15 +15,21 @@ var configExportCmd = &cobra.Command{ json or yaml file. This exported configuration can be changed, and imported again with 'config import' command.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { settings, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } server, err := d.store.Settings.GetServer() - checkErr(err) + if err != nil { + return err + } auther, err := d.store.Auth.Get(settings.AuthMethod) - checkErr(err) + if err != nil { + return err + } data := &settingsFile{ Settings: settings, @@ -32,6 +38,9 @@ and imported again with 'config import' command.`, } err = marshal(args[0], data) - checkErr(err) + if err != nil { + return err + } + return nil }, pythonConfig{}), } diff --git a/cmd/config_import.go b/cmd/config_import.go index 6c609481..7763517d 100644 --- a/cmd/config_import.go +++ b/cmd/config_import.go @@ -34,26 +34,35 @@ database. The path must be for a json or yaml file.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { var key []byte + var err error if d.hadDB { - settings, err := d.store.Settings.Get() - checkErr(err) + settings, settingErr := d.store.Settings.Get() + if settingErr != nil { + return settingErr + } key = settings.Key } else { key = generateKey() } file := settingsFile{} - err := unmarshal(args[0], &file) - checkErr(err) + err = unmarshal(args[0], &file) + if err != nil { + return err + } file.Settings.Key = key err = d.store.Settings.Save(file.Settings) - checkErr(err) + if err != nil { + return err + } err = d.store.Settings.SaveServer(file.Server) - checkErr(err) + if err != nil { + return err + } var rawAuther interface{} if filepath.Ext(args[0]) != ".json" { @@ -63,32 +72,51 @@ The path must be for a json or yaml file.`, } var auther auth.Auther + var autherErr error switch file.Settings.AuthMethod { case auth.MethodJSONAuth: - auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth) + var a interface{} + a, autherErr = getAuther(auth.JSONAuth{}, rawAuther) + auther = a.(*auth.JSONAuth) case auth.MethodNoAuth: - auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth) + var a interface{} + a, autherErr = getAuther(auth.NoAuth{}, rawAuther) + auther = a.(*auth.NoAuth) case auth.MethodProxyAuth: - auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth) + var a interface{} + a, autherErr = getAuther(auth.ProxyAuth{}, rawAuther) + auther = a.(*auth.ProxyAuth) case auth.MethodHookAuth: - auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth) + var a interface{} + a, autherErr = getAuther(&auth.HookAuth{}, rawAuther) + auther = a.(*auth.HookAuth) default: - checkErr(errors.New("invalid auth method")) + return errors.New("invalid auth method") + } + + if autherErr != nil { + return autherErr } err = d.store.Auth.Save(auther) - checkErr(err) + if err != nil { + return err + } - printSettings(file.Server, file.Settings, auther) + return printSettings(file.Server, file.Settings, auther) }, pythonConfig{allowNoDB: true}), } -func getAuther(sample auth.Auther, data interface{}) interface{} { +func getAuther(sample auth.Auther, data interface{}) (interface{}, error) { authType := reflect.TypeOf(sample) auther := reflect.New(authType).Interface() bytes, err := json.Marshal(data) - checkErr(err) + if err != nil { + return nil, err + } err = json.Unmarshal(bytes, &auther) - checkErr(err) - return auther + if err != nil { + return nil, err + } + return auther, nil } diff --git a/cmd/config_init.go b/cmd/config_init.go index f168a448..ea756d0b 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -22,54 +22,161 @@ this options can be changed in the future with the command to the defaults when creating new users and you don't override the options.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { defaults := settings.UserDefaults{} flags := cmd.Flags() - getUserDefaults(flags, &defaults, true) - authMethod, auther := getAuthentication(flags) + err := getUserDefaults(flags, &defaults, true) + if err != nil { + return err + } + authMethod, auther, err := getAuthentication(flags) + if err != nil { + return err + } + + key := generateKey() + + signup, err := getBool(flags, "signup") + if err != nil { + return err + } + + createUserDir, err := getBool(flags, "create-user-dir") + if err != nil { + return err + } + + minLength, err := getUint(flags, "minimum-password-length") + if err != nil { + return err + } + + shell, err := getString(flags, "shell") + if err != nil { + return err + } + + brandingName, err := getString(flags, "branding.name") + if err != nil { + return err + } + + brandingDisableExternal, err := getBool(flags, "branding.disableExternal") + if err != nil { + return err + } + + brandingDisableUsedPercentage, err := getBool(flags, "branding.disableUsedPercentage") + if err != nil { + return err + } + + brandingTheme, err := getString(flags, "branding.theme") + if err != nil { + return err + } + + brandingFiles, err := getString(flags, "branding.files") + if err != nil { + return err + } s := &settings.Settings{ - Key: generateKey(), - Signup: mustGetBool(flags, "signup"), - CreateUserDir: mustGetBool(flags, "create-user-dir"), - MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"), - Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")), + Key: key, + Signup: signup, + CreateUserDir: createUserDir, + MinimumPasswordLength: minLength, + Shell: convertCmdStrToCmdArray(shell), AuthMethod: authMethod, Defaults: defaults, Branding: settings.Branding{ - Name: mustGetString(flags, "branding.name"), - DisableExternal: mustGetBool(flags, "branding.disableExternal"), - DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"), - Theme: mustGetString(flags, "branding.theme"), - Files: mustGetString(flags, "branding.files"), + Name: brandingName, + DisableExternal: brandingDisableExternal, + DisableUsedPercentage: brandingDisableUsedPercentage, + Theme: brandingTheme, + Files: brandingFiles, }, - FileMode: mustGetMode(flags, "file-mode"), - DirMode: mustGetMode(flags, "dir-mode"), + } + + s.FileMode, err = getMode(flags, "file-mode") + if err != nil { + return err + } + + s.DirMode, err = getMode(flags, "file-mode") + if err != nil { + return err + } + + address, err := getString(flags, "address") + if err != nil { + return err + } + + socket, err := getString(flags, "socket") + if err != nil { + return err + } + + root, err := getString(flags, "root") + if err != nil { + return err + } + + baseURL, err := getString(flags, "baseurl") + if err != nil { + return err + } + + tlsKey, err := getString(flags, "key") + if err != nil { + return err + } + + cert, err := getString(flags, "cert") + if err != nil { + return err + } + + port, err := getString(flags, "port") + if err != nil { + return err + } + + log, err := getString(flags, "log") + if err != nil { + return err } ser := &settings.Server{ - Address: mustGetString(flags, "address"), - Socket: mustGetString(flags, "socket"), - Root: mustGetString(flags, "root"), - BaseURL: mustGetString(flags, "baseurl"), - TLSKey: mustGetString(flags, "key"), - TLSCert: mustGetString(flags, "cert"), - Port: mustGetString(flags, "port"), - Log: mustGetString(flags, "log"), + Address: address, + Socket: socket, + Root: root, + BaseURL: baseURL, + TLSKey: tlsKey, + TLSCert: cert, + Port: port, + Log: log, } - err := d.store.Settings.Save(s) - checkErr(err) + err = d.store.Settings.Save(s) + if err != nil { + return err + } err = d.store.Settings.SaveServer(ser) - checkErr(err) + if err != nil { + return err + } err = d.store.Auth.Save(auther) - checkErr(err) + if err != nil { + return err + } fmt.Printf(` Congratulations! You've set up your database to use with File Browser. Now add your first user via 'filebrowser users add' and then you just need to call the main command to boot up the server. `) - printSettings(ser, s, auther) + return printSettings(ser, s, auther) }, pythonConfig{noDB: true}), } diff --git a/cmd/config_set.go b/cmd/config_set.go index 6ba99e73..a7f66902 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -16,77 +16,105 @@ var configSetCmd = &cobra.Command{ Long: `Updates the configuration. Set the flags for the options you want to change. Other options will remain unchanged.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { flags := cmd.Flags() set, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } ser, err := d.store.Settings.GetServer() - checkErr(err) + if err != nil { + return err + } hasAuth := false flags.Visit(func(flag *pflag.Flag) { + if err != nil { + return + } switch flag.Name { case "baseurl": - ser.BaseURL = mustGetString(flags, flag.Name) + ser.BaseURL, err = getString(flags, flag.Name) case "root": - ser.Root = mustGetString(flags, flag.Name) + ser.Root, err = getString(flags, flag.Name) case "socket": - ser.Socket = mustGetString(flags, flag.Name) + ser.Socket, err = getString(flags, flag.Name) case "cert": - ser.TLSCert = mustGetString(flags, flag.Name) + ser.TLSCert, err = getString(flags, flag.Name) case "key": - ser.TLSKey = mustGetString(flags, flag.Name) + ser.TLSKey, err = getString(flags, flag.Name) case "address": - ser.Address = mustGetString(flags, flag.Name) + ser.Address, err = getString(flags, flag.Name) case "port": - ser.Port = mustGetString(flags, flag.Name) + ser.Port, err = getString(flags, flag.Name) case "log": - ser.Log = mustGetString(flags, flag.Name) + ser.Log, err = getString(flags, flag.Name) case "signup": - set.Signup = mustGetBool(flags, flag.Name) + set.Signup, err = getBool(flags, flag.Name) case "auth.method": hasAuth = true case "shell": - set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name)) + var shell string + shell, err = getString(flags, flag.Name) + set.Shell = convertCmdStrToCmdArray(shell) case "create-user-dir": - set.CreateUserDir = mustGetBool(flags, flag.Name) + set.CreateUserDir, err = getBool(flags, flag.Name) case "minimum-password-length": - set.MinimumPasswordLength = mustGetUint(flags, flag.Name) + set.MinimumPasswordLength, err = getUint(flags, flag.Name) case "branding.name": - set.Branding.Name = mustGetString(flags, flag.Name) + set.Branding.Name, err = getString(flags, flag.Name) case "branding.color": - set.Branding.Color = mustGetString(flags, flag.Name) + set.Branding.Color, err = getString(flags, flag.Name) case "branding.theme": - set.Branding.Theme = mustGetString(flags, flag.Name) + set.Branding.Theme, err = getString(flags, flag.Name) case "branding.disableExternal": - set.Branding.DisableExternal = mustGetBool(flags, flag.Name) + set.Branding.DisableExternal, err = getBool(flags, flag.Name) case "branding.disableUsedPercentage": - set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name) + set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name) case "branding.files": - set.Branding.Files = mustGetString(flags, flag.Name) + set.Branding.Files, err = getString(flags, flag.Name) case "file-mode": - set.FileMode = mustGetMode(flags, flag.Name) + set.FileMode, err = getMode(flags, flag.Name) case "dir-mode": - set.DirMode = mustGetMode(flags, flag.Name) + set.DirMode, err = getMode(flags, flag.Name) } }) - getUserDefaults(flags, &set.Defaults, false) + if err != nil { + return err + } + + err = getUserDefaults(flags, &set.Defaults, false) + if err != nil { + return err + } // read the defaults auther, err := d.store.Auth.Get(set.AuthMethod) - checkErr(err) + if err != nil { + return err + } // check if there are new flags for existing auth method - set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther) + set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther) + if err != nil { + return err + } err = d.store.Auth.Save(auther) - checkErr(err) + if err != nil { + return err + } err = d.store.Settings.Save(set) - checkErr(err) + if err != nil { + return err + } err = d.store.Settings.SaveServer(ser) - checkErr(err) - printSettings(ser, set, auther) + if err != nil { + return err + } + + return printSettings(ser, set, auther) }, pythonConfig{}), } diff --git a/cmd/docs.go b/cmd/docs.go index 88d39d18..5f26daf9 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -39,12 +39,19 @@ var docsCmd = &cobra.Command{ Use: "docs", Hidden: true, Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, _ []string) { - dir := mustGetString(cmd.Flags(), "path") - generateDocs(rootCmd, dir) + RunE: func(cmd *cobra.Command, _ []string) error { + dir, err := getString(cmd.Flags(), "path") + if err != nil { + return err + } + + err = generateDocs(rootCmd, dir) + if err != nil { + return err + } names := []string{} - err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error { + err = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return err } @@ -56,30 +63,38 @@ var docsCmd = &cobra.Command{ names = append(names, info.Name()) return nil }) + if err != nil { + return err + } - checkErr(err) printToc(names) + return nil }, } -func generateDocs(cmd *cobra.Command, dir string) { +func generateDocs(cmd *cobra.Command, dir string) error { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } - generateDocs(c, dir) + err := generateDocs(c, dir) + if err != nil { + return err + } } basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md" filename := filepath.Join(dir, basename) f, err := os.Create(filename) - checkErr(err) + if err != nil { + return err + } defer f.Close() - generateMarkdown(cmd, f) + return generateMarkdown(cmd, f) } -func generateMarkdown(cmd *cobra.Command, w io.Writer) { +func generateMarkdown(cmd *cobra.Command, w io.Writer) error { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() @@ -108,7 +123,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) { printOptions(buf, cmd) _, err := buf.WriteTo(w) - checkErr(err) + return err } func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) { diff --git a/cmd/hash.go b/cmd/hash.go index 7d16df5e..3e7d8cdc 100644 --- a/cmd/hash.go +++ b/cmd/hash.go @@ -17,9 +17,12 @@ var hashCmd = &cobra.Command{ Short: "Hashes a password", Long: `Hashes a password using bcrypt algorithm.`, Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { pwd, err := users.HashPwd(args[0]) - checkErr(err) + if err != nil { + return err + } fmt.Println(pwd) + return nil }, } diff --git a/cmd/root.go b/cmd/root.go index fd800188..286f6343 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "io" "io/fs" "log" @@ -25,6 +26,7 @@ import ( "github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/diskcache" + fbErrors "github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/frontend" fbhttp "github.com/filebrowser/filebrowser/v2/http" "github.com/filebrowser/filebrowser/v2/img" @@ -39,6 +41,7 @@ var ( func init() { cobra.OnInitialize(initConfig) + rootCmd.SilenceUsage = true cobra.MousetrapHelpText = "" rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") @@ -112,36 +115,48 @@ set FB_DATABASE. 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".`, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { log.Println(cfgFile) if !d.hadDB { - quickSetup(cmd.Flags(), d) + err := quickSetup(cmd.Flags(), *d) + if err != nil { + return err + } } // build img service workersCount, err := cmd.Flags().GetInt("img-processors") - checkErr(err) + if err != nil { + return err + } if workersCount < 1 { - log.Fatal("Image resize workers count could not be < 1") + return errors.New("image resize workers count could not be < 1") } imgSvc := img.New(workersCount) var fileCache diskcache.Interface = diskcache.NewNoOp() cacheDir, err := cmd.Flags().GetString("cache-dir") - checkErr(err) + if err != nil { + return err + } if cacheDir != "" { if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet - log.Fatalf("can't make directory %s: %s", cacheDir, err) + return fmt.Errorf("can't make directory %s: %w", cacheDir, err) } fileCache = diskcache.New(afero.NewOsFs(), cacheDir) } - server := getRunParams(cmd.Flags(), d.store) + server, err := getRunParams(cmd.Flags(), d.store) + if err != nil { + return err + } setupLog(server.Log) root, err := filepath.Abs(server.Root) - checkErr(err) + if err != nil { + return err + } server.Root = root adr := server.Address + ":" + server.Port @@ -151,22 +166,34 @@ user created with the credentials from options "username" and "password".`, switch { case server.Socket != "": listener, err = net.Listen("unix", server.Socket) - checkErr(err) + if err != nil { + return err + } socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet - checkErr(err) + if err != nil { + return err + } err = os.Chmod(server.Socket, os.FileMode(socketPerm)) - checkErr(err) + if err != nil { + return err + } case server.TLSKey != "" && server.TLSCert != "": cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet - checkErr(err) + if err != nil { + return err + } listener, err = tls.Listen("tcp", adr, &tls.Config{ MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cer}}, ) - checkErr(err) + if err != nil { + return err + } default: listener, err = net.Listen("tcp", adr) - checkErr(err) + if err != nil { + return err + } } assetsFs, err := fs.Sub(frontend.Assets(), "dist") @@ -175,7 +202,9 @@ user created with the credentials from options "username" and "password".`, } handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs) - checkErr(err) + if err != nil { + return err + } defer listener.Close() @@ -194,8 +223,15 @@ user created with the credentials from options "username" and "password".`, }() sigc := make(chan os.Signal, 1) - signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) - <-sigc + signal.Notify(sigc, + os.Interrupt, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + sig := <-sigc + log.Println("Got signal:", sig) shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd defer shutdownRelease() @@ -204,13 +240,28 @@ user created with the credentials from options "username" and "password".`, log.Fatalf("HTTP shutdown error: %v", err) } log.Println("Graceful shutdown complete.") + + switch sig { + case syscall.SIGHUP: + d.err = fbErrors.ErrSighup + case syscall.SIGINT: + d.err = fbErrors.ErrSigint + case syscall.SIGQUIT: + d.err = fbErrors.ErrSigquit + case syscall.SIGTERM: + d.err = fbErrors.ErrSigTerm + } + + return d.err }, pythonConfig{allowNoDB: true}), } //nolint:gocyclo -func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { +func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) { server, err := st.Settings.GetServer() - checkErr(err) + if err != nil { + return nil, err + } if val, set := getStringParamB(flags, "root"); set { server.Root = val @@ -253,7 +304,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { } if isAddrSet && isSocketSet { - checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert")) + return nil, errors.New("--socket flag cannot be used with --address, --port, --key nor --cert") } // Do not use saved Socket if address was manually set. @@ -284,7 +335,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { server.TokenExpirationTime = val } - return server + return server, nil } // getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default @@ -363,7 +414,7 @@ func setupLog(logMethod string) { } } -func quickSetup(flags *pflag.FlagSet, d pythonData) { +func quickSetup(flags *pflag.FlagSet, d pythonData) error { log.Println("Performing quick setup") set := &settings.Settings{ @@ -406,10 +457,14 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { set.AuthMethod = auth.MethodJSONAuth err = d.store.Auth.Save(&auth.JSONAuth{}) } + if err != nil { + return err + } - checkErr(err) err = d.store.Settings.Save(set) - checkErr(err) + if err != nil { + return err + } ser := &settings.Server{ BaseURL: getStringParam(flags, "baseurl"), @@ -422,7 +477,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { } err = d.store.Settings.SaveServer(ser) - checkErr(err) + if err != nil { + return err + } username := getStringParam(flags, "username") password := getStringParam(flags, "password") @@ -430,11 +487,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { if password == "" { var pwd string pwd, err = users.RandomPwd(set.MinimumPasswordLength) - checkErr(err) + if err != nil { + return err + } log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd) password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength) - checkErr(err) + if err != nil { + return err + } } else { log.Printf("User '%s' initialize wth user-provided password\n", username) } @@ -452,14 +513,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { set.Defaults.Apply(user) user.Perm.Admin = true - err = d.store.Users.Save(user) - checkErr(err) + return d.store.Users.Save(user) } func initConfig() { if cfgFile == "" { home, err := homedir.Dir() - checkErr(err) + if err != nil { + panic(err) + } v.AddConfigPath(".") v.AddConfigPath(home) v.AddConfigPath("/etc/filebrowser/") diff --git a/cmd/rule_rm.go b/cmd/rule_rm.go index 4b7ba851..26e801ae 100644 --- a/cmd/rule_rm.go +++ b/cmd/rule_rm.go @@ -40,27 +40,29 @@ including 'index_end'.`, return nil }, - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { i, err := strconv.Atoi(args[0]) - checkErr(err) + if err != nil { + return err + } f := i if len(args) == 2 { f, err = strconv.Atoi(args[1]) - checkErr(err) + if err != nil { + return err + } } - user := func(u *users.User) { + user := func(u *users.User) error { u.Rules = append(u.Rules[:i], u.Rules[f+1:]...) - err := d.store.Users.Save(u) - checkErr(err) + return d.store.Users.Save(u) } - global := func(s *settings.Settings) { + global := func(s *settings.Settings) error { s.Rules = append(s.Rules[:i], s.Rules[f+1:]...) - err := d.store.Settings.Save(s) - checkErr(err) + return d.store.Settings.Save(s) } - runRules(d.store, cmd, user, global) + return runRules(d.store, cmd, user, global) }, pythonConfig{}), } diff --git a/cmd/rules.go b/cmd/rules.go index 3bf91dd1..ffa5b1ae 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -29,41 +29,62 @@ rules.`, Args: cobra.NoArgs, } -func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) { - id := getUserIdentifier(cmd.Flags()) +func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User) error, globalFn func(*settings.Settings) error) error { + id, err := getUserIdentifier(cmd.Flags()) + if err != nil { + return err + } if id != nil { - user, err := st.Users.Get("", id) - checkErr(err) + var user *users.User + user, err = st.Users.Get("", id) + if err != nil { + return err + } if usersFn != nil { - usersFn(user) + err = usersFn(user) + if err != nil { + return err + } } printRules(user.Rules, id) - return + return nil } s, err := st.Settings.Get() - checkErr(err) + if err != nil { + return err + } if globalFn != nil { - globalFn(s) + err = globalFn(s) + if err != nil { + return err + } } printRules(s.Rules, id) + return nil } -func getUserIdentifier(flags *pflag.FlagSet) interface{} { - id := mustGetUint(flags, "id") - username := mustGetString(flags, "username") - - if id != 0 { - return id - } else if username != "" { - return username +func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) { + id, err := getUint(flags, "id") + if err != nil { + return nil, err + } + username, err := getString(flags, "username") + if err != nil { + return nil, err } - return nil + if id != 0 { + return id, nil + } else if username != "" { + return username, nil + } + + return nil, nil } func printRules(rulez []rules.Rule, id interface{}) { diff --git a/cmd/rules_add.go b/cmd/rules_add.go index fcdc7fb4..9d1f0cf9 100644 --- a/cmd/rules_add.go +++ b/cmd/rules_add.go @@ -21,9 +21,15 @@ var rulesAddCmd = &cobra.Command{ Short: "Add a global rule or user rule", Long: `Add a global rule or user rule.`, Args: cobra.ExactArgs(1), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { - allow := mustGetBool(cmd.Flags(), "allow") - regex := mustGetBool(cmd.Flags(), "regex") + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { + allow, err := getBool(cmd.Flags(), "allow") + if err != nil { + return err + } + regex, err := getBool(cmd.Flags(), "regex") + if err != nil { + return err + } exp := args[0] if regex { @@ -41,18 +47,16 @@ var rulesAddCmd = &cobra.Command{ rule.Path = exp } - user := func(u *users.User) { + user := func(u *users.User) error { u.Rules = append(u.Rules, rule) - err := d.store.Users.Save(u) - checkErr(err) + return d.store.Users.Save(u) } - global := func(s *settings.Settings) { + global := func(s *settings.Settings) error { s.Rules = append(s.Rules, rule) - err := d.store.Settings.Save(s) - checkErr(err) + return d.store.Settings.Save(s) } - runRules(d.store, cmd, user, global) + return runRules(d.store, cmd, user, global) }, pythonConfig{}), } diff --git a/cmd/rules_ls.go b/cmd/rules_ls.go index 0a8ed721..67a279dc 100644 --- a/cmd/rules_ls.go +++ b/cmd/rules_ls.go @@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{ Short: "List global rules or user specific rules", Long: `List global rules or user specific rules.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { - runRules(d.store, cmd, nil, nil) + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { + return runRules(d.store, cmd, nil, nil) }, pythonConfig{}), } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index f6966e2e..7142b151 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -21,11 +21,20 @@ var upgradeCmd = &cobra.Command{ import share links because they are incompatible with this version.`, Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { flags := cmd.Flags() - oldDB := mustGetString(flags, "old.database") - oldConf := mustGetString(flags, "old.config") - err := importer.Import(oldDB, oldConf, getStringParam(flags, "database")) - checkErr(err) + oldDB, err := getString(flags, "old.database") + if err != nil { + return err + } + oldConf, err := getString(flags, "old.config") + if err != nil { + return err + } + db, err := getString(flags, "database") + if err != nil { + return err + } + return importer.Import(oldDB, oldConf, db) }, } diff --git a/cmd/users.go b/cmd/users.go index d3f97da6..a70b5fe4 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -79,50 +79,60 @@ func addUserFlags(flags *pflag.FlagSet) { flags.Bool("singleClick", false, "use single clicks only") } -func getViewMode(flags *pflag.FlagSet) users.ViewMode { - viewMode := users.ViewMode(mustGetString(flags, "viewMode")) - if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { - checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")) +func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) { + viewModeStr, err := getString(flags, "viewMode") + if err != nil { + return "", err } - return viewMode + viewMode := users.ViewMode(viewModeStr) + if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { + return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"") + } + return viewMode, nil } //nolint:gocyclo -func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) { +func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error { + var visitErr error visit := func(flag *pflag.Flag) { + if visitErr != nil { + return + } + var err error switch flag.Name { case "scope": - defaults.Scope = mustGetString(flags, flag.Name) + defaults.Scope, err = getString(flags, flag.Name) case "locale": - defaults.Locale = mustGetString(flags, flag.Name) + defaults.Locale, err = getString(flags, flag.Name) case "viewMode": - defaults.ViewMode = getViewMode(flags) + defaults.ViewMode, err = getViewMode(flags) case "singleClick": - defaults.SingleClick = mustGetBool(flags, flag.Name) + defaults.SingleClick, err = getBool(flags, flag.Name) case "perm.admin": - defaults.Perm.Admin = mustGetBool(flags, flag.Name) + defaults.Perm.Admin, err = getBool(flags, flag.Name) case "perm.execute": - defaults.Perm.Execute = mustGetBool(flags, flag.Name) + defaults.Perm.Execute, err = getBool(flags, flag.Name) case "perm.create": - defaults.Perm.Create = mustGetBool(flags, flag.Name) + defaults.Perm.Create, err = getBool(flags, flag.Name) case "perm.rename": - defaults.Perm.Rename = mustGetBool(flags, flag.Name) + defaults.Perm.Rename, err = getBool(flags, flag.Name) case "perm.modify": - defaults.Perm.Modify = mustGetBool(flags, flag.Name) + defaults.Perm.Modify, err = getBool(flags, flag.Name) case "perm.delete": - defaults.Perm.Delete = mustGetBool(flags, flag.Name) + defaults.Perm.Delete, err = getBool(flags, flag.Name) case "perm.share": - defaults.Perm.Share = mustGetBool(flags, flag.Name) + defaults.Perm.Share, err = getBool(flags, flag.Name) case "perm.download": - defaults.Perm.Download = mustGetBool(flags, flag.Name) + defaults.Perm.Download, err = getBool(flags, flag.Name) case "commands": - commands, err := flags.GetStringSlice(flag.Name) - checkErr(err) - defaults.Commands = commands + defaults.Commands, err = flags.GetStringSlice(flag.Name) case "sorting.by": - defaults.Sorting.By = mustGetString(flags, flag.Name) + defaults.Sorting.By, err = getString(flags, flag.Name) case "sorting.asc": - defaults.Sorting.Asc = mustGetBool(flags, flag.Name) + defaults.Sorting.Asc, err = getBool(flags, flag.Name) + } + if err != nil { + visitErr = err } } @@ -131,4 +141,5 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all } else { flags.Visit(visit) } + return visitErr } diff --git a/cmd/users_add.go b/cmd/users_add.go index c3b8af28..9d763d3d 100644 --- a/cmd/users_add.go +++ b/cmd/users_add.go @@ -16,36 +16,57 @@ var usersAddCmd = &cobra.Command{ Short: "Create a new user", Long: `Create a new user and add it to the database.`, Args: cobra.ExactArgs(2), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() - checkErr(err) - getUserDefaults(cmd.Flags(), &s.Defaults, false) + if err != nil { + return err + } + err = getUserDefaults(cmd.Flags(), &s.Defaults, false) + if err != nil { + return err + } password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength) - checkErr(err) + if err != nil { + return err + } + + lockPassword, err := getBool(cmd.Flags(), "lockPassword") + if err != nil { + return err + } user := &users.User{ Username: args[0], Password: password, - LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), + LockPassword: lockPassword, } s.Defaults.Apply(user) servSettings, err := d.store.Settings.GetServer() - checkErr(err) + if err != nil { + return err + } // since getUserDefaults() polluted s.Defaults.Scope // which makes the Scope not the one saved in the db // we need the right s.Defaults.Scope here s2, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root) - checkErr(err) + if err != nil { + return err + } user.Scope = userHome err = d.store.Users.Save(user) - checkErr(err) + if err != nil { + return err + } printUsers([]*users.User{user}) + return nil }, pythonConfig{}), } diff --git a/cmd/users_export.go b/cmd/users_export.go index 3b3798ad..d6009a37 100644 --- a/cmd/users_export.go +++ b/cmd/users_export.go @@ -14,11 +14,16 @@ var usersExportCmd = &cobra.Command{ Long: `Export all users to a json or yaml file. Please indicate the path to the file where you want to write the users.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { list, err := d.store.Users.Gets("") - checkErr(err) + if err != nil { + return err + } err = marshal(args[0], list) - checkErr(err) + if err != nil { + return err + } + return nil }, pythonConfig{}), } diff --git a/cmd/users_find.go b/cmd/users_find.go index 1f6e40c0..0dea071a 100644 --- a/cmd/users_find.go +++ b/cmd/users_find.go @@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{ 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.ExactArgs(1), - Run: findUsers, + RunE: findUsers, } var usersLsCmd = &cobra.Command{ Use: "ls", Short: "List all users.", Args: cobra.NoArgs, - Run: findUsers, + RunE: findUsers, } -var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) { +var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error { var ( list []*users.User user *users.User @@ -46,6 +46,9 @@ var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) { list, err = d.store.Users.Gets("") } - checkErr(err) + if err != nil { + return err + } printUsers(list) + return nil }, pythonConfig{}) diff --git a/cmd/users_import.go b/cmd/users_import.go index dee9d759..d20dca68 100644 --- a/cmd/users_import.go +++ b/cmd/users_import.go @@ -25,34 +25,54 @@ file. You can use this command to import new users to your installation. For that, just don't place their ID on the files list or set it to 0.`, Args: jsonYamlArg, - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { fd, err := os.Open(args[0]) - checkErr(err) + if err != nil { + return err + } defer fd.Close() list := []*users.User{} err = unmarshal(args[0], &list) - checkErr(err) + if err != nil { + return err + } for _, user := range list { err = user.Clean("") - checkErr(err) - } - - if mustGetBool(cmd.Flags(), "replace") { - oldUsers, err := d.store.Users.Gets("") - checkErr(err) - - err = marshal("users.backup.json", list) - checkErr(err) - - for _, user := range oldUsers { - err = d.store.Users.Delete(user.ID) - checkErr(err) + if err != nil { + return err } } - overwrite := mustGetBool(cmd.Flags(), "overwrite") + replace, err := getBool(cmd.Flags(), "replace") + if err != nil { + return err + } + + if replace { + oldUsers, userImportErr := d.store.Users.Gets("") + if userImportErr != nil { + return userImportErr + } + + err = marshal("users.backup.json", list) + if err != nil { + return err + } + + for _, user := range oldUsers { + err = d.store.Users.Delete(user.ID) + if err != nil { + return err + } + } + } + + overwrite, err := getBool(cmd.Flags(), "overwrite") + if err != nil { + return err + } for _, user := range list { onDB, err := d.store.Users.Get("", user.ID) @@ -60,7 +80,7 @@ list or set it to 0.`, // User exists in DB. if err == nil { if !overwrite { - checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")) + return errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered") } // If the usernames mismatch, check if there is another one in the DB @@ -68,7 +88,7 @@ list or set it to 0.`, // operation if user.Username != onDB.Username { if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet - checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID)) + return usernameConflictError(user.Username, conflictuous.ID, user.ID) } } } else { @@ -78,8 +98,11 @@ list or set it to 0.`, } err = d.store.Users.Save(user) - checkErr(err) + if err != nil { + return err + } } + return nil }, pythonConfig{}), } diff --git a/cmd/users_rm.go b/cmd/users_rm.go index 9041aa1b..55b973f4 100644 --- a/cmd/users_rm.go +++ b/cmd/users_rm.go @@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{ Short: "Delete a user by username or id", Long: `Delete a user by username or id`, Args: cobra.ExactArgs(1), - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { username, id := parseUsernameOrID(args[0]) var err error @@ -25,7 +25,10 @@ var usersRmCmd = &cobra.Command{ err = d.store.Users.Delete(id) } - checkErr(err) + if err != nil { + return err + } fmt.Println("user deleted successfully") + return nil }, pythonConfig{}), } diff --git a/cmd/users_update.go b/cmd/users_update.go index 2c58c4af..624bfda8 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -21,14 +21,22 @@ var usersUpdateCmd = &cobra.Command{ Long: `Updates an existing user. Set the flags for the options you want to change.`, Args: cobra.ExactArgs(1), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { username, id := parseUsernameOrID(args[0]) flags := cmd.Flags() - password := mustGetString(flags, "password") - newUsername := mustGetString(flags, "username") + password, err := getString(flags, "password") + if err != nil { + return err + } + newUsername, err := getString(flags, "username") + if err != nil { + return err + } s, err := d.store.Settings.Get() - checkErr(err) + if err != nil { + return err + } var ( user *users.User @@ -40,7 +48,9 @@ options you want to change.`, user, err = d.store.Users.Get("", username) } - checkErr(err) + if err != nil { + return err + } defaults := settings.UserDefaults{ Scope: user.Scope, @@ -51,7 +61,10 @@ options you want to change.`, Sorting: user.Sorting, Commands: user.Commands, } - getUserDefaults(flags, &defaults, false) + err = getUserDefaults(flags, &defaults, false) + if err != nil { + return err + } user.Scope = defaults.Scope user.Locale = defaults.Locale user.ViewMode = defaults.ViewMode @@ -59,7 +72,10 @@ options you want to change.`, user.Perm = defaults.Perm user.Commands = defaults.Commands user.Sorting = defaults.Sorting - user.LockPassword = mustGetBool(flags, "lockPassword") + user.LockPassword, err = getBool(flags, "lockPassword") + if err != nil { + return err + } if newUsername != "" { user.Username = newUsername @@ -67,11 +83,16 @@ options you want to change.`, if password != "" { user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength) - checkErr(err) + if err != nil { + return err + } } err = d.store.Users.Update(user) - checkErr(err) + if err != nil { + return err + } printUsers([]*users.User{user}) + return nil }, pythonConfig{}), } diff --git a/cmd/utils.go b/cmd/utils.go index e88233fb..a4eb4d14 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -23,45 +23,50 @@ import ( const dbPerms = 0640 -func checkErr(err error) { +func returnErr(err error) error { if err != nil { - log.Fatal(err) + return err } + return nil } -func mustGetString(flags *pflag.FlagSet, flag string) string { +func getString(flags *pflag.FlagSet, flag string) (string, error) { s, err := flags.GetString(flag) - checkErr(err) - return s + return s, returnErr(err) } -func mustGetMode(flags *pflag.FlagSet, flag string) fs.FileMode { - s := mustGetString(flags, flag) +func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) { + s, err := getString(flags, flag) + if err != nil { + return 0, err + } b, err := strconv.ParseUint(s, 0, 32) - checkErr(err) - return fs.FileMode(b) + if err != nil { + return 0, err + } + return fs.FileMode(b), nil } -func mustGetBool(flags *pflag.FlagSet, flag string) bool { +func getBool(flags *pflag.FlagSet, flag string) (bool, error) { b, err := flags.GetBool(flag) - checkErr(err) - return b + return b, returnErr(err) } -func mustGetUint(flags *pflag.FlagSet, flag string) uint { +func getUint(flags *pflag.FlagSet, flag string) (uint, error) { b, err := flags.GetUint(flag) - checkErr(err) - return b + return b, returnErr(err) } func generateKey() []byte { k, err := settings.GenerateKey() - checkErr(err) + if err != nil { + panic(err) + } return k } -type cobraFunc func(cmd *cobra.Command, args []string) -type pythonFunc func(cmd *cobra.Command, args []string, data pythonData) +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 @@ -71,6 +76,7 @@ type pythonConfig struct { type pythonData struct { hadDB bool store *storage.Storage + err error } func dbExists(path string) (bool, error) { @@ -94,8 +100,8 @@ func dbExists(path string) (bool, error) { } func python(fn pythonFunc, cfg pythonConfig) cobraFunc { - return func(cmd *cobra.Command, args []string) { - data := pythonData{hadDB: true} + return func(cmd *cobra.Command, args []string) error { + data := &pythonData{hadDB: true} path := getStringParam(cmd.Flags(), "database") absPath, err := filepath.Abs(path) @@ -117,17 +123,23 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { log.Println("Using database: " + absPath) data.hadDB = exists db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil)) - checkErr(err) + if err != nil { + return err + } defer db.Close() data.store, err = bolt.NewStorage(db) - checkErr(err) - fn(cmd, args, data) + if err != nil { + return err + } + return fn(cmd, args, data) } } func marshal(filename string, data interface{}) error { fd, err := os.Create(filename) - checkErr(err) + if err != nil { + return err + } defer fd.Close() switch ext := filepath.Ext(filename); ext { @@ -145,7 +157,9 @@ func marshal(filename string, data interface{}) error { func unmarshal(filename string, data interface{}) error { fd, err := os.Open(filename) - checkErr(err) + if err != nil { + return err + } defer fd.Close() switch ext := filepath.Ext(filename); ext { diff --git a/errors/errors.go b/errors/errors.go index 5fd760c2..f8abee59 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -3,6 +3,15 @@ package errors import ( "errors" "fmt" + "os" + "syscall" +) + +const ( + ExitCodeSigTerm = 128 + int(syscall.SIGTERM) + ExitCodeSighup = 128 + int(syscall.SIGHUP) + ExitCodeSigint = 128 + int(syscall.SIGINT) + ExitCodeSigquit = 128 + int(syscall.SIGQUIT) ) var ( @@ -22,6 +31,10 @@ var ( ErrInvalidRequestParams = errors.New("invalid request params") ErrSourceIsParent = errors.New("source is parent") ErrRootUserDeletion = errors.New("user with id 1 can't be deleted") + ErrSigTerm = errors.New("exit on signal: sigterm") + ErrSighup = errors.New("exit on signal: sighup") + ErrSigint = errors.New("exit on signal: sigint") + ErrSigquit = errors.New("exit on signal: sigquit") ) type ErrShortPassword struct { @@ -31,3 +44,44 @@ type ErrShortPassword struct { func (e ErrShortPassword) Error() string { return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength) } + +// GetExitCode returns the exit code for a given error. +func GetExitCode(err error) int { + if err == nil { + return 0 + } + + exitCodeMap := map[error]int{ + ErrSigTerm: ExitCodeSigTerm, + ErrSighup: ExitCodeSighup, + ErrSigint: ExitCodeSigint, + ErrSigquit: ExitCodeSigquit, + } + + for e, code := range exitCodeMap { + if errors.Is(err, e) { + return code + } + } + + if exitErr, ok := err.(interface{ ExitCode() int }); ok { + return exitErr.ExitCode() + } + + var pathErr *os.PathError + if errors.As(err, &pathErr) { + return 1 + } + + var syscallErr *os.SyscallError + if errors.As(err, &syscallErr) { + return 1 + } + + var errno syscall.Errno + if errors.As(err, &errno) { + return 1 + } + + return 1 +} diff --git a/main.go b/main.go index ab22ef45..d17550c9 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main import ( + "os" + "github.com/filebrowser/filebrowser/v2/cmd" + "github.com/filebrowser/filebrowser/v2/errors" ) func main() { - cmd.Execute() + if err := cmd.Execute(); err != nil { + os.Exit(errors.GetExitCode(err)) + } }