Files
filebrowser/cmd/utils.go
T

263 lines
6.0 KiB
Go
Raw Normal View History

2019-01-05 22:44:33 +00:00
package cmd
import (
2019-01-08 08:57:24 +00:00
"encoding/json"
"errors"
"io/fs"
2019-01-07 20:24:23 +00:00
"log"
2019-01-05 22:44:33 +00:00
"os"
2019-01-08 08:57:24 +00:00
"path/filepath"
"strconv"
2020-10-05 00:52:27 -07:00
"strings"
2019-01-05 22:44:33 +00:00
2022-05-04 00:48:45 +04:00
"github.com/asdine/storm/v3"
homedir "github.com/mitchellh/go-homedir"
"github.com/samber/lo"
2019-01-05 22:44:33 +00:00
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v3"
2020-06-01 01:12:36 +02:00
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/storage"
"github.com/filebrowser/filebrowser/v2/storage/bolt"
2019-01-05 22:44:33 +00:00
)
const databasePermissions = 0640
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(mode, 0, 32)
if err != nil {
return 0, err
}
2025-11-18 11:29:28 +01:00
return fs.FileMode(b), nil
}
2019-01-11 20:25:39 +00:00
func generateKey() []byte {
k, err := settings.GenerateKey()
if err != nil {
panic(err)
}
2019-01-11 20:25:39 +00:00
return k
2019-01-06 12:26:48 +00:00
}
2019-01-07 20:24:23 +00:00
2019-01-09 21:37:47 +00:00
func dbExists(path string) (bool, error) {
stat, err := os.Stat(path)
if err == nil {
return stat.Size() != 0, nil
2019-01-09 21:37:47 +00:00
}
if os.IsNotExist(err) {
d := filepath.Dir(path)
_, err = os.Stat(d)
if os.IsNotExist(err) {
2025-11-15 09:01:21 +01:00
if err := os.MkdirAll(d, 0700); err != nil {
2020-06-01 01:12:36 +02:00
return false, err
}
return false, nil
}
2019-01-09 21:37:47 +00:00
}
return false, err
2019-01-09 21:37:47 +00:00
}
// 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.As(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
}
2025-11-18 11:29:28 +01:00
type store struct {
*storage.Storage
databaseExisted bool
}
2025-11-18 11:29:28 +01:00
type storeOptions struct {
expectsNoDatabase bool
allowsNoDatabase bool
}
2025-11-18 11:29:28 +01:00
type cobraFunc func(cmd *cobra.Command, args []string) error
2025-11-18 11:29:28 +01:00
// withViperAndStore initializes Viper and the storage.Store and passes them to the callback function.
// This function should only be used by [withStore] and the root command. No other command should call
// this function directly.
func withViperAndStore(fn func(cmd *cobra.Command, args []string, v *viper.Viper, store *store) error, options storeOptions) cobraFunc {
return func(cmd *cobra.Command, args []string) error {
v, err := initViper(cmd)
if err != nil {
return err
}
2025-11-18 11:29:28 +01:00
path, err := filepath.Abs(v.GetString("database"))
if err != nil {
return err
}
2019-01-07 20:24:23 +00:00
exists, err := dbExists(path)
2025-11-20 07:56:56 +01:00
switch {
case err != nil:
return err
2025-11-20 07:56:56 +01:00
case exists && options.expectsNoDatabase:
2025-11-18 11:29:28 +01:00
log.Fatal(path + " already exists")
2025-11-20 07:56:56 +01:00
case !exists && !options.expectsNoDatabase && !options.allowsNoDatabase:
2025-11-18 11:29:28 +01:00
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
2025-11-20 07:56:56 +01:00
case !exists && !options.expectsNoDatabase:
2025-11-18 11:29:28 +01:00
log.Println("WARNING: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(path, "filebrowser.db"))
2019-01-07 20:24:23 +00:00
}
2025-11-18 11:29:28 +01:00
log.Println("Using database: " + path)
db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil))
if err != nil {
return err
}
2019-01-07 20:24:23 +00:00
defer db.Close()
2025-11-18 11:29:28 +01:00
storage, err := bolt.NewStorage(db)
if err != nil {
return err
}
2025-11-18 11:29:28 +01:00
store := &store{
Storage: storage,
databaseExisted: exists,
}
return fn(cmd, args, v, store)
2019-01-07 20:24:23 +00:00
}
}
2019-01-08 08:57:24 +00:00
2025-11-18 11:29:28 +01:00
func withStore(fn func(cmd *cobra.Command, args []string, store *store) error, options storeOptions) cobraFunc {
2025-11-20 07:56:56 +01:00
return withViperAndStore(func(cmd *cobra.Command, args []string, _ *viper.Viper, store *store) error {
2025-11-18 11:29:28 +01:00
return fn(cmd, args, store)
}, options)
}
2019-01-08 08:57:24 +00:00
func marshal(filename string, data interface{}) error {
fd, err := os.Create(filename)
if err != nil {
return err
}
2019-01-08 08:57:24 +00:00
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
2025-06-27 08:03:11 +02:00
case ".json":
2019-01-08 08:57:24 +00:00
encoder := json.NewEncoder(fd)
encoder.SetIndent("", " ")
return encoder.Encode(data)
2025-06-27 08:03:11 +02:00
case ".yml", ".yaml":
2019-01-08 08:57:24 +00:00
encoder := yaml.NewEncoder(fd)
return encoder.Encode(data)
default:
return errors.New("invalid format: " + ext)
}
}
func unmarshal(filename string, data interface{}) error {
fd, err := os.Open(filename)
if err != nil {
return err
}
2019-01-08 08:57:24 +00:00
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
case ".json":
return json.NewDecoder(fd).Decode(data)
case ".yml", ".yaml":
return yaml.NewDecoder(fd).Decode(data)
default:
return errors.New("invalid format: " + ext)
}
}
func jsonYamlArg(cmd *cobra.Command, args []string) error {
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
return err
}
switch ext := filepath.Ext(args[0]); ext {
case ".json", ".yml", ".yaml":
return nil
default:
return errors.New("invalid format: " + ext)
}
}
2020-10-05 00:52:27 -07:00
// convertCmdStrToCmdArray checks if cmd string is blank (whitespace included)
2024-09-23 11:55:07 +02:00
// then returns empty string array, else returns the split word array of cmd.
2020-10-05 00:52:27 -07:00
// This is to ensure the result will never be []string{""}
func convertCmdStrToCmdArray(cmd string) []string {
var cmdArray []string
trimmedCmdStr := strings.TrimSpace(cmd)
if trimmedCmdStr != "" {
cmdArray = strings.Split(trimmedCmdStr, " ")
}
return cmdArray
}