diff --git a/cmd/root.go b/cmd/root.go index 5875ae24..ef9f14ff 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,12 +9,12 @@ import ( "net/http" "os" "path/filepath" - "strconv" "strings" "github.com/filebrowser/filebrowser/v2/auth" fbhttp "github.com/filebrowser/filebrowser/v2/http" "github.com/filebrowser/filebrowser/v2/settings" + "github.com/filebrowser/filebrowser/v2/storage" "github.com/filebrowser/filebrowser/v2/users" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" @@ -36,7 +36,7 @@ func init() { 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, "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") @@ -91,59 +91,26 @@ Also, if the database path doesn't exist, File Browser will enter into 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) { - switch logMethod := v.GetString("log"); logMethod { - case "stdout": - log.SetOutput(os.Stdout) - case "stderr": - log.SetOutput(os.Stderr) - case "": - log.SetOutput(ioutil.Discard) - default: - log.SetOutput(&lumberjack.Logger{ - Filename: logMethod, - MaxSize: 100, - MaxAge: 14, - MaxBackups: 10, - }) - } - if !d.hadDB { quickSetup(d) } - port := v.GetInt("port") - address := v.GetString("address") - cert := v.GetString("cert") - key := v.GetString("key") - root := v.GetString("root") + server := getServer(d.store) + setupLog(server.Log) - root, err := filepath.Abs(root) - checkErr(err) - settings, 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) - - handler, err := fbhttp.NewHandler(d.store) + handler, err := fbhttp.NewHandler(d.store, server) checkErr(err) var listener net.Listener - if key != "" && cert != "" { - cer, err := tls.LoadX509KeyPair(cert, key) + if server.TLSKey != "" && server.TLSCert != "" { + cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) checkErr(err) config := &tls.Config{Certificates: []tls.Certificate{cer}} - listener, err = tls.Listen("tcp", address+":"+strconv.Itoa(port), config) + listener, err = tls.Listen("tcp", server.Address+":"+server.Port, config) checkErr(err) } else { - listener, err = net.Listen("tcp", address+":"+strconv.Itoa(port)) + listener, err = net.Listen("tcp", server.Address+":"+server.Port) checkErr(err) } @@ -154,10 +121,52 @@ 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) + 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") + return server +} + +func setupLog(logMethod string) { + switch logMethod { + case "stdout": + log.SetOutput(os.Stdout) + case "stderr": + log.SetOutput(os.Stderr) + case "": + log.SetOutput(ioutil.Discard) + default: + log.SetOutput(&lumberjack.Logger{ + Filename: logMethod, + MaxSize: 100, + MaxAge: 14, + MaxBackups: 10, + }) + } + +} + func quickSetup(d pythonData) { set := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit - BaseURL: v.GetString("baseurl"), Signup: false, AuthMethod: auth.MethodJSONAuth, Defaults: settings.UserDefaults{ @@ -176,9 +185,21 @@ 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"), + } + err := d.store.Settings.Save(set) checkErr(err) + err = d.store.Settings.SaveServer(ser) + checkErr(err) + err = d.store.Auth.Save(&auth.JSONAuth{}) checkErr(err) diff --git a/cmd/utils.go b/cmd/utils.go index d1e24dc2..9921a578 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -98,8 +98,8 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { if os.IsNotExist(err) { data.hadDB = false - if !cfg.noDB || !cfg.allowNoDB { - log.Fatal(path + " does not exid.store. Please run 'filebrowser config init' fird.store.") + if !cfg.noDB && !cfg.allowNoDB { + log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.") } } else if err != nil { panic(err) diff --git a/http/auth.go b/http/auth.go index 0b6a2f6c..d2c4792f 100644 --- a/http/auth.go +++ b/http/auth.go @@ -67,7 +67,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(d.server.Root, tk.User.ID) if err != nil { return http.StatusInternalServerError, err } @@ -91,7 +91,7 @@ var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, e return http.StatusInternalServerError, err } - user, err := auther.Auth(r, d.store.Users, d.Settings.Root) + user, err := auther.Auth(r, d.store.Users, d.server.Root) if err == os.ErrPermission { return http.StatusForbidden, nil } else if err != nil { diff --git a/http/data.go b/http/data.go index d891c280..48cf0a4b 100644 --- a/http/data.go +++ b/http/data.go @@ -16,6 +16,7 @@ type handleFunc func(w http.ResponseWriter, r *http.Request, d *data) (int, erro type data struct { *runner.Runner settings *settings.Settings + server *settings.Server store *storage.Storage user *users.User raw interface{} @@ -38,7 +39,7 @@ func (d *data) Check(path string) bool { return true } -func handle(fn handleFunc, prefix string, storage *storage.Storage) http.Handler { +func handle(fn handleFunc, prefix string, storage *storage.Storage, server *settings.Server) http.Handler { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { settings, err := storage.Settings.Get() if err != nil { @@ -50,6 +51,7 @@ func handle(fn handleFunc, prefix string, storage *storage.Storage) http.Handler Runner: &runner.Runner{Settings: settings}, store: storage, settings: settings, + server: server, }) if status != 0 { diff --git a/http/http.go b/http/http.go index 8333cf62..6765a0bb 100644 --- a/http/http.go +++ b/http/http.go @@ -3,6 +3,7 @@ package http import ( "net/http" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/storage" "github.com/gorilla/mux" ) @@ -12,47 +13,51 @@ type modifyRequest struct { Which []string `json:"which"` // Answer to: which fields? } -func NewHandler(storage *storage.Storage) (http.Handler, error) { +func NewHandler(storage *storage.Storage, server *settings.Server) (http.Handler, error) { r := mux.NewRouter() - index, static := getStaticHandlers(storage) + index, static := getStaticHandlers(storage, server) + + monkey := func(fn handleFunc, prefix string) http.Handler { + return handle(fn, prefix, storage, server) + } r.PathPrefix("/static").Handler(static) r.NotFoundHandler = index api := r.PathPrefix("/api").Subrouter() - api.Handle("/login", handle(loginHandler, "", storage)) - api.Handle("/signup", handle(signupHandler, "", storage)) - api.Handle("/renew", handle(renewHandler, "", storage)) + api.Handle("/login", monkey(loginHandler, "")) + api.Handle("/signup", monkey(signupHandler, "")) + api.Handle("/renew", monkey(renewHandler, "")) users := api.PathPrefix("/users").Subrouter() - users.Handle("", handle(usersGetHandler, "", storage)).Methods("GET") - users.Handle("", handle(userPostHandler, "", storage)).Methods("POST") - users.Handle("/{id:[0-9]+}", handle(userPutHandler, "", storage)).Methods("PUT") - users.Handle("/{id:[0-9]+}", handle(userGetHandler, "", storage)).Methods("GET") - users.Handle("/{id:[0-9]+}", handle(userDeleteHandler, "", storage)).Methods("DELETE") + users.Handle("", monkey(usersGetHandler, "")).Methods("GET") + users.Handle("", monkey(userPostHandler, "")).Methods("POST") + users.Handle("/{id:[0-9]+}", monkey(userPutHandler, "")).Methods("PUT") + users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET") + users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE") - api.PathPrefix("/resources").Handler(handle(resourceGetHandler, "/api/resources", storage)).Methods("GET") - api.PathPrefix("/resources").Handler(handle(resourceDeleteHandler, "/api/resources", storage)).Methods("DELETE") - api.PathPrefix("/resources").Handler(handle(resourcePostPutHandler, "/api/resources", storage)).Methods("POST") - api.PathPrefix("/resources").Handler(handle(resourcePostPutHandler, "/api/resources", storage)).Methods("PUT") - api.PathPrefix("/resources").Handler(handle(resourcePatchHandler, "/api/resources", storage)).Methods("PATCH") + api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET") + api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler, "/api/resources")).Methods("DELETE") + api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST") + api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT") + api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH") - api.PathPrefix("/share").Handler(handle(shareGetsHandler, "/api/share", storage)).Methods("GET") - api.PathPrefix("/share").Handler(handle(sharePostHandler, "/api/share", storage)).Methods("POST") - api.PathPrefix("/share").Handler(handle(shareDeleteHandler, "/api/share", storage)).Methods("DELETE") + api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET") + api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST") + api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE") - api.Handle("/settings", handle(settingsGetHandler, "", storage)).Methods("GET") - api.Handle("/settings", handle(settingsPutHandler, "", storage)).Methods("PUT") + api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET") + api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT") - api.PathPrefix("/raw").Handler(handle(rawHandler, "/api/raw", storage)).Methods("GET") - api.PathPrefix("/command").Handler(handle(commandsHandler, "/api/command", storage)).Methods("GET") - api.PathPrefix("/search").Handler(handle(searchHandler, "/api/search", storage)).Methods("GET") + api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") + api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET") + api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET") public := api.PathPrefix("/public").Subrouter() - public.PathPrefix("/dl").Handler(handle(publicDlHandler, "/api/public/dl/", storage)).Methods("GET") - public.PathPrefix("/share").Handler(handle(publicShareHandler, "/api/public/share/", storage)).Methods("GET") + public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET") + public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET") return r, nil } diff --git a/http/public.go b/http/public.go index 2d734c18..f43e3f66 100644 --- a/http/public.go +++ b/http/public.go @@ -13,7 +13,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(d.server.Root, link.UserID) if err != nil { return errToStatus(err), err } diff --git a/http/share.go b/http/share.go index 9c17dda6..3c98e47c 100644 --- a/http/share.go +++ b/http/share.go @@ -56,7 +56,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(d.server.BaseURL + "/share/" + s.Hash)) return 0, nil } } diff --git a/http/static.go b/http/static.go index bca3821e..2f0fe657 100644 --- a/http/static.go +++ b/http/static.go @@ -11,6 +11,7 @@ import ( "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(d.server.BaseURL+"/static", "/") data := map[string]interface{}{ "Name": d.settings.Branding.Name, "DisableExternal": d.settings.Branding.DisableExternal, - "BaseURL": d.settings.BaseURL, + "BaseURL": d.server.BaseURL, "Version": version.Version, "StaticURL": staticURL, "Signup": d.settings.Signup, @@ -76,7 +77,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *data, box * return 0, nil } -func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) { +func getStaticHandlers(storage *storage.Storage, server *settings.Server) (http.Handler, http.Handler) { box := rice.MustFindBox("../frontend/dist") handler := http.FileServer(box.HTTPBox()) @@ -87,7 +88,7 @@ func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) { w.Header().Set("x-xss-protection", "1; mode=block") return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8") - }, "", storage) + }, "", storage, server) static := handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { if r.Method != http.MethodGet { @@ -113,7 +114,7 @@ func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) { } return handleWithStaticData(w, r, d, box, r.URL.Path, "application/javascript; charset=utf-8") - }, "/static/", storage) + }, "/static/", storage, server) return index, static } diff --git a/http/users.go b/http/users.go index 815a1349..67a9d03e 100644 --- a/http/users.go +++ b/http/users.go @@ -61,7 +61,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(d.server.Root) if err != nil { return http.StatusInternalServerError, err } @@ -78,7 +78,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(d.server.Root, d.raw.(uint)) if err == errors.ErrNotExist { return http.StatusNotFound, err } @@ -147,7 +147,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(d.server.Root, d.raw.(uint)) req.Data.Password = suser.Password } diff --git a/settings/settings.go b/settings/settings.go index 7c4cfb94..3e41c1f5 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -8,8 +8,6 @@ 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"` Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` @@ -19,6 +17,17 @@ type Settings struct { Rules []rules.Rule `json:"rules"` } +// Server specific settings. +type Server struct { + Root string `json:"root"` + BaseURL string `json:"baseURL"` + TLSKey string `json:"tlsKey"` + TLSCert string `json:"tlsCert"` + Port string `json:"port"` + Address string `json:"address"` + Log string `json:"log"` +} + // GetRules implements rules.Provider. func (s *Settings) GetRules() []rules.Rule { return s.Rules diff --git a/settings/storage.go b/settings/storage.go index 940fbf28..f3cb18d3 100644 --- a/settings/storage.go +++ b/settings/storage.go @@ -12,6 +12,8 @@ import ( type StorageBackend interface { Get() (*Settings, error) Save(*Settings) error + GetServer() (*Server, error) + SaveServer(*Server) error } // Storage is a settings storage. @@ -39,8 +41,6 @@ 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 } @@ -86,3 +86,14 @@ func (s *Storage) Save(set *Settings) error { return nil } + +// GetServer wraps StorageBackend.GetServer. +func (s *Storage) GetServer() (*Server, error) { + return s.back.GetServer() +} + +// SaveServer wraps StorageBackend.SaveServer and adds some verification. +func (s *Storage) SaveServer(ser *Server) error { + ser.BaseURL = strings.TrimSuffix(ser.BaseURL, "/") + return s.back.SaveServer(ser) +} diff --git a/storage/bolt/config.go b/storage/bolt/config.go index 0d5f09ef..7fad6010 100644 --- a/storage/bolt/config.go +++ b/storage/bolt/config.go @@ -17,3 +17,12 @@ func (s settingsBackend) Get() (*settings.Settings, error) { func (s settingsBackend) Save(settings *settings.Settings) error { return save(s.db, "settings", settings) } + +func (s settingsBackend) GetServer() (*settings.Server, error) { + server := &settings.Server{} + return server, get(s.db, "server", server) +} + +func (s settingsBackend) SaveServer(server *settings.Server) error { + return save(s.db, "server", server) +} diff --git a/storage/bolt/importer/conf.go b/storage/bolt/importer/conf.go index 335f4148..618a7766 100644 --- a/storage/bolt/importer/conf.go +++ b/storage/bolt/importer/conf.go @@ -33,7 +33,7 @@ type oldAuth struct { } type oldConf struct { - Port int `json:"port" yaml:"port" toml:"port"` + Port string `json:"port" yaml:"port" toml:"port"` BaseURL string `json:"baseURL" yaml:"baseURL" toml:"baseURL"` Log string `json:"log" yaml:"log" toml:"log"` Address string `json:"address" yaml:"address" toml:"address"` @@ -47,7 +47,7 @@ type oldConf struct { } var defaults = &oldConf{ - Port: 0, + Port: "0", Log: "stdout", Defaults: oldDefs{ Commands: []string{"git", "svn", "hg"}, @@ -110,7 +110,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { s := &settings.Settings{ Key: key, - BaseURL: cfg.BaseURL, Signup: false, Defaults: settings.UserDefaults{ Scope: cfg.Defaults.Scope, @@ -130,6 +129,13 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { }, } + server := &settings.Server{ + BaseURL : cfg.BaseURL, + Port : cfg.Port, + Address : cfg.Address, + Log : cfg.Log, + } + var auther auth.Auther switch cfg.Auth.Method { case "proxy": @@ -159,6 +165,11 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { return err } + err = sto.Settings.SaveServer(server) + if err != nil { + return err + } + fmt.Println("Configuration successfully imported.") return nil }