You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.go 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "fmt"
  6. "os"
  7. "strings"
  8. "code.gitea.io/gitea/modules/log"
  9. "code.gitea.io/gitea/modules/setting"
  10. "code.gitea.io/gitea/modules/util"
  11. "github.com/urfave/cli/v2"
  12. )
  13. // cmdHelp is our own help subcommand with more information
  14. func cmdHelp() *cli.Command {
  15. c := &cli.Command{
  16. Name: "help",
  17. Aliases: []string{"h"},
  18. Usage: "Shows a list of commands or help for one command",
  19. ArgsUsage: "[command]",
  20. Action: func(c *cli.Context) (err error) {
  21. lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
  22. targetCmdIdx := 0
  23. if c.Command.Name == "help" {
  24. targetCmdIdx = 1
  25. }
  26. if lineage[targetCmdIdx+1].Command != nil {
  27. err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
  28. } else {
  29. err = cli.ShowAppHelp(c)
  30. }
  31. _, _ = fmt.Fprintf(c.App.Writer, `
  32. DEFAULT CONFIGURATION:
  33. AppPath: %s
  34. WorkPath: %s
  35. CustomPath: %s
  36. ConfigFile: %s
  37. `, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
  38. return err
  39. },
  40. }
  41. return c
  42. }
  43. var helpFlag = cli.HelpFlag
  44. func init() {
  45. // cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
  46. }
  47. func appGlobalFlags() []cli.Flag {
  48. return []cli.Flag{
  49. // make the builtin flags at the top
  50. helpFlag,
  51. // shared configuration flags, they are for global and for each sub-command at the same time
  52. // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
  53. // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
  54. &cli.StringFlag{
  55. Name: "custom-path",
  56. Aliases: []string{"C"},
  57. Usage: "Set custom path (defaults to '{WorkPath}/custom')",
  58. },
  59. &cli.StringFlag{
  60. Name: "config",
  61. Aliases: []string{"c"},
  62. Value: setting.CustomConf,
  63. Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
  64. },
  65. &cli.StringFlag{
  66. Name: "work-path",
  67. Aliases: []string{"w"},
  68. Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
  69. },
  70. }
  71. }
  72. func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
  73. command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
  74. command.Action = prepareWorkPathAndCustomConf(command.Action)
  75. command.HideHelp = true
  76. if command.Name != "help" {
  77. command.Subcommands = append(command.Subcommands, cmdHelp())
  78. }
  79. for i := range command.Subcommands {
  80. prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
  81. }
  82. }
  83. // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
  84. // 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
  85. func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
  86. return func(ctx *cli.Context) error {
  87. var args setting.ArgWorkPathAndCustomConf
  88. // from children to parent, check the global flags
  89. for _, curCtx := range ctx.Lineage() {
  90. if curCtx.IsSet("work-path") && args.WorkPath == "" {
  91. args.WorkPath = curCtx.String("work-path")
  92. }
  93. if curCtx.IsSet("custom-path") && args.CustomPath == "" {
  94. args.CustomPath = curCtx.String("custom-path")
  95. }
  96. if curCtx.IsSet("config") && args.CustomConf == "" {
  97. args.CustomConf = curCtx.String("config")
  98. }
  99. }
  100. setting.InitWorkPathAndCommonConfig(os.Getenv, args)
  101. if ctx.Bool("help") || action == nil {
  102. // the default behavior of "urfave/cli": "nil action" means "show help"
  103. return cmdHelp().Action(ctx)
  104. }
  105. return action(ctx)
  106. }
  107. }
  108. func NewMainApp(version, versionExtra string) *cli.App {
  109. app := cli.NewApp()
  110. app.Name = "Gitea"
  111. app.Usage = "A painless self-hosted Git service"
  112. 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".`
  113. app.Version = version + versionExtra
  114. app.EnableBashCompletion = true
  115. // these sub-commands need to use config file
  116. subCmdWithConfig := []*cli.Command{
  117. CmdWeb,
  118. CmdServ,
  119. CmdHook,
  120. CmdDump,
  121. CmdAdmin,
  122. CmdMigrate,
  123. CmdKeys,
  124. CmdDoctor,
  125. CmdManager,
  126. CmdEmbedded,
  127. CmdMigrateStorage,
  128. CmdDumpRepository,
  129. CmdRestoreRepository,
  130. CmdActions,
  131. cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
  132. }
  133. cmdConvert := util.ToPointer(*cmdDoctorConvert)
  134. cmdConvert.Hidden = true // still support the legacy "./gitea doctor" by the hidden sub-command, remove it in next release
  135. subCmdWithConfig = append(subCmdWithConfig, cmdConvert)
  136. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
  137. subCmdStandalone := []*cli.Command{
  138. CmdCert,
  139. CmdGenerate,
  140. CmdDocs,
  141. }
  142. app.DefaultCommand = CmdWeb.Name
  143. globalFlags := appGlobalFlags()
  144. app.Flags = append(app.Flags, cli.VersionFlag)
  145. app.Flags = append(app.Flags, globalFlags...)
  146. app.HideHelp = true // use our own help action to show helps (with more information like default config)
  147. app.Before = PrepareConsoleLoggerLevel(log.INFO)
  148. for i := range subCmdWithConfig {
  149. prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
  150. }
  151. app.Commands = append(app.Commands, subCmdWithConfig...)
  152. app.Commands = append(app.Commands, subCmdStandalone...)
  153. return app
  154. }
  155. func RunMainApp(app *cli.App, args ...string) error {
  156. err := app.Run(args)
  157. if err == nil {
  158. return nil
  159. }
  160. if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
  161. // the cli package should already have output the error message, so just exit
  162. cli.OsExiter(1)
  163. return err
  164. }
  165. _, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
  166. cli.OsExiter(1)
  167. return err
  168. }