var EventEmitter = require('events').EventEmitter; var _ = require('underscore'); var path = require('path'); var fs = require('fs'); var Promise = require('bluebird'); var machine = require('./DockerMachine'); var virtualBox = require('./VirtualBox'); var setupUtil = require('./SetupUtil'); var util = require('./Util'); var assign = require('object-assign'); var metrics = require('./Metrics'); var bugsnag = require('bugsnag-js'); var rimraf = require('rimraf'); var _currentStep = null; var _error = null; var _cancelled = false; var _retryPromise = null; var _requiredSteps = []; var _steps = [{ name: 'download', title: 'Downloading VirtualBox', message: 'VirtualBox is being downloaded. Kitematic requires VirtualBox to run containers.', totalPercent: 35, percent: 0, run: function (progressCallback) { var packagejson = util.packagejson(); return setupUtil.download(setupUtil.virtualBoxUrl(), path.join(util.supportDir(), packagejson['virtualbox-filename']), packagejson['virtualbox-checksum'], percent => { progressCallback(percent); }); } }, { name: 'install', title: 'Installing VirtualBox & Docker', message: 'VirtualBox & Docker are being installed or upgraded in the background. We may need you to type in your password to continue.', totalPercent: 5, percent: 0, seconds: 5, run: Promise.coroutine(function* (progressCallback) { var cmd = setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd(); if (!virtualBox.installed()) { yield virtualBox.killall(); cmd += ' && ' + setupUtil.installVirtualBoxCmd(); } else { if (!setupUtil.needsBinaryFix()) { return; } } try { progressCallback(50); // TODO: detect when the installation has started so we can simulate progress yield util.exec(setupUtil.macSudoCmd(cmd)); } catch (err) { throw null; } }) }, { name: 'init', title: 'Starting Docker VM', message: 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...', totalPercent: 60, percent: 0, seconds: 53, run: Promise.coroutine(function* (progressCallback) { setupUtil.simulateProgress(this.seconds, progressCallback); yield virtualBox.vmdestroy('kitematic-vm'); var exists = yield machine.exists(); if (!exists || (yield machine.state()) === 'Error') { try { yield machine.rm(); yield machine.create(); } catch (err) { rimraf.sync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name())); yield machine.create(); } return; } else if (exists && (yield machine.state()) === 'Saved') { yield virtualBox.wake(machine.name()); } var isoversion = machine.isoversion(); var packagejson = util.packagejson(); if (!isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) { yield machine.stop(); yield machine.upgrade(); } yield machine.start(); }) }]; var SetupStore = assign(Object.create(EventEmitter.prototype), { PROGRESS_EVENT: 'setup_progress', STEP_EVENT: 'setup_step', ERROR_EVENT: 'setup_error', step: function () { return _currentStep; }, steps: function () { return _.indexBy(_steps, 'name'); }, stepCount: function () { return _requiredSteps.length; }, number: function () { return _.indexOf(_requiredSteps, _currentStep) + 1; }, percent: function () { var sofar = 0; var totalPercent = _requiredSteps.reduce((prev, step) => prev + step.totalPercent, 0); _.each(_requiredSteps, step => { sofar += step.totalPercent * step.percent / 100; }); return Math.min(Math.round(100 * sofar / totalPercent), 99); }, error: function () { return _error; }, cancelled: function () { return _cancelled; }, retry: function () { _error = null; _cancelled = false; if (_retryPromise) { _retryPromise.resolve(); } }, pause: function () { _retryPromise = Promise.defer(); return _retryPromise.promise; }, requiredSteps: Promise.coroutine(function* () { if (_requiredSteps.length) { return Promise.resolve(_requiredSteps); } var packagejson = util.packagejson(); var isoversion = machine.isoversion(); var required = {}; var vboxfile = path.join(util.supportDir(), packagejson['virtualbox-filename']); var vboxNeedsInstall = !virtualBox.installed(); required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== packagejson['virtualbox-checksum']); required.install = vboxNeedsInstall || setupUtil.needsBinaryFix(); required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0; var exists = yield machine.exists(); if (isoversion && setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) { this.steps().init.seconds = 33; } else if (exists && (yield machine.state()) === 'Saved') { this.steps().init.seconds = 8; } else if (exists && (yield machine.state()) !== 'Error') { this.steps().init.seconds = 23; } _requiredSteps = _steps.filter(function (step) { return required[step.name]; }); return Promise.resolve(_requiredSteps); }), updateBinaries: function () { if (setupUtil.needsBinaryFix()) { return Promise.resolve(); } if (setupUtil.shouldUpdateBinaries()) { return util.exec(setupUtil.copyBinariesCmd()); } return Promise.resolve(); }, run: Promise.coroutine(function* () { metrics.track('Started Setup', { virtualbox: virtualBox.installed() ? yield virtualBox.version() : 'Not Installed' }); yield this.updateBinaries(); var steps = yield this.requiredSteps(); for (let step of steps) { console.log(step.name); _currentStep = step; step.percent = 0; while (true) { try { this.emit(this.STEP_EVENT); yield step.run(percent => { if (_currentStep) { step.percent = percent; this.emit(this.PROGRESS_EVENT); } }); metrics.track('Setup Completed Step', { name: step.name }); step.percent = 100; break; } catch (err) { if (err) { throw err; } else { metrics.track('Setup Cancelled'); _cancelled = true; this.emit(this.STEP_EVENT); } yield this.pause(); } } } _currentStep = null; return yield machine.ip(); }), setup: Promise.coroutine(function * () { while (true) { try { var ip = yield this.run(); if (!ip || !ip.length) { throw { message: 'Machine IP could not be fetched. Please retry the setup. If this fails please file a ticket on our GitHub repo.', machine: yield machine.info(), ip: ip, }; } else { metrics.track('Setup Finished'); return ip; } } catch (err) { metrics.track('Setup Failed', { step: _currentStep }); var virtualboxVersion = virtualBox.installed() ? yield virtualBox.version() : 'Not installed'; bugsnag.notify('SetupError', 'Setup failed', { error: err, step: _currentStep, virtualbox: virtualboxVersion }); _error = err; this.emit(this.ERROR_EVENT); yield this.pause(); } } }) }); module.exports = SetupStore;