30h) * * On any failure, pushes a short Telegram alert to HEALTH_ALERT_CHAT_ID via * HEALTH_ALERT_BOT_TOKEN (env). Dedups identical failures within 30 minutes * via cache to avoid spamming on each cron tick. */ class HealthCheckCommand extends Command { protected $signature = 'health:check {--silent : Do not echo OK output (for cron)}'; protected $description = 'Probe DB / cache / storage / backup freshness; alert via Telegram on failure.'; public function handle(): int { $issues = []; try { DB::connection()->select('SELECT 1'); } catch (\Throwable $e) { $issues[] = 'DB: ' . substr($e->getMessage(), 0, 120); } try { $stamp = 'hc:' . (string) microtime(true); Cache::put('health:probe', $stamp, 30); if (Cache::get('health:probe') !== $stamp) { $issues[] = 'Cache: write/read mismatch'; } } catch (\Throwable $e) { $issues[] = 'Cache: ' . substr($e->getMessage(), 0, 120); } try { $f = 'health/' . md5((string) microtime(true)) . '.txt'; Storage::disk('public')->put($f, 'ok'); if (Storage::disk('public')->get($f) !== 'ok') { $issues[] = 'Storage: write/read mismatch'; } Storage::disk('public')->delete($f); } catch (\Throwable $e) { $issues[] = 'Storage: ' . substr($e->getMessage(), 0, 120); } try { $newest = collect(Storage::disk('local')->allFiles('backups')) ->map(fn ($f) => Storage::disk('local')->lastModified($f)) ->max(); if ($newest && (time() - $newest) > 30 * 3600) { $age = round((time() - $newest) / 3600, 1); $issues[] = "Backup: cel mai recent are {$age}h (expectat <30h)."; } } catch (\Throwable $e) { // Backup folder might be empty on a fresh install — not an alert. } if (empty($issues)) { if (! $this->option('silent')) { $this->info('Health OK · ' . now()->toIso8601String()); } return self::SUCCESS; } $signature = md5(implode('|', $issues)); $dedupKey = "health:alert:{$signature}"; if (! Cache::has($dedupKey)) { $this->pushTelegramAlert($issues); Cache::put($dedupKey, 1, 30 * 60); // 30-min cooldown per fingerprint } foreach ($issues as $i) $this->error($i); return self::FAILURE; } private function pushTelegramAlert(array $issues): void { $bot = env('HEALTH_ALERT_BOT_TOKEN'); $chat = env('HEALTH_ALERT_CHAT_ID'); if (! $bot || ! $chat) { Log::warning('health:check failed but HEALTH_ALERT_BOT_TOKEN/CHAT_ID not set', [ 'issues' => $issues, ]); return; } $body = "🚨 AutoCRM health alert\n" . implode("\n", array_map(fn ($i) => '• ' . htmlspecialchars($i), $issues)) . "\n\n" . config('app.url', 'service.mir.md'); try { Http::asJson() ->timeout(10) ->post("https://api.telegram.org/bot{$bot}/sendMessage", [ 'chat_id' => $chat, 'text' => $body, 'parse_mode' => 'HTML', ]); } catch (\Throwable $e) { Log::warning('health:check telegram alert failed', ['err' => $e->getMessage()]); } } }