From 046e640eeb98fb551a46ec9e40ccfa450d8cab59 Mon Sep 17 00:00:00 2001 From: Dominik Deren Date: Tue, 31 Mar 2015 10:38:05 +0100 Subject: [PATCH] - 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 - 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 --- .gitignore | 3 + __tests__/SetupStore-test.js | 19 +++-- appveyor.yml | 21 +++++ gulpfile.js | 65 ++++++++++++-- index.html | 2 +- package.json | 12 ++- src/ContainerDetailsSubheader.react.js | 7 +- src/ContainerHome.react.js | 2 +- src/ContainerHomeFolders.react.js | 3 +- src/ContainerHomePreview.react.js | 3 +- src/ContainerSettingsPorts.react.js | 3 +- src/ContainerSettingsVolumes.react.js | 3 +- src/ContainerStore.js | 12 ++- src/Containers.react.js | 2 +- src/Docker.js | 3 +- src/DockerMachine.js | 91 ++++++++++---------- src/MenuTemplate.js | 44 +++++----- src/Metrics.js | 9 +- src/Resources.js | 20 +++++ src/SetupStore.js | 55 ++++++++---- src/SetupUtil.js | 113 ++++++++++++------------- src/Startup.js | 1 + src/Util.js | 70 +++++++++++---- src/VirtualBox.js | 39 +++++++-- src/browser.js | 9 +- util/deps.ps1 | 28 ++++++ util/reset.ps1 | 15 ++++ 27 files changed, 455 insertions(+), 199 deletions(-) create mode 100644 appveyor.yml create mode 100644 src/Resources.js create mode 100644 src/Startup.js create mode 100644 util/deps.ps1 create mode 100644 util/reset.ps1 diff --git a/.gitignore b/.gitignore index 7b9e66caf1..c6befdab3f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ cache # Tests .test settings.json + +# IDEs +.idea \ No newline at end of file diff --git a/__tests__/SetupStore-test.js b/__tests__/SetupStore-test.js index 1410a883b0..b26471e2c8 100644 --- a/__tests__/SetupStore-test.js +++ b/__tests__/SetupStore-test.js @@ -12,7 +12,7 @@ describe('SetupStore', function () { pit('downloads virtualbox if it is not installed', function () { virtualBox.installed.mockReturnValue(false); setupUtil.download.mockReturnValue(Promise.resolve()); - util.packagejson.mockReturnValue({'virtualbox-filename': ''}); + setupUtil.virtualBoxFileName.mockReturnValue(''); util.supportDir.mockReturnValue(''); return setupStore.steps().download.run().then(() => { expect(setupUtil.download).toBeCalled(); @@ -24,7 +24,7 @@ describe('SetupStore', function () { virtualBox.version.mockReturnValue(Promise.resolve('4.3.16')); setupUtil.compareVersions.mockReturnValue(-1); setupUtil.download.mockReturnValue(Promise.resolve()); - util.packagejson.mockReturnValue({'virtualbox-filename': ''}); + setupUtil.virtualBoxFileName.mockReturnValue(''); util.supportDir.mockReturnValue(''); return setupStore.steps().download.run().then(() => { expect(setupUtil.download).toBeCalled(); @@ -34,10 +34,11 @@ describe('SetupStore', function () { describe('install step', function () { util.exec.mockReturnValue(Promise.resolve()); - setupUtil.copyBinariesCmd.mockReturnValue('copycmd'); - setupUtil.fixBinariesCmd.mockReturnValue('fixcmd'); + util.execProper.mockReturnValue(Promise.resolve()); + setupUtil.copyBinariesCmd.mockReturnValue(Promise.resolve()); + setupUtil.fixBinariesCmd.mockReturnValue(Promise.resolve()); virtualBox.killall.mockReturnValue(Promise.resolve()); - setupUtil.installVirtualBoxCmd.mockReturnValue('installvb'); + setupUtil.installVirtualBoxCmd.mockReturnValue(Promise.resolve()); setupUtil.macSudoCmd.mockImplementation(cmd => 'macsudo ' + cmd); pit('installs virtualbox if it is not installed', function () { @@ -45,7 +46,9 @@ describe('SetupStore', function () { util.exec.mockReturnValue(Promise.resolve()); return setupStore.steps().install.run().then(() => { 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.needsBinaryFix.mockReturnValue(true); 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(); }); }); }); diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..79ad0eb510 --- /dev/null +++ b/appveyor.yml @@ -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 \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 2836301e15..d08a68aa5a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,6 +2,7 @@ var babel = require('gulp-babel'); var changed = require('gulp-changed'); var concat = require('gulp-concat'); var cssmin = require('gulp-cssmin'); +var rename = require('gulp-rename'); var downloadatomshell = require('gulp-download-atom-shell'); var fs = require('fs'); 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')); }); -gulp.task('release', function () { - runSequence('download', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip'); +gulp.task('download-deps', function () { + 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('index.html', ['copy']); gulp.watch('styles/**/*.less', ['styles']); @@ -181,7 +227,14 @@ gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () var env = process.env; 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 + })); + } }); diff --git a/index.html b/index.html index 86d233b8db..2301b021e4 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,6 @@ Kitematic - + diff --git a/package.json b/package.json index c7f5153e58..e657c0d092 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,8 @@ "test": "jest", "release": "gulp release", "release:beta": "gulp release --beta", - "preinstall": "./util/deps", - "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", - "lint": "jsxhint src && jsxhint browser" + "lint": "jsxhint src && jsxhint browser", + "reset": "gulp reset" }, "licenses": [ { @@ -45,19 +44,25 @@ "atom-shell-version": "0.21.3", "virtualbox-version": "4.3.24", "virtualbox-filename": "VirtualBox-4.3.24.pkg", + "virtualbox-filename-win": "VirtualBox-4.3.26.exe", "virtualbox-checksum": "100eee21df3808fc1b1e461e86f10b6d2a748935c5f31bc665f4ff0777e44a0b", + "virtualbox-checksum-win": "9cb265babf307d825f5178693af95ffca077f80ae22cf43868c3538c159123ff", "dependencies": { "ansi-to-html": "0.3.0", + "any-promise": "^0.1.0", "async": "^0.9.0", "bluebird": "^2.9.12", "bugsnag-js": "^2.4.7", "dockerode": "^2.0.7", "exec": "0.2.0", + "fs-extra": "^0.17.0", + "fs-promise": "^0.3.1", "jquery": "^2.1.3", "minimist": "^1.1.0", "mixpanel": "0.0.20", "node-uuid": "^1.4.2", "object-assign": "^2.0.0", + "open": "0.0.5", "react": "^0.12.2", "react-bootstrap": "^0.15.1", "react-retina-image": "^1.1.2", @@ -80,6 +85,7 @@ "gulp-livereload": "^3.8.0", "gulp-plumber": "^0.6.6", "gulp-react": "^2.0.0", + "gulp-rename": "^1.2.0", "gulp-shell": "^0.3.0", "gulp-sourcemaps": "^1.5.0", "gulp-util": "^3.0.4", diff --git a/src/ContainerDetailsSubheader.react.js b/src/ContainerDetailsSubheader.react.js index 8b44ea8c62..98a5f6eb9b 100644 --- a/src/ContainerDetailsSubheader.react.js +++ b/src/ContainerDetailsSubheader.react.js @@ -10,6 +10,8 @@ var machine = require('./DockerMachine'); var RetinaImage = require('react-retina-image'); var Router = require('react-router'); var webPorts = require('./Util').webPorts; +var util = require('./Util'); +var resources = require('./Resources'); var ContainerDetailsSubheader = React.createClass({ mixins: [Router.State, Router.Navigation], @@ -89,7 +91,7 @@ var ContainerDetailsSubheader = React.createClass({ metrics.track('Opened In Browser', { 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; } }); } @@ -105,9 +107,8 @@ var ContainerDetailsSubheader = React.createClass({ if (!this.disableTerminal()) { metrics.track('Terminaled Into Container'); var container = this.props.container; - var terminal = path.join(process.cwd(), 'resources', 'terminal'); 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) { if (code) { console.log(stderr); diff --git a/src/ContainerHome.react.js b/src/ContainerHome.react.js index bb3c4655f1..0bb752b999 100644 --- a/src/ContainerHome.react.js +++ b/src/ContainerHome.react.js @@ -28,7 +28,7 @@ var ContainerHome = React.createClass({ resizeWindow(); }, handleErrorClick: function () { - util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); + util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new'); }, componentWillReceiveProps: function () { this.init(); diff --git a/src/ContainerHomeFolders.react.js b/src/ContainerHomeFolders.react.js index 513cc974b5..e20b95e1fd 100644 --- a/src/ContainerHomeFolders.react.js +++ b/src/ContainerHomeFolders.react.js @@ -5,6 +5,7 @@ var path = require('path'); var exec = require('exec'); var metrics = require('./Metrics'); var Router = require('react-router'); +var util = require('./Util'); var ContainerHomeFolder = React.createClass({ mixins: [Router.State, Router.Navigation], @@ -12,7 +13,7 @@ var ContainerHomeFolder = React.createClass({ metrics.track('Opened Volume Directory', { from: 'home' }); - exec(['open', path], function (err) { + util.openPathOrUrl(path, function (err) { if (err) { throw err; } }); }, diff --git a/src/ContainerHomePreview.react.js b/src/ContainerHomePreview.react.js index 9e0d21a108..91995c82ef 100644 --- a/src/ContainerHomePreview.react.js +++ b/src/ContainerHomePreview.react.js @@ -7,6 +7,7 @@ var Router = require('react-router'); var request = require('request'); var metrics = require('./Metrics'); var webPorts = require('./Util').webPorts; +var util = require('./Util'); var ContainerHomePreview = React.createClass({ mixins: [Router.State, Router.Navigation], @@ -61,7 +62,7 @@ var ContainerHomePreview = React.createClass({ metrics.track('Opened In Browser', { 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; } }); } diff --git a/src/ContainerSettingsPorts.react.js b/src/ContainerSettingsPorts.react.js index 64239a084d..fc9cf3b91b 100644 --- a/src/ContainerSettingsPorts.react.js +++ b/src/ContainerSettingsPorts.react.js @@ -6,6 +6,7 @@ var ContainerStore = require('./ContainerStore'); var ContainerUtil = require('./ContainerUtil'); var metrics = require('./Metrics'); var webPorts = require('./Util').webPorts; +var util = require('./Util'); var ContainerSettingsPorts = React.createClass({ mixins: [Router.State, Router.Navigation], @@ -38,7 +39,7 @@ var ContainerSettingsPorts = React.createClass({ metrics.track('Opened In Browser', { from: 'settings' }); - exec(['open', url], function (err) { + util.openPathOrUrl(url, function (err) { if (err) { throw err; } }); }, diff --git a/src/ContainerSettingsVolumes.react.js b/src/ContainerSettingsVolumes.react.js index 32e699449e..86d2aec584 100644 --- a/src/ContainerSettingsVolumes.react.js +++ b/src/ContainerSettingsVolumes.react.js @@ -6,6 +6,7 @@ var exec = require('exec'); var dialog = remote.require('dialog'); var metrics = require('./Metrics'); var ContainerStore = require('./ContainerStore'); +var util = require('./Util'); var ContainerSettingsVolumes = React.createClass({ mixins: [Router.State, Router.Navigation], @@ -35,7 +36,7 @@ var ContainerSettingsVolumes = React.createClass({ metrics.track('Opened Volume Directory', { from: 'settings' }); - exec(['open', path], function (err) { + util.openPathOrUrl(path, function (err) { if (err) { throw err; } }); }, diff --git a/src/ContainerStore.js b/src/ContainerStore.js index 121033316f..3ced6dbafb 100644 --- a/src/ContainerStore.js +++ b/src/ContainerStore.js @@ -104,7 +104,17 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), { return b.indexOf(':' + key) !== -1; }); 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); + } } }); } diff --git a/src/Containers.react.js b/src/Containers.react.js index af151dabca..e13a2def16 100644 --- a/src/Containers.react.js +++ b/src/Containers.react.js @@ -118,7 +118,7 @@ var Containers = React.createClass({ metrics.track('Opened Issue Reporter', { from: 'app' }); - util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); + util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new'); }, handleMouseEnterDockerTerminal: function () { this.setState({ diff --git a/src/Docker.js b/src/Docker.js index 36ad6c069e..443468798b 100644 --- a/src/Docker.js +++ b/src/Docker.js @@ -2,12 +2,13 @@ var fs = require('fs'); var path = require('path'); var dockerode = require('dockerode'); var Promise = require('bluebird'); +var util = require('./Util'); var Docker = { _host: null, _client: null, 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)) { return; } diff --git a/src/DockerMachine.js b/src/DockerMachine.js index 326d963574..5b3ab38a33 100644 --- a/src/DockerMachine.js +++ b/src/DockerMachine.js @@ -1,27 +1,21 @@ var _ = require('underscore'); var path = require('path'); var Promise = require('bluebird'); -var _ = require('underscore'); var fs = require('fs'); var util = require('./Util'); +var exec = require('child_process').exec; +var resources = require('./Resources'); var NAME = 'dev'; var DockerMachine = { - command: function () { - return path.join(process.cwd(), 'resources', 'docker-machine-' + this.version()); + command() { + return resources.docker_machine(); }, - name: function () { + name() { return NAME; }, - version: function () { - try { - return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))['docker-machine-version']; - } catch (err) { - return null; - } - }, - isoversion: function () { + isoversion() { try { var data = fs.readFileSync(path.join(util.home(), '.docker', 'machine', 'machines', NAME, 'boot2docker.iso'), 'utf8'); var match = data.match(/Boot2Docker-v(\d+\.\d+\.\d+)/); @@ -34,8 +28,8 @@ var DockerMachine = { return null; } }, - info: function () { - return util.exec([DockerMachine.command(), 'ls']).then(stdout => { + info() { + return util.exec([this.command(), 'ls']).then(stdout => { var lines = stdout.trim().split('\n').filter(line => line.indexOf('time=') === -1); var machines = {}; lines.slice(1, lines.length).forEach(line => { @@ -55,44 +49,44 @@ var DockerMachine = { } }); }, - exists: function () { - return DockerMachine.info().then(() => { + exists() { + return this.info().then(() => { return true; }).catch(() => { return false; }); }, - create: function () { - return util.exec([DockerMachine.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]); + create() { + return util.exec([this.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]); }, - start: function () { - return util.exec([DockerMachine.command(), 'start', NAME]); + start() { + return util.exec([this.command(), 'start', NAME]); }, - stop: function () { - return util.exec([DockerMachine.command(), 'stop', NAME]); + stop() { + return util.exec([this.command(), 'stop', NAME]); }, - upgrade: function () { - return util.exec([DockerMachine.command(), 'upgrade', NAME]); + upgrade() { + return util.exec([this.command(), 'upgrade', NAME]); }, - rm: function () { - return util.exec([DockerMachine.command(), 'rm', '-f', NAME]); + rm() { + return util.exec([this.command(), 'rm', '-f', NAME]); }, - ip: function () { - return util.exec([DockerMachine.command(), 'ip', NAME]).then(stdout => { + ip() { + return util.exec([this.command(), 'ip', NAME]).then(stdout => { return Promise.resolve(stdout.trim().replace('\n', '')); }); }, - regenerateCerts: function () { - return util.exec([DockerMachine.command(), 'tls-regenerate-certs', '-f', NAME]); + regenerateCerts() { + return util.exec([this.command(), 'tls-regenerate-certs', '-f', NAME]); }, - state: function () { - return DockerMachine.info().then(info => { + state() { + return this.info().then(info => { return info ? info.state : null; }); }, - disk: function () { - return util.exec([DockerMachine.command(), 'ssh', NAME, 'df']).then(stdout => { + disk() { + return util.exec([this.command(), 'ssh', NAME, 'df']).then(stdout => { try { var lines = stdout.split('\n'); 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 => { try { var lines = stdout.split('\n'); @@ -141,13 +135,13 @@ var DockerMachine = { } }); }, - stats: function () { - DockerMachine.state().then(state => { + stats() { + this.state().then(state => { if (state === 'Stopped') { return Promise.resolve({state: state}); } - var memory = DockerMachine.memory(); - var disk = DockerMachine.disk(); + var memory = this.memory(); + var disk = this.disk(); return Promise.all([memory, disk]).spread((memory, disk) => { return Promise.resolve({ memory: memory, @@ -156,13 +150,18 @@ var DockerMachine = { }); }); }, - dockerTerminal: function () { - var terminal = path.join(process.cwd(), 'resources', 'terminal'); - 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.exec(cmd).then(() => {}); - }); - }, + dockerTerminal() { + if(util.isWindows()) { + this.info().then(machine => { + 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`); + }); + } 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; diff --git a/src/MenuTemplate.js b/src/MenuTemplate.js index f81485e8ae..0c62b5a626 100644 --- a/src/MenuTemplate.js +++ b/src/MenuTemplate.js @@ -21,7 +21,7 @@ var MenuTemplate = function () { }, { label: 'Preferences', - accelerator: 'Command+,', + accelerator: util.CommandOrCtrl() + '+,', enabled: !!docker.host(), click: function () { metrics.track('Opened Preferences', { @@ -42,12 +42,12 @@ var MenuTemplate = function () { }, { label: 'Hide Kitematic', - accelerator: 'Command+H', + accelerator: util.CommandOrCtrl() + '+H', selector: 'hide:' }, { label: 'Hide Others', - accelerator: 'Command+Shift+H', + accelerator: util.CommandOrCtrl() + '+Shift+H', selector: 'hideOtherApplications:' }, { @@ -59,11 +59,11 @@ var MenuTemplate = function () { }, { label: 'Quit', - accelerator: 'Command+Q', + accelerator: util.CommandOrCtrl() + '+Q', click: function() { app.quit(); } - }, + } ] }, { @@ -74,7 +74,7 @@ var MenuTemplate = function () { }, { label: 'Open Docker Command Line Terminal', - accelerator: 'Command+Shift+T', + accelerator: util.CommandOrCtrl() + '+Shift+T', enabled: !!docker.host(), click: function() { metrics.track('Opened Docker Terminal', { @@ -82,7 +82,7 @@ var MenuTemplate = function () { }); machine.dockerTerminal(); } - }, + } ] }, { @@ -90,12 +90,12 @@ var MenuTemplate = function () { submenu: [ { label: 'Undo', - accelerator: 'Command+Z', + accelerator: util.CommandOrCtrl() + '+Z', selector: 'undo:' }, { label: 'Redo', - accelerator: 'Shift+Command+Z', + accelerator: 'Shift+' + util.CommandOrCtrl() + '+Z', selector: 'redo:' }, { @@ -103,24 +103,24 @@ var MenuTemplate = function () { }, { label: 'Cut', - accelerator: 'Command+X', + accelerator: util.CommandOrCtrl() + '+X', selector: 'cut:' }, { label: 'Copy', - accelerator: 'Command+C', + accelerator: util.CommandOrCtrl() + '+C', selector: 'copy:' }, { label: 'Paste', - accelerator: 'Command+V', + accelerator: util.CommandOrCtrl() + '+V', selector: 'paste:' }, { label: 'Select All', - accelerator: 'Command+A', + accelerator: util.CommandOrCtrl() + '+A', selector: 'selectAll:' - }, + } ] }, { @@ -128,9 +128,9 @@ var MenuTemplate = function () { submenu: [ { label: 'Toggle DevTools', - accelerator: 'Alt+Command+I', + accelerator: 'Alt+' + util.CommandOrCtrl() + '+I', click: function() { remote.getCurrentWindow().toggleDevTools(); } - }, + } ] }, { @@ -138,12 +138,12 @@ var MenuTemplate = function () { submenu: [ { label: 'Minimize', - accelerator: 'Command+M', + accelerator: util.CommandOrCtrl() + '+M', selector: 'performMiniaturize:' }, { label: 'Close', - accelerator: 'Command+W', + accelerator: util.CommandOrCtrl() + '+W', click: function () { remote.getCurrentWindow().hide(); } @@ -154,7 +154,7 @@ var MenuTemplate = function () { { label: 'Bring All to Front', selector: 'arrangeInFront:' - }, + } ] }, { @@ -166,11 +166,11 @@ var MenuTemplate = function () { metrics.track('Opened Issue Reporter', { from: 'menu' }); - util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); + util.openPathOrUrl('https://github.com/kitematic/kitematic/issues/new'); } - }, + } ] - }, + } ]; }; diff --git a/src/Metrics.js b/src/Metrics.js index 3447f0f577..8b19edfbd0 100644 --- a/src/Metrics.js +++ b/src/Metrics.js @@ -45,7 +45,14 @@ var Metrics = { 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({ distinct_id: id, version: util.packagejson().version, diff --git a/src/Resources.js b/src/Resources.js new file mode 100644 index 0000000000..f92db66737 --- /dev/null +++ b/src/Resources.js @@ -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()); + } +}; \ No newline at end of file diff --git a/src/SetupStore.js b/src/SetupStore.js index 981b007ae1..87acb0189a 100644 --- a/src/SetupStore.js +++ b/src/SetupStore.js @@ -26,8 +26,7 @@ var _steps = [{ totalPercent: 35, percent: 0, run: function (progressCallback) { - var packagejson = util.packagejson(); - return setupUtil.download(setupUtil.virtualBoxUrl(), path.join(util.supportDir(), packagejson['virtualbox-filename']), packagejson['virtualbox-checksum'], percent => { + return setupUtil.download(setupUtil.virtualBoxUrl(), path.join(util.supportDir(), setupUtil.virtualBoxFileName()), setupUtil.virtualBoxChecksum(), percent => { progressCallback(percent); }); } @@ -39,21 +38,20 @@ var _steps = [{ percent: 0, seconds: 5, run: Promise.coroutine(function* (progressCallback) { - var cmd = setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd(); + yield setupUtil.copyBinariesCmd(); + yield setupUtil.fixBinariesCmd(); + if (!virtualBox.installed()) { yield virtualBox.killall(); - cmd += ' && ' + setupUtil.installVirtualBoxCmd(); - } else { - if (!setupUtil.needsBinaryFix()) { - return; + try { + progressCallback(50); // TODO: detect when the installation has started so we can simulate progress + yield setupUtil.installVirtualBoxCmd(); + } catch (err) { + throw null; } } - try { - progressCallback(50); // TODO: detect when the installation has started so we can simulate progress - yield util.exec(setupUtil.macSudoCmd(cmd)); - } catch (err) { - throw null; - } + + return; }) }, { name: 'init', @@ -70,9 +68,33 @@ var _steps = [{ try { yield machine.rm(); 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) { rimraf.sync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name())); 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; } @@ -156,9 +178,10 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { var packagejson = util.packagejson(); var isoversion = machine.isoversion(); var required = {}; - var vboxfile = path.join(util.supportDir(), packagejson['virtualbox-filename']); + var vboxfile = path.join(util.supportDir(), setupUtil.virtualBoxFileName()); 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.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 { message: 'Machine IP could not be fetched. Please retry the setup. If this fails please file a ticket on our GitHub repo.', machine: yield machine.info(), - ip: ip, + ip: ip }; } console.log('Finished Steps'); diff --git a/src/SetupUtil.js b/src/SetupUtil.js index 95fe6c0b5f..9dfed3c7dc 100644 --- a/src/SetupUtil.js +++ b/src/SetupUtil.js @@ -1,74 +1,63 @@ var _ = require('underscore'); var crypto = require('crypto'); -var fs = require('fs'); +var fs = require('fs-promise'); var path = require('path'); var request = require('request'); var progress = require('request-progress'); var Promise = require('bluebird'); var util = require('./Util'); +var resources = require('./Resources'); var SetupUtil = { - needsBinaryFix: function () { - if (!fs.existsSync('/usr/local') || !fs.existsSync('/usr/local/bin')) { - 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; + needsBinaryFix() { + return !!(util.pathDoesNotExistOrDenied(util.binsPath()) || util.pathDoesNotExistOrDenied(util.dockerBinPath()) || util.pathDoesNotExistOrDenied(util.dockerMachineBinPath())); }, - copycmd: function (src, dest) { - return ['rm', '-f', dest, '&&', 'cp', src, dest]; - }, - escapePath: function (str) { + escapePath(str) { return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); }, - shouldUpdateBinaries: function () { - var packagejson = util.packagejson(); - return !fs.existsSync('/usr/local/bin/docker') || - !fs.existsSync('/usr/local/bin/docker-machine') || - this.checksum('/usr/local/bin/docker-machine') !== this.checksum(path.join(util.resourceDir(), 'docker-machine-' + packagejson['docker-machine-version'])) || - this.checksum('/usr/local/bin/docker') !== this.checksum(path.join(util.resourceDir(), 'docker-' + packagejson['docker-version'])); + shouldUpdateBinaries() { + return !fs.existsSync(util.dockerBinPath()) || + !fs.existsSync(util.dockerMachineBinPath()) || + this.checksum(util.dockerMachineBinPath()) !== this.checksum(resources.docker_machine()) || + this.checksum(util.dockerBinPath()) !== this.checksum(resources.docker()); + }, - copyBinariesCmd: function () { - var packagejson = util.packagejson(); - var cmd = ['mkdir', '-p', '/usr/local/bin']; - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(util.resourceDir(), 'docker-machine-' + packagejson['docker-machine-version'])), '/usr/local/bin/docker-machine')); - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(util.resourceDir(), 'docker-' + packagejson['docker-version'])), '/usr/local/bin/docker')); - return cmd.join(' '); + copyBinariesCmd: Promise.coroutine(function* () { + yield fs.mkdirs(util.binsPath()); + yield fs.copy(resources.docker_machine(), util.dockerMachineBinPath()); + yield fs.copy(resources.docker(), util.dockerBinPath()); + return Promise.resolve(); + }), + fixBinariesCmd: Promise.coroutine(function* () { + 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 () { - var 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(' '); + macSudoCmd(cmd) { + return `${this.escapePath(resources.macsudo())} -p "Kitematic requires administrative privileges to install." sh -c \"${cmd}\"`; }, - installVirtualBoxCmd: function () { - 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) { + simulateProgress(estimateSeconds, progress) { var times = _.range(0, estimateSeconds * 1000, 200); var timers = []; _.each(times, time => { @@ -78,10 +67,10 @@ var SetupUtil = { timers.push(timer); }); }, - checksum: function (filename) { + checksum(filename) { 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) => { if (fs.existsSync(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, zeroExtend = options && options.zeroExtend, v1parts = v1.split('.'), diff --git a/src/Startup.js b/src/Startup.js new file mode 100644 index 0000000000..03f16bf78f --- /dev/null +++ b/src/Startup.js @@ -0,0 +1 @@ +require('./Main'); \ No newline at end of file diff --git a/src/Util.js b/src/Util.js index a9ab1b2e0d..640f641a82 100644 --- a/src/Util.js +++ b/src/Util.js @@ -1,10 +1,12 @@ var exec = require('exec'); +var execProper = require('child_process').exec; var Promise = require('bluebird'); -var fs = require('fs'); +var fs = require('fs-promise'); var path = require('path'); +var open = require('open'); module.exports = { - exec: function (args, options) { + exec(args, options) { options = options || {}; return new Promise((resolve, reject) => { exec(args, options, (stderr, stdout, code) => { @@ -17,32 +19,64 @@ module.exports = { }); }); }, - home: function () { - return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; - }, - supportDir: function () { - var dirs = ['Library', 'Application\ Support', 'Kitematic']; - var acc = process.env.HOME; - dirs.forEach(function (d) { - acc = path.join(acc, d); - if (!fs.existsSync(acc)) { - fs.mkdirSync(acc); - } + execProper(args, options) { + options = options || {}; + var cmd = Array.isArray(args) ? args.join(' ') : args; + return new Promise((resolve, reject) => { + execProper(cmd, options, (stderr, stdout, code) => { + if (code) { + reject(new Error(cmd + ' returned non zero exit code\nstdout:' + stdout + '\nstderr:' + stderr)); + } else { + resolve(stdout); + } + }); }); + }, + 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; }, - resourceDir: function () { - return process.env.RESOURCES_PATH; - }, - packagejson: function () { + packagejson() { return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); }, - settingsjson: function () { + settingsjson() { var settingsjson = {}; try { settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); } catch (err) {} return settingsjson; }, + isWindows() { + return process.platform === 'win32'; + }, + CommandOrCtrl() { + return this.isWindows() ? 'Ctrl' : 'Command'; + }, webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983'] }; diff --git a/src/VirtualBox.js b/src/VirtualBox.js index 43e550dc7c..bf115ebb57 100644 --- a/src/VirtualBox.js +++ b/src/VirtualBox.js @@ -4,10 +4,18 @@ var Promise = require('bluebird'); var VirtualBox = { command: function () { - return '/usr/bin/VBoxManage'; + if(util.isWindows()) { + return 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe'; + } else { + return '/usr/bin/VBoxManage'; + } }, 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 () { 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'); }, + mountSharedDir: function (vmName, pathName, hostPath) { + if (!this.installed()) { + return Promise.reject('VirtualBox not installed.'); + } + + return util.exec([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']); + }, killall: function () { - return this.poweroffall().then(() => { - return util.exec(['pkill', 'VirtualBox']); - }).then(() => { - return util.exec(['pkill', 'VBox']); - }).catch(() => { + 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) { return util.exec([this.command(), 'startvm', name, '--type', 'headless']); diff --git a/src/browser.js b/src/browser.js index 8722673dcb..ff75964d79 100644 --- a/src/browser.js +++ b/src/browser.js @@ -8,7 +8,14 @@ var path = require('path'); process.env.NODE_PATH = path.join(__dirname, '/../node_modules'); process.env.RESOURCES_PATH = path.join(__dirname, '/../resources'); 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 = {}; try { diff --git a/util/deps.ps1 b/util/deps.ps1 new file mode 100644 index 0000000000..caf5f198ce --- /dev/null +++ b/util/deps.ps1 @@ -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) +} \ No newline at end of file diff --git a/util/reset.ps1 b/util/reset.ps1 new file mode 100644 index 0000000000..5d2084a42d --- /dev/null +++ b/util/reset.ps1 @@ -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() +} \ No newline at end of file