code.gitea.io/gitea@v1.22.3/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  
    14  	"github.com/urfave/cli/v2"
    15  )
    16  
    17  // cmdHelp is our own help subcommand with more information
    18  // Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" 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  func appGlobalFlags() []cli.Flag {
    51  	return []cli.Flag{
    52  		// make the builtin flags at the top
    53  		cli.HelpFlag,
    54  
    55  		// shared configuration flags, they are for global and for each sub-command at the same time
    56  		// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
    57  		// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
    58  		&cli.StringFlag{
    59  			Name:    "custom-path",
    60  			Aliases: []string{"C"},
    61  			Usage:   "Set custom path (defaults to '{WorkPath}/custom')",
    62  		},
    63  		&cli.StringFlag{
    64  			Name:    "config",
    65  			Aliases: []string{"c"},
    66  			Value:   setting.CustomConf,
    67  			Usage:   "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
    68  		},
    69  		&cli.StringFlag{
    70  			Name:    "work-path",
    71  			Aliases: []string{"w"},
    72  			Usage:   "Set Gitea's working path (defaults to the Gitea's binary directory)",
    73  		},
    74  	}
    75  }
    76  
    77  func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
    78  	command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
    79  	command.Action = prepareWorkPathAndCustomConf(command.Action)
    80  	command.HideHelp = true
    81  	if command.Name != "help" {
    82  		command.Subcommands = append(command.Subcommands, cmdHelp())
    83  	}
    84  	for i := range command.Subcommands {
    85  		prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
    86  	}
    87  }
    88  
    89  // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
    90  // 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
    91  func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
    92  	return func(ctx *cli.Context) error {
    93  		var args setting.ArgWorkPathAndCustomConf
    94  		// from children to parent, check the global flags
    95  		for _, curCtx := range ctx.Lineage() {
    96  			if curCtx.IsSet("work-path") && args.WorkPath == "" {
    97  				args.WorkPath = curCtx.String("work-path")
    98  			}
    99  			if curCtx.IsSet("custom-path") && args.CustomPath == "" {
   100  				args.CustomPath = curCtx.String("custom-path")
   101  			}
   102  			if curCtx.IsSet("config") && args.CustomConf == "" {
   103  				args.CustomConf = curCtx.String("config")
   104  			}
   105  		}
   106  		setting.InitWorkPathAndCommonConfig(os.Getenv, args)
   107  		if ctx.Bool("help") || action == nil {
   108  			// the default behavior of "urfave/cli": "nil action" means "show help"
   109  			return cmdHelp().Action(ctx)
   110  		}
   111  		return action(ctx)
   112  	}
   113  }
   114  
   115  type AppVersion struct {
   116  	Version string
   117  	Extra   string
   118  }
   119  
   120  func NewMainApp(appVer AppVersion) *cli.App {
   121  	app := cli.NewApp()
   122  	app.Name = "Gitea"
   123  	app.HelpName = "gitea"
   124  	app.Usage = "A painless self-hosted Git service"
   125  	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.`
   126  	app.Version = appVer.Version + appVer.Extra
   127  	app.EnableBashCompletion = true
   128  
   129  	// these sub-commands need to use config file
   130  	subCmdWithConfig := []*cli.Command{
   131  		cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
   132  		CmdWeb,
   133  		CmdServ,
   134  		CmdHook,
   135  		CmdKeys,
   136  		CmdDump,
   137  		CmdAdmin,
   138  		CmdMigrate,
   139  		CmdDoctor,
   140  		CmdManager,
   141  		CmdEmbedded,
   142  		CmdMigrateStorage,
   143  		CmdDumpRepository,
   144  		CmdRestoreRepository,
   145  		CmdActions,
   146  	}
   147  
   148  	// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
   149  	subCmdStandalone := []*cli.Command{
   150  		CmdCert,
   151  		CmdGenerate,
   152  		CmdDocs,
   153  	}
   154  
   155  	app.DefaultCommand = CmdWeb.Name
   156  
   157  	globalFlags := appGlobalFlags()
   158  	app.Flags = append(app.Flags, cli.VersionFlag)
   159  	app.Flags = append(app.Flags, globalFlags...)
   160  	app.HideHelp = true // use our own help action to show helps (with more information like default config)
   161  	app.Before = PrepareConsoleLoggerLevel(log.INFO)
   162  	for i := range subCmdWithConfig {
   163  		prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
   164  	}
   165  	app.Commands = append(app.Commands, subCmdWithConfig...)
   166  	app.Commands = append(app.Commands, subCmdStandalone...)
   167  
   168  	return app
   169  }
   170  
   171  func RunMainApp(app *cli.App, args ...string) error {
   172  	err := app.Run(args)
   173  	if err == nil {
   174  		return nil
   175  	}
   176  	if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
   177  		// the cli package should already have output the error message, so just exit
   178  		cli.OsExiter(1)
   179  		return err
   180  	}
   181  	_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
   182  	cli.OsExiter(1)
   183  	return err
   184  }