feat: email admin when MokoGitea update is detected
The update checker now emails the first admin user when a new version is found on the configured channel. Notifications are deduplicated — only sent once per new version, not on every cron tick. - Added NotifyFunc callback in updatechecker module - Wired to mailer in cron task registration - Created mail_update.go with plain-text email including version, channel, release URL, and docker pull command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,9 +26,14 @@ type UpdateInfo struct {
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// NotifyFunc is called when a new update is detected for the first time.
|
||||
// Set this from the cron/mailer layer to send admin email notifications.
|
||||
var NotifyFunc func(info *UpdateInfo)
|
||||
|
||||
var (
|
||||
cachedInfo *UpdateInfo
|
||||
mu sync.RWMutex
|
||||
cachedInfo *UpdateInfo
|
||||
lastNotifiedVer string
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
// xmlUpdates mirrors the updates.xml structure (Joomla-style).
|
||||
@@ -134,16 +139,22 @@ func CheckForUpdate() error {
|
||||
}
|
||||
|
||||
// Update is available if the latest version string is not a prefix of the current version.
|
||||
// e.g., current "1.26.1+305-gabcdef" does not start with "04.00.00"
|
||||
// This handles both moko semver and git-describe suffixed versions.
|
||||
info.UpdateAvailable = latestVersion != "" && !strings.Contains(currentVersion, latestVersion)
|
||||
|
||||
mu.Lock()
|
||||
cachedInfo = info
|
||||
// Notify only once per new version (avoid spamming on every cron tick)
|
||||
shouldNotify := info.UpdateAvailable && latestVersion != lastNotifiedVer
|
||||
if shouldNotify {
|
||||
lastNotifiedVer = latestVersion
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
if info.UpdateAvailable {
|
||||
log.Info("MokoGitea update available: %s [%s] (current: %s)", latestVersion, channel, currentVersion)
|
||||
if shouldNotify && NotifyFunc != nil {
|
||||
NotifyFunc(info)
|
||||
}
|
||||
} else {
|
||||
log.Debug("MokoGitea is up to date: %s [%s]", currentVersion, channel)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/updatechecker"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/mailer"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/migrations"
|
||||
mirror_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/mirror"
|
||||
packages_cleanup_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/packages/cleanup"
|
||||
@@ -190,6 +191,11 @@ func initBasicTasks() {
|
||||
}
|
||||
|
||||
func registerUpdateChecker() {
|
||||
// Wire up email notification for admin when updates are detected
|
||||
updatechecker.NotifyFunc = func(info *updatechecker.UpdateInfo) {
|
||||
mailer.SendUpdateNotification(info.LatestVersion, info.Channel, info.ReleaseURL, info.DockerImage)
|
||||
}
|
||||
|
||||
RegisterTaskFatal("update_checker", &BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: true,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||
sender_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/mailer/sender"
|
||||
)
|
||||
|
||||
// SendUpdateNotification emails all admin users when a new MokoGitea version is available.
|
||||
func SendUpdateNotification(version, channel, releaseURL, dockerImage string) {
|
||||
if setting.MailService == nil {
|
||||
log.Debug("Update notification skipped: mail service not configured")
|
||||
return
|
||||
}
|
||||
|
||||
admin, err := user_model.GetAdminUser(context.Background())
|
||||
if err != nil {
|
||||
log.Error("SendUpdateNotification: GetAdminUser: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("[MokoGitea] Update available: %s (%s)", version, channel)
|
||||
|
||||
body := fmt.Sprintf(`MokoGitea Update Available
|
||||
|
||||
A new version is available on the %s channel.
|
||||
|
||||
Version: %s
|
||||
Channel: %s
|
||||
Current: %s`, channel, version, channel, setting.AppVer)
|
||||
|
||||
if releaseURL != "" {
|
||||
body += fmt.Sprintf("\nRelease: %s", releaseURL)
|
||||
}
|
||||
if dockerImage != "" {
|
||||
body += fmt.Sprintf("\nDocker: docker pull %s", dockerImage)
|
||||
}
|
||||
|
||||
body += fmt.Sprintf("\n\nUpdate the channel in Site Administration > Dashboard.\n\n— %s", setting.AppName)
|
||||
|
||||
msg := sender_service.NewMessage(admin.EmailTo(), subject, body)
|
||||
msg.Info = "Update notification"
|
||||
|
||||
SendAsync(msg)
|
||||
log.Info("Update notification sent to %s for version %s [%s]", admin.Email, version, channel)
|
||||
}
|
||||
Reference in New Issue
Block a user