UNPKG

iobroker.backitup

Version:

ioBroker.backitup allows you to backup and restore your ioBroker installation and other systems, such as databases, Zigbee, scripts and many more.

568 lines (516 loc) 30.3 kB
const path = require('node:path'); const fs = require('node:fs'); const tools = require('./tools'); let timerCleanFiles; let tmpLog = ''; function loadScripts(callback) { const scripts = {}; let files; try { files = fs.readdirSync(`${__dirname}/scripts`); files.forEach(file => { if (file.endsWith('.js')) { scripts[file.substring(3, file.length - 3)] = require(`${__dirname}/scripts/${file}`); } }); } catch (e) { callback(`error on backup: ${e} Please run "iobroker fix" and reinstall BackItUp`); } return scripts; } function writeIntoFile(fileName, text) { if (text) { console.log(text); try { fs.appendFileSync(fileName, `${text}\n`); } catch (e) { // ignore } } } function createBackupLog(config, adapter) { // Create Backup Logs for History const logFile = path.join(config.notification.bashDir, `${adapter.namespace}.log`); if (fs.existsSync(logFile)) { const data = fs.readFileSync(logFile, 'utf8'); const backupLog = JSON.parse(data); const timestamp = config.timestamp; try { backupLog.unshift({ [timestamp]: tmpLog }); } catch (err) { adapter.log.error(`Backup Logs could not be created: ${err}`); } if (backupLog && backupLog.length > config.notification.entriesNumber) { backupLog.splice(config.notification.entriesNumber, backupLog.length - config.notification.entriesNumber); } fs.writeFileSync(logFile, JSON.stringify(backupLog)); tmpLog = ''; } } function executeScripts(adapter, config, callback, scripts, code) { if (!scripts) { scripts = loadScripts(callback); config.backupDir = path.join(tools.getIobDir(), 'backups').replace(/\\/g, '/'); config.timestamp = new Date().getTime(), config.context = { fileNames: [], errors: {}, done: [], types: [] }; // common variables between scripts if (!fs.existsSync(config.backupDir)) { try { fs.mkdirSync(config.backupDir); fs.chmodSync(config.backupDir, '0775'); } catch (e) { callback(`Backup directory cannot created: ${e} Please reinstall BackItUp and run "iobroker fix"!!`); } } else if ((!config.cifs.enabled) || (config.cifs.enabled && config.cifs.mountType === 'Copy')) { try { fs.chmodSync(config.backupDir, '0775'); } catch (e) { callback(`chmod for Backup directory could not be completed: ${e} Please run "iobroker fix"!!`); } } } for (const name in scripts) { if (scripts.hasOwnProperty(name) && scripts[name] && (!config.afterBackup || scripts[name].afterBackup)) { let func; let options; switch (name) { // Mount tasks case 'mount': if (config.cifs && config.cifs.enabled && config.cifs.mount) { func = scripts[name]; options = config.cifs; } break; case 'umount': if (config.cifs && config.cifs.enabled && config.cifs.mount) { func = scripts[name]; options = config.cifs; } break; // Copy/delete tasks case 'clean': func = scripts[name]; options = { name: config.name, deleteBackupAfter: config.deleteBackupAfter, ignoreErrors: config.ignoreErrors }; break; case 'cifs': case 'webdav': case 'dropbox': case 'onedrive': case 'googledrive': case 'ftp': if (config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name], { name: config.name, deleteBackupAfter: config.deleteBackupAfter }); } break; // Extra data sources case 'mysql': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'sqlite': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'pgsql': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'redis': if ((config.name === 'iobroker' && config[name] && config[name].enabled)) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'historyDB': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'javascripts': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'zigbee': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'esphome': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'zigbee2mqtt': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'nodered': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'yahka': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'grafana': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'jarvis': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; case 'influxDB': if (config.name === 'iobroker' && config[name] && config[name].enabled) { func = scripts[name]; options = Object.assign({}, config[name]); } break; // Main tasks case 'ccu': case 'iobroker': if (config.name === name && config.enabled && config.slaveBackup !== 'Slave') { func = scripts[name]; options = config; } break; // Messaging tasks case 'historyHTML': if (config[name] && config[name].enabled) { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for historyHTML!!'); } } break; case 'historyJSON': if (config[name] && config[name].enabled) { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for historyJSON!!'); } } break; case 'telegram': if (config[name] && config[name].enabled && config[name].notificationsType === 'Telegram') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for telegram!!'); } } break; case 'whatsapp': if (config[name] && config[name].enabled && config[name].notificationsType === 'WhatsApp') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for whatsapp!!'); } } break; case 'gotify': if (config[name] && config[name].enabled && config[name].notificationsType === 'Gotify') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for gotify!!'); } } break; case 'signal': if (config[name] && config[name].enabled && config[name].notificationsType === 'Signal') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for signal!!'); } } break; case 'matrix': if (config[name] && config[name].enabled && config[name].notificationsType === 'Matrix') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for matrix!!'); } } break; case 'email': if (config[name] && config[name].enabled && config[name].notificationsType === 'E-Mail') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for email!!'); } } break; case 'pushover': if (config[name] && config[name].enabled && config[name].notificationsType === 'Pushover') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for pushover!!'); } } break; case 'discord': if (config[name] && config[name].enabled && config[name].notificationsType === 'Discord') { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for discord!!'); } } break; case 'notification': if (config[name]) { func = scripts[name]; try { options = JSON.parse(JSON.stringify(config)); options[name].time = tools.getTimeString(options[name].systemLang); // provide date } catch (e) { callback('cannot parse config for notification!!'); } } break; } scripts[name] = null; if (func) { try { const _options = JSON.parse(JSON.stringify(options)); if (_options.ftp && !_options.ftp.enabled) delete _options.ftp; if (_options.cifs && !_options.cifs.enabled) delete _options.cifs; if (_options.telegram && !_options.telegram.enabled) delete _options.telegram; if (_options.pushover && !_options.pushover.enabled) delete _options.pushover; if (_options.email && !_options.email.enabled) delete _options.email; if (_options.whatsapp && !_options.whatsapp.enabled) delete _options.whatsapp; if (_options.gotify && !_options.gotify.enabled) delete _options.gotify; if (_options.discord && !_options.discord.enabled) delete _options.discord; if (_options.dropbox && !_options.dropbox.enabled) delete _options.dropbox; if (_options.onedrive && !_options.onedrive.enabled) delete _options.onedrive; if (_options.webdav && !_options.webdav.enabled) delete _options.webdav; if (_options.googledrive && !_options.googledrive.enabled) delete _options.googledrive; if (_options.mysql && !_options.mysql.enabled) delete _options.mysql; if (_options.sqlite && !_options.sqlite.enabled) delete _options.sqlite; if (_options.pgsql && !_options.pgsql.enabled) delete _options.pgsql; if (_options.influxDB && !_options.influxDB.enabled) delete _options.influxDB; if (_options.grafana && !_options.grafana.enabled) delete _options.grafana; if (_options.javascripts && !_options.javascripts.enabled) delete _options.javascripts; if (_options.jarvis && !_options.jarvis.enabled) delete _options.jarvis; if (_options.zigbee && !_options.zigbee.enabled) delete _options.zigbee; if (_options.esphome && !_options.esphome.enabled) delete _options.esphome; if (_options.zigbee2mqtt && !_options.zigbee2mqtt.enabled) delete _options.zigbee2mqtt; if (_options.nodered && !_options.nodered.enabled) delete _options.nodered; if (_options.yahka && !_options.yahka.enabled) delete _options.yahka; if (_options.historyDB && !_options.historyDB.enabled) delete _options.historyDB; if (_options.redis && !_options.redis.enabled) delete _options.redis; if (_options.ftp && _options.ftp.backupDir !== undefined) delete _options.ftp.backupDir; if (_options.cifs && _options.cifs.backupDir !== undefined) delete _options.cifs.backupDir; if (_options.mysql && _options.mysql.backupDir !== undefined) delete _options.mysql.backupDir; if (_options.sqlite && _options.sqlite.backupDir !== undefined) delete _options.sqlite.backupDir; if (_options.pgsql && _options.pgsql.backupDir !== undefined) delete _options.pgsql.backupDir; if (_options.influxDB && _options.influxDB.backupDir !== undefined) delete _options.influxDB.backupDir; if (_options.grafana && _options.grafana.backupDir !== undefined) delete _options.grafana.backupDir; if (_options.jarvis && _options.jarvis.backupDir !== undefined) delete _options.jarvis.backupDir; if (_options.zigbee && _options.zigbee.backupDir !== undefined) delete _options.zigbee.backupDir; if (_options.esphome && _options.esphome.backupDir !== undefined) delete _options.esphome.backupDir; if (_options.zigbee2mqtt && _options.zigbee2mqtt.backupDir !== undefined) delete _options.zigbee2mqtt.backupDir; if (_options.nodered && _options.nodered.backupDir !== undefined) delete _options.nodered.backupDir; if (_options.yahka && _options.yahka.backupDir !== undefined) delete _options.yahka.backupDir; if (_options.javascripts && _options.javascripts.backupDir !== undefined) delete _options.javascripts.backupDir; if (_options.redis && _options.redis.backupDir !== undefined) delete _options.redis.backupDir; if (_options.historyDB && _options.historyDB.backupDir !== undefined) delete _options.historyDB.backupDir; if (!_options.nameSuffix && _options.nameSuffix !== undefined) delete _options.nameSuffix; if (_options.enabled !== undefined) delete _options.enabled; if (_options.context !== undefined) delete _options.context; if (_options.name !== undefined) delete _options.name; if (_options.ftp && _options.ftp.pass !== undefined) _options.ftp.pass = '****'; if (_options.cifs && _options.cifs.pass !== undefined) _options.cifs.pass = '****'; if (_options.mysql && _options.mysql.pass !== undefined) _options.mysql.pass = '****'; if (_options.dropbox && _options.dropbox.onedriveAccessJson !== undefined) _options.dropbox.onedriveAccessJson = '****'; if (_options.onedrive && _options.onedrive.accessToken !== undefined) _options.onedrive.accessToken = '****'; if (_options.googledrive && _options.googledrive.accessJson !== undefined) _options.googledrive.accessJson = '****'; if (_options.webdav && _options.webdav.pass !== undefined) _options.webdav.pass = '****'; if (_options.grafana && _options.grafana.apiKey !== undefined) _options.grafana.apiKey = '****'; if (_options.grafana && _options.grafana.pass !== undefined) _options.grafana.pass = '****'; if (_options.zigbee2mqtt && _options.zigbee2mqtt.z2mPassword !== undefined) _options.zigbee2mqtt.z2mPassword = '****'; if (_options.accessToken !== undefined) _options.accessToken = '****'; if (_options.pass !== undefined) _options.pass = '****'; if (_options.adapter !== undefined) delete _options.adapter; if (_options.accessJson !== undefined) _options.accessJson = '****'; if (_options.apiKey !== undefined) _options.apiKey = '****'; if (_options.z2mPassword !== undefined) _options.z2mPassword = '****'; for (const i in _options) { if (_options[i] !== null && _options[i].dropbox && _options[i].dropbox !== undefined) _options[i].dropbox.accessToken = '****'; if (_options[i] !== null && _options[i].onedrive && _options[i].onedrive !== undefined) _options[i].onedrive.onedriveAccessJson = '****'; if (_options[i] !== null && _options[i].cifs && _options[i].cifs !== undefined) _options[i].cifs.pass = '****'; if (_options[i] !== null && _options[i].ftp && _options[i].ftp !== undefined) _options[i].ftp.pass = '****'; if (_options[i] !== null && _options[i].googledrive && _options[i].googledrive !== undefined) _options[i].googledrive.accessJson = '****'; if (_options[i] !== null && _options[i].mysql && _options[i].mysql !== undefined) _options[i].mysql.pass = '****'; if (_options[i] !== null && _options[i].ccu && _options[i].ccu !== undefined) _options[i].ccu.pass = '****'; if (_options[i] !== null && _options[i].webdav && _options[i].webdav !== undefined) _options[i].webdav.pass = '****'; if (_options[i] !== null && _options[i].grafana && _options[i].grafana !== undefined) _options[i].grafana.pass = '****'; if (_options[i] !== null && _options[i].grafana && _options[i].grafana !== undefined) _options[i].grafana.apiKey = '****'; if (_options[i] !== null && _options[i].zigbee2mqtt && _options[i].zigbee2mqtt !== undefined) _options[i].zigbee2mqtt.z2mPassword = '****'; } if (_options.debugging == true) { setTimeout(function () { adapter && adapter.setState('output.line', `[DEBUG] [${name}] start with ${JSON.stringify(_options)}`, true); }, 200); } } catch (e) { callback(`error on backup process: Script "${name}" ${e} Please check the config of BackItUp and execute "iobroker fix"`); timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); return; } if (!options) { callback(`error on backup process: No valid options for "${name}" Please check the config of BackItUp and execute "iobroker fix"`); timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); return; } options.context = config.context; options.backupDir = config.backupDir; options.timestamp = config.timestamp; options.adapter = adapter; // for delete on Multi-Backup if (config.influxDB && config.influxDB.influxDBMulti) options.influxDBMulti = config.influxDB.influxDBMulti; if (config.influxDB && config.influxDB.influxDBEvents) options.influxDBEvents = config.influxDB.influxDBEvents; if (config.mysql && config.mysql.mySqlMulti) options.mySqlMulti = config.mysql.mySqlMulti; if (config.mysql && config.mysql.mySqlEvents) options.mySqlEvents = config.mysql.mySqlEvents; if (config.pgsql && config.pgsql.pgSqlMulti) options.pgSqlMulti = config.pgsql.pgSqlMulti; if (config.pgsql && config.pgsql.pgSqlEvents) options.pgSqlEvents = config.pgsql.pgSqlEvents; if (config.ccuMulti) options.ccuMulti = config.ccuMulti; if (config.ccuEvents) options.ccuEvents = config.ccuEvents; const fileName = path.join(config.backupDir, 'logs.txt'); const log = { debug: function (text) { let lines; if (typeof text !== 'string') { text = text.toString(); } lines = text.toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && adapter && adapter.log.debug(`[${config.name}/${name}] ${line}`); line && !adapter && writeIntoFile(fileName, `[DEBUG] [${config.name}/${name}] ${line}`); adapter && adapter.setState('output.line', `[DEBUG] [${name}] - ${text}`, true); }); tmpLog += `[DEBUG] [${name}] ${text}${text.endsWith('\n') ? '' : '\n'}`; adapter && adapter.setState('output.line', `[DEBUG] [${name}] - ${text}`, true); }, warn: function (warning) { const lines = (warning || '').toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && adapter && adapter.log.warn(`[${config.name}/${name}] ${line}`); line && !adapter && writeIntoFile(fileName, `[WARN] [${config.name}/${name}] ${line}`); }); tmpLog += `[WARN] [${name}] ${warning}${warning.endsWith('\n') ? '' : '\n'}`; adapter && adapter.setState('output.line', `[WARN] [${name}] - ${warning}`, true); }, error: function (err) { const lines = (err || '').toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && adapter && adapter.log.error(`[${config.name}/${name}] ${line}`); line && !adapter && writeIntoFile(fileName, `[ERROR] [${config.name}/${name}] ${line}`); }); tmpLog += `[ERROR] [${name}] ${err}\n`; adapter && adapter.setState('output.line', `[ERROR] [${name}] - ${err}`, true); } }; try { // generic Error handling for all synchron errors in backup scripts func.command(options, log, (err, output, _code) => { options.adapter = null; if (_code !== undefined) { code = _code } if (err) { //if (func.ignoreErrors) { if (options.ignoreErrors) { log.error(`[IGNORED] ${err}`); timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); } else { log.error(err); callback && callback(err); } } else { log.debug(output || 'done'); timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); } }); } catch (e) { callback(`error on backup process: Error when executing script "${name}": ${e} Please check the config of BackItUp and execute "iobroker fix"`); timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); } } else { timerCleanFiles = setTimeout(function () { setImmediate(executeScripts, adapter, config, callback, scripts, code); }, 150); } return; } } adapter && adapter.setState('output.line', `[EXIT] ${code || 0}`, true); createBackupLog(config, adapter); clearTimeout(timerCleanFiles); callback && callback(); } if (typeof module !== 'undefined' && module.parent) { module.exports = executeScripts; }