From 388810b6d858a60ebefee1f64e80e85cf7b64280 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 8 Jan 2019 11:33:49 +0000 Subject: [PATCH] feat: merge server db conf with flags/env/config --- cmd/config.go | 51 ++++++++------- cmd/config_cat.go | 8 ++- cmd/config_export.go | 4 ++ cmd/config_import.go | 20 ++++-- cmd/config_init.go | 14 ++++- cmd/config_set.go | 41 +++++++++---- cmd/root.go | 143 ++++++++++++++++++++++++++++--------------- cmd/upgrade.go | 3 +- cmd/utils.go | 28 +-------- 9 files changed, 194 insertions(+), 118 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 2034e0aa..ca0f256f 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -27,6 +27,7 @@ var configCmd = &cobra.Command{ } func addConfigFlags(flags *pflag.FlagSet) { + addServerFlags(flags) addUserFlags(flags) flags.BoolP("signup", "s", false, "allow users to signup") flags.String("shell", "", "shell command to which other commands should be appended") @@ -84,33 +85,41 @@ func getAuthentication(cmd *cobra.Command) (settings.AuthMethod, auth.Auther) { return method, auther } -func printSettings(s *settings.Settings, auther auth.Auther) { +func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup) - fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod) - fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " ")) + fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup) + fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod) + fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " ")) fmt.Fprintln(w, "\nBranding:") - fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name) - fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files) - fmt.Fprintf(w, "\tDisable external links:\t%t\n", s.Branding.DisableExternal) + fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name) + fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files) + fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal) + fmt.Fprintln(w, "\nServer:") + fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log) + fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port) + fmt.Fprintf(w, "\tBase URL:\t%s\n", ser.BaseURL) + fmt.Fprintf(w, "\tRoot:\t%s\n", ser.Root) + fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address) + fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert) + fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey) fmt.Fprintln(w, "\nDefaults:") - fmt.Fprintf(w, "\tScope:\t%s\n", s.Defaults.Scope) - fmt.Fprintf(w, "\tLocale:\t%s\n", s.Defaults.Locale) - fmt.Fprintf(w, "\tView mode:\t%s\n", s.Defaults.ViewMode) - fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(s.Defaults.Commands, " ")) + fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope) + fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale) + fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode) + fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) fmt.Fprintf(w, "\tSorting:\n") - fmt.Fprintf(w, "\t\tBy:\t%s\n", s.Defaults.Sorting.By) - fmt.Fprintf(w, "\t\tAsc:\t%t\n", s.Defaults.Sorting.Asc) + fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) + fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc) fmt.Fprintf(w, "\tPermissions:\n") - fmt.Fprintf(w, "\t\tAdmin:\t%t\n", s.Defaults.Perm.Admin) - fmt.Fprintf(w, "\t\tExecute:\t%t\n", s.Defaults.Perm.Execute) - fmt.Fprintf(w, "\t\tCreate:\t%t\n", s.Defaults.Perm.Create) - fmt.Fprintf(w, "\t\tRename:\t%t\n", s.Defaults.Perm.Rename) - fmt.Fprintf(w, "\t\tModify:\t%t\n", s.Defaults.Perm.Modify) - fmt.Fprintf(w, "\t\tDelete:\t%t\n", s.Defaults.Perm.Delete) - fmt.Fprintf(w, "\t\tShare:\t%t\n", s.Defaults.Perm.Share) - fmt.Fprintf(w, "\t\tDownload:\t%t\n", s.Defaults.Perm.Download) + fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin) + fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute) + fmt.Fprintf(w, "\t\tCreate:\t%t\n", set.Defaults.Perm.Create) + fmt.Fprintf(w, "\t\tRename:\t%t\n", set.Defaults.Perm.Rename) + fmt.Fprintf(w, "\t\tModify:\t%t\n", set.Defaults.Perm.Modify) + fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete) + fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share) + fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download) w.Flush() b, err := json.MarshalIndent(auther, "", " ") diff --git a/cmd/config_cat.go b/cmd/config_cat.go index fcc45583..eca56dd1 100644 --- a/cmd/config_cat.go +++ b/cmd/config_cat.go @@ -14,10 +14,12 @@ var configCatCmd = &cobra.Command{ Long: `Prints the configuration.`, Args: cobra.NoArgs, Run: python(func(cmd *cobra.Command, args []string, d pythonData) { - s, err := d.store.Settings.Get() + set, err := d.store.Settings.Get() checkErr(err) - auther, err := d.store.Auth.Get(s.AuthMethod) + ser, err := d.store.Settings.GetServer() checkErr(err) - printSettings(s, auther) + auther, err := d.store.Auth.Get(set.AuthMethod) + checkErr(err) + printSettings(ser, set, auther) }, pythonConfig{}), } diff --git a/cmd/config_export.go b/cmd/config_export.go index f85d89b6..80574b29 100644 --- a/cmd/config_export.go +++ b/cmd/config_export.go @@ -16,12 +16,16 @@ var configExportCmd = &cobra.Command{ settings, err := d.store.Settings.Get() checkErr(err) + server, err := d.store.Settings.GetServer() + checkErr(err) + auther, err := d.store.Auth.Get(settings.AuthMethod) checkErr(err) data := &settingsFile{ Settings: settings, Auther: auther, + Server: server, } err = marshal(args[0], data) diff --git a/cmd/config_import.go b/cmd/config_import.go index c8b8ec0f..4f03c0d3 100644 --- a/cmd/config_import.go +++ b/cmd/config_import.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "errors" + "path/filepath" "reflect" "github.com/filebrowser/filebrowser/v2/auth" @@ -16,6 +17,7 @@ func init() { type settingsFile struct { Settings *settings.Settings `json:"settings"` + Server *settings.Server `json:"server"` Auther interface{} `json:"auther"` } @@ -45,23 +47,31 @@ database.`, err = d.store.Settings.Save(file.Settings) checkErr(err) - autherInterf := cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{})) + err = d.store.Settings.SaveServer(file.Server) + checkErr(err) + + var rawAuther interface{} + rawAuther = file.Auther + if filepath.Ext(args[0]) != ".json" { + rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{})) + } var auther auth.Auther switch file.Settings.AuthMethod { case auth.MethodJSONAuth: - auther = getAuther(auth.JSONAuth{}, autherInterf).(*auth.JSONAuth) + auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth) case auth.MethodNoAuth: - auther = getAuther(auth.NoAuth{}, autherInterf).(*auth.NoAuth) + auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth) case auth.MethodProxyAuth: - auther = getAuther(auth.ProxyAuth{}, autherInterf).(*auth.ProxyAuth) + auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth) default: checkErr(errors.New("invalid auth method")) } err = d.store.Auth.Save(auther) checkErr(err) - printSettings(file.Settings, auther) + + printSettings(file.Server, file.Settings, auther) }, pythonConfig{allowNoDB: true}), } diff --git a/cmd/config_init.go b/cmd/config_init.go index 37a698a9..7f9f3434 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -40,8 +40,20 @@ override the options.`, }, } + ser := &settings.Server{ + Address: mustGetString(cmd, "address"), + Root: mustGetString(cmd, "root"), + BaseURL: mustGetString(cmd, "baseurl"), + TLSKey: mustGetString(cmd, "key"), + TLSCert: mustGetString(cmd, "cert"), + Port: mustGetString(cmd, "port"), + Log: mustGetString(cmd, "log"), + } + err := d.store.Settings.Save(s) checkErr(err) + err = d.store.Settings.SaveServer(ser) + checkErr(err) err = d.store.Auth.Save(auther) checkErr(err) @@ -50,6 +62,6 @@ Congratulations! You've set up your database to use with File Browser. Now add your first user via 'filebrowser users new' and then you just need to call the main command to boot up the server. `) - printSettings(s, auther) + printSettings(ser, s, auther) }, pythonConfig{noDB: true}), } diff --git a/cmd/config_set.go b/cmd/config_set.go index b1e6388f..08458e57 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -20,41 +20,60 @@ var configSetCmd = &cobra.Command{ you want to change.`, Args: cobra.NoArgs, Run: python(func(cmd *cobra.Command, args []string, d pythonData) { - s, err := d.store.Settings.Get() + set, err := d.store.Settings.Get() + checkErr(err) + + ser, err := d.store.Settings.GetServer() checkErr(err) hasAuth := false cmd.Flags().Visit(func(flag *pflag.Flag) { switch flag.Name { + case "baseurl": + ser.BaseURL = mustGetString(cmd, flag.Name) + case "root": + ser.Root = mustGetString(cmd, flag.Name) + case "cert": + ser.TLSCert = mustGetString(cmd, flag.Name) + case "key": + ser.TLSKey = mustGetString(cmd, flag.Name) + case "address": + ser.Address = mustGetString(cmd, flag.Name) + case "port": + ser.Port = mustGetString(cmd, flag.Name) + case "log": + ser.Log = mustGetString(cmd, flag.Name) case "signup": - s.Signup = mustGetBool(cmd, flag.Name) + set.Signup = mustGetBool(cmd, flag.Name) case "auth.method": hasAuth = true case "shell": - s.Shell = strings.Split(strings.TrimSpace(mustGetString(cmd, flag.Name)), " ") + set.Shell = strings.Split(strings.TrimSpace(mustGetString(cmd, flag.Name)), " ") case "branding.name": - s.Branding.Name = mustGetString(cmd, flag.Name) + set.Branding.Name = mustGetString(cmd, flag.Name) case "branding.disableExternal": - s.Branding.DisableExternal = mustGetBool(cmd, flag.Name) + set.Branding.DisableExternal = mustGetBool(cmd, flag.Name) case "branding.files": - s.Branding.Files = mustGetString(cmd, flag.Name) + set.Branding.Files = mustGetString(cmd, flag.Name) } }) - getUserDefaults(cmd, &s.Defaults, false) + getUserDefaults(cmd, &set.Defaults, false) var auther auth.Auther if hasAuth { - s.AuthMethod, auther = getAuthentication(cmd) + set.AuthMethod, auther = getAuthentication(cmd) err = d.store.Auth.Save(auther) checkErr(err) } else { - auther, err = d.store.Auth.Get(s.AuthMethod) + auther, err = d.store.Auth.Get(set.AuthMethod) checkErr(err) } - err = d.store.Settings.Save(s) + err = d.store.Settings.Save(set) checkErr(err) - printSettings(s, auther) + err = d.store.Settings.SaveServer(ser) + checkErr(err) + printSettings(ser, set, auther) }, pythonConfig{}), } diff --git a/cmd/root.go b/cmd/root.go index ef9f14ff..25ce85b3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,6 +18,7 @@ import ( "github.com/filebrowser/filebrowser/v2/users" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" + "github.com/spf13/pflag" v "github.com/spf13/viper" lumberjack "gopkg.in/natefinch/lumberjack.v2" ) @@ -28,29 +29,61 @@ var ( func init() { cobra.OnInitialize(initConfig) + flags := rootCmd.Flags() + persistent := rootCmd.PersistentFlags() - f := rootCmd.Flags() - pf := rootCmd.PersistentFlags() + persistent.StringVarP(&cfgFile, "config", "c", "", "config file path") + persistent.StringP("database", "d", "./filebrowser.db", "database path") + flags.String("username", "admin", "username for the first user when using quick config") + flags.String("password", "", "hashed password for the first user when using quick config (default \"admin\")") - pf.StringVarP(&cfgFile, "config", "c", "", "config file path") - vaddP(pf, "database", "d", "./filebrowser.db", "path to the database") - vaddP(f, "address", "a", "127.0.0.1", "address to listen on") - vaddP(f, "log", "l", "stdout", "log output") - vaddP(f, "port", "p", "8080", "port to listen on") - vaddP(f, "cert", "t", "", "tls certificate") - vaddP(f, "key", "k", "", "tls key") - vaddP(f, "root", "r", ".", "root to prepend to relative paths") - vaddP(f, "baseurl", "b", "", "base url") - vadd(f, "username", "admin", "username for the first user when using quick config") - vadd(f, "password", "", "hashed password for the first user when using quick config (default \"admin\")") + addServerFlags(flags) +} - if err := v.BindPFlags(f); err != nil { - panic(err) +func addServerFlags(flags *pflag.FlagSet) { + flags.StringP("address", "a", "127.0.0.1", "address to listen on") + flags.StringP("log", "l", "stdout", "log output") + flags.StringP("port", "p", "8080", "port to listen on") + flags.StringP("cert", "t", "", "tls certificate") + flags.StringP("key", "k", "", "tls key") + flags.StringP("root", "r", ".", "root to prepend to relative paths") + flags.StringP("baseurl", "b", "", "base url") +} + +// NOTE: we could simply bind the flags to viper and use IsSet. +// Although there is a bug on Viper that always returns true on IsSet +// if a flag is binded. Our alternative way is to manually check +// the flag and then the value from env/config/gotten by viper. +// https://github.com/spf13/viper/pull/331 +func getStringViperFlag(flags *pflag.FlagSet, key string) (string, bool) { + value := "" + set := false + + // If set on Flags, use it. + flags.Visit(func(flag *pflag.Flag) { + if flag.Name == key { + set = true + value, _ = flags.GetString(key) + } + }) + + if set { + return value, set } - if err := v.BindPFlags(pf); err != nil { - panic(err) + // If set through viper (env, config), return it. + if v.IsSet(key) { + return v.GetString(key), true } + + // Otherwise use default value on flags. + value, _ = flags.GetString(key) + return value, false +} + +func mustGetStringViperFlag(flags *pflag.FlagSet, key string) string { + val, _ := getStringViperFlag(flags, key) + return val } var rootCmd = &cobra.Command{ @@ -92,10 +125,10 @@ the quick setup mode and a new database will be bootstraped and a new user created with the credentials from options "username" and "password".`, Run: python(func(cmd *cobra.Command, args []string, d pythonData) { if !d.hadDB { - quickSetup(d) + quickSetup(cmd.Flags(), d) } - server := getServer(d.store) + server := getServerWithViper(cmd.Flags(), d.store) setupLog(server.Log) handler, err := fbhttp.NewHandler(d.store, server) @@ -121,27 +154,40 @@ user created with the credentials from options "username" and "password".`, }, pythonConfig{allowNoDB: true}), } -// TODO: get server settings and only replace -// them if set on Viper. Although viper.IsSet -// is bugged and if binded to a pflag, it will -// always return true. -// Also, when doing that, add this options to -// config init, config import, printConfig -// and config set since the DB values will actually -// be used. For now, despite being stored in the DB, -// they won't be used. -func getServer(st *storage.Storage) *settings.Server { - root := v.GetString("root") - root, err := filepath.Abs(root) +func getServerWithViper(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { + server, err := st.Settings.GetServer() checkErr(err) - server := &settings.Server{} - server.BaseURL = v.GetString("baseurl") - server.Root = root - server.Address = v.GetString("address") - server.Port = v.GetString("port") - server.TLSKey = v.GetString("key") - server.TLSCert = v.GetString("cert") - server.Log = v.GetString("log") + + if val, set := getStringViperFlag(flags, "root"); set { + root, err := filepath.Abs(val) + checkErr(err) + server.Root = root + } + + if val, set := getStringViperFlag(flags, "baseurl"); set { + server.BaseURL = val + } + + if val, set := getStringViperFlag(flags, "address"); set { + server.Address = val + } + + if val, set := getStringViperFlag(flags, "port"); set { + server.Port = val + } + + if val, set := getStringViperFlag(flags, "log"); set { + server.Log = val + } + + if val, set := getStringViperFlag(flags, "key"); set { + server.TLSKey = val + } + + if val, set := getStringViperFlag(flags, "cert"); set { + server.TLSCert = val + } + return server } @@ -164,7 +210,7 @@ func setupLog(logMethod string) { } -func quickSetup(d pythonData) { +func quickSetup(flags *pflag.FlagSet, d pythonData) { set := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit Signup: false, @@ -186,12 +232,13 @@ func quickSetup(d pythonData) { } ser := &settings.Server{ - BaseURL: v.GetString("baseurl"), - Log: v.GetString("log"), - TLSKey: v.GetString("key"), - TLSCert: v.GetString("cert"), - Address: v.GetString("address"), - Root: v.GetString("root"), + BaseURL: mustGetStringViperFlag(flags, "baseurl"), + Port: mustGetStringViperFlag(flags, "port"), + Log: mustGetStringViperFlag(flags, "log"), + TLSKey: mustGetStringViperFlag(flags, "key"), + TLSCert: mustGetStringViperFlag(flags, "cert"), + Address: mustGetStringViperFlag(flags, "address"), + Root: mustGetStringViperFlag(flags, "root"), } err := d.store.Settings.Save(set) @@ -203,8 +250,8 @@ func quickSetup(d pythonData) { err = d.store.Auth.Save(&auth.JSONAuth{}) checkErr(err) - username := v.GetString("username") - password := v.GetString("password") + username := mustGetStringViperFlag(flags, "username") + password := mustGetStringViperFlag(flags, "password") if password == "" { password, err = users.HashPwd("admin") diff --git a/cmd/upgrade.go b/cmd/upgrade.go index c0e06567..250542e4 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -3,7 +3,6 @@ package cmd import ( "github.com/filebrowser/filebrowser/v2/storage/bolt/importer" "github.com/spf13/cobra" - v "github.com/spf13/viper" ) func init() { @@ -25,7 +24,7 @@ this version.`, oldDB := mustGetString(cmd, "old.database") oldConf := mustGetString(cmd, "old.config") - err := importer.Import(oldDB, oldConf, v.GetString("database")) + err := importer.Import(oldDB, oldConf, mustGetStringViperFlag(cmd.Flags(), "database")) checkErr(err) }, } diff --git a/cmd/utils.go b/cmd/utils.go index 9921a578..e2809c26 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -13,35 +13,9 @@ import ( "github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage/bolt" "github.com/spf13/cobra" - "github.com/spf13/pflag" - v "github.com/spf13/viper" yaml "gopkg.in/yaml.v2" ) -func vaddP(f *pflag.FlagSet, k, p string, i interface{}, u string) { - switch y := i.(type) { - case bool: - f.BoolP(k, p, y, u) - case int: - f.IntP(k, p, y, u) - case string: - f.StringP(k, p, y, u) - } - v.SetDefault(k, i) -} - -func vadd(f *pflag.FlagSet, k string, i interface{}, u string) { - switch y := i.(type) { - case bool: - f.Bool(k, y, u) - case int: - f.Int(k, y, u) - case string: - f.String(k, y, u) - } - v.SetDefault(k, i) -} - func checkErr(err error) { if err != nil { fmt.Println(err) @@ -92,7 +66,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { return func(cmd *cobra.Command, args []string) { data := pythonData{hadDB: true} - path := v.GetString("database") + path := mustGetStringViperFlag(cmd.Flags(), "database") _, err := os.Stat(path) if os.IsNotExist(err) {