diff --git a/.gitignore b/.gitignore index 581417df6..6851be742 100644 --- a/.gitignore +++ b/.gitignore @@ -53,8 +53,6 @@ cpu.out /bin /dist /custom/* -!/custom/conf -/custom/conf/* !/custom/conf/app.example.ini /data /indexers diff --git a/cmd/actions.go b/cmd/actions.go index 346de5b21..f52a91bd5 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -42,7 +42,7 @@ func runGenerateActionsRunnerToken(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setting.Init(&setting.Options{}) + setting.MustInstalled() scope := c.String("scope") diff --git a/cmd/cmd.go b/cmd/cmd.go index b148007fb..8076aceca 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -58,7 +58,7 @@ func confirm() (bool, error) { } func initDB(ctx context.Context) error { - setting.Init(&setting.Options{}) + setting.MustInstalled() setting.LoadDBSetting() setting.InitSQLLoggersForCli(log.INFO) diff --git a/cmd/doctor.go b/cmd/doctor.go index b596e9ac0..b79436fc0 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -91,7 +91,7 @@ func runRecreateTable(ctx *cli.Context) error { golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) debug := ctx.Bool("debug") - setting.Init(&setting.Options{}) + setting.MustInstalled() setting.LoadDBSetting() if debug { diff --git a/cmd/dump.go b/cmd/dump.go index 7dda7fd2b..0b7c1d32c 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -182,7 +182,7 @@ func runDump(ctx *cli.Context) error { } fileName += "." + outType } - setting.Init(&setting.Options{}) + setting.MustInstalled() // make sure we are logging to the console no matter what the configuration tells us do to // FIXME: don't use CfgProvider directly diff --git a/cmd/embedded.go b/cmd/embedded.go index e51f8477b..204a623cf 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -99,11 +99,6 @@ type assetFile struct { func initEmbeddedExtractor(c *cli.Context) error { setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) - // Read configuration file - setting.Init(&setting.Options{ - AllowEmpty: true, - }) - patterns, err := compileCollectPatterns(c.Args()) if err != nil { return err diff --git a/cmd/mailer.go b/cmd/mailer.go index 74bae1ab6..eaa5a1afe 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -16,7 +16,7 @@ func runSendMail(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setting.Init(&setting.Options{}) + setting.MustInstalled() if err := argsSet(c, "title"); err != nil { return err diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 5a7ede493..c19e28f13 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -51,7 +51,7 @@ func runRestoreRepository(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() - setting.Init(&setting.Options{}) + setting.MustInstalled() var units []string if s := c.String("units"); s != "" { units = strings.Split(s, ",") diff --git a/cmd/serv.go b/cmd/serv.go index 87bf1cce2..01102d380 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -61,7 +61,7 @@ func setup(ctx context.Context, debug bool) { } else { setupConsoleLogger(log.FATAL, false, os.Stderr) } - setting.Init(&setting.Options{}) + setting.MustInstalled() if debug { setting.RunMode = "dev" } diff --git a/cmd/web.go b/cmd/web.go index 115024e8b..7a257a62a 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -101,6 +101,110 @@ func createPIDFile(pidPath string) { } } +func serveInstall(ctx *cli.Context) error { + log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith) + log.Info("App path: %s", setting.AppPath) + log.Info("Work path: %s", setting.AppWorkPath) + log.Info("Custom path: %s", setting.CustomPath) + log.Info("Config file: %s", setting.CustomConf) + log.Info("Prepare to run install page") + + routers.InitWebInstallPage(graceful.GetManager().HammerContext()) + + // Flag for port number in case first time run conflict + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + if ctx.IsSet("install-port") { + if err := setPort(ctx.String("install-port")); err != nil { + return err + } + } + c := install.Routes() + err := listen(c, false) + if err != nil { + log.Critical("Unable to open listener for installer. Is Gitea already running?") + graceful.GetManager().DoGracefulShutdown() + } + select { + case <-graceful.GetManager().IsShutdown(): + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.GetManager().Close() + return err + default: + } + return nil +} + +func serveInstalled(ctx *cli.Context) error { + setting.InitCfgProvider(setting.CustomConf) + setting.LoadCommonSettings() + setting.MustInstalled() + + log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith) + log.Info("App path: %s", setting.AppPath) + log.Info("Work path: %s", setting.AppWorkPath) + log.Info("Custom path: %s", setting.CustomPath) + log.Info("Config file: %s", setting.CustomConf) + log.Info("Run mode: %s", setting.RunMode) + log.Info("Prepare to run web server") + + if setting.AppWorkPathMismatch { + log.Error("WORK_PATH from config %q doesn't match other paths from environment variables or command arguments. "+ + "Only WORK_PATH in config should be set and used. Please remove the other outdated work paths from environment variables and command arguments", setting.CustomConf) + } + + rootCfg := setting.CfgProvider + if rootCfg.Section("").Key("WORK_PATH").String() == "" { + saveCfg, err := rootCfg.PrepareSaving() + if err != nil { + log.Error("Unable to prepare saving WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err) + } else { + rootCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) + saveCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) + if err = saveCfg.Save(); err != nil { + log.Error("Unable to update WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err) + } + } + } + + routers.InitWebInstalled(graceful.GetManager().HammerContext()) + + // We check that AppDataPath exists here (it should have been created during installation) + // We can't check it in `InitWebInstalled`, because some integration tests + // use cmd -> InitWebInstalled, but the AppDataPath doesn't exist during those tests. + if _, err := os.Stat(setting.AppDataPath); err != nil { + log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath) + } + + // Override the provided port number within the configuration + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + + // Set up Chi routes + c := routers.NormalRoutes() + err := listen(c, true) + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.GetManager().Close() + return err +} + +func servePprof() { + http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) + _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true) + // The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it. + log.Info("Starting pprof server on localhost:6060") + log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil)) + finished() +} + func runWeb(ctx *cli.Context) error { if ctx.Bool("verbose") { setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout) @@ -128,75 +232,19 @@ func runWeb(ctx *cli.Context) error { createPIDFile(ctx.String("pid")) } - // Perform pre-initialization - needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext()) - if needsInstall { - // Flag for port number in case first time run conflict - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { - return err - } - } - if ctx.IsSet("install-port") { - if err := setPort(ctx.String("install-port")); err != nil { - return err - } - } - c := install.Routes() - err := listen(c, false) - if err != nil { - log.Critical("Unable to open listener for installer. Is Gitea already running?") - graceful.GetManager().DoGracefulShutdown() - } - select { - case <-graceful.GetManager().IsShutdown(): - <-graceful.GetManager().Done() - log.Info("PID: %d Gitea Web Finished", os.Getpid()) - log.GetManager().Close() + if !setting.InstallLock { + if err := serveInstall(ctx); err != nil { return err - default: } } else { NoInstallListener() } if setting.EnablePprof { - go func() { - http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) - _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true) - // The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it. - log.Info("Starting pprof server on localhost:6060") - log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil)) - finished() - }() + go servePprof() } - log.Info("Global init") - // Perform global initialization - setting.Init(&setting.Options{}) - routers.GlobalInitInstalled(graceful.GetManager().HammerContext()) - - // We check that AppDataPath exists here (it should have been created during installation) - // We can't check it in `GlobalInitInstalled`, because some integration tests - // use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests. - if _, err := os.Stat(setting.AppDataPath); err != nil { - log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath) - } - - // Override the provided port number within the configuration - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { - return err - } - } - - // Set up Chi routes - c := routers.NormalRoutes() - err := listen(c, true) - <-graceful.GetManager().Done() - log.Info("PID: %d Gitea Web Finished", os.Getpid()) - log.GetManager().Close() - return err + return serveInstalled(ctx) } func setPort(port string) error { diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index 3405d7d42..2cdf4e394 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -81,8 +81,6 @@ func main() { }, } app.Action = runEnvironmentToIni - setting.SetCustomPathAndConf("", "", "") - err := app.Run(os.Args) if err != nil { log.Fatal("Failed to run app with %s: %v", os.Args, err) @@ -90,12 +88,13 @@ func main() { } func runEnvironmentToIni(c *cli.Context) error { - providedCustom := c.String("custom-path") - providedConf := c.String("config") - providedWorkPath := c.String("work-path") - setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath) + setting.InitWorkPathAndCommonConfig(os.Getenv, setting.ArgWorkPathAndCustomConf{ + WorkPath: c.String("work-path"), + CustomPath: c.String("custom-path"), + CustomConf: c.String("config"), + }) - cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true}) + cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) } diff --git a/main.go b/main.go index 49093eb8a..1c87824c8 100644 --- a/main.go +++ b/main.go @@ -33,30 +33,58 @@ var ( Tags = "" // MakeVersion holds the current Make version if built with make MakeVersion = "" - - originalAppHelpTemplate = "" - originalCommandHelpTemplate = "" - originalSubcommandHelpTemplate = "" ) func init() { setting.AppVer = Version setting.AppBuiltWith = formatBuiltWith() setting.AppStartTime = time.Now().UTC() +} - // Grab the original help templates - originalAppHelpTemplate = cli.AppHelpTemplate - originalCommandHelpTemplate = cli.CommandHelpTemplate - originalSubcommandHelpTemplate = cli.SubcommandHelpTemplate +// cmdHelp is our own help subcommand with more information +// test cases: +// ./gitea help +// ./gitea -h +// ./gitea web help +// ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info) +// ./gitea admin help auth +// ./gitea -c /tmp/app.ini -h +// ./gitea -c /tmp/app.ini help +// ./gitea help -c /tmp/app.ini +// GITEA_WORK_DIR=/tmp ./gitea help +// GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other +// GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini +var cmdHelp = cli.Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *cli.Context) (err error) { + args := c.Args() + if args.Present() { + err = cli.ShowCommandHelp(c, args.First()) + } else { + err = cli.ShowAppHelp(c) + } + _, _ = fmt.Fprintf(c.App.Writer, ` +DEFAULT CONFIGURATION: + AppPath: %s + WorkPath: %s + CustomPath: %s + ConfigFile: %s + +`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf) + return err + }, } func main() { app := cli.NewApp() app.Name = "Gitea" app.Usage = "A painless self-hosted Git service" - app.Description = `By default, gitea will start serving using the webserver with no -arguments - which can alternatively be run by running the subcommand web.` + app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Version = Version + formatBuiltWith() + app.EnableBashCompletion = true app.Commands = []cli.Command{ cmd.CmdWeb, cmd.CmdServ, @@ -77,118 +105,83 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdRestoreRepository, cmd.CmdActions, } - // Now adjust these commands to add our global configuration options - - // First calculate the default paths and set the AppHelpTemplates in this context - setting.SetCustomPathAndConf("", "", "") - setAppHelpTemplates() // default configuration flags - defaultFlags := []cli.Flag{ + globalFlags := []cli.Flag{ + cli.HelpFlag, cli.StringFlag{ Name: "custom-path, C", - Value: setting.CustomPath, - Usage: "Custom path file path", + Usage: "Set custom path (defaults to '{WorkPath}/custom')", }, cli.StringFlag{ Name: "config, c", Value: setting.CustomConf, - Usage: "Custom configuration file path", + Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')", }, - cli.VersionFlag, cli.StringFlag{ Name: "work-path, w", - Value: setting.AppWorkPath, - Usage: "Set the gitea working path", + Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)", }, } // Set the default to be equivalent to cmdWeb and add the default flags + app.Flags = append(app.Flags, globalFlags...) app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) - app.Flags = append(app.Flags, defaultFlags...) - app.Action = cmd.CmdWeb.Action - - // Add functions to set these paths and these flags to the commands - app.Before = establishCustomPath + app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action) + app.HideHelp = true // use our own help action to show helps (with more information like default config) + app.Commands = append(app.Commands, cmdHelp) for i := range app.Commands { - setFlagsAndBeforeOnSubcommands(&app.Commands[i], defaultFlags, establishCustomPath) + prepareSubcommands(&app.Commands[i], globalFlags) } - app.EnableBashCompletion = true - err := app.Run(os.Args) if err != nil { - log.Fatal("Failed to run app with %s: %v", os.Args, err) + _, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err) } log.GetManager().Close() } -func setFlagsAndBeforeOnSubcommands(command *cli.Command, defaultFlags []cli.Flag, before cli.BeforeFunc) { +func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) { command.Flags = append(command.Flags, defaultFlags...) - command.Before = establishCustomPath + command.Action = prepareWorkPathAndCustomConf(command.Action) + command.HideHelp = true + if command.Name != "help" { + command.Subcommands = append(command.Subcommands, cmdHelp) + } for i := range command.Subcommands { - setFlagsAndBeforeOnSubcommands(&command.Subcommands[i], defaultFlags, before) + prepareSubcommands(&command.Subcommands[i], defaultFlags) } } -func establishCustomPath(ctx *cli.Context) error { - var providedCustom string - var providedConf string - var providedWorkPath string - - currentCtx := ctx - for { - if len(providedCustom) != 0 && len(providedConf) != 0 && len(providedWorkPath) != 0 { - break - } - if currentCtx == nil { - break - } - if currentCtx.IsSet("custom-path") && len(providedCustom) == 0 { - providedCustom = currentCtx.String("custom-path") - } - if currentCtx.IsSet("config") && len(providedConf) == 0 { - providedConf = currentCtx.String("config") - } - if currentCtx.IsSet("work-path") && len(providedWorkPath) == 0 { - providedWorkPath = currentCtx.String("work-path") - } - currentCtx = currentCtx.Parent() - +// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config +// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times +func prepareWorkPathAndCustomConf(a any) func(ctx *cli.Context) error { + if a == nil { + return nil } - setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath) - - setAppHelpTemplates() - - if ctx.IsSet("version") { - cli.ShowVersion(ctx) - os.Exit(0) + action := a.(func(*cli.Context) error) + return func(ctx *cli.Context) error { + var args setting.ArgWorkPathAndCustomConf + curCtx := ctx + for curCtx != nil { + if curCtx.IsSet("work-path") && args.WorkPath == "" { + args.WorkPath = curCtx.String("work-path") + } + if curCtx.IsSet("custom-path") && args.CustomPath == "" { + args.CustomPath = curCtx.String("custom-path") + } + if curCtx.IsSet("config") && args.CustomConf == "" { + args.CustomConf = curCtx.String("config") + } + curCtx = curCtx.Parent() + } + setting.InitWorkPathAndCommonConfig(os.Getenv, args) + if ctx.Bool("help") { + return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx) + } + return action(ctx) } - - return nil -} - -func setAppHelpTemplates() { - cli.AppHelpTemplate = adjustHelpTemplate(originalAppHelpTemplate) - cli.CommandHelpTemplate = adjustHelpTemplate(originalCommandHelpTemplate) - cli.SubcommandHelpTemplate = adjustHelpTemplate(originalSubcommandHelpTemplate) -} - -func adjustHelpTemplate(originalTemplate string) string { - overridden := "" - if _, ok := os.LookupEnv("GITEA_CUSTOM"); ok { - overridden = "(GITEA_CUSTOM)" - } - - return fmt.Sprintf(`%s -DEFAULT CONFIGURATION: - CustomPath: %s %s - CustomConf: %s - AppPath: %s - AppWorkPath: %s - -`, originalTemplate, setting.CustomPath, overridden, setting.CustomConf, setting.AppPath, setting.AppWorkPath) } func formatBuiltWith() string { diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index dd99a1eda..c3100ba66 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -147,9 +147,9 @@ func MainTest(m *testing.M) { os.Exit(1) } + setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") setting.AppDataPath = tmpDataPath - setting.SetCustomPathAndConf("", "", "") unittest.InitSettings() if err = git.InitFull(context.Background()); err != nil { fmt.Printf("Unable to InitFull: %v\n", err) diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 5351ff113..f926a6553 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -42,12 +42,14 @@ func fatalTestError(fmtStr string, args ...interface{}) { os.Exit(1) } -// InitSettings initializes config provider and load common setttings for tests +// InitSettings initializes config provider and load common settings for tests func InitSettings(extraConfigs ...string) { - setting.Init(&setting.Options{ - AllowEmpty: true, - ExtraConfig: strings.Join(extraConfigs, "\n"), - }) + if setting.CustomConf == "" { + setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") + _ = os.Remove(setting.CustomConf) + } + setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n")) + setting.LoadCommonSettings() if err := setting.PrepareAppDataPath(); err != nil { log.Fatalf("Can not prepare APP_DATA_PATH: %v", err) @@ -69,7 +71,7 @@ type TestOptions struct { // MainTest a reusable TestMain(..) function for unit tests that need to use a // test database. Creates the test database, and sets necessary settings. func MainTest(m *testing.M, testOpts *TestOptions) { - setting.SetCustomPathAndConf("", "", "") + setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom") InitSettings() var err error diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go index 10838a751..ceee32285 100644 --- a/modules/doctor/doctor.go +++ b/modules/doctor/doctor.go @@ -28,7 +28,7 @@ type Check struct { } func initDBSkipLogger(ctx context.Context) error { - setting.Init(&setting.Options{}) + setting.MustInstalled() setting.LoadDBSetting() if err := db.InitEngine(ctx); err != nil { return fmt.Errorf("db.InitEngine: %w", err) diff --git a/modules/doctor/paths.go b/modules/doctor/paths.go index 957152349..3f62d587a 100644 --- a/modules/doctor/paths.go +++ b/modules/doctor/paths.go @@ -66,7 +66,7 @@ func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix boo return err } - setting.Init(&setting.Options{}) + setting.MustInstalled() configurationFiles := []configurationFile{ {"Configuration File Path", setting.CustomConf, false, true, false}, diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 5e5e4fecb..a8d7ba794 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -28,9 +29,7 @@ var localMetas = map[string]string{ } func TestMain(m *testing.M) { - setting.Init(&setting.Options{ - AllowEmpty: true, - }) + unittest.InitSettings() if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 4bd2ca8d4..f2322b255 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -33,9 +34,7 @@ var localMetas = map[string]string{ } func TestMain(m *testing.M) { - setting.Init(&setting.Options{ - AllowEmpty: true, - }) + unittest.InitSettings() if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index deec5cc58..94dd98985 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -55,15 +55,15 @@ type ConfigProvider interface { DisableSaving() PrepareSaving() (ConfigProvider, error) + IsLoadedFromEmpty() bool } type iniConfigProvider struct { - opts *Options + file string ini *ini.File - disableSaving bool - - newFile bool // whether the file has not existed previously + disableSaving bool // disable the "Save" method because the config options could be polluted + loadedFromEmpty bool // whether the file has not existed previously } type iniConfigSection struct { @@ -182,53 +182,43 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) { } cfg.NameMapper = ini.SnackCase return &iniConfigProvider{ - ini: cfg, - newFile: true, + ini: cfg, + loadedFromEmpty: true, }, nil } -type Options struct { - CustomConf string // the ini file path - AllowEmpty bool // whether not finding configuration files is allowed - ExtraConfig string - - DisableLoadCommonSettings bool // only used by "Init()", not used by "NewConfigProvider()" -} - // NewConfigProviderFromFile load configuration from file. // NOTE: do not print any log except error. -func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) { +func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) { cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "}) - newFile := true + loadedFromEmpty := true - if opts.CustomConf != "" { - isFile, err := util.IsFile(opts.CustomConf) + if file != "" { + isFile, err := util.IsFile(file) if err != nil { - return nil, fmt.Errorf("unable to check if %s is a file. Error: %v", opts.CustomConf, err) + return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err) } if isFile { - if err := cfg.Append(opts.CustomConf); err != nil { - return nil, fmt.Errorf("failed to load custom conf '%s': %v", opts.CustomConf, err) + if err = cfg.Append(file); err != nil { + return nil, fmt.Errorf("failed to load config file %q: %v", file, err) } - newFile = false + loadedFromEmpty = false } } - if newFile && !opts.AllowEmpty { - return nil, fmt.Errorf("unable to find configuration file: %q, please ensure you are running in the correct environment or set the correct configuration file with -c", CustomConf) - } - - if opts.ExtraConfig != "" { - if err := cfg.Append([]byte(opts.ExtraConfig)); err != nil { - return nil, fmt.Errorf("unable to append more config: %v", err) + if len(extraConfigs) > 0 { + for _, s := range extraConfigs { + if err := cfg.Append([]byte(s)); err != nil { + return nil, fmt.Errorf("unable to append more config: %v", err) + } } } cfg.NameMapper = ini.SnackCase return &iniConfigProvider{ - opts: opts, - ini: cfg, - newFile: newFile, + file: file, + ini: cfg, + loadedFromEmpty: loadedFromEmpty, }, nil } @@ -266,20 +256,17 @@ func (p *iniConfigProvider) Save() error { if p.disableSaving { return errDisableSaving } - filename := p.opts.CustomConf + filename := p.file if filename == "" { - if !p.opts.AllowEmpty { - return fmt.Errorf("custom config path must not be empty") - } - return nil + return fmt.Errorf("config file path must not be empty") } - if p.newFile { + if p.loadedFromEmpty { if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil { - return fmt.Errorf("failed to create '%s': %v", filename, err) + return fmt.Errorf("failed to create %q: %v", filename, err) } } if err := p.ini.SaveTo(filename); err != nil { - return fmt.Errorf("failed to save '%s': %v", filename, err) + return fmt.Errorf("failed to save %q: %v", filename, err) } // Change permissions to be more restrictive @@ -313,11 +300,14 @@ func (p *iniConfigProvider) DisableSaving() { // it makes the "Save" outputs a lot of garbage options // After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped. func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) { - cfgFile := p.opts.CustomConf - if cfgFile == "" { + if p.file == "" { return nil, errors.New("no config file to save") } - return NewConfigProviderFromFile(p.opts) + return NewConfigProviderFromFile(p.file) +} + +func (p *iniConfigProvider) IsLoadedFromEmpty() bool { + return p.loadedFromEmpty } func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) { @@ -356,8 +346,8 @@ func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, erro } iniFile.BlockMode = false return &iniConfigProvider{ - ini: iniFile, - newFile: true, + ini: iniFile, + loadedFromEmpty: true, }, nil } diff --git a/modules/setting/config_provider_test.go b/modules/setting/config_provider_test.go index c5c5196e0..7e7c6be2b 100644 --- a/modules/setting/config_provider_test.go +++ b/modules/setting/config_provider_test.go @@ -67,13 +67,14 @@ key = 123 } func TestNewConfigProviderFromFile(t *testing.T) { - _, err := NewConfigProviderFromFile(&Options{CustomConf: "no-such.ini", AllowEmpty: false}) - assert.ErrorContains(t, err, "unable to find configuration file") + cfg, err := NewConfigProviderFromFile("no-such.ini") + assert.NoError(t, err) + assert.True(t, cfg.IsLoadedFromEmpty()) // load non-existing file and save testFile := t.TempDir() + "/test.ini" testFile1 := t.TempDir() + "/test1.ini" - cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true}) + cfg, err = NewConfigProviderFromFile(testFile) assert.NoError(t, err) sec, _ := cfg.NewSection("foo") @@ -91,7 +92,7 @@ func TestNewConfigProviderFromFile(t *testing.T) { assert.Equal(t, "[foo]\nk1 = a\nk2 = b\n", string(bs)) // load existing file and save - cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true}) + cfg, err = NewConfigProviderFromFile(testFile) assert.NoError(t, err) assert.Equal(t, "a", cfg.Section("foo").Key("k1").String()) sec, _ = cfg.NewSection("bar") @@ -123,7 +124,7 @@ func TestNewConfigProviderForLocale(t *testing.T) { func TestDisableSaving(t *testing.T) { testFile := t.TempDir() + "/test.ini" _ = os.WriteFile(testFile, []byte("k1=a\nk2=b"), 0o644) - cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true}) + cfg, err := NewConfigProviderFromFile(testFile) assert.NoError(t, err) cfg.DisableSaving() diff --git a/modules/setting/path.go b/modules/setting/path.go new file mode 100644 index 000000000..91bb2e9bb --- /dev/null +++ b/modules/setting/path.go @@ -0,0 +1,191 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/log" +) + +var ( + // AppPath represents the path to the gitea binary + AppPath string + + // AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR. + // If that is not set it is the default set here by the linker or failing that the directory of AppPath. + // It is used as the base path for several other paths. + AppWorkPath string + CustomPath string // Custom directory path. Env: GITEA_CUSTOM + CustomConf string + + appWorkPathBuiltin string + customPathBuiltin string + customConfBuiltin string + + AppWorkPathMismatch bool +) + +func getAppPath() (string, error) { + var appPath string + var err error + if IsWindows && filepath.IsAbs(os.Args[0]) { + appPath = filepath.Clean(os.Args[0]) + } else { + appPath, err = exec.LookPath(os.Args[0]) + } + if err != nil { + if !errors.Is(err, exec.ErrDot) { + return "", err + } + appPath, err = filepath.Abs(os.Args[0]) + } + if err != nil { + return "", err + } + appPath, err = filepath.Abs(appPath) + if err != nil { + return "", err + } + // Note: (legacy code) we don't use path.Dir here because it does not handle case which path starts with two "/" in Windows: "//psf/Home/..." + return strings.ReplaceAll(appPath, "\\", "/"), err +} + +func init() { + var err error + if AppPath, err = getAppPath(); err != nil { + log.Fatal("Failed to get app path: %v", err) + } + + if AppWorkPath == "" { + AppWorkPath = filepath.Dir(AppPath) + } + + appWorkPathBuiltin = AppWorkPath + customPathBuiltin = CustomPath + customConfBuiltin = CustomConf +} + +type ArgWorkPathAndCustomConf struct { + WorkPath string + CustomPath string + CustomConf string +} + +type stringWithDefault struct { + Value string + IsSet bool +} + +func (s *stringWithDefault) Set(v string) { + s.Value = v + s.IsSet = true +} + +// InitWorkPathAndCommonConfig will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf and load common settings, +func InitWorkPathAndCommonConfig(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) { + tryAbsPath := func(paths ...string) string { + s := paths[len(paths)-1] + for i := len(paths) - 2; i >= 0; i-- { + if filepath.IsAbs(s) { + break + } + s = filepath.Join(paths[i], s) + } + return s + } + + var err error + tmpWorkPath := stringWithDefault{Value: appWorkPathBuiltin} + if tmpWorkPath.Value == "" { + tmpWorkPath.Value = filepath.Dir(AppPath) + } + tmpCustomPath := stringWithDefault{Value: customPathBuiltin} + if tmpCustomPath.Value == "" { + tmpCustomPath.Value = "custom" + } + tmpCustomConf := stringWithDefault{Value: customConfBuiltin} + if tmpCustomConf.Value == "" { + tmpCustomConf.Value = "conf/app.ini" + } + + readFromEnv := func() { + envWorkPath := getEnvFn("GITEA_WORK_DIR") + if envWorkPath != "" { + tmpWorkPath.Set(envWorkPath) + if !filepath.IsAbs(tmpWorkPath.Value) { + log.Fatal("GITEA_WORK_DIR (work path) must be absolute path") + } + } + + envCustomPath := getEnvFn("GITEA_CUSTOM") + if envCustomPath != "" { + tmpCustomPath.Set(envCustomPath) + if !filepath.IsAbs(tmpCustomPath.Value) { + log.Fatal("GITEA_CUSTOM (custom path) must be absolute path") + } + } + } + + readFromArgs := func() { + if args.WorkPath != "" { + tmpWorkPath.Set(args.WorkPath) + if !filepath.IsAbs(tmpWorkPath.Value) { + log.Fatal("--work-path must be absolute path") + } + } + if args.CustomPath != "" { + tmpCustomPath.Set(args.CustomPath) // if it is not abs, it will be based on work-path, it shouldn't happen + if !filepath.IsAbs(tmpCustomPath.Value) { + log.Error("--custom-path must be absolute path") + } + } + if args.CustomConf != "" { + tmpCustomConf.Set(args.CustomConf) + if !filepath.IsAbs(tmpCustomConf.Value) { + // the config path can be relative to the real current working path + if tmpCustomConf.Value, err = filepath.Abs(tmpCustomConf.Value); err != nil { + log.Fatal("Failed to get absolute path of config %q: %v", tmpCustomConf.Value, err) + } + } + } + } + + readFromEnv() + readFromArgs() + + if !tmpCustomConf.IsSet { + tmpCustomConf.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value, tmpCustomConf.Value)) + } + + // only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready + InitCfgProvider(tmpCustomConf.Value) + configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH") + if configWorkPath != "" { + if !filepath.IsAbs(configWorkPath) { + log.Fatal("WORK_PATH in %q must be absolute path", configWorkPath) + } + configWorkPath = filepath.Clean(configWorkPath) + if tmpWorkPath.Value != "" && (getEnvFn("GITEA_WORK_DIR") != "" || args.WorkPath != "") { + fi1, err1 := os.Stat(tmpWorkPath.Value) + fi2, err2 := os.Stat(configWorkPath) + if err1 != nil || err2 != nil || !os.SameFile(fi1, fi2) { + AppWorkPathMismatch = true + } + } + tmpWorkPath.Set(configWorkPath) + } + + tmpCustomPath.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value)) + + AppWorkPath = tmpWorkPath.Value + CustomPath = tmpCustomPath.Value + CustomConf = tmpCustomConf.Value + + LoadCommonSettings() +} diff --git a/modules/setting/path_test.go b/modules/setting/path_test.go new file mode 100644 index 000000000..fc6a2116d --- /dev/null +++ b/modules/setting/path_test.go @@ -0,0 +1,151 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +type envVars map[string]string + +func (e envVars) Getenv(key string) string { + return e[key] +} + +func TestInitWorkPathAndCommonConfig(t *testing.T) { + testInit := func(defaultWorkPath, defaultCustomPath, defaultCustomConf string) { + AppWorkPathMismatch = false + AppWorkPath = defaultWorkPath + appWorkPathBuiltin = defaultWorkPath + CustomPath = defaultCustomPath + customPathBuiltin = defaultCustomPath + CustomConf = defaultCustomConf + customConfBuiltin = defaultCustomConf + } + + fp := filepath.Join + + tmpDir := t.TempDir() + dirFoo := fp(tmpDir, "foo") + dirBar := fp(tmpDir, "bar") + dirXxx := fp(tmpDir, "xxx") + dirYyy := fp(tmpDir, "yyy") + + t.Run("Default", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, fp(dirFoo, "custom"), CustomPath) + assert.Equal(t, fp(dirFoo, "custom/conf/app.ini"), CustomConf) + }) + + t.Run("WorkDir(env)", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirBar, AppWorkPath) + assert.Equal(t, fp(dirBar, "custom"), CustomPath) + assert.Equal(t, fp(dirBar, "custom/conf/app.ini"), CustomConf) + }) + + t.Run("WorkDir(env,arg)", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirXxx}) + assert.Equal(t, dirXxx, AppWorkPath) + assert.Equal(t, fp(dirXxx, "custom"), CustomPath) + assert.Equal(t, fp(dirXxx, "custom/conf/app.ini"), CustomConf) + }) + + t.Run("CustomPath(env)", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, fp(dirBar, "custom1"), CustomPath) + assert.Equal(t, fp(dirBar, "custom1/conf/app.ini"), CustomConf) + }) + + t.Run("CustomPath(env,arg)", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{CustomPath: "custom2"}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, fp(dirFoo, "custom2"), CustomPath) + assert.Equal(t, fp(dirFoo, "custom2/conf/app.ini"), CustomConf) + }) + + t.Run("CustomConf", func(t *testing.T) { + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: "app1.ini"}) + assert.Equal(t, dirFoo, AppWorkPath) + cwd, _ := os.Getwd() + assert.Equal(t, fp(cwd, "app1.ini"), CustomConf) + + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: fp(dirBar, "app1.ini")}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, fp(dirBar, "app1.ini"), CustomConf) + }) + + t.Run("CustomConfOverrideWorkPath", func(t *testing.T) { + iniWorkPath := fp(tmpDir, "app-workpath.ini") + _ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644) + + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath}) + assert.Equal(t, dirXxx, AppWorkPath) + assert.Equal(t, fp(dirXxx, "custom"), CustomPath) + assert.Equal(t, iniWorkPath, CustomConf) + assert.False(t, AppWorkPathMismatch) + + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath}) + assert.Equal(t, dirXxx, AppWorkPath) + assert.Equal(t, fp(dirXxx, "custom"), CustomPath) + assert.Equal(t, iniWorkPath, CustomConf) + assert.True(t, AppWorkPathMismatch) + + testInit(dirFoo, "", "") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirBar, CustomConf: iniWorkPath}) + assert.Equal(t, dirXxx, AppWorkPath) + assert.Equal(t, fp(dirXxx, "custom"), CustomPath) + assert.Equal(t, iniWorkPath, CustomConf) + assert.True(t, AppWorkPathMismatch) + }) + + t.Run("Builtin", func(t *testing.T) { + testInit(dirFoo, dirBar, dirXxx) + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, dirBar, CustomPath) + assert.Equal(t, dirXxx, CustomConf) + + testInit(dirFoo, "custom1", "cfg.ini") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, fp(dirFoo, "custom1"), CustomPath) + assert.Equal(t, fp(dirFoo, "custom1/cfg.ini"), CustomConf) + + testInit(dirFoo, "custom1", "cfg.ini") + InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirYyy}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirYyy, AppWorkPath) + assert.Equal(t, fp(dirYyy, "custom1"), CustomPath) + assert.Equal(t, fp(dirYyy, "custom1/cfg.ini"), CustomConf) + + testInit(dirFoo, "custom1", "cfg.ini") + InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": dirYyy}.Getenv, ArgWorkPathAndCustomConf{}) + assert.Equal(t, dirFoo, AppWorkPath) + assert.Equal(t, dirYyy, CustomPath) + assert.Equal(t, fp(dirYyy, "cfg.ini"), CustomConf) + + iniWorkPath := fp(tmpDir, "app-workpath.ini") + _ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644) + testInit(dirFoo, "custom1", "cfg.ini") + InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath}) + assert.Equal(t, dirXxx, AppWorkPath) + assert.Equal(t, fp(dirXxx, "custom1"), CustomPath) + assert.Equal(t, iniWorkPath, CustomConf) + }) +} diff --git a/modules/setting/server.go b/modules/setting/server.go index d937faca1..7c033bcc6 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -61,6 +61,7 @@ var ( AssetVersion string // Server settings + Protocol Scheme UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"` ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` @@ -324,7 +325,6 @@ func loadServerFrom(rootCfg ConfigProvider) { StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour) AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data")) if !filepath.IsAbs(AppDataPath) { - log.Info("The provided APP_DATA_PATH: %s is not absolute - it will be made absolute against the work path: %s", AppDataPath, AppWorkPath) AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 6eaddbe2b..0d69847db 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -5,12 +5,8 @@ package setting import ( - "errors" "fmt" "os" - "os/exec" - "path" - "path/filepath" "runtime" "strings" "time" @@ -28,19 +24,9 @@ var ( // AppStartTime store time gitea has started AppStartTime time.Time - // AppPath represents the path to the gitea binary - AppPath string - // AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR. - // If that is not set it is the default set here by the linker or failing that the directory of AppPath. - // - // AppWorkPath is used as the base path for several other paths. - AppWorkPath string - // Other global setting objects CfgProvider ConfigProvider - CustomPath string // Custom directory path - CustomConf string RunMode string RunUser string IsProd bool @@ -51,62 +37,6 @@ var ( IsInTesting = false ) -func getAppPath() (string, error) { - var appPath string - var err error - if IsWindows && filepath.IsAbs(os.Args[0]) { - appPath = filepath.Clean(os.Args[0]) - } else { - appPath, err = exec.LookPath(os.Args[0]) - } - - if err != nil { - if !errors.Is(err, exec.ErrDot) { - return "", err - } - appPath, err = filepath.Abs(os.Args[0]) - } - if err != nil { - return "", err - } - appPath, err = filepath.Abs(appPath) - if err != nil { - return "", err - } - // Note: we don't use path.Dir here because it does not handle case - // which path starts with two "/" in Windows: "//psf/Home/..." - return strings.ReplaceAll(appPath, "\\", "/"), err -} - -func getWorkPath(appPath string) string { - workPath := AppWorkPath - - if giteaWorkPath, ok := os.LookupEnv("GITEA_WORK_DIR"); ok { - workPath = giteaWorkPath - } - if len(workPath) == 0 { - i := strings.LastIndex(appPath, "/") - if i == -1 { - workPath = appPath - } else { - workPath = appPath[:i] - } - } - workPath = strings.ReplaceAll(workPath, "\\", "/") - if !filepath.IsAbs(workPath) { - log.Info("Provided work path %s is not absolute - will be made absolute against the current working directory", workPath) - - absPath, err := filepath.Abs(workPath) - if err != nil { - log.Error("Unable to absolute %s against the current working directory %v. Will absolute against the AppPath %s", workPath, err, appPath) - workPath = filepath.Join(appPath, workPath) - } else { - workPath = absPath - } - } - return strings.ReplaceAll(workPath, "\\", "/") -} - func init() { IsWindows = runtime.GOOS == "windows" if AppVer == "" { @@ -116,12 +46,6 @@ func init() { // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically // By default set this logger at Info - we'll change it later, but we need to start with something. log.SetConsoleLogger(log.DEFAULT, "console", log.INFO) - - var err error - if AppPath, err = getAppPath(); err != nil { - log.Fatal("Failed to get app path: %v", err) - } - AppWorkPath = getWorkPath(AppPath) } // IsRunUserMatchCurrentUser returns false if configured run user does not match @@ -137,36 +61,6 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) { return currentUser, runUser == currentUser } -// SetCustomPathAndConf will set CustomPath and CustomConf with reference to the -// GITEA_CUSTOM environment variable and with provided overrides before stepping -// back to the default -func SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath string) { - if len(providedWorkPath) != 0 { - AppWorkPath = filepath.ToSlash(providedWorkPath) - } - if giteaCustom, ok := os.LookupEnv("GITEA_CUSTOM"); ok { - CustomPath = giteaCustom - } - if len(providedCustom) != 0 { - CustomPath = providedCustom - } - if len(CustomPath) == 0 { - CustomPath = path.Join(AppWorkPath, "custom") - } else if !filepath.IsAbs(CustomPath) { - CustomPath = path.Join(AppWorkPath, CustomPath) - } - - if len(providedConf) != 0 { - CustomConf = providedConf - } - if len(CustomConf) == 0 { - CustomConf = path.Join(CustomPath, "conf/app.ini") - } else if !filepath.IsAbs(CustomConf) { - CustomConf = path.Join(CustomPath, CustomConf) - log.Warn("Using 'custom' directory as relative origin for configuration file: '%s'", CustomConf) - } -} - // PrepareAppDataPath creates app data directory if necessary func PrepareAppDataPath() error { // FIXME: There are too many calls to MkdirAll in old code. It is incorrect. @@ -196,20 +90,23 @@ func PrepareAppDataPath() error { return nil } -func Init(opts *Options) { - if opts.CustomConf == "" { - opts.CustomConf = CustomConf - } +func InitCfgProvider(file string, extraConfigs ...string) { var err error - CfgProvider, err = NewConfigProviderFromFile(opts) - CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls - if err != nil { - log.Fatal("newConfigProviderFromFile[%v]: %v", opts, err) + if CfgProvider, err = NewConfigProviderFromFile(file, extraConfigs...); err != nil { + log.Fatal("Unable to init config provider from %q: %v", file, err) } - if !opts.DisableLoadCommonSettings { - if err := loadCommonSettingsFrom(CfgProvider); err != nil { - log.Fatal("loadCommonSettingsFrom[%v]: %v", opts, err) - } + CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls +} + +func MustInstalled() { + if !InstallLock { + log.Fatal(`Unable to load config file for a installed Gitea instance, you should either use "--config" to set your config file (app.ini), or run "gitea web" command to install Gitea.`) + } +} + +func LoadCommonSettings() { + if err := loadCommonSettingsFrom(CfgProvider); err != nil { + log.Fatal("Unable to load settings from config: %v", err) } } diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 4bf57eafb..923fa51d2 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -68,7 +68,7 @@ func sessionHandler(session ssh.Session) { log.Trace("SSH: Payload: %v", command) - args := []string{"serv", "key-" + keyID, "--config=" + setting.CustomConf} + args := []string{"--config=" + setting.CustomConf, "serv", "key-" + keyID} log.Trace("SSH: Arguments: %v", args) ctx, cancel := context.WithCancel(session.Context()) diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go index b4275e600..6a0cee4a2 100644 --- a/modules/testlogger/testlogger.go +++ b/modules/testlogger/testlogger.go @@ -90,10 +90,11 @@ func (w *testLoggerWriterCloser) Reset() { // PrintCurrentTest prints the current test to os.Stdout func PrintCurrentTest(t testing.TB, skip ...int) func() { + t.Helper() start := time.Now() actualSkip := 1 if len(skip) > 0 { - actualSkip = skip[0] + actualSkip = skip[0] + 1 } _, filename, line, _ := runtime.Caller(actualSkip) diff --git a/routers/init.go b/routers/init.go index 54e8d2b8b..ddbabcc39 100644 --- a/routers/init.go +++ b/routers/init.go @@ -28,7 +28,6 @@ import ( "code.gitea.io/gitea/modules/system" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" actions_router "code.gitea.io/gitea/routers/api/actions" packages_router "code.gitea.io/gitea/routers/api/packages" @@ -101,21 +100,16 @@ func syncAppConfForGit(ctx context.Context) error { return nil } -// GlobalInitInstalled is for global installed configuration. -func GlobalInitInstalled(ctx context.Context) { - if !setting.InstallLock { - log.Fatal("Gitea is not installed") - } +func InitWebInstallPage(ctx context.Context) { + translation.InitLocales(ctx) + setting.LoadSettingsForInstall() + mustInit(svg.Init) +} +// InitWebInstalled is for global installed configuration. +func InitWebInstalled(ctx context.Context) { mustInitCtx(ctx, git.InitFull) - log.Info("Gitea Version: %s%s", setting.AppVer, setting.AppBuiltWith) - log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir()) - log.Info("AppPath: %s", setting.AppPath) - log.Info("AppWorkPath: %s", setting.AppWorkPath) - log.Info("Custom path: %s", setting.CustomPath) - log.Info("Log path: %s", setting.Log.RootPath) - log.Info("Configuration file: %s", setting.CustomConf) - log.Info("Run Mode: %s", util.ToTitleCase(setting.RunMode)) + log.Info("Git version: %s (home: %s)", git.VersionInfo(), git.HomeDir()) // Setup i18n translation.InitLocales(ctx) diff --git a/routers/install/install.go b/routers/install/install.go index 16bb55b68..c94a30b89 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -32,6 +32,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/forms" "gitea.com/go-chi/session" @@ -370,11 +371,16 @@ func SubmitInstall(ctx *context.Context) { } // Save settings. - cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true}) + cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err) } + cfg.Section("").Key("APP_NAME").SetValue(form.AppName) + cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) + cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) + cfg.Section("").Key("RUN_MODE").SetValue("prod") + cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type.String()) cfg.Section("database").Key("HOST").SetValue(setting.Database.Host) cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) @@ -386,9 +392,7 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("database").Key("PATH").SetValue(setting.Database.Path) cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful - cfg.Section("").Key("APP_NAME").SetValue(form.AppName) cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath) - cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) cfg.Section("server").Key("SSH_DOMAIN").SetValue(form.Domain) cfg.Section("server").Key("DOMAIN").SetValue(form.Domain) cfg.Section("server").Key("HTTP_PORT").SetValue(form.HTTPPort) @@ -450,8 +454,6 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(fmt.Sprint(form.NoReplyAddress)) cfg.Section("cron.update_checker").Key("ENABLED").SetValue(fmt.Sprint(form.EnableUpdateChecker)) - cfg.Section("").Key("RUN_MODE").SetValue("prod") - cfg.Section("session").Key("PROVIDER").SetValue("file") cfg.Section("log").Key("MODE").MustString("console") @@ -514,7 +516,13 @@ func SubmitInstall(ctx *context.Context) { // ---- All checks are passed // Reload settings (and re-initialize database connection) - reloadSettings(ctx) + setting.InitCfgProvider(setting.CustomConf) + setting.LoadCommonSettings() + setting.MustInstalled() + setting.LoadDBSetting() + if err := common.InitDBEngine(ctx); err != nil { + log.Fatal("ORM engine initialization failed: %v", err) + } // Create admin account if len(form.AdminName) > 0 { diff --git a/routers/install/setting.go b/routers/install/setting.go deleted file mode 100644 index c14843d8e..000000000 --- a/routers/install/setting.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package install - -import ( - "context" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/svg" - "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/routers/common" -) - -// PreloadSettings preloads the configuration to check if we need to run install -func PreloadSettings(ctx context.Context) bool { - setting.Init(&setting.Options{ - AllowEmpty: true, - }) - if !setting.InstallLock { - log.Info("AppPath: %s", setting.AppPath) - log.Info("AppWorkPath: %s", setting.AppWorkPath) - log.Info("Custom path: %s", setting.CustomPath) - log.Info("Log path: %s", setting.Log.RootPath) - log.Info("Configuration file: %s", setting.CustomConf) - log.Info("Prepare to run install page") - translation.InitLocales(ctx) - if setting.EnableSQLite3 { - log.Info("SQLite3 is supported") - } - - setting.LoadSettingsForInstall() - _ = svg.Init() - } - - return !setting.InstallLock -} - -// reloadSettings reloads the existing settings and starts up the database -func reloadSettings(ctx context.Context) { - setting.Init(&setting.Options{}) - setting.LoadDBSetting() - if setting.InstallLock { - if err := common.InitDBEngine(ctx); err == nil { - log.Info("ORM engine initialization successful!") - } else { - log.Fatal("ORM engine initialization failed: %v", err) - } - } -} diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index be662c22e..2c6989a71 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" "net/url" - "os" "strconv" "strings" @@ -167,20 +166,6 @@ func Config(ctx *context.Context) { ctx.Data["SessionConfig"] = sessionCfg ctx.Data["Git"] = setting.Git - - type envVar struct { - Name, Value string - } - - envVars := map[string]*envVar{} - if len(os.Getenv("GITEA_WORK_DIR")) > 0 { - envVars["GITEA_WORK_DIR"] = &envVar{"GITEA_WORK_DIR", os.Getenv("GITEA_WORK_DIR")} - } - if len(os.Getenv("GITEA_CUSTOM")) > 0 { - envVars["GITEA_CUSTOM"] = &envVar{"GITEA_CUSTOM", os.Getenv("GITEA_CUSTOM")} - } - - ctx.Data["EnvVars"] = envVars ctx.Data["AccessLogTemplate"] = setting.Log.AccessLogTemplate ctx.Data["LogSQL"] = setting.Database.LogSQL diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 2850cc8d3..2ddc0c1ac 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -46,15 +46,6 @@
{{.ScriptType}}
{{.locale.Tr "admin.config.reverse_auth_user"}}
{{.ReverseProxyAuthUser}}
- - {{if .EnvVars}} -
- {{range .EnvVars}} -
{{.Name}}
-
{{.Value}}
- {{end}} - {{end}} - diff --git a/tests/test_utils.go b/tests/test_utils.go index 5540d92e9..bf7d1b3fb 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -42,7 +42,10 @@ func InitTest(requireGitea bool) { if giteaRoot == "" { exitf("Environment variable $GITEA_ROOT not set") } + + setting.IsInTesting = true setting.AppWorkPath = giteaRoot + setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") if requireGitea { giteaBinary := "gitea" if setting.IsWindows { @@ -53,7 +56,6 @@ func InitTest(requireGitea bool) { exitf("Could not find gitea binary at %s", setting.AppPath) } } - giteaConf := os.Getenv("GITEA_CONF") if giteaConf == "" { // By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger. @@ -66,16 +68,12 @@ func InitTest(requireGitea bool) { exitf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify`) } } - - setting.IsInTesting = true - if !path.IsAbs(giteaConf) { - setting.CustomConf = path.Join(giteaRoot, giteaConf) + setting.CustomConf = filepath.Join(giteaRoot, giteaConf) } else { setting.CustomConf = giteaConf } - setting.SetCustomPathAndConf("", "", "") unittest.InitSettings() setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" _ = util.RemoveAll(repo_module.LocalCopyPath()) @@ -175,7 +173,7 @@ func InitTest(requireGitea bool) { defer db.Close() } - routers.GlobalInitInstalled(graceful.GetManager().HammerContext()) + routers.InitWebInstalled(graceful.GetManager().HammerContext()) } func PrepareTestEnv(t testing.TB, skip ...int) func() { @@ -240,10 +238,12 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() { } func PrintCurrentTest(t testing.TB, skip ...int) func() { - if len(skip) == 1 { - skip = []int{skip[0] + 1} + t.Helper() + actualSkip := 1 + if len(skip) > 0 { + actualSkip = skip[0] + 1 } - return testlogger.PrintCurrentTest(t, skip...) + return testlogger.PrintCurrentTest(t, actualSkip) } // Printf takes a format and args and prints the string to os.Stdout