Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
119609c834 | ||
|
|
d48f5665d6 | ||
|
|
54306bdc87 | ||
|
|
33deedf559 | ||
|
|
88d1eecc4e | ||
|
|
43db19f8c8 | ||
|
|
a360f26979 | ||
|
|
ab367a2740 | ||
|
|
5df5508a85 | ||
|
|
6d5aa355e4 | ||
|
|
a3b5584505 | ||
|
|
8db2411cd4 | ||
|
|
c284de9d2c | ||
|
|
984ea7b569 | ||
|
|
fd7b70cf38 | ||
|
|
13e3b46718 | ||
|
|
d759ab0bd8 | ||
|
|
00323a8f37 | ||
|
|
420adea7e6 | ||
|
|
f576d38a7e | ||
|
|
9bdc67c207 | ||
|
|
f41585f039 | ||
|
|
89be0b1873 | ||
|
|
8c5dc7641e | ||
|
|
0a0cb8046f | ||
|
|
f89435c068 | ||
|
|
fb8d41eb9a | ||
|
|
0fadaccaa2 | ||
|
|
e24e1f1aba | ||
|
|
5de4099cba | ||
|
|
d01493106d | ||
|
|
2d9689dd6a | ||
|
|
c4c1cea230 | ||
|
|
ceb5e723f3 | ||
|
|
ebc7d2303d | ||
|
|
23c4e4565b | ||
|
|
17f1e08a58 | ||
|
|
ffc850454e | ||
|
|
13814e1119 |
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Lint Frontend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
package_json_file: "frontend/package.json"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: Lint Backend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.x"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.x"
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -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:
|
||||
|
||||
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install Task
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install Task
|
||||
|
||||
@@ -2,8 +2,13 @@ version: "2"
|
||||
|
||||
linters:
|
||||
default: standard
|
||||
enable:
|
||||
- gocritic
|
||||
- govet
|
||||
- revive
|
||||
exclusions:
|
||||
presets:
|
||||
- std-error-handling
|
||||
- comments
|
||||
paths:
|
||||
- frontend/
|
||||
|
||||
72
CHANGELOG.md
72
CHANGELOG.md
@@ -2,6 +2,78 @@
|
||||
|
||||
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.49.0](https://github.com/filebrowser/filebrowser/compare/v2.48.2...v2.49.0) (2025-11-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add "copy download link to clipboard" button to Share prompt ([#5173](https://github.com/filebrowser/filebrowser/issues/5173)) ([d48f566](https://github.com/filebrowser/filebrowser/commit/d48f5665d6975c4cbbdf9be20dc2e0106db02f01))
|
||||
* add Bulgarian language ([8db2411](https://github.com/filebrowser/filebrowser/commit/8db2411cd43a23ae3292a817e3524cfdb5ae9b86))
|
||||
* Updates for project File Browser ([#5566](https://github.com/filebrowser/filebrowser/issues/5566)) ([54306bd](https://github.com/filebrowser/filebrowser/commit/54306bdc8700fac489326ae81e28ac5db0580d13))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* display friendly error message for password validation on signup ([#5563](https://github.com/filebrowser/filebrowser/issues/5563)) ([6d5aa35](https://github.com/filebrowser/filebrowser/commit/6d5aa355e433d613e5a3ae137f410c63baeddf0f))
|
||||
|
||||
## [2.48.2](https://github.com/filebrowser/filebrowser/compare/v2.48.1...v2.48.2) (2025-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add transitionary support for FB_BASEURL ([984ea7b](https://github.com/filebrowser/filebrowser/commit/984ea7b569e3bd33b6f91ebdf63684a618d51e94))
|
||||
|
||||
|
||||
### Refactorings
|
||||
|
||||
* rename python for clarification ([fd7b70c](https://github.com/filebrowser/filebrowser/commit/fd7b70cf38ac67c8c9ff79f2e7fde5e2ec45a1de))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,18 @@ var cmdsAddCmd = &cobra.Command{
|
||||
Short: "Add a command to run on a specific event",
|
||||
Long: `Add a command to run on a specific event.`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
s, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command := strings.Join(args[1:], " ")
|
||||
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
||||
err = d.store.Settings.Save(s)
|
||||
err = st.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printEvents(s.Commands)
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@ var cmdsLsCmd = &cobra.Command{
|
||||
Short: "List all commands for each event",
|
||||
Long: `List all commands for each event.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||
s, err := st.Settings.Get()
|
||||
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{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ including 'index_end'.`,
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
s, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
s, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -55,11 +55,11 @@ including 'index_end'.`,
|
||||
}
|
||||
|
||||
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
|
||||
err = d.store.Settings.Save(s)
|
||||
err = st.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printEvents(s.Commands)
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
163
cmd/config.go
163
cmd/config.go
@@ -30,12 +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("hide-login-button", false, "hide login button from public pages")
|
||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||
flags.Bool("hideLoginButton", false, "hide login button from public pages")
|
||||
flags.Bool("createUserDir", false, "generate user's home directory automatically")
|
||||
flags.Uint("minimumPasswordLength", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||
|
||||
// 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")
|
||||
@@ -50,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
|
||||
}
|
||||
@@ -88,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
|
||||
}
|
||||
@@ -110,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
|
||||
}
|
||||
@@ -146,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)
|
||||
}
|
||||
@@ -198,6 +204,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||
|
||||
fmt.Fprintln(w, "\nBranding:")
|
||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||
@@ -205,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)
|
||||
@@ -214,7 +222,16 @@ 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)
|
||||
@@ -225,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)
|
||||
@@ -237,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, "", " ")
|
||||
@@ -246,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
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ var configCatCmd = &cobra.Command{
|
||||
Short: "Prints the configuration",
|
||||
Long: `Prints the configuration.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: python(func(_ *cobra.Command, _ []string, d *pythonData) error {
|
||||
set, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(_ *cobra.Command, _ []string, st *store) error {
|
||||
set, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ser, err := d.store.Settings.GetServer()
|
||||
ser, err := st.Settings.GetServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
auther, err := st.Auth.Get(set.AuthMethod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printSettings(ser, set, auther)
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -15,18 +15,18 @@ var configExportCmd = &cobra.Command{
|
||||
json or yaml file. This exported configuration can be changed,
|
||||
and imported again with 'config import' command.`,
|
||||
Args: jsonYamlArg,
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
settings, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
settings, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := d.store.Settings.GetServer()
|
||||
server, err := st.Settings.GetServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
||||
auther, err := st.Auth.Get(settings.AuthMethod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,5 +42,5 @@ and imported again with 'config import' command.`,
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ database.
|
||||
|
||||
The path must be for a json or yaml file.`,
|
||||
Args: jsonYamlArg,
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
var key []byte
|
||||
var err error
|
||||
if d.hadDB {
|
||||
settings, settingErr := d.store.Settings.Get()
|
||||
if st.databaseExisted {
|
||||
settings, settingErr := st.Settings.Get()
|
||||
if settingErr != nil {
|
||||
return settingErr
|
||||
}
|
||||
@@ -54,12 +54,12 @@ The path must be for a json or yaml file.`,
|
||||
}
|
||||
|
||||
file.Settings.Key = key
|
||||
err = d.store.Settings.Save(file.Settings)
|
||||
err = st.Settings.Save(file.Settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(file.Server)
|
||||
err = st.Settings.SaveServer(file.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -98,13 +98,13 @@ The path must be for a json or yaml file.`,
|
||||
return autherErr
|
||||
}
|
||||
|
||||
err = d.store.Auth.Save(auther)
|
||||
err = st.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return printSettings(file.Server, file.Settings, auther)
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}, storeOptions{allowsNoDatabase: true}),
|
||||
}
|
||||
|
||||
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
||||
|
||||
@@ -22,158 +22,31 @@ this options can be changed in the future with the command
|
||||
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{}
|
||||
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||
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")
|
||||
// Save updated config
|
||||
err = st.Settings.Save(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hideLoginButton, err := getBool(flags, "hide-login-button")
|
||||
err = st.Settings.SaveServer(ser)
|
||||
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,
|
||||
HideLoginButton: hideLoginButton,
|
||||
CreateUserDir: createUserDir,
|
||||
MinimumPasswordLength: minLength,
|
||||
Shell: convertCmdStrToCmdArray(shell),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Branding: settings.Branding{
|
||||
Name: brandingName,
|
||||
DisableExternal: brandingDisableExternal,
|
||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
||||
Theme: brandingTheme,
|
||||
Files: brandingFiles,
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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)
|
||||
err = st.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,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}),
|
||||
}, storeOptions{expectsNoDatabase: true}),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -16,107 +15,47 @@ var configSetCmd = &cobra.Command{
|
||||
Long: `Updates the configuration. Set the flags for the options
|
||||
you want to change. Other options will remain unchanged.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||
flags := cmd.Flags()
|
||||
set, err := d.store.Settings.Get()
|
||||
|
||||
// Read existing config
|
||||
set, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ser, err := d.store.Settings.GetServer()
|
||||
ser, err := st.Settings.GetServer()
|
||||
if err != nil {
|
||||
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 "hide-login-button":
|
||||
set.HideLoginButton, err = getBool(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)
|
||||
}
|
||||
})
|
||||
|
||||
auther, err := st.Auth.Get(set.AuthMethod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = getUserDefaults(flags, &set.Defaults, false)
|
||||
// Get updated config
|
||||
auther, err = getSettings(flags, set, ser, auther, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read the defaults
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
// Save updated config
|
||||
err = st.Auth.Save(auther)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if there are new flags for existing auth method
|
||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
||||
err = st.Settings.Save(set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
err = st.Settings.SaveServer(ser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return printSettings(ser, set, auther)
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
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.ReplaceAll(name, "-", " ")
|
||||
names[i] = name
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
toc := ""
|
||||
for _, name := range names {
|
||||
toc += "* [" + name + "](cli/" + strings.ReplaceAll(name, " ", "-") + ".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(_ 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.ReplaceAll(cmd.CommandPath(), " ", "-") + ".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
|
||||
},
|
||||
}
|
||||
|
||||
377
cmd/root.go
377
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(_ *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if newName, ok := flagNamesMigrations[name]; ok {
|
||||
|
||||
if !warnedFlags[name] {
|
||||
warnedFlags[name] = true
|
||||
log.Printf("DEPRECATION NOTICE: 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)
|
||||
RunE: withViperAndStore(func(_ *cobra.Command, _ []string, v *viper.Viper, st *store) error {
|
||||
if !st.databaseExisted {
|
||||
err := quickSetup(v, st.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// build img service
|
||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if workersCount < 1 {
|
||||
imgWorkersCount := v.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 := v.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(v, st.Storage)
|
||||
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 := v.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, st.Storage, 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,78 @@ 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
|
||||
}, storeOptions{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")
|
||||
// TODO(remove): remove after July 2026.
|
||||
} else if v := os.Getenv("FB_BASEURL"); v != "" {
|
||||
log.Println("DEPRECATION NOTICE: Environment variable FB_BASEURL has been deprecated, use FB_BASE_URL instead")
|
||||
server.BaseURL = v
|
||||
}
|
||||
|
||||
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 +344,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 +351,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,7 +372,7 @@ func setupLog(logMethod string) {
|
||||
}
|
||||
}
|
||||
|
||||
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
func quickSetup(v *viper.Viper, s *storage.Storage) error {
|
||||
log.Println("Performing quick setup")
|
||||
|
||||
set := &settings.Settings{
|
||||
@@ -428,7 +386,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
Scope: ".",
|
||||
Locale: "en",
|
||||
SingleClick: false,
|
||||
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
|
||||
AceEditorTheme: v.GetString("defaults.aceEditorTheme"),
|
||||
Perm: users.Permissions{
|
||||
Admin: false,
|
||||
Execute: true,
|
||||
@@ -452,39 +410,44 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
||||
if v.GetBool("noauth") {
|
||||
set.AuthMethod = auth.MethodNoAuth
|
||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||
err = s.Auth.Save(&auth.NoAuth{})
|
||||
} else {
|
||||
set.AuthMethod = auth.MethodJSONAuth
|
||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||
err = s.Auth.Save(&auth.JSONAuth{})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.store.Settings.Save(set)
|
||||
err = s.Settings.Save(set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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: v.GetString("baseURL"),
|
||||
Port: v.GetString("port"),
|
||||
Log: v.GetString("log"),
|
||||
TLSKey: v.GetString("key"),
|
||||
TLSCert: v.GetString("cert"),
|
||||
Address: v.GetString("address"),
|
||||
Root: v.GetString("root"),
|
||||
TokenExpirationTime: v.GetString("tokenExpirationTime"),
|
||||
EnableThumbnails: !v.GetBool("disableThumbnails"),
|
||||
ResizePreview: !v.GetBool("disablePreviewResize"),
|
||||
EnableExec: !v.GetBool("disableExec"),
|
||||
TypeDetectionByHeader: !v.GetBool("disableTypeDetectionByHeader"),
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
err = s.Settings.SaveServer(ser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := getStringParam(flags, "username")
|
||||
password := getStringParam(flags, "password")
|
||||
username := v.GetString("username")
|
||||
password := v.GetString("password")
|
||||
|
||||
if password == "" {
|
||||
var pwd string
|
||||
@@ -515,35 +478,5 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||
set.Defaults.Apply(user)
|
||||
user.Perm.Admin = true
|
||||
|
||||
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()
|
||||
}
|
||||
return s.Users.Save(user)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ including 'index_end'.`,
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||
i, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -55,14 +55,14 @@ including 'index_end'.`,
|
||||
|
||||
user := func(u *users.User) error {
|
||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||
return d.store.Users.Save(u)
|
||||
return st.Users.Save(u)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) error {
|
||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||
return d.store.Settings.Save(s)
|
||||
return st.Settings.Save(s)
|
||||
}
|
||||
|
||||
return runRules(d.store, cmd, user, global)
|
||||
}, pythonConfig{}),
|
||||
return runRules(st.Storage, cmd, user, global)
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -21,15 +21,19 @@ var rulesAddCmd = &cobra.Command{
|
||||
Short: "Add a global rule or user rule",
|
||||
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")
|
||||
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||
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 {
|
||||
@@ -49,14 +53,14 @@ var rulesAddCmd = &cobra.Command{
|
||||
|
||||
user := func(u *users.User) error {
|
||||
u.Rules = append(u.Rules, rule)
|
||||
return d.store.Users.Save(u)
|
||||
return st.Users.Save(u)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) error {
|
||||
s.Rules = append(s.Rules, rule)
|
||||
return d.store.Settings.Save(s)
|
||||
return st.Settings.Save(s)
|
||||
}
|
||||
|
||||
return runRules(d.store, cmd, user, global)
|
||||
}, pythonConfig{}),
|
||||
return runRules(st.Storage, cmd, user, global)
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
|
||||
Short: "List global rules or user specific rules",
|
||||
Long: `List global rules or user specific rules.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||
return runRules(d.store, cmd, nil, nil)
|
||||
}, pythonConfig{}),
|
||||
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||
return runRules(st.Storage, cmd, nil, nil)
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
54
cmd/users.go
54
cmd/users.go
@@ -80,67 +80,66 @@ func addUserFlags(flags *pflag.FlagSet) {
|
||||
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
|
||||
flags.Bool("hideDotfiles", false, "hide dotfiles")
|
||||
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
|
||||
flags.Bool("hide-dotfiles", false, "Hide dotfiles by default")
|
||||
}
|
||||
|
||||
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)
|
||||
case "hide-dotfiles":
|
||||
defaults.HideDotfiles, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,5 +148,6 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
||||
} else {
|
||||
flags.Visit(visit)
|
||||
}
|
||||
return visitErr
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@ var usersAddCmd = &cobra.Command{
|
||||
Short: "Create a new user",
|
||||
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 {
|
||||
s, err := d.store.Settings.Get()
|
||||
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||
flags := cmd.Flags()
|
||||
s, err := st.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,39 +32,36 @@ 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)
|
||||
|
||||
servSettings, err := d.store.Settings.GetServer()
|
||||
servSettings, err := st.Settings.GetServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// since getUserDefaults() polluted s.Defaults.Scope
|
||||
// which makes the Scope not the one saved in the db
|
||||
// we need the right s.Defaults.Scope here
|
||||
s2, err := d.store.Settings.Get()
|
||||
s2, err := st.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,11 +72,11 @@ var usersAddCmd = &cobra.Command{
|
||||
}
|
||||
user.Scope = userHome
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
err = st.Users.Save(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printUsers([]*users.User{user})
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ var usersExportCmd = &cobra.Command{
|
||||
Long: `Export all users to a json or yaml file. Please indicate the
|
||||
path to the file where you want to write the users.`,
|
||||
Args: jsonYamlArg,
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
list, err := d.store.Users.Gets("")
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
list, err := st.Users.Gets("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -25,5 +25,5 @@ path to the file where you want to write the users.`,
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var usersLsCmd = &cobra.Command{
|
||||
RunE: findUsers,
|
||||
}
|
||||
|
||||
var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
var findUsers = withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
var (
|
||||
list []*users.User
|
||||
user *users.User
|
||||
@@ -36,14 +36,14 @@ var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) erro
|
||||
if len(args) == 1 {
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
if username != "" {
|
||||
user, err = d.store.Users.Get("", username)
|
||||
user, err = st.Users.Get("", username)
|
||||
} else {
|
||||
user, err = d.store.Users.Get("", id)
|
||||
user, err = st.Users.Get("", id)
|
||||
}
|
||||
|
||||
list = []*users.User{user}
|
||||
} else {
|
||||
list, err = d.store.Users.Gets("")
|
||||
list, err = st.Users.Gets("")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -51,4 +51,4 @@ var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) erro
|
||||
}
|
||||
printUsers(list)
|
||||
return nil
|
||||
}, pythonConfig{})
|
||||
}, storeOptions{})
|
||||
|
||||
@@ -25,7 +25,8 @@ file. You can use this command to import new users to your
|
||||
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 {
|
||||
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||
flags := cmd.Flags()
|
||||
fd, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -45,13 +46,13 @@ list or set it to 0.`,
|
||||
}
|
||||
}
|
||||
|
||||
replace, err := getBool(cmd.Flags(), "replace")
|
||||
replace, err := flags.GetBool("replace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if replace {
|
||||
oldUsers, userImportErr := d.store.Users.Gets("")
|
||||
oldUsers, userImportErr := st.Users.Gets("")
|
||||
if userImportErr != nil {
|
||||
return userImportErr
|
||||
}
|
||||
@@ -62,20 +63,20 @@ list or set it to 0.`,
|
||||
}
|
||||
|
||||
for _, user := range oldUsers {
|
||||
err = d.store.Users.Delete(user.ID)
|
||||
err = st.Users.Delete(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
||||
overwrite, err := flags.GetBool("overwrite")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range list {
|
||||
onDB, err := d.store.Users.Get("", user.ID)
|
||||
onDB, err := st.Users.Get("", user.ID)
|
||||
|
||||
// User exists in DB.
|
||||
if err == nil {
|
||||
@@ -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 := st.Users.Get("", user.Username); err == nil {
|
||||
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||
}
|
||||
}
|
||||
@@ -97,13 +98,13 @@ list or set it to 0.`,
|
||||
user.ID = 0
|
||||
}
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
err = st.Users.Save(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
func usernameConflictError(username string, originalID, newID uint) error {
|
||||
|
||||
@@ -15,14 +15,14 @@ var usersRmCmd = &cobra.Command{
|
||||
Short: "Delete a user by username or id",
|
||||
Long: `Delete a user by username or id`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
||||
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
var err error
|
||||
|
||||
if username != "" {
|
||||
err = d.store.Users.Delete(username)
|
||||
err = st.Users.Delete(username)
|
||||
} else {
|
||||
err = d.store.Users.Delete(id)
|
||||
err = st.Users.Delete(id)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -30,5 +30,5 @@ var usersRmCmd = &cobra.Command{
|
||||
}
|
||||
fmt.Println("user deleted successfully")
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
@@ -21,19 +21,20 @@ var usersUpdateCmd = &cobra.Command{
|
||||
Long: `Updates an existing user. Set the flags for the
|
||||
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])
|
||||
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||
flags := cmd.Flags()
|
||||
password, err := getString(flags, "password")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newUsername, err := getString(flags, "username")
|
||||
username, id := parseUsernameOrID(args[0])
|
||||
password, err := flags.GetString("password")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := d.store.Settings.Get()
|
||||
newUsername, err := flags.GetString("username")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := st.Settings.Get()
|
||||
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)
|
||||
user, err = st.Users.Get("", id)
|
||||
} else {
|
||||
user, err = d.store.Users.Get("", username)
|
||||
user, err = st.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
|
||||
}
|
||||
@@ -96,11 +99,11 @@ options you want to change.`,
|
||||
}
|
||||
}
|
||||
|
||||
err = d.store.Users.Update(user)
|
||||
err = st.Users.Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printUsers([]*users.User{user})
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}, storeOptions{}),
|
||||
}
|
||||
|
||||
186
cmd/utils.go
186
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,22 @@ 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 +48,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 +58,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,42 +68,137 @@ func dbExists(path string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
// 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 store struct {
|
||||
*storage.Storage
|
||||
databaseExisted bool
|
||||
}
|
||||
|
||||
type storeOptions struct {
|
||||
expectsNoDatabase bool
|
||||
allowsNoDatabase bool
|
||||
}
|
||||
|
||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||
|
||||
// withViperAndStore initializes Viper and the storage.Store and passes them to the callback function.
|
||||
// This function should only be used by [withStore] and the root command. No other command should call
|
||||
// this function directly.
|
||||
func withViperAndStore(fn func(cmd *cobra.Command, args []string, v *viper.Viper, store *store) error, options storeOptions) cobraFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
data := &pythonData{hadDB: true}
|
||||
|
||||
path := getStringParam(cmd.Flags(), "database")
|
||||
absPath, err := filepath.Abs(path)
|
||||
v, err := initViper(cmd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := filepath.Abs(v.GetString("database"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := dbExists(path)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if exists && cfg.noDB {
|
||||
log.Fatal(absPath + " already exists")
|
||||
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
|
||||
log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.")
|
||||
} else if !exists && !cfg.noDB {
|
||||
log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db"))
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case exists && options.expectsNoDatabase:
|
||||
log.Fatal(path + " already exists")
|
||||
case !exists && !options.expectsNoDatabase && !options.allowsNoDatabase:
|
||||
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
|
||||
case !exists && !options.expectsNoDatabase:
|
||||
log.Println("WARNING: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(path, "filebrowser.db"))
|
||||
}
|
||||
|
||||
log.Println("Using database: " + absPath)
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
||||
log.Println("Using database: " + path)
|
||||
|
||||
db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
|
||||
storage, err := bolt.NewStorage(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(cmd, args, data)
|
||||
|
||||
store := &store{
|
||||
Storage: storage,
|
||||
databaseExisted: exists,
|
||||
}
|
||||
|
||||
return fn(cmd, args, v, store)
|
||||
}
|
||||
}
|
||||
|
||||
func withStore(fn func(cmd *cobra.Command, args []string, store *store) error, options storeOptions) cobraFunc {
|
||||
return withViperAndStore(func(cmd *cobra.Command, args []string, _ *viper.Viper, store *store) error {
|
||||
return fn(cmd, args, store)
|
||||
}, options)
|
||||
}
|
||||
|
||||
func marshal(filename string, data interface{}) error {
|
||||
fd, err := os.Create(filename)
|
||||
if err != nil {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,12 +25,12 @@ func TestFileCache(t *testing.T) {
|
||||
// store new key
|
||||
err := cache.Store(ctx, key, []byte(value))
|
||||
require.NoError(t, err)
|
||||
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, value)
|
||||
checkValue(ctx, t, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, value)
|
||||
|
||||
// update existing key
|
||||
err = cache.Store(ctx, key, []byte(newValue))
|
||||
require.NoError(t, err)
|
||||
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, newValue)
|
||||
checkValue(ctx, t, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, newValue)
|
||||
|
||||
// delete key
|
||||
err = cache.Delete(ctx, key)
|
||||
@@ -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(ctx context.Context, t *testing.T, 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
|
||||
|
||||
@@ -71,5 +71,5 @@
|
||||
"vite-plugin-compression2": "^2.3.1",
|
||||
"vue-tsc": "^3.1.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
|
||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
||||
}
|
||||
|
||||
669
frontend/pnpm-lock.yaml
generated
669
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
<th>{{ $t("settings.shareDuration") }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="link in links" :key="link.hash">
|
||||
@@ -24,7 +25,7 @@
|
||||
</td>
|
||||
<td class="small">
|
||||
<button
|
||||
class="action copy-clipboard"
|
||||
class="action"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"
|
||||
@click="copyToClipboard(buildLink(link))"
|
||||
@@ -32,6 +33,17 @@
|
||||
<i class="material-icons">content_paste</i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button
|
||||
class="action"
|
||||
:aria-label="$t('buttons.copyDownloadLinkToClipboard')"
|
||||
:title="$t('buttons.copyDownloadLinkToClipboard')"
|
||||
:disabled="!!link.password_hash"
|
||||
@click="copyToClipboard(buildDownloadLink(link))"
|
||||
>
|
||||
<i class="material-icons">content_paste_go</i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button
|
||||
class="action"
|
||||
@@ -132,7 +144,7 @@
|
||||
<script>
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useFileStore } from "@/stores/file";
|
||||
import { share as api } from "@/api";
|
||||
import * as api from "@/api/index";
|
||||
import dayjs from "dayjs";
|
||||
import { useLayoutStore } from "@/stores/layout";
|
||||
import { copy } from "@/utils/clipboard";
|
||||
@@ -172,7 +184,7 @@ export default {
|
||||
},
|
||||
async beforeMount() {
|
||||
try {
|
||||
const links = await api.get(this.url);
|
||||
const links = await api.share.get(this.url);
|
||||
this.links = links;
|
||||
this.sort();
|
||||
|
||||
@@ -211,9 +223,14 @@ export default {
|
||||
let res = null;
|
||||
|
||||
if (!this.time) {
|
||||
res = await api.create(this.url, this.password);
|
||||
res = await api.share.create(this.url, this.password);
|
||||
} else {
|
||||
res = await api.create(this.url, this.password, this.time, this.unit);
|
||||
res = await api.share.create(
|
||||
this.url,
|
||||
this.password,
|
||||
this.time,
|
||||
this.unit
|
||||
);
|
||||
}
|
||||
|
||||
this.links.push(res);
|
||||
@@ -231,7 +248,7 @@ export default {
|
||||
deleteLink: async function (event, link) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await api.remove(link.hash);
|
||||
await api.share.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
|
||||
if (this.links.length == 0) {
|
||||
@@ -245,7 +262,16 @@ export default {
|
||||
return dayjs(time * 1000).fromNow();
|
||||
},
|
||||
buildLink(share) {
|
||||
return api.getShareURL(share);
|
||||
return api.share.getShareURL(share);
|
||||
},
|
||||
buildDownloadLink(share) {
|
||||
return api.pub.getDownloadURL(
|
||||
{
|
||||
hash: share.hash,
|
||||
path: "",
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
sort() {
|
||||
this.links = this.links.sort((a, b) => {
|
||||
|
||||
@@ -15,10 +15,8 @@ export default {
|
||||
data() {
|
||||
const dataObj = {};
|
||||
const locales = {
|
||||
he: "עברית",
|
||||
hr: "Hrvatski",
|
||||
hu: "Magyar",
|
||||
ar: "العربية",
|
||||
bg: "български език",
|
||||
ca: "Català",
|
||||
cs: "Čeština",
|
||||
de: "Deutsch",
|
||||
@@ -26,15 +24,18 @@ export default {
|
||||
en: "English",
|
||||
es: "Español",
|
||||
fr: "Français",
|
||||
he: "עברית",
|
||||
hr: "Hrvatski",
|
||||
hu: "Magyar",
|
||||
is: "Icelandic",
|
||||
it: "Italiano",
|
||||
ja: "日本語",
|
||||
ko: "한국어",
|
||||
"nl-be": "Dutch (Belgium)",
|
||||
no: "Norsk",
|
||||
"nl-be": "Dutch (Belgium)",
|
||||
pl: "Polski",
|
||||
"pt-br": "Português",
|
||||
pt: "Português (Brasil)",
|
||||
"pt-br": "Português (Brasil)",
|
||||
pt: "Português (Portugal)",
|
||||
ro: "Romanian",
|
||||
ru: "Русский",
|
||||
sk: "Slovenčina",
|
||||
|
||||
@@ -98,7 +98,8 @@ main .spinner .bounce2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.action.disabled {
|
||||
.action.disabled,
|
||||
.action[disabled] {
|
||||
opacity: 0.2;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -460,4 +461,4 @@ html[dir="rtl"] .card-content .small + input {
|
||||
html[dir="rtl"] .card.floating .card-content .file-list {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "إسم المستخدم",
|
||||
"usernameTaken": "إسم المستخدم غير متاح",
|
||||
"wrongCredentials": "بيانات دخول خاطئة",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
273
frontend/src/i18n/bg.json
Normal file
273
frontend/src/i18n/bg.json
Normal file
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"buttons": {
|
||||
"cancel": "Отмени",
|
||||
"clear": "Изчисти",
|
||||
"close": "Затвори",
|
||||
"continue": "Продължи",
|
||||
"copy": "Копирай",
|
||||
"copyFile": "Копирай файл",
|
||||
"copyToClipboard": "Копирай в клипборда",
|
||||
"copyDownloadLinkToClipboard": "Копирай линк за сваляне в клипборда",
|
||||
"create": "Създай",
|
||||
"delete": "Изтрий",
|
||||
"download": "Свали",
|
||||
"file": "Файл",
|
||||
"folder": "Папка",
|
||||
"fullScreen": "Превключване на цял екран",
|
||||
"hideDotfiles": "Скрий файлове започващи с точка",
|
||||
"info": "Информация",
|
||||
"more": "Повече",
|
||||
"move": "Премести",
|
||||
"moveFile": "Премести файл",
|
||||
"new": "Нов",
|
||||
"next": "Следващ",
|
||||
"ok": "Потвърди",
|
||||
"permalink": "Вземи постоянен линк",
|
||||
"previous": "Предишен",
|
||||
"preview": "Преглед",
|
||||
"publish": "Публикуване",
|
||||
"rename": "Преименуване",
|
||||
"replace": "Замяна",
|
||||
"reportIssue": "Докладвай проблем",
|
||||
"save": "Запис",
|
||||
"schedule": "График",
|
||||
"search": "Търсене",
|
||||
"select": "Избери",
|
||||
"selectMultiple": "Избери няколко",
|
||||
"share": "Сподели",
|
||||
"shell": "Превключване на терминала",
|
||||
"submit": "Изпрати",
|
||||
"switchView": "Смени изгледа",
|
||||
"toggleSidebar": "Превключване на страничен панел",
|
||||
"update": "Обнови",
|
||||
"upload": "Качи",
|
||||
"openFile": "Отвори файл",
|
||||
"discardChanges": "Изчисти",
|
||||
"saveChanges": "Запиши промените"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Свали файл",
|
||||
"downloadFolder": "Свали папка",
|
||||
"downloadSelected": "Свали избраното"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Сигурни ли сте, че искате да прекратите?"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Нямате право на достъп.",
|
||||
"internal": "Взникна грешка.",
|
||||
"notFound": "Локацията не може да бъде достигната.",
|
||||
"connection": "Сървъра не може да бъде достигнат."
|
||||
},
|
||||
"files": {
|
||||
"body": "Тяло",
|
||||
"closePreview": "Затвори прегледа",
|
||||
"files": "Файлове",
|
||||
"folders": "Папки",
|
||||
"home": "Начало",
|
||||
"lastModified": "Последна промяна",
|
||||
"loading": "Зареждане ...",
|
||||
"lonely": "Тук е самотно ...",
|
||||
"metadata": "Метаданни",
|
||||
"multipleSelectionEnabled": "Множествения избор е разрешен",
|
||||
"name": "Име",
|
||||
"size": "Размер",
|
||||
"sortByLastModified": "Подредба по последна промяна",
|
||||
"sortByName": "Подредба по име",
|
||||
"sortBySize": "Подредба по размер",
|
||||
"noPreview": "За този файл не е наличен преглед."
|
||||
},
|
||||
"help": {
|
||||
"click": "избери файл или директория",
|
||||
"ctrl": {
|
||||
"click": "избери файлове или директории",
|
||||
"f": "отваря търсене",
|
||||
"s": "запиши файл или свари директория тук"
|
||||
},
|
||||
"del": "изтрий избраните",
|
||||
"doubleClick": "отвори файл или директория",
|
||||
"esc": "изтрий избраното и/или затвори",
|
||||
"f1": "тази информация",
|
||||
"f2": "преименувай файл",
|
||||
"help": "Помощ"
|
||||
},
|
||||
"login": {
|
||||
"createAnAccount": "Създай акаунт",
|
||||
"loginInstead": "Вече имаш акаунт",
|
||||
"password": "Парола",
|
||||
"passwordConfirm": "Парола Потвърждение",
|
||||
"passwordsDontMatch": "Паролите не съвпадат",
|
||||
"signup": "Абониране",
|
||||
"submit": "Вход",
|
||||
"username": "Потребителско име",
|
||||
"usernameTaken": "Потребителското име вече се използва",
|
||||
"wrongCredentials": "Грешни потребителско име и/или парола",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Бяхте разлогнати поради неактивност"
|
||||
}
|
||||
},
|
||||
"permanent": "Постоянен",
|
||||
"prompts": {
|
||||
"copy": "Копирай",
|
||||
"copyMessage": "Избери къде да копираш файловете си:",
|
||||
"currentlyNavigating": "В момента навигира към:",
|
||||
"deleteMessageMultiple": "Сигурни ли сте, че искате да изтриете {count} файл(а)?",
|
||||
"deleteMessageSingle": "Сигурни ли сте, че искате да изтриете този файл/папка?",
|
||||
"deleteMessageShare": "Сигурни ли сте, че искате на изтриете това споделяне({path})?",
|
||||
"deleteUser": "Сигурни ли сте, че искате да изтриете този потребител?",
|
||||
"deleteTitle": "Изтрий файлове",
|
||||
"displayName": "Име за показване:",
|
||||
"download": "Свали файлове",
|
||||
"downloadMessage": "Изберете формата, в който искате да свалите.",
|
||||
"error": "Възникна грешка",
|
||||
"fileInfo": "Информация за файла",
|
||||
"filesSelected": "Избрани са {count} файла.",
|
||||
"lastModified": "Последна промяна",
|
||||
"move": "Премести",
|
||||
"moveMessage": "Избери ново място за вашите файл(ове)/папк(а/и):",
|
||||
"newArchetype": "Създай нова публикация базирана на шаблон. Вашия файл ще бъде създаден в папката за съдържание.",
|
||||
"newDir": "Нова директория",
|
||||
"newDirMessage": "Именувайте вашата нова директория.",
|
||||
"newFile": "Нов файл",
|
||||
"newFileMessage": "Именувайте вашия нов файл.",
|
||||
"numberDirs": "Брой на директорийте",
|
||||
"numberFiles": "Брой на файловете",
|
||||
"rename": "Преименувай",
|
||||
"renameMessage": "Вмъкни ново име за",
|
||||
"replace": "Замени",
|
||||
"replaceMessage": "Файл, които се опитвате да качите има конфликтно име. Искате ли да го пропуснете и да продължите качването или да замените съществуващия файл?\n",
|
||||
"schedule": "График",
|
||||
"scheduleMessage": "Изберете дата и час за публикуване на тази публикация.",
|
||||
"show": "Покажи",
|
||||
"size": "Размер",
|
||||
"upload": "Качване",
|
||||
"uploadFiles": "Качване на {files} файла...",
|
||||
"uploadMessage": "Изберете опция за качване.",
|
||||
"optionalPassword": "Опционална парола",
|
||||
"resolution": "Резолюция",
|
||||
"discardEditorChanges": "Сигурни ли сте, че искате да откажете направените промени?"
|
||||
},
|
||||
"search": {
|
||||
"images": "Изображения",
|
||||
"music": "Музика",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "За търсене, натиснете Enter ...",
|
||||
"search": "Търсене ...",
|
||||
"typeToSearch": "Пишете за търсене ...",
|
||||
"types": "Типове",
|
||||
"video": "Видео"
|
||||
},
|
||||
"settings": {
|
||||
"aceEditorTheme": "Тема на \"Ace редактор\"",
|
||||
"admin": "Админ",
|
||||
"administrator": "Администратор",
|
||||
"allowCommands": "Изпълни команди",
|
||||
"allowEdit": "Редактира, преименува и изтрива файлове и директории",
|
||||
"allowNew": "Създава нови файлове и директорий",
|
||||
"allowPublish": "Публикува нови публикации и страници",
|
||||
"allowSignup": "Разреши потребителите да се абонират",
|
||||
"hideLoginButton": "Скрий логин бутона от публичните страници",
|
||||
"avoidChanges": "(остави празно за да избегнеш промени)",
|
||||
"branding": "Брандиране",
|
||||
"brandingDirectoryPath": "Брандиране - път до директория",
|
||||
"brandingHelp": "Можете да настроите как изглежда вашия File Browser, като промените името и логото му, да добавите стилове и дори да забраните външни линкове към GitHub.\nЗа повече информация за бандиране се обърнете към {0}",
|
||||
"changePassword": "Промени парола",
|
||||
"commandRunner": "Изпълнение на команди",
|
||||
"commandRunnerHelp": "Тук можете да зададете команди, които да се изпълнят при определени събития. Пишете по една команда на ред. Системните променливите {0} и {1} ще са на разположение, като {0} е релативна на {1}. За повече информация относно тази възможност и наличните системни променливи, моля прочетете {2}.",
|
||||
"commandsUpdated": "Командите са запаметени!",
|
||||
"createUserDir": "Създай автоматично собствена директория на потребителя, когато го добавяш.",
|
||||
"minimumPasswordLength": "Минимална дължина на паролата",
|
||||
"tusUploads": "Качване на части",
|
||||
"tusUploadsHelp": "File Browser поддържа качване на части, което позволява съзадавнето на ефективно, надеждно, и възобновяемо качване на части дори и при ненадеждна мрежа.",
|
||||
"tusUploadsChunkSize": "Максимален размер на заявката (за малки качвания ще бъдат използвано директо качване). Можете да въведете цяло число, което означава размера на данните в байтове, или пък текст от вида на 10MB, 1GB и т.н..",
|
||||
"tusUploadsRetryCount": "Брой повторения, когато част от файл не се качи успешно.",
|
||||
"userHomeBasePath": "Основен път до личните директории на потребителите",
|
||||
"userScopeGenerationPlaceholder": "Обхватът ще бъде автоматично генериран",
|
||||
"createUserHomeDirectory": "Създай лична директория на потребителя",
|
||||
"customStylesheet": "Потребителски Стилове",
|
||||
"defaultUserDescription": "Настройки по подразбиране за нови потребители.",
|
||||
"disableExternalLinks": "Забрани външните връзки (с изключение на документацията)",
|
||||
"disableUsedDiskPercentage": "Забрани графиката за използване на диска",
|
||||
"documentation": "документация",
|
||||
"examples": "Примери",
|
||||
"executeOnShell": "Изпълни в шела",
|
||||
"executeOnShellDescription": "По подразбиране, File Browser изпълнява командите директно. Ако искате да ги изпълните в сесия (като Bash или PowerShell), можете да я дефинирате тук заедно с необходимите аргументи и флагове. Ако това е зададено, командата която изпълните ще бъде добавена като аргумент. Това се отнася както за потребителски команди, така и за обработка на събития.",
|
||||
"globalRules": "Това е общия списък от правила за разрешения или забрани. Те са приложими за всеки потребител. Можете да дефинирате специфични правила в настройките на всеки потребител, които ще имат приоритет над общите.",
|
||||
"globalSettings": "Глобални Настройки",
|
||||
"hideDotfiles": "Скрий фаловете започващи с точка",
|
||||
"insertPath": "Вмъкни пътя",
|
||||
"insertRegex": "Вмъкни регулярен израз",
|
||||
"instanceName": "Име на инстанцията",
|
||||
"language": "Език",
|
||||
"lockPassword": "Забрани на потребителя да променя паролата",
|
||||
"newPassword": "Вашата нова парола",
|
||||
"newPasswordConfirm": "Потвърди вашата нова парола",
|
||||
"newUser": "Нов потребител",
|
||||
"password": "Парола",
|
||||
"passwordUpdated": "Паролата е променена!",
|
||||
"path": "Път",
|
||||
"perm": {
|
||||
"create": "Създаване на файлове и директорий",
|
||||
"delete": "Изтриване на файлове и директорий",
|
||||
"download": "Сваляне",
|
||||
"execute": "Изпълни команди",
|
||||
"modify": "Редактирай файлове",
|
||||
"rename": "Преименувай или премести файлове и директорий",
|
||||
"share": "Сподели файлове"
|
||||
},
|
||||
"permissions": "Разрешения",
|
||||
"permissionsHelp": "Можете да зададете потребител да бъде администратор или да изберете разрешения индивидуално. Ако изберете \"Администратор\" всички други опции ще бъдат автоматично отметнати. Управлението на потребителите е привилегия на администратор.\n",
|
||||
"profileSettings": "Настройки на Профила",
|
||||
"ruleExample1": "предотвратете достъпа до всеки файл започващ с точка (като .git, .gitignore) във всяка папка.\n",
|
||||
"ruleExample2": "блокира достъпа до файл именуван Caddyfile поставен в началото за обхвата.",
|
||||
"rules": "Правила",
|
||||
"rulesHelp": "Тук можете да дефинирате списък от правила за разрешаване и забрана за точно този потребител. Блокираните файлове няма да се покажат в списъците и няма да са достъпни за потребителя. Поддържаме регулярни изрази и пътища релативни на обхвата.\n",
|
||||
"scope": "Обхват",
|
||||
"setDateFormat": "Задайте точен формат на дата",
|
||||
"settingsUpdated": "Настройките са обновени!",
|
||||
"shareDuration": "Продължителност на споделянето",
|
||||
"shareManagement": "Управление на споделянето",
|
||||
"shareDeleted": "Споделянето е премахнато!",
|
||||
"singleClick": "Използвайте единичен клик за да отворите файлове или директорий",
|
||||
"themes": {
|
||||
"default": "Настройки по подразбиране",
|
||||
"dark": "Тъмна",
|
||||
"light": "Светла",
|
||||
"title": "Тема"
|
||||
},
|
||||
"user": "Потребител",
|
||||
"userCommands": "Команди",
|
||||
"userCommandsHelp": "Списък разделен с интервал от наличните команди за този потребител.\n",
|
||||
"userCreated": "Потребителя е създаден!",
|
||||
"userDefaults": "Настройки по подразбиране на потребителя",
|
||||
"userDeleted": "Потребителя е изтрит!",
|
||||
"userManagement": "Управление на потребители",
|
||||
"userUpdated": "Потребителя е обновен!",
|
||||
"username": "Потребителско име",
|
||||
"users": "Потребители"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Помощ",
|
||||
"hugoNew": "Hugo New",
|
||||
"login": "Вход",
|
||||
"logout": "Изход",
|
||||
"myFiles": "Моите файлове",
|
||||
"newFile": "Нов файл",
|
||||
"newFolder": "Нова папка",
|
||||
"preview": "Преглед",
|
||||
"settings": "Настройки",
|
||||
"signup": "Абониране",
|
||||
"siteSettings": "Настройки на сървъра"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Връзката е копирана!"
|
||||
},
|
||||
"time": {
|
||||
"days": "Дни",
|
||||
"hours": "Часове",
|
||||
"minutes": "Минути",
|
||||
"seconds": "Секунди",
|
||||
"unit": "Единица за време"
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Usuari",
|
||||
"usernameTaken": "Nom d'usuari no disponible",
|
||||
"wrongCredentials": "Usuari i/o contrasenya incorrectes",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Uživatelské jméno",
|
||||
"usernameTaken": "Uživatelské jméno již existuje",
|
||||
"wrongCredentials": "Nesprávné přihlašovací údaje",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Benutzername",
|
||||
"usernameTaken": "Benutzername ist bereits vergeben",
|
||||
"wrongCredentials": "Falsche Zugangsdaten",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Όνομα χρήστη",
|
||||
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
|
||||
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Username",
|
||||
"usernameTaken": "Username already taken",
|
||||
"wrongCredentials": "Wrong credentials",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Usuario",
|
||||
"usernameTaken": "Nombre usuario no disponible",
|
||||
"wrongCredentials": "Usuario y/o contraseña incorrectos",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "نام کاربری",
|
||||
"usernameTaken": "نام کاربری تکراری",
|
||||
"wrongCredentials": "خطا در اعتبارسنجی",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Utilisateur·ice",
|
||||
"usernameTaken": "Le nom d'utilisateur·ice est déjà pris",
|
||||
"wrongCredentials": "Identifiants incorrects !",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "שם משתמש",
|
||||
"usernameTaken": "שם המשתמש כבר קיים",
|
||||
"wrongCredentials": "פרטי התחברות שגויים",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Korisničko ime",
|
||||
"usernameTaken": "Korisničko ime zauzeto",
|
||||
"wrongCredentials": "Neispravno korisničko ime/lozinka",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Odjavljeni ste zbog neaktivnosti."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Felhasználói név",
|
||||
"usernameTaken": "A felhasználói név már foglalt",
|
||||
"wrongCredentials": "Hibás hitelesítő adatok",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import dayjs from "dayjs";
|
||||
import { createI18n } from "vue-i18n";
|
||||
|
||||
import("dayjs/locale/ar");
|
||||
import("dayjs/locale/bg");
|
||||
import("dayjs/locale/cs");
|
||||
import("dayjs/locale/de");
|
||||
import("dayjs/locale/el");
|
||||
import("dayjs/locale/en");
|
||||
@@ -14,6 +16,7 @@ import("dayjs/locale/is");
|
||||
import("dayjs/locale/it");
|
||||
import("dayjs/locale/ja");
|
||||
import("dayjs/locale/ko");
|
||||
import("dayjs/locale/nb");
|
||||
import("dayjs/locale/nl-be");
|
||||
import("dayjs/locale/pl");
|
||||
import("dayjs/locale/pt-br");
|
||||
@@ -27,8 +30,6 @@ import("dayjs/locale/uk");
|
||||
import("dayjs/locale/vi");
|
||||
import("dayjs/locale/zh-cn");
|
||||
import("dayjs/locale/zh-tw");
|
||||
import("dayjs/locale/cs");
|
||||
import("dayjs/locale/nb");
|
||||
|
||||
// All i18n resources specified in the plugin `include` option can be loaded
|
||||
// at once using the import syntax
|
||||
@@ -109,6 +110,7 @@ export function detectLocale() {
|
||||
case /^uk\b/.test(locale):
|
||||
locale = "uk";
|
||||
break;
|
||||
|
||||
case /^vi\b/.test(locale):
|
||||
locale = "vi";
|
||||
break;
|
||||
@@ -123,6 +125,9 @@ export function detectLocale() {
|
||||
case /^no\b/.test(locale):
|
||||
locale = "no";
|
||||
break;
|
||||
case /^bg\b/.test(locale):
|
||||
locale = "bg";
|
||||
break;
|
||||
default:
|
||||
locale = "en";
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Notendanafn",
|
||||
"usernameTaken": "Þetta norendanafn er þegar í notkun",
|
||||
"wrongCredentials": "Rangar notendaupplýsingar",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Nome utente",
|
||||
"usernameTaken": "Username già usato",
|
||||
"wrongCredentials": "Credenziali errate",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "ユーザー名",
|
||||
"usernameTaken": "ユーザー名はすでに取得されています",
|
||||
"wrongCredentials": "ユーザー名またはパスワードが間違っています",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "사용자 이름",
|
||||
"usernameTaken": "사용자 이름이 존재합니다",
|
||||
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Gebruikersnaam",
|
||||
"usernameTaken": "Gebruikersnaam reeds in gebruik",
|
||||
"wrongCredentials": "Verkeerde inloggegevens",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Brukernavn",
|
||||
"usernameTaken": "Brukernavn er allerede i bruk",
|
||||
"wrongCredentials": "Feil legitimasjon",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Du har blitt logget ut på grunn av inaktivitet"
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Nazwa użytkownika",
|
||||
"usernameTaken": "Ta nazwa użytkownika jest zajęta",
|
||||
"wrongCredentials": "Błędne dane logowania",
|
||||
"passwordTooShort": "Wymagana minimalna liczba znaków hasła: {min}",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Wylogowano z powodu braku aktywności."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Nome do usuário",
|
||||
"usernameTaken": "Nome de usuário já existe",
|
||||
"wrongCredentials": "Ops! Dados incorretos.",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Nome de utilizador",
|
||||
"usernameTaken": "O nome de utilizador já está registado",
|
||||
"wrongCredentials": "Dados errados",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Utilizator",
|
||||
"usernameTaken": "Utilizatorul există",
|
||||
"wrongCredentials": "Informații greșite",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Имя пользователя",
|
||||
"usernameTaken": "Данное имя пользователя уже занято",
|
||||
"wrongCredentials": "Неверные данные",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Používateľské meno",
|
||||
"usernameTaken": "Meno je už obsadené",
|
||||
"wrongCredentials": "Nesprávne prihlasovacie údaje",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Boli ste odhlásení z dôvodu nečinnosti."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Användarnamn",
|
||||
"usernameTaken": "Användarnamn upptaget",
|
||||
"wrongCredentials": "Fel inloggning",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "Du har blivit utloggad på grund av inaktivitet."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Kullanıcı adı",
|
||||
"usernameTaken": "Kullanıcı adı mevcut",
|
||||
"wrongCredentials": "Yanlış hesap bilgileri",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Ім'я користувача",
|
||||
"usernameTaken": "Ім'я користувача вже використовується",
|
||||
"wrongCredentials": "Неправильне ім'я користувача або пароль",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "Tên người dùng",
|
||||
"usernameTaken": "Tên người dùng đã tồn tại",
|
||||
"wrongCredentials": "Thông tin đăng nhập không đúng",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "用户名",
|
||||
"usernameTaken": "用户名已经被使用",
|
||||
"wrongCredentials": "用户名或密码错误",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"username": "帳號",
|
||||
"usernameTaken": "用戶名已存在",
|
||||
"wrongCredentials": "帳號或密碼錯誤",
|
||||
"passwordTooShort": "Password must be at least {min} characters",
|
||||
"logout_reasons": {
|
||||
"inactivity": "You have been logged out due to inactivity."
|
||||
}
|
||||
|
||||
@@ -101,7 +101,11 @@ export async function signup(username: string, password: string) {
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new StatusError(`${res.status} ${res.statusText}`, res.status);
|
||||
const body = await res.text();
|
||||
throw new StatusError(
|
||||
body || `${res.status} ${res.statusText}`,
|
||||
res.status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,13 @@ const submit = async (event: Event) => {
|
||||
error.value = t("login.usernameTaken");
|
||||
} else if (e.status === 403) {
|
||||
error.value = t("login.wrongCredentials");
|
||||
} else if (e.status === 400) {
|
||||
const match = e.message.match(/minimum length is (\d+)/);
|
||||
if (match) {
|
||||
error.value = t("login.passwordTooShort", { min: match[1] });
|
||||
} else {
|
||||
error.value = e.message;
|
||||
}
|
||||
} else {
|
||||
$showError(e);
|
||||
}
|
||||
|
||||
11
go.mod
11
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,8 +24,7 @@ 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/crypto v0.45.0
|
||||
golang.org/x/image v0.33.0
|
||||
golang.org/x/text v0.31.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
@@ -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,9 +70,10 @@ 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
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
|
||||
12
go.sum
12
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=
|
||||
@@ -262,8 +266,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -315,8 +319,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -51,7 +51,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
|
||||
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)
|
||||
@@ -63,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))
|
||||
|
||||
@@ -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
|
||||
|
||||
50
www/docs/cli/filebrowser-completion-bash.md
Normal file
50
www/docs/cli/filebrowser-completion-bash.md
Normal 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
|
||||
|
||||
41
www/docs/cli/filebrowser-completion-fish.md
Normal file
41
www/docs/cli/filebrowser-completion-fish.md
Normal 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user