diff --git a/.golangci.yml b/.golangci.yml index 0bd71b7fb..a4f959e18 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -98,3 +98,6 @@ issues: - path: models/update.go linters: - unused + - path: cmd/dump.go + linters: + - dupl diff --git a/cmd/dump.go b/cmd/dump.go index 0e41ecb8c..7ff986f14 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" "gitea.com/macaron/session" @@ -57,6 +58,8 @@ func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose boo if err != nil { return fmt.Errorf("Could not open directory %s: %s", absPath, err) } + defer dir.Close() + files, err := dir.Readdir(0) if err != nil { return fmt.Errorf("Unable to list files in %s: %s", absPath, err) @@ -197,6 +200,10 @@ func runDump(ctx *cli.Context) error { return err } + if err := storage.Init(); err != nil { + return err + } + if file == nil { file, err = os.Create(fileName) if err != nil { @@ -231,11 +238,21 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include repositories: %v", err) } - if _, err := os.Stat(setting.LFS.ContentPath); !os.IsNotExist(err) { - log.Info("Dumping lfs... %s", setting.LFS.ContentPath) - if err := addRecursive(w, "lfs", setting.LFS.ContentPath, verbose); err != nil { - fatal("Failed to include lfs: %v", err) + if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { + info, err := object.Stat() + if err != nil { + return err } + + return w.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: info, + CustomName: path.Join("data", "lfs", objPath), + }, + ReadCloser: object, + }) + }); err != nil { + fatal("Failed to dump LFS objects: %v", err) } } @@ -302,13 +319,31 @@ func runDump(ctx *cli.Context) error { } excludes = append(excludes, setting.RepoRootPath) - excludes = append(excludes, setting.LFS.ContentPath) + excludes = append(excludes, setting.LFS.Path) + excludes = append(excludes, setting.Attachment.Path) excludes = append(excludes, setting.LogRootPath) if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { fatal("Failed to include data directory: %v", err) } } + if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error { + info, err := object.Stat() + if err != nil { + return err + } + + return w.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: info, + CustomName: path.Join("data", "attachments", objPath), + }, + ReadCloser: object, + }) + }); err != nil { + fatal("Failed to dump attachments: %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. diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index b8e45c954..aed81ddb0 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -7,6 +7,7 @@ package cmd import ( "context" "fmt" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/migrations" @@ -30,8 +31,8 @@ var CmdMigrateStorage = cli.Command{ Usage: "Kinds of files to migrate, currently only 'attachments' is supported", }, cli.StringFlag{ - Name: "store, s", - Value: "local", + Name: "storage, s", + Value: setting.LocalStorageType, Usage: "New storage type, local or minio", }, cli.StringFlag{ @@ -112,15 +113,15 @@ func runMigrateStorage(ctx *cli.Context) error { var dstStorage storage.ObjectStorage var err error - switch ctx.String("store") { - case "local": + switch strings.ToLower(ctx.String("storage")) { + case setting.LocalStorageType: p := ctx.String("path") if p == "" { - log.Fatal("Path must be given when store is loal") + log.Fatal("Path must be given when storage is loal") return nil } dstStorage, err = storage.NewLocalStorage(p) - case "minio": + case setting.MinioStorageType: dstStorage, err = storage.NewMinioStorage( context.Background(), ctx.String("minio-endpoint"), @@ -132,14 +133,14 @@ func runMigrateStorage(ctx *cli.Context) error { ctx.Bool("minio-use-ssl"), ) default: - return fmt.Errorf("Unsupported attachments store type: %s", ctx.String("store")) + return fmt.Errorf("Unsupported attachments storage type: %s", ctx.String("storage")) } if err != nil { return err } - tp := ctx.String("type") + tp := strings.ToLower(ctx.String("type")) switch tp { case "attachments": if err := migrateAttachments(dstStorage); err != nil { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 34a305c4a..fad4978bb 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -778,25 +778,25 @@ MAX_SIZE = 4 MAX_FILES = 5 ; Storage type for attachments, `local` for local disk or `minio` for s3 compatible ; object storage service, default is `local`. -STORE_TYPE = local +STORAGE_TYPE = local ; Allows the storage driver to redirect to authenticated URLs to serve files directly ; Currently, only `minio` is supported. SERVE_DIRECT = false -; Path for attachments. Defaults to `data/attachments` only available when STORE_TYPE is `local` +; Path for attachments. Defaults to `data/attachments` only available when STORAGE_TYPE is `local` PATH = data/attachments -; Minio endpoint to connect only available when STORE_TYPE is `minio` +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` MINIO_ENDPOINT = localhost:9000 -; Minio accessKeyID to connect only available when STORE_TYPE is `minio` +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` MINIO_ACCESS_KEY_ID = -; Minio secretAccessKey to connect only available when STORE_TYPE is `minio` +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` MINIO_SECRET_ACCESS_KEY = -; Minio bucket to store the attachments only available when STORE_TYPE is `minio` +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` MINIO_BUCKET = gitea -; Minio location to create bucket only available when STORE_TYPE is `minio` +; Minio location to create bucket only available when STORAGE_TYPE is `minio` MINIO_LOCATION = us-east-1 -; Minio base path on the bucket only available when STORE_TYPE is `minio` +; Minio base path on the bucket only available when STORAGE_TYPE is `minio` MINIO_BASE_PATH = attachments/ -; Minio enabled ssl only available when STORE_TYPE is `minio` +; Minio enabled ssl only available when STORAGE_TYPE is `minio` MINIO_USE_SSL = false [time] @@ -1161,3 +1161,28 @@ QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" MAX_ATTEMPTS = 3 ; Backoff time per http/https request retry (seconds) RETRY_BACKOFF = 3 + +; default storage for attachments, lfs and avatars +[storage] +; storage type +STORAGE_TYPE = local + +; lfs storage will override storage +[lfs] +STORAGE_TYPE = local + +; customize storage +;[storage.my_minio] +;STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +;MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +;MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +;MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +;MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +;MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +;MINIO_USE_SSL = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index bbeaf4796..c09001c5e 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -220,16 +220,6 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. - `LFS_START_SERVER`: **false**: Enables git-lfs support. -- `LFS_STORE_TYPE`: **local**: Storage type for lfs, `local` for local disk or `minio` for s3 compatible object storage service. -- `LFS_SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. -- `LFS_CONTENT_PATH`: **./data/lfs**: Where to store LFS files, only available when `LFS_STORE_TYPE` is `local`. -- `LFS_MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `LFS_STORE_TYPE` is `minio` -- `LFS_MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `LFS_STORE_TYPE` is `minio` -- `LFS_MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `LFS_STORE_TYPE is` `minio` -- `LFS_MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `LFS_STORE_TYPE` is `minio` -- `LFS_MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `LFS_STORE_TYPE` is `minio` -- `LFS_MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `LFS_STORE_TYPE` is `minio` -- `LFS_MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `LFS_STORE_TYPE` is `minio` - `LFS_JWT_SECRET`: **\**: LFS authentication secret, change this a unique string. - `LFS_HTTP_AUTH_EXPIRY`: **20m**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail. - `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit). @@ -501,16 +491,16 @@ relation to port exhaustion. Use `*/*` for all types. - `MAX_SIZE`: **4**: Maximum size (MB). - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. -- `STORE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local`. +- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. -- `PATH`: **data/attachments**: Path to store attachments only available when STORE_TYPE is `local` -- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when STORE_TYPE is `minio` -- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORE_TYPE is `minio` -- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORE_TYPE is `minio` -- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORE_TYPE is `minio` -- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORE_TYPE is `minio` -- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORE_TYPE is `minio` -- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORE_TYPE is `minio` +- `PATH`: **data/attachments**: Path to store attachments only available when STORAGE_TYPE is `local` +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when STORAGE_TYPE is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio` +- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio` ## Log (`log`) @@ -714,6 +704,56 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations. - `RETRY_BACKOFF`: **3**: Backoff time per http/https request retry (seconds) +## LFS (`lfs`) + +Storage configuration for lfs data. It will be derived from default `[storage]` or +`[storage.xxx]` when set `STORAGE_TYPE` to `xxx`. When derived, the default of `PATH` +is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`. + +- `STORAGE_TYPE`: **local**: Storage type for lfs, `local` for local disk or `minio` for s3 compatible object storage service or other name defined with `[storage.xxx]` +- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. +- `CONTENT_PATH`: **./data/lfs**: Where to store LFS files, only available when `STORAGE_TYPE` is `local`. +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` + +## Storage (`storage`) + +Default storage configuration for attachments, lfs, avatars and etc. + +- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` + +And you can also define a customize storage like below: + +```ini +[storage.my_minio] +STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +MINIO_USE_SSL = false +``` + +And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`. + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index f771f4fc8..efa390bfb 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -72,16 +72,6 @@ menu: - `LANDING_PAGE`: 未登录用户的默认页面,可选 `home` 或 `explore`。 - `LFS_START_SERVER`: 是否启用 git-lfs 支持. 可以为 `true` 或 `false`, 默认是 `false`。 -- `LFS_STORE_TYPE`: **local**: LFS 的存储类型,`local` 将存储到磁盘,`minio` 将存储到 s3 兼容的对象服务。 -- `LFS_SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。 -- `LFS_CONTENT_PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。 -- `LFS_MINIO_ENDPOINT`: **localhost:9000**: Minio 地址,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_ACCESS_KEY_ID`: Minio accessKeyID,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_BUCKET`: **gitea**: Minio bucket,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_LOCATION`: **us-east-1**: Minio location ,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_BASE_PATH`: **lfs/**: Minio base path ,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 -- `LFS_MINIO_USE_SSL`: **false**: Minio 是否启用 ssl ,仅当 `LFS_STORE_TYPE` 为 `minio` 时有效。 - `LFS_JWT_SECRET`: LFS 认证密钥,改成自己的。 ## Database (`database`) @@ -198,15 +188,15 @@ menu: - `ALLOWED_TYPES`: 允许上传的附件类型。比如:`image/jpeg|image/png`,用 `*/*` 表示允许任何类型。 - `MAX_SIZE`: 附件最大限制,单位 MB,比如: `4`。 - `MAX_FILES`: 一次最多上传的附件数量,比如: `5`。 -- `STORE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 -- `PATH`: **data/attachments**: 附件存储路径,仅当 `STORE_TYPE` 为 `local` 时有效。 -- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORE_TYPE` 是 `minio` 时有效。 +- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 +- `PATH`: **data/attachments**: 附件存储路径,仅当 `STORAGE_TYPE` 为 `local` 时有效。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 关于 `ALLOWED_TYPES`, 在 (*)unix 系统中可以使用`file -I ` 来快速获得对应的 `MIME type`。 @@ -310,6 +300,55 @@ IS_INPUT_FILE = false - `MAX_ATTEMPTS`: **3**: 在迁移过程中的 http/https 请求重试次数。 - `RETRY_BACKOFF`: **3**: 等待下一次重试的时间,单位秒。 +## LFS (`lfs`) + +LFS 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[storage]` 继承。如果不为 `local` 或者 `minio` 而为 `xxx`, 则从 `[storage.xxx]` 继承。当继承时, `PATH` 默认为 `data/lfs`,`MINIO_BASE_PATH` 默认为 `lfs/`。 + +- `STORAGE_TYPE`: **local**: LFS 的存储类型,`local` 将存储到磁盘,`minio` 将存储到 s3 兼容的对象服务。 +- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。 +- `CONTENT_PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 地址,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_BASE_PATH`: **lfs/**: Minio base path ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio 是否启用 ssl ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 + +## Storage (`storage`) + +Attachments, lfs, avatars and etc 的默认存储配置。 + +- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 +- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 + +你也可以自定义一个存储的名字如下: + +```ini +[storage.my_minio] +STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +MINIO_USE_SSL = false +``` + +然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。 + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: 为真则在页面底部显示Gitea的字样。 diff --git a/models/admin.go b/models/admin.go index 9ed9c44eb..903e35b0c 100644 --- a/models/admin.go +++ b/models/admin.go @@ -70,6 +70,10 @@ func RemoveAllWithNotice(title, path string) { // RemoveStorageWithNotice removes a file from the storage and // creates a system notice when error occurs. func RemoveStorageWithNotice(bucket storage.ObjectStorage, title, path string) { + removeStorageWithNotice(x, bucket, title, path) +} + +func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path string) { if err := bucket.Delete(path); err != nil { desc := fmt.Sprintf("%s [%s]: %v", title, path, err) log.Warn(title+" [%s]: %v", path, err) diff --git a/models/repo.go b/models/repo.go index 46f91fc7d..a743f6573 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1748,8 +1748,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { continue } - oidPath := filepath.Join(setting.LFS.ContentPath, v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)]) - removeAllWithNotice(sess, "Delete orphaned LFS file", oidPath) + removeStorageWithNotice(sess, storage.LFS, "Delete orphaned LFS file", v.RelativePath()) } if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { diff --git a/models/unit_tests.go b/models/unit_tests.go index e977f706f..c4f9091a4 100644 --- a/models/unit_tests.go +++ b/models/unit_tests.go @@ -67,9 +67,11 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { if err != nil { fatalTestError("url.Parse: %v\n", err) } + setting.Attachment.Storage.Type = setting.LocalStorageType + setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") - setting.Attachment.Path = filepath.Join(setting.AppDataPath, "attachments") - setting.LFS.ContentPath = filepath.Join(setting.AppDataPath, "lfs") + setting.LFS.Storage.Type = setting.LocalStorageType + setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index 4c7368eb6..56ccf5bc5 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -5,42 +5,25 @@ package setting import ( - "path" "path/filepath" "strings" + + "code.gitea.io/gitea/modules/log" ) var ( // Attachment settings Attachment = struct { - StoreType string - Path string - ServeDirect bool - Minio struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - UseSSL bool - Bucket string - Location string - BasePath string - } + Storage AllowedTypes string MaxSize int64 MaxFiles int Enabled bool }{ - StoreType: "local", - ServeDirect: false, - Minio: struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - UseSSL bool - Bucket string - Location string - BasePath string - }{}, + Storage: Storage{ + Type: LocalStorageType, + ServeDirect: false, + }, AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip", MaxSize: 4, MaxFiles: 5, @@ -50,22 +33,36 @@ var ( func newAttachmentService() { sec := Cfg.Section("attachment") - Attachment.StoreType = sec.Key("STORE_TYPE").MustString("local") - Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false) - switch Attachment.StoreType { - case "local": - Attachment.Path = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) + Attachment.Storage.Type = sec.Key("STORAGE_TYPE").MustString("") + if Attachment.Storage.Type == "" { + Attachment.Storage.Type = "default" + } + + if Attachment.Storage.Type != LocalStorageType && Attachment.Storage.Type != MinioStorageType { + storage, ok := storages[Attachment.Storage.Type] + if !ok { + log.Fatal("Failed to get attachment storage type: %s", Attachment.Storage.Type) + } + Attachment.Storage = storage + } + + // Override + Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(Attachment.ServeDirect) + + switch Attachment.Storage.Type { + case LocalStorageType: + Attachment.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "attachments")) if !filepath.IsAbs(Attachment.Path) { - Attachment.Path = path.Join(AppWorkPath, Attachment.Path) + Attachment.Path = filepath.Join(AppWorkPath, Attachment.Path) } - case "minio": - Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString("localhost:9000") - Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString("") - Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("") - Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString("gitea") - Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString("us-east-1") + case MinioStorageType: + Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString(Attachment.Minio.Endpoint) + Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString(Attachment.Minio.AccessKeyID) + Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString(Attachment.Minio.SecretAccessKey) + Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString(Attachment.Minio.Bucket) + Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString(Attachment.Minio.Location) + Attachment.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(Attachment.Minio.UseSSL) Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/") - Attachment.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(false) } Attachment.AllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1) diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index a740a6d62..da34d3a5f 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -21,27 +21,14 @@ import ( // LFS represents the configuration for Git LFS var LFS = struct { StartServer bool `ini:"LFS_START_SERVER"` - ContentPath string `ini:"LFS_CONTENT_PATH"` JWTSecretBase64 string `ini:"LFS_JWT_SECRET"` JWTSecretBytes []byte `ini:"-"` HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` - StoreType string - ServeDirect bool - Minio struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - UseSSL bool - Bucket string - Location string - BasePath string - } -}{ - StoreType: "local", -} + Storage +}{} func newLFSService() { sec := Cfg.Section("server") @@ -49,10 +36,41 @@ func newLFSService() { log.Fatal("Failed to map LFS settings: %v", err) } - LFS.ContentPath = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs")) - if !filepath.IsAbs(LFS.ContentPath) { - LFS.ContentPath = filepath.Join(AppWorkPath, LFS.ContentPath) + lfsSec := Cfg.Section("lfs") + LFS.Storage.Type = lfsSec.Key("STORAGE_TYPE").MustString("") + if LFS.Storage.Type == "" { + LFS.Storage.Type = "default" + } + + if LFS.Storage.Type != LocalStorageType && LFS.Storage.Type != MinioStorageType { + storage, ok := storages[LFS.Storage.Type] + if !ok { + log.Fatal("Failed to get lfs storage type: %s", LFS.Storage.Type) + } + LFS.Storage = storage } + + // Override + LFS.ServeDirect = lfsSec.Key("SERVE_DIRECT").MustBool(LFS.ServeDirect) + switch LFS.Storage.Type { + case LocalStorageType: + // keep compatible + LFS.Path = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs")) + LFS.Path = lfsSec.Key("PATH").MustString(LFS.Path) + if !filepath.IsAbs(LFS.Path) { + LFS.Path = filepath.Join(AppWorkPath, LFS.Path) + } + + case MinioStorageType: + LFS.Minio.Endpoint = lfsSec.Key("MINIO_ENDPOINT").MustString(LFS.Minio.Endpoint) + LFS.Minio.AccessKeyID = lfsSec.Key("MINIO_ACCESS_KEY_ID").MustString(LFS.Minio.AccessKeyID) + LFS.Minio.SecretAccessKey = lfsSec.Key("MINIO_SECRET_ACCESS_KEY").MustString(LFS.Minio.SecretAccessKey) + LFS.Minio.Bucket = lfsSec.Key("MINIO_BUCKET").MustString(LFS.Minio.Bucket) + LFS.Minio.Location = lfsSec.Key("MINIO_LOCATION").MustString(LFS.Minio.Location) + LFS.Minio.UseSSL = lfsSec.Key("MINIO_USE_SSL").MustBool(LFS.Minio.UseSSL) + LFS.Minio.BasePath = lfsSec.Key("MINIO_BASE_PATH").MustString("lfs/") + } + if LFS.LocksPagingNum == 0 { LFS.LocksPagingNum = 50 } @@ -92,14 +110,6 @@ func newLFSService() { } } -func ensureLFSDirectory() { - if LFS.StartServer { - if err := os.MkdirAll(LFS.ContentPath, 0700); err != nil { - log.Fatal("Failed to create '%s': %v", LFS.ContentPath, err) - } - } -} - // CheckLFSVersion will check lfs version, if not satisfied, then disable it. func CheckLFSVersion() { if LFS.StartServer { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7d7eacba6..26f226fe5 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -691,8 +691,6 @@ func NewContext() { SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) - newLFSService() - if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil { log.Fatal("Failed to OAuth2 settings: %v", err) return @@ -761,7 +759,9 @@ func NewContext() { } } + newStorageService() newAttachmentService() + newLFSService() timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("") if timeFormatKey != "" { @@ -1017,7 +1017,6 @@ func NewServices() { InitDBConfig() newService() NewLogServices(false) - ensureLFSDirectory() newCacheService() newSessionService() newCORSService() diff --git a/modules/setting/storage.go b/modules/setting/storage.go new file mode 100644 index 000000000..c678a08f5 --- /dev/null +++ b/modules/setting/storage.go @@ -0,0 +1,69 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package setting + +import ( + "strings" + + "code.gitea.io/gitea/modules/log" + ini "gopkg.in/ini.v1" +) + +// enumerate all storage types +const ( + LocalStorageType = "local" + MinioStorageType = "minio" +) + +// Storage represents configuration of storages +type Storage struct { + Type string + Path string + ServeDirect bool + Minio struct { + Endpoint string + AccessKeyID string + SecretAccessKey string + UseSSL bool + Bucket string + Location string + BasePath string + } +} + +var ( + storages = make(map[string]Storage) +) + +func getStorage(sec *ini.Section) Storage { + var storage Storage + storage.Type = sec.Key("STORAGE_TYPE").MustString(LocalStorageType) + storage.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false) + switch storage.Type { + case LocalStorageType: + case MinioStorageType: + storage.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString("localhost:9000") + storage.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString("") + storage.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("") + storage.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString("gitea") + storage.Minio.Location = sec.Key("MINIO_LOCATION").MustString("us-east-1") + storage.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(false) + } + return storage +} + +func newStorageService() { + sec := Cfg.Section("storage") + storages["default"] = getStorage(sec) + + for _, sec := range Cfg.Section("storage").ChildSections() { + name := strings.TrimPrefix(sec.Name(), "storage.") + if name == "default" || name == LocalStorageType || name == MinioStorageType { + log.Error("storage name %s is system reserved!", name) + continue + } + storages[name] = getStorage(sec) + } +} diff --git a/modules/storage/local.go b/modules/storage/local.go index a937a831f..418598166 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -59,7 +59,7 @@ func (l *LocalStorage) Save(path string, r io.Reader) (int64, error) { } // Stat returns the info of the file -func (l *LocalStorage) Stat(path string) (ObjectInfo, error) { +func (l *LocalStorage) Stat(path string) (os.FileInfo, error) { return os.Stat(filepath.Join(l.dir, path)) } @@ -73,3 +73,28 @@ func (l *LocalStorage) Delete(path string) error { func (l *LocalStorage) URL(path, name string) (*url.URL, error) { return nil, ErrURLNotSupported } + +// IterateObjects iterates across the objects in the local storage +func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error { + return filepath.Walk(l.dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == l.dir { + return nil + } + if info.IsDir() { + return nil + } + relPath, err := filepath.Rel(l.dir, path) + if err != nil { + return err + } + obj, err := os.Open(path) + if err != nil { + return err + } + defer obj.Close() + return fn(relPath, obj) + }) +} diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 30751755f..d205eff7f 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -22,6 +22,19 @@ var ( quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") ) +type minioObject struct { + *minio.Object +} + +func (m *minioObject) Stat() (os.FileInfo, error) { + oi, err := m.Object.Stat() + if err != nil { + return nil, err + } + + return &minioFileInfo{oi}, nil +} + // MinioStorage returns a minio bucket storage type MinioStorage struct { ctx context.Context @@ -69,7 +82,7 @@ func (m *MinioStorage) Open(path string) (Object, error) { if err != nil { return nil, err } - return object, nil + return &minioObject{object}, nil } // Save save a file to minio @@ -104,8 +117,20 @@ func (m minioFileInfo) ModTime() time.Time { return m.LastModified } +func (m minioFileInfo) IsDir() bool { + return strings.HasSuffix(m.ObjectInfo.Key, "/") +} + +func (m minioFileInfo) Mode() os.FileMode { + return os.ModePerm +} + +func (m minioFileInfo) Sys() interface{} { + return nil +} + // Stat returns the stat information of the object -func (m *MinioStorage) Stat(path string) (ObjectInfo, error) { +func (m *MinioStorage) Stat(path string) (os.FileInfo, error) { info, err := m.client.StatObject( m.ctx, m.bucket, @@ -135,3 +160,26 @@ func (m *MinioStorage) URL(path, name string) (*url.URL, error) { reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"") return m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams) } + +// IterateObjects iterates across the objects in the miniostorage +func (m *MinioStorage) IterateObjects(fn func(path string, obj Object) error) error { + var opts = minio.GetObjectOptions{} + lobjectCtx, cancel := context.WithCancel(m.ctx) + defer cancel() + for mObjInfo := range m.client.ListObjects(lobjectCtx, m.bucket, minio.ListObjectsOptions{ + Prefix: m.basePath, + Recursive: true, + }) { + object, err := m.client.GetObject(lobjectCtx, m.bucket, mObjInfo.Key, opts) + if err != nil { + return err + } + if err := func(object *minio.Object, fn func(path string, obj Object) error) error { + defer object.Close() + return fn(strings.TrimPrefix(m.basePath, mObjInfo.Key), &minioObject{object}) + }(object, fn); err != nil { + return err + } + } + return nil +} diff --git a/modules/storage/storage.go b/modules/storage/storage.go index e355d2459..2cf7b17b4 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -10,7 +10,7 @@ import ( "fmt" "io" "net/url" - "time" + "os" "code.gitea.io/gitea/modules/setting" ) @@ -18,28 +18,25 @@ import ( var ( // ErrURLNotSupported represents url is not supported ErrURLNotSupported = errors.New("url method not supported") + // ErrIterateObjectsNotSupported represents IterateObjects not supported + ErrIterateObjectsNotSupported = errors.New("iterateObjects method not supported") ) // Object represents the object on the storage type Object interface { io.ReadCloser io.Seeker -} - -// ObjectInfo represents the object info on the storage -type ObjectInfo interface { - Name() string - Size() int64 - ModTime() time.Time + Stat() (os.FileInfo, error) } // ObjectStorage represents an object storage to handle a bucket and files type ObjectStorage interface { Open(path string) (Object, error) Save(path string, r io.Reader) (int64, error) - Stat(path string) (ObjectInfo, error) + Stat(path string) (os.FileInfo, error) Delete(path string) error URL(path, name string) (*url.URL, error) + IterateObjects(func(path string, obj Object) error) error } // Copy copys a file from source ObjectStorage to dest ObjectStorage @@ -70,14 +67,15 @@ func Init() error { return initLFS() } -func initAttachments() error { +func initStorage(storageCfg setting.Storage) (ObjectStorage, error) { var err error - switch setting.Attachment.StoreType { - case "local": - Attachments, err = NewLocalStorage(setting.Attachment.Path) - case "minio": - minio := setting.Attachment.Minio - Attachments, err = NewMinioStorage( + var s ObjectStorage + switch storageCfg.Type { + case setting.LocalStorageType: + s, err = NewLocalStorage(storageCfg.Path) + case setting.MinioStorageType: + minio := storageCfg.Minio + s, err = NewMinioStorage( context.Background(), minio.Endpoint, minio.AccessKeyID, @@ -88,40 +86,22 @@ func initAttachments() error { minio.UseSSL, ) default: - return fmt.Errorf("Unsupported attachment store type: %s", setting.Attachment.StoreType) + return nil, fmt.Errorf("Unsupported attachment store type: %s", storageCfg.Type) } if err != nil { - return err + return nil, err } - return nil + return s, nil } -func initLFS() error { - var err error - switch setting.LFS.StoreType { - case "local": - LFS, err = NewLocalStorage(setting.LFS.ContentPath) - case "minio": - minio := setting.LFS.Minio - LFS, err = NewMinioStorage( - context.Background(), - minio.Endpoint, - minio.AccessKeyID, - minio.SecretAccessKey, - minio.Bucket, - minio.Location, - minio.BasePath, - minio.UseSSL, - ) - default: - return fmt.Errorf("Unsupported LFS store type: %s", setting.LFS.StoreType) - } - - if err != nil { - return err - } +func initAttachments() (err error) { + Attachments, err = initStorage(setting.Attachment.Storage) + return +} - return nil +func initLFS() (err error) { + LFS, err = initStorage(setting.LFS.Storage) + return } diff --git a/routers/install.go b/routers/install.go index 9eda18f94..028b5d3ee 100644 --- a/routers/install.go +++ b/routers/install.go @@ -71,7 +71,7 @@ func Install(ctx *context.Context) { // Application general settings form.AppName = setting.AppName form.RepoRootPath = setting.RepoRootPath - form.LFSRootPath = setting.LFS.ContentPath + form.LFSRootPath = setting.LFS.Path // Note(unknown): it's hard for Windows users change a running user, // so just use current one if config says default. diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index d3201faec..313704bc3 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -19,7 +19,7 @@ import ( func renderAttachmentSettings(ctx *context.Context) { ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled - ctx.Data["AttachmentStoreType"] = setting.Attachment.StoreType + ctx.Data["AttachmentStoreType"] = setting.Attachment.Storage.Type ctx.Data["AttachmentAllowedTypes"] = setting.Attachment.AllowedTypes ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles diff --git a/routers/repo/lfs.go b/routers/repo/lfs.go index 481a3e57a..6bee2dd10 100644 --- a/routers/repo/lfs.go +++ b/routers/repo/lfs.go @@ -12,7 +12,6 @@ import ( "io" "io/ioutil" "path" - "path/filepath" "sort" "strconv" "strings" @@ -29,7 +28,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" - "code.gitea.io/gitea/modules/util" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" @@ -355,8 +353,8 @@ func LFSDelete(ctx *context.Context) { // FIXME: Warning: the LFS store is not locked - and can't be locked - there could be a race condition here // Please note a similar condition happens in models/repo.go DeleteRepository if count == 0 { - oidPath := filepath.Join(oid[0:2], oid[2:4], oid[4:]) - err = util.Remove(filepath.Join(setting.LFS.ContentPath, oidPath)) + oidPath := path.Join(oid[0:2], oid[2:4], oid[4:]) + err = storage.LFS.Delete(oidPath) if err != nil { ctx.ServerError("LFSDelete", err) return