notifications: send email when manual app update is required

This commit is contained in:
Girish Ramakrishnan
2026-03-21 15:59:41 +01:00
parent cd6acfb91d
commit f2949c1836
5 changed files with 66 additions and 5 deletions
@@ -0,0 +1,15 @@
Dear <%= cloudronName %> Admin,
The following app(s) require manual update:
<% for (const app of apps) { -%>
* <%= app.title %> at <%= app.fqdn %> -> v<%= app.version %>
<% } -%>
Go to the update section of the app to update.
Powered by https://cloudron.io
Don't want such mails? Change your notification preferences at <%= notificationsUrl %>
Sent at: <%= new Date().toUTCString() %>
+17
View File
@@ -339,6 +339,22 @@ async function boxManualUpdateRequired(mailTo, version, changelog) {
await sendMail(mailOptions);
}
async function appManualUpdateRequired(mailTo, apps) {
assert.strictEqual(typeof mailTo, 'string');
assert(Array.isArray(apps));
const mailConfig = await getMailConfig();
const mailOptions = {
from: mailConfig.notificationFrom,
to: mailTo,
subject: `[${mailConfig.cloudronName}] ${apps.length} app(s) require manual update`,
text: render('app_manual_update_required-text.ejs', { cloudronName: mailConfig.cloudronName, apps, notificationsUrl: mailConfig.notificationsUrl })
};
await sendMail(mailOptions);
}
async function certificateRenewalError(mailTo, domain, message) {
assert.strictEqual(typeof mailTo, 'string');
assert.strictEqual(typeof domain, 'string');
@@ -386,6 +402,7 @@ export default {
oomEvent,
rebootRequired,
boxManualUpdateRequired,
appManualUpdateRequired,
boxUpdateError,
lowDiskSpace,
sendTestMail,
+28
View File
@@ -350,6 +350,33 @@ async function pin(type, title, message, options) {
return result.id;
}
async function manualAppUpdate(appsNeedingUpdate) {
assert(Array.isArray(appsNeedingUpdate));
const appsNeedingEmail = [];
for (const app of appsNeedingUpdate) {
const message = `Changelog:\n${app.updateInfo.manifest.changelog}\n`;
const existing = await getByType(TYPE_MANUAL_APP_UPDATE_NEEDED, app.id);
if (!existing || (existing.acknowledged && existing.message !== message)) {
appsNeedingEmail.push({ title: app.manifest.title, fqdn: app.fqdn, version: app.updateInfo.manifest.version });
}
await pin(TYPE_MANUAL_APP_UPDATE_NEEDED, `${app.manifest.title} at ${app.fqdn} requires manual update to version ${app.updateInfo.manifest.version}`,
message, { context: app.id });
}
if (appsNeedingEmail.length === 0) return;
const admins = await users.getAdmins();
for (const admin of admins) {
if (admin.notificationConfig.includes(TYPE_MANUAL_UPDATE_REQUIRED)) {
await safe(mailer.appManualUpdateRequired(admin.email, appsNeedingEmail), { debug: log });
}
}
}
async function unpin(type, options) {
assert.strictEqual(typeof type, 'string'); // TYPE_
assert.strictEqual(typeof options, 'object');
@@ -432,5 +459,6 @@ export default {
TYPE_DOMAIN_CONFIG_CHECK_FAILED,
pin,
unpin,
manualAppUpdate,
_add: add,
};
+5 -4
View File
@@ -359,18 +359,19 @@ async function raiseNotifications() {
}
const result = await apps.list();
const manualUpdateApps = [];
for (const app of result) {
if (!app.updateInfo) continue;
// currently, we do not raise notifications when auto-update is disabled. separate notifications appears spammy when having many apps
// in the future, we can maybe aggregate?
if (!app.updateInfo.isAutoUpdatable) {
log(`autoUpdate: ${app.fqdn} cannot be autoupdated. skipping`);
await notifications.pin(notifications.TYPE_MANUAL_APP_UPDATE_NEEDED, `${app.manifest.title} at ${app.fqdn} requires manual update to version ${app.updateInfo.manifest.version}`,
`Changelog:\n${app.updateInfo.manifest.changelog}\n`, { context: app.id });
manualUpdateApps.push(app);
continue;
}
}
await safe(notifications.manualAppUpdate(manualUpdateApps), { debug: log });
}
async function checkForUpdates(options) {