diff --git a/src/backups.js b/src/backups.js index 6cbb11894..6cbc0ff50 100644 --- a/src/backups.js +++ b/src/backups.js @@ -29,6 +29,8 @@ exports = module.exports = { injectPrivateFields: injectPrivateFields, removePrivateFields: removePrivateFields, + checkConfiguration: checkConfiguration, + SECRET_PLACEHOLDER: String.fromCharCode(0x25CF).repeat(8), // for testing @@ -1256,3 +1258,20 @@ function startCleanupTask(auditSource, callback) { }); }); } + +function checkConfiguration(callback) { + assert.strictEqual(typeof callback, 'function'); + + settings.getBackupConfig(function (error, backupConfig) { + if (error) return callback(error); + + let message = ''; + if (backupConfig.provider === 'noop') { + message = 'Cloudron backups are disabled. Please ensure this server is backed up using alternate means. See https://cloudron.io/documentation/backups/#storage-providers for more information.'; + } else if (backupConfig.provider === 'filesystem' && !backupConfig.externalDisk) { + message = 'Cloudron backups are currently on the same disk as the Cloudron server instance. This is dangerous and can lead to complete data loss if the disk fails. See https://cloudron.io/documentation/backups/#storage-providers for storing backups in an external location.'; + } + + callback(null, message); + }); +} diff --git a/src/cloudron.js b/src/cloudron.js index 0b6e2ecb4..9270b66a2 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -27,6 +27,7 @@ exports = module.exports = { var assert = require('assert'), async = require('async'), + backups = require('./backups.js'), clients = require('./clients.js'), config = require('./config.js'), constants = require('./constants.js'), @@ -194,8 +195,8 @@ function runSystemChecks() { checkDiskSpace, checkMailStatus, checkRebootRequired - ], function () { - debug('runSystemChecks: done'); + ], function (error) { + debug('runSystemChecks: done', error); }); } @@ -204,15 +205,8 @@ function checkBackupConfiguration(callback) { debug('Checking backup configuration'); - settings.getBackupConfig(function (error, backupConfig) { - if (error) return console.error(error); - - let message = ''; - if (backupConfig.provider === 'noop') { - message = 'Cloudron backups are disabled. Please ensure this server is backed up using alternate means. See https://cloudron.io/documentation/backups/#storage-providers for more information.'; - } else if (backupConfig.provider === 'filesystem' && !backupConfig.externalDisk) { - message = 'Cloudron backups are currently on the same disk as the Cloudron server instance. This is dangerous and can lead to complete data loss if the disk fails. See https://cloudron.io/documentation/backups/#storage-providers for storing backups in an external location.'; - } + backups.checkConfiguration(function (error, message) { + if (error) return callback(error); notifications.alert(notifications.ALERT_BACKUP_CONFIG, message, callback); }); @@ -262,26 +256,10 @@ function checkMailStatus(callback) { debug('checking mail status'); - domains.getAll(function (error, allDomains) { + mail.checkConfiguration(function (error, message) { if (error) return callback(error); - async.filterSeries(allDomains, function (domainObject, iteratorCallback) { - mail.getStatus(config.adminDomain(), function (error, result) { - if (error) return iteratorCallback(null, true); - - let mailError = Object.keys(result.dns).some((record) => !result.dns[record].status); - if (result.relay && !result.relay.status) mailError = true; - if (result.rbl && result.rbl.status === false) mailError = true; // rbl is an optional check - - iteratorCallback(null, mailError); - }); - }, function (error, erroredDomainObjects) { - if (error || erroredDomainObjects.length === 0) return callback(error); - - const erroredDomains = erroredDomainObjects.map((d) => d.domain); - debug(`checkMailStatus: ${erroredDomains.join(',')} failed status checks`); - notifications.alert(notifications.ALERT_MAIL_STATUS, erroredDomains.length ? `Email status check of the following domain(s) failed - ${erroredDomains.join(',')}. See the Status tab in the [Email view](/#/email/) for more information.` : '', callback); - }); + notifications.alert(notifications.ALERT_MAIL_STATUS, message, callback); }); } diff --git a/src/mail.js b/src/mail.js index 01d4c0c68..503f4ce60 100644 --- a/src/mail.js +++ b/src/mail.js @@ -2,6 +2,7 @@ exports = module.exports = { getStatus: getStatus, + checkConfiguration: checkConfiguration, getDomains: getDomains, @@ -455,9 +456,9 @@ function getStatus(domain, callback) { // ensure we always have a valid toplevel properties for the api var results = { - dns: {}, - rbl: {}, - relay: {} + dns: {}, // { mx: { expected, value }, dmarc: { expected, value }, dkim: { expected, value }, spf: { expected, value }, ptr: { expected, value } } + rbl: {}, // { status, ip, servers: [{name,site,dns}]} + relay: {} // { status, value } }; function recordResult(what, func) { @@ -504,6 +505,50 @@ function getStatus(domain, callback) { }); } +function checkConfiguration(callback) { + assert.strictEqual(typeof callback, 'function'); + + let messages = {}; + + domains.getAll(function (error, allDomains) { + if (error) return callback(error); + + async.eachSeries(allDomains, function (domainObject, iteratorCallback) { + getStatus(domainObject.domain, function (error, result) { + if (error) return iteratorCallback(error); + + let message = []; + + Object.keys(result.dns).forEach((type) => { + const record = result.dns[type]; + if (!record.status) message.push(`${type.toUpperCase()} DNS record did not match. Expected: \`${record.expected}\`. Actual: \`${record.value}\``); + }); + if (result.relay && !result.relay.status) message.push(`Relay error: ${result.relay.value}`); + if (result.rbl && result.rbl.status === false) { // rbl field contents is optional + const servers = result.rbl.servers.map((bs) => `[${bs.name}](${bs.site})`); // in markdown + message.push(`This server's IP \`${result.rbl.ip}\` is blacklisted in the following servers - ${servers.join(', ')}`); + } + + if (message.length) messages[domainObject.domain] = message; + + iteratorCallback(null); + }); + }, function (error) { + if (error) return callback(error); + + // create bulleted list for each domain + let markdownMessage = ''; + Object.keys(messages).forEach((domain) => { + markdownMessage += `**${domain}**\n`; + markdownMessage += messages[domain].map((m) => `* ${m}\n`).join(''); + markdownMessage += '\n\n'; + }); + + callback(null, markdownMessage); + }); + }); +} + function createMailConfig(mailFqdn, callback) { assert.strictEqual(typeof mailFqdn, 'string'); assert.strictEqual(typeof callback, 'function');