diff --git a/package.json b/package.json index 6fe45cc3..85c0c553 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,15 @@ }, "dependencies": { "coffee-script": "~1.6.3", - "async": "~0.2.9", "request": "~2.22.0", - "posix": "~1.0.2", "express": "~3.2.6", "lodash": "~1.3.1", "csr-gen": "~0.2.1", - "dockerode": "~0.2.3", - "sqlite3": "~2.1.19", + "dockerode": "~0.2.5", "knex": "~0.5.1", - "bluebird": "~0.11.5-0" + "bluebird": "~0.11.5-0", + "JSONStream": "~0.7.1", + "event-stream": "~3.0.20" }, "engines": [ "node >= 0.10.x" diff --git a/src/app.coffee b/src/app.coffee index 6612b1c9..2724d965 100644 --- a/src/app.coffee +++ b/src/app.coffee @@ -4,16 +4,22 @@ os = require 'os' api = require './api' knex = require './db' utils = require './utils' +Docker = require 'dockerode' crypto = require 'crypto' {spawn} = require 'child_process' bootstrap = require './bootstrap' +application = require './application' console.log('Supervisor started..') +# Connect to the host docker instance +docker = Promise.promisifyAll(new Docker(socketPath: '/hostrun/docker.sock')) + newUuid = utils.getDeviceUuid() oldUuid = knex('config').select('value').where(key: 'uuid') Promise.all([newUuid, oldUuid]).then(([newUuid, [oldUuid]]) -> + oldUuid = oldUuid?.value if newUuid is oldUuid return true @@ -37,4 +43,11 @@ Promise.all([newUuid, oldUuid]).then(([newUuid, [oldUuid]]) -> console.log('Starting API server..') api.listen(80) + + console.log('Starting Apps..') + knex('app').select().then((apps) -> + Promise.all(apps.map(application.start)) + ).catch((error) -> + console.log(error) + ) ) diff --git a/src/application.coffee b/src/application.coffee index 1584b4a9..a73c8732 100644 --- a/src/application.coffee +++ b/src/application.coffee @@ -1,135 +1,38 @@ -{spawn} = require('child_process') -{getpwnam} = require('posix') -async = require('async') -{EventEmitter} = require('events') -_ = require('lodash') -state = require('./state') +Promise = require 'bluebird' +Docker = require 'dockerode' +JSONStream = require 'JSONStream' +es = require 'event-stream' +http = require 'http' -class Application extends EventEmitter - constructor: (@repo, @path, @user) -> - EventEmitter.call(this) - @process = null - @inprogress = false - @queue = [] - @options = - cwd: @path - stdio: 'inherit' - uid: getpwnam(@user).uid - gid: getpwnam(@user).gid - env: - USER: @user - USERNAME: @user - HOME: "/home/#{@user}" +docker = Promise.promisifyAll(new Docker(socketPath: '/hostrun/docker.sock')) +# Hack dockerode to promisify internal classes' prototype +Promise.promisifyAll(docker.getImage().__proto__) - _init: (callback) -> - tasks = [ - # Create the directory for the project - (callback) => - spawn('mkdir', ['-p', @path]).on('exit', callback).on('error', callback) +exports.start = (app) -> + docker.getImage(app.imageId).inspectAsync().catch(-> + deferred = Promise.defer() + options = + method: 'POST' + path: "/v1.8/images/create?fromImage=#{app.imageId}" + socketPath: '/hostrun/docker.sock' - # Change the owner to the user - (callback) => - spawn('chown', [@user, @path]).on('exit', callback).on('error', callback) + req = http.request(options, (res) -> + if res.statusCode isnt 200 + return deferred.reject() - # Initalize a new empty git repo - (callback) => - spawn('git', ['init'], @options).on('exit', callback).on('error', callback) + res.pipe(JSONStream.parse('error')) + .pipe(es.mapSync((error) -> + deferred.reject(error) + )) - # Add the remote origin to the repo - (callback) => - spawn('git', ['remote', 'add', 'origin', @repo], @options).on('exit', callback).on('error', callback) - ] - @emit('pre-init') - async.series(tasks, => - @emit('post-init') - callback?(arguments...) + res.on('end', -> + deferred.resolve() + ) ) + req.end() + req.on('error', (error) -> deferred.reject(error)) - _exitHandler: (code, signal) -> - @process = null - - _start: (callback) -> - if not @process - options = - cwd: @path - stdio: 'inherit' - - @process = spawn('sudo', ['-u', @user, 'foreman', 'start'], options) - @process.on('exit', @_exitHandler.bind(@)) - @process.on('error', @_exitHandler.bind(@)) - @emit('start') - callback?() - - _stop: (callback) -> - # Kill will return false if process has already died - handler = => - @process = null - @emit('stop') - callback?(arguments...) - - spawn('pkill', ['-TERM', '-P', @process.pid], @options).on('exit', handler).on('error', handler) - - _update: (callback) -> - tasks = [ - # Stop the application if running - (callback) => - if @process - @_stop(callback) - else - callback() - - # Fetch new commits - (callback) => - spawn('git', ['fetch'], @options).on('exit', callback).on('error', callback) - - # Reset our master branch to origin/master - (callback) => - spawn('git', ['reset', '--hard', 'origin/master'], @options).on('exit', callback).on('error', callback) - - # Save the new commit hash - (callback) => - options = _.clone(@options) - delete options.stdio - ps = spawn('git', ['rev-parse', 'HEAD'], options).on('close', callback).on('error', callback) - - # The hash will always be on the first chunk as I/O buffers are always larger than 40 bytes - ps.stdout.on('data', (hash) -> - hash = '' + hash - state.set('gitHash', hash.trim()) - ) - - # Prune npm dependencies - (callback) => - spawn('npm', ['prune'], @options).on('exit', callback).on('error', callback) - - # Install npm dependencies - (callback) => - spawn('npm', ['install'], @options).on('exit', callback).on('error', callback) - - # Start the app - (callback) => - @_start(callback) - ] - @emit('pre-update') - async.series(tasks, => - @emit('post-update') - callback?(arguments...) - ) - - # These methods shouldn't be called in parallel, queue them if they conflict - ['start', 'stop', 'init', 'update'].forEach((method) -> - Application::[method] = (callback) -> - if @inprogress - @queue.push([method, arguments]) - else - @inprogress = true - @['_' + method](=> - @inprogress = false - if @queue.length isnt 0 - [next, args] = @queue.shift() - @[next](args...) - callback?(arguments...) - ) + return deferred.promise + ).then(-> + docker.runAsync(app.imageId, ['/bin/bash', '-c', '/start web'], process.stdout, true) ) - -module.exports = Application