 55d8910255
			
		
	
	
	55d8910255
	
	
	
		
			
			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>
		
			
				
	
	
		
			522 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			522 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 The Gogs Authors. All rights reserved.
 | |
| // Copyright 2016 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"forgejo.org/models/db"
 | |
| 	"forgejo.org/modules/json"
 | |
| 	"forgejo.org/modules/log"
 | |
| 	"forgejo.org/modules/setting"
 | |
| 	"forgejo.org/modules/storage"
 | |
| 	"forgejo.org/modules/util"
 | |
| 
 | |
| 	"code.forgejo.org/go-chi/session"
 | |
| 	"github.com/mholt/archiver/v3"
 | |
| 	"github.com/urfave/cli/v3"
 | |
| )
 | |
| 
 | |
| func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
 | |
| 	if verbose {
 | |
| 		log.Info("Adding file %s", customName)
 | |
| 	}
 | |
| 
 | |
| 	return w.Write(archiver.File{
 | |
| 		FileInfo: archiver.FileInfo{
 | |
| 			FileInfo:   info,
 | |
| 			CustomName: customName,
 | |
| 		},
 | |
| 		ReadCloser: r,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
 | |
| 	file, err := os.Open(absPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer file.Close()
 | |
| 	fileInfo, err := file.Stat()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return addReader(w, file, fileInfo, filePath, verbose)
 | |
| }
 | |
| 
 | |
| func isSubdir(upper, lower string) (bool, error) {
 | |
| 	if relPath, err := filepath.Rel(upper, lower); err != nil {
 | |
| 		return false, err
 | |
| 	} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| type outputType struct {
 | |
| 	Enum     []string
 | |
| 	Default  string
 | |
| 	selected string
 | |
| }
 | |
| 
 | |
| func (o outputType) Join() string {
 | |
| 	return strings.Join(o.Enum, ", ")
 | |
| }
 | |
| 
 | |
| func (o *outputType) Set(value string) error {
 | |
| 	for _, enum := range o.Enum {
 | |
| 		if enum == value {
 | |
| 			o.selected = value
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("allowed values are %s", o.Join())
 | |
| }
 | |
| 
 | |
| func (o *outputType) Get() any {
 | |
| 	return o.String()
 | |
| }
 | |
| 
 | |
| func (o outputType) String() string {
 | |
| 	if o.selected == "" {
 | |
| 		return o.Default
 | |
| 	}
 | |
| 	return o.selected
 | |
| }
 | |
| 
 | |
| var outputTypeEnum = &outputType{
 | |
| 	Enum:    []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"},
 | |
| 	Default: "zip",
 | |
| }
 | |
| 
 | |
| // CmdDump represents the available dump sub-command.
 | |
| func cmdDump() *cli.Command {
 | |
| 	return &cli.Command{
 | |
| 		Name:  "dump",
 | |
| 		Usage: "Dump Forgejo files and database",
 | |
| 		Description: `Dump compresses all related files and database into zip file.
 | |
| It can be used for backup and capture Forgejo server image to send to maintainer`,
 | |
| 		Action: runDump,
 | |
| 		Flags: []cli.Flag{
 | |
| 			&cli.StringFlag{
 | |
| 				Name:    "file",
 | |
| 				Aliases: []string{"f"},
 | |
| 				Value:   fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()),
 | |
| 				Usage:   "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:    "verbose",
 | |
| 				Aliases: []string{"V"},
 | |
| 				Usage:   "Show process details",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:    "quiet",
 | |
| 				Aliases: []string{"q"},
 | |
| 				Usage:   "Only display warnings and errors",
 | |
| 			},
 | |
| 			&cli.StringFlag{
 | |
| 				Name:    "tempdir",
 | |
| 				Aliases: []string{"t"},
 | |
| 				Usage:   "Temporary dir path",
 | |
| 			},
 | |
| 			&cli.StringFlag{
 | |
| 				Name:    "database",
 | |
| 				Aliases: []string{"d"},
 | |
| 				Usage:   "Specify the database SQL syntax: sqlite3, mysql, postgres",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:    "skip-repository",
 | |
| 				Aliases: []string{"R"},
 | |
| 				Usage:   "Skip repositories",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:    "skip-log",
 | |
| 				Aliases: []string{"L"},
 | |
| 				Usage:   "Skip logs",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-custom-dir",
 | |
| 				Usage: "Skip custom directory",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-lfs-data",
 | |
| 				Usage: "Skip LFS data",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-attachment-data",
 | |
| 				Usage: "Skip attachment data",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-package-data",
 | |
| 				Usage: "Skip package data",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-index",
 | |
| 				Usage: "Skip bleve index data",
 | |
| 			},
 | |
| 			&cli.BoolFlag{
 | |
| 				Name:  "skip-repo-archives",
 | |
| 				Usage: "Skip repository archives",
 | |
| 			},
 | |
| 			&cli.GenericFlag{
 | |
| 				Name:  "type",
 | |
| 				Value: outputTypeEnum,
 | |
| 				Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func fatal(format string, args ...any) {
 | |
| 	fmt.Fprintf(os.Stderr, format+"\n", args...)
 | |
| 	log.Fatal(format, args...)
 | |
| }
 | |
| 
 | |
| func runDump(stdCtx context.Context, ctx *cli.Command) error {
 | |
| 	var file *os.File
 | |
| 	fileName := ctx.String("file")
 | |
| 	outType := ctx.String("type")
 | |
| 	if fileName == "-" {
 | |
| 		file = os.Stdout
 | |
| 		setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
 | |
| 	} else {
 | |
| 		for _, suffix := range outputTypeEnum.Enum {
 | |
| 			if strings.HasSuffix(fileName, "."+suffix) {
 | |
| 				fileName = strings.TrimSuffix(fileName, "."+suffix)
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		fileName += "." + outType
 | |
| 	}
 | |
| 	setting.MustInstalled()
 | |
| 
 | |
| 	// make sure we are logging to the console no matter what the configuration tells us do to
 | |
| 	// FIXME: don't use CfgProvider directly
 | |
| 	if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil {
 | |
| 		fatal("Setting logging mode to console failed: %v", err)
 | |
| 	}
 | |
| 	if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil {
 | |
| 		fatal("Setting console logger to stderr failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Set loglevel to Warn if quiet-mode is requested
 | |
| 	if ctx.Bool("quiet") {
 | |
| 		if _, err := setting.CfgProvider.Section("log.console").NewKey("LEVEL", "Warn"); err != nil {
 | |
| 			fatal("Setting console log-level failed: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !setting.InstallLock {
 | |
| 		log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
 | |
| 		return errors.New("forgejo is not initialized")
 | |
| 	}
 | |
| 	setting.LoadSettings() // cannot access session settings otherwise
 | |
| 
 | |
| 	verbose := ctx.Bool("verbose")
 | |
| 	if verbose && ctx.Bool("quiet") {
 | |
| 		return errors.New("--quiet and --verbose cannot both be set")
 | |
| 	}
 | |
| 
 | |
| 	stdCtx, cancel := installSignals(stdCtx)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	err := db.InitEngine(stdCtx)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := storage.Init(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if file == nil {
 | |
| 		file, err = os.Create(fileName)
 | |
| 		if err != nil {
 | |
| 			fatal("Failed to open %s: %v", fileName, err)
 | |
| 		}
 | |
| 	}
 | |
| 	defer file.Close()
 | |
| 
 | |
| 	absFileName, err := filepath.Abs(fileName)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	var iface any
 | |
| 	if fileName == "-" {
 | |
| 		iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
 | |
| 	} else {
 | |
| 		iface, err = archiver.ByExtension(fileName)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		fatal("Failed to get archiver for extension: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	w, _ := iface.(archiver.Writer)
 | |
| 	if err := w.Create(file); err != nil {
 | |
| 		fatal("Creating archiver.Writer failed: %v", err)
 | |
| 	}
 | |
| 	defer w.Close()
 | |
| 
 | |
| 	if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
 | |
| 		log.Info("Skipping local repositories")
 | |
| 	} else {
 | |
| 		log.Info("Dumping local repositories... %s", setting.RepoRootPath)
 | |
| 		if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
 | |
| 			fatal("Failed to include repositories: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
 | |
| 			log.Info("Skipping LFS data")
 | |
| 		} else if !setting.LFS.StartServer {
 | |
| 			log.Info("LFS not enabled - skipping")
 | |
| 		} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
 | |
| 			info, err := object.Stat()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
 | |
| 		}); err != nil {
 | |
| 			fatal("Failed to dump LFS objects: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	tmpDir := ctx.String("tempdir")
 | |
| 	if tmpDir == "" {
 | |
| 		tmpDir, err = os.MkdirTemp("", "forgejo-dump-*")
 | |
| 		if err != nil {
 | |
| 			fatal("Failed to create temporary directory: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		defer func() {
 | |
| 			if err := util.Remove(tmpDir); err != nil {
 | |
| 				log.Warn("Failed to remove temporary directory: %s: Error: %v", tmpDir, err)
 | |
| 			}
 | |
| 		}()
 | |
| 	}
 | |
| 
 | |
| 	if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
 | |
| 		fatal("Path does not exist: %s", tmpDir)
 | |
| 	}
 | |
| 
 | |
| 	dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql")
 | |
| 	if err != nil {
 | |
| 		fatal("Failed to create temporary file: %v", err)
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		_ = dbDump.Close()
 | |
| 		if err := util.Remove(dbDump.Name()); err != nil {
 | |
| 			log.Warn("Failed to remove temporary database file: %s: Error: %v", dbDump.Name(), err)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	targetDBType := ctx.String("database")
 | |
| 	if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
 | |
| 		log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
 | |
| 	} else {
 | |
| 		log.Info("Dumping database...")
 | |
| 	}
 | |
| 
 | |
| 	if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
 | |
| 		fatal("Failed to dump database: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if err := addFile(w, "forgejo-db.sql", dbDump.Name(), verbose); err != nil {
 | |
| 		fatal("Failed to include forgejo-db.sql: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if len(setting.CustomConf) > 0 {
 | |
| 		log.Info("Adding custom configuration file from %s", setting.CustomConf)
 | |
| 		if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
 | |
| 			fatal("Failed to include specified app.ini: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
 | |
| 		log.Info("Skipping custom directory")
 | |
| 	} else {
 | |
| 		customDir, err := os.Stat(setting.CustomPath)
 | |
| 		if err == nil && customDir.IsDir() {
 | |
| 			if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
 | |
| 				if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
 | |
| 					fatal("Failed to include custom: %v", err)
 | |
| 				}
 | |
| 			} else {
 | |
| 				log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath)
 | |
| 			}
 | |
| 		} else {
 | |
| 			log.Info("Custom dir %s does not exist, skipping", setting.CustomPath)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	isExist, err := util.IsExist(setting.AppDataPath)
 | |
| 	if err != nil {
 | |
| 		log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err)
 | |
| 	}
 | |
| 	if isExist {
 | |
| 		log.Info("Packing data directory...%s", setting.AppDataPath)
 | |
| 
 | |
| 		var excludes []string
 | |
| 		if setting.SessionConfig.OriginalProvider == "file" {
 | |
| 			var opts session.Options
 | |
| 			if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			excludes = append(excludes, opts.ProviderConfig)
 | |
| 		}
 | |
| 
 | |
| 		if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
 | |
| 			log.Info("Skipping bleve index data")
 | |
| 			excludes = append(excludes, setting.Indexer.RepoPath)
 | |
| 			excludes = append(excludes, setting.Indexer.IssuePath)
 | |
| 		}
 | |
| 
 | |
| 		if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") {
 | |
| 			log.Info("Skipping repository archives data")
 | |
| 			excludes = append(excludes, setting.RepoArchive.Storage.Path)
 | |
| 		}
 | |
| 
 | |
| 		excludes = append(excludes, setting.RepoRootPath)
 | |
| 		excludes = append(excludes, setting.LFS.Storage.Path)
 | |
| 		excludes = append(excludes, setting.Attachment.Storage.Path)
 | |
| 		excludes = append(excludes, setting.Packages.Storage.Path)
 | |
| 		excludes = append(excludes, setting.Log.RootPath)
 | |
| 		excludes = append(excludes, absFileName)
 | |
| 		if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
 | |
| 			fatal("Failed to include data directory: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
 | |
| 		log.Info("Skipping attachment data")
 | |
| 	} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
 | |
| 		info, err := object.Stat()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
 | |
| 	}); err != nil {
 | |
| 		fatal("Failed to dump attachments: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
 | |
| 		log.Info("Skipping package data")
 | |
| 	} else if !setting.Packages.Enabled {
 | |
| 		log.Info("Package registry not enabled - skipping")
 | |
| 	} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
 | |
| 		info, err := object.Stat()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
 | |
| 	}); err != nil {
 | |
| 		fatal("Failed to dump packages: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
 | |
| 	// ensuring that it's clear the dump is skipped whether the directory's initialized
 | |
| 	// yet or not.
 | |
| 	if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
 | |
| 		log.Info("Skipping log files")
 | |
| 	} else {
 | |
| 		isExist, err := util.IsExist(setting.Log.RootPath)
 | |
| 		if err != nil {
 | |
| 			log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
 | |
| 		}
 | |
| 		if isExist {
 | |
| 			if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
 | |
| 				fatal("Failed to include log: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if fileName != "-" {
 | |
| 		if err = w.Close(); err != nil {
 | |
| 			_ = util.Remove(fileName)
 | |
| 			fatal("Failed to save %s: %v", fileName, err)
 | |
| 		}
 | |
| 
 | |
| 		if err := os.Chmod(fileName, 0o600); err != nil {
 | |
| 			log.Info("Can't change file access permissions mask to 0600: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if fileName != "-" {
 | |
| 		log.Info("Finish dumping in file %s", fileName)
 | |
| 	} else {
 | |
| 		log.Info("Finish dumping to stdout")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
 | |
| func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
 | |
| 	absPath, err := filepath.Abs(absPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	dir, err := os.Open(absPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer dir.Close()
 | |
| 
 | |
| 	files, err := dir.Readdir(0)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, file := range files {
 | |
| 		currentAbsPath := filepath.Join(absPath, file.Name())
 | |
| 		currentInsidePath := path.Join(insidePath, file.Name())
 | |
| 
 | |
| 		if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
 | |
| 			log.Debug("Skipping %q (matched an excluded path)", currentAbsPath)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if file.IsDir() {
 | |
| 			if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		} else {
 | |
| 			// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
 | |
| 			shouldAdd := file.Mode().IsRegular()
 | |
| 			if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
 | |
| 				target, err := filepath.EvalSymlinks(currentAbsPath)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				targetStat, err := os.Stat(target)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				shouldAdd = targetStat.Mode().IsRegular()
 | |
| 			}
 | |
| 			if shouldAdd {
 | |
| 				if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |