diff --git a/app/Http/Controllers/HealthController.php b/app/Http/Controllers/HealthController.php new file mode 100644 index 00000000..6b3eb1e3 --- /dev/null +++ b/app/Http/Controllers/HealthController.php @@ -0,0 +1,98 @@ +header('Content-Type', 'text/plain'); + } + + public function health(): JsonResponse + { + $checks = [ + 'database' => $this->checkDatabase(), + 'redis' => $this->checkRedis(), + 'disk' => $this->checkDisk(), + ]; + + $healthy = collect($checks)->every(fn ($check) => $check['status'] === 'ok'); + + return response()->json([ + 'status' => $healthy ? 'healthy' : 'unhealthy', + 'checks' => $checks, + 'timestamp' => now()->toIso8601String(), + ], $healthy ? 200 : 503); + } + + private function checkDatabase(): array + { + try { + DB::connection()->getPdo(); + DB::select('SELECT 1'); + + return ['status' => 'ok']; + } catch (\Exception $e) { + return [ + 'status' => 'error', + 'message' => $e->getMessage(), + ]; + } + } + + private function checkRedis(): array + { + try { + $response = Redis::ping(); + if ($response === true || $response == 'PONG' || (is_object($response) && method_exists($response, 'getPayload') && $response->getPayload() === 'PONG')) { + return ['status' => 'ok']; + } + + return [ + 'status' => 'error', + 'message' => 'Unexpected response from Redis', + ]; + } catch (\Exception $e) { + return [ + 'status' => 'error', + 'message' => $e->getMessage(), + ]; + } + } + + private function checkDisk(): array + { + try { + $path = storage_path(); + $freeBytes = disk_free_space($path); + $totalBytes = disk_total_space($path); + + if ($freeBytes === false || $totalBytes === false) { + return [ + 'status' => 'error', + 'message' => 'Unable to read disk space', + ]; + } + + $usedPercent = round((($totalBytes - $freeBytes) / $totalBytes) * 100, 1); + $status = $usedPercent > 95 ? 'error' : ($usedPercent > 85 ? 'warning' : 'ok'); + + return [ + 'status' => $status, + 'used_percent' => $usedPercent, + ]; + } catch (\Exception $e) { + return [ + 'status' => 'error', + 'message' => $e->getMessage(), + ]; + } + } +} diff --git a/routes/api.php b/routes/api.php index aa15423f..cab5f181 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,6 +22,7 @@ use App\Http\Controllers\AuthController; use App\Http\Controllers\EmailChangeController; use App\Http\Controllers\EmailVerificationController; +use App\Http\Controllers\HealthController; use App\Http\Controllers\InboxController; use App\Http\Controllers\InstanceActorController; use App\Http\Controllers\NodeInfoController; @@ -37,6 +38,10 @@ use App\Http\Middleware\FederationEnabled; use Illuminate\Support\Facades\Route; +// Health check endpoints +Route::get('/ping', [HealthController::class, 'ping'])->name('health.ping'); +Route::get('/health', [HealthController::class, 'health'])->name('health.check'); + // NodeInfo endpoints Route::group(['prefix' => 'nodeinfo'], function () { Route::get('2.0', [NodeInfoController::class, 'nodeInfo20'])