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 }