Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Refactored so configuration not required, app conf by name, repo refe…
…rence
  • Loading branch information
haraldrudell committed Jun 23, 2012
1 parent e7d9699 commit 1f60dac
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 65 deletions.
8 changes: 6 additions & 2 deletions app.js
@@ -1,7 +1,9 @@
// nodegod // nodegod
// keeps a number of node apps running // keeps a number of node apps running
var haraldops = require('haraldops') var haraldops = require('haraldops')
var defaults = haraldops.init({ appName: 'Node God', path: __dirname, logger: console.log }) var defaults = haraldops.init({
appName: 'Node God', logger: console.log,
sessionSecret: 'veryGreat', PORT: 1111 })


// https://github.com/visionmedia/express // https://github.com/visionmedia/express
var express = require('express') var express = require('express')
Expand All @@ -11,7 +13,7 @@ var godview = require('./routes/godview')
// Configuration // Configuration
var app = module.exports = express.createServer() var app = module.exports = express.createServer()
godview.setTitle(defaults.init.appName) godview.setTitle(defaults.init.appName)
godcontrol.init(app, defaults, __dirname) godcontrol.init(app, defaults)
app.configure(function(){ app.configure(function(){
app.set('views', __dirname + '/views') app.set('views', __dirname + '/views')
app.set('view engine', 'ejs') app.set('view engine', 'ejs')
Expand All @@ -26,12 +28,14 @@ app.configure(function(){
app.use(app.router) app.use(app.router)
app.use(express.static(__dirname + '/public')) app.use(express.static(__dirname + '/public'))
}) })
/*
require('ejsinbrowser').writeScript({ require('ejsinbrowser').writeScript({
folder: app.settings.views, folder: app.settings.views,
ext: app.settings['view engine'], ext: app.settings['view engine'],
jsGlobalVariable: 'NODEGOD', jsGlobalVariable: 'NODEGOD',
templates: 'partials', templates: 'partials',
filename: __dirname + '/public/javascripts/templates.js'}) filename: __dirname + '/public/javascripts/templates.js'})
*/
app.configure('development', function(){ app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true })) app.use(express.errorHandler({ dumpExceptions: true, showStack: true }))
}) })
Expand Down
9 changes: 3 additions & 6 deletions apps.json
@@ -1,9 +1,6 @@
{ {
"nodejs3": { "My Cool Node One-Liner": {
"state": "run", "start": [ "--eval", "setTimeout(false, 10000)" ],
"name": "LinkedIn", "launchBrowser": "http://localhost:1111"
"watchFiles": [ "example.js" ],
"start": "/Users/foxyboy/Desktop/airfox/node/linkedin/example/example.js",
"folder": "/Users/foxyboy/Desktop/airfox/node/linkedin/example"
} }
} }
27 changes: 23 additions & 4 deletions lib/appentity.js
Expand Up @@ -34,7 +34,7 @@ function AppEntity(o) {
var self = this var self = this
var child // child process object var child // child process object
var commandQueue = [ ] // commands that affect state var commandQueue = [ ] // commands that affect state
var watchFiles = o.watchFiles // watch file data from json var watchFiles = o.watchFiles || [] // watch file data from json
var folder = o.folder // base folder for this app var folder = o.folder // base folder for this app
var didKill // wether kill was issued to child process var didKill // wether kill was issued to child process
var start = o.start var start = o.start
Expand All @@ -52,11 +52,11 @@ function AppEntity(o) {
var doRestart var doRestart


if (o.name) this.name = o.name if (o.name) this.name = o.name
if (o.start && o.start != start) { if (o.start && stringOrArrayDifferent(start, o.start)) {
start = o.start start = o.start
doRestart = true doRestart = true
} }
if (o.watchFiles.length) { if (o.watchFiles && o.watchFiles.length) {
watchFiles = o.watchFiles watchFiles = o.watchFiles
doWatch = true doWatch = true
} }
Expand Down Expand Up @@ -186,7 +186,7 @@ function AppEntity(o) {
} }


function launchChild(debug) { function launchChild(debug) {
var args = start.split(' ') var args = Array.isArray(start) ? start : start.split(' ')
if (debug) args.unshift('--debug-brk') if (debug) args.unshift('--debug-brk')
didKill = false didKill = false
child = child_process.spawn('node', args) child = child_process.spawn('node', args)
Expand Down Expand Up @@ -248,6 +248,25 @@ function AppEntity(o) {


} }


function stringOrArrayDifferent(o, o1) {
var different = false
if (o != o1) {
if (Array.isArray(o) && Array.isArray(o1) &&
o.length == o1.length) {
// two arrays of the same length: examine elements
// loop until true is returned
different = o.some(function(element, index) {
return element != o1[index]
})
} else different = true
}
return different
}

function isObject(o) {
return o != null && typeof o.valueOf() == 'object'
}

// receive function for pushing model updates to clients // receive function for pushing model updates to clients
function eventListener(func) { function eventListener(func) {
listener = func listener = func
Expand Down
4 changes: 2 additions & 2 deletions lib/godcontrol.js
Expand Up @@ -45,8 +45,8 @@ function appEvent(app, isCrash) {
sendToClient(data) sendToClient(data)
} }


function init(app, defaults, dirname) { function init(app, defaults) {
sendToClient = godsocket.init(app, clientToControl) sendToClient = godsocket.init(app, clientToControl)
godmodel.loadAppFiles(defaults, dirname) godmodel.loadAppFiles(defaults)
appentity.eventListener(appEvent) appentity.eventListener(appEvent)
} }
66 changes: 43 additions & 23 deletions lib/godmodel.js
Expand Up @@ -19,52 +19,72 @@ module.exports = {


var appFiles = {} var appFiles = {}
var apps = {} var apps = {}
var defaultFolder var parentFolder
var launchedBrowser


function loadAppFiles(defaults, appJsFolder) { // defaults come from haraldops
function loadAppFiles(defaults) {


// initialize a file store used to store pids of running apps
// we use these if we happen to crash :)
var myStore = store.get(path.join( var myStore = store.get(path.join(
defaults.init.tmpFolder, defaults.init.tmpFolder,
defaults.init.identifier + 'store.json')) defaults.init.identifier + 'store.json'))
appentity.setStore(myStore) appentity.setStore(myStore)


// default is the parent folder of app.js or parent of cwd // default parentFolder is the parent folder of our application folder
if (!defaultFolder) defaultFolder = path.resolve(appJsFolder, '..') parentFolder = path.join(defaults.init.appFolder, '..')


// parse each appFile // find files containing app configurations
var fileArray = getfilenames.getFileArray(defaults.appFiles, undefined, 'json') var fileArray = defaults.appFiles
var fileArrayFolder
if (fileArray) {
// we have a list of files: if we loaded a defaults file, default to that folder, otherwise node god's app folder
fileArrayFolder = defaults.init.defaultsFile ? path.dirname(defaults.init.defaultsFile) : defaults.init.appFolder
} else {
// no list of files: search for apps.json where possible settings file is and in nodegod's app folder
var f = 'apps'
fileArray = [ path.join(defaults.init.appFolder, f) ]
if (defaults.init.defaultsFile) fileArray.unshift(path.join(path.dirname(defaults.init.defaultsFile), f))
}
var fileArray = getfilenames.getFileArray(fileArray, fileArrayFolder, 'json')
fileArray.forEach(function (filename) { fileArray.forEach(function (filename) {
processAppFile(filename) processAppFile(filename)
appFiles[filename] = true appFiles[filename] = true
}) })
} }


// key is app identifier // key is app name
// name printable app name // name printable app name
// script // script
// state: initial state of the app // state: initial state of the app
// watchFiles // watchFiles
function processAppFile(filename) { function processAppFile(filename) {
console.log('process', filename) console.log('process', filename)
var fileApps = haraldops.loadJson(filename)
for (var fileAppId in fileApps) { var fileObject = haraldops.loadSettings(filename)

for (var appName in fileObject) {
// prepare the fileApp object
var fileApp = fileApps[fileAppId] // parse the fileApp object
fileApp.id = fileAppId var fileApp = fileObject[appName]
if (!fileApp.folder) fileApp.folder = path.join(defaultFolder, fileAppId) var o = {}
if (!fileApp.name) fileApp.name = fileAppId o.name = appName
if (!fileApp.start) { o.id = fileApp.id || haraldops.createIdentifier(appName)
fileApp.start = path.join(defaultFolder, fileAppId, 'app.js') o.folder = fileApp.folder || path.join(parentFolder, o.id)
o.start = fileApp.start || path.join(o.folder, 'app.js')
o.state = fileApp.state || 'run'

if (fileApp.launchBrowser && !launchedBrowser) {
launchedBrowser = true
haraldops.browseTo(fileApp.launchBrowser)
} }


// update or create the app entity // update or create the app entity
var app = apps[fileAppId] var app = apps[o.id]
if (app) { if (app) app.update(o)
app.update(fileApp) else {
} else { app = new appentity.AppEntity(o)
app = new appentity.AppEntity(fileApp) apps[o.id] = app
apps[fileAppId] = app
app.doCommand() app.doCommand()
} }
} }
Expand Down
3 changes: 2 additions & 1 deletion lib/watchit.js
Expand Up @@ -20,7 +20,8 @@ function WatchIt(restartFunc) {


// return value: number of files being watched // return value: number of files being watched
this.updateFiles = function(fileValue, folder) { this.updateFiles = function(fileValue, folder) {
newFiles = getfilenames.getFileArray(fileValue, folder, 'js') var newFiles = []
if (fileValue) newFiles = getfilenames.getFileArray(fileValue, folder, 'js')
if (!areStringArraysSame(files, newFiles)) { if (!areStringArraysSame(files, newFiles)) {
files = newFiles files = newFiles
if (active) { if (active) {
Expand Down
6 changes: 5 additions & 1 deletion package.json
Expand Up @@ -2,7 +2,7 @@
"name": "nodegod", "name": "nodegod",
"description": "Web interface to node apps, file watch and crash restart by Harald Rudell", "description": "Web interface to node apps, file watch and crash restart by Harald Rudell",
"author": "Harald Rudell <harald@allgoodapps.com> (http://www.haraldrudell.com", "author": "Harald Rudell <harald@allgoodapps.com> (http://www.haraldrudell.com",
"version": "0.0.2", "version": "0.0.3",
"contributors": [ "contributors": [
{ {
"name": "Harald Rudell", "name": "Harald Rudell",
Expand All @@ -18,6 +18,10 @@
"socket.io": "", "socket.io": "",
"haraldops": "" "haraldops": ""
}, },
"repository" : {
"type" : "git",
"url" : "https://haraldrudell@github.com/haraldrudell/nodegod.git"
},
"scripts": { "scripts": {
"start": "node app" "start": "node app"
} }
Expand Down
63 changes: 37 additions & 26 deletions readme.md
@@ -1,55 +1,66 @@
# Node God # Node God
Launches node applications, restarts them on crash or file updates, produces a shared log. Launches node applications, restarts them on crash or file updates, produces a shared log.


# Required Configuration # Run as demo
Configured by JSON-files in the filesystem


## Node God itself ```js
* nodegod.js needs to be in the folder nodegod$ node app
* $HOME
* $HOME/apps
* the folder were Node God's app.js resides === 2012-06-23 13:24:21 Node God starting
info - socket.io started
process /home/foxyboy/Desktop/c505/node/nodegod/apps.json
Application Node God on node v0.6.14 available on port 1111 in development mode
127.0.0.1
```

opens a browser window that monitors an app that exits every 10 seconds.

# Configuration Files

## nodegod.json
Node God itself is configured using a json file that provides port number, a session secret and the location of other json files containing application configurations. The paths searched for nodegod.json are:
* $HOME/apps
* $HOME
* Node God's launch folder, where app.js is located


```js ```js
{ {
"PORT": 1111,
"haraldops": {
},
"appFiles": "/home/foxyboy/apps/apps.json", "appFiles": "/home/foxyboy/apps/apps.json",
"sessionSecret": "verygreat"
} }
``` ```
* PORT: optional number: the port for Node God, ovverriden by env.PORT, default 3000 * PORT: optional number: the port for Node God, defaults to env.PORT or 1111
* appFiles: required path: where to find json file with app configurations * appFiles: optional string or array of strings: filenames to search for app configurations.
* sessionSecret: required string, secret for web session * if strings are not fully qualified paths, the folder where nodegod.json was found or node god's app folder are searched
* default: apps.json in either the noegod.json folder or nodegod's app folder
* sessionSecret: optional string, has a default value
* defaultFolder: a parent folder for deployed apps, default the parent folder of where nodegod's app.js is located * defaultFolder: a parent folder for deployed apps, default the parent folder of where nodegod's app.js is located


Note: pids are stored at $HOME/tmp or the global temp folder Note: pids are stored in a file at $HOME/tmp or the global temp folder


## Configuring apps ## Configuring files for monitored apps


```js ```js
{ {
"nodejs3": { "Node.js #3": {
"state": "run",
"name": "Node.js #3",
"watchFiles": [ "package.json", "app.js", "lib", "routes", "/home/foxyboy/apps/nodejs3.json" ] "watchFiles": [ "package.json", "app.js", "lib", "routes", "/home/foxyboy/apps/nodejs3.json" ]
} }
} }
``` ```


* Key: the identifier (computer-friendly string) used for this app, here "nodejs3." This is the name of the default deployment folder for this app * Key: the name of this app
* state: the initial state of the app: run/stop/debug * id: optional string: the identifier (computer-friendly string) used for this app. default is derived from the app name, for "Node.js #3" here it would be "nodejs3"
* folder: optional: the folder where the app is deployed, default as described under nodegod settings * state: optional string: the initial state of the app: run/stop/debug, default run
* start: path to the JavaScript launching the app, default app.js * folder: optional path: the folder where the app is deployed. Default is a sibling folder to nodegod, ie. the parent folder of nodegod with id appended.
* name: Human readable app name, defaule is Key * start: optional string or array of strings: parameters to the node executable, default app.js in the app's folder
* watchFiles: a single entry or array of filenames and folders to watch. If any file changes the app is restarted * watchFiles: optional string or array of Strings: filenames and folders to watch. If any file changes the app is restarted
* launchBrowser: optional string: url for which a browser window is launched once


# Features # Features
* Web frontend for managing any number of apps * Web frontend for managing any number of apps
* App lifecycle management with states and transitions for run/stop/debug * App lifecycle management with states and transitions for run/stop/debug
* App is automatically restarted unless it crashes in less than 3 seconds * App is automatically restarted unless it crashes in less than 3 seconds
* If app state is crashed, watchers are still active so the app relaunches on update * If app state is crashed, watchers are still active so relaunch os attempted on file updates
* File watchers restart the app after a 3 second delay, so that all file writes have time to complete * File watchers restart the app after a 3 second delay, so that all file writes have time to complete
* Ability to reload app configurations as files are added * Ability to reload app configurations as files are added
* If Node God crashes, it will relaunch managed apps on restart so that they become managed * If Node God crashes, it will relaunch managed apps on restart so that they become managed

0 comments on commit 1f60dac

Please sign in to comment.