Merge branch 'master' into backend-refactor

This commit is contained in:
Sean Li 2014-09-02 22:53:38 -07:00
commit d58e5f14d6
22 changed files with 665 additions and 407 deletions

View File

@ -80,7 +80,7 @@ Example:
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. A roadmap of the project is kept
on the [Kitematic Roadmap](https://trello.com/b/xea5AHRk/kitematic-roadmap) Trello board.
on the [Kitematic Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) Trello board.
It's up to *you* to make a strong case to convince the project's developers of
the merits of this feature. Please provide as much detail and context as possible.

View File

@ -3,7 +3,7 @@
**Note:** If the installer gets stuck at any step for more than 1 minute, there is probably an error. Please help us troubleshoot by running it from the command line, and submit the logs to [contact@kitematic.com](mailto:contact@kitematic.com).
1. `cd <dir with Kitematic.app>`
2. Run `./Kitematic.app/Contents/MacOS/node-webkit`
2. Run `./Kitematic.app/Contents/MacOS/node-webkit`
Kitematic is still in Beta. Any effort in helping us find issues and improving the experience is greatly appreciated!
@ -45,7 +45,7 @@ Kitematic is still in Beta. Any effort in helping us find issues and improving t
- Remove VirtualBox
- rm /usr/local/bin/boot2docker
- sudo route delete 172.17.0.0/16 192.168.59.103 (disable routing to containers through VM)
- sudo route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway 192.168.60.103 (disable routing to containers through VM)
- rm -rf ~/Library/Application\ Support/Kitematic (remove app data)
- rm /Library/LaunchAgents/com.kitematic.route.plist (remove launch job that sets up routing to the containers)
@ -61,7 +61,7 @@ Kitematic's documentation and other information can be found at [http://kitemati
Please read through our [Contributing Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
Development [Roadmap](https://trello.com/b/xea5AHRk/kitematic-roadmap) can be found on our Trello board.
Development [Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) can be found on our Trello board.
## Community
@ -74,7 +74,7 @@ Keep track of development and community news.
## Versioning
For transparency into our release cycle and in striving to maintain backward compatibility, Kitematic is maintained under the [Semantic Versioning Guidelines](http://semver.org/). We'll try very hard adhere to those rules whenever possible.
For transparency into our release cycle and in striving to maintain backward compatibility, Kitematic is maintained under the [Semantic Versioning Guidelines](http://semver.org/). We'll try very hard to adhere to those rules whenever possible.
## Creators

View File

@ -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;

View File

@ -166,6 +166,10 @@
// Server and Client
"Docker": true,
"Util": true,
"Boot2Docker": true,
"Installer": true,
"VirtualBox": true,
"boot2dockerexec": true,
"getBoot2DockerIp": true,
"getBoot2DockerState": true,

View File

@ -1,14 +1,54 @@
var exec = require('exec');
var path = require('path');
boot2dockerexec = function (command, callback) {
exec(path.join(Util.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(Util.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);
callback(err);
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]);
}
});
};
installBoot2DockerAddons = function (callback) {
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) {
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 {

View File

@ -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();
}
});
};

View File

@ -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');
}
}

View File

@ -1,54 +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);
});
};
VirtualBox.hostOnlyAdapters = function (vm, callback) {
this.exec('showvminfo ' + vm + ' --machinereadable', function (err, stdout, stderr) {
if (err) {
callback(err);
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);
}
});
};
setupResolver = function (callback) {
var installFile = path.join(Util.getBinDir(), 'install');
var cocoaSudo = path.join(Util.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);
return;
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);
}
console.log('Virtualbox Installation & Resolver config complete.');
callback();
});
};
setupVirtualBox = function (callback) {
child_process.exec('open -W ' + path.join(Util.getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) {
console.log(stdout);
if (error) {
console.log(error);
callback(error);
return;
}
console.log('Virtualbox Installation running.');
callback();
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);
});
});
};

View File

@ -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;
};

View File

@ -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);
}
});
}
});

View File

@ -1,141 +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) {
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');
}
});
@ -143,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;
});
@ -154,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');

View File

@ -1,8 +1,13 @@
<template name="setup_layout">
{{setTitle}}
<div class="setup content text-center">
<h2>Welcome to Kitematic</h2>
<p>This will set up everything needed to run Kitematic on your Mac.</p>
{{#if isUpdating}}
<h2>Welcome Back</h2>
<p>Kitematic needs to update itself to continue.</p>
{{else}}
<h2>Welcome to Kitematic</h2>
<p>This will set up everything needed to run Kitematic on your Mac.</p>
{{/if}}
{{> yield}}
</div>
</template>

View File

@ -0,0 +1,11 @@
Template.setup_layout.rendered = function () {
Meteor.setInterval(function () {
$('.header .icons a').tooltip();
}, 1000);
};
Template.setup_layout.helpers({
isUpdating: function () {
return Session.get('isUpdating');
}
});

View File

@ -60,7 +60,7 @@ Apps.helpers({
return Images.findOne(this.imageId);
},
hostUrl: function () {
return this.name + '.dev';
return this.name + '.kite';
},
ports: function () {
var app = this;
@ -78,7 +78,7 @@ Apps.helpers({
var app = this;
var image = Images.findOne(app.imageId);
if (image && image.meta.app && image.meta.app.webPort) {
return 'http://' + app.name + '.dev:' + image.meta.app.webPort;
return 'http://' + app.name + '.kite:' + image.meta.app.webPort;
} else if (image && image.meta.app && image.meta.app.webPort === false) {
return null;
} else {
@ -93,14 +93,14 @@ Apps.helpers({
}
});
if (pickedPort) {
return 'http://' + app.name + '.dev:' + pickedPort;
return 'http://' + app.name + '.kite:' + pickedPort;
} else {
if (keys.length > 0) {
// Picks the first port that's not SSH
for (var i = 0; i < keys.length; i++) {
var port = parseInt(keys[i].split('/')[0], 10);
if (port !== 22) {
return 'http://' + app.name + '.dev:' + port;
return 'http://' + app.name + '.kite:' + port;
}
}
return null;

View File

@ -1,7 +1,26 @@
Installs = new Meteor.Collection('installs');
schemaInstalls = new SimpleSchema({
createdAt: {
type: Date,
autoValue: function() {
var now = new Date();
if (this.isInsert) {
return now;
} else if (this.isUpsert) {
return {$setOnInsert: now};
} else {
this.unset();
}
},
denyUpdate: true,
label: 'Time of install created'
},
version: {
type: String,
label: 'Installed version',
optional: true
}
});
Installs.allow({
@ -16,4 +35,4 @@ Installs.allow({
}
});
Installs.attachSchema(schemaInstalls);
Installs.attachSchema(schemaInstalls);

View File

@ -70,3 +70,77 @@ Util.copyVolumes = function (directory, appName) {
Util.hasDockerfile = function (directory) {
return fs.existsSync(path.join(directory, 'Dockerfile'));
};
/**
* Compares two software version numbers (e.g. "1.7.1" or "1.2b").
*
* @param {string} v1 The first version to be compared.
* @param {string} v2 The second version to be compared.
* @param {object} [options] Optional flags that affect comparison behavior:
* <ul>
* <li>
* <tt>lexicographical: true</tt> compares each part of the version strings lexicographically instead of
* naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
* "1.2".
* </li>
* <li>
* <tt>zeroExtend: true</tt> changes the result if one version string has less parts than the other. In
* this case the shorter string will be padded with "zero" parts instead of being considered smaller.
* </li>
* </ul>
* @returns {number|NaN}
* <ul>
* <li>0 if the versions are equal</li>
* <li>a negative integer iff v1 < v2</li>
* <li>a positive integer iff v1 > v2</li>
* <li>NaN if either version string is in the wrong format</li>
* </ul>
*
*/
Util.compareVersions = function (v1, v2, options) {
var lexicographical = options && options.lexicographical,
zeroExtend = options && options.zeroExtend,
v1parts = v1.split('.'),
v2parts = v2.split('.');
function isValidPart(x) {
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
}
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
return NaN;
}
if (zeroExtend) {
while (v1parts.length < v2parts.length) v1parts.push('0');
while (v2parts.length < v1parts.length) v2parts.push('0');
}
if (!lexicographical) {
v1parts = v1parts.map(Number);
v2parts = v2parts.map(Number);
}
for (var i = 0; i < v1parts.length; ++i) {
if (v2parts.length == i) {
return 1;
}
if (v1parts[i] == v2parts[i]) {
continue;
}
else if (v1parts[i] > v2parts[i]) {
return 1;
}
else {
return -1;
}
}
if (v1parts.length != v2parts.length) {
return -1;
}
return 0;
};

View File

@ -14,7 +14,7 @@ Apps.restart = function (app, callback) {
}).run();
callback(null);
// Use dig to refresh the DNS
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {});
exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {});
} else {
callback(null);
}

View File

@ -1,7 +1,7 @@
Dockerode = Meteor.require('dockerode');
var DOCKER_HOST='192.168.59.103';
docker = new Dockerode({host: '192.168.59.103', port: '2375'});
var DOCKER_HOST='192.168.60.103';
docker = new Dockerode({host: DOCKER_HOST, port: '2375'});
Docker = {};
@ -72,7 +72,7 @@ Docker.runContainer = function (app, image, callback) {
if (err) { callback(err, null); return; }
console.log('Started container: ' + container.id);
// Use dig to refresh the DNS
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {});
exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {});
callback(null, container);
});
});

View File

@ -1,45 +1,39 @@
#!/bin/sh
# Lookup the Kitematic VM resolver for .dev domains
# This script must be run as root and sets up Mac OS X to route all .kite domains to the virtual box VM with the name
# 'boot2docker-vm'. It does the following:
# 1) Adds a file under /etc/resolver/kite
# 2) Sets up a LaunchAgent for adding entries to the route table to route all requests to the Docker subnet (172.17.0.0/16)
# And expects the $IFNAME variable to contain the interface on which to send traffic to the boot2docker VM.
mkdir -p /etc/resolver
echo "nameserver 172.17.42.1" > /etc/resolver/dev
echo "nameserver 172.17.42.1" > /etc/resolver/kite
DIR=$(dirname "$0")
USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'`
/bin/rm -rf /Library/LaunchAgents/com.kitematic.route.plist
echo '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.kitematic.route</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/route</string>
<string>-n</string>
<string>add</string>
<string>172.17.0.0/16</string>
<string>192.168.59.103</string>
<string>bash</string>
<string>-c</string>
<string>/usr/sbin/scutil -w State:/Network/Interface/$IFNAME/IPv4;/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY</string>
</array>
<key>KeepAlive</key>
<false/>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
<key>UserName</key>
<string>root</string>
<key>LaunchOnlyOnce</key>
<true/>
</dict>
</plist>' > /Library/LaunchAgents/com.kitematic.route.plist
sudo -u $USER $DIR/boot2docker init
VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0
VBoxManage dhcpserver add --netname=vboxnet0 --ip=192.168.59.99 --netmask=255.255.255.0 --lowerip=192.168.59.103 --upperip=192.168.59.103
</plist>" > /Library/LaunchAgents/com.kitematic.route.plist
# Add entries to routing table for Kitematic VM
/sbin/route delete 172.17.0.0/16 192.168.59.103
/sbin/route -n add 172.17.0.0/16 192.168.59.103
/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY > /dev/null 2>&1 || true
/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY

View File

@ -1,6 +1,6 @@
user=root
{{ range $index, $value := $ }}
{{ with $address := index $value.Addresses 0 }}
address=/{{$value.Name}}.dev/{{$address.IP}}
address=/{{$value.Name}}.kite/{{$address.IP}}
{{ end }}
{{ end }}

View File

@ -15,6 +15,7 @@ pushd cache
if [ ! -f $BASE_IMAGE_VERSION_FILE ]; then
cecho "-----> Downloading Kitematic base images..." $purple
curl -L --progress-bar -o $BASE_IMAGE_VERSION_FILE https://s3.amazonaws.com/kite-installer/$BASE_IMAGE_VERSION_FILE
cp $BASE_IMAGE_VERSION_FILE ../resources/$BASE_IMAGE_FILE
fi
if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then
@ -60,11 +61,6 @@ if [ ! -f $COCOASUDO_FILE ]; then
chmod +x $COCOASUDO_FILE
fi
if [ ! -f $BASE_IMAGE_FILE ]; then
cp ../cache/$BASE_IMAGE_VERSION_FILE $BASE_IMAGE_FILE
fi
cp ../cache/$BOOT2DOCKER_CLI_VERSION_FILE $BOOT2DOCKER_CLI_FILE
chmod +x $BOOT2DOCKER_CLI_FILE

View File

@ -1,4 +1,4 @@
BASE_IMAGE_VERSION=0.0.1
BASE_IMAGE_VERSION=0.0.2
BASE_IMAGE_VERSION_FILE=base-images-$BASE_IMAGE_VERSION.tar.gz
BASE_IMAGE_FILE=base-images.tar.gz