diff --git a/extensions/systemd/systemd.js b/extensions/systemd/systemd.js index f20eac74f..15ed96403 100644 --- a/extensions/systemd/systemd.js +++ b/extensions/systemd/systemd.js @@ -10,6 +10,10 @@ class SystemdProcessManager extends cli.ProcessManager { return `ghost_${this.instance.name}`; } + get logSuggestion() { + return 'journalctl -n 50'; + } + start() { this._precheck(); diff --git a/lib/commands/restart.js b/lib/commands/restart.js index f1f28258a..3bff7be9a 100644 --- a/lib/commands/restart.js +++ b/lib/commands/restart.js @@ -4,6 +4,12 @@ const Command = require('../command'); class RestartCommand extends Command { run() { const instance = this.system.getInstance(); + const restart = (() => { + return instance.process.restart(process.cwd(), this.system.environment) + .then(() => { + return instance.process.ensureStarted(process.cwd(), this.system.environment); + }); + }); if (!instance.running()) { return Promise.reject(new Error('Ghost instance is not currently running.')); @@ -11,7 +17,7 @@ class RestartCommand extends Command { instance.loadRunningEnvironment(true); - return this.ui.run(instance.process.restart(process.cwd(), this.system.environment), 'Restarting Ghost'); + return this.ui.run(restart, 'Restarting Ghost'); } } diff --git a/lib/commands/run.js b/lib/commands/run.js index 485e7235c..27f4a5648 100644 --- a/lib/commands/run.js +++ b/lib/commands/run.js @@ -71,7 +71,11 @@ class RunCommand extends Command { return; } - instance.process.error(message.error); + // Ghost error log happens AFTER this event + // REMOVE WHEN ghost is released and we bump the cli engine? + setTimeout(() => { + instance.process.error(message.error); + }, 1000); }); } diff --git a/lib/commands/start.js b/lib/commands/start.js index f37606369..8ef325201 100644 --- a/lib/commands/start.js +++ b/lib/commands/start.js @@ -37,10 +37,16 @@ class StartCommand extends Command { const start = () => { return Promise.resolve(processInstance.start(process.cwd(), this.system.environment)) - .then(() => { instance.running(this.system.environment); }); + .then(() => { + return processInstance.ensureStarted(process.cwd(), this.system.environment); + }) + .then(() => { + instance.running(this.system.environment); + }); }; return this.ui.run(start, 'Starting Ghost', runOptions).then(() => { + // @TODO: shouldn't this happen before start? // If process manager doesn't support enable behavior OR it's already enabled, don't try to enable if (!ProcessManager.supportsEnableBehavior(processInstance) || processInstance.isEnabled()) { argv.enable = false; diff --git a/lib/process-manager.js b/lib/process-manager.js index a9abab0a2..405303024 100644 --- a/lib/process-manager.js +++ b/lib/process-manager.js @@ -1,5 +1,7 @@ 'use strict'; +const net = require('net'); const every = require('lodash/every'); +const errors = require('./errors'); const requiredMethods = [ 'start', 'stop', @@ -25,6 +27,10 @@ class ProcessManager { this.instance = instance; } + get logSuggestion() { + return 'ghost log'; + } + /** * Method called to start the Ghost process * @@ -60,6 +66,68 @@ class ProcessManager { // Base Implementation } + /** + * General implementation of figuring out if the Ghost blog has started successfully. + * Port long polling. + * + * @returns {Promise} + */ + ensureStarted(cwd, env, options) { + options = options || {}; + + const intervalTimeoutInMS = options.intervalTimeoutInMS || 1000; + const maxTries = options.maxTries || 20; + + let tries = 0; + let retryOnConnect = options.hasOwnProperty('retryOnConnect') ? options.retryOnConnect : true; + + return new Promise((resolve, reject) => { + const interval = setInterval(() => { + const ghostSocket = net.connect(this.instance.config.get('server.port')); + + ghostSocket.on('connect', (() => { + /** + * Try it one more time to connect to Ghost. + * By that, we ensure Ghost does not fall over after Ghost's express server has started. + */ + if (retryOnConnect) { + clearInterval(interval); + ghostSocket.destroy(); + + return this.ensureStarted(cwd, env, { + intervalTimeoutInMS: 2 * 1000, + maxTries: 3, + retryOnConnect: false + }).then(resolve).catch(reject); + } + + clearInterval(interval); + ghostSocket.destroy(); + resolve(); + })); + + ghostSocket.on('error', ((err) => { + if (tries > maxTries) { + clearInterval(interval); + ghostSocket.destroy(); + + // Force stop, otherwise extensions will restart Ghost and `ghost ls` will show it's running. + return Promise.resolve(this.stop(cwd, env)) + .then(() => { + return reject(new errors.GhostError({ + message: 'Ghost did not start.', + suggestion: this.logSuggestion, + err: err + })); + }); + } + + tries = tries + 1; + })); + }, intervalTimeoutInMS); + }); + } + /** * This function checks if this process manager can be used on this system * diff --git a/lib/utils/local-process.js b/lib/utils/local-process.js index 0a1a72d3f..39247763f 100644 --- a/lib/utils/local-process.js +++ b/lib/utils/local-process.js @@ -98,6 +98,10 @@ class LocalProcess extends ProcessManager { }); } + ensureStarted() { + return Promise.resolve(); + } + /** * Called by the `ghost run` sub-process, this notifies the parent process that * Ghost has started