feat: merge server db conf with flags/env/config

This commit is contained in:
Henrique Dias 2019-01-08 11:33:49 +00:00
parent fc06f642f2
commit 388810b6d8
9 changed files with 194 additions and 118 deletions

View File

@ -27,6 +27,7 @@ var configCmd = &cobra.Command{
} }
func addConfigFlags(flags *pflag.FlagSet) { func addConfigFlags(flags *pflag.FlagSet) {
addServerFlags(flags)
addUserFlags(flags) addUserFlags(flags)
flags.BoolP("signup", "s", false, "allow users to signup") flags.BoolP("signup", "s", false, "allow users to signup")
flags.String("shell", "", "shell command to which other commands should be appended") 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 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) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup) fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod) fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " ")) fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
fmt.Fprintln(w, "\nBranding:") fmt.Fprintln(w, "\nBranding:")
fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name) fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files) fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
fmt.Fprintf(w, "\tDisable external links:\t%t\n", s.Branding.DisableExternal) 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.Fprintln(w, "\nDefaults:")
fmt.Fprintf(w, "\tScope:\t%s\n", s.Defaults.Scope) fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
fmt.Fprintf(w, "\tLocale:\t%s\n", s.Defaults.Locale) fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
fmt.Fprintf(w, "\tView mode:\t%s\n", s.Defaults.ViewMode) fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(s.Defaults.Commands, " ")) fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
fmt.Fprintf(w, "\tSorting:\n") fmt.Fprintf(w, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", s.Defaults.Sorting.By) fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
fmt.Fprintf(w, "\t\tAsc:\t%t\n", s.Defaults.Sorting.Asc) fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
fmt.Fprintf(w, "\tPermissions:\n") fmt.Fprintf(w, "\tPermissions:\n")
fmt.Fprintf(w, "\t\tAdmin:\t%t\n", s.Defaults.Perm.Admin) fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin)
fmt.Fprintf(w, "\t\tExecute:\t%t\n", s.Defaults.Perm.Execute) fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute)
fmt.Fprintf(w, "\t\tCreate:\t%t\n", s.Defaults.Perm.Create) fmt.Fprintf(w, "\t\tCreate:\t%t\n", set.Defaults.Perm.Create)
fmt.Fprintf(w, "\t\tRename:\t%t\n", s.Defaults.Perm.Rename) fmt.Fprintf(w, "\t\tRename:\t%t\n", set.Defaults.Perm.Rename)
fmt.Fprintf(w, "\t\tModify:\t%t\n", s.Defaults.Perm.Modify) fmt.Fprintf(w, "\t\tModify:\t%t\n", set.Defaults.Perm.Modify)
fmt.Fprintf(w, "\t\tDelete:\t%t\n", s.Defaults.Perm.Delete) fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete)
fmt.Fprintf(w, "\t\tShare:\t%t\n", s.Defaults.Perm.Share) fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share)
fmt.Fprintf(w, "\t\tDownload:\t%t\n", s.Defaults.Perm.Download) fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download)
w.Flush() w.Flush()
b, err := json.MarshalIndent(auther, "", " ") b, err := json.MarshalIndent(auther, "", " ")

View File

@ -14,10 +14,12 @@ var configCatCmd = &cobra.Command{
Long: `Prints the configuration.`, Long: `Prints the configuration.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
s, err := d.store.Settings.Get() set, err := d.store.Settings.Get()
checkErr(err) checkErr(err)
auther, err := d.store.Auth.Get(s.AuthMethod) ser, err := d.store.Settings.GetServer()
checkErr(err) checkErr(err)
printSettings(s, auther) auther, err := d.store.Auth.Get(set.AuthMethod)
checkErr(err)
printSettings(ser, set, auther)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -16,12 +16,16 @@ var configExportCmd = &cobra.Command{
settings, err := d.store.Settings.Get() settings, err := d.store.Settings.Get()
checkErr(err) checkErr(err)
server, err := d.store.Settings.GetServer()
checkErr(err)
auther, err := d.store.Auth.Get(settings.AuthMethod) auther, err := d.store.Auth.Get(settings.AuthMethod)
checkErr(err) checkErr(err)
data := &settingsFile{ data := &settingsFile{
Settings: settings, Settings: settings,
Auther: auther, Auther: auther,
Server: server,
} }
err = marshal(args[0], data) err = marshal(args[0], data)

View File

@ -3,6 +3,7 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"path/filepath"
"reflect" "reflect"
"github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/auth"
@ -16,6 +17,7 @@ func init() {
type settingsFile struct { type settingsFile struct {
Settings *settings.Settings `json:"settings"` Settings *settings.Settings `json:"settings"`
Server *settings.Server `json:"server"`
Auther interface{} `json:"auther"` Auther interface{} `json:"auther"`
} }
@ -45,23 +47,31 @@ database.`,
err = d.store.Settings.Save(file.Settings) err = d.store.Settings.Save(file.Settings)
checkErr(err) 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 var auther auth.Auther
switch file.Settings.AuthMethod { switch file.Settings.AuthMethod {
case auth.MethodJSONAuth: case auth.MethodJSONAuth:
auther = getAuther(auth.JSONAuth{}, autherInterf).(*auth.JSONAuth) auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth)
case auth.MethodNoAuth: case auth.MethodNoAuth:
auther = getAuther(auth.NoAuth{}, autherInterf).(*auth.NoAuth) auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
case auth.MethodProxyAuth: case auth.MethodProxyAuth:
auther = getAuther(auth.ProxyAuth{}, autherInterf).(*auth.ProxyAuth) auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
default: default:
checkErr(errors.New("invalid auth method")) checkErr(errors.New("invalid auth method"))
} }
err = d.store.Auth.Save(auther) err = d.store.Auth.Save(auther)
checkErr(err) checkErr(err)
printSettings(file.Settings, auther)
printSettings(file.Server, file.Settings, auther)
}, pythonConfig{allowNoDB: true}), }, pythonConfig{allowNoDB: true}),
} }

View File

@ -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) err := d.store.Settings.Save(s)
checkErr(err) checkErr(err)
err = d.store.Settings.SaveServer(ser)
checkErr(err)
err = d.store.Auth.Save(auther) err = d.store.Auth.Save(auther)
checkErr(err) 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 Now add your first user via 'filebrowser users new' 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(s, auther) printSettings(ser, s, auther)
}, pythonConfig{noDB: true}), }, pythonConfig{noDB: true}),
} }

View File

@ -20,41 +20,60 @@ var configSetCmd = &cobra.Command{
you want to change.`, you want to change.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { 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) checkErr(err)
hasAuth := false hasAuth := false
cmd.Flags().Visit(func(flag *pflag.Flag) { cmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name { 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": case "signup":
s.Signup = mustGetBool(cmd, flag.Name) set.Signup = mustGetBool(cmd, flag.Name)
case "auth.method": case "auth.method":
hasAuth = true hasAuth = true
case "shell": 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": case "branding.name":
s.Branding.Name = mustGetString(cmd, flag.Name) set.Branding.Name = mustGetString(cmd, flag.Name)
case "branding.disableExternal": case "branding.disableExternal":
s.Branding.DisableExternal = mustGetBool(cmd, flag.Name) set.Branding.DisableExternal = mustGetBool(cmd, flag.Name)
case "branding.files": 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 var auther auth.Auther
if hasAuth { if hasAuth {
s.AuthMethod, auther = getAuthentication(cmd) set.AuthMethod, auther = getAuthentication(cmd)
err = d.store.Auth.Save(auther) err = d.store.Auth.Save(auther)
checkErr(err) checkErr(err)
} else { } else {
auther, err = d.store.Auth.Get(s.AuthMethod) auther, err = d.store.Auth.Get(set.AuthMethod)
checkErr(err) checkErr(err)
} }
err = d.store.Settings.Save(s) err = d.store.Settings.Save(set)
checkErr(err) checkErr(err)
printSettings(s, auther) err = d.store.Settings.SaveServer(ser)
checkErr(err)
printSettings(ser, set, auther)
}, pythonConfig{}), }, pythonConfig{}),
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/filebrowser/filebrowser/v2/users" "github.com/filebrowser/filebrowser/v2/users"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
v "github.com/spf13/viper" v "github.com/spf13/viper"
lumberjack "gopkg.in/natefinch/lumberjack.v2" lumberjack "gopkg.in/natefinch/lumberjack.v2"
) )
@ -28,29 +29,61 @@ var (
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
flags := rootCmd.Flags()
persistent := rootCmd.PersistentFlags()
f := rootCmd.Flags() persistent.StringVarP(&cfgFile, "config", "c", "", "config file path")
pf := rootCmd.PersistentFlags() 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") addServerFlags(flags)
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\")")
if err := v.BindPFlags(f); err != nil { func addServerFlags(flags *pflag.FlagSet) {
panic(err) 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 { // If set through viper (env, config), return it.
panic(err) 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{ 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".`, user created with the credentials from options "username" and "password".`,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
if !d.hadDB { if !d.hadDB {
quickSetup(d) quickSetup(cmd.Flags(), d)
} }
server := getServer(d.store) server := getServerWithViper(cmd.Flags(), d.store)
setupLog(server.Log) setupLog(server.Log)
handler, err := fbhttp.NewHandler(d.store, server) handler, err := fbhttp.NewHandler(d.store, server)
@ -121,27 +154,40 @@ user created with the credentials from options "username" and "password".`,
}, pythonConfig{allowNoDB: true}), }, pythonConfig{allowNoDB: true}),
} }
// TODO: get server settings and only replace func getServerWithViper(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
// them if set on Viper. Although viper.IsSet server, err := st.Settings.GetServer()
// 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)
checkErr(err) checkErr(err)
server := &settings.Server{}
server.BaseURL = v.GetString("baseurl") if val, set := getStringViperFlag(flags, "root"); set {
server.Root = root root, err := filepath.Abs(val)
server.Address = v.GetString("address") checkErr(err)
server.Port = v.GetString("port") server.Root = root
server.TLSKey = v.GetString("key") }
server.TLSCert = v.GetString("cert")
server.Log = v.GetString("log") 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 return server
} }
@ -164,7 +210,7 @@ func setupLog(logMethod string) {
} }
func quickSetup(d pythonData) { func quickSetup(flags *pflag.FlagSet, d pythonData) {
set := &settings.Settings{ set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit Key: generateRandomBytes(64), // 256 bit
Signup: false, Signup: false,
@ -186,12 +232,13 @@ func quickSetup(d pythonData) {
} }
ser := &settings.Server{ ser := &settings.Server{
BaseURL: v.GetString("baseurl"), BaseURL: mustGetStringViperFlag(flags, "baseurl"),
Log: v.GetString("log"), Port: mustGetStringViperFlag(flags, "port"),
TLSKey: v.GetString("key"), Log: mustGetStringViperFlag(flags, "log"),
TLSCert: v.GetString("cert"), TLSKey: mustGetStringViperFlag(flags, "key"),
Address: v.GetString("address"), TLSCert: mustGetStringViperFlag(flags, "cert"),
Root: v.GetString("root"), Address: mustGetStringViperFlag(flags, "address"),
Root: mustGetStringViperFlag(flags, "root"),
} }
err := d.store.Settings.Save(set) err := d.store.Settings.Save(set)
@ -203,8 +250,8 @@ func quickSetup(d pythonData) {
err = d.store.Auth.Save(&auth.JSONAuth{}) err = d.store.Auth.Save(&auth.JSONAuth{})
checkErr(err) checkErr(err)
username := v.GetString("username") username := mustGetStringViperFlag(flags, "username")
password := v.GetString("password") password := mustGetStringViperFlag(flags, "password")
if password == "" { if password == "" {
password, err = users.HashPwd("admin") password, err = users.HashPwd("admin")

View File

@ -3,7 +3,6 @@ package cmd
import ( import (
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer" "github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
"github.com/spf13/cobra" "github.com/spf13/cobra"
v "github.com/spf13/viper"
) )
func init() { func init() {
@ -25,7 +24,7 @@ this version.`,
oldDB := mustGetString(cmd, "old.database") oldDB := mustGetString(cmd, "old.database")
oldConf := mustGetString(cmd, "old.config") oldConf := mustGetString(cmd, "old.config")
err := importer.Import(oldDB, oldConf, v.GetString("database")) err := importer.Import(oldDB, oldConf, mustGetStringViperFlag(cmd.Flags(), "database"))
checkErr(err) checkErr(err)
}, },
} }

View File

@ -13,35 +13,9 @@ import (
"github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt" "github.com/filebrowser/filebrowser/v2/storage/bolt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
yaml "gopkg.in/yaml.v2" 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) { func checkErr(err error) {
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -92,7 +66,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
return func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) {
data := pythonData{hadDB: true} data := pythonData{hadDB: true}
path := v.GetString("database") path := mustGetStringViperFlag(cmd.Flags(), "database")
_, err := os.Stat(path) _, err := os.Stat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {