diff --git a/.github/update.log b/.github/update.log index 72af4d1403..2c2bdbb30a 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1184,3 +1184,4 @@ Update On Thu Nov 13 19:41:20 CET 2025 Update On Fri Nov 14 19:38:12 CET 2025 Update On Sat Nov 15 19:34:45 CET 2025 Update On Sun Nov 16 19:35:35 CET 2025 +Update On Mon Nov 17 19:42:18 CET 2025 diff --git a/filebrowser/CHANGELOG.md b/filebrowser/CHANGELOG.md index 5c25523b7f..a02ee24aee 100644 --- a/filebrowser/CHANGELOG.md +++ b/filebrowser/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [2.48.1](https://github.com/filebrowser/filebrowser/compare/v2.48.0...v2.48.1) (2025-11-17) + + +### Bug Fixes + +* options should only override if set ([420adea](https://github.com/filebrowser/filebrowser/commit/420adea7e61a1c182cddd6fb2544a0752e5709f7)) + +## [2.48.0](https://github.com/filebrowser/filebrowser/compare/v2.47.0...v2.48.0) (2025-11-17) + + +### Features + +* consistent flags and environment variables ([#5549](https://github.com/filebrowser/filebrowser/issues/5549)) ([0a0cb80](https://github.com/filebrowser/filebrowser/commit/0a0cb8046fce52f1ff926171b34bcdb7cd39aab3)) + + +### Bug Fixes + +* add tokenExpirationTime to `config init` and troubleshoot docs ([#5546](https://github.com/filebrowser/filebrowser/issues/5546)) ([8c5dc76](https://github.com/filebrowser/filebrowser/commit/8c5dc7641e6f8aadd9e5d5d3b25a2ad9f1ec9a1e)) +* use all available flags in quick setup ([f41585f](https://github.com/filebrowser/filebrowser/commit/f41585f0392d65c08c01ab65b62d3eeb04c03b7d)) + + +### Refactorings + +* reuse logic for config init and set ([89be0b1](https://github.com/filebrowser/filebrowser/commit/89be0b1873527987dd2dddac746e93b8bc684d46)) + ## [2.47.0](https://github.com/filebrowser/filebrowser/compare/v2.46.1...v2.47.0) (2025-11-16) diff --git a/filebrowser/Taskfile.yml b/filebrowser/Taskfile.yml index 5977dff009..378e340987 100644 --- a/filebrowser/Taskfile.yml +++ b/filebrowser/Taskfile.yml @@ -49,7 +49,10 @@ tasks: cmds: - task: docs:cli:generate - git add www/docs/cli - - "git commit -m 'chore(docs): update CLI documentation'" + - | + if [[ `git status www/docs/cli --porcelain` ]]; then + git commit -m 'chore(docs): update CLI documentation' + fi - task: release:dry-run - task: release:make diff --git a/filebrowser/cmd/cmd_test.go b/filebrowser/cmd/cmd_test.go new file mode 100644 index 0000000000..e4b45c4784 --- /dev/null +++ b/filebrowser/cmd/cmd_test.go @@ -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) + } +} diff --git a/filebrowser/cmd/cmds_ls.go b/filebrowser/cmd/cmds_ls.go index fa901a56c5..ad700eb789 100644 --- a/filebrowser/cmd/cmds_ls.go +++ b/filebrowser/cmd/cmds_ls.go @@ -19,7 +19,8 @@ var cmdsLsCmd = &cobra.Command{ if err != nil { return err } - evt, err := getString(cmd.Flags(), "event") + + evt, err := cmd.Flags().GetString("event") if err != nil { return err } @@ -32,6 +33,7 @@ var cmdsLsCmd = &cobra.Command{ show["after_"+evt] = s.Commands["after_"+evt] printEvents(show) } + return nil }, pythonConfig{}), } diff --git a/filebrowser/cmd/config.go b/filebrowser/cmd/config.go index 6b73961043..550ab5c912 100644 --- a/filebrowser/cmd/config.go +++ b/filebrowser/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,17 +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 } @@ -91,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 } @@ -113,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 } @@ -149,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) } @@ -201,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) @@ -208,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) @@ -217,10 +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) @@ -231,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) @@ -243,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, "", " ") @@ -252,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 +} diff --git a/filebrowser/cmd/config_import.go b/filebrowser/cmd/config_import.go index 7763517d1c..63d394d796 100644 --- a/filebrowser/cmd/config_import.go +++ b/filebrowser/cmd/config_import.go @@ -37,7 +37,7 @@ The path must be for a json or yaml file.`, RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { var key []byte var err error - if d.hadDB { + if d.databaseExisted { settings, settingErr := d.store.Settings.Get() if settingErr != nil { return settingErr @@ -104,7 +104,7 @@ The path must be for a json or yaml file.`, } return printSettings(file.Server, file.Settings, auther) - }, pythonConfig{allowNoDB: true}), + }, pythonConfig{allowsNoDatabase: true}), } func getAuther(sample auth.Auther, data interface{}) (interface{}, error) { diff --git a/filebrowser/cmd/config_init.go b/filebrowser/cmd/config_init.go index 47d02d8aeb..2787f08055 100644 --- a/filebrowser/cmd/config_init.go +++ b/filebrowser/cmd/config_init.go @@ -23,170 +23,29 @@ to the defaults when creating new users and you don't override the options.`, Args: cobra.NoArgs, RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { - defaults := settings.UserDefaults{} flags := cmd.Flags() - err := getUserDefaults(flags, &defaults, true) - if err != nil { - return err - } - authMethod, auther, err := getAuthentication(flags) + + // Initialize config + s := &settings.Settings{Key: generateKey()} + ser := &settings.Server{} + + // Fill config with options + auther, err := getSettings(flags, s, ser, nil, true) if err != nil { return err } - key := generateKey() - - signup, err := getBool(flags, "signup") - if err != nil { - return err - } - - hideLoginButton, err := getBool(flags, "hide-login-button") - 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 - } - - tusChunkSize, err := flags.GetUint64("tus.chunkSize") - if err != nil { - return err - } - - tusRetryCount, err := flags.GetUint16("tus.retryCount") - if err != nil { - return err - } - - s := &settings.Settings{ - Key: key, - Signup: signup, - HideLoginButton: hideLoginButton, - CreateUserDir: createUserDir, - MinimumPasswordLength: minLength, - Shell: convertCmdStrToCmdArray(shell), - AuthMethod: authMethod, - Defaults: defaults, - Branding: settings.Branding{ - Name: brandingName, - DisableExternal: brandingDisableExternal, - DisableUsedPercentage: brandingDisableUsedPercentage, - Theme: brandingTheme, - Files: brandingFiles, - }, - Tus: settings.Tus{ - ChunkSize: tusChunkSize, - RetryCount: tusRetryCount, - }, - } - - s.FileMode, err = getMode(flags, "file-mode") - if err != nil { - return err - } - - s.DirMode, err = getMode(flags, "dir-mode") - if err != nil { - return err - } - - address, err := getString(flags, "address") - if err != nil { - return err - } - - socket, err := getString(flags, "socket") - if err != nil { - return err - } - - root, err := getString(flags, "root") - if err != nil { - return err - } - - baseURL, err := getString(flags, "baseurl") - if err != nil { - return err - } - - tlsKey, err := getString(flags, "key") - if err != nil { - return err - } - - cert, err := getString(flags, "cert") - if err != nil { - return err - } - - port, err := getString(flags, "port") - if err != nil { - return err - } - - log, err := getString(flags, "log") - if err != nil { - return err - } - - ser := &settings.Server{ - Address: address, - Socket: socket, - Root: root, - BaseURL: baseURL, - TLSKey: tlsKey, - TLSCert: cert, - Port: port, - Log: log, - } - + // Save updated config err = d.store.Settings.Save(s) if err != nil { return err } + err = d.store.Settings.SaveServer(ser) if err != nil { return err } + err = d.store.Auth.Save(auther) if err != nil { return err @@ -198,5 +57,5 @@ Now add your first user via 'filebrowser users add' and then you just need to call the main command to boot up the server. `) return printSettings(ser, s, auther) - }, pythonConfig{noDB: true}), + }, pythonConfig{expectsNoDatabase: true}), } diff --git a/filebrowser/cmd/config_set.go b/filebrowser/cmd/config_set.go index c362e2e10e..d25b659652 100644 --- a/filebrowser/cmd/config_set.go +++ b/filebrowser/cmd/config_set.go @@ -2,7 +2,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/spf13/pflag" ) func init() { @@ -18,6 +17,8 @@ you want to change. Other options will remain unchanged.`, Args: cobra.NoArgs, RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { flags := cmd.Flags() + + // Read existing config set, err := d.store.Settings.Get() if err != nil { return err @@ -28,94 +29,28 @@ you want to change. Other options will remain unchanged.`, return err } - hasAuth := false - flags.Visit(func(flag *pflag.Flag) { - if err != nil { - return - } - switch flag.Name { - case "baseurl": - ser.BaseURL, err = getString(flags, flag.Name) - case "root": - ser.Root, err = getString(flags, flag.Name) - case "socket": - ser.Socket, err = getString(flags, flag.Name) - case "cert": - ser.TLSCert, err = getString(flags, flag.Name) - case "key": - ser.TLSKey, err = getString(flags, flag.Name) - case "address": - ser.Address, err = getString(flags, flag.Name) - case "port": - ser.Port, err = getString(flags, flag.Name) - case "log": - ser.Log, err = getString(flags, flag.Name) - case "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) - 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 { - return err - } - - err = getUserDefaults(flags, &set.Defaults, false) - if err != nil { - return err - } - - // read the defaults auther, err := d.store.Auth.Get(set.AuthMethod) if err != nil { return err } - // check if there are new flags for existing auth method - set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther) + // Get updated config + auther, err = getSettings(flags, set, ser, auther, false) if err != nil { return err } + // Save updated config err = d.store.Auth.Save(auther) if err != nil { return err } + err = d.store.Settings.Save(set) if err != nil { return err } + err = d.store.Settings.SaveServer(ser) if err != nil { return err diff --git a/filebrowser/cmd/root.go b/filebrowser/cmd/root.go index 0d103b2985..6a44e2f375 100644 --- a/filebrowser/cmd/root.go +++ b/filebrowser/cmd/root.go @@ -13,15 +13,13 @@ 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" @@ -35,28 +33,67 @@ import ( ) var ( - cfgFile string + flagNamesMigrations = map[string]string{ + "file-mode": "fileMode", + "dir-mode": "dirMode", + "hide-login-button": "hideLoginButton", + "create-user-dir": "createUserDir", + "minimum-password-length": "minimumPasswordLength", + "socket-perm": "socketPerm", + "disable-thumbnails": "disableThumbnails", + "disable-preview-resize": "disablePreviewResize", + "disable-exec": "disableExec", + "disable-type-detection-by-header": "disableTypeDetectionByHeader", + "img-processors": "imageProcessors", + "cache-dir": "cacheDir", + "token-expiration-time": "tokenExpirationTime", + "baseurl": "baseURL", + } + + warnedFlags = map[string]bool{} ) +// TODO(remove): remove after July 2026. +func migrateFlagNames(f *pflag.FlagSet, name string) pflag.NormalizedName { + if newName, ok := flagNamesMigrations[name]; ok { + + if !warnedFlags[name] { + warnedFlags[name] = true + fmt.Printf("WARNING: Flag --%s has been deprecated, use --%s instead\n", name, newName) + } + + name = newName + } + + return pflag.NormalizedName(name) +} + func init() { - cobra.OnInitialize(initConfig) rootCmd.SilenceUsage = true + rootCmd.SetGlobalNormalizationFunc(migrateFlagNames) + cobra.MousetrapHelpText = "" rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") - flags := rootCmd.Flags() + // Flags available across the whole program persistent := rootCmd.PersistentFlags() - - persistent.StringVarP(&cfgFile, "config", "c", "", "config file path") + persistent.StringP("config", "c", "", "config file path") persistent.StringP("database", "d", "./filebrowser.db", "database path") - flags.Bool("noauth", false, "use the noauth auther when using quick setup") - flags.String("username", "admin", "username for the first user when using quick config") - flags.String("password", "", "hashed password for the first user when using quick config") + // Runtime flags for the root command + flags := rootCmd.Flags() + flags.Bool("noauth", false, "use the noauth auther when using quick setup") + flags.String("username", "admin", "username for the first user when using quick setup") + flags.String("password", "", "hashed password for the first user when using quick setup") + flags.Uint32("socketPerm", 0666, "unix socket file permissions") + flags.String("cacheDir", "", "file cache directory (disabled if empty)") + flags.Int("imageProcessors", 4, "image processors count") addServerFlags(flags) } +// addServerFlags adds server related flags to the given FlagSet. These flags are available +// in both the root command, config set and config init commands. func addServerFlags(flags *pflag.FlagSet) { flags.StringP("address", "a", "127.0.0.1", "address to listen on") flags.StringP("log", "l", "stdout", "log output") @@ -65,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") - 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{ @@ -88,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/ @@ -101,44 +137,32 @@ If you don't set "config", it will look for a configuration file called The precedence of the configuration values are as follows: -- flags -- environment variables -- configuration file -- database values -- defaults - -The environment variables are prefixed by "FB_" followed by the option -name in caps. So to set "database" via an env variable, you should -set FB_DATABASE. +- Flags +- Environment variables +- Configuration file +- Database values +- Defaults Also, if the database path doesn't exist, File Browser will enter into the quick setup mode and a new database will be bootstrapped and a new user created with the credentials from options "username" and "password".`, RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { - log.Println(cfgFile) - - if !d.hadDB { - err := quickSetup(cmd.Flags(), *d) + if !d.databaseExisted { + err := quickSetup(*d) if err != nil { return err } } // build img service - workersCount, err := cmd.Flags().GetInt("img-processors") - if err != nil { - return err - } - if workersCount < 1 { + imgWorkersCount := d.viper.GetInt("imageProcessors") + if imgWorkersCount < 1 { return errors.New("image resize workers count could not be < 1") } - imgSvc := img.New(workersCount) + imageService := img.New(imgWorkersCount) var fileCache diskcache.Interface = diskcache.NewNoOp() - cacheDir, err := cmd.Flags().GetString("cache-dir") - if err != nil { - return err - } + cacheDir := d.viper.GetString("cacheDir") if cacheDir != "" { if err := os.MkdirAll(cacheDir, 0700); err != nil { return fmt.Errorf("can't make directory %s: %w", cacheDir, err) @@ -146,7 +170,7 @@ user created with the credentials from options "username" and "password".`, fileCache = diskcache.New(afero.NewOsFs(), cacheDir) } - server, err := getRunParams(cmd.Flags(), d.store) + server, err := getServerSettings(d.viper, d.store) if err != nil { return err } @@ -168,10 +192,7 @@ user created with the credentials from options "username" and "password".`, if err != nil { return err } - socketPerm, err := cmd.Flags().GetUint32("socket-perm") - if err != nil { - return err - } + socketPerm := d.viper.GetUint32("socketPerm") err = os.Chmod(server.Socket, os.FileMode(socketPerm)) if err != nil { return err @@ -200,7 +221,7 @@ user created with the credentials from options "username" and "password".`, panic(err) } - handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs) + handler, err := fbhttp.NewHandler(imageService, fileCache, d.store, server, assetsFs) if err != nil { return err } @@ -241,53 +262,73 @@ user created with the credentials from options "username" and "password".`, log.Println("Graceful shutdown complete.") return nil - }, pythonConfig{allowNoDB: true}), + }, pythonConfig{allowsNoDatabase: true}), } -func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) { +func getServerSettings(v *viper.Viper, st *storage.Storage) (*settings.Server, error) { server, err := st.Settings.GetServer() if err != nil { return nil, err } - if val, set := getStringParamB(flags, "root"); set { - server.Root = val - } - - if val, set := getStringParamB(flags, "baseurl"); set { - server.BaseURL = val - } - - if val, set := getStringParamB(flags, "log"); set { - server.Log = val - } - isSocketSet := false isAddrSet := false - if val, set := getStringParamB(flags, "address"); set { - server.Address = val - isAddrSet = isAddrSet || set + if v.IsSet("address") { + server.Address = v.GetString("address") + isAddrSet = true } - if val, set := getStringParamB(flags, "port"); set { - server.Port = val - isAddrSet = isAddrSet || set + if v.IsSet("log") { + server.Log = v.GetString("log") } - if val, set := getStringParamB(flags, "key"); set { - server.TLSKey = val - isAddrSet = isAddrSet || set + if v.IsSet("port") { + server.Port = v.GetString("port") + isAddrSet = true } - if val, set := getStringParamB(flags, "cert"); set { - server.TLSCert = val - isAddrSet = isAddrSet || set + if v.IsSet("cert") { + server.TLSCert = v.GetString("cert") + isAddrSet = true } - if val, set := getStringParamB(flags, "socket"); set { - server.Socket = val - isSocketSet = isSocketSet || set + if v.IsSet("key") { + server.TLSKey = v.GetString("key") + isAddrSet = true + } + + if v.IsSet("root") { + server.Root = v.GetString("root") + } + + if v.IsSet("socket") { + server.Socket = v.GetString("socket") + isSocketSet = true + } + + if v.IsSet("baseURL") { + server.BaseURL = v.GetString("baseURL") + } + + if v.IsSet("tokenExpirationTime") { + server.TokenExpirationTime = v.GetString("tokenExpirationTime") + } + + if v.IsSet("disableThumbnails") { + server.EnableThumbnails = !v.GetBool("disableThumbnails") + } + + if v.IsSet("disablePreviewResize") { + server.ResizePreview = !v.GetBool("disablePreviewResize") + } + + if v.IsSet("disableTypeDetectionByHeader") { + server.TypeDetectionByHeader = !v.GetBool("disableTypeDetectionByHeader") + } + + if v.IsSet("disableExec") { + server.EnableExec = !v.GetBool("disableExec") } if isAddrSet && isSocketSet { @@ -299,18 +340,6 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, server.Socket = "" } - disableThumbnails := getBoolParam(flags, "disable-thumbnails") - server.EnableThumbnails = !disableThumbnails - - disablePreviewResize := getBoolParam(flags, "disable-preview-resize") - server.ResizePreview = !disablePreviewResize - - disableTypeDetectionByHeader := getBoolParam(flags, "disable-type-detection-by-header") - server.TypeDetectionByHeader = !disableTypeDetectionByHeader - - disableExec := getBoolParam(flags, "disable-exec") - server.EnableExec = !disableExec - if server.EnableExec { log.Println("WARNING: Command Runner feature enabled!") log.Println("WARNING: This feature has known security vulnerabilities and should not") @@ -318,71 +347,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199") } - if val, set := getStringParamB(flags, "token-expiration-time"); set { - server.TokenExpirationTime = val - } - return server, nil } -// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default -// -// NOTE: we could simply bind the flags to viper and use IsSet. -// Although there is a bug on Viper that always returns true on IsSet -// if a flag is binded. Our alternative way is to manually check -// the flag and then the value from env/config/gotten by viper. -// https://github.com/spf13/viper/pull/331 -func getBoolParamB(flags *pflag.FlagSet, key string) (value, ok bool) { - value, _ = flags.GetBool(key) - - // If set on Flags, use it. - if flags.Changed(key) { - return value, true - } - - // If set through viper (env, config), return it. - if v.IsSet(key) { - return v.GetBool(key), true - } - - // Otherwise use default value on flags. - return value, false -} - -func getBoolParam(flags *pflag.FlagSet, key string) bool { - val, _ := getBoolParamB(flags, key) - return val -} - -// getStringParamB returns a parameter as a string and a boolean to tell if it is different from the default -// -// NOTE: we could simply bind the flags to viper and use IsSet. -// Although there is a bug on Viper that always returns true on IsSet -// if a flag is binded. Our alternative way is to manually check -// the flag and then the value from env/config/gotten by viper. -// https://github.com/spf13/viper/pull/331 -func getStringParamB(flags *pflag.FlagSet, key string) (string, bool) { - value, _ := flags.GetString(key) - - // If set on Flags, use it. - if flags.Changed(key) { - return value, true - } - - // If set through viper (env, config), return it. - if v.IsSet(key) { - return v.GetString(key), true - } - - // Otherwise use default value on flags. - return value, false -} - -func getStringParam(flags *pflag.FlagSet, key string) string { - val, _ := getStringParamB(flags, key) - return val -} - func setupLog(logMethod string) { switch logMethod { case "stdout": @@ -401,7 +368,7 @@ func setupLog(logMethod string) { } } -func quickSetup(flags *pflag.FlagSet, d pythonData) error { +func quickSetup(d pythonData) error { log.Println("Performing quick setup") set := &settings.Settings{ @@ -415,7 +382,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error { Scope: ".", Locale: "en", SingleClick: false, - AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"), + AceEditorTheme: d.viper.GetString("defaults.aceEditorTheme"), Perm: users.Permissions{ Admin: false, Execute: true, @@ -439,7 +406,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error { } var err error - if _, noauth := getStringParamB(flags, "noauth"); noauth { + if d.viper.GetBool("noauth") { set.AuthMethod = auth.MethodNoAuth err = d.store.Auth.Save(&auth.NoAuth{}) } else { @@ -456,13 +423,18 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error { } ser := &settings.Server{ - BaseURL: getStringParam(flags, "baseurl"), - Port: getStringParam(flags, "port"), - Log: getStringParam(flags, "log"), - TLSKey: getStringParam(flags, "key"), - TLSCert: getStringParam(flags, "cert"), - Address: getStringParam(flags, "address"), - Root: getStringParam(flags, "root"), + BaseURL: d.viper.GetString("baseURL"), + Port: d.viper.GetString("port"), + Log: d.viper.GetString("log"), + TLSKey: d.viper.GetString("key"), + TLSCert: d.viper.GetString("cert"), + Address: d.viper.GetString("address"), + Root: d.viper.GetString("root"), + TokenExpirationTime: d.viper.GetString("tokenExpirationTime"), + EnableThumbnails: !d.viper.GetBool("disableThumbnails"), + ResizePreview: !d.viper.GetBool("disablePreviewResize"), + EnableExec: !d.viper.GetBool("disableExec"), + TypeDetectionByHeader: !d.viper.GetBool("disableTypeDetectionByHeader"), } err = d.store.Settings.SaveServer(ser) @@ -470,8 +442,8 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error { return err } - username := getStringParam(flags, "username") - password := getStringParam(flags, "password") + username := d.viper.GetString("username") + password := d.viper.GetString("password") if password == "" { var pwd string @@ -504,32 +476,3 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error { return d.store.Users.Save(user) } - -func initConfig() { - if cfgFile == "" { - home, err := homedir.Dir() - if err != nil { - panic(err) - } - v.AddConfigPath(".") - v.AddConfigPath(home) - v.AddConfigPath("/etc/filebrowser/") - v.SetConfigName(".filebrowser") - } else { - v.SetConfigFile(cfgFile) - } - - v.SetEnvPrefix("FB") - v.AutomaticEnv() - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) - - 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() - } -} diff --git a/filebrowser/cmd/rules.go b/filebrowser/cmd/rules.go index ffa5b1aefb..bdb1d1cf63 100644 --- a/filebrowser/cmd/rules.go +++ b/filebrowser/cmd/rules.go @@ -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 } diff --git a/filebrowser/cmd/rules_add.go b/filebrowser/cmd/rules_add.go index 9d1f0cf962..d58a69879f 100644 --- a/filebrowser/cmd/rules_add.go +++ b/filebrowser/cmd/rules_add.go @@ -22,14 +22,18 @@ var rulesAddCmd = &cobra.Command{ Long: `Add a global rule or user rule.`, Args: cobra.ExactArgs(1), RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { - allow, err := getBool(cmd.Flags(), "allow") + flags := cmd.Flags() + + allow, err := flags.GetBool("allow") if err != nil { return err } - regex, err := getBool(cmd.Flags(), "regex") + + regex, err := flags.GetBool("regex") if err != nil { return err } + exp := args[0] if regex { diff --git a/filebrowser/cmd/users.go b/filebrowser/cmd/users.go index c2e2ce1ef7..86434a4296 100644 --- a/filebrowser/cmd/users.go +++ b/filebrowser/cmd/users.go @@ -82,63 +82,64 @@ func addUserFlags(flags *pflag.FlagSet) { flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users") } -func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) { - viewModeStr, err := getString(flags, "viewMode") +func getAndParseViewMode(flags *pflag.FlagSet) (users.ViewMode, error) { + viewModeStr, err := flags.GetString("viewMode") if err != nil { return "", err } + viewMode := users.ViewMode(viewModeStr) if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"") } + return viewMode, nil } func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error { - var visitErr error + errs := []error{} + visit := func(flag *pflag.Flag) { - if visitErr != nil { - return - } var err error switch flag.Name { case "scope": - defaults.Scope, err = getString(flags, flag.Name) + defaults.Scope, err = flags.GetString(flag.Name) case "locale": - defaults.Locale, err = getString(flags, flag.Name) + defaults.Locale, err = flags.GetString(flag.Name) case "viewMode": - defaults.ViewMode, err = getViewMode(flags) + defaults.ViewMode, err = getAndParseViewMode(flags) case "singleClick": - defaults.SingleClick, err = getBool(flags, flag.Name) + defaults.SingleClick, err = flags.GetBool(flag.Name) case "aceEditorTheme": - defaults.AceEditorTheme, err = getString(flags, flag.Name) + defaults.AceEditorTheme, err = flags.GetString(flag.Name) case "perm.admin": - defaults.Perm.Admin, err = getBool(flags, flag.Name) + defaults.Perm.Admin, err = flags.GetBool(flag.Name) case "perm.execute": - defaults.Perm.Execute, err = getBool(flags, flag.Name) + defaults.Perm.Execute, err = flags.GetBool(flag.Name) case "perm.create": - defaults.Perm.Create, err = getBool(flags, flag.Name) + defaults.Perm.Create, err = flags.GetBool(flag.Name) case "perm.rename": - defaults.Perm.Rename, err = getBool(flags, flag.Name) + defaults.Perm.Rename, err = flags.GetBool(flag.Name) case "perm.modify": - defaults.Perm.Modify, err = getBool(flags, flag.Name) + defaults.Perm.Modify, err = flags.GetBool(flag.Name) case "perm.delete": - defaults.Perm.Delete, err = getBool(flags, flag.Name) + defaults.Perm.Delete, err = flags.GetBool(flag.Name) case "perm.share": - defaults.Perm.Share, err = getBool(flags, flag.Name) + defaults.Perm.Share, err = flags.GetBool(flag.Name) case "perm.download": - defaults.Perm.Download, err = getBool(flags, flag.Name) + defaults.Perm.Download, err = flags.GetBool(flag.Name) case "commands": defaults.Commands, err = flags.GetStringSlice(flag.Name) case "sorting.by": - defaults.Sorting.By, err = getString(flags, flag.Name) + defaults.Sorting.By, err = flags.GetString(flag.Name) case "sorting.asc": - defaults.Sorting.Asc, err = getBool(flags, flag.Name) + defaults.Sorting.Asc, err = flags.GetBool(flag.Name) case "hideDotfiles": - defaults.HideDotfiles, err = getBool(flags, flag.Name) + defaults.HideDotfiles, err = flags.GetBool(flag.Name) } + if err != nil { - visitErr = err + errs = append(errs, err) } } @@ -147,5 +148,6 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all } else { flags.Visit(visit) } - return visitErr + + return errors.Join(errs...) } diff --git a/filebrowser/cmd/users_add.go b/filebrowser/cmd/users_add.go index dce7ff98db..bfc70069bd 100644 --- a/filebrowser/cmd/users_add.go +++ b/filebrowser/cmd/users_add.go @@ -17,11 +17,12 @@ var usersAddCmd = &cobra.Command{ Long: `Create a new user and add it to the database.`, Args: cobra.ExactArgs(2), RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { + flags := cmd.Flags() s, err := d.store.Settings.Get() if err != nil { return err } - err = getUserDefaults(cmd.Flags(), &s.Defaults, false) + err = getUserDefaults(flags, &s.Defaults, false) if err != nil { return err } @@ -31,27 +32,24 @@ var usersAddCmd = &cobra.Command{ return err } - lockPassword, err := getBool(cmd.Flags(), "lockPassword") - if err != nil { - return err - } - - dateFormat, err := getBool(cmd.Flags(), "dateFormat") - if err != nil { - return err - } - - hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles") - if err != nil { - return err - } - user := &users.User{ - Username: args[0], - Password: password, - LockPassword: lockPassword, - DateFormat: dateFormat, - HideDotfiles: hideDotfiles, + Username: args[0], + Password: password, + } + + user.LockPassword, err = flags.GetBool("lockPassword") + if err != nil { + return err + } + + user.DateFormat, err = flags.GetBool("dateFormat") + if err != nil { + return err + } + + user.HideDotfiles, err = flags.GetBool("hideDotfiles") + if err != nil { + return err } s.Defaults.Apply(user) diff --git a/filebrowser/cmd/users_import.go b/filebrowser/cmd/users_import.go index 74353c2c9f..d08889dfe4 100644 --- a/filebrowser/cmd/users_import.go +++ b/filebrowser/cmd/users_import.go @@ -26,6 +26,7 @@ installation. For that, just don't place their ID on the files list or set it to 0.`, Args: jsonYamlArg, RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { + flags := cmd.Flags() fd, err := os.Open(args[0]) if err != nil { return err @@ -45,7 +46,7 @@ list or set it to 0.`, } } - replace, err := getBool(cmd.Flags(), "replace") + replace, err := flags.GetBool("replace") if err != nil { return err } @@ -69,7 +70,7 @@ list or set it to 0.`, } } - overwrite, err := getBool(cmd.Flags(), "overwrite") + overwrite, err := flags.GetBool("overwrite") if err != nil { return err } diff --git a/filebrowser/cmd/users_update.go b/filebrowser/cmd/users_update.go index a939e60582..59854a812f 100644 --- a/filebrowser/cmd/users_update.go +++ b/filebrowser/cmd/users_update.go @@ -22,13 +22,14 @@ var usersUpdateCmd = &cobra.Command{ options you want to change.`, Args: cobra.ExactArgs(1), RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { - username, id := parseUsernameOrID(args[0]) flags := cmd.Flags() - password, err := getString(flags, "password") + username, id := parseUsernameOrID(args[0]) + password, err := flags.GetString("password") if err != nil { return err } - newUsername, err := getString(flags, "username") + + newUsername, err := flags.GetString("username") if err != nil { return err } @@ -41,13 +42,11 @@ options you want to change.`, var ( user *users.User ) - if id != 0 { user, err = d.store.Users.Get("", id) } else { user, err = d.store.Users.Get("", username) } - if err != nil { return err } @@ -61,10 +60,12 @@ options you want to change.`, Sorting: user.Sorting, Commands: user.Commands, } + err = getUserDefaults(flags, &defaults, false) if err != nil { return err } + user.Scope = defaults.Scope user.Locale = defaults.Locale user.ViewMode = defaults.ViewMode @@ -72,15 +73,17 @@ options you want to change.`, user.Perm = defaults.Perm user.Commands = defaults.Commands user.Sorting = defaults.Sorting - user.LockPassword, err = getBool(flags, "lockPassword") + user.LockPassword, err = flags.GetBool("lockPassword") if err != nil { return err } - user.DateFormat, err = getBool(flags, "dateFormat") + + user.DateFormat, err = flags.GetBool("dateFormat") if err != nil { return err } - user.HideDotfiles, err = getBool(flags, "hideDotfiles") + + user.HideDotfiles, err = flags.GetBool("hideDotfiles") if err != nil { return err } diff --git a/filebrowser/cmd/utils.go b/filebrowser/cmd/utils.go index 3ed5c989f2..a136db1010 100644 --- a/filebrowser/cmd/utils.go +++ b/filebrowser/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,32 +24,21 @@ import ( "github.com/filebrowser/filebrowser/v2/storage/bolt" ) -const dbPerms = 0640 +const databasePermissions = 0640 -func getString(flags *pflag.FlagSet, flag string) (string, error) { - return flags.GetString(flag) -} - -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) { - return flags.GetBool(flag) -} - -func getUint(flags *pflag.FlagSet, flag string) (uint, error) { - return flags.GetUint(flag) -} - func generateKey() []byte { k, err := settings.GenerateKey() if err != nil { @@ -55,19 +47,6 @@ func generateKey() []byte { return k } -type cobraFunc func(cmd *cobra.Command, args []string) error -type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error - -type pythonConfig struct { - noDB bool - allowNoDB bool -} - -type pythonData struct { - hadDB bool - store *storage.Storage -} - func dbExists(path string) (bool, error) { stat, err := os.Stat(path) if err == nil { @@ -88,38 +67,131 @@ func dbExists(path string) (bool, error) { return false, err } +// Generate the replacements for all environment variables. This allows to +// use FB_BRANDING_DISABLE_EXTERNAL environment variables, even when the +// option name is branding.disableExternal. +func generateEnvKeyReplacements(cmd *cobra.Command) []string { + replacements := []string{} + + cmd.Flags().VisitAll(func(f *pflag.Flag) { + oldName := strings.ToUpper(f.Name) + newName := strings.ToUpper(lo.SnakeCase(f.Name)) + replacements = append(replacements, oldName, newName) + }) + + return replacements +} + +func initViper(cmd *cobra.Command) (*viper.Viper, error) { + v := viper.New() + + // Get config file from flag + cfgFile, err := cmd.Flags().GetString("config") + if err != nil { + return nil, err + } + + // Configuration file + if cfgFile == "" { + home, err := homedir.Dir() + if err != nil { + return nil, err + } + v.AddConfigPath(".") + v.AddConfigPath(home) + v.AddConfigPath("/etc/filebrowser/") + v.SetConfigName(".filebrowser") + } else { + v.SetConfigFile(cfgFile) + } + + // Environment variables + v.SetEnvPrefix("FB") + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(generateEnvKeyReplacements(cmd)...)) + + // Bind the flags + err = v.BindPFlags(cmd.Flags()) + if err != nil { + return nil, err + } + + // Read in configuration + if err := v.ReadInConfig(); err != nil { + if errors.Is(err, viper.ConfigParseError{}) { + return nil, err + } + + log.Println("No config file used") + } else { + log.Printf("Using config file: %s", v.ConfigFileUsed()) + } + + // Return Viper + return v, nil +} + +type cobraFunc func(cmd *cobra.Command, args []string) error +type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error + +type pythonConfig struct { + expectsNoDatabase bool + allowsNoDatabase bool +} + +type pythonData struct { + databaseExisted bool + viper *viper.Viper + store *storage.Storage +} + func python(fn pythonFunc, cfg pythonConfig) cobraFunc { return func(cmd *cobra.Command, args []string) error { - data := &pythonData{hadDB: true} + v, err := initViper(cmd) + if err != nil { + return err + } + + data := &pythonData{databaseExisted: true} + path := v.GetString("database") + + // Only make the viper instance available to the root command (filebrowser). + // This is to make sure that we don't make the mistake of using it somewhere + // else. + if cmd.Name() == "filebrowser" { + data.viper = v + } - path := getStringParam(cmd.Flags(), "database") absPath, err := filepath.Abs(path) if err != nil { - panic(err) + return err } - exists, err := dbExists(path) + exists, err := dbExists(path) if err != nil { - panic(err) - } else if exists && cfg.noDB { + return err + } else if exists && cfg.expectsNoDatabase { log.Fatal(absPath + " already exists") - } else if !exists && !cfg.noDB && !cfg.allowNoDB { + } else if !exists && !cfg.expectsNoDatabase && !cfg.allowsNoDatabase { log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.") - } else if !exists && !cfg.noDB { + } else if !exists && !cfg.expectsNoDatabase { log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db")) } log.Println("Using database: " + absPath) - data.hadDB = exists - db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil)) + data.databaseExisted = exists + + db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil)) if err != nil { return err } defer db.Close() + data.store, err = bolt.NewStorage(db) if err != nil { return err } + return fn(cmd, args, data) } } diff --git a/filebrowser/docker/common/defaults/settings.json b/filebrowser/docker/common/defaults/settings.json index e787ef875d..cf7fb4eea2 100644 --- a/filebrowser/docker/common/defaults/settings.json +++ b/filebrowser/docker/common/defaults/settings.json @@ -5,4 +5,4 @@ "log": "stdout", "database": "/database/filebrowser.db", "root": "/srv" -} \ No newline at end of file +} diff --git a/filebrowser/go.mod b/filebrowser/go.mod index 47da261994..abaeff87ee 100644 --- a/filebrowser/go.mod +++ b/filebrowser/go.mod @@ -16,6 +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/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 diff --git a/filebrowser/go.sum b/filebrowser/go.sum index c6b0e41bcc..551f034ebd 100644 --- a/filebrowser/go.sum +++ b/filebrowser/go.sum @@ -202,6 +202,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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= diff --git a/filebrowser/www/docs/cli/filebrowser-config-init.md b/filebrowser/www/docs/cli/filebrowser-config-init.md index 126082d02d..6ea484e64a 100644 --- a/filebrowser/www/docs/cli/filebrowser-config-init.md +++ b/filebrowser/www/docs/cli/filebrowser-config-init.md @@ -17,63 +17,60 @@ filebrowser config init [flags] ## Options ``` - --aceEditorTheme string ace editor's syntax highlighting theme for users - -a, --address string address to listen on (default "127.0.0.1") - --auth.command string command for auth.method=hook - --auth.header string HTTP header for auth.method=proxy - --auth.method string authentication type (default "json") - -b, --baseurl string base url - --branding.color string set the theme color - --branding.disableExternal disable external links such as GitHub links - --branding.disableUsedPercentage disable used disk percentage graph - --branding.files string path to directory with images and custom styles - --branding.name string replace 'File Browser' by this name - --branding.theme string set the theme - --cache-dir string file cache directory (disabled if empty) - -t, --cert string tls certificate - --commands strings a list of the commands a user can execute - --create-user-dir generate user's home directory automatically - --dateFormat use date format (true for absolute time, false for relative) - --dir-mode string mode bits that new directories are created with (default "0o750") - --disable-exec disables Command Runner feature (default true) - --disable-preview-resize disable resize of image previews - --disable-thumbnails disable image thumbnails - --disable-type-detection-by-header disables type detection by reading file headers - --file-mode string mode bits that new files are created with (default "0o640") - -h, --help help for init - --hide-login-button hide login button from public pages - --hideDotfiles hide dotfiles - --img-processors int image processors count (default 4) - -k, --key string tls key - --locale string locale for users (default "en") - --lockPassword lock password - -l, --log string log output (default "stdout") - --minimum-password-length uint minimum password length for new users (default 12) - --perm.admin admin perm for users - --perm.create create perm for users (default true) - --perm.delete delete perm for users (default true) - --perm.download download perm for users (default true) - --perm.execute execute perm for users (default true) - --perm.modify modify perm for users (default true) - --perm.rename rename perm for users (default true) - --perm.share share perm for users (default true) - -p, --port string port to listen on (default "8080") - --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") - --recaptcha.key string ReCaptcha site key - --recaptcha.secret string ReCaptcha secret - -r, --root string root to prepend to relative paths (default ".") - --scope string scope for users (default ".") - --shell string shell command to which other commands should be appended - -s, --signup allow users to signup - --singleClick use single clicks only - --socket string socket to listen to (cannot be used with address, port, cert nor key flags) - --socket-perm uint32 unix socket file permissions (default 438) - --sorting.asc sorting by ascending order - --sorting.by string sorting mode (name, size or modified) (default "name") - --token-expiration-time string user session timeout (default "2h") - --tus.chunkSize uint the tus chunk size (default 10485760) - --tus.retryCount uint16 the tus retry count (default 5) - --viewMode string view mode for users (default "list") + --aceEditorTheme string ace editor's syntax highlighting theme for users + -a, --address string address to listen on (default "127.0.0.1") + --auth.command string command for auth.method=hook + --auth.header string HTTP header for auth.method=proxy + --auth.method string authentication type (default "json") + -b, --baseURL string base url + --branding.color string set the theme color + --branding.disableExternal disable external links such as GitHub links + --branding.disableUsedPercentage disable used disk percentage graph + --branding.files string path to directory with images and custom styles + --branding.name string replace 'File Browser' by this name + --branding.theme string set the theme + -t, --cert string tls certificate + --commands strings a list of the commands a user can execute + --createUserDir generate user's home directory automatically + --dateFormat use date format (true for absolute time, false for relative) + --dirMode string mode bits that new directories are created with (default "0o750") + --disableExec disables Command Runner feature (default true) + --disablePreviewResize disable resize of image previews + --disableThumbnails disable image thumbnails + --disableTypeDetectionByHeader disables type detection by reading file headers + --fileMode string mode bits that new files are created with (default "0o640") + -h, --help help for init + --hideDotfiles hide dotfiles + --hideLoginButton hide login button from public pages + -k, --key string tls key + --locale string locale for users (default "en") + --lockPassword lock password + -l, --log string log output (default "stdout") + --minimumPasswordLength uint minimum password length for new users (default 12) + --perm.admin admin perm for users + --perm.create create perm for users (default true) + --perm.delete delete perm for users (default true) + --perm.download download perm for users (default true) + --perm.execute execute perm for users (default true) + --perm.modify modify perm for users (default true) + --perm.rename rename perm for users (default true) + --perm.share share perm for users (default true) + -p, --port string port to listen on (default "8080") + --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") + --recaptcha.key string ReCaptcha site key + --recaptcha.secret string ReCaptcha secret + -r, --root string root to prepend to relative paths (default ".") + --scope string scope for users (default ".") + --shell string shell command to which other commands should be appended + -s, --signup allow users to signup + --singleClick use single clicks only + --socket string socket to listen to (cannot be used with address, port, cert nor key flags) + --sorting.asc sorting by ascending order + --sorting.by string sorting mode (name, size or modified) (default "name") + --tokenExpirationTime string user session timeout (default "2h") + --tus.chunkSize uint the tus chunk size (default 10485760) + --tus.retryCount uint16 the tus retry count (default 5) + --viewMode string view mode for users (default "list") ``` ## Options inherited from parent commands diff --git a/filebrowser/www/docs/cli/filebrowser-config-set.md b/filebrowser/www/docs/cli/filebrowser-config-set.md index 8d8ea8f53e..93515ab403 100644 --- a/filebrowser/www/docs/cli/filebrowser-config-set.md +++ b/filebrowser/www/docs/cli/filebrowser-config-set.md @@ -14,63 +14,60 @@ filebrowser config set [flags] ## Options ``` - --aceEditorTheme string ace editor's syntax highlighting theme for users - -a, --address string address to listen on (default "127.0.0.1") - --auth.command string command for auth.method=hook - --auth.header string HTTP header for auth.method=proxy - --auth.method string authentication type (default "json") - -b, --baseurl string base url - --branding.color string set the theme color - --branding.disableExternal disable external links such as GitHub links - --branding.disableUsedPercentage disable used disk percentage graph - --branding.files string path to directory with images and custom styles - --branding.name string replace 'File Browser' by this name - --branding.theme string set the theme - --cache-dir string file cache directory (disabled if empty) - -t, --cert string tls certificate - --commands strings a list of the commands a user can execute - --create-user-dir generate user's home directory automatically - --dateFormat use date format (true for absolute time, false for relative) - --dir-mode string mode bits that new directories are created with (default "0o750") - --disable-exec disables Command Runner feature (default true) - --disable-preview-resize disable resize of image previews - --disable-thumbnails disable image thumbnails - --disable-type-detection-by-header disables type detection by reading file headers - --file-mode string mode bits that new files are created with (default "0o640") - -h, --help help for set - --hide-login-button hide login button from public pages - --hideDotfiles hide dotfiles - --img-processors int image processors count (default 4) - -k, --key string tls key - --locale string locale for users (default "en") - --lockPassword lock password - -l, --log string log output (default "stdout") - --minimum-password-length uint minimum password length for new users (default 12) - --perm.admin admin perm for users - --perm.create create perm for users (default true) - --perm.delete delete perm for users (default true) - --perm.download download perm for users (default true) - --perm.execute execute perm for users (default true) - --perm.modify modify perm for users (default true) - --perm.rename rename perm for users (default true) - --perm.share share perm for users (default true) - -p, --port string port to listen on (default "8080") - --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") - --recaptcha.key string ReCaptcha site key - --recaptcha.secret string ReCaptcha secret - -r, --root string root to prepend to relative paths (default ".") - --scope string scope for users (default ".") - --shell string shell command to which other commands should be appended - -s, --signup allow users to signup - --singleClick use single clicks only - --socket string socket to listen to (cannot be used with address, port, cert nor key flags) - --socket-perm uint32 unix socket file permissions (default 438) - --sorting.asc sorting by ascending order - --sorting.by string sorting mode (name, size or modified) (default "name") - --token-expiration-time string user session timeout (default "2h") - --tus.chunkSize uint the tus chunk size (default 10485760) - --tus.retryCount uint16 the tus retry count (default 5) - --viewMode string view mode for users (default "list") + --aceEditorTheme string ace editor's syntax highlighting theme for users + -a, --address string address to listen on (default "127.0.0.1") + --auth.command string command for auth.method=hook + --auth.header string HTTP header for auth.method=proxy + --auth.method string authentication type (default "json") + -b, --baseURL string base url + --branding.color string set the theme color + --branding.disableExternal disable external links such as GitHub links + --branding.disableUsedPercentage disable used disk percentage graph + --branding.files string path to directory with images and custom styles + --branding.name string replace 'File Browser' by this name + --branding.theme string set the theme + -t, --cert string tls certificate + --commands strings a list of the commands a user can execute + --createUserDir generate user's home directory automatically + --dateFormat use date format (true for absolute time, false for relative) + --dirMode string mode bits that new directories are created with (default "0o750") + --disableExec disables Command Runner feature (default true) + --disablePreviewResize disable resize of image previews + --disableThumbnails disable image thumbnails + --disableTypeDetectionByHeader disables type detection by reading file headers + --fileMode string mode bits that new files are created with (default "0o640") + -h, --help help for set + --hideDotfiles hide dotfiles + --hideLoginButton hide login button from public pages + -k, --key string tls key + --locale string locale for users (default "en") + --lockPassword lock password + -l, --log string log output (default "stdout") + --minimumPasswordLength uint minimum password length for new users (default 12) + --perm.admin admin perm for users + --perm.create create perm for users (default true) + --perm.delete delete perm for users (default true) + --perm.download download perm for users (default true) + --perm.execute execute perm for users (default true) + --perm.modify modify perm for users (default true) + --perm.rename rename perm for users (default true) + --perm.share share perm for users (default true) + -p, --port string port to listen on (default "8080") + --recaptcha.host string use another host for ReCAPTCHA. recaptcha.net might be useful in China (default "https://www.google.com") + --recaptcha.key string ReCaptcha site key + --recaptcha.secret string ReCaptcha secret + -r, --root string root to prepend to relative paths (default ".") + --scope string scope for users (default ".") + --shell string shell command to which other commands should be appended + -s, --signup allow users to signup + --singleClick use single clicks only + --socket string socket to listen to (cannot be used with address, port, cert nor key flags) + --sorting.asc sorting by ascending order + --sorting.by string sorting mode (name, size or modified) (default "name") + --tokenExpirationTime string user session timeout (default "2h") + --tus.chunkSize uint the tus chunk size (default 10485760) + --tus.retryCount uint16 the tus retry count (default 5) + --viewMode string view mode for users (default "list") ``` ## Options inherited from parent commands diff --git a/filebrowser/www/docs/cli/filebrowser.md b/filebrowser/www/docs/cli/filebrowser.md index a8cbe66929..8383ec97da 100644 --- a/filebrowser/www/docs/cli/filebrowser.md +++ b/filebrowser/www/docs/cli/filebrowser.md @@ -13,12 +13,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/ @@ -26,15 +28,11 @@ 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 @@ -47,28 +45,28 @@ filebrowser [flags] ## Options ``` - -a, --address string address to listen on (default "127.0.0.1") - -b, --baseurl string base url - --cache-dir string file cache directory (disabled if empty) - -t, --cert string tls certificate - -c, --config string config file path - -d, --database string database path (default "./filebrowser.db") - --disable-exec disables Command Runner feature (default true) - --disable-preview-resize disable resize of image previews - --disable-thumbnails disable image thumbnails - --disable-type-detection-by-header disables type detection by reading file headers - -h, --help help for filebrowser - --img-processors int image processors count (default 4) - -k, --key string tls key - -l, --log string log output (default "stdout") - --noauth use the noauth auther when using quick setup - --password string hashed password for the first user when using quick config - -p, --port string port to listen on (default "8080") - -r, --root string root to prepend to relative paths (default ".") - --socket string socket to listen to (cannot be used with address, port, cert nor key flags) - --socket-perm uint32 unix socket file permissions (default 438) - --token-expiration-time string user session timeout (default "2h") - --username string username for the first user when using quick config (default "admin") + -a, --address string address to listen on (default "127.0.0.1") + -b, --baseURL string base url + --cacheDir string file cache directory (disabled if empty) + -t, --cert string tls certificate + -c, --config string config file path + -d, --database string database path (default "./filebrowser.db") + --disableExec disables Command Runner feature (default true) + --disablePreviewResize disable resize of image previews + --disableThumbnails disable image thumbnails + --disableTypeDetectionByHeader disables type detection by reading file headers + -h, --help help for filebrowser + --imageProcessors int image processors count (default 4) + -k, --key string tls key + -l, --log string log output (default "stdout") + --noauth use the noauth auther when using quick setup + --password string hashed password for the first user when using quick setup + -p, --port string port to listen on (default "8080") + -r, --root string root to prepend to relative paths (default ".") + --socket string socket to listen to (cannot be used with address, port, cert nor key flags) + --socketPerm uint32 unix socket file permissions (default 438) + --tokenExpirationTime string user session timeout (default "2h") + --username string username for the first user when using quick setup (default "admin") ``` ## See Also diff --git a/filebrowser/www/docs/troubleshooting.md b/filebrowser/www/docs/troubleshooting.md new file mode 100644 index 0000000000..a994957e7a --- /dev/null +++ b/filebrowser/www/docs/troubleshooting.md @@ -0,0 +1,9 @@ +# Troubleshooting + +## Session Timeout + +By default, user sessions expire after **2 hours**. If you're uploading large files over slower connections, you may need to increase this timeout to prevent sessions from expiring mid-upload. You can configure the session timeout using the `tokenExpirationTime` setting. + +You can either set this option during runtime by using the flag `--tokenExpirationTime`, the environment variable `FB_TOKEN_EXPIRATION_TIME`, or in your configuration file. If you want to persist this to the configuration, please use [`filebrowser config set`](cli/filebrowser-config-set.md). + +Valid duration formats include `"2h"`, `"30m"`, `"24h"`, or combinations like `"2h30m"`. diff --git a/filebrowser/www/mkdocs.yml b/filebrowser/www/mkdocs.yml index 7558c53464..c71c679863 100644 --- a/filebrowser/www/mkdocs.yml +++ b/filebrowser/www/mkdocs.yml @@ -100,6 +100,7 @@ nav: - customization.md - authentication.md - command-execution.md + - Troubleshooting: troubleshooting.md - Deployment: deployment.md - Command Line Usage: - cli/filebrowser.md diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index a55d78ec64..920ea0645e 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .56 -LINUX_KERNEL_HASH-6.12.56 = 55432b2af352f7bf3053c348d8549df2f2deeaa4a361c65d638c2f3b2ca7ec96 +LINUX_VERSION-6.12 = .58 +LINUX_KERNEL_HASH-6.12.58 = 5f1c4c546660a6a81046fdfa6195306bad2c8d17c0d69876dc100a85ad4613ac diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index 706c50ef7b..6bedc40098 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .115 -LINUX_KERNEL_HASH-6.6.115 = 0a98c05e8d0f6b49fad71b8d779410a0811ea5ae17d81744fe30718633fd9047 +LINUX_VERSION-6.6 = .116 +LINUX_KERNEL_HASH-6.6.116 = a9a59742c29be284c205dc87cbe9b065f9688488132c8f5a6057a5539230a51d diff --git a/lede/target/linux/qualcommax/patches-6.12/0151-arm64-qcom-ipq6018-nss_port5.patch b/lede/target/linux/qualcommax/patches-6.12/0151-arm64-qcom-ipq6018-nss_port5.patch deleted file mode 100644 index 9e4372a878..0000000000 --- a/lede/target/linux/qualcommax/patches-6.12/0151-arm64-qcom-ipq6018-nss_port5.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 9989fcd49c52500a2bf1f6d49411690dec45d2dc Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= -Date: Sat, 2 Aug 2025 12:47:08 +0300 -Subject: [PATCH] clk: qcom: gcc-ipq6018: rework nss_port5 clock to multiple - conf - -Rework nss_port5 to use the new multiple configuration implementation -and correctly fix the clocks for this port under some corner case. - -In OpenWrt, this patch avoids intermittent dmesg errors of the form -nss_port5_rx_clk_src: rcg didn't update its configuration. - -This is a mechanical, straightforward port of -commit e88f03230dc07aa3293b6aeb078bd27370bb2594 -("clk: qcom: gcc-ipq8074: rework nss_port5/6 clock to multiple conf") -to gcc-ipq6018, with two conflicts resolved: different frequency of the -P_XO clock source, and only 5 Ethernet ports. - -This was originally developed by JiaY-shi . - -Link: https://lore.kernel.org/all/20231220221724.3822-4-ansuelsmth@gmail.com/ -Signed-off-by: Marko Mäkelä -Tested-by: Marko Mäkelä ---- - drivers/clk/qcom/gcc-ipq6018.c | 60 +++++++++++++++++++++------------- - 1 file changed, 38 insertions(+), 22 deletions(-) - ---- a/drivers/clk/qcom/gcc-ipq6018.c -+++ b/drivers/clk/qcom/gcc-ipq6018.c -@@ -511,15 +511,23 @@ static struct clk_rcg2 apss_ahb_clk_src - }, - }; - --static const struct freq_tbl ftbl_nss_port5_rx_clk_src[] = { -- F(24000000, P_XO, 1, 0, 0), -- F(25000000, P_UNIPHY1_RX, 12.5, 0, 0), -- F(25000000, P_UNIPHY0_RX, 5, 0, 0), -- F(78125000, P_UNIPHY1_RX, 4, 0, 0), -- F(125000000, P_UNIPHY1_RX, 2.5, 0, 0), -- F(125000000, P_UNIPHY0_RX, 1, 0, 0), -- F(156250000, P_UNIPHY1_RX, 2, 0, 0), -- F(312500000, P_UNIPHY1_RX, 1, 0, 0), -+static const struct freq_conf ftbl_nss_port5_rx_clk_src_25[] = { -+ C(P_UNIPHY1_RX, 12.5, 0, 0), -+ C(P_UNIPHY0_RX, 5, 0, 0), -+}; -+ -+static const struct freq_conf ftbl_nss_port5_rx_clk_src_125[] = { -+ C(P_UNIPHY1_RX, 2.5, 0, 0), -+ C(P_UNIPHY0_RX, 1, 0, 0), -+}; -+ -+static const struct freq_multi_tbl ftbl_nss_port5_rx_clk_src[] = { -+ FMS(24000000, P_XO, 1, 0, 0), -+ FM(25000000, ftbl_nss_port5_rx_clk_src_25), -+ FMS(78125000, P_UNIPHY1_RX, 4, 0, 0), -+ FM(125000000, ftbl_nss_port5_rx_clk_src_125), -+ FMS(156250000, P_UNIPHY1_RX, 2, 0, 0), -+ FMS(312500000, P_UNIPHY1_RX, 1, 0, 0), - { } - }; - -@@ -547,26 +555,34 @@ gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32 - - static struct clk_rcg2 nss_port5_rx_clk_src = { - .cmd_rcgr = 0x68060, -- .freq_tbl = ftbl_nss_port5_rx_clk_src, -+ .freq_multi_tbl = ftbl_nss_port5_rx_clk_src, - .hid_width = 5, - .parent_map = gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "nss_port5_rx_clk_src", - .parent_data = gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias, - .num_parents = 7, -- .ops = &clk_rcg2_ops, -+ .ops = &clk_rcg2_fm_ops, - }, - }; - --static const struct freq_tbl ftbl_nss_port5_tx_clk_src[] = { -- F(24000000, P_XO, 1, 0, 0), -- F(25000000, P_UNIPHY1_TX, 12.5, 0, 0), -- F(25000000, P_UNIPHY0_TX, 5, 0, 0), -- F(78125000, P_UNIPHY1_TX, 4, 0, 0), -- F(125000000, P_UNIPHY1_TX, 2.5, 0, 0), -- F(125000000, P_UNIPHY0_TX, 1, 0, 0), -- F(156250000, P_UNIPHY1_TX, 2, 0, 0), -- F(312500000, P_UNIPHY1_TX, 1, 0, 0), -+static const struct freq_conf ftbl_nss_port5_tx_clk_src_25[] = { -+ C(P_UNIPHY1_TX, 12.5, 0, 0), -+ C(P_UNIPHY0_TX, 5, 0, 0), -+}; -+ -+static const struct freq_conf ftbl_nss_port5_tx_clk_src_125[] = { -+ C(P_UNIPHY1_TX, 2.5, 0, 0), -+ C(P_UNIPHY0_TX, 1, 0, 0), -+}; -+ -+static const struct freq_multi_tbl ftbl_nss_port5_tx_clk_src[] = { -+ FMS(24000000, P_XO, 1, 0, 0), -+ FM(25000000, ftbl_nss_port5_tx_clk_src_25), -+ FMS(78125000, P_UNIPHY1_TX, 4, 0, 0), -+ FM(125000000, ftbl_nss_port5_tx_clk_src_125), -+ FMS(156250000, P_UNIPHY1_TX, 2, 0, 0), -+ FMS(312500000, P_UNIPHY1_TX, 1, 0, 0), - { } - }; - -@@ -594,14 +610,14 @@ gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32 - - static struct clk_rcg2 nss_port5_tx_clk_src = { - .cmd_rcgr = 0x68068, -- .freq_tbl = ftbl_nss_port5_tx_clk_src, -+ .freq_multi_tbl = ftbl_nss_port5_tx_clk_src, - .hid_width = 5, - .parent_map = gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "nss_port5_tx_clk_src", - .parent_data = gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias, - .num_parents = 7, -- .ops = &clk_rcg2_ops, -+ .ops = &clk_rcg2_fm_ops, - }, - }; - diff --git a/mieru/pkg/protocol/const.go b/mieru/pkg/protocol/const.go deleted file mode 100644 index 55f005e6ea..0000000000 --- a/mieru/pkg/protocol/const.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2024 mieru authors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package protocol - -import "time" - -const ( - // periodicOutputInterval triggers periodic output of packet transport, - // even if there is no new data to send. - periodicOutputInterval = 1 * time.Millisecond - - // backPressureDelay is a short sleep to add back pressure - // to the writer. - backPressureDelay = 100 * time.Microsecond -) diff --git a/mieru/pkg/protocol/session.go b/mieru/pkg/protocol/session.go index 686086ecf6..3608eb385d 100644 --- a/mieru/pkg/protocol/session.go +++ b/mieru/pkg/protocol/session.go @@ -54,6 +54,14 @@ const ( serverRespTimeout = 10 * time.Second sessionHeartbeatInterval = 5 * time.Second + // periodicOutputInterval triggers periodic output of packet transport, + // even if there is no new data to send. + periodicOutputInterval = 1 * time.Millisecond + + // backPressureDelay is a short sleep to add back pressure + // to the writer. + backPressureDelay = 100 * time.Microsecond + // Number of ack to trigger early retransmission. earlyRetransmission = 3 // Maximum number of early retransmission attempt. @@ -1236,15 +1244,7 @@ func (s *Session) sendWindowSize() int { // receiveWindowSize determines how many more packets this session can receive. func (s *Session) receiveWindowSize() int { - var underlayWaitingPackets int - if s.conn != nil { - packetUnderlay, ok := s.conn.(*PacketUnderlay) - if ok { - // Other packets sharing the same UDP socket reduce the congestion window. - underlayWaitingPackets = len(packetUnderlay.packetQueue) - } - } - return mathext.Max(0, int(s.cubicSendAlgorithm.CongestionWindowSize())-s.recvBuf.Len()-underlayWaitingPackets) + return mathext.Max(0, int(s.cubicSendAlgorithm.CongestionWindowSize())-s.recvBuf.Len()) } func (s *Session) checkQuota(userName string) (ok bool, err error) { diff --git a/mieru/pkg/protocol/underlay_base.go b/mieru/pkg/protocol/underlay_base.go index c2de26df70..3a0cf8a723 100644 --- a/mieru/pkg/protocol/underlay_base.go +++ b/mieru/pkg/protocol/underlay_base.go @@ -36,8 +36,8 @@ const ( sessionCleanInterval = 5 * time.Second // Buffer received network packets before they are dropped by OS kernel. - packetChanCapacityClient = 4 * 1024 - packetChanCapacityServer = 4 * 1024 + packetChanCapacityClient = segmentTreeCapacity + packetChanCapacityServer = segmentTreeCapacity ) // baseUnderlay contains a partial implementation of underlay. diff --git a/mieru/test/deploy/mihomo/Dockerfile b/mieru/test/deploy/mihomo/Dockerfile index 31a737b04d..c245f82fa2 100644 --- a/mieru/test/deploy/mihomo/Dockerfile +++ b/mieru/test/deploy/mihomo/Dockerfile @@ -23,14 +23,17 @@ WORKDIR /test COPY mihomo mieru mita httpserver sockshttpclient socksudpclient udpserver \ test/deploy/mihomo/mihomo-client-tcp.yaml \ test/deploy/mihomo/mihomo-client-tcp-no-wait.yaml \ + test/deploy/mihomo/mihomo-client-udp.yaml \ + test/deploy/mihomo/mihomo-client-udp-no-wait.yaml \ test/deploy/mihomo/mihomo-server.yaml \ test/deploy/mihomo/client_tcp_no_wait.json \ test/deploy/mihomo/client_tcp.json \ test/deploy/mihomo/client_udp_no_wait.json \ test/deploy/mihomo/client_udp.json \ test/deploy/mihomo/server_tcp.json \ + test/deploy/mihomo/server_udp.json \ test/deploy/mihomo/libtest.sh \ - test/deploy/mihomo/test_client_tcp.sh \ + test/deploy/mihomo/test_client.sh \ test/deploy/mihomo/test_server.sh \ test/deploy/mihomo/test.sh /test/ diff --git a/mieru/test/deploy/mihomo/client_udp.json b/mieru/test/deploy/mihomo/client_udp.json index 48c897fc49..de4210a46b 100644 --- a/mieru/test/deploy/mihomo/client_udp.json +++ b/mieru/test/deploy/mihomo/client_udp.json @@ -24,7 +24,7 @@ ], "activeProfile": "default", "rpcPort": 8989, - "socks5Port": 1082, + "socks5Port": 1085, "advancedSettings": { "noCheckUpdate": true }, diff --git a/mieru/test/deploy/mihomo/client_udp_no_wait.json b/mieru/test/deploy/mihomo/client_udp_no_wait.json index b43aa806c8..6146567d3f 100644 --- a/mieru/test/deploy/mihomo/client_udp_no_wait.json +++ b/mieru/test/deploy/mihomo/client_udp_no_wait.json @@ -25,7 +25,7 @@ ], "activeProfile": "default", "rpcPort": 8989, - "socks5Port": 1082, + "socks5Port": 1085, "advancedSettings": { "noCheckUpdate": true }, diff --git a/mieru/test/deploy/mihomo/mihomo-client-udp-no-wait.yaml b/mieru/test/deploy/mihomo/mihomo-client-udp-no-wait.yaml new file mode 100644 index 0000000000..259b7a9a62 --- /dev/null +++ b/mieru/test/deploy/mihomo/mihomo-client-udp-no-wait.yaml @@ -0,0 +1,19 @@ +dns: + enable: true + nameserver: + - 8.8.8.8 +log-level: warning +mixed-port: 1084 +mode: rule +proxies: + - name: mieru + type: mieru + server: 127.0.0.1 + port-range: 8964-8965 + transport: UDP + udp: true + username: baozi + password: manlianpenfen + handshake-mode: HANDSHAKE_NO_WAIT +rules: + - MATCH,mieru diff --git a/mieru/test/deploy/mihomo/mihomo-client-udp.yaml b/mieru/test/deploy/mihomo/mihomo-client-udp.yaml new file mode 100644 index 0000000000..97b51b695e --- /dev/null +++ b/mieru/test/deploy/mihomo/mihomo-client-udp.yaml @@ -0,0 +1,18 @@ +dns: + enable: true + nameserver: + - 8.8.8.8 +log-level: warning +mixed-port: 1083 +mode: rule +proxies: + - name: mieru + type: mieru + server: 127.0.0.1 + port-range: 8964-8965 + transport: UDP + udp: true + username: baozi + password: manlianpenfen +rules: + - MATCH,mieru diff --git a/mieru/test/deploy/mihomo/server_udp.json b/mieru/test/deploy/mihomo/server_udp.json new file mode 100644 index 0000000000..38ecf7b746 --- /dev/null +++ b/mieru/test/deploy/mihomo/server_udp.json @@ -0,0 +1,28 @@ +{ + "portBindings": [ + { + "portRange": "8964-8965", + "protocol": "UDP" + }, + { + "port": 9648, + "protocol": "UDP" + }, + { + "port": 6489, + "protocol": "UDP" + }, + { + "port": 4896, + "protocol": "UDP" + } + ], + "users": [ + { + "name": "baozi", + "password": "manlianpenfen", + "allowLoopbackIP": true + } + ], + "loggingLevel": "INFO" +} diff --git a/mieru/test/deploy/mihomo/test.sh b/mieru/test/deploy/mihomo/test.sh index 07a67865e2..9695c119f0 100755 --- a/mieru/test/deploy/mihomo/test.sh +++ b/mieru/test/deploy/mihomo/test.sh @@ -40,10 +40,10 @@ echo "========== BEGIN OF SERVER TEST ==========" ./test_server.sh echo "========== END OF SERVER TEST ==========" -# Run client TCP test. -echo "========== BEGIN OF CLIENT TCP TEST ==========" -./test_client_tcp.sh -echo "========== END OF CLIENT TCP TEST ==========" +# Run client test. +echo "========== BEGIN OF CLIENT TEST ==========" +./test_client.sh +echo "========== END OF CLIENT TEST ==========" echo "Test is successful." sleep 1 diff --git a/mieru/test/deploy/mihomo/test_client_tcp.sh b/mieru/test/deploy/mihomo/test_client.sh similarity index 56% rename from mieru/test/deploy/mihomo/test_client_tcp.sh rename to mieru/test/deploy/mihomo/test_client.sh index debe79af86..5d8a837cf0 100755 --- a/mieru/test/deploy/mihomo/test_client_tcp.sh +++ b/mieru/test/deploy/mihomo/test_client.sh @@ -21,28 +21,6 @@ # Load test library. source ./libtest.sh -# Update mieru server with TCP config. -./mita apply config server_tcp.json -if [[ "$?" -ne 0 ]]; then - echo "command 'mita apply config server_tcp.json' failed" - exit 1 -fi -echo "mieru server config:" -./mita describe config - -# Start mieru server proxy. -./mita start -if [[ "$?" -ne 0 ]]; then - echo "command 'mita start' failed" - exit 1 -fi - -# Start mihomo. -./mihomo -f mihomo-client-tcp.yaml & -sleep 1 -./mihomo -f mihomo-client-tcp-no-wait.yaml & -sleep 1 - function run_tcp_tests() { local port="$1" local suffix="${2:-}" @@ -58,17 +36,6 @@ function run_tcp_tests() { exit 1 fi - sleep 1 - echo ">>> http - new connections - TCP ${suffix} <<<" - ./sockshttpclient -proxy_mode=http -dst_host=127.0.0.1 -dst_port=8080 \ - -local_http_host=127.0.0.1 -local_http_port=${port} \ - -test_case=new_conn -num_request=1000 - if [ "$?" -ne "0" ]; then - print_mieru_server_thread_dump - echo "TCP - test HTTP new_conn ${suffix} failed." - exit 1 - fi - sleep 1 echo ">>> socks5 - reuse one connection - TCP ${suffix} <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ @@ -84,7 +51,7 @@ function run_tcp_tests() { echo ">>> socks5 UDP associate - TCP ${suffix} <<<" ./socksudpclient -dst_host=127.0.0.1 -dst_port=9090 \ -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ - -interval_ms=10 -num_request=100 -num_conn=60 + -interval_ms=10 -num_request=100 -num_conn=30 if [ "$?" -ne "0" ]; then print_mieru_server_thread_dump echo "TCP - test socks5 udp_associate ${suffix} failed." @@ -92,18 +59,96 @@ function run_tcp_tests() { fi } -# Start testing. +function run_udp_tests() { + local port="$1" + local suffix="${2:-}" + + sleep 1 + echo ">>> socks5 - new connections - UDP ${suffix} <<<" + ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ + -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ + -test_case=new_conn -num_request=3000 + if [ "$?" -ne "0" ]; then + print_mieru_server_thread_dump + echo "UDP - test socks5 new_conn ${suffix} failed." + exit 1 + fi + + sleep 1 + echo ">>> socks5 - reuse one connection - UDP ${suffix} <<<" + ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ + -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ + -test_case=reuse_conn -test_time_sec=30 + if [ "$?" -ne "0" ]; then + print_mieru_server_thread_dump + echo "UDP - test socks5 reuse_conn ${suffix} failed." + exit 1 + fi + + sleep 1 + echo ">>> socks5 UDP associate - UDP ${suffix} <<<" + ./socksudpclient -dst_host=127.0.0.1 -dst_port=9090 \ + -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ + -interval_ms=10 -num_request=100 -num_conn=30 + if [ "$?" -ne "0" ]; then + print_mieru_server_thread_dump + echo "UDP - test socks5 udp_associate ${suffix} failed." + exit 1 + fi +} + +echo "========== BEGIN OF CLIENT TCP TEST ==========" +./mita apply config server_tcp.json +if [[ "$?" -ne 0 ]]; then + echo "command 'mita apply config server_tcp.json' failed" + exit 1 +fi +echo "mieru server config:" +./mita describe config +./mita start +if [[ "$?" -ne 0 ]]; then + echo "command 'mita start' failed" + exit 1 +fi +./mihomo -f mihomo-client-tcp.yaml & +sleep 1 +./mihomo -f mihomo-client-tcp-no-wait.yaml & +sleep 1 run_tcp_tests 1080 run_tcp_tests 1081 "(handshake no wait)" - -# Print metrics and memory statistics. print_mieru_server_metrics sleep 1 - -# Stop mieru server proxy. ./mita stop if [[ "$?" -ne 0 ]]; then echo "command 'mita stop' failed" exit 1 fi -sleep 1 +echo "========== END OF CLIENT TCP TEST ==========" + +# echo "========== BEGIN OF CLIENT UDP TEST ==========" +# ./mita apply config server_udp.json +# if [[ "$?" -ne 0 ]]; then +# echo "command 'mita apply config server_udp.json' failed" +# exit 1 +# fi +# echo "mieru server config:" +# ./mita describe config +# ./mita start +# if [[ "$?" -ne 0 ]]; then +# echo "command 'mita start' failed" +# exit 1 +# fi +# ./mihomo -f mihomo-client-udp.yaml & +# sleep 1 +# ./mihomo -f mihomo-client-udp-no-wait.yaml & +# sleep 1 +# run_udp_tests 1083 +# run_udp_tests 1084 "(handshake no wait)" +# print_mieru_server_metrics +# sleep 1 +# ./mita stop +# if [[ "$?" -ne 0 ]]; then +# echo "command 'mita stop' failed" +# exit 1 +# fi +# echo "========== END OF CLIENT UDP TEST ==========" diff --git a/mieru/test/deploy/mihomo/test_server.sh b/mieru/test/deploy/mihomo/test_server.sh index 8bcd66ad7d..019ae74ee1 100755 --- a/mieru/test/deploy/mihomo/test_server.sh +++ b/mieru/test/deploy/mihomo/test_server.sh @@ -23,10 +23,12 @@ source ./libtest.sh function run_new_conn_test() { local config="$1" + local port="$2" + sleep 1 echo ">>> socks5 - new connections with mihomo server - $config <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ - -local_proxy_host=127.0.0.1 -local_proxy_port=1082 \ + -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ -test_case=new_conn -num_request=3000 if [ "$?" -ne "0" ]; then print_mieru_client_log @@ -37,10 +39,12 @@ function run_new_conn_test() { function run_udp_associate_test() { local config="$1" + local port="$2" + sleep 1 echo ">>> socks5 UDP associate - with mihomo server - $config <<<" ./socksudpclient -dst_host=127.0.0.1 -dst_port=9090 \ - -local_proxy_host=127.0.0.1 -local_proxy_port=1082 \ + -local_proxy_host=127.0.0.1 -local_proxy_port=${port} \ -interval_ms=10 -num_request=100 -num_conn=30 if [ "$?" -ne "0" ]; then print_mieru_client_log @@ -50,7 +54,8 @@ function run_udp_associate_test() { } function test_mieru_with_config() { - config="$1" + local config="$1" + local port="$2" # Update mieru client with TCP config. ./mieru apply config $config @@ -69,8 +74,8 @@ function test_mieru_with_config() { fi # Start testing. - run_new_conn_test "$config" - run_udp_associate_test "$config" + run_new_conn_test "${config}" "${port}" + run_udp_associate_test "${config}" "${port}" # Stop mieru client. ./mieru stop @@ -87,11 +92,12 @@ function test_mieru_with_config() { ./mihomo -f mihomo-server.yaml & sleep 1 -test_mieru_with_config client_tcp.json -test_mieru_with_config client_tcp_no_wait.json -test_mieru_with_config client_udp.json -test_mieru_with_config client_udp_no_wait.json +echo "========== BEGIN OF SERVER TCP TEST ==========" +test_mieru_with_config client_tcp.json 1082 +test_mieru_with_config client_tcp_no_wait.json 1082 +echo "========== END OF SERVER TCP TEST ==========" -echo "Test is successful." -sleep 1 -exit 0 +echo "========== BEGIN OF SERVER UDP TEST ==========" +test_mieru_with_config client_udp.json 1085 +test_mieru_with_config client_udp_no_wait.json 1085 +echo "========== END OF SERVER UDP TEST ==========" diff --git a/openwrt-packages/filebrowser/Makefile b/openwrt-packages/filebrowser/Makefile index dfbe4194f9..de6c5bd319 100644 --- a/openwrt-packages/filebrowser/Makefile +++ b/openwrt-packages/filebrowser/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=filebrowser -PKG_VERSION:=2.47.0 +PKG_VERSION:=2.48.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v${PKG_VERSION}? -PKG_HASH:=f90b2a9981545570006c0984aa39ee7178f4efd71d1ddf660c8661ea2f9bfbd6 +PKG_HASH:=556b8092c69b65c11d917c8d1e0fef418ea88a9d437c2ec7b1cab506973eb743 PKG_LICENSE:=Apache-2.0 PKG_LICENSE_FILES:=LICENSE diff --git a/openwrt-packages/lua-maxminddb/Makefile b/openwrt-packages/lua-maxminddb/Makefile deleted file mode 100644 index e92f3d1092..0000000000 --- a/openwrt-packages/lua-maxminddb/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-only -# -# Copyright (C) 2020 jerryk -# Copyright (C) 2021 ImmortalWrt.org - -include $(TOPDIR)/rules.mk - -PKG_NAME:=lua-maxminddb -PKG_VERSION:=0.2 -PKG_RELEASE:=2 - -PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://github.com/fabled/lua-maxminddb.git -PKG_SOURCE_DATE:=2019-03-14 -PKG_SOURCE_VERSION:=93da9f4e6c814c3a23044dd2cdd22d4a6b4f665b -PKG_MIRROR_HASH:=bebf4fbb25c33013ca1e09b8d1a50ee9ae5ce1c810d3335b285f9bf2d1f316b2 - -PKG_LICENSE:=MIT -PKG_LICENSE_FILES:=LICENSE - -PKG_BUILD_PARALLEL:=1 - -include $(INCLUDE_DIR)/package.mk - -define Package/lua-maxminddb - SUBMENU:=Lua - SECTION:=lang - CATEGORY:=Languages - TITLE:=libmaxminddb bindings for lua - URL:=https://github.com/fabled/lua-maxminddb - DEPENDS:=+lua +libmaxminddb -endef - -TARGET_CFLAGS += $(FPIC) - -MAKE_VARS += LUA_PKG=lua - -define Package/lua-maxminddb/install - $(INSTALL_DIR) $(1)/usr/lib/lua - $(INSTALL_BIN) $(PKG_BUILD_DIR)/maxminddb.so $(1)/usr/lib/lua/ -endef - -$(eval $(call BuildPackage,lua-maxminddb)) diff --git a/openwrt-packages/luci-app-amlogic/Makefile b/openwrt-packages/luci-app-amlogic/Makefile index e9805d6310..fdddf652e3 100644 --- a/openwrt-packages/luci-app-amlogic/Makefile +++ b/openwrt-packages/luci-app-amlogic/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-amlogic -PKG_VERSION:=3.1.276 +PKG_VERSION:=3.1.278 PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 License diff --git a/openwrt-packages/luci-app-amlogic/luasrc/model/cbi/amlogic/amlogic_config.lua b/openwrt-packages/luci-app-amlogic/luasrc/model/cbi/amlogic/amlogic_config.lua index 8e2dd4cddf..caa1c8f4f3 100644 --- a/openwrt-packages/luci-app-amlogic/luasrc/model/cbi/amlogic/amlogic_config.lua +++ b/openwrt-packages/luci-app-amlogic/luasrc/model/cbi/amlogic/amlogic_config.lua @@ -76,7 +76,6 @@ local known_tags = { local ophub_extra_tags = { kernel_flippy = "kernel_flippy [Mainline Stable Kernel by Flippy]", kernel_h6 = "kernel_h6 [Allwinner H6 Kernel]", - kernel_dev = "kernel_dev [Development Kernel]", kernel_beta = "kernel_beta [Beta Kernel]", } -- Conditionally add the extra tags to the list. diff --git a/openwrt-packages/luci-app-amlogic/po/zh-cn/amlogic.po b/openwrt-packages/luci-app-amlogic/po/zh-cn/amlogic.po index 14ec5f5b7d..61c1ee5d9c 100644 --- a/openwrt-packages/luci-app-amlogic/po/zh-cn/amlogic.po +++ b/openwrt-packages/luci-app-amlogic/po/zh-cn/amlogic.po @@ -413,9 +413,6 @@ msgstr "kernel_flippy [Flippy 的主线稳定内核]" msgid "kernel_h6 [Allwinner H6 Kernel]" msgstr "kernel_h6 [Allwinner H6 内核]" -msgid "kernel_dev [Development Kernel]" -msgstr "kernel_dev [开发版内核]" - msgid "kernel_beta [Beta Kernel]" msgstr "kernel_beta [测试版内核]" diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua index 48b0651917..4dfd6d77ae 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua @@ -338,7 +338,7 @@ o.remove = function(self, section) local new_val = (v.type == "Xray") and "xray" or "sing-box" m:set(section, self.option, new_val) - local dns_field = s.fields[new_val .. "_dns_mode"] + local dns_field = s.fields[v.type == "Xray" and "xray_dns_mode" or "singbox_dns_mode"] local v2ray_dns_mode = dns_field and dns_field:formvalue(section) if v2ray_dns_mode then m:set(section, "v2ray_dns_mode", v2ray_dns_mode) @@ -350,6 +350,8 @@ o.remove = function(self, section) end o = s:option(ListValue, "xray_dns_mode", translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") @@ -363,6 +365,8 @@ o.write = function(self, section, value) end o = s:option(ListValue, "singbox_dns_mode", translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") @@ -387,53 +391,53 @@ o:value("149.112.112.112", "149.112.112.112 (Quad9-Recommended)") o:value("208.67.220.220", "208.67.220.220 (OpenDNS)") o:value("208.67.222.222", "208.67.222.222 (OpenDNS)") o:depends({dns_mode = "dns2socks"}) +o:depends({xray_dns_mode = "udp"}) o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "tcp"}) -if has_singbox or has_xray then - o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH")) - o:value("https://1.1.1.1/dns-query", "CloudFlare") - o:value("https://1.1.1.2/dns-query", "CloudFlare-Security") - o:value("https://8.8.4.4/dns-query", "Google 8844") - o:value("https://8.8.8.8/dns-query", "Google 8888") - o:value("https://9.9.9.9/dns-query", "Quad9-Recommended 9.9.9.9") - o:value("https://149.112.112.112/dns-query", "Quad9-Recommended 149.112.112.112") - o:value("https://208.67.222.222/dns-query", "OpenDNS") - o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard") - o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS") - o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)") - o.default = "https://1.1.1.1/dns-query" - o.validate = function(self, value, t) - if value ~= "" then - value = api.trim(value) - local flag = 0 - local util = require "luci.util" - local val = util.split(value, ",") - local url = val[1] - val[1] = nil - for i = 1, #val do - local v = val[i] - if v then - if not api.datatypes.ipmask4(v) then - flag = 1 - end +o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH")) +o:value("https://1.1.1.1/dns-query", "1.1.1.1 (CloudFlare)") +o:value("https://1.1.1.2/dns-query", "1.1.1.2 (CloudFlare-Security)") +o:value("https://8.8.4.4/dns-query", "8.8.4.4 (Google)") +o:value("https://8.8.8.8/dns-query", "8.8.8.8 (Google)") +o:value("https://9.9.9.9/dns-query", "9.9.9.9 (Quad9)") +o:value("https://149.112.112.112/dns-query", "149.112.112.112 (Quad9)") +o:value("https://208.67.222.222/dns-query", "208.67.222.222 (OpenDNS)") +o:value("https://dns.adguard.com/dns-query,94.140.14.14", "94.140.14.14 (AdGuard)") +o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "116.202.176.26 (LibreDNS)") +o:value("https://doh.libredns.gr/ads,116.202.176.26", "116.202.176.26 (LibreDNS-NoAds)") +o.default = "https://1.1.1.1/dns-query" +o.validate = function(self, value, t) + if value ~= "" then + value = api.trim(value) + local flag = 0 + local util = require "luci.util" + local val = util.split(value, ",") + local url = val[1] + val[1] = nil + for i = 1, #val do + local v = val[i] + if v then + if not api.datatypes.ipmask4(v) then + flag = 1 end end - if flag == 0 then - return value - end end - return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" + if flag == 0 then + return value + end end - o:depends({xray_dns_mode = "tcp+doh"}) - o:depends({singbox_dns_mode = "doh"}) - - o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) - o.datatype = "ipaddr" - o:depends({dns_mode = "sing-box"}) - o:depends({dns_mode = "xray"}) + return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" end +o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "doh"}) + +o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) +o.datatype = "ipaddr" +o:depends({dns_mode = "sing-box"}) +o:depends({dns_mode = "xray"}) o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o.default = "none" diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index c0228939ae..0306f01f4f 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -432,6 +432,8 @@ if api.is_finded("smartdns") then end o = s:taboption("DNS", ListValue, "xray_dns_mode", translate("Remote DNS") .. " " .. translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") @@ -446,6 +448,8 @@ o.write = function(self, section, value) end o = s:taboption("DNS", ListValue, "singbox_dns_mode", translate("Remote DNS") .. " " .. translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") @@ -485,8 +489,10 @@ o:value("208.67.222.222", "208.67.222.222 (OpenDNS)") o:depends({dns_mode = "dns2socks"}) o:depends({dns_mode = "tcp"}) o:depends({dns_mode = "udp"}) +o:depends({xray_dns_mode = "udp"}) o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "tcp"}) ---- DoH diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua index ed69645788..6ea08be46d 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1544,8 +1544,7 @@ function gen_config(var) } if remote_dns_udp_server then - local server_port = tonumber(remote_dns_port) or 53 - remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port + remote_server.address = remote_dns_udp_server end if remote_dns_tcp_server then @@ -1597,9 +1596,9 @@ function gen_config(var) if remote_dns_udp_server then local server_port = tonumber(remote_dns_port) or 53 remote_server.type = "udp" - remote_server.server = remote_dns_udp_server + remote_server.server = remote_dns_server remote_server.server_port = server_port - tmp_address = remote_dns_udp_server + tmp_address = remote_dns_server end if remote_dns_tcp_server then diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua index 22b18b1d74..a55d3a8f42 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -580,6 +580,8 @@ function gen_config(var) local direct_dns_udp_server = var["-direct_dns_udp_server"] local direct_dns_tcp_server = var["-direct_dns_tcp_server"] local direct_dns_query_strategy = var["-direct_dns_query_strategy"] + local remote_dns_udp_server = var["-remote_dns_udp_server"] + local remote_dns_udp_port = var["-remote_dns_udp_port"] local remote_dns_tcp_server = var["-remote_dns_tcp_server"] local remote_dns_tcp_port = var["-remote_dns_tcp_port"] local remote_dns_doh_url = var["-remote_dns_doh_url"] @@ -1175,7 +1177,7 @@ function gen_config(var) end end - if remote_dns_tcp_server and remote_dns_tcp_port then + if (remote_dns_udp_server and remote_dns_udp_port) or (remote_dns_tcp_server and remote_dns_tcp_port) then if not routing then routing = { domainStrategy = "IPOnDemand", @@ -1230,8 +1232,13 @@ function gen_config(var) local _remote_dns = { --tag = "dns-global-remote", queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4", - address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53 } + if remote_dns_udp_server then + _remote_dns.address = remote_dns_udp_server + _remote_dns.port = tonumber(remote_dns_udp_port) or 53 + else + address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53 + end local _remote_dns_host if remote_dns_doh_url and remote_dns_doh_host then @@ -1309,8 +1316,8 @@ function gen_config(var) protocol = "dokodemo-door", tag = "dns-in", settings = { - address = remote_dns_tcp_server, - port = tonumber(remote_dns_tcp_port), + address = remote_dns_udp_server or remote_dns_tcp_server, + port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port), network = "tcp,udp" } }) @@ -1322,9 +1329,9 @@ function gen_config(var) tag = dns_outbound_tag } or nil, settings = { - address = remote_dns_tcp_server, - port = tonumber(remote_dns_tcp_port), - network = "tcp", + address = remote_dns_udp_server or remote_dns_tcp_server, + port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port), + network = remote_dns_udp_server and "udp" or "tcp", nonIPQuery = "drop" } }) diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh index 0d863e9403..eecfeb0461 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh @@ -486,11 +486,12 @@ run_singbox() { [ -n "$remote_dns_query_strategy" ] && _extra_param="${_extra_param} -remote_dns_query_strategy ${remote_dns_query_strategy}" case "$remote_dns_protocol" in - tcp) - local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') + udp|tcp) + local _proto="$remote_dns_protocol" + local _dns=$(get_first_dns remote_dns_${_proto}_server 53 | sed 's/#/:/g') local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') - _extra_param="${_extra_param} -remote_dns_server ${_dns_address} -remote_dns_port ${_dns_port} -remote_dns_tcp_server tcp://${_dns}" + _extra_param="${_extra_param} -remote_dns_server ${_dns_address} -remote_dns_port ${_dns_port} -remote_dns_${_proto}_server ${_proto}://${_dns}" ;; doh) local _doh_url _doh_host _doh_port _doh_bootstrap @@ -508,7 +509,7 @@ run_singbox() { run_xray() { local flag type node tcp_redir_port tcp_proxy_way udp_redir_port socks_address socks_port socks_username socks_password http_address http_port http_username http_password - local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port + local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port local loglevel log_file config_file server_host server_port no_run local _extra_param="" eval_set_val $@ @@ -557,18 +558,27 @@ run_xray() { [ -n "$remote_dns_client_ip" ] && _extra_param="${_extra_param} -remote_dns_client_ip ${remote_dns_client_ip}" [ "$remote_fakedns" = "1" ] && _extra_param="${_extra_param} -remote_dns_fake 1" [ -n "$dns_cache" ] && _extra_param="${_extra_param} -dns_cache ${dns_cache}" - [ -n "${remote_dns_tcp_server}" ] && { - local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') - local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') - local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') - _extra_param="${_extra_param} -remote_dns_tcp_server ${_dns_address} -remote_dns_tcp_port ${_dns_port}" - } - [ -n "${remote_dns_doh}" ] && { - local _doh_url _doh_host _doh_port _doh_bootstrap - parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap - [ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_doh_ip ${_doh_bootstrap}" - _extra_param="${_extra_param} -remote_dns_doh_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}" - } + + case "$remote_dns_protocol" in + udp) + local _dns=$(get_first_dns remote_dns_udp_server 53 | sed 's/#/:/g') + local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') + local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') + _extra_param="${_extra_param} -remote_dns_udp_server ${_dns_address} -remote_dns_udp_port ${_dns_port}" + ;; + tcp|tcp+doh) + local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') + local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') + local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') + _extra_param="${_extra_param} -remote_dns_tcp_server ${_dns_address} -remote_dns_tcp_port ${_dns_port}" + [ "$remote_dns_protocol" = "tcp+doh" ] && { + local _doh_url _doh_host _doh_port _doh_bootstrap + parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap + [ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_doh_ip ${_doh_bootstrap}" + _extra_param="${_extra_param} -remote_dns_doh_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}" + } + ;; + esac _extra_param="${_extra_param} -loglevel $loglevel" [ -n "$no_run" ] && _extra_param="${_extra_param} -no_run 1" lua $UTIL_XRAY gen_config ${_extra_param} > $config_file @@ -963,9 +973,10 @@ run_redir() { _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in - tcp) - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" + udp|tcp) + local _proto="$v2ray_dns_mode" + _args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}" + resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${_proto}://${REMOTE_DNS}" ;; doh) remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") @@ -1041,14 +1052,23 @@ run_redir() { ;; esac - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - if [ "$v2ray_dns_mode" = "tcp+doh" ]; then - remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") - _args="${_args} remote_dns_doh=${remote_dns_doh}" - resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" - else - resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" - fi + _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" + case "$v2ray_dns_mode" in + udp) + _args="${_args} remote_dns_udp_server=${REMOTE_DNS}" + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> udp://${REMOTE_DNS}" + ;; + tcp|tcp+doh) + _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" + if [ "$v2ray_dns_mode" = "tcp+doh" ]; then + remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") + _args="${_args} remote_dns_doh=${remote_dns_doh}" + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" + else + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" + fi + ;; + esac local remote_fakedns=$(config_t_get global remote_fakedns 0) [ "${remote_fakedns}" = "1" ] && { fakedns=1 @@ -1498,9 +1518,10 @@ start_dns() { _args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}" _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in - tcp) - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - echolog " - Sing-Box DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" + udp|tcp) + local _proto="$v2ray_dns_mode" + _args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}" + echolog " - Sing-Box DNS(${TUN_DNS}) -> ${_proto}://${REMOTE_DNS}" ;; doh) remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") @@ -1531,19 +1552,27 @@ start_dns() { [ -n "${_remote_dns_client_ip}" ] && _args="${_args} remote_dns_client_ip=${_remote_dns_client_ip}" TCP_PROXY_DNS=1 _args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}" - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) - if [ "$v2ray_dns_mode" = "tcp+doh" ]; then - remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") - _args="${_args} remote_dns_doh=${remote_dns_doh}" - echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" + case "$v2ray_dns_mode" in + udp) + _args="${_args} remote_dns_udp_server=${REMOTE_DNS}" + echolog " - Xray DNS(${TUN_DNS}) -> udp://${REMOTE_DNS}" + ;; + tcp|tcp+doh) + _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + if [ "$v2ray_dns_mode" = "tcp+doh" ]; then + remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") + _args="${_args} remote_dns_doh=${remote_dns_doh}" + echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" - local _doh_url _doh_host _doh_port _doh_bootstrap - parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap - [ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}" - else - echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" - fi + local _doh_url _doh_host _doh_port _doh_bootstrap + parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap + [ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}" + else + echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" + fi + ;; + esac _args="${_args} dns_socks_address=127.0.0.1 dns_socks_port=${tcp_node_socks_port}" run_xray ${_args} } @@ -1849,7 +1878,7 @@ acl_app() { dnsmasq_filter_proxy_ipv6=0 remote_dns_query_strategy="UseIP" [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4" - run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file + run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file fi set_cache_var "node_${tcp_node}_$(echo -n "${remote_dns}" | md5sum | cut -d " " -f1)" "${_dns_port}" } @@ -1944,7 +1973,7 @@ acl_app() { remote_dns_query_strategy="UseIP" [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4" [ "$dns_mode" = "xray" ] && [ "$v2ray_dns_mode" = "tcp+doh" ] && remote_dns_doh=${remote_dns_doh:-https://1.1.1.1/dns-query} - _extra_param="dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_tcp_server=${remote_dns} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip}" + _extra_param="dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip}" fi [ -n "$udp_node" ] && ([ "$udp_node" = "tcp" ] || [ "$udp_node" = "$tcp_node" ]) && { config_file="${config_file//TCP_/TCP_UDP_}" diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua index 48b0651917..4dfd6d77ae 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua @@ -338,7 +338,7 @@ o.remove = function(self, section) local new_val = (v.type == "Xray") and "xray" or "sing-box" m:set(section, self.option, new_val) - local dns_field = s.fields[new_val .. "_dns_mode"] + local dns_field = s.fields[v.type == "Xray" and "xray_dns_mode" or "singbox_dns_mode"] local v2ray_dns_mode = dns_field and dns_field:formvalue(section) if v2ray_dns_mode then m:set(section, "v2ray_dns_mode", v2ray_dns_mode) @@ -350,6 +350,8 @@ o.remove = function(self, section) end o = s:option(ListValue, "xray_dns_mode", translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") @@ -363,6 +365,8 @@ o.write = function(self, section, value) end o = s:option(ListValue, "singbox_dns_mode", translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") @@ -387,53 +391,53 @@ o:value("149.112.112.112", "149.112.112.112 (Quad9-Recommended)") o:value("208.67.220.220", "208.67.220.220 (OpenDNS)") o:value("208.67.222.222", "208.67.222.222 (OpenDNS)") o:depends({dns_mode = "dns2socks"}) +o:depends({xray_dns_mode = "udp"}) o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "tcp"}) -if has_singbox or has_xray then - o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH")) - o:value("https://1.1.1.1/dns-query", "CloudFlare") - o:value("https://1.1.1.2/dns-query", "CloudFlare-Security") - o:value("https://8.8.4.4/dns-query", "Google 8844") - o:value("https://8.8.8.8/dns-query", "Google 8888") - o:value("https://9.9.9.9/dns-query", "Quad9-Recommended 9.9.9.9") - o:value("https://149.112.112.112/dns-query", "Quad9-Recommended 149.112.112.112") - o:value("https://208.67.222.222/dns-query", "OpenDNS") - o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard") - o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS") - o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)") - o.default = "https://1.1.1.1/dns-query" - o.validate = function(self, value, t) - if value ~= "" then - value = api.trim(value) - local flag = 0 - local util = require "luci.util" - local val = util.split(value, ",") - local url = val[1] - val[1] = nil - for i = 1, #val do - local v = val[i] - if v then - if not api.datatypes.ipmask4(v) then - flag = 1 - end +o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH")) +o:value("https://1.1.1.1/dns-query", "1.1.1.1 (CloudFlare)") +o:value("https://1.1.1.2/dns-query", "1.1.1.2 (CloudFlare-Security)") +o:value("https://8.8.4.4/dns-query", "8.8.4.4 (Google)") +o:value("https://8.8.8.8/dns-query", "8.8.8.8 (Google)") +o:value("https://9.9.9.9/dns-query", "9.9.9.9 (Quad9)") +o:value("https://149.112.112.112/dns-query", "149.112.112.112 (Quad9)") +o:value("https://208.67.222.222/dns-query", "208.67.222.222 (OpenDNS)") +o:value("https://dns.adguard.com/dns-query,94.140.14.14", "94.140.14.14 (AdGuard)") +o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "116.202.176.26 (LibreDNS)") +o:value("https://doh.libredns.gr/ads,116.202.176.26", "116.202.176.26 (LibreDNS-NoAds)") +o.default = "https://1.1.1.1/dns-query" +o.validate = function(self, value, t) + if value ~= "" then + value = api.trim(value) + local flag = 0 + local util = require "luci.util" + local val = util.split(value, ",") + local url = val[1] + val[1] = nil + for i = 1, #val do + local v = val[i] + if v then + if not api.datatypes.ipmask4(v) then + flag = 1 end end - if flag == 0 then - return value - end end - return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" + if flag == 0 then + return value + end end - o:depends({xray_dns_mode = "tcp+doh"}) - o:depends({singbox_dns_mode = "doh"}) - - o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) - o.datatype = "ipaddr" - o:depends({dns_mode = "sing-box"}) - o:depends({dns_mode = "xray"}) + return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" end +o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "doh"}) + +o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) +o.datatype = "ipaddr" +o:depends({dns_mode = "sing-box"}) +o:depends({dns_mode = "xray"}) o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o.default = "none" diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index c0228939ae..0306f01f4f 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -432,6 +432,8 @@ if api.is_finded("smartdns") then end o = s:taboption("DNS", ListValue, "xray_dns_mode", translate("Remote DNS") .. " " .. translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") @@ -446,6 +448,8 @@ o.write = function(self, section, value) end o = s:taboption("DNS", ListValue, "singbox_dns_mode", translate("Remote DNS") .. " " .. translate("Request protocol")) +o.default = "tcp" +o:value("udp", "UDP") o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") @@ -485,8 +489,10 @@ o:value("208.67.222.222", "208.67.222.222 (OpenDNS)") o:depends({dns_mode = "dns2socks"}) o:depends({dns_mode = "tcp"}) o:depends({dns_mode = "udp"}) +o:depends({xray_dns_mode = "udp"}) o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp+doh"}) +o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "tcp"}) ---- DoH diff --git a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua index ed69645788..6ea08be46d 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1544,8 +1544,7 @@ function gen_config(var) } if remote_dns_udp_server then - local server_port = tonumber(remote_dns_port) or 53 - remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port + remote_server.address = remote_dns_udp_server end if remote_dns_tcp_server then @@ -1597,9 +1596,9 @@ function gen_config(var) if remote_dns_udp_server then local server_port = tonumber(remote_dns_port) or 53 remote_server.type = "udp" - remote_server.server = remote_dns_udp_server + remote_server.server = remote_dns_server remote_server.server_port = server_port - tmp_address = remote_dns_udp_server + tmp_address = remote_dns_server end if remote_dns_tcp_server then diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua index 22b18b1d74..a55d3a8f42 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -580,6 +580,8 @@ function gen_config(var) local direct_dns_udp_server = var["-direct_dns_udp_server"] local direct_dns_tcp_server = var["-direct_dns_tcp_server"] local direct_dns_query_strategy = var["-direct_dns_query_strategy"] + local remote_dns_udp_server = var["-remote_dns_udp_server"] + local remote_dns_udp_port = var["-remote_dns_udp_port"] local remote_dns_tcp_server = var["-remote_dns_tcp_server"] local remote_dns_tcp_port = var["-remote_dns_tcp_port"] local remote_dns_doh_url = var["-remote_dns_doh_url"] @@ -1175,7 +1177,7 @@ function gen_config(var) end end - if remote_dns_tcp_server and remote_dns_tcp_port then + if (remote_dns_udp_server and remote_dns_udp_port) or (remote_dns_tcp_server and remote_dns_tcp_port) then if not routing then routing = { domainStrategy = "IPOnDemand", @@ -1230,8 +1232,13 @@ function gen_config(var) local _remote_dns = { --tag = "dns-global-remote", queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4", - address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53 } + if remote_dns_udp_server then + _remote_dns.address = remote_dns_udp_server + _remote_dns.port = tonumber(remote_dns_udp_port) or 53 + else + address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53 + end local _remote_dns_host if remote_dns_doh_url and remote_dns_doh_host then @@ -1309,8 +1316,8 @@ function gen_config(var) protocol = "dokodemo-door", tag = "dns-in", settings = { - address = remote_dns_tcp_server, - port = tonumber(remote_dns_tcp_port), + address = remote_dns_udp_server or remote_dns_tcp_server, + port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port), network = "tcp,udp" } }) @@ -1322,9 +1329,9 @@ function gen_config(var) tag = dns_outbound_tag } or nil, settings = { - address = remote_dns_tcp_server, - port = tonumber(remote_dns_tcp_port), - network = "tcp", + address = remote_dns_udp_server or remote_dns_tcp_server, + port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port), + network = remote_dns_udp_server and "udp" or "tcp", nonIPQuery = "drop" } }) diff --git a/small/luci-app-passwall/root/usr/share/passwall/app.sh b/small/luci-app-passwall/root/usr/share/passwall/app.sh index 0d863e9403..eecfeb0461 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/app.sh @@ -486,11 +486,12 @@ run_singbox() { [ -n "$remote_dns_query_strategy" ] && _extra_param="${_extra_param} -remote_dns_query_strategy ${remote_dns_query_strategy}" case "$remote_dns_protocol" in - tcp) - local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') + udp|tcp) + local _proto="$remote_dns_protocol" + local _dns=$(get_first_dns remote_dns_${_proto}_server 53 | sed 's/#/:/g') local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') - _extra_param="${_extra_param} -remote_dns_server ${_dns_address} -remote_dns_port ${_dns_port} -remote_dns_tcp_server tcp://${_dns}" + _extra_param="${_extra_param} -remote_dns_server ${_dns_address} -remote_dns_port ${_dns_port} -remote_dns_${_proto}_server ${_proto}://${_dns}" ;; doh) local _doh_url _doh_host _doh_port _doh_bootstrap @@ -508,7 +509,7 @@ run_singbox() { run_xray() { local flag type node tcp_redir_port tcp_proxy_way udp_redir_port socks_address socks_port socks_username socks_password http_address http_port http_username http_password - local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port + local dns_listen_port direct_dns_query_strategy direct_dns_port direct_dns_udp_server direct_dns_tcp_server remote_dns_protocol remote_dns_udp_server remote_dns_tcp_server remote_dns_doh remote_dns_client_ip remote_fakedns remote_dns_query_strategy dns_cache dns_socks_address dns_socks_port local loglevel log_file config_file server_host server_port no_run local _extra_param="" eval_set_val $@ @@ -557,18 +558,27 @@ run_xray() { [ -n "$remote_dns_client_ip" ] && _extra_param="${_extra_param} -remote_dns_client_ip ${remote_dns_client_ip}" [ "$remote_fakedns" = "1" ] && _extra_param="${_extra_param} -remote_dns_fake 1" [ -n "$dns_cache" ] && _extra_param="${_extra_param} -dns_cache ${dns_cache}" - [ -n "${remote_dns_tcp_server}" ] && { - local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') - local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') - local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') - _extra_param="${_extra_param} -remote_dns_tcp_server ${_dns_address} -remote_dns_tcp_port ${_dns_port}" - } - [ -n "${remote_dns_doh}" ] && { - local _doh_url _doh_host _doh_port _doh_bootstrap - parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap - [ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_doh_ip ${_doh_bootstrap}" - _extra_param="${_extra_param} -remote_dns_doh_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}" - } + + case "$remote_dns_protocol" in + udp) + local _dns=$(get_first_dns remote_dns_udp_server 53 | sed 's/#/:/g') + local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') + local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') + _extra_param="${_extra_param} -remote_dns_udp_server ${_dns_address} -remote_dns_udp_port ${_dns_port}" + ;; + tcp|tcp+doh) + local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g') + local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}') + local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}') + _extra_param="${_extra_param} -remote_dns_tcp_server ${_dns_address} -remote_dns_tcp_port ${_dns_port}" + [ "$remote_dns_protocol" = "tcp+doh" ] && { + local _doh_url _doh_host _doh_port _doh_bootstrap + parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap + [ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_doh_ip ${_doh_bootstrap}" + _extra_param="${_extra_param} -remote_dns_doh_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}" + } + ;; + esac _extra_param="${_extra_param} -loglevel $loglevel" [ -n "$no_run" ] && _extra_param="${_extra_param} -no_run 1" lua $UTIL_XRAY gen_config ${_extra_param} > $config_file @@ -963,9 +973,10 @@ run_redir() { _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in - tcp) - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" + udp|tcp) + local _proto="$v2ray_dns_mode" + _args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}" + resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${_proto}://${REMOTE_DNS}" ;; doh) remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") @@ -1041,14 +1052,23 @@ run_redir() { ;; esac - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - if [ "$v2ray_dns_mode" = "tcp+doh" ]; then - remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") - _args="${_args} remote_dns_doh=${remote_dns_doh}" - resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" - else - resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" - fi + _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" + case "$v2ray_dns_mode" in + udp) + _args="${_args} remote_dns_udp_server=${REMOTE_DNS}" + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> udp://${REMOTE_DNS}" + ;; + tcp|tcp+doh) + _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" + if [ "$v2ray_dns_mode" = "tcp+doh" ]; then + remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") + _args="${_args} remote_dns_doh=${remote_dns_doh}" + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" + else + resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}" + fi + ;; + esac local remote_fakedns=$(config_t_get global remote_fakedns 0) [ "${remote_fakedns}" = "1" ] && { fakedns=1 @@ -1498,9 +1518,10 @@ start_dns() { _args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}" _args="${_args} remote_dns_protocol=${v2ray_dns_mode}" case "$v2ray_dns_mode" in - tcp) - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - echolog " - Sing-Box DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" + udp|tcp) + local _proto="$v2ray_dns_mode" + _args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}" + echolog " - Sing-Box DNS(${TUN_DNS}) -> ${_proto}://${REMOTE_DNS}" ;; doh) remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") @@ -1531,19 +1552,27 @@ start_dns() { [ -n "${_remote_dns_client_ip}" ] && _args="${_args} remote_dns_client_ip=${_remote_dns_client_ip}" TCP_PROXY_DNS=1 _args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}" - _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" - local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) - if [ "$v2ray_dns_mode" = "tcp+doh" ]; then - remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") - _args="${_args} remote_dns_doh=${remote_dns_doh}" - echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" + case "$v2ray_dns_mode" in + udp) + _args="${_args} remote_dns_udp_server=${REMOTE_DNS}" + echolog " - Xray DNS(${TUN_DNS}) -> udp://${REMOTE_DNS}" + ;; + tcp|tcp+doh) + _args="${_args} remote_dns_tcp_server=${REMOTE_DNS}" + local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp) + if [ "$v2ray_dns_mode" = "tcp+doh" ]; then + remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") + _args="${_args} remote_dns_doh=${remote_dns_doh}" + echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}" - local _doh_url _doh_host _doh_port _doh_bootstrap - parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap - [ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}" - else - echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" - fi + local _doh_url _doh_host _doh_port _doh_bootstrap + parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap + [ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}" + else + echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}" + fi + ;; + esac _args="${_args} dns_socks_address=127.0.0.1 dns_socks_port=${tcp_node_socks_port}" run_xray ${_args} } @@ -1849,7 +1878,7 @@ acl_app() { dnsmasq_filter_proxy_ipv6=0 remote_dns_query_strategy="UseIP" [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4" - run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file + run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file fi set_cache_var "node_${tcp_node}_$(echo -n "${remote_dns}" | md5sum | cut -d " " -f1)" "${_dns_port}" } @@ -1944,7 +1973,7 @@ acl_app() { remote_dns_query_strategy="UseIP" [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4" [ "$dns_mode" = "xray" ] && [ "$v2ray_dns_mode" = "tcp+doh" ] && remote_dns_doh=${remote_dns_doh:-https://1.1.1.1/dns-query} - _extra_param="dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_tcp_server=${remote_dns} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip}" + _extra_param="dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip}" fi [ -n "$udp_node" ] && ([ "$udp_node" = "tcp" ] || [ "$udp_node" = "$tcp_node" ]) && { config_file="${config_file//TCP_/TCP_UDP_}" diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 8051665240..aedca9f4a6 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -30,13 +30,13 @@ define Download/geosite HASH:=c41cd987bdd3dda6df67bbf15790713c833e59c619e289e7673bcec178db90c0 endef -GEOSITE_IRAN_VER:=202511100042 +GEOSITE_IRAN_VER:=202511170041 GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER) define Download/geosite-ir URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/ URL_FILE:=iran.dat FILE:=$(GEOSITE_IRAN_FILE) - HASH:=ca18f8e1676f6df20a1fcf5338e033a9e7d6aae7494b13d35e27f2d45bf02f50 + HASH:=aa94b6efc99660838f85d700bbfea38d71321eeb1b3cc6cf92ade1db775d9e25 endef define Package/v2ray-geodata/template diff --git a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index 481e183b42..2f31e827bf 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -4,7 +4,7 @@ namespace ServiceLib.Handler.Fmt; public class BaseFmt { - private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure", "verify" }; + private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" }; protected static string GetIpv6(string address) { diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt index 14c6fd6cd9..bbd2989cb4 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt @@ -73,7 +73,7 @@ open class FmtBase { config.security = null } // Support multiple possible query keys for allowInsecure like the C# implementation - val allowInsecureKeys = arrayOf("insecure", "allowInsecure", "allow_insecure", "verify") + val allowInsecureKeys = arrayOf("insecure", "allowInsecure", "allow_insecure") config.insecure = when { allowInsecureKeys.any { queryParam[it] == "1" } -> true allowInsecureKeys.any { queryParam[it] == "0" } -> false diff --git a/yt-dlp/test/test_networking.py b/yt-dlp/test/test_networking.py index 631e7458e6..f78a8d5770 100644 --- a/yt-dlp/test/test_networking.py +++ b/yt-dlp/test/test_networking.py @@ -755,6 +755,17 @@ class TestHTTPRequestHandler(TestRequestHandlerBase): assert res.read(0) == b'' assert res.read() == b'