Files
Atomcms-edit/app/Console/Commands/SystemCheckCommand.php
T
2026-05-09 17:32:17 +02:00

5072 lines
180 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use App\Models\Miscellaneous\WebsiteSetting;
use App\Services\RconService;
use Database\Seeders\RadioContestSeeder;
use Database\Seeders\RadioGiveawaySeeder;
use Database\Seeders\RadioListenerPointSeeder;
use Database\Seeders\RadioSettingsSeeder;
use Database\Seeders\RadioSongRequestSeeder;
use Database\Seeders\RadioSongVoteSeeder;
use Database\Seeders\RadioTestSeeder;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Schema;
class SystemCheckCommand extends Command
{
#[\Override]
protected $signature = 'atom:check {--fix : Start the deep-repair interactive wizard} {--interactive : Alias for --fix} {--auto : Run all fixes automatically without asking} {--platform=auto : Force platform detection} {--lang=en : Set language (en, nl)} {--skip-migrations : Skip database migrations} {--skip-duplicates : Skip already ran migrations} {--skip-env : Skip .env validation} {--skip-build : Skip frontend asset building} {--fix-env : Fix .env file with proper formatting} {--setup : Run full setup (migrations, build, storage)}';
#[\Override]
protected $description = 'Ultimate Master Diagnostic & Auto-Repair Tool for Atom CMS';
/** @var array<mixed> */
private array $checks = [];
private int $errors = 0;
private bool $skipMigrations = false;
private bool $skipDuplicates = false;
private bool $skipBuild = false;
private bool $fixEnv = false;
private bool $setup = false;
private int $warnings = 0;
private string $platform = 'auto';
private string $webUser = 'www-data';
private string $webGroup = 'www-data';
private string $phpIniPath = '';
/** @var array<mixed> */
private array $missingSeeders = [];
public function handle(): int
{
if (! $this->option('skip-env')) {
try {
$envContent = file_get_contents(base_path('.env'));
if ($envContent !== false && ! str_contains($envContent, "\n") && strlen($envContent) > 500) {
$this->warn('⚠️ .env file appears to be on a single line. This will cause errors.');
$this->warn(' Please ensure .env has proper line breaks between each key-value pair.');
}
} catch (\Exception) {
// Ignore
}
}
$lang = $this->option('lang');
if (is_string($lang) && in_array($lang, ['en', 'nl'])) {
App::setLocale($lang);
}
$this->skipMigrations = (bool) $this->option('skip-migrations');
$this->skipDuplicates = (bool) $this->option('skip-duplicates');
$this->skipBuild = (bool) $this->option('skip-build');
$this->fixEnv = (bool) $this->option('fix-env');
$this->setup = (bool) $this->option('setup');
if ($this->fixEnv) {
$this->fixEnvFile();
return 0;
}
if ($this->setup) {
return $this->runFullSetup();
}
$platformOption = $this->option('platform');
$this->platform = $platformOption !== 'auto' && is_string($platformOption) ? $platformOption : $this->detectPlatform();
$this->detectWebUserContext();
$this->phpIniPath = php_ini_loaded_file() ?: 'Not found';
$this->info('╔════════════════════════════════════════════════════════════╗');
$this->info('║ ATOM CMS MASTER REPAIR COMMAND ║');
$this->info('╚════════════════════════════════════════════════════════════╝');
$this->info(' Platform: ' . strtoupper($this->platform));
if ($this->isWindows()) {
$this->info(' Stack : ' . ($this->isXampp() ? 'XAMPP' : ($this->isWamp() ? 'WAMP' : ($this->isNginx() ? 'NGINX' : 'IIS/Native'))));
} else {
$this->info(' Stack : ' . ($this->isNginx() ? 'NGINX' : 'APACHE'));
}
$this->info(' User : ' . $this->webUser . ' / ' . $this->webGroup);
$this->info(' PHP : ' . PHP_VERSION);
$this->newLine();
$this->runDiagnosticRoutine();
$this->displaySummary();
if ($this->option('fix') || $this->option('interactive')) {
return $this->runFixWizard();
}
return $this->errors > 0 ? 1 : 0;
}
/**
* @return array<string, array{class: string, table: string|null}>
*/
private function discoverAllSeeders(): array
{
$seedersPath = database_path('seeders');
$seeders = [];
if (! File::exists($seedersPath)) {
return $seeders;
}
$files = File::files($seedersPath);
foreach ($files as $file) {
$filename = $file->getFilenameWithoutExtension();
if ($filename === 'DatabaseSeeder' || str_ends_with($filename, 'Base')) {
continue;
}
$className = "Database\\Seeders\\{$filename}";
if (! class_exists($className)) {
continue;
}
$reflection = new \ReflectionClass($className);
if (! $reflection->isInstantiable()) {
continue;
}
$seeders[$filename] = [
'class' => $className,
'table' => $this->detectSeederTable($className),
];
}
return $seeders;
}
private function detectSeederTable(string $className): string
{
$seederName = str_replace('Seeder', '', class_basename($className));
$tableMappings = [
'WebsiteSettings' => 'website_settings',
'WebsiteLanguage' => 'website_languages',
'WebsitePermission' => 'permissions',
'WebsiteWordfilter' => 'website_wordfilter',
'WebsiteMaintenanceTasks' => 'website_maintenance_tasks',
'WebsiteArticle' => 'website_articles',
'WebsiteTeam' => 'website_teams',
'WebsiteShopCategories' => 'website_shop_categories',
'WebsiteShopArticle' => 'website_shop_articles',
'WebsiteRuleCategory' => 'website_rule_categories',
'WebsiteRule' => 'website_rules',
'WebsiteHelperCenterCategory' => 'website_help_center_categories',
'WebsiteRareValuesCategory' => 'website_rare_value_categories',
'HousekeepingPermission' => 'website_housekeeping_permissions',
'RadioSettings' => 'radio_settings',
'RadioSongVote' => 'radio_song_votes',
'RadioContest' => 'radio_contests',
'RadioSongRequest' => 'radio_song_requests',
'RadioGiveaway' => 'radio_giveaways',
'RadioListenerPoint' => 'radio_listener_points',
'RadioTest' => 'radio_tests',
'BaseSettings' => 'base_settings',
'WebsiteArticleFeature' => 'website_shop_article_features',
];
if (isset($tableMappings[$seederName])) {
return $tableMappings[$seederName];
}
$modelName = str_replace('Seeder', '', class_basename($className));
$modelClass = 'App\\Models\\' . $modelName;
if (class_exists($modelClass)) {
/** @var Model $model */
$model = new $modelClass;
return $model->getTable();
}
$snakeCase = strtolower((string) preg_replace('/(?<!^)[A-Z]/', '_$0', $seederName));
return $snakeCase . 's';
}
/**
* @return array<string, list<string>>
*/
private function discoverAllTables(): array
{
$migrationsPath = database_path('migrations');
$tables = [];
if (! File::exists($migrationsPath)) {
return $tables;
}
$files = File::files($migrationsPath);
$validMethods = ['bigInteger', 'binary', 'boolean', 'char', 'date', 'datetime', 'decimal', 'double', 'enum', 'float', 'foreignId', 'foreignIdFor', 'foreignUlid', 'foreignUuid', 'geometry', 'geometryCollection', 'id', 'integer', 'json', 'jsonb', 'lineString', 'longText', 'macAddress', 'mediumInteger', 'mediumText', 'morphs', 'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken', 'set', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string', 'text', 'time', 'timestamp', 'timestamps', 'timestampsTz', 'tinyInteger', 'tinyText', 'unsignedBigInteger', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger', 'unsignedTinyInteger', 'uuid', 'year'];
$specialMethods = [
'morphs' => ['_id', '_type'],
'nullableMorphs' => ['_id', '_type'],
'nullableUuidMorphs' => ['_id', '_type'],
'timestamps' => ['created_at', 'updated_at'],
'nullableTimestamps' => ['created_at', 'updated_at'],
'softDeletes' => ['deleted_at'],
'softDeletesTz' => ['deleted_at'],
'rememberToken' => ['remember_token'],
];
foreach ($files as $file) {
$content = File::get($file->getPathname());
if (preg_match('/Schema::create\([\'"](\w+)[\'"]/', $content, $matches)) {
$tableName = $matches[1];
$tables[$tableName] = [];
preg_match_all('/\$table->(\w+)\([\'"]([^\'"]+)[\'"]\)/', $content, $matches2);
foreach ($matches2[1] as $index => $method) {
if (in_array($method, $validMethods)) {
$columnName = $matches2[2][$index];
if (! in_array($columnName, $tables[$tableName])) {
$tables[$tableName][] = $columnName;
}
} elseif (isset($specialMethods[$method])) {
foreach ($specialMethods[$method] as $col) {
if (! in_array($col, $tables[$tableName])) {
$tables[$tableName][] = $col;
}
}
}
}
preg_match_all('/\$table->(\w+)\(([^\)]+)\)/', $content, $matches3);
foreach ($matches3[1] as $index => $method) {
if (isset($specialMethods[$method])) {
$paramName = trim($matches3[2][$index], "'\"");
foreach ($specialMethods[$method] as $suffix) {
$col = $paramName . $suffix;
if (! in_array($col, $tables[$tableName])) {
$tables[$tableName][] = $col;
}
}
}
}
if (preg_match_all('/\$table->id\(\)/', $content) && ! in_array('id', $tables[$tableName])) {
$tables[$tableName][] = 'id';
}
}
if (preg_match_all('/Schema::table\([\'"](\w+)[\'"]/', $content, $tableMatches)) {
foreach ($tableMatches[1] as $tableName) {
if (! isset($tables[$tableName])) {
$tables[$tableName] = [];
}
preg_match_all('/\$table->(\w+)\([\'"]([^\'"]+)[\'"]\)/', $content, $matches2);
foreach ($matches2[1] as $index => $method) {
if (in_array($method, $validMethods)) {
$columnName = $matches2[2][$index];
if (! in_array($columnName, $tables[$tableName])) {
$tables[$tableName][] = $columnName;
}
} elseif (isset($specialMethods[$method])) {
foreach ($specialMethods[$method] as $col) {
if (! in_array($col, $tables[$tableName])) {
$tables[$tableName][] = $col;
}
}
}
}
if (preg_match_all('/\$table->id\(\)/', $content) && ! in_array('id', $tables[$tableName])) {
$tables[$tableName][] = 'id';
}
}
}
}
$coreTables = [
'users' => ['id', 'username', 'mail', 'password', 'rank', 'look', 'motto'],
];
return array_merge($coreTables, $tables);
}
private function checkAllDatabaseTables(): void
{
try {
$discoveredTables = array_keys($this->discoverAllTables());
$dbTables = DB::connection()->select('SHOW TABLES');
$dbTables = array_map(function ($t) {
if (is_array($t)) {
return reset($t);
}
if (is_object($t)) {
return (array) $t;
}
}, $dbTables);
// Flatten if objects were converted to arrays
$flatTables = [];
foreach ($dbTables as $t) {
$flatTables[] = is_array($t) ? reset($t) : $t;
}
$dbTables = array_filter($flatTables);
$missingTables = array_diff($discoveredTables, $dbTables);
$optionalTables = [
'users_session_logs',
'radio_settings',
'radio_djs',
'radio_requests',
'radio_song_votes',
'radio_contests',
'radio_giveaways',
'radio_listener_points',
'radio_song_requests',
'radio_ranks',
'radio_shouts',
'radio_schedules',
'radio_banners',
'radio_history',
'radio_applications',
];
foreach ($missingTables as $table) {
if (in_array($table, $optionalTables)) {
continue;
}
$this->addCheck("Missing Table: {$table}", '⚠️', 'Will be auto-created');
}
} catch (\Exception $e) {
$this->addCheck('Database Tables Check', '⚠️', 'Could not check: ' . $e->getMessage());
}
}
private function runDiagnosticRoutine(): void
{
$this->comment('--- [1/7] Environment & Security ---');
$this->checkEnvFile();
$this->checkAppKey();
$this->checkDebugMode();
$this->checkComposerSecurity();
$this->checkRequiredFiles();
$this->checkStorageSymlink();
$discoveredTables = $this->discoverAllTables();
$discoveredSeeders = $this->discoverAllSeeders();
$this->comment("\n--- [2/7] Database ---");
$this->checkDatabaseConnection();
$this->checkDeepDatabaseSchema($discoveredTables);
$this->checkMigrationsStatus();
$this->checkSeedersStatus($discoveredSeeders);
$this->checkAllDatabaseTables();
$this->checkRequiredSettingsData();
$this->checkRadioTables();
$this->checkAdminUser();
$this->comment("\n--- [3/7] PHP Stack ---");
$this->checkPHPExtensions();
$this->checkPHPConfiguration();
$this->checkForPhpUpdates();
$this->checkCacheOptimization();
$this->checkSessionConfiguration();
$this->checkFilamentPages();
$this->comment("\n--- [4/7] Web Server ---");
$this->checkWebServerConfiguration();
$this->checkSSLCertificates();
$this->comment("\n--- [5/7] System ---");
$this->checkFilePermissions();
$this->checkFirewallPorts();
$this->comment("\n--- [6/7] Assets ---");
$this->checkAssetsStatus();
$this->checkRedisConnection();
$this->checkCronJobsStatus();
$this->checkQueueWorkersStatus();
$this->checkSupervisorConfig();
$this->checkFrontendManifest();
$this->comment("\n--- [7/7] HTTP Errors ---");
$this->checkHttpErrors();
}
private function runFixWizard(): int
{
$auto = (bool) $this->option('auto');
$this->info("\n" . str_repeat('=', 65));
$this->info(' INTERACTIVE MASTER FIX WIZARD ');
$this->info(str_repeat('=', 65));
$fixed = 0;
if ($auto) {
$this->info('🔧 Running in AUTO-FIX mode - fixing EVERYTHING...');
return $this->fixEverything($auto);
}
// Security Audit Fix
if ($this->hasError('Security Audit')) {
$confirm = $auto ? true : $this->confirm('🛡️ Security vulnerabilities found in dependencies. Run update?', true);
if ($confirm === true) {
$this->info('Running composer update...');
$result = $this->runSystemCommandWithRetry('composer update', 2, 1000);
if ($result['success']) {
$this->info('✅ Dependencies updated.');
$fixed++;
} else {
$this->error('❌ Update failed: ' . ($result['output'] ?: 'Unknown error'));
}
}
}
// SSL/HTTPS Fix
if ($this->hasError('SSL Status') || $this->hasError('Mixed HTTPS')) {
$confirm = $auto ? true : $this->confirm('🔒 SSL/HTTPS issues found. Fix automatically?', true);
if ($confirm === true) {
$this->fixHttpsConfiguration();
$this->info('✅ HTTPS configuration fixed.');
$fixed++;
}
}
if ($this->hasError('Environment File') || $this->hasError('Application Key')) {
$confirm = $auto ? true : $this->confirm('⚠️ Fix critical environment issues?', true);
if ($confirm === true) {
if (! File::exists(base_path('.env'))) {
File::copy(base_path('.env.example'), base_path('.env'));
}
$this->call('key:generate', ['--force' => true]);
$this->info('✅ Environment initialized.');
$fixed++;
}
}
if ($this->hasError('Table:') || $this->hasError('Column:') || $this->hasError('Missing Table:')) {
$confirm = $auto ? true : $this->confirm('💾 Create database backup before applying fixes?', true);
if ($confirm === true) {
if ($this->confirm('💾 Backup database?', true) === true) {
$this->backupDatabase();
}
$this->info('🗄️ Running migrations...');
if ($this->skipMigrations) {
$this->info('⏭️ Migrations skipped (--skip-migrations)');
} else {
if ($this->isWindows()) {
$this->addMysqlToPath();
}
if ($this->skipDuplicates) {
$this->runMigrationsWithSkip();
} else {
$this->call('migrate', ['--force' => true]);
}
$this->info('✅ Migrations complete.');
}
$fixed++;
}
}
if ($this->missingSeeders !== []) {
$confirm = $auto ? true : $this->confirm('🌱 ' . count($this->missingSeeders) . ' seeders missing data. Run seeders?', true);
if ($confirm) {
$this->info('🌱 Running ALL seeders...');
$this->call('db:seed', ['--force' => true]);
$this->info('✅ All seeders complete.');
$fixed++;
}
}
if ($this->hasError('Admin User')) {
$confirm = $auto ? true : $this->confirm('👤 No Admin found. Create one?', true);
if ($confirm) {
if ($auto) {
// Auto-create admin with default credentials
$this->createAdminUser('Admin', 'admin@atom.local', 'admin123');
} else {
$username = (string) $this->ask('Enter Admin Username', 'Admin');
$email = (string) $this->ask('Enter Admin Email', 'admin@example.com');
$password = (string) $this->secret('Enter Admin Password');
$this->createAdminUser($username, $email, $password);
}
$fixed++;
}
}
if ($this->isApache() && ($this->hasError('Apache Rewrite') || $this->hasError('Index.php'))) {
$confirm = $auto ? true : $this->confirm('🌐 Repair Apache configuration?', true);
if ($confirm) {
$this->repairApache();
$this->info('✅ Apache rules fixed.');
$fixed++;
}
} elseif ($this->isIIS() && $this->hasError('IIS Routing')) {
$confirm = $auto ? true : $this->confirm('🌐 Create web.config for IIS?', true);
if ($confirm) {
$this->repairIIS();
$this->info('✅ IIS config created.');
$fixed++;
}
} elseif ($this->isNginx()) {
$confirm = $auto ? true : $this->confirm('🌐 Generate Nginx config template?', true);
if ($confirm) {
$this->repairNginx();
$fixed++;
}
}
if ($this->isLinux() && ($this->hasError('Writable:') || $this->hasError('Permissions:'))) {
$confirm = $auto ? true : $this->confirm('🔐 Fix Linux file permissions?', true);
if ($confirm) {
$this->repairPermissions();
$this->info('✅ Permissions applied.');
$fixed++;
}
}
if ($this->hasWarning('PHP.ini:')) {
$confirm = $auto ? true : $this->confirm('⚙️ Tune PHP performance limits?', true);
if ($confirm && $this->repairPHPConfig()) {
$this->info('✅ PHP.ini optimized.');
$fixed++;
}
}
if ($this->hasError('PHP Ext:') && $this->isLinux()) {
$confirm = $auto ? true : $this->confirm('📦 Install missing extensions?', true);
if ($confirm) {
$count = $this->repairPHPExtensions();
$this->info("✅ Installed {$count} extensions.");
$fixed += $count;
}
}
if ($this->hasError('Storage Link')) {
$confirm = $auto ? true : $this->confirm('🔗 Fix storage symlink?', true);
if ($confirm) {
if (File::exists(public_path('storage'))) {
@unlink(public_path('storage'));
}
$this->call('storage:link');
$this->info('✅ Storage restored.');
$fixed++;
}
}
if ($this->hasError('Frontend Assets') || $this->hasError('Vite Manifest')) {
$confirm = $auto ? true : $this->confirm('🎨 Build frontend assets?', true);
if ($confirm) {
$this->buildFrontendAssets();
$fixed++;
}
}
if ($this->isLinux() && $this->hasWarning('Supervisor')) {
$confirm = $auto ? true : $this->confirm('🐘 Generate Supervisor config?', true);
if ($confirm) {
$this->generateSupervisorConfig();
$fixed++;
}
}
if ($this->hasError('Redis') && $this->isLinux()) {
$confirm = $auto ? true : $this->confirm('🟦 Install Redis Server?', true);
if ($confirm) {
$this->installRedisIfNeeded($fixed);
}
}
// Known common errors - auto fix
$this->fixKnownCommonErrors();
// Fix HTTP errors
$this->fixHttpErrors($auto);
// Final verification - re-check everything
$this->newLine();
$this->info('🔍 Running final verification...');
$this->newLine();
$this->errors = 0;
$this->warnings = 0;
$this->checks = [];
$this->runDiagnosticRoutine();
$this->newLine();
$this->info('════════════════════════════════════════════════════════════');
$this->info(' FINAL VERIFICATION ');
$this->info('════════════════════════════════════════════════════════════');
$this->displaySummary();
if ($this->errors === 0 && $this->warnings === 0) {
$this->info('🎉 CMS is 100% healthy!');
} elseif ($this->errors === 0) {
$this->warn('⚠️ CMS is operational with ' . $this->warnings . ' warnings');
} else {
$this->error('❌ CMS has ' . $this->errors . ' remaining issues');
$this->info('Run atom:check --auto again to attempt fixes');
}
// Final optimization
if ($this->confirm('🚀 Run final optimizations (config:cache, routes:cache)?', false)) {
$this->call('config:cache');
$this->call('route:cache');
$this->info('✅ Optimization complete.');
$fixed++;
}
$this->newLine();
$this->info("🎉 Wizard complete. Fixed: {$fixed} items.");
return 0;
}
// --- CHECKS ---
private function checkComposerSecurity(): void
{
if (file_exists(base_path('composer.lock'))) {
$result = $this->runSystemCommand('composer audit');
if ($result['success']) {
$this->addCheck('Security Audit', '✅', 'No vulnerabilities found');
} else {
$count = 0;
foreach (explode("\n", (string) $result['output']) as $line) {
if (str_contains($line, 'Package:')) {
$count++;
}
}
$msg = $count > 0 ? "{$count} Vulnerabilities found!" : 'Audit failed / Issues found';
$this->addCheck('Security Audit', '❌', $msg);
$this->errors++;
}
} else {
$this->addCheck('Security Audit', '⚠️', 'composer.lock missing');
}
}
private function checkEnvFile(): void
{
$ok = File::exists(base_path('.env'));
$this->addCheck('Environment File', $ok ? '✅' : '❌', $ok ? 'OK' : 'Missing');
}
private function checkAppKey(): void
{
$key = config('app.key');
$this->addCheck('Application Key', empty($key) ? '❌' : '✅', empty($key) ? 'Missing' : 'Set');
}
private function checkDebugMode(): void
{
$debug = config('app.debug');
$this->addCheck('Debug Mode', $debug ? '⚠️' : '✅', $debug ? 'Enabled' : 'Disabled');
}
private function checkDatabaseConnection(): void
{
try {
DB::connection()->getPdo();
$this->addCheck('Database Connection', '✅', 'Connected');
} catch (\Exception) {
$this->addCheck('Database Connection', '❌', 'Failed');
}
}
private function checkDeepDatabaseSchema(array $discoveredTables = []): void
{
$tablesToCheck = $discoveredTables;
$coreTablesFromSql = ['users', 'rooms', 'items', 'bans', 'chatlogs', 'photos', 'hotel_users'];
$skipColumnCheck = [
'users_session_logs',
'personal_access_tokens',
'website_ip_whitelist',
'website_ip_blacklist',
'website_permissions',
'taggables',
'cache',
'radio_ranks',
'radio_contests',
'radio_giveaways',
'website_blocked_countries',
'website_shop_articles',
'website_shop_article_features',
];
foreach ($tablesToCheck as $table => $columns) {
if (! Schema::hasTable($table)) {
if (! in_array($table, $skipColumnCheck)) {
$this->addCheck("Table: {$table}", '❌', 'Table Missing');
}
continue;
}
if (empty($columns)) {
$this->addCheck("Table: {$table}", '✅', 'Exists');
continue;
}
if (in_array($table, $coreTablesFromSql)) {
$this->addCheck("Table: {$table}", '✅', 'Exists (SQL)');
continue;
}
if (in_array($table, $skipColumnCheck)) {
$this->addCheck("Table: {$table}", '✅', 'OK (skipped)');
continue;
}
$missingCols = [];
foreach ($columns as $col) {
if (! Schema::hasColumn($table, $col)) {
$missingCols[] = $col;
}
}
if ($missingCols !== []) {
$this->addCheck("Column: {$table}", '❌', 'Missing cols: ' . implode(', ', $missingCols));
$this->errors++;
} else {
$this->addCheck("Table: {$table}", '✅', 'OK');
}
}
}
private function checkRequiredSettingsData(): void
{
if (! Schema::hasTable('website_settings')) {
return;
}
$requiredKeys = ['hotel_name', 'rcon_ip', 'rcon_port'];
foreach ($requiredKeys as $key) {
$exists = DB::table('website_settings')->where('key', $key)->exists();
$this->addCheck("Setting Data: {$key}", $exists ? '✅' : '⚠️', $exists ? 'Found' : 'Missing Row');
if (! $exists) {
$this->warnings++;
}
}
}
private function checkMigrationsStatus(): void
{
try {
if (! Schema::hasTable('migrations')) {
$this->addCheck('Migrations Table', '❌', 'Missing');
return;
}
$ran = DB::table('migrations')->pluck('migration')->toArray();
$files = File::files(base_path('database/migrations'));
$pending = 0;
foreach ($files as $f) {
$name = str_replace('.php', '', $f->getFilename());
if (! in_array($name, $ran)) {
$pending++;
}
}
$this->addCheck('Migrations Status', $pending === 0 ? '✅' : '❌', $pending === 0 ? 'Up to date' : "{$pending} pending");
if ($pending > 0) {
$this->errors++;
}
} catch (\Exception $e) {
$this->addCheck('Migrations Status', '❌', 'Failed: ' . $e->getMessage());
$this->errors++;
}
}
private function checkSeedersStatus(array $discoveredSeeders = []): void
{
if ($discoveredSeeders === []) {
$discoveredSeeders = $this->discoverAllSeeders();
}
$missingSeeders = [];
$optionalTables = [
'radio_settings',
'radio_song_votes',
'radio_contests',
'radio_song_requests',
'radio_giveaways',
'radio_listener_points',
'radio_tests',
'testings',
];
foreach ($discoveredSeeders as $seeder => $config) {
$table = $config['table'];
if (empty($table)) {
continue;
}
if (! Schema::hasTable($table)) {
if (in_array($table, $optionalTables)) {
$this->addCheck("Seeder: {$seeder}", '✅', 'Optional (skip)');
} else {
$this->addCheck("Seeder: {$seeder}", '❌', "Table {$table} missing");
$missingSeeders[] = $config['class'];
$this->errors++;
}
continue;
}
$count = DB::table($table)->count();
if ($count > 0) {
if ($table === 'website_articles') {
$this->addCheck("Seeder: {$seeder}", '✅', 'Er is al nieuws gevonden, ga verder');
} else {
$this->addCheck("Seeder: {$seeder}", '✅', "{$count} rows");
}
} elseif (in_array($table, $optionalTables)) {
$this->addCheck("Seeder: {$seeder}", '✅', 'Optional (skip)');
} else {
$this->addCheck("Seeder: {$seeder}", '⚠️', 'No data');
$this->warnings++;
$missingSeeders[] = $config['class'];
}
}
$this->missingSeeders = $missingSeeders;
}
private function checkRequiredTables(): void
{
$tables = ['users', 'website_settings', 'permissions', 'articles', 'radio_settings', 'categories', 'rooms', 'photos', 'hotel_users', 'bans', 'items'];
foreach ($tables as $t) {
if (! Schema::hasTable($t)) {
$this->addCheck("Table: {$t}", '❌', 'Missing');
}
}
}
private function checkRequiredSettings(): void
{
if (! Schema::hasTable('website_settings')) {
return;
}
$keys = ['hotel_name', 'hotel_url', 'rcon_ip', 'recaptcha_site_key'];
foreach ($keys as $k) {
$exists = DB::table('website_settings')->where('key', $k)->exists();
$this->addCheck("Setting: {$k}", $exists ? '✅' : '⚠️', $exists ? 'Set' : 'Missing');
}
}
private function checkRadioTables(): void
{
$tables = ['radio_song_votes', 'radio_contests', 'radio_giveaways', 'radio_listener_points', 'radio_song_requests', 'radio_ranks', 'radio_shouts', 'radio_schedules', 'radio_banners', 'radio_history', 'radio_applications'];
foreach ($tables as $t) {
if (! Schema::hasTable($t)) {
$this->addCheck("Table: {$t}", '⚠️', 'Missing');
}
}
}
private function checkAdminUser(): void
{
if (! Schema::hasTable('users')) {
return;
}
try {
$hasAdmin = DB::table('users')->where('rank', '>=', 7)->exists();
$this->addCheck('Admin User', $hasAdmin ? '✅' : '❌', $hasAdmin ? 'Found' : 'Missing');
if (! $hasAdmin) {
$this->errors++;
}
} catch (\Exception) {
$this->addCheck('Admin User', '❌', 'Check Failed');
}
}
private function checkPHPExtensions(): void
{
$exts = ['curl', 'mbstring', 'pdo_mysql', 'xml', 'bcmath', 'openssl', 'gd', 'zip', 'intl', 'redis', 'fileinfo'];
foreach ($exts as $e) {
$ok = extension_loaded($e);
$this->addCheck("PHP Ext: {$e}", $ok ? '✅' : '❌', $ok ? 'OK' : 'Missing');
}
}
private function checkPHPConfiguration(): void
{
$limits = ['memory_limit' => 256, 'upload_max_filesize' => 32, 'post_max_size' => 32];
foreach ($limits as $key => $target) {
$val = ini_get($key);
$ok = ((int) $val >= $target || $val == -1);
$this->addCheck("PHP.ini: {$key}", $ok ? '✅' : '⚠️', "Val: {$val}");
}
}
private function checkForPhpUpdates(): void
{
$this->addCheck('PHP Version', '✅', PHP_VERSION);
}
private function checkCacheOptimization(): void
{
$ok = File::exists(base_path('bootstrap/cache/config.php'));
$this->addCheck('Config Cache', $ok ? '✅' : '⚠️', $ok ? 'Optimized' : 'Not cached');
}
private function checkSessionConfiguration(): void
{
$driver = config('session.driver');
$this->addCheck('Session Driver', $driver !== 'file' ? '✅' : '⚠️', $driver);
}
private function checkFilamentPages(): void
{
$files = [
'app/Filament/Pages/Monitoring/AlertSettings.php' => 'AlertSettings page',
'resources/views/filament/pages/monitoring/alert-settings.blade.php' => 'AlertSettings view',
'app/Filament/Widgets/UpdateCheckerWidget.php' => 'UpdateCheckerWidget',
'resources/views/filament/widgets/update-checker.blade.php' => 'UpdateChecker view',
'app/Services/EmulatorUpdateService.php' => 'EmulatorUpdateService',
'app/Services/NitroUpdateService.php' => 'NitroUpdateService',
'app/Services/RconService.php' => 'RconService',
];
$missing = [];
foreach ($files as $path => $label) {
if (! file_exists(base_path($path))) {
$missing[] = $label;
}
}
if ($missing === []) {
$this->addCheck('Filament Pages & Widgets', '✅', 'All files present');
} else {
$this->addCheck('Filament Pages & Widgets', '❌', 'Missing: ' . implode(', ', $missing));
$this->errors++;
}
// Check emulator service
$serviceName = setting('emulator_service_name', 'emulator');
$result = $this->runSystemCommand("systemctl is-active {$serviceName} 2>/dev/null || echo 'inactive'");
if (trim((string) $result['output']) === 'active') {
$this->addCheck('Emulator Service', '✅', $serviceName . ' is running');
} else {
$this->addCheck('Emulator Service', '⚠️', $serviceName . ' is not running');
$this->warnings++;
}
// Check emulator JAR
$jarPath = setting('emulator_jar_path', '/var/www/Emulator');
$jarFiles = glob("{$jarPath}/*.jar");
if ($jarFiles !== [] && $jarFiles !== false) {
$this->addCheck('Emulator JAR', '✅', basename($jarFiles[0]));
} else {
$this->addCheck('Emulator JAR', '⚠️', 'No JAR found in ' . $jarPath);
$this->warnings++;
}
}
private function checkWebServerConfiguration(): void
{
// Detect Cloudflare
$this->checkCloudflare();
// Detect NPM (Nginx Proxy Manager)
$this->checkNpmProxy();
if ($this->isApache()) {
$path = public_path('.htaccess');
$ok = File::exists($path) && str_contains(File::get($path), 'RewriteEngine On');
$this->addCheck('Apache Rewrite', $ok ? '✅' : '⚠️', $ok ? 'OK' : 'Check .htaccess');
}
if ($this->isIIS()) {
$ok = File::exists(public_path('web.config'));
$this->addCheck('IIS Routing', $ok ? '✅' : '❌', $ok ? 'OK' : 'Missing web.config');
}
if ($this->isNginx()) {
$this->addCheck('Nginx Config', '⚠️', 'Check try_files manually');
}
}
private function checkCloudflare(): void
{
$headers = [
'HTTP_CF_CONNECTING_IP',
'HTTP_CF_IPCOUNTRY',
'HTTP_CF_RAY',
'HTTP_CF_VISITOR',
'HTTP_CF_REQUEST_ID',
];
$detected = false;
$foundHeaders = [];
// Check HTTP headers (when running via web)
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$detected = true;
$foundHeaders[] = $header;
}
}
$serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? '';
if (str_contains((string) $serverSoftware, 'cloudflare')) {
$detected = true;
$foundHeaders[] = 'SERVER: cloudflare';
}
// Check via DNS if not detected via headers (CLI mode)
if (! $detected) {
$siteUrl = setting('site_url', config('app.url', ''));
if (! empty($siteUrl)) {
$domain = parse_url((string) $siteUrl, PHP_URL_HOST);
if (! empty($domain)) {
// Check nameservers
$nsResult = $this->runSystemCommand("dig NS {$domain} +short 2>/dev/null | grep -i cloudflare");
if (! in_array(trim((string) $nsResult['output']), ['', '0'], true)) {
$detected = true;
$foundHeaders[] = 'NS: cloudflare';
}
// Check if proxied (CF IP range)
if (! $detected) {
$ipResult = $this->runSystemCommand("dig {$domain} +short 2>/dev/null | head -1");
$ip = trim($ipResult['output'] ?? '');
if ($ip !== '' && $ip !== '0') {
// Cloudflare IP ranges (simplified check)
$cfRanges = ['104.16.', '104.17.', '104.18.', '104.19.', '104.20.', '104.21.', '104.22.', '104.23.', '104.24.', '104.25.', '104.26.', '104.27.', '104.28.', '104.29.', '104.30.', '104.31.', '172.64.', '172.65.', '172.66.', '172.67.', '172.68.', '172.69.', '172.70.', '172.71.', '103.21.244.', '103.22.200.', '103.31.4.', '141.101.64.', '108.162.192.', '190.93.240.', '188.114.96.', '197.234.240.', '198.41.128.', '162.158.', '131.0.72.'];
foreach ($cfRanges as $range) {
if (str_starts_with($ip, $range)) {
$detected = true;
$foundHeaders[] = 'IP: Cloudflare range';
break;
}
}
}
}
}
}
}
if ($detected) {
$this->addCheck('Cloudflare', '✅', 'Active (' . count($foundHeaders) . ' headers)');
} else {
$this->addCheck('Cloudflare', '⚠️', 'Not detected (CLI mode)');
}
// Check custom port
$this->checkCustomPort();
}
private function checkCustomPort(): void
{
$port = (int) ($_SERVER['SERVER_PORT'] ?? 80);
$host = (string) ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '');
// Extract port from host header
if (preg_match('/:(\d+)$/', $host, $matches)) {
$port = (int) $matches[1];
}
$customPorts = [8080, 3000, 81, 8000, 8888, 4433, 8443];
if ($port === 80 || $port === 443) {
$this->addCheck('Server Port', '✅', 'Standard (' . $port . ')');
} elseif (in_array($port, $customPorts)) {
$this->addCheck('Server Port', '⚠️', 'Custom port: ' . $port . ' (check proxy)');
} else {
$this->addCheck('Server Port', '⚠️', 'Non-standard port: ' . $port);
}
// Check if behind proxy
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) || isset($_SERVER['HTTP_X_REAL_IP'])) {
$this->addCheck('Behind Proxy', '✅', 'Detected (check trust proxies)');
} else {
$this->addCheck('Behind Proxy', '✅', 'Not required');
}
// Check WebSocket
$this->checkWebSocket();
// Check Client Settings
$this->checkClientSettings();
}
private function checkWebSocket(): void
{
$wsPort = (int) config('habbo.websocket.port', env('WEBSOCKET_PORT', 3001));
$wsHost = (string) config('habbo.websocket.host', env('WEBSOCKET_HOST', '127.0.0.1'));
$wsEnabled = (bool) config('habbo.websocket.enabled', env('WEBSOCKET_ENABLED', false));
if ($wsEnabled) {
$this->addCheck('WebSocket', '✅', "Enabled ({$wsHost}:{$wsPort})");
// Check if port is open
$socket = @fsockopen($wsHost, $wsPort, $errno, $errstr, 2);
if ($socket) {
$this->addCheck('WebSocket Port', '✅', "Port {$wsPort} is open");
fclose($socket);
} else {
$this->addCheck('WebSocket Port', '❌', "Port {$wsPort} is closed - check emulator");
}
} else {
$this->addCheck('WebSocket', '✅', 'Not required');
}
// Check Nitro/Client WebSocket
$nitroWsUrl = config('habbo.client.nitro_websocket_url');
if (! empty($nitroWsUrl) && is_string($nitroWsUrl)) {
$this->addCheck('Nitro WS URL', '✅', $nitroWsUrl);
} else {
$this->addCheck('Nitro WS URL', '✅', 'Not required');
}
}
private function checkClientSettings(): void
{
// Check client SWF path
$flashEnabled = (bool) config('habbo.client.flash_enabled', env('FLASH_CLIENT_ENABLED', false));
if ($flashEnabled) {
$swfPath = config('habbo.flash.habbo_swf', 'Habbo.swf');
$swfBasePath = config('habbo.flash.swf_base_path');
if (! empty($swfBasePath) && is_string($swfBasePath)) {
$this->addCheck('Flash SWF', '✅', $swfBasePath . '/' . $swfPath);
} else {
$this->addCheck('Flash SWF', '⚠️', 'SWF path not configured');
}
}
// Check Nitro client path
$nitroPath = config('habbo.client.nitro_path', '/client/html5/nitro-client');
$nitroFullPath = public_path($nitroPath);
if (is_dir($nitroFullPath)) {
$this->addCheck('Nitro Client', '✅', $nitroPath . ' exists');
} else {
$this->addCheck('Nitro Client', '⚠️', $nitroPath . ' not found');
}
// Check emulator connection
$emulatorIp = (string) config('habbo.flash.host', env('EMULATOR_IP', '127.0.0.1'));
$emulatorPort = (int) config('habbo.flash.port', env('EMULATOR_PORT', 3000));
$socket = @fsockopen($emulatorIp, $emulatorPort, $errno, $errstr, 2);
if ($socket) {
$this->addCheck('Emulator', '✅', "{$emulatorIp}:{$emulatorPort} online");
fclose($socket);
} else {
$this->addCheck('Emulator', '❌', "{$emulatorIp}:{$emulatorPort} offline");
}
// Check external texts/variables
$externalTexts = config('habbo.flash.external_texts');
$externalVars = config('habbo.flash.external_variables');
if (! empty($externalTexts) && is_string($externalTexts)) {
$this->addCheck('External Texts', '✅', 'Configured');
} else {
$this->addCheck('External Texts', '✅', 'Not required');
}
if (! empty($externalVars) && is_string($externalVars)) {
$this->addCheck('External Variables', '✅', 'Configured');
} else {
$this->addCheck('External Variables', '✅', 'Not required');
}
// Check client URL in database
try {
$clientUrl = DB::table('website_settings')->where('key', 'client_url')->first();
if ($clientUrl && isset($clientUrl->value)) {
$this->addCheck('Client URL Setting', '✅', (string) $clientUrl->value);
} else {
$this->addCheck('Client URL Setting', '⚠️', 'Not set in database');
}
} catch (\Exception) {
// Ignore
}
}
private function checkNpmProxy(): void
{
$indicators = [];
// Check for NPM default ports
$host = (string) ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '');
if (preg_match('/(:81|:443|:80$)/', $host) && str_contains($host, ':81')) {
$indicators[] = 'Port 81 (NPM admin)';
}
// Check SERVER headers for NPM
$server = (string) ($_SERVER['SERVER_SOFTWARE'] ?? '');
if ((str_contains($server, 'nginx') || str_contains($server, 'NPM')) && (! str_contains($server, 'Apache') && ! str_contains($server, 'cloudflare'))) {
$indicators[] = 'Nginx detected';
}
// Check for NPM proxy headers
$proxyHeaders = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_PROTO',
'HTTP_X_REAL_IP',
'HTTP_X_NPM',
];
foreach ($proxyHeaders as $header) {
if (isset($_SERVER[$header])) {
$indicators[] = $header;
}
}
// Check response headers if available
if ($indicators !== []) {
$this->addCheck('NPM Proxy', '✅', 'Detected - ' . implode(', ', $indicators));
} else {
$this->addCheck('NPM Proxy', '✅', 'Not required');
}
}
private function checkSSLCertificates(): void
{
$appUrl = config('app.url');
$isHttps = str_starts_with((string) $appUrl, 'https');
$this->addCheck('SSL Status', $isHttps ? '✅' : '⚠️', $isHttps ? 'HTTPS' : 'HTTP');
if (! $isHttps) {
$this->errors++;
}
if ($isHttps) {
$this->checkMixedHttps($appUrl);
}
}
private function checkMixedHttps(string $appUrl): void
{
$baseUrl = rtrim($appUrl, '/');
$issues = [];
$httpUrl = str_replace('https://', 'http://', $baseUrl);
$httpsCheckUrl = $baseUrl . '/';
$httpReachable = $this->checkUrlReachable($httpUrl);
$httpsReachable = $this->checkUrlReachable($httpsCheckUrl);
if ($httpReachable && $httpsReachable) {
$issues[] = 'Both HTTP and HTTPS accessible - configure redirect';
}
if ($httpReachable) {
$redirectCheck = $this->checkHttpRedirect($httpUrl);
if (! $redirectCheck) {
$issues[] = 'HTTP is not redirecting to HTTPS';
}
}
$forceHttps = config('habbo.site.force_https');
if (! $forceHttps) {
$issues[] = 'FORCE_HTTPS not enabled (set FORCE_HTTPS=true in .env)';
}
if ($this->isWindows()) {
$this->checkWindowsHttpsConfig($issues);
} else {
$this->checkLinuxHttpsConfig($issues);
}
if ($issues === []) {
$this->addCheck('Mixed HTTPS', '✅', 'Secure configuration');
} else {
foreach ($issues as $issue) {
$this->addCheck('Mixed HTTPS', '⚠️', $issue);
$this->errors++;
}
}
}
private function checkWindowsHttpsConfig(array &$issues): void
{
$envPath = base_path('.env');
if (File::exists($envPath)) {
$envContent = File::get($envPath);
if (preg_match('/^APP_URL=http:\/\//m', $envContent)) {
$issues[] = 'Windows: APP_URL uses HTTP instead of HTTPS';
}
if (! preg_match('/^FORCE_HTTPS=true/m', $envContent)) {
$issues[] = 'Windows: FORCE_HTTPS not set in .env';
}
}
$this->checkIISConfig($issues);
}
private function checkLinuxHttpsConfig(array &$issues): void
{
$envPath = base_path('.env');
if (File::exists($envPath)) {
$envContent = File::get($envPath);
if (preg_match('/^APP_URL=http:\/\//m', $envContent)) {
$issues[] = 'Linux: APP_URL uses HTTP instead of HTTPS';
}
if (! preg_match('/^FORCE_HTTPS=true/m', $envContent)) {
$issues[] = 'Linux: FORCE_HTTPS not set in .env';
}
}
if ($this->isNginx()) {
$this->checkNginxSslConfig($issues);
} elseif ($this->isApache()) {
$this->checkApacheSslConfig($issues);
}
}
private function checkIISConfig(array &$issues): void
{
$webConfig = base_path('web.config');
if (File::exists($webConfig)) {
$content = File::get($webConfig);
if (! str_contains($content, 'httpsRedirect') && ! str_contains($content, 'rewrite')) {
$issues[] = 'IIS: No HTTPS redirect rules found in web.config';
}
} else {
$issues[] = 'IIS: web.config not found - create for HTTPS redirect';
}
}
private function checkNginxSslConfig(array &$issues): void
{
$domain = parse_url((string) config('app.url'), PHP_URL_HOST);
$nginxPaths = [
'/etc/nginx/sites-available/' . $domain,
'/etc/nginx/sites-enabled/' . $domain,
'/etc/nginx/conf.d/' . $domain . '.conf',
];
$found = false;
foreach ($nginxPaths as $path) {
if (File::exists($path)) {
$found = true;
$content = File::get($path);
$hasHttps = str_contains($content, 'listen 443') || str_contains($content, 'listen 443 ssl');
$hasRedirect = str_contains($content, 'return 301') || str_contains($content, 'return 302');
if (! $hasHttps) {
$issues[] = 'Nginx: No SSL/TLS configuration found';
}
if (! $hasRedirect) {
$issues[] = 'Nginx: HTTP to HTTPS redirect not configured';
}
break;
}
}
if (! $found) {
$issues[] = 'Nginx: Config file not found in standard locations';
}
}
private function checkApacheSslConfig(array &$issues): void
{
$htaccess = public_path('.htaccess');
if (File::exists($htaccess)) {
$content = File::get($htaccess);
if (! str_contains($content, 'HTTPS') && ! str_contains($content, 'httpsRedirect')) {
$issues[] = 'Apache: No HTTPS redirect in .htaccess';
}
}
$apacheConfigs = glob('/etc/apache2/sites-*/*');
$foundSsl = false;
foreach ($apacheConfigs as $config) {
if (is_file($config) && is_readable($config)) {
$content = File::get($config);
if (str_contains($content, 'SSLEngine') || str_contains($content, '443')) {
$foundSsl = true;
break;
}
}
}
if (! $foundSsl) {
$issues[] = 'Apache: No SSL virtual host found';
}
}
private function checkHttpRedirect(string $httpUrl): bool
{
try {
$context = stream_context_create([
'http' => [
'method' => 'HEAD',
'timeout' => 5,
'follow_location' => 0,
'ignore_errors' => true,
],
]);
$response = @file_get_contents($httpUrl, false, $context);
foreach ($http_response_header as $header) {
if (stripos($header, 'Location:') !== false || stripos($header, 'location:') !== false) {
$location = trim(substr($header, strpos($header, ':') + 1));
return stripos($location, 'https://') !== false;
}
}
return false;
} catch (\Exception) {
return false;
}
}
private function checkUrlReachable(string $url): bool
{
if ($this->checkUrlWithCurl($url)) {
return true;
}
if ($this->checkUrlWithFileGetContents($url)) {
return true;
}
return $this->checkUrlWithSocket($url);
}
private function checkUrlWithCurl(string $url): bool
{
if (! function_exists('curl_init')) {
return false;
}
try {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_FOLLOWLOCATION => false,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return in_array($httpCode, [200, 301, 302, 303, 307, 308]);
} catch (\Exception) {
return false;
}
}
private function checkUrlWithFileGetContents(string $url): bool
{
try {
$context = stream_context_create([
'http' => [
'method' => 'HEAD',
'timeout' => 5,
'ignore_errors' => true,
],
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
]);
$response = @file_get_contents($url, false, $context);
return $response !== false;
} catch (\Exception) {
return false;
}
}
private function checkUrlWithSocket(string $url): bool
{
try {
$parsed = parse_url($url);
$host = $parsed['host'] ?? 'localhost';
$port = $parsed['port'] ?? (($parsed['scheme'] ?? 'http') === 'https' ? 443 : 80);
$path = $parsed['path'] ?? '/';
$socket = @fsockopen(
($port === 443 ? 'ssl://' : '') . $host,
$port,
$errno,
$errstr,
5,
);
if ($socket) {
$request = "HEAD {$path} HTTP/1.1\r\n";
$request .= "Host: {$host}\r\n";
$request .= "Connection: close\r\n\r\n";
fwrite($socket, $request);
$response = fread($socket, 1024);
fclose($socket);
return stripos($response, 'HTTP/') !== false;
}
return false;
} catch (\Exception) {
return false;
}
}
private function checkFilePermissions(): void
{
$paths = ['storage', 'bootstrap/cache', 'public/uploads'];
foreach ($paths as $p) {
$full = base_path($p);
if (! is_dir($full)) {
$this->addCheck("Path: {$p}", '❌', 'Missing');
// Create missing directories
if ($this->confirm("Create missing directory: {$p}?", true)) {
@mkdir($full, 0755, true);
$this->info(" ✅ Created: {$p}");
}
continue;
}
$writable = is_writable($full);
$this->addCheck("Writable: {$p}", $writable ? '✅' : '❌', $writable ? 'Yes' : 'No');
}
}
private function checkFirewallPorts(): void
{
$ports = [80 => 'HTTP', 443 => 'HTTPS', 3306 => 'MySQL', 3000 => 'Nitro'];
foreach (array_keys($ports) as $port) {
$connection = @fsockopen('127.0.0.1', $port, $errno, $errstr, 0.05);
if ($connection) {
$this->addCheck("Port {$port}", '✅', 'Open');
fclose($connection);
} else {
$this->addCheck("Port {$port}", '⚠️', 'Closed');
}
}
}
private function checkStorageSymlink(): void
{
$ok = File::exists(public_path('storage'));
$this->addCheck('Storage Link', $ok ? '✅' : '❌', $ok ? 'OK' : 'Missing');
}
private function checkAssetsStatus(): void
{
$ok = File::exists(public_path('assets/css/app.css')) || File::exists(public_path('build/assets'));
$this->addCheck('Frontend Assets', $ok ? '✅' : '⚠️', $ok ? 'Built' : 'Not found');
}
private function checkFrontendManifest(): void
{
$manifestPath = public_path('build/manifest.json');
if (File::exists($manifestPath)) {
$this->addCheck('Vite Manifest', '✅', 'Found');
} else {
$this->addCheck('Vite Manifest', '❌', 'Missing');
$this->errors++;
}
}
private function checkHttpErrors(): void
{
// 400 Bad Request
$this->check400Error();
// 401 Unauthorized
$this->check401Error();
// 403 Forbidden
$this->check403Error();
// 404 Not Found
$this->check404Error();
// 419 Page Expired (CSRF)
$this->check419Error();
// 429 Too Many Requests
$this->check429Error();
// 500 Internal Server Error
$this->check500Error();
// 502 Bad Gateway
$this->check502Error();
// 503 Service Unavailable
$this->check503Error();
// 504 Gateway Timeout
$this->check504Error();
}
private function check400Error(): void
{
$issues = [];
// Check for cookie issues
if (config('session.driver') === 'file') {
$sessionPath = storage_path('framework/sessions');
if (! is_writable($sessionPath)) {
$issues[] = 'Sessions directory not writable';
}
}
// Check for large POST data
$postMax = ini_get('post_max_size');
$uploadMax = ini_get('upload_max_filesize');
if ((int) $postMax < 32 || (int) $uploadMax < 32) {
$issues[] = 'POST/upload limits too low';
}
if ($issues !== []) {
$this->addCheck('HTTP 400 (Bad Request)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 400 (Bad Request)', '✅', 'OK');
}
}
private function check401Error(): void
{
$issues = [];
// Check .env authentication
if (config('app.env') === 'production' && empty(config('app.key'))) {
$issues[] = 'APP_KEY missing';
}
if ($issues !== []) {
$this->addCheck('HTTP 401 (Unauthorized)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 401 (Unauthorized)', '✅', 'OK');
}
}
private function check403Error(): void
{
$issues = [];
// Check public/index.php exists
if (! File::exists(public_path('index.php'))) {
$issues[] = 'index.php missing';
}
// Check .htaccess (Apache)
if ($this->isApache() && ! File::exists(public_path('.htaccess'))) {
$issues[] = '.htaccess missing';
}
// Check storage permissions
$storagePath = storage_path();
if (! is_writable($storagePath)) {
$issues[] = 'Storage not writable';
}
// Check public permissions
$publicPath = public_path();
if (! is_readable($publicPath)) {
$issues[] = 'Public not readable';
}
// Check CSRF token mismatch - common issue
if (config('session.driver') !== 'file') {
$sessionDriver = config('session.driver');
if ($sessionDriver === 'redis' && ! extension_loaded('redis')) {
$issues[] = 'Redis extension missing';
}
}
if ($issues !== []) {
$this->addCheck('HTTP 403 (Forbidden)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 403 (Forbidden)', '✅', 'OK');
}
}
private function check404Error(): void
{
$issues = [];
// Check routes cache
if (File::exists(base_path('bootstrap/cache/routes.php')) && config('cache.default') !== 'array') {
// Routes might be cached but stale
}
// Check public directory
if (! is_dir(public_path())) {
$issues[] = 'Public directory missing';
}
// Check storage symlink
if (! File::exists(public_path('storage'))) {
$issues[] = 'Storage symlink missing';
}
// Check .htaccess for proper rewrite rules
if ($this->isApache()) {
$htaccess = public_path('.htaccess');
if (File::exists($htaccess)) {
$content = File::get($htaccess);
if (! str_contains($content, 'RewriteRule')) {
$issues[] = 'Missing rewrite rules';
}
}
}
// Check nginx config
if ($this->isNginx()) {
$nginxConfig = base_path('atom-nginx.conf');
if (! File::exists($nginxConfig)) {
$issues[] = 'Nginx config not generated';
}
}
if ($issues !== []) {
$this->addCheck('HTTP 404 (Not Found)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 404 (Not Found)', '✅', 'OK');
}
}
private function check419Error(): void
{
$issues = [];
// CSRF token expired - usually session related
if (config('session.lifetime') < 60) {
$issues[] = 'Session lifetime too short';
}
if (config('session.driver') === 'file') {
$sessionPath = storage_path('framework/sessions');
if (! is_writable($sessionPath)) {
$issues[] = 'Sessions not writable';
}
}
if ($issues !== []) {
$this->addCheck('HTTP 419 (Page Expired)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 419 (Page Expired)', '✅', 'OK');
}
}
private function check429Error(): void
{
$issues = [];
// Rate limiting issues
if (config('cache.default') === 'file') {
$issues[] = 'File cache for rate limiting (slow)';
}
if ($issues !== []) {
$this->addCheck('HTTP 429 (Too Many Requests)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 429 (Too Many Requests)', '✅', 'OK');
}
}
private function check500Error(): void
{
$issues = [];
// Check PHP errors
$errorLog = ini_get('error_log');
if (! in_array($errorLog, ['', '0', false], true) && File::exists($errorLog)) {
$recentErrors = $this->getRecentErrors($errorLog, 10);
if (count($recentErrors) > 5) {
$issues[] = 'Many PHP errors in log';
}
}
// Check debug mode
if (! config('app.debug')) {
// Debug is off - 500 errors won't show details
}
// Check composer autoload
if (! File::exists(base_path('vendor/autoload.php'))) {
$issues[] = 'Composer dependencies not installed';
}
// Check bootstrap/cache
$bootstrapCache = base_path('bootstrap/cache');
if (! is_writable($bootstrapCache)) {
$issues[] = 'Bootstrap cache not writable';
}
// Check storage permissions
if (! is_writable(storage_path())) {
$issues[] = 'Storage not writable';
}
// Check database connection
try {
DB::connection()->getPdo();
} catch (\Exception) {
$issues[] = 'Database connection failed';
}
if ($issues !== []) {
$this->addCheck('HTTP 500 (Internal Error)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 500 (Internal Error)', '✅', 'OK');
}
}
private function check502Error(): void
{
$issues = [];
$isWindows = DIRECTORY_SEPARATOR === '\\';
// Check PHP-FPM (Linux only)
if (! $isWindows && ($this->isNginx() || $this->isApache())) {
$phpFpm = shell_exec('systemctl status php*-fpm 2>/dev/null | head -1');
if (in_array($phpFpm, ['', '0', false, null], true)) {
$issues[] = 'PHP-FPM may not be running';
}
}
// Check socket files
if (! $isWindows) {
$phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
$socketPaths = [
"/var/run/php/php{$phpVersion}-fpm.sock",
'/var/run/php-fpm.sock',
"/run/php/php{$phpVersion}-fpm.sock",
];
$socketExists = array_any($socketPaths, fn ($path) => File::exists($path));
if (! $socketExists) {
$issues[] = 'PHP-FPM socket not found';
}
} else {
// On Windows with nginx, check PHP-CGI
$phpCgiRunning = shell_exec('tasklist 2>nul | findstr php-cgi');
if (in_array($phpCgiRunning, ['', '0', false, null], true)) {
$issues[] = 'PHP-CGI may not be running';
}
}
if ($issues !== []) {
$this->addCheck('HTTP 502 (Bad Gateway)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 502 (Bad Gateway)', '✅', 'OK');
}
}
private function check503Error(): void
{
$issues = [];
// Check maintenance mode
$maintenanceFile = storage_path('framework/maintenance.php');
if (File::exists($maintenanceFile)) {
$issues[] = 'Maintenance mode enabled';
}
// Check queue workers
if (config('queue.default') !== 'sync') {
$result = $this->runSystemCommand('pgrep -f "queue:work"');
if (in_array(trim((string) $result['output']), ['', '0'], true)) {
$issues[] = 'Queue workers not running';
}
}
// Check database connection
try {
DB::connection()->getPdo();
} catch (\Exception) {
$issues[] = 'Database unavailable';
}
// Check Redis
if (config('cache.default') === 'redis') {
try {
Redis::connection()->ping();
} catch (\Exception) {
$issues[] = 'Redis unavailable';
}
}
if ($issues !== []) {
$this->addCheck('HTTP 503 (Unavailable)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 503 (Unavailable)', '✅', 'OK');
}
}
private function check504Error(): void
{
$issues = [];
// Check PHP execution time
$maxExecutionTime = ini_get('max_execution_time');
if ($maxExecutionTime < 60 && $maxExecutionTime != 0) {
$issues[] = 'Max execution time too low';
}
// Check database query time
try {
DB::connection()->getPdo();
} catch (\Exception) {
$issues[] = 'Database timeout';
}
// Check Redis timeout
if (config('cache.default') === 'redis') {
try {
Redis::connection()->ping();
} catch (\Exception) {
$issues[] = 'Redis timeout';
}
}
// Check PHP-FPM timeout
if ($this->isNginx()) {
$nginxConfig = base_path('atom-nginx.conf');
if (File::exists($nginxConfig)) {
$content = File::get($nginxConfig);
if (! str_contains($content, 'fastcgi_read_timeout')) {
$issues[] = 'Nginx fastcgi timeout not set';
}
}
}
if ($issues !== []) {
$this->addCheck('HTTP 504 (Gateway Timeout)', '⚠️', implode(', ', $issues));
} else {
$this->addCheck('HTTP 504 (Gateway Timeout)', '✅', 'OK');
}
}
private function getRecentErrors(string $logFile, int $lines = 10): array
{
if (! File::exists($logFile)) {
return [];
}
$content = File::get($logFile);
$arr = explode("\n", $content);
return array_slice($arr, -$lines);
}
private function checkRedisConnection(): void
{
if (! extension_loaded('redis')) {
return;
}
$redisClass = '\Redis';
if (class_exists($redisClass)) {
try {
$redis = new $redisClass;
$host = config('database.redis.default.host', '127.0.0.1');
$port = config('database.redis.default.port', 6379);
$redis->connect($host, $port);
if ($pass = config('database.redis.default.password')) {
$redis->auth($pass);
}
if ($redis->ping()) {
$this->addCheck('Redis', '✅', 'Connected');
} else {
$this->addCheck('Redis', '❌', 'Ping Failed');
$this->errors++;
}
} catch (\Exception $e) {
$this->addCheck('Redis', '❌', 'Failed: ' . $e->getMessage());
$this->errors++;
}
}
}
private function checkCronJobsStatus(): void
{
if ($this->isWindows()) {
return;
}
$cronResult = $this->runSystemCommand('crontab -l');
$cron = $cronResult['output'];
$ok = str_contains((string) $cron, 'schedule:run');
$this->addCheck('Laravel Cron', $ok ? '✅' : '⚠️', $ok ? 'Active' : 'Missing');
}
private function checkQueueWorkersStatus(): void
{
if ($this->isWindows()) {
$result = $this->runSystemCommand('tasklist /FI "IMAGENAME eq php.exe" /FO CSV');
$ok = array_filter(explode("\n", (string) $result['output']), fn ($line) => str_contains((string) $line, 'queue:work')) !== [];
} else {
$result = $this->runSystemCommand('pgrep -f "queue:work"');
$ok = ! in_array(trim((string) $result['output']), ['', '0'], true);
}
$this->addCheck('Queue Worker', $ok ? '✅' : '⚠️', $ok ? 'Running' : 'Stopped');
}
private function checkSupervisorConfig(): void
{
if ($this->isLinux()) {
$path = '/etc/supervisor/conf.d/atom-worker.conf';
$exists = File::exists($path) || File::exists('/etc/supervisor.d/atom.ini');
$this->addCheck('Supervisor', $exists ? '✅' : '⚠️', $exists ? 'Found' : 'Missing');
if (! $exists) {
$this->warnings++;
}
}
}
private function checkRequiredFiles(): void
{
if (! File::exists(public_path('index.php'))) {
$this->addCheck('public/index.php', '❌', 'Missing');
}
}
// --- REPAIRS ---
private function createAdminUser(?string $username = null, ?string $email = null, ?string $password = null): void
{
if ($username === null) {
$username = $this->ask('Enter Admin Username', 'Admin');
}
if ($email === null) {
$email = $this->ask('Enter Admin Email', 'admin@example.com');
}
if ($password === null) {
$password = $this->secret('Enter Admin Password');
}
if (empty($password)) {
$this->error('❌ Password is required.');
return;
}
try {
$existingAdmin = DB::table('users')->where('username', $username)->orWhere('rank', '>=', 7)->first();
if ($existingAdmin) {
$this->info("✅ Admin user already exists ({$existingAdmin->username})");
return;
}
DB::table('users')->insert([
'username' => $username,
'mail' => $email,
'password' => Hash::make($password),
'rank' => 7,
'look' => 'hr-115-42.hd-190-1.ch-215-66.lg-270-66.sh-300-66',
'motto' => 'Atom CMS Admin',
'ip_register' => '127.0.0.1',
'ip_current' => '127.0.0.1',
'account_created' => time(),
'last_login' => time(),
]);
$this->info("✅ Admin user '{$username}' created.");
} catch (\Exception $e) {
$this->error('❌ Failed: ' . $e->getMessage());
}
}
// --- REPAIRS ---
private function buildFrontendAssets(): void
{
if ($this->skipBuild) {
$this->info('⏭️ Asset building skipped');
return;
}
$isWindows = DIRECTORY_SEPARATOR === '\\';
if ($isWindows) {
$this->info('⏭️ Asset building skipped on Windows');
return;
}
$manager = 'npm';
$cmd = 'npm install && npm run build';
if (File::exists(base_path('yarn.lock'))) {
$manager = 'yarn';
$cmd = 'yarn && yarn build';
} elseif (File::exists(base_path('pnpm-lock.yaml'))) {
$manager = 'pnpm';
$cmd = 'pnpm install && pnpm build';
}
$this->info("🚀 Building assets with {$manager}...");
exec("{$cmd} 2>&1", $output, $exitCode);
if ($exitCode === 0) {
$this->info('✅ Assets built.');
} else {
$this->warn('⚠️ Build failed - skipped');
}
}
private function generateSupervisorConfig(): void
{
if ($this->isWindows()) {
$this->generateWindowsQueueService();
return;
}
$path = base_path('atom-worker.conf');
$user = $this->webUser;
$root = base_path();
$content = "[program:atom-worker]
process_name=%(program_name)s_%(process_num)02d
command=php {$root}/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user={$user}
numprocs=2
redirect_stderr=true
stdout_logfile={$root}/storage/logs/worker.log
";
File::put($path, $content);
$supervisorPath = '/etc/supervisor/conf.d/atom-worker.conf';
$isRoot = function_exists('posix_getuid') && posix_getuid() === 0;
if ($this->isWindows()) {
$this->info('⏭️ Supervisor config not needed on Windows');
return;
}
if ($isRoot) {
@copy($path, $supervisorPath);
$this->runSystemCommand('sudo supervisorctl reread', true);
$this->runSystemCommand('sudo supervisorctl update', true);
$this->runSystemCommand('sudo supervisorctl start atom-worker:*', true);
$this->info("✅ Generated and installed 'atom-worker.conf' to {$supervisorPath}");
} else {
@copy($path, $supervisorPath);
$this->info("✅ Generated 'atom-worker.conf'. Installed to {$supervisorPath}");
$this->info(' Run: sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start atom-worker:*');
}
}
private function generateWindowsQueueService(): void
{
$root = base_path();
$batPath = base_path('queue-worker.bat');
$content = "@echo off
cd /d \"{$root}\"
:start
php artisan queue:work --sleep=3 --tries=3 --max-time=3600
goto start
";
File::put($batPath, $content);
$this->info("✅ Generated 'queue-worker.bat'");
$this->info(' To run as Windows Service:');
$this->info(' 1. Download NSSM: https://nssm.cc/download');
$this->info(" 2. Run: nssm install AtomWorker \"{$batPath}\"");
$this->info(' 3. Or use Task Scheduler: taskschd.msc');
}
private function repairApache(): void
{
$path = public_path('.htaccess');
$rules = "<IfModule mod_rewrite.c>\n <IfModule mod_negotiation.c>\n Options -MultiViews -Indexes\n </IfModule>\n\n RewriteEngine On\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteCond %{REQUEST_FILENAME} !-f\n RewriteRule ^ index.php [L]\n</IfModule>";
File::put($path, $rules);
if ($this->isLinux()) {
exec('sudo a2enmod rewrite 2>/dev/null');
}
}
private function repairIIS(): void
{
$path = public_path('web.config');
$data = '<?xml version="1.0" encoding="UTF-8"?><configuration><system.webServer><rewrite><rules><rule name="Laravel" stopProcessing="true"><match url="^" /><conditions><add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /><add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /></conditions><action type="Rewrite" url="index.php" /></rule></rules></rewrite></system.webServer></configuration>';
File::put($path, $data);
}
private function repairNginx(): void
{
$phpVer = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
$template = 'server {
listen 80;
server_name example.com;
root ' . public_path() . ";
add_header X-Frame-Options \"SAMEORIGIN\";
add_header X-Content-Type-Options \"nosniff\";
index index.php;
charset utf-8;
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php{$phpVer}-fpm.sock;
fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}";
File::put(base_path('atom-nginx.conf'), $template);
$this->info('✅ Template created: atom-nginx.conf');
}
private function repairPermissions(): void
{
$isWindows = DIRECTORY_SEPARATOR === '\\';
if ($isWindows) {
$paths = [base_path('storage'), base_path('bootstrap/cache'), public_path('uploads')];
foreach ($paths as $path) {
if (! file_exists($path)) {
@mkdir($path, 0755, true);
$this->info(" ✅ Created: {$path}");
continue;
}
if (is_dir($path)) {
if ($this->isXampp() || $this->isWamp() || $this->isNginx()) {
@chmod($path, 0777);
$this->recursiveChmod($path, 0777);
} else {
@chmod($path, 0755);
$this->recursiveChmod($path, 0755);
}
}
}
$this->info('✅ Windows permissions fixed');
return;
}
// Linux permissions
$paths = [base_path('storage'), base_path('bootstrap/cache'), public_path('uploads')];
foreach ($paths as $path) {
if (! file_exists($path)) {
@mkdir($path, 0755, true);
$this->info(" ✅ Created: {$path}");
continue;
}
if (is_dir($path)) {
@exec("sudo chown -R {$this->webUser}:{$this->webGroup} {$path} 2>/dev/null");
@exec("find {$path} -type d -exec chmod 775 {} \; 2>/dev/null");
@exec("find {$path} -type f -exec chmod 664 {} \; 2>/dev/null");
}
}
$this->info('✅ Linux permissions fixed');
}
private function recursiveChmod(string $dir, int $mode): void
{
if (! is_dir($dir)) {
return;
}
$files = @array_diff(scandir($dir), ['.', '..']);
if ($files === false) {
return;
}
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
@chmod($path, $mode);
$this->recursiveChmod($path, $mode);
} else {
@chmod($path, $mode);
}
}
}
private function repairLinuxPermissions(): void
{
$this->repairPermissions();
}
private function fixKnownCommonErrors(): void
{
$this->info('🔧 Fixing known common errors...');
// Clear all Laravel caches
$this->call('cache:clear');
$this->call('view:clear');
$this->call('route:clear');
$this->call('config:clear');
$this->call('event:clear');
$this->call('clear-compiled');
$this->info('✅ All caches cleared');
// Fix storage permissions
$this->repairPermissions();
$this->info('✅ Storage permissions fixed');
// Fix bootstrap/cache permissions
$cachePath = base_path('bootstrap/cache');
if (is_dir($cachePath)) {
if ($this->isWindows()) {
@chmod($cachePath, 0777);
} else {
@chmod($cachePath, 0755);
exec("sudo chown -R {$this->webUser}:{$this->webGroup} {$cachePath} 2>/dev/null");
}
}
// Fix sessions table if needed
if (Schema::hasTable('sessions')) {
try {
DB::statement('OPTIMIZE TABLE sessions');
$this->info('✅ Sessions table optimized');
} catch (\Exception) {
// Ignore
}
}
// Ensure storage directories exist
$directories = [
storage_path('app'),
storage_path('app/public'),
storage_path('framework/cache'),
storage_path('framework/cache/data'),
storage_path('framework/sessions'),
storage_path('framework/views'),
storage_path('logs'),
];
foreach ($directories as $dir) {
if (! is_dir($dir)) {
@mkdir($dir, 0755, true);
}
}
$this->info('✅ Storage directories ensured');
// Fix radio tables - always run to ensure they exist
$this->createRadioTables();
// Fix .env if needed
if (! File::exists(base_path('.env'))) {
File::copy(base_path('.env.example'), base_path('.env'));
$this->info('✅ .env file created from example');
}
// Fix APP_KEY if missing
if (empty(config('app.key'))) {
$this->call('key:generate', ['--force' => true]);
$this->info('✅ APP_KEY generated');
}
// Fix database if using SQLite (common error)
if (config('database.default') === 'sqlite') {
$dbPath = database_path('database.sqlite');
if (! File::exists($dbPath)) {
touch($dbPath);
chmod($dbPath, 0755);
$this->info('✅ SQLite database created');
}
}
// Check and fix common migration issues
try {
$this->call('migrate:status', ['--format' => 'compact']);
} catch (\Exception) {
$this->warn('⚠️ Migration status check failed');
}
// Fix queue table if using database driver
if (config('queue.default') === 'database') {
if (! Schema::hasTable('jobs')) {
$this->call('queue:table', ['--force' => true]);
$this->info('✅ Queue jobs table created');
}
if (! Schema::hasTable('failed_jobs')) {
$this->call('queue:failed-table', ['--create' => true]);
$this->info('✅ Failed jobs table created');
}
}
// Fix broadcasting if using Pusher
if (config('broadcasting.default') === 'pusher' && empty(config('broadcasting.connections.pusher.key'))) {
$this->warn('⚠️ Pusher credentials missing in config');
}
// Fix Redis connection if configured but not connected
if (config('cache.default') === 'redis') {
try {
Redis::connection()->ping();
} catch (\Exception) {
$this->warn('⚠️ Redis not connected, falling back to file cache');
// Can't auto-switch, just warn
}
}
$this->info('✅ Known common errors fixed');
}
private function repairPHPConfig(): bool
{
if (! is_writable($this->phpIniPath)) {
return false;
}
$c = File::get($this->phpIniPath);
$c = preg_replace(['/memory_limit\s*=.*/', '/upload_max_filesize\s*=.*/', '/post_max_size\s*=.*/'], ['memory_limit=512M', 'upload_max_filesize=64M', 'post_max_size=64M'], $c);
return (bool) File::put($this->phpIniPath, $c);
}
private function repairPHPExtensions(): int
{
$v = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
$missing = [];
foreach (['gd', 'zip', 'redis', 'intl', 'bcmath', 'xml'] as $ext) {
if (! extension_loaded($ext)) {
$missing[] = $ext;
}
}
if ($missing === []) {
return 0;
}
if ($this->isWindows()) {
$this->info('Installing PHP extensions on Windows...');
// Check for Chocolatey
$chocoExists = shell_exec('where choco 2>nul');
if (! in_array($chocoExists, ['', '0', false, null], true)) {
foreach ($missing as $ext) {
$this->info("Installing PHP {$ext} via Chocolatey...");
// Try to install PHP extension via choco
shell_exec("choco install php{$v}-{$ext} -y 2>nul");
}
} else {
$this->warn('Cannot auto-install extensions on Windows without Chocolatey');
$this->warn('Missing extensions: ' . implode(', ', $missing));
$this->warn('To install manually:');
$this->warn('1. Download PHP extensions from: https://windows.php.net/downloads/pecl/releases/');
$this->warn('2. Or enable in php.ini: extension=php_' . implode('.dll, extension=php_', $missing) . '.dll');
}
return count($missing);
}
// Linux
$pm = (shell_exec('which apt 2>/dev/null')) ? 'apt' : ((shell_exec('which yum 2>/dev/null')) ? 'yum' : 'pacman');
$isWindows = DIRECTORY_SEPARATOR === '\\';
foreach ($missing as $ext) {
if ($isWindows) {
$this->warn("⚠️ Cannot install {$ext} on Windows - install manually");
continue;
}
$pkg = ($pm === 'apt') ? "php{$v}-{$ext}" : "php-{$ext}";
$this->info("Installing {$pkg}...");
// Check if we can install without sudo (already root)
$isRoot = function_exists('posix_getuid') && posix_getuid() === 0;
$cmd = $isRoot ? "{$pm} install -y {$pkg}" : "sudo {$pm} install -y {$pkg}";
exec("{$cmd} 2>/dev/null", $output, $exitCode);
if ($exitCode !== 0 && ! $isRoot) {
$this->warn("⚠️ Cannot install {$pkg} - run manually: sudo {$pm} install -y {$pkg}");
}
}
return count($missing);
}
private function updatePhpVersion(): int
{
if ($this->isLinux()) {
$this->info('Adding PHP repository...');
exec('sudo add-apt-repository -y ppa:ondrej/php && sudo apt-get update 2>/dev/null');
$this->warn("Repository added. Run 'sudo apt upgrade' to finish.");
return 1;
}
return 0;
}
private function createRadioTables(): void
{
$radioTables = [
'radio_song_votes',
'radio_contests',
'radio_giveaways',
'radio_listener_points',
'radio_song_requests',
'radio_ranks',
'radio_shouts',
'radio_schedules',
'radio_banners',
'radio_history',
'radio_applications',
];
$this->info('🔊 Checking radio tables...');
$missingTables = [];
foreach ($radioTables as $table) {
if (! Schema::hasTable($table)) {
$missingTables[] = $table;
}
}
if ($this->isWindows()) {
$mysqlPath = $this->findMysql();
if ($mysqlPath) {
$mysqlDir = dirname($mysqlPath);
$currentPath = getenv('PATH') ?: '';
if (! str_contains($currentPath, $mysqlDir)) {
putenv("PATH={$currentPath};{$mysqlDir}");
}
}
}
if ($missingTables !== []) {
$this->info('📦 Creating ' . count($missingTables) . ' missing radio tables...');
// Run all migrations
if ($this->skipMigrations) {
$this->info('⏭️ Migrations skipped (--skip-migrations)');
} else {
if ($this->isWindows()) {
$this->addMysqlToPath();
}
if ($this->skipDuplicates) {
$this->runMigrationsWithSkip();
} else {
$this->call('migrate', ['--force' => true]);
}
}
}
// Run all radio seeders
$radioSeeders = [
RadioSettingsSeeder::class,
RadioContestSeeder::class,
RadioGiveawaySeeder::class,
RadioSongRequestSeeder::class,
RadioSongVoteSeeder::class,
RadioListenerPointSeeder::class,
RadioTestSeeder::class,
];
foreach ($radioSeeders as $seeder) {
if (class_exists($seeder)) {
try {
$this->call('db:seed', ['--class' => $seeder, '--force' => true]);
} catch (\Exception) {
// Ignore individual seeder failures
}
}
}
// Enable radio in settings
try {
DB::table('website_settings')->updateOrInsert(
['key' => 'radio_enabled'],
['value' => '1', 'comment' => 'Radio enabled (0=no, 1=yes)'],
);
$this->info('✅ Radio enabled');
} catch (\Exception) {
// Ignore
}
$this->info('✅ Radio tables and data created');
}
private function installRedisIfNeeded(int &$f): void
{
if ($this->isLinux()) {
$this->info('Installing Redis Server (Linux)...');
exec('sudo apt-get install -y redis-server 2>/dev/null');
exec('sudo systemctl enable redis-server && sudo systemctl start redis-server 2>/dev/null');
$f++;
return;
}
if ($this->isWindows()) {
$this->info('Installing Redis Server (Windows)...');
// Check if Redis is already installed
$redisExists = shell_exec('where redis-server 2>nul') ?: shell_exec('where redis 2>nul');
if (in_array($redisExists, ['', '0', false, null], true)) {
// Try to install via Chocolatey
$chocoExists = shell_exec('where choco 2>nul');
if (! in_array($chocoExists, ['', '0', false, null], true)) {
$this->info('Installing Redis via Chocolatey...');
shell_exec('choco install redis-64 -y 2>nul');
} else {
$this->warn('Redis not installed. To install on Windows:');
$this->warn('1. Download Redis from: https://github.com/microsoftarchive/redis/releases');
$this->warn('2. Or install via Chocolatey: choco install redis-64');
$this->warn('3. Or use Memurai/Redis Windows: https://www.memurai.com/');
}
}
// Check if Redis is running
$redisRunning = shell_exec('sc query Redis 2>nul') ?: shell_exec('sc query redis 2>nul');
if (in_array($redisRunning, ['', '0', false, null], true)) {
$this->info('Starting Redis service...');
shell_exec('net start redis 2>nul') ?: shell_exec('sc start redis 2>nul');
}
$f++;
return;
}
}
private function fixEverything(bool $auto = false): int
{
$fixed = 0;
$errors = [];
$interactive = ! $auto;
$this->info('');
$this->info('════════════════════════════════════════════════════════════');
$this->info(' FIXING EVERYTHING IN ATOMCMS ');
$this->info('════════════════════════════════════════════════════════════');
$this->info('');
// Step 1: Environment
try {
$this->info('[1/11] 🔧 Fixing Environment...');
$envExists = File::exists(base_path('.env'));
$appKeySet = ! empty(config('app.key'));
if ($envExists && $appKeySet && $interactive) {
$this->warn(' ⚠️ Environment looks good already!');
if (! $this->confirm(' 💡 Weet je zeker dat je dit opnieuw wilt doen?', false)) {
$this->info(' ⏭️ Overgeslagen');
goto step2;
}
}
if (! $envExists) {
File::copy(base_path('.env.example'), base_path('.env'));
$this->info(' ✅ .env created');
}
if (! $appKeySet) {
$this->call('key:generate', ['--force' => true]);
$this->info(' ✅ APP_KEY generated');
}
if ($envExists && $appKeySet) {
$this->info(' ✅ Environment was already good');
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Environment: ' . $e->getMessage();
$this->warn(' ⚠️ Environment error: ' . $e->getMessage());
}
step2:
// Step 2: Clear all caches
try {
$this->info('[2/11] 🗑️ Clearing all caches...');
if ($interactive && ! $this->confirm(' 💡 Caches legen? (kan helpen bij problemen)', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step3;
}
$this->call('cache:clear');
$this->call('view:clear');
$this->call('route:clear');
$this->call('config:clear');
$this->call('event:clear');
$this->call('clear-compiled');
$this->call('optimize:clear');
$this->info(' ✅ All caches cleared');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Cache clear: ' . $e->getMessage();
$this->warn(' ⚠️ Cache clear error: ' . $e->getMessage());
}
step3:
// Step 3: Fix permissions (with multiple fallbacks)
try {
$this->info('[3/11] 🔐 Fixing permissions...');
$storageOk = is_writable(storage_path()) && is_writable(public_path('uploads'));
if ($storageOk && $interactive) {
$this->warn(' ⚠️ Permissions zien er goed uit!');
if (! $this->confirm(' 💡 Weet je zeker dat je permissions opnieuw wilt fixen?', false)) {
$this->info(' ⏭️ Overgeslagen');
goto step4;
}
}
$permissionSuccess = false;
// Attempt 1: Standard repair
try {
$this->repairPermissions();
$permissionSuccess = true;
$this->info(' ✅ Permissions fixed');
} catch (\Exception) {
$this->warn(' ⚠️ Standard repair failed, trying chmod...');
}
// Attempt 2: Direct chmod
if (! $permissionSuccess) {
try {
$dirs = [storage_path(), base_path('bootstrap/cache'), public_path('uploads')];
foreach ($dirs as $dir) {
if (is_dir($dir)) {
@chmod($dir, 0775);
$this->runSystemCommand("chmod -R 775 {$dir} 2>/dev/null");
}
}
$permissionSuccess = true;
$this->info(' ✅ Permissions fixed (chmod)');
} catch (\Exception) {
$this->warn(' ⚠️ chmod failed, trying sudo...');
}
}
// Attempt 3: sudo chmod
if (! $permissionSuccess && $this->isLinux()) {
try {
$this->runSystemCommand('sudo chmod -R 775 ' . storage_path() . ' 2>/dev/null');
$this->runSystemCommand('sudo chmod -R 775 ' . base_path('bootstrap/cache') . ' 2>/dev/null');
$this->runSystemCommand('sudo chmod -R 775 ' . public_path('uploads') . ' 2>/dev/null');
$permissionSuccess = true;
$this->info(' ✅ Permissions fixed (sudo)');
} catch (\Exception) {
$this->warn(' ⚠️ All permission attempts failed');
$errors[] = 'Permissions: All 3 attempts failed';
}
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Permissions: ' . $e->getMessage();
$this->warn(' ⚠️ Permissions error: ' . $e->getMessage());
}
step4:
// Step 4: Run ALL migrations (with multiple fallbacks)
try {
$this->info('[4/11] 🗄️ Running ALL migrations...');
if ($interactive && ! $this->confirm(' 💡 Database migrations draaien?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step5;
}
if ($this->skipMigrations) {
$this->info(' ⏭️ Migrations skipped (--skip-migrations)');
} else {
if ($this->isWindows()) {
$this->addMysqlToPath();
}
$migrationSuccess = false;
// Attempt 1: Normal migration
try {
$this->call('migrate', ['--force' => true]);
$migrationSuccess = true;
$this->info(' ✅ Migrations complete');
} catch (\Exception) {
$this->warn(' ⚠️ Normal migration failed, trying with --step...');
}
// Attempt 2: Migration with --step (if normal failed)
if (! $migrationSuccess) {
try {
$this->call('migrate', ['--force' => true, '--step' => true]);
$migrationSuccess = true;
$this->info(' ✅ Migrations complete (step mode)');
} catch (\Exception) {
$this->warn(' ⚠️ Step migration failed, trying rollback + migrate...');
}
}
// Attempt 3: Rollback and migrate fresh (if still failed)
if (! $migrationSuccess) {
try {
$this->call('migrate:rollback', ['--force' => true, '--step' => 1]);
$this->call('migrate', ['--force' => true]);
$migrationSuccess = true;
$this->info(' ✅ Migrations complete (rollback + migrate)');
} catch (\Exception) {
$this->warn(' ⚠️ Rollback failed, trying fresh...');
}
}
// Attempt 4: NEVER USE migrate:fresh - it deletes all data!
// Only show warning if all attempts failed
if (! $migrationSuccess) {
$this->warn(' ⚠️ All migration attempts failed');
$this->warn(' 💡 Run manually: php artisan migrate --force');
$errors[] = 'Migrations: All attempts failed (manual fix needed)';
}
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Migrations: ' . $e->getMessage();
$this->warn(' ⚠️ Migration error: ' . $e->getMessage());
}
step5:
// Step 5: Run ALL seeders (with multiple fallbacks)
try {
$this->info('[5/11] 🌱 Running ALL seeders...');
if ($interactive && ! $this->confirm(' 💡 Database seeders draaien?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step6;
}
$seederSuccess = false;
// Attempt 1: Normal seeder
try {
$this->call('db:seed', ['--force' => true]);
$seederSuccess = true;
$this->info(' ✅ All seeders complete');
} catch (\Exception $e1) {
if (str_contains($e1->getMessage(), 'Duplicate entry') || str_contains($e1->getMessage(), 'already exists')) {
$seederSuccess = true;
$this->info(' ✅ Seeders already applied (duplicates ignored)');
} else {
$this->warn(' ⚠️ Normal seeder failed, trying individual seeders...');
}
}
// Attempt 2: Run individual seeders (if normal failed)
if (! $seederSuccess) {
try {
$seeders = $this->discoverAllSeeders();
$seededCount = 0;
foreach ($seeders as $seeder) {
try {
$this->call('db:seed', ['--class' => $seeder, '--force' => true]);
$seededCount++;
} catch (\Exception) {
// Skip individual seeder errors
}
}
if ($seededCount > 0) {
$seederSuccess = true;
$this->info("{$seededCount} seeders completed");
}
} catch (\Exception) {
$this->warn(' ⚠️ Individual seeders failed, trying with --class option...');
}
}
// Attempt 3: Try with class option
if (! $seederSuccess) {
try {
$this->call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
$seederSuccess = true;
$this->info(' ✅ Seeders complete (with class option)');
} catch (\Exception) {
$this->warn(' ⚠️ All seeder attempts failed');
$errors[] = 'Seeders: All 3 attempts failed';
}
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Seeders: ' . $e->getMessage();
$this->warn(' ⚠️ Seeder error: ' . $e->getMessage());
}
step6:
// Step 6: Fix storage (with multiple fallbacks)
try {
$this->info('[6/11] 📁 Fixing storage...');
$storageLinkExists = File::exists(public_path('storage'));
if ($storageLinkExists && $interactive) {
$this->warn(' ⚠️ Storage symlink bestaat al!');
if (! $this->confirm(' 💡 Weet je zeker dat je storage opnieuw wilt fixen?', false)) {
$this->info(' ⏭️ Overgeslagen');
goto step6b;
}
}
$storageSuccess = false;
// Attempt 1: Laravel storage:link
try {
if (File::exists(public_path('storage'))) {
@unlink(public_path('storage'));
}
$this->call('storage:link');
$storageSuccess = true;
$this->info(' ✅ Storage symlink created');
} catch (\Exception) {
$this->warn(' ⚠️ storage:link failed, trying manual symlink...');
}
// Attempt 2: Manual symlink
if (! $storageSuccess) {
try {
if (File::exists(public_path('storage'))) {
@unlink(public_path('storage'));
}
symlink(storage_path('app/public'), public_path('storage'));
$storageSuccess = true;
$this->info(' ✅ Storage symlink created (manual)');
} catch (\Exception) {
$this->warn(' ⚠️ Manual symlink failed, trying copy...');
}
}
// Attempt 3: Copy instead of symlink
if (! $storageSuccess) {
try {
$this->runSystemCommand('cp -r ' . storage_path('app/public') . ' ' . public_path('storage'));
$storageSuccess = true;
$this->info(' ✅ Storage copied (no symlink)');
} catch (\Exception) {
$this->warn(' ⚠️ All storage attempts failed');
$errors[] = 'Storage: All 3 attempts failed';
}
}
// Create all required directories
$directories = [
storage_path('app'),
storage_path('app/public'),
storage_path('framework/cache'),
storage_path('framework/cache/data'),
storage_path('framework/sessions'),
storage_path('framework/views'),
storage_path('logs'),
];
foreach ($directories as $dir) {
if (! is_dir($dir)) {
@mkdir($dir, 0755, true);
}
}
$this->info(' ✅ Storage directories created');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Storage: ' . $e->getMessage();
$this->warn(' ⚠️ Storage error: ' . $e->getMessage());
}
// Step 5b: Supervisor config
if ($this->isLinux()) {
try {
$this->info('[5b/11] 🐘 Generating Supervisor config...');
$supervisorPath = '/etc/supervisor/conf.d/atom-worker.conf';
$supervisorExists = File::exists($supervisorPath) || File::exists('/etc/supervisor.d/atom.ini');
if ($supervisorExists) {
$this->info(' ✅ Supervisor config exists');
} else {
if ($interactive && ! $this->confirm(' 💡 Supervisor config genereren?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step6b;
}
$this->generateSupervisorConfig();
$this->info(' ✅ Supervisor config generated');
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Supervisor: ' . $e->getMessage();
$this->warn(' ⚠️ Supervisor error: ' . $e->getMessage());
}
}
step6b:
// Step 6b: Fix Radio
try {
$this->info('[6c/12] 📻 Fixing Radio tables...');
if ($interactive && ! $this->confirm(' 💡 Radio tabellen fixen?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step7;
}
$this->createRadioTables();
$this->info(' ✅ Radio tables fixed');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Radio: ' . $e->getMessage();
$this->warn(' ⚠️ Radio error: ' . $e->getMessage());
}
step7:
// Step 7: Create admin user if not exists
try {
$this->info('[7/12] 👤 Checking admin user...');
$hasAdmin = DB::table('users')->where('rank', '>=', 7)->exists();
if ($hasAdmin) {
$this->warn(' ⚠️ Admin gebruiker bestaat al!');
if ($interactive && ! $this->confirm(' 💡 Weet je zeker dat je een nieuwe admin wilt aanmaken?', false)) {
$this->info(' ⏭️ Overgeslagen');
goto step8;
}
}
if (! $hasAdmin) {
$this->createAdminUser('Admin', 'admin@atom.local', 'admin123');
$this->info(' ✅ Admin user created');
} else {
$this->info(' ✅ Admin user exists (was al goed)');
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Admin user: ' . $e->getMessage();
$this->warn(' ⚠️ Admin error: ' . $e->getMessage());
$this->warn(' 💡 Create admin manually: php artisan make:admin');
}
step8:
// Step 8: Fix web server config
try {
$this->info('[8/12] 🌐 Fixing web server config...');
if ($interactive && ! $this->confirm(' 💡 Web server config opnieuw genereren?', false)) {
$this->info(' ⏭️ Overgeslagen');
goto step9;
}
if ($this->isApache() || $this->isWamp()) {
$this->repairApache();
$this->info(' ✅ Apache config fixed');
} elseif ($this->isIIS()) {
$this->repairIIS();
$this->info(' ✅ IIS config fixed');
} elseif ($this->isNginx() || $this->isXampp()) {
$this->repairNginx();
$this->info(' ✅ Nginx config generated');
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Web server: ' . $e->getMessage();
$this->warn(' ⚠️ Web server error: ' . $e->getMessage());
}
step9:
// Step 9: Fix PHP config
try {
$this->info('[9/12] ⚙️ Optimizing PHP config...');
if ($interactive && ! $this->confirm(' 💡 PHP config optimaliseren?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step10;
}
$this->repairPHPConfig();
$this->info(' ✅ PHP config optimized');
$this->repairPHPExtensions();
$this->info(' ✅ PHP extensions checked');
$this->call('config:cache');
$this->info(' ✅ Config cache created');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'PHP config: ' . $e->getMessage();
$this->warn(' ⚠️ PHP config error: ' . $e->getMessage());
}
step10:
// Step 10: Build assets (with multiple fallbacks)
try {
$this->info('[10/12] 🎨 Building assets...');
if ($interactive && ! $this->confirm(' 💡 Frontend assets opnieuw bouwen? (kan lang duren)', true)) {
$this->info(' ⏭️ Overgeslagen');
goto step11;
}
$buildSuccess = false;
// Attempt 1: buildFrontendAssets method
try {
$this->buildFrontendAssets();
$buildSuccess = true;
$this->info(' ✅ Assets built');
} catch (\Exception) {
$this->warn(' ⚠️ Standard build failed, trying npm run build...');
}
// Attempt 2: npm run build
if (! $buildSuccess) {
try {
$result = $this->runSystemCommand('cd ' . base_path() . ' && npm run build 2>&1');
if ($result['success'] || str_contains((string) $result['output'], 'built in')) {
$buildSuccess = true;
$this->info(' ✅ Assets built (npm run build)');
}
} catch (\Exception) {
$this->warn(' ⚠️ npm run build failed, trying npm run build:atom...');
}
}
// Attempt 3: npm run build:atom
if (! $buildSuccess) {
try {
$result = $this->runSystemCommand('cd ' . base_path() . ' && npm run build:atom 2>&1');
if ($result['success'] || str_contains((string) $result['output'], 'built in')) {
$buildSuccess = true;
$this->info(' ✅ Assets built (npm run build:atom)');
}
} catch (\Exception) {
$this->warn(' ⚠️ All build attempts failed');
$errors[] = 'Assets: All 3 attempts failed';
}
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Assets: ' . $e->getMessage();
$this->warn(' ⚠️ Assets error: ' . $e->getMessage());
}
step11:
// Step 11: Fix HTTP errors
try {
$this->info('[11/12] 🌐 Fixing HTTP errors...');
if ($interactive && ! $this->confirm(' 💡 HTTP errors controleren en fixen?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto stepFilament;
}
$this->fixHttpErrors($auto);
$this->info(' ✅ HTTP errors checked');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'HTTP errors: ' . $e->getMessage();
$this->warn(' ⚠️ HTTP error check: ' . $e->getMessage());
}
stepFilament:
// Step 12: Fix HTTPS / Mixed Content
try {
$this->info('[12/13] 🔒 Fixing HTTPS configuration...');
if ($interactive && ! $this->confirm(' 💡 HTTPS en mixed content fixen?', true)) {
$this->info(' ⏭️ Overgeslagen');
goto finalOpt;
}
$this->fixHttpsConfiguration();
$this->info(' ✅ HTTPS configuration fixed');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'HTTPS: ' . $e->getMessage();
$this->warn(' ⚠️ HTTPS error: ' . $e->getMessage());
}
// Step 13: Fix Filament pages and widgets
try {
$this->info('[13/16] 📄 Fixing Filament pages & widgets...');
$this->fixFilamentPages();
$this->info(' ✅ Filament files checked');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Filament: ' . $e->getMessage();
$this->warn(' ⚠️ Filament error: ' . $e->getMessage());
}
// Step 14: Fix emulator service (auto-detect + multiple fallbacks)
try {
$this->info('[14/16] 🖥️ Checking emulator service...');
// Auto-detect emulator service
$serviceName = $this->detectEmulatorService();
$this->info(" 🔍 Detected service: {$serviceName}");
// Update setting if different
if ($serviceName !== setting('emulator_service_name')) {
WebsiteSetting::updateOrCreate(
['key' => 'emulator_service_name'],
['value' => $serviceName],
);
$this->info(" ✅ Updated setting to: {$serviceName}");
}
$result = $this->runSystemCommand("systemctl is-active {$serviceName} 2>/dev/null || echo 'inactive'");
if (trim((string) $result['output']) !== 'active') {
$this->warn(" ⚠️ Emulator service '{$serviceName}' is not running");
if ($auto || $this->confirm(' 💡 Emulator service starten?', true)) {
$serviceStarted = false;
// Attempt 1: systemctl start
try {
$this->runSystemCommand("systemctl start {$serviceName} 2>/dev/null");
sleep(2);
$check = $this->runSystemCommand("systemctl is-active {$serviceName} 2>/dev/null");
if (trim((string) $check['output']) === 'active') {
$serviceStarted = true;
$this->info(' ✅ Emulator service started (systemctl)');
}
} catch (\Exception) {
// Continue to next attempt
}
// Attempt 2: service start
if (! $serviceStarted) {
try {
$this->runSystemCommand("service {$serviceName} start 2>/dev/null");
sleep(2);
$serviceStarted = true;
$this->info(' ✅ Emulator service started (service)');
} catch (\Exception) {
// Continue to next attempt
}
}
// Attempt 3: Direct java command
if (! $serviceStarted) {
try {
$jarPath = setting('emulator_jar_path', '/var/www/Emulator');
$jarFiles = glob("{$jarPath}/*.jar");
if ($jarFiles !== [] && $jarFiles !== false) {
$this->runSystemCommand("cd {$jarPath} && nohup java -jar " . basename($jarFiles[0]) . ' > /dev/null 2>&1 &');
sleep(3);
$serviceStarted = true;
$this->info(' ✅ Emulator started (direct java)');
}
} catch (\Exception) {
$this->warn(' ⚠️ All emulator start attempts failed');
$errors[] = 'Emulator: All start attempts failed';
}
}
}
} else {
$this->info(" ✅ Emulator service '{$serviceName}' is running");
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Emulator: ' . $e->getMessage();
$this->warn(' ⚠️ Emulator error: ' . $e->getMessage());
}
// Step 15: Fix database tables
try {
$this->info('[15/16] 🗄️ Checking database tables...');
$requiredTables = ['users', 'permissions', 'website_settings', 'rooms', 'items', 'catalog_items'];
$missing = [];
foreach ($requiredTables as $table) {
if (! Schema::hasTable($table)) {
$missing[] = $table;
}
}
if ($missing !== []) {
$this->warn(' ⚠️ Missing tables: ' . implode(', ', $missing));
if ($auto || $this->confirm(' 💡 Database migrations draaien?', true)) {
$this->call('migrate', ['--force' => true]);
$this->info(' ✅ Migrations executed');
}
} else {
$this->info(' ✅ All required tables exist');
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Database tables: ' . $e->getMessage();
$this->warn(' ⚠️ Database error: ' . $e->getMessage());
}
// Step 16: Fix file permissions for critical files
try {
$this->info('[16/16] 🔐 Fixing file permissions...');
$criticalDirs = [
storage_path(),
base_path('bootstrap/cache'),
public_path('uploads'),
public_path('storage'),
base_path('app/Filament'),
base_path('app/Services'),
base_path('resources/views/filament'),
];
foreach ($criticalDirs as $dir) {
if (is_dir($dir)) {
$this->runSystemCommand("chown -R {$this->webUser}:{$this->webGroup} {$dir} 2>/dev/null");
$this->runSystemCommand("chmod -R 775 {$dir} 2>/dev/null");
}
}
$this->info(' ✅ File permissions fixed');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Permissions: ' . $e->getMessage();
$this->warn(' ⚠️ Permission error: ' . $e->getMessage());
}
// Step 17: Ensure critical database settings exist
try {
$this->info('[17/17] ⚙️ Ensuring critical settings...');
$requiredSettings = [
'emulator_jar_path' => '/var/www/Emulator',
'emulator_service_name' => 'emulator',
'emulator_version' => '4.0.5',
];
foreach ($requiredSettings as $key => $default) {
$current = setting($key);
if (empty($current)) {
WebsiteSetting::updateOrCreate(
['key' => $key],
['value' => $default],
);
$this->info(" ✅ Set {$key} = {$default}");
}
}
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Settings: ' . $e->getMessage();
$this->warn(' ⚠️ Settings error: ' . $e->getMessage());
}
// Step 18: Auto-fix everything else that can go wrong
try {
$this->info('[18/18] 🔧 Ultimate auto-fix...');
// 1. Auto-create missing database tables
$this->autoCreateMissingTables();
// 2. Auto-fix missing PHP extensions
$this->autoFixPhpExtensions();
// 3. Auto-fix file permissions (all platforms)
$this->autoFixAllPermissions();
// 4. Auto-fix configuration
$this->autoFixConfiguration();
// 5. Auto-restart all services
$this->autoRestartServices();
$this->info(' ✅ Ultimate auto-fix complete');
$fixed++;
} catch (\Exception $e) {
$errors[] = 'Ultimate fix: ' . $e->getMessage();
$this->warn(' ⚠️ Ultimate fix error: ' . $e->getMessage());
}
finalOpt:
// Final optimization
try {
$this->info('');
$this->info('🚀 Running final optimizations...');
$this->call('config:cache');
$this->call('route:cache');
$this->call('view:cache');
$this->call('event:cache');
$this->call('filament:cache-components');
$this->info(' ✅ Optimizations complete');
} catch (\Exception $e) {
$errors[] = 'Optimization: ' . $e->getMessage();
$this->warn(' ⚠️ Optimization error: ' . $e->getMessage());
}
// Summary
$this->info('');
if ($errors === []) {
$this->info('════════════════════════════════════════════════════════════');
$this->info("🎉 ATOMCMS IS 100% FIXED! ({$fixed}/17 steps completed)");
$this->info('════════════════════════════════════════════════════════════');
if (DIRECTORY_SEPARATOR === '\\') {
$this->info('');
$this->info('📝 WINDOWS INSTALLATION STEPS:');
$this->info('-----------------------------------');
$this->info('1. Install dependencies:');
$this->info(' composer install');
$this->info(' npm install');
$this->info('');
$this->info('2. Set up database:');
$this->info(' php artisan migrate --seed');
$this->info(' php artisan key:generate');
$this->info('');
$this->info('3. Build assets:');
$this->info(' npm run build:atom');
$this->info(' (For dev: npm run dev:atom)');
$this->info('');
$this->info('4. IIS: Point to public folder');
$this->info(' Grant Full control to IUSR and IIS_IUSRS');
}
} else {
$this->warn('════════════════════════════════════════════════════════════');
$this->warn('⚠️ FIXED WITH ' . count($errors) . ' WARNINGS');
$this->warn('════════════════════════════════════════════════════════════');
foreach ($errors as $error) {
$this->warn(' - ' . $error);
}
$this->info('');
$this->info('💡 Most issues are fixed. Restart your server for full effect.');
$this->info(' - Nginx/Apache: sudo systemctl restart nginx/apache');
$this->info(' - PHP-FPM: sudo systemctl restart php*-fpm');
$this->info(' - Queue: php artisan queue:work --daemon');
}
$this->info('');
return 0;
}
private function fixHttpErrors(bool $auto = false): void
{
$this->info('🔧 Fixing HTTP errors...');
// Fix 400 - Ensure proper session config
$this->fix400Errors($auto);
// Fix 403 - Permissions
$this->fix403Errors();
// Fix 404 - Routes and symlinks
$this->fix404Errors();
// Fix 419 - Session issues
$this->fix419Errors();
// Fix 500 - Common server errors
$this->fix500Errors();
// Fix 502 - PHP-FPM
$this->fix502Errors();
// Fix 503 - Maintenance and queue
$this->fix503Errors();
// Fix 504 - Timeouts
$this->fix504Errors();
$this->info('✅ HTTP errors fixed');
}
private function fix400Errors(bool $auto): void
{
// Fix sessions directory
$sessionPath = storage_path('framework/sessions');
if (! is_dir($sessionPath)) {
@mkdir($sessionPath, 0755, true);
}
@chmod($sessionPath, 0755);
// Fix POST limits
if ($auto) {
$this->repairPHPConfig();
}
}
private function fix403Errors(): void
{
// Fix permissions
$this->repairPermissions();
// Fix .htaccess
if ($this->isApache()) {
$htaccess = public_path('.htaccess');
if (! File::exists($htaccess)) {
$this->repairApache();
}
}
// Ensure public is readable
@chmod(public_path(), 0755);
}
private function fix404Errors(): void
{
// Fix storage symlink
if (! File::exists(public_path('storage'))) {
if (File::exists(public_path('storage'))) {
@unlink(public_path('storage'));
}
$this->call('storage:link');
}
// Generate nginx config if needed
if ($this->isNginx()) {
$nginxConfig = base_path('atom-nginx.conf');
if (! File::exists($nginxConfig)) {
$this->repairNginx();
}
}
// Clear routes cache
$this->call('route:clear');
}
private function fix419Errors(): void
{
// Clear sessions
$sessionPath = storage_path('framework/sessions');
if (is_dir($sessionPath)) {
$files = glob($sessionPath . '/*');
foreach ($files as $file) {
if (is_file($file)) {
@unlink($file);
}
}
}
// Clear cache
$this->call('cache:clear');
// Clear session files
$sessionPath = storage_path('framework/sessions');
if (is_dir($sessionPath)) {
foreach (glob("$sessionPath/*") as $file) {
if (is_file($file)) {
@unlink($file);
}
}
}
}
private function fix500Errors(): void
{
// Clear all caches
$this->call('cache:clear');
$this->call('view:clear');
$this->call('route:clear');
$this->call('config:clear');
$this->call('clear-compiled');
// Fix permissions
$this->repairPermissions();
// Ensure bootstrap/cache is writable
$bootstrapCache = base_path('bootstrap/cache');
@chmod($bootstrapCache, 0755);
// Fix database
try {
DB::connection()->getPdo();
} catch (\Exception) {
// Can't fix database connection automatically
}
}
private function fix502Errors(): void
{
$phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
// Check if PHP-FPM is running
$isWindows = DIRECTORY_SEPARATOR === '\\';
if ($isWindows) {
$this->info(' ⚠️ PHP-FPM on Windows - check your PHP setup');
return;
}
// Try to restart PHP-FPM
$this->info(' 🔄 Restarting PHP-FPM...');
// Try different service names
$services = [
"php{$phpVersion}-fpm",
'php-fpm',
"php{$phpVersion}-fpm.service",
];
$restarted = false;
foreach ($services as $service) {
exec("sudo systemctl restart {$service} 2>/dev/null", $out, $code);
if ($code === 0) {
$this->info(" ✅ Restarted PHP-FPM ({$service})");
$restarted = true;
break;
}
}
if (! $restarted) {
// Try to start if not running
exec('sudo systemctl start php-fpm 2>/dev/null');
exec("sudo systemctl start php{$phpVersion}-fpm 2>/dev/null");
$this->info(' ⚠️ Could not auto-restart PHP-FPM');
$this->info(' 💡 Try manually: sudo systemctl restart php-fpm');
}
// Check socket permissions
$socketPaths = [
"/var/run/php/php{$phpVersion}-fpm.sock",
'/var/run/php-fpm.sock',
"/run/php/php{$phpVersion}-fpm.sock",
];
foreach ($socketPaths as $path) {
if (File::exists($path)) {
@chmod($path, 0666);
$this->info(" ✅ Fixed socket: {$path}");
}
}
}
private function fix503Errors(): void
{
// Disable maintenance mode
$maintenanceFile = storage_path('framework/maintenance.php');
if (File::exists($maintenanceFile)) {
@unlink($maintenanceFile);
}
// Restart queue workers
$isWindows = DIRECTORY_SEPARATOR === '\\';
if (! $isWindows) {
@exec("pkill -f 'queue:work' 2>/dev/null");
$this->info('Queue workers stopped - restart with supervisor');
} else {
$this->info('Queue workers managed manually on Windows');
}
// Check database
try {
DB::connection()->getPdo();
} catch (\Exception) {
$this->warn('Database unavailable');
}
}
private function fix504Errors(): void
{
// Increase PHP timeout
$iniPath = php_ini_loaded_file();
if (! in_array($iniPath, ['', '0', false], true) && is_writable($iniPath)) {
$content = File::get($iniPath);
if (! str_contains($content, 'max_execution_time')) {
$content .= "\nmax_execution_time = 300";
File::put($iniPath, $content);
}
}
// Increase PHP-FPM timeout in nginx
if ($this->isNginx()) {
$nginxConfig = base_path('atom-nginx.conf');
if (File::exists($nginxConfig)) {
$content = File::get($nginxConfig);
if (! str_contains($content, 'fastcgi_read_timeout')) {
$content = str_replace('location ~ \.php$ {', "location ~ \.php$ {\n fastcgi_read_timeout 300;", $content);
File::put($nginxConfig, $content);
}
}
}
}
private function backupDatabase(): void
{
$this->info('📦 Starting database backup...');
$filename = 'backup-' . date('Y-m-d-H-i-s') . '.sql';
$path = storage_path('app/backups');
if (! File::exists($path)) {
File::makeDirectory($path, 0755, true);
}
$config = config('database.connections.mysql');
if ($this->isWindows()) {
$this->addMysqlToPath();
}
if (! $this->checkCommandExists('mysqldump')) {
$this->error('❌ mysqldump is not installed or not in PATH');
return;
}
$passPart = empty($config['password']) ? '' : '--password=' . escapeshellarg((string) $config['password']);
$command = sprintf('mysqldump --user=%s %s --host=%s --port=%s %s > %s/%s',
escapeshellarg((string) $config['username']), $passPart, escapeshellarg((string) $config['host']),
escapeshellarg((string) $config['port']), escapeshellarg((string) $config['database']),
escapeshellarg($path), escapeshellarg($filename),
);
$result = $this->runSystemCommand($command);
if ($result['success']) {
$this->info("✅ Backup created: {$filename}");
} else {
$this->error('❌ Backup failed: ' . ($result['output'] ?: 'Unknown error'));
}
}
// --- UTILS ---
private function detectPlatform(): string
{
if (DIRECTORY_SEPARATOR === '\\') {
return $this->detectWindowsStack();
}
$soft = $_SERVER['SERVER_SOFTWARE'] ?? '';
if (str_contains((string) $soft, 'Nginx')) {
return 'nginx';
}
if (str_contains((string) $soft, 'Apache')) {
return 'apache';
}
if (str_contains((string) $soft, 'Microsoft-IIS')) {
return 'iis';
}
return 'linux';
}
private function detectEmulatorService(): string
{
// Common emulator service names
$possibleNames = [
'emulator',
'arcturus',
'arcturus-emulator',
'arcturus-morningstar',
'arcturus-ms',
'nitro',
'hotel',
'habbo',
'retro',
];
// Check setting first
$settingName = setting('emulator_service_name', '');
if (! empty($settingName)) {
$result = $this->runSystemCommand("systemctl list-unit-files {$settingName}.service 2>/dev/null | grep -q {$settingName} && echo 'exists'");
if (trim((string) $result['output']) === 'exists') {
return $settingName;
}
}
// Check each possible name
foreach ($possibleNames as $name) {
$result = $this->runSystemCommand("systemctl list-unit-files {$name}.service 2>/dev/null | grep -q {$name} && echo 'exists'");
if (trim((string) $result['output']) === 'exists') {
return $name;
}
}
// Check for Java processes running emulator JARs
$result = $this->runSystemCommand("ps aux | grep -i 'java.*jar' | grep -v grep | awk '{print $11}' | head -1");
if (! in_array(trim((string) $result['output']), ['', '0'], true)) {
// Try to find service from running process
$result2 = $this->runSystemCommand("systemctl list-units --type=service --state=running | grep -i 'emul\\|arct\\|nitro\\|hotel' | awk '{print $1}' | sed 's/.service//' | head -1");
if (! in_array(trim((string) $result2['output']), ['', '0'], true)) {
return trim((string) $result2['output']);
}
}
// Check all running services for Java-based services
$result = $this->runSystemCommand('systemctl list-units --type=service --state=running 2>/dev/null');
$lines = explode("\n", $result['output'] ?? '');
foreach ($lines as $line) {
if (str_contains(strtolower($line), 'java') || str_contains(strtolower($line), 'jar')) {
preg_match('/(\S+)\.service/', $line, $matches);
if (isset($matches[1]) && ($matches[1] !== '' && $matches[1] !== '0')) {
return $matches[1];
}
}
}
// Default fallback
return 'emulator';
}
private function detectWindowsStack(): string
{
$soft = $_SERVER['SERVER_SOFTWARE'] ?? '';
if (str_contains((string) $soft, 'Microsoft-IIS')) {
return 'iis';
}
if (str_contains((string) $soft, 'Apache')) {
return 'wamp';
}
if (str_contains((string) $soft, 'nginx')) {
return 'nginx';
}
$processList = shell_exec('tasklist 2>nul') ?: '';
if (str_contains($processList, 'apache')) {
return 'wamp';
}
if (str_contains($processList, 'nginx')) {
return 'nginx';
}
if (str_contains($processList, 'httpd')) {
return 'wamp';
}
return 'windows';
}
private function isXampp(): bool
{
return $this->platform === 'xampp';
}
private function isWamp(): bool
{
return $this->platform === 'wamp';
}
private function autoCreateMissingTables(): void
{
$this->info(' 🔍 Checking for missing tables...');
// Run migrations to create missing tables
try {
$this->call('migrate', ['--force' => true]);
$this->info(' ✅ Missing tables created');
} catch (\Exception) {
// Try with skip duplicates
try {
$this->runMigrationsWithSkip();
$this->info(' ✅ Missing tables created (with skips)');
} catch (\Exception) {
// Last resort: create essential tables manually
$this->createEssentialTables();
}
}
}
private function createEssentialTables(): void
{
$essentialTables = [
'users' => "CREATE TABLE IF NOT EXISTS `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`rank` int(11) NOT NULL DEFAULT 1,
`credits` int(11) NOT NULL DEFAULT 0,
`pixels` int(11) NOT NULL DEFAULT 0,
`points` int(11) NOT NULL DEFAULT 0,
`online` enum('0','1','2') NOT NULL DEFAULT '0',
`last_login` int(11) NOT NULL DEFAULT 0,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_username_unique` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
'permissions' => "CREATE TABLE IF NOT EXISTS `permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rank_name` varchar(255) NOT NULL,
`rank_level` int(11) NOT NULL DEFAULT 1,
`acc_placefurni` enum('0','1') NOT NULL DEFAULT '0',
`acc_moverotate` enum('0','1') NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
'website_settings' => 'CREATE TABLE IF NOT EXISTS `website_settings` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(255) NOT NULL,
`value` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `website_settings_key_unique` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
];
foreach ($essentialTables as $table => $sql) {
try {
DB::statement($sql);
$this->info(" ✅ Created table: {$table}");
} catch (\Exception) {
// Table might already exist
}
}
}
private function autoFixPhpExtensions(): void
{
$this->info(' 🔍 Checking PHP extensions...');
$required = ['curl', 'mbstring', 'pdo_mysql', 'xml', 'bcmath', 'openssl', 'gd', 'zip', 'intl', 'redis'];
foreach ($required as $ext) {
if (! extension_loaded($ext)) {
$this->info(" ⚠️ Missing extension: {$ext}");
// Try to install on Linux
if ($this->isLinux()) {
$this->runSystemCommand("sudo apt-get install -y php-{$ext} 2>/dev/null");
}
}
}
$this->info(' ✅ PHP extensions checked');
}
private function autoFixAllPermissions(): void
{
$this->info(' 🔍 Fixing all permissions...');
$dirs = [
storage_path(),
base_path('bootstrap/cache'),
public_path('uploads'),
public_path('storage'),
];
foreach ($dirs as $dir) {
if (is_dir($dir)) {
if ($this->isWindows()) {
@chmod($dir, 0777);
} else {
$this->runSystemCommand("sudo chmod -R 775 {$dir} 2>/dev/null");
$this->runSystemCommand("sudo chown -R {$this->webUser}:{$this->webGroup} {$dir} 2>/dev/null");
}
}
}
$this->info(' ✅ All permissions fixed');
}
private function autoFixConfiguration(): void
{
$this->info(' 🔍 Checking configuration...');
// Ensure .env exists
if (! File::exists(base_path('.env')) && File::exists(base_path('.env.example'))) {
File::copy(base_path('.env.example'), base_path('.env'));
$this->call('key:generate', ['--force' => true]);
$this->info(' ✅ .env created');
}
// Ensure APP_KEY is set
if (empty(config('app.key'))) {
$this->call('key:generate', ['--force' => true]);
$this->info(' ✅ APP_KEY generated');
}
$this->info(' ✅ Configuration checked');
}
private function autoRestartServices(): void
{
$this->info(' 🔍 Restarting services...');
if ($this->isLinux()) {
// Restart PHP-FPM
$phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
$this->runSystemCommand("sudo systemctl restart php{$phpVersion}-fpm 2>/dev/null");
// Restart web server
if ($this->isNginx()) {
$this->runSystemCommand('sudo systemctl restart nginx 2>/dev/null');
} elseif ($this->isApache()) {
$this->runSystemCommand('sudo systemctl restart apache2 2>/dev/null');
}
// Restart emulator
$serviceName = setting('emulator_service_name', 'emulator');
$this->runSystemCommand("sudo systemctl restart {$serviceName} 2>/dev/null");
}
$this->info(' ✅ Services restarted');
}
private function detectWebUserContext(): string
{
if ($this->isWindows()) {
if ($this->isXampp() || $this->isWamp()) {
$this->webUser = get_current_user();
$this->webGroup = 'Administrators';
} else {
$this->webUser = 'SYSTEM';
$this->webGroup = 'SYSTEM';
}
return $this->webUser;
}
$candidates = ['www-data', 'nginx', 'apache', 'http', 'www'];
$passwd = @file_get_contents('/etc/passwd') ?: '';
foreach ($candidates as $user) {
if (str_contains($passwd, $user)) {
$this->webUser = $user;
$this->webGroup = $user;
return $user;
}
}
$whoami = $this->runSystemCommand('whoami', true);
return $this->webUser = $whoami['success'] ? trim((string) $whoami['output']) : 'www-data';
}
private function isWindows(): bool
{
return $this->platform === 'windows';
}
private function isLinux(): bool
{
return ! $this->isWindows();
}
private function isApache(): bool
{
return $this->platform === 'apache' || str_contains($_SERVER['SERVER_SOFTWARE'] ?? '', 'Apache');
}
private function isIIS(): bool
{
return str_contains($_SERVER['SERVER_SOFTWARE'] ?? '', 'Microsoft-IIS') || $this->platform === 'iis';
}
private function isNginx(): bool
{
return $this->platform === 'nginx' || str_contains($_SERVER['SERVER_SOFTWARE'] ?? '', 'Nginx');
}
private function addCheck(string $n, string $s, string $m): void
{
$this->checks[] = [$n, $s, $m];
if ($s === '❌') {
$this->errors++;
}
if ($s === '⚠️') {
$this->warnings++;
}
}
private function hasError(string $p): bool
{
return array_any($this->checks, fn ($c) => str_contains((string) $c[0], $p) && $c[1] === '❌');
}
private function hasWarning(string $p): bool
{
return array_any($this->checks, fn ($c) => str_contains((string) $c[0], $p) && $c[1] === '⚠️');
}
private function displaySummary(): void
{
$this->newLine();
// Recount errors from checks (not from internal increments)
$realErrors = 0;
$realWarnings = 0;
foreach ($this->checks as $check) {
if ($check[1] === '❌') {
$realErrors++;
}
if ($check[1] === '⚠️') {
$realWarnings++;
}
}
$this->table(['Feature', 'Status', 'Message'], $this->checks);
if ($realErrors > 0) {
$this->error("\nFound {$realErrors} errors! Run with --fix.");
} elseif ($realWarnings > 0) {
$this->warn("\nAll OK! ({$realWarnings} warnings)");
} else {
$this->info("\n🎉 Everything looks perfect!");
}
}
private function runSystemCommand(string $command, bool $silent = false): array
{
$output = [];
$exitCode = 0;
if ($this->isWindows()) {
exec($command . ' 2>NUL', $output, $exitCode);
} else {
exec($command . ' 2>&1', $output, $exitCode);
}
$result = [
'success' => $exitCode === 0,
'output' => implode("\n", $output),
'exit_code' => $exitCode,
'command' => $command,
];
if (! $silent && ! $result['success'] && $output !== []) {
$this->warn("Command failed: {$command}");
$this->warn('Output: ' . $result['output']);
}
return $result;
}
private function runSystemCommandWithRetry(string $command, int $retries = 3, int $delayMs = 500): array
{
for ($i = 0; $i < $retries; $i++) {
$result = $this->runSystemCommand($command, true);
if ($result['success']) {
return $result;
}
if ($i < $retries - 1) {
usleep($delayMs * 1000);
}
}
return $this->runSystemCommand($command, false);
}
private function checkCommandExists(string $command): bool
{
$which = $this->isWindows() ? 'where' : 'which';
$result = $this->runSystemCommand("{$which} {$command}", true);
return $result['success'] && ! in_array(trim((string) $result['output']), ['', '0'], true);
}
private function findMysqldump(): ?string
{
if (! $this->isWindows()) {
return null;
}
$possiblePaths = [
'C:\xampp\mysql\bin\mysqldump.exe',
'C:\xampp\mysql\bin\mysqldump',
'D:\xampp\mysql\bin\mysqldump.exe',
'D:\xampp\mysql\bin\mysqldump',
'C:\nginx\mysql\bin\mysqldump.exe',
'C:\nginx\mysql\bin\mysqldump',
'D:\nginx\mysql\bin\mysqldump.exe',
'D:\nginx\mysql\bin\mysqldump',
'C:\phpmysql\mysqldump.exe',
'C:\mysql\bin\mysqldump.exe',
'C:\Program Files\MariaDB 11.8\bin\mysqldump.exe',
'C:\Program Files\MariaDB 11.8\bin\mariadb-dump.exe',
'C:\Program Files (x86)\MariaDB 11.8\bin\mysqldump.exe',
getenv('USERPROFILE') . '\xampp\mysql\bin\mysqldump.exe',
getenv('USERPROFILE') . '\xampp\mysql\bin\mysqldump',
getenv('USERPROFILE') . '\nginx\mysql\bin\mysqldump.exe',
'C:\wamp\bin\mysql\mysql5.7.31\bin\mysqldump.exe',
'C:\wamp64\bin\mysql\mysql5.7.31\bin\mysqldump.exe',
'C:\laragon\bin\mysql\mysql-5.7.40-winx64\bin\mysqldump.exe',
'C:\laragon\bin\mysql\mysql-8.0.30-winx64\bin\mysqldump.exe',
];
foreach ($possiblePaths as $path) {
if (file_exists($path)) {
return $path;
}
}
return null;
}
private function findMysql(): ?string
{
if (! $this->isWindows()) {
return null;
}
$possiblePaths = [
'C:\xampp\mysql\bin\mysql.exe',
'C:\xampp\mysql\bin\mysql',
'D:\xampp\mysql\bin\mysql.exe',
'D:\xampp\mysql\bin\mysql',
'C:\nginx\mysql\bin\mysql.exe',
'C:\nginx\mysql\bin\mysql',
'D:\nginx\mysql\bin\mysql.exe',
'D:\nginx\mysql\bin\mysql',
'C:\phpmysql\mysql.exe',
'C:\mysql\bin\mysql.exe',
'C:\Program Files\MariaDB 11.8\bin\mysql.exe',
'C:\Program Files\MariaDB 11.8\bin\mariadb.exe',
'C:\Program Files (x86)\MariaDB 11.8\bin\mysql.exe',
'C:\Program Files (x86)\MariaDB 11.8\bin\mariadb.exe',
getenv('USERPROFILE') . '\xampp\mysql\bin\mysql.exe',
getenv('USERPROFILE') . '\xampp\mysql\bin\mysql',
getenv('USERPROFILE') . '\nginx\mysql\bin\mysql.exe',
'C:\wamp\bin\mysql\mysql5.7.31\bin\mysql.exe',
'C:\wamp64\bin\mysql\mysql5.7.31\bin\mysql.exe',
'C:\laragon\bin\mysql\mysql-5.7.40-winx64\bin\mysql.exe',
'C:\laragon\bin\mysql\mysql-8.0.30-winx64\bin\mysql.exe',
];
foreach ($possiblePaths as $path) {
if (file_exists($path)) {
return $path;
}
}
return null;
}
private function addMysqlToPath(): bool
{
if (! $this->isWindows()) {
return false;
}
$mysqlPath = dirname($this->findMysql() ?: $this->findMysqldump() ?: '');
if (in_array($mysqlPath, ['', '0', '.', '\\'], true)) {
return false;
}
$currentPath = getenv('PATH') ?: '';
if (str_contains($currentPath, $mysqlPath)) {
return true;
}
$newPath = $currentPath . ';' . $mysqlPath;
putenv("PATH={$newPath}");
return true;
}
private function fixEnvFile(): void
{
$this->info('🔧 Fixing .env file...');
$envPath = base_path('.env');
if (! File::exists($envPath)) {
$this->error('❌ .env file not found!');
return;
}
$currentContent = File::get($envPath);
// Check if .env is on single line (broken) OR if user wants to regenerate
$isBroken = ! str_contains($currentContent, "\n") && strlen($currentContent) > 500;
$this->info('Current .env status: ' . ($isBroken ? 'BROKEN (single line)' : 'OK'));
if ($isBroken || $this->confirm('Regenerate .env file with new settings?', true)) {
if ($isBroken) {
$this->warn('⚠️ .env file is on a single line - fixing...');
}
// Ask for site URL
$siteUrl = $this->ask('What is your site URL?', 'http://localhost');
// Ask for database credentials
$dbHost = $this->ask('Database host?', '127.0.0.1');
$dbPort = $this->ask('Database port?', '3306');
$dbDatabase = $this->ask('Database name?', 'habbo');
$dbUsername = $this->ask('Database username?', 'root');
$dbPassword = $this->secret('Database password?');
// Ask about Redis
$useRedis = $this->confirm('Use Redis for caching/queue?', false);
// Ask about session driver
$sessionDriver = $this->ask('Session driver? (database/file)', 'database');
// Generate the fixed .env
$newContent = $this->generateFixedEnv($siteUrl, $dbHost, $dbPort, $dbDatabase, $dbUsername, $dbPassword, $sessionDriver, $useRedis);
File::put($envPath, $newContent);
$this->info('✅ .env file fixed!');
} else {
$this->info('✅ .env file looks good!');
}
// Clear config cache to reload .env
$this->call('config:clear');
// Generate key if missing
try {
$key = config('app.key');
if (empty($key)) {
$this->call('key:generate', ['--force' => true]);
$this->info('✅ App key generated');
}
} catch (\Exception) {
$this->call('key:generate', ['--force' => true]);
$this->info('✅ App key generated');
}
}
private function generateFixedEnv(string $siteUrl, string $dbHost, string $dbPort, string $dbDatabase, string $dbUsername, string $dbPassword, string $sessionDriver, bool $useRedis): string
{
$redisConfig = '';
$cacheDriver = 'file';
$broadcastDriver = 'log';
$queueConnection = 'sync';
if ($useRedis) {
$redisConfig = '
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
';
$cacheDriver = 'redis';
$broadcastDriver = 'redis';
$queueConnection = 'redis';
}
return "APP_NAME=\"Atom CMS\"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL={$siteUrl}
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST={$dbHost}
DB_PORT={$dbPort}
DB_DATABASE={$dbDatabase}
DB_USERNAME={$dbUsername}
DB_PASSWORD={$dbPassword}
BROADCAST_DRIVER={$broadcastDriver}
CACHE_DRIVER={$cacheDriver}
FILESYSTEM_DISK=local
QUEUE_CONNECTION={$queueConnection}
SESSION_DRIVER={$sessionDriver}
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=\"hello@example.com\"
MAIL_FROM_NAME=\"\${APP_NAME}\"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY=\"\${PUSHER_APP_KEY}\"
VITE_PUSHER_HOST=\"\${PUSHER_HOST}\"
VITE_PUSHER_PORT=\"\${PUSHER_PORT}\"
VITE_PUSHER_SCHEME=\"\${PUSHER_SCHEME}\"
VITE_PUSHER_APP_CLUSTER=\"\${PUSHER_APP_CLUSTER}\"
RCON_HOST=127.0.0.1
RCON_PORT=3001
FINDRETROS_NAME=
FINDRETROS_ENABLED=false
GOOGLE_RECAPTCHA_SITE_KEY=
GOOGLE_RECAPTCHA_SECRET_KEY=
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
RENAME_COLLIDING_TABLES=false
FLASH_CLIENT_ENABLED=false
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
SWF_BASE_PATH=client/flash
HABBO_SWF=Habbo.swf
PRODUCTION_FOLDER=gordon/PRODUCTION
EXTERNAL_FURNIDATA=gamedata/furnidata.xml
EXTERNAL_FIGUREMAP=gamedata/figuremap.xml
EXTERNAL_FIGUREDATA=gamedata/figuredata.xml
EXTERNAL_PRODUCTDATA=gamedata/productdata.txt
EXTERNAL_TEXTS=gamedata/external_flash_texts.txt
EXTERNAL_VARIABLES=gamedata/external_variables.txt
EXTERNAL_OVERRIDE_TEXTS=gamedata/override/external_flash_override_texts.txt
EXTERNAL_OVERRIDE_VARIABLES=gamedata/override/external_override_variables.txt
CONVERT_PASSWORDS=false
FORCE_HTTPS=false
APP_LOCALE=en
PASSWORD_RESET_TOKEN_TIME=15
PAYPAL_MODE='sandbox'
PAYPAL_PAYMENT_ACTION='Order'
PAYPAL_CURRENCY='USD'
PAYPAL_NOTIFY_URL=
PAYPAL_LOCALE='en_US'
PAYPAL_VALIDATE_SSL=true
PAYPAL_SANDBOX_CLIENT_ID=
PAYPAL_SANDBOX_CLIENT_SECRET=
PAYPAL_SANDBOX_APP_ID=
PAYPAL_LIVE_CLIENT_ID=
PAYPAL_LIVE_CLIENT_SECRET=
PAYPAL_LIVE_APP_ID=
FORTIFY_PREFIX=
";
}
private function getDefaultEnvTemplate(): string
{
return 'APP_NAME="Atom CMS"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=habbo
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=database
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
RCON_HOST=127.0.0.1
RCON_PORT=3001
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
FLASH_CLIENT_ENABLED=false
SWF_BASE_PATH=client/flash
HABBO_SWF=Habbo.swf
PRODUCTION_FOLDER=gordon/PRODUCTION
APP_LOCALE=en
FORCE_HTTPS=false
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
GOOGLE_RECAPTCHA_SITE_KEY=
GOOGLE_RECAPTCHA_SECRET_KEY=
PAYPAL_MODE=
';
}
private function runMigrationsWithSkip(): void
{
$ranMigrations = DB::table('migrations')->pluck('migration')->toArray();
$migrationFiles = File::files(base_path('database/migrations'));
$pendingMigrations = [];
foreach ($migrationFiles as $file) {
$migrationName = str_replace('.php', '', $file->getFilename());
if (! in_array($migrationName, $ranMigrations)) {
$pendingMigrations[] = $migrationName;
}
}
if ($pendingMigrations === []) {
$this->info('⏭️ No pending migrations (all already ran)');
} else {
$this->info('📦 Running ' . count($pendingMigrations) . ' new migrations...');
$this->call('migrate', ['--force' => true]);
}
}
private function runFullSetup(): int
{
$this->info('════════════════════════════════════════════════════════════');
$this->info('🚀 RUNNING FULL ATOM CMS SETUP');
$this->info('════════════════════════════════════════════════════════════');
$this->info('');
$fixed = 0;
$errors = [];
// Step 1: Fix .env if needed
$this->info('[1/6] 📝 Checking .env file...');
try {
$envContent = file_get_contents(base_path('.env'));
if (! str_contains($envContent, "\n") && strlen($envContent) > 500) {
$this->warn(' .env is broken - use --fix-env first!');
$errors[] = '.env file needs fixing (run: atom:check --fix-env)';
} else {
$this->info(' ✅ .env looks good');
$fixed++;
}
} catch (\Exception $e) {
$this->error(' ❌ .env error: ' . $e->getMessage());
$errors[] = '.env: ' . $e->getMessage();
}
// Step 2: Clear caches
$this->info('[2/6] 💨 Clearing caches...');
try {
$this->call('cache:clear');
$this->call('config:clear');
$this->call('route:clear');
$this->call('view:clear');
$this->info(' ✅ Caches cleared');
$fixed++;
} catch (\Exception $e) {
$this->error(' ❌ Cache clear failed: ' . $e->getMessage());
$errors[] = 'Cache: ' . $e->getMessage();
}
// Step 3: Run migrations
$this->info('[3/6] 🗄️ Running migrations...');
try {
$isWindows = DIRECTORY_SEPARATOR === '\\';
if ($isWindows) {
$this->addMysqlToPath();
}
$this->call('migrate', ['--force' => true]);
$this->info(' ✅ Migrations complete');
$fixed++;
} catch (\Exception $e) {
$this->error(' ❌ Migration failed: ' . $e->getMessage());
$errors[] = 'Migrations: ' . $e->getMessage();
}
// Step 4: Run seeders
$this->info('[4/6] 🌱 Running seeders...');
try {
$this->call('db:seed', ['--force' => true]);
$this->info(' ✅ Seeders complete');
$fixed++;
} catch (\Exception $e) {
$this->warn(' ⚠️ Seeder warning: ' . $e->getMessage());
}
// Step 5: Storage link
$this->info('[5/6] 🔗 Setting up storage...');
try {
if (File::exists(public_path('storage'))) {
@unlink(public_path('storage'));
}
$this->call('storage:link');
$this->info(' ✅ Storage linked');
$fixed++;
} catch (\Exception $e) {
$this->error(' ❌ Storage link failed: ' . $e->getMessage());
$errors[] = 'Storage: ' . $e->getMessage();
}
// Step 6: Build assets
$this->info('[6/6] 🎨 Building assets...');
$isWindows = DIRECTORY_SEPARATOR === '\\';
try {
if ($isWindows) {
$this->info(' ⏭️ Skipped on Windows - run manually: npm install && npm run build');
} else {
$manager = 'npm';
$cmd = 'npm install && npm run build';
if (File::exists(base_path('yarn.lock'))) {
$manager = 'yarn';
$cmd = 'yarn && yarn build';
} elseif (File::exists(base_path('pnpm-lock.yaml'))) {
$manager = 'pnpm';
$cmd = 'pnpm install && pnpm build';
}
$this->info(" Building with {$manager}...");
exec("{$cmd} 2>&1", $output, $exitCode);
if ($exitCode === 0) {
$this->info(' ✅ Assets built');
$fixed++;
} else {
$this->warn(' ⚠️ Build failed - try manually');
}
}
} catch (\Exception $e) {
$this->warn(' ⚠️ Build warning: ' . $e->getMessage());
}
// Final optimizations
$this->info('');
$this->info('🔧 Running optimizations...');
try {
$this->call('config:cache');
$this->call('route:cache');
$this->call('view:cache');
$this->info(' ✅ Optimizations complete');
} catch (\Exception) {
// Ignore
}
// Summary
$this->info('');
$this->info('════════════════════════════════════════════════════════════');
if ($errors === []) {
$this->info("🎉 SETUP COMPLETE! ({$fixed}/6 steps)");
$this->info('════════════════════════════════════════════════════════════');
$this->info('');
$this->info('📝 NEXT STEPS:');
$this->info(' 1. Start your server (nginx/apache/php artisan serve)');
$this->info(' 2. Visit your site URL');
$this->info(' 3. Login to Filament admin: /admin');
} else {
$this->warn('⚠️ SETUP COMPLETED WITH ERRORS:');
$this->warn('════════════════════════════════════════════════════════════');
foreach ($errors as $error) {
$this->warn(' - ' . $error);
}
$this->info('');
$this->info('💡 Try fixing errors and run again');
}
return $errors === [] ? 0 : 1;
}
private function fixHttpsConfiguration(): void
{
$envPath = base_path('.env');
$envContent = File::get($envPath);
$appUrl = config('app.url');
$isHttps = str_starts_with((string) $appUrl, 'https');
if (! $isHttps && str_starts_with((string) $appUrl, 'http://')) {
$newAppUrl = str_replace('http://', 'https://', $appUrl);
$envContent = preg_replace(
'/^APP_URL=.*$/m',
'APP_URL=' . $newAppUrl,
$envContent,
);
File::put($envPath, $envContent);
$this->info(' ✅ APP_URL updated to HTTPS');
}
$forceHttpsPattern = '/^FORCE_HTTPS=.*$/m';
if (preg_match($forceHttpsPattern, (string) $envContent)) {
$envContent = preg_replace('/^FORCE_HTTPS=.*$/m', 'FORCE_HTTPS=true', (string) $envContent);
} else {
$envContent .= "\nFORCE_HTTPS=true";
}
File::put($envPath, $envContent);
$this->info(' ✅ FORCE_HTTPS=true added to .env');
if ($this->isNginx()) {
$this->fixNginxHttpsRedirect();
}
$this->call('config:clear');
$this->info(' ✅ Configuration cleared');
}
private function fixNginxHttpsRedirect(): void
{
$nginxConfigs = glob('/etc/nginx/sites-*/*');
$domain = parse_url((string) config('app.url'), PHP_URL_HOST);
foreach ($nginxConfigs as $config) {
if (is_file($config) && is_readable($config)) {
$content = File::get($config);
if (str_contains($content, $domain)) {
$hasHttpsRedirect = str_contains($content, 'return 301 https://');
$hasHttpBlock = str_contains($content, 'listen 80');
if ($hasHttpBlock && ! $hasHttpsRedirect) {
$this->info(' ⚠️ Found nginx config but no HTTP->HTTPS redirect');
$this->info(' 💡 Add to your nginx config for port 80:');
$this->info(' return 301 https://$server_name$request_uri;');
}
return;
}
}
}
$this->info(' ⚠️ Could not find nginx config automatically');
$this->info(' 💡 Create /etc/nginx/sites-available/your-site with HTTP->HTTPS redirect');
}
private function fixFilament(): void
{
$platform = $this->detectPlatform();
$this->info(' 🧹 Clearing Filament caches...');
// Clear all Laravel caches
$this->call('cache:clear');
$this->call('config:clear');
$this->call('route:clear');
$this->call('view:clear');
$this->call('event:clear');
$this->call('optimize:clear');
$this->info(' 🎨 Publishing Filament assets...');
// Publish and install Filament
try {
$this->call('filament:install', ['--force' => true]);
} catch (\Exception $e) {
$this->warn(' ⚠️ Filament install warning: ' . $e->getMessage());
}
// Platform-specific fixes
if ($this->isLinux()) {
$this->info(' 🐧 Applying Linux-specific fixes...');
// Fix permissions for storage
$this->fixStoragePermissions();
// Clear OPcache if available
if (function_exists('opcache_get_status') && opcache_get_status() !== false) {
@opcache_reset();
$this->info(' ✅ OPcache cleared');
}
} elseif ($this->isWindows()) {
$this->info(' 🪟 Applying Windows-specific fixes...');
// On Windows, just ensure storage is writable
$this->fixStoragePermissions();
}
// Rebuild caches
$this->info(' 💾 Rebuilding caches...');
$this->call('config:cache');
$this->call('route:cache');
$this->call('view:cache');
$this->call('event:cache');
$this->call('optimize');
// Web server specific fixes
if ($platform === 'nginx') {
$this->info(' 🌐 Fixing Nginx configuration...');
$this->fixNginxConfig();
} elseif (in_array($platform, ['apache', 'wamp', 'xampp'], true)) {
$this->info(' 🌐 Fixing Apache configuration...');
$this->fixApacheConfig();
}
$this->info(' ✅ Filament repair complete for ' . strtoupper($platform));
}
private function fixStoragePermissions(): void
{
$directories = [
storage_path('app'),
storage_path('app/public'),
storage_path('framework/cache'),
storage_path('framework/cache/data'),
storage_path('framework/sessions'),
storage_path('framework/views'),
storage_path('logs'),
];
foreach ($directories as $dir) {
if (is_dir($dir)) {
@chmod($dir, 0755);
}
}
}
private function fixNginxConfig(): void
{
// Check if nginx config has proper fastcgi settings
$nginxConf = '/etc/nginx/nginx.conf';
if (file_exists($nginxConf)) {
$content = file_get_contents($nginxConf);
if (! str_contains($content, 'fastcgi_buffer_size')) {
$this->warn(' ⚠️ Consider adding fastcgi_buffer_size to nginx.conf for better performance');
}
}
}
private function fixApacheConfig(): void
{
// Ensure .htaccess is properly configured
$htaccess = public_path('.htaccess');
if (file_exists($htaccess)) {
$content = file_get_contents($htaccess);
// Check for required directives
if (! str_contains($content, 'Options -MultiViews')) {
$this->warn(' ⚠️ Consider adding "Options -MultiViews" to .htaccess');
}
}
}
private function fixFilamentPages(): void
{
$this->info('🔧 Checking and fixing missing Filament pages, widgets and views...');
// Define ALL expected files: [source path in git, target local path]
$files = [
// AlertSettings page
[
'git' => 'app/Filament/Pages/Monitoring/AlertSettings.php',
'local' => base_path('app/Filament/Pages/Monitoring/AlertSettings.php'),
],
[
'git' => 'resources/views/filament/pages/monitoring/alert-settings.blade.php',
'local' => base_path('resources/views/filament/pages/monitoring/alert-settings.blade.php'),
],
// UpdateCheckerWidget
[
'git' => 'app/Filament/Widgets/UpdateCheckerWidget.php',
'local' => base_path('app/Filament/Widgets/UpdateCheckerWidget.php'),
],
[
'git' => 'resources/views/filament/widgets/update-checker.blade.php',
'local' => base_path('resources/views/filament/widgets/update-checker.blade.php'),
],
// Services
[
'git' => 'app/Services/EmulatorUpdateService.php',
'local' => base_path('app/Services/EmulatorUpdateService.php'),
],
[
'git' => 'app/Services/NitroUpdateService.php',
'local' => base_path('app/Services/NitroUpdateService.php'),
],
[
'git' => 'app/Services/RconService.php',
'local' => base_path('app/Services/RconService.php'),
],
[
'git' => 'app/Services/AlertService.php',
'local' => base_path('app/Services/AlertService.php'),
],
[
'git' => 'app/Services/AlertSettings.php',
'local' => base_path('app/Services/AlertSettings.php'),
],
];
foreach ($files as $file) {
if (! file_exists($file['local'])) {
$this->info(" Looking for {$file['git']} in git history...");
$content = null;
$result = $this->runSystemCommand("git show HEAD:{$file['git']}");
if ($result['success'] && ! empty($result['output'])) {
$content = $result['output'];
} else {
$this->warn(" Could not find {$file['git']} in git HEAD. Skipping.");
continue;
}
if ($content !== null) {
$dir = dirname($file['local']);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
$this->info(" Created directory: {$dir}");
}
if (file_put_contents($file['local'], $content) !== false) {
$this->info(" ✅ Restored {$file['local']}");
} else {
$this->error(" ❌ Failed to write {$file['local']}");
}
}
} else {
$this->info("{$file['git']} exists");
}
}
// Check PHP syntax of all important files
$this->info("\n🔍 Checking PHP syntax of critical files...");
$phpFiles = [
'app/Filament/Pages/Monitoring/AlertSettings.php',
'app/Filament/Widgets/UpdateCheckerWidget.php',
'app/Services/EmulatorUpdateService.php',
'app/Services/NitroUpdateService.php',
'app/Services/RconService.php',
'app/Services/AlertService.php',
'app/Services/AlertSettings.php',
];
foreach ($phpFiles as $phpFile) {
$fullPath = base_path($phpFile);
if (file_exists($fullPath)) {
$result = $this->runSystemCommand("php -l {$fullPath} 2>&1");
if (str_contains((string) $result['output'], 'Errors parsing')) {
$this->error(" ❌ Syntax error in {$phpFile}: {$result['output']}");
} else {
$this->info("{$phpFile} syntax OK");
}
}
}
// Check file permissions
$this->info("\n🔍 Checking file permissions...");
$dirs = [
storage_path(),
base_path('bootstrap/cache'),
public_path('uploads'),
public_path('storage'),
];
foreach ($dirs as $dir) {
if (is_dir($dir)) {
if (is_writable($dir)) {
$this->info("{$dir} is writable");
} else {
$this->error("{$dir} is NOT writable");
}
} else {
$this->warn(" ⚠️ {$dir} does not exist");
}
}
// Check database connection
$this->info("\n🔍 Checking database connection...");
try {
DB::connection()->getPdo();
$this->info(' ✅ Database connection OK');
} catch (\Exception $e) {
$this->error(' ❌ Database connection failed: ' . $e->getMessage());
}
// Check required tables
$this->info("\n🔍 Checking required tables...");
$requiredTables = ['users', 'permissions', 'website_settings', 'rooms', 'items'];
foreach ($requiredTables as $table) {
if (Schema::hasTable($table)) {
$count = DB::table($table)->count();
$this->info("{$table} exists ({$count} rows)");
} else {
$this->error("{$table} does NOT exist");
}
}
// Check RCON connection
$this->info("\n🔍 Checking RCON connection...");
try {
$rcon = new RconService;
if ($rcon->isConnected()) {
$this->info(' ✅ RCON connection OK');
} else {
$this->warn(' ⚠️ RCON not connected (emulator might be offline)');
}
} catch (\Exception $e) {
$this->warn(' ⚠️ RCON check failed: ' . $e->getMessage());
}
// Check emulator service
$this->info("\n🔍 Checking emulator service...");
$serviceName = setting('emulator_service_name', 'emulator');
$result = $this->runSystemCommand("systemctl is-active {$serviceName} 2>/dev/null || echo 'inactive'");
if (trim((string) $result['output']) === 'active') {
$this->info(" ✅ Emulator service '{$serviceName}' is running");
} else {
$this->warn(" ⚠️ Emulator service '{$serviceName}' is not running");
}
// Check emulator JAR
$this->info("\n🔍 Checking emulator JAR...");
$jarPath = setting('emulator_jar_path', '/var/www/Emulator');
$jarFiles = glob("{$jarPath}/*.jar");
if ($jarFiles !== [] && $jarFiles !== false) {
$this->info(' ✅ Found JAR: ' . basename($jarFiles[0]));
} else {
$this->warn(" ⚠️ No JAR found in {$jarPath}");
}
// Check Nitro client
$this->info("\n🔍 Checking Nitro client...");
$nitroPath = setting('nitro_client_path', '/var/www/atomcms/nitro-client');
if (is_dir($nitroPath)) {
$this->info(' ✅ Nitro client directory exists');
if (file_exists("{$nitroPath}/package.json")) {
$this->info(' ✅ package.json exists');
} else {
$this->warn(' ⚠️ package.json missing');
}
} else {
$this->warn(' ⚠️ Nitro client directory not found');
}
// Check webroot
$this->info("\n🔍 Checking webroot...");
$webroot = setting('nitro_webroot', '/var/www/Client');
if (is_dir($webroot)) {
$this->info(" ✅ Webroot exists: {$webroot}");
$indexFile = "{$webroot}/index.html";
if (file_exists($indexFile)) {
$this->info(' ✅ index.html exists');
} else {
$this->warn(' ⚠️ index.html missing in webroot');
}
} else {
$this->warn(" ⚠️ Webroot not found: {$webroot}");
}
$this->info("\n✅ All checks complete!");
}
}