Refactor Cron and merge dashboard tasks (#10745)
	
		
	
				
					
				
			* Refactor Cron and merge dashboard tasks * Merge Cron and Dashboard tasks * Make every cron task report a system notice on completion * Refactor the creation of these tasks * Ensure that execution counts of tasks is correct * Allow cron tasks to be started from the cron page * golangci-lint fixes * Enforce that only one task with the same name can be registered Signed-off-by: Andrew Thornton <art27@cantab.net> * fix name check Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @guillep2k * as per @lafriks Signed-off-by: Andrew Thornton <art27@cantab.net> * Add git.CommandContext variants Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>tokarchuk/v1.17
							parent
							
								
									c18144086f
								
							
						
					
					
						commit
						9a2e47b23a
					
				@ -0,0 +1,72 @@ | 
				
			||||
// 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 cron | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"time" | 
				
			||||
 | 
				
			||||
	"code.gitea.io/gitea/models" | 
				
			||||
	"github.com/unknwon/i18n" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
// Config represents a basic configuration interface that cron task
 | 
				
			||||
type Config interface { | 
				
			||||
	IsEnabled() bool | 
				
			||||
	DoRunAtStart() bool | 
				
			||||
	GetSchedule() string | 
				
			||||
	FormatMessage(name, status string, doer *models.User, args ...interface{}) string | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// BaseConfig represents the basic config for a Cron task
 | 
				
			||||
type BaseConfig struct { | 
				
			||||
	Enabled    bool | 
				
			||||
	RunAtStart bool | 
				
			||||
	Schedule   string | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// OlderThanConfig represents a cron task with OlderThan setting
 | 
				
			||||
type OlderThanConfig struct { | 
				
			||||
	BaseConfig | 
				
			||||
	OlderThan time.Duration | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// UpdateExistingConfig represents a cron task with UpdateExisting setting
 | 
				
			||||
type UpdateExistingConfig struct { | 
				
			||||
	BaseConfig | 
				
			||||
	UpdateExisting bool | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// GetSchedule returns the schedule for the base config
 | 
				
			||||
func (b *BaseConfig) GetSchedule() string { | 
				
			||||
	return b.Schedule | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// IsEnabled returns the enabled status for the config
 | 
				
			||||
func (b *BaseConfig) IsEnabled() bool { | 
				
			||||
	return b.Enabled | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// DoRunAtStart returns whether the task should be run at the start
 | 
				
			||||
func (b *BaseConfig) DoRunAtStart() bool { | 
				
			||||
	return b.RunAtStart | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// FormatMessage returns a message for the task
 | 
				
			||||
func (b *BaseConfig) FormatMessage(name, status string, doer *models.User, args ...interface{}) string { | 
				
			||||
	realArgs := make([]interface{}, 0, len(args)+2) | 
				
			||||
	realArgs = append(realArgs, i18n.Tr("en-US", "admin.dashboard."+name)) | 
				
			||||
	if doer == nil { | 
				
			||||
		realArgs = append(realArgs, "(Cron)") | 
				
			||||
	} else { | 
				
			||||
		realArgs = append(realArgs, doer.Name) | 
				
			||||
	} | 
				
			||||
	if len(args) > 0 { | 
				
			||||
		realArgs = append(realArgs, args...) | 
				
			||||
	} | 
				
			||||
	if doer == nil || (doer.ID == -1 && doer.Name == "(Cron)") { | 
				
			||||
		return i18n.Tr("en-US", "admin.dashboard.cron."+status, realArgs...) | 
				
			||||
	} | 
				
			||||
	return i18n.Tr("en-US", "admin.dashboard.task."+status, realArgs...) | 
				
			||||
} | 
				
			||||
@ -0,0 +1,166 @@ | 
				
			||||
// 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 cron | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"context" | 
				
			||||
	"fmt" | 
				
			||||
	"reflect" | 
				
			||||
	"sync" | 
				
			||||
 | 
				
			||||
	"code.gitea.io/gitea/models" | 
				
			||||
	"code.gitea.io/gitea/modules/graceful" | 
				
			||||
	"code.gitea.io/gitea/modules/log" | 
				
			||||
	"code.gitea.io/gitea/modules/process" | 
				
			||||
	"code.gitea.io/gitea/modules/setting" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
var lock = sync.Mutex{} | 
				
			||||
var started = false | 
				
			||||
var tasks = []*Task{} | 
				
			||||
var tasksMap = map[string]*Task{} | 
				
			||||
 | 
				
			||||
// Task represents a Cron task
 | 
				
			||||
type Task struct { | 
				
			||||
	lock      sync.Mutex | 
				
			||||
	Name      string | 
				
			||||
	config    Config | 
				
			||||
	fun       func(context.Context, *models.User, Config) error | 
				
			||||
	ExecTimes int64 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// DoRunAtStart returns if this task should run at the start
 | 
				
			||||
func (t *Task) DoRunAtStart() bool { | 
				
			||||
	return t.config.DoRunAtStart() | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// IsEnabled returns if this task is enabled as cron task
 | 
				
			||||
func (t *Task) IsEnabled() bool { | 
				
			||||
	return t.config.IsEnabled() | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// GetConfig will return a copy of the task's config
 | 
				
			||||
func (t *Task) GetConfig() Config { | 
				
			||||
	if reflect.TypeOf(t.config).Kind() == reflect.Ptr { | 
				
			||||
		// Pointer:
 | 
				
			||||
		return reflect.New(reflect.ValueOf(t.config).Elem().Type()).Interface().(Config) | 
				
			||||
	} | 
				
			||||
	// Not pointer:
 | 
				
			||||
	return reflect.New(reflect.TypeOf(t.config)).Elem().Interface().(Config) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// Run will run the task incrementing the cron counter with no user defined
 | 
				
			||||
func (t *Task) Run() { | 
				
			||||
	t.RunWithUser(&models.User{ | 
				
			||||
		ID:        -1, | 
				
			||||
		Name:      "(Cron)", | 
				
			||||
		LowerName: "(cron)", | 
				
			||||
	}, t.config) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// RunWithUser will run the task incrementing the cron counter at the time with User
 | 
				
			||||
func (t *Task) RunWithUser(doer *models.User, config Config) { | 
				
			||||
	if !taskStatusTable.StartIfNotRunning(t.Name) { | 
				
			||||
		return | 
				
			||||
	} | 
				
			||||
	t.lock.Lock() | 
				
			||||
	if config == nil { | 
				
			||||
		config = t.config | 
				
			||||
	} | 
				
			||||
	t.ExecTimes++ | 
				
			||||
	t.lock.Unlock() | 
				
			||||
	defer func() { | 
				
			||||
		taskStatusTable.Stop(t.Name) | 
				
			||||
		if err := recover(); err != nil { | 
				
			||||
			// Recover a panic within the
 | 
				
			||||
			combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | 
				
			||||
			log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr) | 
				
			||||
		} | 
				
			||||
	}() | 
				
			||||
	graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { | 
				
			||||
		ctx, cancel := context.WithCancel(baseCtx) | 
				
			||||
		defer cancel() | 
				
			||||
		pm := process.GetManager() | 
				
			||||
		pid := pm.Add(config.FormatMessage(t.Name, "process", doer), cancel) | 
				
			||||
		defer pm.Remove(pid) | 
				
			||||
		if err := t.fun(ctx, doer, config); err != nil { | 
				
			||||
			if models.IsErrCancelled(err) { | 
				
			||||
				message := err.(models.ErrCancelled).Message | 
				
			||||
				if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil { | 
				
			||||
					log.Error("CreateNotice: %v", err) | 
				
			||||
				} | 
				
			||||
				return | 
				
			||||
			} | 
				
			||||
			if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil { | 
				
			||||
				log.Error("CreateNotice: %v", err) | 
				
			||||
			} | 
				
			||||
			return | 
				
			||||
		} | 
				
			||||
		if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil { | 
				
			||||
			log.Error("CreateNotice: %v", err) | 
				
			||||
		} | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// GetTask gets the named task
 | 
				
			||||
func GetTask(name string) *Task { | 
				
			||||
	lock.Lock() | 
				
			||||
	defer lock.Unlock() | 
				
			||||
	log.Info("Getting %s in %v", name, tasksMap[name]) | 
				
			||||
 | 
				
			||||
	return tasksMap[name] | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// RegisterTask allows a task to be registered with the cron service
 | 
				
			||||
func RegisterTask(name string, config Config, fun func(context.Context, *models.User, Config) error) error { | 
				
			||||
	log.Debug("Registering task: %s", name) | 
				
			||||
	_, err := setting.GetCronSettings(name, config) | 
				
			||||
	if err != nil { | 
				
			||||
		log.Error("Unable to register cron task with name: %s Error: %v", name, err) | 
				
			||||
		return err | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	task := &Task{ | 
				
			||||
		Name:   name, | 
				
			||||
		config: config, | 
				
			||||
		fun:    fun, | 
				
			||||
	} | 
				
			||||
	lock.Lock() | 
				
			||||
	locked := true | 
				
			||||
	defer func() { | 
				
			||||
		if locked { | 
				
			||||
			lock.Unlock() | 
				
			||||
		} | 
				
			||||
	}() | 
				
			||||
	if _, has := tasksMap[task.Name]; has { | 
				
			||||
		log.Error("A task with this name: %s has already been registered", name) | 
				
			||||
		return fmt.Errorf("duplicate task with name: %s", task.Name) | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	if config.IsEnabled() { | 
				
			||||
		// We cannot use the entry return as there is no way to lock it
 | 
				
			||||
		if _, err = c.AddJob(name, config.GetSchedule(), task); err != nil { | 
				
			||||
			log.Error("Unable to register cron task with name: %s Error: %v", name, err) | 
				
			||||
			return err | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	tasks = append(tasks, task) | 
				
			||||
	tasksMap[task.Name] = task | 
				
			||||
	if started && config.IsEnabled() && config.DoRunAtStart() { | 
				
			||||
		lock.Unlock() | 
				
			||||
		locked = false | 
				
			||||
		task.Run() | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return nil | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// RegisterTaskFatal will register a task but if there is an error log.Fatal
 | 
				
			||||
func RegisterTaskFatal(name string, config Config, fun func(context.Context, *models.User, Config) error) { | 
				
			||||
	if err := RegisterTask(name, config, fun); err != nil { | 
				
			||||
		log.Fatal("Unable to register cron task %s Error: %v", name, err) | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,118 @@ | 
				
			||||
// 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 cron | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"context" | 
				
			||||
	"time" | 
				
			||||
 | 
				
			||||
	"code.gitea.io/gitea/models" | 
				
			||||
	"code.gitea.io/gitea/modules/migrations" | 
				
			||||
	repository_service "code.gitea.io/gitea/modules/repository" | 
				
			||||
	mirror_service "code.gitea.io/gitea/services/mirror" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
func registerUpdateMirrorTask() { | 
				
			||||
	RegisterTaskFatal("update_mirrors", &BaseConfig{ | 
				
			||||
		Enabled:    true, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 10m", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return mirror_service.Update(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerRepoHealthCheck() { | 
				
			||||
	type RepoHealthCheckConfig struct { | 
				
			||||
		BaseConfig | 
				
			||||
		Timeout time.Duration | 
				
			||||
		Args    []string `delim:" "` | 
				
			||||
	} | 
				
			||||
	RegisterTaskFatal("repo_health_check", &RepoHealthCheckConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    true, | 
				
			||||
			RunAtStart: false, | 
				
			||||
			Schedule:   "@every 24h", | 
				
			||||
		}, | 
				
			||||
		Timeout: 60 * time.Second, | 
				
			||||
		Args:    []string{}, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		rhcConfig := config.(*RepoHealthCheckConfig) | 
				
			||||
		return repository_service.GitFsck(ctx, rhcConfig.Timeout, rhcConfig.Args) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerCheckRepoStats() { | 
				
			||||
	RegisterTaskFatal("check_repo_stats", &BaseConfig{ | 
				
			||||
		Enabled:    true, | 
				
			||||
		RunAtStart: true, | 
				
			||||
		Schedule:   "@every 24h", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return models.CheckRepoStats(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerArchiveCleanup() { | 
				
			||||
	RegisterTaskFatal("archive_cleanup", &OlderThanConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    true, | 
				
			||||
			RunAtStart: true, | 
				
			||||
			Schedule:   "@every 24h", | 
				
			||||
		}, | 
				
			||||
		OlderThan: 24 * time.Hour, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		acConfig := config.(*OlderThanConfig) | 
				
			||||
		return models.DeleteOldRepositoryArchives(ctx, acConfig.OlderThan) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerSyncExternalUsers() { | 
				
			||||
	RegisterTaskFatal("sync_external_users", &UpdateExistingConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    true, | 
				
			||||
			RunAtStart: false, | 
				
			||||
			Schedule:   "@every 24h", | 
				
			||||
		}, | 
				
			||||
		UpdateExisting: true, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		realConfig := config.(*UpdateExistingConfig) | 
				
			||||
		return models.SyncExternalUsers(ctx, realConfig.UpdateExisting) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerDeletedBranchesCleanup() { | 
				
			||||
	RegisterTaskFatal("deleted_branches_cleanup", &OlderThanConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    true, | 
				
			||||
			RunAtStart: true, | 
				
			||||
			Schedule:   "@every 24h", | 
				
			||||
		}, | 
				
			||||
		OlderThan: 24 * time.Hour, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		realConfig := config.(*OlderThanConfig) | 
				
			||||
		models.RemoveOldDeletedBranches(ctx, realConfig.OlderThan) | 
				
			||||
		return nil | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerUpdateMigrationPosterID() { | 
				
			||||
	RegisterTaskFatal("update_migration_poster_id", &BaseConfig{ | 
				
			||||
		Enabled:    true, | 
				
			||||
		RunAtStart: true, | 
				
			||||
		Schedule:   "@every 24h", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return migrations.UpdateMigrationPosterID(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func initBasicTasks() { | 
				
			||||
	registerUpdateMirrorTask() | 
				
			||||
	registerRepoHealthCheck() | 
				
			||||
	registerCheckRepoStats() | 
				
			||||
	registerArchiveCleanup() | 
				
			||||
	registerSyncExternalUsers() | 
				
			||||
	registerDeletedBranchesCleanup() | 
				
			||||
	registerUpdateMigrationPosterID() | 
				
			||||
} | 
				
			||||
@ -0,0 +1,119 @@ | 
				
			||||
// 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 cron | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"context" | 
				
			||||
	"time" | 
				
			||||
 | 
				
			||||
	"code.gitea.io/gitea/models" | 
				
			||||
	repo_module "code.gitea.io/gitea/modules/repository" | 
				
			||||
	"code.gitea.io/gitea/modules/setting" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
func registerDeleteInactiveUsers() { | 
				
			||||
	RegisterTaskFatal("delete_inactive_accounts", &OlderThanConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    false, | 
				
			||||
			RunAtStart: false, | 
				
			||||
			Schedule:   "@annually", | 
				
			||||
		}, | 
				
			||||
		OlderThan: 0 * time.Second, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		olderThanConfig := config.(*OlderThanConfig) | 
				
			||||
		return models.DeleteInactiveUsers(ctx, olderThanConfig.OlderThan) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerDeleteRepositoryArchives() { | 
				
			||||
	RegisterTaskFatal("delete_repo_archives", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@annually", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return models.DeleteRepositoryArchives(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerGarbageCollectRepositories() { | 
				
			||||
	type RepoHealthCheckConfig struct { | 
				
			||||
		BaseConfig | 
				
			||||
		Timeout time.Duration | 
				
			||||
		Args    []string `delim:" "` | 
				
			||||
	} | 
				
			||||
	RegisterTaskFatal("git_gc_repos", &RepoHealthCheckConfig{ | 
				
			||||
		BaseConfig: BaseConfig{ | 
				
			||||
			Enabled:    false, | 
				
			||||
			RunAtStart: false, | 
				
			||||
			Schedule:   "@every 72h", | 
				
			||||
		}, | 
				
			||||
		Timeout: time.Duration(setting.Git.Timeout.GC) * time.Second, | 
				
			||||
		Args:    setting.Git.GCArgs, | 
				
			||||
	}, func(ctx context.Context, _ *models.User, config Config) error { | 
				
			||||
		rhcConfig := config.(*RepoHealthCheckConfig) | 
				
			||||
		return repo_module.GitGcRepos(ctx, rhcConfig.Timeout, rhcConfig.Args...) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerRewriteAllPublicKeys() { | 
				
			||||
	RegisterTaskFatal("resync_all_sshkeys", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 72h", | 
				
			||||
	}, func(_ context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return models.RewriteAllPublicKeys() | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerRepositoryUpdateHook() { | 
				
			||||
	RegisterTaskFatal("resync_all_hooks", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 72h", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return repo_module.SyncRepositoryHooks(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerReinitMissingRepositories() { | 
				
			||||
	RegisterTaskFatal("reinit_missing_repos", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 72h", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return repo_module.ReinitMissingRepositories(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerDeleteMissingRepositories() { | 
				
			||||
	RegisterTaskFatal("delete_missing_repos", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 72h", | 
				
			||||
	}, func(ctx context.Context, user *models.User, _ Config) error { | 
				
			||||
		return repo_module.DeleteMissingRepositories(ctx, user) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func registerRemoveRandomAvatars() { | 
				
			||||
	RegisterTaskFatal("delete_generated_repository_avatars", &BaseConfig{ | 
				
			||||
		Enabled:    false, | 
				
			||||
		RunAtStart: false, | 
				
			||||
		Schedule:   "@every 72h", | 
				
			||||
	}, func(ctx context.Context, _ *models.User, _ Config) error { | 
				
			||||
		return models.RemoveRandomAvatars(ctx) | 
				
			||||
	}) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func initExtendedTasks() { | 
				
			||||
	registerDeleteInactiveUsers() | 
				
			||||
	registerDeleteRepositoryArchives() | 
				
			||||
	registerGarbageCollectRepositories() | 
				
			||||
	registerRewriteAllPublicKeys() | 
				
			||||
	registerRepositoryUpdateHook() | 
				
			||||
	registerReinitMissingRepositories() | 
				
			||||
	registerDeleteMissingRepositories() | 
				
			||||
	registerRemoveRandomAvatars() | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue