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>
This commit is contained in:
parent
c18144086f
commit
9a2e47b23a
25 changed files with 851 additions and 453 deletions
|
@ -151,7 +151,7 @@ func TestLDAPUserSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer prepareTestEnv(t)()
|
defer prepareTestEnv(t)()
|
||||||
addAuthSourceLDAP(t, "")
|
addAuthSourceLDAP(t, "")
|
||||||
models.SyncExternalUsers(context.Background())
|
models.SyncExternalUsers(context.Background(), true)
|
||||||
|
|
||||||
session := loginUser(t, "user1")
|
session := loginUser(t, "user1")
|
||||||
// Check if users exists
|
// Check if users exists
|
||||||
|
@ -216,7 +216,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
|
||||||
defer prepareTestEnv(t)()
|
defer prepareTestEnv(t)()
|
||||||
addAuthSourceLDAP(t, "sshPublicKey")
|
addAuthSourceLDAP(t, "sshPublicKey")
|
||||||
|
|
||||||
models.SyncExternalUsers(context.Background())
|
models.SyncExternalUsers(context.Background(), true)
|
||||||
|
|
||||||
// Check if users has SSH keys synced
|
// Check if users has SSH keys synced
|
||||||
for _, u := range gitLDAPUsers {
|
for _, u := range gitLDAPUsers {
|
||||||
|
|
|
@ -20,6 +20,8 @@ type NoticeType int
|
||||||
const (
|
const (
|
||||||
//NoticeRepository type
|
//NoticeRepository type
|
||||||
NoticeRepository NoticeType = iota + 1
|
NoticeRepository NoticeType = iota + 1
|
||||||
|
// NoticeTask type
|
||||||
|
NoticeTask
|
||||||
)
|
)
|
||||||
|
|
||||||
// Notice represents a system notice for admin.
|
// Notice represents a system notice for admin.
|
||||||
|
@ -36,11 +38,14 @@ func (n *Notice) TrStr() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNotice creates new system notice.
|
// CreateNotice creates new system notice.
|
||||||
func CreateNotice(tp NoticeType, desc string) error {
|
func CreateNotice(tp NoticeType, desc string, args ...interface{}) error {
|
||||||
return createNotice(x, tp, desc)
|
return createNotice(x, tp, desc, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNotice(e Engine, tp NoticeType, desc string) error {
|
func createNotice(e Engine, tp NoticeType, desc string, args ...interface{}) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
desc = fmt.Sprintf(desc, args...)
|
||||||
|
}
|
||||||
n := &Notice{
|
n := &Notice{
|
||||||
Type: tp,
|
Type: tp,
|
||||||
Description: desc,
|
Description: desc,
|
||||||
|
@ -50,8 +55,8 @@ func createNotice(e Engine, tp NoticeType, desc string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepositoryNotice creates new system notice with type NoticeRepository.
|
// CreateRepositoryNotice creates new system notice with type NoticeRepository.
|
||||||
func CreateRepositoryNotice(desc string) error {
|
func CreateRepositoryNotice(desc string, args ...interface{}) error {
|
||||||
return createNotice(x, NoticeRepository, desc)
|
return createNotice(x, NoticeRepository, desc, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllWithNotice removes all directories in given path and
|
// RemoveAllWithNotice removes all directories in given path and
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -561,11 +560,11 @@ func RemoveDeletedBranch(repoID int64, branch string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOldDeletedBranches removes old deleted branches
|
// RemoveOldDeletedBranches removes old deleted branches
|
||||||
func RemoveOldDeletedBranches(ctx context.Context) {
|
func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
|
||||||
// Nothing to do for shutdown or terminate
|
// Nothing to do for shutdown or terminate
|
||||||
log.Trace("Doing: DeletedBranchesCleanup")
|
log.Trace("Doing: DeletedBranchesCleanup")
|
||||||
|
|
||||||
deleteBefore := time.Now().Add(-setting.Cron.DeletedBranchesCleanup.OlderThan)
|
deleteBefore := time.Now().Add(-olderThan)
|
||||||
_, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
|
_, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DeletedBranchesCleanup: %v", err)
|
log.Error("DeletedBranchesCleanup: %v", err)
|
||||||
|
|
|
@ -85,6 +85,28 @@ func (err ErrSSHDisabled) Error() string {
|
||||||
return "SSH is disabled"
|
return "SSH is disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrCancelled represents an error due to context cancellation
|
||||||
|
type ErrCancelled struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrCancelled checks if an error is a ErrCancelled.
|
||||||
|
func IsErrCancelled(err error) bool {
|
||||||
|
_, ok := err.(ErrCancelled)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrCancelled) Error() string {
|
||||||
|
return "Cancelled: " + err.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCancelledf returns an ErrCancelled for the provided format and args
|
||||||
|
func ErrCancelledf(format string, args ...interface{}) error {
|
||||||
|
return ErrCancelled{
|
||||||
|
fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ____ ___
|
// ____ ___
|
||||||
// | | \______ ___________
|
// | | \______ ___________
|
||||||
// | | / ___// __ \_ __ \
|
// | | / ___// __ \_ __ \
|
||||||
|
|
|
@ -1853,35 +1853,44 @@ func GetPrivateRepositoryCount(u *User) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepositoryArchives deletes all repositories' archives.
|
// DeleteRepositoryArchives deletes all repositories' archives.
|
||||||
func DeleteRepositoryArchives() error {
|
func DeleteRepositoryArchives(ctx context.Context) error {
|
||||||
return x.
|
return x.
|
||||||
Where("id > 0").
|
Where("id > 0").
|
||||||
Iterate(new(Repository),
|
Iterate(new(Repository),
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
repo := bean.(*Repository)
|
repo := bean.(*Repository)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrCancelledf("before deleting repository archives for %s", repo.FullName())
|
||||||
|
default:
|
||||||
|
}
|
||||||
return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
|
return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOldRepositoryArchives deletes old repository archives.
|
// DeleteOldRepositoryArchives deletes old repository archives.
|
||||||
func DeleteOldRepositoryArchives(ctx context.Context) {
|
func DeleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration) error {
|
||||||
log.Trace("Doing: ArchiveCleanup")
|
log.Trace("Doing: ArchiveCleanup")
|
||||||
|
|
||||||
if err := x.Where("id > 0").Iterate(new(Repository), func(idx int, bean interface{}) error {
|
if err := x.Where("id > 0").Iterate(new(Repository), func(idx int, bean interface{}) error {
|
||||||
return deleteOldRepositoryArchives(ctx, idx, bean)
|
return deleteOldRepositoryArchives(ctx, olderThan, idx, bean)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("ArchiveClean: %v", err)
|
log.Trace("Error: ArchiveClean: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Trace("Finished: ArchiveCleanup")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteOldRepositoryArchives(ctx context.Context, idx int, bean interface{}) error {
|
func deleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration, idx int, bean interface{}) error {
|
||||||
repo := bean.(*Repository)
|
repo := bean.(*Repository)
|
||||||
basePath := filepath.Join(repo.RepoPath(), "archives")
|
basePath := filepath.Join(repo.RepoPath(), "archives")
|
||||||
|
|
||||||
for _, ty := range []string{"zip", "targz"} {
|
for _, ty := range []string{"zip", "targz"} {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s", repo, ty)
|
return ErrCancelledf("before deleting old repository archives with filetype %s for %s", ty, repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1904,12 +1913,12 @@ func deleteOldRepositoryArchives(ctx context.Context, idx int, bean interface{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan)
|
minimumOldestTime := time.Now().Add(-olderThan)
|
||||||
for _, info := range files {
|
for _, info := range files {
|
||||||
if info.ModTime().Before(minimumOldestTime) && !info.IsDir() {
|
if info.ModTime().Before(minimumOldestTime) && !info.IsDir() {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s - %s", repo, ty, info.Name())
|
return ErrCancelledf("before deleting old repository archive file %s with filetype %s for %s", info.Name(), ty, repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
toDelete := filepath.Join(path, info.Name())
|
toDelete := filepath.Join(path, info.Name())
|
||||||
|
@ -1936,13 +1945,13 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
id := com.StrTo(result["id"]).MustInt64()
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("CheckRepoStats: Aborting due to shutdown")
|
log.Warn("CheckRepoStats: Cancelled before checking %s for Repo[%d]", checker.desc, id)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
id := com.StrTo(result["id"]).MustInt64()
|
|
||||||
log.Trace("Updating %s: %d", checker.desc, id)
|
log.Trace("Updating %s: %d", checker.desc, id)
|
||||||
_, err = x.Exec(checker.correctSQL, id, id)
|
_, err = x.Exec(checker.correctSQL, id, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1952,7 +1961,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRepoStats checks the repository stats
|
// CheckRepoStats checks the repository stats
|
||||||
func CheckRepoStats(ctx context.Context) {
|
func CheckRepoStats(ctx context.Context) error {
|
||||||
log.Trace("Doing: CheckRepoStats")
|
log.Trace("Doing: CheckRepoStats")
|
||||||
|
|
||||||
checkers := []*repoChecker{
|
checkers := []*repoChecker{
|
||||||
|
@ -1987,13 +1996,13 @@ func CheckRepoStats(ctx context.Context) {
|
||||||
"issue count 'num_comments'",
|
"issue count 'num_comments'",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i := range checkers {
|
for _, checker := range checkers {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("CheckRepoStats: Aborting due to shutdown")
|
log.Warn("CheckRepoStats: Cancelled before %s", checker.desc)
|
||||||
return
|
return ErrCancelledf("before checking %s", checker.desc)
|
||||||
default:
|
default:
|
||||||
repoStatsCheck(ctx, checkers[i])
|
repoStatsCheck(ctx, checker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2004,13 +2013,13 @@ func CheckRepoStats(ctx context.Context) {
|
||||||
log.Error("Select %s: %v", desc, err)
|
log.Error("Select %s: %v", desc, err)
|
||||||
} else {
|
} else {
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
id := com.StrTo(result["id"]).MustInt64()
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("CheckRepoStats: Aborting due to shutdown")
|
log.Warn("CheckRepoStats: Cancelled during %s for repo ID %d", desc, id)
|
||||||
return
|
return ErrCancelledf("during %s for repo ID %d", desc, id)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
id := com.StrTo(result["id"]).MustInt64()
|
|
||||||
log.Trace("Updating %s: %d", desc, id)
|
log.Trace("Updating %s: %d", desc, id)
|
||||||
_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
|
_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2027,13 +2036,13 @@ func CheckRepoStats(ctx context.Context) {
|
||||||
log.Error("Select %s: %v", desc, err)
|
log.Error("Select %s: %v", desc, err)
|
||||||
} else {
|
} else {
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
id := com.StrTo(result["id"]).MustInt64()
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("CheckRepoStats: Aborting due to shutdown")
|
log.Warn("CheckRepoStats: Cancelled")
|
||||||
return
|
return ErrCancelledf("during %s for repo ID %d", desc, id)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
id := com.StrTo(result["id"]).MustInt64()
|
|
||||||
log.Trace("Updating %s: %d", desc, id)
|
log.Trace("Updating %s: %d", desc, id)
|
||||||
_, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id)
|
_, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2050,13 +2059,13 @@ func CheckRepoStats(ctx context.Context) {
|
||||||
log.Error("Select repository count 'num_forks': %v", err)
|
log.Error("Select repository count 'num_forks': %v", err)
|
||||||
} else {
|
} else {
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
id := com.StrTo(result["id"]).MustInt64()
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("CheckRepoStats: Aborting due to shutdown")
|
log.Warn("CheckRepoStats: Cancelled")
|
||||||
return
|
return ErrCancelledf("during %s for repo ID %d", desc, id)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
id := com.StrTo(result["id"]).MustInt64()
|
|
||||||
log.Trace("Updating repository count 'num_forks': %d", id)
|
log.Trace("Updating repository count 'num_forks': %d", id)
|
||||||
|
|
||||||
repo, err := GetRepositoryByID(id)
|
repo, err := GetRepositoryByID(id)
|
||||||
|
@ -2079,6 +2088,7 @@ func CheckRepoStats(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ***** END: Repository.NumForks *****
|
// ***** END: Repository.NumForks *****
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetArchiveRepoState sets if a repo is archived
|
// SetArchiveRepoState sets if a repo is archived
|
||||||
|
@ -2189,12 +2199,17 @@ func (repo *Repository) generateRandomAvatar(e Engine) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
|
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
|
||||||
func RemoveRandomAvatars() error {
|
func RemoveRandomAvatars(ctx context.Context) error {
|
||||||
return x.
|
return x.
|
||||||
Where("id > 0").BufferSize(setting.Database.IterateBufferSize).
|
Where("id > 0").BufferSize(setting.Database.IterateBufferSize).
|
||||||
Iterate(new(Repository),
|
Iterate(new(Repository),
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
repository := bean.(*Repository)
|
repository := bean.(*Repository)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrCancelledf("before random avatars removed for %s", repository.FullName())
|
||||||
|
default:
|
||||||
|
}
|
||||||
stringifiedID := strconv.FormatInt(repository.ID, 10)
|
stringifiedID := strconv.FormatInt(repository.ID, 10)
|
||||||
if repository.Avatar == stringifiedID {
|
if repository.Avatar == stringifiedID {
|
||||||
return repository.DeleteAvatar()
|
return repository.DeleteAvatar()
|
||||||
|
|
|
@ -1321,16 +1321,30 @@ func DeleteUser(u *User) (err error) {
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteInactivateUsers deletes all inactivate users and email addresses.
|
// DeleteInactiveUsers deletes all inactive users and email addresses.
|
||||||
func DeleteInactivateUsers() (err error) {
|
func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err error) {
|
||||||
users := make([]*User, 0, 10)
|
users := make([]*User, 0, 10)
|
||||||
if err = x.
|
if olderThan > 0 {
|
||||||
Where("is_active = ?", false).
|
if err = x.
|
||||||
Find(&users); err != nil {
|
Where("is_active = ? and created_unix < ?", false, time.Now().Add(-olderThan).Unix()).
|
||||||
return fmt.Errorf("get all inactive users: %v", err)
|
Find(&users); err != nil {
|
||||||
|
return fmt.Errorf("get all inactive users: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = x.
|
||||||
|
Where("is_active = ?", false).
|
||||||
|
Find(&users); err != nil {
|
||||||
|
return fmt.Errorf("get all inactive users: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// FIXME: should only update authorized_keys file once after all deletions.
|
// FIXME: should only update authorized_keys file once after all deletions.
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrCancelledf("Before delete inactive user %s", u.Name)
|
||||||
|
default:
|
||||||
|
}
|
||||||
if err = DeleteUser(u); err != nil {
|
if err = DeleteUser(u); err != nil {
|
||||||
// Ignore users that were set inactive by admin.
|
// Ignore users that were set inactive by admin.
|
||||||
if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) {
|
if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) {
|
||||||
|
@ -1814,25 +1828,23 @@ func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []str
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncExternalUsers is used to synchronize users with external authorization source
|
// SyncExternalUsers is used to synchronize users with external authorization source
|
||||||
func SyncExternalUsers(ctx context.Context) {
|
func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
|
||||||
log.Trace("Doing: SyncExternalUsers")
|
log.Trace("Doing: SyncExternalUsers")
|
||||||
|
|
||||||
ls, err := LoginSources()
|
ls, err := LoginSources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SyncExternalUsers: %v", err)
|
log.Error("SyncExternalUsers: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
updateExisting := setting.Cron.SyncExternalUsers.UpdateExisting
|
|
||||||
|
|
||||||
for _, s := range ls {
|
for _, s := range ls {
|
||||||
if !s.IsActived || !s.IsSyncEnabled {
|
if !s.IsActived || !s.IsSyncEnabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Aborted due to shutdown before update of %s", s.Name)
|
log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
|
||||||
return
|
return ErrCancelledf("Before update of %s", s.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1850,12 +1862,12 @@ func SyncExternalUsers(ctx context.Context) {
|
||||||
Find(&users)
|
Find(&users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SyncExternalUsers: %v", err)
|
log.Error("SyncExternalUsers: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Aborted due to shutdown before update of %s", s.Name)
|
log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
|
||||||
return
|
return ErrCancelledf("Before update of %s", s.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1877,7 +1889,7 @@ func SyncExternalUsers(ctx context.Context) {
|
||||||
for _, su := range sr {
|
for _, su := range sr {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Aborted due to shutdown at update of %s before completed update of users", s.Name)
|
log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", s.Name)
|
||||||
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
|
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
|
||||||
if sshKeysNeedUpdate {
|
if sshKeysNeedUpdate {
|
||||||
err = RewriteAllPublicKeys()
|
err = RewriteAllPublicKeys()
|
||||||
|
@ -1885,7 +1897,7 @@ func SyncExternalUsers(ctx context.Context) {
|
||||||
log.Error("RewriteAllPublicKeys: %v", err)
|
log.Error("RewriteAllPublicKeys: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return ErrCancelledf("During update of %s before completed update of users", s.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if len(su.Username) == 0 {
|
if len(su.Username) == 0 {
|
||||||
|
@ -1980,8 +1992,8 @@ func SyncExternalUsers(ctx context.Context) {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Aborted due to shutdown at update of %s before delete users", s.Name)
|
log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", s.Name)
|
||||||
return
|
return ErrCancelledf("During update of %s before delete users", s.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2008,4 +2020,5 @@ func SyncExternalUsers(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||||
|
|
||||||
// AdminDashboardForm form for admin dashboard operations
|
// AdminDashboardForm form for admin dashboard operations
|
||||||
type AdminDashboardForm struct {
|
type AdminDashboardForm struct {
|
||||||
Op int `binding:"required"`
|
Op string `binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates form fields
|
// Validate validates form fields
|
||||||
|
|
|
@ -9,143 +9,86 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/migrations"
|
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/sync"
|
"code.gitea.io/gitea/modules/sync"
|
||||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
|
||||||
|
|
||||||
"github.com/gogs/cron"
|
"github.com/gogs/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
mirrorUpdate = "mirror_update"
|
|
||||||
gitFsck = "git_fsck"
|
|
||||||
checkRepos = "check_repos"
|
|
||||||
archiveCleanup = "archive_cleanup"
|
|
||||||
syncExternalUsers = "sync_external_users"
|
|
||||||
deletedBranchesCleanup = "deleted_branches_cleanup"
|
|
||||||
updateMigrationPosterID = "update_migration_post_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
var c = cron.New()
|
var c = cron.New()
|
||||||
|
|
||||||
// Prevent duplicate running tasks.
|
// Prevent duplicate running tasks.
|
||||||
var taskStatusTable = sync.NewStatusTable()
|
var taskStatusTable = sync.NewStatusTable()
|
||||||
|
|
||||||
// Func defines a cron function body
|
|
||||||
type Func func()
|
|
||||||
|
|
||||||
// WithUnique wrap a cron func with an unique running check
|
|
||||||
func WithUnique(name string, body func(context.Context)) Func {
|
|
||||||
return func() {
|
|
||||||
if !taskStatusTable.StartIfNotRunning(name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer taskStatusTable.Stop(name)
|
|
||||||
graceful.GetManager().RunWithShutdownContext(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext begins cron tasks
|
// NewContext begins cron tasks
|
||||||
// Each cron task is run within the shutdown context as a running server
|
// Each cron task is run within the shutdown context as a running server
|
||||||
// AtShutdown the cron server is stopped
|
// AtShutdown the cron server is stopped
|
||||||
func NewContext() {
|
func NewContext() {
|
||||||
var (
|
initBasicTasks()
|
||||||
entry *cron.Entry
|
initExtendedTasks()
|
||||||
err error
|
|
||||||
)
|
|
||||||
if setting.Cron.UpdateMirror.Enabled {
|
|
||||||
entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, mirror_service.Update))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Update mirrors]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.UpdateMirror.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(mirrorUpdate, mirror_service.Update)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setting.Cron.RepoHealthCheck.Enabled {
|
|
||||||
entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, func(ctx context.Context) {
|
|
||||||
if err := repo_module.GitFsck(ctx); err != nil {
|
|
||||||
log.Error("GitFsck: %s", err)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Repository health check]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.RepoHealthCheck.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(gitFsck, func(ctx context.Context) {
|
|
||||||
if err := repo_module.GitFsck(ctx); err != nil {
|
|
||||||
log.Error("GitFsck: %s", err)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setting.Cron.CheckRepoStats.Enabled {
|
|
||||||
entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, WithUnique(checkRepos, models.CheckRepoStats))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Check repository statistics]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.CheckRepoStats.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(checkRepos, models.CheckRepoStats)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setting.Cron.ArchiveCleanup.Enabled {
|
|
||||||
entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Clean up old repository archives]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.ArchiveCleanup.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setting.Cron.SyncExternalUsers.Enabled {
|
|
||||||
entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, WithUnique(syncExternalUsers, models.SyncExternalUsers))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Synchronize external users]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.SyncExternalUsers.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(syncExternalUsers, models.SyncExternalUsers)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if setting.Cron.DeletedBranchesCleanup.Enabled {
|
|
||||||
entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Cron[Remove old deleted branches]: %v", err)
|
|
||||||
}
|
|
||||||
if setting.Cron.DeletedBranchesCleanup.RunAtStart {
|
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err = c.AddFunc("Update migrated repositories' issues and comments' posterid", setting.Cron.UpdateMigrationPosterID.Schedule, WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID))
|
lock.Lock()
|
||||||
if err != nil {
|
for _, task := range tasks {
|
||||||
log.Fatal("Cron[Update migrated repositories]: %v", err)
|
if task.IsEnabled() && task.DoRunAtStart() {
|
||||||
|
go task.Run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entry.Prev = time.Now()
|
|
||||||
entry.ExecTimes++
|
|
||||||
go WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID)()
|
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
graceful.GetManager().RunAtShutdown(context.Background(), c.Stop)
|
started = true
|
||||||
|
lock.Unlock()
|
||||||
|
graceful.GetManager().RunAtShutdown(context.Background(), func() {
|
||||||
|
c.Stop()
|
||||||
|
lock.Lock()
|
||||||
|
started = false
|
||||||
|
lock.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTasks returns all running cron tasks.
|
// TaskTableRow represents a task row in the tasks table
|
||||||
func ListTasks() []*cron.Entry {
|
type TaskTableRow struct {
|
||||||
return c.Entries()
|
Name string
|
||||||
|
Spec string
|
||||||
|
Next time.Time
|
||||||
|
Prev time.Time
|
||||||
|
ExecTimes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskTable represents a table of tasks
|
||||||
|
type TaskTable []*TaskTableRow
|
||||||
|
|
||||||
|
// ListTasks returns all running cron tasks.
|
||||||
|
func ListTasks() TaskTable {
|
||||||
|
entries := c.Entries()
|
||||||
|
eMap := map[string]*cron.Entry{}
|
||||||
|
for _, e := range entries {
|
||||||
|
eMap[e.Description] = e
|
||||||
|
}
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
tTable := make([]*TaskTableRow, 0, len(tasks))
|
||||||
|
for _, task := range tasks {
|
||||||
|
spec := "-"
|
||||||
|
var (
|
||||||
|
next time.Time
|
||||||
|
prev time.Time
|
||||||
|
)
|
||||||
|
if e, ok := eMap[task.Name]; ok {
|
||||||
|
spec = e.Spec
|
||||||
|
next = e.Next
|
||||||
|
prev = e.Prev
|
||||||
|
}
|
||||||
|
task.lock.Lock()
|
||||||
|
tTable = append(tTable, &TaskTableRow{
|
||||||
|
Name: task.Name,
|
||||||
|
Spec: spec,
|
||||||
|
Next: next,
|
||||||
|
Prev: prev,
|
||||||
|
ExecTimes: task.ExecTimes,
|
||||||
|
})
|
||||||
|
task.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tTable
|
||||||
}
|
}
|
||||||
|
|
72
modules/cron/setting.go
Normal file
72
modules/cron/setting.go
Normal file
|
@ -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...)
|
||||||
|
}
|
166
modules/cron/tasks.go
Normal file
166
modules/cron/tasks.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
118
modules/cron/tasks_basic.go
Normal file
118
modules/cron/tasks_basic.go
Normal file
|
@ -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()
|
||||||
|
}
|
119
modules/cron/tasks_extended.go
Normal file
119
modules/cron/tasks_extended.go
Normal file
|
@ -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()
|
||||||
|
}
|
|
@ -45,22 +45,32 @@ func (c *Command) String() string {
|
||||||
|
|
||||||
// NewCommand creates and returns a new Git Command based on given command and arguments.
|
// NewCommand creates and returns a new Git Command based on given command and arguments.
|
||||||
func NewCommand(args ...string) *Command {
|
func NewCommand(args ...string) *Command {
|
||||||
|
return NewCommandContext(DefaultContext, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandContext creates and returns a new Git Command based on given command and arguments.
|
||||||
|
func NewCommandContext(ctx context.Context, args ...string) *Command {
|
||||||
// Make an explicit copy of GlobalCommandArgs, otherwise append might overwrite it
|
// Make an explicit copy of GlobalCommandArgs, otherwise append might overwrite it
|
||||||
cargs := make([]string, len(GlobalCommandArgs))
|
cargs := make([]string, len(GlobalCommandArgs))
|
||||||
copy(cargs, GlobalCommandArgs)
|
copy(cargs, GlobalCommandArgs)
|
||||||
return &Command{
|
return &Command{
|
||||||
name: GitExecutable,
|
name: GitExecutable,
|
||||||
args: append(cargs, args...),
|
args: append(cargs, args...),
|
||||||
parentContext: DefaultContext,
|
parentContext: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
|
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
|
||||||
func NewCommandNoGlobals(args ...string) *Command {
|
func NewCommandNoGlobals(args ...string) *Command {
|
||||||
|
return NewCommandContextNoGlobals(DefaultContext, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
|
||||||
|
func NewCommandContextNoGlobals(ctx context.Context, args ...string) *Command {
|
||||||
return &Command{
|
return &Command{
|
||||||
name: GitExecutable,
|
name: GitExecutable,
|
||||||
args: args,
|
args: args,
|
||||||
parentContext: DefaultContext,
|
parentContext: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,11 +150,11 @@ func Init(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fsck verifies the connectivity and validity of the objects in the database
|
// Fsck verifies the connectivity and validity of the objects in the database
|
||||||
func Fsck(repoPath string, timeout time.Duration, args ...string) error {
|
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
|
||||||
// Make sure timeout makes sense.
|
// Make sure timeout makes sense.
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = -1
|
timeout = -1
|
||||||
}
|
}
|
||||||
_, err := NewCommand("fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
|
_, err := NewCommandContext(ctx, "fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,17 +13,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateMigrationPosterID updates all migrated repositories' issues and comments posterID
|
// UpdateMigrationPosterID updates all migrated repositories' issues and comments posterID
|
||||||
func UpdateMigrationPosterID(ctx context.Context) {
|
func UpdateMigrationPosterID(ctx context.Context) error {
|
||||||
for _, gitService := range structs.SupportedFullGitService {
|
for _, gitService := range structs.SupportedFullGitService {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("UpdateMigrationPosterID aborted due to shutdown before %s", gitService.Name())
|
log.Warn("UpdateMigrationPosterID aborted before %s", gitService.Name())
|
||||||
|
return models.ErrCancelledf("during UpdateMigrationPosterID before %s", gitService.Name())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if err := updateMigrationPosterIDByGitService(ctx, gitService); err != nil {
|
if err := updateMigrationPosterIDByGitService(ctx, gitService); err != nil {
|
||||||
log.Error("updateMigrationPosterIDByGitService failed: %v", err)
|
log.Error("updateMigrationPosterIDByGitService failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServiceType) error {
|
func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServiceType) error {
|
||||||
|
@ -37,7 +39,7 @@ func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServ
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("UpdateMigrationPosterIDByGitService(%s) aborted due to shutdown", tp.Name())
|
log.Warn("UpdateMigrationPosterIDByGitService(%s) cancelled", tp.Name())
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
@ -54,7 +56,7 @@ func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServ
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("UpdateMigrationPosterIDByGitService(%s) aborted due to shutdown", tp.Name())
|
log.Warn("UpdateMigrationPosterIDByGitService(%s) cancelled", tp.Name())
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,19 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
"github.com/unknwon/com"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitFsck calls 'git fsck' to check repository health.
|
// GitFsck calls 'git fsck' to check repository health.
|
||||||
func GitFsck(ctx context.Context) error {
|
func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
|
||||||
log.Trace("Doing: GitFsck")
|
log.Trace("Doing: GitFsck")
|
||||||
|
|
||||||
if err := models.Iterate(
|
if err := models.Iterate(
|
||||||
|
@ -27,24 +27,24 @@ func GitFsck(ctx context.Context) error {
|
||||||
new(models.Repository),
|
new(models.Repository),
|
||||||
builder.Expr("id>0 AND is_fsck_enabled=?", true),
|
builder.Expr("id>0 AND is_fsck_enabled=?", true),
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
|
repo := bean.(*models.Repository)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown")
|
return models.ErrCancelledf("before fsck of %s", repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
repo := bean.(*models.Repository)
|
log.Trace("Running health check on repository %v", repo)
|
||||||
repoPath := repo.RepoPath()
|
repoPath := repo.RepoPath()
|
||||||
log.Trace("Running health check on repository %s", repoPath)
|
if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
|
||||||
if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil {
|
log.Warn("Failed to health check repository (%v): %v", repo, err)
|
||||||
desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err)
|
if err = models.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
|
||||||
log.Warn(desc)
|
|
||||||
if err = models.CreateRepositoryNotice(desc); err != nil {
|
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
log.Trace("Error: GitFsck: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,32 +53,43 @@ func GitFsck(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
|
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
|
||||||
func GitGcRepos(ctx context.Context) error {
|
func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) error {
|
||||||
log.Trace("Doing: GitGcRepos")
|
log.Trace("Doing: GitGcRepos")
|
||||||
args := append([]string{"gc"}, setting.Git.GCArgs...)
|
args = append([]string{"gc"}, args...)
|
||||||
|
|
||||||
if err := models.Iterate(
|
if err := models.Iterate(
|
||||||
models.DefaultDBContext(),
|
models.DefaultDBContext(),
|
||||||
new(models.Repository),
|
new(models.Repository),
|
||||||
builder.Gt{"id": 0},
|
builder.Gt{"id": 0},
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
|
repo := bean.(*models.Repository)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown")
|
return models.ErrCancelledf("before GC of %s", repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
log.Trace("Running git gc on %v", repo)
|
||||||
repo := bean.(*models.Repository)
|
command := git.NewCommandContext(ctx, args...).
|
||||||
if err := repo.GetOwner(); err != nil {
|
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
|
||||||
return err
|
var stdout string
|
||||||
|
var err error
|
||||||
|
if timeout > 0 {
|
||||||
|
var stdoutBytes []byte
|
||||||
|
stdoutBytes, err = command.RunInDirTimeout(
|
||||||
|
timeout,
|
||||||
|
repo.RepoPath())
|
||||||
|
stdout = string(stdoutBytes)
|
||||||
|
} else {
|
||||||
|
stdout, err = command.RunInDir(repo.RepoPath())
|
||||||
}
|
}
|
||||||
if stdout, err := git.NewCommand(args...).
|
|
||||||
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())).
|
if err != nil {
|
||||||
RunInDirTimeout(
|
|
||||||
time.Duration(setting.Git.Timeout.GC)*time.Second,
|
|
||||||
repo.RepoPath()); err != nil {
|
|
||||||
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
|
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||||
return fmt.Errorf("Repository garbage collection failed: Error: %v", err)
|
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
||||||
|
if err = models.CreateRepositoryNotice(desc); err != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -90,7 +101,7 @@ func GitGcRepos(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gatherMissingRepoRecords() ([]*models.Repository, error) {
|
func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) {
|
||||||
repos := make([]*models.Repository, 0, 10)
|
repos := make([]*models.Repository, 0, 10)
|
||||||
if err := models.Iterate(
|
if err := models.Iterate(
|
||||||
models.DefaultDBContext(),
|
models.DefaultDBContext(),
|
||||||
|
@ -98,24 +109,33 @@ func gatherMissingRepoRecords() ([]*models.Repository, error) {
|
||||||
builder.Gt{"id": 0},
|
builder.Gt{"id": 0},
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
repo := bean.(*models.Repository)
|
repo := bean.(*models.Repository)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return models.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
|
||||||
|
default:
|
||||||
|
}
|
||||||
if !com.IsDir(repo.RepoPath()) {
|
if !com.IsDir(repo.RepoPath()) {
|
||||||
repos = append(repos, repo)
|
repos = append(repos, repo)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil {
|
if strings.HasPrefix("Aborted gathering missing repo", err.Error()) {
|
||||||
return nil, fmt.Errorf("CreateRepositoryNotice: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err2 := models.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err2)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return repos, nil
|
return repos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMissingRepositories deletes all repository records that lost Git files.
|
// DeleteMissingRepositories deletes all repository records that lost Git files.
|
||||||
func DeleteMissingRepositories(doer *models.User) error {
|
func DeleteMissingRepositories(ctx context.Context, doer *models.User) error {
|
||||||
repos, err := gatherMissingRepoRecords()
|
repos, err := gatherMissingRepoRecords(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("gatherMissingRepoRecords: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
|
@ -123,10 +143,16 @@ func DeleteMissingRepositories(doer *models.User) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return models.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
|
||||||
|
default:
|
||||||
|
}
|
||||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||||
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
|
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
|
||||||
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
|
log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
|
||||||
return fmt.Errorf("CreateRepositoryNotice: %v", err)
|
if err2 := models.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,10 +160,10 @@ func DeleteMissingRepositories(doer *models.User) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
|
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
|
||||||
func ReinitMissingRepositories() error {
|
func ReinitMissingRepositories(ctx context.Context) error {
|
||||||
repos, err := gatherMissingRepoRecords()
|
repos, err := gatherMissingRepoRecords(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("gatherMissingRepoRecords: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
|
@ -145,10 +171,16 @@ func ReinitMissingRepositories() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return models.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
|
||||||
|
default:
|
||||||
|
}
|
||||||
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
||||||
if err := git.InitRepository(repo.RepoPath(), true); err != nil {
|
if err := git.InitRepository(repo.RepoPath(), true); err != nil {
|
||||||
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil {
|
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
|
||||||
return fmt.Errorf("CreateRepositoryNotice: %v", err)
|
if err2 := models.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,17 +160,18 @@ func SyncRepositoryHooks(ctx context.Context) error {
|
||||||
new(models.Repository),
|
new(models.Repository),
|
||||||
builder.Gt{"id": 0},
|
builder.Gt{"id": 0},
|
||||||
func(idx int, bean interface{}) error {
|
func(idx int, bean interface{}) error {
|
||||||
|
repo := bean.(*models.Repository)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown")
|
return models.ErrCancelledf("before sync repository hooks for %s", repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createDelegateHooks(bean.(*models.Repository).RepoPath()); err != nil {
|
if err := createDelegateHooks(repo.RepoPath()); err != nil {
|
||||||
return fmt.Errorf("SyncRepositoryHook: %v", err)
|
return fmt.Errorf("SyncRepositoryHook: %v", err)
|
||||||
}
|
}
|
||||||
if bean.(*models.Repository).HasWiki() {
|
if repo.HasWiki() {
|
||||||
if err := createDelegateHooks(bean.(*models.Repository).WikiPath()); err != nil {
|
if err := createDelegateHooks(repo.WikiPath()); err != nil {
|
||||||
return fmt.Errorf("SyncRepositoryHook: %v", err)
|
return fmt.Errorf("SyncRepositoryHook: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,129 +4,8 @@
|
||||||
|
|
||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
// GetCronSettings maps the cron subsection to the provided config
|
||||||
"time"
|
func GetCronSettings(name string, config interface{}) (interface{}, error) {
|
||||||
|
err := Cfg.Section("cron." + name).MapTo(config)
|
||||||
"code.gitea.io/gitea/modules/log"
|
return config, err
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
|
|
||||||
// Cron tasks
|
|
||||||
Cron = struct {
|
|
||||||
UpdateMirror struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
} `ini:"cron.update_mirrors"`
|
|
||||||
RepoHealthCheck struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
Timeout time.Duration
|
|
||||||
Args []string `delim:" "`
|
|
||||||
} `ini:"cron.repo_health_check"`
|
|
||||||
CheckRepoStats struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
} `ini:"cron.check_repo_stats"`
|
|
||||||
ArchiveCleanup struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
OlderThan time.Duration
|
|
||||||
} `ini:"cron.archive_cleanup"`
|
|
||||||
SyncExternalUsers struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
UpdateExisting bool
|
|
||||||
} `ini:"cron.sync_external_users"`
|
|
||||||
DeletedBranchesCleanup struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
OlderThan time.Duration
|
|
||||||
} `ini:"cron.deleted_branches_cleanup"`
|
|
||||||
UpdateMigrationPosterID struct {
|
|
||||||
Schedule string
|
|
||||||
} `ini:"cron.update_migration_poster_id"`
|
|
||||||
}{
|
|
||||||
UpdateMirror: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: false,
|
|
||||||
Schedule: "@every 10m",
|
|
||||||
},
|
|
||||||
RepoHealthCheck: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
Timeout time.Duration
|
|
||||||
Args []string `delim:" "`
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: false,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
Timeout: 60 * time.Second,
|
|
||||||
Args: []string{},
|
|
||||||
},
|
|
||||||
CheckRepoStats: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: true,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
},
|
|
||||||
ArchiveCleanup: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
OlderThan time.Duration
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: true,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
OlderThan: 24 * time.Hour,
|
|
||||||
},
|
|
||||||
SyncExternalUsers: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
UpdateExisting bool
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: false,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
UpdateExisting: true,
|
|
||||||
},
|
|
||||||
DeletedBranchesCleanup: struct {
|
|
||||||
Enabled bool
|
|
||||||
RunAtStart bool
|
|
||||||
Schedule string
|
|
||||||
OlderThan time.Duration
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
RunAtStart: true,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
OlderThan: 24 * time.Hour,
|
|
||||||
},
|
|
||||||
UpdateMigrationPosterID: struct {
|
|
||||||
Schedule string
|
|
||||||
}{
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCron() {
|
|
||||||
if err := Cfg.Section("cron").MapTo(&Cron); err != nil {
|
|
||||||
log.Fatal("Failed to map Cron settings: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -981,7 +981,6 @@ func NewContext() {
|
||||||
u.Path = path.Join(u.Path, "api", "swagger")
|
u.Path = path.Join(u.Path, "api", "swagger")
|
||||||
API.SwaggerURL = u.String()
|
API.SwaggerURL = u.String()
|
||||||
|
|
||||||
newCron()
|
|
||||||
newGit()
|
newGit()
|
||||||
|
|
||||||
sec = Cfg.Section("mirror")
|
sec = Cfg.Section("mirror")
|
||||||
|
|
|
@ -1842,26 +1842,37 @@ dashboard.operation_switch = Switch
|
||||||
dashboard.operation_run = Run
|
dashboard.operation_run = Run
|
||||||
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
|
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
|
||||||
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
|
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
|
||||||
dashboard.delete_inactivate_accounts = Delete all unactivated accounts
|
dashboard.task.started=Started Task: %s
|
||||||
dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted.
|
dashboard.task.process=Task: %s
|
||||||
|
dashboard.task.cancelled=Task: %s cancelled: %[3]s
|
||||||
|
dashboard.task.error=Error in Task: %s: %[3]s
|
||||||
|
dashboard.task.finished=Task: %s started by %s has finished
|
||||||
|
dashboard.task.unknown=Unknown task: %s
|
||||||
|
dashboard.cron.started=Started Cron: %[1]s
|
||||||
|
dashboard.cron.process=Cron: %[1]s
|
||||||
|
dashboard.cron.cancelled=Cron: %s cancelled: %[3]s
|
||||||
|
dashboard.cron.error=Error in Cron: %s: %[3]s
|
||||||
|
dashboard.cron.finished=Cron: %[1]s has finished
|
||||||
|
dashboard.delete_inactive_accounts = Delete all unactivated accounts
|
||||||
|
dashboard.delete_inactive_accounts.started = Delete all unactivated accounts task started.
|
||||||
dashboard.delete_repo_archives = Delete all repository archives
|
dashboard.delete_repo_archives = Delete all repository archives
|
||||||
dashboard.delete_repo_archives_success = All repository archives have been deleted.
|
dashboard.delete_repo_archives.started = Delete all repository archives task started.
|
||||||
dashboard.delete_missing_repos = Delete all repositories missing their Git files
|
dashboard.delete_missing_repos = Delete all repositories missing their Git files
|
||||||
dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted.
|
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
|
||||||
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
|
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
|
||||||
dashboard.delete_generated_repository_avatars_success = Generated repository avatars were deleted.
|
dashboard.update_mirrors = Update Mirrors
|
||||||
|
dashboard.repo_health_check = Health check all repositories
|
||||||
|
dashboard.check_repo_stats = Check all repository statistics
|
||||||
|
dashboard.archive_cleanup = Delete old repository archives
|
||||||
|
dashboard.deleted_branches_cleanup = Clean-up deleted branches
|
||||||
|
dashboard.update_migration_poster_id = Update migration poster IDs
|
||||||
dashboard.git_gc_repos = Garbage collect all repositories
|
dashboard.git_gc_repos = Garbage collect all repositories
|
||||||
dashboard.git_gc_repos_success = All repositories have finished garbage collection.
|
dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys.
|
||||||
dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)
|
dashboard.resync_all_sshkeys.desc = (Not needed for the built-in SSH server.)
|
||||||
dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated.
|
|
||||||
dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories.
|
dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories.
|
||||||
dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized.
|
|
||||||
dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist
|
dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist
|
||||||
dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized.
|
|
||||||
dashboard.sync_external_users = Synchronize external user data
|
dashboard.sync_external_users = Synchronize external user data
|
||||||
dashboard.sync_external_users_started = External user data synchronization has started.
|
|
||||||
dashboard.git_fsck = Execute health checks on all repositories
|
dashboard.git_fsck = Execute health checks on all repositories
|
||||||
dashboard.git_fsck_started = Repository health checks have started.
|
|
||||||
dashboard.server_uptime = Server Uptime
|
dashboard.server_uptime = Server Uptime
|
||||||
dashboard.current_goroutine = Current Goroutines
|
dashboard.current_goroutine = Current Goroutines
|
||||||
dashboard.current_memory_usage = Current Memory Usage
|
dashboard.current_memory_usage = Current Memory Usage
|
||||||
|
@ -2266,6 +2277,7 @@ notices.delete_selected = Delete Selected
|
||||||
notices.delete_all = Delete All Notices
|
notices.delete_all = Delete All Notices
|
||||||
notices.type = Type
|
notices.type = Type
|
||||||
notices.type_1 = Repository
|
notices.type_1 = Repository
|
||||||
|
notices.type_2 = Task
|
||||||
notices.desc = Description
|
notices.desc = Description
|
||||||
notices.op = Op.
|
notices.op = Op.
|
||||||
notices.delete_success = The system notices have been deleted.
|
notices.delete_success = The system notices have been deleted.
|
||||||
|
|
|
@ -21,11 +21,9 @@ import (
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/cron"
|
"code.gitea.io/gitea/modules/cron"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
|
@ -124,22 +122,6 @@ func updateSystemStatus() {
|
||||||
sysStatus.NumGC = m.NumGC
|
sysStatus.NumGC = m.NumGC
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operation Operation types.
|
|
||||||
type Operation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
cleanInactivateUser Operation = iota + 1
|
|
||||||
cleanRepoArchives
|
|
||||||
cleanMissingRepos
|
|
||||||
gitGCRepos
|
|
||||||
syncSSHAuthorizedKey
|
|
||||||
syncRepositoryUpdateHook
|
|
||||||
reinitMissingRepository
|
|
||||||
syncExternalUsers
|
|
||||||
gitFsck
|
|
||||||
deleteGeneratedRepositoryAvatars
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dashboard show admin panel dashboard
|
// Dashboard show admin panel dashboard
|
||||||
func Dashboard(ctx *context.Context) {
|
func Dashboard(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("admin.dashboard")
|
ctx.Data["Title"] = ctx.Tr("admin.dashboard")
|
||||||
|
@ -162,48 +144,13 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
|
||||||
ctx.Data["SysStatus"] = sysStatus
|
ctx.Data["SysStatus"] = sysStatus
|
||||||
|
|
||||||
// Run operation.
|
// Run operation.
|
||||||
if form.Op > 0 {
|
if form.Op != "" {
|
||||||
var err error
|
task := cron.GetTask(form.Op)
|
||||||
var success string
|
if task != nil {
|
||||||
shutdownCtx := graceful.GetManager().ShutdownContext()
|
go task.RunWithUser(ctx.User, nil)
|
||||||
|
ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
|
||||||
switch Operation(form.Op) {
|
|
||||||
case cleanInactivateUser:
|
|
||||||
success = ctx.Tr("admin.dashboard.delete_inactivate_accounts_success")
|
|
||||||
err = models.DeleteInactivateUsers()
|
|
||||||
case cleanRepoArchives:
|
|
||||||
success = ctx.Tr("admin.dashboard.delete_repo_archives_success")
|
|
||||||
err = models.DeleteRepositoryArchives()
|
|
||||||
case cleanMissingRepos:
|
|
||||||
success = ctx.Tr("admin.dashboard.delete_missing_repos_success")
|
|
||||||
err = repo_module.DeleteMissingRepositories(ctx.User)
|
|
||||||
case gitGCRepos:
|
|
||||||
success = ctx.Tr("admin.dashboard.git_gc_repos_success")
|
|
||||||
err = repo_module.GitGcRepos(shutdownCtx)
|
|
||||||
case syncSSHAuthorizedKey:
|
|
||||||
success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success")
|
|
||||||
err = models.RewriteAllPublicKeys()
|
|
||||||
case syncRepositoryUpdateHook:
|
|
||||||
success = ctx.Tr("admin.dashboard.resync_all_hooks_success")
|
|
||||||
err = repo_module.SyncRepositoryHooks(shutdownCtx)
|
|
||||||
case reinitMissingRepository:
|
|
||||||
success = ctx.Tr("admin.dashboard.reinit_missing_repos_success")
|
|
||||||
err = repo_module.ReinitMissingRepositories()
|
|
||||||
case syncExternalUsers:
|
|
||||||
success = ctx.Tr("admin.dashboard.sync_external_users_started")
|
|
||||||
go graceful.GetManager().RunWithShutdownContext(models.SyncExternalUsers)
|
|
||||||
case gitFsck:
|
|
||||||
success = ctx.Tr("admin.dashboard.git_fsck_started")
|
|
||||||
err = repo_module.GitFsck(shutdownCtx)
|
|
||||||
case deleteGeneratedRepositoryAvatars:
|
|
||||||
success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success")
|
|
||||||
err = models.RemoveRandomAvatars()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Flash.Error(err.Error())
|
|
||||||
} else {
|
} else {
|
||||||
ctx.Flash.Success(success)
|
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/external"
|
"code.gitea.io/gitea/modules/markup/external"
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
|
"code.gitea.io/gitea/modules/options"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/ssh"
|
"code.gitea.io/gitea/modules/ssh"
|
||||||
"code.gitea.io/gitea/modules/task"
|
"code.gitea.io/gitea/modules/task"
|
||||||
|
@ -33,7 +34,9 @@ import (
|
||||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
|
||||||
|
"gitea.com/macaron/i18n"
|
||||||
"gitea.com/macaron/macaron"
|
"gitea.com/macaron/macaron"
|
||||||
|
unknwoni18n "github.com/unknwon/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkRunMode() {
|
func checkRunMode() {
|
||||||
|
@ -79,6 +82,33 @@ func initDBEngine(ctx context.Context) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitLocales loads the locales
|
||||||
|
func InitLocales() {
|
||||||
|
localeNames, err := options.Dir("locale")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to list locale files: %v", err)
|
||||||
|
}
|
||||||
|
localFiles := make(map[string][]byte)
|
||||||
|
|
||||||
|
for _, name := range localeNames {
|
||||||
|
localFiles[name], err = options.Locale(name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to load %s locale file. %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i18n.I18n(i18n.Options{
|
||||||
|
SubURL: setting.AppSubURL,
|
||||||
|
Files: localFiles,
|
||||||
|
Langs: setting.Langs,
|
||||||
|
Names: setting.Names,
|
||||||
|
DefaultLang: "en-US",
|
||||||
|
Redirect: false,
|
||||||
|
CookieDomain: setting.SessionConfig.Domain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GlobalInit is for global configuration reload-able.
|
// GlobalInit is for global configuration reload-able.
|
||||||
func GlobalInit(ctx context.Context) {
|
func GlobalInit(ctx context.Context) {
|
||||||
setting.NewContext()
|
setting.NewContext()
|
||||||
|
@ -91,6 +121,11 @@ func GlobalInit(ctx context.Context) {
|
||||||
log.Trace("Custom path: %s", setting.CustomPath)
|
log.Trace("Custom path: %s", setting.CustomPath)
|
||||||
log.Trace("Log path: %s", setting.LogRootPath)
|
log.Trace("Log path: %s", setting.LogRootPath)
|
||||||
|
|
||||||
|
// Setup i18n
|
||||||
|
InitLocales()
|
||||||
|
|
||||||
|
log.Info("%s", unknwoni18n.Tr("en-US", "admin.dashboard.delete_repo_archives"))
|
||||||
|
|
||||||
NewServices()
|
NewServices()
|
||||||
|
|
||||||
if setting.InstallLock {
|
if setting.InstallLock {
|
||||||
|
|
|
@ -296,7 +296,7 @@ func Password(m *models.Mirror) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update checks and updates mirror repositories.
|
// Update checks and updates mirror repositories.
|
||||||
func Update(ctx context.Context) {
|
func Update(ctx context.Context) error {
|
||||||
log.Trace("Doing: Update")
|
log.Trace("Doing: Update")
|
||||||
if err := models.MirrorsIterate(func(idx int, bean interface{}) error {
|
if err := models.MirrorsIterate(func(idx int, bean interface{}) error {
|
||||||
m := bean.(*models.Mirror)
|
m := bean.(*models.Mirror)
|
||||||
|
@ -306,14 +306,17 @@ func Update(ctx context.Context) {
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("Aborted due to shutdown")
|
return fmt.Errorf("Aborted")
|
||||||
default:
|
default:
|
||||||
mirrorQueue.Add(m.RepoID)
|
mirrorQueue.Add(m.RepoID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("Update: %v", err)
|
log.Trace("Update: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
log.Trace("Finished: Update")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncMirrors checks and syncs mirrors.
|
// SyncMirrors checks and syncs mirrors.
|
||||||
|
|
|
@ -15,49 +15,50 @@
|
||||||
{{.i18n.Tr "admin.dashboard.operations"}}
|
{{.i18n.Tr "admin.dashboard.operations"}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table segment">
|
<div class="ui attached table segment">
|
||||||
<form method="post" action="{{AppSubUrl}}/admin">
|
<form method="post" action="{{.AppSubUrl}}/admin">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<table class="ui very basic table">
|
<table class="ui very basic table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.delete_inactivate_accounts"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.delete_inactive_accounts"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="1">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="delete_inactive_accounts">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.delete_repo_archives"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.delete_repo_archives"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="2">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="delete_repo_archives">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.delete_missing_repos"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.delete_missing_repos"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="3">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="delete_missing_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.git_gc_repos"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.git_gc_repos"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="4">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="git_gc_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.resync_all_sshkeys"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.resync_all_sshkeys"}}<br/>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="5">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
{{.i18n.Tr "admin.dashboard.resync_all_sshkeys.desc"}}</td>
|
||||||
|
<td><button type="submit" class="ui green button" name="op" value="resync_all_sshkeys">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.resync_all_hooks"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.resync_all_hooks"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="6">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="resync_all_hooks">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="7">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="reinit_missing_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.sync_external_users"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.sync_external_users"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="8">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="sync_external_users">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="9">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="git_fsck">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.i18n.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
|
<td>{{.i18n.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
|
||||||
<td><button type="submit" class="ui green button" name="op" value="10">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
<td><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -7,28 +7,33 @@
|
||||||
{{.i18n.Tr "admin.monitor.cron"}}
|
{{.i18n.Tr "admin.monitor.cron"}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table segment">
|
<div class="ui attached table segment">
|
||||||
<table class="ui very basic striped table">
|
<form method="post" action="{{.AppSubUrl}}/admin">
|
||||||
<thead>
|
{{.CsrfTokenHtml}}
|
||||||
<tr>
|
<table class="ui very basic striped table">
|
||||||
<th>{{.i18n.Tr "admin.monitor.name"}}</th>
|
<thead>
|
||||||
<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
|
|
||||||
<th>{{.i18n.Tr "admin.monitor.next"}}</th>
|
|
||||||
<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
|
|
||||||
<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .Entries}}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.Description}}</td>
|
<th></th>
|
||||||
<td>{{.Spec}}</td>
|
<th>{{.i18n.Tr "admin.monitor.name"}}</th>
|
||||||
<td>{{DateFmtLong .Next}}</td>
|
<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
|
||||||
<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
|
<th>{{.i18n.Tr "admin.monitor.next"}}</th>
|
||||||
<td>{{.ExecTimes}}</td>
|
<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
|
||||||
|
<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{{range .Entries}}
|
||||||
|
<tr>
|
||||||
|
<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.i18n.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right" 16}}</button></td>
|
||||||
|
<td>{{$.i18n.Tr (printf "admin.dashboard.%s" .Name)}}</td>
|
||||||
|
<td>{{.Spec}}</td>
|
||||||
|
<td>{{DateFmtLong .Next}}</td>
|
||||||
|
<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
|
||||||
|
<td>{{.ExecTimes}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
|
|
Loading…
Reference in a new issue