1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Move server-related Aphlict options to a configuration file

Summary: Ref T10697. This isn't everything but starts generalizing options and moving us toward a cluster-ready state of affairs.

Test Plan: Started server in various configurations, hit most (all?) of the error cases with bad configs, sent test notifications.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10697

Differential Revision: https://secure.phabricator.com/D15701
This commit is contained in:
epriestley 2016-04-13 11:03:42 -07:00
parent e32ce529d7
commit c84dee522b
8 changed files with 273 additions and 107 deletions

1
.gitignore vendored
View file

@ -16,6 +16,7 @@
/conf/keys/device.pub /conf/keys/device.pub
/conf/keys/device.key /conf/keys/device.key
/conf/keys/device.id /conf/keys/device.id
/conf/aphlict/aphlict.custom.json
# Impact Font # Impact Font
/resources/font/impact.ttf /resources/font/impact.ttf

16
conf/aphlict/README Normal file
View file

@ -0,0 +1,16 @@
To customize this configuration, you have two options: create a custom
configuration file in this directory, or specify a path to a configuration file
explicitly when starting Aphlict.
To create a custom configuration file, copy `aphlict.default.json` in this
directory and rename it `aphlict.custom.json`. If this file exists, it will
be read by default.
To specify a path when starting Aphlict, use the `--config` flag:
phabricator/ $ ./bin/aphlict start --config path/to/config.json
Specifying a configuration file explicitly overrides default configuration.
For more information about configuring notifications, see the article
"Notifications User Guide: Setup and Configuration" in the documentation.

View file

@ -0,0 +1,18 @@
{
"servers": [
{
"type": "client",
"port": 22280,
"listen": "0.0.0.0",
"ssl.key": null,
"ssl.cert": null
},
{
"type": "admin",
"port": 22281,
"listen": "127.0.0.1",
"ssl.key": null,
"ssl.cert": null
}
]
}

View file

@ -4,8 +4,7 @@ abstract class PhabricatorAphlictManagementWorkflow
extends PhabricatorManagementWorkflow { extends PhabricatorManagementWorkflow {
private $debug = false; private $debug = false;
private $clientHost; private $configPath;
private $clientPort;
final protected function setDebug($debug) { final protected function setDebug($debug) {
$this->debug = $debug; $this->debug = $debug;
@ -15,21 +14,167 @@ abstract class PhabricatorAphlictManagementWorkflow
protected function getLaunchArguments() { protected function getLaunchArguments() {
return array( return array(
array( array(
'name' => 'client-host', 'name' => 'config',
'param' => 'hostname', 'param' => 'file',
'help' => pht('Hostname to bind to for the client server.'), 'help' => pht(
), 'Use a specific configuration file instead of the default '.
array( 'configuration.'),
'name' => 'client-port',
'param' => 'port',
'help' => pht('Port to bind to for the client server.'),
), ),
); );
} }
protected function parseLaunchArguments(PhutilArgumentParser $args) { protected function parseLaunchArguments(PhutilArgumentParser $args) {
$this->clientHost = $args->getArg('client-host'); $config_file = $args->getArg('config');
$this->clientPort = $args->getArg('client-port'); if ($config_file) {
$full_path = Filesystem::resolvePath($config_file);
$show_path = $full_path;
} else {
$root = dirname(dirname(phutil_get_library_root('phabricator')));
$try = array(
'phabricator/conf/aphlict/aphlict.custom.json',
'phabricator/conf/aphlict/aphlict.default.json',
);
foreach ($try as $config) {
$full_path = $root.'/'.$config;
$show_path = $config;
if (Filesystem::pathExists($full_path)) {
break;
}
}
}
echo tsprintf(
"%s\n",
pht(
'Reading configuration from: %s',
$show_path));
try {
$data = Filesystem::readFile($full_path);
} catch (Exception $ex) {
throw new PhutilArgumentUsageException(
pht(
'Failed to read configuration file. %s',
$ex->getMessage()));
}
try {
$data = phutil_json_decode($data);
} catch (Exception $ex) {
throw new PhutilArgumentUsageException(
pht(
'Configuration file is not properly formatted JSON. %s',
$ex->getMessage()));
}
try {
PhutilTypeSpec::checkMap(
$data,
array(
'servers' => 'list<wild>',
));
} catch (Exception $ex) {
throw new PhutilArgumentUsageException(
pht(
'Configuration file has improper configuration keys at top '.
'level. %s',
$ex->getMessage()));
}
$servers = $data['servers'];
$has_client = false;
$has_admin = false;
$port_map = array();
foreach ($servers as $index => $server) {
PhutilTypeSpec::checkMap(
$server,
array(
'type' => 'string',
'port' => 'int',
'listen' => 'optional string|null',
'ssl.key' => 'optional string|null',
'ssl.cert' => 'optional string|null',
));
$port = $server['port'];
if (!isset($port_map[$port])) {
$port_map[$port] = $index;
} else {
throw new PhutilArgumentUsageException(
pht(
'Two servers (at indexes "%s" and "%s") both bind to the same '.
'port ("%s"). Each server must bind to a unique port.',
$port_map[$port],
$index,
$port));
}
$type = $server['type'];
switch ($type) {
case 'admin':
$has_admin = true;
break;
case 'client':
$has_client = true;
break;
default:
throw new PhutilArgumentUsageException(
pht(
'A specified server (at index "%s", on port "%s") has an '.
'invalid type ("%s"). Valid types are: admin, client.',
$index,
$port,
$type));
}
$ssl_key = idx($server, 'ssl.key');
$ssl_cert = idx($server, 'ssl.cert');
if (($ssl_key && !$ssl_cert) || ($ssl_cert && !$ssl_key)) {
throw new PhutilArgumentUsageException(
pht(
'A specified server (at index "%s", on port "%s") specifies '.
'only one of "%s" and "%s". Each server must specify neither '.
'(to disable SSL) or specify both (to enable it).',
$index,
$port,
'ssl.key',
'ssl.cert'));
}
}
if (!$servers) {
throw new PhutilArgumentUsageException(
pht(
'Configuration file does not specify any servers. This service '.
'will not be able to interact with the outside world if it does '.
'not listen on any ports. You must specify at least one "%s" '.
'server and at least one "%s" server.',
'admin',
'client'));
}
if (!$has_client) {
throw new PhutilArgumentUsageException(
pht(
'Configuration file does not specify any client servers. This '.
'service will be unable to transmit any notifications without a '.
'client server. You must specify at least one server with '.
'type "%s".',
'client'));
}
if (!$has_admin) {
throw new PhutilArgumentUsageException(
pht(
'Configuration file does not specify any administrative '.
'servers. This service will be unable to receive messages. '.
'You must specify at least one server with type "%s".',
'admin'));
}
$this->configPath = $full_path;
} }
final public function getPIDPath() { final public function getPIDPath() {
@ -148,38 +293,12 @@ abstract class PhabricatorAphlictManagementWorkflow
} }
private function getServerArgv() { private function getServerArgv() {
$ssl_key = PhabricatorEnv::getEnvConfig('notification.ssl-key');
$ssl_cert = PhabricatorEnv::getEnvConfig('notification.ssl-cert');
$server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri');
$server_uri = new PhutilURI($server_uri);
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
$log = $this->getLogPath(); $log = $this->getLogPath();
$server_argv = array(); $server_argv = array();
$server_argv[] = '--client-port='.coalesce( $server_argv[] = '--config='.$this->configPath;
$this->clientPort,
$client_uri->getPort());
$server_argv[] = '--admin-port='.$server_uri->getPort();
$server_argv[] = '--admin-host='.$server_uri->getDomain();
if ($ssl_key) {
$server_argv[] = '--ssl-key='.$ssl_key;
}
if ($ssl_cert) {
$server_argv[] = '--ssl-cert='.$ssl_cert;
}
$server_argv[] = '--log='.$log; $server_argv[] = '--log='.$log;
if ($this->clientHost) {
$server_argv[] = '--client-host='.$this->clientHost;
}
return $server_argv; return $server_argv;
} }

View file

@ -182,6 +182,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'Garbage collectors are now configured with "%s".', 'Garbage collectors are now configured with "%s".',
'bin/garbage set-policy'); 'bin/garbage set-policy');
$aphlict_reason = pht(
'Configuration of the notification server has changed substantially. '.
'For discussion, see T10794.');
$ancient_config += array( $ancient_config += array(
'phid.external-loaders' => 'phid.external-loaders' =>
pht( pht(
@ -298,6 +302,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'phd.variant-config' => pht( 'phd.variant-config' => pht(
'This configuration is no longer relevant because daemons '. 'This configuration is no longer relevant because daemons '.
'restart automatically on configuration changes.'), 'restart automatically on configuration changes.'),
'notification.ssl-cert' => $aphlict_reason,
'notification.ssl-key' => $aphlict_reason,
); );
return $ancient_config; return $ancient_config;

View file

@ -46,14 +46,6 @@ final class PhabricatorNotificationConfigOptions
->setDescription(pht('Location of the notification receiver server.')), ->setDescription(pht('Location of the notification receiver server.')),
$this->newOption('notification.log', 'string', '/var/log/aphlict.log') $this->newOption('notification.log', 'string', '/var/log/aphlict.log')
->setDescription(pht('Location of the server log file.')), ->setDescription(pht('Location of the server log file.')),
$this->newOption('notification.ssl-key', 'string', null)
->setLocked(true)
->setDescription(
pht('Path to SSL key to use for secure WebSockets.')),
$this->newOption('notification.ssl-cert', 'string', null)
->setLocked(true)
->setDescription(
pht('Path to SSL certificate to use for secure WebSockets.')),
$this->newOption( $this->newOption(
'notification.pidfile', 'notification.pidfile',
'string', 'string',

View file

@ -59,19 +59,44 @@ After installing Node.js, you can control the notification server with the
phabricator/ $ bin/aphlict start phabricator/ $ bin/aphlict start
The server must be able to listen on port **22280** for Aphlict to work. In By default, the server must be able to listen on port `22280`. If you're using
particular, if you're running in EC2, you need to unblock this port in the a host firewall (like a security group in EC2), make sure traffic can reach the
server's security group configuration. You can change this port in the server.
`notification.client-uri` config.
You may need to adjust these settings: The server configuration is controlled by a configuration file, which is
separate from Phabricator's configuration settings. The default file can
be found at `phabricator/conf/aphlict/aphlict.default.json`.
- `notification.ssl-cert` Point this at an SSL certificate for secure To make adjustments to the default configuration, either copy this file to
WebSockets. create `aphlict.custom.json` in the same directory (this file will be used if
- `notification.ssl-key` Point this at an SSL keyfile for secure WebSockets. it exists) or specify a configuration file explicitly with the `--config` flag:
In particular, if your server uses HTTPS, you **must** configure these options. phabricator/ $ bin/aphlict start --config path/to/config.json
Browsers will not allow you to use non-SSL websockets from an SSL web page.
The configuration file has these settings:
- `servers`: A list of servers to start.
Each server in the `servers` list should be an object with these keys:
- `type`: //Required string.// The type of server to start. Options are
`admin` or `client`. Normally, you should run one of each.
- `port`: //Required int.// The port this server should listen on.
- `listen`: //Optional string.// Which interface to bind to. By default,
the `admin` server is bound to localhost (so only other services on the
local machine can connect to it), while the `client` server is bound
to `0.0.0.0` (so any client can connect.
- `ssl.key`: //Optional string.// If you want to use SSL on this port,
the path to an SSL key.
- `ssl.cert`: //Optional string.// If you want to use SSL on this port,
the path to an SSL certificate.
The defaults are appropriate for simple cases, but you may need to adjust them
if you are running a more complex configuration.
Configuring Phabricator
=======================
You may also want to adjust these settings: You may also want to adjust these settings:

View file

@ -7,15 +7,10 @@ var util = require('util');
var fs = require('fs'); var fs = require('fs');
function parse_command_line_arguments(argv) { function parse_command_line_arguments(argv) {
var config = { var args = {
'client-port': 22280,
'admin-port': 22281,
'client-host': '0.0.0.0',
'admin-host': '127.0.0.1',
log: '/var/log/aphlict.log', log: '/var/log/aphlict.log',
'ssl-key': null, test: false,
'ssl-cert': null, config: null
test: false
}; };
for (var ii = 2; ii < argv.length; ii++) { for (var ii = 2; ii < argv.length; ii++) {
@ -24,16 +19,18 @@ function parse_command_line_arguments(argv) {
if (!matches) { if (!matches) {
throw new Error('Unknown argument "' + arg + '"!'); throw new Error('Unknown argument "' + arg + '"!');
} }
if (!(matches[1] in config)) { if (!(matches[1] in args)) {
throw new Error('Unknown argument "' + matches[1] + '"!'); throw new Error('Unknown argument "' + matches[1] + '"!');
} }
config[matches[1]] = matches[2]; args[matches[1]] = matches[2];
} }
config['client-port'] = parseInt(config['client-port'], 10); return args;
config['admin-port'] = parseInt(config['admin-port'], 10); }
return config; function parse_config(args) {
var data = fs.readFileSync(args.config);
return JSON.parse(data);
} }
require('./lib/AphlictLog'); require('./lib/AphlictLog');
@ -41,7 +38,8 @@ require('./lib/AphlictLog');
var debug = new JX.AphlictLog() var debug = new JX.AphlictLog()
.addConsole(console); .addConsole(console);
var config = parse_command_line_arguments(process.argv); var args = parse_command_line_arguments(process.argv);
var config = parse_config(args);
function set_exit_code(code) { function set_exit_code(code) {
process.on('exit', function() { process.on('exit', function() {
@ -51,7 +49,7 @@ function set_exit_code(code) {
process.on('uncaughtException', function(err) { process.on('uncaughtException', function(err) {
var context = null; var context = null;
if (err.code == 'EACCES' && err.path == config.log) { if (err.code == 'EACCES' && err.path == args.log) {
context = util.format( context = util.format(
'Unable to open logfile ("%s"). Check that permissions are set ' + 'Unable to open logfile ("%s"). Check that permissions are set ' +
'correctly.', 'correctly.',
@ -71,8 +69,8 @@ process.on('uncaughtException', function(err) {
}); });
// Add the logfile so we'll fail if we can't write to it. // Add the logfile so we'll fail if we can't write to it.
if (config.log) { if (args.log) {
debug.addLog(config.log); debug.addLog(args.log);
} }
try { try {
@ -90,51 +88,37 @@ try {
require('./lib/AphlictAdminServer'); require('./lib/AphlictAdminServer');
require('./lib/AphlictClientServer'); require('./lib/AphlictClientServer');
var ssl_config = { var ii;
enabled: (config['ssl-key'] || config['ssl-cert'])
};
// Load the SSL certificates (if any were provided) now, so that runs with
// `--test` will see any errors.
if (ssl_config.enabled) {
ssl_config.key = fs.readFileSync(config['ssl-key']);
ssl_config.cert = fs.readFileSync(config['ssl-cert']);
} else {
ssl_config.key = null;
ssl_config.cert = null;
}
var servers = []; var servers = [];
for (ii = 0; ii < config.servers.length; ii++) {
var spec = config.servers[ii];
servers.push({ spec.listen = spec.listen || '0.0.0.0';
type: 'client',
port: config['client-port'],
listen: config['client-host'],
'ssl.key': ssl_config.key,
'ssl.certificate': ssl_config.cert
});
servers.push({ if (spec['ssl.key']) {
type: 'admin', spec['ssl.key'] = fs.readFileSync(spec['ssl.key']);
port: config['admin-port'], }
listen: config['admin-host'],
'ssl.key': null, if (spec['ssl.cert']){
'ssl.cert': null spec['ssl.cert'] = fs.readFileSync(spec['ssl.cert']);
}); }
servers.push(spec);
}
// If we're just doing a configuration test, exit here before starting any // If we're just doing a configuration test, exit here before starting any
// servers. // servers.
if (config.test) { if (args.test) {
debug.log('Configuration test OK.'); debug.log('Configuration test OK.');
set_exit_code(0); set_exit_code(0);
return; return;
} }
debug.log('Starting servers (service PID %d).', process.pid);
var aphlict_servers = []; var aphlict_servers = [];
var aphlict_clients = []; var aphlict_clients = [];
var aphlict_admins = []; var aphlict_admins = [];
var ii;
for (ii = 0; ii < servers.length; ii++) { for (ii = 0; ii < servers.length; ii++) {
var server = servers[ii]; var server = servers[ii];
var is_client = (server.type == 'client'); var is_client = (server.type == 'client');
@ -161,6 +145,12 @@ for (ii = 0; ii < servers.length; ii++) {
aphlict_server.setLogger(debug); aphlict_server.setLogger(debug);
aphlict_server.listen(server.port, server.listen); aphlict_server.listen(server.port, server.listen);
debug.log(
'Started %s server (Port %d, %s).',
server.type,
server.port,
server['ssl.key'] ? 'With SSL' : 'No SSL');
aphlict_servers.push(aphlict_server); aphlict_servers.push(aphlict_server);
if (is_client) { if (is_client) {
@ -174,5 +164,3 @@ for (ii = 0; ii < aphlict_admins.length; ii++) {
var admin_server = aphlict_admins[ii]; var admin_server = aphlict_admins[ii];
admin_server.setClientServers(aphlict_clients); admin_server.setClientServers(aphlict_clients);
} }
debug.log('Started Server (PID %d)', process.pid);