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.

358 lines (317 loc) 13.4 kB
const fs = require('node:fs'); const path = require('node:path'); let logWebIF = ''; let statusColor = ''; let restoreStatus = ''; let startFinish = ''; let httpServer; function writeIntoFile(fileName, text) { if (text) { console.log(text); try { fs.appendFileSync(fileName, `${text }\n`); } catch (e) { // ignore } } } function getConfig(options, backupType, storageType) { let config = options[backupType]; if (!config) { for (const attr in options) { if (options.hasOwnProperty(attr) && typeof options[attr] === 'object' && options[attr][backupType] ) { config = options[attr][backupType]; break; } } if (!config) { for (const attr in options) { if (options.hasOwnProperty(attr) && typeof options[attr] === 'object' && options[attr][storageType] ) { config = options[attr][storageType]; break; } } } } else if (storageType) { return config[storageType]; } return config; } function getFile(options, storageType, fileName, toSaveName, log, callback) { if (fs.existsSync(toSaveName)) { callback(null, toSaveName); } else { const name = fileName.split('/').pop(); let backupType = name.split('.')[0]; if (backupType !== 'nodered' && backupType !== 'zigbee' && backupType !== 'jarvis' && backupType !== 'yahka' && backupType !== 'esphome') { backupType = name.split('_')[0]; } if (name.match(/^\d\d\d\d_\d\d_\d\d-\d\d_\d\d_\d\d_backupiobroker\.tar\.gz$/)) { backupType = 'iobroker'; } let config = getConfig(options, backupType, storageType); if (storageType !== 'local') { const _getFile = require(`./list/${storageType}`).getFile; _getFile(config, fileName, toSaveName, log, callback); } else { const tools = require('./tools'); tools.copyFile(fileName, toSaveName, callback); } } } function startDetachedRestore(bashDir) { const { spawn } = require('node:child_process'); const isWin = process.platform.startsWith('win'); const spawnOptions = { detached: true, cwd: __dirname, stdio: [ 'ignore', 'ignore', 'ignore', ], }; if (isWin) { spawnOptions.shell = true; } const cmd = spawn(isWin ? `${bashDir}/stopIOB.bat` : 'bash', [isWin ? '' : `${bashDir}/stopIOB.sh`], spawnOptions); cmd.unref(); } function startIOB(bashDir) { const child_process = require('node:child_process'); const isWin = process.platform.startsWith('win'); startFinish = '[Restart]'; let timeFinish = 10000; if (fs.existsSync('/opt/scripts/.docker_config/.thisisdocker')) { timeFinish = 3000; logWebIF += '[EXIT] **** Docker Container restart now... ****\n'; } setTimeout(() => startFinish = '[Finish]', timeFinish); child_process.exec(isWin ? `${bashDir}/startIOB.bat` : `bash ${bashDir}/startIOB.sh`, (error, stdout, stderr) => { if (error) { logWebIF += `[ERROR] ${error}\n`; logWebIF += `[ERROR] ${stderr}\n`; } else if (stdout) { logWebIF += `${stdout}\n`; } setTimeout(() => { try { httpServer && httpServer.close(); httpServer = null; } catch (e) { console.error(e); } process.exit(); }, 30 * 1000); }); } function restore(adapter, options, storageType, fileName, currentTheme, currentProtocol, bashDir, log, callback) { options = JSON.parse(JSON.stringify(options)); if (storageType === 'nas / copy') { storageType = 'cifs'; } if (adapter) { const tools = require('./tools'); const backupDir = path.join(tools.getIobDir(), 'backups').replace(/\\/g, '/'); if (storageType === 'local') { try { fs.chmodSync(backupDir, '0775'); log.debug(`set chmod for "${backupDir}" successfully`); } catch (err) { log.debug(`cannot set chmod for "${backupDir}": ${err}`); } } const name = fileName.split('/').pop(); const toSaveName = path.join(backupDir, name); getFile(options, storageType, fileName, toSaveName, log, err => { if (!err && fs.existsSync(toSaveName)) { let backupType = name.split('.')[0]; if (backupType !== 'nodered' && backupType !== 'zigbee' && backupType !== 'jarvis' && backupType !== 'yahka' && backupType !== 'esphome' ) { backupType = name.split('_')[0]; } if (backupType === 'redis') { fs.writeFileSync(`${bashDir}/.redis.info`, 'Stop redis-server before Restore'); log.debug('Redis-Server stopped'); } if (name.match(/^\d\d\d\d_\d\d_\d\d-\d\d_\d\d_\d\d_backupiobroker\.tar\.gz$/)) { backupType = 'iobroker'; } let config = getConfig(options, backupType); config.backupDir = config.backupDir || backupDir; config.backupType = config.backupType || backupType; config.name = config.name || backupType; const _module = require(`./restore/${backupType}`); if (_module.isStop) { if (backupType === 'iobroker' && fs.existsSync(bashDir)) { // copy restore files const restoreDir = path.join(bashDir, 'restore'); const restoreSource = path.join(__dirname, 'restore'); tools.copyFile(path.join(__dirname, 'restore.js'), path.join(bashDir, 'restore.js')); if (!fs.existsSync(restoreDir)) { fs.mkdirSync(restoreDir); } tools.copyFile(path.join(restoreSource, `${backupType}.js`), path.join(restoreDir, `${backupType}.js`)); } config.fileName = toSaveName; config.theme = currentTheme; config.currentProtocol = currentProtocol; config.bashDir = bashDir; fs.writeFileSync(`${backupType === 'iobroker' ? bashDir : __dirname}/restore.json`, JSON.stringify(config, null, 2)); startDetachedRestore(bashDir); return callback && callback({ error: '' }); } const log = { debug: text => { const lines = text.toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && adapter.log.debug(`[${backupType}] ${line}`); }); adapter && adapter.setState('output.line', `[DEBUG] [${backupType}] - ${text}`, true); }, error: textError => { const lines = textError.toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && adapter.log.error(`[${backupType}] ${line}`); }); adapter && adapter.setState('output.line', `[ERROR] [${backupType}] - ${textError}`, true); }, exit: exitCode => { adapter && adapter.setState('output.line', `[EXIT] ${exitCode || 0}`, true); } }; _module.restore(config, toSaveName, log, adapter, (err, exitCode) => { log.exit(exitCode); callback({ error: err, exitCode }); }); } else { callback({ error: err || `File ${toSaveName} not found` }); } }); } else { try { const config = options; const _module = require(`./restore/${config.backupType}`); _module.restore(config, config.fileName, log, (err, exitCode) => { log.exit(exitCode); callback({ error: err, exitCode }) }); } catch (e) { log.error(e); log.exit(-1); } } } // WebInterface Restore function restoreIF(currentTheme, currentProtocol, bashDir) { const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors()); app.get('/status.json', (req, res) => { res.setHeader('content-type', 'application/json'); res.json({ logWebIF: logWebIF || 'Restore is started ...', startFinish, statusColor, restoreStatus, dark: currentTheme === 'dark' || currentTheme === 'react-blue' || currentTheme === 'react-dark', }); }); if (currentProtocol === 'https:') { const https = require('node:https'); let privateKey = ''; let certificate = ''; if (fs.existsSync(path.join(bashDir, 'iob.key')) && fs.existsSync(path.join(bashDir, 'iob.crt'))) { try { privateKey = fs.readFileSync(path.join(bashDir, 'iob.key'), 'utf8'); certificate = fs.readFileSync(path.join(bashDir, 'iob.crt'), 'utf8'); } catch (e) { console.log('no certificates found'); } } const credentials = { key: privateKey, cert: certificate }; httpServer = https.createServer(credentials, app); } else { const http = require('node:http'); httpServer = http.createServer(app); } httpServer.listen(8091); } if (typeof module !== 'undefined' && module.parent) { module.exports = { restore, getFile, }; } else { if (fs.existsSync(`${__dirname}/restore.json`)) { const config = require(`${__dirname}/restore.json`); const logName = path.join(config.backupDir, 'logs.txt').replace(/\\/g, '/'); startFinish = '[Restore]'; restoreStatus = 'The ioBroker is currently being restored'; restoreIF(config.theme, config.currentProtocol, config.bashDir); const log = { debug: text => { const lines = text.toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && writeIntoFile(logName, `[DEBUG] [${config.name}] ${line}`); if (line) { logWebIF += `[DEBUG] [${config.name}] ${line}\n`; } }); }, error: err => { const lines = err.toString().split('\n'); lines.forEach(line => { line = line.replace(/\r/g, ' ').trim(); line && writeIntoFile(logName, `[ERROR] [${config.name}] ${line}`); if (line) { logWebIF += `[ERROR] [${config.name}] ${line}\n`; } }); }, exit: exitCode => { writeIntoFile(logName, `[EXIT] ${exitCode}`); if (exitCode == 0 || exitCode === 'mysql restore done' || exitCode === 'sqlite restore done' || exitCode === 'redis restore done' || exitCode === 'historyDB restore done' || exitCode === 'zigbee database restore done' || exitCode === 'ESPHome data restore done' || exitCode === 'node-red restore done' || exitCode === 'yahka database restore done' || exitCode === 'jarvis database restore done' || exitCode === 'influxDB restore done' || exitCode === 'postgresql restore done' || exitCode === 'Grafana restore done' || exitCode === 'javascript restore done' ) { logWebIF += `[EXIT] ${exitCode} **** Restore completed successfully!! ****\n`; statusColor = '#00b204'; restoreStatus = 'Restore completed successfully!! Starting iobroker... Please wait!'; } else { logWebIF += `[EXIT] ${exitCode} **** Restore was canceled!! ****\n`; statusColor = '#c62828'; restoreStatus = 'Restore was canceled!! If ioBroker does not start automatically, please start it manually'; } } }; restore(null, config, null, null, null, null, null, log, () => startIOB(config.bashDir)); } else { console.log(`No config found at "${path.normalize(path.join(__dirname, 'restore.json'))}"`); } }