Initial commit

This commit is contained in:
root
2026-05-09 17:28:23 +02:00
commit 9d73f82529
5575 changed files with 281989 additions and 0 deletions
+724
View File
@@ -0,0 +1,724 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Process;
class AutoDetectService
{
private static ?self $instance = null;
private array $cache = [];
public static function getInstance(): self
{
return self::$instance ??= new self;
}
// ─── Emulator ──────────────────────────────────────────────
public function detectEmulatorJarPath(): string
{
return $this->cache['emulator_jar_path'] ??= $this->doDetectEmulatorJarPath();
}
private function doDetectEmulatorJarPath(): string
{
// 1. Check setting
$setting = $this->getSetting('emulator_jar_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
// 2. Check well-known directories
$dirNames = ['Emulator', 'emulator', 'emulator-source', 'arcturus', 'morningstar', 'hotel', 'habbo-emulator', 'nitro-emulator'];
$found = $this->smartSearch($dirNames, ['*.jar'], ['/var/www', '/opt', '/home', '/root', '/srv', base_path()]);
if ($found) {
return $found;
}
// 3. Find any directory containing a JAR
$result = Process::timeout(10)->run(
'find /var/www /opt /home /root /srv -maxdepth 4 -name "*.jar" -type f 2>/dev/null | head -1',
);
if ($result->successful()) {
$jarFile = trim($result->output());
if ($jarFile !== '' && str_ends_with($jarFile, '.jar')) {
return dirname($jarFile);
}
}
return '/root/emulator';
}
public function detectEmulatorSourcePath(): string
{
return $this->cache['emulator_source_path'] ??= $this->doDetectEmulatorSourcePath();
}
private function doDetectEmulatorSourcePath(): string
{
$setting = $this->getSetting('emulator_source_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$dirNames = ['emulator-source', 'emulator', 'arcturus-source', 'morningstar-source', 'Arcturus-Emulator', 'Morningstar', 'hotel-source'];
$found = $this->smartSearch($dirNames, ['pom.xml', 'build.gradle', 'src'], ['/var/www', '/opt', '/home', '/root', '/srv', base_path()]);
if ($found) {
return $found;
}
return '/var/www/emulator-source';
}
public function detectEmulatorServiceName(): string
{
return $this->cache['emulator_service'] ??= $this->doDetectEmulatorServiceName();
}
private function doDetectEmulatorServiceName(): string
{
$setting = $this->getSetting('emulator_service_name');
if ($setting && $setting !== '' && $setting !== '0') {
// Verify it actually exists
$check = Process::timeout(5)->run(
'systemctl list-unit-files ' . escapeshellarg($setting) . '.service 2>/dev/null | grep -q ' . escapeshellarg($setting) . ' && echo found',
);
if ($check->successful() && trim($check->output()) === 'found') {
return $setting;
}
}
// Try common service names
$possibleServices = [
'emulator', 'arcturus', 'morningstar', 'arcturus-emulator',
'habbo', 'hotel', 'game', 'nitro-emulator', 'hotel-server',
'arcturus-emu', 'morningstar-emu',
];
foreach ($possibleServices as $service) {
$check = Process::timeout(5)->run(
'systemctl list-unit-files ' . escapeshellarg($service) . '.service 2>/dev/null | grep -q ' . escapeshellarg($service) . ' && echo found',
);
if ($check->successful() && trim($check->output()) === 'found') {
return $service;
}
}
// Also check running services
$running = Process::timeout(5)->run('systemctl list-units --type=service --state=running --no-legend 2>/dev/null');
if ($running->successful()) {
$lines = explode("\n", $running->output());
foreach ($lines as $line) {
foreach ($possibleServices as $service) {
if (str_contains($line, $service)) {
return $service;
}
}
}
}
// Check for any Java process that looks like an emulator
$javaProc = Process::timeout(5)->run("ps aux | grep -i 'java.*jar\\|arcturus\\|morningstar\\|emulator' | grep -v grep | head -1");
if ($javaProc->successful() && trim($javaProc->output()) !== '') {
$output = trim($javaProc->output());
// Try to extract service name from systemd
if (preg_match('/systemd.*--name[=\s]+(\S+)/', $output, $matches)) {
return $matches[1];
}
}
return 'emulator';
}
public function detectEmulatorJavaProcess(): ?array
{
$result = Process::timeout(5)->run(
"ps aux | grep -i '[j]ava.*jar' | head -1",
);
if (! $result->successful() || trim($result->output()) === '') {
return null;
}
$line = trim($result->output());
$parts = preg_split('/\s+/', $line);
return [
'pid' => $parts[1] ?? null,
'full_command' => $line,
];
}
// ─── Nitro Client / Renderer ───────────────────────────────
public function detectNitroClientPath(): string
{
return $this->cache['nitro_client_path'] ??= $this->doDetectNitroClientPath();
}
private function doDetectNitroClientPath(): string
{
$setting = $this->getSetting('nitro_client_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$dirNames = ['nitro-client', 'nitro-client-v3', 'nitro-V3', 'nitro-V3-client', 'Nitro-V3', 'Nitro_Client', 'nitro', 'client'];
$found = $this->smartSearch($dirNames, ['package.json', 'src'], ['/var/www', '/opt', '/home', '/root', base_path(), base_path() . '/..']);
if ($found) {
return $found;
}
return base_path() . '/nitro-client';
}
public function detectNitroRendererPath(): string
{
return $this->cache['nitro_renderer_path'] ??= $this->doDetectNitroRendererPath();
}
private function doDetectNitroRendererPath(): string
{
$setting = $this->getSetting('nitro_renderer_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$dirNames = ['nitro-renderer', 'nitro-render-v3', 'Nitro_Render_V3', 'Nitro-Render-V3', 'nitro-render', 'renderer'];
$found = $this->smartSearch($dirNames, ['package.json', 'src'], ['/var/www', '/opt', '/home', '/root', base_path(), base_path() . '/..']);
if ($found) {
return $found;
}
return base_path() . '/nitro-renderer';
}
public function detectNitroWebroot(): string
{
return $this->cache['nitro_webroot'] ??= $this->doDetectNitroWebroot();
}
private function doDetectNitroWebroot(): string
{
$setting = $this->getSetting('nitro_webroot');
if ($setting && $this->exists($setting)) {
return $setting;
}
// Check nginx config for root directives
$nginxRoot = $this->detectNginxWebroot();
if ($nginxRoot) {
return $nginxRoot;
}
$possiblePaths = [
'/var/www/Client', '/var/www/client', '/var/www/Nitro', '/var/www/nitro',
'/var/www/html/Client', '/var/www/html/client',
'/var/www/atomcms/public/Client', '/var/www/atomcms/public/nitro',
];
foreach ($possiblePaths as $path) {
if ($this->exists($path)) {
return $path;
}
}
return '/var/www/Client';
}
public function detectNitroBuildPath(): string
{
return $this->cache['nitro_build_path'] ??= $this->doDetectNitroBuildPath();
}
private function doDetectNitroBuildPath(): string
{
$setting = $this->getSetting('nitro_build_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$clientPath = $this->detectNitroClientPath();
$distPath = $clientPath . '/dist';
if ($this->exists($distPath)) {
return $distPath;
}
$possiblePaths = [
'/var/www/nitro-client/dist', '/var/www/nitro-V3/dist',
'/var/www/atomcms/nitro-client/dist',
];
foreach ($possiblePaths as $path) {
if ($this->exists($path)) {
return $path;
}
}
return $clientPath . '/dist';
}
// ─── Gamedata ──────────────────────────────────────────────
public function detectGamedataPath(): string
{
return $this->cache['gamedata_path'] ??= $this->doDetectGamedataPath();
}
private function doDetectGamedataPath(): string
{
// Always scan first to detect actual case on disk
$result = Process::timeout(5)->run('ls -1 /var/www/ 2>/dev/null | grep -i "^gamedata$"');
if ($result->successful() && trim($result->output()) !== '') {
$actualDir = trim($result->output());
$path = '/var/www/' . $actualDir;
if ($this->exists($path)) {
// Save detected path to setting for future use
$this->saveSetting('gamedata_path', $path);
return $path;
}
}
// Fallback to setting if scan didn't find it
$setting = $this->getSetting('gamedata_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
// Last resort: check common paths
$possiblePaths = [
'/var/www/atomcms/public/gamedata', '/var/www/html/gamedata',
'/opt/gamedata', '/root/gamedata',
];
foreach ($possiblePaths as $path) {
if ($this->exists($path)) {
return $path;
}
}
return '/var/www/gamedata';
}
public function detectFurnitureIconsPath(): string
{
$setting = $this->getSetting('furniture_icons_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$gamedata = $this->detectGamedataPath();
$iconsPath = $gamedata . '/icons';
if ($this->exists($iconsPath)) {
return $iconsPath;
}
return '/var/www/Gamedata/icons';
}
public function detectCatalogIconsPath(): string
{
$setting = $this->getSetting('catalog_icons_path');
if ($setting && $this->exists($setting)) {
return $setting;
}
$gamedata = $this->detectGamedataPath();
$cataloguePath = $gamedata . '/catalogue';
if ($this->exists($cataloguePath)) {
return $cataloguePath;
}
return '/var/www/Gamedata/catalogue';
}
// ─── Nginx ─────────────────────────────────────────────────
public function detectNginxConfigPath(): ?string
{
$possibleConfigs = [
'/etc/nginx/sites-enabled/cms.conf',
'/etc/nginx/sites-enabled/atom.conf',
'/etc/nginx/sites-enabled/default',
'/etc/nginx/conf.d/atom.conf',
'/etc/nginx/conf.d/cms.conf',
'/etc/nginx/conf.d/default.conf',
];
foreach ($possibleConfigs as $config) {
if (file_exists($config)) {
return $config;
}
}
return null;
}
public function detectNginxWebroot(): ?string
{
$configPath = $this->detectNginxConfigPath();
if (! $configPath) {
return null;
}
$content = @file_get_contents($configPath);
if (! $content) {
return null;
}
// Look for root directives that point to Client/nitro directories
if (preg_match_all('/^\s*root\s+(.+);/m', $content, $matches)) {
foreach ($matches[1] as $root) {
$root = trim($root);
if ((str_contains(strtolower($root), 'client') || str_contains(strtolower($root), 'nitro')) && $this->exists($root)) {
return $root;
}
}
// Return first valid root
foreach ($matches[1] as $root) {
$root = trim($root);
if ($this->exists($root)) {
return $root;
}
}
}
return null;
}
public function detectGamedataUrlPath(): ?string
{
$configPath = $this->detectNginxConfigPath();
if (! $configPath) {
return null;
}
$content = @file_get_contents($configPath);
if (! $content) {
return null;
}
// Look for location /gamedata { alias /var/www/Gamedata; }
// Return the location path (the URL path), not the alias path
if (preg_match('/location\s+(\/gamedata)\s*\{/i', $content, $matches)) {
return $matches[1]; // Returns /gamedata
}
// Fallback: check if there's a root that includes gamedata
if (preg_match_all('/^\s*root\s+(.+);/m', $content, $matches)) {
foreach ($matches[1] as $root) {
$root = trim($root);
if (str_contains(strtolower($root), 'gamedata')) {
$segments = explode('/', trim($root, '/'));
return '/' . end($segments);
}
}
}
return null;
}
// ─── PHP Configuration ─────────────────────────────────────
public function detectPhpVersion(): string
{
$result = Process::timeout(5)->run('php -r "echo PHP_MAJOR_VERSION.\'.\'.PHP_MINOR_VERSION;"');
if ($result->successful()) {
return trim($result->output());
}
return '8.2';
}
public function detectPhpIniPath(): ?string
{
$result = Process::timeout(5)->run('php -i 2>/dev/null | grep "Loaded Configuration File" | cut -d">" -f2 | xargs');
if ($result->successful()) {
$path = trim($result->output());
if (file_exists($path)) {
return $path;
}
}
return null;
}
public function detectPhpFpmService(): ?string
{
$version = $this->detectPhpVersion();
$candidates = [
"php{$version}-fpm",
'php-fpm',
'php8.3-fpm', 'php8.2-fpm', 'php8.1-fpm', 'php8.0-fpm', 'php7.4-fpm',
];
foreach ($candidates as $service) {
$check = Process::timeout(5)->run(
'systemctl list-unit-files ' . escapeshellarg($service) . '.service 2>/dev/null | grep -q ' . escapeshellarg($service) . ' && echo found',
);
if ($check->successful() && trim($check->output()) === 'found') {
return $service;
}
}
return null;
}
// ─── Java ──────────────────────────────────────────────────
public function detectJavaVersion(): ?string
{
$result = Process::timeout(5)->run('java -version 2>&1 | head -1');
if ($result->successful()) {
return trim($result->output());
}
return null;
}
public function detectJavaPath(): ?string
{
$result = Process::timeout(5)->run('which java 2>/dev/null');
if ($result->successful()) {
$path = trim($result->output());
if ($path !== '') {
return $path;
}
}
// Check common locations
$commonPaths = [
'/usr/bin/java', '/usr/local/bin/java',
'/usr/lib/jvm/default/bin/java',
];
foreach ($commonPaths as $path) {
if (file_exists($path)) {
return $path;
}
}
return null;
}
// ─── System Tools ──────────────────────────────────────────
public function detectGitPath(): ?string
{
$result = Process::timeout(5)->run('which git 2>/dev/null');
return $result->successful() ? trim($result->output()) : null;
}
public function detectNodePath(): ?string
{
$result = Process::timeout(5)->run('which node 2>/dev/null');
return $result->successful() ? trim($result->output()) : null;
}
public function detectYarnPath(): ?string
{
$result = Process::timeout(5)->run('which yarn 2>/dev/null');
return $result->successful() ? trim($result->output()) : null;
}
public function detectNpmPath(): ?string
{
$result = Process::timeout(5)->run('which npm 2>/dev/null');
return $result->successful() ? trim($result->output()) : null;
}
public function detectMavenPath(): ?string
{
$result = Process::timeout(5)->run('which mvn 2>/dev/null || which mvnw 2>/dev/null');
if ($result->successful()) {
$path = trim($result->output());
if ($path !== '') {
return $path;
}
}
// Check source path for mvnw
$sourcePath = $this->detectEmulatorSourcePath();
$mvnw = $sourcePath . '/mvnw';
if (file_exists($mvnw)) {
return $mvnw;
}
return null;
}
public function detectGradlePath(): ?string
{
$result = Process::timeout(5)->run('which gradle 2>/dev/null');
if ($result->successful()) {
$path = trim($result->output());
if ($path !== '') {
return $path;
}
}
$sourcePath = $this->detectEmulatorSourcePath();
$gradlew = $sourcePath . '/gradlew';
if (file_exists($gradlew)) {
return $gradlew;
}
return null;
}
// ─── Full Detection ────────────────────────────────────────
public function detectAll(): array
{
return [
// Emulator
'emulator_jar_path' => $this->detectEmulatorJarPath(),
'emulator_source_path' => $this->detectEmulatorSourcePath(),
'emulator_service_name' => $this->detectEmulatorServiceName(),
'emulator_java_process' => $this->detectEmulatorJavaProcess(),
// Nitro
'nitro_client_path' => $this->detectNitroClientPath(),
'nitro_renderer_path' => $this->detectNitroRendererPath(),
'nitro_webroot' => $this->detectNitroWebroot(),
'nitro_build_path' => $this->detectNitroBuildPath(),
// Gamedata
'gamedata_path' => $this->detectGamedataPath(),
'furniture_icons_path' => $this->detectFurnitureIconsPath(),
'catalog_icons_path' => $this->detectCatalogIconsPath(),
// System
'nginx_config_path' => $this->detectNginxConfigPath(),
'php_version' => $this->detectPhpVersion(),
'php_ini_path' => $this->detectPhpIniPath(),
'php_fpm_service' => $this->detectPhpFpmService(),
'java_version' => $this->detectJavaVersion(),
'java_path' => $this->detectJavaPath(),
// Tools
'git_path' => $this->detectGitPath(),
'node_path' => $this->detectNodePath(),
'yarn_path' => $this->detectYarnPath(),
'npm_path' => $this->detectNpmPath(),
'maven_path' => $this->detectMavenPath(),
'gradle_path' => $this->detectGradlePath(),
];
}
public function clearCache(): void
{
$this->cache = [];
}
// ─── Private Helpers ───────────────────────────────────────
private function smartSearch(array $dirNames, array $validators, array $searchDirs): ?string
{
// Phase 1: exact directory name match with validator check
foreach ($searchDirs as $searchDir) {
if (! $this->exists($searchDir)) {
continue;
}
foreach ($dirNames as $dirName) {
$fullPath = rtrim((string) $searchDir, '/') . '/' . $dirName;
if (! $this->exists($fullPath)) {
continue;
}
foreach ($validators as $validator) {
if (str_contains((string) $validator, '*')) {
$result = Process::timeout(5)->run(
'find ' . escapeshellarg($fullPath) . ' -maxdepth 2 -name ' . escapeshellarg((string) $validator) . ' 2>/dev/null | head -1',
);
if ($result->successful() && trim($result->output()) !== '') {
return $fullPath;
}
} elseif ($this->exists($fullPath . '/' . $validator)) {
return $fullPath;
}
}
}
}
// Phase 2: deep search via find
foreach ($searchDirs as $searchDir) {
if (! $this->exists($searchDir)) {
continue;
}
$namePattern = implode('|', $dirNames);
$result = Process::timeout(15)->run(
'find ' . escapeshellarg((string) $searchDir) . ' -maxdepth 4 -type d 2>/dev/null | grep -iE "(' . implode('|', array_map(preg_quote(...), $dirNames)) . ')" | head -5',
);
if ($result->successful()) {
$lines = explode("\n", trim($result->output()));
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') {
continue;
}
foreach ($validators as $validator) {
if (str_contains((string) $validator, '*')) {
$check = Process::timeout(5)->run(
'find ' . escapeshellarg($line) . ' -maxdepth 2 -name ' . escapeshellarg((string) $validator) . ' 2>/dev/null | head -1',
);
if ($check->successful() && trim($check->output()) !== '') {
return $line;
}
} elseif ($this->exists($line . '/' . $validator)) {
return $line;
}
}
}
}
}
return null;
}
private function exists(string $path): bool
{
$result = Process::timeout(5)->run(
'test -e ' . escapeshellarg($path) . ' && echo yes || echo no',
);
return trim($result->output()) === 'yes';
}
private function getSetting(string $key): ?string
{
try {
$settings = resolve(SettingsService::class);
$value = $settings->getOrDefault($key, '');
return ($value !== '' && $value !== '0') ? $value : null;
} catch (\Exception) {
return null;
}
}
private function saveSetting(string $key, string $value): void
{
try {
$settings = resolve(SettingsService::class);
$settings->set($key, $value);
} catch (\Exception) {
// Silently fail - detection still works without saving
}
}
}