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; } }