Skip to content

Commit

Permalink
Merge pull request #73 from TryGhost/systemd
Browse files Browse the repository at this point in the history
Add systemd process manager
  • Loading branch information
acburdine committed Nov 17, 2016
2 parents 6a73465 + 52cdb93 commit fc65f04
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 16 deletions.
3 changes: 3 additions & 0 deletions lib/commands/config/advanced.js
Expand Up @@ -26,6 +26,9 @@ module.exports = [{
}, {
name: 'port',
description: 'Port ghost should listen on'
}, {
name: 'process',
description: 'Type of process manager to run Ghost with'
}, {
name: 'db',
description: 'Type of database Ghost should use',
Expand Down
21 changes: 10 additions & 11 deletions lib/commands/run.js
Expand Up @@ -19,11 +19,14 @@ module.exports.Command = BaseCommand.extend({
process.env.NODE_ENV = (options.production || process.env.NODE_ENV === 'production') ?
'production' : 'development';

var KnexMigrator = require('knex-migrator'),
var resolveProcessManager = require('../process').resolve,
KnexMigrator = require('knex-migrator'),
spawn = require('child_process').spawn,
Config = require('../utils/config'),
config = Config.load('config.' + process.env.NODE_ENV + '.json'),
path = require('path'),
self = this,
knexMigrator;
knexMigrator, pm;

// knex-migrator needs to know the content path
process.env.paths__contentPath = path.join(process.cwd(), 'content');
Expand All @@ -32,6 +35,8 @@ module.exports.Command = BaseCommand.extend({
knexMigratorFilePath: path.join(process.cwd(), 'current')
});

pm = resolveProcessManager(config);

return knexMigrator.isDatabaseOK()
.catch(function (error) {
if (error.code === 'DB_NOT_INITIALISED' ||
Expand All @@ -54,23 +59,17 @@ module.exports.Command = BaseCommand.extend({

self.child.on('message', function (message) {
if (message.started) {
self.sendMessage(message);
pm.success();
return;
}

self.sendMessage({started: false, error: message.error});
pm.error(message.error);
});
}).catch(function (error) {
this.sendMessage({started: false, error: error.message});
pm.error(error.message);
});
},

sendMessage: function sendMessage(message) {
if (process.send) {
process.send(message);
}
},

exit: function exit() {
if (this.child) {
this.child.kill();
Expand Down
16 changes: 13 additions & 3 deletions lib/commands/setup.js
Expand Up @@ -24,7 +24,8 @@ module.exports.Command = BaseCommand.extend({
var local = options.local || false,
Promise = require('bluebird'),
path = require('path'),
self = this;
self = this,
config;

delete options.local;

Expand All @@ -36,7 +37,11 @@ module.exports.Command = BaseCommand.extend({
options.environment = 'development';
}

return this.runCommand('config', null, null, options).then(function setupChecks() {
options.return = true;

return this.runCommand('config', null, null, options).then(function setupChecks(_config) {
config = _config;

if (!options.stack) {
return Promise.resolve();
}
Expand All @@ -55,7 +60,12 @@ module.exports.Command = BaseCommand.extend({

return Promise.reject();
});
}).then(function afterConfig() {
}).then(function afterSysChecks() {
var resolveProcessManager = require('../process').resolve,
pm = resolveProcessManager(config);

return self.ui.run(pm.setup(), 'Setting up ' + pm.name + ' process manager');
}).then(function afterPmSetup() {
// If 'local' is specified we will automatically start ghost
if (local) {
return Promise.resolve({start: true});
Expand Down
17 changes: 15 additions & 2 deletions lib/process/index.js
@@ -1,7 +1,8 @@
var CoreObject = require('core-object'),
each = require('lodash/forEach'),
knownManagers = {
local: '../process/local'
local: './local',
systemd: './systemd'
},
BaseProcess;

Expand Down Expand Up @@ -34,11 +35,23 @@ module.exports = BaseProcess = CoreObject.extend({
init: function init(config) {
this._super();
this.config = config;
},

success: function success() {
// base implementation - does nothing
},

error: function error() {
// base implementation - does nothing
},

setup: function setup() {
// base implementation - does nothing
}
});

module.exports.resolve = function resolveProcessManager(config) {
var manager = config.get('process', 'local'),
var manager = config.get('process', 'systemd'),
ProcessManager, pmInstance, missingFns;

// First check if the manager is in a location that is known
Expand Down
12 changes: 12 additions & 0 deletions lib/process/local.js
Expand Up @@ -65,5 +65,17 @@ module.exports = BaseProcess.extend({
}).then(function () {
fs.unlinkSync(path.join(cwd, PID_FILE));
});
},

success: function success() {
if (process.send) {
process.send({started: true});
}
},

error: function error(error) {
if (process.send) {
process.send({error: true, message: error.message});
}
}
});
15 changes: 15 additions & 0 deletions lib/process/systemd/ghost.service.template
@@ -0,0 +1,15 @@
[Unit]
Description=Ghost systemd service for blog: <%= name %>
# Documentation=add documentation link here

[Service]
Type=notify
NotifyAccess=all
WorkingDirectory=<%= dir %>
User=<%= user %>
Environment="NODE_ENV=<%= environment %>"
ExecStart=<%= ghost_exec_path %> run
Restart=on-failure

[Install]
WantedBy=multi-user.target
61 changes: 61 additions & 0 deletions lib/process/systemd/index.js
@@ -0,0 +1,61 @@
var BaseProcess = require('../index'),
execSync = require('child_process').execSync;

module.exports = BaseProcess.extend({
name: 'systemd',

init: function init(config) {
this._super(config);
this.service = 'ghost_' + config.get('pname');
},

setup: function setup() {
var Promise = require('bluebird'),
template = require('lodash/template'),
path = require('path'),
fs = require('fs'),
service = template(fs.readFileSync(path.join(__dirname, 'ghost.service.template'), 'utf8')),
serviceFileName = this.service + '.service';

fs.writeFileSync(path.join(process.cwd(), serviceFileName), service({
name: this.config.get('pname'),
dir: process.cwd(),
user: process.getuid(),
// TODO: once setup supports passing in node_env, replace
// this line with the environment. Until now, it's safe to
// hardcode 'production' in.
environment: 'production',
ghost_exec_path: process.argv.slice(0,2).join(' ')
}), 'utf8');

try {
// Because of the loading spinner, we must run this using execSync in the case
// that the sudo command prompts for a password. Running a promisified version of exec
// does not work in this case.
execSync('sudo mv ' + serviceFileName + ' /lib/systemd/system', {
cwd: process.cwd(),
stdio: ['inherit', 'inherit', 'inherit']
});
} catch (e) {
return Promise.reject('Ghost service file could not be put in place, ensure you have proper sudo permissions and systemd is installed.');
}
},

start: function start() {
execSync('systemctl start ' + this.service, {
stdio: ['inherit', 'inherit', 'inherit']
});
},

stop: function stop() {
execSync('systemctl stop ' + this.service, {
stdio: ['inherit', 'inherit', 'inherit']
});
},

success: function success() {
// This seems to work better than execSync, probably because it's
// asynchronous and thus doesn't interfere with the event loop
require('child_process').exec('systemd-notify --ready');
}
});

0 comments on commit fc65f04

Please sign in to comment.