feat: make server options a struct (#615)

pull/616/head
Henrique Dias 2019-01-08 10:29:09 +00:00 committed by GitHub
parent 73b8d2ee7e
commit 0e7abaa7fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 159 additions and 90 deletions

View File

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

View File

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

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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