mirror of
https://github.com/go-gitea/gitea
synced 2024-11-18 08:04:25 +00:00
e81ccc406b
Change all license headers to comply with REUSE specification. Fix #16132 Co-authored-by: flynnnnnnnnnn <flynnnnnnnnnn@github> Co-authored-by: John Olheiser <john.olheiser@gmail.com>
249 lines
6.7 KiB
Go
249 lines
6.7 KiB
Go
// 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)
|
|
}
|