Compare commits

..

23 Commits

Author SHA1 Message Date
Henrique Dias
fb8d41eb9a chore(release): 2.47.0 2025-11-16 14:28:15 +01:00
Henrique Dias
0fadaccaa2 chore(docs): update CLI documentation 2025-11-16 14:28:03 +01:00
Henrique Dias
e24e1f1aba feat: add TUS settings to the command line (#5556) 2025-11-16 14:13:58 +01:00
Henrique Dias
5de4099cba fix: exit 0 when gracefully shutting down (#5555) 2025-11-16 14:13:21 +01:00
Henrique Dias
d01493106d docs: improved config 2025-11-16 10:31:08 +01:00
Henrique Dias
2d9689dd6a docs: add CLI usage and integrate generation in release 2025-11-16 10:14:59 +01:00
Henrique Dias
c4c1cea230 docs: remove partially incorrect env variables info 2025-11-16 09:01:54 +01:00
Henrique Dias
ceb5e723f3 feat: remove importer of v1 config (#5550) 2025-11-16 07:52:53 +01:00
renovate[bot]
ebc7d2303d chore(deps): update dependency vue-tsc to v3.1.4 (#5551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 20:14:10 +01:00
Henrique Dias
23c4e4565b chore: remove 'nolint' comments 2025-11-15 09:01:21 +01:00
Henrique Dias
17f1e08a58 chore(release): 2.46.1 2025-11-15 08:51:04 +01:00
Henrique Dias
ffc850454e fix: remove duplicated 'hide-defaults' flag (is 'hideDefaults') (#5548) 2025-11-15 08:50:22 +01:00
Henrique Dias
13814e1119 fix: env key replacer and remove unused function (#5547) 2025-11-15 08:40:37 +01:00
Henrique Dias
4e9e312984 chore(release): 2.46.0 2025-11-14 18:15:33 +01:00
Henrique Dias
ce3b407c51 docs: clarify status 2025-11-14 17:49:52 +01:00
transifex-integration[bot]
fb5d099f85 feat: Updates for project File Browser (#5544) 2025-11-14 17:47:21 +01:00
Omar Hussein
1ace579a55 feat: add context menu (#3343)
Co-authored-by: Henrique Dias <mail@hacdias.com>
2025-11-14 16:53:16 +01:00
Lucky Jain
ac7b49c148 feat: add option to hide the login button from public-facing pages (#3922)
Co-authored-by: Henrique Dias <mail@hacdias.com>
2025-11-14 16:21:08 +01:00
Henrique Dias
9d44932dba chore: use more standard golangci-lint options 2025-11-14 16:18:12 +01:00
Ahmad Hesam
0d973d3aad feat: add 'hide-dotfiles' as command line parameter (#3802)
Co-authored-by: Henrique Dias <mail@hacdias.com>
2025-11-14 08:19:03 +01:00
Henrique Dias
cacc0999e9 chore: let functions be longer 2025-11-14 08:11:10 +01:00
Henrique Dias
42d1b6f3ae docs: fix badge in readme 2025-11-13 18:03:51 +01:00
Henrique Dias
bb10c3dfa9 docs: clarify release 2025-11-13 17:39:51 +01:00
123 changed files with 1786 additions and 987 deletions

View File

@@ -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:

View File

@@ -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/

View File

@@ -2,8 +2,41 @@
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.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)

View File

@@ -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

View File

@@ -2,7 +2,7 @@
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
</p>
[![Build](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
[![Build](https://github.com/filebrowser/filebrowser/actions/workflows/ci.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/ci.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser/v2)](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
[![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg)](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

View File

@@ -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,20 @@ tasks:
release:
desc: Create a new release
cmds:
- task: release-dry-run
- task: release-make
- task: docs:cli:generate
- git add www/docs/cli
- 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 +69,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

View File

@@ -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()

View File

@@ -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

View File

@@ -31,6 +31,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
addServerFlags(flags)
addUserFlags(flags)
flags.BoolP("signup", "s", false, "allow users to signup")
flags.Bool("hide-login-button", false, "hide login button from public pages")
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
flags.String("shell", "", "shell command to which other commands should be appended")
@@ -51,8 +52,11 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
// NB: these are string so they can be presented as octal in the help text
// as that's the conventional representation for modes in Unix.
flags.String("file-mode", fmt.Sprintf("%O", settings.DefaultFileMode), "Mode bits that new files are created with")
flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "Mode bits that new directories are created with")
flags.String("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) {
@@ -192,9 +196,10 @@ 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)
@@ -213,8 +218,12 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
fmt.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)

View File

@@ -41,6 +41,11 @@ override the options.`,
return err
}
hideLoginButton, err := getBool(flags, "hide-login-button")
if err != nil {
return err
}
createUserDir, err := getBool(flags, "create-user-dir")
if err != nil {
return err
@@ -81,9 +86,20 @@ override the options.`,
return err
}
tusChunkSize, err := flags.GetUint64("tus.chunkSize")
if err != nil {
return err
}
tusRetryCount, err := flags.GetUint16("tus.retryCount")
if err != nil {
return err
}
s := &settings.Settings{
Key: key,
Signup: signup,
HideLoginButton: hideLoginButton,
CreateUserDir: createUserDir,
MinimumPasswordLength: minLength,
Shell: convertCmdStrToCmdArray(shell),
@@ -96,6 +112,10 @@ override the options.`,
Theme: brandingTheme,
Files: brandingFiles,
},
Tus: settings.Tus{
ChunkSize: tusChunkSize,
RetryCount: tusRetryCount,
},
}
s.FileMode, err = getMode(flags, "file-mode")

View File

@@ -50,6 +50,8 @@ you want to change. Other options will remain unchanged.`,
ser.Port, err = getString(flags, flag.Name)
case "log":
ser.Log, err = getString(flags, flag.Name)
case "hide-login-button":
set.HideLoginButton, err = getBool(flags, flag.Name)
case "signup":
set.Signup, err = getBool(flags, flag.Name)
case "auth.method":
@@ -78,6 +80,10 @@ you want to change. Other options will remain unchanged.`,
set.FileMode, err = getMode(flags, flag.Name)
case "dir-mode":
set.DirMode, err = getMode(flags, flag.Name)
case "tus.chunkSize":
set.Tus.ChunkSize, err = flags.GetUint64(flag.Name)
case "tus.retryCount":
set.Tus.RetryCount, err = flags.GetUint16(flag.Name)
}
})

View File

@@ -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
},
}

View File

@@ -26,7 +26,6 @@ import (
"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"
@@ -70,7 +69,7 @@ func addServerFlags(flags *pflag.FlagSet) {
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.Int("img-processors", 4, "image processors count")
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
flags.Bool("disable-exec", true, "disables Command Runner feature")
@@ -141,7 +140,7 @@ user created with the credentials from options "username" and "password".`,
return err
}
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)
@@ -169,7 +168,7 @@ user created with the credentials from options "username" and "password".`,
if err != nil {
return err
}
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
socketPerm, err := cmd.Flags().GetUint32("socket-perm")
if err != nil {
return err
}
@@ -178,7 +177,7 @@ user created with the credentials from options "username" and "password".`,
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
}
@@ -233,7 +232,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,22 +240,10 @@ 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
return nil
}, pythonConfig{allowNoDB: true}),
}
//nolint:gocyclo
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) {
server, err := st.Settings.GetServer()
if err != nil {
@@ -420,6 +407,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
set := &settings.Settings{
Key: generateKey(),
Signup: false,
HideLoginButton: true,
CreateUserDir: false,
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
@@ -533,8 +521,7 @@ func initConfig() {
v.SetEnvPrefix("FB")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
if err := v.ReadInConfig(); err != nil {
var configParseError v.ConfigParseError

View File

@@ -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)
},
}

View File

@@ -94,7 +94,6 @@ func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
return viewMode, nil
}
//nolint:gocyclo
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
var visitErr error
visit := func(flag *pflag.Flag) {
@@ -135,6 +134,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.Sorting.By, err = getString(flags, flag.Name)
case "sorting.asc":
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
case "hideDotfiles":
defaults.HideDotfiles, err = getBool(flags, flag.Name)
}
if err != nil {
visitErr = err

View File

@@ -87,7 +87,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)
}
}

View File

@@ -23,16 +23,8 @@ import (
const dbPerms = 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)
return flags.GetString(flag)
}
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
@@ -48,13 +40,11 @@ func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
}
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
b, err := flags.GetBool(flag)
return b, returnErr(err)
return flags.GetBool(flag)
}
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
b, err := flags.GetUint(flag)
return b, returnErr(err)
return flags.GetUint(flag)
}
func generateKey() []byte {
@@ -76,7 +66,6 @@ type pythonConfig struct {
type pythonData struct {
hadDB bool
store *storage.Storage
err error
}
func dbExists(path string) (bool, error) {
@@ -89,7 +78,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

View File

@@ -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',
],
],
},
};

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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()

View File

@@ -600,7 +600,6 @@ var types = map[string]string{
".epub": "application/epub+zip",
}
//nolint:gochecknoinits
func init() {
for ext, typ := range types {
// skip errors

View File

@@ -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):

View 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>

View File

@@ -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,

View File

@@ -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();

View File

@@ -5,6 +5,7 @@
</div>
<div class="card-content">
<p>{{ $t("prompts.moveMessage") }}</p>
<file-list
ref="fileList"
@update:selected="(val) => (dest = val)"

View 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;
}

View File

@@ -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;
}
}

View File

@@ -166,6 +166,7 @@
"allowNew": "إنشاء ملفات و مجلدات جديدة",
"allowPublish": "نشر مقالات و صفحات جديدة",
"allowSignup": "اسمح للمستخدمين بالاشتراك",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
"branding": "الشعار",
"brandingDirectoryPath": "مسار مجلد الشعار",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
"branding": "Εξατομίκευση",
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",

View File

@@ -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",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "ایجاد فایلها و پوشه های جدید",
"allowPublish": "انتشار پست ها و صفحات جدید",
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
"branding": "برندسازی",
"brandingDirectoryPath": "مسیر پوشه برند",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "יצירת קבצים ותיקיות חדשות",
"allowPublish": "פרסום פוסטים ודפים חדשים",
"allowSignup": "אפשר למשתמשים חדשים להירשם",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
"branding": "מיתוג",
"brandingDirectoryPath": "נתיב תיקיית מיתוג",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "ファイルやフォルダーの新規作成",
"allowPublish": "新しい投稿やページの公開",
"allowSignup": "ユーザーの新規登録を許可",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(変更しない場合は空白のままにしてください)",
"branding": "ブランディング",
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",

View File

@@ -166,6 +166,7 @@
"allowNew": "파일/디렉토리 생성 허용",
"allowPublish": "새 포스트/페이지 생성 허용",
"allowSignup": "사용자 가입 허용",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(수정하지 않으면 비워두세요)",
"branding": "브랜딩",
"brandingDirectoryPath": "브랜드 디렉토리 경로",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "Создание новых файлов или каталогов",
"allowPublish": "Публикация новых записей и страниц",
"allowSignup": "Разрешить пользователям регистрироваться",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
"branding": "Брендинг",
"brandingDirectoryPath": "Путь к каталогу брендов",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "Створення нових файлів або каталогів",
"allowPublish": "Публікація нових записів та сторінок",
"allowSignup": "Дозволити користувачам реєструватися",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
"branding": "Брендинг",
"brandingDirectoryPath": "Шлях до каталогу брендів",

View File

@@ -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",

View File

@@ -166,6 +166,7 @@
"allowNew": "创建新文件和文件夹",
"allowPublish": "发布新的帖子与页面",
"allowSignup": "允许用户注册",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(留空以避免更改)",
"branding": "品牌",
"brandingDirectoryPath": "品牌信息文件夹路径",

View File

@@ -166,6 +166,7 @@
"allowNew": "建立新檔案和目錄",
"allowPublish": "發佈新的貼文與頁面",
"allowSignup": "允許使用者註冊",
"hideLoginButton": "Hide the login button from public pages",
"avoidChanges": "(留空以避免更改)",
"branding": "品牌",
"brandingDirectoryPath": "品牌資訊資料夾路徑",

View File

@@ -1,6 +1,7 @@
interface ISettings {
signup: boolean;
createUserDir: boolean;
hideLoginButton: boolean;
minimumPasswordLength: number;
userHomeBasePath: string;
defaults: SettingsDefaults;

View File

@@ -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,
};

View File

@@ -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>

View File

@@ -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

6
go.mod
View File

@@ -16,7 +16,6 @@ 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/shirou/gopsutil/v4 v4.25.10
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.1
@@ -24,7 +23,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 +38,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 +57,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 +69,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

2
go.sum
View File

@@ -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,6 +197,7 @@ 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=

View File

@@ -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()

View File

@@ -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))

View File

@@ -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)
}

View 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)
}
})

View File

@@ -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
}

View File

@@ -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)
}

View 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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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())
}

View File

@@ -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)
}
}

View File

@@ -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))

View File

@@ -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)

View File

@@ -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, "-")

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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)
}

View 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
```

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,50 @@
# filebrowser completion bash
Generate the autocompletion script for bash
## Synopsis
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(filebrowser completion bash)
To load completions for every new session, execute once:
### Linux:
filebrowser completion bash > /etc/bash_completion.d/filebrowser
### macOS:
filebrowser completion bash > $(brew --prefix)/etc/bash_completion.d/filebrowser
You will need to start a new shell for this setup to take effect.
```
filebrowser completion bash
```
## Options
```
-h, --help help for bash
--no-descriptions disable completion descriptions
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser completion](filebrowser-completion.md) - Generate the autocompletion script for the specified shell

View File

@@ -0,0 +1,41 @@
# filebrowser completion fish
Generate the autocompletion script for fish
## Synopsis
Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
filebrowser completion fish | source
To load completions for every new session, execute once:
filebrowser completion fish > ~/.config/fish/completions/filebrowser.fish
You will need to start a new shell for this setup to take effect.
```
filebrowser completion fish [flags]
```
## Options
```
-h, --help help for fish
--no-descriptions disable completion descriptions
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser completion](filebrowser-completion.md) - Generate the autocompletion script for the specified shell

View File

@@ -0,0 +1,38 @@
# filebrowser completion powershell
Generate the autocompletion script for powershell
## Synopsis
Generate the autocompletion script for powershell.
To load completions in your current shell session:
filebrowser completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
```
filebrowser completion powershell [flags]
```
## Options
```
-h, --help help for powershell
--no-descriptions disable completion descriptions
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser completion](filebrowser-completion.md) - Generate the autocompletion script for the specified shell

View File

@@ -0,0 +1,52 @@
# filebrowser completion zsh
Generate the autocompletion script for zsh
## Synopsis
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(filebrowser completion zsh)
To load completions for every new session, execute once:
### Linux:
filebrowser completion zsh > "${fpath[1]}/_filebrowser"
### macOS:
filebrowser completion zsh > $(brew --prefix)/share/zsh/site-functions/_filebrowser
You will need to start a new shell for this setup to take effect.
```
filebrowser completion zsh [flags]
```
## Options
```
-h, --help help for zsh
--no-descriptions disable completion descriptions
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser completion](filebrowser-completion.md) - Generate the autocompletion script for the specified shell

View File

@@ -0,0 +1,31 @@
# filebrowser completion
Generate the autocompletion script for the specified shell
## Synopsis
Generate the autocompletion script for filebrowser for the specified shell.
See each sub-command's help for details on how to use the generated script.
## Options
```
-h, --help help for completion
```
## 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 completion bash](filebrowser-completion-bash.md) - Generate the autocompletion script for bash
* [filebrowser completion fish](filebrowser-completion-fish.md) - Generate the autocompletion script for fish
* [filebrowser completion powershell](filebrowser-completion-powershell.md) - Generate the autocompletion script for powershell
* [filebrowser completion zsh](filebrowser-completion-zsh.md) - Generate the autocompletion script for zsh

View File

@@ -0,0 +1,29 @@
# filebrowser config cat
Prints the configuration
## Synopsis
Prints the configuration.
```
filebrowser config cat [flags]
```
## Options
```
-h, --help help for cat
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser config](filebrowser-config.md) - Configuration management utility

View File

@@ -0,0 +1,31 @@
# filebrowser config export
Export the configuration to a file
## Synopsis
Export the configuration to a file. The path must be for a
json or yaml file. This exported configuration can be changed,
and imported again with 'config import' command.
```
filebrowser config export <path> [flags]
```
## Options
```
-h, --help help for export
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser config](filebrowser-config.md) - Configuration management utility

View File

@@ -0,0 +1,36 @@
# filebrowser config import
Import a configuration file
## Synopsis
Import a configuration file. This will replace all the existing
configuration. Can be used with or without unexisting databases.
If used with a nonexisting database, a key will be generated
automatically. Otherwise the key will be kept the same as in the
database.
The path must be for a json or yaml file.
```
filebrowser config import <path> [flags]
```
## Options
```
-h, --help help for import
```
## Options inherited from parent commands
```
-c, --config string config file path
-d, --database string database path (default "./filebrowser.db")
```
## See Also
* [filebrowser config](filebrowser-config.md) - Configuration management utility

Some files were not shown because too many files have changed in this diff Show More