feat: consistent flags and environment variables (#5549)
- In the root command, all flags are now correctly available as environmental variables, except for `--config` flag. This was already supposed to be the case, but due to bugs in the implementation it didn't work properly. - All configuration options (unless I missed something) that are available as flags should now properly update the configuration when using the `config init` and `config set` commands. - Flag names are now consistently in the lowerCamelCase format. All flags that were in a different format have been updated in a backwards compatible way. For a transitionary period of at least 6 months, both will work: - `--dir-mode` --> `--dirMode` - `--hide-login-button` --> `--hideLoginButton` - `--create-user-dir` --> `--createUserDir` - `--minimum-password-length` --> `--minimumPasswordLength` - `--socket-perm` --> `--socketPerm` - `--disable-thumbnails` --> `--disableThumbnails` - `--disable-preview-resize` --> `--disablePreviewResize` - `--disable-exec` --> `--disableExec` - `--disable-type-detection-by-header` --> `--disableTypeDetectionByHeader` - `--img-processors` --> `--imageProcessors` - `--cache-dir` --> `--cacheDir` - `--token-expiration-time` --> `--tokenExpirationTime` - `--baseurl` --> `--baseURL`pull/5546/head
parent
f89435c068
commit
0a0cb8046f
|
|
@ -0,0 +1,35 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TestEnvCollisions ensures that there are no collisions in the produced environment
|
||||
// variable names for all commands and their flags.
|
||||
func TestEnvCollisions(t *testing.T) {
|
||||
testEnvCollisions(t, rootCmd)
|
||||
}
|
||||
|
||||
func testEnvCollisions(t *testing.T, cmd *cobra.Command) {
|
||||
for _, cmd := range cmd.Commands() {
|
||||
testEnvCollisions(t, cmd)
|
||||
}
|
||||
|
||||
replacements := generateEnvKeyReplacements(cmd)
|
||||
envVariables := []string{}
|
||||
|
||||
for i := range replacements {
|
||||
if i%2 != 0 {
|
||||
envVariables = append(envVariables, replacements[i])
|
||||
}
|
||||
}
|
||||
|
||||
duplicates := lo.FindDuplicates(envVariables)
|
||||
|
||||
if len(duplicates) > 0 {
|
||||
t.Errorf("Found duplicate environment variable keys for command %q: %v", cmd.Name(), duplicates)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@ var cmdsLsCmd = &cobra.Command{
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evt, err := getString(cmd.Flags(), "event")
|
||||
|
||||
evt, err := cmd.Flags().GetString("event")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -32,6 +33,7 @@ var cmdsLsCmd = &cobra.Command{
|
|||
show["after_"+evt] = s.Commands["after_"+evt]
|
||||
printEvents(show)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,11 @@ var configCmd = &cobra.Command{
|
|||
func addConfigFlags(flags *pflag.FlagSet) {
|
||||
addServerFlags(flags)
|
||||
addUserFlags(flags)
|
||||
|
||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||
flags.Bool("hide-login-button", false, "hide login button from public pages")
|
||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||
flags.Bool("hideLoginButton", false, "hide login button from public pages")
|
||||
flags.Bool("createUserDir", false, "generate user's home directory automatically")
|
||||
flags.Uint("minimumPasswordLength", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||
|
||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
|
|
@ -50,17 +51,18 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
||||
|
||||
// NB: these are string so they can be presented as octal in the help text
|
||||
// as that's the conventional representation for modes in Unix.
|
||||
flags.String("file-mode", fmt.Sprintf("%O", settings.DefaultFileMode), "mode bits that new files are created with")
|
||||
flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "mode bits that new directories are created with")
|
||||
flags.String("fileMode", fmt.Sprintf("%O", settings.DefaultFileMode), "mode bits that new files are created with")
|
||||
flags.String("dirMode", fmt.Sprintf("%O", settings.DefaultDirMode), "mode bits that new directories are created with")
|
||||
|
||||
flags.Uint64("tus.chunkSize", settings.DefaultTusChunkSize, "the tus chunk size")
|
||||
flags.Uint16("tus.retryCount", settings.DefaultTusRetryCount, "the tus retry count")
|
||||
}
|
||||
|
||||
func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
|
||||
methodStr, err := getString(flags, "auth.method")
|
||||
methodStr, err := flags.GetString("auth.method")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
|
@ -91,7 +93,7 @@ func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.Auth
|
|||
}
|
||||
|
||||
func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
header, err := getString(flags, "auth.header")
|
||||
header, err := flags.GetString("auth.header")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -113,15 +115,17 @@ func getNoAuth() auth.Auther {
|
|||
|
||||
func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
jsonAuth := &auth.JSONAuth{}
|
||||
host, err := getString(flags, "recaptcha.host")
|
||||
host, err := flags.GetString("recaptcha.host")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := getString(flags, "recaptcha.key")
|
||||
|
||||
key, err := flags.GetString("recaptcha.key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret, err := getString(flags, "recaptcha.secret")
|
||||
|
||||
secret, err := flags.GetString("recaptcha.secret")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -149,11 +153,10 @@ func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (au
|
|||
}
|
||||
|
||||
func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||
command, err := getString(flags, "auth.command")
|
||||
command, err := flags.GetString("auth.command")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if command == "" {
|
||||
command = defaultAuther["command"].(string)
|
||||
}
|
||||
|
|
@ -201,6 +204,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||
|
||||
fmt.Fprintln(w, "\nBranding:")
|
||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||
|
|
@ -208,6 +212,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
|
||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
||||
fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme)
|
||||
|
||||
fmt.Fprintln(w, "\nServer:")
|
||||
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||
|
|
@ -218,9 +223,14 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
||||
fmt.Fprintf(w, "\tThumbnails Enabled:\t%t\n", ser.EnableThumbnails)
|
||||
fmt.Fprintf(w, "\tResize Preview:\t%t\n", ser.ResizePreview)
|
||||
fmt.Fprintf(w, "\tType Detection by Header:\t%t\n", ser.TypeDetectionByHeader)
|
||||
|
||||
fmt.Fprintln(w, "\nTUS:")
|
||||
fmt.Fprintf(w, "\tChunk size:\t%d\n", set.Tus.ChunkSize)
|
||||
fmt.Fprintf(w, "\tRetry count:\t%d\n", set.Tus.RetryCount)
|
||||
|
||||
fmt.Fprintln(w, "\nDefaults:")
|
||||
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
||||
fmt.Fprintf(w, "\tHideDotfiles:\t%t\n", set.Defaults.HideDotfiles)
|
||||
|
|
@ -231,9 +241,11 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
|
||||
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
|
||||
fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme)
|
||||
|
||||
fmt.Fprintf(w, "\tSorting:\n")
|
||||
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
||||
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
|
||||
|
||||
fmt.Fprintf(w, "\tPermissions:\n")
|
||||
fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin)
|
||||
fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute)
|
||||
|
|
@ -243,6 +255,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete)
|
||||
fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share)
|
||||
fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download)
|
||||
|
||||
w.Flush()
|
||||
|
||||
b, err := json.MarshalIndent(auther, "", " ")
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ The path must be for a json or yaml file.`,
|
|||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
var key []byte
|
||||
var err error
|
||||
if d.hadDB {
|
||||
if d.databaseExisted {
|
||||
settings, settingErr := d.store.Settings.Get()
|
||||
if settingErr != nil {
|
||||
return settingErr
|
||||
|
|
@ -104,7 +104,7 @@ The path must be for a json or yaml file.`,
|
|||
}
|
||||
|
||||
return printSettings(file.Server, file.Settings, auther)
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}, pythonConfig{allowsNoDatabase: true}),
|
||||
}
|
||||
|
||||
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
)
|
||||
|
||||
|
|
@ -23,170 +24,147 @@ to the defaults when creating new users and you don't
|
|||
override the options.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
defaults := settings.UserDefaults{}
|
||||
flags := cmd.Flags()
|
||||
err := getUserDefaults(flags, &defaults, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authMethod, auther, err := getAuthentication(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := generateKey()
|
||||
|
||||
signup, err := getBool(flags, "signup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hideLoginButton, err := getBool(flags, "hide-login-button")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createUserDir, err := getBool(flags, "create-user-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minLength, err := getUint(flags, "minimum-password-length")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell, err := getString(flags, "shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingName, err := getString(flags, "branding.name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingDisableExternal, err := getBool(flags, "branding.disableExternal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingDisableUsedPercentage, err := getBool(flags, "branding.disableUsedPercentage")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingTheme, err := getString(flags, "branding.theme")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brandingFiles, err := getString(flags, "branding.files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tusChunkSize, err := flags.GetUint64("tus.chunkSize")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tusRetryCount, err := flags.GetUint16("tus.retryCount")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// General Settings
|
||||
s := &settings.Settings{
|
||||
Key: key,
|
||||
Signup: signup,
|
||||
HideLoginButton: hideLoginButton,
|
||||
CreateUserDir: createUserDir,
|
||||
MinimumPasswordLength: minLength,
|
||||
Shell: convertCmdStrToCmdArray(shell),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Branding: settings.Branding{
|
||||
Name: brandingName,
|
||||
DisableExternal: brandingDisableExternal,
|
||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
||||
Theme: brandingTheme,
|
||||
Files: brandingFiles,
|
||||
},
|
||||
Tus: settings.Tus{
|
||||
ChunkSize: tusChunkSize,
|
||||
RetryCount: tusRetryCount,
|
||||
},
|
||||
Key: generateKey(),
|
||||
}
|
||||
|
||||
s.FileMode, err = getMode(flags, "file-mode")
|
||||
err := getUserDefaults(flags, &s.Defaults, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DirMode, err = getMode(flags, "dir-mode")
|
||||
s.Signup, err = flags.GetBool("signup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address, err := getString(flags, "address")
|
||||
s.HideLoginButton, err = flags.GetBool("hideLoginButton")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socket, err := getString(flags, "socket")
|
||||
s.CreateUserDir, err = flags.GetBool("createUserDir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err := getString(flags, "root")
|
||||
s.MinimumPasswordLength, err = flags.GetUint("minimumPasswordLength")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseURL, err := getString(flags, "baseurl")
|
||||
shell, err := flags.GetString("shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Shell = convertCmdStrToCmdArray(shell)
|
||||
|
||||
s.FileMode, err = getAndParseFileMode(flags, "fileMode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsKey, err := getString(flags, "key")
|
||||
s.DirMode, err = getAndParseFileMode(flags, "dirMode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert, err := getString(flags, "cert")
|
||||
s.Branding.Name, err = flags.GetString("branding.name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := getString(flags, "port")
|
||||
s.Branding.DisableExternal, err = flags.GetBool("branding.disableExternal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log, err := getString(flags, "log")
|
||||
s.Branding.DisableUsedPercentage, err = flags.GetBool("branding.disableUsedPercentage")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
Address: address,
|
||||
Socket: socket,
|
||||
Root: root,
|
||||
BaseURL: baseURL,
|
||||
TLSKey: tlsKey,
|
||||
TLSCert: cert,
|
||||
Port: port,
|
||||
Log: log,
|
||||
s.Branding.Theme, err = flags.GetString("branding.themes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Branding.Files, err = flags.GetString("branding.files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Tus.ChunkSize, err = flags.GetUint64("tus.chunkSize")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Tus.RetryCount, err = flags.GetUint16("tus.retryCount")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
s.AuthMethod, auther, err = getAuthentication(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Server Settings
|
||||
ser := &settings.Server{}
|
||||
ser.Address, err = flags.GetString("address")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.Socket, err = flags.GetString("socket")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.Root, err = flags.GetString("root")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.BaseURL, err = flags.GetString("baseURL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.TLSKey, err = flags.GetString("key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.TLSCert, err = flags.GetString("cert")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.Port, err = flags.GetString("port")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser.Log, err = flags.GetString("log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -198,5 +176,5 @@ Now add your first user via 'filebrowser users add' and then you just
|
|||
need to call the main command to boot up the server.
|
||||
`)
|
||||
return printSettings(ser, s, auther)
|
||||
}, pythonConfig{noDB: true}),
|
||||
}, pythonConfig{expectsNoDatabase: true}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ you want to change. Other options will remain unchanged.`,
|
|||
Args: cobra.NoArgs,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
set, err := d.store.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -29,64 +30,86 @@ you want to change. Other options will remain unchanged.`,
|
|||
}
|
||||
|
||||
hasAuth := false
|
||||
|
||||
flags.Visit(func(flag *pflag.Flag) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch flag.Name {
|
||||
case "baseurl":
|
||||
ser.BaseURL, err = getString(flags, flag.Name)
|
||||
case "root":
|
||||
ser.Root, err = getString(flags, flag.Name)
|
||||
case "socket":
|
||||
ser.Socket, err = getString(flags, flag.Name)
|
||||
case "cert":
|
||||
ser.TLSCert, err = getString(flags, flag.Name)
|
||||
case "key":
|
||||
ser.TLSKey, err = getString(flags, flag.Name)
|
||||
// Server flags from [addServerFlags]
|
||||
case "address":
|
||||
ser.Address, err = getString(flags, flag.Name)
|
||||
case "port":
|
||||
ser.Port, err = getString(flags, flag.Name)
|
||||
ser.Address, err = flags.GetString(flag.Name)
|
||||
case "log":
|
||||
ser.Log, err = getString(flags, flag.Name)
|
||||
case "hide-login-button":
|
||||
set.HideLoginButton, err = getBool(flags, flag.Name)
|
||||
ser.Log, err = flags.GetString(flag.Name)
|
||||
case "port":
|
||||
ser.Port, err = flags.GetString(flag.Name)
|
||||
case "cert":
|
||||
ser.TLSCert, err = flags.GetString(flag.Name)
|
||||
case "key":
|
||||
ser.TLSKey, err = flags.GetString(flag.Name)
|
||||
case "root":
|
||||
ser.Root, err = flags.GetString(flag.Name)
|
||||
case "socket":
|
||||
ser.Socket, err = flags.GetString(flag.Name)
|
||||
case "baseURL":
|
||||
ser.BaseURL, err = flags.GetString(flag.Name)
|
||||
case "tokenExpirationTime":
|
||||
ser.TokenExpirationTime, err = flags.GetString(flag.Name)
|
||||
case "disableThumbnails":
|
||||
ser.EnableThumbnails, err = flags.GetBool(flag.Name)
|
||||
ser.EnableThumbnails = !ser.EnableThumbnails
|
||||
case "disablePreviewResize":
|
||||
ser.ResizePreview, err = flags.GetBool(flag.Name)
|
||||
ser.ResizePreview = !ser.ResizePreview
|
||||
case "disableExec":
|
||||
ser.EnableExec, err = flags.GetBool(flag.Name)
|
||||
ser.EnableExec = !ser.EnableExec
|
||||
case "disableTypeDetectionByHeader":
|
||||
ser.TypeDetectionByHeader, err = flags.GetBool(flag.Name)
|
||||
ser.TypeDetectionByHeader = !ser.TypeDetectionByHeader
|
||||
|
||||
// Settings flags from [addConfigFlags]
|
||||
case "signup":
|
||||
set.Signup, err = getBool(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
set.Signup, err = flags.GetBool(flag.Name)
|
||||
case "hideLoginButton":
|
||||
set.HideLoginButton, err = flags.GetBool(flag.Name)
|
||||
case "createUserDir":
|
||||
set.CreateUserDir, err = flags.GetBool(flag.Name)
|
||||
case "minimumPasswordLength":
|
||||
set.MinimumPasswordLength, err = flags.GetUint(flag.Name)
|
||||
case "shell":
|
||||
var shell string
|
||||
shell, err = getString(flags, flag.Name)
|
||||
shell, err = flags.GetString(flag.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
set.Shell = convertCmdStrToCmdArray(shell)
|
||||
case "create-user-dir":
|
||||
set.CreateUserDir, err = getBool(flags, flag.Name)
|
||||
case "minimum-password-length":
|
||||
set.MinimumPasswordLength, err = getUint(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "branding.name":
|
||||
set.Branding.Name, err = getString(flags, flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color, err = getString(flags, flag.Name)
|
||||
set.Branding.Name, err = flags.GetString(flag.Name)
|
||||
case "branding.theme":
|
||||
set.Branding.Theme, err = getString(flags, flag.Name)
|
||||
case "branding.disableExternal":
|
||||
set.Branding.DisableExternal, err = getBool(flags, flag.Name)
|
||||
case "branding.disableUsedPercentage":
|
||||
set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
|
||||
set.Branding.Theme, err = flags.GetString(flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color, err = flags.GetString(flag.Name)
|
||||
case "branding.files":
|
||||
set.Branding.Files, err = getString(flags, flag.Name)
|
||||
case "file-mode":
|
||||
set.FileMode, err = getMode(flags, flag.Name)
|
||||
case "dir-mode":
|
||||
set.DirMode, err = getMode(flags, flag.Name)
|
||||
set.Branding.Files, err = flags.GetString(flag.Name)
|
||||
case "branding.disableExternal":
|
||||
set.Branding.DisableExternal, err = flags.GetBool(flag.Name)
|
||||
case "branding.disableUsedPercentage":
|
||||
set.Branding.DisableUsedPercentage, err = flags.GetBool(flag.Name)
|
||||
case "fileMode":
|
||||
set.FileMode, err = getAndParseFileMode(flags, flag.Name)
|
||||
case "dirMode":
|
||||
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
||||
case "tus.chunkSize":
|
||||
set.Tus.ChunkSize, err = flags.GetUint64(flag.Name)
|
||||
case "tus.retryCount":
|
||||
set.Tus.RetryCount, err = flags.GetUint16(flag.Name)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
283
cmd/root.go
283
cmd/root.go
|
|
@ -13,15 +13,13 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
v "github.com/spf13/viper"
|
||||
"github.com/spf13/viper"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
|
|
@ -35,28 +33,67 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
flagNamesMigrations = map[string]string{
|
||||
"file-mode": "fileMode",
|
||||
"dir-mode": "dirMode",
|
||||
"hide-login-button": "hideLoginButton",
|
||||
"create-user-dir": "createUserDir",
|
||||
"minimum-password-length": "minimumPasswordLength",
|
||||
"socket-perm": "socketPerm",
|
||||
"disable-thumbnails": "disableThumbnails",
|
||||
"disable-preview-resize": "disablePreviewResize",
|
||||
"disable-exec": "disableExec",
|
||||
"disable-type-detection-by-header": "disableTypeDetectionByHeader",
|
||||
"img-processors": "imageProcessors",
|
||||
"cache-dir": "cacheDir",
|
||||
"token-expiration-time": "tokenExpirationTime",
|
||||
"baseurl": "baseURL",
|
||||
}
|
||||
|
||||
warnedFlags = map[string]bool{}
|
||||
)
|
||||
|
||||
// TODO(remove): remove after July 2026.
|
||||
func migrateFlagNames(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if newName, ok := flagNamesMigrations[name]; ok {
|
||||
|
||||
if !warnedFlags[name] {
|
||||
warnedFlags[name] = true
|
||||
fmt.Printf("WARNING: Flag --%s has been deprecated, use --%s instead\n", name, newName)
|
||||
}
|
||||
|
||||
name = newName
|
||||
}
|
||||
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
rootCmd.SilenceUsage = true
|
||||
rootCmd.SetGlobalNormalizationFunc(migrateFlagNames)
|
||||
|
||||
cobra.MousetrapHelpText = ""
|
||||
|
||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||
|
||||
flags := rootCmd.Flags()
|
||||
// Flags available across the whole program
|
||||
persistent := rootCmd.PersistentFlags()
|
||||
|
||||
persistent.StringVarP(&cfgFile, "config", "c", "", "config file path")
|
||||
persistent.StringP("config", "c", "", "config file path")
|
||||
persistent.StringP("database", "d", "./filebrowser.db", "database path")
|
||||
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
||||
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")
|
||||
|
||||
// Runtime flags for the root command
|
||||
flags := rootCmd.Flags()
|
||||
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
||||
flags.String("username", "admin", "username for the first user when using quick setup")
|
||||
flags.String("password", "", "hashed password for the first user when using quick setup")
|
||||
flags.Uint32("socketPerm", 0666, "unix socket file permissions")
|
||||
flags.String("cacheDir", "", "file cache directory (disabled if empty)")
|
||||
flags.Int("imageProcessors", 4, "image processors count")
|
||||
addServerFlags(flags)
|
||||
}
|
||||
|
||||
// addServerFlags adds server related flags to the given FlagSet. These flags are available
|
||||
// in both the root command, config set and config init commands.
|
||||
func addServerFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP("address", "a", "127.0.0.1", "address to listen on")
|
||||
flags.StringP("log", "l", "stdout", "log output")
|
||||
|
|
@ -65,15 +102,12 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||
flags.StringP("key", "k", "", "tls key")
|
||||
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
||||
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
||||
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
|
||||
flags.StringP("baseurl", "b", "", "base url")
|
||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||
flags.String("token-expiration-time", "2h", "user session timeout")
|
||||
flags.Int("img-processors", 4, "image processors count")
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", true, "disables Command Runner feature")
|
||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
||||
flags.StringP("baseURL", "b", "", "base url")
|
||||
flags.String("tokenExpirationTime", "2h", "user session timeout")
|
||||
flags.Bool("disableThumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disablePreviewResize", false, "disable resize of image previews")
|
||||
flags.Bool("disableExec", true, "disables Command Runner feature")
|
||||
flags.Bool("disableTypeDetectionByHeader", false, "disables type detection by reading file headers")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
|
|
@ -88,12 +122,14 @@ 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.
|
||||
For this command, all flags are available as environmental variables,
|
||||
except for "--config", which specifies the configuration file to use.
|
||||
The environment variables are prefixed by "FB_" followed by the flag name in
|
||||
UPPER_SNAKE_CASE. For example, the flag "--disablePreviewResize" is available
|
||||
as FB_DISABLE_PREVIEW_RESIZE.
|
||||
|
||||
If you don't set "config", it will look for a configuration file called
|
||||
.filebrowser.{json, toml, yaml, yml} in the following directories:
|
||||
If "--config" is not specified, File Browser will look for a configuration
|
||||
file named .filebrowser.{json, toml, yaml, yml} in the following directories:
|
||||
|
||||
- ./
|
||||
- $HOME/
|
||||
|
|
@ -101,44 +137,32 @@ If you don't set "config", it will look for a configuration file called
|
|||
|
||||
The precedence of the configuration values are as follows:
|
||||
|
||||
- flags
|
||||
- environment variables
|
||||
- configuration file
|
||||
- database values
|
||||
- defaults
|
||||
|
||||
The environment variables are prefixed by "FB_" followed by the option
|
||||
name in caps. So to set "database" via an env variable, you should
|
||||
set FB_DATABASE.
|
||||
- Flags
|
||||
- Environment variables
|
||||
- Configuration file
|
||||
- Database values
|
||||
- Defaults
|
||||
|
||||
Also, if the database path doesn't exist, File Browser will enter into
|
||||
the quick setup mode and a new database will be bootstrapped and a new
|
||||
user created with the credentials from options "username" and "password".`,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
log.Println(cfgFile)
|
||||
|
||||
if !d.hadDB {
|
||||
err := quickSetup(cmd.Flags(), *d)
|
||||
if !d.databaseExisted {
|
||||
err := quickSetup(*d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// build img service
|
||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if workersCount < 1 {
|
||||
imgWorkersCount := d.viper.GetInt("imageProcessors")
|
||||
if imgWorkersCount < 1 {
|
||||
return errors.New("image resize workers count could not be < 1")
|
||||
}
|
||||
imgSvc := img.New(workersCount)
|
||||
imageService := img.New(imgWorkersCount)
|
||||
|
||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheDir := d.viper.GetString("cacheDir")
|
||||
if cacheDir != "" {
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil {
|
||||
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
||||
|
|
@ -146,7 +170,7 @@ user created with the credentials from options "username" and "password".`,
|
|||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
}
|
||||
|
||||
server, err := getRunParams(cmd.Flags(), d.store)
|
||||
server, err := getServerSettings(d.viper, d.store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -168,10 +192,7 @@ user created with the credentials from options "username" and "password".`,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socketPerm := d.viper.GetUint32("socketPerm")
|
||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -200,7 +221,7 @@ user created with the credentials from options "username" and "password".`,
|
|||
panic(err)
|
||||
}
|
||||
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||
handler, err := fbhttp.NewHandler(imageService, fileCache, d.store, server, assetsFs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -241,55 +262,59 @@ user created with the credentials from options "username" and "password".`,
|
|||
log.Println("Graceful shutdown complete.")
|
||||
|
||||
return nil
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}, pythonConfig{allowsNoDatabase: true}),
|
||||
}
|
||||
|
||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) {
|
||||
func getServerSettings(v *viper.Viper, st *storage.Storage) (*settings.Server, error) {
|
||||
server, err := st.Settings.GetServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "root"); set {
|
||||
if val, set := vGetStringIsSet(v, "root"); set {
|
||||
server.Root = val
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "baseurl"); set {
|
||||
if val, set := vGetStringIsSet(v, "baseURL"); set {
|
||||
server.BaseURL = val
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "log"); set {
|
||||
if val, set := vGetStringIsSet(v, "log"); set {
|
||||
server.Log = val
|
||||
}
|
||||
|
||||
isSocketSet := false
|
||||
isAddrSet := false
|
||||
|
||||
if val, set := getStringParamB(flags, "address"); set {
|
||||
if val, set := vGetStringIsSet(v, "address"); set {
|
||||
server.Address = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "port"); set {
|
||||
if val, set := vGetStringIsSet(v, "port"); set {
|
||||
server.Port = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "key"); set {
|
||||
if val, set := vGetStringIsSet(v, "key"); set {
|
||||
server.TLSKey = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "cert"); set {
|
||||
if val, set := vGetStringIsSet(v, "cert"); set {
|
||||
server.TLSCert = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "socket"); set {
|
||||
if val, set := vGetStringIsSet(v, "socket"); set {
|
||||
server.Socket = val
|
||||
isSocketSet = isSocketSet || set
|
||||
}
|
||||
|
||||
if val, set := vGetStringIsSet(v, "tokenExpirationTime"); set {
|
||||
server.TokenExpirationTime = val
|
||||
}
|
||||
|
||||
if isAddrSet && isSocketSet {
|
||||
return nil, errors.New("--socket flag cannot be used with --address, --port, --key nor --cert")
|
||||
}
|
||||
|
|
@ -299,17 +324,10 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||
server.Socket = ""
|
||||
}
|
||||
|
||||
disableThumbnails := getBoolParam(flags, "disable-thumbnails")
|
||||
server.EnableThumbnails = !disableThumbnails
|
||||
|
||||
disablePreviewResize := getBoolParam(flags, "disable-preview-resize")
|
||||
server.ResizePreview = !disablePreviewResize
|
||||
|
||||
disableTypeDetectionByHeader := getBoolParam(flags, "disable-type-detection-by-header")
|
||||
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
||||
|
||||
disableExec := getBoolParam(flags, "disable-exec")
|
||||
server.EnableExec = !disableExec
|
||||
server.EnableThumbnails = !v.GetBool("disableThumbnails")
|
||||
server.ResizePreview = !v.GetBool("disablePreviewResize")
|
||||
server.TypeDetectionByHeader = !v.GetBool("disableTypeDetectionByHeader")
|
||||
server.EnableExec = !v.GetBool("disableExec")
|
||||
|
||||
if server.EnableExec {
|
||||
log.Println("WARNING: Command Runner feature enabled!")
|
||||
|
|
@ -318,69 +336,11 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||
log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199")
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "token-expiration-time"); set {
|
||||
server.TokenExpirationTime = val
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// getBoolParamB 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 getBoolParamB(flags *pflag.FlagSet, key string) (value, ok bool) {
|
||||
value, _ = flags.GetBool(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.GetBool(key), true
|
||||
}
|
||||
|
||||
// Otherwise use default value on flags.
|
||||
return value, false
|
||||
}
|
||||
|
||||
func getBoolParam(flags *pflag.FlagSet, key string) bool {
|
||||
val, _ := getBoolParamB(flags, key)
|
||||
return val
|
||||
}
|
||||
|
||||
// getStringParamB 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 getStringParamB(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 getStringParam(flags *pflag.FlagSet, key string) string {
|
||||
val, _ := getStringParamB(flags, key)
|
||||
return val
|
||||
func vGetStringIsSet(v *viper.Viper, key string) (string, bool) {
|
||||
return v.GetString(key), v.IsSet(key)
|
||||
}
|
||||
|
||||
func setupLog(logMethod string) {
|
||||
|
|
@ -401,7 +361,7 @@ func setupLog(logMethod string) {
|
|||
}
|
||||
}
|
||||
|
||||
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
func quickSetup(d pythonData) error {
|
||||
log.Println("Performing quick setup")
|
||||
|
||||
set := &settings.Settings{
|
||||
|
|
@ -415,7 +375,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||
Scope: ".",
|
||||
Locale: "en",
|
||||
SingleClick: false,
|
||||
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
|
||||
AceEditorTheme: d.viper.GetString("defaults.aceEditorTheme"),
|
||||
Perm: users.Permissions{
|
||||
Admin: false,
|
||||
Execute: true,
|
||||
|
|
@ -439,7 +399,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||
}
|
||||
|
||||
var err error
|
||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
||||
if _, noauth := vGetStringIsSet(d.viper, "noauth"); noauth {
|
||||
set.AuthMethod = auth.MethodNoAuth
|
||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||
} else {
|
||||
|
|
@ -456,13 +416,13 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
BaseURL: getStringParam(flags, "baseurl"),
|
||||
Port: getStringParam(flags, "port"),
|
||||
Log: getStringParam(flags, "log"),
|
||||
TLSKey: getStringParam(flags, "key"),
|
||||
TLSCert: getStringParam(flags, "cert"),
|
||||
Address: getStringParam(flags, "address"),
|
||||
Root: getStringParam(flags, "root"),
|
||||
BaseURL: d.viper.GetString("baseURL"),
|
||||
Port: d.viper.GetString("port"),
|
||||
Log: d.viper.GetString("log"),
|
||||
TLSKey: d.viper.GetString("key"),
|
||||
TLSCert: d.viper.GetString("cert"),
|
||||
Address: d.viper.GetString("address"),
|
||||
Root: d.viper.GetString("root"),
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
|
|
@ -470,8 +430,8 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||
return err
|
||||
}
|
||||
|
||||
username := getStringParam(flags, "username")
|
||||
password := getStringParam(flags, "password")
|
||||
username := d.viper.GetString("username")
|
||||
password := d.viper.GetString("password")
|
||||
|
||||
if password == "" {
|
||||
var pwd string
|
||||
|
|
@ -504,32 +464,3 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||
|
||||
return d.store.Users.Save(user)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile == "" {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath(home)
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
v.SetConfigName(".filebrowser")
|
||||
} else {
|
||||
v.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
v.SetEnvPrefix("FB")
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
var configParseError v.ConfigParseError
|
||||
if errors.As(err, &configParseError) {
|
||||
panic(err)
|
||||
}
|
||||
cfgFile = "No config file used"
|
||||
} else {
|
||||
cfgFile = "Using config file: " + v.ConfigFileUsed()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,11 +69,12 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
|||
}
|
||||
|
||||
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
||||
id, err := getUint(flags, "id")
|
||||
id, err := flags.GetUint("id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username, err := getString(flags, "username")
|
||||
|
||||
username, err := flags.GetString("username")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,18 @@ var rulesAddCmd = &cobra.Command{
|
|||
Long: `Add a global rule or user rule.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
allow, err := getBool(cmd.Flags(), "allow")
|
||||
flags := cmd.Flags()
|
||||
|
||||
allow, err := flags.GetBool("allow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regex, err := getBool(cmd.Flags(), "regex")
|
||||
|
||||
regex, err := flags.GetBool("regex")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exp := args[0]
|
||||
|
||||
if regex {
|
||||
|
|
|
|||
50
cmd/users.go
50
cmd/users.go
|
|
@ -82,63 +82,64 @@ func addUserFlags(flags *pflag.FlagSet) {
|
|||
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
|
||||
}
|
||||
|
||||
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
||||
viewModeStr, err := getString(flags, "viewMode")
|
||||
func getAndParseViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
||||
viewModeStr, err := flags.GetString("viewMode")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
viewMode := users.ViewMode(viewModeStr)
|
||||
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
||||
return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")
|
||||
}
|
||||
|
||||
return viewMode, nil
|
||||
}
|
||||
|
||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
|
||||
var visitErr error
|
||||
errs := []error{}
|
||||
|
||||
visit := func(flag *pflag.Flag) {
|
||||
if visitErr != nil {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
switch flag.Name {
|
||||
case "scope":
|
||||
defaults.Scope, err = getString(flags, flag.Name)
|
||||
defaults.Scope, err = flags.GetString(flag.Name)
|
||||
case "locale":
|
||||
defaults.Locale, err = getString(flags, flag.Name)
|
||||
defaults.Locale, err = flags.GetString(flag.Name)
|
||||
case "viewMode":
|
||||
defaults.ViewMode, err = getViewMode(flags)
|
||||
defaults.ViewMode, err = getAndParseViewMode(flags)
|
||||
case "singleClick":
|
||||
defaults.SingleClick, err = getBool(flags, flag.Name)
|
||||
defaults.SingleClick, err = flags.GetBool(flag.Name)
|
||||
case "aceEditorTheme":
|
||||
defaults.AceEditorTheme, err = getString(flags, flag.Name)
|
||||
defaults.AceEditorTheme, err = flags.GetString(flag.Name)
|
||||
case "perm.admin":
|
||||
defaults.Perm.Admin, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Admin, err = flags.GetBool(flag.Name)
|
||||
case "perm.execute":
|
||||
defaults.Perm.Execute, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Execute, err = flags.GetBool(flag.Name)
|
||||
case "perm.create":
|
||||
defaults.Perm.Create, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Create, err = flags.GetBool(flag.Name)
|
||||
case "perm.rename":
|
||||
defaults.Perm.Rename, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Rename, err = flags.GetBool(flag.Name)
|
||||
case "perm.modify":
|
||||
defaults.Perm.Modify, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Modify, err = flags.GetBool(flag.Name)
|
||||
case "perm.delete":
|
||||
defaults.Perm.Delete, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Delete, err = flags.GetBool(flag.Name)
|
||||
case "perm.share":
|
||||
defaults.Perm.Share, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Share, err = flags.GetBool(flag.Name)
|
||||
case "perm.download":
|
||||
defaults.Perm.Download, err = getBool(flags, flag.Name)
|
||||
defaults.Perm.Download, err = flags.GetBool(flag.Name)
|
||||
case "commands":
|
||||
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
||||
case "sorting.by":
|
||||
defaults.Sorting.By, err = getString(flags, flag.Name)
|
||||
defaults.Sorting.By, err = flags.GetString(flag.Name)
|
||||
case "sorting.asc":
|
||||
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
||||
defaults.Sorting.Asc, err = flags.GetBool(flag.Name)
|
||||
case "hideDotfiles":
|
||||
defaults.HideDotfiles, err = getBool(flags, flag.Name)
|
||||
defaults.HideDotfiles, err = flags.GetBool(flag.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
visitErr = err
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,5 +148,6 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||
} else {
|
||||
flags.Visit(visit)
|
||||
}
|
||||
return visitErr
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@ var usersAddCmd = &cobra.Command{
|
|||
Long: `Create a new user and add it to the database.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
s, err := d.store.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||
err = getUserDefaults(flags, &s.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -31,27 +32,24 @@ var usersAddCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
lockPassword, err := getBool(cmd.Flags(), "lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dateFormat, err := getBool(cmd.Flags(), "dateFormat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &users.User{
|
||||
Username: args[0],
|
||||
Password: password,
|
||||
LockPassword: lockPassword,
|
||||
DateFormat: dateFormat,
|
||||
HideDotfiles: hideDotfiles,
|
||||
}
|
||||
|
||||
user.LockPassword, err = flags.GetBool("lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.DateFormat, err = flags.GetBool("dateFormat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.HideDotfiles, err = flags.GetBool("hideDotfiles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Defaults.Apply(user)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ installation. For that, just don't place their ID on the files
|
|||
list or set it to 0.`,
|
||||
Args: jsonYamlArg,
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
fd, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -45,7 +46,7 @@ list or set it to 0.`,
|
|||
}
|
||||
}
|
||||
|
||||
replace, err := getBool(cmd.Flags(), "replace")
|
||||
replace, err := flags.GetBool("replace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -69,7 +70,7 @@ list or set it to 0.`,
|
|||
}
|
||||
}
|
||||
|
||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
||||
overwrite, err := flags.GetBool("overwrite")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,14 @@ var usersUpdateCmd = &cobra.Command{
|
|||
options you want to change.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
flags := cmd.Flags()
|
||||
password, err := getString(flags, "password")
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
password, err := flags.GetString("password")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newUsername, err := getString(flags, "username")
|
||||
|
||||
newUsername, err := flags.GetString("username")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -41,13 +42,11 @@ options you want to change.`,
|
|||
var (
|
||||
user *users.User
|
||||
)
|
||||
|
||||
if id != 0 {
|
||||
user, err = d.store.Users.Get("", id)
|
||||
} else {
|
||||
user, err = d.store.Users.Get("", username)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -61,10 +60,12 @@ options you want to change.`,
|
|||
Sorting: user.Sorting,
|
||||
Commands: user.Commands,
|
||||
}
|
||||
|
||||
err = getUserDefaults(flags, &defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Scope = defaults.Scope
|
||||
user.Locale = defaults.Locale
|
||||
user.ViewMode = defaults.ViewMode
|
||||
|
|
@ -72,15 +73,17 @@ options you want to change.`,
|
|||
user.Perm = defaults.Perm
|
||||
user.Commands = defaults.Commands
|
||||
user.Sorting = defaults.Sorting
|
||||
user.LockPassword, err = getBool(flags, "lockPassword")
|
||||
user.LockPassword, err = flags.GetBool("lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.DateFormat, err = getBool(flags, "dateFormat")
|
||||
|
||||
user.DateFormat, err = flags.GetBool("dateFormat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.HideDotfiles, err = getBool(flags, "hideDotfiles")
|
||||
|
||||
user.HideDotfiles, err = flags.GetBool("hideDotfiles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
150
cmd/utils.go
150
cmd/utils.go
|
|
@ -12,8 +12,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
|
|
@ -21,32 +24,21 @@ import (
|
|||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||
)
|
||||
|
||||
const dbPerms = 0640
|
||||
const databasePermissions = 0640
|
||||
|
||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
||||
return flags.GetString(flag)
|
||||
}
|
||||
|
||||
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
||||
s, err := getString(flags, flag)
|
||||
func getAndParseFileMode(flags *pflag.FlagSet, name string) (fs.FileMode, error) {
|
||||
mode, err := flags.GetString(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b, err := strconv.ParseUint(s, 0, 32)
|
||||
|
||||
b, err := strconv.ParseUint(mode, 0, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fs.FileMode(b), nil
|
||||
}
|
||||
|
||||
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
||||
return flags.GetBool(flag)
|
||||
}
|
||||
|
||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
||||
return flags.GetUint(flag)
|
||||
}
|
||||
|
||||
func generateKey() []byte {
|
||||
k, err := settings.GenerateKey()
|
||||
if err != nil {
|
||||
|
|
@ -55,19 +47,6 @@ func generateKey() []byte {
|
|||
return k
|
||||
}
|
||||
|
||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
||||
|
||||
type pythonConfig struct {
|
||||
noDB bool
|
||||
allowNoDB bool
|
||||
}
|
||||
|
||||
type pythonData struct {
|
||||
hadDB bool
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
func dbExists(path string) (bool, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
|
@ -88,38 +67,131 @@ func dbExists(path string) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
// Generate the replacements for all environment variables. This allows to
|
||||
// use FB_BRANDING_DISABLE_EXTERNAL environment variables, even when the
|
||||
// option name is branding.disableExternal.
|
||||
func generateEnvKeyReplacements(cmd *cobra.Command) []string {
|
||||
replacements := []string{}
|
||||
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
oldName := strings.ToUpper(f.Name)
|
||||
newName := strings.ToUpper(lo.SnakeCase(f.Name))
|
||||
replacements = append(replacements, oldName, newName)
|
||||
})
|
||||
|
||||
return replacements
|
||||
}
|
||||
|
||||
func initViper(cmd *cobra.Command) (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
|
||||
// Get config file from flag
|
||||
cfgFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configuration file
|
||||
if cfgFile == "" {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath(home)
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
v.SetConfigName(".filebrowser")
|
||||
} else {
|
||||
v.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
v.SetEnvPrefix("FB")
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(generateEnvKeyReplacements(cmd)...))
|
||||
|
||||
// Bind the flags
|
||||
err = v.BindPFlags(cmd.Flags())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read in configuration
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if errors.Is(err, viper.ConfigParseError{}) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("No config file used")
|
||||
} else {
|
||||
log.Printf("Using config file: %s", v.ConfigFileUsed())
|
||||
}
|
||||
|
||||
// Return Viper
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
||||
|
||||
type pythonConfig struct {
|
||||
expectsNoDatabase bool
|
||||
allowsNoDatabase bool
|
||||
}
|
||||
|
||||
type pythonData struct {
|
||||
databaseExisted bool
|
||||
viper *viper.Viper
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
data := &pythonData{hadDB: true}
|
||||
v, err := initViper(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := &pythonData{databaseExisted: true}
|
||||
path := v.GetString("database")
|
||||
|
||||
// Only make the viper instance available to the root command (filebrowser).
|
||||
// This is to make sure that we don't make the mistake of using it somewhere
|
||||
// else.
|
||||
if cmd.Name() == "filebrowser" {
|
||||
data.viper = v
|
||||
}
|
||||
|
||||
path := getStringParam(cmd.Flags(), "database")
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
exists, err := dbExists(path)
|
||||
|
||||
exists, err := dbExists(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if exists && cfg.noDB {
|
||||
return err
|
||||
} else if exists && cfg.expectsNoDatabase {
|
||||
log.Fatal(absPath + " already exists")
|
||||
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
|
||||
} else if !exists && !cfg.expectsNoDatabase && !cfg.allowsNoDatabase {
|
||||
log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.")
|
||||
} else if !exists && !cfg.noDB {
|
||||
} else if !exists && !cfg.expectsNoDatabase {
|
||||
log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db"))
|
||||
}
|
||||
|
||||
log.Println("Using database: " + absPath)
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
||||
data.databaseExisted = exists
|
||||
|
||||
db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fn(cmd, args, data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -16,6 +16,7 @@ require (
|
|||
github.com/marusama/semaphore/v2 v2.5.0
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.10
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -202,6 +202,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
|
||||
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
|
||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||
|
|
|
|||
Loading…
Reference in New Issue