Files
cloudron-box/src/cron.js
T
Girish Ramakrishnan 77a5f01585 reverseproxy: rebuild only when needed
re-creating nginx configs is only needed in 3 cases:
* provider changes. we create a rebuild file for this
* nginx config is somehow corrupt by external changes. user can click ui button

on startup, dashboard also always creates the nginx configs. so it's always up to provide the button
2022-11-29 18:17:53 +01:00

265 lines
8.7 KiB
JavaScript

'use strict';
// IMPORTANT: These patterns are together because they spin tasks which acquire a lock
// If the patterns overlap all the time, then the task may not ever get a chance to run!
// If you change this change dashboard patterns in settings.html
const DEFAULT_CLEANUP_BACKUPS_PATTERN = '00 30 1,3,5,23 * * *',
DEFAULT_AUTOUPDATE_PATTERN = '00 00 1,3,5,23 * * *';
exports = module.exports = {
startJobs,
stopJobs,
handleSettingsChanged,
DEFAULT_AUTOUPDATE_PATTERN,
};
const appHealthMonitor = require('./apphealthmonitor.js'),
apps = require('./apps.js'),
assert = require('assert'),
AuditSource = require('./auditsource.js'),
backups = require('./backups.js'),
cloudron = require('./cloudron.js'),
constants = require('./constants.js'),
CronJob = require('cron').CronJob,
debug = require('debug')('box:cron'),
dyndns = require('./dyndns.js'),
eventlog = require('./eventlog.js'),
janitor = require('./janitor.js'),
paths = require('./paths.js'),
safe = require('safetydance'),
scheduler = require('./scheduler.js'),
settings = require('./settings.js'),
system = require('./system.js'),
updater = require('./updater.js'),
updateChecker = require('./updatechecker.js'),
_ = require('underscore');
const gJobs = {
autoUpdater: null,
backup: null,
updateChecker: null,
systemChecks: null,
diskSpaceChecker: null,
certificateRenew: null,
cleanupBackups: null,
cleanupEventlog: null,
cleanupTokens: null,
dockerVolumeCleaner: null,
dynamicDns: null,
schedulerSync: null,
appHealthMonitor: null,
diskUsage: null
};
// cron format
// Seconds: 0-59
// Minutes: 0-59
// Hours: 0-23
// Day of Month: 1-31
// Months: 0-11
// Day of Week: 0-6
function getCronSeed() {
let hour = null;
let minute = null;
const seedData = safe.fs.readFileSync(paths.CRON_SEED_FILE, 'utf8') || '';
const parts = seedData.split(':');
if (parts.length === 2) {
hour = parseInt(parts[0]) || null;
minute = parseInt(parts[1]) || null;
}
if ((hour == null || hour < 0 || hour > 23) || (minute == null || minute < 0 || minute > 60)) {
hour = Math.floor(24 * Math.random());
minute = Math.floor(60 * Math.random());
debug(`getCronSeed: writing new cron seed file with ${hour}:${minute} to ${paths.CRON_SEED_FILE}`);
safe.fs.writeFileSync(paths.CRON_SEED_FILE, `${hour}:${minute}`);
}
return { hour, minute };
}
async function startJobs() {
const { hour, minute } = getCronSeed();
debug(`startJobs: starting cron jobs with hour ${hour} and minute ${minute}`);
gJobs.systemChecks = new CronJob({
cronTime: `00 ${minute} 2 * * *`, // once a day. if you change this interval, change the notification messages with correct duration
onTick: async () => await safe(cloudron.runSystemChecks(), { debug }),
start: true
});
gJobs.diskUsage = new CronJob({
cronTime: `00 ${minute} 3 * * *`, // once a day
onTick: async () => await safe(cloudron.updateDiskUsage(), { debug }),
start: true
});
gJobs.diskSpaceChecker = new CronJob({
cronTime: '00 30 * * * *', // every 30 minutes. if you change this interval, change the notification messages with correct duration
onTick: async () => await safe(system.checkDiskSpace(), { debug }),
start: true
});
// this is run separately from the update itself so that the user can disable automatic updates but can still get a notification
gJobs.updateCheckerJob = new CronJob({
cronTime: `00 ${minute} 1,5,9,13,17,21,23 * * *`,
onTick: async () => await safe(updateChecker.checkForUpdates({ automatic: true }), { debug }),
start: true
});
gJobs.cleanupTokens = new CronJob({
cronTime: '00 */30 * * * *', // every 30 minutes
onTick: async () => await safe(janitor.cleanupTokens(), { debug }),
start: true
});
gJobs.cleanupBackups = new CronJob({
cronTime: DEFAULT_CLEANUP_BACKUPS_PATTERN,
onTick: async () => await safe(backups.startCleanupTask(AuditSource.CRON), { debug }),
start: true
});
gJobs.cleanupEventlog = new CronJob({
cronTime: '00 */30 * * * *', // every 30 minutes
onTick: async () => await safe(eventlog.cleanup({ creationTime: new Date(Date.now() - 60 * 60 * 24 * 60 * 1000) }), { debug }), // 60 days ago
start: true
});
gJobs.dockerVolumeCleaner = new CronJob({
cronTime: '00 00 */12 * * *', // every 12 hours
onTick: async () => await safe(janitor.cleanupDockerVolumes(), { debug }),
start: true
});
gJobs.schedulerSync = new CronJob({
cronTime: constants.TEST ? '*/10 * * * * *' : '00 */1 * * * *', // every minute
onTick: async () => await safe(scheduler.sync(), { debug }),
start: true
});
// randomized per Cloudron based on hourlySeed
gJobs.certificateRenew = new CronJob({
cronTime: `00 10 ${hour} * * *`,
onTick: async () => await safe(cloudron.renewCerts({}, AuditSource.CRON), { debug }),
start: true
});
gJobs.appHealthMonitor = new CronJob({
cronTime: '*/10 * * * * *', // every 10 seconds
onTick: async () => await safe(appHealthMonitor.run(10), { debug }), // 10 is the max run time
start: true
});
const allSettings = await settings.list();
const tz = allSettings[settings.TIME_ZONE_KEY];
backupConfigChanged(allSettings[settings.BACKUP_CONFIG_KEY], tz);
autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY], tz);
dynamicDnsChanged(allSettings[settings.DYNAMIC_DNS_KEY]);
}
// eslint-disable-next-line no-unused-vars
async function handleSettingsChanged(key, value) {
assert.strictEqual(typeof key, 'string');
// value is a variant
switch (key) {
case settings.TIME_ZONE_KEY:
case settings.BACKUP_CONFIG_KEY:
case settings.AUTOUPDATE_PATTERN_KEY:
case settings.DYNAMIC_DNS_KEY:
debug('handleSettingsChanged: recreating all jobs');
await stopJobs();
await startJobs();
break;
default:
break;
}
}
function backupConfigChanged(value, tz) {
assert.strictEqual(typeof value, 'object');
assert.strictEqual(typeof tz, 'string');
debug(`backupConfigChanged: schedule ${value.schedulePattern} (${tz})`);
if (gJobs.backup) gJobs.backup.stop();
gJobs.backup = new CronJob({
cronTime: value.schedulePattern,
onTick: async () => await safe(backups.startBackupTask(AuditSource.CRON), { debug }),
start: true,
timeZone: tz
});
}
function autoupdatePatternChanged(pattern, tz) {
assert.strictEqual(typeof pattern, 'string');
assert.strictEqual(typeof tz, 'string');
debug(`autoupdatePatternChanged: pattern - ${pattern} (${tz})`);
if (gJobs.autoUpdater) gJobs.autoUpdater.stop();
if (pattern === constants.AUTOUPDATE_PATTERN_NEVER) return;
gJobs.autoUpdater = new CronJob({
cronTime: pattern,
onTick: async function() {
const updateInfo = updateChecker.getUpdateInfo();
// do box before app updates. for the off chance that the box logic fixes some app update logic issue
if (updateInfo.box && !updateInfo.box.unstable) {
debug('Starting box autoupdate to %j', updateInfo.box);
const [error] = await safe(updater.updateToLatest({ skipBackup: false }, AuditSource.CRON));
if (error) debug(`Failed to box autoupdate: ${error.message}`);
return;
}
const appUpdateInfo = _.omit(updateInfo, 'box');
if (Object.keys(appUpdateInfo).length > 0) {
debug('Starting app update to %j', appUpdateInfo);
const [error] = await safe(apps.autoupdateApps(appUpdateInfo, AuditSource.CRON));
if (error) debug(`Failed to app autoupdate: ${error.message}`);
} else {
debug('No app auto updates available');
}
},
start: true,
timeZone: tz
});
}
function dynamicDnsChanged(enabled) {
assert.strictEqual(typeof enabled, 'boolean');
debug('Dynamic DNS setting changed to %s', enabled);
if (enabled) {
gJobs.dynamicDns = new CronJob({
cronTime: '5 * * * * *', // we only update the records if the ip has changed.
onTick: async () => await safe(dyndns.sync(AuditSource.CRON), { debug }),
start: true
});
} else {
if (gJobs.dynamicDns) gJobs.dynamicDns.stop();
gJobs.dynamicDns = null;
}
}
async function stopJobs() {
for (const job in gJobs) {
if (!gJobs[job]) continue;
gJobs[job].stop();
gJobs[job] = null;
}
}