Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d759ab0bd8 | ||
|
|
00323a8f37 | ||
|
|
420adea7e6 | ||
|
|
f576d38a7e | ||
|
|
9bdc67c207 | ||
|
|
f41585f039 | ||
|
|
89be0b1873 | ||
|
|
8c5dc7641e | ||
|
|
0a0cb8046f | ||
|
|
f89435c068 | ||
|
|
fb8d41eb9a | ||
|
|
0fadaccaa2 | ||
|
|
e24e1f1aba | ||
|
|
5de4099cba | ||
|
|
d01493106d | ||
|
|
2d9689dd6a | ||
|
|
c4c1cea230 | ||
|
|
ceb5e723f3 | ||
|
|
ebc7d2303d | ||
|
|
23c4e4565b | ||
|
|
17f1e08a58 | ||
|
|
ffc850454e | ||
|
|
13814e1119 | ||
|
|
4e9e312984 | ||
|
|
ce3b407c51 | ||
|
|
fb5d099f85 | ||
|
|
1ace579a55 | ||
|
|
ac7b49c148 | ||
|
|
9d44932dba | ||
|
|
0d973d3aad | ||
|
|
cacc0999e9 | ||
|
|
42d1b6f3ae | ||
|
|
bb10c3dfa9 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -97,7 +97,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install Task
|
||||
uses: go-task/setup-task@v1
|
||||
- run: task build-frontend
|
||||
- run: task build:frontend
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
125
.golangci.yml
125
.golangci.yml
@@ -1,132 +1,9 @@
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
# inverted configuration with `default: all` and `disable` is not scalable during updates of golangci-lint
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoinits
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godox
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- mnd
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
- shadow
|
||||
lll:
|
||||
line-length: 140
|
||||
misspell:
|
||||
locale: US
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- return
|
||||
ignored-numbers:
|
||||
- "0"
|
||||
- "1"
|
||||
- "2"
|
||||
- "3"
|
||||
- "0666"
|
||||
- "0700"
|
||||
- "0700"
|
||||
ignored-functions:
|
||||
- strings.SplitN
|
||||
- make
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # require an explanation for nolint directives
|
||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
||||
staticcheck:
|
||||
checks:
|
||||
- "all"
|
||||
- "-QF*"
|
||||
default: standard
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- gochecknoinits
|
||||
path: cmd/.*.go
|
||||
- linters:
|
||||
- dupl
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- gocyclo
|
||||
- lll
|
||||
- scopelint
|
||||
path: .*_test.go
|
||||
- linters:
|
||||
- misspell
|
||||
text: "[aA]uther"
|
||||
- linters:
|
||||
- mnd
|
||||
text: strconv.Parse
|
||||
paths:
|
||||
- frontend/
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/filebrowser/filebrowser
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- frontend/
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -2,8 +2,66 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||
|
||||
## [2.48.1](https://github.com/filebrowser/filebrowser/compare/v2.48.0...v2.48.1) (2025-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* options should only override if set ([420adea](https://github.com/filebrowser/filebrowser/commit/420adea7e61a1c182cddd6fb2544a0752e5709f7))
|
||||
|
||||
## [2.48.0](https://github.com/filebrowser/filebrowser/compare/v2.47.0...v2.48.0) (2025-11-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* consistent flags and environment variables ([#5549](https://github.com/filebrowser/filebrowser/issues/5549)) ([0a0cb80](https://github.com/filebrowser/filebrowser/commit/0a0cb8046fce52f1ff926171b34bcdb7cd39aab3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add tokenExpirationTime to `config init` and troubleshoot docs ([#5546](https://github.com/filebrowser/filebrowser/issues/5546)) ([8c5dc76](https://github.com/filebrowser/filebrowser/commit/8c5dc7641e6f8aadd9e5d5d3b25a2ad9f1ec9a1e))
|
||||
* use all available flags in quick setup ([f41585f](https://github.com/filebrowser/filebrowser/commit/f41585f0392d65c08c01ab65b62d3eeb04c03b7d))
|
||||
|
||||
|
||||
### Refactorings
|
||||
|
||||
* reuse logic for config init and set ([89be0b1](https://github.com/filebrowser/filebrowser/commit/89be0b1873527987dd2dddac746e93b8bc684d46))
|
||||
|
||||
## [2.47.0](https://github.com/filebrowser/filebrowser/compare/v2.46.1...v2.47.0) (2025-11-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add TUS settings to the command line ([#5556](https://github.com/filebrowser/filebrowser/issues/5556)) ([e24e1f1](https://github.com/filebrowser/filebrowser/commit/e24e1f1abae9e80add620c4ad65660ca1b575a49))
|
||||
* remove importer of v1 config ([#5550](https://github.com/filebrowser/filebrowser/issues/5550)) ([ceb5e72](https://github.com/filebrowser/filebrowser/commit/ceb5e723f3ee2c966bb561a804015246450280ca))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exit 0 when gracefully shutting down ([#5555](https://github.com/filebrowser/filebrowser/issues/5555)) ([5de4099](https://github.com/filebrowser/filebrowser/commit/5de4099cba2cf012d4a213c8eb29c412fc72c151))
|
||||
|
||||
## [2.46.1](https://github.com/filebrowser/filebrowser/compare/v2.46.0...v2.46.1) (2025-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* env key replacer and remove unused function ([#5547](https://github.com/filebrowser/filebrowser/issues/5547)) ([13814e1](https://github.com/filebrowser/filebrowser/commit/13814e11197ebd9101940883e3ca85998f86d442))
|
||||
* remove duplicated 'hide-defaults' flag (is 'hideDefaults') ([#5548](https://github.com/filebrowser/filebrowser/issues/5548)) ([ffc8504](https://github.com/filebrowser/filebrowser/commit/ffc850454e4cb8f10b970511681d6c627340afc7))
|
||||
|
||||
## [2.46.0](https://github.com/filebrowser/filebrowser/compare/v2.45.3...v2.46.0) (2025-11-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add 'hide-dotfiles' as command line parameter ([#3802](https://github.com/filebrowser/filebrowser/issues/3802)) ([0d973d3](https://github.com/filebrowser/filebrowser/commit/0d973d3aad70ceb88950f2cd9c297fc76e7955b1))
|
||||
* add context menu ([#3343](https://github.com/filebrowser/filebrowser/issues/3343)) ([1ace579](https://github.com/filebrowser/filebrowser/commit/1ace579a553486bb15af2d11f537414156606434))
|
||||
* add option to hide the login button from public-facing pages ([#3922](https://github.com/filebrowser/filebrowser/issues/3922)) ([ac7b49c](https://github.com/filebrowser/filebrowser/commit/ac7b49c1484b4e27a1149310542ccd1e90659ee2))
|
||||
* Updates for project File Browser ([#5544](https://github.com/filebrowser/filebrowser/issues/5544)) ([fb5d099](https://github.com/filebrowser/filebrowser/commit/fb5d099f8514516216f407be012d2e3f25de2441))
|
||||
|
||||
## [2.45.3](https://github.com/filebrowser/filebrowser/compare/v2.45.2...v2.45.3) (2025-11-13)
|
||||
|
||||
This is a test release to ensure the updated workflow works.
|
||||
|
||||
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ task docs
|
||||
To start a local server on port `8000` to view the built documentation:
|
||||
|
||||
```bash
|
||||
task docs-serve
|
||||
task docs:serve
|
||||
```
|
||||
|
||||
## Release
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
||||
</p>
|
||||
|
||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/ci.yaml)
|
||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||
|
||||
@@ -19,7 +19,7 @@ This project is a finished product which fulfills its goal: be a single binary w
|
||||
- It can take a while until someone gets back to you. Please be patient.
|
||||
- [Issues](https://github.com/filebrowser/filebrowser/issues) are meant to track bugs. Unrelated issues will be converted into [discussions](https://github.com/filebrowser/filebrowser/discussions).
|
||||
- No new features will be implemented by maintainers. Pull requests for new features will be reviewed on a case by case basis.
|
||||
- The priority is triaging issues, addressing security issues, and fixing bug fixes.
|
||||
- The priority is triaging issues, addressing security issues and reviewing pull requests meant to solve bugs.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
38
Taskfile.yml
38
Taskfile.yml
@@ -10,14 +10,14 @@ vars:
|
||||
-v ./CONTRIBUTING.md:/docs/docs/contributing.md
|
||||
|
||||
tasks:
|
||||
build-frontend:
|
||||
build:frontend:
|
||||
desc: Build frontend assets
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm install --frozen-lockfile
|
||||
- pnpm run build
|
||||
|
||||
build-backend:
|
||||
build:backend:
|
||||
desc: Build backend binary
|
||||
cmds:
|
||||
- go build -ldflags='-s -w -X "github.com/filebrowser/filebrowser/v2/version.Version={{.VERSION}}" -X "github.com/filebrowser/filebrowser/v2/version.CommitSHA={{.GIT_COMMIT}}"' -o filebrowser .
|
||||
@@ -30,16 +30,16 @@ tasks:
|
||||
build:
|
||||
desc: Build both frontend and backend
|
||||
cmds:
|
||||
- task: build-frontend
|
||||
- task: build-backend
|
||||
- task: build:frontend
|
||||
- task: build:backend
|
||||
|
||||
release-make:
|
||||
release:make:
|
||||
internal: true
|
||||
prompt: Do you wish to proceed?
|
||||
cmds:
|
||||
- pnpm dlx commit-and-tag-version -s
|
||||
|
||||
release-dry-run:
|
||||
release:dry-run:
|
||||
internal: true
|
||||
cmds:
|
||||
- pnpm dlx commit-and-tag-version --dry-run --skip
|
||||
@@ -47,10 +47,24 @@ tasks:
|
||||
release:
|
||||
desc: Create a new release
|
||||
cmds:
|
||||
- task: release-dry-run
|
||||
- task: release-make
|
||||
- task: docs:cli:generate
|
||||
- git add www/docs/cli
|
||||
- |
|
||||
if [[ `git status www/docs/cli --porcelain` ]]; then
|
||||
git commit -m 'chore(docs): update CLI documentation'
|
||||
fi
|
||||
- task: release:dry-run
|
||||
- task: release:make
|
||||
|
||||
docs-image-make:
|
||||
docs:cli:generate:
|
||||
cmds:
|
||||
- rm -rf www/docs/cli
|
||||
- mkdir -p www/docs/cli
|
||||
- go run . docs
|
||||
generates:
|
||||
- www/docs/cli
|
||||
|
||||
docs:docker:generate:
|
||||
internal: true
|
||||
cmds:
|
||||
- docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||
@@ -59,11 +73,11 @@ tasks:
|
||||
desc: Generate documentation
|
||||
cmds:
|
||||
- rm -rf www/public
|
||||
- task: docs-image-make
|
||||
- task: docs:docker:generate
|
||||
- docker run --rm {{.SITE_DOCKER_FLAGS}} filebrowser.site build -d "public"
|
||||
|
||||
docs-serve:
|
||||
docs:serve:
|
||||
desc: Serve documentation
|
||||
cmds:
|
||||
- task: docs-image-make
|
||||
- task: docs:docker:generate
|
||||
- docker run --rm -it -p 8000:8000 {{.SITE_DOCKER_FLAGS}} filebrowser.site
|
||||
|
||||
@@ -103,7 +103,7 @@ func (a *HookAuth) RunCommand() (string, error) {
|
||||
command[i] = os.Expand(arg, envMapping)
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", a.Cred.Password))
|
||||
out, err := cmd.Output()
|
||||
|
||||
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store, _ *settings.Settings, s
|
||||
|
||||
// If ReCaptcha is enabled, check the code.
|
||||
if a.ReCaptcha != nil && a.ReCaptcha.Secret != "" {
|
||||
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
|
||||
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
35
cmd/cmd_test.go
Normal file
35
cmd/cmd_test.go
Normal file
@@ -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{}),
|
||||
}
|
||||
|
||||
166
cmd/config.go
166
cmd/config.go
@@ -30,11 +30,18 @@ var configCmd = &cobra.Command{
|
||||
func addConfigFlags(flags *pflag.FlagSet) {
|
||||
addServerFlags(flags)
|
||||
addUserFlags(flags)
|
||||
|
||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||
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")
|
||||
|
||||
// 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("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.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||
flags.String("auth.command", "", "command for auth.method=hook")
|
||||
@@ -49,14 +56,13 @@ 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.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
|
||||
}
|
||||
@@ -87,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
|
||||
}
|
||||
@@ -109,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
|
||||
}
|
||||
@@ -145,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)
|
||||
}
|
||||
@@ -192,10 +199,12 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
|
||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
||||
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)
|
||||
@@ -203,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)
|
||||
@@ -212,9 +222,19 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
||||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||
fmt.Fprintf(w, "\tToken Expiration Time:\t%s\n", ser.TokenExpirationTime)
|
||||
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)
|
||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
||||
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
||||
@@ -222,9 +242,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)
|
||||
@@ -234,6 +256,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, "", " ")
|
||||
@@ -243,3 +266,118 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSettings(flags *pflag.FlagSet, set *settings.Settings, ser *settings.Server, auther auth.Auther, all bool) (auth.Auther, error) {
|
||||
errs := []error{}
|
||||
hasAuth := false
|
||||
|
||||
visit := func(flag *pflag.Flag) {
|
||||
var err error
|
||||
|
||||
switch flag.Name {
|
||||
// Server flags from [addServerFlags]
|
||||
case "address":
|
||||
ser.Address, err = flags.GetString(flag.Name)
|
||||
case "log":
|
||||
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 = 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 = flags.GetString(flag.Name)
|
||||
if err == nil {
|
||||
set.Shell = convertCmdStrToCmdArray(shell)
|
||||
}
|
||||
case "fileMode":
|
||||
set.FileMode, err = getAndParseFileMode(flags, flag.Name)
|
||||
case "dirMode":
|
||||
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "branding.name":
|
||||
set.Branding.Name, err = flags.GetString(flag.Name)
|
||||
case "branding.theme":
|
||||
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 = 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 "tus.chunkSize":
|
||||
set.Tus.ChunkSize, err = flags.GetUint64(flag.Name)
|
||||
case "tus.retryCount":
|
||||
set.Tus.RetryCount, err = flags.GetUint16(flag.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if all {
|
||||
flags.VisitAll(visit)
|
||||
} else {
|
||||
flags.Visit(visit)
|
||||
}
|
||||
|
||||
err := nerrors.Join(errs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = getUserDefaults(flags, &set.Defaults, all)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if all {
|
||||
set.AuthMethod, auther, err = getAuthentication(flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return auther, nil
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -23,150 +23,29 @@ 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)
|
||||
|
||||
// Initialize config
|
||||
s := &settings.Settings{Key: generateKey()}
|
||||
ser := &settings.Server{}
|
||||
|
||||
// Fill config with options
|
||||
auther, err := getSettings(flags, s, ser, nil, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := generateKey()
|
||||
|
||||
signup, err := getBool(flags, "signup")
|
||||
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
|
||||
}
|
||||
|
||||
s := &settings.Settings{
|
||||
Key: key,
|
||||
Signup: signup,
|
||||
CreateUserDir: createUserDir,
|
||||
MinimumPasswordLength: minLength,
|
||||
Shell: convertCmdStrToCmdArray(shell),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Branding: settings.Branding{
|
||||
Name: brandingName,
|
||||
DisableExternal: brandingDisableExternal,
|
||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
||||
Theme: brandingTheme,
|
||||
Files: brandingFiles,
|
||||
},
|
||||
}
|
||||
|
||||
s.FileMode, err = getMode(flags, "file-mode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DirMode, err = getMode(flags, "dir-mode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address, err := getString(flags, "address")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socket, err := getString(flags, "socket")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err := getString(flags, "root")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseURL, err := getString(flags, "baseurl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsKey, err := getString(flags, "key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert, err := getString(flags, "cert")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := getString(flags, "port")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log, err := getString(flags, "log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
Address: address,
|
||||
Socket: socket,
|
||||
Root: root,
|
||||
BaseURL: baseURL,
|
||||
TLSKey: tlsKey,
|
||||
TLSCert: cert,
|
||||
Port: port,
|
||||
Log: log,
|
||||
}
|
||||
|
||||
// Save updated config
|
||||
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
|
||||
@@ -178,5 +57,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}),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -18,6 +17,8 @@ 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()
|
||||
|
||||
// Read existing config
|
||||
set, err := d.store.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -28,88 +29,28 @@ you want to change. Other options will remain unchanged.`,
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
case "address":
|
||||
ser.Address, err = getString(flags, flag.Name)
|
||||
case "port":
|
||||
ser.Port, err = getString(flags, flag.Name)
|
||||
case "log":
|
||||
ser.Log, err = getString(flags, flag.Name)
|
||||
case "signup":
|
||||
set.Signup, err = getBool(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "shell":
|
||||
var shell string
|
||||
shell, err = getString(flags, flag.Name)
|
||||
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 "branding.name":
|
||||
set.Branding.Name, err = getString(flags, flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color, err = getString(flags, 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)
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = getUserDefaults(flags, &set.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read the defaults
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if there are new flags for existing auth method
|
||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
||||
// Get updated config
|
||||
auther, err = getSettings(flags, set, ser, auther, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save updated config
|
||||
err = d.store.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.Save(set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
158
cmd/docs.go
158
cmd/docs.go
@@ -3,36 +3,18 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
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)
|
||||
docsCmd.Flags().String("out", "www/docs/cli", "directory to write the docs to")
|
||||
}
|
||||
|
||||
var docsCmd = &cobra.Command{
|
||||
@@ -40,115 +22,61 @@ var docsCmd = &cobra.Command{
|
||||
Hidden: true,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
dir, err := getString(cmd.Flags(), "path")
|
||||
outputDir, err := cmd.Flags().GetString("out")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = generateDocs(rootCmd, dir)
|
||||
tempDir, err := os.MkdirTemp(os.TempDir(), "filebrowser-docs-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names := []string{}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
err = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return err
|
||||
}
|
||||
rootCmd.Root().DisableAutoGenTag = true
|
||||
|
||||
if !strings.HasPrefix(info.Name(), "filebrowser") {
|
||||
return nil
|
||||
}
|
||||
|
||||
names = append(names, info.Name())
|
||||
return nil
|
||||
err = doc.GenMarkdownTreeCustom(cmd.Root(), tempDir, func(f string) string {
|
||||
return ""
|
||||
}, func(s string) string {
|
||||
return s
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printToc(names)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func generateDocs(cmd *cobra.Command, dir string) error {
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
||||
continue
|
||||
}
|
||||
|
||||
err := generateDocs(c, dir)
|
||||
entries, err := os.ReadDir(tempDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
||||
filename := filepath.Join(dir, basename)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return generateMarkdown(cmd, f)
|
||||
}
|
||||
|
||||
func generateMarkdown(cmd *cobra.Command, w io.Writer) error {
|
||||
cmd.InitDefaultHelpCmd()
|
||||
cmd.InitDefaultHelpFlag()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
name := cmd.CommandPath()
|
||||
|
||||
short := cmd.Short
|
||||
long := cmd.Long
|
||||
if long == "" {
|
||||
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() {
|
||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.UseLine())
|
||||
}
|
||||
|
||||
if cmd.Example != "" {
|
||||
buf.WriteString("## Examples\n\n")
|
||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.Example)
|
||||
}
|
||||
|
||||
printOptions(buf, cmd)
|
||||
_, err := buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
||||
_, _ = buf.WriteString("| Name | Shorthand | Usage |\n")
|
||||
_, _ = buf.WriteString("|------|-----------|-------|\n")
|
||||
|
||||
fs.VisitAll(func(f *pflag.Flag) {
|
||||
_, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
|
||||
})
|
||||
}
|
||||
|
||||
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
|
||||
flags := cmd.NonInheritedFlags()
|
||||
flags.SetOutput(buf)
|
||||
if flags.HasAvailableFlags() {
|
||||
buf.WriteString("## Options\n\n")
|
||||
generateFlagsTable(flags, buf)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
parentFlags := cmd.InheritedFlags()
|
||||
parentFlags.SetOutput(buf)
|
||||
if parentFlags.HasAvailableFlags() {
|
||||
buf.WriteString("### Inherited\n\n")
|
||||
generateFlagsTable(parentFlags, buf)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
headerRegex := regexp.MustCompile(`(?m)^(##)(.*)$`)
|
||||
linkRegex := regexp.MustCompile(`\(filebrowser(.*)\.md\)`)
|
||||
|
||||
fmt.Println("Generated Documents:")
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := path.Join(tempDir, entry.Name())
|
||||
dstPath := path.Join(outputDir, strings.ReplaceAll(entry.Name(), "_", "-"))
|
||||
|
||||
data, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = headerRegex.ReplaceAll(data, []byte("#$2"))
|
||||
data = linkRegex.ReplaceAllFunc(data, func(b []byte) []byte {
|
||||
return bytes.ReplaceAll(b, []byte("_"), []byte("-"))
|
||||
})
|
||||
data = bytes.ReplaceAll(data, []byte("## SEE ALSO"), []byte("## See Also"))
|
||||
|
||||
err = os.WriteFile(dstPath, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("- " + dstPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
362
cmd/root.go
362
cmd/root.go
@@ -13,20 +13,17 @@ 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"
|
||||
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/frontend"
|
||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||
"github.com/filebrowser/filebrowser/v2/img"
|
||||
@@ -36,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")
|
||||
@@ -66,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") //nolint:mnd
|
||||
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{
|
||||
@@ -89,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/
|
||||
@@ -102,52 +137,40 @@ 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 { //nolint:govet
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil {
|
||||
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -169,16 +192,13 @@ user created with the credentials from options "username" and "password".`,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
socketPerm := d.viper.GetUint32("socketPerm")
|
||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case server.TLSKey != "" && server.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
|
||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,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
|
||||
}
|
||||
@@ -233,7 +253,7 @@ user created with the credentials from options "username" and "password".`,
|
||||
sig := <-sigc
|
||||
log.Println("Got signal:", sig)
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
@@ -241,66 +261,74 @@ user created with the credentials from options "username" and "password".`,
|
||||
}
|
||||
log.Println("Graceful shutdown complete.")
|
||||
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
d.err = fbErrors.ErrSighup
|
||||
case syscall.SIGINT:
|
||||
d.err = fbErrors.ErrSigint
|
||||
case syscall.SIGQUIT:
|
||||
d.err = fbErrors.ErrSigquit
|
||||
case syscall.SIGTERM:
|
||||
d.err = fbErrors.ErrSigTerm
|
||||
}
|
||||
|
||||
return d.err
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
return nil
|
||||
}, pythonConfig{allowsNoDatabase: true}),
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
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 {
|
||||
server.Root = val
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "baseurl"); set {
|
||||
server.BaseURL = val
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "log"); set {
|
||||
server.Log = val
|
||||
}
|
||||
|
||||
isSocketSet := false
|
||||
isAddrSet := false
|
||||
|
||||
if val, set := getStringParamB(flags, "address"); set {
|
||||
server.Address = val
|
||||
isAddrSet = isAddrSet || set
|
||||
if v.IsSet("address") {
|
||||
server.Address = v.GetString("address")
|
||||
isAddrSet = true
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "port"); set {
|
||||
server.Port = val
|
||||
isAddrSet = isAddrSet || set
|
||||
if v.IsSet("log") {
|
||||
server.Log = v.GetString("log")
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "key"); set {
|
||||
server.TLSKey = val
|
||||
isAddrSet = isAddrSet || set
|
||||
if v.IsSet("port") {
|
||||
server.Port = v.GetString("port")
|
||||
isAddrSet = true
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "cert"); set {
|
||||
server.TLSCert = val
|
||||
isAddrSet = isAddrSet || set
|
||||
if v.IsSet("cert") {
|
||||
server.TLSCert = v.GetString("cert")
|
||||
isAddrSet = true
|
||||
}
|
||||
|
||||
if val, set := getStringParamB(flags, "socket"); set {
|
||||
server.Socket = val
|
||||
isSocketSet = isSocketSet || set
|
||||
if v.IsSet("key") {
|
||||
server.TLSKey = v.GetString("key")
|
||||
isAddrSet = true
|
||||
}
|
||||
|
||||
if v.IsSet("root") {
|
||||
server.Root = v.GetString("root")
|
||||
}
|
||||
|
||||
if v.IsSet("socket") {
|
||||
server.Socket = v.GetString("socket")
|
||||
isSocketSet = true
|
||||
}
|
||||
|
||||
if v.IsSet("baseURL") {
|
||||
server.BaseURL = v.GetString("baseURL")
|
||||
}
|
||||
|
||||
if v.IsSet("tokenExpirationTime") {
|
||||
server.TokenExpirationTime = v.GetString("tokenExpirationTime")
|
||||
}
|
||||
|
||||
if v.IsSet("disableThumbnails") {
|
||||
server.EnableThumbnails = !v.GetBool("disableThumbnails")
|
||||
}
|
||||
|
||||
if v.IsSet("disablePreviewResize") {
|
||||
server.ResizePreview = !v.GetBool("disablePreviewResize")
|
||||
}
|
||||
|
||||
if v.IsSet("disableTypeDetectionByHeader") {
|
||||
server.TypeDetectionByHeader = !v.GetBool("disableTypeDetectionByHeader")
|
||||
}
|
||||
|
||||
if v.IsSet("disableExec") {
|
||||
server.EnableExec = !v.GetBool("disableExec")
|
||||
}
|
||||
|
||||
if isAddrSet && isSocketSet {
|
||||
@@ -312,18 +340,6 @@ 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
|
||||
|
||||
if server.EnableExec {
|
||||
log.Println("WARNING: Command Runner feature enabled!")
|
||||
log.Println("WARNING: This feature has known security vulnerabilities and should not")
|
||||
@@ -331,71 +347,9 @@ 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 setupLog(logMethod string) {
|
||||
switch logMethod {
|
||||
case "stdout":
|
||||
@@ -414,12 +368,13 @@ 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{
|
||||
Key: generateKey(),
|
||||
Signup: false,
|
||||
HideLoginButton: true,
|
||||
CreateUserDir: false,
|
||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||
@@ -427,7 +382,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,
|
||||
@@ -451,7 +406,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
||||
if d.viper.GetBool("noauth") {
|
||||
set.AuthMethod = auth.MethodNoAuth
|
||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||
} else {
|
||||
@@ -468,13 +423,18 @@ 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"),
|
||||
TokenExpirationTime: d.viper.GetString("tokenExpirationTime"),
|
||||
EnableThumbnails: !d.viper.GetBool("disableThumbnails"),
|
||||
ResizePreview: !d.viper.GetBool("disablePreviewResize"),
|
||||
EnableExec: !d.viper.GetBool("disableExec"),
|
||||
TypeDetectionByHeader: !d.viper.GetBool("disableTypeDetectionByHeader"),
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
@@ -482,8 +442,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
|
||||
@@ -516,33 +476,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(".", "_"))
|
||||
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 {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(upgradeCmd)
|
||||
|
||||
upgradeCmd.Flags().String("old.database", "", "")
|
||||
upgradeCmd.Flags().String("old.config", "", "")
|
||||
_ = upgradeCmd.MarkFlagRequired("old.database")
|
||||
}
|
||||
|
||||
var upgradeCmd = &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "Upgrades an old configuration",
|
||||
Long: `Upgrades an old configuration. This command DOES NOT
|
||||
import share links because they are incompatible with
|
||||
this version.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
flags := cmd.Flags()
|
||||
oldDB, err := getString(flags, "old.database")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldConf, err := getString(flags, "old.config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := getString(flags, "database")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return importer.Import(oldDB, oldConf, db)
|
||||
},
|
||||
}
|
||||
51
cmd/users.go
51
cmd/users.go
@@ -82,62 +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
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
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 = flags.GetBool(flag.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
visitErr = err
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,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,
|
||||
Username: args[0],
|
||||
Password: password,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -87,7 +88,7 @@ list or set it to 0.`,
|
||||
// with the new username. If there is, print an error and cancel the
|
||||
// operation
|
||||
if user.Username != onDB.Username {
|
||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil {
|
||||
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
163
cmd/utils.go
163
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,42 +24,21 @@ import (
|
||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||
)
|
||||
|
||||
const dbPerms = 0640
|
||||
const databasePermissions = 0640
|
||||
|
||||
func returnErr(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
||||
s, err := flags.GetString(flag)
|
||||
return s, returnErr(err)
|
||||
}
|
||||
|
||||
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) {
|
||||
b, err := flags.GetBool(flag)
|
||||
return b, returnErr(err)
|
||||
}
|
||||
|
||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
||||
b, err := flags.GetUint(flag)
|
||||
return b, returnErr(err)
|
||||
}
|
||||
|
||||
func generateKey() []byte {
|
||||
k, err := settings.GenerateKey()
|
||||
if err != nil {
|
||||
@@ -65,20 +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
|
||||
err error
|
||||
}
|
||||
|
||||
func dbExists(path string) (bool, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil {
|
||||
@@ -89,7 +57,7 @@ func dbExists(path string) (bool, error) {
|
||||
d := filepath.Dir(path)
|
||||
_, err = os.Stat(d)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
|
||||
if err := os.MkdirAll(d, 0700); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
@@ -99,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,34 +0,0 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'body-leading-blank': [1, 'always'],
|
||||
'body-max-line-length': [2, 'always', 100],
|
||||
'footer-leading-blank': [1, 'always'],
|
||||
'footer-max-line-length': [2, 'always', 100],
|
||||
'header-max-length': [2, 'always', 100],
|
||||
'scope-case': [2, 'always', 'lower-case'],
|
||||
'subject-case': [
|
||||
2,
|
||||
'never',
|
||||
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
|
||||
],
|
||||
'subject-full-stop': [2, 'never', '.'],
|
||||
'type-case': [2, 'always', 'lower-case'],
|
||||
'type-empty': [2, 'never'],
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat',
|
||||
'fix',
|
||||
'perf',
|
||||
'revert',
|
||||
'refactor',
|
||||
'build',
|
||||
'ci',
|
||||
'test',
|
||||
'chore',
|
||||
'docs',
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -2,7 +2,7 @@ package diskcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1" //nolint:gosec
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -103,7 +103,7 @@ func (f *FileCache) getScopedLocks(key string) (lock sync.Locker) {
|
||||
}
|
||||
|
||||
func (f *FileCache) getFileName(key string) string {
|
||||
hasher := sha1.New() //nolint:gosec
|
||||
hasher := sha1.New()
|
||||
_, _ = hasher.Write([]byte(key))
|
||||
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||
return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestFileCache(t *testing.T) {
|
||||
require.False(t, exists)
|
||||
}
|
||||
|
||||
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:revive
|
||||
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) {
|
||||
t.Helper()
|
||||
// check actual file content
|
||||
b, err := afero.ReadFile(fs, fileFullPath)
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"log": "stdout",
|
||||
"database": "/database/filebrowser.db",
|
||||
"root": "/srv"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,6 @@ package errors
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ExitCodeSigTerm = 128 + int(syscall.SIGTERM)
|
||||
ExitCodeSighup = 128 + int(syscall.SIGHUP)
|
||||
ExitCodeSigint = 128 + int(syscall.SIGINT)
|
||||
ExitCodeSigquit = 128 + int(syscall.SIGQUIT)
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,10 +22,6 @@ var (
|
||||
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||
ErrSourceIsParent = errors.New("source is parent")
|
||||
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
||||
ErrSigTerm = errors.New("exit on signal: sigterm")
|
||||
ErrSighup = errors.New("exit on signal: sighup")
|
||||
ErrSigint = errors.New("exit on signal: sigint")
|
||||
ErrSigquit = errors.New("exit on signal: sigquit")
|
||||
)
|
||||
|
||||
type ErrShortPassword struct {
|
||||
@@ -44,44 +31,3 @@ type ErrShortPassword struct {
|
||||
func (e ErrShortPassword) Error() string {
|
||||
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
||||
}
|
||||
|
||||
// GetExitCode returns the exit code for a given error.
|
||||
func GetExitCode(err error) int {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
exitCodeMap := map[error]int{
|
||||
ErrSigTerm: ExitCodeSigTerm,
|
||||
ErrSighup: ExitCodeSighup,
|
||||
ErrSigint: ExitCodeSigint,
|
||||
ErrSigquit: ExitCodeSigquit,
|
||||
}
|
||||
|
||||
for e, code := range exitCodeMap {
|
||||
if errors.Is(err, e) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(interface{ ExitCode() int }); ok {
|
||||
return exitErr.ExitCode()
|
||||
}
|
||||
|
||||
var pathErr *os.PathError
|
||||
if errors.As(err, &pathErr) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var syscallErr *os.SyscallError
|
||||
if errors.As(err, &syscallErr) {
|
||||
return 1
|
||||
}
|
||||
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"crypto/md5" //nolint:gosec
|
||||
"crypto/sha1" //nolint:gosec
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
@@ -90,7 +90,7 @@ func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
||||
|
||||
if opts.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
@@ -183,7 +183,6 @@ func (i *FileInfo) Checksum(algo string) error {
|
||||
|
||||
var h hash.Hash
|
||||
|
||||
//nolint:gosec
|
||||
switch algo {
|
||||
case "md5":
|
||||
h = md5.New()
|
||||
|
||||
@@ -600,7 +600,6 @@ var types = map[string]string{
|
||||
".epub": "application/epub+zip",
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
for ext, typ := range types {
|
||||
// skip errors
|
||||
|
||||
16
frontend/pnpm-lock.yaml
generated
16
frontend/pnpm-lock.yaml
generated
@@ -161,7 +161,7 @@ importers:
|
||||
version: 2.3.1(rollup@4.52.5)
|
||||
vue-tsc:
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3(typescript@5.9.3)
|
||||
version: 3.1.4(typescript@5.9.3)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -1303,8 +1303,8 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@vue/language-core@3.1.3':
|
||||
resolution: {integrity: sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==}
|
||||
'@vue/language-core@3.1.4':
|
||||
resolution: {integrity: sha512-n/58wm8SkmoxMWkUNUH/PwoovWe4hmdyPJU2ouldr3EPi1MLoS7iDN46je8CsP95SnVBs2axInzRglPNKvqMcg==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
@@ -2484,8 +2484,8 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.0.2
|
||||
|
||||
vue-tsc@3.1.3:
|
||||
resolution: {integrity: sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==}
|
||||
vue-tsc@3.1.4:
|
||||
resolution: {integrity: sha512-GsRJxttj4WkmXW/zDwYPGMJAN3np/4jTzoDFQTpTsI5Vg/JKMWamBwamlmLihgSVHO66y9P7GX+uoliYxeI4Hw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
@@ -3828,7 +3828,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vue/language-core@3.1.3(typescript@5.9.3)':
|
||||
'@vue/language-core@3.1.4(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.23
|
||||
'@vue/compiler-dom': 3.5.24
|
||||
@@ -4966,10 +4966,10 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.5.24(typescript@5.9.3)
|
||||
|
||||
vue-tsc@3.1.3(typescript@5.9.3):
|
||||
vue-tsc@3.1.4(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@volar/typescript': 2.4.23
|
||||
'@vue/language-core': 3.1.3(typescript@5.9.3)
|
||||
'@vue/language-core': 3.1.4(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
vue@3.5.24(typescript@5.9.3):
|
||||
|
||||
47
frontend/src/components/ContextMenu.vue
Normal file
47
frontend/src/components/ContextMenu.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div
|
||||
class="context-menu"
|
||||
ref="contextMenu"
|
||||
v-show="show"
|
||||
:style="{
|
||||
top: `${props.pos.y}px`,
|
||||
left: `${left}px`,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed, onUnmounted } from "vue";
|
||||
|
||||
const emit = defineEmits(["hide"]);
|
||||
const props = defineProps<{ show: boolean; pos: { x: number; y: number } }>();
|
||||
const contextMenu = ref<HTMLElement | null>(null);
|
||||
|
||||
const left = computed(() => {
|
||||
return Math.min(
|
||||
props.pos.x,
|
||||
window.innerWidth - (contextMenu.value?.clientWidth ?? 0)
|
||||
);
|
||||
});
|
||||
|
||||
const hideContextMenu = () => {
|
||||
emit("hide");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(val) => {
|
||||
if (val) {
|
||||
document.addEventListener("click", hideContextMenu);
|
||||
} else {
|
||||
document.removeEventListener("click", hideContextMenu);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", hideContextMenu);
|
||||
});
|
||||
</script>
|
||||
@@ -63,6 +63,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link
|
||||
v-if="!hideLoginButton"
|
||||
class="action"
|
||||
to="/login"
|
||||
:aria-label="$t('sidebar.login')"
|
||||
@@ -124,6 +125,7 @@ import * as auth from "@/utils/auth";
|
||||
import {
|
||||
version,
|
||||
signup,
|
||||
hideLoginButton,
|
||||
disableExternal,
|
||||
disableUsedPercentage,
|
||||
noAuth,
|
||||
@@ -153,6 +155,7 @@ export default {
|
||||
return this.currentPromptName === "sidebar";
|
||||
},
|
||||
signup: () => signup,
|
||||
hideLoginButton: () => hideLoginButton,
|
||||
version: () => version,
|
||||
disableExternal: () => disableExternal,
|
||||
disableUsedPercentage: () => disableUsedPercentage,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected"
|
||||
:data-ext="getExtension(name).toLowerCase()"
|
||||
@contextmenu="contextMenu"
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
@@ -239,6 +240,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
|
||||
else click(event);
|
||||
};
|
||||
|
||||
const contextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (
|
||||
fileStore.selected.length === 0 ||
|
||||
event.ctrlKey ||
|
||||
fileStore.selected.indexOf(props.index) === -1
|
||||
) {
|
||||
click(event);
|
||||
}
|
||||
};
|
||||
|
||||
const click = (event: Event | KeyboardEvent) => {
|
||||
if (!singleClick.value && fileStore.selectedCount !== 0)
|
||||
event.preventDefault();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.moveMessage") }}</p>
|
||||
<file-list
|
||||
ref="fileList"
|
||||
@update:selected="(val) => (dest = val)"
|
||||
|
||||
21
frontend/src/css/context-menu.css
Normal file
21
frontend/src/css/context-menu.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background: var(--surfacePrimary);
|
||||
min-width: 180px;
|
||||
max-width: 220px;
|
||||
border: 1px solid var(--borderSecondary);
|
||||
box-shadow: 0 2px 4px var(--borderPrimary);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.context-menu .action {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.context-menu .action .counter {
|
||||
left: 1.75em;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
@import "./mobile.css";
|
||||
@import "./epubReader.css";
|
||||
@import "./mdPreview.css";
|
||||
@import "./context-menu.css";
|
||||
|
||||
/* For testing only
|
||||
:focus {
|
||||
@@ -459,4 +460,4 @@ html[dir="rtl"] .card-content .small + input {
|
||||
html[dir="rtl"] .card.floating .card-content .file-list {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "إنشاء ملفات و مجلدات جديدة",
|
||||
"allowPublish": "نشر مقالات و صفحات جديدة",
|
||||
"allowSignup": "اسمح للمستخدمين بالاشتراك",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
||||
"branding": "الشعار",
|
||||
"brandingDirectoryPath": "مسار مجلد الشعار",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Crear nous fitxers i carpetes",
|
||||
"allowPublish": "Publicar nous posts i pàgines",
|
||||
"allowSignup": "Permetre registre d'usuaris",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(deixar en blanc per evitar canvis)",
|
||||
"branding": "Marca",
|
||||
"brandingDirectoryPath": "Ruta de la carpeta de personalització de marca",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Vytvářet nové soubory a adresáře",
|
||||
"allowPublish": "Publikovat nové příspěvky a stránky",
|
||||
"allowSignup": "Povolit uživatelům registraci",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(ponechte prázdné pro zabránění změnám)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Cesta ke složce s brandingem",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Erstellen neuer Dateien und Ordner",
|
||||
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
|
||||
"allowSignup": "Erlaube Benutzern sich zu registrieren",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
||||
"branding": "Design",
|
||||
"brandingDirectoryPath": "Designverzeichnispfad",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
||||
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
||||
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||
"branding": "Εξατομίκευση",
|
||||
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Create new files and directories",
|
||||
"allowPublish": "Publish new posts and pages",
|
||||
"allowSignup": "Allow users to signup",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(leave blank to avoid changes)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding directory path",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Crear nuevos archivos y carpetas",
|
||||
"allowPublish": "Publicar nuevos posts y páginas",
|
||||
"allowSignup": "Permitir registro de usuarios",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
||||
"branding": "Marca",
|
||||
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
||||
"allowPublish": "انتشار پست ها و صفحات جدید",
|
||||
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
||||
"branding": "برندسازی",
|
||||
"brandingDirectoryPath": "مسیر پوشه برند",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
||||
"allowPublish": "Publier de nouveaux posts et pages",
|
||||
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
||||
"branding": "Image de marque",
|
||||
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "יצירת קבצים ותיקיות חדשות",
|
||||
"allowPublish": "פרסום פוסטים ודפים חדשים",
|
||||
"allowSignup": "אפשר למשתמשים חדשים להירשם",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
||||
"branding": "מיתוג",
|
||||
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Stvori nove datoteke i mape",
|
||||
"allowPublish": "Objavi nove objave i stranice",
|
||||
"allowSignup": "Dopusti registraciju korisnicima",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
||||
"branding": "Brendiranje",
|
||||
"brandingDirectoryPath": "Put brendiranja",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Új fájlok és mappák létrehozása",
|
||||
"allowPublish": "Új bejegyzések és oldalak létrehozása",
|
||||
"allowSignup": "Felhasználók regisztrációjának engedélyezése",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(üresen hagyva nincs változás)",
|
||||
"branding": "Márkázás",
|
||||
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Búa til ný skjöl og möppur",
|
||||
"allowPublish": "Gefa út nýjar færslur og síður",
|
||||
"allowSignup": "Leyfa nýjum notendum að skrá sig",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
|
||||
"branding": "Útlit",
|
||||
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Crea nuovi files o cartelle",
|
||||
"allowPublish": "Pubblica nuovi post e pagine",
|
||||
"allowSignup": "Permetti agli utenti di registrarsi",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Directory del branding",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "ファイルやフォルダーの新規作成",
|
||||
"allowPublish": "新しい投稿やページの公開",
|
||||
"allowSignup": "ユーザーの新規登録を許可",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(変更しない場合は空白のままにしてください)",
|
||||
"branding": "ブランディング",
|
||||
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "파일/디렉토리 생성 허용",
|
||||
"allowPublish": "새 포스트/페이지 생성 허용",
|
||||
"allowSignup": "사용자 가입 허용",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||
"branding": "브랜딩",
|
||||
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Nieuwe bestanden of mappen aanmaken",
|
||||
"allowPublish": "Publiceer nieuwe berichten en pagina's",
|
||||
"allowSignup": "Sta gebruikers toe om zich te registreren",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding directory path",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Opprett nye filer og direktorater",
|
||||
"allowPublish": "Publiser nye innlegg og sider",
|
||||
"allowSignup": "Tilat brukere å registrere seg",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
||||
"branding": "Merkevarebygging",
|
||||
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Tworzenie nowych plików lub folderów",
|
||||
"allowPublish": "Tworzenie nowych wpisów i stron",
|
||||
"allowSignup": "Pozwól użytkownikom na rejestrację",
|
||||
"hideLoginButton": "Ukryj przycisk logowania na stronach publicznych",
|
||||
"avoidChanges": "(pozostaw puste, aby uniknąć zmian)",
|
||||
"branding": "Personalizacja",
|
||||
"brandingDirectoryPath": "Ścieżka do folderu personalizacji",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Criar novos arquivos e pastas",
|
||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||
"allowSignup": "Permitir cadastro de usuários",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
"branding": "Customização",
|
||||
"brandingDirectoryPath": "Diretório de customização",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Criar novos ficheiros e pastas",
|
||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||
"allowSignup": "Permitir que os utilizadores criem contas",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
"branding": "Marca",
|
||||
"brandingDirectoryPath": "Caminho da pasta de marca",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Crează noi fișiere sau directoare",
|
||||
"allowPublish": "Publică noi pagini și postări",
|
||||
"allowSignup": "Permite utilizatorilor să se înregistreze",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Calea către directorul de branding",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Создание новых файлов или каталогов",
|
||||
"allowPublish": "Публикация новых записей и страниц",
|
||||
"allowSignup": "Разрешить пользователям регистрироваться",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
||||
"branding": "Брендинг",
|
||||
"brandingDirectoryPath": "Путь к каталогу брендов",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Vytvárať nové súbory a priečinky",
|
||||
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
||||
"allowSignup": "Povoliť registráciu používateľov",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
||||
"branding": "Vlastný vzhľad",
|
||||
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Skapa nya filer eller mappar",
|
||||
"allowPublish": "Publicera nya inlägg och sidor",
|
||||
"allowSignup": "Tillåt användare att registrera sig",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
||||
"branding": "Varumärke",
|
||||
"brandingDirectoryPath": "Sökväg till varumärkes katalog",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
||||
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
||||
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||
"branding": "Marka",
|
||||
"brandingDirectoryPath": "Marka dizin yolu",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Створення нових файлів або каталогів",
|
||||
"allowPublish": "Публікація нових записів та сторінок",
|
||||
"allowSignup": "Дозволити користувачам реєструватися",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
||||
"branding": "Брендинг",
|
||||
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "Tạo tệp và thư mục mới",
|
||||
"allowPublish": "Xuất bản bài viết và trang mới",
|
||||
"allowSignup": "Cho phép người dùng đăng ký",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(để trống để tránh thay đổi)",
|
||||
"branding": "Thương hiệu",
|
||||
"brandingDirectoryPath": "Đường dẫn thư mục thương hiệu",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "创建新文件和文件夹",
|
||||
"allowPublish": "发布新的帖子与页面",
|
||||
"allowSignup": "允许用户注册",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(留空以避免更改)",
|
||||
"branding": "品牌",
|
||||
"brandingDirectoryPath": "品牌信息文件夹路径",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"allowNew": "建立新檔案和目錄",
|
||||
"allowPublish": "發佈新的貼文與頁面",
|
||||
"allowSignup": "允許使用者註冊",
|
||||
"hideLoginButton": "Hide the login button from public pages",
|
||||
"avoidChanges": "(留空以避免更改)",
|
||||
"branding": "品牌",
|
||||
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
||||
|
||||
1
frontend/src/types/settings.d.ts
vendored
1
frontend/src/types/settings.d.ts
vendored
@@ -1,6 +1,7 @@
|
||||
interface ISettings {
|
||||
signup: boolean;
|
||||
createUserDir: boolean;
|
||||
hideLoginButton: boolean;
|
||||
minimumPasswordLength: number;
|
||||
userHomeBasePath: string;
|
||||
defaults: SettingsDefaults;
|
||||
|
||||
@@ -18,6 +18,7 @@ const enableExec: boolean = window.FileBrowser.EnableExec;
|
||||
const tusSettings = window.FileBrowser.TusSettings;
|
||||
const origin = window.location.origin;
|
||||
const tusEndpoint = `/api/tus`;
|
||||
const hideLoginButton = window.FileBrowser.HideLoginButton;
|
||||
|
||||
export {
|
||||
name,
|
||||
@@ -39,4 +40,5 @@ export {
|
||||
tusSettings,
|
||||
origin,
|
||||
tusEndpoint,
|
||||
hideLoginButton,
|
||||
};
|
||||
|
||||
@@ -207,7 +207,10 @@
|
||||
<h2 v-if="fileStore.req?.numDirs ?? false">
|
||||
{{ t("files.folders") }}
|
||||
</h2>
|
||||
<div v-if="fileStore.req?.numDirs ?? false">
|
||||
<div
|
||||
v-if="fileStore.req?.numDirs ?? false"
|
||||
@contextmenu="showContextMenu"
|
||||
>
|
||||
<item
|
||||
v-for="item in dirs"
|
||||
:key="base64(item.name)"
|
||||
@@ -223,8 +226,13 @@
|
||||
</item>
|
||||
</div>
|
||||
|
||||
<h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
|
||||
<div v-if="fileStore.req?.numFiles ?? false">
|
||||
<h2 v-if="fileStore.req?.numFiles ?? false">
|
||||
{{ t("files.files") }}
|
||||
</h2>
|
||||
<div
|
||||
v-if="fileStore.req?.numFiles ?? false"
|
||||
@contextmenu="showContextMenu"
|
||||
>
|
||||
<item
|
||||
v-for="item in files"
|
||||
:key="base64(item.name)"
|
||||
@@ -239,6 +247,53 @@
|
||||
>
|
||||
</item>
|
||||
</div>
|
||||
<context-menu
|
||||
:show="isContextMenuVisible"
|
||||
:pos="contextMenuPos"
|
||||
@hide="hideContextMenu"
|
||||
>
|
||||
<action
|
||||
v-if="headerButtons.share"
|
||||
icon="share"
|
||||
:label="t('buttons.share')"
|
||||
show="share"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.rename"
|
||||
icon="mode_edit"
|
||||
:label="t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.copy"
|
||||
id="copy-button"
|
||||
icon="content_copy"
|
||||
:label="t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.move"
|
||||
id="move-button"
|
||||
icon="forward"
|
||||
:label="t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.delete"
|
||||
id="delete-button"
|
||||
icon="delete"
|
||||
:label="t('buttons.delete')"
|
||||
show="delete"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.download"
|
||||
icon="file_download"
|
||||
:label="t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="fileStore.selectedCount"
|
||||
/>
|
||||
<action icon="info" :label="t('buttons.info')" show="info" />
|
||||
</context-menu>
|
||||
|
||||
<input
|
||||
style="display: none"
|
||||
@@ -291,6 +346,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Search from "@/components/Search.vue";
|
||||
import Item from "@/components/files/ListingItem.vue";
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
@@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
|
||||
const dragCounter = ref<number>(0);
|
||||
const width = ref<number>(window.innerWidth);
|
||||
const itemWeight = ref<number>(0);
|
||||
const isContextMenuVisible = ref<boolean>(false);
|
||||
const contextMenuPos = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||
|
||||
const $showError = inject<IToastError>("$showError")!;
|
||||
|
||||
@@ -438,7 +496,7 @@ watch(req, () => {
|
||||
|
||||
onMounted(() => {
|
||||
// Check the columns size for the first time.
|
||||
colunmsResize();
|
||||
columnsResize();
|
||||
|
||||
// How much every listing item affects the window height
|
||||
setItemWeight();
|
||||
@@ -642,7 +700,7 @@ const paste = (event: Event) => {
|
||||
action(overwrite, rename);
|
||||
};
|
||||
|
||||
const colunmsResize = () => {
|
||||
const columnsResize = () => {
|
||||
// Update the columns size based on the window width.
|
||||
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||
if (items_ === null) return;
|
||||
@@ -850,7 +908,7 @@ const toggleMultipleSelection = () => {
|
||||
};
|
||||
|
||||
const windowsResize = throttle(() => {
|
||||
colunmsResize();
|
||||
columnsResize();
|
||||
width.value = window.innerWidth;
|
||||
|
||||
// Listing element is not displayed
|
||||
@@ -977,4 +1035,17 @@ const revealPreviousItem = () => {
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const showContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
isContextMenuVisible.value = true;
|
||||
contextMenuPos.value = {
|
||||
x: event.clientX + 8,
|
||||
y: event.clientY + Math.floor(window.scrollY),
|
||||
};
|
||||
};
|
||||
|
||||
const hideContextMenu = () => {
|
||||
isContextMenuVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
{{ t("settings.createUserDir") }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" v-model="settings.hideLoginButton" />
|
||||
{{ t("settings.hideLoginButton") }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
||||
<input
|
||||
|
||||
7
go.mod
7
go.mod
@@ -16,7 +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/pelletier/go-toml/v2 v2.2.4
|
||||
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
|
||||
@@ -24,7 +24,6 @@ require (
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/image v0.33.0
|
||||
golang.org/x/text v0.31.0
|
||||
@@ -40,6 +39,7 @@ require (
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
@@ -58,9 +58,11 @@ require (
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
@@ -68,6 +70,7 @@ require (
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -47,6 +47,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -196,10 +197,13 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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=
|
||||
|
||||
@@ -28,7 +28,6 @@ var (
|
||||
cmdNotAllowed = []byte("Command not allowed.")
|
||||
)
|
||||
|
||||
//nolint:unparam
|
||||
func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) {
|
||||
txt := http.StatusText(status)
|
||||
if err != nil || status >= 400 {
|
||||
@@ -49,7 +48,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
var raw string
|
||||
|
||||
for {
|
||||
_, msg, err := conn.ReadMessage() //nolint:govet
|
||||
_, msg, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
return 0, nil
|
||||
@@ -63,7 +62,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
|
||||
// Fail fast
|
||||
if !d.server.EnableExec || !d.user.Perm.Execute {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
@@ -72,21 +71,21 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
||||
|
||||
command, name, err := runner.ParseCommand(d.settings, raw)
|
||||
if err != nil {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if !slices.Contains(d.user.Commands, name) {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Dir = d.user.FullPath(r.URL.Path)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
|
||||
@@ -124,12 +124,12 @@ func createPreview(imgSvc ImgService, fileCache FileCache,
|
||||
options []img.Option
|
||||
)
|
||||
|
||||
switch {
|
||||
case previewSize == PreviewSizeBig:
|
||||
switch previewSize {
|
||||
case PreviewSizeBig:
|
||||
width = 1080
|
||||
height = 1080
|
||||
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
||||
case previewSize == PreviewSizeThumb:
|
||||
case PreviewSizeThumb:
|
||||
width = 256
|
||||
height = 256
|
||||
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
||||
|
||||
@@ -98,8 +98,8 @@ var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Reques
|
||||
file := d.raw.(*files.FileInfo)
|
||||
|
||||
if file.IsDir {
|
||||
file.Listing.Sorting = files.Sorting{By: "name", Asc: false}
|
||||
file.Listing.ApplySort()
|
||||
file.Sorting = files.Sorting{By: "name", Asc: false}
|
||||
file.ApplySort()
|
||||
return renderJSON(w, r, file)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const passwordBcrypt = "$2y$10$TFAmdCbyd/mEZDe5fUeZJu.MaJQXRTwdqb/IQV.eTn6dWrF58gCSe" //nolint:gosec
|
||||
const passwordBcrypt = "$2y$10$TFAmdCbyd/mEZDe5fUeZJu.MaJQXRTwdqb/IQV.eTn6dWrF58gCSe"
|
||||
testCases := map[string]struct {
|
||||
share *share.Link
|
||||
req *http.Request
|
||||
@@ -70,7 +70,7 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := db.Close(); err != nil { //nolint:govet
|
||||
if err := db.Close(); err != nil {
|
||||
t.Errorf("failed to close db: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -32,7 +32,7 @@ func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]strin
|
||||
fileSlice = append(fileSlice, f.Path)
|
||||
} else {
|
||||
for _, name := range names {
|
||||
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:govet
|
||||
name, err := url.QueryUnescape(strings.ReplaceAll(name, "+", "%2B"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||
}
|
||||
|
||||
if file.IsDir {
|
||||
file.Listing.Sorting = d.user.Sorting
|
||||
file.Listing.ApplySort()
|
||||
file.Sorting = d.user.Sorting
|
||||
file.ApplySort()
|
||||
return renderJSON(w, r, file)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type settingsData struct {
|
||||
Signup bool `json:"signup"`
|
||||
HideLoginButton bool `json:"hideLoginButton"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
@@ -24,6 +25,7 @@ type settingsData struct {
|
||||
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
data := &settingsData{
|
||||
Signup: d.settings.Signup,
|
||||
HideLoginButton: d.settings.HideLoginButton,
|
||||
CreateUserDir: d.settings.CreateUserDir,
|
||||
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||
@@ -55,6 +57,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
|
||||
d.settings.Tus = req.Tus
|
||||
d.settings.Shell = req.Shell
|
||||
d.settings.Commands = req.Commands
|
||||
d.settings.HideLoginButton = req.HideLoginButton
|
||||
|
||||
err = d.store.Settings.Save(d.settings)
|
||||
return errToStatus(err), err
|
||||
|
||||
@@ -111,7 +111,6 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
var expire int64 = 0
|
||||
|
||||
if body.Expires != "" {
|
||||
//nolint:govet
|
||||
num, err := strconv.Atoi(body.Expires)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
||||
@@ -46,11 +46,12 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
"ResizePreview": d.server.ResizePreview,
|
||||
"EnableExec": d.server.EnableExec,
|
||||
"TusSettings": d.settings.Tus,
|
||||
"HideLoginButton": d.settings.HideLoginButton,
|
||||
}
|
||||
|
||||
if d.settings.Branding.Files != "" {
|
||||
fPath := filepath.Join(d.settings.Branding.Files, "custom.css")
|
||||
_, err := os.Stat(fPath) //nolint:govet
|
||||
_, err := os.Stat(fPath)
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Printf("couldn't load custom styles: %v", err)
|
||||
@@ -62,7 +63,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
}
|
||||
|
||||
if d.settings.AuthMethod == auth.MethodJSONAuth {
|
||||
raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:govet
|
||||
raw, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
|
||||
case ResizeModeFill:
|
||||
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
|
||||
case ResizeModeFit:
|
||||
fallthrough //nolint:gocritic
|
||||
fallthrough
|
||||
default:
|
||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||
}
|
||||
|
||||
3
main.go
3
main.go
@@ -4,11 +4,10 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/cmd"
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(errors.GetExitCode(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
|
||||
command[i] = os.Expand(arg, envMapping)
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope)) //nolint:gocritic
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
|
||||
|
||||
@@ -48,8 +48,8 @@ func parseSearch(value string) *searchOptions {
|
||||
}
|
||||
|
||||
// removes the options from the value
|
||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
||||
value = strings.ReplaceAll(value, "case:insensitive", "")
|
||||
value = strings.ReplaceAll(value, "case:sensitive", "")
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
||||
|
||||
@@ -42,7 +42,7 @@ func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string,
|
||||
func cleanUsername(s string) string {
|
||||
// Remove any trailing space to avoid ending on -
|
||||
s = strings.Trim(s, " ")
|
||||
s = strings.Replace(s, "..", "", -1)
|
||||
s = strings.ReplaceAll(s, "..", "")
|
||||
|
||||
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
|
||||
s = invalidFilenameChars.ReplaceAllString(s, "-")
|
||||
|
||||
@@ -22,6 +22,7 @@ type AuthMethod string
|
||||
type Settings struct {
|
||||
Key []byte `json:"key"`
|
||||
Signup bool `json:"signup"`
|
||||
HideLoginButton bool `json:"hideLoginButton"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
@@ -34,6 +35,7 @@ type Settings struct {
|
||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||
FileMode fs.FileMode `json:"fileMode"`
|
||||
DirMode fs.FileMode `json:"dirMode"`
|
||||
HideDotfiles bool `json:"hideDotfiles"`
|
||||
}
|
||||
|
||||
// GetRules implements rules.Provider.
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/storage"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
type oldDefs struct {
|
||||
Commands []string `json:"commands" yaml:"commands" toml:"commands"`
|
||||
Scope string `json:"scope" yaml:"scope" toml:"scope"`
|
||||
ViewMode string `json:"viewMode" yaml:"viewMode" toml:"viewMode"`
|
||||
Locale string `json:"locale" yaml:"locale" toml:"locale"`
|
||||
AllowCommands bool `json:"allowCommands" yaml:"allowCommands" toml:"allowCommands"`
|
||||
AllowEdit bool `json:"allowEdit" yaml:"allowEdit" toml:"allowEdit"`
|
||||
AllowNew bool `json:"allowNew" yaml:"allowNew" toml:"allowNew"`
|
||||
}
|
||||
|
||||
type oldAuth struct {
|
||||
Method string `json:"method" yaml:"method" toml:"method"` // default none proxy
|
||||
Header string `json:"header" yaml:"header" toml:"header"`
|
||||
Command string `json:"command" yaml:"command" toml:"command"`
|
||||
}
|
||||
|
||||
type oldConf struct {
|
||||
Port string `json:"port" yaml:"port" toml:"port"`
|
||||
BaseURL string `json:"baseURL" yaml:"baseURL" toml:"baseURL"`
|
||||
Log string `json:"log" yaml:"log" toml:"log"`
|
||||
Address string `json:"address" yaml:"address" toml:"address"`
|
||||
Defaults oldDefs `json:"defaults" yaml:"defaults" toml:"defaults"`
|
||||
ReCaptcha struct {
|
||||
Key string `json:"key" yaml:"key" toml:"key"`
|
||||
Secret string `json:"secret" yaml:"secret" toml:"secret"`
|
||||
Host string `json:"host" yaml:"host" toml:"host"`
|
||||
} `json:"recaptcha" yaml:"recaptcha" toml:"recaptcha"`
|
||||
Auth oldAuth `json:"auth" yaml:"auth" toml:"auth"`
|
||||
}
|
||||
|
||||
var defaults = &oldConf{
|
||||
Port: "0",
|
||||
Log: "stdout",
|
||||
Defaults: oldDefs{
|
||||
Commands: []string{"git", "svn", "hg"},
|
||||
ViewMode: string(users.MosaicViewMode),
|
||||
AllowCommands: true,
|
||||
AllowEdit: true,
|
||||
AllowNew: true,
|
||||
Locale: "en",
|
||||
},
|
||||
Auth: oldAuth{
|
||||
Method: "default",
|
||||
},
|
||||
}
|
||||
|
||||
func readConf(path string) (*oldConf, error) {
|
||||
cfg := &oldConf{}
|
||||
if path != "" {
|
||||
ext := filepath.Ext(path)
|
||||
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
switch ext {
|
||||
case ".json":
|
||||
err = json.NewDecoder(fd).Decode(cfg)
|
||||
case ".toml":
|
||||
err = toml.NewDecoder(fd).Decode(cfg)
|
||||
case ".yaml", ".yml":
|
||||
err = yaml.NewDecoder(fd).Decode(cfg)
|
||||
default:
|
||||
return nil, errors.New("unsupported config extension " + ext)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cfg = defaults
|
||||
path, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Defaults.Scope = path
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
||||
cfg, err := readConf(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commands := map[string][]string{}
|
||||
err = db.Get("config", "commands", &commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := []byte{}
|
||||
err = db.Get("config", "key", &key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := &settings.Settings{
|
||||
Key: key,
|
||||
Signup: false,
|
||||
Defaults: settings.UserDefaults{
|
||||
Scope: cfg.Defaults.Scope,
|
||||
Commands: cfg.Defaults.Commands,
|
||||
ViewMode: users.ViewMode(cfg.Defaults.ViewMode),
|
||||
Locale: cfg.Defaults.Locale,
|
||||
Perm: users.Permissions{
|
||||
Admin: false,
|
||||
Execute: cfg.Defaults.AllowCommands,
|
||||
Create: cfg.Defaults.AllowNew,
|
||||
Rename: cfg.Defaults.AllowEdit,
|
||||
Modify: cfg.Defaults.AllowEdit,
|
||||
Delete: cfg.Defaults.AllowEdit,
|
||||
Share: true,
|
||||
Download: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
server := &settings.Server{
|
||||
BaseURL: cfg.BaseURL,
|
||||
Port: cfg.Port,
|
||||
Address: cfg.Address,
|
||||
Log: cfg.Log,
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
switch cfg.Auth.Method {
|
||||
case "proxy":
|
||||
auther = &auth.ProxyAuth{Header: cfg.Auth.Header}
|
||||
s.AuthMethod = auth.MethodProxyAuth
|
||||
case "hook":
|
||||
auther = &auth.HookAuth{Command: cfg.Auth.Command}
|
||||
s.AuthMethod = auth.MethodHookAuth
|
||||
case "none":
|
||||
auther = &auth.NoAuth{}
|
||||
s.AuthMethod = auth.MethodNoAuth
|
||||
default:
|
||||
auther = &auth.JSONAuth{
|
||||
ReCaptcha: &auth.ReCaptcha{
|
||||
Host: cfg.ReCaptcha.Host,
|
||||
Key: cfg.ReCaptcha.Key,
|
||||
Secret: cfg.ReCaptcha.Secret,
|
||||
},
|
||||
}
|
||||
s.AuthMethod = auth.MethodJSONAuth
|
||||
}
|
||||
|
||||
err = sto.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sto.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sto.Settings.SaveServer(server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Configuration successfully imported.")
|
||||
return nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||
)
|
||||
|
||||
// Import imports an old configuration to a newer database.
|
||||
func Import(oldDBPath, oldConf, newDBPath string) error {
|
||||
oldDB, err := storm.Open(oldDBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer oldDB.Close()
|
||||
|
||||
newDB, err := storm.Open(newDBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newDB.Close()
|
||||
|
||||
sto, err := bolt.NewStorage(newDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = importUsers(oldDB, sto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = importConf(oldDB, oldConf, sto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
"github.com/filebrowser/filebrowser/v2/storage"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
type oldUser struct {
|
||||
ID int `storm:"id,increment"`
|
||||
Admin bool `json:"admin"`
|
||||
AllowCommands bool `json:"allowCommands"` // Execute commands
|
||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||
AllowPublish bool `json:"allowPublish"` // Publish content (to use with static gen)
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
Commands []string `json:"commands"`
|
||||
Locale string `json:"locale"`
|
||||
Password string `json:"password"`
|
||||
Rules []*rules.Rule `json:"rules"`
|
||||
Scope string `json:"filesystem"`
|
||||
Username string `json:"username" storm:"index,unique"`
|
||||
ViewMode string `json:"viewMode"`
|
||||
}
|
||||
|
||||
func readOldUsers(db *storm.DB) ([]*oldUser, error) {
|
||||
var oldUsers []*oldUser
|
||||
err := db.Bolt.View(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket([]byte("User")).ForEach(func(_ []byte, v []byte) error {
|
||||
if len(v) > 0 && string(v)[0] == '{' {
|
||||
user := &oldUser{}
|
||||
err := json.Unmarshal(v, user)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldUsers = append(oldUsers, user)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return oldUsers, err
|
||||
}
|
||||
|
||||
func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
|
||||
list := []*users.User{}
|
||||
|
||||
for _, oldUser := range old {
|
||||
user := &users.User{
|
||||
Username: oldUser.Username,
|
||||
Password: oldUser.Password,
|
||||
Scope: oldUser.Scope,
|
||||
Locale: oldUser.Locale,
|
||||
LockPassword: oldUser.LockPassword,
|
||||
ViewMode: users.ViewMode(oldUser.ViewMode),
|
||||
Commands: oldUser.Commands,
|
||||
Rules: []rules.Rule{},
|
||||
Perm: users.Permissions{
|
||||
Admin: oldUser.Admin,
|
||||
Execute: oldUser.AllowCommands,
|
||||
Create: oldUser.AllowNew,
|
||||
Rename: oldUser.AllowEdit,
|
||||
Modify: oldUser.AllowEdit,
|
||||
Delete: oldUser.AllowEdit,
|
||||
Share: true,
|
||||
Download: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range oldUser.Rules {
|
||||
user.Rules = append(user.Rules, *rule)
|
||||
}
|
||||
|
||||
err := user.Clean("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = append(list, user)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func importUsers(old *storm.DB, sto *storage.Storage) error {
|
||||
oldUsers, err := readOldUsers(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newUsers, err := convertUsersToNew(oldUsers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range newUsers {
|
||||
err = sto.Users.Save(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%d users successfully imported into the new DB.\n", len(newUsers))
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
var assets embed.FS
|
||||
var commonPasswords map[string]struct{}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
// Password list sourced from:
|
||||
// https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *Storage) Gets(baseScope string) ([]*User, error) {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if err := user.Clean(baseScope); err != nil { //nolint:govet
|
||||
if err := user.Clean(baseScope); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ var checkableFields = []string{
|
||||
|
||||
// Clean cleans up a user and verifies if all its fields
|
||||
// are alright to be saved.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (u *User) Clean(baseScope string, fields ...string) error {
|
||||
if len(fields) == 0 {
|
||||
fields = checkableFields
|
||||
@@ -93,7 +91,7 @@ func (u *User) Clean(baseScope string, fields ...string) error {
|
||||
|
||||
if u.Fs == nil {
|
||||
scope := u.Scope
|
||||
scope = filepath.Join(baseScope, filepath.Join("/", scope)) //nolint:gocritic
|
||||
scope = filepath.Join(baseScope, filepath.Join("/", scope))
|
||||
u.Fs = afero.NewBasePathFs(afero.NewOsFs(), scope)
|
||||
}
|
||||
|
||||
|
||||
49
www/docs/authentication.md
Normal file
49
www/docs/authentication.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Authentication
|
||||
|
||||
There are three possible authentication methods. Each one of them has its own capabilities and specification. If you are interested in contributing with one more authentication method, please [check the guidelines](contributing.md).
|
||||
|
||||
## JSON Auth (default)
|
||||
|
||||
We call it JSON Authentication but it is just the default authentication method and the one that is provided by default if you don't make any changes. It is set by default, but if you've made changes before you can revert to using JSON auth:
|
||||
|
||||
```sh
|
||||
filebrowser config set --auth.method=json
|
||||
```
|
||||
|
||||
This method can also be extended with **reCAPTCHA** verification during login:
|
||||
|
||||
```sh
|
||||
filebrowser config set --auth.method=json \
|
||||
--recaptcha.key site-key \
|
||||
--recaptcha.secret private-key
|
||||
```
|
||||
|
||||
By default, we use [Google's reCAPTCHA](https://developers.google.com/recaptcha/docs/display) service. If you live in China, or want to use other provider, you can change the host with the following command:
|
||||
|
||||
```sh
|
||||
filebrowser config set --recaptcha.host https://recaptcha.net
|
||||
```
|
||||
|
||||
Where `https://recaptcha.net` is any provider you want.
|
||||
|
||||
## Proxy Header
|
||||
|
||||
If you have a reverse proxy you want to use to login your users, you do it via our `proxy` authentication method. To configure this method, your proxy must send an HTTP header containing the username of the logged in user:
|
||||
|
||||
```sh
|
||||
filebrowser config set --auth.method=proxy --auth.header=X-My-Header
|
||||
```
|
||||
|
||||
Where `X-My-Header` is the HTTP header provided by your proxy with the username.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> File Browser will blindly trust the provided header. If the proxy can be bypassed, an attacker could simply attach the header and get admin access.
|
||||
|
||||
### No Authentication
|
||||
|
||||
We also provide a no authentication mechanism for users that want to use File Browser privately such in a home network. By setting this authentication method, the user with **id 1** will be used as the default users. Creating more users won't have any effect.
|
||||
|
||||
```sh
|
||||
filebrowser config set --auth.method=noauth
|
||||
```
|
||||
29
www/docs/cli/filebrowser-cmds-add.md
Normal file
29
www/docs/cli/filebrowser-cmds-add.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# filebrowser cmds add
|
||||
|
||||
Add a command to run on a specific event
|
||||
|
||||
## Synopsis
|
||||
|
||||
Add a command to run on a specific event.
|
||||
|
||||
```
|
||||
filebrowser cmds add <event> <command> [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-h, --help help for add
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file path
|
||||
-d, --database string database path (default "./filebrowser.db")
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
* [filebrowser cmds](filebrowser-cmds.md) - Command runner management utility
|
||||
|
||||
30
www/docs/cli/filebrowser-cmds-ls.md
Normal file
30
www/docs/cli/filebrowser-cmds-ls.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# filebrowser cmds ls
|
||||
|
||||
List all commands for each event
|
||||
|
||||
## Synopsis
|
||||
|
||||
List all commands for each event.
|
||||
|
||||
```
|
||||
filebrowser cmds ls [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-e, --event string event name, without 'before' or 'after'
|
||||
-h, --help help for ls
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file path
|
||||
-d, --database string database path (default "./filebrowser.db")
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
* [filebrowser cmds](filebrowser-cmds.md) - Command runner management utility
|
||||
|
||||
37
www/docs/cli/filebrowser-cmds-rm.md
Normal file
37
www/docs/cli/filebrowser-cmds-rm.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# filebrowser cmds rm
|
||||
|
||||
Removes a command from an event hooker
|
||||
|
||||
## Synopsis
|
||||
|
||||
Removes a command from an event hooker. The provided index
|
||||
is the same that's printed when you run 'cmds ls'. Note
|
||||
that after each removal/addition, the index of the
|
||||
commands change. So be careful when removing them after each
|
||||
other.
|
||||
|
||||
You can also specify an optional parameter (index_end) so
|
||||
you can remove all commands from 'index' to 'index_end',
|
||||
including 'index_end'.
|
||||
|
||||
```
|
||||
filebrowser cmds rm <event> <index> [index_end] [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-h, --help help for rm
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file path
|
||||
-d, --database string database path (default "./filebrowser.db")
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
* [filebrowser cmds](filebrowser-cmds.md) - Command runner management utility
|
||||
|
||||
28
www/docs/cli/filebrowser-cmds.md
Normal file
28
www/docs/cli/filebrowser-cmds.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# filebrowser cmds
|
||||
|
||||
Command runner management utility
|
||||
|
||||
## Synopsis
|
||||
|
||||
Command runner management utility.
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-h, --help help for cmds
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file path
|
||||
-d, --database string database path (default "./filebrowser.db")
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
* [filebrowser](filebrowser.md) - A stylish web-based file browser
|
||||
* [filebrowser cmds add](filebrowser-cmds-add.md) - Add a command to run on a specific event
|
||||
* [filebrowser cmds ls](filebrowser-cmds-ls.md) - List all commands for each event
|
||||
* [filebrowser cmds rm](filebrowser-cmds-rm.md) - Removes a command from an event hooker
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user