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