2013-07-21 00:41:52 +00:00
|
|
|
{spawn} = require('child_process')
|
|
|
|
{getpwnam} = require('posix')
|
|
|
|
async = require('async')
|
2013-07-21 14:21:20 +00:00
|
|
|
{EventEmitter} = require('events')
|
|
|
|
_ = require('lodash')
|
|
|
|
state = require('./state')
|
2013-07-21 00:41:52 +00:00
|
|
|
|
2013-07-21 14:21:20 +00:00
|
|
|
class Application extends EventEmitter
|
2013-07-21 00:41:52 +00:00
|
|
|
constructor: (@repo, @path, @user) ->
|
2013-07-21 14:21:20 +00:00
|
|
|
EventEmitter.call(this)
|
2013-07-21 00:41:52 +00:00
|
|
|
@process = null
|
|
|
|
@inprogress = false
|
|
|
|
@queue = []
|
|
|
|
@options =
|
|
|
|
cwd: @path
|
|
|
|
stdio: 'inherit'
|
|
|
|
uid: getpwnam(@user).uid
|
2013-07-21 15:55:17 +00:00
|
|
|
gid: getpwnam(@user).gid
|
2013-07-21 00:41:52 +00:00
|
|
|
env:
|
|
|
|
USER: @user
|
|
|
|
USERNAME: @user
|
2013-07-21 15:55:17 +00:00
|
|
|
HOME: "/home/#{@user}"
|
2013-07-21 00:41:52 +00:00
|
|
|
|
|
|
|
_init: (callback) ->
|
|
|
|
tasks = [
|
|
|
|
# Create the directory for the project
|
|
|
|
(callback) =>
|
|
|
|
spawn('mkdir', ['-p', @path]).on('exit', callback).on('error', callback)
|
|
|
|
|
|
|
|
# Change the owner to the user
|
|
|
|
(callback) =>
|
|
|
|
spawn('chown', [@user, @path]).on('exit', callback).on('error', callback)
|
|
|
|
|
|
|
|
# Initalize a new empty git repo
|
|
|
|
(callback) =>
|
|
|
|
spawn('git', ['init'], @options).on('exit', callback).on('error', callback)
|
|
|
|
|
|
|
|
# Add the remote origin to the repo
|
|
|
|
(callback) =>
|
|
|
|
spawn('git', ['remote', 'add', 'origin', @repo], @options).on('exit', callback).on('error', callback)
|
|
|
|
]
|
2013-07-21 14:21:20 +00:00
|
|
|
@emit('pre-init')
|
|
|
|
async.series(tasks, =>
|
|
|
|
@emit('post-init')
|
|
|
|
callback?(arguments...)
|
|
|
|
)
|
2013-07-21 00:41:52 +00:00
|
|
|
|
2013-07-23 10:20:47 +00:00
|
|
|
_exitHandler: (code, signal) ->
|
|
|
|
@process = null
|
|
|
|
|
2013-07-21 00:41:52 +00:00
|
|
|
_start: (callback) ->
|
|
|
|
if not @process
|
2013-07-21 15:55:17 +00:00
|
|
|
options =
|
|
|
|
cwd: @path
|
|
|
|
stdio: 'inherit'
|
|
|
|
|
|
|
|
@process = spawn('sudo', ['-u', @user, 'foreman', 'start'], options)
|
2013-07-23 10:20:47 +00:00
|
|
|
@process.on('exit', @_exitHandler.bind(@))
|
|
|
|
@process.on('error', @_exitHandler.bind(@))
|
2013-07-21 14:21:20 +00:00
|
|
|
@emit('start')
|
2013-07-21 00:41:52 +00:00
|
|
|
callback?()
|
|
|
|
|
|
|
|
_stop: (callback) ->
|
|
|
|
# Kill will return false if process has already died
|
|
|
|
handler = =>
|
|
|
|
@process = null
|
2013-07-21 14:21:20 +00:00
|
|
|
@emit('stop')
|
|
|
|
callback?(arguments...)
|
2013-07-23 10:20:47 +00:00
|
|
|
|
2013-07-21 00:41:52 +00:00
|
|
|
spawn('pkill', ['-TERM', '-P', @process.pid], @options).on('exit', handler).on('error', handler)
|
|
|
|
|
|
|
|
_update: (callback) ->
|
|
|
|
tasks = [
|
|
|
|
# Stop the application if running
|
2013-07-21 13:31:14 +00:00
|
|
|
(callback) =>
|
2013-07-23 10:20:47 +00:00
|
|
|
if @process
|
2013-07-21 13:31:14 +00:00
|
|
|
@_stop(callback)
|
|
|
|
else
|
|
|
|
callback()
|
2013-07-23 10:20:47 +00:00
|
|
|
|
|
|
|
# Fetch new commits
|
|
|
|
(callback) =>
|
|
|
|
spawn('git', ['fetch'], @options).on('exit', callback).on('error', callback)
|
|
|
|
|
|
|
|
# Reset our master branch to origin/master
|
2013-07-21 00:41:52 +00:00
|
|
|
(callback) =>
|
2013-07-23 10:20:47 +00:00
|
|
|
spawn('git', ['reset', '--hard', 'origin/master'], @options).on('exit', callback).on('error', callback)
|
2013-07-21 00:41:52 +00:00
|
|
|
|
2013-07-21 14:21:20 +00:00
|
|
|
# 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())
|
|
|
|
)
|
|
|
|
|
2013-07-23 10:20:47 +00:00
|
|
|
# Prune npm dependencies
|
|
|
|
(callback) =>
|
|
|
|
spawn('npm', ['prune'], @options).on('exit', callback).on('error', callback)
|
|
|
|
|
2013-07-21 00:41:52 +00:00
|
|
|
# Install npm dependencies
|
|
|
|
(callback) =>
|
|
|
|
spawn('npm', ['install'], @options).on('exit', callback).on('error', callback)
|
|
|
|
|
|
|
|
# Start the app
|
2013-07-21 13:31:14 +00:00
|
|
|
(callback) =>
|
2013-07-23 10:20:47 +00:00
|
|
|
@_start(callback)
|
2013-07-21 00:41:52 +00:00
|
|
|
]
|
2013-07-21 14:21:20 +00:00
|
|
|
@emit('pre-update')
|
|
|
|
async.series(tasks, =>
|
|
|
|
@emit('post-update')
|
|
|
|
callback?(arguments...)
|
|
|
|
)
|
2013-07-23 10:20:47 +00:00
|
|
|
|
2013-07-21 00:41:52 +00:00
|
|
|
# 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...)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
module.exports = Application
|