diff --git a/.gitignore b/.gitignore index e8be61e269..689e2c608d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ npm-debug.log identity # Resources -resources/boot2docker-* resources/docker-* # Cache diff --git a/__tests__/Boot2Docker-test.js b/__tests__/Boot2Docker-test.js deleted file mode 100644 index 5d9482128c..0000000000 --- a/__tests__/Boot2Docker-test.js +++ /dev/null @@ -1,29 +0,0 @@ -jest.dontMock('../src/Boot2Docker'); -var boot2docker = require('../src/Boot2Docker'); - -var fs = require('fs'); -var util = require('../src/Util'); -var Promise = require('bluebird'); - -describe('Boot2Docker', () => { - it('iso version parsed correctly', function () { - fs.readFileSync.mockReturnValueOnce('9adjaldijaslkjd123Boot2Docker-v1.4.1aisudhha82jj123'); - expect(boot2docker.isoversion()).toBe('1.4.1'); - }); - - pit('should exist if status command succeeds', function () { - util.exec.mockReturnValueOnce(Promise.resolve(true)); - return boot2docker.exists().then(exists => { - expect(util.exec).toBeCalledWith([boot2docker.command(), 'status']); - expect(exists).toBe(true); - }); - }); - - pit('should not exist if status command fails', function () { - util.exec.mockReturnValueOnce(Promise.reject(false)); - return boot2docker.exists().then(exists => { - expect(util.exec).toBeCalledWith([boot2docker.command(), 'status']); - expect(exists).toBe(false); - }); - }); -}); diff --git a/__tests__/SetupStore-test.js b/__tests__/SetupStore-test.js index 4b9f73238b..e0a393bfb1 100644 --- a/__tests__/SetupStore-test.js +++ b/__tests__/SetupStore-test.js @@ -2,7 +2,7 @@ jest.dontMock('../src/SetupStore'); var setupStore = require('../src/SetupStore'); var virtualBox = require('../src/VirtualBox'); var util = require('../src/Util'); -var boot2docker = require('../src/Boot2Docker'); +var machine = require('../src/DockerMachine'); var setupUtil = require('../src/SetupUtil'); var Promise = require('bluebird'); @@ -12,6 +12,8 @@ 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': ''}); + util.supportDir.mockReturnValue(''); return setupStore.steps().download.run().then(() => { expect(setupUtil.download).toBeCalled(); }); @@ -22,6 +24,8 @@ 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': ''}); + util.supportDir.mockReturnValue(''); return setupStore.steps().download.run().then(() => { expect(setupUtil.download).toBeCalled(); }); @@ -30,8 +34,8 @@ describe('SetupStore', function () { describe('install step', function () { util.exec.mockReturnValue(Promise.resolve()); - util.copyBinariesCmd.mockReturnValue('copycmd'); - util.fixBinariesCmd.mockReturnValue('fixcmd'); + setupUtil.copyBinariesCmd.mockReturnValue('copycmd'); + setupUtil.fixBinariesCmd.mockReturnValue('fixcmd'); virtualBox.killall.mockReturnValue(Promise.resolve()); setupUtil.installVirtualBoxCmd.mockReturnValue('installvb'); setupUtil.macSudoCmd.mockImplementation(cmd => 'macsudo ' + cmd); @@ -45,17 +49,6 @@ describe('SetupStore', function () { }); }); - pit('installs virtualbox if it is installed but has an outdated version', function () { - virtualBox.installed.mockReturnValue(true); - virtualBox.version.mockReturnValue(Promise.resolve('4.3.16')); - setupUtil.compareVersions.mockReturnValue(-1); - util.exec.mockReturnValue(Promise.resolve()); - return setupStore.steps().install.run().then(() => { - expect(virtualBox.killall).toBeCalled(); - expect(util.exec).toBeCalledWith('macsudo copycmd && fixcmd && installvbcmd'); - }); - }); - pit('only installs binaries if virtualbox is installed', function () { virtualBox.installed.mockReturnValue(true); setupUtil.compareVersions.mockReturnValue(0); @@ -69,35 +62,30 @@ describe('SetupStore', function () { describe('init step', function () { virtualBox.vmdestroy.mockReturnValue(Promise.resolve()); pit('inintializes the boot2docker vm if it does not exist', function () { - boot2docker.exists.mockReturnValue(Promise.resolve(false)); - boot2docker.init.mockReturnValue(Promise.resolve()); + machine.exists.mockReturnValue(Promise.resolve(false)); + machine.create.mockReturnValue(Promise.resolve()); return setupStore.steps().init.run().then(() => { - expect(boot2docker.init).toBeCalled(); + expect(machine.create).toBeCalled(); }); }); - pit('upgrades the boot2docker vm if it exists and is out of date', function () { - boot2docker.exists.mockReturnValue(Promise.resolve(true)); - boot2docker.isoversion.mockReturnValue('1.0'); - boot2docker.haskeys.mockReturnValue(true); - boot2docker.stop.mockReturnValue(Promise.resolve()); - boot2docker.upgrade.mockReturnValue(Promise.resolve()); + pit('upgrades the vm if it exists and is out of date', function () { + machine.exists.mockReturnValue(Promise.resolve(true)); + machine.state.mockReturnValue(Promise.resolve('Running')); + machine.isoversion.mockReturnValue('1.0'); + machine.stop.mockReturnValue(Promise.resolve()); + machine.start.mockReturnValue(Promise.resolve()); + machine.upgrade.mockReturnValue(Promise.resolve()); setupUtil.compareVersions.mockReturnValue(-1); - return setupStore.steps().init.run().then(() => { - boot2docker.init.mockClear(); - expect(boot2docker.init).not.toBeCalled(); - expect(boot2docker.upgrade).toBeCalled(); - }); - }); - }); - - describe('start step', function () { - pit('starts the boot2docker vm if it is not running', function () { - boot2docker.status.mockReturnValue(false); - boot2docker.waitstatus.mockReturnValue(Promise.resolve()); - boot2docker.start.mockReturnValue(Promise.resolve()); - return setupStore.steps().start.run().then(() => { - expect(boot2docker.start).toBeCalled(); + machine.create.mockClear(); + machine.upgrade.mockClear(); + machine.stop.mockClear(); + machine.start.mockClear(); + return setupStore.steps().init.run(() => {}).then(() => { + expect(machine.create).not.toBeCalled(); + expect(machine.stop).toBeCalled(); + expect(machine.start).toBeCalled(); + expect(machine.upgrade).toBeCalled(); }); }); }); diff --git a/boot2docker- b/boot2docker- deleted file mode 100755 index 59f0bef01e..0000000000 --- a/boot2docker- +++ /dev/null @@ -1 +0,0 @@ -{"error":"Not Found"} \ No newline at end of file diff --git a/browser/main.js b/browser/main.js index 2f029fa8da..9a7fb94fe9 100644 --- a/browser/main.js +++ b/browser/main.js @@ -26,7 +26,7 @@ var windowOptions = { 'min-height': 700, resizable: true, frame: false, - show: true + show: false }; app.on('activate-with-no-open-windows', function () { @@ -38,14 +38,14 @@ app.on('activate-with-no-open-windows', function () { app.on('ready', function() { mainWindow = new BrowserWindow(windowOptions); - var saveVMOnQuit = false; + var closeVMOnQuit = false; if (argv.test) { mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/tests.html'))); } else { mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/index.html'))); app.on('will-quit', function () { - if (saveVMOnQuit) { - exec('/usr/bin/VBoxManage controlvm boot2docker-vm savestate', function () {}); + if (closeVMOnQuit) { + exec('/usr/bin/VBoxManage controlvm dev poweroff', function () {}); } }); } @@ -97,14 +97,14 @@ app.on('ready', function() { ipc.on('command', function (event, arg) { console.log('Command: ' + arg); if (arg === 'application:quit-install') { - saveVMOnQuit = false; + closeVMOnQuit = false; autoUpdater.quitAndInstall(); } }); } ipc.on('vm', function (event, arg) { - saveVMOnQuit = arg; + closeVMOnQuit = arg; }); }); }); diff --git a/gulpfile.js b/gulpfile.js index 8b46023602..4aebeead54 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -30,9 +30,11 @@ settings.beta = isBeta; var options = { dev: process.argv.indexOf('release') === -1, beta: isBeta, - filename: isBeta ? 'Kitematic (Beta).app' : 'Kitematic.app', - name: isBeta ? 'Kitematic (Beta)' : 'Kitematic', - icon: isBeta ? './util/kitematic-beta.icns' : './util/kitematic.icns' + appFilename: isBeta ? 'Kitematic (Beta).app' : 'Kitematic.app', + appName: isBeta ? 'Kitematic (Beta)' : 'Kitematic', + name: 'Kitematic', + icon: isBeta ? './util/kitematic-beta.icns' : './util/kitematic.icns', + bundle: 'com.kitemaic.app' }; gulp.task('js', function () { @@ -46,14 +48,14 @@ gulp.task('js', function () { .pipe(react()) .pipe(babel({blacklist: ['regenerator']})) .pipe(gulpif(options.dev, sourcemaps.write('.'))) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); gulp.task('images', function() { return gulp.src('images/*') .pipe(gulpif(options.dev, changed('./build'))) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); @@ -68,7 +70,7 @@ gulp.task('styles', function () { .pipe(gulpif(options.dev, sourcemaps.init())) .pipe(less()) .pipe(gulpif(options.dev, sourcemaps.write())) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build')) .pipe(gulpif(!options.dev, cssmin())) .pipe(concat('main.css')) .pipe(gulpif(options.dev, livereload())); @@ -83,12 +85,12 @@ gulp.task('download', function (cb) { gulp.task('copy', function () { gulp.src('index.html') - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); gulp.src('fonts/**') .pipe(gulpif(options.dev, changed('./build'))) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build')) .pipe(gulpif(options.dev, livereload())); }); @@ -112,10 +114,10 @@ gulp.task('dist', function () { '/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist' ], { templateData: { - filename: options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), - name: options.name.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), + filename: options.appFilename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), + name: options.appName.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), version: packagejson.version, - bundle: 'com.kitematic.app', + bundle: options.bundle, icon: options.icon } })); @@ -125,7 +127,7 @@ gulp.task('dist', function () { 'cp -R node_modules/' + d + ' dist/osx/<%= filename %>/Contents/Resources/app/node_modules/' ], { templateData: { - filename: options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + filename: options.appFilename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') } })); }); @@ -137,7 +139,7 @@ gulp.task('sign', function () { try { var signing_identity = fs.readFileSync('./identity', 'utf8').trim(); return gulp.src('').pipe(shell([ - 'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + 'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.appFilename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') ], { cwd: './dist/osx/' })); @@ -148,7 +150,7 @@ gulp.task('sign', function () { gulp.task('zip', function () { return gulp.src('').pipe(shell([ - 'ditto -c -k --sequesterRsrc --keepParent ' + options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + ' ' + options.name.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + '-' + packagejson.version + '.zip' + 'ditto -c -k --sequesterRsrc --keepParent ' + options.appFilename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + ' ' + options.name.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + '-' + packagejson.version + '.zip' ], { cwd: './dist/osx/' })); @@ -163,7 +165,7 @@ gulp.task('settings', function () { }; return src; }; - string_src('settings.json', JSON.stringify(settings)).pipe(gulp.dest('dist/osx/' + options.filename.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 () { @@ -171,12 +173,13 @@ gulp.task('release', function () { }); gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () { - livereload.listen(); - gulp.watch('src/**/*.js', ['js', function () {livereload();}]); + gulp.watch('src/**/*.js', ['js']); gulp.watch('index.html', ['copy']); gulp.watch('styles/**/*.less', ['styles']); gulp.watch('images/**', ['images']); + livereload.listen(); + var env = process.env; env.NODE_ENV = 'development'; gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom .'], { diff --git a/images/button-restart.png b/images/button-restart.png index 929fbcee64..69f030d3ec 100644 Binary files a/images/button-restart.png and b/images/button-restart.png differ diff --git a/images/button-restart@2x.png b/images/button-restart@2x.png index c754a825c6..c710010d35 100644 Binary files a/images/button-restart@2x.png and b/images/button-restart@2x.png differ diff --git a/images/button-terminal.png b/images/button-terminal.png index a91a88a80a..e64a22aa5e 100644 Binary files a/images/button-terminal.png and b/images/button-terminal.png differ diff --git a/images/button-terminal@2x.png b/images/button-terminal@2x.png index 85ef53a825..be2c92fa24 100644 Binary files a/images/button-terminal@2x.png and b/images/button-terminal@2x.png differ diff --git a/images/button-view.png b/images/button-view.png index f1c7a12b0c..4d67f928f4 100644 Binary files a/images/button-view.png and b/images/button-view.png differ diff --git a/images/button-view@2x.png b/images/button-view@2x.png index 3ec758f454..a445545558 100644 Binary files a/images/button-view@2x.png and b/images/button-view@2x.png differ diff --git a/package.json b/package.json index 3c1728fa16..234f3fe94a 100644 --- a/package.json +++ b/package.json @@ -40,50 +40,50 @@ ] }, "docker-version": "1.5.0", - "boot2docker-version": "1.5.0", - "atom-shell-version": "0.21.1", + "docker-machine-version": "0.1.0", + "atom-shell-version": "0.21.2", "virtualbox-version": "4.3.22", "virtualbox-filename": "VirtualBox-4.3.22.pkg", "virtualbox-checksum": "4a7dff25bdeef0d112e16ac11bee6d52e856d36bb412aa75576036ba560082eb", "virtualbox-required-version": "4.3.0", "dependencies": { - "ansi-to-html": "0.2.0", + "ansi-to-html": "0.3.0", "async": "^0.9.0", - "bluebird": "^2.9.6", + "bluebird": "^2.9.12", "bugsnag-js": "^2.4.7", "dockerode": "^2.0.7", - "exec": "0.1.2", + "exec": "0.2.0", "jquery": "^2.1.3", "minimist": "^1.1.0", "mixpanel": "0.0.20", "node-uuid": "^1.4.2", "object-assign": "^2.0.0", "react": "^0.12.2", - "react-bootstrap": "^0.13.2", + "react-bootstrap": "^0.15.1", "react-retina-image": "^1.1.2", - "react-router": "^0.11.6", + "react-router": "^0.12.4", "request": "^2.53.0", "request-progress": "^0.3.1", "rimraf": "^2.2.8", - "underscore": "^1.7.0" + "underscore": "^1.8.2" }, "devDependencies": { - "babel": "^4.0.1", - "gulp": "^3.8.10", + "babel": "^4.5.5", + "gulp": "^3.8.11", "gulp-babel": "^4.0.0", "gulp-changed": "^1.1.1", - "gulp-concat": "^2.3.4", + "gulp-concat": "^2.5.2", "gulp-cssmin": "^0.1.6", "gulp-download-atom-shell": "0.0.4", - "gulp-if": "^1.2.4", - "gulp-less": "^2.0.1", - "gulp-livereload": "^2.1.1", + "gulp-if": "^1.2.5", + "gulp-less": "^3.0.1", + "gulp-livereload": "^3.8.0", "gulp-plumber": "^0.6.6", "gulp-react": "^2.0.0", - "gulp-shell": "^0.2.11", - "gulp-sourcemaps": "^1.2.8", - "gulp-util": "^3.0.0", - "jest-cli": "^0.2.2", + "gulp-shell": "^0.3.0", + "gulp-sourcemaps": "^1.5.0", + "gulp-util": "^3.0.4", + "jest-cli": "^0.4.0", "jsxhint": "^0.12.1", "react-tools": "^0.12.2", "run-sequence": "^1.0.2" diff --git a/src/ContainerDetailsSubheader.react.js b/src/ContainerDetailsSubheader.react.js index 4b69edc99d..7ea6453a98 100644 --- a/src/ContainerDetailsSubheader.react.js +++ b/src/ContainerDetailsSubheader.react.js @@ -6,7 +6,7 @@ var path = require('path'); var metrics = require('./Metrics'); var ContainerStore = require('./ContainerStore'); var ContainerUtil = require('./ContainerUtil'); -var boot2docker = require('./Boot2Docker'); +var machine = require('./DockerMachine'); var RetinaImage = require('react-retina-image'); var Router = require('react-router'); var webPorts = require('./Util').webPorts; @@ -105,11 +105,13 @@ var ContainerDetailsSubheader = React.createClass({ metrics.track('Terminaled Into Container'); var container = this.props.container; var terminal = path.join(process.cwd(), 'resources', 'terminal'); - var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh']; - exec(cmd, function (stderr, stdout, code) { - if (code) { - console.log(stderr); - } + machine.ip().then(ip => { + var cmd = [terminal, 'ssh', '-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/ContainerStore.js b/src/ContainerStore.js index 2231c3736f..25fdce97b3 100644 --- a/src/ContainerStore.js +++ b/src/ContainerStore.js @@ -119,10 +119,15 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), { if (containerData.State && !containerData.State.Running) { self.fetchContainer(containerData.name, callback); } else { - container.start({ - PublishAllPorts: true, + var startopts = { Binds: binds - }, function (err) { + }; + if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) { + startopts.PortBindings = containerData.NetworkSettings.Ports; + } else{ + startopts.PublishAllPorts = true; + } + container.start(startopts, function (err) { if (err) { callback(err); return; @@ -140,8 +145,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), { var count = 1; var name = base; while (true) { - var exists = _.findWhere(_.values(_containers), {Name: name}) || _.findWhere(_.values(_containers), {Name: name}); - if (!exists) { + if (!this.containers()[name]) { return name; } else { count++; diff --git a/src/ContainerUtil.js b/src/ContainerUtil.js index 9fef385b68..bae761d0cc 100644 --- a/src/ContainerUtil.js +++ b/src/ContainerUtil.js @@ -17,7 +17,7 @@ var ContainerUtil = { return {}; } var res = {}; - var ip = docker.host; + var ip = docker.host(); _.each(container.NetworkSettings.Ports, function (value, key) { var dockerPort = key.split('/')[0]; var localUrl = null; diff --git a/src/Containers.react.js b/src/Containers.react.js index 21bf5c4f28..b34264fa50 100644 --- a/src/Containers.react.js +++ b/src/Containers.react.js @@ -9,8 +9,7 @@ var remote = require('remote'); var metrics = require('./Metrics'); var autoUpdater = remote.require('auto-updater'); var RetinaImage = require('react-retina-image'); -var path = require('path'); -var docker = require('./Docker'); +var machine = require('./DockerMachine'); var util = require('./Util'); var Containers = React.createClass({ @@ -94,19 +93,26 @@ var Containers = React.createClass({ ipc.send('command', 'application:quit-install'); }, handleClickPreferences: function () { + metrics.track('Opened Preferences', { + from: 'app' + }); this.transitionTo('preferences'); }, handleClickDockerTerminal: function () { - var terminal = path.join(process.cwd(), 'resources', 'terminal'); - var cmd = [terminal, `DOCKER_HOST=${'tcp://' + docker.host + ':2376'} DOCKER_CERT_PATH=${path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.boot2docker/certs/boot2docker-vm')} DOCKER_TLS_VERIFY=1 $SHELL`]; - util.exec(cmd).then(() => {}); + metrics.track('Opened Docker Terminal', { + from: 'app' + }); + machine.dockerTerminal(); }, handleClickReportIssue: function () { + metrics.track('Opened Issue Reporter', { + from: 'app' + }); util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); }, handleMouseEnterDockerTerminal: function () { this.setState({ - currentButtonLabel: 'Open terminal to use Docker CLI.' + currentButtonLabel: 'Open Docker-Ready Mac Terminal.' }); }, handleMouseLeaveDockerTerminal: function () { @@ -116,7 +122,7 @@ var Containers = React.createClass({ }, handleMouseEnterReportIssue: function () { this.setState({ - currentButtonLabel: 'Report issues or suggest feedbacks.' + currentButtonLabel: 'Report an Issue' }); }, handleMouseLeaveReportIssue: function () { @@ -126,7 +132,7 @@ var Containers = React.createClass({ }, handleMouseEnterPreferences: function () { this.setState({ - currentButtonLabel: 'Change app preferences.' + currentButtonLabel: 'Change app Preferences.' }); }, handleMouseLeavePreferences: function () { diff --git a/src/Docker.js b/src/Docker.js index 83c9786c12..be0ddffaff 100644 --- a/src/Docker.js +++ b/src/Docker.js @@ -3,17 +3,20 @@ var path = require('path'); var dockerode = require('dockerode'); var Docker = { - host: null, + _host: null, _client: null, - setHost: function(host) { - this.host = host; - var certDir = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.boot2docker/certs/boot2docker-vm'); + setup: function(url, name) { + var certDir = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.docker/machine/machines', name); if (!fs.existsSync(certDir)) { return; } + var ip = url.split(':')[1].replace('//', ''); + var port = url.split(':')[2]; + this._host = ip; this._client = new dockerode({ - host: host, - port: 2376, + protocol: 'https', + host: ip, + port: port, ca: fs.readFileSync(path.join(certDir, 'ca.pem')), cert: fs.readFileSync(path.join(certDir, 'cert.pem')), key: fs.readFileSync(path.join(certDir, 'key.pem')) @@ -21,6 +24,9 @@ var Docker = { }, client: function () { return this._client; + }, + host: function () { + return this._host; } }; diff --git a/src/Boot2Docker.js b/src/DockerMachine.js similarity index 52% rename from src/Boot2Docker.js rename to src/DockerMachine.js index f643cf8d51..6af597ef5e 100644 --- a/src/Boot2Docker.js +++ b/src/DockerMachine.js @@ -5,20 +5,25 @@ var _ = require('underscore'); var fs = require('fs'); var util = require('./Util'); -var Boot2Docker = { +var NAME = 'dev'; + +var DockerMachine = { command: function () { - return path.join(process.cwd(), 'resources', 'boot2docker-' + this.version()); + return path.join(process.cwd(), 'resources', 'docker-machine-' + this.version()); + }, + name: function () { + return NAME; }, version: function () { try { - return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))['boot2docker-version']; + return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))['docker-machine-version']; } catch (err) { return null; } }, isoversion: function () { try { - var data = fs.readFileSync(path.join(util.home(), '.boot2docker', '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+)/); if (match) { return match[1]; @@ -29,53 +34,62 @@ var Boot2Docker = { return null; } }, - exists: function () { - return util.exec([Boot2Docker.command(), 'status']).then(() => { - return Promise.resolve(true); - }).catch(() => { - return Promise.resolve(false); - }); - }, - status: function () { - return util.exec([Boot2Docker.command(), 'status']).then(stdout => { - return Promise.resolve(stdout.trim()); - }); - }, - init: function () { - return util.exec([Boot2Docker.command(), 'init']); - }, - start: function () { - return util.exec([Boot2Docker.command(), 'start']); - }, - stop: function () { - return util.exec([Boot2Docker.command(), 'stop']); - }, - upgrade: function () { - return util.exec([Boot2Docker.command(), 'upgrade']); - }, - destroy: function () { - return util.exec([Boot2Docker.command(), 'destroy']); - }, - ip: function () { - return util.exec([Boot2Docker.command(), 'ip']).then(stdout => { - return Promise.resolve(stdout.trim().replace('\n', '')); - }); - }, - erase: function () { - return util.exec(['rm', '-rf', path.join(util.home(), 'VirtualBox\\ VMs/boot2docker-vm')]); - }, - state: function () { - util.exec([Boot2Docker.command(), 'info']).then(stdout => { - try { - var info = JSON.parse(stdout); - return Promise.resolve(info.State); - } catch (err) { - return Promise.reject(err); + info: function () { + return util.exec([DockerMachine.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 => { + var tokens = line.trim().split(/[\s]+/).filter(token => token !== '*'); + var machine = { + name: tokens[0], + driver: tokens[1], + state: tokens[2], + url: tokens[3] || '' + }; + machines[machine.name] = machine; + }); + if (machines[NAME]) { + return machines[NAME]; + } else { + throw new Error('Machine does not exist.'); } }); }, + exists: function () { + return DockerMachine.info().then(() => { + return true; + }).catch(() => { + return false; + }); + }, + + create: function () { + return util.exec([DockerMachine.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]); + }, + start: function () { + return util.exec([DockerMachine.command(), 'start', NAME]); + }, + stop: function () { + return util.exec([DockerMachine.command(), 'stop', NAME]); + }, + upgrade: function () { + return util.exec([DockerMachine.command(), 'upgrade', NAME]); + }, + rm: function () { + return util.exec([DockerMachine.command(), 'rm', '-f', NAME]); + }, + ip: function () { + return util.exec([DockerMachine.command(), 'ip']).then(stdout => { + return Promise.resolve(stdout.trim().replace('\n', '')); + }); + }, + state: function () { + return DockerMachine.info().then(info => { + return info ? info.state : null; + }); + }, disk: function () { - return util.exec([Boot2Docker.command(), 'ssh', 'df']).then(stdout => { + return util.exec([DockerMachine.command(), 'ssh', NAME, 'df']).then(stdout => { try { var lines = stdout.split('\n'); var dataline = _.find(lines, function (line) { @@ -99,7 +113,7 @@ var Boot2Docker = { }); }, memory: function () { - return util.exec([this.command(), 'ssh', 'free -m']).then(stdout => { + return util.exec([this.command(), 'ssh', NAME, 'free -m']).then(stdout => { try { var lines = stdout.split('\n'); var dataline = _.find(lines, function (line) { @@ -125,32 +139,35 @@ var Boot2Docker = { }); }, stats: function () { - Boot2Docker.state().then(state => { - if (state === 'poweroff') { + DockerMachine.state().then(state => { + if (state === 'Stopped') { return Promise.resolve({state: state}); } - var memory = Boot2Docker.memory(); - var disk = Boot2Docker.disk(); + var memory = DockerMachine.memory(); + var disk = DockerMachine.disk(); return Promise.all([memory, disk]).spread((memory, disk) => { return Promise.resolve({ - state: state, memory: memory, disk: disk }); }); }); }, - haskeys: function () { - return fs.existsSync(path.join(util.home(), '.ssh', 'id_boot2docker')); - }, - waitstatus: Promise.coroutine(function* () { + waitWhileState: Promise.coroutine(function* (status) { while (true) { - var current = yield Boot2Docker.status(); + var current = yield DockerMachine.state(); if (status !== current.trim()) { return; } } - }) + }), + 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(() => {}); + }); + }, }; -module.exports = Boot2Docker; +module.exports = DockerMachine; diff --git a/src/Main.js b/src/Main.js index 63c9d0d69c..ba8008bb15 100644 --- a/src/Main.js +++ b/src/Main.js @@ -1,14 +1,4 @@ var remote = require('remote'); -if (localStorage.getItem('settings.width') && localStorage.getItem('settings.height')) { - remote.getCurrentWindow().setSize(parseInt(localStorage.getItem('settings.width')), parseInt(localStorage.getItem('settings.height'))); - remote.getCurrentWindow().center(); -} - -window.addEventListener('resize', function () { - localStorage.setItem('settings.width', window.innerWidth); - localStorage.setItem('settings.height', window.innerHeight); -}); - require.main.paths.splice(0, 0, process.env.NODE_PATH); var app = remote.require('app'); var React = require('react'); @@ -16,15 +6,24 @@ var fs = require('fs'); var path = require('path'); var docker = require('./Docker'); var router = require('./router'); -var boot2docker = require('./boot2docker'); +var machine = require('./DockerMachine'); var ContainerStore = require('./ContainerStore'); var SetupStore = require('./SetupStore'); var metrics = require('./Metrics'); - -var MenuTemplate = require('./MenuTemplate'); +var template = require('./MenuTemplate'); var Menu = remote.require('menu'); -var menu = Menu.buildFromTemplate(MenuTemplate); -Menu.setApplicationMenu(menu); + +if (localStorage.getItem('settings.width') && localStorage.getItem('settings.height')) { + remote.getCurrentWindow().setSize(parseInt(localStorage.getItem('settings.width')), parseInt(localStorage.getItem('settings.height'))); + remote.getCurrentWindow().center(); +} + +window.addEventListener('resize', function () { + localStorage.setItem('settings.width', window.outerWidth); + localStorage.setItem('settings.height', window.outerHeight); +}); + +Menu.setApplicationMenu(Menu.buildFromTemplate(template())); var settingsjson; try { @@ -52,12 +51,13 @@ bugsnag.metaData = { }; bugsnag.beforeNotify = function(payload) { - var re = new RegExp(process.cwd().replace(' ', '\\s').replace('(','\\(').replace(')','\\)'), 'g'); - payload.stacktrace = payload.stacktrace.replace(re, ''); - payload.context = payload.context.replace(re, ''); - payload.file = payload.file.replace(re, ''); + var re = new RegExp(process.cwd().replace(/\s+/g, '\\s+').replace(/\(/g,'\\(').replace(/\)/g,'\\)').replace(/\//g, '\\/'), 'g'); + payload.stacktrace = payload.stacktrace.replace(/%20/g, ' ').replace(re, ''); + payload.context = payload.context.replace(/%20/g, ' ').replace(re, ''); + payload.file = payload.file.replace(/%20/g, ' ').replace(re, ''); payload.url = ''; }; +bugsnag.notify(new Error('test')); document.onkeydown = function (e) { e = e || window.event; @@ -82,13 +82,15 @@ setInterval(function () { }, 14400000); router.run(Handler => React.render(, document.body)); -SetupStore.run().then(boot2docker.ip).then(ip => { - docker.setHost(ip); +SetupStore.run().then(machine.info).then(machine => { + docker.setup(machine.url, machine.name); + Menu.setApplicationMenu(Menu.buildFromTemplate(template())); ContainerStore.init(function (err) { if (err) { console.log(err); } router.transitionTo('containers'); }); }).catch(err => { console.log(err); + console.log(err.stack); bugsnag.notify(err); }); diff --git a/src/MenuTemplate.js b/src/MenuTemplate.js index 300f92b670..1baba2d784 100644 --- a/src/MenuTemplate.js +++ b/src/MenuTemplate.js @@ -1,168 +1,177 @@ var remote = require('remote'); var app = remote.require('app'); -var path = require('path'); -var docker = require('./Docker'); var router = require('./Router'); var util = require('./Util'); var metrics = require('./Metrics'); +var machine = require('./DockerMachine'); +var docker = require('./Docker'); // main.js -var MenuTemplate = [ -{ - label: 'Kitematic', - submenu: [ - { - label: 'About Kitematic', - selector: 'orderFrontStandardAboutPanel:' - }, - { - type: 'separator' - }, - { - label: 'Preferences', - accelerator: 'Command+,', - click: function () { - router.transitionTo('preferences'); - } - }, - { - type: 'separator' - }, - { - label: 'Services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide Kitematic', - accelerator: 'Command+H', - selector: 'hide:' - }, - { - label: 'Hide Others', - accelerator: 'Command+Shift+H', - selector: 'hideOtherApplications:' - }, - { - label: 'Show All', - selector: 'unhideAllApplications:' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function() { - app.quit(); - } - }, - ] -}, -{ - label: 'File', - submenu: [ - { - type: 'separator' - }, - { - label: 'Open Docker Terminal', - accelerator: 'Command+Shift+T', - click: function() { - metrics.track('Opened Docker Terminal'); - var terminal = path.join(process.cwd(), 'resources', 'terminal'); - var cmd = [terminal, `DOCKER_HOST=${'tcp://' + docker.host + ':2376'} DOCKER_CERT_PATH=${path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.boot2docker/certs/boot2docker-vm')} DOCKER_TLS_VERIFY=1 $SHELL`]; - util.exec(cmd).then(() => {}); - } - }, - ] -}, -{ - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'Command+Z', - selector: 'undo:' - }, - { - label: 'Redo', - accelerator: 'Shift+Command+Z', - selector: 'redo:' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'Command+X', - selector: 'cut:' - }, - { - label: 'Copy', - accelerator: 'Command+C', - selector: 'copy:' - }, - { - label: 'Paste', - accelerator: 'Command+V', - selector: 'paste:' - }, - { - label: 'Select All', - accelerator: 'Command+A', - selector: 'selectAll:' - }, - ] -}, -{ - label: 'View', - submenu: [ +var MenuTemplate = function () { + return [ { - label: 'Toggle DevTools', - accelerator: 'Alt+Command+I', - click: function() { remote.getCurrentWindow().toggleDevTools(); } + label: 'Kitematic', + submenu: [ + { + label: 'About Kitematic', + selector: 'orderFrontStandardAboutPanel:' + }, + { + type: 'separator' + }, + { + label: 'Preferences', + accelerator: 'Command+,', + enabled: !!docker.host(), + click: function () { + metrics.track('Opened Preferences', { + from: 'menu' + }); + router.transitionTo('preferences'); + } + }, + { + type: 'separator' + }, + { + label: 'Services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: 'Hide Kitematic', + accelerator: 'Command+H', + selector: 'hide:' + }, + { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + selector: 'hideOtherApplications:' + }, + { + label: 'Show All', + selector: 'unhideAllApplications:' + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: function() { + app.quit(); + } + }, + ] }, - ] -}, -{ - label: 'Window', - submenu: [ - { - label: 'Minimize', - accelerator: 'Command+M', - selector: 'performMiniaturize:' - }, - { - label: 'Close', - accelerator: 'Command+W', - click: function () { - remote.getCurrentWindow().hide(); - } - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - selector: 'arrangeInFront:' - }, - ] -}, -{ - label: 'Help', - submenu: [ { - label: 'Report an Issue...', - click: function () { - util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); - } + label: 'File', + submenu: [ + { + type: 'separator' + }, + { + label: 'Open Docker Terminal', + accelerator: 'Command+Shift+T', + enabled: !!docker.host(), + click: function() { + metrics.track('Opened Docker Terminal', { + from: 'menu' + }); + machine.dockerTerminal(); + } + }, + ] }, - ] -}, -]; - + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'Command+Z', + selector: 'undo:' + }, + { + label: 'Redo', + accelerator: 'Shift+Command+Z', + selector: 'redo:' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'Command+X', + selector: 'cut:' + }, + { + label: 'Copy', + accelerator: 'Command+C', + selector: 'copy:' + }, + { + label: 'Paste', + accelerator: 'Command+V', + selector: 'paste:' + }, + { + label: 'Select All', + accelerator: 'Command+A', + selector: 'selectAll:' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Toggle DevTools', + accelerator: 'Alt+Command+I', + click: function() { remote.getCurrentWindow().toggleDevTools(); } + }, + ] + }, + { + label: 'Window', + submenu: [ + { + label: 'Minimize', + accelerator: 'Command+M', + selector: 'performMiniaturize:' + }, + { + label: 'Close', + accelerator: 'Command+W', + click: function () { + remote.getCurrentWindow().hide(); + } + }, + { + type: 'separator' + }, + { + label: 'Bring All to Front', + selector: 'arrangeInFront:' + }, + ] + }, + { + label: 'Help', + submenu: [ + { + label: 'Report an Issue...', + click: function () { + metrics.track('Opened Issue Reporter', { + from: 'menu' + }); + util.exec(['open', 'https://github.com/kitematic/kitematic/issues/new']); + } + }, + ] + }, + ]; +}; module.exports = MenuTemplate; diff --git a/src/Preferences.react.js b/src/Preferences.react.js index 30d1db7f25..039beff19a 100644 --- a/src/Preferences.react.js +++ b/src/Preferences.react.js @@ -3,7 +3,7 @@ var ipc = require('ipc'); var metrics = require('./Metrics'); var Router = require('react-router'); -if (localStorage.getItem('settings.saveVMOnQuit') === 'true') { +if (localStorage.getItem('settings.closeVMOnQuit') === 'true') { ipc.send('vm', true); } else { ipc.send('vm', false); @@ -13,7 +13,7 @@ var Preferences = React.createClass({ mixins: [Router.Navigation], getInitialState: function () { return { - saveVMOnQuit: localStorage.getItem('settings.saveVMOnQuit') === 'true', + closeVMOnQuit: localStorage.getItem('settings.closeVMOnQuit') === 'true', metricsEnabled: metrics.enabled() }; }, @@ -21,14 +21,15 @@ var Preferences = React.createClass({ this.goBack(); metrics.track('Went Back From Preferences'); }, - handleChangeSaveVMOnQuit: function (e) { + handleChangeCloseVMOnQuit: function (e) { var checked = e.target.checked; this.setState({ - saveVMOnQuit: checked + closeVMOnQuit: checked }); + localStorage.setItem('settings.closeVMOnQuit', checked); ipc.send('vm', checked); - metrics.track('Toggled Save VM On Quit', { - save: checked + metrics.track('Toggled Close VM On Quit', { + close: checked }); }, handleChangeMetricsEnabled: function (e) { @@ -49,10 +50,10 @@ var Preferences = React.createClass({
VM Settings
- Save Linux VM state on closing Kitematic + Shut Down Linux VM on closing Kitematic
- +
App Settings
diff --git a/src/Routes.js b/src/Routes.js index 1571a49f6b..eab468fa1c 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -27,13 +27,13 @@ var App = React.createClass({ var routes = ( - - - - - - - + + + + + + + diff --git a/src/SetupStore.js b/src/SetupStore.js index 497e8f191f..a7dd7d231a 100644 --- a/src/SetupStore.js +++ b/src/SetupStore.js @@ -3,7 +3,7 @@ var _ = require('underscore'); var path = require('path'); var fs = require('fs'); var Promise = require('bluebird'); -var boot2docker = require('./Boot2Docker'); +var machine = require('./DockerMachine'); var virtualBox = require('./VirtualBox'); var setupUtil = require('./SetupUtil'); var util = require('./Util'); @@ -36,9 +36,8 @@ var _steps = [{ percent: 0, seconds: 5, run: Promise.coroutine(function* (progressCallback) { - var packagejson = util.packagejson(); - var cmd = util.copyBinariesCmd() + ' && ' + util.fixBinariesCmd(); - if (!virtualBox.installed() || setupUtil.compareVersions(yield virtualBox.version(), packagejson['virtualbox-required-version']) < 0) { + var cmd = setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd(); + if (!virtualBox.installed()) { yield virtualBox.killall(); cmd += ' && ' + setupUtil.installVirtualBoxCmd(); } else { @@ -55,45 +54,35 @@ var _steps = [{ }) }, { name: 'init', - title: 'Setting up Docker VM', - message: 'To run Docker containers on your computer, we are setting up a Linux virtual machine provided by boot2docker.', - totalPercent: 15, + title: 'Starting Docker VM', + message: 'To run Docker containers on your computer, Kitematic is starting a Linux virutal machine. This may take a minute...', + totalPercent: 60, percent: 0, - seconds: 11, + seconds: 52, run: Promise.coroutine(function* (progressCallback) { setupUtil.simulateProgress(this.seconds, progressCallback); yield virtualBox.vmdestroy('kitematic-vm'); - var exists = yield boot2docker.exists(); + var exists = yield machine.exists(); if (!exists) { - yield boot2docker.init(); + yield machine.create(); + return; + } else if ((yield machine.state()) === 'Error') { + try { + yield machine.rm(); + } catch (err) {} + yield machine.create(); return; } - if (!boot2docker.haskeys()) { - throw new Error('Boot2Docker SSH keys do not exist. Fix this by removing the existing Boot2Docker VM setup and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); - } - - var isoversion = boot2docker.isoversion(); - if (!isoversion || setupUtil.compareVersions(isoversion, boot2docker.version()) < 0) { - yield boot2docker.stop(); - yield boot2docker.upgrade(); + var isoversion = machine.isoversion(); + var packagejson = util.packagejson(); + if (!isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) { + console.log('upgrading'); + yield machine.stop(); + yield machine.upgrade(); } + yield machine.start(); }) -}, { - name: 'start', - title: 'Starting Docker VM', - message: "Kitematic is starting the boot2docker VM. This may take about a minute.", - totalPercent: 45, - percent: 0, - seconds: 35, - run: function (progressCallback) { - setupUtil.simulateProgress(this.seconds, progressCallback); - return boot2docker.waitstatus('saving').then(boot2docker.status).then(status => { - if (status !== 'running') { - return boot2docker.start(); - } - }); - } }]; var SetupStore = assign(Object.create(EventEmitter.prototype), { @@ -140,18 +129,18 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { return Promise.resolve(_requiredSteps); } var packagejson = util.packagejson(); - var isoversion = boot2docker.isoversion(); + var isoversion = machine.isoversion(); var required = {}; var vboxfile = path.join(util.supportDir(), packagejson['virtualbox-filename']); - var vboxInstallRequired = virtualBox.installed() ? setupUtil.compareVersions(yield virtualBox.version(), packagejson['virtualbox-required-version']) < 0 : true; - required.download = vboxInstallRequired && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== packagejson['virtualbox-checksum']); - required.install = vboxInstallRequired || setupUtil.needsBinaryFix(); - required.init = !(yield boot2docker.exists()) || !isoversion || setupUtil.compareVersions(isoversion, boot2docker.version()) < 0; - required.start = required.install || required.init || (yield boot2docker.status()) !== 'running'; + required.download = !virtualBox.installed() && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== packagejson['virtualbox-checksum']); + required.install = !virtualBox.installed() || setupUtil.needsBinaryFix(); + required.init = !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0; - var exists = yield boot2docker.exists(); - if (exists) { - this.steps().start.seconds = 13; + var exists = yield machine.exists(); + if (isoversion && setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) { + this.steps().init.seconds = 33; + } else if (exists && (yield machine.state()) !== 'Error') { + this.steps().init.seconds = 13; } _requiredSteps = _steps.filter(function (step) { @@ -164,7 +153,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { return Promise.resolve(); } if (setupUtil.shouldUpdateBinaries()) { - return util.exec(util.copyBinariesCmd()); + return util.exec(setupUtil.copyBinariesCmd()); } return Promise.resolve(); }, @@ -194,9 +183,10 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { metrics.track('Setup Failed', { step: step.name }); - console.log('Setup encountered an error.'); - console.log(err); if (err) { + console.log('Setup encountered an error.'); + console.log(err); + console.log(err.stack); _error = err; this.emit(this.ERROR_EVENT); } else { diff --git a/src/SetupUtil.js b/src/SetupUtil.js index 5598ecaa01..9b7f2d1973 100644 --- a/src/SetupUtil.js +++ b/src/SetupUtil.js @@ -12,7 +12,7 @@ var SetupUtil = { if (!fs.existsSync('/usr/local') || !fs.existsSync('/usr/local/bin')) { return true; } - if (!fs.existsSync('/usr/local/bin/docker') && !fs.existsSync('/usr/local/bin/boot2docker')) { + if (!fs.existsSync('/usr/local/bin/docker') && !fs.existsSync('/usr/local/bin/docker-machine')) { return fs.statSync('/usr/local/bin').gid !== 80 || fs.statSync('/usr/local/bin').uid !== process.getuid(); } @@ -20,28 +20,50 @@ var SetupUtil = { return true; } - if (fs.existsSync('/usr/local/bin/boot2docker') && (fs.statSync('/usr/local/bin/boot2docker').gid !== 80 || fs.statSync('/usr/local/bin/boot2docker').uid !== process.getuid())) { + 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) { + return ['rm', '-f', dest, '&&', 'cp', src, dest]; + }, + escapePath: function (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/boot2docker') || - this.checksum('/usr/local/bin/boot2docker') !== this.checksum(path.join(util.resourceDir(), 'boot2docker-' + packagejson['boot2docker-version'])) || + !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'])); }, + 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(' '); + }, + fixBinariesCmd: function () { + var cmd = []; + cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, this.escapePath(path.join('/usr/local/bin', 'docker-machine'))]); + cmd.push('&&'); + cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, this.escapePath(path.join('/usr/local/bin', 'docker'))]); + return cmd.join(' '); + }, installVirtualBoxCmd: function () { var packagejson = util.packagejson(); - return `installer -pkg ${util.escapePath(path.join(util.supportDir(), packagejson['virtualbox-filename']))} -target /`; + 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 `${util.escapePath(path.join(util.resourceDir(), 'macsudo'))} -p "Kitematic requires administrative privileges to install." sh -c \"${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); diff --git a/src/Util.js b/src/Util.js index 3907dae89d..949ac410c5 100644 --- a/src/Util.js +++ b/src/Util.js @@ -35,27 +35,5 @@ module.exports = { packagejson: function () { return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); }, - copycmd: function (src, dest) { - return ['rm', '-f', dest, '&&', 'cp', src, dest]; - }, - copyBinariesCmd: function () { - var packagejson = this.packagejson(); - var cmd = ['mkdir', '-p', '/usr/local/bin']; - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(this.resourceDir(), 'boot2docker-' + packagejson['boot2docker-version'])), '/usr/local/bin/boot2docker')); - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(this.escapePath(path.join(this.resourceDir(), 'docker-' + packagejson['docker-version'])), '/usr/local/bin/docker')); - return cmd.join(' '); - }, - fixBinariesCmd: function () { - var cmd = []; - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, this.escapePath(path.join('/usr/local/bin', 'boot2docker'))]); - cmd.push('&&'); - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, this.escapePath(path.join('/usr/local/bin', 'docker'))]); - return cmd.join(' '); - }, - escapePath: function (str) { - return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); - }, webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983'] }; diff --git a/styles/header.less b/styles/header.less index 946c32a430..004d149f85 100644 --- a/styles/header.less +++ b/styles/header.less @@ -48,6 +48,10 @@ background-color: @traffic-light-green; border-color: @traffic-light-green-border; } + &.button-fullscreenclose { + background-color: @traffic-light-green; + border-color: @traffic-light-green-border; + } &.disabled { background-color: @traffic-light-gray; border-color: @traffic-light-gray-border; diff --git a/styles/variables.less b/styles/variables.less index 214b5a5c6a..86a901b9f4 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -9,7 +9,7 @@ @traffic-light-red-border: #E33E32; @traffic-light-yellow: #FFBE05; @traffic-light-yellow-border: #E2A100; -@traffic-light-gray: #DDDDDD; +@traffic-light-gray: #E5E5E5; @traffic-light-gray-border: #D3D3D3; @gray-darkest: #253237; diff --git a/util/deps b/util/deps index 9527e4c1e0..7d7dff31d1 100755 --- a/util/deps +++ b/util/deps @@ -1,18 +1,18 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" BASE=$DIR/.. -BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") -BOOT2DOCKER_CLI_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION +DOCKER_MACHINE_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-machine-version']" "$(cat $BASE/package.json)") +DOCKER_MACHINE_CLI_FILE=docker-machine-$DOCKER_MACHINE_CLI_VERSION DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-version']" "$(cat $BASE/package.json)") DOCKER_CLI_FILE=docker-$DOCKER_CLI_VERSION pushd $BASE/resources > /dev/null -if [ ! -f $BOOT2DOCKER_CLI_FILE ]; then - echo "-----> Downloading Boot2docker CLI..." - rm -rf boot2docker-* - curl -L -o $BOOT2DOCKER_CLI_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v$BOOT2DOCKER_CLI_VERSION/boot2docker-v$BOOT2DOCKER_CLI_VERSION-darwin-amd64 - chmod +x $BOOT2DOCKER_CLI_FILE +if [ ! -f $DOCKER_MACHINE_CLI_FILE ]; then + echo "-----> Downloading Docker Machine CLI..." + rm -rf docker-machine* + curl -L -o $DOCKER_MACHINE_CLI_FILE https://github.com/docker/machine/releases/download/v$DOCKER_MACHINE_CLI_VERSION/docker-machine_darwin-amd64 + chmod +x $DOCKER_MACHINE_CLI_FILE fi if [ ! -f $DOCKER_CLI_FILE ]; then diff --git a/util/reset b/util/reset index 0c5afc1cdd..56eb096e45 100755 --- a/util/reset +++ b/util/reset @@ -2,12 +2,10 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -sudo rm -f /usr/local/bin/docker -sudo rm -f /usr/local/bin/boot2docker +sudo rm -f /usr/local/bin/docker* pkill VirtualBox pkill VBox rm -rf ~/Library/Application\ Support/Kitematic/ -rm -rf ~/.boot2docker -rm -rf ~/VirtualBox\ VMs/boot2docker-vm +rm -rf ~/.docker rm -rf ~/Library/VirtualBox/ $DIR/VirtualBox_Uninstall.tool