2020-01-11 17:24:57 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-01-11 17:24:57 +03:00
package cmd
import (
2022-08-19 04:27:27 +03:00
"errors"
2020-01-11 17:24:57 +03:00
"fmt"
2020-04-06 13:44:47 +03:00
golog "log"
2020-01-11 17:24:57 +03:00
"os"
"strings"
2020-04-06 13:44:47 +03:00
"text/tabwriter"
2020-01-11 17:24:57 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2020-04-06 13:44:47 +03:00
"code.gitea.io/gitea/models/migrations"
2022-11-02 11:54:36 +03:00
migrate_base "code.gitea.io/gitea/models/migrations/base"
2020-12-02 07:56:04 +03:00
"code.gitea.io/gitea/modules/doctor"
2020-01-30 05:00:27 +03:00
"code.gitea.io/gitea/modules/log"
2020-01-11 17:24:57 +03:00
"code.gitea.io/gitea/modules/setting"
2020-12-02 07:56:04 +03:00
2020-01-11 17:24:57 +03:00
"github.com/urfave/cli"
2021-11-17 15:34:35 +03:00
"xorm.io/xorm"
2020-01-11 17:24:57 +03:00
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli . Command {
Name : "doctor" ,
2022-05-20 06:39:52 +03:00
Usage : "Diagnose and optionally fix problems" ,
Description : "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage." ,
2020-01-11 17:24:57 +03:00
Action : runDoctor ,
2020-04-06 13:44:47 +03:00
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "list" ,
Usage : "List the available checks" ,
} ,
cli . BoolFlag {
Name : "default" ,
Usage : "Run the default checks (if neither --run or --all is set, this is the default behaviour)" ,
} ,
cli . StringSliceFlag {
Name : "run" ,
Usage : "Run the provided checks - (if --default is set, the default checks will also run)" ,
} ,
cli . BoolFlag {
Name : "all" ,
Usage : "Run all the available checks" ,
} ,
cli . BoolFlag {
Name : "fix" ,
Usage : "Automatically fix what we can" ,
} ,
cli . StringFlag {
Name : "log-file" ,
Usage : ` Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable ` ,
} ,
2020-12-02 07:56:04 +03:00
cli . BoolFlag {
Name : "color, H" ,
Usage : "Use color for outputted information" ,
} ,
2020-04-06 13:44:47 +03:00
} ,
2020-09-07 00:52:01 +03:00
Subcommands : [ ] cli . Command {
cmdRecreateTable ,
} ,
}
var cmdRecreateTable = cli . Command {
Name : "recreate-table" ,
Usage : "Recreate tables from XORM definitions and copy the data." ,
ArgsUsage : "[TABLE]... : (TABLEs to recreate - leave blank for all)" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "debug" ,
Usage : "Print SQL commands sent" ,
} ,
} ,
Description : ` The database definitions Gitea uses change across versions , sometimes changing default values and leaving old unused columns .
This command will cause Xorm to recreate tables , copying over the data and deleting the old table .
You should back - up your database before doing this and ensure that your database is up - to - date first . ` ,
Action : runRecreateTable ,
2020-01-11 17:24:57 +03:00
}
2020-09-07 00:52:01 +03:00
func runRecreateTable ( ctx * cli . Context ) error {
// Redirect the default golog to here
golog . SetFlags ( 0 )
golog . SetPrefix ( "" )
golog . SetOutput ( log . NewLoggerAsWriter ( "INFO" , log . GetLogger ( log . DEFAULT ) ) )
2023-05-04 06:55:35 +03:00
setting . Init ( & setting . Options { } )
2023-02-19 19:12:01 +03:00
setting . LoadDBSetting ( )
2020-09-07 00:52:01 +03:00
2023-02-19 19:12:01 +03:00
setting . Log . EnableXORMLog = ctx . Bool ( "debug" )
2020-09-07 00:52:01 +03:00
setting . Database . LogSQL = ctx . Bool ( "debug" )
2023-02-19 19:12:01 +03:00
// FIXME: don't use CfgProvider directly
setting . CfgProvider . Section ( "log" ) . Key ( "XORM" ) . SetValue ( "," )
2020-09-07 00:52:01 +03:00
2023-02-19 19:12:01 +03:00
setting . InitSQLLog ( ! ctx . Bool ( "debug" ) )
2021-11-07 06:11:27 +03:00
stdCtx , cancel := installSignals ( )
defer cancel ( )
if err := db . InitEngine ( stdCtx ) ; err != nil {
2020-09-07 00:52:01 +03:00
fmt . Println ( err )
fmt . Println ( "Check if you are using the right config file. You can use a --config directive to specify one." )
return nil
}
args := ctx . Args ( )
names := make ( [ ] string , 0 , ctx . NArg ( ) )
for i := 0 ; i < ctx . NArg ( ) ; i ++ {
names = append ( names , args . Get ( i ) )
}
2021-09-19 14:49:59 +03:00
beans , err := db . NamesToBean ( names ... )
2020-09-07 00:52:01 +03:00
if err != nil {
return err
}
2022-11-02 11:54:36 +03:00
recreateTables := migrate_base . RecreateTables ( beans ... )
2020-09-07 00:52:01 +03:00
2022-01-20 02:26:57 +03:00
return db . InitEngineWithMigration ( stdCtx , func ( x * xorm . Engine ) error {
2020-09-07 00:52:01 +03:00
if err := migrations . EnsureUpToDate ( x ) ; err != nil {
return err
}
return recreateTables ( x )
} )
}
2022-08-19 04:27:27 +03:00
func setDoctorLogger ( ctx * cli . Context ) {
2020-04-06 13:44:47 +03:00
logFile := ctx . String ( "log-file" )
if ! ctx . IsSet ( "log-file" ) {
logFile = "doctor.log"
}
2020-12-02 07:56:04 +03:00
colorize := log . CanColorStdout
if ctx . IsSet ( "color" ) {
colorize = ctx . Bool ( "color" )
}
2020-04-06 13:44:47 +03:00
if len ( logFile ) == 0 {
2020-12-02 07:56:04 +03:00
log . NewLogger ( 1000 , "doctor" , "console" , fmt . Sprintf ( ` { "level":"NONE","stacktracelevel":"NONE","colorize":%t} ` , colorize ) )
2022-08-19 04:27:27 +03:00
return
}
defer func ( ) {
recovered := recover ( )
if recovered == nil {
return
}
err , ok := recovered . ( error )
if ! ok {
panic ( recovered )
}
if errors . Is ( err , os . ErrPermission ) {
fmt . Fprintf ( os . Stderr , "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n" , logFile , err )
} else {
fmt . Fprintf ( os . Stderr , "ERROR: Unable to write logs to provided file: %s\n %v\n" , logFile , err )
}
fmt . Fprintf ( os . Stderr , "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n" )
log . NewLogger ( 1000 , "doctor" , "console" , fmt . Sprintf ( ` { "level":"NONE","stacktracelevel":"NONE","colorize":%t} ` , colorize ) )
} ( )
if logFile == "-" {
2020-12-02 07:56:04 +03:00
log . NewLogger ( 1000 , "doctor" , "console" , fmt . Sprintf ( ` { "level":"trace","stacktracelevel":"NONE","colorize":%t} ` , colorize ) )
2020-04-06 13:44:47 +03:00
} else {
log . NewLogger ( 1000 , "doctor" , "file" , fmt . Sprintf ( ` { "filename":%q,"level":"trace","stacktracelevel":"NONE"} ` , logFile ) )
}
2022-08-19 04:27:27 +03:00
}
func runDoctor ( ctx * cli . Context ) error {
stdCtx , cancel := installSignals ( )
defer cancel ( )
// Silence the default loggers
log . DelNamedLogger ( "console" )
log . DelNamedLogger ( log . DEFAULT )
// Now setup our own
setDoctorLogger ( ctx )
colorize := log . CanColorStdout
if ctx . IsSet ( "color" ) {
colorize = ctx . Bool ( "color" )
}
2020-04-06 13:44:47 +03:00
// Finally redirect the default golog to here
golog . SetFlags ( 0 )
golog . SetPrefix ( "" )
golog . SetOutput ( log . NewLoggerAsWriter ( "INFO" , log . GetLogger ( log . DEFAULT ) ) )
if ctx . IsSet ( "list" ) {
2020-12-02 07:56:04 +03:00
w := tabwriter . NewWriter ( os . Stdout , 0 , 8 , 1 , '\t' , 0 )
2020-04-06 13:44:47 +03:00
_ , _ = w . Write ( [ ] byte ( "Default\tName\tTitle\n" ) )
2020-12-02 07:56:04 +03:00
for _ , check := range doctor . Checks {
if check . IsDefault {
2020-04-06 13:44:47 +03:00
_ , _ = w . Write ( [ ] byte { '*' } )
}
_ , _ = w . Write ( [ ] byte { '\t' } )
2020-12-02 07:56:04 +03:00
_ , _ = w . Write ( [ ] byte ( check . Name ) )
2020-04-06 13:44:47 +03:00
_ , _ = w . Write ( [ ] byte { '\t' } )
2020-12-02 07:56:04 +03:00
_ , _ = w . Write ( [ ] byte ( check . Title ) )
2020-04-06 13:44:47 +03:00
_ , _ = w . Write ( [ ] byte { '\n' } )
}
return w . Flush ( )
}
2020-12-02 07:56:04 +03:00
var checks [ ] * doctor . Check
2020-04-06 13:44:47 +03:00
if ctx . Bool ( "all" ) {
2020-12-02 07:56:04 +03:00
checks = doctor . Checks
2020-04-06 13:44:47 +03:00
} else if ctx . IsSet ( "run" ) {
addDefault := ctx . Bool ( "default" )
names := ctx . StringSlice ( "run" )
for i , name := range names {
names [ i ] = strings . ToLower ( strings . TrimSpace ( name ) )
}
2020-12-02 07:56:04 +03:00
for _ , check := range doctor . Checks {
if addDefault && check . IsDefault {
2020-04-06 13:44:47 +03:00
checks = append ( checks , check )
continue
}
for _ , name := range names {
2020-12-02 07:56:04 +03:00
if name == check . Name {
2020-04-06 13:44:47 +03:00
checks = append ( checks , check )
break
}
}
}
} else {
2020-12-02 07:56:04 +03:00
for _ , check := range doctor . Checks {
if check . IsDefault {
2020-04-06 13:44:47 +03:00
checks = append ( checks , check )
}
}
}
2020-12-02 07:56:04 +03:00
// Now we can set up our own logger to return information about what the doctor is doing
if err := log . NewNamedLogger ( "doctorouter" ,
2022-06-16 12:10:33 +03:00
0 ,
2020-12-02 07:56:04 +03:00
"console" ,
"console" ,
fmt . Sprintf ( ` { "level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1} ` , colorize ) ) ; err != nil {
fmt . Println ( err )
2020-01-30 05:00:27 +03:00
return err
}
2020-05-29 16:24:15 +03:00
2020-12-02 07:56:04 +03:00
logger := log . GetLogger ( "doctorouter" )
defer logger . Close ( )
2021-11-07 06:11:27 +03:00
return doctor . RunChecks ( stdCtx , logger , ctx . Bool ( "fix" ) , checks )
2020-08-23 19:02:35 +03:00
}