diff --git a/cmd/convert.go b/cmd/convert.go index b9ed9f1627422..37118903af9ee 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -14,11 +14,22 @@ import ( ) // CmdConvert represents the available convert sub-command. +// +// FIXME: DEPRECATED: Remove in 1.20 var CmdConvert = cli.Command{ Name: "convert", Usage: "Convert the database", Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", Action: runConvert, + Hidden: true, +} + +// CmdDoctorConvert represents the available convert sub-command. +var CmdDoctorConvert = cli.Command{ + Name: "convert", + Usage: "Convert the database", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Action: runConvert, } func runConvert(ctx *cli.Context) error { diff --git a/cmd/doctor.go b/cmd/doctor.go index ceb6e3fbabe5a..142d18076c749 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -6,6 +6,7 @@ package cmd import ( "errors" "fmt" + "io" golog "log" "os" "strings" @@ -26,40 +27,11 @@ import ( 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", - }, - }, + Description: "Helper commands 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.", Subcommands: []cli.Command{ + cmdDoctorCheck, cmdRecreateTable, + CmdConvert, }, } @@ -81,6 +53,142 @@ You should back-up your database before doing this and ensure that your database Action: runRecreateTable, } +var cmdDoctorCheck = cli.Command{ + Name: "check", + Usage: "Runs doctor check(s)", + ArgsUsage: "[check-name]... : check(s) to run - leave blank to just run the default checks.\n\n", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "fix", + Usage: "Automatically fix what we can", + }, + cli.StringFlag{ + Name: "log-file", + Usage: `Name of the log file (if empty defaults to: "doctor.log"). Set to "-" to output to stdout, leave unset to not run logs`, + }, + cli.BoolFlag{ + Name: "color, H", + Usage: "Use color for outputted information", + }, + cli.BoolFlag{ + Name: "verbose, V", + Usage: "log to stdout, (shorthand for --log-file=-", + }, + }, + Action: runDoctorCheck, +} + +func init() { + sb := new(strings.Builder) + writeChecks(sb) + + CmdDoctor.Subcommands[0].ArgsUsage += sb.String() +} + +func writeChecks(sb io.Writer) { + _, _ = sb.Write([]byte("CHECKS:\n")) + w := tabwriter.NewWriter(sb, 0, 8, 1, ' ', 0) + _, _ = w.Write([]byte(" \tlist\tPrints list of available checks\n")) + _, _ = w.Write([]byte(" \tall\tRuns all available checks\n")) + _, _ = w.Write([]byte(" \tdefault\tRuns checks marked with (*) below\n")) + for _, check := range doctor.Checks { + _, _ = w.Write([]byte(" \t")) + _, _ = w.Write([]byte(check.Name)) + _, _ = w.Write([]byte{'\t'}) + if check.IsDefault { + _, _ = w.Write([]byte("(*) ")) + } + _, _ = w.Write([]byte(check.Title)) + _, _ = w.Write([]byte{'\n'}) + } + _ = w.Flush() +} + +func runDoctorCheck(ctx *cli.Context) error { + stdCtx, cancel := installSignals() + defer cancel() + + // Silence the default loggers + log.DelNamedLogger("console") + log.DelNamedLogger(log.DEFAULT) + + // Now setup our logger + 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))) + + // 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() + + var checks []*doctor.Check + if ctx.NArg() > 0 { + names := make([]string, 0, ctx.NArg()) + args := ctx.Args() + for i := 0; i < ctx.NArg(); i++ { + names = append(names, args.Get(i)) + } + + addDefault := false + all := false + for i, name := range names { + names[i] = strings.ToLower(strings.TrimSpace(name)) + switch names[i] { + case "default": + addDefault = true + case "list": + sb := new(strings.Builder) + writeChecks(sb) + logger.Info("%s", log.NewColoredValue(sb.String(), log.Reset)) + case "all": + all = true + } + } + + if all { + checks = doctor.Checks + } else { + 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) + } + } + } + + return doctor.RunChecks(stdCtx, logger, ctx.Bool("fix"), checks) +} + func runRecreateTable(ctx *cli.Context) error { // Redirect the default golog to here golog.SetFlags(0) @@ -126,8 +234,17 @@ func runRecreateTable(ctx *cli.Context) error { func setDoctorLogger(ctx *cli.Context) { logFile := ctx.String("log-file") - if !ctx.IsSet("log-file") { - logFile = "doctor.log" + if ctx.IsSet("log-file") { + if logFile == "" { + // verbose is set and log-file="" then assume that we mean --log-file=- + if ctx.Bool("verbose") { + logFile = "-" + } else { + logFile = "doctor.log" + } + } + } else if ctx.Bool("verbose") { + logFile = "-" } colorize := log.CanColorStdout if ctx.IsSet("color") { @@ -164,85 +281,3 @@ func setDoctorLogger(ctx *cli.Context) { 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) -} diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index f2e72d4fc036a..42cc5dce4bcb7 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -371,18 +371,22 @@ This command is idempotent. ### convert -Converts an existing MySQL database from utf8 to utf8mb4. +(DEPRECATED: from 1.19 use `gitea doctor convert` instead.) Converts an existing MySQL database from utf8 to utf8mb4. ### doctor -Diagnose the problems of current Gitea instance according the given configuration. -Currently there are a check list below: +Provides sub-commands that can fix problems with the current Gitea instance. + +### doctor check + +Diagnose and potentially fix problems with the current Gitea instance. Several checks are run by default, but additional ones can be run: + +- `gitea doctor check list` - will list all the available checks +- `gitea doctor check all` - will run all available checks +- `gitea doctor check default` - will run the default checks +- `gitea doctor check [check(s)]...` - will run the named checks -- Check if OpenSSH authorized_keys file id correct - When your Gitea instance support OpenSSH, your Gitea instance binary path will be written to `authorized_keys` - when there is any public key added or changed on your Gitea instance. - Sometimes if you moved or renamed your Gitea binary when upgrade and you haven't run `Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)` on your Admin Panel. Then all pull/push via SSH will not be work. - This check will help you to check if it works well. +Some problems can be automatically fixed by passing the `--fix` option. Extra logging can be set with `--log-file=...` or `--verbose`. For contributors, if you want to add more checks, you can write a new function like `func(ctx *cli.Context) ([]string, error)` and append it to `doctor.go`. @@ -397,8 +401,6 @@ var checklist = []check{ } ``` -This function will receive a command line context and return a list of details about the problems or error. - #### doctor recreate-table Sometimes when there are migrations the old columns and default values may be left @@ -429,6 +431,10 @@ gitea doctor recreate-table It is highly recommended to back-up your database before running these commands. +### doctor convert + +Converts an existing MySQL database from utf8 to utf8mb4. + ### manager Manage running server operations: