Skip to content

Commit fc65f04

Browse files
authored
Merge pull request #73 from TryGhost/systemd
Add systemd process manager
2 parents 6a73465 + 52cdb93 commit fc65f04

File tree

7 files changed

+129
-16
lines changed

7 files changed

+129
-16
lines changed

lib/commands/config/advanced.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ module.exports = [{
2626
}, {
2727
name: 'port',
2828
description: 'Port ghost should listen on'
29+
}, {
30+
name: 'process',
31+
description: 'Type of process manager to run Ghost with'
2932
}, {
3033
name: 'db',
3134
description: 'Type of database Ghost should use',

lib/commands/run.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ module.exports.Command = BaseCommand.extend({
1919
process.env.NODE_ENV = (options.production || process.env.NODE_ENV === 'production') ?
2020
'production' : 'development';
2121

22-
var KnexMigrator = require('knex-migrator'),
22+
var resolveProcessManager = require('../process').resolve,
23+
KnexMigrator = require('knex-migrator'),
2324
spawn = require('child_process').spawn,
25+
Config = require('../utils/config'),
26+
config = Config.load('config.' + process.env.NODE_ENV + '.json'),
2427
path = require('path'),
2528
self = this,
26-
knexMigrator;
29+
knexMigrator, pm;
2730

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

38+
pm = resolveProcessManager(config);
39+
3540
return knexMigrator.isDatabaseOK()
3641
.catch(function (error) {
3742
if (error.code === 'DB_NOT_INITIALISED' ||
@@ -54,23 +59,17 @@ module.exports.Command = BaseCommand.extend({
5459

5560
self.child.on('message', function (message) {
5661
if (message.started) {
57-
self.sendMessage(message);
62+
pm.success();
5863
return;
5964
}
6065

61-
self.sendMessage({started: false, error: message.error});
66+
pm.error(message.error);
6267
});
6368
}).catch(function (error) {
64-
this.sendMessage({started: false, error: error.message});
69+
pm.error(error.message);
6570
});
6671
},
6772

68-
sendMessage: function sendMessage(message) {
69-
if (process.send) {
70-
process.send(message);
71-
}
72-
},
73-
7473
exit: function exit() {
7574
if (this.child) {
7675
this.child.kill();

lib/commands/setup.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ module.exports.Command = BaseCommand.extend({
2424
var local = options.local || false,
2525
Promise = require('bluebird'),
2626
path = require('path'),
27-
self = this;
27+
self = this,
28+
config;
2829

2930
delete options.local;
3031

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

39-
return this.runCommand('config', null, null, options).then(function setupChecks() {
40+
options.return = true;
41+
42+
return this.runCommand('config', null, null, options).then(function setupChecks(_config) {
43+
config = _config;
44+
4045
if (!options.stack) {
4146
return Promise.resolve();
4247
}
@@ -55,7 +60,12 @@ module.exports.Command = BaseCommand.extend({
5560

5661
return Promise.reject();
5762
});
58-
}).then(function afterConfig() {
63+
}).then(function afterSysChecks() {
64+
var resolveProcessManager = require('../process').resolve,
65+
pm = resolveProcessManager(config);
66+
67+
return self.ui.run(pm.setup(), 'Setting up ' + pm.name + ' process manager');
68+
}).then(function afterPmSetup() {
5969
// If 'local' is specified we will automatically start ghost
6070
if (local) {
6171
return Promise.resolve({start: true});

lib/process/index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
var CoreObject = require('core-object'),
22
each = require('lodash/forEach'),
33
knownManagers = {
4-
local: '../process/local'
4+
local: './local',
5+
systemd: './systemd'
56
},
67
BaseProcess;
78

@@ -34,11 +35,23 @@ module.exports = BaseProcess = CoreObject.extend({
3435
init: function init(config) {
3536
this._super();
3637
this.config = config;
38+
},
39+
40+
success: function success() {
41+
// base implementation - does nothing
42+
},
43+
44+
error: function error() {
45+
// base implementation - does nothing
46+
},
47+
48+
setup: function setup() {
49+
// base implementation - does nothing
3750
}
3851
});
3952

4053
module.exports.resolve = function resolveProcessManager(config) {
41-
var manager = config.get('process', 'local'),
54+
var manager = config.get('process', 'systemd'),
4255
ProcessManager, pmInstance, missingFns;
4356

4457
// First check if the manager is in a location that is known

lib/process/local.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,17 @@ module.exports = BaseProcess.extend({
6565
}).then(function () {
6666
fs.unlinkSync(path.join(cwd, PID_FILE));
6767
});
68+
},
69+
70+
success: function success() {
71+
if (process.send) {
72+
process.send({started: true});
73+
}
74+
},
75+
76+
error: function error(error) {
77+
if (process.send) {
78+
process.send({error: true, message: error.message});
79+
}
6880
}
6981
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[Unit]
2+
Description=Ghost systemd service for blog: <%= name %>
3+
# Documentation=add documentation link here
4+
5+
[Service]
6+
Type=notify
7+
NotifyAccess=all
8+
WorkingDirectory=<%= dir %>
9+
User=<%= user %>
10+
Environment="NODE_ENV=<%= environment %>"
11+
ExecStart=<%= ghost_exec_path %> run
12+
Restart=on-failure
13+
14+
[Install]
15+
WantedBy=multi-user.target

lib/process/systemd/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
var BaseProcess = require('../index'),
2+
execSync = require('child_process').execSync;
3+
4+
module.exports = BaseProcess.extend({
5+
name: 'systemd',
6+
7+
init: function init(config) {
8+
this._super(config);
9+
this.service = 'ghost_' + config.get('pname');
10+
},
11+
12+
setup: function setup() {
13+
var Promise = require('bluebird'),
14+
template = require('lodash/template'),
15+
path = require('path'),
16+
fs = require('fs'),
17+
service = template(fs.readFileSync(path.join(__dirname, 'ghost.service.template'), 'utf8')),
18+
serviceFileName = this.service + '.service';
19+
20+
fs.writeFileSync(path.join(process.cwd(), serviceFileName), service({
21+
name: this.config.get('pname'),
22+
dir: process.cwd(),
23+
user: process.getuid(),
24+
// TODO: once setup supports passing in node_env, replace
25+
// this line with the environment. Until now, it's safe to
26+
// hardcode 'production' in.
27+
environment: 'production',
28+
ghost_exec_path: process.argv.slice(0,2).join(' ')
29+
}), 'utf8');
30+
31+
try {
32+
// Because of the loading spinner, we must run this using execSync in the case
33+
// that the sudo command prompts for a password. Running a promisified version of exec
34+
// does not work in this case.
35+
execSync('sudo mv ' + serviceFileName + ' /lib/systemd/system', {
36+
cwd: process.cwd(),
37+
stdio: ['inherit', 'inherit', 'inherit']
38+
});
39+
} catch (e) {
40+
return Promise.reject('Ghost service file could not be put in place, ensure you have proper sudo permissions and systemd is installed.');
41+
}
42+
},
43+
44+
start: function start() {
45+
execSync('systemctl start ' + this.service, {
46+
stdio: ['inherit', 'inherit', 'inherit']
47+
});
48+
},
49+
50+
stop: function stop() {
51+
execSync('systemctl stop ' + this.service, {
52+
stdio: ['inherit', 'inherit', 'inherit']
53+
});
54+
},
55+
56+
success: function success() {
57+
// This seems to work better than execSync, probably because it's
58+
// asynchronous and thus doesn't interfere with the event loop
59+
require('child_process').exec('systemd-notify --ready');
60+
}
61+
});

0 commit comments

Comments
 (0)