Add default storage configurations (#12813)
Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
		
							parent
							
								
									4c6ac08182
								
							
						
					
					
						commit
						3878e985b6
					
				| @ -98,3 +98,6 @@ issues: | |||||||
|     - path: models/update.go |     - path: models/update.go | ||||||
|       linters: |       linters: | ||||||
|         - unused |         - unused | ||||||
|  |     - path: cmd/dump.go | ||||||
|  |       linters: | ||||||
|  |         - dupl | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								cmd/dump.go
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								cmd/dump.go
									
									
									
									
									
								
							| @ -18,6 +18,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	"code.gitea.io/gitea/modules/storage" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/macaron/session" | 	"gitea.com/macaron/session" | ||||||
| @ -57,6 +58,8 @@ func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose boo | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Could not open directory %s: %s", absPath, err) | 		return fmt.Errorf("Could not open directory %s: %s", absPath, err) | ||||||
| 	} | 	} | ||||||
|  | 	defer dir.Close() | ||||||
|  | 
 | ||||||
| 	files, err := dir.Readdir(0) | 	files, err := dir.Readdir(0) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Unable to list files in %s: %s", absPath, err) | 		return fmt.Errorf("Unable to list files in %s: %s", absPath, err) | ||||||
| @ -197,6 +200,10 @@ func runDump(ctx *cli.Context) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := storage.Init(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if file == nil { | 	if file == nil { | ||||||
| 		file, err = os.Create(fileName) | 		file, err = os.Create(fileName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -231,11 +238,21 @@ func runDump(ctx *cli.Context) error { | |||||||
| 			fatal("Failed to include repositories: %v", err) | 			fatal("Failed to include repositories: %v", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if _, err := os.Stat(setting.LFS.ContentPath); !os.IsNotExist(err) { | 		if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { | ||||||
| 			log.Info("Dumping lfs... %s", setting.LFS.ContentPath) | 			info, err := object.Stat() | ||||||
| 			if err := addRecursive(w, "lfs", setting.LFS.ContentPath, verbose); err != nil { | 			if err != nil { | ||||||
| 				fatal("Failed to include lfs: %v", err) | 				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.RepoRootPath) | ||||||
| 		excludes = append(excludes, setting.LFS.ContentPath) | 		excludes = append(excludes, setting.LFS.Path) | ||||||
|  | 		excludes = append(excludes, setting.Attachment.Path) | ||||||
| 		excludes = append(excludes, setting.LogRootPath) | 		excludes = append(excludes, setting.LogRootPath) | ||||||
| 		if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { | 		if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { | ||||||
| 			fatal("Failed to include data directory: %v", err) | 			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,
 | 	// 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
 | 	// ensuring that it's clear the dump is skipped whether the directory's initialized
 | ||||||
| 	// yet or not.
 | 	// yet or not.
 | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ package cmd | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/models/migrations" | 	"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", | 			Usage: "Kinds of files to migrate, currently only 'attachments' is supported", | ||||||
| 		}, | 		}, | ||||||
| 		cli.StringFlag{ | 		cli.StringFlag{ | ||||||
| 			Name:  "store, s", | 			Name:  "storage, s", | ||||||
| 			Value: "local", | 			Value: setting.LocalStorageType, | ||||||
| 			Usage: "New storage type, local or minio", | 			Usage: "New storage type, local or minio", | ||||||
| 		}, | 		}, | ||||||
| 		cli.StringFlag{ | 		cli.StringFlag{ | ||||||
| @ -112,15 +113,15 @@ func runMigrateStorage(ctx *cli.Context) error { | |||||||
| 
 | 
 | ||||||
| 	var dstStorage storage.ObjectStorage | 	var dstStorage storage.ObjectStorage | ||||||
| 	var err error | 	var err error | ||||||
| 	switch ctx.String("store") { | 	switch strings.ToLower(ctx.String("storage")) { | ||||||
| 	case "local": | 	case setting.LocalStorageType: | ||||||
| 		p := ctx.String("path") | 		p := ctx.String("path") | ||||||
| 		if p == "" { | 		if p == "" { | ||||||
| 			log.Fatal("Path must be given when store is loal") | 			log.Fatal("Path must be given when storage is loal") | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 		dstStorage, err = storage.NewLocalStorage(p) | 		dstStorage, err = storage.NewLocalStorage(p) | ||||||
| 	case "minio": | 	case setting.MinioStorageType: | ||||||
| 		dstStorage, err = storage.NewMinioStorage( | 		dstStorage, err = storage.NewMinioStorage( | ||||||
| 			context.Background(), | 			context.Background(), | ||||||
| 			ctx.String("minio-endpoint"), | 			ctx.String("minio-endpoint"), | ||||||
| @ -132,14 +133,14 @@ func runMigrateStorage(ctx *cli.Context) error { | |||||||
| 			ctx.Bool("minio-use-ssl"), | 			ctx.Bool("minio-use-ssl"), | ||||||
| 		) | 		) | ||||||
| 	default: | 	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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	tp := ctx.String("type") | 	tp := strings.ToLower(ctx.String("type")) | ||||||
| 	switch tp { | 	switch tp { | ||||||
| 	case "attachments": | 	case "attachments": | ||||||
| 		if err := migrateAttachments(dstStorage); err != nil { | 		if err := migrateAttachments(dstStorage); err != nil { | ||||||
|  | |||||||
| @ -778,25 +778,25 @@ MAX_SIZE = 4 | |||||||
| MAX_FILES = 5 | MAX_FILES = 5 | ||||||
| ; Storage type for attachments, `local` for local disk or `minio` for s3 compatible | ; Storage type for attachments, `local` for local disk or `minio` for s3 compatible | ||||||
| ; object storage service, default is `local`. | ; 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 | ; Allows the storage driver to redirect to authenticated URLs to serve files directly | ||||||
| ; Currently, only `minio` is supported. | ; Currently, only `minio` is supported. | ||||||
| SERVE_DIRECT = false | 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 | 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_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_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_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_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_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_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 | MINIO_USE_SSL = false | ||||||
| 
 | 
 | ||||||
| [time] | [time] | ||||||
| @ -1161,3 +1161,28 @@ QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" | |||||||
| MAX_ATTEMPTS = 3 | MAX_ATTEMPTS = 3 | ||||||
| ; Backoff time per http/https request retry (seconds) | ; Backoff time per http/https request retry (seconds) | ||||||
| RETRY_BACKOFF = 3 | 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 | ||||||
|  | |||||||
| @ -220,16 +220,6 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. | |||||||
| - `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. | - `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. | ||||||
| 
 | 
 | ||||||
| - `LFS_START_SERVER`: **false**: Enables git-lfs support. | - `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`: **\<empty\>**: LFS authentication secret, change this a unique string. | - `LFS_JWT_SECRET`: **\<empty\>**: 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_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). | - `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. |    Use `*/*` for all types. | ||||||
| - `MAX_SIZE`: **4**: Maximum size (MB). | - `MAX_SIZE`: **4**: Maximum size (MB). | ||||||
| - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. | - `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. | - `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` | - `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 STORE_TYPE is `minio` | - `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 STORE_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 STORE_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 STORE_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 STORE_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 STORE_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 STORE_TYPE is `minio` | - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio` | ||||||
| 
 | 
 | ||||||
| ## Log (`log`) | ## 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. | - `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations. | ||||||
| - `RETRY_BACKOFF`: **3**: Backoff time per http/https request retry (seconds) | - `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`) | ## Other (`other`) | ||||||
| 
 | 
 | ||||||
| - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. | - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. | ||||||
|  | |||||||
| @ -72,16 +72,6 @@ menu: | |||||||
| - `LANDING_PAGE`: 未登录用户的默认页面,可选 `home` 或 `explore`。 | - `LANDING_PAGE`: 未登录用户的默认页面,可选 `home` 或 `explore`。 | ||||||
| 
 | 
 | ||||||
| - `LFS_START_SERVER`: 是否启用 git-lfs 支持. 可以为 `true` 或 `false`, 默认是 `false`。 | - `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 认证密钥,改成自己的。 | - `LFS_JWT_SECRET`: LFS 认证密钥,改成自己的。 | ||||||
| 
 | 
 | ||||||
| ## Database (`database`) | ## Database (`database`) | ||||||
| @ -198,15 +188,15 @@ menu: | |||||||
| - `ALLOWED_TYPES`: 允许上传的附件类型。比如:`image/jpeg|image/png`,用 `*/*` 表示允许任何类型。 | - `ALLOWED_TYPES`: 允许上传的附件类型。比如:`image/jpeg|image/png`,用 `*/*` 表示允许任何类型。 | ||||||
| - `MAX_SIZE`: 附件最大限制,单位 MB,比如: `4`。 | - `MAX_SIZE`: 附件最大限制,单位 MB,比如: `4`。 | ||||||
| - `MAX_FILES`: 一次最多上传的附件数量,比如: `5`。 | - `MAX_FILES`: 一次最多上传的附件数量,比如: `5`。 | ||||||
| - `STORE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 | - `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 | ||||||
| - `PATH`: **data/attachments**: 附件存储路径,仅当 `STORE_TYPE` 为 `local` 时有效。 | - `PATH`: **data/attachments**: 附件存储路径,仅当 `STORAGE_TYPE` 为 `local` 时有效。 | ||||||
| - `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| - `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| - `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORE_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,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| - `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORE_TYPE` 是 `minio` 时有效。 | - `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 | ||||||
| 
 | 
 | ||||||
| 关于 `ALLOWED_TYPES`, 在 (*)unix 系统中可以使用`file -I <filename>` 来快速获得对应的 `MIME type`。 | 关于 `ALLOWED_TYPES`, 在 (*)unix 系统中可以使用`file -I <filename>` 来快速获得对应的 `MIME type`。 | ||||||
| 
 | 
 | ||||||
| @ -310,6 +300,55 @@ IS_INPUT_FILE = false | |||||||
| - `MAX_ATTEMPTS`: **3**: 在迁移过程中的 http/https 请求重试次数。 | - `MAX_ATTEMPTS`: **3**: 在迁移过程中的 http/https 请求重试次数。 | ||||||
| - `RETRY_BACKOFF`: **3**: 等待下一次重试的时间,单位秒。 | - `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`) | ## Other (`other`) | ||||||
| 
 | 
 | ||||||
| - `SHOW_FOOTER_BRANDING`: 为真则在页面底部显示Gitea的字样。 | - `SHOW_FOOTER_BRANDING`: 为真则在页面底部显示Gitea的字样。 | ||||||
|  | |||||||
| @ -70,6 +70,10 @@ func RemoveAllWithNotice(title, path string) { | |||||||
| // RemoveStorageWithNotice removes a file from the storage and
 | // RemoveStorageWithNotice removes a file from the storage and
 | ||||||
| // creates a system notice when error occurs.
 | // creates a system notice when error occurs.
 | ||||||
| func RemoveStorageWithNotice(bucket storage.ObjectStorage, title, path string) { | 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 { | 	if err := bucket.Delete(path); err != nil { | ||||||
| 		desc := fmt.Sprintf("%s [%s]: %v", title, path, err) | 		desc := fmt.Sprintf("%s [%s]: %v", title, path, err) | ||||||
| 		log.Warn(title+" [%s]: %v", path, err) | 		log.Warn(title+" [%s]: %v", path, err) | ||||||
|  | |||||||
| @ -1748,8 +1748,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		oidPath := filepath.Join(setting.LFS.ContentPath, v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)]) | 		removeStorageWithNotice(sess, storage.LFS, "Delete orphaned LFS file", v.RelativePath()) | ||||||
| 		removeAllWithNotice(sess, "Delete orphaned LFS file", oidPath) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { | 	if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { | ||||||
|  | |||||||
| @ -67,9 +67,11 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fatalTestError("url.Parse: %v\n", err) | 		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.Storage.Type = setting.LocalStorageType | ||||||
| 	setting.LFS.ContentPath = filepath.Join(setting.AppDataPath, "lfs") | 	setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") | ||||||
| 	if err = storage.Init(); err != nil { | 	if err = storage.Init(); err != nil { | ||||||
| 		fatalTestError("storage.Init: %v\n", err) | 		fatalTestError("storage.Init: %v\n", err) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -5,42 +5,25 @@ | |||||||
| package setting | package setting | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	// Attachment settings
 | 	// Attachment settings
 | ||||||
| 	Attachment = struct { | 	Attachment = struct { | ||||||
| 		StoreType   string | 		Storage | ||||||
| 		Path        string |  | ||||||
| 		ServeDirect bool |  | ||||||
| 		Minio       struct { |  | ||||||
| 			Endpoint        string |  | ||||||
| 			AccessKeyID     string |  | ||||||
| 			SecretAccessKey string |  | ||||||
| 			UseSSL          bool |  | ||||||
| 			Bucket          string |  | ||||||
| 			Location        string |  | ||||||
| 			BasePath        string |  | ||||||
| 		} |  | ||||||
| 		AllowedTypes string | 		AllowedTypes string | ||||||
| 		MaxSize      int64 | 		MaxSize      int64 | ||||||
| 		MaxFiles     int | 		MaxFiles     int | ||||||
| 		Enabled      bool | 		Enabled      bool | ||||||
| 	}{ | 	}{ | ||||||
| 		StoreType:   "local", | 		Storage: Storage{ | ||||||
|  | 			Type:        LocalStorageType, | ||||||
| 			ServeDirect: false, | 			ServeDirect: false, | ||||||
| 		Minio: struct { | 		}, | ||||||
| 			Endpoint        string |  | ||||||
| 			AccessKeyID     string |  | ||||||
| 			SecretAccessKey string |  | ||||||
| 			UseSSL          bool |  | ||||||
| 			Bucket          string |  | ||||||
| 			Location        string |  | ||||||
| 			BasePath        string |  | ||||||
| 		}{}, |  | ||||||
| 		AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip", | 		AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip", | ||||||
| 		MaxSize:      4, | 		MaxSize:      4, | ||||||
| 		MaxFiles:     5, | 		MaxFiles:     5, | ||||||
| @ -50,22 +33,36 @@ var ( | |||||||
| 
 | 
 | ||||||
| func newAttachmentService() { | func newAttachmentService() { | ||||||
| 	sec := Cfg.Section("attachment") | 	sec := Cfg.Section("attachment") | ||||||
| 	Attachment.StoreType = sec.Key("STORE_TYPE").MustString("local") | 	Attachment.Storage.Type = sec.Key("STORAGE_TYPE").MustString("") | ||||||
| 	Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false) | 	if Attachment.Storage.Type == "" { | ||||||
| 	switch Attachment.StoreType { | 		Attachment.Storage.Type = "default" | ||||||
| 	case "local": |  | ||||||
| 		Attachment.Path = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) |  | ||||||
| 		if !filepath.IsAbs(Attachment.Path) { |  | ||||||
| 			Attachment.Path = path.Join(AppWorkPath, Attachment.Path) |  | ||||||
| 	} | 	} | ||||||
| 	case "minio": | 
 | ||||||
| 		Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString("localhost:9000") | 	if Attachment.Storage.Type != LocalStorageType && Attachment.Storage.Type != MinioStorageType { | ||||||
| 		Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString("") | 		storage, ok := storages[Attachment.Storage.Type] | ||||||
| 		Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("") | 		if !ok { | ||||||
| 		Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString("gitea") | 			log.Fatal("Failed to get attachment storage type: %s", Attachment.Storage.Type) | ||||||
| 		Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString("us-east-1") | 		} | ||||||
|  | 		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 = filepath.Join(AppWorkPath, Attachment.Path) | ||||||
|  | 		} | ||||||
|  | 	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.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) | 	Attachment.AllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1) | ||||||
|  | |||||||
| @ -21,27 +21,14 @@ import ( | |||||||
| // LFS represents the configuration for Git LFS
 | // LFS represents the configuration for Git LFS
 | ||||||
| var LFS = struct { | var LFS = struct { | ||||||
| 	StartServer     bool          `ini:"LFS_START_SERVER"` | 	StartServer     bool          `ini:"LFS_START_SERVER"` | ||||||
| 	ContentPath     string        `ini:"LFS_CONTENT_PATH"` |  | ||||||
| 	JWTSecretBase64 string        `ini:"LFS_JWT_SECRET"` | 	JWTSecretBase64 string        `ini:"LFS_JWT_SECRET"` | ||||||
| 	JWTSecretBytes  []byte        `ini:"-"` | 	JWTSecretBytes  []byte        `ini:"-"` | ||||||
| 	HTTPAuthExpiry  time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` | 	HTTPAuthExpiry  time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` | ||||||
| 	MaxFileSize     int64         `ini:"LFS_MAX_FILE_SIZE"` | 	MaxFileSize     int64         `ini:"LFS_MAX_FILE_SIZE"` | ||||||
| 	LocksPagingNum  int           `ini:"LFS_LOCKS_PAGING_NUM"` | 	LocksPagingNum  int           `ini:"LFS_LOCKS_PAGING_NUM"` | ||||||
| 
 | 
 | ||||||
| 	StoreType   string | 	Storage | ||||||
| 	ServeDirect bool | }{} | ||||||
| 	Minio       struct { |  | ||||||
| 		Endpoint        string |  | ||||||
| 		AccessKeyID     string |  | ||||||
| 		SecretAccessKey string |  | ||||||
| 		UseSSL          bool |  | ||||||
| 		Bucket          string |  | ||||||
| 		Location        string |  | ||||||
| 		BasePath        string |  | ||||||
| 	} |  | ||||||
| }{ |  | ||||||
| 	StoreType: "local", |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func newLFSService() { | func newLFSService() { | ||||||
| 	sec := Cfg.Section("server") | 	sec := Cfg.Section("server") | ||||||
| @ -49,10 +36,41 @@ func newLFSService() { | |||||||
| 		log.Fatal("Failed to map LFS settings: %v", err) | 		log.Fatal("Failed to map LFS settings: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	LFS.ContentPath = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs")) | 	lfsSec := Cfg.Section("lfs") | ||||||
| 	if !filepath.IsAbs(LFS.ContentPath) { | 	LFS.Storage.Type = lfsSec.Key("STORAGE_TYPE").MustString("") | ||||||
| 		LFS.ContentPath = filepath.Join(AppWorkPath, LFS.ContentPath) | 	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 { | 	if LFS.LocksPagingNum == 0 { | ||||||
| 		LFS.LocksPagingNum = 50 | 		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.
 | // CheckLFSVersion will check lfs version, if not satisfied, then disable it.
 | ||||||
| func CheckLFSVersion() { | func CheckLFSVersion() { | ||||||
| 	if LFS.StartServer { | 	if LFS.StartServer { | ||||||
|  | |||||||
| @ -691,8 +691,6 @@ func NewContext() { | |||||||
| 	SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) | 	SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) | ||||||
| 	SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) | 	SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) | ||||||
| 
 | 
 | ||||||
| 	newLFSService() |  | ||||||
| 
 |  | ||||||
| 	if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil { | 	if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil { | ||||||
| 		log.Fatal("Failed to OAuth2 settings: %v", err) | 		log.Fatal("Failed to OAuth2 settings: %v", err) | ||||||
| 		return | 		return | ||||||
| @ -761,7 +759,9 @@ func NewContext() { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	newStorageService() | ||||||
| 	newAttachmentService() | 	newAttachmentService() | ||||||
|  | 	newLFSService() | ||||||
| 
 | 
 | ||||||
| 	timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("") | 	timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("") | ||||||
| 	if timeFormatKey != "" { | 	if timeFormatKey != "" { | ||||||
| @ -1017,7 +1017,6 @@ func NewServices() { | |||||||
| 	InitDBConfig() | 	InitDBConfig() | ||||||
| 	newService() | 	newService() | ||||||
| 	NewLogServices(false) | 	NewLogServices(false) | ||||||
| 	ensureLFSDirectory() |  | ||||||
| 	newCacheService() | 	newCacheService() | ||||||
| 	newSessionService() | 	newSessionService() | ||||||
| 	newCORSService() | 	newCORSService() | ||||||
|  | |||||||
							
								
								
									
										69
									
								
								modules/setting/storage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								modules/setting/storage.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -59,7 +59,7 @@ func (l *LocalStorage) Save(path string, r io.Reader) (int64, error) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Stat returns the info of the file
 | // 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)) | 	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) { | func (l *LocalStorage) URL(path, name string) (*url.URL, error) { | ||||||
| 	return nil, ErrURLNotSupported | 	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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | |||||||
| @ -22,6 +22,19 @@ var ( | |||||||
| 	quoteEscaper               = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") | 	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
 | // MinioStorage returns a minio bucket storage
 | ||||||
| type MinioStorage struct { | type MinioStorage struct { | ||||||
| 	ctx      context.Context | 	ctx      context.Context | ||||||
| @ -69,7 +82,7 @@ func (m *MinioStorage) Open(path string) (Object, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return object, nil | 	return &minioObject{object}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Save save a file to minio
 | // Save save a file to minio
 | ||||||
| @ -104,8 +117,20 @@ func (m minioFileInfo) ModTime() time.Time { | |||||||
| 	return m.LastModified | 	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
 | // 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( | 	info, err := m.client.StatObject( | ||||||
| 		m.ctx, | 		m.ctx, | ||||||
| 		m.bucket, | 		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)+"\"") | 	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) | 	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 | ||||||
|  | } | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"time" | 	"os" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
| @ -18,28 +18,25 @@ import ( | |||||||
| var ( | var ( | ||||||
| 	// ErrURLNotSupported represents url is not supported
 | 	// ErrURLNotSupported represents url is not supported
 | ||||||
| 	ErrURLNotSupported = errors.New("url method 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
 | // Object represents the object on the storage
 | ||||||
| type Object interface { | type Object interface { | ||||||
| 	io.ReadCloser | 	io.ReadCloser | ||||||
| 	io.Seeker | 	io.Seeker | ||||||
| } | 	Stat() (os.FileInfo, error) | ||||||
| 
 |  | ||||||
| // ObjectInfo represents the object info on the storage
 |  | ||||||
| type ObjectInfo interface { |  | ||||||
| 	Name() string |  | ||||||
| 	Size() int64 |  | ||||||
| 	ModTime() time.Time |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ObjectStorage represents an object storage to handle a bucket and files
 | // ObjectStorage represents an object storage to handle a bucket and files
 | ||||||
| type ObjectStorage interface { | type ObjectStorage interface { | ||||||
| 	Open(path string) (Object, error) | 	Open(path string) (Object, error) | ||||||
| 	Save(path string, r io.Reader) (int64, error) | 	Save(path string, r io.Reader) (int64, error) | ||||||
| 	Stat(path string) (ObjectInfo, error) | 	Stat(path string) (os.FileInfo, error) | ||||||
| 	Delete(path string) error | 	Delete(path string) error | ||||||
| 	URL(path, name string) (*url.URL, 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
 | // Copy copys a file from source ObjectStorage to dest ObjectStorage
 | ||||||
| @ -70,14 +67,15 @@ func Init() error { | |||||||
| 	return initLFS() | 	return initLFS() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func initAttachments() error { | func initStorage(storageCfg setting.Storage) (ObjectStorage, error) { | ||||||
| 	var err error | 	var err error | ||||||
| 	switch setting.Attachment.StoreType { | 	var s ObjectStorage | ||||||
| 	case "local": | 	switch storageCfg.Type { | ||||||
| 		Attachments, err = NewLocalStorage(setting.Attachment.Path) | 	case setting.LocalStorageType: | ||||||
| 	case "minio": | 		s, err = NewLocalStorage(storageCfg.Path) | ||||||
| 		minio := setting.Attachment.Minio | 	case setting.MinioStorageType: | ||||||
| 		Attachments, err = NewMinioStorage( | 		minio := storageCfg.Minio | ||||||
|  | 		s, err = NewMinioStorage( | ||||||
| 			context.Background(), | 			context.Background(), | ||||||
| 			minio.Endpoint, | 			minio.Endpoint, | ||||||
| 			minio.AccessKeyID, | 			minio.AccessKeyID, | ||||||
| @ -88,40 +86,22 @@ func initAttachments() error { | |||||||
| 			minio.UseSSL, | 			minio.UseSSL, | ||||||
| 		) | 		) | ||||||
| 	default: | 	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 { | 	if err != nil { | ||||||
| 		return err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return s, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func initLFS() error { | func initAttachments() (err error) { | ||||||
| 	var err error | 	Attachments, err = initStorage(setting.Attachment.Storage) | ||||||
| 	switch setting.LFS.StoreType { | 	return | ||||||
| 	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 { | func initLFS() (err error) { | ||||||
| 		return err | 	LFS, err = initStorage(setting.LFS.Storage) | ||||||
| 	} | 	return | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -71,7 +71,7 @@ func Install(ctx *context.Context) { | |||||||
| 	// Application general settings
 | 	// Application general settings
 | ||||||
| 	form.AppName = setting.AppName | 	form.AppName = setting.AppName | ||||||
| 	form.RepoRootPath = setting.RepoRootPath | 	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,
 | 	// Note(unknown): it's hard for Windows users change a running user,
 | ||||||
| 	// 	so just use current one if config says default.
 | 	// 	so just use current one if config says default.
 | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import ( | |||||||
| 
 | 
 | ||||||
| func renderAttachmentSettings(ctx *context.Context) { | func renderAttachmentSettings(ctx *context.Context) { | ||||||
| 	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled | 	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["AttachmentAllowedTypes"] = setting.Attachment.AllowedTypes | ||||||
| 	ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize | 	ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize | ||||||
| 	ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles | 	ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"path" | 	"path" | ||||||
| 	"path/filepath" |  | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -29,7 +28,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
| 
 | 
 | ||||||
| 	gogit "github.com/go-git/go-git/v5" | 	gogit "github.com/go-git/go-git/v5" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" | 	"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
 | 	// 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
 | 	// Please note a similar condition happens in models/repo.go DeleteRepository
 | ||||||
| 	if count == 0 { | 	if count == 0 { | ||||||
| 		oidPath := filepath.Join(oid[0:2], oid[2:4], oid[4:]) | 		oidPath := path.Join(oid[0:2], oid[2:4], oid[4:]) | ||||||
| 		err = util.Remove(filepath.Join(setting.LFS.ContentPath, oidPath)) | 		err = storage.LFS.Delete(oidPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.ServerError("LFSDelete", err) | 			ctx.ServerError("LFSDelete", err) | ||||||
| 			return | 			return | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user