filebrowser/backend/cmd/root.go

237 lines
7.3 KiB
Go
Raw Normal View History

package cmd
import (
2025-01-31 20:26:21 +00:00
"context"
2023-09-03 22:03:00 +00:00
"flag"
2024-07-30 17:45:27 +00:00
"fmt"
"os"
2025-01-31 20:26:21 +00:00
"os/signal"
2024-10-07 22:44:53 +00:00
"strings"
2025-01-31 20:26:21 +00:00
"syscall"
2024-08-04 17:50:35 +00:00
2024-12-17 00:01:55 +00:00
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
"github.com/gtsteffaniak/filebrowser/backend/files"
fbhttp "github.com/gtsteffaniak/filebrowser/backend/http"
"github.com/gtsteffaniak/filebrowser/backend/img"
2025-01-21 14:02:43 +00:00
"github.com/gtsteffaniak/filebrowser/backend/logger"
2024-12-17 00:01:55 +00:00
"github.com/gtsteffaniak/filebrowser/backend/settings"
"github.com/gtsteffaniak/filebrowser/backend/storage"
"github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
2024-11-21 00:15:30 +00:00
"github.com/swaggo/swag"
2024-12-17 00:01:55 +00:00
"github.com/gtsteffaniak/filebrowser/backend/users"
"github.com/gtsteffaniak/filebrowser/backend/version"
)
2024-10-07 22:44:53 +00:00
func getStore(config string) (*storage.Storage, bool) {
// Use the config file (global flag)
settings.Initialize(config)
store, hasDB, err := storage.InitializeDb(settings.Config.Server.Database)
if err != nil {
2025-01-21 14:02:43 +00:00
logger.Fatal(fmt.Sprintf("could not load db info: %v", err))
2024-10-07 22:44:53 +00:00
}
return store, hasDB
}
2024-10-07 22:44:53 +00:00
func generalUsage() {
2025-01-05 19:05:33 +00:00
fmt.Printf(`usage: ./filebrowser <command> [options]
2025-01-21 14:02:43 +00:00
commands:
-h Print help
-c Print the default config file
version Print version information
set -u Username and password for the new user
set -a Create user as admin
set -s Specify a user scope
set -h Print this help message
`)
2024-10-07 22:44:53 +00:00
}
2024-08-24 22:02:33 +00:00
2024-10-07 22:44:53 +00:00
func StartFilebrowser() {
// Global flags
var configPath string
var help bool
// Override the default usage output to use generalUsage()
flag.Usage = generalUsage
2024-12-17 00:01:55 +00:00
flag.StringVar(&configPath, "c", "config.yaml", "Path to the config file, default: config.yaml")
2024-10-07 22:44:53 +00:00
flag.BoolVar(&help, "h", false, "Get help about commands")
// Parse global flags (before subcommands)
flag.Parse() // print generalUsage on error
// Show help if requested
if help {
generalUsage()
return
}
2024-08-24 22:02:33 +00:00
2024-10-07 22:44:53 +00:00
// Create a new FlagSet for the 'set' subcommand
setCmd := flag.NewFlagSet("set", flag.ExitOnError)
var user, scope, dbConfig string
var asAdmin bool
setCmd.StringVar(&user, "u", "", "Comma-separated username and password: \"set -u <username>,<password>\"")
setCmd.BoolVar(&asAdmin, "a", false, "Create user as admin user, used in combination with -u")
setCmd.StringVar(&scope, "s", "", "Specify a user scope, otherwise default user config scope is used")
2024-12-17 00:01:55 +00:00
setCmd.StringVar(&dbConfig, "c", "config.yaml", "Path to the config file, default: config.yaml")
2024-10-07 22:44:53 +00:00
2025-01-31 20:26:21 +00:00
// Create context and channels for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan struct{}) // Signals server has stopped
shutdownComplete := make(chan struct{}) // Signals shutdown process is complete
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
2024-10-07 22:44:53 +00:00
// Parse subcommand flags only if a subcommand is specified
if len(os.Args) > 1 {
switch os.Args[1] {
case "set":
2025-02-01 13:10:46 +00:00
err := setCmd.Parse(os.Args[2:])
2024-08-24 22:02:33 +00:00
if err != nil {
2024-10-07 22:44:53 +00:00
setCmd.PrintDefaults()
os.Exit(1)
2020-07-27 17:01:02 +00:00
}
2024-10-07 22:44:53 +00:00
userInfo := strings.Split(user, ",")
if len(userInfo) < 2 {
2025-02-01 13:10:46 +00:00
fmt.Printf("not enough info to create user: \"set -u username,password\", only provided %v\n", userInfo)
2024-10-07 22:44:53 +00:00
setCmd.PrintDefaults()
os.Exit(1)
2024-08-04 17:50:35 +00:00
}
2024-10-07 22:44:53 +00:00
username := userInfo[0]
password := userInfo[1]
2025-02-01 13:10:46 +00:00
store, ok := getStore(dbConfig)
if !ok {
logger.Fatal("could not load db info")
2024-08-04 17:50:35 +00:00
}
2025-02-01 13:10:46 +00:00
user, err := store.Users.Get("", username)
if err != nil {
newUser := users.User{
Username: username,
Password: password,
}
if scope != "" {
newUser.Scope = scope
} else {
newUser.Scope = settings.Config.UserDefaults.Scope
}
// Create the user logic
if asAdmin {
logger.Info(fmt.Sprintf("Creating user as admin: %s\n", username))
} else {
logger.Info(fmt.Sprintf("Creating non-admin user: %s\n", username))
}
err = storage.CreateUser(newUser, asAdmin)
if err != nil {
logger.Error(fmt.Sprintf("could not create user: %v", err))
}
return
2024-08-04 17:50:35 +00:00
}
2025-02-01 13:10:46 +00:00
user.Password = password
2024-10-07 22:44:53 +00:00
if scope != "" {
2025-02-01 13:10:46 +00:00
user.Scope = scope
}
if asAdmin {
user.Perm.Admin = true
2024-10-07 22:44:53 +00:00
}
2025-02-01 13:10:46 +00:00
err = store.Users.Save(user)
2024-10-07 22:44:53 +00:00
if err != nil {
2025-02-01 13:10:46 +00:00
logger.Error(fmt.Sprintf("could not update user: %v", err))
2024-10-07 22:44:53 +00:00
}
2025-02-01 13:10:46 +00:00
fmt.Printf("successfully updated user: %s\n", username)
2024-10-07 22:44:53 +00:00
return
2025-02-01 13:10:46 +00:00
2024-10-07 22:44:53 +00:00
case "version":
2025-01-21 14:02:43 +00:00
fmt.Printf(`FileBrowser Quantum - A modern web-based file manager
Version : %v
Commit : %v
Release Info : https://github.com/gtsteffaniak/filebrowser/releases/tag/%v
`, version.Version, version.CommitSHA, version.Version)
2024-10-07 22:44:53 +00:00
return
}
2024-10-07 22:44:53 +00:00
}
store, dbExists := getStore(configPath)
database := fmt.Sprintf("Using existing database : %v", settings.Config.Server.Database)
if !dbExists {
database = fmt.Sprintf("Creating new database : %v", settings.Config.Server.Database)
}
2025-01-05 19:05:33 +00:00
sources := []string{}
for _, v := range settings.Config.Server.Sources {
sources = append(sources, v.Name+": "+v.Path)
}
2025-01-31 20:26:21 +00:00
authMethods := []string{}
2025-02-01 13:10:46 +00:00
if settings.Config.Auth.Methods.PasswordAuth.Enabled {
2025-01-31 20:26:21 +00:00
authMethods = append(authMethods, "Password")
}
if settings.Config.Auth.Methods.ProxyAuth.Enabled {
authMethods = append(authMethods, "Proxy")
}
if settings.Config.Auth.Methods.NoAuth {
logger.Warning("Configured with no authentication, this is not recommended.")
authMethods = []string{"Disabled"}
}
2025-01-21 14:02:43 +00:00
logger.Info(fmt.Sprintf("Initializing FileBrowser Quantum (%v)", version.Version))
logger.Info(fmt.Sprintf("Using Config file : %v", configPath))
2025-01-31 20:26:21 +00:00
logger.Info(fmt.Sprintf("Auth Methods : %v", authMethods))
2025-01-21 14:02:43 +00:00
logger.Info(database)
logger.Info(fmt.Sprintf("Sources : %v", sources))
2024-10-07 22:44:53 +00:00
serverConfig := settings.Config.Server
2024-11-21 00:15:30 +00:00
swagInfo := docs.SwaggerInfo
swagInfo.BasePath = serverConfig.BaseURL
swag.Register(docs.SwaggerInfo.InstanceName(), swagInfo)
2024-10-07 22:44:53 +00:00
// initialize indexing and schedule indexing ever n minutes (default 5)
2025-01-05 19:05:33 +00:00
sourceConfigs := settings.Config.Server.Sources
if len(sourceConfigs) == 0 {
2025-01-21 14:02:43 +00:00
logger.Fatal("No sources configured, exiting...")
2025-01-05 19:05:33 +00:00
}
for _, source := range sourceConfigs {
go files.Initialize(source)
}
2025-01-31 20:26:21 +00:00
// Start the rootCMD in a goroutine
go func() {
if err := rootCMD(ctx, store, &serverConfig, shutdownComplete); err != nil {
logger.Fatal(fmt.Sprintf("Error starting filebrowser: %v", err))
}
close(done) // Signal that the server has stopped
}()
// Wait for a shutdown signal or the server to stop
select {
case <-signalChan:
logger.Info("Received shutdown signal. Shutting down gracefully...")
cancel() // Trigger context cancellation
case <-done:
logger.Info("Server stopped unexpectedly. Shutting down...")
2023-09-02 16:05:40 +00:00
}
2025-01-31 20:26:21 +00:00
<-shutdownComplete // Ensure we don't exit prematurely
// Wait for the server to stop
logger.Info("Shutdown complete.")
2023-09-02 16:05:40 +00:00
}
2025-01-31 20:26:21 +00:00
func rootCMD(ctx context.Context, store *storage.Storage, serverConfig *settings.Server, shutdownComplete chan struct{}) error {
2024-10-07 22:44:53 +00:00
if serverConfig.NumImageProcessors < 1 {
2025-01-21 14:02:43 +00:00
logger.Fatal("Image resize workers count could not be < 1")
2024-10-07 22:44:53 +00:00
}
imgSvc := img.New(serverConfig.NumImageProcessors)
2025-01-31 20:26:21 +00:00
cacheDir := settings.Config.Server.CacheDir
2024-10-07 22:44:53 +00:00
var fileCache diskcache.Interface
// Use file cache if cacheDir is specified
if cacheDir != "" {
var err error
fileCache, err = diskcache.NewFileCache(cacheDir)
if err != nil {
2025-01-21 14:02:43 +00:00
logger.Fatal(fmt.Sprintf("failed to create file cache: %v", err))
2024-10-07 22:44:53 +00:00
}
2023-09-02 00:22:38 +00:00
} else {
2024-10-07 22:44:53 +00:00
// No-op cache if no cacheDir is specified
fileCache = diskcache.NewNoOp()
}
2025-01-31 20:26:21 +00:00
fbhttp.StartHttp(ctx, imgSvc, store, fileCache, shutdownComplete)
2024-10-07 22:44:53 +00:00
return nil
}