2023-07-21 11:28:19 +02:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
2023-07-09 14:52:21 +02:00
"context"
2023-07-21 11:28:19 +02:00
"fmt"
"os"
2023-07-09 14:52:21 +02:00
"path/filepath"
2023-08-05 17:36:45 +02:00
"strings"
2023-07-21 11:28:19 +02:00
2023-07-09 14:52:21 +02:00
"code.gitea.io/gitea/cmd/forgejo"
2023-07-21 11:28:19 +02:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v2"
)
// cmdHelp is our own help subcommand with more information
2023-12-15 16:49:01 +01:00
// 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
2023-07-21 11:28:19 +02:00
func cmdHelp ( ) * cli . Command {
c := & 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 ) {
2023-07-25 16:38:27 +02:00
lineage := c . Lineage ( ) // The order is from child to parent: help, doctor, Gitea, {Command:nil}
targetCmdIdx := 0
if c . Command . Name == "help" {
targetCmdIdx = 1
}
if lineage [ targetCmdIdx + 1 ] . Command != nil {
err = cli . ShowCommandHelp ( lineage [ targetCmdIdx + 1 ] , lineage [ targetCmdIdx ] . Command . Name )
2023-07-21 11:28:19 +02:00
} 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
} ,
}
return c
}
func appGlobalFlags ( ) [ ] cli . Flag {
return [ ] cli . Flag {
// make the builtin flags at the top
2023-12-15 16:49:01 +01:00
cli . HelpFlag ,
2023-07-21 11:28:19 +02:00
// 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.
& cli . StringFlag {
Name : "custom-path" ,
Aliases : [ ] string { "C" } ,
Usage : "Set custom path (defaults to '{WorkPath}/custom')" ,
} ,
& cli . StringFlag {
Name : "config" ,
Aliases : [ ] string { "c" } ,
Value : setting . CustomConf ,
Usage : "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')" ,
} ,
& cli . StringFlag {
Name : "work-path" ,
Aliases : [ ] string { "w" } ,
2023-01-09 14:01:00 +01:00
Usage : "Set Forgejo's working path (defaults to the directory of the Forgejo binary)" ,
2023-07-21 11:28:19 +02:00
} ,
}
}
func prepareSubcommandWithConfig ( command * cli . Command , globalFlags [ ] cli . Flag ) {
command . Flags = append ( append ( [ ] cli . Flag { } , globalFlags ... ) , command . Flags ... )
command . Action = prepareWorkPathAndCustomConf ( command . Action )
command . HideHelp = true
if command . Name != "help" {
command . Subcommands = append ( command . Subcommands , cmdHelp ( ) )
}
for i := range command . Subcommands {
prepareSubcommandWithConfig ( command . Subcommands [ i ] , globalFlags )
}
}
// 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 cli . ActionFunc ) func ( ctx * cli . Context ) error {
return func ( ctx * cli . Context ) error {
var args setting . ArgWorkPathAndCustomConf
2023-07-25 16:38:27 +02:00
// from children to parent, check the global flags
for _ , curCtx := range ctx . Lineage ( ) {
2023-07-21 11:28:19 +02:00
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" )
}
}
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 ( ctx )
}
return action ( ctx )
}
}
2023-08-05 17:36:45 +02:00
func NewMainApp ( version , versionExtra string ) * cli . App {
2023-07-09 14:52:21 +02:00
path , err := os . Executable ( )
if err != nil {
panic ( err )
}
executable := filepath . Base ( path )
var subCmdsStandalone [ ] * cli . Command = make ( [ ] * cli . Command , 0 , 10 )
var subCmdWithConfig [ ] * cli . Command = make ( [ ] * cli . Command , 0 , 10 )
//
// If the executable is forgejo-cli, provide a Forgejo specific CLI
// that is NOT compatible with Gitea.
//
if executable == "forgejo-cli" {
subCmdsStandalone = append ( subCmdsStandalone , 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.
//
subCmdsStandalone = append ( subCmdsStandalone , forgejo . CmdForgejo ( context . Background ( ) ) )
subCmdWithConfig = append ( subCmdWithConfig , CmdActions )
}
return innerNewMainApp ( version , versionExtra , subCmdsStandalone , subCmdWithConfig )
}
func innerNewMainApp ( version , versionExtra string , subCmdsStandaloneArgs , subCmdWithConfigArgs [ ] * cli . Command ) * cli . App {
2023-07-21 11:28:19 +02:00
app := cli . NewApp ( )
2023-02-24 13:38:29 +01:00
app . HelpName = "forgejo"
app . Name = "Forgejo"
app . Usage = "Beyond coding. We forge."
app . Description = ` By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web". `
2023-08-05 17:36:45 +02:00
app . Version = version + versionExtra
2023-07-21 11:28:19 +02:00
app . EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := [ ] * cli . Command {
2023-12-15 16:49:01 +01:00
cmdHelp ( ) , // the "help" sub-command was used to show the more information for "work path" and "custom config"
2023-07-21 11:28:19 +02:00
CmdWeb ,
CmdServ ,
CmdHook ,
2023-12-15 16:49:01 +01:00
CmdKeys ,
2023-07-21 11:28:19 +02:00
CmdDump ,
CmdAdmin ,
CmdMigrate ,
CmdDoctor ,
CmdManager ,
CmdEmbedded ,
CmdMigrateStorage ,
CmdDumpRepository ,
CmdRestoreRepository ,
}
2023-07-09 14:52:21 +02:00
subCmdWithConfig = append ( subCmdWithConfig , subCmdWithConfigArgs ... )
2023-07-21 11:28:19 +02:00
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := [ ] * cli . Command {
CmdCert ,
CmdGenerate ,
CmdDocs ,
}
2023-07-09 14:52:21 +02:00
subCmdStandalone = append ( subCmdStandalone , subCmdsStandaloneArgs ... )
2023-07-21 11:28:19 +02:00
app . DefaultCommand = CmdWeb . Name
globalFlags := appGlobalFlags ( )
2023-08-05 15:24:49 +02:00
app . Flags = append ( app . Flags , cli . VersionFlag )
2023-07-21 11:28:19 +02:00
app . Flags = append ( app . Flags , globalFlags ... )
app . HideHelp = true // use our own help action to show helps (with more information like default config)
app . Before = PrepareConsoleLoggerLevel ( log . INFO )
for i := range subCmdWithConfig {
prepareSubcommandWithConfig ( subCmdWithConfig [ i ] , globalFlags )
}
app . Commands = append ( app . Commands , subCmdWithConfig ... )
app . Commands = append ( app . Commands , subCmdStandalone ... )
return app
}
2023-08-05 17:36:45 +02:00
func RunMainApp ( app * cli . App , args ... string ) error {
err := app . Run ( args )
if err == nil {
return nil
}
if strings . HasPrefix ( err . Error ( ) , "flag provided but not defined:" ) {
// the cli package should already have output the error message, so just exit
cli . OsExiter ( 1 )
return err
}
_ , _ = fmt . Fprintf ( app . ErrWriter , "Command error: %v\n" , err )
cli . OsExiter ( 1 )
return err
}