diff --git a/index.js b/index.js
index 3a3d3a4989..ba3a23f38f 100644
--- a/index.js
+++ b/index.js
@@ -43,10 +43,7 @@ var start = function (callback) {
freeport(function(err, mongoPort) {
console.log('MongoDB: ' + mongoPort);
console.log('webPort: ' + webPort);
- child_process.exec('kill $(ps aux -e | grep PURPOSE=KITEMATIC | awk \'{print $2}\')', function (error, stdout, stderr) {
- console.log(error);
- console.log(stdout);
- console.log(stderr);
+ child_process.exec('kill $(ps aux -e | grep PURPOSE=KITEMATIC | awk \'{print $2}\') && rm ' + path.join(dataPath, 'mongod.lock'), function (error, stdout, stderr) {
var mongoChild = child_process.spawn(path.join(process.cwd(), 'resources', 'mongod'), ['--bind_ip', '127.0.0.1', '--dbpath', dataPath, '--port', mongoPort, '--unixSocketPrefix', dataPath], {
env: {
PURPOSE: 'KITEMATIC'
@@ -54,7 +51,12 @@ var start = function (callback) {
});
var started = false;
mongoChild.stdout.setEncoding('utf8');
+ mongoChild.stderr.setEncoding('utf8');
+ mongoChild.stderr.on('data', function (data) {
+ console.log(data);
+ });
mongoChild.stdout.on('data', function (data) {
+ console.log(data);
if (data.indexOf('waiting for connections on port ' + mongoPort)) {
if (!started) {
started = true;
diff --git a/meteor/.jshintrc b/meteor/.jshintrc
index e2c8f682e4..ab5b799ac1 100755
--- a/meteor/.jshintrc
+++ b/meteor/.jshintrc
@@ -128,7 +128,7 @@
// Packages
"Fiber": true,
"moment": true,
- "Docker": true,
+ "Dockerode": true,
"byline": true,
"fs": true,
"zlib": true,
@@ -142,6 +142,9 @@
"chokidar": true,
"docker": true,
"async": true,
+ "child_process": true,
+ "convert": true,
+ "Convert": true,
// Collections
"SimpleSchema": false,
@@ -161,8 +164,13 @@
"SetupController": true,
// Server and Client
+ "Docker": true,
+ "Util": true,
+ "Boot2Docker": true,
+ "Installer": true,
+ "VirtualBox": true,
+
"boot2dockerexec": true,
- "getBinDir": true,
"getBoot2DockerIp": true,
"getBoot2DockerState": true,
"getBoot2DockerDiskUsage": true,
@@ -170,7 +178,6 @@
"getBoot2DockerInfo": true,
"boot2DockerVMExists": true,
"eraseBoot2DockerVMFiles": true,
- "getHomePath": true,
"initBoot2Docker": true,
"isVirtualBoxInstalled": true,
"upgradeBoot2Docker": true,
@@ -191,46 +198,17 @@
"fixInterval": true,
"stopFixInterval": true,
"runSetup": true,
- "removeBindFolder": true,
"removeAppWatcher": true,
"addAppWatcher": true,
"resolveWatchers": true,
- "recoverApps": true,
- "restartApp": true,
- "deleteApp": true,
- "deleteFolder": true,
- "loadKiteVolumes": true,
- "getAppLogs": true,
- "hasDockerfile": true,
- "runContainer": true,
- "runContainerSync": true,
- "restartContainer": true,
- "restartContainerSync": true,
- "createTarFile": true,
- "createTarFileSync": true,
- "getImageData": true,
- "getImageDataSync": true,
- "removeImage": true,
- "removeImageSync": true,
- "deleteImage": true,
"checkDefaultImages": true,
"resolveDefaultImages": true,
"checkDefaultContainers": true,
"resolveDefaultContainers": true,
"killAndRemoveContainers": true,
- "deleteImageSync": true,
"upContainers": true,
"reloadDefaultContainers": true,
"removeImages": true,
- "pullImageFromDockerfile": true,
- "buildImage": true,
- "getImageMetaData": true,
- "getImageJSON": true,
- "rebuildImage": true,
- "saveImageFolderSync": true,
- "rebuildImageSync": true,
- "saveImageFolder": true,
- "copyFolder": true,
// Forms
"showFormErrors": true,
@@ -239,11 +217,6 @@
"FormSchema": true,
"showFormSuccess": true,
"resetForm": true,
- "removeContainer": true,
- "removeContainerSync": true,
- "deleteAppSync": true,
- "getContainerData": true,
- "getContainerDataSync": true,
// Testing
"require": false,
diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js
index 8e939a2dc3..589b85b67d 100644
--- a/meteor/client/lib/boot2docker.js
+++ b/meteor/client/lib/boot2docker.js
@@ -1,14 +1,54 @@
var exec = require('exec');
var path = require('path');
-boot2dockerexec = function (command, callback) {
- exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout) {
- callback(err, stdout);
+Boot2Docker = {};
+
+Boot2Docker.REQUIRED_IP = '192.168.60.103';
+
+Boot2Docker.exec = function (command, callback) {
+ exec(path.join(Util.getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) {
+ callback(err, stdout, stderr);
});
};
-getBoot2DockerIp = function (callback) {
- boot2dockerexec('ip', function (err, stdout) {
+Boot2Docker.exists = function (callback) {
+ this.exec('info', function (err) {
+ if (err) {
+ callback(null, false);
+ } else {
+ callback(null, true);
+ }
+ });
+};
+
+Boot2Docker.stop = function (callback) {
+ this.exec('stop', function (err, stdout) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null);
+ }
+ });
+};
+
+Boot2Docker.erase = function (callback) {
+ var VMFileLocation = path.join(Util.getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm');
+ exec('rm -rf ' + VMFileLocation, function (err) {
+ callback(err);
+ });
+};
+
+Boot2Docker.upgrade = function (callback) {
+ var self = this;
+ self.stop(function (err) {
+ self.exec('upgrade', function (err, stdout) {
+ callback(err);
+ });
+ });
+};
+
+Boot2Docker.ip = function (callback) {
+ this.exec('ip', function (err, stdout) {
if (err) {
callback(err, null);
} else {
@@ -17,12 +57,54 @@ getBoot2DockerIp = function (callback) {
});
};
-getBoot2DockerState = function (callback) {
- boot2dockerexec(' info', function (err, stdout) {
- if (err) {
- callback(err, null);
+Boot2Docker.setIp = function (ifname, ip, callback) {
+ this.exec('ssh "sudo ifconfig ' + ifname + ' ' + ip + ' netmask 255.255.255.0"', function (err, stdout) {
+ callback(err);
+ });
+};
+
+Boot2Docker.init = function (callback) {
+ this.exec('init', function (err) {
+ callback(err);
+ });
+};
+
+Boot2Docker.start = function (callback) {
+ var self = this;
+ self.exists(function (err, exists) {
+ if (!exists) {
+ callback('Cannot start if the boot2docker VM doesn\'t exist');
return;
}
+ self.exec('up -v', function (err, stdout) {
+ // Sometimes boot2docker returns an error code even though it's working / waiting, so treat that as
+ // Success as well
+ if (!err || (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1)) {
+ self.correct(function (err) {
+ if (err) { callback(err); return; }
+ self.injectUtilities(function (err) {
+ callback(err);
+ });
+ });
+ } else {
+ callback(err);
+ }
+ });
+ });
+};
+
+Boot2Docker.correct = function (callback) {
+ Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) {
+ if (err) { callback(err); return; }
+ VirtualBox.removeDHCP(function (err) {
+ callback();
+ });
+ });
+};
+
+Boot2Docker.state = function (callback) {
+ this.exec('info', function (err, stdout, stderr) {
+ if (err) { callback(err, null); return; }
try {
var info = JSON.parse(stdout);
callback(null, info.State);
@@ -32,8 +114,8 @@ getBoot2DockerState = function (callback) {
});
};
-getBoot2DockerDiskUsage = function (callback) {
- boot2dockerexec('ssh "df"', function (err, stdout) {
+Boot2Docker.diskUsage = function (callback) {
+ this.exec('ssh "df"', function (err, stdout) {
if (err) {
callback(err, null);
return;
@@ -61,8 +143,8 @@ getBoot2DockerDiskUsage = function (callback) {
});
};
-getBoot2DockerMemoryUsage = function (callback) {
- boot2dockerexec('ssh "free -m"', function (err, stdout) {
+Boot2Docker.memoryUsage = function (callback) {
+ this.exec('ssh "free -m"', function (err, stdout) {
if (err) {
callback(err, null);
return;
@@ -92,178 +174,105 @@ getBoot2DockerMemoryUsage = function (callback) {
});
};
-getBoot2DockerInfo = function (callback) {
- boot2dockerexec('ssh "sudo ifconfig eth1 192.168.59.103 netmask 255.255.255.0"', function (err, stdout) {
- exec('VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0', function (err, stdout) {
- getBoot2DockerState(function (err, state) {
+Boot2Docker.stats = function (callback) {
+ var self = this;
+ self.state(function (err, state) {
+ if (err) { callback(err, null); return; }
+ if (state === 'poweroff') {
+ callback(null, {state: state});
+ return;
+ }
+ self.memoryUsage(function (err, mem) {
if (err) {
- callback(err, null);
+ callback(null, {state: state});
return;
}
- if (state === 'poweroff') {
- callback(null, {state: state});
- } else {
- getBoot2DockerMemoryUsage(function (err, mem) {
- if (err) { callback(null, {state: state}); }
- getBoot2DockerDiskUsage(function (err, disk) {
- if (err) { callback(null, {state: state}); }
- callback(null, {
- state: state,
- memory: mem,
- disk: disk
- });
- });
- });
- }
- });
- });
- });
-};
-
-boot2DockerVMExists = function (callback) {
- boot2dockerexec('info', function (err) {
- if (err) {
- callback(null, false);
- } else {
- callback(null, true);
- }
- });
-};
-
-eraseBoot2DockerVMFiles = function (callback) {
- var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm');
- exec('rm -rf ' + VMFileLocation, function (err) {
- callback(err);
- });
-};
-
-initBoot2Docker = function (callback) {
- isVirtualBoxInstalled(function (err, installed) {
- if (err) {
- callback(err);
- return;
- }
- if (installed) {
- boot2dockerexec('init', function (err) {
- console.log(err);
+ self.diskUsage(function (err, disk) {
if (err) {
- if (err.indexOf('exit status 1') !== -1) {
- eraseBoot2DockerVMFiles(function () {
- boot2dockerexec('init', function (err) {
- callback(err);
- });
- });
- } else {
- callback(err);
- }
- } else {
- callback();
+ callback(null, {state: state, memory: mem});
+ return;
}
+ callback(null, {
+ state: state,
+ memory: mem,
+ disk: disk
+ });
});
+ });
+ });
+};
+
+/**
+ * Get the VM's version.
+ * Node that this only works if the VM is up and running.
+ */
+Boot2Docker.vmVersion = function (callback) {
+ this.exec('ssh "cat /etc/version', function (err, stdout, stderr) {
+ if (err) {
+ callback(err);
+ return;
} else {
- callback(new Error('initBoot2Docker called but VirtualBox isn\'t installed.'));
+ callback(null, stdout);
}
});
};
-upgradeBoot2Docker = function (callback) {
- boot2dockerexec('upgrade', function (err, stdout) {
- console.log(stdout);
+Boot2Docker.version = function (callback) {
+ this.exec('version', function (err, stdout, stderr) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ var match = stdout.match(/Client version: v(\d\.\d\.\d)/);
+ if (!match || match.length < 2) {
+ callback('Could not parse the boot2docker cli version.');
+ } else {
+ callback(null, match[1]);
+ }
+ });
+};
+
+Boot2Docker.injectUtilities = function (callback) {
+ exec('/bin/cat ' + path.join(Util.getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(Util.getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) {
callback(err);
});
};
-installBoot2DockerAddons = function (callback) {
- exec('/bin/cat ' + path.join(getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) {
- console.log(stdout);
- callback(err);
- });
- boot2dockerexec('ssh "sudo ifconfig eth1 192.168.59.103 netmask 255.255.255.0"', function (err, stdout) {});
- exec('VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0', function (err, stdout) {});
-};
-
-startBoot2Docker = function (callback) {
- isVirtualBoxInstalled(function (err, installed) {
- if (err) {
- callback(err);
- return;
- }
- if (installed) {
- boot2DockerVMExists(function (err, exists) {
- if (exists) {
- boot2dockerexec('up -v', function (err, stdout) {
- console.log(err);
- console.log(stdout);
- if (err) {
- if (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1) {
- installBoot2DockerAddons(function (err) {
- callback(err);
- });
- } else {
- callback(err);
- }
- } else {
- installBoot2DockerAddons(function (err) {
- callback(err);
- });
- }
- });
- } else {
- callback(new Error('startBoot2Docker called but boot2docker-vm doesn\'t exist.'));
- }
- });
- } else {
- callback(new Error('startBoot2Docker called but VirtualBox isn\'t installed.'));
- }
- });
-};
-
-stopBoot2Docker = function (callback) {
- boot2dockerexec('stop', function (err, stdout) {
- console.log(stdout);
- console.log(err);
- if (err) {
- callback(err);
- return;
- }
- callback(null);
- });
-};
-
-checkBoot2DockerVM = function (callback) {
- boot2DockerVMExists(function (err) {
+Boot2Docker.check = function (callback) {
+ var self = this;
+ self.exists(function (err, exists) {
if (err) {
callback(err);
return;
} else {
- getBoot2DockerState(function (err, state) {
+ self.state(function (err, state) {
if (state !== 'running') {
callback('boot2docker not running');
} else {
- callback();
+ self.correct(function (err) {
+ callback(err);
+ });
}
});
}
});
};
-// Make sure the VM exists, is up and is running.
-resolveBoot2DockerVM = function (callback) {
- boot2DockerVMExists(function (err, exists) {
-
+Boot2Docker.resolve = function (callback) {
+ var self = this;
+ self.exists(function (err, exists) {
// If somehow the boot2docker VM doesn't exist anymor then re-create it.
if (!exists) {
- initBoot2Docker(function () {
- startBoot2Docker(function (err) {
+ self.init(function () {
+ self.start(function (err) {
callback(err);
});
});
} else {
-
// If it exists but it's not running.. restart it.
- getBoot2DockerState(function (err, state) {
+ self.state(function (err, state) {
if (state !== 'running') {
- startBoot2Docker(function (err) {
+ self.start(function (err) {
callback(err);
});
} else {
diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js
new file mode 100644
index 0000000000..459ed06f7b
--- /dev/null
+++ b/meteor/client/lib/installer.js
@@ -0,0 +1,160 @@
+var async = require('async');
+
+Installer = {};
+
+Installer.CURRENT_VERSION = '0.0.2';
+
+Installer.isUpToDate = function () {
+ return !!Installs.findOne({version: Installer.CURRENT_VERSION});
+};
+
+/**
+ * Install steps. A step is a function that accepts a function (err) callback and returns once that step is complete.keys:
+ * - run: Function that runs the installation step and calls the callback with an error if failed.
+ * - pastMessage: Message to show after step completion
+ * - message: Message to show while step is running
+ * - imperativeMessage: Message to show before running
+ */
+Installer.steps = [
+ {
+ run: function (callback) {
+ var installed = VirtualBox.installed();
+ if (!installed) {
+ VirtualBox.install(function (err) {
+ callback(err);
+ });
+ } else {
+ // Version 4.3.12 is required.
+ VirtualBox.version(function (err, installedVersion) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ var needsUpdate = Util.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0;
+ if (needsUpdate) {
+ VirtualBox.install(function (err) {
+ callback(err);
+ });
+ } else {
+ callback();
+ }
+ });
+ }
+ },
+ pastMessage: 'VirtualBox installed',
+ message: 'Installing VirtualBox',
+ futureMessage: 'Install VirtualBox if necessary'
+ },
+
+ // Initialize Boot2Docker if necessary.
+ {
+ run: function (callback) {
+ Boot2Docker.exists(function (err, exists) {
+ if (err) { callback(err); return; }
+ if (!exists) {
+ Boot2Docker.init(function (err) {
+ callback(err);
+ });
+ } else {
+ Boot2Docker.stop(function (err) {
+ if (err) { callback(err); return; }
+ Boot2Docker.upgrade(function (err) {
+ callback(err);
+ });
+ });
+ }
+ });
+ },
+ pastMessage: 'Setup the Boot2Docker VM (if required)',
+ message: 'Setting up the Boot2Docker VM',
+ futureMessage: 'Set up the Boot2Docker VM(if required)'
+ },
+
+ {
+ run: function (callback) {
+ VirtualBox.addCustomHostAdapter('boot2docker-vm', function (err, ifname) {
+ callback(err);
+ });
+ },
+ pastMessage: 'Added custom host adapter to the Boot2Docker VM',
+ message: 'Adding custom host adapter to the Boot2Docker VM',
+ futureMessage: 'Add custom host adapter to the Boot2Docker VM'
+ },
+
+ // Start the Kitematic VM
+ {
+ run: function (callback) {
+ Boot2Docker.state(function (err, state) {
+ if (err) { callback(err); return; }
+ if (state !== 'running') {
+ console.log('starting');
+ Boot2Docker.start(function (err) {
+ callback(err);
+ });
+ } else {
+ Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) {
+ callback(err);
+ });
+ }
+ });
+ },
+ pastMessage: 'Started the Boot2Docker VM',
+ message: 'Starting the Boot2Docker VM',
+ futureMessage: 'Start the Kitematic VM',
+ },
+
+ {
+ run: function (callback) {
+ VirtualBox.setupRouting('boot2docker-vm', function (err, ifname) {
+ callback(err);
+ });
+ },
+ pastMessage: 'Container routing set up',
+ message: 'Setting up container routing (root required)',
+ futureMessage: 'Set up container routing to VM (root required)'
+ },
+
+ // Set up the default Kitematic images
+ {
+ run: function (callback) {
+ Meteor.call('reloadDefaultContainers', function (err) {
+ callback(err);
+ });
+ },
+ pastMessage: 'Started the Boot2Docker VM',
+ message: 'Setting up the default Kitematic images...',
+ subMessage: '(This may take a few minutes)',
+ futureMessage: 'Set up the default Kitematic images',
+ }
+];
+
+Installer.run = function (callback) {
+ var currentStep = 0;
+ Session.set('currentInstallStep', currentStep);
+ Session.set('numberOfInstallSteps', this.steps.length);
+ async.eachSeries(this.steps, function (step, callback) {
+ console.log('Performing step ' + currentStep);
+ step.run(function (err) {
+ if (err) {
+ callback(err);
+ } else {
+ currentStep += 1;
+ Session.set('currentInstallStep', currentStep);
+ callback();
+ }
+ });
+ }, function (err) {
+ if (err) {
+ // if any of the steps fail
+ console.log('Kitematic setup failed at step ' + currentStep);
+ console.log(err);
+ Session.set('failedStep', currentStep);
+ Session.set('failedError', err);
+ callback(err);
+ } else {
+ // Setup Finished
+ console.log('Setup finished.');
+ callback();
+ }
+ });
+};
diff --git a/meteor/client/lib/requires.js b/meteor/client/lib/requires.js
new file mode 100644
index 0000000000..2b261be406
--- /dev/null
+++ b/meteor/client/lib/requires.js
@@ -0,0 +1,2 @@
+path = require('path');
+fs = require('fs');
diff --git a/meteor/client/lib/router.js b/meteor/client/lib/router.js
index 0e9f2ecb6e..6e8168efd6 100755
--- a/meteor/client/lib/router.js
+++ b/meteor/client/lib/router.js
@@ -48,12 +48,16 @@ Router.map(function () {
controller: 'SetupController',
action: function () {
if (this.ready()) {
- var install = Installs.findOne();
- if (!install) {
- console.log('No installs detected, running installer again.');
- this.redirect('/setup/intro');
+ if (!Installer.isUpToDate()) {
+ if (!Installs.findOne()) {
+ console.log('No installs detected, running installer again.');
+ this.redirect('/setup/intro');
+ } else {
+ // There's an install but it's lower than the current version, re-run as an 'update'.
+ Session.set('isUpdating', true);
+ this.redirect('/setup/intro');
+ }
} else {
- startFixInterval();
this.redirect('/apps');
}
}
diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js
index 4dbbcace5d..cd2468ef2d 100644
--- a/meteor/client/lib/sync.js
+++ b/meteor/client/lib/sync.js
@@ -15,7 +15,7 @@ removeAppWatcher = function (id) {
addAppWatcher = function (app) {
removeAppWatcher(app._id);
- var appPath = path.join(path.join(getHomePath(), 'Kitematic'), app.name);
+ var appPath = path.join(path.join(Util.getHomePath(), 'Kitematic'), app.name);
var vmDir = path.join('/var/lib/docker/binds', app.name);
var vmPath = 'ssh://docker@localhost:2022/' + vmDir;
var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/});
@@ -30,7 +30,7 @@ addAppWatcher = function (app) {
syncing = true;
var errorPattern = /The\sfile\s(.*)\son\shost/g;
var archiveErrorPattern = /Archive\s(.*)\son\shost\s.*\sshould\sbe\sDELETED/g;
- var cmd = path.join(getBinDir(), 'unison');
+ var cmd = path.join(Util.getBinDir(), 'unison');
var args = [
cmd,
vmPath,
@@ -46,7 +46,7 @@ addAppWatcher = function (app) {
'Name\ {*.tmp,*.unison,*.swp,*.pyc,.DS_Store}',
'-auto',
'-sshargs',
- '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(getHomePath(), '.ssh/id_boot2docker')
+ '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(Util.getHomePath(), '.ssh/id_boot2docker')
];
if (!fs.existsSync(appPath)) {
@@ -70,12 +70,12 @@ addAppWatcher = function (app) {
if (err.indexOf('The archive file is missing on some hosts') !== -1) {
var results = archiveErrorPattern.exec(err);
var location = results[1].replace(' ', '\\ ');
- var fullLocation = path.join(getHomePath(), 'Library/Application\\ Support/Unison', location);
+ var fullLocation = path.join(Util.getHomePath(), 'Library/Application\\ Support/Unison', location);
var cmd = '/bin/rm -rf ' + fullLocation;
exec(cmd, function () {});
}
} catch (e) {
- // console.error(e);
+ //console.error(e);
}
syncing = false;
if (willSyncAgain) {
@@ -118,4 +118,4 @@ resolveWatchers = function (callback) {
});
callback();
-};
\ No newline at end of file
+};
diff --git a/meteor/client/lib/utilities.js b/meteor/client/lib/utilities.js
index c3fe268798..3315ad4040 100755
--- a/meteor/client/lib/utilities.js
+++ b/meteor/client/lib/utilities.js
@@ -1,17 +1,3 @@
-var path = require('path');
-
-getBinDir = function () {
- if (process.env.NODE_ENV === 'development') {
- return path.join(path.join(process.env.PWD, '..'), 'resources');
- } else {
- return path.join(process.cwd(), 'resources');
- }
-};
-
-getHomePath = function () {
- return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
-};
-
showFormErrors = function ($form, errors) {
for (var name in errors) {
if (errors.hasOwnProperty(name)) {
@@ -31,7 +17,7 @@ clearFormErrors = function ($form) {
$form.find('.form-group.has-error .help-block.error').remove();
$form.find('.form-group.has-error').removeClass('has-error');
};
-
+
resetForm = function ($form) {
$form.find('input').val('');
};
@@ -41,4 +27,4 @@ trackLink = function (trackLabel) {
console.log(trackLabel);
ga('send', 'event', 'link', 'click', trackLabel);
}
-};
\ No newline at end of file
+};
diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js
index 6d362492a9..4368f5c72f 100644
--- a/meteor/client/lib/virtualbox.js
+++ b/meteor/client/lib/virtualbox.js
@@ -1,55 +1,158 @@
var fs = require('fs');
-var child_process = require('child_process');
+var exec = require('exec');
var path = require('path');
-isVirtualBoxInstalled = function (callback) {
- fs.exists('/usr/bin/VBoxManage', function (exists) {
- callback(null, exists);
+VirtualBox = {};
+
+VirtualBox.REQUIRED_VERSION = '4.3.12';
+VirtualBox.INCLUDED_VERSION = '4.3.12';
+VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.12.pkg';
+
+// Info for the hostonly interface we add to the VM.
+VirtualBox.HOSTONLY_HOSTIP = '192.168.60.3';
+VirtualBox.HOSTONLY_NETWORKMASK = '255.255.255.0';
+
+VirtualBox.installed = function () {
+ return fs.existsSync('/usr/bin/VBoxManage');
+};
+
+VirtualBox.exec = function (command, callback) {
+ exec('/usr/bin/VBoxManage ' + command, function (error, stdout, stderr) {
+ callback(error, stdout, stderr);
});
};
-isResolverSetup = function (callback) {
- fs.readFile('/etc/resolver/dev', {
- encoding: 'utf8'
- }, function (err, data) {
+VirtualBox.install = function (callback) {
+ // -W waits for the process to close before finishing.
+ exec('open -W ' + path.join(Util.getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
+ if (error) {
+ callback(error);
+ return;
+ }
+ callback(null);
+ });
+};
+
+VirtualBox.version = function (callback) {
+ if (!this.installed()) {
+ callback('VirtualBox not installed.');
+ return;
+ }
+ this.exec('-v', function (err, stdout, stderr) {
if (err) {
- callback(err, false);
- } else {
- if (data.indexOf('nameserver 172.17.42.1') !== -1) {
- callback(null, true);
- } else {
- callback(null, false);
+ callback(err);
+ return;
+ }
+ // Output is x.x.xryyyyyy
+ var match = stdout.match(/(\d+\.\d+\.\d+).*/);
+ if (!match || match.length < 2) {
+ callback('VBoxManage -v output format not recognized.');
+ return;
+ }
+ callback(null, match[1]);
+ });
+};
+
+VirtualBox.hostOnlyIfs = function (callback) {
+ this.exec('list hostonlyifs', function (err, stdout, stderr) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ var lines = stdout.split('\n');
+ var hostOnlyIfs = {};
+ var currentIf = null;
+ _.each(lines, function (line) {
+ if (!line.length) {
+ return;
}
- }
+ var pieces = line.split(':');
+ var key = pieces[0].trim();
+ var value = pieces[1] ? pieces[1].trim() : null;
+ if (key === 'Name') {
+ currentIf = value;
+ hostOnlyIfs[value] = {};
+ }
+ hostOnlyIfs[currentIf][key] = value;
+ });
+ callback(null, hostOnlyIfs);
});
};
-setupResolver = function (callback) {
- var installFile = path.join(getBinDir(), 'install');
- var cocoaSudo = path.join(getBinDir(), 'cocoasudo');
- var execCommand = cocoaSudo + ' --prompt="Kitematic Setup wants to make changes. Type your password to allow this." ' + installFile;
- child_process.exec(execCommand, function (error, stdout, stderr) {
- console.log(stdout);
- if (error) {
- console.log(error);
- callback(error);
+VirtualBox.hostOnlyAdapters = function (vm, callback) {
+ this.exec('showvminfo ' + vm + ' --machinereadable', function (err, stdout, stderr) {
+ if (err) {
+ callback(err);
return;
}
- console.log('Virtualbox Installation & Resolver config complete.');
- callback();
- });
-};
-
-setupVirtualBox = function (callback) {
- child_process.exec('open -W ' + path.join(getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) {
- console.log(stdout);
- if (error) {
- console.log(error);
- callback(error);
- return;
+ var matches = stdout.match(/(hostonlyadapter\d+)="(vboxnet\d+)"/g);
+ if (!matches.length) {
+ callback(null, {});
+ } else {
+ var objs = {};
+ _.each(matches, function (match) {
+ var pieces = match.split('=');
+ objs[pieces[0]] = pieces[1].replace(/"/g, '');
+ });
+ callback(null, objs);
}
- console.log('Virtualbox Installation running.');
- callback();
});
};
+VirtualBox.hostOnlyAdapter = function (callback) {
+ var self = this;
+ self.hostOnlyIfs(function (err, ifs) {
+ var iface = _.findWhere(_.toArray(ifs), {IPAddress: VirtualBox.HOSTONLY_HOSTIP});
+ if (!iface) {
+ self.exec('hostonlyif create', function (err, stdout, stderr) {
+ var match = stdout.match(/Interface '(vboxnet\d+)' was successfully created/);
+ if (!match) {
+ callback('Could not parse output of hostonlyif create');
+ return;
+ }
+ self.exec('hostonlyif ipconfig ' + match[1] + ' --ip ' + VirtualBox.HOSTONLY_HOSTIP + ' --netmask ' + VirtualBox.HOSTONLY_NETWORKMASK, function(err, stdout, stderr) {
+ if (err) { callback(err); return; }
+ callback(null, match[1]);
+ });
+ });
+ } else {
+ callback(null, iface.Name);
+ }
+ });
+};
+
+VirtualBox.addCustomHostAdapter = function (vm, callback) {
+ var self = this;
+ self.hostOnlyAdapter(function (err, ifname) {
+ if (err) { callback(err); return; }
+ self.exec('modifyvm ' + vm + ' --nic3 hostonly --nictype3 virtio --cableconnected3 on --hostonlyadapter3 ' + ifname, function (err, stdout, stderr) {
+ callback(err, ifname);
+ });
+ });
+};
+
+VirtualBox.setupRouting = function (vm, callback) {
+ // Get the host only adapter or create it if it doesn't exist
+ this.addCustomHostAdapter(vm, function (err, ifname) {
+ var installFile = path.join(Util.getBinDir(), 'install');
+ var cocoaSudo = path.join(Util.getBinDir(), 'cocoasudo');
+ var execCommand = cocoaSudo + ' --prompt="Kitematic needs your password to allow routing *.kite requests to containers." ' + installFile;
+ exec(execCommand, {env: {IFNAME: ifname, GATEWAY: Boot2Docker.REQUIRED_IP}}, function (error, stdout, stderr) {
+ if (error) {
+ callback(error);
+ return;
+ }
+ callback();
+ });
+ });
+};
+
+VirtualBox.removeDHCP = function (callback) {
+ var self = this;
+ self.hostOnlyAdapter(function (err, ifname) {
+ if (err) { callback(err); return; }
+ self.exec('dhcpserver remove --ifname ' + ifname, function (err, stdout, stderr) {
+ callback(err);
+ });
+ });
+};
diff --git a/meteor/client/main.js b/meteor/client/main.js
index 7abe7d59a1..429201d295 100755
--- a/meteor/client/main.js
+++ b/meteor/client/main.js
@@ -68,24 +68,11 @@ Meteor.call('getDockerHost', function (err, host) {
Session.set('dockerHost', host);
});
-updateBoot2DockerInfo = function () {
- getBoot2DockerInfo(function (err, info) {
- if (err) {
- return;
- }
- Session.set('boot2dockerState', info.state);
- if (info.state !== 'poweroff' && info.memory && info.disk) {
- Session.set('boot2dockerMemoryUsage', info.memory);
- Session.set('boot2dockerDiskUsage', info.disk);
- }
- });
-};
-
fixBoot2DockerVM = function (callback) {
- checkBoot2DockerVM(function (err) {
+ Boot2Docker.check(function (err) {
if (err) {
Session.set('available', false);
- resolveBoot2DockerVM(function (err) {
+ Boot2Docker.resolve(function (err) {
if (err) {
callback(err);
} else {
@@ -138,28 +125,41 @@ fixDefaultContainers = function (callback) {
};
Meteor.setInterval(function () {
- updateBoot2DockerInfo();
+ Boot2Docker.exists(function (err, exists) {
+ if (err) { console.log(err); return; }
+ if (exists) {
+ Boot2Docker.state(function (err, state) {
+ if (err) { console.log(err); return; }
+ Session.set('boot2dockerState', state);
+ if (state === 'running') {
+ Boot2Docker.stats(function (err, stats) {
+ if (err) { console.log(err); return; }
+ if (stats.state !== 'poweroff' && stats.memory && stats.disk) {
+ Session.set('boot2dockerMemoryUsage', stats.memory);
+ Session.set('boot2dockerDiskUsage', stats.disk);
+ }
+ });
+ }
+ });
+ }
+ });
}, 5000);
-fixInterval = null;
-startFixInterval = function () {
- stopFixInterval();
- fixInterval = Meteor.setInterval(function () {
+Meteor.setInterval(function () {
+ if (Installer.isUpToDate()) {
resolveWatchers(function () {});
- fixBoot2DockerVM(function (err) {
- if (err) { console.log(err); return; }
- // Meteor.call('recoverApps');
- fixDefaultImages(function (err) {
+ if (!Session.get('boot2dockerOff')) {
+ fixBoot2DockerVM(function (err) {
if (err) { console.log(err); return; }
- fixDefaultContainers(function (err) {
- if (err) { console.log(err); }
+ Meteor.call('recoverApps');
+ fixDefaultImages(function (err) {
+ if (err) { console.log(err); return; }
+ fixDefaultContainers(function (err) {
+ if (err) { console.log(err); }
+ });
});
});
- });
- }, 5000);
-};
+ }
+ }
+}, 5000);
-stopFixInterval = function () {
- Meteor.clearInterval(fixInterval);
- fixInterval = null;
-};
\ No newline at end of file
diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.js b/meteor/client/views/dashboard/apps/dashboard-single-app.js
index acbd198e79..5d32c4818f 100755
--- a/meteor/client/views/dashboard/apps/dashboard-single-app.js
+++ b/meteor/client/views/dashboard/apps/dashboard-single-app.js
@@ -21,8 +21,8 @@ Template.dashboard_single_app.events({
},
'click .btn-terminal': function () {
var app = this;
- var cmd = path.join(getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
- var terminalCmd = path.join(getBinDir(), 'terminal') + ' ' + cmd;
+ var cmd = path.join(Util.getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
+ var terminalCmd = path.join(Util.getBinDir(), 'terminal') + ' ' + cmd;
var exec = require('child_process').exec;
console.log(terminalCmd);
exec(terminalCmd, function (err, stdout) {
diff --git a/meteor/client/views/dashboard/settings/dashboard-settings.js b/meteor/client/views/dashboard/settings/dashboard-settings.js
index cf12f91c27..1a2ef4b7bd 100644
--- a/meteor/client/views/dashboard/settings/dashboard-settings.js
+++ b/meteor/client/views/dashboard/settings/dashboard-settings.js
@@ -3,18 +3,22 @@ Template.dashboard_settings.events({
var $btn = $(e.currentTarget);
$btn.html('Starting Boot2Docker...');
$btn.attr("disabled", "disabled");
- startFixInterval();
- startBoot2Docker(function (err) {
- if (err) { console.error(err); }
+ Session.set('boot2dockerOff', false);
+ Boot2Docker.start(function (err) {
+ if (err) {
+ console.log(err);
+ }
});
},
'click .btn-stop-boot2docker': function (e) {
var $btn = $(e.currentTarget);
$btn.html('Stopping Boot2Docker...');
$btn.attr("disabled", "disabled");
- stopFixInterval();
- stopBoot2Docker(function (err) {
- if (err) { console.error(err); }
+ Session.set('boot2dockerOff', true);
+ Boot2Docker.stop(function (err) {
+ if (err) {
+ console.log(err);
+ }
});
}
});
diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js
index 004ac755cf..d5774fbbd7 100644
--- a/meteor/client/views/dashboard/setup/setup-install.js
+++ b/meteor/client/views/dashboard/setup/setup-install.js
@@ -1,142 +1,13 @@
-var async = require('async');
-
-// Install steps. A step is a function that accepts a function (err) callback and returns once that step is complete.
-// keys:
-// - install: Function that runs the installation step and calls the callback with an error if failed.
-// - pastMessage: Message to show after step completion
-// - message: Message to show while step is running
-// - imperativeMessage: Message to show before running
-var steps = [
-
- // Set up VirtualBox
- {
- install: function (callback) {
- isVirtualBoxInstalled(function (err, virtualBoxInstalled) {
- var installedYet = false;
- if (!virtualBoxInstalled) {
- setupVirtualBox(function (err) {
- callback(err);
- });
- } else {
- callback();
- }
- });
- },
- pastMessage: 'VirtualBox installed',
- message: 'Installing VirtualBox',
- futureMessage: 'Install VirtualBox if necessary'
- },
-
- // Set up the routing.
- {
- install: function (callback) {
- setupResolver(function (err) {
- callback(err);
- });
- },
- pastMessage: 'Container routing set up (root required).',
- message: 'Setting up container routing (root required).',
- subMessage: '(This may take a few minutes)',
- futureMessage: 'Set up container routing to VM (root required).'
- },
-
- // Set up the VM for running Kitematic apps
- {
- install: function (callback) {
- console.log('Checking if vm exists...');
- boot2DockerVMExists(function (err, exists) {
- console.log('VM exists: ' + exists);
- if (exists) {
- console.log('Stopping vm');
- stopBoot2Docker(function () {
- console.log('Upgrading vm');
- upgradeBoot2Docker(function () {
- callback();
- });
- });
- } else {
- console.log('init VM');
- initBoot2Docker(function () {
- callback();
- });
- }
- });
- },
- pastMessage: 'Set up the Kitematic VM',
- message: 'Setting up the Kitematic VM...',
- futureMessage: 'Set up the Kitematic VM'
- },
-
- // Start the Kitematic VM
- {
- install: function (callback) {
- startBoot2Docker(function (err) {
- callback(err);
- });
- },
- pastMessage: 'Started the Kitematic VM',
- message: 'Starting the Kitematic VM',
- subMessage: '(This may take a few minutes)',
- futureMessage: 'Start the Kitematic VM',
- },
-
- // Set up the default Kitematic images
- {
- install: function (callback) {
- Meteor.call('reloadDefaultContainers', function (err) {
- callback(err);
- });
- },
- pastMessage: 'Started the Kitematic VM',
- message: 'Setting up the default Kitematic images...',
- subMessage: '(This may take a few minutes)',
- futureMessage: 'Set up the default Kitematic images',
- }
-];
-
-runSetup = function (callback) {
- // Run through the Kitematic installation, skipping steps if required.
- var currentStep = 0;
- Session.set('currentInstallStep', currentStep);
- Session.set('numberOfInstallSteps', steps.length);
- async.eachSeries(steps, function (step, callback) {
- console.log('Performing step ' + currentStep);
- step.install(function (err) {
- if (err) {
- callback(err);
- } else {
- currentStep += 1;
- Session.set('currentInstallStep', currentStep);
- callback();
- }
- });
- }, function (err) {
- if (err) {
- // if any of the steps fail
- console.log('Kitematic setup failed at step ' + currentStep);
- console.log(err);
- Session.set('failedStep', currentStep);
- Session.set('failedError', err);
- callback(err);
- } else {
- // Setup Finished
- console.log('Setup finished.');
- callback();
- }
- });
-};
-
var installStarted = false;
Template.setup_install.rendered = function() {
if(!installStarted) {
installStarted = true;
- runSetup(function (err) {
+ Installer.run(function (err) {
if (err) {
console.log('Setup failed.');
console.log(err);
} else {
- Installs.insert({});
- startFixInterval();
+ Installs.insert({version: Installer.CURRENT_VERSION});
Router.go('dashboard_apps');
}
});
@@ -144,7 +15,7 @@ Template.setup_install.rendered = function() {
};
Template.setup_install.steps = function () {
- return steps.map(function (step, index) {
+ return Installer.steps.map(function (step, index) {
step.index = index;
return step;
});
@@ -155,7 +26,7 @@ Template.setup_install.helpers({
return Session.get('currentInstallStep');
},
installComplete: function () {
- return Session.get('currentInstallStep') === steps.length;
+ return Session.get('currentInstallStep') === Installer.steps.length;
},
failedStep: function () {
return Session.get('failedStep');
diff --git a/meteor/client/views/dashboard/setup/setup-layout.html b/meteor/client/views/dashboard/setup/setup-layout.html
index 12c0f8682b..ce3765beb4 100644
--- a/meteor/client/views/dashboard/setup/setup-layout.html
+++ b/meteor/client/views/dashboard/setup/setup-layout.html
@@ -1,8 +1,13 @@
{{setTitle}}
This will set up everything needed to run Kitematic on your Mac. Kitematic needs to update itself to continue. This will set up everything needed to run Kitematic on your Mac.Welcome to Kitematic
- Welcome Back
+ Welcome to Kitematic
+