app->singleton(TelegramBotContext::class); $this->app->singleton(CorunaCrypto::class, function () { $override = config('coruna.session_key'); return new CorunaCrypto(is_string($override) && $override !== '' ? $override : null); }); $this->app->singleton('xxbb.crypto', function () { $key = config('coruna.xxbb.session_key', 'Ek8pl31K2yeHgQwy'); return new CorunaCrypto(is_string($key) && $key !== '' ? $key : 'Ek8pl31K2yeHgQwy'); }); $this->app->singleton(CorunaArchive::class, function ($app) { return new CorunaArchive( $app->make(CorunaCrypto::class), (string) config('coruna.seven_zip', ''), ); }); $this->app->bind(OcrDriver::class, TesseractOcrDriver::class); $this->app->singleton(AlchemyBalanceService::class, function () { return AlchemyBalanceService::make(); }); } public function boot(): void { // Floor for outbound calls that don't set their own timeout. Explicit // Http::timeout() still wins. 120s leaves room for slow uploads; // connect fails fast enough that a dead host doesn't sit for the full window. Http::globalOptions([ 'connect_timeout' => 20, 'timeout' => 120, ]); if (! $this->app->runningInConsole()) { $limit = (int) ini_get('max_execution_time'); if ($limit > 0 && $limit < 60) { @set_time_limit(60); } } try { $this->app->make(SettingsService::class)->applyToConfig(); } catch (\Throwable) { // settings table may not exist yet during migrate } if (! $this->app->runningUnitTests()) { $this->recordLastSql(); } // Override Nutgram's production safe_mode (md5 APP_KEY) so webhook updates // are not silently dropped when Telegram secret_token is unset/mismatched. $this->app->afterResolving(Nutgram::class, function (Nutgram $bot): void { if ($this->app->runningUnitTests() || $this->app->runningInConsole()) { return; } $secret = (string) config('coruna.telegram.webhook_secret', ''); $webhook = $secret !== '' ? new LaravelWebhook( getToken: static fn () => request()?->header('X-Telegram-Bot-Api-Secret-Token'), secretToken: $secret, ) : new LaravelWebhook; $webhook->setSafeMode($secret !== ''); $bot->setRunningMode($webhook); }); } /** * Keep the current process's last SQL in memory. On memory-exhaustion * shutdown, append it to oom-sql.log (shared last-sql.json is useless * under many FPM workers). */ private function recordLastSql(): void { $oomPath = storage_path('logs/oom-sql.log'); $snap = (object) ['line' => '']; DB::beforeExecuting(function (string $query, array $bindings) use ($snap): void { $uri = 'cli'; try { if (app()->runningInConsole()) { $uri = 'cli:'.implode(' ', $_SERVER['argv'] ?? []); } else { $req = request(); $uri = $req->method().' '.$req->getRequestUri(); } } catch (\Throwable) { } $snap->line = json_encode([ 'at' => date('c'), 'pid' => getmypid(), 'mem' => memory_get_usage(true), 'uri' => $uri, 'sql' => substr($query, 0, 1500), 'bindings' => array_slice($bindings, 0, 8), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: ''; }); register_shutdown_function(function () use ($oomPath, $snap): void { $err = error_get_last(); $msg = (string) ($err['message'] ?? ''); if ($msg === '' || ! str_contains($msg, 'Allowed memory size')) { return; } $line = $snap->line !== '' ? $snap->line : json_encode([ 'at' => date('c'), 'pid' => getmypid(), 'mem' => memory_get_usage(true), 'uri' => 'unknown', 'sql' => '', 'error' => substr($msg, 0, 240), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); @file_put_contents($oomPath, $line."\n", FILE_APPEND | LOCK_EX); }); DB::listen(function ($event): void { if ($event->time < 800 && memory_get_usage(true) < 80 * 1024 * 1024) { return; } Log::warning('slow_or_heavy_query', [ 'ms' => $event->time, 'mem' => memory_get_usage(true), 'sql' => substr((string) $event->sql, 0, 400), ]); }); } }