diff --git a/browser/main.js b/browser/main.js index 8c31209cdd..ee51d8d578 100644 --- a/browser/main.js +++ b/browser/main.js @@ -11,10 +11,18 @@ var BrowserWindow = require('browser-window'); var ipc = require('ipc'); var argv = require('minimist')(process.argv); +var saveVMOnQuit = false; process.env.NODE_PATH = __dirname + '/../node_modules'; +process.env.RESOURCES_PATH = __dirname + '/../resources'; process.chdir(path.join(__dirname, '..')); +if (argv.integration) { + process.env.TEST_TYPE = 'integration'; +} else { + process.env.TEST_TYPE = 'test'; +} + app.on('activate-with-no-open-windows', function () { if (mainWindow) { mainWindow.show(); @@ -23,38 +31,35 @@ app.on('activate-with-no-open-windows', function () { }); app.on('ready', function() { - var windowOptions = { + mainWindow = new BrowserWindow({ width: 1000, height: 700, 'min-width': 1000, 'min-height': 700, resizable: true, - frame: false - }; + frame: false, + show: false + }); - mainWindow = new BrowserWindow(windowOptions); - mainWindow.hide(); if (argv.test) { - mainWindow.loadUrl('file://' + __dirname + '/../specs/specs.html'); + mainWindow.loadUrl('file://' + __dirname + '/../tests/tests.html'); } else { mainWindow.loadUrl('file://' + __dirname + '/../build/index.html'); + app.on('will-quit', function (e) { + if (saveVMOnQuit) { + exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); + } + }); } - process.on('uncaughtException', app.quit); - - var saveVMOnQuit = false; - app.on('will-quit', function (e) { - if (saveVMOnQuit) { - exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); - } - }); - mainWindow.webContents.on('new-window', function (e) { e.preventDefault(); }); mainWindow.webContents.on('did-finish-load', function() { - mainWindow.show(); + if (!argv.test) { + mainWindow.show(); + } mainWindow.focus(); mainWindow.setTitle(''); @@ -92,12 +97,12 @@ app.on('ready', function() { autoUpdater.quitAndInstall(); } }); + + autoUpdater.checkForUpdates(); } ipc.on('vm', function (event, arg) { saveVMOnQuit = arg; }); - - autoUpdater.checkForUpdates(); }); }); diff --git a/deps b/deps index e01dfa4f77..7e1cbc3cc2 100755 --- a/deps +++ b/deps @@ -1,36 +1,15 @@ #!/bin/bash - BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PLATFORM='unknown' -unamestr=`uname` -if [[ "$unamestr" == 'Linux' ]]; then - PLATFORM='linux' -elif [[ "$unamestr" == 'Darwin' ]]; then - PLATFORM='darwin' -fi - -export BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") -export BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION -export ATOM_SHELL_VERSION=$(node -pe "JSON.parse(process.argv[1])['atom-shell-version']" "$(cat $BASE/package.json)") -export ATOM_SHELL_VERSION_FILE=atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip - -mkdir -p $BASE/cache/$PLATFORM -pushd $BASE/cache/$PLATFORM > /dev/null -if [ ! -f $ATOM_SHELL_VERSION_FILE ]; then - echo "-----> Downloading atom-shell..." - rm -rf atom-shell-* - curl -L -o $ATOM_SHELL_VERSION_FILE https://github.com/atom/atom-shell/releases/download/v$ATOM_SHELL_VERSION/atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip - unzip $ATOM_SHELL_VERSION_FILE -fi -popd > /dev/null +BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") +BOOT2DOCKER_CLI_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION pushd $BASE/resources > /dev/null -if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then +if [ ! -f $BOOT2DOCKER_CLI_FILE ]; then echo "-----> Downloading Boot2docker CLI..." rm -rf boot2docker-* - curl -L -o $BOOT2DOCKER_CLI_VERSION_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v${BOOT2DOCKER_CLI_VERSION}/boot2docker-v${BOOT2DOCKER_CLI_VERSION}-darwin-amd64 - chmod +x $BOOT2DOCKER_CLI_VERSION_FILE + 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 fi popd > /dev/null diff --git a/resources/virtualbox-4.3.18.pkg b/gulp similarity index 100% rename from resources/virtualbox-4.3.18.pkg rename to gulp diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000000..546108ce42 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,170 @@ +var concat = require('gulp-concat'); +var cssmin = require('gulp-cssmin'); +var downloadatomshell = require('gulp-download-atom-shell'); +var fs = require('fs'); +var gulp = require('gulp'); +var gulpif = require('gulp-if'); +var gutil = require('gulp-util'); +var http = require('http'); +var less = require('gulp-less'); +var livereload = require('gulp-livereload'); +var plumber = require('gulp-plumber'); +var react = require('gulp-react'); +var runSequence = require('run-sequence'); +var shell = require('gulp-shell'); +var sourcemaps = require('gulp-sourcemaps'); +var packagejson = require('./package.json'); + +var dependencies = Object.keys(packagejson.dependencies); +var devDependencies = Object.keys(packagejson.devDependencies); +var options = { + dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1, + test: process.argv.indexOf('test') !== -1, + integration: process.argv.indexOf('--integration') !== -1, + filename: 'Kitematic.app', + name: 'Kitematic' +}; + +gulp.task('js', function () { + gulp.src('src/**/*.js') + .pipe(plumber(function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); + // emit the end event, to properly end the task + this.emit('end'); + })) + .pipe(react()) + .pipe(gulp.dest((options.dev || options.test) ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('images', function() { + return gulp.src('images/*') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('styles', function () { + return gulp.src('styles/main.less') + .pipe(plumber(function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); + // emit the end event, to properly end the task + this.emit('end'); + })) + .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(gulpif(!options.dev, cssmin())) + .pipe(concat('main.css')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('download', function (cb) { + downloadatomshell({ + version: packagejson['atom-shell-version'], + outputDir: 'cache' + }, cb); +}); + +gulp.task('copy', function () { + gulp.src('index.html') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); + + gulp.src('fonts/**') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('dist', function (cb) { + var stream = gulp.src('').pipe(shell([ + 'rm -Rf ./dist', + 'mkdir -p ./dist/osx', + 'cp -R ./cache/Atom.app ./dist/osx/<%= filename %>', + 'mv ./dist/osx/<%= filename %>/Contents/MacOS/Atom ./dist/osx/<%= filename %>/Contents/MacOS/<%= name %>', + 'mkdir -p ./dist/osx/<%= filename %>/Contents/Resources/app', + 'cp -R browser dist/osx/<%= filename %>/Contents/Resources/app', + 'cp package.json dist/osx/<%= filename %>/Contents/Resources/app/', + 'mkdir -p dist/osx/<%= filename %>/Contents/Resources/app/resources', + 'cp -v resources/* dist/osx/<%= filename %>/Contents/Resources/app/resources/ || :', + 'cp kitematic.icns dist/osx/<%= filename %>/Contents/Resources/atom.icns', + '/usr/libexec/PlistBuddy -c "Set :CFBundleVersion <%= version %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier <%= bundle %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist' + ], { + templateData: { + filename: options.filename, + name: options.name, + version: packagejson.version, + bundle: 'com.kitematic.app' + } + })); + + dependencies.forEach(function (d) { + stream = stream.pipe(shell([ + 'cp -R node_modules/' + d + ' dist/osx/<%= filename %>/Contents/Resources/app/node_modules/' + ], { + templateData: { + filename: options.filename + } + })); + }); + + return stream; +}); + +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 + ], { + cwd: './dist/osx/' + })); + } catch (error) { + gutil.log(gutil.colors.red('Error: ' + error.message)); + } +}); + +gulp.task('zip', function () { + return gulp.src('').pipe(shell([ + 'ditto -c -k --sequesterRsrc --keepParent ' + options.filename + ' ' + options.name + '-' + packagejson.version + '.zip' + ], { + cwd: './dist/osx/' + })); +}); + +gulp.task('release', function () { + runSequence('download', 'dist', ['copy', 'images', 'js', 'styles'], 'sign', 'zip'); +}); + +gulp.task('test', ['download', 'copy', 'js'], function () { + var env = process.env; + env.NODE_ENV = 'test'; + if (options.integration) { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test --integration'], { + env: env + })); + } else { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test'], { + env: env + })); + } +}); + +gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () { + 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 .'], { + env: env + })); +}); diff --git a/package.json b/package.json index de2dd005a1..a80e514676 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,12 @@ }, "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { - "start": "rsync ./index.html ./build/ && rsync ./fonts/* ./build/ && rsync ./images/* ./build && jsx --watch src/ build/ & wess -w -m -i ./styles/main.less -o ./build/main.css & wiper -w ./build/**/*.* & npm run start:$(uname) && kill $(pgrep node)", - "start:Darwin": "NODE_ENV=development ./cache/darwin/Atom.app/Contents/MacOS/Atom .", - "start:Linux": "NODE_ENV=development ./cache/linux/atom .", - "test": "jsx --watch src build/ & rsync ./images/* ./build && wess -m -i ./styles/main.less -o ./build/main.css && npm run test:$(uname) && kill $(pgrep node)", - "test:Darwin": "NODE_ENV=test ./cache/darwin/Atom.app/Contents/MacOS/Atom . --test", - "test:Linux": "NODE_ENV=test ./cache/darwin/atom .", - "release": "./release" + "start": "gulp", + "test": "gulp test --silent", + "integration-tests": "gulp test --silent --integration", + "all-tests": "npm test && npm run integration-tests", + "release": "gulp run release", + "preinstall": "./deps" }, "licenses": [ { @@ -32,15 +31,17 @@ "debug" ] }, - "boot2docker-version": "1.3.2", + "boot2docker-version": "1.4.1", "atom-shell-version": "0.21.0", + "virtualbox-version": "4.3.20", + "virtualbox-filename": "VirtualBox-4.3.20-96996-OSX.dmg", + "virtualbox-required-version": "4.3.18", "dependencies": { "ansi-to-html": "0.2.0", "async": "^0.9.0", - "bugsnag-js": "^2.4.6", + "bugsnag-js": "git+https://git@github.com/bugsnag/bugsnag-js", "dockerode": "2.0.4", "exec": "0.1.2", - "gulp-react": "^2.0.0", "jest-cli": "^0.2.1", "jquery": "^2.1.3", "minimist": "^1.1.0", @@ -57,15 +58,33 @@ "underscore": "^1.7.0" }, "devDependencies": { + "browserify": "^6.2.0", "ecstatic": "^0.5.8", "glob": "^4.0.6", - "jasmine-node": "^1.14.5", - "jasmine-reporters": "^1.0.1", - "jasmine-tagged": "^1.1.2", - "jasmine-terminal-reporter": "^0.9.1", - "react-tools": "^0.12.2", - "wess": "^0.2.2", - "wiper": "^1.0.2", + "gulp": "^3.8.10", + "gulp-atom": "0.0.5", + "gulp-concat": "^2.3.4", + "gulp-cssmin": "^0.1.6", + "gulp-download-atom-shell": "0.0.4", + "gulp-if": "^1.2.4", + "gulp-imagemin": "^2.0.0", + "gulp-less": "^2.0.1", + "gulp-livereload": "^2.1.1", + "gulp-notify": "^1.4.2", + "gulp-plumber": "^0.6.6", + "gulp-react": "^2.0.0", + "gulp-shell": "^0.2.11", + "gulp-sourcemaps": "^1.2.8", + "gulp-streamify": "0.0.5", + "gulp-uglify": "^0.3.1", + "gulp-uglifyjs": "^0.5.0", + "gulp-util": "^3.0.0", + "reactify": "^0.15.2", + "rimraf": "^2.2.8", + "run-sequence": "^1.0.2", + "time-require": "^0.1.2", + "vinyl-source-stream": "^0.1.1", + "watchify": "^2.1.1", "zombie": "^2.5.1" } } diff --git a/release b/release deleted file mode 100755 index 7aa58dac37..0000000000 --- a/release +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export NAME=$(node -pe "JSON.parse(process.argv[1])['name']" "$(cat $BASE/package.json)") -export VERSION=$(node -pe "JSON.parse(process.argv[1])['version']" "$(cat $BASE/package.json)") -export FILENAME=$NAME.app -export BUNDLE=com.kitematic.app - -rm -Rf ./dist -mkdir -p ./dist/osx -cp -R ./cache/Atom.app ./dist/osx/$FILENAME -mv ./dist/osx/$FILENAME/Contents/MacOS/Atom ./dist/osx/$FILENAME/Contents/MacOS/$NAME -mkdir -p ./dist/osx/$FILENAME/Contents/Resources/app -cp -R browser dist/osx/$FILENAME/Contents/Resources/app -cp package.json dist/osx/$FILENAME/Contents/Resources/app/ -mkdir -p dist/osx/$FILENAME/Contents/Resources/app/resources -mkdir -p dist/osx/$FILENAME/Contents/Resources/app/node_modules -cp -v resources/* dist/osx/$FILENAME/Contents/Resources/app/resources/ || : -cp kitematic.icns dist/osx/$FILENAME/Contents/Resources/atom.icns -/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $NAME" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleName $NAME" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $NAME" dist/osx/$FILENAME/Contents/Info.plist - -rsync ./index.html ./dist/osx/$FILENAME/Contents/Resources/app/build/ -rsync ./fonts/* ./dist/osx/$FILENAME/Contents/Resources/app/build/ -rsync ./images/* ./dist/osx/$FILENAME/Contents/Resources/app/build/ -jsx src/ ./dist/osx/$FILENAME/Contents/Resources/app/build/ -wess -m -i ./styles/main.less -o ./dist/osx/$FILENAME/Contents/Resources/app/build/main.css - -if [ -f $BASE/identity ]; then - codesign --deep --force --verbose --sign "$(cat $BASE/identity)" ./dist/osx/$FILENAME -fi - -ditto -c -k --sequesterRsrc --keepParent ./dist/osx/$FILENAME ./dist/osx/$NAME-$VERSION.zip diff --git a/resources/cocoasudo b/resources/cocoasudo new file mode 100755 index 0000000000..ccf0bf8aa1 Binary files /dev/null and b/resources/cocoasudo differ diff --git a/specs/ContainerStore-spec.js b/specs/ContainerStore-spec.js deleted file mode 100755 index 5c8edb3a4c..0000000000 --- a/specs/ContainerStore-spec.js +++ /dev/null @@ -1,8 +0,0 @@ -var ContainerStore = require('../build/ContainerStore'); -var TestUtils = require('react/addons').TestUtils; - -describe('ContainerStore', function() { - it('returns an empty array initially', function() { - expect(ContainerStore.containers()).toEqual({}); - }); -}); diff --git a/specs/tests.js b/specs/tests.js new file mode 100644 index 0000000000..394df65268 --- /dev/null +++ b/specs/tests.js @@ -0,0 +1,26 @@ +window.jasmineRequire = require('./jasmine-2.1.3/jasmine'); +require('./jasmine-2.1.3/jasmine-html'); +require('./jasmine-2.1.3/boot'); +var consoleReporter = require('./jasmine-2.1.3/console'); +var app = require('remote').require('app'); + +jasmine.getEnv().addReporter(new consoleReporter.ConsoleReporter()({ + showColors: true, + timer: new jasmine.Timer(), + print: function() { + process.stdout.write.apply(process.stdout, arguments); + }, + onComplete: function () { + app.quit(); + } +})); + +console.log('hi'); +var fs = require('fs'); +var tests = fs.readdirSync('./tests').filter(function (f) { + return f.indexOf('-' + process.env.TEST_TYPE) !== -1; +}); + +tests.forEach(function (t) { + require('./' + t); +}); diff --git a/src/Main.js b/src/Main.js index ae2d5bc526..d908e18656 100644 --- a/src/Main.js +++ b/src/Main.js @@ -6,19 +6,18 @@ var docker = require('./docker'); var router = require('./router'); var boot2docker = require('./boot2docker'); var ContainerStore = require('./ContainerStore'); +var SetupStore = require('./ContainerStore'); var Menu = require('./Menu'); var remote = require('remote'); var app = remote.require('app'); var ipc = require('ipc'); - var Route = Router.Route; var NotFoundRoute = Router.NotFoundRoute; var DefaultRoute = Router.DefaultRoute; var Link = Router.Link; var RouteHandler = Router.RouteHandler; - if (process.env.NODE_ENV === 'development') { var script = document.createElement('script'); script.type = 'text/javascript'; @@ -28,8 +27,16 @@ if (process.env.NODE_ENV === 'development') { } if (!window.location.hash.length || window.location.hash === '#/') { - router.run(function (Handler) { - React.render(, document.body); + SetupStore.run(function (err) { + boot2docker.ip(function (err, ip) { + docker.setHost(ip); + router.transitionTo('containers'); + ContainerStore.init(function () { + router.run(function (Handler) { + React.render(, document.body); + }); + }); + }); }); } else { boot2docker.ip(function (err, ip) { diff --git a/src/Setup.react.js b/src/Setup.react.js index 5a77893b5a..c86f389ed4 100644 --- a/src/Setup.react.js +++ b/src/Setup.react.js @@ -5,133 +5,9 @@ var async = require('async'); var assign = require('object-assign'); var fs = require('fs'); var path = require('path'); -var boot2docker = require('./Boot2Docker'); var virtualbox = require('./Virtualbox'); var util = require('./Util'); -var docker = require('./Docker'); -var ContainerStore = require('./ContainerStore'); - -var setupSteps = [ - { - run: function (callback, progressCallback) { - console.log(util.supportDir()); - var installed = virtualbox.installed(); - if (!installed) { - util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(util.supportDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (!virtualbox.installed()) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - // Download a newer version of Virtualbox - util.downloadFile(Setup.BASE_URL + virtualbox.INSTALLER_FILENAME, path.join(util.getResourceDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.kill(function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (err) {callback(err); return;} - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }); - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - callback(); - } - }); - } - }, - message: 'Downloading VirtualBox...' - }, - { - run: function (callback) { - virtualbox.deleteVM('kitematic-vm', function (err, removed) { - if (err) { - console.log(err); - } - callback(); - }); - }, - message: 'Cleaning up existing Docker VM...' - }, - - // Initialize Boot2Docker if necessary. - { - run: function (callback) { - boot2docker.exists(function (err, exists) { - if (err) { callback(err); return; } - if (!exists) { - boot2docker.init(function (err) { - callback(err); - }); - } else { - if (!boot2docker.sshKeyExists()) { - callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); - } else { - boot2docker.isoVersion(function (err, version) { - if (err || util.compareVersions(version, boot2docker.version()) < 0) { - boot2docker.stop(function(err) { - boot2docker.upgrade(function (err) { - callback(err); - }); - }); - } else { - callback(); - } - }); - } - } - }); - }, - message: 'Setting up the Docker VM...' - }, - { - run: function (callback) { - boot2docker.waitWhileStatus('saving', function (err) { - boot2docker.status(function (err, status) { - if (err) {callback(err); return;} - if (status !== 'running') { - boot2docker.start(function (err) { - callback(err); - }); - } else { - callback(); - } - }); - }); - }, - message: 'Starting the Docker VM...' - }, - { - run: function (callback) { - boot2docker.ip(function (err, ip) { - if (err) {callback(err); return;} - console.log('Setting host IP to: ' + ip); - docker.setHost(ip); - callback(err); - }); - }, - message: 'Detecting Docker VM...' - } -]; +var SetupStore = require('./SetupStore'); var Setup = React.createClass({ mixins: [ Router.Navigation ], @@ -140,6 +16,14 @@ var Setup = React.createClass({ message: '', progress: 0 }; + }, + componentWillMount: function () { + SetupStore.on(SetupStore.PROGRESS_EVENT, this.update); + }, + componentDidMount: function () { + }, + update: function () { + }, render: function () { var radial; @@ -165,53 +49,6 @@ var Setup = React.createClass({ ); } - }, - componentWillMount: function () { - this.setState({}); - }, - componentDidMount: function () { - var self = this; - this.setup(function (err) { - if (!err) { - boot2docker.ip(function (err, ip) { - docker.setHost(ip); - ContainerStore.init(function () { - self.transitionTo('containers'); - }); - }); - } - }); - }, - setup: function (callback) { - var self = this; - var currentStep = 0; - async.eachSeries(setupSteps, function (step, callback) { - console.log('Performing step ' + currentStep); - self.setState({progress: 0}); - self.setState({message: step.message}); - step.run(function (err) { - if (err) { - callback(err); - } else { - currentStep += 1; - callback(); - } - }, function (progress) { - self.setState({progress: progress}); - }); - }, function (err) { - if (err) { - // if any of the steps fail - console.log('Kitematic setup failed at step ' + currentStep); - console.log(err); - self.setState({error: err.message}); - callback(err); - } else { - // Setup Finished - console.log('Setup finished.'); - callback(); - } - }); } }); diff --git a/src/SetupStore.js b/src/SetupStore.js new file mode 100644 index 0000000000..3dcf5fbdb1 --- /dev/null +++ b/src/SetupStore.js @@ -0,0 +1,201 @@ +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var exec = require('exec'); +var boot2docker = require('./Boot2Docker'); +var virtualbox = require('./Virtualbox'); +var setupUtil = require('./SetupUtil'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +var _currentStep = null; +var _error = null; +var _progress = null; + +var SetupStore = assign(EventEmitter.prototype, { + PROGRESS_EVENT: 'setup_progress', + STEP_EVENT: 'setup_step', + ERROR_EVENT: 'setup_error', + downloadVirtualboxStep: { + _download: function (callback, progressCallback) { + setupUtil.virtualboxSHA256(packagejson['virtualbox-version'], packagejson['virtualbox-filename'], function (err, checksum) { + if (err) { + callback(err); + return; + } + var url = 'http://download.virtualbox.org/virtualbox/' + packagejson['virtualbox-version'] + '/' + packagejson['virtualbox-filename']; + var downloadPath = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + setupUtil.download(url, downloadPath, checksum, function (err) { + callback(err); + }, function (progress) { + progressCallback(progress); + }); + }); + }, + run: function (callback, progressCallback) { + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (err) {callback(err); return;} + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + this._download(callback, progressCallback); + } else { + callback(); + } + }); + } else { + this._download(callback, progressCallback); + } + }, + name: 'downloading_virtualbox', + message: 'Downloading Virtualbox', + }, + installVirtualboxStep: { + _install: function (callback) { + exec(['hdiutil', 'attach', path.join(setupUtil.supportDir(), 'VirtualBox-4.3.20-96996-OSX.dmg')], function (stderr, stdout, code) { + if (code) { + callback(stderr); + return; + } + var iconPath = path.join(setupUtil.resourceDir(), 'kitematic.icns'); + setupUtil.isSudo(function (err, isSudo) { + sudoCmd = isSudo ? ['sudo'] : [path.join(setupUtil.resourceDir(), 'cocoasudo'), '--icon=' + iconPath, '--prompt=Kitematic requires administrative privileges to install VirtualBox and copy itself to the Applications folder.']; + sudoCmd.push.apply(sudoCmd, ['installer', '-pkg', '/Volumes/VirtualBox/VirtualBox.pkg', '-target', '/']); + exec(sudoCmd, function (stderr, stdout, code) { + if (code) { + console.log(stderr); + console.log(stdout); + callback('Could not install virtualbox.'); + } else { + exec(['hdiutil', 'detach', '/Volumes/VirtualBox'], function(stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + callback(); + } + }); + } + }); + }); + }); + }, + run: function (callback) { + var self = this; + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + virtualbox.kill(function (err) { + if (err) {callback(err); return;} + self._install(function(err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } else { + self._install(function(err) { + callback(err); + }); + } + }, + name: 'installing_virtualbox', + message: 'Installing VirtualBox', + }, + cleanupKitematicStep: { + run: function (callback) { + virtualbox.vmdestroy('kitematic-vm', function (err, removed) { + if (err) { + console.log(err); + } + callback(); + }); + }, + name: 'cleanup_kitematic', + message: 'Cleaning up existing Kitematic install...' + }, + initBoot2DockerStep: { + run: function (callback) { + boot2docker.exists(function (err, exists) { + if (err) { callback(err); return; } + if (!exists) { + boot2docker.init(function (err) { + callback(err); + }); + } else { + if (!boot2docker.sshKeyExists()) { + callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); + } else { + boot2docker.isoVersion(function (err, version) { + if (err || setupUtil.compareVersions(version, boot2docker.version()) < 0) { + boot2docker.stop(function(err) { + boot2docker.upgrade(function (err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } + } + }); + }, + name: 'init_boot2docker', + message: 'Setting up the Docker VM...' + }, + startBoot2DockerStep: { + run: function (callback) { + boot2docker.waitWhileStatus('saving', function (err) { + boot2docker.status(function (err, status) { + if (err) {callback(err); return;} + if (status !== 'running') { + boot2docker.start(function (err) { + callback(err); + }); + } else { + callback(); + } + }); + }); + }, + name: 'start_boot2docker', + message: 'Starting the Docker VM...' + }, + step: function () { + return _currentStep; + }, + progress: function () { + return _progress; + }, + run: function (callback) { + var self = this; + var steps = [this.downloadVirtualboxStep, this.installVirtualboxStep, this.cleanupKitematicStep, this.initBoot2DockerStep, this.startBoot2DockerStep]; + async.eachSeries(steps, function (step, callback) { + console.log(step.name); + _currentStep = step; + _progress = null; + step.run(function (err) { + if (err) { + callback(err); + } else { + self.emit(self.STEP_EVENT); + callback(); + } + }, function (progress) { + self.emit(self.PROGRESS_EVENT, progress); + _progress = progress; + }); + }, function (err) { + if (err) { + self.emit(self.ERROR_EVENT, _error); + callback(err); + } else { + callback(); + } + }); + } +}); + +module.exports = SetupStore; diff --git a/src/Util.js b/src/SetupUtil.js similarity index 66% rename from src/Util.js rename to src/SetupUtil.js index 78a4c61035..d3b8d696e9 100644 --- a/src/Util.js +++ b/src/SetupUtil.js @@ -1,13 +1,13 @@ -var path = require('path'); -var fs = require('fs'); -var nodeCrypto = require('crypto'); var request = require('request'); var progress = require('request-progress'); +var path = require('path'); +var crypto = require('crypto'); +var fs = require('fs'); var exec = require('exec'); -var Util = { +var SetupUtil = { supportDir: function (callback) { - var dirs = ['Application\ Support', 'Kitematic']; + var dirs = ['Library', 'Application\ Support', 'Kitematic']; var acc = process.env.HOME; dirs.forEach(function (d) { acc = path.join(acc, d); @@ -17,6 +17,19 @@ var Util = { }); return acc; }, + resourceDir: function (callback) { + return process.env.RESOURCES_PATH; + }, + isSudo: function (callback) { + exec(['sudo', '-n', '-u', 'root', 'true'], function (stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + var isSudo = stderr.indexOf('a password is required') === -1; + callback(null, isSudo); + } + }); + }, download: function (url, filename, checksum, callback, progressCallback) { var doDownload = function () { progress(request({ @@ -37,8 +50,7 @@ var Util = { // Compare checksum to see if it already exists first if (fs.existsSync(filename)) { - var existingChecksum = nodeCrypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); - console.log(existingChecksum); + var existingChecksum = crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); if (existingChecksum !== checksum) { fs.unlinkSync(filename); doDownload(); @@ -49,6 +61,22 @@ var Util = { doDownload(); } }, + virtualboxSHA256: function (version, filename, callback) { + var checksumUrl = 'http://dlc-cdn.sun.com/virtualbox/' + version + '/SHA256SUMS'; + request(checksumUrl, function (error, response, body) { + if (error) { + callback(error); + return; + } + var checksums = body.split('\n').map(function (line) { + return line.split(' *'); + }).reduce(function (obj, pair) { + obj[pair[1]] = pair[0]; + return obj; + }, {}); + callback(null, checksums[filename]); + }); + }, compareVersions: function (v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, @@ -100,4 +128,4 @@ var Util = { } }; -module.exports = Util; +module.exports = SetupUtil; diff --git a/src/Virtualbox.js b/src/Virtualbox.js index 503d4efad2..446537c461 100644 --- a/src/Virtualbox.js +++ b/src/Virtualbox.js @@ -5,32 +5,18 @@ var async = require('async'); var util = require('./Util'); var VirtualBox = { - REQUIRED_VERSION: '4.3.18', - INCLUDED_VERSION: '4.3.18', - INSTALLER_FILENAME: 'virtualbox-4.3.18.pkg', - INSTALLER_CHECKSUM: '5836c94481c460c648b9216386591a2915293ac86b9bb6c57746637796af6af2', command: function () { return '/usr/bin/VBoxManage'; }, installed: function () { return fs.existsSync('/usr/bin/VBoxManage') && fs.existsSync('/Applications/VirtualBox.app/Contents/MacOS/VirtualBox'); }, - install: function (callback) { - // -W waits for the process to close before finishing. - exec('open -W ' + path.join(util.supportDir(), this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) { - if (code) { - callback(stderr); - return; - } - callback(null); - }); - }, version: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('/usr/bin/VBoxManage -v', function (stderr, stdout, code) { + exec([this.command(), '-v'], function (stderr, stdout, code) { if (code) { callback(stderr); return; @@ -44,12 +30,12 @@ var VirtualBox = { callback(null, match[1]); }); }, - saveVMs: function (callback) { + poweroff: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('/usr/bin/VBoxManage list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} /usr/bin/VBoxManage controlvm {} savestate', function (stderr, stdout, code) { + exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} acpipowerbutton', function (stderr, stdout, code) { if (code) { callback(stderr); } else { @@ -58,7 +44,7 @@ var VirtualBox = { }); }, kill: function (callback) { - this.saveRunningVMs(function (err) { + this.poweroff(function (err) { if (err) {callback(err); return;} exec('pkill VirtualBox', function (stderr, stdout, code) { if (code) {callback(stderr); return;} @@ -69,7 +55,7 @@ var VirtualBox = { }); }); }, - vmState: function (name, callback) { + vmstate: function (name, callback) { exec(this.command() + ' showvminfo ' + name + ' --machinereadable', function (stderr, stdout, code) { if (code) { callback(stderr); return; } var match = stdout.match(/VMState="(\w+)"/); @@ -80,8 +66,9 @@ var VirtualBox = { callback(null, match[1]); }); }, - deleteVM:function (name, callback) { - VirtualBox.vmState(name, function (err, state) { + vmdestroy: function (name, callback) { + var self = this; + this.vmstate(name, function (err, state) { // No VM found if (err) { callback(null, false); return; } exec('/usr/bin/VBoxManage controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) { @@ -91,7 +78,7 @@ var VirtualBox = { async.until(function () { return state === 'poweroff'; }, function (callback) { - VirtualBox.vmState(name, function (err, newState) { + self.vmstate(name, function (err, newState) { if (err) { callback(err); return; } state = newState; setTimeout(callback, 250); diff --git a/tests/SetupStore-integration.js b/tests/SetupStore-integration.js new file mode 100644 index 0000000000..4060a12812 --- /dev/null +++ b/tests/SetupStore-integration.js @@ -0,0 +1,100 @@ +var virtualbox = require('../build/Virtualbox'); +var SetupStore = require('../build/SetupStore'); +var setupUtil = require('../build/SetupUtil'); +var path = require('path'); +var fs = require('fs'); +var child_process = require('child_process'); +var exec = require('exec'); +var rimraf = require('rimraf'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 300000; // 5 minutes + +describe('Setup', function () { + describe('without virtualbox installed or downloaded', function () { + var virtualboxFile = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + beforeEach(function () { + if (fs.existsSync(virtualboxFile)) { + fs.unlinkSync(virtualboxFile); + } + spyOn(virtualbox, 'installed').andCallFake(function (callback) { + callback(false); + }); + }); + + it('downloads virtualbox', function (done) { + SetupStore.downloadVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + expect(fs.existsSync(virtualboxFile)).toBe(true); + done(); + }, function (progress) { + + }); + }); + }); + + describe('with virtualbox downloaded but not installed', function () { + beforeEach(function (done) { + // 5 minute timeout per test + + SetupStore.downloadVirtualboxStep.run(function (err) { + if (virtualbox.installed()) { + virtualbox.kill(function (callback) { + done(); + }); + } else { + done(); + } + }, function (progress) {}); + }); + + it('does install virtualbox', function (done) { + SetupStore.installVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + done(); + }); + }); + }); + + describe('with virtualbox installed', function () { + + // Before each teardown the boot2docker VM, keys and anything else + + describe('and with a kitematic vm', function () { + + }); + + describe('and without a boot2docker vm', function () { + + }); + + describe('and with an old boot2docker vm', function () { + + }); + + describe('and with a very old boot2docker vm', function () { + + }); + + describe('and with a boot2docker vm running', function () { + + }); + + describe('and with a boot2docker vm but with no ssh keys', function () { + + }); + + describe('and with a boot2docker vm being powered off', function () { + + }); + + describe('and with a boot2docker vm being removed', function () { + + }); + + describe('and with a boot2docker vm initialized but not running', function () { + + }); + }); + +}); diff --git a/tests/SetupUtil-integration.js b/tests/SetupUtil-integration.js new file mode 100644 index 0000000000..b56ef2e2e0 --- /dev/null +++ b/tests/SetupUtil-integration.js @@ -0,0 +1,10 @@ +var setupUtil = require('../build/SetupUtil'); + +describe('SetupUtils', function() { + it('returns live sha256 checksum for a given virtualbox version & filename', function (done) { + setupUtil.virtualboxSHA256('4.3.20', 'VirtualBox-4.3.20-96996-OSX.dmg', function (err, checksum) { + expect(checksum).toBe('744e77119a640a5974160213c9912568a3d88dbd06a2fc6b6970070941732705'); + done(); + }); + }); +}); diff --git a/specs/jasmine-2.1.3/boot.js b/tests/jasmine-2.1.3/boot.js similarity index 100% rename from specs/jasmine-2.1.3/boot.js rename to tests/jasmine-2.1.3/boot.js diff --git a/specs/jasmine-2.1.3/console.js b/tests/jasmine-2.1.3/console.js similarity index 98% rename from specs/jasmine-2.1.3/console.js rename to tests/jasmine-2.1.3/console.js index a65876e911..96026ab63a 100755 --- a/specs/jasmine-2.1.3/console.js +++ b/tests/jasmine-2.1.3/console.js @@ -57,8 +57,6 @@ getJasmineRequireObj().ConsoleReporter = function() { }, failedSuites = []; - print('ConsoleReporter is deprecated and will be removed in a future version.'); - this.jasmineStarted = function() { specCount = 0; failureCount = 0; diff --git a/specs/jasmine-2.1.3/jasmine-html.js b/tests/jasmine-2.1.3/jasmine-html.js similarity index 100% rename from specs/jasmine-2.1.3/jasmine-html.js rename to tests/jasmine-2.1.3/jasmine-html.js diff --git a/specs/jasmine-2.1.3/jasmine.css b/tests/jasmine-2.1.3/jasmine.css similarity index 100% rename from specs/jasmine-2.1.3/jasmine.css rename to tests/jasmine-2.1.3/jasmine.css diff --git a/specs/jasmine-2.1.3/jasmine.js b/tests/jasmine-2.1.3/jasmine.js similarity index 100% rename from specs/jasmine-2.1.3/jasmine.js rename to tests/jasmine-2.1.3/jasmine.js diff --git a/specs/jasmine-2.1.3/jasmine_favicon.png b/tests/jasmine-2.1.3/jasmine_favicon.png similarity index 100% rename from specs/jasmine-2.1.3/jasmine_favicon.png rename to tests/jasmine-2.1.3/jasmine_favicon.png diff --git a/specs/specs.html b/tests/tests.html similarity index 59% rename from specs/specs.html rename to tests/tests.html index 6cef94b504..0d298c493c 100755 --- a/specs/specs.html +++ b/tests/tests.html @@ -4,7 +4,6 @@ - - + diff --git a/specs/specs.js b/tests/tests.js similarity index 69% rename from specs/specs.js rename to tests/tests.js index 153e947977..e21261d882 100644 --- a/specs/specs.js +++ b/tests/tests.js @@ -2,19 +2,22 @@ window.jasmineRequire = require('./jasmine-2.1.3/jasmine'); require('./jasmine-2.1.3/jasmine-html'); require('./jasmine-2.1.3/boot'); var consoleReporter = require('./jasmine-2.1.3/console'); +var app = require('remote').require('app'); jasmine.getEnv().addReporter(new consoleReporter.ConsoleReporter()({ showColors: true, timer: new jasmine.Timer(), print: function() { - console.log(arguments); process.stdout.write.apply(process.stdout, arguments); + }, + onComplete: function () { + app.quit(); } })); var fs = require('fs'); -var tests = fs.readdirSync('./specs').filter(function (f) { - return f.indexOf('-spec') !== -1; +var tests = fs.readdirSync('./tests').filter(function (f) { + return f.indexOf('-' + process.env.TEST_TYPE) !== -1; }); tests.forEach(function (t) { diff --git a/tests/util/vboxuninstall b/tests/util/vboxuninstall new file mode 100755 index 0000000000..becb743bc6 --- /dev/null +++ b/tests/util/vboxuninstall @@ -0,0 +1,245 @@ +#!/bin/bash +# $Id: VirtualBox_Uninstall.tool 89624 2013-10-07 16:13:23Z bird $ +## @file +# VirtualBox Uninstaller Script. +# + +# +# Copyright (C) 2007-2013 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# Override any funny stuff from the user. +export PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH" + +# +# Display a simple welcome message first. +# +echo "" +echo "Welcome to the VirtualBox uninstaller script." +echo "" + +# +# Check for arguments and display +# +my_default_prompt=0 +if test "$#" != "0"; then + if test "$#" != "1" -o "$1" != "--unattended"; then + echo "Error: Unknown argument(s): $*" + echo "" + echo "Usage: uninstall.sh [--unattended]" + echo "" + echo "If the '--unattended' option is not given, you will be prompted" + echo "for a Yes/No before doing the actual uninstallation." + echo "" + exit 4; + fi + my_default_prompt="Yes" +fi + +# +# Collect directories and files to remove. +# Note: Do NOT attempt adding directories or filenames with spaces! +# +declare -a my_directories +declare -a my_files + +# Users files first +test -f "${HOME}/Library/LaunchAgents/org.virtualbox.vboxwebsrv.plist" && my_files+=("${HOME}/Library/LaunchAgents/org.virtualbox.vboxwebsrv.plist") + +test -d /Library/StartupItems/VirtualBox/ && my_directories+=("/Library/StartupItems/VirtualBox/") +test -d /Library/Receipts/VBoxStartupItems.pkg/ && my_directories+=("/Library/Receipts/VBoxStartupItems.pkg/") + +test -d "/Library/Application Support/VirtualBox/LaunchDaemons/" && my_directories+=("/Library/Application Support/VirtualBox/LaunchDaemons/") +test -d "/Library/Application Support/VirtualBox/VBoxDrv.kext/" && my_directories+=("/Library/Application Support/VirtualBox/VBoxDrv.kext/") +test -d "/Library/Application Support/VirtualBox/VBoxUSB.kext/" && my_directories+=("/Library/Application Support/VirtualBox/VBoxUSB.kext/") +test -d "/Library/Application Support/VirtualBox/VBoxNetFlt.kext/" && my_directories+=("/Library/Application Support/VirtualBox/VBoxNetFlt.kext/") +test -d "/Library/Application Support/VirtualBox/VBoxNetAdp.kext/" && my_directories+=("/Library/Application Support/VirtualBox/VBoxNetAdp.kext/") +# Pre 4.3.0rc1 locations: +test -d /Library/Extensions/VBoxDrv.kext/ && my_directories+=("/Library/Extensions/VBoxDrv.kext/") +test -d /Library/Extensions/VBoxUSB.kext/ && my_directories+=("/Library/Extensions/VBoxUSB.kext/") +test -d /Library/Extensions/VBoxNetFlt.kext/ && my_directories+=("/Library/Extensions/VBoxNetFlt.kext/") +test -d /Library/Extensions/VBoxNetAdp.kext/ && my_directories+=("/Library/Extensions/VBoxNetAdp.kext/") +# Tiger support is obsolete, but we leave it here for a clean removing of older +# VirtualBox versions +test -d /Library/Extensions/VBoxDrvTiger.kext/ && my_directories+=("/Library/Extensions/VBoxDrvTiger.kext/") +test -d /Library/Extensions/VBoxUSBTiger.kext/ && my_directories+=("/Library/Extensions/VBoxUSBTiger.kext/") +test -d /Library/Receipts/VBoxKEXTs.pkg/ && my_directories+=("/Library/Receipts/VBoxKEXTs.pkg/") + +test -f /usr/bin/VirtualBox && my_files+=("/usr/bin/VirtualBox") +test -f /usr/bin/VBoxManage && my_files+=("/usr/bin/VBoxManage") +test -f /usr/bin/VBoxVRDP && my_files+=("/usr/bin/VBoxVRDP") +test -f /usr/bin/VBoxHeadless && my_files+=("/usr/bin/VBoxHeadless") +test -f /usr/bin/vboxwebsrv && my_files+=("/usr/bin/vboxwebsrv") +test -f /usr/bin/VBoxBalloonCtrl && my_files+=("/usr/bin/VBoxBalloonCtrl") +test -f /usr/bin/VBoxAutostart && my_files+=("/usr/bin/VBoxAutostart") +test -f /usr/bin/vbox-img && my_files+=("/usr/bin/vbox-img") +test -d /Library/Receipts/VirtualBoxCLI.pkg/ && my_directories+=("/Library/Receipts/VirtualBoxCLI.pkg/") +test -f /Library/LaunchDaemons/org.virtualbox.startup.plist && my_files+=("/Library/LaunchDaemons/org.virtualbox.startup.plist") + +test -d /Applications/VirtualBox.app/ && my_directories+=("/Applications/VirtualBox.app/") +test -d /Library/Receipts/VirtualBox.pkg/ && my_directories+=("/Library/Receipts/VirtualBox.pkg/") + +# legacy +test -d /Library/Receipts/VBoxDrv.pkg/ && my_directories+=("/Library/Receipts/VBoxDrv.pkg/") +test -d /Library/Receipts/VBoxUSB.pkg/ && my_directories+=("/Library/Receipts/VBoxUSB.pkg/") + +# python stuff +python_versions="2.3 2.5 2.6 2.7" +for p in $python_versions; do + test -f /Library/Python/$p/site-packages/vboxapi/VirtualBox_constants.py && my_files+=("/Library/Python/$p/site-packages/vboxapi/VirtualBox_constants.py") + test -f /Library/Python/$p/site-packages/vboxapi/VirtualBox_constants.pyc && my_files+=("/Library/Python/$p/site-packages/vboxapi/VirtualBox_constants.pyc") + test -f /Library/Python/$p/site-packages/vboxapi/__init__.py && my_files+=("/Library/Python/$p/site-packages/vboxapi/__init__.py") + test -f /Library/Python/$p/site-packages/vboxapi/__init__.pyc && my_files+=("/Library/Python/$p/site-packages/vboxapi/__init__.pyc") + test -f /Library/Python/$p/site-packages/vboxapi-1.0-py$p.egg-info && my_files+=("/Library/Python/$p/site-packages/vboxapi-1.0-py$p.egg-info") + test -d /Library/Python/$p/site-packages/vboxapi/ && my_directories+=("/Library/Python/$p/site-packages/vboxapi/") +done + +# +# Collect KEXTs to remove. +# Note that the unload order is significant. +# +declare -a my_kexts +for kext in org.virtualbox.kext.VBoxUSB org.virtualbox.kext.VBoxNetFlt org.virtualbox.kext.VBoxNetAdp org.virtualbox.kext.VBoxDrv; do + if /usr/sbin/kextstat -b $kext -l | grep -q $kext; then + my_kexts+=("$kext") + fi +done + +# +# Collect packages to forget +# +my_pb='org\.virtualbox\.pkg\.' +my_pkgs=`/usr/sbin/pkgutil --pkgs="${my_pb}vboxkexts|${my_pb}vboxstartupitems|${my_pb}virtualbox|${my_pb}virtualboxcli"` + +# +# Did we find anything to uninstall? +# +if test -z "${my_directories[*]}" -a -z "${my_files[*]}" -a -z "${my_kexts[*]}" -a -z "$my_pkgs"; then + echo "No VirtualBox files, directories, KEXTs or packages to uninstall." + echo "Done." + exit 0; +fi + +# +# Look for running VirtualBox processes and warn the user +# if something is running. Since deleting the files of +# running processes isn't fatal as such, we will leave it +# to the user to choose whether to continue or not. +# +# Note! comm isn't supported on Tiger, so we make -c to do the stripping. +# +my_processes="`ps -axco 'pid uid command' | grep -wEe '(VirtualBox|VirtualBoxVM|VBoxManage|VBoxHeadless|vboxwebsrv|VBoxXPCOMIPCD|VBoxSVC|VBoxNetDHCP|VBoxNetNAT)' | grep -vw grep | grep -vw VirtualBox_Uninstall.tool | tr '\n' '\a'`"; +if test -n "$my_processes"; then + echo 'Warning! Found the following active VirtualBox processes:' + echo "$my_processes" | tr '\a' '\n' + echo "" + echo "We recommend that you quit all VirtualBox processes before" + echo "uninstalling the product." + echo "" + if test "$my_default_prompt" != "Yes"; then + echo "Do you wish to continue none the less (Yes/No)?" + read my_answer + if test "$my_answer" != "Yes" -a "$my_answer" != "YES" -a "$my_answer" != "yes"; then + echo "Aborting uninstall. (answer: '$my_answer')". + exit 2; + fi + echo "" + my_answer="" + fi +fi + +# +# Display the files and directories that will be removed +# and get the user's consent before continuing. +# +if test -n "${my_files[*]}" -o -n "${my_directories[*]}"; then + echo "The following files and directories (bundles) will be removed:" + for file in "${my_files[@]}"; do echo " $file"; done + for dir in "${my_directories[@]}"; do echo " $dir"; done + echo "" +fi +if test -n "${my_kexts[*]}"; then + echo "And the following KEXTs will be unloaded:" + for kext in "${my_kexts[@]}"; do echo " $kext"; done + echo "" +fi +if test -n "$my_pkgs"; then + echo "And the traces of following packages will be removed:" + for kext in $my_pkgs; do echo " $kext"; done + echo "" +fi + +if test "$my_default_prompt" != "Yes"; then + echo "Do you wish to uninstall VirtualBox (Yes/No)?" + read my_answer + if test "$my_answer" != "Yes" -a "$my_answer" != "YES" -a "$my_answer" != "yes"; then + echo "Aborting uninstall. (answer: '$my_answer')". + exit 2; + fi + echo "" +fi + +# +# Unregister has to be done before the files are removed. +# +LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister +if [ -e ${LSREGISTER} ]; then + ${LSREGISTER} -u /Applications/VirtualBox.app > /dev/null + ${LSREGISTER} -u /Applications/VirtualBox.app/Contents/Resources/vmstarter.app > /dev/null +fi + +# +# Display the sudo usage instructions and execute the command. +# +echo "The uninstallation processes requires administrative privileges" +echo "because some of the installed files cannot be removed by a normal" +echo "user. You may be prompted for your password now..." +echo "" + +if test -n "${my_files[*]}" -o -n "${my_directories[*]}"; then + /usr/bin/sudo -p "Please enter %u's password:" /bin/rm -Rf "${my_files[@]}" "${my_directories[@]}" + my_rc=$? + if test "$my_rc" -ne 0; then + echo "An error occurred durning 'sudo rm', there should be a message above. (rc=$my_rc)" + test -x /usr/bin/sudo || echo "warning: Cannot find /usr/bin/sudo or it's not an executable." + test -x /bin/rm || echo "warning: Cannot find /bin/rm or it's not an executable" + echo "" + echo "The uninstall failed. Please retry." + exit 1; + fi +fi + +my_rc=0 +for kext in "${my_kexts[@]}"; do + echo unloading $kext + /usr/bin/sudo -p "Please enter %u's password (unloading $kext):" /sbin/kextunload -m $kext + my_rc2=$? + if test "$my_rc2" -ne 0; then + echo "An error occurred durning 'sudo /sbin/kextunload -m $kext', there should be a message above. (rc=$my_rc2)" + test -x /usr/bin/sudo || echo "warning: Cannot find /usr/bin/sudo or it's not an executable." + test -x /sbin/kextunload || echo "warning: Cannot find /sbin/kextunload or it's not an executable" + my_rc=$my_rc2 + fi +done +if test "$my_rc" -eq 0; then + echo "Successfully unloaded VirtualBox kernel extensions." +else + echo "Failed to unload one or more KEXTs, please reboot the machine to complete the uninstall." + exit 1; +fi + +# Cleaning up pkgutil database +for my_pkg in $my_pkgs; do + /usr/bin/sudo -p "Please enter %u's password (removing $my_pkg):" /usr/sbin/pkgutil --forget "$my_pkg" +done + +echo "Done." +exit 0;