feat: better error handling for sys calls

pull/5286/head
jagadam97 2025-07-13 05:01:43 +05:30
parent 7c716862c1
commit 196f98dd91
22 changed files with 130 additions and 37 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,7 +15,7 @@ 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) checkErr(err)
command := strings.Join(args[1:], " ") command := strings.Join(args[1:], " ")
@ -23,5 +23,6 @@ var cmdsAddCmd = &cobra.Command{
err = d.store.Settings.Save(s) err = d.store.Settings.Save(s)
checkErr(err) checkErr(err)
printEvents(s.Commands) printEvents(s.Commands)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -14,7 +14,7 @@ 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) checkErr(err)
evt := mustGetString(cmd.Flags(), "event") evt := mustGetString(cmd.Flags(), "event")
@ -27,5 +27,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,7 +35,7 @@ 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) checkErr(err)
evt := args[0] evt := args[0]
@ -52,5 +52,6 @@ including 'index_end'.`,
err = d.store.Settings.Save(s) err = d.store.Settings.Save(s)
checkErr(err) checkErr(err)
printEvents(s.Commands) printEvents(s.Commands)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -13,7 +13,7 @@ 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) checkErr(err)
ser, err := d.store.Settings.GetServer() ser, err := d.store.Settings.GetServer()
@ -21,5 +21,6 @@ var configCatCmd = &cobra.Command{
auther, err := d.store.Auth.Get(set.AuthMethod) auther, err := d.store.Auth.Get(set.AuthMethod)
checkErr(err) checkErr(err)
printSettings(ser, set, auther) printSettings(ser, set, auther)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -15,7 +15,7 @@ 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) checkErr(err)
@ -33,5 +33,6 @@ and imported again with 'config import' command.`,
err = marshal(args[0], data) err = marshal(args[0], data)
checkErr(err) checkErr(err)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -34,7 +34,7 @@ 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
if d.hadDB { if d.hadDB {
settings, err := d.store.Settings.Get() settings, err := d.store.Settings.Get()
@ -80,6 +80,7 @@ The path must be for a json or yaml file.`,
checkErr(err) checkErr(err)
printSettings(file.Server, file.Settings, auther) printSettings(file.Server, file.Settings, auther)
return nil
}, pythonConfig{allowNoDB: true}), }, pythonConfig{allowNoDB: true}),
} }

View File

@ -22,7 +22,7 @@ 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) getUserDefaults(flags, &defaults, true)
@ -69,5 +69,6 @@ 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) printSettings(ser, s, auther)
return nil
}, pythonConfig{noDB: true}), }, pythonConfig{noDB: true}),
} }

View File

@ -16,7 +16,7 @@ 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) checkErr(err)
@ -84,5 +84,6 @@ you want to change. Other options will remain unchanged.`,
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
checkErr(err) checkErr(err)
printSettings(ser, set, auther) printSettings(ser, set, auther)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -25,6 +25,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 +40,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,11 +114,11 @@ 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) quickSetup(cmd.Flags(), *d)
} }
// build img service // build img service
@ -194,8 +196,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,6 +213,19 @@ 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}),
} }

View File

@ -40,7 +40,7 @@ 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) checkErr(err)
f := i f := i
@ -62,5 +62,6 @@ including 'index_end'.`,
} }
runRules(d.store, cmd, user, global) runRules(d.store, cmd, user, global)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -21,7 +21,7 @@ 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 := mustGetBool(cmd.Flags(), "allow")
regex := mustGetBool(cmd.Flags(), "regex") regex := mustGetBool(cmd.Flags(), "regex")
exp := args[0] exp := args[0]
@ -54,5 +54,6 @@ var rulesAddCmd = &cobra.Command{
} }
runRules(d.store, cmd, user, global) runRules(d.store, cmd, user, global)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -13,7 +13,8 @@ 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) runRules(d.store, cmd, nil, nil)
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -16,7 +16,7 @@ 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) checkErr(err)
getUserDefaults(cmd.Flags(), &s.Defaults, false) getUserDefaults(cmd.Flags(), &s.Defaults, false)
@ -47,5 +47,6 @@ var usersAddCmd = &cobra.Command{
err = d.store.Users.Save(user) err = d.store.Users.Save(user)
checkErr(err) checkErr(err)
printUsers([]*users.User{user}) printUsers([]*users.User{user})
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -14,11 +14,12 @@ 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) checkErr(err)
err = marshal(args[0], list) err = marshal(args[0], list)
checkErr(err) checkErr(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
@ -48,4 +48,5 @@ var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) {
checkErr(err) checkErr(err)
printUsers(list) printUsers(list)
return nil
}, pythonConfig{}) }, pythonConfig{})

View File

@ -25,7 +25,7 @@ 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) checkErr(err)
defer fd.Close() defer fd.Close()
@ -80,6 +80,7 @@ list or set it to 0.`,
err = d.store.Users.Save(user) err = d.store.Users.Save(user)
checkErr(err) checkErr(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
@ -27,5 +27,6 @@ var usersRmCmd = &cobra.Command{
checkErr(err) checkErr(err)
fmt.Println("user deleted successfully") fmt.Println("user deleted successfully")
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -21,7 +21,7 @@ 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 := mustGetString(flags, "password")
@ -73,5 +73,6 @@ options you want to change.`,
err = d.store.Users.Update(user) err = d.store.Users.Update(user)
checkErr(err) checkErr(err)
printUsers([]*users.User{user}) printUsers([]*users.User{user})
return nil
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -50,8 +50,8 @@ func generateKey() []byte {
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
@ -61,6 +61,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) {
@ -84,8 +85,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)
@ -111,7 +112,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
defer db.Close() defer db.Close()
data.store, err = bolt.NewStorage(db) data.store, err = bolt.NewStorage(db)
checkErr(err) checkErr(err)
fn(cmd, args, data) return fn(cmd, args, data)
} }
} }

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))
}
} }