2020-01-11 22:24:57 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2020-01-11 22:24:57 +08:00
package cmd
import (
2025-03-07 13:08:53 -08:00
"context"
2020-01-11 22:24:57 +08:00
"fmt"
2020-04-06 11:44:47 +01:00
golog "log"
2020-01-11 22:24:57 +08:00
"os"
2023-05-22 06:35:11 +08:00
"path/filepath"
2020-01-11 22:24:57 +08:00
"strings"
2020-04-06 11:44:47 +01:00
"text/tabwriter"
2020-01-11 22:24:57 +08:00
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2020-04-06 11:44:47 +01:00
"code.gitea.io/gitea/models/migrations"
2022-11-02 16:54:36 +08:00
migrate_base "code.gitea.io/gitea/models/migrations/base"
2023-12-11 23:55:10 +08:00
"code.gitea.io/gitea/modules/container"
2020-01-29 23:00:27 -03:00
"code.gitea.io/gitea/modules/log"
2020-01-11 22:24:57 +08:00
"code.gitea.io/gitea/modules/setting"
2024-01-20 10:07:31 +08:00
"code.gitea.io/gitea/services/doctor"
2020-12-02 04:56:04 +00:00
2025-06-10 14:35:12 +02:00
"github.com/urfave/cli/v3"
2021-11-17 20:34:35 +08:00
"xorm.io/xorm"
2020-01-11 22:24:57 +08:00
)
2026-03-25 10:53:13 -04:00
func newDoctorCommand ( ) * cli . Command {
return & cli . Command {
Name : "doctor" ,
Usage : "Diagnose and optionally fix problems, convert or re-create database tables" ,
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." ,
Commands : [ ] * cli . Command {
newDoctorCheckCommand ( ) ,
newRecreateTableCommand ( ) ,
newDoctorConvertCommand ( ) ,
} ,
}
2023-12-11 23:55:10 +08:00
}
2026-03-25 10:53:13 -04:00
func newDoctorCheckCommand ( ) * cli . Command {
return & cli . Command {
Name : "check" ,
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." ,
Action : runDoctorCheck ,
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 (no verbose log output by default). Set to "-" to output to stdout ` ,
} ,
& cli . BoolFlag {
Name : "color" ,
Aliases : [ ] string { "H" } ,
Usage : "Use color for outputted information" ,
} ,
2020-04-06 11:44:47 +01:00
} ,
2026-03-25 10:53:13 -04:00
}
2023-07-25 22:38:27 +08:00
}
2026-03-25 10:53:13 -04:00
func newRecreateTableCommand ( ) * cli . Command {
return & 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" ,
} ,
2020-09-06 22:52:01 +01:00
} ,
2026-03-25 10:53:13 -04:00
Description : ` The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
2020-09-06 22:52:01 +01:00
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. ` ,
2026-03-25 10:53:13 -04:00
Action : runRecreateTable ,
}
2020-01-11 22:24:57 +08:00
}
2025-06-10 14:35:12 +02:00
func runRecreateTable ( ctx context . Context , cmd * cli . Command ) error {
2020-09-06 22:52:01 +01:00
// Redirect the default golog to here
golog . SetFlags ( 0 )
golog . SetPrefix ( "" )
2023-05-22 06:35:11 +08:00
golog . SetOutput ( log . LoggerToWriter ( log . GetLogger ( log . DEFAULT ) . Info ) )
2020-09-06 22:52:01 +01:00
2025-06-10 14:35:12 +02:00
debug := cmd . Bool ( "debug" )
2023-06-21 13:50:26 +08:00
setting . MustInstalled ( )
2023-02-20 00:12:01 +08:00
setting . LoadDBSetting ( )
2020-09-06 22:52:01 +01:00
2023-05-22 06:35:11 +08:00
if debug {
setting . InitSQLLoggersForCli ( log . DEBUG )
} else {
setting . InitSQLLoggersForCli ( log . INFO )
}
2021-11-07 11:11:27 +08:00
2023-05-22 06:35:11 +08:00
setting . Database . LogSQL = debug
2025-06-10 14:35:12 +02:00
if err := db . InitEngine ( ctx ) ; err != nil {
2020-09-06 22:52:01 +01: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
}
2025-06-10 14:35:12 +02:00
args := cmd . Args ( )
names := make ( [ ] string , 0 , cmd . NArg ( ) )
for i := 0 ; i < cmd . NArg ( ) ; i ++ {
2020-09-06 22:52:01 +01:00
names = append ( names , args . Get ( i ) )
}
2021-09-19 19:49:59 +08:00
beans , err := db . NamesToBean ( names ... )
2020-09-06 22:52:01 +01:00
if err != nil {
return err
}
2022-11-02 16:54:36 +08:00
recreateTables := migrate_base . RecreateTables ( beans ... )
2020-09-06 22:52:01 +01:00
2025-08-27 19:00:01 +08:00
return db . InitEngineWithMigration ( context . Background ( ) , func ( ctx context . Context , x * xorm . Engine ) error {
2025-03-07 13:08:53 -08:00
if err := migrations . EnsureUpToDate ( ctx , x ) ; err != nil {
2020-09-06 22:52:01 +01:00
return err
}
return recreateTables ( x )
} )
}
2025-06-10 14:35:12 +02:00
func setupDoctorDefaultLogger ( cmd * cli . Command , colorize bool ) {
2023-05-22 06:35:11 +08:00
// Silence the default loggers
setupConsoleLogger ( log . FATAL , log . CanColorStderr , os . Stderr )
2025-06-10 14:35:12 +02:00
logFile := cmd . String ( "log-file" )
2025-03-29 22:32:28 +01:00
switch logFile {
case "" :
2023-07-25 22:38:27 +08:00
return // if no doctor log-file is set, do not show any log from default logger
2025-03-29 22:32:28 +01:00
case "-" :
2023-05-22 06:35:11 +08:00
setupConsoleLogger ( log . TRACE , colorize , os . Stdout )
2025-03-29 22:32:28 +01:00
default :
2023-05-22 06:35:11 +08:00
logFile , _ = filepath . Abs ( logFile )
writeMode := log . WriterMode { Level : log . TRACE , WriterOption : log . WriterFileOption { FileName : logFile } }
writer , err := log . NewEventWriter ( "console-to-file" , "file" , writeMode )
if err != nil {
log . FallbackErrorf ( "unable to create file log writer: %v" , err )
return
}
2023-06-28 14:02:06 +08:00
log . GetManager ( ) . GetLogger ( log . DEFAULT ) . ReplaceAllWriters ( writer )
2020-04-06 11:44:47 +01:00
}
2022-08-19 02:27:27 +01:00
}
2025-06-10 14:35:12 +02:00
func runDoctorCheck ( ctx context . Context , cmd * cli . Command ) error {
2022-08-19 02:27:27 +01:00
colorize := log . CanColorStdout
2025-06-10 14:35:12 +02:00
if cmd . IsSet ( "color" ) {
colorize = cmd . Bool ( "color" )
2022-08-19 02:27:27 +01:00
}
2020-04-06 11:44:47 +01:00
2025-06-10 14:35:12 +02:00
setupDoctorDefaultLogger ( cmd , colorize )
2023-05-22 06:35:11 +08:00
// Finally redirect the default golang's log to here
2020-04-06 11:44:47 +01:00
golog . SetFlags ( 0 )
golog . SetPrefix ( "" )
2023-05-22 06:35:11 +08:00
golog . SetOutput ( log . LoggerToWriter ( log . GetLogger ( log . DEFAULT ) . Info ) )
2020-04-06 11:44:47 +01:00
2025-06-10 14:35:12 +02:00
if cmd . IsSet ( "list" ) {
2020-12-02 04:56:04 +00:00
w := tabwriter . NewWriter ( os . Stdout , 0 , 8 , 1 , '\t' , 0 )
2020-04-06 11:44:47 +01:00
_ , _ = w . Write ( [ ] byte ( "Default\tName\tTitle\n" ) )
2023-12-11 23:55:10 +08:00
doctor . SortChecks ( doctor . Checks )
2020-12-02 04:56:04 +00:00
for _ , check := range doctor . Checks {
if check . IsDefault {
2020-04-06 11:44:47 +01:00
_ , _ = w . Write ( [ ] byte { '*' } )
}
_ , _ = w . Write ( [ ] byte { '\t' } )
2020-12-02 04:56:04 +00:00
_ , _ = w . Write ( [ ] byte ( check . Name ) )
2020-04-06 11:44:47 +01:00
_ , _ = w . Write ( [ ] byte { '\t' } )
2020-12-02 04:56:04 +00:00
_ , _ = w . Write ( [ ] byte ( check . Title ) )
2020-04-06 11:44:47 +01:00
_ , _ = w . Write ( [ ] byte { '\n' } )
}
return w . Flush ( )
}
2020-12-02 04:56:04 +00:00
var checks [ ] * doctor . Check
2025-06-10 14:35:12 +02:00
if cmd . Bool ( "all" ) {
2023-12-11 23:55:10 +08:00
checks = make ( [ ] * doctor . Check , len ( doctor . Checks ) )
copy ( checks , doctor . Checks )
2025-06-10 14:35:12 +02:00
} else if cmd . IsSet ( "run" ) {
addDefault := cmd . Bool ( "default" )
runNamesSet := container . SetOf ( cmd . StringSlice ( "run" ) ... )
2020-12-02 04:56:04 +00:00
for _ , check := range doctor . Checks {
2023-12-11 23:55:10 +08:00
if ( addDefault && check . IsDefault ) || runNamesSet . Contains ( check . Name ) {
2020-04-06 11:44:47 +01:00
checks = append ( checks , check )
2023-12-11 23:55:10 +08:00
runNamesSet . Remove ( check . Name )
2020-04-06 11:44:47 +01:00
}
}
2023-12-11 23:55:10 +08:00
if len ( runNamesSet ) > 0 {
return fmt . Errorf ( "unknown checks: %q" , strings . Join ( runNamesSet . Values ( ) , "," ) )
}
2020-04-06 11:44:47 +01:00
} else {
2020-12-02 04:56:04 +00:00
for _ , check := range doctor . Checks {
if check . IsDefault {
2020-04-06 11:44:47 +01:00
checks = append ( checks , check )
}
}
}
2025-06-10 14:35:12 +02:00
return doctor . RunChecks ( ctx , colorize , cmd . Bool ( "fix" ) , checks )
2020-08-23 11:02:35 -05:00
}