Read https://github.com/filebrowser/filebrowser/pull/575.

Former-commit-id: 7aedcaaf72b863033e3f089d6df308d41a3fd00c [formerly bdbe4d49161b901c4adf9c245895a1be2d62e4a7] [formerly acfc1ec67c423e0b3e065a8c1f8897c5249af65b [formerly d309066def8319e9da89d00ca6463ec4aea62d34]]
Former-commit-id: 0c7d925a38a68ccabdf2c4bbd8c302ee89b93509 [formerly a6173925a1382955d93b334ded93f70d6dddd694]
Former-commit-id: e032e0804dd051df86f42962de2b39caec5318b7
This commit is contained in:
Henrique Dias
2019-01-05 22:44:33 +00:00
committed by GitHub
parent 53a4601361
commit 12b2c21522
121 changed files with 5410 additions and 4697 deletions

12
cmd/cmd.go Normal file
View File

@@ -0,0 +1,12 @@
package cmd
import (
"log"
)
// Execute executes the commands.
func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}

31
cmd/cmds.go Normal file
View File

@@ -0,0 +1,31 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(cmdsCmd)
}
var cmdsCmd = &cobra.Command{
Use: "cmds",
Short: "Command runner management utility",
Long: `Command runner management utility.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(0)
},
}
func printEvents(m map[string][]string) {
for evt, cmds := range m {
for i, cmd := range cmds {
fmt.Printf("%s(%d): %s\n", evt, i, cmd)
}
}
}

35
cmd/cmds_add.go Normal file
View File

@@ -0,0 +1,35 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
cmdsCmd.AddCommand(cmdsAddCmd)
cmdsAddCmd.Flags().StringP("command", "c", "", "command to add")
cmdsAddCmd.Flags().StringP("event", "e", "", "corresponding event")
cmdsAddCmd.MarkFlagRequired("command")
cmdsAddCmd.MarkFlagRequired("event")
}
var cmdsAddCmd = &cobra.Command{
Use: "add",
Short: "Add a command to run on a specific event",
Long: `Add a command to run on a specific event.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
evt := mustGetString(cmd, "event")
command := mustGetString(cmd, "command")
s.Commands[evt] = append(s.Commands[evt], command)
err = st.Settings.Save(s)
checkErr(err)
printEvents(s.Commands)
},
}

34
cmd/cmds_ls.go Normal file
View File

@@ -0,0 +1,34 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
cmdsCmd.AddCommand(cmdsLsCmd)
cmdsLsCmd.Flags().StringP("event", "e", "", "event name, without 'before' or 'after'")
}
var cmdsLsCmd = &cobra.Command{
Use: "ls",
Short: "List all commands for each event",
Long: `List all commands for each event.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
evt := mustGetString(cmd, "event")
if evt == "" {
printEvents(s.Commands)
} else {
show := map[string][]string{}
show["before_"+evt] = s.Commands["before_"+evt]
show["after_"+evt] = s.Commands["after_"+evt]
printEvents(show)
}
},
}

36
cmd/cmds_rm.go Normal file
View File

@@ -0,0 +1,36 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
cmdsCmd.AddCommand(cmdsRmCmd)
cmdsRmCmd.Flags().StringP("event", "e", "", "corresponding event")
cmdsRmCmd.Flags().UintP("index", "i", 0, "command index")
cmdsRmCmd.MarkFlagRequired("event")
cmdsRmCmd.MarkFlagRequired("index")
}
var cmdsRmCmd = &cobra.Command{
Use: "rm",
Short: "Removes a command from an event hooker",
Long: `Removes a command from an event hooker.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
evt := mustGetString(cmd, "event")
i, err := cmd.Flags().GetUint("index")
checkErr(err)
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][i+1:]...)
err = st.Settings.Save(s)
checkErr(err)
printEvents(s.Commands)
},
}

136
cmd/config.go Normal file
View File

@@ -0,0 +1,136 @@
package cmd
import (
"encoding/json"
nerrors "errors"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(configCmd)
}
var configCmd = &cobra.Command{
Use: "config",
Short: "Configuration management utility",
Long: `Configuration management utility.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(0)
},
}
func addConfigFlags(cmd *cobra.Command) {
addUserFlags(cmd)
cmd.Flags().StringP("baseURL", "b", "/", "base url of this installation")
cmd.Flags().BoolP("signup", "s", false, "allow users to signup")
cmd.Flags().String("shell", "", "shell command to which other commands should be appended")
cmd.Flags().StringP("address", "a", "127.0.0.1", "default address to listen to")
cmd.Flags().StringP("log", "l", "stderr", "log output")
cmd.Flags().IntP("port", "p", 0, "default port to listen to")
cmd.Flags().String("tls.cert", "", "tls certificate path")
cmd.Flags().String("tls.key", "", "tls key path")
cmd.Flags().String("auth.method", string(auth.MethodJSONAuth), "authentication type")
cmd.Flags().String("auth.header", "", "HTTP header for auth.method=proxy")
cmd.Flags().String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
cmd.Flags().String("recaptcha.key", "", "ReCaptcha site key")
cmd.Flags().String("recaptcha.secret", "", "ReCaptcha secret")
cmd.Flags().String("branding.name", "", "replace 'File Browser' by this name")
cmd.Flags().String("branding.files", "", "path to directory with images and custom styles")
cmd.Flags().Bool("branding.disableExternal", false, "disable external links such as GitHub links")
}
func getAuthentication(cmd *cobra.Command) (settings.AuthMethod, auth.Auther) {
method := settings.AuthMethod(mustGetString(cmd, "auth.method"))
var auther auth.Auther
if method == auth.MethodProxyAuth {
header := mustGetString(cmd, "auth.header")
if header == "" {
panic(nerrors.New("you must set the flag 'auth.header' for method 'proxy'"))
}
auther = &auth.ProxyAuth{Header: header}
}
if method == auth.MethodNoAuth {
auther = &auth.NoAuth{}
}
if method == auth.MethodJSONAuth {
jsonAuth := &auth.JSONAuth{}
host := mustGetString(cmd, "recaptcha.host")
key := mustGetString(cmd, "recaptcha.key")
secret := mustGetString(cmd, "recaptcha.secret")
if key != "" && secret != "" {
jsonAuth.ReCaptcha = &auth.ReCaptcha{
Host: host,
Key: key,
Secret: secret,
}
}
auther = jsonAuth
}
if auther == nil {
panic(errors.ErrInvalidAuthMethod)
}
return method, auther
}
func printSettings(s *settings.Settings, auther auth.Auther) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "\nBase URL:\t%s\n", s.BaseURL)
fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup)
fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod)
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " "))
fmt.Fprintf(w, "Log:\t%s\t\n", s.Log)
fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tAddress:\t%s\n", s.Server.Address)
fmt.Fprintf(w, "\tPort:\t%d\n", s.Server.Port)
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", s.Server.TLSCert)
fmt.Fprintf(w, "\tTLS Key:\t%s\n", s.Server.TLSKey)
fmt.Fprintln(w, "\nBranding:")
fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files)
fmt.Fprintf(w, "\tDisable external links:\t%t\n", s.Branding.DisableExternal)
fmt.Fprintln(w, "\nDefaults:")
fmt.Fprintf(w, "\tScope:\t%s\n", s.Defaults.Scope)
fmt.Fprintf(w, "\tLocale:\t%s\n", s.Defaults.Locale)
fmt.Fprintf(w, "\tView mode:\t%s\n", s.Defaults.ViewMode)
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(s.Defaults.Commands, " "))
fmt.Fprintf(w, "\tSorting:\n")
fmt.Fprintf(w, "\t\tBy:\t%s\n", s.Defaults.Sorting.By)
fmt.Fprintf(w, "\t\tAsc:\t%t\n", s.Defaults.Sorting.Asc)
fmt.Fprintf(w, "\tPermissions:\n")
fmt.Fprintf(w, "\t\tAdmin:\t%t\n", s.Defaults.Perm.Admin)
fmt.Fprintf(w, "\t\tExecute:\t%t\n", s.Defaults.Perm.Execute)
fmt.Fprintf(w, "\t\tCreate:\t%t\n", s.Defaults.Perm.Create)
fmt.Fprintf(w, "\t\tRename:\t%t\n", s.Defaults.Perm.Rename)
fmt.Fprintf(w, "\t\tModify:\t%t\n", s.Defaults.Perm.Modify)
fmt.Fprintf(w, "\t\tDelete:\t%t\n", s.Defaults.Perm.Delete)
fmt.Fprintf(w, "\t\tShare:\t%t\n", s.Defaults.Perm.Share)
fmt.Fprintf(w, "\t\tDownload:\t%t\n", s.Defaults.Perm.Download)
w.Flush()
b, err := json.MarshalIndent(auther, "", " ")
checkErr(err)
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
}

26
cmd/config_cat.go Normal file
View File

@@ -0,0 +1,26 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
configCmd.AddCommand(configCatCmd)
}
var configCatCmd = &cobra.Command{
Use: "cat",
Short: "Prints the configuration",
Long: `Prints the configuration.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
auther, err := st.Auth.Get(s.AuthMethod)
checkErr(err)
printSettings(s, auther)
},
}

76
cmd/config_init.go Normal file
View File

@@ -0,0 +1,76 @@
package cmd
import (
"errors"
"fmt"
"os"
"strings"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/spf13/cobra"
)
func init() {
configCmd.AddCommand(configInitCmd)
rootCmd.AddCommand(configInitCmd)
addConfigFlags(configInitCmd)
configInitCmd.MarkFlagRequired("scope")
}
var configInitCmd = &cobra.Command{
Use: "init",
Short: "Initialize a new database",
Long: `Initialize a new database to use with File Browser. All of
this options can be changed in the future with the command
"filebrowser config set". The user related flags apply
to the defaults when creating new users and you don't
override the options.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
if _, err := os.Stat(databasePath); err == nil {
panic(errors.New(databasePath + " already exists"))
}
defaults := settings.UserDefaults{}
getUserDefaults(cmd, &defaults, true)
authMethod, auther := getAuthentication(cmd)
db, err := storm.Open(databasePath)
checkErr(err)
defer db.Close()
st := getStorage(db)
s := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: mustGetString(cmd, "baseURL"),
Log: mustGetString(cmd, "log"),
Signup: mustGetBool(cmd, "signup"),
Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "),
AuthMethod: authMethod,
Defaults: defaults,
Server: settings.Server{
Address: mustGetString(cmd, "address"),
Port: mustGetInt(cmd, "port"),
TLSCert: mustGetString(cmd, "tls.cert"),
TLSKey: mustGetString(cmd, "tls.key"),
},
Branding: settings.Branding{
Name: mustGetString(cmd, "branding.name"),
DisableExternal: mustGetBool(cmd, "branding.disableExternal"),
Files: mustGetString(cmd, "branding.files"),
},
}
err = st.Settings.Save(s)
checkErr(err)
err = st.Auth.Save(auther)
checkErr(err)
fmt.Printf(`
Congratulations! You've set up your database to use with File Browser.
Now add your first user via 'filebrowser users new' and then you just
need to call the main command to boot up the server.
`)
printSettings(s, auther)
},
}

76
cmd/config_set.go Normal file
View File

@@ -0,0 +1,76 @@
package cmd
import (
"strings"
"github.com/filebrowser/filebrowser/v2/auth"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func init() {
configCmd.AddCommand(configSetCmd)
addConfigFlags(configSetCmd)
}
var configSetCmd = &cobra.Command{
Use: "set",
Short: "Updates the configuration",
Long: `Updates the configuration. Set the flags for the options
you want to change.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
hasAuth := false
cmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name {
case "baseURL":
s.BaseURL = mustGetString(cmd, flag.Name)
case "signup":
s.Signup = mustGetBool(cmd, flag.Name)
case "auth.method":
hasAuth = true
case "shell":
s.Shell = strings.Split(strings.TrimSpace(mustGetString(cmd, flag.Name)), " ")
case "branding.name":
s.Branding.Name = mustGetString(cmd, flag.Name)
case "branding.disableExternal":
s.Branding.DisableExternal = mustGetBool(cmd, flag.Name)
case "branding.files":
s.Branding.Files = mustGetString(cmd, flag.Name)
case "log":
s.Log = mustGetString(cmd, flag.Name)
case "address":
s.Server.Address = mustGetString(cmd, flag.Name)
case "port":
s.Server.Port = mustGetInt(cmd, flag.Name)
case "tls.cert":
s.Server.TLSCert = mustGetString(cmd, flag.Name)
case "tls.key":
s.Server.TLSKey = mustGetString(cmd, flag.Name)
}
})
getUserDefaults(cmd, &s.Defaults, false)
var auther auth.Auther
if hasAuth {
s.AuthMethod, auther = getAuthentication(cmd)
err = st.Auth.Save(auther)
checkErr(err)
} else {
auther, err = st.Auth.Get(s.AuthMethod)
checkErr(err)
}
err = st.Settings.Save(s)
checkErr(err)
printSettings(s, auther)
},
}

129
cmd/docs.go Normal file
View File

@@ -0,0 +1,129 @@
package cmd
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(docsCmd)
docsCmd.Flags().StringP("path", "p", "./docs", "path to save the docs")
}
func printToc(names []string) {
for i, name := range names {
name = strings.TrimSuffix(name, filepath.Ext(name))
name = strings.Replace(name, "-", " ", -1)
names[i] = name
}
sort.Strings(names)
toc := ""
for _, name := range names {
toc += "* [" + name + "](cli/" + strings.Replace(name, " ", "-", -1) + ".md)\n"
}
fmt.Println(toc)
}
var docsCmd = &cobra.Command{
Use: "docs",
Hidden: true,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
dir := mustGetString(cmd, "path")
generateDocs(rootCmd, dir)
names := []string{}
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
}
if !strings.HasPrefix(info.Name(), "filebrowser") {
return nil
}
names = append(names, info.Name())
return nil
})
checkErr(err)
printToc(names)
},
}
func generateDocs(cmd *cobra.Command, dir string) {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
generateDocs(c, dir)
}
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
filename := filepath.Join(dir, basename)
f, err := os.Create(filename)
checkErr(err)
defer f.Close()
generateMarkdown(cmd, f)
}
func generateMarkdown(cmd *cobra.Command, w io.Writer) {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()
buf := new(bytes.Buffer)
name := cmd.CommandPath()
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
buf.WriteString("---\ndescription: " + short + "\n---\n\n")
buf.WriteString("# " + name + "\n\n")
buf.WriteString("## Synopsis\n\n")
buf.WriteString(long + "\n\n")
if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
}
if len(cmd.Example) > 0 {
buf.WriteString("## Examples\n\n")
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
}
printOptions(buf, cmd, name)
_, err := buf.WriteTo(w)
checkErr(err)
}
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
if flags.HasAvailableFlags() {
buf.WriteString("## Options\n\n```\n")
flags.PrintDefaults()
buf.WriteString("```\n\n")
}
parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(buf)
if parentFlags.HasAvailableFlags() {
buf.WriteString("### Inherited\n\n```\n")
parentFlags.PrintDefaults()
buf.WriteString("```\n")
}
}

30
cmd/import.go Normal file
View File

@@ -0,0 +1,30 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(importCmd)
importCmd.Flags().String("old.database", "", "")
importCmd.Flags().String("old.config", "", "")
importCmd.MarkFlagRequired("old.database")
}
var importCmd = &cobra.Command{
Use: "import",
Short: "Imports an old configuration",
Long: `Imports an old configuration. This command DOES NOT
import share links because they are incompatible with
this version.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
oldDB := mustGetString(cmd, "old.database")
oldConf := mustGetString(cmd, "old.config")
err := importer.Import(oldDB, oldConf, databasePath)
checkErr(err)
},
}

203
cmd/root.go Normal file
View File

@@ -0,0 +1,203 @@
package cmd
import (
"crypto/rand"
"crypto/tls"
"errors"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strconv"
"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"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
var (
databasePath string
)
func init() {
rootCmd.PersistentFlags().StringVarP(&databasePath, "database", "d", "./filebrowser.db", "path to the database")
rootCmd.Flags().StringP("address", "a", "", "address to listen on (default comes from database)")
rootCmd.Flags().StringP("log", "l", "", "log output (default comes from database)")
rootCmd.Flags().IntP("port", "p", 0, "port to listen on (default comes from database)")
rootCmd.Flags().StringP("cert", "c", "", "tls certificate (default comes from database)")
rootCmd.Flags().StringP("key", "k", "", "tls key (default comes from database)")
rootCmd.Flags().StringP("scope", "s", "", "scope for users")
}
var rootCmd = &cobra.Command{
Use: "filebrowser",
Short: "A stylish web-based file browser",
Long: `File Browser CLI lets you create the database to use with File Browser,
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) {
if _, err := os.Stat(databasePath); os.IsNotExist(err) {
quickSetup(cmd)
}
db := getDB()
defer db.Close()
st := getStorage(db)
startServer(cmd, st)
},
}
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 serverVisitAndReplace(cmd *cobra.Command, s *settings.Settings) {
cmd.Flags().Visit(func(flag *pflag.Flag) {
switch flag.Name {
case "log":
s.Log = mustGetString(cmd, flag.Name)
case "address":
s.Server.Address = mustGetString(cmd, flag.Name)
case "port":
s.Server.Port = mustGetInt(cmd, flag.Name)
case "cert":
s.Server.TLSCert = mustGetString(cmd, flag.Name)
case "key":
s.Server.TLSKey = mustGetString(cmd, flag.Name)
}
})
}
func quickSetup(cmd *cobra.Command) {
scope := mustGetString(cmd, "scope")
if scope == "" {
panic(errors.New("scope flag must be set for quick setup"))
}
db, err := storm.Open(databasePath)
checkErr(err)
defer db.Close()
set := &settings.Settings{
Key: generateRandomBytes(64), // 256 bit
BaseURL: "",
Log: "stderr",
Signup: false,
AuthMethod: auth.MethodJSONAuth,
Server: settings.Server{
Port: 0,
Address: "127.0.0.1",
TLSCert: mustGetString(cmd, "cert"),
TLSKey: mustGetString(cmd, "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(cmd, set)
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)
}
func startServer(cmd *cobra.Command, st *storage.Storage) {
settings, err := st.Settings.Get()
checkErr(err)
serverVisitAndReplace(cmd, 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
}

38
cmd/rule_rm.go Normal file
View File

@@ -0,0 +1,38 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
rulesCmd.AddCommand(rulesRmCommand)
rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove")
rulesRmCommand.MarkFlagRequired("index")
}
var rulesRmCommand = &cobra.Command{
Use: "rm",
Short: "Remove a global rule or user rule",
Long: `Remove a global rule or user rule.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
index := mustGetUint(cmd, "index")
user := func(u *users.User, st *storage.Storage) {
u.Rules = append(u.Rules[:index], u.Rules[index+1:]...)
err := st.Users.Save(u)
checkErr(err)
}
global := func(s *settings.Settings, st *storage.Storage) {
s.Rules = append(s.Rules[:index], s.Rules[index+1:]...)
err := st.Settings.Save(s)
checkErr(err)
}
runRules(cmd, user, global)
},
}

91
cmd/rules.go Normal file
View File

@@ -0,0 +1,91 @@
package cmd
import (
"fmt"
"os"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(rulesCmd)
rulesCmd.PersistentFlags().StringP("username", "u", "", "username of user to which the rules apply")
rulesCmd.PersistentFlags().UintP("id", "i", 0, "id of user to which the rules apply")
}
var rulesCmd = &cobra.Command{
Use: "rules",
Short: "Rules management utility",
Long: `On each subcommand you'll have available at least two flags:
"username" and "id". You must either set only one of them
or none. If you set one of them, the command will apply to
an user, otherwise it will be applied to the global set or
rules.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(0)
},
}
func runRules(cmd *cobra.Command, users func(*users.User, *storage.Storage), global func(*settings.Settings, *storage.Storage)) {
db := getDB()
defer db.Close()
st := getStorage(db)
id := getUserIdentifier(cmd)
if id != nil {
user, err := st.Users.Get(id)
checkErr(err)
if users != nil {
users(user, st)
}
printRules(user.Rules, id)
return
}
settings, err := st.Settings.Get()
checkErr(err)
if global != nil {
global(settings, st)
}
printRules(settings.Rules, id)
}
func getUserIdentifier(cmd *cobra.Command) interface{} {
id := mustGetUint(cmd, "id")
username := mustGetString(cmd, "username")
if id != 0 {
return id
} else if username != "" {
return username
}
return nil
}
func printRules(rules []rules.Rule, id interface{}) {
if id == nil {
fmt.Printf("Global Rules:\n\n")
} else {
fmt.Printf("Rules for user %v:\n\n", id)
}
for id, rule := range rules {
fmt.Printf("(%d) ", id)
if rule.Regex {
fmt.Printf("Allow: %t\tRegex: %s\n", rule.Allow, rule.Regexp.Raw)
} else {
fmt.Printf("Allow: %t\tPath: %s\n", rule.Allow, rule.Path)
}
}
}

67
cmd/rules_add.go Normal file
View File

@@ -0,0 +1,67 @@
package cmd
import (
"errors"
"regexp"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
rulesCmd.AddCommand(rulesAddCmd)
rulesAddCmd.Flags().BoolP("allow", "a", false, "allow rule instead of disallow")
rulesAddCmd.Flags().StringP("path", "p", "", "path to which the rule applies")
rulesAddCmd.Flags().StringP("regex", "r", "", "regex to which the rule applies")
}
var rulesAddCmd = &cobra.Command{
Use: "add",
Short: "Add a global rule or user rule",
Long: `Add a global rule or user rule. You must
set either path or regex.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
allow := mustGetBool(cmd, "allow")
path := mustGetString(cmd, "path")
regex := mustGetString(cmd, "regex")
if path == "" && regex == "" {
panic(errors.New("you must set either --path or --regex flags"))
}
if path != "" && regex != "" {
panic(errors.New("you can't set --path and --regex flags at the same time"))
}
if regex != "" {
regexp.MustCompile(regex)
}
rule := rules.Rule{
Allow: allow,
Path: path,
Regex: regex != "",
Regexp: &rules.Regexp{
Raw: regex,
},
}
user := func(u *users.User, st *storage.Storage) {
u.Rules = append(u.Rules, rule)
err := st.Users.Save(u)
checkErr(err)
}
global := func(s *settings.Settings, st *storage.Storage) {
s.Rules = append(s.Rules, rule)
err := st.Settings.Save(s)
checkErr(err)
}
runRules(cmd, user, global)
},
}

19
cmd/rules_ls.go Normal file
View File

@@ -0,0 +1,19 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
rulesCmd.AddCommand(rulesLsCommand)
}
var rulesLsCommand = &cobra.Command{
Use: "ls",
Short: "List global rules or user specific rules",
Long: `List global rules or user specific rules.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
runRules(cmd, nil, nil)
},
}

134
cmd/users.go Normal file
View File

@@ -0,0 +1,134 @@
package cmd
import (
"errors"
"fmt"
"os"
"text/tabwriter"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func init() {
rootCmd.AddCommand(usersCmd)
}
var usersCmd = &cobra.Command{
Use: "users",
Short: "Users management utility",
Long: `Users management utility.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(0)
},
}
func printUsers(users []*users.User) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, user := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
user.ID,
user.Username,
user.Scope,
user.Locale,
user.ViewMode,
user.Perm.Admin,
user.Perm.Execute,
user.Perm.Create,
user.Perm.Rename,
user.Perm.Modify,
user.Perm.Delete,
user.Perm.Share,
user.Perm.Download,
user.LockPassword,
)
}
w.Flush()
}
func usernameOrIDRequired(cmd *cobra.Command, args []string) error {
username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id")
if username == "" && id == 0 {
return errors.New("'username' of 'id' flag required")
}
return nil
}
func addUserFlags(cmd *cobra.Command) {
cmd.Flags().Bool("perm.admin", false, "admin perm for users")
cmd.Flags().Bool("perm.execute", true, "execute perm for users")
cmd.Flags().Bool("perm.create", true, "create perm for users")
cmd.Flags().Bool("perm.rename", true, "rename perm for users")
cmd.Flags().Bool("perm.modify", true, "modify perm for users")
cmd.Flags().Bool("perm.delete", true, "delete perm for users")
cmd.Flags().Bool("perm.share", true, "share perm for users")
cmd.Flags().Bool("perm.download", true, "download perm for users")
cmd.Flags().String("sorting.by", "name", "sorting mode (name, size or modified)")
cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order")
cmd.Flags().Bool("lockPassword", false, "lock password")
cmd.Flags().StringSlice("commands", nil, "a list of the commands a user can execute")
cmd.Flags().String("scope", "", "scope for users")
cmd.Flags().String("locale", "en", "locale for users")
cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users")
}
func getViewMode(cmd *cobra.Command) users.ViewMode {
viewMode := users.ViewMode(mustGetString(cmd, "viewMode"))
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\""))
}
return viewMode
}
func getUserDefaults(cmd *cobra.Command, defaults *settings.UserDefaults, all bool) {
visit := func(flag *pflag.Flag) {
switch flag.Name {
case "scope":
defaults.Scope = mustGetString(cmd, "scope")
case "locale":
defaults.Locale = mustGetString(cmd, "locale")
case "viewMode":
defaults.ViewMode = getViewMode(cmd)
case "perm.admin":
defaults.Perm.Admin = mustGetBool(cmd, "perm.admin")
case "perm.execute":
defaults.Perm.Execute = mustGetBool(cmd, "perm.execute")
case "perm.create":
defaults.Perm.Create = mustGetBool(cmd, "perm.create")
case "perm.rename":
defaults.Perm.Rename = mustGetBool(cmd, "perm.rename")
case "perm.modify":
defaults.Perm.Modify = mustGetBool(cmd, "perm.modify")
case "perm.delete":
defaults.Perm.Delete = mustGetBool(cmd, "perm.delete")
case "perm.share":
defaults.Perm.Share = mustGetBool(cmd, "perm.share")
case "perm.download":
defaults.Perm.Download = mustGetBool(cmd, "perm.download")
case "commands":
commands, err := cmd.Flags().GetStringSlice("commands")
checkErr(err)
defaults.Commands = commands
case "sorting.by":
defaults.Sorting.By = mustGetString(cmd, "sorting.by")
case "sorting.asc":
defaults.Sorting.Asc = mustGetBool(cmd, "sorting.asc")
}
}
if all {
cmd.Flags().VisitAll(visit)
} else {
cmd.Flags().Visit(visit)
}
}

57
cmd/users_find.go Normal file
View File

@@ -0,0 +1,57 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersFindCmd)
usersCmd.AddCommand(usersLsCmd)
usersFindCmd.Flags().StringP("username", "u", "", "username to find")
usersFindCmd.Flags().UintP("id", "i", 0, "id to find")
}
var usersFindCmd = &cobra.Command{
Use: "find",
Short: "Find a user by username or id",
Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
Args: cobra.NoArgs,
Run: findUsers,
}
var usersLsCmd = &cobra.Command{
Use: "ls",
Short: "List all users.",
Args: cobra.NoArgs,
Run: findUsers,
}
var findUsers = func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id")
var err error
var list []*users.User
var user *users.User
if username != "" {
user, err = st.Users.Get(username)
} else if id != 0 {
user, err = st.Users.Get(id)
} else {
list, err = st.Users.Gets()
}
checkErr(err)
if user != nil {
list = []*users.User{user}
}
printUsers(list)
}

47
cmd/users_new.go Normal file
View File

@@ -0,0 +1,47 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersNewCmd)
addUserFlags(usersNewCmd)
usersNewCmd.Flags().StringP("username", "u", "", "new users's username")
usersNewCmd.Flags().StringP("password", "p", "", "new user's password")
usersNewCmd.MarkFlagRequired("username")
usersNewCmd.MarkFlagRequired("password")
}
var usersNewCmd = &cobra.Command{
Use: "new",
Short: "Create a new user",
Long: `Create a new user and add it to the database.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
s, err := st.Settings.Get()
checkErr(err)
getUserDefaults(cmd, &s.Defaults, false)
password, _ := cmd.Flags().GetString("password")
password, err = users.HashPwd(password)
checkErr(err)
user := &users.User{
Username: mustGetString(cmd, "username"),
Password: password,
LockPassword: mustGetBool(cmd, "lockPassword"),
}
s.Defaults.Apply(user)
err = st.Users.Save(user)
checkErr(err)
printUsers([]*users.User{user})
},
}

39
cmd/users_rm.go Normal file
View File

@@ -0,0 +1,39 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersRmCmd)
usersRmCmd.Flags().StringP("username", "u", "", "username to delete")
usersRmCmd.Flags().UintP("id", "i", 0, "id to delete")
}
var usersRmCmd = &cobra.Command{
Use: "rm",
Short: "Delete a user by username or id",
Long: `Delete a user by username or id`,
Args: usernameOrIDRequired,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
username, _ := cmd.Flags().GetString("username")
id, _ := cmd.Flags().GetUint("id")
var err error
if username != "" {
err = st.Users.Delete(username)
} else {
err = st.Users.Delete(id)
}
checkErr(err)
fmt.Println("user deleted successfully")
},
}

74
cmd/users_update.go Normal file
View File

@@ -0,0 +1,74 @@
package cmd
import (
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
"github.com/spf13/cobra"
)
func init() {
usersCmd.AddCommand(usersUpdateCmd)
usersUpdateCmd.Flags().UintP("id", "i", 0, "id of the user")
usersUpdateCmd.Flags().StringP("username", "u", "", "user to change or new username if flag 'id' is set")
usersUpdateCmd.Flags().StringP("password", "p", "", "new password")
addUserFlags(usersUpdateCmd)
}
var usersUpdateCmd = &cobra.Command{
Use: "update",
Short: "Updates an existing user",
Long: `Updates an existing user. Set the flags for the
options you want to change.`,
Args: usernameOrIDRequired,
Run: func(cmd *cobra.Command, args []string) {
db := getDB()
defer db.Close()
st := getStorage(db)
id, _ := cmd.Flags().GetUint("id")
username := mustGetString(cmd, "username")
password := mustGetString(cmd, "password")
var user *users.User
var err error
if id != 0 {
user, err = st.Users.Get(id)
} else {
user, err = st.Users.Get(username)
}
checkErr(err)
defaults := settings.UserDefaults{
Scope: user.Scope,
Locale: user.Locale,
ViewMode: user.ViewMode,
Perm: user.Perm,
Sorting: user.Sorting,
Commands: user.Commands,
}
getUserDefaults(cmd, &defaults, false)
user.Scope = defaults.Scope
user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode
user.Perm = defaults.Perm
user.Commands = defaults.Commands
user.Sorting = defaults.Sorting
user.LockPassword = mustGetBool(cmd, "lockPassword")
if user.Username != username && username != "" {
user.Username = username
}
if password != "" {
user.Password, err = users.HashPwd(password)
checkErr(err)
}
err = st.Users.Update(user)
checkErr(err)
printUsers([]*users.User{user})
},
}

55
cmd/utils.go Normal file
View File

@@ -0,0 +1,55 @@
package cmd
import (
"errors"
"os"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
"github.com/spf13/cobra"
)
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func mustGetString(cmd *cobra.Command, flag string) string {
s, err := cmd.Flags().GetString(flag)
checkErr(err)
return s
}
func mustGetBool(cmd *cobra.Command, flag string) bool {
b, err := cmd.Flags().GetBool(flag)
checkErr(err)
return b
}
func mustGetInt(cmd *cobra.Command, flag string) int {
b, err := cmd.Flags().GetInt(flag)
checkErr(err)
return b
}
func mustGetUint(cmd *cobra.Command, flag string) uint {
b, err := cmd.Flags().GetUint(flag)
checkErr(err)
return b
}
func getDB() *storm.DB {
if _, err := os.Stat(databasePath); err != nil {
panic(errors.New(databasePath + " does not exist. Please run 'filebrowser init' first."))
}
db, err := storm.Open(databasePath)
checkErr(err)
return db
}
func getStorage(db *storm.DB) *storage.Storage {
return bolt.NewStorage(db)
}

20
cmd/version.go Normal file
View File

@@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"github.com/filebrowser/filebrowser/v2/version"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("File Browser Version " + version.Version)
},
}