2019-01-05 22:44:33 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
2019-05-17 10:48:06 +00:00
|
|
|
"errors"
|
2019-01-05 22:44:33 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2019-05-17 10:48:06 +00:00
|
|
|
"os/signal"
|
2019-01-06 13:01:42 +00:00
|
|
|
"path/filepath"
|
2019-01-06 05:11:15 +00:00
|
|
|
"strings"
|
2019-05-17 10:48:06 +00:00
|
|
|
"syscall"
|
2019-01-05 22:44:33 +00:00
|
|
|
|
|
|
|
"github.com/filebrowser/filebrowser/v2/auth"
|
2019-01-06 13:20:17 +00:00
|
|
|
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
2019-01-05 22:44:33 +00:00
|
|
|
"github.com/filebrowser/filebrowser/v2/settings"
|
2019-01-08 10:29:09 +00:00
|
|
|
"github.com/filebrowser/filebrowser/v2/storage"
|
2019-01-05 22:44:33 +00:00
|
|
|
"github.com/filebrowser/filebrowser/v2/users"
|
2019-01-06 21:34:56 +00:00
|
|
|
homedir "github.com/mitchellh/go-homedir"
|
2019-01-05 22:44:33 +00:00
|
|
|
"github.com/spf13/cobra"
|
2019-01-08 14:07:55 +00:00
|
|
|
"github.com/spf13/pflag"
|
2019-01-06 05:11:15 +00:00
|
|
|
v "github.com/spf13/viper"
|
2019-01-05 22:44:33 +00:00
|
|
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
|
|
|
)
|
|
|
|
|
2019-01-06 05:11:15 +00:00
|
|
|
var (
|
|
|
|
cfgFile string
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2019-01-06 13:21:31 +00:00
|
|
|
cobra.OnInitialize(initConfig)
|
2019-01-06 21:34:56 +00:00
|
|
|
|
|
|
|
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
|
|
|
|
2019-01-08 14:07:55 +00:00
|
|
|
flags := rootCmd.Flags()
|
|
|
|
persistent := rootCmd.PersistentFlags()
|
2019-01-06 13:21:31 +00:00
|
|
|
|
2019-01-08 14:07:55 +00:00
|
|
|
persistent.StringVarP(&cfgFile, "config", "c", "", "config file path")
|
|
|
|
persistent.StringP("database", "d", "./filebrowser.db", "database path")
|
2019-01-08 18:43:04 +00:00
|
|
|
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
2019-01-08 14:07:55 +00:00
|
|
|
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\")")
|
2019-01-06 05:11:15 +00:00
|
|
|
|
2019-01-08 14:07:55 +00:00
|
|
|
addServerFlags(flags)
|
|
|
|
}
|
|
|
|
|
|
|
|
func addServerFlags(flags *pflag.FlagSet) {
|
|
|
|
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")
|
2019-05-17 10:48:06 +00:00
|
|
|
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
2019-01-08 14:07:55 +00:00
|
|
|
flags.StringP("baseurl", "b", "", "base url")
|
|
|
|
}
|
2019-01-06 05:11:15 +00:00
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
var rootCmd = &cobra.Command{
|
2019-02-15 11:54:44 +00:00
|
|
|
Use: "filebrowser",
|
|
|
|
Short: "A stylish web-based file browser",
|
2019-01-05 22:44:33 +00:00
|
|
|
Long: `File Browser CLI lets you create the database to use with File Browser,
|
2019-01-06 13:21:31 +00:00
|
|
|
manage your users and all the configurations without acessing the
|
2019-01-05 22:44:33 +00:00
|
|
|
web interface.
|
2019-01-06 21:34:56 +00:00
|
|
|
|
2019-01-06 13:21:31 +00:00
|
|
|
If you've never run File Browser, you'll need to have a database for
|
|
|
|
it. Don't worry: you don't need to setup a separate database server.
|
|
|
|
We're using Bolt DB which is a single file database and all managed
|
|
|
|
by ourselves.
|
|
|
|
|
|
|
|
For this specific command, all the flags you have available (except
|
|
|
|
"config" for the configuration file), can be given either through
|
|
|
|
environment variables or configuration files.
|
|
|
|
|
|
|
|
If you don't set "config", it will look for a configuration file called
|
|
|
|
.filebrowser.{json, toml, yaml, yml} in the following directories:
|
|
|
|
|
|
|
|
- ./
|
|
|
|
- $HOME/
|
|
|
|
- /etc/filebrowser/
|
|
|
|
|
|
|
|
The precedence of the configuration values are as follows:
|
|
|
|
|
2019-01-08 17:00:36 +00:00
|
|
|
- flags
|
|
|
|
- environment variables
|
2019-01-06 13:21:31 +00:00
|
|
|
- configuration file
|
2019-01-08 16:09:25 +00:00
|
|
|
- database values
|
2019-01-06 13:21:31 +00:00
|
|
|
- defaults
|
2019-01-06 05:11:15 +00:00
|
|
|
|
2019-01-06 13:21:31 +00:00
|
|
|
The environment variables are prefixed by "FB_" followed by the option
|
|
|
|
name in caps. So to set "database" via an env variable, you should
|
2019-01-08 17:00:36 +00:00
|
|
|
set FB_DATABASE.
|
2019-01-06 13:21:31 +00:00
|
|
|
|
|
|
|
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".`,
|
2019-01-07 20:24:23 +00:00
|
|
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
2019-01-08 18:40:14 +00:00
|
|
|
log.Println(cfgFile)
|
|
|
|
|
2019-01-07 20:24:23 +00:00
|
|
|
if !d.hadDB {
|
2019-01-08 14:07:55 +00:00
|
|
|
quickSetup(cmd.Flags(), d)
|
2019-01-07 20:24:23 +00:00
|
|
|
}
|
2019-01-06 05:11:15 +00:00
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
server := getRunParams(cmd.Flags(), d.store)
|
2019-01-08 10:29:09 +00:00
|
|
|
setupLog(server.Log)
|
2019-01-07 20:24:23 +00:00
|
|
|
|
2019-01-08 14:43:46 +00:00
|
|
|
root, err := filepath.Abs(server.Root)
|
|
|
|
checkErr(err)
|
|
|
|
server.Root = root
|
|
|
|
|
2019-01-08 18:40:14 +00:00
|
|
|
adr := server.Address + ":" + server.Port
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2019-01-07 20:24:23 +00:00
|
|
|
var listener net.Listener
|
|
|
|
|
2019-05-17 10:48:06 +00:00
|
|
|
if server.Socket != "" {
|
|
|
|
listener, err = net.Listen("unix", server.Socket)
|
|
|
|
checkErr(err)
|
|
|
|
} else if server.TLSKey != "" && server.TLSCert != "" {
|
2019-01-08 10:29:09 +00:00
|
|
|
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey)
|
2019-01-07 20:24:23 +00:00
|
|
|
checkErr(err)
|
2019-01-08 18:40:14 +00:00
|
|
|
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}})
|
2019-01-07 20:24:23 +00:00
|
|
|
checkErr(err)
|
|
|
|
} else {
|
2019-01-08 18:40:14 +00:00
|
|
|
listener, err = net.Listen("tcp", adr)
|
2019-01-07 20:24:23 +00:00
|
|
|
checkErr(err)
|
|
|
|
}
|
|
|
|
|
2019-05-17 10:48:06 +00:00
|
|
|
sigc := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go cleanupHandler(listener, sigc)
|
|
|
|
|
2019-01-08 18:40:14 +00:00
|
|
|
handler, err := fbhttp.NewHandler(d.store, server)
|
|
|
|
checkErr(err)
|
|
|
|
|
2019-05-17 10:48:06 +00:00
|
|
|
defer listener.Close()
|
|
|
|
|
2019-01-07 20:24:23 +00:00
|
|
|
log.Println("Listening on", listener.Addr().String())
|
|
|
|
if err := http.Serve(listener, handler); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2019-01-07 20:34:44 +00:00
|
|
|
}, pythonConfig{allowNoDB: true}),
|
2019-01-07 20:24:23 +00:00
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2019-05-17 10:48:06 +00:00
|
|
|
func cleanupHandler(listener net.Listener, c chan os.Signal) {
|
|
|
|
sig := <-c
|
|
|
|
log.Printf("Caught signal %s: shutting down.", sig)
|
|
|
|
listener.Close()
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
2019-01-08 14:07:55 +00:00
|
|
|
server, err := st.Settings.GetServer()
|
2019-01-08 10:29:09 +00:00
|
|
|
checkErr(err)
|
2019-01-08 14:07:55 +00:00
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "root"); set {
|
2019-01-08 14:43:46 +00:00
|
|
|
server.Root = val
|
2019-01-08 14:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "baseurl"); set {
|
2019-01-08 14:07:55 +00:00
|
|
|
server.BaseURL = val
|
|
|
|
}
|
|
|
|
|
2019-05-17 10:48:06 +00:00
|
|
|
if val, set := getParamB(flags, "log"); set {
|
|
|
|
server.Log = val
|
|
|
|
}
|
|
|
|
|
|
|
|
isSocketSet := false
|
|
|
|
isAddrSet := false
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "address"); set {
|
2019-01-08 14:07:55 +00:00
|
|
|
server.Address = val
|
2019-05-17 10:48:06 +00:00
|
|
|
isAddrSet = isAddrSet || set
|
2019-01-08 14:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "port"); set {
|
2019-01-08 14:07:55 +00:00
|
|
|
server.Port = val
|
2019-05-17 10:48:06 +00:00
|
|
|
isAddrSet = isAddrSet || set
|
2019-01-08 14:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "key"); set {
|
2019-01-08 14:07:55 +00:00
|
|
|
server.TLSKey = val
|
2019-05-17 10:48:06 +00:00
|
|
|
isAddrSet = isAddrSet || set
|
2019-01-08 14:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
if val, set := getParamB(flags, "cert"); set {
|
2019-01-08 14:07:55 +00:00
|
|
|
server.TLSCert = val
|
2019-05-17 10:48:06 +00:00
|
|
|
isAddrSet = isAddrSet || set
|
|
|
|
}
|
|
|
|
|
|
|
|
if val, set := getParamB(flags, "socket"); set {
|
|
|
|
server.Socket = val
|
|
|
|
isSocketSet = isSocketSet || set
|
|
|
|
}
|
|
|
|
|
|
|
|
if isAddrSet && isSocketSet {
|
2019-05-20 21:05:22 +00:00
|
|
|
checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert"))
|
2019-05-17 10:48:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do not use saved Socket if address was manually set.
|
|
|
|
if isAddrSet && server.Socket != "" {
|
|
|
|
server.Socket = ""
|
2019-01-08 14:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 10:29:09 +00:00
|
|
|
return server
|
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
// getParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
|
|
|
//
|
|
|
|
// 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 getParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
|
|
|
value, _ := flags.GetString(key)
|
|
|
|
|
|
|
|
// If set on Flags, use it.
|
|
|
|
if flags.Changed(key) {
|
|
|
|
return value, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If set through viper (env, config), return it.
|
|
|
|
if v.IsSet(key) {
|
|
|
|
return v.GetString(key), true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise use default value on flags.
|
|
|
|
return value, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func getParam(flags *pflag.FlagSet, key string) string {
|
|
|
|
val, _ := getParamB(flags, key)
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
2019-01-08 10:29:09 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 14:07:55 +00:00
|
|
|
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
2019-01-05 22:44:33 +00:00
|
|
|
set := &settings.Settings{
|
2019-04-20 13:15:28 +00:00
|
|
|
Key: generateKey(),
|
|
|
|
Signup: false,
|
2019-02-15 15:12:02 +00:00
|
|
|
CreateUserDir: false,
|
2019-01-05 22:44:33 +00:00
|
|
|
Defaults: settings.UserDefaults{
|
2019-01-06 13:01:42 +00:00
|
|
|
Scope: ".",
|
2019-01-05 22:44:33 +00:00
|
|
|
Locale: "en",
|
|
|
|
Perm: users.Permissions{
|
|
|
|
Admin: false,
|
|
|
|
Execute: true,
|
|
|
|
Create: true,
|
|
|
|
Rename: true,
|
|
|
|
Modify: true,
|
|
|
|
Delete: true,
|
|
|
|
Share: true,
|
|
|
|
Download: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
var err error
|
|
|
|
if _, noauth := getParamB(flags, "noauth"); noauth {
|
2019-01-08 18:43:04 +00:00
|
|
|
set.AuthMethod = auth.MethodNoAuth
|
|
|
|
err = d.store.Auth.Save(&auth.NoAuth{})
|
|
|
|
} else {
|
|
|
|
set.AuthMethod = auth.MethodJSONAuth
|
|
|
|
err = d.store.Auth.Save(&auth.JSONAuth{})
|
|
|
|
}
|
2019-01-08 20:10:27 +00:00
|
|
|
|
2019-01-08 18:43:04 +00:00
|
|
|
checkErr(err)
|
|
|
|
err = d.store.Settings.Save(set)
|
|
|
|
checkErr(err)
|
|
|
|
|
2019-01-08 10:29:09 +00:00
|
|
|
ser := &settings.Server{
|
2019-01-08 20:10:27 +00:00
|
|
|
BaseURL: getParam(flags, "baseurl"),
|
|
|
|
Port: getParam(flags, "port"),
|
|
|
|
Log: getParam(flags, "log"),
|
|
|
|
TLSKey: getParam(flags, "key"),
|
|
|
|
TLSCert: getParam(flags, "cert"),
|
|
|
|
Address: getParam(flags, "address"),
|
|
|
|
Root: getParam(flags, "root"),
|
2019-01-08 10:29:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = d.store.Settings.SaveServer(ser)
|
|
|
|
checkErr(err)
|
|
|
|
|
2019-01-08 20:10:27 +00:00
|
|
|
username := getParam(flags, "username")
|
|
|
|
password := getParam(flags, "password")
|
2019-01-06 17:55:47 +00:00
|
|
|
|
|
|
|
if password == "" {
|
|
|
|
password, err = users.HashPwd("admin")
|
|
|
|
checkErr(err)
|
|
|
|
}
|
|
|
|
|
2019-01-06 13:21:31 +00:00
|
|
|
if username == "" || password == "" {
|
2019-01-08 18:40:14 +00:00
|
|
|
log.Fatal("username and password cannot be empty during quick setup")
|
2019-01-06 13:21:31 +00:00
|
|
|
}
|
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
user := &users.User{
|
2019-01-06 13:21:31 +00:00
|
|
|
Username: username,
|
2019-01-05 22:44:33 +00:00
|
|
|
Password: password,
|
|
|
|
LockPassword: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
set.Defaults.Apply(user)
|
|
|
|
user.Perm.Admin = true
|
|
|
|
|
2019-01-07 20:24:23 +00:00
|
|
|
err = d.store.Users.Save(user)
|
2019-01-05 22:44:33 +00:00
|
|
|
checkErr(err)
|
|
|
|
}
|
|
|
|
|
2019-01-06 13:20:17 +00:00
|
|
|
func initConfig() {
|
|
|
|
if cfgFile == "" {
|
|
|
|
home, err := homedir.Dir()
|
2019-01-05 22:44:33 +00:00
|
|
|
checkErr(err)
|
2019-01-06 13:20:17 +00:00
|
|
|
v.AddConfigPath(".")
|
|
|
|
v.AddConfigPath(home)
|
|
|
|
v.AddConfigPath("/etc/filebrowser/")
|
|
|
|
v.SetConfigName(".filebrowser")
|
2019-01-05 22:44:33 +00:00
|
|
|
} else {
|
2019-01-06 13:20:17 +00:00
|
|
|
v.SetConfigFile(cfgFile)
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
2019-01-06 13:20:17 +00:00
|
|
|
v.SetEnvPrefix("FB")
|
|
|
|
v.AutomaticEnv()
|
|
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2019-01-06 13:20:17 +00:00
|
|
|
if err := v.ReadInConfig(); err != nil {
|
|
|
|
if _, ok := err.(v.ConfigParseError); ok {
|
|
|
|
panic(err)
|
|
|
|
}
|
2019-01-08 18:40:14 +00:00
|
|
|
cfgFile = "No config file used"
|
|
|
|
} else {
|
|
|
|
cfgFile = "Using config file: " + v.ConfigFileUsed()
|
2019-01-06 13:20:17 +00:00
|
|
|
}
|
2019-01-08 18:40:14 +00:00
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|