merge with master

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>

Former-commit-id: dfac33db9ca3454cd3af02a732a64a02a8268b01 [formerly 38be015f7b3376a3bc992725c1cb1a019c1843cb] [formerly 5a4e310b96ebc5895e64f8eeb2f592890d4b63b0 [formerly 2b8bd28158]]
Former-commit-id: a30bfcebe6bbc115990b8f8398171b32942a8767 [formerly 553fafa22b46b2689ece093f3b0cf85b3cff1c87]
Former-commit-id: 5ce047d4e3f5217a179685f8b2850d4eb157376e
This commit is contained in:
Henrique Dias
2019-01-06 13:20:17 +00:00
25 changed files with 233 additions and 386 deletions

View File

@@ -1,30 +1,53 @@
package cmd
import (
"crypto/rand"
"crypto/tls"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
fbhttp "github.com/filebrowser/filebrowser/v2/http"
homedir "github.com/mitchellh/go-homedir"
"github.com/filebrowser/filebrowser/v2/settings"
"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"
)
var (
cfgFile string
)
func init() {
f := rootCmd.Flags()
pf := rootCmd.PersistentFlags()
f.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')")
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, "scope", "s", ".", "scope to prepend to a user's scope when it is relative")
if err := v.BindPFlags(f); err != nil {
panic(err)
}
if err := v.BindPFlags(pf); err != nil {
panic(err)
}
}
var rootCmd = &cobra.Command{
Use: "filebrowser",
Short: "A stylish web-based file browser",
@@ -33,89 +56,133 @@ manage your user and all the configurations without accessing the
web interface.
If you've never run File Browser, you will need to create the database.
See 'filebrowser help config init' for more information.
This command is used to start up the server. By default it starts listening
on localhost on a random port unless specified otherwise in the database or
via flags.
Use the available flags to override the database/default options. These flags
values won't be persisted to the database. To persist configuration to the database
use the command 'filebrowser config set'.`,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
startServer(st)
},
See 'filebrowser help config init' for more information.`,
Run: serveAndListen,
}
var (
cfgFile string
)
func serveAndListen(cmd *cobra.Command, args []string) {
initConfig()
// POSSIBLE WORKAROUND TO IDENTIFY WHEN DEFAULT VALUES ARE BEING USED
var defaults = struct {
database string
address string
log string
port int
scope string
admin string
}{
"./filebrowser.db",
"127.0.0.1",
"stderr",
80,
"/srv",
"admin",
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 _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) {
quickSetup(cmd)
}
db := getDB()
defer db.Close()
st := getStorage(db)
port := v.GetInt("port")
address := v.GetString("address")
cert := v.GetString("cert")
key := v.GetString("key")
scope := v.GetString("scope")
scope, err := filepath.Abs(scope)
checkErr(err)
settings, err := st.Settings.Get()
checkErr(err)
settings.Scope = scope
err = st.Settings.Save(settings)
checkErr(err)
handler, err := fbhttp.NewHandler(st)
checkErr(err)
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)
checkErr(err)
} else {
listener, err = net.Listen("tcp", address+":"+strconv.Itoa(port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, handler); err != nil {
log.Fatal(err)
}
}
func init() {
cobra.OnInitialize(initConfig)
//rootCmd.SetVersionTemplate("File Browser {{printf \"version %s\" .Version}}\n")
func quickSetup(cmd *cobra.Command) {
db, err := storm.Open(v.GetString("database"))
checkErr(err)
defer db.Close()
f := rootCmd.Flags()
pf := rootCmd.PersistentFlags()
pf.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')")
vaddP(pf, "database", "d", "./filebrowser.db", "path to the database")
vaddP(f, "address", "a", defaults.address, "address to listen on")
vaddP(f, "log", "l", defaults.log, "log output")
vaddP(f, "port", "p", defaults.port, "port to listen on")
vaddP(f, "cert", "t", "", "tls certificate (default comes from database)")
vaddP(f, "key", "k", "", "tls key (default comes from database)")
vaddP(f, "scope", "s", defaults.scope, "scope for users")
vaddP(f, "force", "f", false, "overwrite DB config with runtime params")
vaddP(f, "admin", "f", defaults.admin, "first username")
vaddP(f, "passwd", "f", "", "first username password hash")
vaddP(f, "baseurl", "b", "", "base URL")
// Bind the full flag sets to the configuration
if err := v.BindPFlags(f); err != nil {
panic(err)
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: "",
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Defaults: settings.UserDefaults{
Scope: ".",
Locale: "en",
Perm: users.Permissions{
Admin: false,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
},
}
if err := v.BindPFlags(pf); err != nil {
panic(err)
st := getStorage(db)
err = st.Settings.Save(set)
checkErr(err)
err = st.Auth.Save(&auth.JSONAuth{})
checkErr(err)
password, err := users.HashPwd("admin")
checkErr(err)
user := &users.User{
Username: "admin",
Password: password,
LockPassword: false,
}
set.Defaults.Apply(user)
user.Perm.Admin = true
err = st.Users.Save(user)
checkErr(err)
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile == "" {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
panic(err)
}
checkErr(err)
v.AddConfigPath(".")
v.AddConfigPath(home)
v.AddConfigPath("/etc/filebrowser/")
v.SetConfigName(".filebrowser")
} else {
// Use config file from the flag.
v.SetConfigFile(cfgFile)
}
@@ -131,174 +198,4 @@ func initConfig() {
} else {
log.Println("Using config file:", v.ConfigFileUsed())
}
log.Println("FORCE:", v.GetBool("force"))
/*
if DB exists
if force false
database has highest priority, if undefined in DB use config params
else
config params overwrite existing and non-existing params in DB
else
(quick)Setup with provided config params
*/
/*
DISPLAY WARNINGS WHEN DEFAULT VALUES ARE USED
This allows to know if a CLI flag was provided:
log.Println(rootCmd.Flags().Changed("database"))
However, that is not enough in order to know if a value came from a config file or from envvars.
This should allow so. But it seems not to work as expected (see spf13/viper#323):
log.Println(v.IsSet("database"))
*/
if _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) {
quickSetup()
}
}
/*
func serverVisitAndReplace(s *settings.Settings) {
rootCmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name {
case "log":
s.Log = v.GetString(flag.Name)
case "address":
s.Server.Address = v.GetString(flag.Name)
case "port":
s.Server.Port = v.GetInt(flag.Name)
case "cert":
s.Server.TLSCert = v.GetString(flag.Name)
case "key":
s.Server.TLSKey = v.GetString(flag.Name)
}
})
}
*/
func quickSetup() {
scope := v.GetString("scope")
if scope == defaults.scope {
log.Println("[WARN] Using default value '/srv' as param 'scope'")
}
db, err := storm.Open(v.GetString("database"))
checkErr(err)
defer db.Close()
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: v.GetString("baseurl"),
Log: v.GetString("log"),
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Server: settings.Server{
Port: v.GetInt("port"),
Address: v.GetString("address"),
TLSCert: v.GetString("cert"),
TLSKey: v.GetString("key"),
},
Defaults: settings.UserDefaults{
Scope: scope,
Locale: "en",
Perm: users.Permissions{
Admin: false,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
},
}
// serverVisitAndReplace(set)
st := getStorage(db)
err = st.Settings.Save(set)
checkErr(err)
err = st.Auth.Save(&auth.JSONAuth{})
checkErr(err)
password := v.GetString("password")
if password == "" {
password, err = users.HashPwd("admin")
checkErr(err)
}
user := &users.User{
Username: v.GetString("admin"),
Password: password,
LockPassword: false,
}
set.Defaults.Apply(user)
user.Perm.Admin = true
err = st.Users.Save(user)
checkErr(err)
}
func setupLogger(s *settings.Settings) {
switch s.Log {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: s.Log,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
}
func startServer(st *storage.Storage) {
settings, err := st.Settings.Get()
checkErr(err)
// serverVisitAndReplace(settings)
setupLogger(settings)
handler, err := fbhttp.NewHandler(st)
checkErr(err)
var listener net.Listener
if settings.Server.TLSKey != "" && settings.Server.TLSCert != "" {
cer, err := tls.LoadX509KeyPair(settings.Server.TLSCert, settings.Server.TLSKey)
checkErr(err)
config := &tls.Config{Certificates: []tls.Certificate{cer}}
listener, err = tls.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port), config)
checkErr(err)
} else {
listener, err = net.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port))
checkErr(err)
}
log.Println("Listening on", listener.Addr().String())
if err := http.Serve(listener, handler); err != nil {
log.Fatal(err)
}
}
func generateRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)
checkErr(err)
// Note that err == nil only if we read len(b) bytes.
return b
}