forgejo/cmd/doctor.go
Earl Warren 55d8910255
Some checks failed
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Integration tests for the release process / release-simulation (push) Has been cancelled
chore(upgrade): urfave/cli from v2 to v3 (#8035)
urfave/cli  v2 will eventually become unmaintained, switch over to v3 which is the latest supported version.

Note: the `docs` command would be a lot of work to restore with v3 ([the package is still in alpha](https://github.com/urfave/cli-docs)) An alternative to avoid a breaking change would be to not upgrade from v2 to v3 for that reason alone.
Note: these commits were cherry-picked from https://code.forgejo.org/forgefriends/forgefriends
Note: it is best reviewed side by side with no display of whitespace changes (there are a lot of those when converting vars to func).

- a few functional changes were necessary and are noted in context in the file changes tab
- https://cli.urfave.org/migrate-v2-to-v3/ upgrade instructions were followed in the most minimal way possible
- upgrade gof3 to v3.10.8 which includes and upgrade from urfave/cli  v2 to urfave/cli  v3
- upgrade gitlab.com/gitlab-org/api/client-go v0.129.0 because it is an indirect dependency of gof3 and requires a change because of a deprecated field that otherwise triggers a lint error but nothing else otherwise
- verified that the [script](https://codeberg.org/forgejo/docs/src/branch/next/scripts/cli-docs.sh) that generates the [CLI documentation](https://codeberg.org/forgejo/docs/src/branch/next/scripts/cli-docs.sh) still works. There are cosmetic differences and the **help** subcommand is no longer advertised (although it is still supported) but the `--help` option is advertised as expected so it is fine.
- end-to-end tests [passed](https://code.forgejo.org/forgejo/end-to-end/pulls/667) (they use the Forgejo CLI to some extent)

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [x] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Breaking features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/8035): <!--number 8035 --><!--line 0 --><!--description VGhlIGBmb3JnZWpvIGRvY3NgIGNvbW1hbmQgaXMgZGVwcmVjYXRlZCBhbmQgQ0xJIGVycm9ycyBhcmUgbm93IGRpc3BsYXllZCBvbiBzdGRlcnIgaW5zdGVhZCBvZiBzdGRvdXQuIFRoZXNlIGJyZWFraW5nIGNoYW5nZXMgaGFwcGVuZWQgYmVjYXVzZSB0aGUgcGFja2FnZSB1c2VkIHRvIHBhcnNlIHRoZSBjb21tYW5kIGxpbmUgYXJndW1lbnRzIHdhcyBbdXBncmFkZWQgZnJvbSB2MiB0byB2M10oaHR0cHM6Ly9jbGkudXJmYXZlLm9yZy9taWdyYXRlLXYyLXRvLXYzLykuIEEgW3NlcGFyYXRlIHByb2plY3Qgd2FzIGluaXRpYXRlZF0oaHR0cHM6Ly9naXRodWIuY29tL3VyZmF2ZS9jbGktZG9jcykgdG8gcmUtaW1wbGVtZW50IHRoZSBgZG9jc2AgY29tbWFuZCwgYnV0IGl0IGlzIG5vdCB5ZXQgcHJvZHVjdGlvbiByZWFkeS4=-->The `forgejo docs` command is deprecated and CLI errors are now displayed on stderr instead of stdout. These breaking changes happened because the package used to parse the command line arguments was [upgraded from v2 to v3](https://cli.urfave.org/migrate-v2-to-v3/). A [separate project was initiated](https://github.com/urfave/cli-docs) to re-implement the `docs` command, but it is not yet production ready.<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: limiting-factor <limiting-factor@posteo.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8035
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2025-06-01 22:16:37 +02:00

232 lines
6.4 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
golog "log"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"forgejo.org/models/db"
"forgejo.org/models/migrations"
migrate_base "forgejo.org/models/migrations/base"
"forgejo.org/modules/container"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"forgejo.org/services/doctor"
"github.com/urfave/cli/v3"
)
// CmdDoctor represents the available doctor sub-command.
func cmdDoctor() *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 Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Commands: []*cli.Command{
cmdDoctorCheck(),
cmdRecreateTable(),
cmdDoctorConvert(),
},
}
}
func cmdDoctorCheck() *cli.Command {
return &cli.Command{
Name: "check",
Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Forgejo 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",
},
},
}
}
func cmdRecreateTable() *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",
},
},
Description: `The database definitions Forgejo 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(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
// Redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
debug := ctx.Bool("debug")
setting.MustInstalled()
setting.LoadDBSetting()
if debug {
setting.InitSQLLoggersForCli(log.DEBUG)
} else {
setting.InitSQLLoggersForCli(log.INFO)
}
setting.Database.LogSQL = debug
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 := range ctx.NArg() {
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 db.Engine) error {
engine, err := db.GetMasterEngine(x)
if err != nil {
return err
}
if err := migrations.EnsureUpToDate(engine); err != nil {
return err
}
return recreateTables(engine)
})
}
func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) {
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
logFile := ctx.String("log-file")
switch logFile {
case "":
return // if no doctor log-file is set, do not show any log from default logger
case "-":
setupConsoleLogger(log.TRACE, colorize, os.Stdout)
default:
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
}
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
}
func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
colorize := log.CanColorStdout
if ctx.IsSet("color") {
colorize = ctx.Bool("color")
}
setupDoctorDefaultLogger(ctx, colorize)
// Finally redirect the default golang's log to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks)
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 = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks)
} else if ctx.IsSet("run") {
addDefault := ctx.Bool("default")
runNamesSet := container.SetOf(ctx.StringSlice("run")...)
for _, check := range doctor.Checks {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
runNamesSet.Remove(check.Name)
}
}
if len(runNamesSet) > 0 {
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
}
} else {
for _, check := range doctor.Checks {
if check.IsDefault {
checks = append(checks, check)
}
}
}
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
}