domotz-remote-pawn
Version:
Domotz Agent
740 lines (658 loc) • 29.3 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2017 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
*/
var modPath = require('path');
var modUtil = require('util');
var modFs = require('fs');
var modHttp = require('http');
var modHttps = require('https');
var modChildProcess = require('child_process');
var modRequest = require('request');
const MODULE_NAME = 'AGENT_UPDATER';
var enableDebug = process.env.DLEVEL || 'info';
console.debug = function() {
console.log.apply(console, arguments);
}
if (process.version.charAt(1) === '0') {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
}
var logger = {
log: function(level, message) {
if (message === undefined) {
return;
}
var timestamp = new Date().toISOString();
var formattedLevel = level.charAt(0).toUpperCase(); // Manually capitalize
var logPrefix = MODULE_NAME + ' [' + formattedLevel + ' ' + timestamp + '] ';
// Use util.format to handle %s replacement for arguments
var params = Array.prototype.slice.call(arguments, 2)[0];
// Build the formatted message using util.format if parameters are present
var formattedMessage = logPrefix;
if (params.length > 0)
formattedMessage += modUtil.format.apply(modUtil, [message].concat(params));
else
formattedMessage += message;
// Use apply to pass the formatted message and the remaining arguments
console[level].apply(console, [formattedMessage]);
},
info: function(message) {
this.log('info', message, Array.prototype.slice.call(arguments, 1));
},
error: function(message) {
this.log('error', message, Array.prototype.slice.call(arguments, 1));
},
debug: function(message) {
if (enableDebug === 'debug') {
this.log('debug', message, Array.prototype.slice.call(arguments, 1));
}
},
warn: function(message) {
this.log('warn', message, Array.prototype.slice.call(arguments, 1));
}
};
var utils = {
// Derived from : https://stackoverflow.com/questions/18052762/remove-directory-which-is-not-empty
recursiveRemoveFolder: function(pathname) {
logger.debug('recursiveRemoveFolder - called with path %s', pathname);
if (modFs.existsSync(pathname) && pathname !== '/') {
modFs.readdirSync(pathname).forEach(function(file) {
var currentPath = modPath.join(pathname, file);
if (modFs.lstatSync(currentPath).isDirectory()) {
utils.recursiveRemoveFolder(currentPath);
} else {
modFs.unlinkSync(currentPath);
}
});
modFs.rmdirSync(pathname);
}
},
getCloudHostname: function() {
try {
return process.env.PORTAL_API_ENDPOINT
.replace("/portal-api/v1/", "")
.replace("https://", "");
}
catch (e) {
return 'portal.domotz.com';
}
},
getAgentPid: function() {
if (process.env.LISTENER_PID_FILE === undefined) {
logger.warn("Missing LISTENER_PID_FILE variable");
return null;
}
var pidStr;
try {
pidStr = modFs.readFileSync(process.env.LISTENER_PID_FILE, 'utf-8');
} catch (e) {
logger.warn('Listener PID file is missing in %s', process.env.LISTENER_PID_FILE);
return null;
}
if (pidStr === '' || pidStr === undefined) {
logger.warn('Listener PID is missing in %s', process.env.LISTENER_PID_FILE);
return null;
}
return parseInt(pidStr);
},
getAgentPort: function() {
try {
return modFs.readFileSync(process.env.DOMOTZ_LISTENER_PORT_FILE, 'utf-8');
} catch (e) {
logger.error('Missing %s file, unable to get agent port, using default port (3000)', process.env.DOMOTZ_LISTENER_PORT_FILE);
return 3000;
}
},
tryobj: function(objVar, keysArr) {
if (objVar === null || objVar === undefined) {
return null;
}
var lastObj = objVar;
keysArr.forEach(function(key) {
if (lastObj[key] === null || lastObj[key] === undefined) {
return;
}
lastObj = lastObj[key];
});
return lastObj;
},
skip_fn: function() {
},
};
// Timers
const BASE_WARMUP_PERIOD = 5 * 1000; // 5 seconds
const ONE_HOUR_IN_MSEC = 60 * 60 * 1000; // 1 hour
const UPDATE_CHECK_PERIOD = 11 * 60 * 60 * 1000; // 11 hours
const MAX_PROCESS_AGE = 48 * 60 * 60 * 1000; // 2 days
const AGENT_RESTART_DELAY = 30 * 1000;
const AGENT_STATUS_DELAY = 10 * 1000;
const MAX_AGENT_STATUS_RETRY = 6;
// Hostnames and endpoints
const UPDATE_INFO_HOSTNAME = utils.getCloudHostname();
const UPDATE_INFO_PATH = '/assets/domotz-npm-agent.json';
const UPDATE_AGENT_START_PATH = '/domotz-agent-update/start';
const UPDATE_AGENT_END_PATH = '/domotz-agent-update/end';
// Local running agent
const AGENT_HOSTNAME = 'http://127.0.0.1';
const AGENT_PORT = utils.getAgentPort();
const AGENT_STATUS_PATH = '/api/v1/status';
const AGENT_STATUS_URL = AGENT_HOSTNAME + ':' + AGENT_PORT + AGENT_STATUS_PATH;
// Node + npm
const NODE_COMMAND = modPath.join(process.env.DOMOTZ_BIN_DIR, 'domotz_node');
const NPM_CACHE_DIR = process.env.DOMOTZ_CACHE_DIR || process.env.DOMOTZ_ROOT_DIR;
const NPM_BASE_COMMAND = [
modPath.join(process.env.DOMOTZ_BIN_DIR, 'domotz_npm'),
'--cache',
modPath.join(NPM_CACHE_DIR, '.npm'),
'--userconfig',
modPath.join(NPM_CACHE_DIR, '.npmrc'),
'--unsafe-perm',
];
('use strict');
var pkgJson = require(modPath.join(process.env.DOMOTZ_LIB_DIR, 'node_modules', 'domotz-remote-pawn', 'package.json'));
var envCopy = Object.create(process.env);
envCopy.HOME = '/tmp';
var spawnOptions = { stdio: 'ignore', shell: false, env: envCopy };
var updaterStatus = {
error: false,
update_in_progress: false,
modifyCurrentStatus: function(inProgress, error) {
this.update_in_progress = inProgress;
this.error = error;
},
/**
* Starts the update process if it is not already in progress.
*
* @function startProgress
* @returns {boolean}
* Returns `true` if the update process was started, `false` if it was already in progress.
*
* @example
* if (updaterStatus.startProgress()) {
* logger.debug('Update process started.');
* } else {
* logger.debug('Update process already in progress.');
* }
*/
startProgress: function() {
if (this.update_in_progress === true) {
return false;
}
this.modifyCurrentStatus(true, false);
return true;
},
};
/**
* Useful data structure to carry data around
* @typedef {Object} UpgradeParams
* @property {string} registry - The registry URL for the package.
* @property {string} name - The name of the package to upgrade.
* @property {string} from_version - The current version of the package.
* @property {string} to_version - The target version to upgrade to.
*/
/**
* Payload structure to send data to the cloud (Revamper) to notify the start and end of the agent update
* @typedef {Object} UpdateAgentPayload
* @property {string} [mac_address] - The MAC address of the device.
* @property {string} from_version - The current version of the package.
* @property {string} to_version - The target version to upgrade to.
* @property {string} update_type - The type of update, e.g., 'NPM'.
* @property {string} platform - The platform identifier (e.g., 'linux', 'windows').
* @property {string} architecture - The architecture type (e.g., 'x64', 'arm').
* @property {boolean} skip_cloud_notification - If true, do not notify cloud, used in case the agent is not responding to the /status.
*/
var updater = {
updaterStartTimestamp: null,
sendStartUpdateEvent: function(payload, onStartUpdateEvent) {
updater.sendUpdateEvent(UPDATE_AGENT_START_PATH, payload, onStartUpdateEvent);
},
sendEndUpdateEvent: function(payload, onEndUpdateEvent) {
updater.sendUpdateEvent(UPDATE_AGENT_END_PATH, payload, onEndUpdateEvent);
},
sendUpdateEvent: function(endpoint, updateAgentPayload, callback) {
logger.info('sendUpdateEvent - Start calling %s with payload %s',
endpoint,
JSON.stringify(updateAgentPayload)
);
const options = {
url: 'https://' + UPDATE_INFO_HOSTNAME + endpoint,
body: updateAgentPayload,
json: true,
};
modRequest.post(options, function(error, response, body) {
if (error) {
logger.error('Error:', error.message);
} else {
logger.info('sendUpdateEvent - Response: %s Body %s',
JSON.stringify(response),
JSON.stringify(body)
);
}
callback();
});
},
/**
* After calling the /status build the UpdateAgentPayload to call the /start
* @param {UpgradeParams} upgradeParams
* @param error
* @param response
* @param body
*/
callbackAgentStatus: function(upgradeParams, error, response, body) {
var updateAgentPayload = {
mac_address: null,
from_version: upgradeParams.from_version,
to_version: upgradeParams.to_version,
update_type: 'NPM',
platform: process.env.DPLATFORM,
architecture: process.env.DARCHITECTURE,
skip_cloud_notification: true
};
if (error) {
logger.error('callbackAgentStatus - Error fetching %s, continue without notify cloud', error.message);
} else if (response.statusCode !== 200) {
logger.warn('callbackAgentStatus - Unexpected status code: %s, continue without notify cloud', response.statusCode);
} else {
logger.info('callbackAgentStatus - Success! Building StartUpdateEvent payload');
try {
// Parse the /status response to extract the MAC address
const defaultInterface = body.default_interface;
// Construct the payload
updateAgentPayload.mac_address = body.interfaces_list[defaultInterface].mac;
updateAgentPayload.skip_cloud_notification = false;
} catch (parseError) {
logger.error('Error parsing /status data ending update process:', parseError.message);
}
}
if (updateAgentPayload.skip_cloud_notification) {
updater.upgradePawn(upgradeParams, updateAgentPayload);
} else {
return updater.sendStartUpdateEvent(
updateAgentPayload,
updater.upgradePawn.bind(this, upgradeParams, updateAgentPayload)
);
}
},
/**
*
* @param {function} callback what to do after the /status is called
*/
callAgentStatus: function(callback) {
const options = {
url: AGENT_STATUS_URL,
json: true
};
logger.info('callAgentStatus - Start calling %s', options.url);
modRequest.get(options, callback);
},
/**
* Call the agent status until get a 200 with status ok, there are a max number of retries (MAX_AGENT_STATUS_RETRY).
* @param {UpgradeParams} upgradeParams
* @param {UpdateAgentPayload} updateAgentPayload
*/
waitUntilAgentIsUp: function(upgradeParams, updateAgentPayload) {
logger.info('waitUntilAgentIsUp - Start polling agent on /status');
var retries = 0;
const checkAgentStartup = function() {
logger.info('waitUntilAgentIsUp - Call %s of max %s', retries, MAX_AGENT_STATUS_RETRY);
updater.callAgentStatus(function(error, response, json) {
retries++;
if (error) {
logger.debug('Retrying due to error:', error.message);
} else if (response.statusCode !== 200) {
logger.error('waitUntilAgentIsUp - Unexpected status code: %s', response.statusCode);
} else if (json) {
logger.debug('waitUntilAgentIsUp - JSON: %s', JSON.stringify(json, null, 2));
if (json.status && json.package && json.package.agent_version) {
if (json.status === 'ok' && json.package.agent_version === upgradeParams.to_version) {
logger.info('waitUntilAgentIsUp - Success! Response: json.status %s json.package.agent_version %s',
json.status,
json.package.agent_version
);
// Inform the cloud that update is ended well, then the update is ended.
updater.sendEndUpdateEvent(updateAgentPayload, utils.skip_fn);
return;
} else {
logger.error('waitUntilAgentIsUp - /status returned wrong values status: %s, version: %s. Version should be %s',
json.status,
json.package.agent_version,
upgradeParams.to_version
);
return;
}
} else {
logger.warn('waitUntilAgentIsUp - Missing fields in JSON response');
}
}
if (retries < MAX_AGENT_STATUS_RETRY) {
logger.debug('waitUntilAgentIsUp - Will recall /status in %s', AGENT_STATUS_DELAY);
setTimeout(checkAgentStartup, AGENT_STATUS_DELAY);
} else {
logger.error('waitUntilAgentIsUp - Exceeded maximus retries on /status');
}
});
};
checkAgentStartup();
},
/**
* @param {string} tmpInstallationFolder
* @param {string} oldPawnFolder
*/
prepareFileSystemForInstallation: function(tmpInstallationFolder, oldPawnFolder) {
logger.info('Cleaning up old temporary folders...');
utils.recursiveRemoveFolder(tmpInstallationFolder);
utils.recursiveRemoveFolder(oldPawnFolder);
logger.info('Creating new installation folders (%s)', tmpInstallationFolder);
modFs.mkdirSync(tmpInstallationFolder);
},
/**
* @param {string} stablePawnFolder
* @param {string} oldPawnFolder
* @param {string} tmpPawnFolder
* @param {string} tmpInstallationFolder
* @param {Function} [afterCleanUpCallback] Function to call after cleanup
*/
postAgentInstallProcedure: function(
stablePawnFolder,
oldPawnFolder,
tmpPawnFolder,
tmpInstallationFolder,
afterCleanUpCallback
) {
try {
if (process.env.DPLATFORM === 'docker') {
modChildProcess.spawnSync('cp', ['-R', stablePawnFolder, oldPawnFolder], spawnOptions);
modChildProcess.spawnSync('rm', ['-rf', stablePawnFolder], spawnOptions);
modChildProcess.spawnSync('cp', ['-R', tmpPawnFolder, stablePawnFolder], spawnOptions);
modChildProcess.spawnSync('rm', ['-rf', tmpPawnFolder], spawnOptions);
} else {
modFs.renameSync(stablePawnFolder, oldPawnFolder);
modFs.renameSync(tmpPawnFolder, stablePawnFolder);
}
logger.info('Remote pawn updated');
updaterStatus.modifyCurrentStatus(false, false);
utils.recursiveRemoveFolder(tmpInstallationFolder);
if (afterCleanUpCallback) {
logger.info('postAgentInstallProcedure - In %s will start the polling', AGENT_RESTART_DELAY);
setTimeout(afterCleanUpCallback, AGENT_RESTART_DELAY);
} else {
logger.info('postAgentInstallProcedure - Agent update ended successfully');
}
} catch (e) {
logger.error('postAgentInstallProcedure - Error on post-upgrade procedure (%s)', JSON.stringify(e));
updaterStatus.modifyCurrentStatus(false, true);
} finally {
// Clean the npm cache and Force the agent restart
utils.recursiveRemoveFolder(modPath.join(NPM_CACHE_DIR, '.npm'));
var agentPid = utils.getAgentPid();
if (agentPid !== null) {
logger.info('killing listener (pid=%d)', agentPid);
try {
process.kill(agentPid, 'SIGTERM');
} catch (e) {
logger.error('error killing listener (pid=%d)', agentPid);
}
}
else {
logger.warn('Skipping kill domotz_listener process, pid not found');
}
}
},
onNpmInstallExit: function(updateAgentPayload, stablePawnFolder, oldPawnFolder, tmpPawnFolder, tmpInstallationFolder, upgradeParams) {
return function(code) {
var resultOk = code === 0;
logger.info('Post install procedure (result=%s)', resultOk);
if (resultOk !== true) {
logger.error('ERROR: remote pawn update failed');
updaterStatus.modifyCurrentStatus(false, true);
process.exit(1); // If the updater process is simulated, we can only exit at this point
}
if (updateAgentPayload.skip_cloud_notification) {
updater.postAgentInstallProcedure(
stablePawnFolder,
oldPawnFolder,
tmpPawnFolder,
tmpInstallationFolder
);
} else {
updater.postAgentInstallProcedure(
stablePawnFolder,
oldPawnFolder,
tmpPawnFolder,
tmpInstallationFolder,
updater.waitUntilAgentIsUp.bind(this, upgradeParams, updateAgentPayload)
);
}
};
},
/**
* The actual upgrade process for upgrading the agent.
*
* @function upgradePawn
* @param {UpgradeParams} upgradeParams - Parameters related to the upgrade process.
* @param {UpdateAgentPayload} updateAgentPayload - The payload to send to the end update to the cloud.
* @returns {void}
*/
upgradePawn: function(upgradeParams, updateAgentPayload) {
var name = upgradeParams.name;
var version = upgradeParams.to_version;
var tmpInstallationFolder = modPath.join(process.env.DOMOTZ_LIB_DIR, 'tmp_inst');
var tmpPawnFolder = modPath.join(tmpInstallationFolder, 'node_modules', 'domotz-remote-pawn');
var stablePawnFolder = modPath.join(process.env.DOMOTZ_LIB_DIR, 'node_modules', 'domotz-remote-pawn');
var oldPawnFolder = stablePawnFolder + '_old';
updater.prepareFileSystemForInstallation(tmpInstallationFolder, oldPawnFolder);
var npmCommand = NPM_BASE_COMMAND.concat(['--legacy-bundle', 'install', name + '@' + version, '--prefix', tmpInstallationFolder]);
logger.info('upgradePawn - Invoking npm installer: %s', npmCommand.join(' '));
var npmInstaller = modChildProcess.spawn(NODE_COMMAND, npmCommand, spawnOptions);
npmInstaller.on('error', function(err) {
logger.error('upgradePawn - Error on npm install (%s). Cleaning up the npm cache.', JSON.stringify(err));
utils.recursiveRemoveFolder(modPath.join(NPM_CACHE_DIR, '.npm'));
updaterStatus.modifyCurrentStatus(false, true);
});
npmInstaller.on('exit',
this.onNpmInstallExit(
updateAgentPayload,
stablePawnFolder,
oldPawnFolder,
tmpPawnFolder,
tmpInstallationFolder,
upgradeParams
)
);
},
/**
* @param {Buffer} updateInfo should represent a JSON string with 'registry' 'name' and 'version' keys
* @param {Boolean} forceVersion If skip any version control and do the update
* (used when the agent wants to update)
*/
handleJsonUpdateInfo: function(updateInfo, forceVersion) {
var parsedUpdateInfo = JSON.parse(updateInfo.toString('utf8'));
logger.debug('handleJsonUpdateInfo - Input JSON: %s', JSON.stringify(parsedUpdateInfo));
if (Object.keys(parsedUpdateInfo).length < 3) {
logger.error('handleJsonUpdateInfo - Not enough keys, should have "registry", "name" and "version"');
updaterStatus.modifyCurrentStatus(false, false);
return;
}
var dPlatform = process.env.DPLATFORM;
var minReqRegistry = utils.tryobj(parsedUpdateInfo, [dPlatform, 'registry']) || utils.tryobj(parsedUpdateInfo, ['registry']);
var minReqName = utils.tryobj(parsedUpdateInfo, [dPlatform, 'name']) || utils.tryobj(parsedUpdateInfo, ['name']);
var minReqVersion = utils.tryobj(parsedUpdateInfo, [dPlatform, 'version']) || utils.tryobj(parsedUpdateInfo, ['version']);
logger.info('handleJsonUpdateInfo - Current version %s future version %s', pkgJson.version, minReqVersion);
var upgradeParams = {
registry: minReqRegistry,
name: minReqName,
from_version: pkgJson.version,
to_version: minReqVersion,
};
if (pkgJson.version !== minReqVersion || forceVersion === true) {
logger.info('handleJsonUpdateInfo - Start Agent Update process. from %s. Name: %s, Version: %s -> %s',
upgradeParams.registry,
upgradeParams.name,
upgradeParams.from_version,
upgradeParams.to_version
);
updater.callAgentStatus(updater.callbackAgentStatus.bind(this, upgradeParams));
return;
} else {
logger.info('handleJsonUpdateInfo - Skip update');
}
updaterStatus.modifyCurrentStatus(false, false);
},
getUpdateInfoViaHttps: function() {
var requestOptions = {
hostname: UPDATE_INFO_HOSTNAME,
path:
UPDATE_INFO_PATH +
'?agent=' +
encodeURIComponent(process.env.DPLATFORM + '.' + process.env.DARCHITECTURE + '.' + process.env.DVERSION + '.js.' + pkgJson.version),
headers: { 'Content-Type': 'application/json' },
};
logger.info('getUpdateInfoViaHttps - hostname %s - path %s',
requestOptions.hostname,
requestOptions.path
);
var handleRawHttpBody = function(res) {
var body = new Buffer(0);
res.on('data', function(chunk) {
body = Buffer.concat([body, chunk]);
});
res.on('end', function() {
if (!this.complete || res.statusCode !== 200) {
logger.error('handleRawHttpBody - HTTP Error (complete=%s, statusCode=%d)', this.complete, res.statusCode);
updaterStatus.modifyCurrentStatus(false, true);
return;
}
try {
logger.info('handleRawHttpBody - Handling response from server (len=%d, timestamp=%s)',
body.length,
new Date().toISOString()
);
updater.handleJsonUpdateInfo(body, false);
} catch (e) {
logger.error('handleRawHttpBody - Error on callback : %s', e.message);
updaterStatus.modifyCurrentStatus(false, true);
}
});
};
modHttps.get(requestOptions, handleRawHttpBody).on('error', function(err) {
logger.error('Error: ' + err.message);
updaterStatus.modifyCurrentStatus(false, true);
});
},
taskScheduler: function() {
logger.debug('taskScheduler - Scheduler: checking for tasks to execute ...');
if (updaterStatus.startProgress() === true) {
// If the updater is up and running for more than MAX_PROCESS_AGE (2 days)
if (new Date().getTime() - this.updaterStartTimestamp.getTime() > MAX_PROCESS_AGE) {
logger.info('Graceful shutdown');
process.exit(0);
}
logger.debug('taskScheduler - Scheduler: running getUpdateInfoViaHttps task');
setImmediate(updater.getUpdateInfoViaHttps);
}
var nextRun = UPDATE_CHECK_PERIOD + Math.round(ONE_HOUR_IN_MSEC * Math.random());
logger.info('taskScheduler - Scheduler: next run in %s seconds (%s)',
nextRun / 1000, new Date(Date.now() + nextRun)
);
setTimeout(updater.taskScheduler.bind(this), nextRun);
},
createHttpService: function (unixSocket) {
var requestHandler = function(httpRequest, response) {
var body = new Buffer(0);
response.on('error', function(err) {
logger.info('Error sending response :%s', err);
});
if (httpRequest.method.toUpperCase() !== 'POST') {
response.statusCode = 405; // METHOD NOT ALLOWED
response.end();
return;
}
httpRequest.on('error', function(err) {
logger.info('Error receiving request :%s', err);
});
httpRequest.on('data', function(chunk) {
body = Buffer.concat([body, chunk]);
});
httpRequest.on('end', function() {
logger.info('Received request (len=%d, busy=%s)',
body.length,
updaterStatus.update_in_progress
);
if (updaterStatus.startProgress() === true) {
var helperFn = function() {
try {
updater.handleJsonUpdateInfo(body, true);
} catch (e) {
logger.info('Error serving request : %s', JSON.stringify(e));
updaterStatus.modifyCurrentStatus(false, true);
}
};
setTimeout(helperFn, 1000);
}
response.statusCode = 202; // ACCEPTED
response.end();
});
};
try {
modFs.unlinkSync(unixSocket);
} catch (e) {
logger.info('Old Unix socket not found %s', unixSocket);
}
modHttp
.createServer(requestHandler)
.listen(unixSocket, function() {
logger.info('Listening on unix socket %s', unixSocket);
})
.on('error', function(err) {
logger.error('Unable to listen on unix socket %s', err);
});
},
start: function () {
logger.info('Initializing the updater process ...');
logger.debug('Current Node.js version: %s', process.version);
logger.debug('Agent pid %s', utils.getAgentPid());
this.updaterStartTimestamp = new Date();
if (!process.env.DOMOTZ_UPDATER_UNIX_SOCKET) {
setTimeout(function() {
logger.error('Exit: wrong environment variables');
}, 30000);
return;
}
var warmup = BASE_WARMUP_PERIOD + Math.round(4 * BASE_WARMUP_PERIOD * Math.random());
logger.info('Starting the scheduler in %d seconds', warmup / 1000);
// this bind is required to use updaterStartTimestamp
setTimeout(updater.taskScheduler.bind(this), warmup);
logger.info('Starting the http service');
updater.createHttpService(process.env.DOMOTZ_UPDATER_UNIX_SOCKET);
},
};
if (require.main === module) {
updater.start();
} else {
if (global.jasmine) {
module.exports.mock_mod_fs = modFs;
module.exports.mock_mod_http = modHttp;
module.exports.mock_mod_https = modHttps;
module.exports.mock_mod_child_process = modChildProcess;
module.exports.mock_mod_request = modRequest;
module.exports.utils = utils;
module.exports.updater = updater;
module.exports.updaterStatus = updaterStatus;
}
module.exports.start = updater.start;
module.exports.createHttpService = updater.createHttpService;
}