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.

269 lines (238 loc) 11.6 kB
const fs = require('node:fs'); const fsAsync = require('node:fs').promises; const path = require('node:path'); const fse = require('fs-extra'); let waitRestore; let timerDone; async function postData(options, log, tmpDir) { return new Promise(async (resolve) => { const dashboardDir = path.join(tmpDir, 'dashboards').replace(/\\/g, '/'); const datasourceDir = path.join(tmpDir, 'datasource').replace(/\\/g, '/'); const folderDir = path.join(tmpDir, 'folder').replace(/\\/g, '/'); const dashBoards = await fsAsync.readdir(dashboardDir); const dataSources = await fsAsync.readdir(datasourceDir); const folders = await fsAsync.readdir(folderDir); const host = options.host ? options.host : options.grafana.host; const port = options.port ? options.port : options.grafana.port; const apiKey = options.apiKey ? options.apiKey : options.grafana.apiKey; const protocol = options.protocol ? options.protocol : options.grafana.protocol; const signedCertificates = options.signedCertificates ? options.signedCertificates : options.grafana.signedCertificates ? options.grafana.signedCertificates : true; const axios = require('axios'); const https = require('node:https'); // Check available let available; try { available = await axios({ method: 'get', url: `${protocol}://${host}:${port}`, validateStatus: () => true, httpsAgent: new https.Agent({ rejectUnauthorized: signedCertificates, }) }); } catch (err) { log.debug(`Grafana is not available: ${err}`); } if (available && available.status) { log.debug(`Grafana is available ... Status: ${available.status}`); // post datasource try { await Promise.all(dataSources.map(async (dataSource) => { let dataSourcePth = path.join(datasourceDir, dataSource).replace(/\\/g, '/'); let dataSourceFile = await fsAsync.readFile(dataSourcePth); let dataSourceName = dataSource.split('.').shift(); log.debug(`Try to Restore: ${dataSourcePth}`); await axios({ method: 'POST', baseURL: `${protocol}://${host}:${port}`, url: '/api/datasources', data: dataSourceFile, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, /* auth: { username: username, password: pass, }, */ httpsAgent: new https.Agent({ rejectUnauthorized: signedCertificates, }), }) .then(result => { log.debug(`datasoure restore "${dataSourceName}" finish: ${JSON.stringify(result.data)}`) }).catch(err => { log.debug(`cannot restore datasource "${dataSourceName}": ${JSON.stringify(err.response.data['message'])}`) }); })); } catch (err) { log.debug(`Grafana datasource restore not possible: ${err}`); } // restore folders try { await Promise.all(folders.map(async (folderFile) => { const folderPath = path.join(folderDir, folderFile).replace(/\\/g, '/'); const folderJson = JSON.parse(await fsAsync.readFile(folderPath, 'utf8')); const folderTitle = folderJson.title; const folderUid = folderJson.uid; if (folderUid) { log.debug(`Try to restore folder: ${folderTitle} (${folderUid})`); await axios({ method: 'POST', baseURL: `${protocol}://${host}:${port}`, url: '/api/folders', data: folderJson, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, httpsAgent: new https.Agent({ rejectUnauthorized: signedCertificates, }), }).then(result => { log.debug(`Folder "${folderTitle}" restored`); }).catch(err => { const message = err.response?.data?.message || err.message; log.debug(`Cannot restore folder "${folderTitle}": ${message}`); }); } })); } catch (err) { log.debug(`Grafana folder restore not possible: ${err}`); } const folderMapPath = path.join(folderDir, 'dashboard_folder_map.json').replace(/\\/g, '/'); let dashboardFolderMap = {}; try { const mapData = await fsAsync.readFile(folderMapPath, 'utf8'); dashboardFolderMap = JSON.parse(mapData); log.debug(`Loaded dashboard-folder mapping with ${Object.keys(dashboardFolderMap).length} entries`); } catch (err) { log.debug(`No dashboard-folder mapping found or invalid: ${err}`); } // post Dashboards try { await Promise.all(dashBoards.map(async (dashBoard) => { let dashBoardPth = path.join(dashboardDir, dashBoard).replace(/\\/g, '/'); let dashBoardFile = await fsAsync.readFile(dashBoardPth); let dashBoardName = dashBoard.split('.').shift(); log.debug(`Try to Restore: ${dashBoardPth}`); const apiOptions = { baseURL: `${protocol}://${host}:${port}`, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, httpsAgent: new https.Agent({ rejectUnauthorized: signedCertificates, }), }; let dashJson = JSON.parse(dashBoardFile); const folderUid = dashboardFolderMap[dashBoardName]; if (folderUid && folderUid !== 'general') { dashJson.folderUid = folderUid; } await axios.post('/api/dashboards/db', dashJson, apiOptions) .then(result => log.debug(`dashboard restore for "${dashBoardName}" finish: ${JSON.stringify(result.data)}`)) .catch(err => log.debug(`cannot restore dashboard "${dashBoardName}": ${JSON.stringify(err.response.data)}`)); })); } catch (err) { log.debug(`Grafana dashboard restore not possible: ${err}`); } // request finish resolve(); } else { log.debug('Grafana is not available!'); // request finish resolve(); } }); } function restore(options, fileName, log, adapter, callback) { if ((options && options.host && options.port && options.apiKey && options.protocol) || (options && options.grafana && options.grafana.host && options.grafana.port && options.grafana.apiKey && options.grafana.protocol) ) { log.debug('Start Grafana Restore ...'); const tmpDir = path.join(options.backupDir, 'grafana_tmp').replace(/\\/g, '/'); log.debug(`filename for restore: ${fileName}`); if (fs.existsSync(tmpDir)) { try { fse.removeSync(tmpDir); if (!fs.existsSync(tmpDir)) { log.debug('old Grafana tmp directory was successfully deleted'); } } catch (e) { log.debug('old Grafana tmp directory cannot deleted'); } } const desiredMode = '0o2775'; try { fse.ensureDirSync(tmpDir, desiredMode); log.debug(`Grafana tmp directory created: ${tmpDir}`) } catch (e) { log.debug(`Grafana tmp directory cannot created: ${e}`); } try { log.debug('start decompress'); const decompress = require('../targz').decompress; waitRestore = setTimeout(() => decompress({ src: fileName, dest: tmpDir, }, async (err, stdout, stderr) => { if (err) { log.error('Grafana restore not completed'); log.error(err); if (callback) { callback(err, stderr); callback = null; clearTimeout(timerDone); clearTimeout(waitRestore); } } else { if (callback) { try { log.debug('Grafana request started'); await postData(options, log, tmpDir); log.debug('Grafana request ended'); log.debug('Try deleting the Grafana tmp directory'); fse.removeSync(tmpDir); if (!fs.existsSync(tmpDir)) { log.debug('Grafana tmp directory was successfully deleted'); } } catch (err) { callback && callback(err); callback = null; clearTimeout(timerDone); clearTimeout(waitRestore); } timerDone = setTimeout(() => { log.debug('Grafana Restore completed successfully'); callback && callback(null, 'Grafana restore done'); callback = null; clearTimeout(timerDone); clearTimeout(waitRestore); }, 2000); } } }), 1000); } catch (e) { if (callback) { callback(e); callback = null; clearTimeout(timerDone); clearTimeout(waitRestore); } } } else { log.debug('Grafana restore not completed. Please check your Configuration'); callback && callback(null, 'Grafana restore not completed. Please check your Configuration'); } } module.exports = { restore, isStop: false, };