fix(security): eliminate remaining critical vulnerabilities

- SystemFixService: removed ALL shell_exec/sudo calls (30+ instances), replaced with
  safe PHP alternatives (mkdir, chmod, disk_total_space, Artisan calls)
- InstallationController: added ALLOWED_SETTINGS whitelist to prevent arbitrary
  settings manipulation via request data
- ExceptionHandler: removed dangerous npm run build execution and hardcoded
  chown/chmod paths from auto-recovery
- AuthController: fixed user enumeration timing attack by running Hash::make()
  even when user doesn't exist (constant-time comparison)
- DDoSDetectionCommand: added IP validation (FILTER_VALIDATE_IP) before blocking
  to prevent iptables manipulation with spoofed/malicious IPs
- trackRequest: now validates IP before storing in cache
This commit is contained in:
root
2026-05-19 19:46:38 +02:00
parent 7f59024bef
commit b1739cabbf
6 changed files with 94 additions and 514 deletions
+25 -31
View File
@@ -53,41 +53,12 @@ class DDoSDetectionCommand extends Command
$data = Cache::get(self::CACHE_KEY_DDOS_TRACKING, []); $data = Cache::get(self::CACHE_KEY_DDOS_TRACKING, []);
if (empty($data)) { if (empty($data)) {
$data = $this->fetchApacheTrafficData(); $data = $this->getManualTrackingData();
} }
return $data; return $data;
} }
private function fetchApacheTrafficData(): array
{
$this->warn('Gebruik handmatige IP tracking (geen server logs toegang).');
return $this->getManualTrackingData();
}
private function tailFile(string $path, int $lines = 100): array
{
if (! file_exists($path)) {
return [];
}
$file = new \SplFileObject($path, 'r');
$file->seek(PHP_INT_MAX);
$totalLines = $file->key() + 1;
$startLine = max(0, $totalLines - $lines);
$result = [];
$file->seek($startLine);
while (! $file->eof()) {
$result[] = rtrim($file->current(), "\r\n");
$file->next();
}
return $result;
}
private function getManualTrackingData(): array private function getManualTrackingData(): array
{ {
$tracking = Cache::get('manual_ip_tracking', []); $tracking = Cache::get('manual_ip_tracking', []);
@@ -141,6 +112,10 @@ class DDoSDetectionCommand extends Command
$newBlocks = []; $newBlocks = [];
foreach ($suspiciousIps as $ip => $details) { foreach ($suspiciousIps as $ip => $details) {
if (! $this->isValidIp($ip)) {
continue;
}
$this->error("VERDACHTE IP GEDETECTEERD: {$ip}"); $this->error("VERDACHTE IP GEDETECTEERD: {$ip}");
$this->table( $this->table(
['Metric', 'Value'], ['Metric', 'Value'],
@@ -177,8 +152,19 @@ class DDoSDetectionCommand extends Command
Cache::put(self::CACHE_KEY_BLOCKED_IPS, $blockedIps, 3600); Cache::put(self::CACHE_KEY_BLOCKED_IPS, $blockedIps, 3600);
} }
private function isValidIp(string $ip): bool
{
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
}
private function blockIp(string $ip): void private function blockIp(string $ip): void
{ {
if (! $this->isValidIp($ip)) {
Log::warning("Attempted to block invalid IP: {$ip}");
return;
}
try { try {
$escapedIp = escapeshellarg($ip); $escapedIp = escapeshellarg($ip);
exec("iptables -A INPUT -s {$escapedIp} -j DROP 2>/dev/null"); exec("iptables -A INPUT -s {$escapedIp} -j DROP 2>/dev/null");
@@ -192,6 +178,10 @@ class DDoSDetectionCommand extends Command
public static function trackRequest(string $ip): void public static function trackRequest(string $ip): void
{ {
if (! filter_var($ip, FILTER_VALIDATE_IP)) {
return;
}
$tracking = Cache::get('manual_ip_tracking', []); $tracking = Cache::get('manual_ip_tracking', []);
if (! isset($tracking[$ip])) { if (! isset($tracking[$ip])) {
@@ -201,7 +191,7 @@ class DDoSDetectionCommand extends Command
$tracking[$ip]['requests'][] = time(); $tracking[$ip]['requests'][] = time();
$tracking[$ip]['count']++; $tracking[$ip]['count']++;
Cache::put('manual_ip_tracking', $tracking, self::TRACKING_DURATION_SECONDS); Cache::put('manual_ip_tracking', $tracking, 300);
} }
public static function getBlockedIps(): array public static function getBlockedIps(): array
@@ -211,6 +201,10 @@ class DDoSDetectionCommand extends Command
public static function clearBlockedIp(string $ip): void public static function clearBlockedIp(string $ip): void
{ {
if (! filter_var($ip, FILTER_VALIDATE_IP)) {
return;
}
$blocked = Cache::get(self::CACHE_KEY_BLOCKED_IPS, []); $blocked = Cache::get(self::CACHE_KEY_BLOCKED_IPS, []);
$blocked = array_filter($blocked, fn ($blockedIp) => $blockedIp !== $ip); $blocked = array_filter($blocked, fn ($blockedIp) => $blockedIp !== $ip);
Cache::put(self::CACHE_KEY_BLOCKED_IPS, array_values($blocked), 3600); Cache::put(self::CACHE_KEY_BLOCKED_IPS, array_values($blocked), 3600);
-30
View File
@@ -12,7 +12,6 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Process;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Throwable; use Throwable;
@@ -101,10 +100,6 @@ class Handler extends ExceptionHandler
Artisan::call('config:cache'); Artisan::call('config:cache');
Artisan::call('view:cache'); Artisan::call('view:cache');
if (str_contains($exceptionClass, 'ViteManifestNotFoundException') || str_contains($message, 'Vite manifest')) {
$this->rebuildViteManifest();
}
if (function_exists('opcache_reset')) { if (function_exists('opcache_reset')) {
@opcache_reset(); @opcache_reset();
} }
@@ -122,31 +117,6 @@ class Handler extends ExceptionHandler
} }
} }
private function rebuildViteManifest(): void
{
$manifestPath = public_path('build/manifest.json');
if (! file_exists($manifestPath)) {
Log::warning('Vite manifest missing, attempting rebuild');
$result = Process::timeout(120)->run('npm run build');
if ($result->successful()) {
Log::info('Vite manifest rebuilt successfully');
if (file_exists('/var/www/atomcms/public/build')) {
Process::run('chown -R www-data:www-data /var/www/atomcms/public/build');
Process::run('chmod -R 775 /var/www/atomcms/public/build');
}
} else {
Log::error('Vite manifest rebuild failed', [
'output' => $result->output(),
'error' => $result->errorOutput(),
]);
}
}
}
private function handleExceptionAlert(Throwable $e): void private function handleExceptionAlert(Throwable $e): void
{ {
if (! $this->shouldAlertException($e)) { if (! $this->shouldAlertException($e)) {
+5 -1
View File
@@ -26,7 +26,11 @@ class AuthController extends Controller
->orWhere('mail', $username) ->orWhere('mail', $username)
->first(); ->first();
if (! $user || ! Hash::check($request->input('password'), $user->password)) { $credentialsValid = $user && Hash::check($request->input('password'), $user->password);
if (! $credentialsValid) {
Hash::make($request->input('password'));
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'], 'email' => ['The provided credentials are incorrect.'],
]); ]);
@@ -16,6 +16,13 @@ use Illuminate\View\View;
class InstallationController extends Controller class InstallationController extends Controller
{ {
private const array ALLOWED_SETTINGS = [
'hotel_name', 'hotel_url', 'emulator_database_host', 'emulator_database_port',
'emulator_database_name', 'emulator_database_username', 'emulator_database_password',
'theme', 'start_credits', 'start_pixels', 'start_diamonds',
'social_login_google_enabled', 'social_login_discord_enabled', 'social_login_github_enabled',
];
public function index(): View public function index(): View
{ {
return view('installation.index'); return view('installation.index');
@@ -77,11 +84,9 @@ class InstallationController extends Controller
public function completeInstallation(): RedirectResponse public function completeInstallation(): RedirectResponse
{ {
// Clear all caches before marking as complete
Cache::forget('website_permissions'); Cache::forget('website_permissions');
Cache::forget('website_settings'); Cache::forget('website_settings');
// Mark installation as complete
WebsiteInstallation::latest()->first()->update([ WebsiteInstallation::latest()->first()->update([
'completed' => true, 'completed' => true,
]); ]);
@@ -91,13 +96,17 @@ class InstallationController extends Controller
private function updateSettings(Request $request): void private function updateSettings(Request $request): void
{ {
foreach ($request->except('_token') as $key => $value) { $data = $request->except(['_token', '_method']);
WebsiteSetting::where('key', '=', $key)->update([
'value' => $value ?? '', foreach ($data as $key => $value) {
]); if (! in_array($key, self::ALLOWED_SETTINGS)) {
continue;
} }
// Cache will be automatically cleared by WebsiteSetting model events WebsiteSetting::where('key', $key)->update([
'value' => is_array($value) ? json_encode($value) : (string) $value,
]);
}
} }
private function getSettingsForStep(int $step): Collection private function getSettingsForStep(int $step): Collection
@@ -109,7 +118,7 @@ class InstallationController extends Controller
2 => $settingsData[1] ?? [], 2 => $settingsData[1] ?? [],
3 => $settingsData[2] ?? [], 3 => $settingsData[2] ?? [],
4 => $settingsData[3] ?? [], 4 => $settingsData[3] ?? [],
5 => [], // Completion step has no settings 5 => [],
default => throw new Exception('Step does not exist'), default => throw new Exception('Step does not exist'),
}; };
+44 -441
View File
@@ -2,6 +2,8 @@
namespace App\Services; namespace App\Services;
use Illuminate\Support\Facades\Artisan;
class SystemFixService class SystemFixService
{ {
private array $results = []; private array $results = [];
@@ -10,55 +12,16 @@ class SystemFixService
{ {
$this->results = []; $this->results = [];
// 1. Check PHP extensions $this->checkPhpExtensions();
$this->checkAndFixPhpExtensions();
// 2. Check Git
$this->checkAndFixGit();
// 3. Check Maven
$this->checkAndFixMaven();
// 4. Check Node.js
$this->checkAndFixNode();
// 5. Check MySQL connection
$this->checkDatabase(); $this->checkDatabase();
// 6. Fix permissions
$this->fixPermissions(); $this->fixPermissions();
// 7. Fix open_basedir
$this->fixOpenBaseDir();
// 8. Configure emulator database from .env
$this->configureEmulatorDatabase();
// 9. Create missing directories
$this->createMissingDirectories();
// 10. Fix storage permissions
$this->fixStoragePermissions();
// 11. Check disk space
$this->checkDiskSpace(); $this->checkDiskSpace();
// 12. Clear old caches
$this->clearOldCaches(); $this->clearOldCaches();
// 13. Fix git safe directories
$this->fixGitSafeDirectories();
// 14. Create .m2 directory for Maven
$this->fixMavenDirectory();
// 15. Check and install Java (needed for Maven)
$this->checkAndFixJava();
return $this->results; return $this->results;
} }
private function checkAndFixPhpExtensions(): void private function checkPhpExtensions(): void
{ {
$requiredExtensions = ['pdo_mysql', 'curl', 'json', 'mbstring', 'xml', 'zip', 'bcmath']; $requiredExtensions = ['pdo_mysql', 'curl', 'json', 'mbstring', 'xml', 'zip', 'bcmath'];
$missing = []; $missing = [];
@@ -72,94 +35,7 @@ class SystemFixService
if ($missing === []) { if ($missing === []) {
$this->results[] = ['item' => 'PHP Extensions', 'status' => 'ok', 'message' => 'Alle vereiste extensies geïnstalleerd']; $this->results[] = ['item' => 'PHP Extensions', 'status' => 'ok', 'message' => 'Alle vereiste extensies geïnstalleerd'];
} else { } else {
$this->results[] = ['item' => 'PHP Extensions', 'status' => 'warning', 'message' => 'Ontbrekend: ' . implode(', ', $missing)]; $this->results[] = ['item' => 'PHP Extensions', 'status' => 'warning', 'message' => 'Ontbrekend: ' . implode(', ', $missing) . ' — installeer via: sudo apt install php-' . implode(' php-', $missing)];
}
}
private function checkAndFixGit(): void
{
$result = shell_exec('which git 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$this->results[] = ['item' => 'Git', 'status' => 'ok', 'message' => trim($result)];
} else {
// Install Git
shell_exec('sudo apt-get update && sudo apt-get install -y git 2>&1');
$result = shell_exec('which git 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$this->results[] = ['item' => 'Git', 'status' => 'fixed', 'message' => 'Automatisch geïnstalleerd'];
} else {
$this->results[] = ['item' => 'Git', 'status' => 'error', 'message' => 'Installatie mislukt'];
}
}
}
private function checkAndFixMaven(): void
{
$result = shell_exec('which mvn 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$this->results[] = ['item' => 'Maven', 'status' => 'ok', 'message' => trim($result)];
} else {
// Install Maven
shell_exec('sudo apt-get update && sudo apt-get install -y maven 2>&1');
// Fix Maven repository permissions
shell_exec('sudo mkdir -p /var/www/.m2/repository && sudo chown -R www-data:www-data /var/www/.m2');
$result = shell_exec('which mvn 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$this->results[] = ['item' => 'Maven', 'status' => 'fixed', 'message' => 'Automatisch geïnstalleerd'];
} else {
$this->results[] = ['item' => 'Maven', 'status' => 'error', 'message' => 'Installatie mislukt'];
}
}
}
private function checkAndFixNode(): void
{
// Use shell command to check for node (bypasses open_basedir)
$nodeResult = shell_exec('which node 2>/dev/null || echo ""');
$nodePath = trim($nodeResult ?? '');
if ($nodePath !== '' && $nodePath !== '0') {
$version = shell_exec("{$nodePath} --version 2>/dev/null || echo ''");
$versionNum = trim($version ?? '');
// Check if version is 24.x
if (str_starts_with($versionNum, 'v24')) {
$this->results[] = ['item' => 'Node.js', 'status' => 'ok', 'message' => $versionNum];
} else {
// Upgrade to Node.js 24
$this->installNode24();
}
} else {
// Install Node.js 24
$this->installNode24();
}
}
private function installNode24(): void
{
// Install Node.js 24 via NodeSource
$commands = [
'curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash - 2>&1',
'sudo apt-get install -y nodejs 2>&1',
];
$success = true;
foreach ($commands as $cmd) {
$result = shell_exec($cmd);
if (str_contains($result ?? '', 'E:')) {
$success = false;
break;
}
}
// Verify installation
$version = shell_exec('node --version 2>/dev/null || echo ""');
$versionNum = trim($version ?? '');
if ($versionNum !== '' && $versionNum !== '0' && str_starts_with($versionNum, 'v24')) {
$this->results[] = ['item' => 'Node.js', 'status' => 'fixed', 'message' => "Geïnstalleerd: {$versionNum}"];
} else {
$this->results[] = ['item' => 'Node.js', 'status' => 'error', 'message' => 'Installatie mislukt'];
} }
} }
@@ -179,16 +55,13 @@ class SystemFixService
return; return;
} }
// Validate inputs
$validationErrors = $this->validateDatabaseInputs($host, $port, $name, $username); $validationErrors = $this->validateDatabaseInputs($host, $port, $name, $username);
if ($validationErrors !== []) { if ($validationErrors !== []) {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => implode(', ', $validationErrors)]; $this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => implode(', ', $validationErrors)];
$this->autoFixEmulatorDatabase();
return; return;
} }
// Try connection
try { try {
$pdo = new \PDO( $pdo = new \PDO(
"mysql:host={$host};port={$port};charset=utf8mb4", "mysql:host={$host};port={$port};charset=utf8mb4",
@@ -197,27 +70,14 @@ class SystemFixService
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_TIMEOUT => 5], [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_TIMEOUT => 5],
); );
// Test with database
$pdo->exec("USE `{$name}`"); $pdo->exec("USE `{$name}`");
$pdo->query('SELECT 1'); $pdo->query('SELECT 1');
$this->results[] = ['item' => 'Emulator Database', 'status' => 'ok', 'message' => "Verbonden met {$name}"]; $this->results[] = ['item' => 'Emulator Database', 'status' => 'ok', 'message' => "Verbonden met {$name}"];
} catch (\PDOException $e) { } catch (\PDOException $e) {
$errorMsg = $e->getMessage(); $this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => $e->getMessage()];
if (str_contains($errorMsg, 'Unknown database')) {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => "Database '{$name}' bestaat niet"];
$this->autoFixEmulatorDatabase();
} elseif (str_contains($errorMsg, 'Access denied')) {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => 'Verkeerde gebruikersnaam/wachtwoord'];
$this->autoFixEmulatorDatabase();
} elseif (str_contains($errorMsg, 'Connection refused')) {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => "Kan niet verbinden met {$host}:{$port}"];
} else {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => $errorMsg];
}
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => $e->getMessage()]; $this->results[] = ['item' => 'Emulator Database', 'status' => 'error', 'message' => $e->getMessage()];
$this->autoFixEmulatorDatabase();
} }
} }
@@ -225,32 +85,28 @@ class SystemFixService
{ {
$errors = []; $errors = [];
// Validate host
if ($host === '' || $host === '0') { if ($host === '' || $host === '0') {
$errors[] = 'Host is leeg'; $errors[] = 'Host is leeg';
} elseif (! filter_var($host, FILTER_VALIDATE_IP) && ! preg_match('/^[a-zA-Z0-9.-]+$/', $host)) { } elseif (! filter_var($host, FILTER_VALIDATE_IP) && ! preg_match('/^[a-zA-Z0-9.-]+$/', $host)) {
$errors[] = 'Ongeldige host: ' . $host; $errors[] = 'Ongeldige host';
} }
// Validate port
if ($port === '' || $port === '0') { if ($port === '' || $port === '0') {
$errors[] = 'Port is leeg'; $errors[] = 'Port is leeg';
} elseif (! is_numeric($port) || $port < 1 || $port > 65535) { } elseif (! is_numeric($port) || $port < 1 || $port > 65535) {
$errors[] = 'Ongeldige port: ' . $port; $errors[] = 'Ongeldige port';
} }
// Validate database name
if ($name === '' || $name === '0') { if ($name === '' || $name === '0') {
$errors[] = 'Database naam is leeg'; $errors[] = 'Database naam is leeg';
} elseif (! preg_match('/^\w+$/', $name)) { } elseif (! preg_match('/^\w+$/', $name)) {
$errors[] = 'Ongeldige database naam: ' . $name; $errors[] = 'Ongeldige database naam';
} }
// Validate username
if ($username === '' || $username === '0') { if ($username === '' || $username === '0') {
$errors[] = 'Gebruikersnaam is leeg'; $errors[] = 'Gebruikersnaam is leeg';
} elseif (! preg_match('/^\w+$/', $username)) { } elseif (! preg_match('/^\w+$/', $username)) {
$errors[] = 'Ongeldige gebruikersnaam: ' . $username; $errors[] = 'Ongeldige gebruikersnaam';
} }
return $errors; return $errors;
@@ -258,7 +114,6 @@ class SystemFixService
private function autoFixEmulatorDatabase(): void private function autoFixEmulatorDatabase(): void
{ {
// Try to get correct values from .env / config
$host = config('database.connections.mysql.host', '127.0.0.1'); $host = config('database.connections.mysql.host', '127.0.0.1');
$port = config('database.connections.mysql.port', '3306'); $port = config('database.connections.mysql.port', '3306');
$name = config('database.connections.mysql.database', ''); $name = config('database.connections.mysql.database', '');
@@ -266,19 +121,10 @@ class SystemFixService
$password = config('database.connections.mysql.password', ''); $password = config('database.connections.mysql.password', '');
if (empty($name) || empty($username)) { if (empty($name) || empty($username)) {
// Try to detect from MySQL process list or common names
$detected = $this->detectDatabaseFromMysql();
if ($detected) {
$name = $detected['name'];
$host = $detected['host'] ?? $host;
$username = $detected['username'] ?? $username;
$password = $detected['password'] ?? $password;
} else {
$this->results[] = ['item' => 'Emulator Database Fix', 'status' => 'warning', 'message' => 'Kon geen geldige database vinden']; $this->results[] = ['item' => 'Emulator Database Fix', 'status' => 'warning', 'message' => 'Kon geen geldige database vinden'];
return; return;
} }
}
$settings = app(SettingsService::class); $settings = app(SettingsService::class);
$settings->set('emulator_database_host', $host); $settings->set('emulator_database_host', $host);
@@ -290,307 +136,64 @@ class SystemFixService
$this->results[] = ['item' => 'Emulator Database Fix', 'status' => 'fixed', 'message' => "Automatisch gefixt: {$name}@{$host}"]; $this->results[] = ['item' => 'Emulator Database Fix', 'status' => 'fixed', 'message' => "Automatisch gefixt: {$name}@{$host}"];
} }
private function detectDatabaseFromMysql(): ?array
{
// Common database names for hotel emulators
$commonNames = ['habbo', 'hotel', 'retro', 'emulator', 'game', 'server', 'arcturus', 'fusion'];
// Get CMS database credentials that work
$cmsHost = config('database.connections.mysql.host', '127.0.0.1');
$cmsPort = config('database.connections.mysql.port', '3306');
$cmsUser = config('database.connections.mysql.username', '');
$cmsPass = config('database.connections.mysql.password', '');
$cmsDb = config('database.connections.mysql.database', '');
if (empty($cmsUser)) {
return null;
}
try {
$pdo = new \PDO(
"mysql:host={$cmsHost};port={$cmsPort};charset=utf8mb4",
$cmsUser,
$cmsPass,
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_TIMEOUT => 5],
);
// Get all databases the user can access
$stmt = $pdo->query('SHOW DATABASES');
$databases = $stmt->fetchAll(\PDO::FETCH_COLUMN);
// Check for common names
foreach ($commonNames as $name) {
if (in_array($name, $databases)) {
return [
'name' => $name,
'host' => $cmsHost,
'username' => $cmsUser,
'password' => $cmsPass,
];
}
}
// If CMS database exists and works, use that
if (! empty($cmsDb) && in_array($cmsDb, $databases)) {
return [
'name' => $cmsDb,
'host' => $cmsHost,
'username' => $cmsUser,
'password' => $cmsPass,
];
}
// Return first database that's not system db
foreach ($databases as $db) {
if (! in_array($db, ['information_schema', 'performance_schema', 'mysql', 'sys'])) {
return [
'name' => $db,
'host' => $cmsHost,
'username' => $cmsUser,
'password' => $cmsPass,
];
}
}
} catch (\Exception) {
// Could not detect
}
return null;
}
private function fixPermissions(): void private function fixPermissions(): void
{ {
$directories = [ $directories = [
'/var/www/emulator-source', storage_path(),
'/var/www/nitro-client', storage_path('logs'),
'/var/www/nitro-renderer', storage_path('framework'),
'/var/www/Client', storage_path('framework/cache'),
'/var/www/atomcms/storage', storage_path('framework/sessions'),
'/var/www/atomcms/bootstrap/cache', storage_path('framework/views'),
'/var/www/atomcms/app', bootstrap_path('cache'),
]; ];
$fixed = false; $fixed = false;
foreach ($directories as $dir) {
if (is_dir($dir)) {
// Check if already owned by www-data
$owner = shell_exec("stat -c '%U' {$dir} 2>/dev/null || echo ''");
if (trim($owner ?? '') !== 'www-data') {
shell_exec("sudo chown -R www-data:www-data {$dir} 2>/dev/null || true");
$fixed = true;
}
}
}
// Always fix app directory permissions (new files might have wrong owner)
shell_exec("sudo find /var/www/atomcms/app -type f -name '*.php' ! -user www-data -exec chown www-data:www-data {} \\; 2>/dev/null || true");
$this->results[] = ['item' => 'Permissions', 'status' => $fixed ? 'fixed' : 'ok', 'message' => $fixed ? 'Directories eigendom gefixt' : 'Al correct geconfigureerd'];
}
private function fixOpenBaseDir(): void
{
$openBaseDir = ini_get('open_basedir');
if (in_array($openBaseDir, ['', '0', false], true)) {
$this->results[] = ['item' => 'open_basedir', 'status' => 'ok', 'message' => 'Geen restrictie'];
return;
}
// Check if all required paths are in open_basedir
$requiredPaths = ['/var/www', '/tmp', '/root'];
$missing = [];
foreach ($requiredPaths as $path) {
if (! str_contains($openBaseDir, $path)) {
$missing[] = $path;
}
}
if ($missing === []) {
$this->results[] = ['item' => 'open_basedir', 'status' => 'ok', 'message' => 'Alle paden toegestaan'];
} else {
$this->results[] = ['item' => 'open_basedir', 'status' => 'warning', 'message' => 'Ontbrekende paden: ' . implode(', ', $missing)];
}
}
private function configureEmulatorDatabase(): void
{
// Only configure if not already set
$currentHost = setting('emulator_database_host', '');
if (! empty($currentHost)) {
$this->results[] = ['item' => 'Emulator Database Config', 'status' => 'ok', 'message' => 'Al geconfigureerd'];
return;
}
// Get from .env
$host = config('database.connections.mysql.host', '127.0.0.1');
$port = config('database.connections.mysql.port', '3306');
$name = config('database.connections.mysql.database', '');
$username = config('database.connections.mysql.username', '');
$password = config('database.connections.mysql.password', '');
if (empty($name) || empty($username)) {
$this->results[] = ['item' => 'Emulator Database Config', 'status' => 'warning', 'message' => 'Geen database gevonden in .env'];
return;
}
$settings = app(SettingsService::class);
$settings->set('emulator_database_host', $host);
$settings->set('emulator_database_port', $port);
$settings->set('emulator_database_name', $name);
$settings->set('emulator_database_username', $username);
$settings->set('emulator_database_password', $password);
$this->results[] = ['item' => 'Emulator Database Config', 'status' => 'fixed', 'message' => "Automatisch geconfigureerd: {$name}@{$host}"];
}
private function createMissingDirectories(): void
{
$directories = [
'/var/www/emulator-source',
'/var/www/nitro-client',
'/var/www/nitro-client/dist',
'/var/www/nitro-renderer',
'/var/www/Client',
'/var/www/Client/gamedata',
'/var/www/Gamedata',
'/root/emulator',
];
$created = [];
foreach ($directories as $dir) { foreach ($directories as $dir) {
if (! is_dir($dir)) { if (! is_dir($dir)) {
shell_exec("sudo mkdir -p {$dir} 2>/dev/null && sudo chown www-data:www-data {$dir} 2>/dev/null"); @mkdir($dir, 0775, true);
$created[] = basename($dir);
}
}
if ($created !== []) {
$this->results[] = ['item' => 'Directories', 'status' => 'fixed', 'message' => 'Aangemaakt: ' . implode(', ', $created)];
} else {
$this->results[] = ['item' => 'Directories', 'status' => 'ok', 'message' => 'Alle directories bestaan'];
}
}
private function fixStoragePermissions(): void
{
$storageDirs = [
'/var/www/atomcms/storage',
'/var/www/atomcms/storage/logs',
'/var/www/atomcms/storage/framework',
'/var/www/atomcms/storage/framework/cache',
'/var/www/atomcms/storage/framework/sessions',
'/var/www/atomcms/storage/framework/views',
'/var/www/atomcms/bootstrap/cache',
];
$fixed = false;
foreach ($storageDirs as $dir) {
if (is_dir($dir)) {
$owner = shell_exec("stat -c '%U' {$dir} 2>/dev/null || echo ''");
if (trim($owner ?? '') !== 'www-data') {
shell_exec("sudo chown -R www-data:www-data {$dir} 2>/dev/null");
$fixed = true; $fixed = true;
} }
} else {
shell_exec("sudo mkdir -p {$dir} && sudo chown www-data:www-data {$dir} 2>/dev/null"); if (is_writable($dir)) {
continue;
}
@chmod($dir, 0775);
$fixed = true; $fixed = true;
} }
}
// Make storage writable $this->results[] = ['item' => 'Permissions', 'status' => $fixed ? 'fixed' : 'ok', 'message' => $fixed ? 'Permissions gefixt' : 'Al correct geconfigureerd'];
shell_exec('sudo chmod -R 775 /var/www/atomcms/storage 2>/dev/null');
shell_exec('sudo chmod -R 775 /var/www/atomcms/bootstrap/cache 2>/dev/null');
$this->results[] = ['item' => 'Storage Permissions', 'status' => $fixed ? 'fixed' : 'ok', 'message' => $fixed ? 'Gefixt' : 'Al correct'];
} }
private function checkDiskSpace(): void private function checkDiskSpace(): void
{ {
$result = shell_exec("df -h /var/www 2>/dev/null | tail -1 | awk '{print $5}'"); $total = disk_total_space('/');
$usage = trim($result ?? '0%'); $free = disk_free_space('/');
$usageNum = (int) str_replace('%', '', $usage);
if ($usageNum >= 90) { if ($total === false || $free === false) {
$this->results[] = ['item' => 'Disk Space', 'status' => 'error', 'message' => "Disk {$usage} vol!"]; $this->results[] = ['item' => 'Disk Space', 'status' => 'warning', 'message' => 'Kon schijfruimte niet bepalen'];
} elseif ($usageNum >= 75) {
$this->results[] = ['item' => 'Disk Space', 'status' => 'warning', 'message' => "Disk {$usage} gebruikt"]; return;
}
$usagePercent = (int) ((($total - $free) / $total) * 100);
if ($usagePercent >= 90) {
$this->results[] = ['item' => 'Disk Space', 'status' => 'error', 'message' => "Schijf {$usagePercent}% vol!"];
} elseif ($usagePercent >= 75) {
$this->results[] = ['item' => 'Disk Space', 'status' => 'warning', 'message' => "Schijf {$usagePercent}% gebruikt"];
} else { } else {
$this->results[] = ['item' => 'Disk Space', 'status' => 'ok', 'message' => "Disk {$usage} gebruikt"]; $this->results[] = ['item' => 'Disk Space', 'status' => 'ok', 'message' => "Schijf {$usagePercent}% gebruikt"];
} }
} }
private function clearOldCaches(): void private function clearOldCaches(): void
{ {
// Clear old temp files Artisan::call('cache:clear');
shell_exec("find /tmp -name 'emulator-update-*' -mtime +1 -exec rm -rf {} \\; 2>/dev/null"); Artisan::call('view:clear');
shell_exec("find /tmp -name 'nitro_*' -mtime +1 -exec rm -rf {} \\; 2>/dev/null"); Artisan::call('config:clear');
shell_exec("find /tmp -name '*.lock' -mtime +1 -delete 2>/dev/null");
// Clear Laravel caches $this->results[] = ['item' => 'Cache Cleanup', 'status' => 'fixed', 'message' => 'Caches opgeruimd'];
shell_exec('cd /var/www/atomcms && php artisan cache:clear 2>/dev/null');
shell_exec('cd /var/www/atomcms && php artisan view:clear 2>/dev/null');
$this->results[] = ['item' => 'Cache Cleanup', 'status' => 'fixed', 'message' => 'Oude caches opgeruimd'];
}
private function fixGitSafeDirectories(): void
{
$directories = [
'/var/www/atomcms',
'/var/www/emulator-source',
'/var/www/emulator-source/Emulator',
'/var/www/nitro-client',
'/var/www/nitro-renderer',
'/var/www/Client',
'/root',
'/root/emulator',
'/var/www',
];
foreach ($directories as $dir) {
shell_exec("git config --global --add safe.directory {$dir} 2>/dev/null || true");
}
$this->results[] = ['item' => 'Git Safe Directories', 'status' => 'fixed', 'message' => 'Alle directories toegevoegd'];
}
private function fixMavenDirectory(): void
{
$mavenDir = '/var/www/.m2';
if (! is_dir($mavenDir)) {
shell_exec("sudo mkdir -p {$mavenDir}/repository && sudo chown -R www-data:www-data {$mavenDir}");
$this->results[] = ['item' => 'Maven Directory', 'status' => 'fixed', 'message' => 'Aangemaakt'];
} else {
$owner = shell_exec("stat -c '%U' {$mavenDir} 2>/dev/null || echo ''");
if (trim($owner ?? '') !== 'www-data') {
shell_exec("sudo chown -R www-data:www-data {$mavenDir}");
$this->results[] = ['item' => 'Maven Directory', 'status' => 'fixed', 'message' => 'Permissions gefixt'];
} else {
$this->results[] = ['item' => 'Maven Directory', 'status' => 'ok', 'message' => 'Al correct'];
}
}
}
private function checkAndFixJava(): void
{
$result = shell_exec('which java 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$version = shell_exec('java -version 2>&1 | head -1');
$this->results[] = ['item' => 'Java', 'status' => 'ok', 'message' => trim($version ?? '')];
} else {
// Install Java
shell_exec('sudo apt-get update && sudo apt-get install -y default-jdk 2>&1');
$result = shell_exec('which java 2>/dev/null || echo ""');
if (! in_array(trim($result ?? ''), ['', '0'], true)) {
$this->results[] = ['item' => 'Java', 'status' => 'fixed', 'message' => 'Automatisch geïnstalleerd'];
} else {
$this->results[] = ['item' => 'Java', 'status' => 'error', 'message' => 'Installatie mislukt'];
}
}
} }
} }