code.gitea.io/gitea@v1.22.3/cmd/migrate_storage.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"io/fs"
    11  	"strings"
    12  
    13  	actions_model "code.gitea.io/gitea/models/actions"
    14  	"code.gitea.io/gitea/models/db"
    15  	git_model "code.gitea.io/gitea/models/git"
    16  	"code.gitea.io/gitea/models/migrations"
    17  	packages_model "code.gitea.io/gitea/models/packages"
    18  	repo_model "code.gitea.io/gitea/models/repo"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/log"
    21  	packages_module "code.gitea.io/gitea/modules/packages"
    22  	"code.gitea.io/gitea/modules/setting"
    23  	"code.gitea.io/gitea/modules/storage"
    24  
    25  	"github.com/urfave/cli/v2"
    26  )
    27  
    28  // CmdMigrateStorage represents the available migrate storage sub-command.
    29  var CmdMigrateStorage = &cli.Command{
    30  	Name:        "migrate-storage",
    31  	Usage:       "Migrate the storage",
    32  	Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
    33  	Action:      runMigrateStorage,
    34  	Flags: []cli.Flag{
    35  		&cli.StringFlag{
    36  			Name:    "type",
    37  			Aliases: []string{"t"},
    38  			Value:   "",
    39  			Usage:   "Type of stored files to copy.  Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts",
    40  		},
    41  		&cli.StringFlag{
    42  			Name:    "storage",
    43  			Aliases: []string{"s"},
    44  			Value:   "",
    45  			Usage:   "New storage type: local (default) or minio",
    46  		},
    47  		&cli.StringFlag{
    48  			Name:    "path",
    49  			Aliases: []string{"p"},
    50  			Value:   "",
    51  			Usage:   "New storage placement if store is local (leave blank for default)",
    52  		},
    53  		&cli.StringFlag{
    54  			Name:  "minio-endpoint",
    55  			Value: "",
    56  			Usage: "Minio storage endpoint",
    57  		},
    58  		&cli.StringFlag{
    59  			Name:  "minio-access-key-id",
    60  			Value: "",
    61  			Usage: "Minio storage accessKeyID",
    62  		},
    63  		&cli.StringFlag{
    64  			Name:  "minio-secret-access-key",
    65  			Value: "",
    66  			Usage: "Minio storage secretAccessKey",
    67  		},
    68  		&cli.StringFlag{
    69  			Name:  "minio-bucket",
    70  			Value: "",
    71  			Usage: "Minio storage bucket",
    72  		},
    73  		&cli.StringFlag{
    74  			Name:  "minio-location",
    75  			Value: "",
    76  			Usage: "Minio storage location to create bucket",
    77  		},
    78  		&cli.StringFlag{
    79  			Name:  "minio-base-path",
    80  			Value: "",
    81  			Usage: "Minio storage base path on the bucket",
    82  		},
    83  		&cli.BoolFlag{
    84  			Name:  "minio-use-ssl",
    85  			Usage: "Enable SSL for minio",
    86  		},
    87  		&cli.BoolFlag{
    88  			Name:  "minio-insecure-skip-verify",
    89  			Usage: "Skip SSL verification",
    90  		},
    91  		&cli.StringFlag{
    92  			Name:  "minio-checksum-algorithm",
    93  			Value: "",
    94  			Usage: "Minio checksum algorithm (default/md5)",
    95  		},
    96  	},
    97  }
    98  
    99  func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
   100  	return db.Iterate(ctx, nil, func(ctx context.Context, attach *repo_model.Attachment) error {
   101  		_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
   102  		return err
   103  	})
   104  }
   105  
   106  func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error {
   107  	return db.Iterate(ctx, nil, func(ctx context.Context, mo *git_model.LFSMetaObject) error {
   108  		_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
   109  		return err
   110  	})
   111  }
   112  
   113  func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
   114  	return db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
   115  		if user.CustomAvatarRelativePath() == "" {
   116  			return nil
   117  		}
   118  		_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
   119  		return err
   120  	})
   121  }
   122  
   123  func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
   124  	return db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
   125  		if repo.CustomAvatarRelativePath() == "" {
   126  			return nil
   127  		}
   128  		_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
   129  		return err
   130  	})
   131  }
   132  
   133  func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
   134  	return db.Iterate(ctx, nil, func(ctx context.Context, archiver *repo_model.RepoArchiver) error {
   135  		p := archiver.RelativePath()
   136  		_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p)
   137  		return err
   138  	})
   139  }
   140  
   141  func migratePackages(ctx context.Context, dstStorage storage.ObjectStorage) error {
   142  	return db.Iterate(ctx, nil, func(ctx context.Context, pb *packages_model.PackageBlob) error {
   143  		p := packages_module.KeyToRelativePath(packages_module.BlobHash256Key(pb.HashSHA256))
   144  		_, err := storage.Copy(dstStorage, p, storage.Packages, p)
   145  		return err
   146  	})
   147  }
   148  
   149  func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) error {
   150  	return db.Iterate(ctx, nil, func(ctx context.Context, task *actions_model.ActionTask) error {
   151  		if task.LogExpired {
   152  			// the log has been cleared
   153  			return nil
   154  		}
   155  		if !task.LogInStorage {
   156  			// running tasks store logs in DBFS
   157  			return nil
   158  		}
   159  		p := task.LogFilename
   160  		_, err := storage.Copy(dstStorage, p, storage.Actions, p)
   161  		return err
   162  	})
   163  }
   164  
   165  func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
   166  	return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
   167  		if artifact.Status == int64(actions_model.ArtifactStatusExpired) {
   168  			return nil
   169  		}
   170  
   171  		_, err := storage.Copy(dstStorage, artifact.StoragePath, storage.ActionsArtifacts, artifact.StoragePath)
   172  		if err != nil {
   173  			// ignore files that do not exist
   174  			if errors.Is(err, fs.ErrNotExist) {
   175  				return nil
   176  			}
   177  			return err
   178  		}
   179  
   180  		return nil
   181  	})
   182  }
   183  
   184  func runMigrateStorage(ctx *cli.Context) error {
   185  	stdCtx, cancel := installSignals()
   186  	defer cancel()
   187  
   188  	if err := initDB(stdCtx); err != nil {
   189  		return err
   190  	}
   191  
   192  	log.Info("AppPath: %s", setting.AppPath)
   193  	log.Info("AppWorkPath: %s", setting.AppWorkPath)
   194  	log.Info("Custom path: %s", setting.CustomPath)
   195  	log.Info("Log path: %s", setting.Log.RootPath)
   196  	log.Info("Configuration file: %s", setting.CustomConf)
   197  
   198  	if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
   199  		log.Fatal("Failed to initialize ORM engine: %v", err)
   200  		return err
   201  	}
   202  
   203  	if err := storage.Init(); err != nil {
   204  		return err
   205  	}
   206  
   207  	var dstStorage storage.ObjectStorage
   208  	var err error
   209  	switch strings.ToLower(ctx.String("storage")) {
   210  	case "":
   211  		fallthrough
   212  	case string(setting.LocalStorageType):
   213  		p := ctx.String("path")
   214  		if p == "" {
   215  			log.Fatal("Path must be given when storage is local")
   216  			return nil
   217  		}
   218  		dstStorage, err = storage.NewLocalStorage(
   219  			stdCtx,
   220  			&setting.Storage{
   221  				Path: p,
   222  			})
   223  	case string(setting.MinioStorageType):
   224  		dstStorage, err = storage.NewMinioStorage(
   225  			stdCtx,
   226  			&setting.Storage{
   227  				MinioConfig: setting.MinioStorageConfig{
   228  					Endpoint:           ctx.String("minio-endpoint"),
   229  					AccessKeyID:        ctx.String("minio-access-key-id"),
   230  					SecretAccessKey:    ctx.String("minio-secret-access-key"),
   231  					Bucket:             ctx.String("minio-bucket"),
   232  					Location:           ctx.String("minio-location"),
   233  					BasePath:           ctx.String("minio-base-path"),
   234  					UseSSL:             ctx.Bool("minio-use-ssl"),
   235  					InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
   236  					ChecksumAlgorithm:  ctx.String("minio-checksum-algorithm"),
   237  				},
   238  			})
   239  	default:
   240  		return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
   241  	}
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
   247  		"attachments":       migrateAttachments,
   248  		"lfs":               migrateLFS,
   249  		"avatars":           migrateAvatars,
   250  		"repo-avatars":      migrateRepoAvatars,
   251  		"repo-archivers":    migrateRepoArchivers,
   252  		"packages":          migratePackages,
   253  		"actions-log":       migrateActionsLog,
   254  		"actions-artifacts": migrateActionsArtifacts,
   255  	}
   256  
   257  	tp := strings.ToLower(ctx.String("type"))
   258  	if m, ok := migratedMethods[tp]; ok {
   259  		if err := m(stdCtx, dstStorage); err != nil {
   260  			return err
   261  		}
   262  		log.Info("%s files have successfully been copied to the new storage.", tp)
   263  		return nil
   264  	}
   265  
   266  	return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
   267  }