// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2016 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT // Gitea (git with a cup of tea) is a painless self-hosted Git Service. package main // import "code.gitea.io/gitea" import ( "context" "fmt" "os" "path/filepath" "runtime" "strings" "time" "code.gitea.io/gitea/cmd" "code.gitea.io/gitea/cmd/forgejo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" // register supported doc types _ "code.gitea.io/gitea/modules/markup/asciicast" _ "code.gitea.io/gitea/modules/markup/console" _ "code.gitea.io/gitea/modules/markup/csv" _ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/orgmode" "github.com/urfave/cli" ) var ( // Version holds the current Gitea version Version = "development" // Tags holds the build tags used Tags = "" // MakeVersion holds the current Make version if built with make MakeVersion = "" ) func init() { setting.AppVer = Version setting.AppBuiltWith = formatBuiltWith() setting.AppStartTime = time.Now().UTC() } // cmdHelp is our own help subcommand with more information // test cases: // ./gitea help // ./gitea -h // ./gitea web help // ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info) // ./gitea admin // ./gitea admin help // ./gitea admin auth help // ./gitea -c /tmp/app.ini -h // ./gitea -c /tmp/app.ini help // ./gitea help -c /tmp/app.ini // GITEA_WORK_DIR=/tmp ./gitea help // GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other // GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini var cmdHelp = cli.Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *cli.Context) (err error) { args := c.Args() if args.Present() { err = cli.ShowCommandHelp(c, args.First()) } else { err = cli.ShowAppHelp(c) } _, _ = fmt.Fprintf(c.App.Writer, ` DEFAULT CONFIGURATION: AppPath: %s WorkPath: %s CustomPath: %s ConfigFile: %s `, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf) return err }, } func main() { path, err := os.Executable() if err != nil { panic(err) } executable := filepath.Base(path) var subCmds []cli.Command // // If the executable is forgejo-cli, provide a Forgejo specific CLI // that is NOT compatible with Gitea. // if executable == "forgejo-cli" { subCmds = []cli.Command{ forgejo.CmdActions(context.Background()), } } else { // // Otherwise provide a Gitea compatible CLI which includes Forgejo // specific additions under the forgejo-cli subcommand. It allows // admins to migration from Gitea to Forgejo by replacing the gitea // binary and rename it to forgejo if they want. // subCmds = []cli.Command{ forgejo.CmdForgejo(context.Background()), cmd.CmdActions, } } mainApp(subCmds...) } func mainApp(subCmds ...cli.Command) { app := cli.NewApp() app.Name = "Gitea" app.Usage = "A painless self-hosted Git service" 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".` app.Version = Version + formatBuiltWith() app.EnableBashCompletion = true // these sub-commands need to use config file subCmdWithIni := []cli.Command{ cmd.CmdWeb, cmd.CmdServ, cmd.CmdHook, cmd.CmdDump, cmd.CmdAdmin, cmd.CmdMigrate, cmd.CmdKeys, cmd.CmdConvert, cmd.CmdDoctor, cmd.CmdF3, cmd.CmdManager, cmd.CmdEmbedded, cmd.CmdMigrateStorage, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so } subCmdWithIni = append(subCmdWithIni, subCmds...) // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []cli.Command{ cmd.CmdCert, cmd.CmdGenerate, cmd.CmdDocs, } // shared configuration flags, they are for global and for each sub-command at the same time // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore. globalFlags := []cli.Flag{ cli.HelpFlag, cli.StringFlag{ Name: "custom-path, C", Usage: "Set custom path (defaults to '{WorkPath}/custom')", }, cli.StringFlag{ Name: "config, c", Value: setting.CustomConf, Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')", }, cli.StringFlag{ Name: "work-path, w", Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)", }, } // Set the default to be equivalent to cmdWeb and add the default flags app.Flags = append(app.Flags, globalFlags...) app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action) app.HideHelp = true // use our own help action to show helps (with more information like default config) app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithIni { prepareSubcommands(&subCmdWithIni[i], globalFlags) } app.Commands = append(app.Commands, subCmdWithIni...) app.Commands = append(app.Commands, subCmdStandalone...) err := app.Run(os.Args) if err != nil { _, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err) } log.GetManager().Close() } func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) { command.Flags = append(command.Flags, defaultFlags...) command.Action = prepareWorkPathAndCustomConf(command.Action) command.HideHelp = true if command.Name != "help" { command.Subcommands = append(command.Subcommands, cmdHelp) } for i := range command.Subcommands { prepareSubcommands(&command.Subcommands[i], defaultFlags) } } // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // 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 func prepareWorkPathAndCustomConf(action any) func(ctx *cli.Context) error { return func(ctx *cli.Context) error { var args setting.ArgWorkPathAndCustomConf curCtx := ctx for curCtx != nil { if curCtx.IsSet("work-path") && args.WorkPath == "" { args.WorkPath = curCtx.String("work-path") } if curCtx.IsSet("custom-path") && args.CustomPath == "" { args.CustomPath = curCtx.String("custom-path") } if curCtx.IsSet("config") && args.CustomConf == "" { args.CustomConf = curCtx.String("config") } curCtx = curCtx.Parent() } setting.InitWorkPathAndCommonConfig(os.Getenv, args) if ctx.Bool("help") || action == nil { // the default behavior of "urfave/cli": "nil action" means "show help" return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx) } return action.(func(*cli.Context) error)(ctx) } } func formatBuiltWith() string { version := runtime.Version() if len(MakeVersion) > 0 { version = MakeVersion + ", " + runtime.Version() } if len(Tags) == 0 { return " built with " + version } return " built with " + version + " : " + strings.ReplaceAll(Tags, " ", ", ") }