*/ function deployment_check_extensions(array $required, ?callable $loader = null): array { $loader ??= static fn(string $extension): bool => extension_loaded($extension); $result = []; foreach ($required as $extension) { $result[(string)$extension] = (bool)$loader((string)$extension); } return $result; } /** * @return array */ function deployment_check_environment(array $required, ?callable $reader = null): array { $reader ??= static fn(string $name): mixed => getenv($name); $result = []; foreach ($required as $name) { $value = $reader((string)$name); $result[(string)$name] = $value !== false && $value !== null && trim((string)$value) !== ''; } return $result; } /** * @return array */ function deployment_check_directories(array $directories, ?callable $checker = null): array { $checker ??= static fn(string $directory): bool => is_dir($directory) && is_writable($directory); $result = []; foreach ($directories as $directory) { $result[(string)$directory] = (bool)$checker((string)$directory); } return $result; } /** * Return a deliberately value-free report suitable for CLI output or logging. * * @param array $checks * @return array */ function deployment_format_check_report(array $checks): array { $report = []; foreach ($checks as $name => $passed) { $report[$name] = $passed ? 'OK' : 'FAIL'; } return $report; } function deployment_check_schema(PDO $pdo): bool { foreach (['roles', 'permissions', 'role_permissions', 'users', 'clients', 'client_contacts', 'jobcard_sequences', 'technical_information', 'credentials', 'sla_agreements', 'jobcards', 'jobcard_assignments', 'jobcard_status_history', 'time_entries', 'attachments', 'notifications', 'audit_events'] as $table) { $quoted = '`' . str_replace('`', '``', $table) . '`'; $pdo->query("SELECT 1 FROM {$quoted} LIMIT 1"); } return true; } function deployment_print_check(string $label, bool $passed): void { printf("[%s] %s\n", $passed ? 'OK' : 'FAIL', $label); } function deployment_run_healthcheck(): int { $requiredExtensions = ['pdo_mysql', 'mbstring', 'openssl', 'sodium', 'json', 'fileinfo']; $requiredEnvironment = [ 'APP_ENV', 'APP_KEY', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'ADMIN_EMAIL', 'ADMIN_PASSWORD', ]; $runtimeDirectories = [ dirname(__DIR__) . '/storage', dirname(__DIR__) . '/storage/logs', dirname(__DIR__) . '/storage/uploads', ]; fwrite(STDOUT, "JOBcard deployment health check\n"); $allPassed = true; foreach (deployment_format_check_report(deployment_check_extensions($requiredExtensions)) as $extension => $status) { $passed = $status === 'OK'; deployment_print_check("PHP extension: {$extension}", $passed); $allPassed = $allPassed && $passed; } foreach (deployment_format_check_report(deployment_check_environment($requiredEnvironment)) as $name => $status) { $passed = $status === 'OK'; // Only the variable name and status are emitted; values are never printed. deployment_print_check("Environment variable: {$name}", $passed); $allPassed = $allPassed && $passed; } foreach (deployment_format_check_report(deployment_check_directories($runtimeDirectories)) as $directory => $status) { $passed = $status === 'OK'; deployment_print_check("Writable runtime directory: {$directory}", $passed); $allPassed = $allPassed && $passed; } try { require_once dirname(__DIR__) . '/app/Domain/Credential/TechnicalInformation.php'; require_once dirname(__DIR__) . '/app/Domain/Credential/CredentialVault.php'; new \App\Domain\Credential\CredentialVault((string)getenv('APP_KEY')); require_once dirname(__DIR__) . '/config/bootstrap.php'; deployment_check_schema(db()); deployment_print_check('Database connection and schema: core tables available', true); } catch (Throwable) { // Database exception text can contain infrastructure details; keep the check output safe. deployment_print_check('Database connection and schema: roles table available', false); $allPassed = false; } fwrite(STDOUT, $allPassed ? "Health check passed.\n" : "Health check failed.\n"); return $allPassed ? 0 : 1; } if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) { exit(deployment_run_healthcheck()); }