addOption('host', null, InputOption::VALUE_REQUIRED, 'address to listen on', getenv('CARAMAIL_HOST') ?: '0.0.0.0')
->addOption('port', 'p', InputOption::VALUE_REQUIRED, 'TCP port for the clients', getenv('CARAMAIL_PORT') ?: '4500')
->addOption('ws-port', null, InputOption::VALUE_REQUIRED, 'WebSocket port (behind the web server on 443; 0 = none)', getenv('CARAMAIL_WS_PORT') ?: '8080')
->addOption('admin-key', null, InputOption::VALUE_REQUIRED, 'key that makes a client a moderator (in its tempid: k=...)', getenv('CARAMAIL_ADMIN_KEY') ?: '')
->addOption('super-key', null, InputOption::VALUE_REQUIRED, 'key that makes a client an administrator (admin 2)', getenv('CARAMAIL_SUPER_KEY') ?: '')
->addOption('owner-ips', null, InputOption::VALUE_REQUIRED, 'the owner\'s public addresses, comma separated: the login form offers them normal / moderator / admin', getenv('CARAMAIL_OWNER_IPS') ?: '');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!function_exists('stream_socket_server') || getenv('JUMPHP_WEB_TERMINAL')) {
$output->writeln('The server needs a real machine (Docker, a service): a browser cannot listen on a port.');
return Command::FAILURE;
}
$log = static function (string $line) use ($output): void {
$output->writeln(date('[Y-m-d H:i:s] ') . $line);
};
$store = new RoomStore($this->data . '/rooms.json');
$chat = new ChatServer($store, (string)$input->getOption('admin-key'), $output->isVerbose() ? $log : static function (string $l) use ($log): void {
if (preg_match('/^(login|leave|drop|unknown|.* ejects )/', $l)) {
$log($l);
}
});
$chat->setRoles((string)$input->getOption('super-key'), explode(',', (string)$input->getOption('owner-ips')));
$server = new TcpServer($chat, $log);
try {
$address = $server->listen((string)$input->getOption('host'), (int)$input->getOption('port'));
if ((int)$input->getOption('ws-port') > 0) {
$address .= ', WebSocket ' . $server->listenWebSocket((string)$input->getOption('host'), (int)$input->getOption('ws-port'));
}
} catch (\RuntimeException $e) {
$output->writeln('' . $e->getMessage() . '');
return Command::FAILURE;
}
$log(ChatServer::BANNER . " listening on $address, data in {$this->data}");
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
foreach ([SIGTERM, SIGINT, SIGHUP] as $signal) {
pcntl_signal($signal, static fn () => $server->stop());
}
}
$status = new StatusFile($this->data . '/status.json');
$saveEvery = 60;
$server->run(function () use ($chat, $status, $store, &$saveEvery): void {
$status->write($chat->stats());
if (--$saveEvery <= 0) {
$saveEvery = 60;
$store->save($chat->rooms());
}
});
$chat->shutdown();
$status->remove();
$log('stopped, rooms saved');
return Command::SUCCESS;
}
}