diff --git a/cmd/config_init.go b/cmd/config_init.go index 37a698a9..4cc2c41a 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -28,7 +28,7 @@ override the options.`, authMethod, auther := getAuthentication(cmd) s := &settings.Settings{ - Key: generateRandomBytes(64), // 256 bit + Runtime: runtimeNonDefaults(), Signup: mustGetBool(cmd, "signup"), Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "), AuthMethod: authMethod, diff --git a/cmd/root.go b/cmd/root.go index 52b8dd00..d379456a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,13 +2,13 @@ package cmd import ( "crypto/tls" + "fmt" "io/ioutil" "log" "net" "net/http" "os" "path/filepath" - "strconv" "strings" "github.com/filebrowser/filebrowser/v2/auth" @@ -35,15 +35,16 @@ func init() { 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") + vaddP(f, "address", "a", settings.RuntimeDefaults["address"], "address to listen on") + vaddP(f, "log", "l", settings.RuntimeDefaults["log"], "log output") + vaddP(f, "port", "p", settings.RuntimeDefaults["port"], "port to listen on") + vaddP(f, "cert", "t", settings.RuntimeDefaults["cert"], "tls certificate") + vaddP(f, "key", "k", settings.RuntimeDefaults["key"], "tls key") + vaddP(f, "root", "r", settings.RuntimeDefaults["root"], "root path to prepend to all relative paths") + vaddP(f, "baseurl", "b", settings.RuntimeDefaults["baseurl"], "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\")") + vaddP(f, "force", "f", false, "") if err := v.BindPFlags(f); err != nil { panic(err) @@ -97,7 +98,7 @@ user created with the credentials from options "username" and "password".`, log.Println(cfgFile) - switch logMethod := v.GetString("log"); logMethod { + switch logMethod := settings.RuntimeCfg["log"]; logMethod { case "stdout": log.SetOutput(os.Stdout) case "stderr": @@ -117,44 +118,40 @@ user created with the credentials from options "username" and "password".`, quickSetup(d) } - // TODO: check if these fields (including baseurl) are available in the DB. proceed according to --force - - port := v.GetInt("port") - address := v.GetString("address") - cert := v.GetString("cert") - key := v.GetString("key") - root := v.GetString("root") - - root, err := filepath.Abs(root) - checkErr(err) - settings, err := d.store.Settings.Get() + s, err := d.store.Settings.Get() checkErr(err) - // Despite Base URL and Scope being "server" type of - // variables, we persist them to the database because - // they are needed during the execution and not only - // to start up the server. - settings.BaseURL = v.GetString("baseurl") - settings.Root = root - err = d.store.Settings.Save(settings) - checkErr(err) + if !v.GetBool("force") { + for k := range settings.RuntimeCfg { + if y, ok := s.Runtime[k]; ok { + settings.RuntimeCfg[k] = y + } + } + } - handler, err := fbhttp.NewHandler(d.store) + r, err := filepath.Abs(settings.RuntimeCfg["root"]) checkErr(err) + settings.RuntimeCfg["root"] = r + + adr := settings.RuntimeCfg["address"] + ":" + settings.RuntimeCfg["port"] + cert := settings.RuntimeCfg["cert"] + key := settings.RuntimeCfg["key"] var listener net.Listener if key != "" && cert != "" { cer, err := tls.LoadX509KeyPair(cert, key) checkErr(err) - config := &tls.Config{Certificates: []tls.Certificate{cer}} - listener, err = tls.Listen("tcp", address+":"+strconv.Itoa(port), config) + listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) checkErr(err) } else { - listener, err = net.Listen("tcp", address+":"+strconv.Itoa(port)) + listener, err = net.Listen("tcp", adr) checkErr(err) } + handler, err := fbhttp.NewHandler(d.store) + checkErr(err) + log.Println("Listening on", listener.Addr().String()) if err := http.Serve(listener, handler); err != nil { log.Fatal(err) @@ -162,13 +159,26 @@ user created with the credentials from options "username" and "password".`, }, pythonConfig{allowNoDB: true}), } +func runtimeNonDefaults() (m map[string]string) { + for k, d := range settings.RuntimeDefaults { + if x, ok := settings.RuntimeCfg[k]; ok && (x != d) { + log.Println(fmt.Sprintf("Non-default value for key '%s': %s [default: %s]", k, x, d)) + m[k] = x + } + } + if settings.RuntimeCfg["key"] == "" { + log.Println("Generate random 256 bit key") + m["key"] = string(generateRandomBytes(64)) // 256 bit + } + return +} + func quickSetup(d pythonData) { - // TODO: save also port, address, cert, key, scope; if their values differ from defaults + log.Println("Executing quick setup...") set := &settings.Settings{ - Key: generateRandomBytes(64), // 256 bit - BaseURL: v.GetString("baseurl"), + Runtime: runtimeNonDefaults(), Signup: false, AuthMethod: auth.MethodJSONAuth, Defaults: settings.UserDefaults{ @@ -216,6 +226,8 @@ func quickSetup(d pythonData) { err = d.store.Users.Save(user) checkErr(err) + + log.Println("Quick setup finished") } func initConfig() { @@ -239,6 +251,11 @@ func initConfig() { panic(err) } cfgFile = "No config file used" + } else { + cfgFile = "Using config file: " + v.ConfigFileUsed() + } + + for k := range settings.RuntimeDefaults { + settings.RuntimeCfg[k] = v.GetString(k) } - cfgFile = "Using config file: " + v.ConfigFileUsed() } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index d4b91b22..a4b41368 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -1,9 +1,9 @@ package cmd import ( + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage/bolt/importer" "github.com/spf13/cobra" - v "github.com/spf13/viper" ) func init() { @@ -26,7 +26,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, settings.RuntimeCfg["database"]) checkErr(err) }, } diff --git a/http/auth.go b/http/auth.go index 0b6a2f6c..590caf67 100644 --- a/http/auth.go +++ b/http/auth.go @@ -7,9 +7,10 @@ import ( "strings" "time" - "github.com/dgrijalva/jwt-go" + jwt "github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go/request" "github.com/filebrowser/filebrowser/v2/errors" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/users" ) @@ -50,7 +51,7 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) { func withUser(fn handleFunc) handleFunc { return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { keyFunc := func(token *jwt.Token) (interface{}, error) { - return d.settings.Key, nil + return settings.RuntimeCfg["key"], nil } var tk authToken @@ -67,7 +68,7 @@ func withUser(fn handleFunc) handleFunc { w.Header().Add("X-Renew-Token", "true") } - d.user, err = d.store.Users.Get(d.settings.Root, tk.User.ID) + d.user, err = d.store.Users.Get(settings.RuntimeCfg["root"], tk.User.ID) if err != nil { return http.StatusInternalServerError, err } @@ -169,7 +170,7 @@ func printToken(w http.ResponseWriter, r *http.Request, d *data, user *users.Use } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signed, err := token.SignedString(d.settings.Key) + signed, err := token.SignedString(settings.RuntimeCfg["key"]) if err != nil { return http.StatusInternalServerError, err } diff --git a/http/public.go b/http/public.go index 2d734c18..5690aa68 100644 --- a/http/public.go +++ b/http/public.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/filebrowser/filebrowser/v2/files" + "github.com/filebrowser/filebrowser/v2/settings" ) var withHashFile = func(fn handleFunc) handleFunc { @@ -13,7 +14,7 @@ var withHashFile = func(fn handleFunc) handleFunc { return errToStatus(err), err } - user, err := d.store.Users.Get(d.settings.Root, link.UserID) + user, err := d.store.Users.Get(settings.RuntimeCfg["root"], link.UserID) if err != nil { return errToStatus(err), err } diff --git a/http/share.go b/http/share.go index 9c17dda6..304afe34 100644 --- a/http/share.go +++ b/http/share.go @@ -9,6 +9,7 @@ import ( "time" "github.com/filebrowser/filebrowser/v2/errors" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/share" ) @@ -56,7 +57,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request var err error s, err = d.store.Share.GetPermanent(r.URL.Path, d.user.ID) if err == nil { - w.Write([]byte(d.settings.BaseURL + "/share/" + s.Hash)) + w.Write([]byte(settings.RuntimeCfg["baseurl"] + "/share/" + s.Hash)) return 0, nil } } diff --git a/http/static.go b/http/static.go index bca3821e..7f510463 100644 --- a/http/static.go +++ b/http/static.go @@ -9,8 +9,9 @@ import ( "strings" "text/template" - "github.com/GeertJohan/go.rice" + rice "github.com/GeertJohan/go.rice" "github.com/filebrowser/filebrowser/v2/auth" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/version" ) @@ -18,12 +19,12 @@ import ( func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box *rice.Box, file, contentType string) (int, error) { w.Header().Set("Content-Type", contentType) - staticURL := strings.TrimPrefix(d.settings.BaseURL+"/static", "/") + staticURL := strings.TrimPrefix(settings.RuntimeCfg["baseurl"]+"/static", "/") data := map[string]interface{}{ "Name": d.settings.Branding.Name, "DisableExternal": d.settings.Branding.DisableExternal, - "BaseURL": d.settings.BaseURL, + "BaseURL": settings.RuntimeCfg["baseurl"], "Version": version.Version, "StaticURL": staticURL, "Signup": d.settings.Signup, diff --git a/http/users.go b/http/users.go index 815a1349..540ebbcd 100644 --- a/http/users.go +++ b/http/users.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/filebrowser/filebrowser/v2/errors" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/users" "github.com/gorilla/mux" ) @@ -61,7 +62,7 @@ func withSelfOrAdmin(fn handleFunc) handleFunc { } var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - users, err := d.store.Users.Gets(d.settings.Root) + users, err := d.store.Users.Gets(settings.RuntimeCfg["root"]) if err != nil { return http.StatusInternalServerError, err } @@ -78,7 +79,7 @@ var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d * }) var userGetHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - u, err := d.store.Users.Get(d.settings.Root, d.raw.(uint)) + u, err := d.store.Users.Get(settings.RuntimeCfg["root"], d.raw.(uint)) if err == errors.ErrNotExist { return http.StatusNotFound, err } @@ -147,7 +148,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request req.Data.Password, err = users.HashPwd(req.Data.Password) } else { var suser *users.User - suser, err = d.store.Users.Get(d.settings.Root, d.raw.(uint)) + suser, err = d.store.Users.Get(settings.RuntimeCfg["root"], d.raw.(uint)) req.Data.Password = suser.Password } diff --git a/settings/runtime.go b/settings/runtime.go new file mode 100644 index 00000000..0109b830 --- /dev/null +++ b/settings/runtime.go @@ -0,0 +1,15 @@ +package settings + +// RuntimeDefaults defines default values for runtime parameters +var RuntimeDefaults = map[string]string{ + "root": ".", + "baseurl": "", + "address": "127.0.0.1", + "port": "8080", + "cert": "", + "key": "", + "log": "stdout", +} + +// RuntimeCfg contains parameters to be used at runtime +var RuntimeCfg = map[string]string{} diff --git a/settings/settings.go b/settings/settings.go index 7c4cfb94..9a420554 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -7,9 +7,7 @@ type AuthMethod string // Settings contain the main settings of the application. type Settings struct { - Key []byte `json:"key"` - BaseURL string `json:"baseURL"` - Root string `json:"root"` + Runtime map[string]string `json:"runtime"` Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` diff --git a/settings/storage.go b/settings/storage.go index 940fbf28..7cf354bd 100644 --- a/settings/storage.go +++ b/settings/storage.go @@ -1,9 +1,6 @@ package settings import ( - "strings" - - "github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/rules" "github.com/filebrowser/filebrowser/v2/users" ) @@ -39,12 +36,13 @@ var defaultEvents = []string{ // Save saves the settings for the current instance. func (s *Storage) Save(set *Settings) error { - set.BaseURL = strings.TrimSuffix(set.BaseURL, "/") - - if len(set.Key) == 0 { - return errors.ErrEmptyKey - } + /* + set.BaseURL = strings.TrimSuffix(set.BaseURL, "/") + if len(set.Key) == 0 { + return errors.ErrEmptyKey + } + */ if set.Defaults.Locale == "" { set.Defaults.Locale = "en" } diff --git a/storage/bolt/importer/conf.go b/storage/bolt/importer/conf.go index 335f4148..c7d7917c 100644 --- a/storage/bolt/importer/conf.go +++ b/storage/bolt/importer/conf.go @@ -13,8 +13,8 @@ import ( "github.com/asdine/storm" "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage" - "github.com/pelletier/go-toml" - "gopkg.in/yaml.v2" + toml "github.com/pelletier/go-toml" + yaml "gopkg.in/yaml.v2" ) type oldDefs struct { @@ -109,9 +109,9 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { } s := &settings.Settings{ - Key: key, - BaseURL: cfg.BaseURL, - Signup: false, + //Key: key, + //BaseURL: cfg.BaseURL, + Signup: false, Defaults: settings.UserDefaults{ Scope: cfg.Defaults.Scope, Commands: cfg.Defaults.Commands,