Initial Ladill Hosting app with Gitea deploy pipeline.
Deploy Ladill Hosting / deploy (push) Failing after 17s
Deploy Ladill Hosting / deploy (push) Failing after 17s
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>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user