77a5f01585
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
265 lines
8.7 KiB
JavaScript
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;
|
|
}
|
|
}
|