mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Mon Nov 17 19:42:26 CET 2025
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TestEnvCollisions ensures that there are no collisions in the produced environment
|
||||
// variable names for all commands and their flags.
|
||||
func TestEnvCollisions(t *testing.T) {
|
||||
testEnvCollisions(t, rootCmd)
|
||||
}
|
||||
|
||||
func testEnvCollisions(t *testing.T, cmd *cobra.Command) {
|
||||
for _, cmd := range cmd.Commands() {
|
||||
testEnvCollisions(t, cmd)
|
||||
}
|
||||
|
||||
replacements := generateEnvKeyReplacements(cmd)
|
||||
envVariables := []string{}
|
||||
|
||||
for i := range replacements {
|
||||
if i%2 != 0 {
|
||||
envVariables = append(envVariables, replacements[i])
|
||||
}
|
||||
}
|
||||
|
||||
duplicates := lo.FindDuplicates(envVariables)
|
||||
|
||||
if len(duplicates) > 0 {
|
||||
t.Errorf("Found duplicate environment variable keys for command %q: %v", cmd.Name(), duplicates)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ var cmdsLsCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evt, err := getString(cmd.Flags(), "event")
|
||||
|
||||
evt, err := cmd.Flags().GetString("event")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -32,6 +33,7 @@ var cmdsLsCmd = &cobra.Command{
|
||||
show["after_"+evt] = s.Commands["after_"+evt]
|
||||
printEvents(show)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
+143
-14
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+11
-152
@@ -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}),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+141
-198
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +69,12 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
||||
}
|
||||
|
||||
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
||||
id, err := getUint(flags, "id")
|
||||
id, err := flags.GetUint("id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username, err := getString(flags, "username")
|
||||
|
||||
username, err := flags.GetString("username")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -22,14 +22,18 @@ var rulesAddCmd = &cobra.Command{
|
||||
Long: `Add a global rule or user rule.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
allow, err := getBool(cmd.Flags(), "allow")
|
||||
flags := cmd.Flags()
|
||||
|
||||
allow, err := flags.GetBool("allow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regex, err := getBool(cmd.Flags(), "regex")
|
||||
|
||||
regex, err := flags.GetBool("regex")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exp := args[0]
|
||||
|
||||
if regex {
|
||||
|
||||
+26
-24
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@ var usersAddCmd = &cobra.Command{
|
||||
Long: `Create a new user and add it to the database.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
s, err := d.store.Settings.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||
err = getUserDefaults(flags, &s.Defaults, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -31,27 +32,24 @@ var usersAddCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
lockPassword, err := getBool(cmd.Flags(), "lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dateFormat, err := getBool(cmd.Flags(), "dateFormat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &users.User{
|
||||
Username: args[0],
|
||||
Password: password,
|
||||
LockPassword: lockPassword,
|
||||
DateFormat: dateFormat,
|
||||
HideDotfiles: hideDotfiles,
|
||||
Username: args[0],
|
||||
Password: password,
|
||||
}
|
||||
|
||||
user.LockPassword, err = flags.GetBool("lockPassword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.DateFormat, err = flags.GetBool("dateFormat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.HideDotfiles, err = flags.GetBool("hideDotfiles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Defaults.Apply(user)
|
||||
|
||||
@@ -26,6 +26,7 @@ installation. For that, just don't place their ID on the files
|
||||
list or set it to 0.`,
|
||||
Args: jsonYamlArg,
|
||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
||||
flags := cmd.Flags()
|
||||
fd, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -45,7 +46,7 @@ list or set it to 0.`,
|
||||
}
|
||||
}
|
||||
|
||||
replace, err := getBool(cmd.Flags(), "replace")
|
||||
replace, err := flags.GetBool("replace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,7 +70,7 @@ list or set it to 0.`,
|
||||
}
|
||||
}
|
||||
|
||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
||||
overwrite, err := flags.GetBool("overwrite")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+111
-39
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"log": "stdout",
|
||||
"database": "/database/filebrowser.db",
|
||||
"root": "/srv"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`.
|
||||
@@ -100,6 +100,7 @@ nav:
|
||||
- customization.md
|
||||
- authentication.md
|
||||
- command-execution.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
- Deployment: deployment.md
|
||||
- Command Line Usage:
|
||||
- cli/filebrowser.md
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
From 9989fcd49c52500a2bf1f6d49411690dec45d2dc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@iki.fi>
|
||||
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 <shi05275@163.com>.
|
||||
|
||||
Link: https://lore.kernel.org/all/20231220221724.3822-4-ansuelsmth@gmail.com/
|
||||
Signed-off-by: Marko Mäkelä <marko.makela@iki.fi>
|
||||
Tested-by: Marko Mäkelä <marko.makela@iki.fi>
|
||||
---
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
],
|
||||
"activeProfile": "default",
|
||||
"rpcPort": 8989,
|
||||
"socks5Port": 1082,
|
||||
"socks5Port": 1085,
|
||||
"advancedSettings": {
|
||||
"noCheckUpdate": true
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
],
|
||||
"activeProfile": "default",
|
||||
"rpcPort": 8989,
|
||||
"socks5Port": 1082,
|
||||
"socks5Port": 1085,
|
||||
"advancedSettings": {
|
||||
"noCheckUpdate": true
|
||||
},
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+85
-40
@@ -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 =========="
|
||||
@@ -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 =========="
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2020 jerryk <jerrykuku@qq.com>
|
||||
# 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))
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 [测试版内核]"
|
||||
|
||||
|
||||
+43
-39
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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_}"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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_}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -755,6 +755,17 @@ class TestHTTPRequestHandler(TestRequestHandlerBase):
|
||||
assert res.read(0) == b''
|
||||
assert res.read() == b'<video src="/vid.mp4" /></html>'
|
||||
|
||||
def test_partial_read_greater_than_response_then_full_read(self, handler):
|
||||
with handler() as rh:
|
||||
for encoding in ('', 'gzip', 'deflate'):
|
||||
res = validate_and_send(rh, Request(
|
||||
f'http://127.0.0.1:{self.http_port}/content-encoding',
|
||||
headers={'ytdl-encoding': encoding}))
|
||||
assert res.headers.get('Content-Encoding') == encoding
|
||||
assert res.read(512) == b'<html><video src="/vid.mp4" /></html>'
|
||||
assert res.read(0) == b''
|
||||
assert res.read() == b''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('handler', ['Urllib', 'Requests', 'CurlCFFI'], indirect=True)
|
||||
@pytest.mark.handler_flaky('CurlCFFI', reason='segfaults')
|
||||
@@ -920,6 +931,28 @@ class TestUrllibRequestHandler(TestRequestHandlerBase):
|
||||
assert res.fp.fp is None
|
||||
assert res.closed
|
||||
|
||||
def test_data_uri_partial_read_then_full_read(self, handler):
|
||||
with handler() as rh:
|
||||
res = validate_and_send(rh, Request('data:text/plain,hello%20world'))
|
||||
assert res.read(6) == b'hello '
|
||||
assert res.read(0) == b''
|
||||
assert res.read() == b'world'
|
||||
# Should automatically close the underlying file object
|
||||
assert res.fp.closed
|
||||
assert res.closed
|
||||
|
||||
def test_data_uri_partial_read_greater_than_response_then_full_read(self, handler):
|
||||
with handler() as rh:
|
||||
res = validate_and_send(rh, Request('data:text/plain,hello%20world'))
|
||||
assert res.read(512) == b'hello world'
|
||||
# Response and its underlying file object should already be closed now
|
||||
assert res.fp.closed
|
||||
assert res.closed
|
||||
assert res.read(0) == b''
|
||||
assert res.read() == b''
|
||||
assert res.fp.closed
|
||||
assert res.closed
|
||||
|
||||
def test_http_error_returns_content(self, handler):
|
||||
# urllib HTTPError will try close the underlying response if reference to the HTTPError object is lost
|
||||
def get_response():
|
||||
|
||||
@@ -40,7 +40,7 @@ TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
pytestmark = pytest.mark.handler_flaky(
|
||||
'Websockets',
|
||||
os.name != 'nt' and sys.implementation.name == 'pypy',
|
||||
os.name == 'nt' or sys.implementation.name == 'pypy',
|
||||
reason='segfaults',
|
||||
)
|
||||
|
||||
|
||||
@@ -691,6 +691,10 @@ from .frontendmasters import (
|
||||
FrontendMastersIE,
|
||||
FrontendMastersLessonIE,
|
||||
)
|
||||
from .frontro import (
|
||||
TheChosenGroupIE,
|
||||
TheChosenIE,
|
||||
)
|
||||
from .fujitv import FujiTVFODPlus7IE
|
||||
from .funk import FunkIE
|
||||
from .funker530 import Funker530IE
|
||||
@@ -1094,7 +1098,10 @@ from .markiza import (
|
||||
from .massengeschmacktv import MassengeschmackTVIE
|
||||
from .masters import MastersIE
|
||||
from .matchtv import MatchTVIE
|
||||
from .mave import MaveIE
|
||||
from .mave import (
|
||||
MaveChannelIE,
|
||||
MaveIE,
|
||||
)
|
||||
from .mbn import MBNIE
|
||||
from .mdr import MDRIE
|
||||
from .medaltv import MedalTVIE
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none, parse_iso8601, url_or_none
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class FrontoBaseIE(InfoExtractor):
|
||||
def _get_auth_headers(self, url):
|
||||
return traverse_obj(self._get_cookies(url), {
|
||||
'authorization': ('frAccessToken', 'value', {lambda token: f'Bearer {token}' if token else None}),
|
||||
})
|
||||
|
||||
|
||||
class FrontroVideoBaseIE(FrontoBaseIE):
|
||||
_CHANNEL_ID = None
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
metadata = self._download_json(
|
||||
'https://api.frontrow.cc/query', video_id, data=json.dumps({
|
||||
'operationName': 'Video',
|
||||
'variables': {'channelID': self._CHANNEL_ID, 'videoID': video_id},
|
||||
'query': '''query Video($channelID: ID!, $videoID: ID!) {
|
||||
video(ChannelID: $channelID, VideoID: $videoID) {
|
||||
... on Video {title description updatedAt thumbnail createdAt duration likeCount comments views url hasAccess}
|
||||
}
|
||||
}''',
|
||||
}).encode(), headers={
|
||||
'content-type': 'application/json',
|
||||
**self._get_auth_headers(url),
|
||||
})['data']['video']
|
||||
if not traverse_obj(metadata, 'hasAccess'):
|
||||
self.raise_login_required()
|
||||
|
||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(metadata['url'], video_id)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
**traverse_obj(metadata, {
|
||||
'title': ('title', {str}),
|
||||
'description': ('description', {str}),
|
||||
'thumbnail': ('thumbnail', {url_or_none}),
|
||||
'timestamp': ('createdAt', {parse_iso8601}),
|
||||
'modified_timestamp': ('updatedAt', {parse_iso8601}),
|
||||
'duration': ('duration', {int_or_none}),
|
||||
'like_count': ('likeCount', {int_or_none}),
|
||||
'comment_count': ('comments', {int_or_none}),
|
||||
'view_count': ('views', {int_or_none}),
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class FrontroGroupBaseIE(FrontoBaseIE):
|
||||
_CHANNEL_ID = None
|
||||
_VIDEO_EXTRACTOR = None
|
||||
_VIDEO_URL_TMPL = None
|
||||
|
||||
def _real_extract(self, url):
|
||||
group_id = self._match_id(url)
|
||||
|
||||
metadata = self._download_json(
|
||||
'https://api.frontrow.cc/query', group_id, note='Downloading playlist metadata',
|
||||
data=json.dumps({
|
||||
'operationName': 'PaginatedStaticPageContainer',
|
||||
'variables': {'channelID': self._CHANNEL_ID, 'first': 500, 'pageContainerID': group_id},
|
||||
'query': '''query PaginatedStaticPageContainer($channelID: ID!, $pageContainerID: ID!) {
|
||||
pageContainer(ChannelID: $channelID, PageContainerID: $pageContainerID) {
|
||||
... on StaticPageContainer { id title updatedAt createdAt itemRefs {edges {node {
|
||||
id contentItem { ... on ItemVideo { videoItem: item {
|
||||
id
|
||||
}}}
|
||||
}}}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
}).encode(), headers={
|
||||
'content-type': 'application/json',
|
||||
**self._get_auth_headers(url),
|
||||
})['data']['pageContainer']
|
||||
|
||||
entries = []
|
||||
for video_id in traverse_obj(metadata, (
|
||||
'itemRefs', 'edges', ..., 'node', 'contentItem', 'videoItem', 'id', {str}),
|
||||
):
|
||||
entries.append(self.url_result(
|
||||
self._VIDEO_URL_TMPL % video_id, self._VIDEO_EXTRACTOR, video_id))
|
||||
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
'id': group_id,
|
||||
'entries': entries,
|
||||
**traverse_obj(metadata, {
|
||||
'title': ('title', {str}),
|
||||
'timestamp': ('createdAt', {parse_iso8601}),
|
||||
'modified_timestamp': ('updatedAt', {parse_iso8601}),
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class TheChosenIE(FrontroVideoBaseIE):
|
||||
_CHANNEL_ID = '12884901895'
|
||||
|
||||
_VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/video/(?P<id>[0-9]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://watch.thechosen.tv/video/184683594325',
|
||||
'md5': '3f878b689588c71b38ec9943c54ff5b0',
|
||||
'info_dict': {
|
||||
'id': '184683594325',
|
||||
'ext': 'mp4',
|
||||
'title': 'Season 3 Episode 2: Two by Two',
|
||||
'description': 'md5:174c373756ecc8df46b403f4fcfbaf8c',
|
||||
'comment_count': int,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'duration': 4212,
|
||||
'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683594325/',
|
||||
'timestamp': 1698954546,
|
||||
'upload_date': '20231102',
|
||||
'modified_timestamp': int,
|
||||
'modified_date': str,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://watch.thechosen.tv/video/184683596189',
|
||||
'md5': 'd581562f9d29ce82f5b7770415334151',
|
||||
'info_dict': {
|
||||
'id': '184683596189',
|
||||
'ext': 'mp4',
|
||||
'title': 'Season 4 Episode 8: Humble',
|
||||
'description': 'md5:20a57bead43da1cf77cd5b0fe29bbc76',
|
||||
'comment_count': int,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'duration': 5092,
|
||||
'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683596189/',
|
||||
'timestamp': 1715019474,
|
||||
'upload_date': '20240506',
|
||||
'modified_timestamp': int,
|
||||
'modified_date': str,
|
||||
},
|
||||
}]
|
||||
|
||||
|
||||
class TheChosenGroupIE(FrontroGroupBaseIE):
|
||||
_CHANNEL_ID = '12884901895'
|
||||
_VIDEO_EXTRACTOR = TheChosenIE
|
||||
_VIDEO_URL_TMPL = 'https://watch.thechosen.tv/video/%s'
|
||||
|
||||
_VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/group/(?P<id>[0-9]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://watch.thechosen.tv/group/309237658592',
|
||||
'info_dict': {
|
||||
'id': '309237658592',
|
||||
'title': 'Season 3',
|
||||
'timestamp': 1746203969,
|
||||
'upload_date': '20250502',
|
||||
'modified_timestamp': int,
|
||||
'modified_date': str,
|
||||
},
|
||||
'playlist_count': 8,
|
||||
}]
|
||||
+119
-36
@@ -1,7 +1,9 @@
|
||||
import re
|
||||
import functools
|
||||
import math
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
InAdvancePagedList,
|
||||
clean_html,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
@@ -10,15 +12,64 @@ from ..utils import (
|
||||
from ..utils.traversal import require, traverse_obj
|
||||
|
||||
|
||||
class MaveIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?P<channel>[\w-]+)\.mave\.digital/(?P<id>ep-\d+)'
|
||||
class MaveBaseIE(InfoExtractor):
|
||||
_API_BASE_URL = 'https://api.mave.digital/v1/website'
|
||||
_API_BASE_STORAGE_URL = 'https://store.cloud.mts.ru/mave/'
|
||||
|
||||
def _load_channel_meta(self, channel_id, display_id):
|
||||
return traverse_obj(self._download_json(
|
||||
f'{self._API_BASE_URL}/{channel_id}/', display_id,
|
||||
note='Downloading channel metadata'), 'podcast')
|
||||
|
||||
def _load_episode_meta(self, channel_id, episode_code, display_id):
|
||||
return self._download_json(
|
||||
f'{self._API_BASE_URL}/{channel_id}/episodes/{episode_code}',
|
||||
display_id, note='Downloading episode metadata')
|
||||
|
||||
def _create_entry(self, channel_id, channel_meta, episode_meta):
|
||||
episode_code = traverse_obj(episode_meta, ('code', {int}, {require('episode code')}))
|
||||
return {
|
||||
'display_id': f'{channel_id}-{episode_code}',
|
||||
'extractor_key': MaveIE.ie_key(),
|
||||
'extractor': MaveIE.IE_NAME,
|
||||
'webpage_url': f'https://{channel_id}.mave.digital/ep-{episode_code}',
|
||||
'channel_id': channel_id,
|
||||
'channel_url': f'https://{channel_id}.mave.digital/',
|
||||
'vcodec': 'none',
|
||||
**traverse_obj(episode_meta, {
|
||||
'id': ('id', {str}),
|
||||
'url': ('audio', {urljoin(self._API_BASE_STORAGE_URL)}),
|
||||
'title': ('title', {str}),
|
||||
'description': ('description', {clean_html}),
|
||||
'thumbnail': ('image', {urljoin(self._API_BASE_STORAGE_URL)}),
|
||||
'duration': ('duration', {int_or_none}),
|
||||
'season_number': ('season', {int_or_none}),
|
||||
'episode_number': ('number', {int_or_none}),
|
||||
'view_count': ('listenings', {int_or_none}),
|
||||
'like_count': ('reactions', lambda _, v: v['type'] == 'like', 'count', {int_or_none}, any),
|
||||
'dislike_count': ('reactions', lambda _, v: v['type'] == 'dislike', 'count', {int_or_none}, any),
|
||||
'age_limit': ('is_explicit', {bool}, {lambda x: 18 if x else None}),
|
||||
'timestamp': ('publish_date', {parse_iso8601}),
|
||||
}),
|
||||
**traverse_obj(channel_meta, {
|
||||
'series_id': ('id', {str}),
|
||||
'series': ('title', {str}),
|
||||
'channel': ('title', {str}),
|
||||
'uploader': ('author', {str}),
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class MaveIE(MaveBaseIE):
|
||||
IE_NAME = 'mave'
|
||||
_VALID_URL = r'https?://(?P<channel_id>[\w-]+)\.mave\.digital/ep-(?P<episode_code>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://ochenlichnoe.mave.digital/ep-25',
|
||||
'md5': 'aa3e513ef588b4366df1520657cbc10c',
|
||||
'info_dict': {
|
||||
'id': '4035f587-914b-44b6-aa5a-d76685ad9bc2',
|
||||
'ext': 'mp3',
|
||||
'display_id': 'ochenlichnoe-ep-25',
|
||||
'display_id': 'ochenlichnoe-25',
|
||||
'title': 'Между мной и миром: психология самооценки',
|
||||
'description': 'md5:4b7463baaccb6982f326bce5c700382a',
|
||||
'uploader': 'Самарский университет',
|
||||
@@ -45,7 +96,7 @@ class MaveIE(InfoExtractor):
|
||||
'info_dict': {
|
||||
'id': '41898bb5-ff57-4797-9236-37a8e537aa21',
|
||||
'ext': 'mp3',
|
||||
'display_id': 'budem-ep-12',
|
||||
'display_id': 'budem-12',
|
||||
'title': 'Екатерина Михайлова: "Горе от ума" не про женщин написана',
|
||||
'description': 'md5:fa3bdd59ee829dfaf16e3efcb13f1d19',
|
||||
'uploader': 'Полина Цветкова+Евгения Акопова',
|
||||
@@ -68,40 +119,72 @@ class MaveIE(InfoExtractor):
|
||||
'upload_date': '20241230',
|
||||
},
|
||||
}]
|
||||
_API_BASE_URL = 'https://api.mave.digital/'
|
||||
|
||||
def _real_extract(self, url):
|
||||
channel_id, slug = self._match_valid_url(url).group('channel', 'id')
|
||||
display_id = f'{channel_id}-{slug}'
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
data = traverse_obj(
|
||||
self._search_nuxt_json(webpage, display_id),
|
||||
('data', lambda _, v: v['activeEpisodeData'], any, {require('podcast data')}))
|
||||
channel_id, episode_code = self._match_valid_url(url).group(
|
||||
'channel_id', 'episode_code')
|
||||
display_id = f'{channel_id}-{episode_code}'
|
||||
|
||||
channel_meta = self._load_channel_meta(channel_id, display_id)
|
||||
episode_meta = self._load_episode_meta(channel_id, episode_code, display_id)
|
||||
|
||||
return self._create_entry(channel_id, channel_meta, episode_meta)
|
||||
|
||||
|
||||
class MaveChannelIE(MaveBaseIE):
|
||||
IE_NAME = 'mave:channel'
|
||||
_VALID_URL = r'https?://(?P<id>[\w-]+)\.mave\.digital/?(?:$|[?#])'
|
||||
_TESTS = [{
|
||||
'url': 'https://budem.mave.digital/',
|
||||
'info_dict': {
|
||||
'id': 'budem',
|
||||
'title': 'Все там будем',
|
||||
'description': 'md5:f04ae12a42be0f1d765c5e326b41987a',
|
||||
},
|
||||
'playlist_mincount': 15,
|
||||
}, {
|
||||
'url': 'https://ochenlichnoe.mave.digital/',
|
||||
'info_dict': {
|
||||
'id': 'ochenlichnoe',
|
||||
'title': 'Очень личное',
|
||||
'description': 'md5:ee36a6a52546b91b487fe08c552fdbb2',
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
}, {
|
||||
'url': 'https://geekcity.mave.digital/',
|
||||
'info_dict': {
|
||||
'id': 'geekcity',
|
||||
'title': 'Мужчины в трико',
|
||||
'description': 'md5:4164d425d60a0d97abdce9d1f6f8e049',
|
||||
},
|
||||
'playlist_mincount': 80,
|
||||
}]
|
||||
_PAGE_SIZE = 50
|
||||
|
||||
def _entries(self, channel_id, channel_meta, page_num):
|
||||
page_data = self._download_json(
|
||||
f'{self._API_BASE_URL}/{channel_id}/episodes', channel_id, query={
|
||||
'view': 'all',
|
||||
'page': page_num + 1,
|
||||
'sort': 'newest',
|
||||
'format': 'all',
|
||||
}, note=f'Downloading page {page_num + 1}')
|
||||
for ep in traverse_obj(page_data, ('episodes', lambda _, v: v['audio'] and v['id'])):
|
||||
yield self._create_entry(channel_id, channel_meta, ep)
|
||||
|
||||
def _real_extract(self, url):
|
||||
channel_id = self._match_id(url)
|
||||
|
||||
channel_meta = self._load_channel_meta(channel_id, channel_id)
|
||||
|
||||
return {
|
||||
'display_id': display_id,
|
||||
'channel_id': channel_id,
|
||||
'channel_url': f'https://{channel_id}.mave.digital/',
|
||||
'vcodec': 'none',
|
||||
'thumbnail': re.sub(r'_\d+(?=\.(?:jpg|png))', '', self._og_search_thumbnail(webpage, default='')) or None,
|
||||
**traverse_obj(data, ('activeEpisodeData', {
|
||||
'url': ('audio', {urljoin(self._API_BASE_URL)}),
|
||||
'id': ('id', {str}),
|
||||
'_type': 'playlist',
|
||||
'id': channel_id,
|
||||
**traverse_obj(channel_meta, {
|
||||
'title': ('title', {str}),
|
||||
'description': ('description', {clean_html}),
|
||||
'duration': ('duration', {int_or_none}),
|
||||
'season_number': ('season', {int_or_none}),
|
||||
'episode_number': ('number', {int_or_none}),
|
||||
'view_count': ('listenings', {int_or_none}),
|
||||
'like_count': ('reactions', lambda _, v: v['type'] == 'like', 'count', {int_or_none}, any),
|
||||
'dislike_count': ('reactions', lambda _, v: v['type'] == 'dislike', 'count', {int_or_none}, any),
|
||||
'age_limit': ('is_explicit', {bool}, {lambda x: 18 if x else None}),
|
||||
'timestamp': ('publish_date', {parse_iso8601}),
|
||||
})),
|
||||
**traverse_obj(data, ('podcast', 'podcast', {
|
||||
'series_id': ('id', {str}),
|
||||
'series': ('title', {str}),
|
||||
'channel': ('title', {str}),
|
||||
'uploader': ('author', {str}),
|
||||
})),
|
||||
'description': ('description', {str}),
|
||||
}),
|
||||
'entries': InAdvancePagedList(
|
||||
functools.partial(self._entries, channel_id, channel_meta),
|
||||
math.ceil(channel_meta['episodes_count'] / self._PAGE_SIZE), self._PAGE_SIZE),
|
||||
}
|
||||
|
||||
@@ -3150,6 +3150,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
self._downloader.deprecated_feature('[youtube] include_duplicate_formats extractor argument is deprecated. '
|
||||
'Use formats=duplicate extractor argument instead')
|
||||
|
||||
def is_super_resolution(f_url):
|
||||
return '1' in traverse_obj(f_url, ({parse_qs}, 'xtags', ..., {urllib.parse.parse_qs}, 'sr', ...))
|
||||
|
||||
def solve_sig(s, spec):
|
||||
return ''.join(s[i] for i in spec)
|
||||
|
||||
@@ -3202,7 +3205,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
def get_stream_id(fmt_stream):
|
||||
return str_or_none(fmt_stream.get('itag')), traverse_obj(fmt_stream, 'audioTrack', 'id'), fmt_stream.get('isDrc')
|
||||
|
||||
def process_format_stream(fmt_stream, proto, missing_pot):
|
||||
def process_format_stream(fmt_stream, proto, missing_pot, super_resolution=False):
|
||||
itag = str_or_none(fmt_stream.get('itag'))
|
||||
audio_track = fmt_stream.get('audioTrack') or {}
|
||||
quality = fmt_stream.get('quality')
|
||||
@@ -3253,10 +3256,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
dct = {
|
||||
'asr': int_or_none(fmt_stream.get('audioSampleRate')),
|
||||
'filesize': int_or_none(fmt_stream.get('contentLength')),
|
||||
'format_id': f'{itag}{"-drc" if fmt_stream.get("isDrc") else ""}',
|
||||
'format_id': join_nonempty(itag, (
|
||||
'drc' if fmt_stream.get('isDrc')
|
||||
else 'sr' if super_resolution
|
||||
else None)),
|
||||
'format_note': join_nonempty(
|
||||
join_nonempty(audio_track.get('displayName'), audio_track.get('audioIsDefault') and '(default)', delim=' '),
|
||||
name, fmt_stream.get('isDrc') and 'DRC',
|
||||
name, fmt_stream.get('isDrc') and 'DRC', super_resolution and 'AI-upscaled',
|
||||
try_get(fmt_stream, lambda x: x['projectionType'].replace('RECTANGULAR', '').lower()),
|
||||
try_get(fmt_stream, lambda x: x['spatialAudioType'].replace('SPATIAL_AUDIO_TYPE_', '').lower()),
|
||||
is_damaged and 'DAMAGED', missing_pot and 'MISSING POT',
|
||||
@@ -3342,7 +3348,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
self.report_warning(msg, video_id, only_once=True)
|
||||
continue
|
||||
|
||||
fmt = process_format_stream(fmt_stream, proto, missing_pot=require_po_token and not po_token)
|
||||
fmt = process_format_stream(
|
||||
fmt_stream, proto, missing_pot=require_po_token and not po_token,
|
||||
super_resolution=is_super_resolution(fmt_url))
|
||||
if not fmt:
|
||||
continue
|
||||
|
||||
|
||||
@@ -305,6 +305,8 @@ class UrllibResponseAdapter(Response):
|
||||
status=getattr(res, 'status', None) or res.getcode(), reason=getattr(res, 'reason', None))
|
||||
|
||||
def read(self, amt=None):
|
||||
if self.closed:
|
||||
return b''
|
||||
try:
|
||||
data = self.fp.read(amt)
|
||||
underlying = getattr(self.fp, 'fp', None)
|
||||
|
||||
Reference in New Issue
Block a user