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  }