Files
ladill-hosting/app/Console/Commands/CheckNodeHealthCommand.php
T
isaaccladandCursor e251a4cf60
Deploy Ladill Hosting / deploy (push) Failing after 17s
Initial Ladill Hosting app with Gitea deploy pipeline.
Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 16:24:20 +00:00

163 lines
5.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\HostingNode;
use App\Services\Hosting\NodeMonitoringService;
use Illuminate\Console\Command;
class CheckNodeHealthCommand extends Command
{
protected $signature = 'hosting:check-node-health
{--node= : Check a specific node by ID}
{--all : Check all active nodes}';
protected $description = 'Run health checks on hosting nodes to collect resource metrics';
public function handle(NodeMonitoringService $monitoringService): int
{
$nodeId = $this->option('node');
if ($nodeId) {
return $this->checkSingleNode($monitoringService, (int) $nodeId);
}
return $this->checkAllNodes($monitoringService);
}
private function checkSingleNode(NodeMonitoringService $monitoringService, int $nodeId): int
{
$node = HostingNode::find($nodeId);
if (!$node) {
$this->error("Node with ID {$nodeId} not found.");
return self::FAILURE;
}
$this->info("Checking node: {$node->name} ({$node->ip_address})...");
$result = $monitoringService->checkNode($node);
$this->displayNodeResult($node, $result);
return self::SUCCESS;
}
private function checkAllNodes(NodeMonitoringService $monitoringService): int
{
$nodes = HostingNode::whereIn('status', [
HostingNode::STATUS_ACTIVE,
HostingNode::STATUS_FULL,
HostingNode::STATUS_MAINTENANCE,
])->get();
if ($nodes->isEmpty()) {
$this->warn('No active nodes found to check.');
return self::SUCCESS;
}
$this->info("Checking {$nodes->count()} node(s)...");
$this->newLine();
$results = [];
$bar = $this->output->createProgressBar($nodes->count());
$bar->start();
foreach ($nodes as $node) {
$results[$node->id] = $monitoringService->checkNode($node);
$bar->advance();
}
$bar->finish();
$this->newLine(2);
$this->displaySummaryTable($nodes, $results);
$unhealthy = collect($results)->filter(fn($r) => ($r['health'] ?? '') !== NodeMonitoringService::HEALTH_OK);
if ($unhealthy->isNotEmpty()) {
$this->newLine();
$this->warn("⚠️ {$unhealthy->count()} node(s) require attention.");
}
return self::SUCCESS;
}
private function displayNodeResult(HostingNode $node, array $result): void
{
$health = $result['health'] ?? 'unknown';
$healthIcon = match ($health) {
NodeMonitoringService::HEALTH_OK => '✅',
NodeMonitoringService::HEALTH_WARNING => '⚠️',
NodeMonitoringService::HEALTH_CRITICAL => '🔴',
NodeMonitoringService::HEALTH_UNREACHABLE => '❌',
default => '❓',
};
$this->newLine();
$this->info("{$healthIcon} Health: " . strtoupper($health));
$this->newLine();
if (isset($result['error'])) {
$this->error("Error: {$result['error']}");
return;
}
$this->table(
['Metric', 'Value'],
[
['CPU Usage', ($result['cpu']['usage_percent'] ?? 'N/A') . '%'],
['RAM Usage', ($result['memory']['usage_percent'] ?? 'N/A') . '%'],
['Disk Usage (root)', ($result['disk']['root']['usage_percent'] ?? 'N/A') . '%'],
['Load (1m)', $result['load']['load_1'] ?? 'N/A'],
['Load per Core', $result['load']['load_per_core'] ?? 'N/A'],
['Uptime', $result['uptime']['formatted'] ?? 'N/A'],
['Processes', $result['processes']['total'] ?? 'N/A'],
['Response Time', ($result['response_time_ms'] ?? 'N/A') . 'ms'],
]
);
if (!empty($result['services'])) {
$this->newLine();
$this->info('Services:');
foreach ($result['services'] as $service => $running) {
$icon = $running ? '✅' : '❌';
$this->line(" {$icon} {$service}");
}
}
}
private function displaySummaryTable($nodes, array $results): void
{
$rows = [];
foreach ($nodes as $node) {
$result = $results[$node->id] ?? [];
$health = $result['health'] ?? 'unknown';
$healthIcon = match ($health) {
NodeMonitoringService::HEALTH_OK => '✅',
NodeMonitoringService::HEALTH_WARNING => '⚠️',
NodeMonitoringService::HEALTH_CRITICAL => '🔴',
NodeMonitoringService::HEALTH_UNREACHABLE => '❌',
default => '❓',
};
$rows[] = [
$node->name,
$node->ip_address,
"{$healthIcon} " . strtoupper($health),
isset($result['cpu']['usage_percent']) ? $result['cpu']['usage_percent'] . '%' : 'N/A',
isset($result['memory']['usage_percent']) ? $result['memory']['usage_percent'] . '%' : 'N/A',
isset($result['disk']['root']['usage_percent']) ? $result['disk']['root']['usage_percent'] . '%' : 'N/A',
$result['load']['load_1'] ?? 'N/A',
"{$node->current_accounts}/{$node->max_accounts}",
];
}
$this->table(
['Node', 'IP', 'Health', 'CPU', 'RAM', 'Disk', 'Load', 'Accounts'],
$rows
);
}
}