You've already forked Atomcms-edit
725 lines
24 KiB
PHP
Executable File
725 lines
24 KiB
PHP
Executable File
<?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
|
|
}
|
|
}
|
|
}
|