code.gitea.io/gitea@v1.21.7/cmd/doctor.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	golog "log"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"text/tabwriter"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	"code.gitea.io/gitea/models/migrations"
    16  	migrate_base "code.gitea.io/gitea/models/migrations/base"
    17  	"code.gitea.io/gitea/modules/container"
    18  	"code.gitea.io/gitea/modules/doctor"
    19  	"code.gitea.io/gitea/modules/log"
    20  	"code.gitea.io/gitea/modules/setting"
    21  
    22  	"github.com/urfave/cli/v2"
    23  	"xorm.io/xorm"
    24  )
    25  
    26  // CmdDoctor represents the available doctor sub-command.
    27  var CmdDoctor = &cli.Command{
    28  	Name:        "doctor",
    29  	Usage:       "Diagnose and optionally fix problems",
    30  	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.",
    31  
    32  	Subcommands: []*cli.Command{
    33  		cmdDoctorCheck,
    34  		cmdRecreateTable,
    35  		cmdDoctorConvert,
    36  	},
    37  }
    38  
    39  var cmdDoctorCheck = &cli.Command{
    40  	Name:        "check",
    41  	Usage:       "Diagnose and optionally fix problems",
    42  	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.",
    43  	Action:      runDoctorCheck,
    44  	Flags: []cli.Flag{
    45  		&cli.BoolFlag{
    46  			Name:  "list",
    47  			Usage: "List the available checks",
    48  		},
    49  		&cli.BoolFlag{
    50  			Name:  "default",
    51  			Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
    52  		},
    53  		&cli.StringSliceFlag{
    54  			Name:  "run",
    55  			Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
    56  		},
    57  		&cli.BoolFlag{
    58  			Name:  "all",
    59  			Usage: "Run all the available checks",
    60  		},
    61  		&cli.BoolFlag{
    62  			Name:  "fix",
    63  			Usage: "Automatically fix what we can",
    64  		},
    65  		&cli.StringFlag{
    66  			Name:  "log-file",
    67  			Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
    68  		},
    69  		&cli.BoolFlag{
    70  			Name:    "color",
    71  			Aliases: []string{"H"},
    72  			Usage:   "Use color for outputted information",
    73  		},
    74  	},
    75  }
    76  
    77  var cmdRecreateTable = &cli.Command{
    78  	Name:      "recreate-table",
    79  	Usage:     "Recreate tables from XORM definitions and copy the data.",
    80  	ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
    81  	Flags: []cli.Flag{
    82  		&cli.BoolFlag{
    83  			Name:  "debug",
    84  			Usage: "Print SQL commands sent",
    85  		},
    86  	},
    87  	Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
    88  
    89  This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
    90  
    91  You should back-up your database before doing this and ensure that your database is up-to-date first.`,
    92  	Action: runRecreateTable,
    93  }
    94  
    95  func runRecreateTable(ctx *cli.Context) error {
    96  	stdCtx, cancel := installSignals()
    97  	defer cancel()
    98  
    99  	// Redirect the default golog to here
   100  	golog.SetFlags(0)
   101  	golog.SetPrefix("")
   102  	golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
   103  
   104  	debug := ctx.Bool("debug")
   105  	setting.MustInstalled()
   106  	setting.LoadDBSetting()
   107  
   108  	if debug {
   109  		setting.InitSQLLoggersForCli(log.DEBUG)
   110  	} else {
   111  		setting.InitSQLLoggersForCli(log.INFO)
   112  	}
   113  
   114  	setting.Database.LogSQL = debug
   115  	if err := db.InitEngine(stdCtx); err != nil {
   116  		fmt.Println(err)
   117  		fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
   118  		return nil
   119  	}
   120  
   121  	args := ctx.Args()
   122  	names := make([]string, 0, ctx.NArg())
   123  	for i := 0; i < ctx.NArg(); i++ {
   124  		names = append(names, args.Get(i))
   125  	}
   126  
   127  	beans, err := db.NamesToBean(names...)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	recreateTables := migrate_base.RecreateTables(beans...)
   132  
   133  	return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
   134  		if err := migrations.EnsureUpToDate(x); err != nil {
   135  			return err
   136  		}
   137  		return recreateTables(x)
   138  	})
   139  }
   140  
   141  func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
   142  	// Silence the default loggers
   143  	setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
   144  
   145  	logFile := ctx.String("log-file")
   146  	if logFile == "" {
   147  		return // if no doctor log-file is set, do not show any log from default logger
   148  	} else if logFile == "-" {
   149  		setupConsoleLogger(log.TRACE, colorize, os.Stdout)
   150  	} else {
   151  		logFile, _ = filepath.Abs(logFile)
   152  		writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
   153  		writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
   154  		if err != nil {
   155  			log.FallbackErrorf("unable to create file log writer: %v", err)
   156  			return
   157  		}
   158  		log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
   159  	}
   160  }
   161  
   162  func runDoctorCheck(ctx *cli.Context) error {
   163  	stdCtx, cancel := installSignals()
   164  	defer cancel()
   165  
   166  	colorize := log.CanColorStdout
   167  	if ctx.IsSet("color") {
   168  		colorize = ctx.Bool("color")
   169  	}
   170  
   171  	setupDoctorDefaultLogger(ctx, colorize)
   172  
   173  	// Finally redirect the default golang's log to here
   174  	golog.SetFlags(0)
   175  	golog.SetPrefix("")
   176  	golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
   177  
   178  	if ctx.IsSet("list") {
   179  		w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
   180  		_, _ = w.Write([]byte("Default\tName\tTitle\n"))
   181  		doctor.SortChecks(doctor.Checks)
   182  		for _, check := range doctor.Checks {
   183  			if check.IsDefault {
   184  				_, _ = w.Write([]byte{'*'})
   185  			}
   186  			_, _ = w.Write([]byte{'\t'})
   187  			_, _ = w.Write([]byte(check.Name))
   188  			_, _ = w.Write([]byte{'\t'})
   189  			_, _ = w.Write([]byte(check.Title))
   190  			_, _ = w.Write([]byte{'\n'})
   191  		}
   192  		return w.Flush()
   193  	}
   194  
   195  	var checks []*doctor.Check
   196  	if ctx.Bool("all") {
   197  		checks = make([]*doctor.Check, len(doctor.Checks))
   198  		copy(checks, doctor.Checks)
   199  	} else if ctx.IsSet("run") {
   200  		addDefault := ctx.Bool("default")
   201  		runNamesSet := container.SetOf(ctx.StringSlice("run")...)
   202  		for _, check := range doctor.Checks {
   203  			if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
   204  				checks = append(checks, check)
   205  				runNamesSet.Remove(check.Name)
   206  			}
   207  		}
   208  		if len(runNamesSet) > 0 {
   209  			return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
   210  		}
   211  	} else {
   212  		for _, check := range doctor.Checks {
   213  			if check.IsDefault {
   214  				checks = append(checks, check)
   215  			}
   216  		}
   217  	}
   218  	return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
   219  }