Implement Linux support

This commit is contained in:
Guillaume Hain 2015-06-20 12:28:00 +02:00
parent 0ed907553b
commit de0b2efd2a
No known key found for this signature in database
GPG Key ID: D2B2A8B99474CF36
10 changed files with 141 additions and 25 deletions

View File

@ -22,6 +22,7 @@ module.exports = function (grunt) {
var BASENAME = 'Kitematic';
var OSX_APPNAME = BASENAME + ' (Beta)';
var WINDOWS_APPNAME = BASENAME + ' (Alpha)';
var LINUX_APPNAME = BASENAME + ' (Alpha)';
var OSX_OUT = './dist';
var OSX_OUT_X64 = OSX_OUT + '/' + OSX_APPNAME + '-darwin-x64';
var OSX_FILENAME = OSX_OUT_X64 + '/' + OSX_APPNAME + '.app';
@ -57,6 +58,19 @@ module.exports = function (grunt) {
'app-bundle-id': 'com.kitematic.kitematic',
'app-version': packagejson.version
}
},
linux: {
options: {
name: LINUX_APPNAME,
dir: 'build/',
out: 'dist/linux/',
version: packagejson['electron-version'],
platform: 'linux',
arch: 'x64',
asar: true,
'app-bundle-id': 'com.kitematic.kitematic',
'app-version': packagejson.version
}
}
},
@ -243,7 +257,11 @@ module.exports = function (grunt) {
});
grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']);
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']);
if(process.platform === 'linux') {
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:linux']);
} else {
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']);
}
process.on('SIGINT', function () {
grunt.task.run(['shell:electron:kill']);

View File

@ -3,7 +3,7 @@
[![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)](https://kitematic.com)
Kitematic is a simple application for managing Docker containers on Mac and Windows.
Kitematic is a simple application for managing Docker containers on Mac, Linux and Windows.
![Kitematic Screenshot](https://cloud.githubusercontent.com/assets/251292/8246120/d3ab271a-15ed-11e5-8736-9a730a27c79a.png)

View File

@ -17,6 +17,7 @@ import Router from 'react-router';
import routes from './routes';
import routerContainer from './router';
import repositoryActions from './actions/RepositoryActions';
import util from './utils/Util';
var app = remote.require('app');
hubUtil.init();
@ -46,7 +47,8 @@ var router = Router.create({
router.run(Handler => React.render(<Handler/>, document.body));
routerContainer.set(router);
setupUtil.setup().then(() => {
let setup = util.isLinux() ? setupUtil.nativeSetup : setupUtil.nonNativeSetup;
setup().then(() => {
Menu.setApplicationMenu(Menu.buildFromTemplate(template()));
docker.init();
if (!hub.prompted() && !hub.loggedin()) {

View File

@ -35,10 +35,11 @@ var Preferences = React.createClass({
});
},
render: function () {
return (
<div className="preferences">
<div className="preferences-content">
<a onClick={this.handleGoBackClick}>Go Back</a>
var vmSettings;
if (process.platform !== 'linux') {
vmSettings = (
<div>
<div className="title">VM Settings</div>
<div className="option">
<div className="option-name">
@ -48,6 +49,15 @@ var Preferences = React.createClass({
<input type="checkbox" checked={this.state.closeVMOnQuit} onChange={this.handleChangeCloseVMOnQuit}/>
</div>
</div>
</div>
);
}
return (
<div className="preferences">
<div className="preferences-content">
<a onClick={this.handleGoBackClick}>Go Back</a>
{vmSettings}
<div className="title">App Settings</div>
<div className="option">
<div className="option-name">

View File

@ -3,6 +3,7 @@ import Router from 'react-router';
import Radial from './Radial.react.js';
import RetinaImage from 'react-retina-image';
import Header from './Header.react';
import Util from '../utils/Util';
import metrics from '../utils/MetricsUtil';
import setupStore from '../stores/SetupStore';
import setupActions from '../actions/SetupActions';
@ -43,6 +44,13 @@ var Setup = React.createClass({
shell.openExternal('https://www.docker.com/docker-toolbox');
},
handleLinuxDockerInstall: function () {
metrics.track('Opening Linux Docker installation instructions', {
from: 'setup'
});
shell.openExternal('http://docs.docker.com/linux/started/');
},
renderContents: function () {
return (
<div className="contents">
@ -74,6 +82,25 @@ var Setup = React.createClass({
},
renderError: function () {
let deleteVmAndRetry;
if (Util.isLinux()) {
if (!this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
);
}
} else {
if (this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button>
);
} else {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
);
}
}
return (
<div className="setup">
<Header hideLogin={true}/>
@ -93,7 +120,7 @@ var Setup = React.createClass({
<p className="error">{this.state.error.message || this.state.error}</p>
<p className="setup-actions">
<button className="btn btn-action" onClick={this.handleErrorRetry}>Retry Setup</button>
{this.state.started ? <button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button> : <button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>}
{{deleteVmAndRetry}}
</p>
</div>
</div>

View File

@ -151,6 +151,11 @@ var DockerMachine = {
}
});
});
} else if (util.isLinux()) {
cmd = cmd || process.env.SHELL;
var terminal = util.linuxTerminal();
if (terminal)
util.exec(terminal.concat([cmd])).then(() => {});
} else {
cmd = cmd || process.env.SHELL;
this.url(machineName).then(machineUrl => {

View File

@ -3,6 +3,7 @@ import fs from 'fs';
import path from 'path';
import dockerode from 'dockerode';
import _ from 'underscore';
import child_process from 'child_process';
import util from './Util';
import hubUtil from './HubUtil';
import metrics from '../utils/MetricsUtil';
@ -20,20 +21,25 @@ export default {
throw new Error('Falsy ip or name passed to docker client setup');
}
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
if (!fs.existsSync(certDir)) {
throw new Error('Certificate directory does not exist');
}
if (util.isLinux()) {
this.host = 'localhost';
this.client = new dockerode({socketPath: '/var/run/docker.sock'});
} else {
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
if (!fs.existsSync(certDir)) {
throw new Error('Certificate directory does not exist');
}
this.host = ip;
this.client = new dockerode({
protocol: 'https',
host: ip,
port: 2376,
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
key: fs.readFileSync(path.join(certDir, 'key.pem'))
});
this.host = ip;
this.client = new dockerode({
protocol: 'https',
host: ip,
port: 2376,
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
key: fs.readFileSync(path.join(certDir, 'key.pem'))
});
}
},
init () {
@ -66,6 +72,14 @@ export default {
});
},
isDockerRunning () {
try {
child_process.execSync('ps ax | grep "docker daemon" | grep -v grep');
} catch (error) {
throw new Error('Cannot connect to the Docker daemon. The daemon is not running.');
}
},
startContainer (name, containerData) {
let startopts = {
Binds: containerData.Binds || []

View File

@ -2,8 +2,8 @@ import _ from 'underscore';
import fs from 'fs';
import path from 'path';
import Promise from 'bluebird';
import util from './Util';
import bugsnag from 'bugsnag-js';
import util from './Util';
import virtualBox from './VirtualBoxUtil';
import setupServerActions from '../actions/SetupServerActions';
import metrics from './MetricsUtil';
@ -51,7 +51,31 @@ export default {
return _retryPromise.promise;
},
async setup () {
async nativeSetup () {
while (true) {
try {
docker.setup('localhost', machine.name());
docker.isDockerRunning();
break;
} catch (error) {
router.get().transitionTo('setup');
metrics.track('Native Setup Failed');
setupServerActions.error({error});
let message = error.message.split('\n');
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';
bugsnag.notify('Native Setup Failed', lastLine, {
'Docker Machine Logs': error.message
}, 'info');
this.clearTimers();
await this.pause();
}
}
},
async nonNativeSetup () {
let virtualBoxVersion = null;
let machineVersion = null;
while (true) {

View File

@ -5,6 +5,7 @@ import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import remote from 'remote';
var dialog = remote.require('dialog');
var app = remote.require('app');
module.exports = {
@ -34,6 +35,9 @@ module.exports = {
isWindows: function () {
return process.platform === 'win32';
},
isLinux: function () {
return process.platform === 'linux';
},
binsPath: function () {
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
},
@ -156,5 +160,17 @@ module.exports = {
linuxToWindowsPath: function (linuxAbsPath) {
return linuxAbsPath.replace('/c', 'C:').split('/').join('\\');
},
linuxTerminal: function () {
if (fs.existsSync('/usr/bin/x-terminal-emulator')) {
return ['/usr/bin/x-terminal-emulator', '-e'];
} else {
dialog.showMessageBox({
type: 'warning',
buttons: ['OK'],
message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/kitematic/kitematic/wiki/Common-Issues-and-Fixes#early-linux-support-from-zedtux.'
});
return;
}
},
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
};

View File

@ -23,8 +23,8 @@
@color-box-button: lighten(@gray-lightest, 5%);
@color-background: lighten(@gray-lightest, 4.5%);
@font-regular: "Helvetica Neue", Segoe UI, Arial, "Lucida Grande", sans-serif;
@font-code: Menlo, Consolas;
@font-regular: "Helvetica Neue", Segoe UI, "Ubuntu", Arial, "Lucida Grande", sans-serif;
@font-code: Menlo, Consolas, "DejaVu Sans Mono";
@border-radius: 0.2rem;