From 1df373d238c83e663c6d83116a0086cb1a11d119 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Oct 2023 14:28:52 +0200 Subject: [PATCH 1/7] feat: Support for post_start_hook --- lib/API/schema.json | 4 ++++ lib/God.js | 44 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/API/schema.json b/lib/API/schema.json index fc5f868cd5..e602b7d19b 100644 --- a/lib/API/schema.json +++ b/lib/API/schema.json @@ -258,6 +258,10 @@ "docDefault": false, "docDescription": "Start a script even if it is already running (only the script path is considered)" }, + "post_start_hook": { + "type": "string", + "docDescription": "Script to run after app start" + }, "append_env_to_name": { "type": "boolean", "docDefault": false, diff --git a/lib/God.js b/lib/God.js index 620e5b5d49..61ca865ad6 100644 --- a/lib/God.js +++ b/lib/God.js @@ -212,7 +212,37 @@ God.executeApp = function executeApp(env, cb) { God.registerCron(env_copy) /** Callback when application is launched */ - var readyCb = function ready(proc) { + var appRunningCb = function(clu) { + var post_start_hook = env_copy['post_start_hook']; + if (post_start_hook) { + try { + var hookFn = require(env_copy.pm_cwd + '/' + post_start_hook); + if (typeof hookFn !== 'function') { + throw new Error('post_start_hook module.exports must be a function'); + } + hookFn({ + pid: clu.process.pid, + stdin: clu.stdin, + stdout: clu.stdout, + stderr: clu.stderr, + pm2_env: clu.pm2_env, + }, function (hook_err) { + if (hook_err) { + console.error('post_start_hook returned error:', hook_err); + } + return hooksDoneCb(clu); + }); + } catch (require_hook_err) { + console.error('executing post_start_hook failed:', require_hook_err.message); + return hooksDoneCb(clu); + } + } else { + return hooksDoneCb(clu); + } + }; + + /** Callback when post-start hook is done */ + var hooksDoneCb = function ready(proc) { // If vizion enabled run versioning retrieval system if (proc.pm2_env.vizion !== false && proc.pm2_env.vizion !== "false") God.finalizeProcedure(proc); @@ -265,12 +295,12 @@ God.executeApp = function executeApp(env, cb) { return clu.once('online', function () { if (!clu.pm2_env.wait_ready) - return readyCb(clu); + return appRunningCb(clu); // Timeout if the ready message has not been sent before listen_timeout var ready_timeout = setTimeout(function() { God.bus.removeListener('process:msg', listener) - return readyCb(clu) + return appRunningCb(clu) }, clu.pm2_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); var listener = function (packet) { @@ -279,7 +309,7 @@ God.executeApp = function executeApp(env, cb) { packet.process.pm_id === clu.pm2_env.pm_id) { clearTimeout(ready_timeout); God.bus.removeListener('process:msg', listener) - return readyCb(clu) + return appRunningCb(clu) } } @@ -321,12 +351,12 @@ God.executeApp = function executeApp(env, cb) { }); if (!clu.pm2_env.wait_ready) - return readyCb(clu); + return appRunningCb(clu); // Timeout if the ready message has not been sent before listen_timeout var ready_timeout = setTimeout(function() { God.bus.removeListener('process:msg', listener) - return readyCb(clu) + return appRunningCb(clu) }, clu.pm2_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); var listener = function (packet) { @@ -335,7 +365,7 @@ God.executeApp = function executeApp(env, cb) { packet.process.pm_id === clu.pm2_env.pm_id) { clearTimeout(ready_timeout); God.bus.removeListener('process:msg', listener) - return readyCb(clu) + return appRunningCb(clu) } } God.bus.on('process:msg', listener); From b7a2792197576530729d03219311fb2b5e445e34 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 Nov 2023 15:29:27 +0100 Subject: [PATCH 2/7] tests for post_start_hook --- test/fixtures/post_start_hook/echo.js | 5 + .../post_start_hook/post_start_hook_errors.js | 15 +++ .../post_start_hook/post_start_hook_normal.js | 26 ++++ .../post_start_hook/post_start_hook_throws.js | 15 +++ test/programmatic/post_start_hook.mocha.js | 126 ++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 test/fixtures/post_start_hook/echo.js create mode 100644 test/fixtures/post_start_hook/post_start_hook_errors.js create mode 100644 test/fixtures/post_start_hook/post_start_hook_normal.js create mode 100644 test/fixtures/post_start_hook/post_start_hook_throws.js create mode 100644 test/programmatic/post_start_hook.mocha.js diff --git a/test/fixtures/post_start_hook/echo.js b/test/fixtures/post_start_hook/echo.js new file mode 100644 index 0000000000..6a295dcacd --- /dev/null +++ b/test/fixtures/post_start_hook/echo.js @@ -0,0 +1,5 @@ +console.log('app running'); + +process.stdin.on('data', function(chunk) { + process.stdout.write(chunk); +}); diff --git a/test/fixtures/post_start_hook/post_start_hook_errors.js b/test/fixtures/post_start_hook/post_start_hook_errors.js new file mode 100644 index 0000000000..6430916dc3 --- /dev/null +++ b/test/fixtures/post_start_hook/post_start_hook_errors.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * This is a post-start hook that will be called after an app started. + * @param {object} info + * @param {string} info.pid The apps PID + * @param {object} info.stdin The apps STDIN stream + * @param {object} info.stdout The apps STDOUT stream + * @param {object} info.stderr The apps STDERR stream + * @param {object} pm2_env The apps environment variables + * @returns {void} + */ +module.exports = function hook(info, cb) { + cb(new Error('error-from-post-start-hook-' + info.pid)); +} diff --git a/test/fixtures/post_start_hook/post_start_hook_normal.js b/test/fixtures/post_start_hook/post_start_hook_normal.js new file mode 100644 index 0000000000..abb2e022ba --- /dev/null +++ b/test/fixtures/post_start_hook/post_start_hook_normal.js @@ -0,0 +1,26 @@ +'use strict'; + +/** + * This is a post-start hook that will be called after an app started. + * @param {object} info + * @param {string} info.pid The apps PID + * @param {object} info.stdin The apps STDIN stream + * @param {object} info.stdout The apps STDOUT stream + * @param {object} info.stderr The apps STDERR stream + * @param {object} pm2_env The apps environment variables + * @returns {void} + */ +module.exports = function hook(info, cb) { + console.log('hello-from-post-start-hook-' + info.pid); + info.pm2_env.post_start_hook_info = { + pid: info.pid, + stdin: info.stdin, + stdout: info.stdout, + stderr: info.stderr, + have_env: info.pm2_env.post_start_hook_test, + }; + if (info.stdin) { + info.stdin.write('post-start-hook-hello-to-' + info.pid + '\n'); + } + cb(null); +} diff --git a/test/fixtures/post_start_hook/post_start_hook_throws.js b/test/fixtures/post_start_hook/post_start_hook_throws.js new file mode 100644 index 0000000000..448fec1082 --- /dev/null +++ b/test/fixtures/post_start_hook/post_start_hook_throws.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * This is a post-start hook that will be called after an app started. + * @param {object} info + * @param {string} info.pid The apps PID + * @param {object} info.stdin The apps STDIN stream + * @param {object} info.stdout The apps STDOUT stream + * @param {object} info.stderr The apps STDERR stream + * @param {object} pm2_env The apps environment variables + * @returns {void} + */ +module.exports = function hook(info, cb) { + throw new Error('thrown-from-post-start-hook-' + info.pid); +} diff --git a/test/programmatic/post_start_hook.mocha.js b/test/programmatic/post_start_hook.mocha.js new file mode 100644 index 0000000000..29481bf020 --- /dev/null +++ b/test/programmatic/post_start_hook.mocha.js @@ -0,0 +1,126 @@ +process.chdir(__dirname) + +var PM2 = require('../..') +var should = require('should') +const fs = require("fs"); + +describe('When a post_start_hook is configured', function() { + before(function(done) { + PM2.delete('all', function() { done() }) + }) + + after(function(done) { + PM2.kill(done) + }) + + afterEach(function(done) { + PM2.delete('all', done) + }) + + function defineTestsForMode(mode) { + describe('when running app in ' + mode + ' mode', function() { + it('should start app and run the post_start_hook script', function(done) { + PM2.start({ + script: './../fixtures/post_start_hook/echo.js', + post_start_hook: './../fixtures/post_start_hook/post_start_hook_normal.js', + exec_mode: mode, + env: { + post_start_hook_test: 'true' + } + }, (err) => { + should(err).be.null() + PM2.list(function(err, list) { + try { + should(err).be.null() + should(list.length).eql(1) + should.exists(list[0].pm2_env.post_start_hook_info) + should(list[0].pm2_env.post_start_hook_info.pid).eql(list[0].pid) + should(list[0].pm2_env.post_start_hook_info.have_env).eql('true') + var log_file = list[0].pm2_env.PM2_HOME + '/pm2.log'; + fs.readFileSync(log_file).toString().should.containEql('hello-from-post-start-hook-' + list[0].pid) + if (mode === 'fork') { + should.exist(list[0].pm2_env.post_start_hook_info.stdin) + should.exist(list[0].pm2_env.post_start_hook_info.stdout) + should.exist(list[0].pm2_env.post_start_hook_info.stderr) + var out_file = list[0].pm2_env.pm_out_log_path; + setTimeout(function() { + fs.readFileSync(out_file).toString().should.containEql('post-start-hook-hello-to-' + list[0].pid) + done() + }, 100) + } else { + done(); + } + } catch(e) { + done(e) + } + }) + }) + }) + + it('should log error in pm2 log but keep app running when post_start_hook script throws', function(done) { + PM2.start({ + script: './../fixtures/post_start_hook/echo.js', + post_start_hook: './../fixtures/post_start_hook/post_start_hook_throws.js', + exec_mode: mode, + }, (err) => { + should(err).be.null() + PM2.list(function(err, list) { + try { + should(err).be.null() + should(list.length).eql(1) + var log_file = list[0].pm2_env.PM2_HOME + '/pm2.log'; + fs.readFileSync(log_file).toString().should.containEql('thrown-from-post-start-hook-' + list[0].pid) + done() + } catch(e) { + done(e) + } + }) + }) + }) + + it('should log error in pm2 log but keep app running when post_start_hook script returns error', function(done) { + PM2.start({ + script: './../fixtures/post_start_hook/echo.js', + post_start_hook: './../fixtures/post_start_hook/post_start_hook_errors.js', + exec_mode: mode, + }, (err) => { + should(err).be.null() + PM2.list(function(err, list) { + try { + should(err).be.null() + should(list.length).eql(1) + var log_file = list[0].pm2_env.PM2_HOME + '/pm2.log'; + fs.readFileSync(log_file).toString().should.containEql('error-from-post-start-hook-' + list[0].pid) + done() + } catch(e) { + done(e) + } + }) + }) + }) + + it('should log error in pm2 log but keep app running when post_start_hook script is not found', function(done) { + PM2.start({ + script: './../fixtures/post_start_hook/echo.js', + post_start_hook: './../fixtures/post_start_hook/post_start_hook_nonexistent.js', + exec_mode: mode, + }, (err) => { + should(err).be.null() + PM2.list(function(err, list) { + try { + should(err).be.null() + should(list.length).eql(1) + var log_file = list[0].pm2_env.PM2_HOME + '/pm2.log'; + fs.readFileSync(log_file).toString().should.match(/PM2 error: executing post_start_hook failed: Cannot find module .*post_start_hook_nonexistent\.js/) + done() + } catch(e) { + done(e) + } + }) + }) + }) + }) + } + defineTestsForMode('fork') + defineTestsForMode('cluster') +}) From 05fdf06aac3abfdde08c794cf196e107d4e55cb1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 Nov 2023 15:40:00 +0100 Subject: [PATCH 3/7] example for post_start_hook --- examples/post-start-secret-delivery/README.md | 8 ++++++ examples/post-start-secret-delivery/app.js | 25 +++++++++++++++++++ .../post-start-secret-delivery/config.json | 4 +++ .../post-start-hook.js | 23 +++++++++++++++++ .../post-start-secret-delivery/process.yaml | 7 ++++++ .../post-start-secret-delivery/secrets.json | 8 ++++++ 6 files changed, 75 insertions(+) create mode 100644 examples/post-start-secret-delivery/README.md create mode 100644 examples/post-start-secret-delivery/app.js create mode 100644 examples/post-start-secret-delivery/config.json create mode 100644 examples/post-start-secret-delivery/post-start-hook.js create mode 100644 examples/post-start-secret-delivery/process.yaml create mode 100644 examples/post-start-secret-delivery/secrets.json diff --git a/examples/post-start-secret-delivery/README.md b/examples/post-start-secret-delivery/README.md new file mode 100644 index 0000000000..e249e197d2 --- /dev/null +++ b/examples/post-start-secret-delivery/README.md @@ -0,0 +1,8 @@ +# Secret injection through post_start_hook +This example shows a method to retrieve run-time secrets from a vault and deliver them to newly-started app instances using a post_start_hook. +In this set-up PM2, with the help of the hook, acts as a 'trusted intermediate'; it is the only entity that has +full access to the secret store, and it is responsible for delivering the appropriate secrets to the appropriate app instances. + +One key point of this solution is that the secret data is not passed through environment variables, which could be exposed. +Instead, the secret data is delivered to the app using its stdin file descriptor. +Note that this will not work in cluster mode, as in that case apps run with stdin detached. diff --git a/examples/post-start-secret-delivery/app.js b/examples/post-start-secret-delivery/app.js new file mode 100644 index 0000000000..9b107e0cf0 --- /dev/null +++ b/examples/post-start-secret-delivery/app.js @@ -0,0 +1,25 @@ +const readline = require('node:readline/promises'); +const { stdin, stdout } = require('node:process'); +const rl = readline.createInterface({ input: stdin, output: stdout }); + +const defaultConfig = require('./config.json'); + +async function main() { + // Read overrides from stdin + const overridesStr = await rl.question('overrides? '); + let overrides; + try { + overrides = JSON.parse(overridesStr); + } catch (e) { + console.error(`Error parsing >${overridesStr}<:`, e); + process.exit(1); + } + + // Merge overrides into default config to form final config + const config = Object.assign({}, defaultConfig, overrides); + console.log(`App running with config: ${JSON.stringify(config, null, 2)}`); + // Keep it alive + setInterval(() => {}, 1000); +} + +main().catch(console.error); diff --git a/examples/post-start-secret-delivery/config.json b/examples/post-start-secret-delivery/config.json new file mode 100644 index 0000000000..270ecb220b --- /dev/null +++ b/examples/post-start-secret-delivery/config.json @@ -0,0 +1,4 @@ +{ + "fooKey": "defaultFooValue", + "barKey": "defaultBarValue" +} diff --git a/examples/post-start-secret-delivery/post-start-hook.js b/examples/post-start-secret-delivery/post-start-hook.js new file mode 100644 index 0000000000..ae8bcd3575 --- /dev/null +++ b/examples/post-start-secret-delivery/post-start-hook.js @@ -0,0 +1,23 @@ +'use strict'; +const fs = require('fs').promises; + +/** + * This is a post-start hook that will be called after an app started. + * @param {object} info + * @param {string} info.pid The apps PID + * @param {object} info.stdin The apps STDIN stream + * @param {object} info.stdout The apps STDOUT stream + * @param {object} info.stderr The apps STDERR stream + * @param {object} pm2_env The apps environment variables + * @returns {Promise} + */ +async function hook(info) { + const appName = info.pm2_env.name; + // In a real scenario secrets would be retrieved from some secret store + const allSecrets = JSON.parse(await fs.readFile('secrets.json', 'utf8')); + const appOverrides = allSecrets[appName] || {}; + // Write the overrides json to the apps STDIN stream + info.stdin.write(JSON.stringify(appOverrides) + '\n'); +} + +module.exports = require('util').callbackify(hook); diff --git a/examples/post-start-secret-delivery/process.yaml b/examples/post-start-secret-delivery/process.yaml new file mode 100644 index 0000000000..8c030eb27b --- /dev/null +++ b/examples/post-start-secret-delivery/process.yaml @@ -0,0 +1,7 @@ +- name: app-1 + script: app.js + post_start_hook: post-start-hook.js + +- name: app-2 + script: app.js + post_start_hook: post-start-hook.js diff --git a/examples/post-start-secret-delivery/secrets.json b/examples/post-start-secret-delivery/secrets.json new file mode 100644 index 0000000000..61f3f79d69 --- /dev/null +++ b/examples/post-start-secret-delivery/secrets.json @@ -0,0 +1,8 @@ +{ + "app-1": { + "barKey": "secretBarValueForApp1" + }, + "app-2": { + "barKey": "secretBarValueForApp2" + } +} From 7e049d5a9a33d108fa53b37ff62b56ec1431f866 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 Nov 2023 16:10:13 +0100 Subject: [PATCH 4/7] stdin/stdout/stderr object -> Stream --- examples/post-start-secret-delivery/post-start-hook.js | 6 +++--- test/fixtures/post_start_hook/post_start_hook_normal.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/post-start-secret-delivery/post-start-hook.js b/examples/post-start-secret-delivery/post-start-hook.js index ae8bcd3575..44c9591f81 100644 --- a/examples/post-start-secret-delivery/post-start-hook.js +++ b/examples/post-start-secret-delivery/post-start-hook.js @@ -5,9 +5,9 @@ const fs = require('fs').promises; * This is a post-start hook that will be called after an app started. * @param {object} info * @param {string} info.pid The apps PID - * @param {object} info.stdin The apps STDIN stream - * @param {object} info.stdout The apps STDOUT stream - * @param {object} info.stderr The apps STDERR stream + * @param {Stream} info.stdin The apps STDIN stream + * @param {Stream} info.stdout The apps STDOUT stream + * @param {Stream} info.stderr The apps STDERR stream * @param {object} pm2_env The apps environment variables * @returns {Promise} */ diff --git a/test/fixtures/post_start_hook/post_start_hook_normal.js b/test/fixtures/post_start_hook/post_start_hook_normal.js index abb2e022ba..9e039f7ab5 100644 --- a/test/fixtures/post_start_hook/post_start_hook_normal.js +++ b/test/fixtures/post_start_hook/post_start_hook_normal.js @@ -4,9 +4,9 @@ * This is a post-start hook that will be called after an app started. * @param {object} info * @param {string} info.pid The apps PID - * @param {object} info.stdin The apps STDIN stream - * @param {object} info.stdout The apps STDOUT stream - * @param {object} info.stderr The apps STDERR stream + * @param {Stream} info.stdin The apps STDIN stream + * @param {Stream} info.stdout The apps STDOUT stream + * @param {Stream} info.stderr The apps STDERR stream * @param {object} pm2_env The apps environment variables * @returns {void} */ From 2f1b0578771cb036e0cd3a83bcbdcec63c2135d8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 Nov 2023 16:19:06 +0100 Subject: [PATCH 5/7] update jsdoc --- examples/post-start-secret-delivery/post-start-hook.js | 2 +- .../fixtures/post_start_hook/post_start_hook_errors.js | 10 ---------- .../fixtures/post_start_hook/post_start_hook_normal.js | 10 ---------- .../fixtures/post_start_hook/post_start_hook_throws.js | 10 ---------- 4 files changed, 1 insertion(+), 31 deletions(-) diff --git a/examples/post-start-secret-delivery/post-start-hook.js b/examples/post-start-secret-delivery/post-start-hook.js index 44c9591f81..eab6384710 100644 --- a/examples/post-start-secret-delivery/post-start-hook.js +++ b/examples/post-start-secret-delivery/post-start-hook.js @@ -4,7 +4,7 @@ const fs = require('fs').promises; /** * This is a post-start hook that will be called after an app started. * @param {object} info - * @param {string} info.pid The apps PID + * @param {number} info.pid The apps PID * @param {Stream} info.stdin The apps STDIN stream * @param {Stream} info.stdout The apps STDOUT stream * @param {Stream} info.stderr The apps STDERR stream diff --git a/test/fixtures/post_start_hook/post_start_hook_errors.js b/test/fixtures/post_start_hook/post_start_hook_errors.js index 6430916dc3..abe9917568 100644 --- a/test/fixtures/post_start_hook/post_start_hook_errors.js +++ b/test/fixtures/post_start_hook/post_start_hook_errors.js @@ -1,15 +1,5 @@ 'use strict'; -/** - * This is a post-start hook that will be called after an app started. - * @param {object} info - * @param {string} info.pid The apps PID - * @param {object} info.stdin The apps STDIN stream - * @param {object} info.stdout The apps STDOUT stream - * @param {object} info.stderr The apps STDERR stream - * @param {object} pm2_env The apps environment variables - * @returns {void} - */ module.exports = function hook(info, cb) { cb(new Error('error-from-post-start-hook-' + info.pid)); } diff --git a/test/fixtures/post_start_hook/post_start_hook_normal.js b/test/fixtures/post_start_hook/post_start_hook_normal.js index 9e039f7ab5..655a910ccc 100644 --- a/test/fixtures/post_start_hook/post_start_hook_normal.js +++ b/test/fixtures/post_start_hook/post_start_hook_normal.js @@ -1,15 +1,5 @@ 'use strict'; -/** - * This is a post-start hook that will be called after an app started. - * @param {object} info - * @param {string} info.pid The apps PID - * @param {Stream} info.stdin The apps STDIN stream - * @param {Stream} info.stdout The apps STDOUT stream - * @param {Stream} info.stderr The apps STDERR stream - * @param {object} pm2_env The apps environment variables - * @returns {void} - */ module.exports = function hook(info, cb) { console.log('hello-from-post-start-hook-' + info.pid); info.pm2_env.post_start_hook_info = { diff --git a/test/fixtures/post_start_hook/post_start_hook_throws.js b/test/fixtures/post_start_hook/post_start_hook_throws.js index 448fec1082..d17c9e0683 100644 --- a/test/fixtures/post_start_hook/post_start_hook_throws.js +++ b/test/fixtures/post_start_hook/post_start_hook_throws.js @@ -1,15 +1,5 @@ 'use strict'; -/** - * This is a post-start hook that will be called after an app started. - * @param {object} info - * @param {string} info.pid The apps PID - * @param {object} info.stdin The apps STDIN stream - * @param {object} info.stdout The apps STDOUT stream - * @param {object} info.stderr The apps STDERR stream - * @param {object} pm2_env The apps environment variables - * @returns {void} - */ module.exports = function hook(info, cb) { throw new Error('thrown-from-post-start-hook-' + info.pid); } From 605482fa4bba27dc1e072b2982775999f12de85f Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 27 Feb 2024 18:16:41 +0100 Subject: [PATCH 6/7] no prefixing of hook with service path --- lib/God.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/God.js b/lib/God.js index 61ca865ad6..c394b13e64 100644 --- a/lib/God.js +++ b/lib/God.js @@ -216,7 +216,7 @@ God.executeApp = function executeApp(env, cb) { var post_start_hook = env_copy['post_start_hook']; if (post_start_hook) { try { - var hookFn = require(env_copy.pm_cwd + '/' + post_start_hook); + var hookFn = require(post_start_hook); if (typeof hookFn !== 'function') { throw new Error('post_start_hook module.exports must be a function'); } From 4d24960e17c3cec300642ed0d89d2ba5987f63bd Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 19 Mar 2024 19:36:56 +0100 Subject: [PATCH 7/7] Support globally installed scripts as post_start_hook --- lib/God.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/God.js b/lib/God.js index c394b13e64..91880ea375 100644 --- a/lib/God.js +++ b/lib/God.js @@ -25,6 +25,7 @@ var Utility = require('./Utility'); var cst = require('../constants.js'); var timesLimit = require('async/timesLimit'); var Configuration = require('./Configuration.js'); +var which = require('./tools/which'); /** * Override cluster module configuration @@ -215,8 +216,24 @@ God.executeApp = function executeApp(env, cb) { var appRunningCb = function(clu) { var post_start_hook = env_copy['post_start_hook']; if (post_start_hook) { + // Full path script resolution + var hook_path = path.resolve(clu.pm2_env.cwd, post_start_hook); + + // If script does not exist after resolution + if (!fs.existsSync(hook_path)) { + var ckd; + // Try resolve command available in $PATH + if ((ckd = which(post_start_hook))) { + if (typeof(ckd) !== 'string') + ckd = ckd.toString(); + hook_path = ckd; + } + else + // Throw critical error + return new Error(`post_start_hook not found: ${post_start_hook}`); + } try { - var hookFn = require(post_start_hook); + var hookFn = require(hook_path); if (typeof hookFn !== 'function') { throw new Error('post_start_hook module.exports must be a function'); }