Files

220 lines
6.3 KiB
Go
Raw Permalink Normal View History

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
2020-04-06 11:44:47 +01:00
golog "log"
"os"
2023-05-22 06:35:11 +08:00
"path/filepath"
"strings"
2020-04-06 11:44:47 +01:00
"text/tabwriter"
"code.gitea.io/gitea/models/db"
2020-04-06 11:44:47 +01:00
"code.gitea.io/gitea/models/migrations"
2022-11-02 16:54:36 +08:00
migrate_base "code.gitea.io/gitea/models/migrations/base"
2023-12-11 23:55:10 +08:00
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/doctor"
2020-12-02 04:56:04 +00:00
2025-06-10 14:35:12 +02:00
"github.com/urfave/cli/v3"
"xorm.io/xorm"
)
func newDoctorCommand() *cli.Command {
return &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
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.",
Commands: []*cli.Command{
newDoctorCheckCommand(),
newRecreateTableCommand(),
newDoctorConvertCommand(),
},
}
2023-12-11 23:55:10 +08:00
}
func newDoctorCheckCommand() *cli.Command {
return &cli.Command{
Name: "check",
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: runDoctorCheck,
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 (no verbose log output by default). Set to "-" to output to stdout`,
},
&cli.BoolFlag{
Name: "color",
Aliases: []string{"H"},
Usage: "Use color for outputted information",
},
2020-04-06 11:44:47 +01:00
},
}
}
func newRecreateTableCommand() *cli.Command {
return &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",
},
2020-09-06 22:52:01 +01:00
},
Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
2020-09-06 22:52:01 +01:00
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,
}
}
2025-06-10 14:35:12 +02:00
func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
2020-09-06 22:52:01 +01:00
// Redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
2023-05-22 06:35:11 +08:00
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
2020-09-06 22:52:01 +01:00
2025-06-10 14:35:12 +02:00
debug := cmd.Bool("debug")
2023-06-21 13:50:26 +08:00
setting.MustInstalled()
setting.LoadDBSetting()
2020-09-06 22:52:01 +01:00
2023-05-22 06:35:11 +08:00
if debug {
setting.InitSQLLoggersForCli(log.DEBUG)
} else {
setting.InitSQLLoggersForCli(log.INFO)
}
2021-11-07 11:11:27 +08:00
2023-05-22 06:35:11 +08:00
setting.Database.LogSQL = debug
2025-06-10 14:35:12 +02:00
if err := db.InitEngine(ctx); err != nil {
2020-09-06 22:52:01 +01:00
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
}
2025-06-10 14:35:12 +02:00
args := cmd.Args()
names := make([]string, 0, cmd.NArg())
for i := 0; i < cmd.NArg(); i++ {
2020-09-06 22:52:01 +01:00
names = append(names, args.Get(i))
}
beans, err := db.NamesToBean(names...)
2020-09-06 22:52:01 +01:00
if err != nil {
return err
}
2022-11-02 16:54:36 +08:00
recreateTables := migrate_base.RecreateTables(beans...)
2020-09-06 22:52:01 +01:00
2025-08-27 19:00:01 +08:00
return db.InitEngineWithMigration(context.Background(), func(ctx context.Context, x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(ctx, x); err != nil {
2020-09-06 22:52:01 +01:00
return err
}
return recreateTables(x)
})
}
2025-06-10 14:35:12 +02:00
func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
2023-05-22 06:35:11 +08:00
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
2025-06-10 14:35:12 +02:00
logFile := cmd.String("log-file")
2025-03-29 22:32:28 +01:00
switch logFile {
case "":
return // if no doctor log-file is set, do not show any log from default logger
2025-03-29 22:32:28 +01:00
case "-":
2023-05-22 06:35:11 +08:00
setupConsoleLogger(log.TRACE, colorize, os.Stdout)
2025-03-29 22:32:28 +01:00
default:
2023-05-22 06:35:11 +08:00
logFile, _ = filepath.Abs(logFile)
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
if err != nil {
log.FallbackErrorf("unable to create file log writer: %v", err)
return
}
2023-06-28 14:02:06 +08:00
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
2020-04-06 11:44:47 +01:00
}
2022-08-19 02:27:27 +01:00
}
2025-06-10 14:35:12 +02:00
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
2022-08-19 02:27:27 +01:00
colorize := log.CanColorStdout
2025-06-10 14:35:12 +02:00
if cmd.IsSet("color") {
colorize = cmd.Bool("color")
2022-08-19 02:27:27 +01:00
}
2020-04-06 11:44:47 +01:00
2025-06-10 14:35:12 +02:00
setupDoctorDefaultLogger(cmd, colorize)
2023-05-22 06:35:11 +08:00
// Finally redirect the default golang's log to here
2020-04-06 11:44:47 +01:00
golog.SetFlags(0)
golog.SetPrefix("")
2023-05-22 06:35:11 +08:00
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
2020-04-06 11:44:47 +01:00
2025-06-10 14:35:12 +02:00
if cmd.IsSet("list") {
2020-12-02 04:56:04 +00:00
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
2020-04-06 11:44:47 +01:00
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
2023-12-11 23:55:10 +08:00
doctor.SortChecks(doctor.Checks)
2020-12-02 04:56:04 +00:00
for _, check := range doctor.Checks {
if check.IsDefault {
2020-04-06 11:44:47 +01:00
_, _ = w.Write([]byte{'*'})
}
_, _ = w.Write([]byte{'\t'})
2020-12-02 04:56:04 +00:00
_, _ = w.Write([]byte(check.Name))
2020-04-06 11:44:47 +01:00
_, _ = w.Write([]byte{'\t'})
2020-12-02 04:56:04 +00:00
_, _ = w.Write([]byte(check.Title))
2020-04-06 11:44:47 +01:00
_, _ = w.Write([]byte{'\n'})
}
return w.Flush()
}
2020-12-02 04:56:04 +00:00
var checks []*doctor.Check
2025-06-10 14:35:12 +02:00
if cmd.Bool("all") {
2023-12-11 23:55:10 +08:00
checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks)
2025-06-10 14:35:12 +02:00
} else if cmd.IsSet("run") {
addDefault := cmd.Bool("default")
runNamesSet := container.SetOf(cmd.StringSlice("run")...)
2020-12-02 04:56:04 +00:00
for _, check := range doctor.Checks {
2023-12-11 23:55:10 +08:00
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
2020-04-06 11:44:47 +01:00
checks = append(checks, check)
2023-12-11 23:55:10 +08:00
runNamesSet.Remove(check.Name)
2020-04-06 11:44:47 +01:00
}
}
2023-12-11 23:55:10 +08:00
if len(runNamesSet) > 0 {
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
}
2020-04-06 11:44:47 +01:00
} else {
2020-12-02 04:56:04 +00:00
for _, check := range doctor.Checks {
if check.IsDefault {
2020-04-06 11:44:47 +01:00
checks = append(checks, check)
}
}
}
2025-06-10 14:35:12 +02:00
return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks)
2020-08-23 11:02:35 -05:00
}