gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "context"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strings"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/setting"
  12. "github.com/urfave/cli/v3"
  13. )
  14. var cliHelpPrinterOld = cli.HelpPrinter
  15. func init() {
  16. cli.HelpPrinter = cliHelpPrinterNew
  17. }
  18. // cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position):
  19. // * ./gitea -c /dev/null -h
  20. // * ./gitea -c help /dev/null help
  21. // * ./gitea help -c /dev/null
  22. // * ./gitea help -c /dev/null web
  23. // * ./gitea help web -c /dev/null
  24. // * ./gitea web help -c /dev/null
  25. // * ./gitea web -h -c /dev/null
  26. func cliHelpPrinterNew(out io.Writer, templ string, data any) {
  27. cmd, _ := data.(*cli.Command)
  28. if cmd != nil {
  29. prepareWorkPathAndCustomConf(cmd)
  30. }
  31. cliHelpPrinterOld(out, templ, data)
  32. if setting.CustomConf != "" {
  33. _, _ = fmt.Fprintf(out, `
  34. DEFAULT CONFIGURATION:
  35. AppPath: %s
  36. WorkPath: %s
  37. CustomPath: %s
  38. ConfigFile: %s
  39. `, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
  40. }
  41. }
  42. func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
  43. originBefore := originCmd.Before
  44. originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
  45. prepareWorkPathAndCustomConf(cmd)
  46. if originBefore != nil {
  47. return originBefore(ctx, cmd)
  48. }
  49. return ctx, nil
  50. }
  51. }
  52. // prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
  53. // command line flags, environment variables, config file
  54. func prepareWorkPathAndCustomConf(cmd *cli.Command) {
  55. var args setting.ArgWorkPathAndCustomConf
  56. if cmd.IsSet("work-path") {
  57. args.WorkPath = cmd.String("work-path")
  58. }
  59. if cmd.IsSet("custom-path") {
  60. args.CustomPath = cmd.String("custom-path")
  61. }
  62. if cmd.IsSet("config") {
  63. args.CustomConf = cmd.String("config")
  64. }
  65. setting.InitWorkPathAndCommonConfig(os.Getenv, args)
  66. }
  67. type AppVersion struct {
  68. Version string
  69. Extra string
  70. }
  71. func NewMainApp(appVer AppVersion) *cli.Command {
  72. app := &cli.Command{}
  73. app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]"
  74. app.Usage = "A painless self-hosted Git service"
  75. app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
  76. app.Version = appVer.Version + appVer.Extra
  77. app.EnableShellCompletion = true
  78. app.Flags = []cli.Flag{
  79. &cli.StringFlag{
  80. Name: "work-path",
  81. Aliases: []string{"w"},
  82. TakesFile: true,
  83. Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
  84. },
  85. &cli.StringFlag{
  86. Name: "config",
  87. Aliases: []string{"c"},
  88. TakesFile: true,
  89. Value: setting.CustomConf,
  90. Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
  91. },
  92. &cli.StringFlag{
  93. Name: "custom-path",
  94. Aliases: []string{"C"},
  95. TakesFile: true,
  96. Usage: "Set custom path (defaults to '{WorkPath}/custom')",
  97. },
  98. }
  99. // these sub-commands need to use a config file
  100. subCmdWithConfig := []*cli.Command{
  101. CmdWeb,
  102. CmdServ,
  103. CmdHook,
  104. CmdKeys,
  105. CmdDump,
  106. CmdAdmin,
  107. CmdMigrate,
  108. CmdDoctor,
  109. CmdManager,
  110. CmdEmbedded,
  111. CmdMigrateStorage,
  112. CmdDumpRepository,
  113. CmdRestoreRepository,
  114. CmdActions,
  115. }
  116. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
  117. subCmdStandalone := []*cli.Command{
  118. cmdCert(),
  119. CmdGenerate,
  120. CmdDocs,
  121. }
  122. // TODO: we should eventually drop the default command,
  123. // but not sure whether it would break Windows users who used to double-click the EXE to run.
  124. app.DefaultCommand = CmdWeb.Name
  125. app.Before = PrepareConsoleLoggerLevel(log.INFO)
  126. for i := range subCmdWithConfig {
  127. prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])
  128. }
  129. app.Commands = append(app.Commands, subCmdWithConfig...)
  130. app.Commands = append(app.Commands, subCmdStandalone...)
  131. setting.InitGiteaEnvVars()
  132. return app
  133. }
  134. func RunMainApp(app *cli.Command, args ...string) error {
  135. ctx, cancel := installSignals()
  136. defer cancel()
  137. err := app.Run(ctx, args)
  138. if err == nil {
  139. return nil
  140. }
  141. if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
  142. // the cli package should already have output the error message, so just exit
  143. cli.OsExiter(1)
  144. return err
  145. }
  146. _, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
  147. cli.OsExiter(1)
  148. return err
  149. }