123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package cmd
-
- import (
- "errors"
- "fmt"
- golog "log"
- "os"
- "strings"
- "text/tabwriter"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/migrations"
- migrate_base "code.gitea.io/gitea/models/migrations/base"
- "code.gitea.io/gitea/modules/doctor"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/urfave/cli"
- "xorm.io/xorm"
- )
-
- // CmdDoctor represents the available doctor sub-command.
- var CmdDoctor = cli.Command{
- Name: "doctor",
- 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: runDoctor,
- 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`,
- },
- cli.BoolFlag{
- Name: "color, H",
- Usage: "Use color for outputted information",
- },
- },
- 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,
- }
-
- 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)))
-
- setting.LoadFromExisting()
- setting.InitDBConfig()
-
- setting.EnableXORMLog = ctx.Bool("debug")
- setting.Database.LogSQL = ctx.Bool("debug")
- setting.Cfg.Section("log").Key("XORM").SetValue(",")
-
- setting.NewXORMLogService(!ctx.Bool("debug"))
- stdCtx, cancel := installSignals()
- defer cancel()
-
- if err := db.InitEngine(stdCtx); err != nil {
- 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))
- }
-
- beans, err := db.NamesToBean(names...)
- if err != nil {
- return err
- }
- recreateTables := migrate_base.RecreateTables(beans...)
-
- return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
- if err := migrations.EnsureUpToDate(x); err != nil {
- return err
- }
- return recreateTables(x)
- })
- }
-
- func setDoctorLogger(ctx *cli.Context) {
- logFile := ctx.String("log-file")
- if !ctx.IsSet("log-file") {
- logFile = "doctor.log"
- }
- colorize := log.CanColorStdout
- if ctx.IsSet("color") {
- colorize = ctx.Bool("color")
- }
-
- if len(logFile) == 0 {
- log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
- 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 == "-" {
- log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
- } else {
- log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
- }
- }
-
- 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")
- }
-
- // 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") {
- w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
- _, _ = w.Write([]byte("Default\tName\tTitle\n"))
- for _, check := range doctor.Checks {
- if check.IsDefault {
- _, _ = w.Write([]byte{'*'})
- }
- _, _ = w.Write([]byte{'\t'})
- _, _ = w.Write([]byte(check.Name))
- _, _ = w.Write([]byte{'\t'})
- _, _ = w.Write([]byte(check.Title))
- _, _ = w.Write([]byte{'\n'})
- }
- return w.Flush()
- }
-
- var checks []*doctor.Check
- if ctx.Bool("all") {
- checks = doctor.Checks
- } 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))
- }
-
- for _, check := range doctor.Checks {
- if addDefault && check.IsDefault {
- checks = append(checks, check)
- continue
- }
- for _, name := range names {
- if name == check.Name {
- checks = append(checks, check)
- break
- }
- }
- }
- } else {
- for _, check := range doctor.Checks {
- if check.IsDefault {
- checks = append(checks, check)
- }
- }
- }
-
- // Now we can set up our own logger to return information about what the doctor is doing
- if err := log.NewNamedLogger("doctorouter",
- 0,
- "console",
- "console",
- fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil {
- fmt.Println(err)
- return err
- }
-
- logger := log.GetLogger("doctorouter")
- defer logger.Close()
- return doctor.RunChecks(stdCtx, logger, ctx.Bool("fix"), checks)
- }
|