feat: better error handling for sys kill signals

This commit is contained in:
Jagadam Dinesh Reddy 2025-07-22 11:55:21 +05:30 committed by GitHub
parent 21ad653b7e
commit 1582b8b2cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 876 additions and 361 deletions

View File

@ -1,12 +1,6 @@
package cmd package cmd
import (
"log"
)
// Execute executes the commands. // Execute executes the commands.
func Execute() { func Execute() error {
if err := rootCmd.Execute(); err != nil { return rootCmd.Execute()
log.Fatal(err)
}
} }

View File

@ -15,13 +15,18 @@ var cmdsAddCmd = &cobra.Command{
Short: "Add a command to run on a specific event", Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`, Long: `Add a command to run on a specific event.`,
Args: cobra.MinimumNArgs(2), 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() s, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
command := strings.Join(args[1:], " ") command := strings.Join(args[1:], " ")
s.Commands[args[0]] = append(s.Commands[args[0]], command) s.Commands[args[0]] = append(s.Commands[args[0]], command)
err = d.store.Settings.Save(s) err = d.store.Settings.Save(s)
checkErr(err) if err != nil {
return err
}
printEvents(s.Commands) printEvents(s.Commands)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -14,10 +14,15 @@ var cmdsLsCmd = &cobra.Command{
Short: "List all commands for each event", Short: "List all commands for each event",
Long: `List all commands for each event.`, Long: `List all commands for each event.`,
Args: cobra.NoArgs, 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() s, err := d.store.Settings.Get()
checkErr(err) if err != nil {
evt := mustGetString(cmd.Flags(), "event") return err
}
evt, err := getString(cmd.Flags(), "event")
if err != nil {
return err
}
if evt == "" { if evt == "" {
printEvents(s.Commands) printEvents(s.Commands)
@ -27,5 +32,6 @@ var cmdsLsCmd = &cobra.Command{
show["after_"+evt] = s.Commands["after_"+evt] show["after_"+evt] = s.Commands["after_"+evt]
printEvents(show) printEvents(show)
} }
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -35,22 +35,31 @@ including 'index_end'.`,
return nil 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() s, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
evt := args[0] evt := args[0]
i, err := strconv.Atoi(args[1]) i, err := strconv.Atoi(args[1])
checkErr(err) if err != nil {
return err
}
f := i f := i
if len(args) == 3 { if len(args) == 3 {
f, err = strconv.Atoi(args[2]) 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:]...) s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
err = d.store.Settings.Save(s) err = d.store.Settings.Save(s)
checkErr(err) if err != nil {
return err
}
printEvents(s.Commands) printEvents(s.Commands)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -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") flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "Mode bits that new directories are created with")
} }
//nolint:gocyclo func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) { methodStr, err := getString(flags, "auth.method")
method := settings.AuthMethod(mustGetString(flags, "auth.method")) if err != nil {
return "", nil, err
}
method := settings.AuthMethod(methodStr)
var defaultAuther map[string]interface{} var defaultAuther map[string]interface{}
if len(defaults) > 0 { if len(defaults) > 0 {
@ -68,38 +71,56 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
method = def.AuthMethod method = def.AuthMethod
case auth.Auther: case auth.Auther:
ms, err := json.Marshal(def) ms, err := json.Marshal(def)
checkErr(err) if err != nil {
return "", nil, err
}
err = json.Unmarshal(ms, &defaultAuther) err = json.Unmarshal(ms, &defaultAuther)
checkErr(err) if err != nil {
return "", nil, err
}
} }
} }
} }
} }
var auther auth.Auther return method, defaultAuther, nil
if method == auth.MethodProxyAuth { }
header := mustGetString(flags, "auth.header")
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 == "" { if header == "" {
header = defaultAuther["header"].(string) header = defaultAuther["header"].(string)
} }
if header == "" { if header == "" {
checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'")) return nil, nerrors.New("you must set the flag 'auth.header' for method 'proxy'")
} }
auther = &auth.ProxyAuth{Header: header} return &auth.ProxyAuth{Header: header}, nil
} }
if method == auth.MethodNoAuth { func getNoAuth() auth.Auther {
auther = &auth.NoAuth{} return &auth.NoAuth{}
} }
if method == auth.MethodJSONAuth { func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
jsonAuth := &auth.JSONAuth{} jsonAuth := &auth.JSONAuth{}
host := mustGetString(flags, "recaptcha.host") host, err := getString(flags, "recaptcha.host")
key := mustGetString(flags, "recaptcha.key") if err != nil {
secret := mustGetString(flags, "recaptcha.secret") 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 key == "" {
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
@ -120,31 +141,54 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
Secret: secret, Secret: secret,
} }
} }
auther = jsonAuth return jsonAuth, nil
} }
if method == auth.MethodHookAuth { func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
command := mustGetString(flags, "auth.command") command, err := getString(flags, "auth.command")
if err != nil {
return nil, err
}
if command == "" { if command == "" {
command = defaultAuther["command"].(string) command = defaultAuther["command"].(string)
} }
if command == "" { if command == "" {
checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'")) return nil, nerrors.New("you must set the flag 'auth.command' for method 'hook'")
} }
auther = &auth.HookAuth{Command: command} return &auth.HookAuth{Command: command}, nil
}
if auther == nil {
panic(errors.ErrInvalidAuthMethod)
}
return method, auther
} }
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) { 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) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup) 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() w.Flush()
b, err := json.MarshalIndent(auther, "", " ") b, err := json.MarshalIndent(auther, "", " ")
checkErr(err) if err != nil {
return err
}
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b)) fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
return nil
} }

View File

@ -13,13 +13,19 @@ var configCatCmd = &cobra.Command{
Short: "Prints the configuration", Short: "Prints the configuration",
Long: `Prints the configuration.`, Long: `Prints the configuration.`,
Args: cobra.NoArgs, 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() set, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
ser, err := d.store.Settings.GetServer() ser, err := d.store.Settings.GetServer()
checkErr(err) if err != nil {
return err
}
auther, err := d.store.Auth.Get(set.AuthMethod) auther, err := d.store.Auth.Get(set.AuthMethod)
checkErr(err) if err != nil {
printSettings(ser, set, auther) return err
}
return printSettings(ser, set, auther)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -15,15 +15,21 @@ var configExportCmd = &cobra.Command{
json or yaml file. This exported configuration can be changed, json or yaml file. This exported configuration can be changed,
and imported again with 'config import' command.`, and imported again with 'config import' command.`,
Args: jsonYamlArg, 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() settings, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
server, err := d.store.Settings.GetServer() server, err := d.store.Settings.GetServer()
checkErr(err) if err != nil {
return err
}
auther, err := d.store.Auth.Get(settings.AuthMethod) auther, err := d.store.Auth.Get(settings.AuthMethod)
checkErr(err) if err != nil {
return err
}
data := &settingsFile{ data := &settingsFile{
Settings: settings, Settings: settings,
@ -32,6 +38,9 @@ and imported again with 'config import' command.`,
} }
err = marshal(args[0], data) err = marshal(args[0], data)
checkErr(err) if err != nil {
return err
}
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -34,26 +34,35 @@ database.
The path must be for a json or yaml file.`, The path must be for a json or yaml file.`,
Args: jsonYamlArg, 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 key []byte
var err error
if d.hadDB { if d.hadDB {
settings, err := d.store.Settings.Get() settings, settingErr := d.store.Settings.Get()
checkErr(err) if settingErr != nil {
return settingErr
}
key = settings.Key key = settings.Key
} else { } else {
key = generateKey() key = generateKey()
} }
file := settingsFile{} file := settingsFile{}
err := unmarshal(args[0], &file) err = unmarshal(args[0], &file)
checkErr(err) if err != nil {
return err
}
file.Settings.Key = key file.Settings.Key = key
err = d.store.Settings.Save(file.Settings) err = d.store.Settings.Save(file.Settings)
checkErr(err) if err != nil {
return err
}
err = d.store.Settings.SaveServer(file.Server) err = d.store.Settings.SaveServer(file.Server)
checkErr(err) if err != nil {
return err
}
var rawAuther interface{} var rawAuther interface{}
if filepath.Ext(args[0]) != ".json" { 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 auther auth.Auther
var autherErr error
switch file.Settings.AuthMethod { switch file.Settings.AuthMethod {
case auth.MethodJSONAuth: 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: 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: 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: case auth.MethodHookAuth:
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth) var a interface{}
a, autherErr = getAuther(&auth.HookAuth{}, rawAuther)
auther = a.(*auth.HookAuth)
default: default:
checkErr(errors.New("invalid auth method")) return errors.New("invalid auth method")
}
if autherErr != nil {
return autherErr
} }
err = d.store.Auth.Save(auther) 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}), }, pythonConfig{allowNoDB: true}),
} }
func getAuther(sample auth.Auther, data interface{}) interface{} { func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
authType := reflect.TypeOf(sample) authType := reflect.TypeOf(sample)
auther := reflect.New(authType).Interface() auther := reflect.New(authType).Interface()
bytes, err := json.Marshal(data) bytes, err := json.Marshal(data)
checkErr(err) if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, &auther) err = json.Unmarshal(bytes, &auther)
checkErr(err) if err != nil {
return auther return nil, err
}
return auther, nil
} }

View File

@ -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 to the defaults when creating new users and you don't
override the options.`, override the options.`,
Args: cobra.NoArgs, 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{} defaults := settings.UserDefaults{}
flags := cmd.Flags() flags := cmd.Flags()
getUserDefaults(flags, &defaults, true) err := getUserDefaults(flags, &defaults, true)
authMethod, auther := getAuthentication(flags) 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{ s := &settings.Settings{
Key: generateKey(), Key: key,
Signup: mustGetBool(flags, "signup"), Signup: signup,
CreateUserDir: mustGetBool(flags, "create-user-dir"), CreateUserDir: createUserDir,
MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"), MinimumPasswordLength: minLength,
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")), Shell: convertCmdStrToCmdArray(shell),
AuthMethod: authMethod, AuthMethod: authMethod,
Defaults: defaults, Defaults: defaults,
Branding: settings.Branding{ Branding: settings.Branding{
Name: mustGetString(flags, "branding.name"), Name: brandingName,
DisableExternal: mustGetBool(flags, "branding.disableExternal"), DisableExternal: brandingDisableExternal,
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"), DisableUsedPercentage: brandingDisableUsedPercentage,
Theme: mustGetString(flags, "branding.theme"), Theme: brandingTheme,
Files: mustGetString(flags, "branding.files"), 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{ ser := &settings.Server{
Address: mustGetString(flags, "address"), Address: address,
Socket: mustGetString(flags, "socket"), Socket: socket,
Root: mustGetString(flags, "root"), Root: root,
BaseURL: mustGetString(flags, "baseurl"), BaseURL: baseURL,
TLSKey: mustGetString(flags, "key"), TLSKey: tlsKey,
TLSCert: mustGetString(flags, "cert"), TLSCert: cert,
Port: mustGetString(flags, "port"), Port: port,
Log: mustGetString(flags, "log"), Log: log,
} }
err := d.store.Settings.Save(s) err = d.store.Settings.Save(s)
checkErr(err) if err != nil {
return err
}
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
checkErr(err) if err != nil {
return err
}
err = d.store.Auth.Save(auther) err = d.store.Auth.Save(auther)
checkErr(err) if err != nil {
return err
}
fmt.Printf(` fmt.Printf(`
Congratulations! You've set up your database to use with File Browser. 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 Now add your first user via 'filebrowser users add' and then you just
need to call the main command to boot up the server. need to call the main command to boot up the server.
`) `)
printSettings(ser, s, auther) return printSettings(ser, s, auther)
}, pythonConfig{noDB: true}), }, pythonConfig{noDB: true}),
} }

View File

@ -16,77 +16,105 @@ var configSetCmd = &cobra.Command{
Long: `Updates the configuration. Set the flags for the options Long: `Updates the configuration. Set the flags for the options
you want to change. Other options will remain unchanged.`, you want to change. Other options will remain unchanged.`,
Args: cobra.NoArgs, 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() flags := cmd.Flags()
set, err := d.store.Settings.Get() set, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
ser, err := d.store.Settings.GetServer() ser, err := d.store.Settings.GetServer()
checkErr(err) if err != nil {
return err
}
hasAuth := false hasAuth := false
flags.Visit(func(flag *pflag.Flag) { flags.Visit(func(flag *pflag.Flag) {
if err != nil {
return
}
switch flag.Name { switch flag.Name {
case "baseurl": case "baseurl":
ser.BaseURL = mustGetString(flags, flag.Name) ser.BaseURL, err = getString(flags, flag.Name)
case "root": case "root":
ser.Root = mustGetString(flags, flag.Name) ser.Root, err = getString(flags, flag.Name)
case "socket": case "socket":
ser.Socket = mustGetString(flags, flag.Name) ser.Socket, err = getString(flags, flag.Name)
case "cert": case "cert":
ser.TLSCert = mustGetString(flags, flag.Name) ser.TLSCert, err = getString(flags, flag.Name)
case "key": case "key":
ser.TLSKey = mustGetString(flags, flag.Name) ser.TLSKey, err = getString(flags, flag.Name)
case "address": case "address":
ser.Address = mustGetString(flags, flag.Name) ser.Address, err = getString(flags, flag.Name)
case "port": case "port":
ser.Port = mustGetString(flags, flag.Name) ser.Port, err = getString(flags, flag.Name)
case "log": case "log":
ser.Log = mustGetString(flags, flag.Name) ser.Log, err = getString(flags, flag.Name)
case "signup": case "signup":
set.Signup = mustGetBool(flags, flag.Name) set.Signup, err = getBool(flags, flag.Name)
case "auth.method": case "auth.method":
hasAuth = true hasAuth = true
case "shell": 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": case "create-user-dir":
set.CreateUserDir = mustGetBool(flags, flag.Name) set.CreateUserDir, err = getBool(flags, flag.Name)
case "minimum-password-length": case "minimum-password-length":
set.MinimumPasswordLength = mustGetUint(flags, flag.Name) set.MinimumPasswordLength, err = getUint(flags, flag.Name)
case "branding.name": case "branding.name":
set.Branding.Name = mustGetString(flags, flag.Name) set.Branding.Name, err = getString(flags, flag.Name)
case "branding.color": case "branding.color":
set.Branding.Color = mustGetString(flags, flag.Name) set.Branding.Color, err = getString(flags, flag.Name)
case "branding.theme": case "branding.theme":
set.Branding.Theme = mustGetString(flags, flag.Name) set.Branding.Theme, err = getString(flags, flag.Name)
case "branding.disableExternal": case "branding.disableExternal":
set.Branding.DisableExternal = mustGetBool(flags, flag.Name) set.Branding.DisableExternal, err = getBool(flags, flag.Name)
case "branding.disableUsedPercentage": case "branding.disableUsedPercentage":
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name) set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
case "branding.files": case "branding.files":
set.Branding.Files = mustGetString(flags, flag.Name) set.Branding.Files, err = getString(flags, flag.Name)
case "file-mode": case "file-mode":
set.FileMode = mustGetMode(flags, flag.Name) set.FileMode, err = getMode(flags, flag.Name)
case "dir-mode": 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 // read the defaults
auther, err := d.store.Auth.Get(set.AuthMethod) 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 // 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) err = d.store.Auth.Save(auther)
checkErr(err) if err != nil {
return err
}
err = d.store.Settings.Save(set) err = d.store.Settings.Save(set)
checkErr(err) if err != nil {
return err
}
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
checkErr(err) if err != nil {
printSettings(ser, set, auther) return err
}
return printSettings(ser, set, auther)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -39,12 +39,19 @@ var docsCmd = &cobra.Command{
Use: "docs", Use: "docs",
Hidden: true, Hidden: true,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) { RunE: func(cmd *cobra.Command, _ []string) error {
dir := mustGetString(cmd.Flags(), "path") dir, err := getString(cmd.Flags(), "path")
generateDocs(rootCmd, dir) if err != nil {
return err
}
err = generateDocs(rootCmd, dir)
if err != nil {
return err
}
names := []string{} 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() { if err != nil || info.IsDir() {
return err return err
} }
@ -56,30 +63,38 @@ var docsCmd = &cobra.Command{
names = append(names, info.Name()) names = append(names, info.Name())
return nil return nil
}) })
if err != nil {
return err
}
checkErr(err)
printToc(names) printToc(names)
return nil
}, },
} }
func generateDocs(cmd *cobra.Command, dir string) { func generateDocs(cmd *cobra.Command, dir string) error {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue continue
} }
generateDocs(c, dir) err := generateDocs(c, dir)
if err != nil {
return err
}
} }
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md" basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
filename := filepath.Join(dir, basename) filename := filepath.Join(dir, basename)
f, err := os.Create(filename) f, err := os.Create(filename)
checkErr(err) if err != nil {
return err
}
defer f.Close() 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.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag() cmd.InitDefaultHelpFlag()
@ -108,7 +123,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
printOptions(buf, cmd) printOptions(buf, cmd)
_, err := buf.WriteTo(w) _, err := buf.WriteTo(w)
checkErr(err) return err
} }
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) { func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {

View File

@ -17,9 +17,12 @@ var hashCmd = &cobra.Command{
Short: "Hashes a password", Short: "Hashes a password",
Long: `Hashes a password using bcrypt algorithm.`, Long: `Hashes a password using bcrypt algorithm.`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(_ *cobra.Command, args []string) { RunE: func(_ *cobra.Command, args []string) error {
pwd, err := users.HashPwd(args[0]) pwd, err := users.HashPwd(args[0])
checkErr(err) if err != nil {
return err
}
fmt.Println(pwd) fmt.Println(pwd)
return nil
}, },
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"io/fs" "io/fs"
"log" "log"
@ -25,6 +26,7 @@ import (
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/diskcache" "github.com/filebrowser/filebrowser/v2/diskcache"
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/frontend" "github.com/filebrowser/filebrowser/v2/frontend"
fbhttp "github.com/filebrowser/filebrowser/v2/http" fbhttp "github.com/filebrowser/filebrowser/v2/http"
"github.com/filebrowser/filebrowser/v2/img" "github.com/filebrowser/filebrowser/v2/img"
@ -39,6 +41,7 @@ var (
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
rootCmd.SilenceUsage = true
cobra.MousetrapHelpText = "" cobra.MousetrapHelpText = ""
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") 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 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 the quick setup mode and a new database will be bootstrapped and a new
user created with the credentials from options "username" and "password".`, 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) log.Println(cfgFile)
if !d.hadDB { if !d.hadDB {
quickSetup(cmd.Flags(), d) err := quickSetup(cmd.Flags(), *d)
if err != nil {
return err
}
} }
// build img service // build img service
workersCount, err := cmd.Flags().GetInt("img-processors") workersCount, err := cmd.Flags().GetInt("img-processors")
checkErr(err) if err != nil {
return err
}
if workersCount < 1 { 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) imgSvc := img.New(workersCount)
var fileCache diskcache.Interface = diskcache.NewNoOp() var fileCache diskcache.Interface = diskcache.NewNoOp()
cacheDir, err := cmd.Flags().GetString("cache-dir") cacheDir, err := cmd.Flags().GetString("cache-dir")
checkErr(err) if err != nil {
return err
}
if cacheDir != "" { if cacheDir != "" {
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet 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) 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) setupLog(server.Log)
root, err := filepath.Abs(server.Root) root, err := filepath.Abs(server.Root)
checkErr(err) if err != nil {
return err
}
server.Root = root server.Root = root
adr := server.Address + ":" + server.Port adr := server.Address + ":" + server.Port
@ -151,22 +166,34 @@ user created with the credentials from options "username" and "password".`,
switch { switch {
case server.Socket != "": case server.Socket != "":
listener, err = net.Listen("unix", 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 socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
checkErr(err) if err != nil {
return err
}
err = os.Chmod(server.Socket, os.FileMode(socketPerm)) err = os.Chmod(server.Socket, os.FileMode(socketPerm))
checkErr(err) if err != nil {
return err
}
case server.TLSKey != "" && server.TLSCert != "": case server.TLSKey != "" && server.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet 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{ listener, err = tls.Listen("tcp", adr, &tls.Config{
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer}}, Certificates: []tls.Certificate{cer}},
) )
checkErr(err) if err != nil {
return err
}
default: default:
listener, err = net.Listen("tcp", adr) listener, err = net.Listen("tcp", adr)
checkErr(err) if err != nil {
return err
}
} }
assetsFs, err := fs.Sub(frontend.Assets(), "dist") 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) handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
checkErr(err) if err != nil {
return err
}
defer listener.Close() defer listener.Close()
@ -194,8 +223,15 @@ user created with the credentials from options "username" and "password".`,
}() }()
sigc := make(chan os.Signal, 1) sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) signal.Notify(sigc,
<-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 shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
defer shutdownRelease() defer shutdownRelease()
@ -204,13 +240,28 @@ user created with the credentials from options "username" and "password".`,
log.Fatalf("HTTP shutdown error: %v", err) log.Fatalf("HTTP shutdown error: %v", err)
} }
log.Println("Graceful shutdown complete.") 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}), }, pythonConfig{allowNoDB: true}),
} }
//nolint:gocyclo //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() server, err := st.Settings.GetServer()
checkErr(err) if err != nil {
return nil, err
}
if val, set := getStringParamB(flags, "root"); set { if val, set := getStringParamB(flags, "root"); set {
server.Root = val server.Root = val
@ -253,7 +304,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
} }
if isAddrSet && isSocketSet { 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. // 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 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 // 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") log.Println("Performing quick setup")
set := &settings.Settings{ set := &settings.Settings{
@ -406,10 +457,14 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
set.AuthMethod = auth.MethodJSONAuth set.AuthMethod = auth.MethodJSONAuth
err = d.store.Auth.Save(&auth.JSONAuth{}) err = d.store.Auth.Save(&auth.JSONAuth{})
} }
if err != nil {
return err
}
checkErr(err)
err = d.store.Settings.Save(set) err = d.store.Settings.Save(set)
checkErr(err) if err != nil {
return err
}
ser := &settings.Server{ ser := &settings.Server{
BaseURL: getStringParam(flags, "baseurl"), BaseURL: getStringParam(flags, "baseurl"),
@ -422,7 +477,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
} }
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
checkErr(err) if err != nil {
return err
}
username := getStringParam(flags, "username") username := getStringParam(flags, "username")
password := getStringParam(flags, "password") password := getStringParam(flags, "password")
@ -430,11 +487,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
if password == "" { if password == "" {
var pwd string var pwd string
pwd, err = users.RandomPwd(set.MinimumPasswordLength) 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) log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd)
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength) password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
checkErr(err) if err != nil {
return err
}
} else { } else {
log.Printf("User '%s' initialize wth user-provided password\n", username) 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) set.Defaults.Apply(user)
user.Perm.Admin = true user.Perm.Admin = true
err = d.store.Users.Save(user) return d.store.Users.Save(user)
checkErr(err)
} }
func initConfig() { func initConfig() {
if cfgFile == "" { if cfgFile == "" {
home, err := homedir.Dir() home, err := homedir.Dir()
checkErr(err) if err != nil {
panic(err)
}
v.AddConfigPath(".") v.AddConfigPath(".")
v.AddConfigPath(home) v.AddConfigPath(home)
v.AddConfigPath("/etc/filebrowser/") v.AddConfigPath("/etc/filebrowser/")

View File

@ -40,27 +40,29 @@ including 'index_end'.`,
return nil 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]) i, err := strconv.Atoi(args[0])
checkErr(err) if err != nil {
return err
}
f := i f := i
if len(args) == 2 { if len(args) == 2 {
f, err = strconv.Atoi(args[1]) 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:]...) u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
err := d.store.Users.Save(u) return d.store.Users.Save(u)
checkErr(err)
} }
global := func(s *settings.Settings) { global := func(s *settings.Settings) error {
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...) s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
err := d.store.Settings.Save(s) return d.store.Settings.Save(s)
checkErr(err)
} }
runRules(d.store, cmd, user, global) return runRules(d.store, cmd, user, global)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -29,41 +29,62 @@ rules.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) { func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User) error, globalFn func(*settings.Settings) error) error {
id := getUserIdentifier(cmd.Flags()) id, err := getUserIdentifier(cmd.Flags())
if err != nil {
return err
}
if id != nil { if id != nil {
user, err := st.Users.Get("", id) var user *users.User
checkErr(err) user, err = st.Users.Get("", id)
if err != nil {
return err
}
if usersFn != nil { if usersFn != nil {
usersFn(user) err = usersFn(user)
if err != nil {
return err
}
} }
printRules(user.Rules, id) printRules(user.Rules, id)
return return nil
} }
s, err := st.Settings.Get() s, err := st.Settings.Get()
checkErr(err) if err != nil {
return err
}
if globalFn != nil { if globalFn != nil {
globalFn(s) err = globalFn(s)
if err != nil {
return err
}
} }
printRules(s.Rules, id) printRules(s.Rules, id)
return nil
} }
func getUserIdentifier(flags *pflag.FlagSet) interface{} { func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
id := mustGetUint(flags, "id") id, err := getUint(flags, "id")
username := mustGetString(flags, "username") if err != nil {
return nil, err
if id != 0 { }
return id username, err := getString(flags, "username")
} else if username != "" { if err != nil {
return username 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{}) { func printRules(rulez []rules.Rule, id interface{}) {

View File

@ -21,9 +21,15 @@ var rulesAddCmd = &cobra.Command{
Short: "Add a global rule or user rule", Short: "Add a global rule or user rule",
Long: `Add a global rule or user rule.`, Long: `Add a global rule or user rule.`,
Args: cobra.ExactArgs(1), 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 {
allow := mustGetBool(cmd.Flags(), "allow") allow, err := getBool(cmd.Flags(), "allow")
regex := mustGetBool(cmd.Flags(), "regex") if err != nil {
return err
}
regex, err := getBool(cmd.Flags(), "regex")
if err != nil {
return err
}
exp := args[0] exp := args[0]
if regex { if regex {
@ -41,18 +47,16 @@ var rulesAddCmd = &cobra.Command{
rule.Path = exp rule.Path = exp
} }
user := func(u *users.User) { user := func(u *users.User) error {
u.Rules = append(u.Rules, rule) u.Rules = append(u.Rules, rule)
err := d.store.Users.Save(u) return d.store.Users.Save(u)
checkErr(err)
} }
global := func(s *settings.Settings) { global := func(s *settings.Settings) error {
s.Rules = append(s.Rules, rule) s.Rules = append(s.Rules, rule)
err := d.store.Settings.Save(s) return d.store.Settings.Save(s)
checkErr(err)
} }
runRules(d.store, cmd, user, global) return runRules(d.store, cmd, user, global)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
Short: "List global rules or user specific rules", Short: "List global rules or user specific rules",
Long: `List global rules or user specific rules.`, Long: `List global rules or user specific rules.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
runRules(d.store, cmd, nil, nil) return runRules(d.store, cmd, nil, nil)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -21,11 +21,20 @@ var upgradeCmd = &cobra.Command{
import share links because they are incompatible with import share links because they are incompatible with
this version.`, this version.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) { RunE: func(cmd *cobra.Command, _ []string) error {
flags := cmd.Flags() flags := cmd.Flags()
oldDB := mustGetString(flags, "old.database") oldDB, err := getString(flags, "old.database")
oldConf := mustGetString(flags, "old.config") if err != nil {
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database")) return err
checkErr(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)
}, },
} }

View File

@ -79,50 +79,60 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.Bool("singleClick", false, "use single clicks only") flags.Bool("singleClick", false, "use single clicks only")
} }
func getViewMode(flags *pflag.FlagSet) users.ViewMode { func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
viewMode := users.ViewMode(mustGetString(flags, "viewMode")) viewModeStr, err := getString(flags, "viewMode")
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { if err != nil {
checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")) 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 //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) { visit := func(flag *pflag.Flag) {
if visitErr != nil {
return
}
var err error
switch flag.Name { switch flag.Name {
case "scope": case "scope":
defaults.Scope = mustGetString(flags, flag.Name) defaults.Scope, err = getString(flags, flag.Name)
case "locale": case "locale":
defaults.Locale = mustGetString(flags, flag.Name) defaults.Locale, err = getString(flags, flag.Name)
case "viewMode": case "viewMode":
defaults.ViewMode = getViewMode(flags) defaults.ViewMode, err = getViewMode(flags)
case "singleClick": case "singleClick":
defaults.SingleClick = mustGetBool(flags, flag.Name) defaults.SingleClick, err = getBool(flags, flag.Name)
case "perm.admin": case "perm.admin":
defaults.Perm.Admin = mustGetBool(flags, flag.Name) defaults.Perm.Admin, err = getBool(flags, flag.Name)
case "perm.execute": case "perm.execute":
defaults.Perm.Execute = mustGetBool(flags, flag.Name) defaults.Perm.Execute, err = getBool(flags, flag.Name)
case "perm.create": case "perm.create":
defaults.Perm.Create = mustGetBool(flags, flag.Name) defaults.Perm.Create, err = getBool(flags, flag.Name)
case "perm.rename": case "perm.rename":
defaults.Perm.Rename = mustGetBool(flags, flag.Name) defaults.Perm.Rename, err = getBool(flags, flag.Name)
case "perm.modify": case "perm.modify":
defaults.Perm.Modify = mustGetBool(flags, flag.Name) defaults.Perm.Modify, err = getBool(flags, flag.Name)
case "perm.delete": case "perm.delete":
defaults.Perm.Delete = mustGetBool(flags, flag.Name) defaults.Perm.Delete, err = getBool(flags, flag.Name)
case "perm.share": case "perm.share":
defaults.Perm.Share = mustGetBool(flags, flag.Name) defaults.Perm.Share, err = getBool(flags, flag.Name)
case "perm.download": case "perm.download":
defaults.Perm.Download = mustGetBool(flags, flag.Name) defaults.Perm.Download, err = getBool(flags, flag.Name)
case "commands": case "commands":
commands, err := flags.GetStringSlice(flag.Name) defaults.Commands, err = flags.GetStringSlice(flag.Name)
checkErr(err)
defaults.Commands = commands
case "sorting.by": case "sorting.by":
defaults.Sorting.By = mustGetString(flags, flag.Name) defaults.Sorting.By, err = getString(flags, flag.Name)
case "sorting.asc": 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 { } else {
flags.Visit(visit) flags.Visit(visit)
} }
return visitErr
} }

View File

@ -16,36 +16,57 @@ var usersAddCmd = &cobra.Command{
Short: "Create a new user", Short: "Create a new user",
Long: `Create a new user and add it to the database.`, Long: `Create a new user and add it to the database.`,
Args: cobra.ExactArgs(2), 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() s, err := d.store.Settings.Get()
checkErr(err) if err != nil {
getUserDefaults(cmd.Flags(), &s.Defaults, false) return err
}
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
if err != nil {
return err
}
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength) 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{ user := &users.User{
Username: args[0], Username: args[0],
Password: password, Password: password,
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), LockPassword: lockPassword,
} }
s.Defaults.Apply(user) s.Defaults.Apply(user)
servSettings, err := d.store.Settings.GetServer() servSettings, err := d.store.Settings.GetServer()
checkErr(err) if err != nil {
return err
}
// since getUserDefaults() polluted s.Defaults.Scope // since getUserDefaults() polluted s.Defaults.Scope
// which makes the Scope not the one saved in the db // which makes the Scope not the one saved in the db
// we need the right s.Defaults.Scope here // we need the right s.Defaults.Scope here
s2, err := d.store.Settings.Get() s2, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root) userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
checkErr(err) if err != nil {
return err
}
user.Scope = userHome user.Scope = userHome
err = d.store.Users.Save(user) err = d.store.Users.Save(user)
checkErr(err) if err != nil {
return err
}
printUsers([]*users.User{user}) printUsers([]*users.User{user})
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -14,11 +14,16 @@ var usersExportCmd = &cobra.Command{
Long: `Export all users to a json or yaml file. Please indicate the Long: `Export all users to a json or yaml file. Please indicate the
path to the file where you want to write the users.`, path to the file where you want to write the users.`,
Args: jsonYamlArg, 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("") list, err := d.store.Users.Gets("")
checkErr(err) if err != nil {
return err
}
err = marshal(args[0], list) err = marshal(args[0], list)
checkErr(err) if err != nil {
return err
}
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{
Short: "Find a user by username or id", 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.`, Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: findUsers, RunE: findUsers,
} }
var usersLsCmd = &cobra.Command{ var usersLsCmd = &cobra.Command{
Use: "ls", Use: "ls",
Short: "List all users.", Short: "List all users.",
Args: cobra.NoArgs, 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 ( var (
list []*users.User list []*users.User
user *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("") list, err = d.store.Users.Gets("")
} }
checkErr(err) if err != nil {
return err
}
printUsers(list) printUsers(list)
return nil
}, pythonConfig{}) }, pythonConfig{})

View File

@ -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 installation. For that, just don't place their ID on the files
list or set it to 0.`, list or set it to 0.`,
Args: jsonYamlArg, 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]) fd, err := os.Open(args[0])
checkErr(err) if err != nil {
return err
}
defer fd.Close() defer fd.Close()
list := []*users.User{} list := []*users.User{}
err = unmarshal(args[0], &list) err = unmarshal(args[0], &list)
checkErr(err) if err != nil {
return err
}
for _, user := range list { for _, user := range list {
err = user.Clean("") err = user.Clean("")
checkErr(err) if err != nil {
return err
}
} }
if mustGetBool(cmd.Flags(), "replace") { replace, err := getBool(cmd.Flags(), "replace")
oldUsers, err := d.store.Users.Gets("") if err != nil {
checkErr(err) return err
}
if replace {
oldUsers, userImportErr := d.store.Users.Gets("")
if userImportErr != nil {
return userImportErr
}
err = marshal("users.backup.json", list) err = marshal("users.backup.json", list)
checkErr(err) if err != nil {
return err
}
for _, user := range oldUsers { for _, user := range oldUsers {
err = d.store.Users.Delete(user.ID) err = d.store.Users.Delete(user.ID)
checkErr(err) if err != nil {
return err
}
} }
} }
overwrite := mustGetBool(cmd.Flags(), "overwrite") overwrite, err := getBool(cmd.Flags(), "overwrite")
if err != nil {
return err
}
for _, user := range list { for _, user := range list {
onDB, err := d.store.Users.Get("", user.ID) onDB, err := d.store.Users.Get("", user.ID)
@ -60,7 +80,7 @@ list or set it to 0.`,
// User exists in DB. // User exists in DB.
if err == nil { if err == nil {
if !overwrite { 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 // If the usernames mismatch, check if there is another one in the DB
@ -68,7 +88,7 @@ list or set it to 0.`,
// operation // operation
if user.Username != onDB.Username { if user.Username != onDB.Username {
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet 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 { } else {
@ -78,8 +98,11 @@ list or set it to 0.`,
} }
err = d.store.Users.Save(user) err = d.store.Users.Save(user)
checkErr(err) if err != nil {
return err
} }
}
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{
Short: "Delete a user by username or id", Short: "Delete a user by username or id",
Long: `Delete a user by username or id`, Long: `Delete a user by username or id`,
Args: cobra.ExactArgs(1), 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]) username, id := parseUsernameOrID(args[0])
var err error var err error
@ -25,7 +25,10 @@ var usersRmCmd = &cobra.Command{
err = d.store.Users.Delete(id) err = d.store.Users.Delete(id)
} }
checkErr(err) if err != nil {
return err
}
fmt.Println("user deleted successfully") fmt.Println("user deleted successfully")
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -21,14 +21,22 @@ var usersUpdateCmd = &cobra.Command{
Long: `Updates an existing user. Set the flags for the Long: `Updates an existing user. Set the flags for the
options you want to change.`, options you want to change.`,
Args: cobra.ExactArgs(1), 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]) username, id := parseUsernameOrID(args[0])
flags := cmd.Flags() flags := cmd.Flags()
password := mustGetString(flags, "password") password, err := getString(flags, "password")
newUsername := mustGetString(flags, "username") if err != nil {
return err
}
newUsername, err := getString(flags, "username")
if err != nil {
return err
}
s, err := d.store.Settings.Get() s, err := d.store.Settings.Get()
checkErr(err) if err != nil {
return err
}
var ( var (
user *users.User user *users.User
@ -40,7 +48,9 @@ options you want to change.`,
user, err = d.store.Users.Get("", username) user, err = d.store.Users.Get("", username)
} }
checkErr(err) if err != nil {
return err
}
defaults := settings.UserDefaults{ defaults := settings.UserDefaults{
Scope: user.Scope, Scope: user.Scope,
@ -51,7 +61,10 @@ options you want to change.`,
Sorting: user.Sorting, Sorting: user.Sorting,
Commands: user.Commands, Commands: user.Commands,
} }
getUserDefaults(flags, &defaults, false) err = getUserDefaults(flags, &defaults, false)
if err != nil {
return err
}
user.Scope = defaults.Scope user.Scope = defaults.Scope
user.Locale = defaults.Locale user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode user.ViewMode = defaults.ViewMode
@ -59,7 +72,10 @@ options you want to change.`,
user.Perm = defaults.Perm user.Perm = defaults.Perm
user.Commands = defaults.Commands user.Commands = defaults.Commands
user.Sorting = defaults.Sorting user.Sorting = defaults.Sorting
user.LockPassword = mustGetBool(flags, "lockPassword") user.LockPassword, err = getBool(flags, "lockPassword")
if err != nil {
return err
}
if newUsername != "" { if newUsername != "" {
user.Username = newUsername user.Username = newUsername
@ -67,11 +83,16 @@ options you want to change.`,
if password != "" { if password != "" {
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength) user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
checkErr(err) if err != nil {
return err
}
} }
err = d.store.Users.Update(user) err = d.store.Users.Update(user)
checkErr(err) if err != nil {
return err
}
printUsers([]*users.User{user}) printUsers([]*users.User{user})
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -23,45 +23,50 @@ import (
const dbPerms = 0640 const dbPerms = 0640
func checkErr(err error) { func returnErr(err error) error {
if err != nil { 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) s, err := flags.GetString(flag)
checkErr(err) return s, returnErr(err)
return s
} }
func mustGetMode(flags *pflag.FlagSet, flag string) fs.FileMode { func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
s := mustGetString(flags, flag) s, err := getString(flags, flag)
if err != nil {
return 0, err
}
b, err := strconv.ParseUint(s, 0, 32) b, err := strconv.ParseUint(s, 0, 32)
checkErr(err) if err != nil {
return fs.FileMode(b) 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) b, err := flags.GetBool(flag)
checkErr(err) return b, returnErr(err)
return b
} }
func mustGetUint(flags *pflag.FlagSet, flag string) uint { func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
b, err := flags.GetUint(flag) b, err := flags.GetUint(flag)
checkErr(err) return b, returnErr(err)
return b
} }
func generateKey() []byte { func generateKey() []byte {
k, err := settings.GenerateKey() k, err := settings.GenerateKey()
checkErr(err) if err != nil {
panic(err)
}
return k return k
} }
type cobraFunc func(cmd *cobra.Command, args []string) type cobraFunc func(cmd *cobra.Command, args []string) error
type pythonFunc func(cmd *cobra.Command, args []string, data pythonData) type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
type pythonConfig struct { type pythonConfig struct {
noDB bool noDB bool
@ -71,6 +76,7 @@ type pythonConfig struct {
type pythonData struct { type pythonData struct {
hadDB bool hadDB bool
store *storage.Storage store *storage.Storage
err error
} }
func dbExists(path string) (bool, error) { func dbExists(path string) (bool, error) {
@ -94,8 +100,8 @@ func dbExists(path string) (bool, error) {
} }
func python(fn pythonFunc, cfg pythonConfig) cobraFunc { func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
return func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) error {
data := pythonData{hadDB: true} data := &pythonData{hadDB: true}
path := getStringParam(cmd.Flags(), "database") path := getStringParam(cmd.Flags(), "database")
absPath, err := filepath.Abs(path) absPath, err := filepath.Abs(path)
@ -117,17 +123,23 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
log.Println("Using database: " + absPath) log.Println("Using database: " + absPath)
data.hadDB = exists data.hadDB = exists
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil)) db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
checkErr(err) if err != nil {
return err
}
defer db.Close() defer db.Close()
data.store, err = bolt.NewStorage(db) data.store, err = bolt.NewStorage(db)
checkErr(err) if err != nil {
fn(cmd, args, data) return err
}
return fn(cmd, args, data)
} }
} }
func marshal(filename string, data interface{}) error { func marshal(filename string, data interface{}) error {
fd, err := os.Create(filename) fd, err := os.Create(filename)
checkErr(err) if err != nil {
return err
}
defer fd.Close() defer fd.Close()
switch ext := filepath.Ext(filename); ext { switch ext := filepath.Ext(filename); ext {
@ -145,7 +157,9 @@ func marshal(filename string, data interface{}) error {
func unmarshal(filename string, data interface{}) error { func unmarshal(filename string, data interface{}) error {
fd, err := os.Open(filename) fd, err := os.Open(filename)
checkErr(err) if err != nil {
return err
}
defer fd.Close() defer fd.Close()
switch ext := filepath.Ext(filename); ext { switch ext := filepath.Ext(filename); ext {

View File

@ -3,6 +3,15 @@ package errors
import ( import (
"errors" "errors"
"fmt" "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 ( var (
@ -22,6 +31,10 @@ var (
ErrInvalidRequestParams = errors.New("invalid request params") ErrInvalidRequestParams = errors.New("invalid request params")
ErrSourceIsParent = errors.New("source is parent") ErrSourceIsParent = errors.New("source is parent")
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted") 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 { type ErrShortPassword struct {
@ -31,3 +44,44 @@ type ErrShortPassword struct {
func (e ErrShortPassword) Error() string { func (e ErrShortPassword) Error() string {
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength) 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
}

View File

@ -1,9 +1,14 @@
package main package main
import ( import (
"os"
"github.com/filebrowser/filebrowser/v2/cmd" "github.com/filebrowser/filebrowser/v2/cmd"
"github.com/filebrowser/filebrowser/v2/errors"
) )
func main() { func main() {
cmd.Execute() if err := cmd.Execute(); err != nil {
os.Exit(errors.GetExitCode(err))
}
} }