49 if (defined(
'SIGUSR1') && $signum == SIGUSR1) {
70 flock($handle, LOCK_UN);
72 $logger = \Plop\Plop::getInstance();
74 'Removed lock on pidfile (%(pidfile)s)',
75 array(
'pidfile' => $pidfile)
87 public static function run()
94 class_alias(__CLASS__,
"Erebot",
false);
97 $dic = new \Symfony\Component\DependencyInjection\ContainerBuilder();
98 $dic->setParameter(
'Erebot.src_dir', __DIR__);
99 $loader = new \Symfony\Component\DependencyInjection\Loader\XmlFileLoader(
101 new \Symfony\Component\Config\FileLocator(getcwd())
104 $dicConfig = dirname(__DIR__) .
105 DIRECTORY_SEPARATOR .
'data' .
106 DIRECTORY_SEPARATOR .
'defaults.xml';
107 $dicCwdConfig = getcwd() . DIRECTORY_SEPARATOR .
'defaults.xml';
108 if (!strncasecmp(__FILE__,
'phar://', 7)) {
109 if (!file_exists($dicCwdConfig)) {
110 copy($dicConfig, $dicCwdConfig);
112 $dicConfig = $dicCwdConfig;
113 } elseif (file_exists($dicCwdConfig)) {
114 $dicConfig = $dicCwdConfig;
116 $loader->load($dicConfig);
120 $hasPosix = in_array(
'posix', get_loaded_extensions());
121 $hasPcntl = in_array(
'pcntl', get_loaded_extensions());
123 $logger = $dic->get(
'logging');
124 $translator = $dic->get(
'translator');
128 $version =
'dev-master';
129 if (!strncmp(__FILE__,
'phar://', 7)) {
130 $phar = new \Phar(\Phar::running(
true));
131 $md = $phar->getMetadata();
132 $version = $md[
'version'];
134 if (defined(
'Erebot_PHARS')) {
135 $phars = unserialize(Erebot_PHARS);
137 foreach ($phars as $module => $metadata) {
138 if (strncasecmp($module,
'Erebot_Module_', 14)) {
141 $version .=
"\n with $module version ${metadata['version']}";
145 \Console_CommandLine::registerAction(
'StoreProxy',
'\\Erebot\\Console\\StoreProxyAction');
146 $parser = new \Console_CommandLine(
150 $translator->gettext(
'A modular IRC bot written in PHP'),
151 'version' => $version,
152 'add_help_option' =>
true,
153 'add_version_option' =>
true,
154 'force_posix' =>
false,
157 $parser->accept(
new \Erebot\Console\MessageProvider());
158 $parser->renderer->options_on_different_lines =
true;
160 $defaultConfigFile = getcwd() . DIRECTORY_SEPARATOR .
'Erebot.xml';
164 'short_name' =>
'-c',
165 'long_name' =>
'--config',
166 'description' => $translator->gettext(
167 'Path to the configuration file to use instead '.
168 'of "Erebot.xml", relative to the current '.
171 'help_name' =>
'FILE',
172 'action' =>
'StoreString',
173 'default' => $defaultConfigFile,
180 'short_name' =>
'-d',
181 'long_name' =>
'--daemon',
182 'description' => $translator->gettext(
183 'Run the bot in the background (daemon).'.
184 ' [requires the POSIX and pcntl extensions]'
186 'action' =>
'StoreTrue',
190 $noDaemon = new \Erebot\Console\ParallelOption(
193 'short_name' =>
'-n',
194 'long_name' =>
'--no-daemon',
195 'description' => $translator->gettext(
196 'Do not run the bot in the background. '.
197 'This is the default, unless the -d option '.
198 'is used or the bot is configured otherwise.'
200 'action' =>
'StoreProxy',
201 'action_params' => array(
'option' =>
'daemon'),
204 $parser->addOption($noDaemon);
209 'short_name' =>
'-p',
210 'long_name' =>
'--pidfile',
211 'description' => $translator->gettext(
212 "Store the bot's PID in this file."
214 'help_name' =>
'FILE',
215 'action' =>
'StoreString',
223 'short_name' =>
'-g',
224 'long_name' =>
'--group',
225 'description' => $translator->gettext(
226 'Set group identity to this GID/group during '.
227 'startup. The default is to NOT change group '.
228 'identity, unless configured otherwise.'.
229 ' [requires the POSIX extension]'
231 'help_name' =>
'GROUP/GID',
232 'action' =>
'StoreString',
240 'short_name' =>
'-u',
241 'long_name' =>
'--user',
242 'description' => $translator->gettext(
243 'Set user identity to this UID/username during '.
244 'startup. The default is to NOT change user '.
245 'identity, unless configured otherwise.'.
246 ' [requires the POSIX extension]'
248 'help_name' =>
'USER/UID',
249 'action' =>
'StoreString',
255 $parsed = $parser->parse();
256 }
catch (\Exception $exc) {
257 $parser->displayError($exc->getMessage());
262 $config = new \Erebot\Config\Main(
263 $parsed->options[
'config'],
264 \Erebot\Config\Main::LOAD_FROM_FILE,
268 $coreCls = $dic->getParameter(
'core.classes.core');
269 $bot =
new $coreCls($config, $translator);
270 $dic->set(
'bot', $bot);
275 'daemon' =>
'mustDaemonize',
276 'group' =>
'getGroupIdentity',
277 'user' =>
'getUserIdentity',
278 'pidfile' =>
'getPidfile',
280 foreach ($overrides as $option => $func) {
281 if ($parsed->options[$option] === null) {
282 $parsed->options[$option] = $config->$func();
291 if ($parsed->options[
'daemon']) {
294 $translator->gettext(
295 'The posix extension is required in order '.
296 'to start the bot in the background'
304 $translator->gettext(
305 'The pcntl extension is required in order '.
306 'to start the bot in the background'
312 foreach (array(
'SIGCHLD',
'SIGUSR1',
'SIGALRM') as $signal) {
313 if (defined($signal)) {
316 array(__CLASS__,
'startupSighandler')
322 $translator->gettext(
'Starting the bot in the background...')
327 $translator->gettext(
328 'Could not start in the background (unable to fork)'
334 pcntl_wait($dummy, WUNTRACED);
336 pcntl_signal_dispatch();
339 $parent = posix_getppid();
342 foreach (array(
'SIGTSTP',
'SIGTOU',
'SIGTIN',
'SIGHUP') as $signal) {
343 if (defined($signal)) {
344 pcntl_signal(constant($signal), SIG_IGN);
349 foreach (array(
'SIGCHLD',
'SIGUSR1',
'SIGALRM') as $signal) {
350 if (defined($signal)) {
351 pcntl_signal(constant($signal), SIG_DFL);
358 $translator->gettext(
'Could not change umask')
362 if (posix_setsid() == -1) {
364 $translator->gettext(
365 'Could not start in the background (unable to create a new session)'
376 $translator->gettext(
377 'Could not start in the background (unable to fork)'
387 if (!chdir(DIRECTORY_SEPARATOR)) {
389 $translator->gettext(
'Could not change directory to "%(path)s"'),
390 array(
'path' => DIRECTORY_SEPARATOR)
395 foreach (array(
'STDIN',
'STDOUT',
'STDERR') as $stream) {
396 if (defined($stream)) {
397 fclose(constant($stream));
406 $stdin = fopen(
'/dev/null',
'r');
407 $stdout = fopen(
'/dev/null',
'w');
408 $stderr = fopen(
'/dev/null',
'w');
410 if (defined(
'SIGUSR1')) {
411 posix_kill($parent, SIGUSR1);
414 $translator->gettext(
'Successfully started in the background')
420 $identd = $dic->get(
'identd');
421 }
catch (\InvalidArgumentException $e) {
427 $prompt = $dic->get(
'prompt');
428 }
catch (\InvalidArgumentException $e) {
433 if ($parsed->options[
'group'] !== null &&
434 $parsed->options[
'group'] !=
'') {
437 $translator->gettext(
438 'The posix extension is needed in order '.
439 'to change group identity.'
442 } elseif (posix_getuid() !== 0) {
444 $translator->gettext(
445 'Only the "root" user may change group identity! '.
446 'Your current UID is %(uid)d'
448 array(
'uid' => posix_getuid())
451 if (ctype_digit($parsed->options[
'group'])) {
452 $info = posix_getgrgid((
int) $parsed->options[
'group']);
454 $info = posix_getgrnam($parsed->options[
'group']);
457 if ($info ===
false) {
459 $translator->gettext(
'No such group "%(group)s"'),
460 array(
'group' => $parsed->options[
'group'])
465 if (!posix_setgid($info[
'gid'])) {
467 $translator->gettext(
468 'Could not set group identity '.
469 'to "%(name)s" (%(id)d)'
472 'id' => $info[
'gid'],
473 'name' => $info[
'name'],
480 $translator->gettext(
481 'Successfully changed group identity '.
482 'to "%(name)s" (%(id)d)'
485 'name' => $info[
'name'],
486 'id' => $info[
'gid'],
493 if ($parsed->options[
'user'] !== null ||
494 $parsed->options[
'user'] !=
'') {
497 $translator->gettext(
498 'The posix extension is needed in order '.
499 'to change user identity.'
502 } elseif (posix_getuid() !== 0) {
504 $translator->gettext(
505 'Only the "root" user may change user identity! '.
506 'Your current UID is %(uid)d'
508 array(
'uid' => posix_getuid())
511 if (ctype_digit($parsed->options[
'user'])) {
512 $info = posix_getpwuid((
int) $parsed->options[
'user']);
514 $info = posix_getpwnam($parsed->options[
'user']);
517 if ($info ===
false) {
519 $translator->gettext(
'No such user "%(user)s"'),
520 array(
'user' => $parsed->options[
'user'])
525 if (!posix_setuid($info[
'uid'])) {
527 $translator->gettext(
528 'Could not set user identity '.
529 'to "%(name)s" (%(id)d)'
532 'name' => $info[
'name'],
533 'id' => $info[
'uid'],
539 $translator->gettext(
540 'Successfully changed user identity '.
541 'to "%(name)s" (%(id)d)'
544 'name' => $info[
'name'],
545 'id' => $info[
'uid'],
552 if ($parsed->options[
'pidfile'] !== null &&
553 $parsed->options[
'pidfile'] !=
'') {
554 $pid = @file_get_contents($parsed->options[
'pidfile']);
557 if ($pid !==
false) {
558 $pid = (int) rtrim($pid);
561 $translator->gettext(
562 'The pidfile (%(pidfile)s) contained garbage. ' .
565 array(
'pidfile' => $parsed->options[
'pidfile'])
570 $res = posix_errno();
574 $translator->gettext(
575 'Erebot is already running ' .
584 $translator->gettext(
585 'Found stalled PID %(pid)d in pidfile '.
586 '"%(pidfile)s". Removing it'
589 'pidfile' => $parsed->options[
'pidfile'],
593 @unlink($parsed->options[
'pidfile']);
598 $translator->gettext(
599 'Found another program\'s PID %(pid)d in '.
600 'pidfile "%(pidfile)s". Exiting'
603 'pidfile' => $parsed->options[
'pidfile'],
611 $translator->gettext(
612 'Unknown error while checking for '.
613 'the existence of another running '.
614 'instance of Erebot (%(error)s)'
616 array(
'error' => posix_get_last_error())
623 $pidfile = fopen($parsed->options[
'pidfile'],
'wt');
624 flock($pidfile, LOCK_EX | LOCK_NB, $wouldBlock);
627 $translator->gettext(
628 'Could not lock pidfile (%(pidfile)s). '.
629 'Is the bot already running?'
631 array(
'pidfile' => $parsed->options[
'pidfile'])
636 $pid = sprintf(
"%u\n", getmypid());
637 $res = fwrite($pidfile, $pid);
638 if ($res !== strlen($pid)) {
640 $translator->gettext(
641 'Unable to write PID to pidfile (%(pidfile)s)'
643 array(
'pidfile' => $parsed->options[
'pidfile'])
649 $translator->gettext(
650 'PID (%(pid)d) written into %(pidfile)s'
653 'pidfile' => $parsed->options[
'pidfile'],
658 register_shutdown_function(
659 array(__CLASS__,
'cleanupPidfile'),
661 $parsed->options[
'pidfile']
666 if ($hasPosix && posix_getuid() === 0) {
668 $translator->gettext(
'You SHOULD NOT run Erebot as root!')
672 if ($identd !== null) {
676 if ($prompt !== null) {
682 $bot->start($dic->get(
'factory.connection'));
static startupSighandler($signum)
static cleanupPidfile($handle, $pidfile)
Provides the entry-point for Erebot.