- Working on changes required to setup client on windows.

- For now changes in SetupStore are made, to skip steps that do MacOS setup.
- Adding deps.ps1 script for installing all required dependencies on windows.
- Adding reset.ps1 script for removing all dependencies on windows.
- Modifying files to work with windows dependencies
- Fix for duplicate react instances in the code.
- A fix required to load only one instance of React in the application. Without such fix, on windows application loads two instances of React at start time, and crashes.
- Modifying files to support windows paths
- Modifying deps and reset powershell scripts to loose chocolatey dependency. Adding windows VirtualBox installation steps.
- Improved a workaround for adding Users directory to the shared folder in the VM on windows. Added few more fixes for Windows/Mac compatibility.
- Removing unnecessary log from deps.ps1 script,
- Updating Gulpfile to download and copy all the dependencies on application start,
- Cleaning pre and post install scripts from package.json, as they are not realy friendly for different platforms.
- Removing changes from browser.js as they were not necessary or even breaking.
- Cleaning pre and post install scripts from package.json, as they are not really friendly for different platforms.
- Fixing changes in package.json.
- Fixed reset.ps1 file to delete all folders created by the app,
- Fixed metrics.js file to properly use userAgent depending on the OS.
- Uncommented updateBinaries line in SetupStore.
- Adding open as a dependency.
- Fixing the setupStore tests.
- Adding proper directory to PATH for docker and docker-machine binaries.
- Adding support for the docker terminal command. It opens a CMD on windows and sets required env variables for the docker to work. Docker and Docker-Machine should be on the path, as they are set up there on Kitematic startup.
- Added Resources module which provides paths to all files stored in the resources directory,
- Added docker and docker-machine binary paths to Util module, to have a single place for calling those paths,
- Added binaries directory path, to Util module, to have a single place for calling this path,
- Added another exec command, which utilizes "child_process" module, and it's "exec" command, as the "exec" used right now is deprecated,
- Refactored the Installation step in the SetupStore, and connected with that SetupUtil commands to perform copying binaries from node instead of bash commands. That way application is more cross-platform friendly,
- Added new dependencies in form of fs-extra, fs-promise and any-promise, to allow yielding of fs commands in SetupUtil.
- The setup process now works correctly on windows from the beginning to end.
- Fixing broken tests.
- Improving tests.
- Adding elevated permissions to the reset script, as it is required to kill processes and remove VirtualBox.
- Added ability to run reset scripts through gulp,
- Added new npm script for resetting the environment "npm run reset".
- Refactored docker-machine module, to use Resources module for finding the machine command and general code cleaning,
- Added windows keyboard shortcut support.
- Adding the use of Resources module, instead of direct access of files in the resources dir.
- Testing appveyor.
- Testing appveyor + io.js.
- Back to node 0.10.
- Fixing permission issues with appveyor.
- Testing travis.

Signed-off-by: Dominik Deren <dominik.deren@live.com>

- Working on changes required to setup client on windows.
- For now changes in SetupStore are made, to skip steps that do MacOS setup.

- Adding deps.ps1 script for installing all required dependencies on windows.
- Adding reset.ps1 script for removing all dependencies on windows.

Fix for duplicate react instances in the code.

Modifying files to support windows paths

- Removing unnecessary log from deps.ps1 script,
- Updating Gulpfile to download and copy all the dependencies on application start,
- Cleaning pre and post install scripts from package.json, as they are not realy friendly for different platforms.
- Removing changes from browser.js as they were not necessary or even breaking.

- Fixing changes in package.json.

- Uncommented updateBinaries line in SetupStore.

- Adding open as a dependency.

- Adding proper directory to PATH for docker and docker-machine binaries.

- Fixing broken tests.

- Improving tests.

- Adding elevated permissions to the reset script, as it is required to kill processes and remove VirtualBox.

- Added ability to run reset scripts through gulp,
- Added new npm script for resetting the environment "npm run reset".

- Refactored docker-machine module, to use Resources module for finding the machine command and general code cleaning,
- Added windows keyboard shortcut support.

- Adding the use of Resources module, instead of direct access of files in the resources dir.

- Testing appveyor + io.js.

- Back to node 0.10.

- Fixing permission issues with appveyor.

Signed-off-by: Dominik Deren <dominik.deren@live.com>
This commit is contained in:
Dominik Deren 2015-03-31 10:38:05 +01:00
parent 2ee03ed440
commit 046e640eeb
27 changed files with 455 additions and 199 deletions

3
.gitignore vendored
View File

@ -17,3 +17,6 @@ cache
# Tests # Tests
.test .test
settings.json settings.json
# IDEs
.idea

View File

@ -12,7 +12,7 @@ describe('SetupStore', function () {
pit('downloads virtualbox if it is not installed', function () { pit('downloads virtualbox if it is not installed', function () {
virtualBox.installed.mockReturnValue(false); virtualBox.installed.mockReturnValue(false);
setupUtil.download.mockReturnValue(Promise.resolve()); setupUtil.download.mockReturnValue(Promise.resolve());
util.packagejson.mockReturnValue({'virtualbox-filename': ''}); setupUtil.virtualBoxFileName.mockReturnValue('');
util.supportDir.mockReturnValue(''); util.supportDir.mockReturnValue('');
return setupStore.steps().download.run().then(() => { return setupStore.steps().download.run().then(() => {
expect(setupUtil.download).toBeCalled(); expect(setupUtil.download).toBeCalled();
@ -24,7 +24,7 @@ describe('SetupStore', function () {
virtualBox.version.mockReturnValue(Promise.resolve('4.3.16')); virtualBox.version.mockReturnValue(Promise.resolve('4.3.16'));
setupUtil.compareVersions.mockReturnValue(-1); setupUtil.compareVersions.mockReturnValue(-1);
setupUtil.download.mockReturnValue(Promise.resolve()); setupUtil.download.mockReturnValue(Promise.resolve());
util.packagejson.mockReturnValue({'virtualbox-filename': ''}); setupUtil.virtualBoxFileName.mockReturnValue('');
util.supportDir.mockReturnValue(''); util.supportDir.mockReturnValue('');
return setupStore.steps().download.run().then(() => { return setupStore.steps().download.run().then(() => {
expect(setupUtil.download).toBeCalled(); expect(setupUtil.download).toBeCalled();
@ -34,10 +34,11 @@ describe('SetupStore', function () {
describe('install step', function () { describe('install step', function () {
util.exec.mockReturnValue(Promise.resolve()); util.exec.mockReturnValue(Promise.resolve());
setupUtil.copyBinariesCmd.mockReturnValue('copycmd'); util.execProper.mockReturnValue(Promise.resolve());
setupUtil.fixBinariesCmd.mockReturnValue('fixcmd'); setupUtil.copyBinariesCmd.mockReturnValue(Promise.resolve());
setupUtil.fixBinariesCmd.mockReturnValue(Promise.resolve());
virtualBox.killall.mockReturnValue(Promise.resolve()); virtualBox.killall.mockReturnValue(Promise.resolve());
setupUtil.installVirtualBoxCmd.mockReturnValue('installvb'); setupUtil.installVirtualBoxCmd.mockReturnValue(Promise.resolve());
setupUtil.macSudoCmd.mockImplementation(cmd => 'macsudo ' + cmd); setupUtil.macSudoCmd.mockImplementation(cmd => 'macsudo ' + cmd);
pit('installs virtualbox if it is not installed', function () { pit('installs virtualbox if it is not installed', function () {
@ -45,7 +46,9 @@ describe('SetupStore', function () {
util.exec.mockReturnValue(Promise.resolve()); util.exec.mockReturnValue(Promise.resolve());
return setupStore.steps().install.run().then(() => { return setupStore.steps().install.run().then(() => {
expect(virtualBox.killall).toBeCalled(); expect(virtualBox.killall).toBeCalled();
expect(util.exec).toBeCalledWith('macsudo copycmd && fixcmd && installvbcmd'); expect(setupUtil.copyBinariesCmd).toBeCalled();
expect(setupUtil.fixBinariesCmd).toBeCalled();
expect(setupUtil.installVirtualBoxCmd).toBeCalled();
}); });
}); });
@ -54,7 +57,9 @@ describe('SetupStore', function () {
setupUtil.compareVersions.mockReturnValue(0); setupUtil.compareVersions.mockReturnValue(0);
setupUtil.needsBinaryFix.mockReturnValue(true); setupUtil.needsBinaryFix.mockReturnValue(true);
return setupStore.steps().install.run().then(() => { return setupStore.steps().install.run().then(() => {
expect(util.exec).toBeCalledWith('macsudo copycmd && fixcmd'); expect(setupUtil.copyBinariesCmd).toBeCalled();
expect(setupUtil.fixBinariesCmd).toBeCalled();
expect(setupUtil.installVirtualBoxCmd).not.toBeCalled();
}); });
}); });
}); });

21
appveyor.yml Normal file
View File

@ -0,0 +1,21 @@
# Test against this version of Node.js
environment:
nodejs_version: "0.10"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# Updating NPM to avoid permission issues: http://www.appveyor.com/docs/lang/nodejs-iojs#locking-errors-eperm-eexist-tgz-lock
- npm -g install npm@2
- set PATH=%APPDATA%\npm;%PATH%
# install modules
- npm install
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
# run tests
- npm test

View File

@ -2,6 +2,7 @@ var babel = require('gulp-babel');
var changed = require('gulp-changed'); var changed = require('gulp-changed');
var concat = require('gulp-concat'); var concat = require('gulp-concat');
var cssmin = require('gulp-cssmin'); var cssmin = require('gulp-cssmin');
var rename = require('gulp-rename');
var downloadatomshell = require('gulp-download-atom-shell'); var downloadatomshell = require('gulp-download-atom-shell');
var fs = require('fs'); var fs = require('fs');
var gulp = require('gulp'); var gulp = require('gulp');
@ -167,11 +168,56 @@ gulp.task('settings', function () {
string_src('settings.json', JSON.stringify(settings)).pipe(gulp.dest('dist/osx/' + options.appFilename.replace(' ', '\ ').replace('(','\(').replace(')','\)') + '/Contents/Resources/app')); string_src('settings.json', JSON.stringify(settings)).pipe(gulp.dest('dist/osx/' + options.appFilename.replace(' ', '\ ').replace('(','\(').replace(')','\)') + '/Contents/Resources/app'));
}); });
gulp.task('release', function () { gulp.task('download-deps', function () {
runSequence('download', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip'); if(process.platform === 'win32') {
return gulp.src('').pipe(
shell(['powershell.exe -ExecutionPolicy unrestricted -File util\\deps.ps1'])
);
} else {
return gulp.src('').pipe(
shell(['./util/deps'])
);
}
}); });
gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () { gulp.task('copy-icns', ['download'], function () {
if(process.platform === 'win32') {
return gulp.src(options.icon)
.pipe(rename('atom.icns'))
.pipe(gulp.dest('./cache/resources'));
} else {
return gulp.src(options.icon)
.pipe(rename('atom.icns'))
.pipe(gulp.dest('./cache/Atom.app/Contents/Resources'));
}
});
gulp.task('copy-plist', ['download'], function (done) {
if(process.platform === 'darwin') {
return gulp.src('./util/Info.plist')
.pipe(gulp.dest('./cache/Atom.app/Contents'));
} else {
done();
}
});
gulp.task('reset', function () {
if(process.platform === 'win32') {
return gulp.src('').pipe(
shell(['powershell.exe -ExecutionPolicy unrestricted -Command "Start-Process powershell -verb runas -ArgumentList \\\"-ExecutionPolicy unrestricted -file c:\\Users\\Dominik\\Documents\\GitHub\\kitematic\\util\\reset.ps1\\\" -Wait"'])
);
} else {
return gulp.src('').pipe(
shell(['./util/reset'])
);
}
});
gulp.task('release', function () {
runSequence('download-deps', 'download', 'copy-icns', 'copy-plist', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip');
});
gulp.task('default', ['download-deps', 'download', 'copy-icns', 'copy-plist', 'copy', 'js', 'images', 'styles'], function () {
gulp.watch('src/**/*.js', ['js']); gulp.watch('src/**/*.js', ['js']);
gulp.watch('index.html', ['copy']); gulp.watch('index.html', ['copy']);
gulp.watch('styles/**/*.less', ['styles']); gulp.watch('styles/**/*.less', ['styles']);
@ -181,7 +227,14 @@ gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function ()
var env = process.env; var env = process.env;
env.NODE_ENV = 'development'; env.NODE_ENV = 'development';
gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom .'], {
env: env if(process.platform === 'win32') {
})); gulp.src('').pipe(shell(['cache\\atom.exe .'], {
env: env
}));
} else {
gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom .'], {
env: env
}));
}
}); });

View File

@ -6,6 +6,6 @@
<title>Kitematic</title> <title>Kitematic</title>
</head> </head>
<body> <body>
<script src="Main.js"></script> <script src="Startup.js"></script>
</body> </body>
</html> </html>

View File

@ -15,9 +15,8 @@
"test": "jest", "test": "jest",
"release": "gulp release", "release": "gulp release",
"release:beta": "gulp release --beta", "release:beta": "gulp release --beta",
"preinstall": "./util/deps", "lint": "jsxhint src && jsxhint browser",
"postinstall": "if [ `uname` == 'Darwin' ]; then gulp download && cp util/Info.plist cache/Atom.app/Contents/Info.plist && cp util/kitematic.icns cache/Atom.app/Contents/Resources/atom.icns; fi", "reset": "gulp reset"
"lint": "jsxhint src && jsxhint browser"
}, },
"licenses": [ "licenses": [
{ {
@ -45,19 +44,25 @@
"atom-shell-version": "0.21.3", "atom-shell-version": "0.21.3",
"virtualbox-version": "4.3.24", "virtualbox-version": "4.3.24",
"virtualbox-filename": "VirtualBox-4.3.24.pkg", "virtualbox-filename": "VirtualBox-4.3.24.pkg",
"virtualbox-filename-win": "VirtualBox-4.3.26.exe",
"virtualbox-checksum": "100eee21df3808fc1b1e461e86f10b6d2a748935c5f31bc665f4ff0777e44a0b", "virtualbox-checksum": "100eee21df3808fc1b1e461e86f10b6d2a748935c5f31bc665f4ff0777e44a0b",
"virtualbox-checksum-win": "9cb265babf307d825f5178693af95ffca077f80ae22cf43868c3538c159123ff",
"dependencies": { "dependencies": {
"ansi-to-html": "0.3.0", "ansi-to-html": "0.3.0",
"any-promise": "^0.1.0",
"async": "^0.9.0", "async": "^0.9.0",
"bluebird": "^2.9.12", "bluebird": "^2.9.12",
"bugsnag-js": "^2.4.7", "bugsnag-js": "^2.4.7",
"dockerode": "^2.0.7", "dockerode": "^2.0.7",
"exec": "0.2.0", "exec": "0.2.0",
"fs-extra": "^0.17.0",
"fs-promise": "^0.3.1",
"jquery": "^2.1.3", "jquery": "^2.1.3",
"minimist": "^1.1.0", "minimist": "^1.1.0",
"mixpanel": "0.0.20", "mixpanel": "0.0.20",
"node-uuid": "^1.4.2", "node-uuid": "^1.4.2",
"object-assign": "^2.0.0", "object-assign": "^2.0.0",
"open": "0.0.5",
"react": "^0.12.2", "react": "^0.12.2",
"react-bootstrap": "^0.15.1", "react-bootstrap": "^0.15.1",
"react-retina-image": "^1.1.2", "react-retina-image": "^1.1.2",
@ -80,6 +85,7 @@
"gulp-livereload": "^3.8.0", "gulp-livereload": "^3.8.0",
"gulp-plumber": "^0.6.6", "gulp-plumber": "^0.6.6",
"gulp-react": "^2.0.0", "gulp-react": "^2.0.0",
"gulp-rename": "^1.2.0",
"gulp-shell": "^0.3.0", "gulp-shell": "^0.3.0",
"gulp-sourcemaps": "^1.5.0", "gulp-sourcemaps": "^1.5.0",
"gulp-util": "^3.0.4", "gulp-util": "^3.0.4",

View File

@ -10,6 +10,8 @@ var machine = require('./DockerMachine');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var Router = require('react-router'); var Router = require('react-router');
var webPorts = require('./Util').webPorts; var webPorts = require('./Util').webPorts;
var util = require('./Util');
var resources = require('./Resources');
var ContainerDetailsSubheader = React.createClass({ var ContainerDetailsSubheader = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
@ -89,7 +91,7 @@ var ContainerDetailsSubheader = React.createClass({
metrics.track('Opened In Browser', { metrics.track('Opened In Browser', {
from: 'header' from: 'header'
}); });
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { util.openPathOrUrl(this.state.ports[this.state.defaultPort].url, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
} }
@ -105,9 +107,8 @@ var ContainerDetailsSubheader = React.createClass({
if (!this.disableTerminal()) { if (!this.disableTerminal()) {
metrics.track('Terminaled Into Container'); metrics.track('Terminaled Into Container');
var container = this.props.container; var container = this.props.container;
var terminal = path.join(process.cwd(), 'resources', 'terminal');
machine.ip().then(ip => { machine.ip().then(ip => {
var cmd = [terminal, 'ssh', '-p', '22', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', 'exec', '-i', '-t', container.Name, 'sh']; var cmd = [resources.terminal(), 'ssh', '-p', '22', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', 'exec', '-i', '-t', container.Name, 'sh'];
exec(cmd, function (stderr, stdout, code) { exec(cmd, function (stderr, stdout, code) {
if (code) { if (code) {
console.log(stderr); console.log(stderr);

View File

@ -28,7 +28,7 @@ var ContainerHome = React.createClass({
resizeWindow(); resizeWindow();
}, },
handleErrorClick: function () { handleErrorClick: function () {
util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new');
}, },
componentWillReceiveProps: function () { componentWillReceiveProps: function () {
this.init(); this.init();

View File

@ -5,6 +5,7 @@ var path = require('path');
var exec = require('exec'); var exec = require('exec');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var Router = require('react-router'); var Router = require('react-router');
var util = require('./Util');
var ContainerHomeFolder = React.createClass({ var ContainerHomeFolder = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
@ -12,7 +13,7 @@ var ContainerHomeFolder = React.createClass({
metrics.track('Opened Volume Directory', { metrics.track('Opened Volume Directory', {
from: 'home' from: 'home'
}); });
exec(['open', path], function (err) { util.openPathOrUrl(path, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}, },

View File

@ -7,6 +7,7 @@ var Router = require('react-router');
var request = require('request'); var request = require('request');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var webPorts = require('./Util').webPorts; var webPorts = require('./Util').webPorts;
var util = require('./Util');
var ContainerHomePreview = React.createClass({ var ContainerHomePreview = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
@ -61,7 +62,7 @@ var ContainerHomePreview = React.createClass({
metrics.track('Opened In Browser', { metrics.track('Opened In Browser', {
from: 'preview' from: 'preview'
}); });
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { util.openPathOrUrl(this.state.ports[this.state.defaultPort].url, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
} }

View File

@ -6,6 +6,7 @@ var ContainerStore = require('./ContainerStore');
var ContainerUtil = require('./ContainerUtil'); var ContainerUtil = require('./ContainerUtil');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var webPorts = require('./Util').webPorts; var webPorts = require('./Util').webPorts;
var util = require('./Util');
var ContainerSettingsPorts = React.createClass({ var ContainerSettingsPorts = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
@ -38,7 +39,7 @@ var ContainerSettingsPorts = React.createClass({
metrics.track('Opened In Browser', { metrics.track('Opened In Browser', {
from: 'settings' from: 'settings'
}); });
exec(['open', url], function (err) { util.openPathOrUrl(url, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}, },

View File

@ -6,6 +6,7 @@ var exec = require('exec');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var ContainerStore = require('./ContainerStore'); var ContainerStore = require('./ContainerStore');
var util = require('./Util');
var ContainerSettingsVolumes = React.createClass({ var ContainerSettingsVolumes = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
@ -35,7 +36,7 @@ var ContainerSettingsVolumes = React.createClass({
metrics.track('Opened Volume Directory', { metrics.track('Opened Volume Directory', {
from: 'settings' from: 'settings'
}); });
exec(['open', path], function (err) { util.openPathOrUrl(path, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}, },

View File

@ -104,7 +104,17 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
return b.indexOf(':' + key) !== -1; return b.indexOf(':' + key) !== -1;
}); });
if (!existingBind) { if (!existingBind) {
binds.push(path.join(util.home(), 'Kitematic', name, key)+ ':' + key); var home = util.home();
if(util.isWindows()) {
home = home.charAt(0).toLowerCase() + home.slice(1);
home = "/" + home.replace(':', '').replace(/\\/g, '/');
var fullPath = path.join(home, 'Kitematic', name, key);
fullPath = fullPath.replace(/\\/g, '/');
binds.push(fullPath + ':' + key);
} else {
binds.push(path.join(home, 'Kitematic', name, key) + ':' + key);
}
} }
}); });
} }

View File

@ -118,7 +118,7 @@ var Containers = React.createClass({
metrics.track('Opened Issue Reporter', { metrics.track('Opened Issue Reporter', {
from: 'app' from: 'app'
}); });
util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new');
}, },
handleMouseEnterDockerTerminal: function () { handleMouseEnterDockerTerminal: function () {
this.setState({ this.setState({

View File

@ -2,12 +2,13 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var dockerode = require('dockerode'); var dockerode = require('dockerode');
var Promise = require('bluebird'); var Promise = require('bluebird');
var util = require('./Util');
var Docker = { var Docker = {
_host: null, _host: null,
_client: null, _client: null,
setup: function(ip, name) { setup: function(ip, name) {
var certDir = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.docker/machine/machines', name); var certDir = path.join(util.home(), '.docker/machine/machines', name);
if (!fs.existsSync(certDir)) { if (!fs.existsSync(certDir)) {
return; return;
} }

View File

@ -1,27 +1,21 @@
var _ = require('underscore'); var _ = require('underscore');
var path = require('path'); var path = require('path');
var Promise = require('bluebird'); var Promise = require('bluebird');
var _ = require('underscore');
var fs = require('fs'); var fs = require('fs');
var util = require('./Util'); var util = require('./Util');
var exec = require('child_process').exec;
var resources = require('./Resources');
var NAME = 'dev'; var NAME = 'dev';
var DockerMachine = { var DockerMachine = {
command: function () { command() {
return path.join(process.cwd(), 'resources', 'docker-machine-' + this.version()); return resources.docker_machine();
}, },
name: function () { name() {
return NAME; return NAME;
}, },
version: function () { isoversion() {
try {
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))['docker-machine-version'];
} catch (err) {
return null;
}
},
isoversion: function () {
try { try {
var data = fs.readFileSync(path.join(util.home(), '.docker', 'machine', 'machines', NAME, 'boot2docker.iso'), 'utf8'); var data = fs.readFileSync(path.join(util.home(), '.docker', 'machine', 'machines', NAME, 'boot2docker.iso'), 'utf8');
var match = data.match(/Boot2Docker-v(\d+\.\d+\.\d+)/); var match = data.match(/Boot2Docker-v(\d+\.\d+\.\d+)/);
@ -34,8 +28,8 @@ var DockerMachine = {
return null; return null;
} }
}, },
info: function () { info() {
return util.exec([DockerMachine.command(), 'ls']).then(stdout => { return util.exec([this.command(), 'ls']).then(stdout => {
var lines = stdout.trim().split('\n').filter(line => line.indexOf('time=') === -1); var lines = stdout.trim().split('\n').filter(line => line.indexOf('time=') === -1);
var machines = {}; var machines = {};
lines.slice(1, lines.length).forEach(line => { lines.slice(1, lines.length).forEach(line => {
@ -55,44 +49,44 @@ var DockerMachine = {
} }
}); });
}, },
exists: function () { exists() {
return DockerMachine.info().then(() => { return this.info().then(() => {
return true; return true;
}).catch(() => { }).catch(() => {
return false; return false;
}); });
}, },
create: function () { create() {
return util.exec([DockerMachine.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]); return util.exec([this.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]);
}, },
start: function () { start() {
return util.exec([DockerMachine.command(), 'start', NAME]); return util.exec([this.command(), 'start', NAME]);
}, },
stop: function () { stop() {
return util.exec([DockerMachine.command(), 'stop', NAME]); return util.exec([this.command(), 'stop', NAME]);
}, },
upgrade: function () { upgrade() {
return util.exec([DockerMachine.command(), 'upgrade', NAME]); return util.exec([this.command(), 'upgrade', NAME]);
}, },
rm: function () { rm() {
return util.exec([DockerMachine.command(), 'rm', '-f', NAME]); return util.exec([this.command(), 'rm', '-f', NAME]);
}, },
ip: function () { ip() {
return util.exec([DockerMachine.command(), 'ip', NAME]).then(stdout => { return util.exec([this.command(), 'ip', NAME]).then(stdout => {
return Promise.resolve(stdout.trim().replace('\n', '')); return Promise.resolve(stdout.trim().replace('\n', ''));
}); });
}, },
regenerateCerts: function () { regenerateCerts() {
return util.exec([DockerMachine.command(), 'tls-regenerate-certs', '-f', NAME]); return util.exec([this.command(), 'tls-regenerate-certs', '-f', NAME]);
}, },
state: function () { state() {
return DockerMachine.info().then(info => { return this.info().then(info => {
return info ? info.state : null; return info ? info.state : null;
}); });
}, },
disk: function () { disk() {
return util.exec([DockerMachine.command(), 'ssh', NAME, 'df']).then(stdout => { return util.exec([this.command(), 'ssh', NAME, 'df']).then(stdout => {
try { try {
var lines = stdout.split('\n'); var lines = stdout.split('\n');
var dataline = _.find(lines, function (line) { var dataline = _.find(lines, function (line) {
@ -115,7 +109,7 @@ var DockerMachine = {
} }
}); });
}, },
memory: function () { memory() {
return util.exec([this.command(), 'ssh', NAME, 'free -m']).then(stdout => { return util.exec([this.command(), 'ssh', NAME, 'free -m']).then(stdout => {
try { try {
var lines = stdout.split('\n'); var lines = stdout.split('\n');
@ -141,13 +135,13 @@ var DockerMachine = {
} }
}); });
}, },
stats: function () { stats() {
DockerMachine.state().then(state => { this.state().then(state => {
if (state === 'Stopped') { if (state === 'Stopped') {
return Promise.resolve({state: state}); return Promise.resolve({state: state});
} }
var memory = DockerMachine.memory(); var memory = this.memory();
var disk = DockerMachine.disk(); var disk = this.disk();
return Promise.all([memory, disk]).spread((memory, disk) => { return Promise.all([memory, disk]).spread((memory, disk) => {
return Promise.resolve({ return Promise.resolve({
memory: memory, memory: memory,
@ -156,13 +150,18 @@ var DockerMachine = {
}); });
}); });
}, },
dockerTerminal: function () { dockerTerminal() {
var terminal = path.join(process.cwd(), 'resources', 'terminal'); if(util.isWindows()) {
this.info().then(machine => { this.info().then(machine => {
var cmd = [terminal, `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 $SHELL`]; util.execProper(`start cmd.exe /k "SET DOCKER_HOST=${machine.url}&& SET DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)}&& SET DOCKER_TLS_VERIFY=1`);
util.exec(cmd).then(() => {}); });
}); } else {
}, this.info().then(machine => {
var cmd = [resources.terminal(), `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 $SHELL`];
util.exec(cmd).then(() => {});
});
}
}
}; };
module.exports = DockerMachine; module.exports = DockerMachine;

View File

@ -21,7 +21,7 @@ var MenuTemplate = function () {
}, },
{ {
label: 'Preferences', label: 'Preferences',
accelerator: 'Command+,', accelerator: util.CommandOrCtrl() + '+,',
enabled: !!docker.host(), enabled: !!docker.host(),
click: function () { click: function () {
metrics.track('Opened Preferences', { metrics.track('Opened Preferences', {
@ -42,12 +42,12 @@ var MenuTemplate = function () {
}, },
{ {
label: 'Hide Kitematic', label: 'Hide Kitematic',
accelerator: 'Command+H', accelerator: util.CommandOrCtrl() + '+H',
selector: 'hide:' selector: 'hide:'
}, },
{ {
label: 'Hide Others', label: 'Hide Others',
accelerator: 'Command+Shift+H', accelerator: util.CommandOrCtrl() + '+Shift+H',
selector: 'hideOtherApplications:' selector: 'hideOtherApplications:'
}, },
{ {
@ -59,11 +59,11 @@ var MenuTemplate = function () {
}, },
{ {
label: 'Quit', label: 'Quit',
accelerator: 'Command+Q', accelerator: util.CommandOrCtrl() + '+Q',
click: function() { click: function() {
app.quit(); app.quit();
} }
}, }
] ]
}, },
{ {
@ -74,7 +74,7 @@ var MenuTemplate = function () {
}, },
{ {
label: 'Open Docker Command Line Terminal', label: 'Open Docker Command Line Terminal',
accelerator: 'Command+Shift+T', accelerator: util.CommandOrCtrl() + '+Shift+T',
enabled: !!docker.host(), enabled: !!docker.host(),
click: function() { click: function() {
metrics.track('Opened Docker Terminal', { metrics.track('Opened Docker Terminal', {
@ -82,7 +82,7 @@ var MenuTemplate = function () {
}); });
machine.dockerTerminal(); machine.dockerTerminal();
} }
}, }
] ]
}, },
{ {
@ -90,12 +90,12 @@ var MenuTemplate = function () {
submenu: [ submenu: [
{ {
label: 'Undo', label: 'Undo',
accelerator: 'Command+Z', accelerator: util.CommandOrCtrl() + '+Z',
selector: 'undo:' selector: 'undo:'
}, },
{ {
label: 'Redo', label: 'Redo',
accelerator: 'Shift+Command+Z', accelerator: 'Shift+' + util.CommandOrCtrl() + '+Z',
selector: 'redo:' selector: 'redo:'
}, },
{ {
@ -103,24 +103,24 @@ var MenuTemplate = function () {
}, },
{ {
label: 'Cut', label: 'Cut',
accelerator: 'Command+X', accelerator: util.CommandOrCtrl() + '+X',
selector: 'cut:' selector: 'cut:'
}, },
{ {
label: 'Copy', label: 'Copy',
accelerator: 'Command+C', accelerator: util.CommandOrCtrl() + '+C',
selector: 'copy:' selector: 'copy:'
}, },
{ {
label: 'Paste', label: 'Paste',
accelerator: 'Command+V', accelerator: util.CommandOrCtrl() + '+V',
selector: 'paste:' selector: 'paste:'
}, },
{ {
label: 'Select All', label: 'Select All',
accelerator: 'Command+A', accelerator: util.CommandOrCtrl() + '+A',
selector: 'selectAll:' selector: 'selectAll:'
}, }
] ]
}, },
{ {
@ -128,9 +128,9 @@ var MenuTemplate = function () {
submenu: [ submenu: [
{ {
label: 'Toggle DevTools', label: 'Toggle DevTools',
accelerator: 'Alt+Command+I', accelerator: 'Alt+' + util.CommandOrCtrl() + '+I',
click: function() { remote.getCurrentWindow().toggleDevTools(); } click: function() { remote.getCurrentWindow().toggleDevTools(); }
}, }
] ]
}, },
{ {
@ -138,12 +138,12 @@ var MenuTemplate = function () {
submenu: [ submenu: [
{ {
label: 'Minimize', label: 'Minimize',
accelerator: 'Command+M', accelerator: util.CommandOrCtrl() + '+M',
selector: 'performMiniaturize:' selector: 'performMiniaturize:'
}, },
{ {
label: 'Close', label: 'Close',
accelerator: 'Command+W', accelerator: util.CommandOrCtrl() + '+W',
click: function () { click: function () {
remote.getCurrentWindow().hide(); remote.getCurrentWindow().hide();
} }
@ -154,7 +154,7 @@ var MenuTemplate = function () {
{ {
label: 'Bring All to Front', label: 'Bring All to Front',
selector: 'arrangeInFront:' selector: 'arrangeInFront:'
}, }
] ]
}, },
{ {
@ -166,11 +166,11 @@ var MenuTemplate = function () {
metrics.track('Opened Issue Reporter', { metrics.track('Opened Issue Reporter', {
from: 'menu' from: 'menu'
}); });
util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new');
} }
}, }
] ]
}, }
]; ];
}; };

View File

@ -45,7 +45,14 @@ var Metrics = {
localStorage.setItem('metrics.id', uuid.v4()); localStorage.setItem('metrics.id', uuid.v4());
} }
var os = navigator.userAgent.match(/Mac OS X (\d+_\d+_\d+)/)[1].replace(/_/g, '.'); var os;
if(util.isWindows()) {
os = navigator.userAgent;
} else {
os = navigator.userAgent.match(/Mac OS X (\d+_\d+_\d+)/)[1].replace(/_/g, '.');
}
mixpanel.track(name, assign({ mixpanel.track(name, assign({
distinct_id: id, distinct_id: id,
version: util.packagejson().version, version: util.packagejson().version,

20
src/Resources.js Normal file
View File

@ -0,0 +1,20 @@
var util = require('./Util');
var path = require('path');
module.exports = {
resourceDir() {
return process.env.RESOURCES_PATH;
},
macsudo() {
return path.join(this.resourceDir(), 'macsudo');
},
terminal() {
return path.join(this.resourceDir(), 'terminal');
},
docker() {
return path.join(this.resourceDir(), 'docker-' + util.packagejson()['docker-version'] + util.binsEnding());
},
docker_machine() {
return path.join(this.resourceDir(), 'docker-machine-' + util.packagejson()['docker-machine-version'] + util.binsEnding());
}
};

View File

@ -26,8 +26,7 @@ var _steps = [{
totalPercent: 35, totalPercent: 35,
percent: 0, percent: 0,
run: function (progressCallback) { run: function (progressCallback) {
var packagejson = util.packagejson(); return setupUtil.download(setupUtil.virtualBoxUrl(), path.join(util.supportDir(), setupUtil.virtualBoxFileName()), setupUtil.virtualBoxChecksum(), percent => {
return setupUtil.download(setupUtil.virtualBoxUrl(), path.join(util.supportDir(), packagejson['virtualbox-filename']), packagejson['virtualbox-checksum'], percent => {
progressCallback(percent); progressCallback(percent);
}); });
} }
@ -39,21 +38,20 @@ var _steps = [{
percent: 0, percent: 0,
seconds: 5, seconds: 5,
run: Promise.coroutine(function* (progressCallback) { run: Promise.coroutine(function* (progressCallback) {
var cmd = setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd(); yield setupUtil.copyBinariesCmd();
yield setupUtil.fixBinariesCmd();
if (!virtualBox.installed()) { if (!virtualBox.installed()) {
yield virtualBox.killall(); yield virtualBox.killall();
cmd += ' && ' + setupUtil.installVirtualBoxCmd(); try {
} else { progressCallback(50); // TODO: detect when the installation has started so we can simulate progress
if (!setupUtil.needsBinaryFix()) { yield setupUtil.installVirtualBoxCmd();
return; } catch (err) {
throw null;
} }
} }
try {
progressCallback(50); // TODO: detect when the installation has started so we can simulate progress return;
yield util.exec(setupUtil.macSudoCmd(cmd));
} catch (err) {
throw null;
}
}) })
}, { }, {
name: 'init', name: 'init',
@ -70,9 +68,33 @@ var _steps = [{
try { try {
yield machine.rm(); yield machine.rm();
yield machine.create(); yield machine.create();
if(util.isWindows()) {
var home = util.home();
var driveLetter = home.charAt(0);
var parts = home.split('\\').slice(0, -1);
var usersDirName = parts[parts.length-1];
var usersDirPath = parts.join('\\');
var shareName = driveLetter + "/" + usersDirName;
yield machine.stop();
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
yield machine.start();
}
} catch (err) { } catch (err) {
rimraf.sync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name())); rimraf.sync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name()));
yield machine.create(); yield machine.create();
if(util.isWindows()) {
var home = util.home();
var driveLetter = home.charAt(0);
var parts = home.split('\\').slice(0, -1);
var usersDirName = parts[parts.length-1];
var usersDirPath = parts.join('\\');
var shareName = driveLetter + "/" + usersDirName;
yield machine.stop();
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
yield machine.start();
}
} }
return; return;
} }
@ -156,9 +178,10 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
var packagejson = util.packagejson(); var packagejson = util.packagejson();
var isoversion = machine.isoversion(); var isoversion = machine.isoversion();
var required = {}; var required = {};
var vboxfile = path.join(util.supportDir(), packagejson['virtualbox-filename']); var vboxfile = path.join(util.supportDir(), setupUtil.virtualBoxFileName());
var vboxNeedsInstall = !virtualBox.installed(); var vboxNeedsInstall = !virtualBox.installed();
required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== packagejson['virtualbox-checksum']);
required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== setupUtil.virtualBoxChecksum());
required.install = vboxNeedsInstall || setupUtil.needsBinaryFix(); required.install = vboxNeedsInstall || setupUtil.needsBinaryFix();
required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0; required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0;
@ -233,7 +256,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
throw { throw {
message: 'Machine IP could not be fetched. Please retry the setup. If this fails please file a ticket on our GitHub repo.', 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(), machine: yield machine.info(),
ip: ip, ip: ip
}; };
} }
console.log('Finished Steps'); console.log('Finished Steps');

View File

@ -1,74 +1,63 @@
var _ = require('underscore'); var _ = require('underscore');
var crypto = require('crypto'); var crypto = require('crypto');
var fs = require('fs'); var fs = require('fs-promise');
var path = require('path'); var path = require('path');
var request = require('request'); var request = require('request');
var progress = require('request-progress'); var progress = require('request-progress');
var Promise = require('bluebird'); var Promise = require('bluebird');
var util = require('./Util'); var util = require('./Util');
var resources = require('./Resources');
var SetupUtil = { var SetupUtil = {
needsBinaryFix: function () { needsBinaryFix() {
if (!fs.existsSync('/usr/local') || !fs.existsSync('/usr/local/bin')) { return !!(util.pathDoesNotExistOrDenied(util.binsPath()) || util.pathDoesNotExistOrDenied(util.dockerBinPath()) || util.pathDoesNotExistOrDenied(util.dockerMachineBinPath()));
return true;
}
if (fs.statSync('/usr/local/bin').gid !== 80 || fs.statSync('/usr/local/bin').uid !== process.getuid()) {
return true;
}
if (fs.existsSync('/usr/local/bin/docker') && (fs.statSync('/usr/local/bin/docker').gid !== 80 || fs.statSync('/usr/local/bin/docker').uid !== process.getuid())) {
return true;
}
if (fs.existsSync('/usr/local/bin/docker-machine') && (fs.statSync('/usr/local/bin/docker-machine').gid !== 80 || fs.statSync('/usr/local/bin/docker-machine').uid !== process.getuid())) {
return true;
}
return false;
}, },
copycmd: function (src, dest) { escapePath(str) {
return ['rm', '-f', dest, '&&', 'cp', src, dest];
},
escapePath: function (str) {
return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
}, },
shouldUpdateBinaries: function () { shouldUpdateBinaries() {
var packagejson = util.packagejson(); return !fs.existsSync(util.dockerBinPath()) ||
return !fs.existsSync('/usr/local/bin/docker') || !fs.existsSync(util.dockerMachineBinPath()) ||
!fs.existsSync('/usr/local/bin/docker-machine') || this.checksum(util.dockerMachineBinPath()) !== this.checksum(resources.docker_machine()) ||
this.checksum('/usr/local/bin/docker-machine') !== this.checksum(path.join(util.resourceDir(), 'docker-machine-' + packagejson['docker-machine-version'])) || this.checksum(util.dockerBinPath()) !== this.checksum(resources.docker());
this.checksum('/usr/local/bin/docker') !== this.checksum(path.join(util.resourceDir(), 'docker-' + packagejson['docker-version']));
}, },
copyBinariesCmd: function () { copyBinariesCmd: Promise.coroutine(function* () {
var packagejson = util.packagejson(); yield fs.mkdirs(util.binsPath());
var cmd = ['mkdir', '-p', '/usr/local/bin']; yield fs.copy(resources.docker_machine(), util.dockerMachineBinPath());
cmd.push('&&'); yield fs.copy(resources.docker(), util.dockerBinPath());
cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(util.resourceDir(), 'docker-machine-' + packagejson['docker-machine-version'])), '/usr/local/bin/docker-machine')); return Promise.resolve();
cmd.push('&&'); }),
cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(util.resourceDir(), 'docker-' + packagejson['docker-version'])), '/usr/local/bin/docker')); fixBinariesCmd: Promise.coroutine(function* () {
return cmd.join(' '); if(util.isWindows()) {
return;
}
yield fs.chown(util.binsPath(), process.getuid(), '80');
yield fs.chown(util.dockerBinPath(), process.getuid(), '80');
yield fs.chown(util.dockerMachineBinPath(), process.getuid(), '80');
return Promise.resolve();
}),
installVirtualBoxCmd: Promise.coroutine(function* () {
if(util.isWindows()) {
yield util.execProper(`powershell.exe -ExecutionPolicy unrestricted -Command "Start-Process \\\"${path.join(util.supportDir(), this.virtualBoxFileName())}\\\" -ArgumentList \\\"--silent --msiparams REBOOT=ReallySuppress\\\" -Verb runAs -Wait"`);
} else {
yield util.exec(this.macSudoCmd(`installer -pkg ${this.escapePath(path.join(util.supportDir(), this.virtualBoxFileName()))} -target /`));
}
return Promise.resolve();
}),
virtualBoxUrl() {
if(util.isWindows()) {
return 'http://download.virtualbox.org/virtualbox/4.3.26/VirtualBox-4.3.26-98988-Win.exe';
} else {
return `https://github.com/kitematic/virtualbox/releases/download/${util.packagejson()['virtualbox-version']}/${this.virtualBoxFileName()}`;
}
}, },
fixBinariesCmd: function () { macSudoCmd(cmd) {
var cmd = []; return `${this.escapePath(resources.macsudo())} -p "Kitematic requires administrative privileges to install." sh -c \"${cmd}\"`;
cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin')]);
cmd.push('&&');
cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin', 'docker-machine')]);
cmd.push('&&');
cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin', 'docker')]);
return cmd.join(' ');
}, },
installVirtualBoxCmd: function () { simulateProgress(estimateSeconds, progress) {
var packagejson = util.packagejson();
return `installer -pkg ${this.escapePath(path.join(util.supportDir(), packagejson['virtualbox-filename']))} -target /`;
},
virtualBoxUrl: function () {
var packagejson = util.packagejson();
return `https://github.com/kitematic/virtualbox/releases/download/${packagejson['virtualbox-version']}/${packagejson['virtualbox-filename']}`;
},
macSudoCmd: function (cmd) {
return `${this.escapePath(path.join(util.resourceDir(), 'macsudo'))} -p "Kitematic requires administrative privileges to install." sh -c \"${cmd}\"`;
},
simulateProgress: function (estimateSeconds, progress) {
var times = _.range(0, estimateSeconds * 1000, 200); var times = _.range(0, estimateSeconds * 1000, 200);
var timers = []; var timers = [];
_.each(times, time => { _.each(times, time => {
@ -78,10 +67,10 @@ var SetupUtil = {
timers.push(timer); timers.push(timer);
}); });
}, },
checksum: function (filename) { checksum(filename) {
return crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); return crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex');
}, },
download: function (url, filename, checksum, percentCallback) { download(url, filename, checksum, percentCallback) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (fs.existsSync(filename)) { if (fs.existsSync(filename)) {
var existingChecksum = this.checksum(filename); var existingChecksum = this.checksum(filename);
@ -109,7 +98,13 @@ var SetupUtil = {
}); });
}); });
}, },
compareVersions: function (v1, v2, options) { virtualBoxFileName() {
return util.isWindows() ? util.packagejson()['virtualbox-filename-win'] : util.packagejson()['virtualbox-filename'];
},
virtualBoxChecksum() {
return util.isWindows() ? util.packagejson()['virtualbox-checksum-win'] : util.packagejson()['virtualbox-checksum'];
},
compareVersions(v1, v2, options) {
var lexicographical = options && options.lexicographical, var lexicographical = options && options.lexicographical,
zeroExtend = options && options.zeroExtend, zeroExtend = options && options.zeroExtend,
v1parts = v1.split('.'), v1parts = v1.split('.'),

1
src/Startup.js Normal file
View File

@ -0,0 +1 @@
require('./Main');

View File

@ -1,10 +1,12 @@
var exec = require('exec'); var exec = require('exec');
var execProper = require('child_process').exec;
var Promise = require('bluebird'); var Promise = require('bluebird');
var fs = require('fs'); var fs = require('fs-promise');
var path = require('path'); var path = require('path');
var open = require('open');
module.exports = { module.exports = {
exec: function (args, options) { exec(args, options) {
options = options || {}; options = options || {};
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(args, options, (stderr, stdout, code) => { exec(args, options, (stderr, stdout, code) => {
@ -17,32 +19,64 @@ module.exports = {
}); });
}); });
}, },
home: function () { execProper(args, options) {
return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; options = options || {};
}, var cmd = Array.isArray(args) ? args.join(' ') : args;
supportDir: function () { return new Promise((resolve, reject) => {
var dirs = ['Library', 'Application\ Support', 'Kitematic']; execProper(cmd, options, (stderr, stdout, code) => {
var acc = process.env.HOME; if (code) {
dirs.forEach(function (d) { reject(new Error(cmd + ' returned non zero exit code\nstdout:' + stdout + '\nstderr:' + stderr));
acc = path.join(acc, d); } else {
if (!fs.existsSync(acc)) { resolve(stdout);
fs.mkdirSync(acc); }
} });
}); });
},
home() {
return process.env[this.isWindows() ? 'USERPROFILE' : 'HOME'];
},
binsPath() {
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
},
binsEnding() {
return this.isWindows() ? '.exe' : '';
},
dockerBinPath() {
return path.join(this.binsPath(), 'docker' + this.binsEnding());
},
dockerMachineBinPath() {
return path.join(this.binsPath(), 'docker-machine' + this.binsEnding());
},
pathDoesNotExistOrDenied(path) {
if(this.isWindows()) {
return (!fs.existsSync(path));
} else {
return (!fs.existsSync(path) || fs.statSync(path).gid !== 80 || fs.statSync(path).uid !== process.getuid());
}
},
openPathOrUrl(pathOrUrl, callback) {
open(pathOrUrl, callback);
},
supportDir() {
var acc = path.join(this.home(), 'Library', 'Application\ Support', 'Kitematic');
fs.mkdirsSync(acc);
return acc; return acc;
}, },
resourceDir: function () { packagejson() {
return process.env.RESOURCES_PATH;
},
packagejson: function () {
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
}, },
settingsjson: function () { settingsjson() {
var settingsjson = {}; var settingsjson = {};
try { try {
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
} catch (err) {} } catch (err) {}
return settingsjson; return settingsjson;
}, },
isWindows() {
return process.platform === 'win32';
},
CommandOrCtrl() {
return this.isWindows() ? 'Ctrl' : 'Command';
},
webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983'] webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983']
}; };

View File

@ -4,10 +4,18 @@ var Promise = require('bluebird');
var VirtualBox = { var VirtualBox = {
command: function () { command: function () {
return '/usr/bin/VBoxManage'; if(util.isWindows()) {
return 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe';
} else {
return '/usr/bin/VBoxManage';
}
}, },
installed: function () { installed: function () {
return fs.existsSync('/usr/bin/VBoxManage') && fs.existsSync('/Applications/VirtualBox.app'); if(util.isWindows()) {
return fs.existsSync('C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe') && fs.existsSync('C:\\Program Files\\Oracle\\VirtualBox\\VirtualBox.exe');
} else {
return fs.existsSync('/usr/bin/VBoxManage') && fs.existsSync('/Applications/VirtualBox.app');
}
}, },
version: function () { version: function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -26,14 +34,29 @@ var VirtualBox = {
} }
return util.exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} poweroff'); return util.exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} poweroff');
}, },
killall: function () { mountSharedDir: function (vmName, pathName, hostPath) {
return this.poweroffall().then(() => { if (!this.installed()) {
return util.exec(['pkill', 'VirtualBox']); return Promise.reject('VirtualBox not installed.');
}).then(() => { }
return util.exec(['pkill', 'VBox']);
}).catch(() => {
}); return util.exec([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']);
},
killall: function () {
if(util.isWindows()) {
return this.poweroffall().then(() => {
return util.exec(['powershell.exe', '\"get-process VBox* | stop-process\"']);
}).catch(() => {
});
} else {
return this.poweroffall().then(() => {
return util.exec(['pkill', 'VirtualBox']);
}).then(() => {
return util.exec(['pkill', 'VBox']);
}).catch(() => {
});
}
}, },
wake: function (name) { wake: function (name) {
return util.exec([this.command(), 'startvm', name, '--type', 'headless']); return util.exec([this.command(), 'startvm', name, '--type', 'headless']);

View File

@ -8,7 +8,14 @@ var path = require('path');
process.env.NODE_PATH = path.join(__dirname, '/../node_modules'); process.env.NODE_PATH = path.join(__dirname, '/../node_modules');
process.env.RESOURCES_PATH = path.join(__dirname, '/../resources'); process.env.RESOURCES_PATH = path.join(__dirname, '/../resources');
process.chdir(path.join(__dirname, '..')); process.chdir(path.join(__dirname, '..'));
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
if(process.platform === 'win32') {
process.env.PATH = process.env.PATH + ';' + process.env['USERPROFILE'] + '\\Kitematic-bins';
} else {
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
}
var size = {}, settingsjson = {}; var size = {}, settingsjson = {};
try { try {

28
util/deps.ps1 Normal file
View File

@ -0,0 +1,28 @@
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$BasePath = $dir + '\..\'
$packageJson = get-content ($BasePath + 'package.json')
[System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") > $null
$serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$packageJsonContent = $serializer.DeserializeObject($packageJson)
$webclient = New-Object System.Net.WebClient
$DOCKER_MACHINE_CLI_VERSION = $packageJsonContent['docker-machine-version']
$DOCKER_MACHINE_CLI_FILE = 'docker-machine-' + $DOCKER_MACHINE_CLI_VERSION + '.exe'
$DOCKER_CLI_VERSION = $packageJsonContent['docker-version']
$DOCKER_CLI_FILE = 'docker-' + $DOCKER_CLI_VERSION + '.exe'
if(-Not (test-path ($BasePath + '\resources\' + $DOCKER_CLI_FILE))) {
echo "-----> Downloading Docker CLI..."
$source = "https://master.dockerproject.com/windows/amd64/docker.exe"
$destination = $BasePath + "\resources\" + $DOCKER_CLI_FILE
$webclient.DownloadFile($source, $destination)
}
if(-Not (test-path ($BasePath + '\resources\' + $DOCKER_MACHINE_CLI_FILE))) {
echo "-----> Downloading Docker Machine CLI..."
$source = "https://github.com/docker/machine/releases/download/v0.1.0/docker-machine_windows-amd64.exe"
$destination = $BasePath + "\resources\" + $DOCKER_MACHINE_CLI_FILE
$webclient.DownloadFile($source, $destination)
}

15
util/reset.ps1 Normal file
View File

@ -0,0 +1,15 @@
get-process VBox* | stop-process
$paths = '~/Kitematic/', '~/.docker', '~/.VirtualBox/', '~/Kitematic-bins/', '~/Library/Application Support/Kitematic'
Foreach($path in $paths) {
if(test-path $path) {
Remove-Item $path -Force -Recurse
}
}
$virtualBoxApp = Get-WmiObject -Class Win32_Product | Where {$_.Name -Match 'VirtualBox'}
if($virtualBoxApp -ne $null) {
$virtualBoxApp.Uninstall()
}