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 }