Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.action.startup.php
Go to the documentation of this file.
1<?php
2/*
3 *
4 * * Copyright (c) 2022-2025 Bearsampp
5 * * License: GNU General Public License version 3 or later; see LICENSE.txt
6 * * Website: https://bearsampp.com
7 * * Github: https://github.com/Bearsampp
8 *
9 */
10
17{
19 private $splash;
20
22 private $restart;
23
25 private $startTime;
26
28 private $error;
29
31 private $rootPath;
32
34 private $filesToScan;
35
37 const GAUGE_SERVICES = 5;
38
40 const GAUGE_OTHERS = 19;
41
48 public function __construct($args)
49 {
50 global $bearsamppRoot, $bearsamppCore, $bearsamppLang, $bearsamppBins, $bearsamppWinbinder;
51 $this->writeLog('Starting ' . APP_TITLE);
52
53 // Admin check is now performed in root.php before ActionStartup is instantiated
54 // This prevents screen flashes and ensures the check happens before any WinBinder initialization
55 Log::info('Administrator privileges confirmed - proceeding with startup');
56
57 // Init
58 $this->splash = new Splash();
59 $this->restart = false;
60 $this->startTime = Util::getMicrotime();
61 $this->error = '';
62
63 $this->rootPath = Path::getRootPath();
64 $this->filesToScan = array();
65
66 $gauge = self::GAUGE_SERVICES * count($bearsamppBins->getServices());
67 $gauge += self::GAUGE_OTHERS + 1;
68
69 // Start splash screen
70 $this->splash->init(
72 $gauge,
73 sprintf($bearsamppLang->getValue(Lang::STARTUP_STARTING_TEXT), APP_TITLE . ' ' . $bearsamppCore->getAppVersion())
74 );
75
76 $bearsamppWinbinder->setHandler($this->splash->getWbWindow(), $this, 'processWindow', 1000);
77 $bearsamppWinbinder->mainLoop();
78 $bearsamppWinbinder->reset();
79 }
80
90 public function processWindow($window, $id, $ctrl, $param1, $param2)
91 {
92 global $bearsamppRoot, $bearsamppCore, $bearsamppLang, $bearsamppBins, $bearsamppTools, $bearsamppApps, $bearsamppWinbinder;
93
94 Log::trace('Starting processWindow method');
95
96 // Admin check is now performed in the constructor before anything else
97 // No need to check again here
98
99 // Rotation logs
100 Log::trace('Performing log rotation');
101 $this->rotationLogs();
102
103 // Clean
104 Log::trace('Starting cleanup operations');
105 $this->cleanTmpFolders();
106 $this->cleanOldBehaviors();
107
108 // List procs
109 Log::trace('Listing running processes');
110 if ($bearsamppRoot->getProcs() !== false) {
111 $this->writeLog('List procs:');
112 $listProcs = array();
113 foreach ($bearsamppRoot->getProcs() as $proc) {
115 $listProcs[] = '-> ' . basename($unixExePath) . ' (PID ' . $proc[Win32Ps::PROCESS_ID] . ') in ' . $unixExePath;
116 }
117 sort($listProcs);
118 foreach ($listProcs as $proc) {
119 $this->writeLog($proc);
120 }
121 Log::trace('Found ' . count($listProcs) . ' running processes');
122 } else {
123 Log::trace('No processes found or unable to retrieve process list');
124 }
125
126 // List modules
127 Log::trace('Listing bins modules');
128 $this->writeLog('List bins modules:');
129 foreach ($bearsamppBins->getAll() as $module) {
130 if (!$module->isEnable()) {
131 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
132 Log::trace('Bin module ' . $module->getName() . ' is disabled');
133 } else {
134 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
135 Log::trace('Bin module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
136 }
137 }
138
139 Log::trace('Listing tools modules');
140 $this->writeLog('List tools modules:');
141 foreach ($bearsamppTools->getAll() as $module) {
142 if (!$module->isEnable()) {
143 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
144 Log::trace('Tool module ' . $module->getName() . ' is disabled');
145 } else {
146 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
147 Log::trace('Tool module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
148 }
149 }
150
151 Log::trace('Listing apps modules');
152 $this->writeLog('List apps modules:');
153 foreach ($bearsamppApps->getAll() as $module) {
154 if (!$module->isEnable()) {
155 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
156 Log::trace('App module ' . $module->getName() . ' is disabled');
157 } else {
158 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
159 Log::trace('App module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
160 }
161 }
162
163 // Kill old instances
164 Log::trace('Killing old instances');
165 $this->killOldInstances();
166
167 // Prepare app
168 Log::trace('Preparing application - refreshing hostname');
169 $this->refreshHostname();
170
171 Log::trace('Checking launch startup settings');
172 $this->checkLaunchStartup();
173
174 Log::trace('Checking browser configuration');
175 $this->checkBrowser();
176
177 Log::trace('Gathering system information');
178 $this->sysInfos();
179
180 Log::trace('Refreshing aliases');
181 $this->refreshAliases();
182
183 Log::trace('Refreshing virtual hosts');
184 $this->refreshVhosts();
185
186 // Check app path
187 Log::trace('Checking application path');
188 $this->checkPath();
189
190 Log::trace('Scanning folders');
191 $this->scanFolders();
192
193 Log::trace('Changing paths in files');
194 $this->changePath();
195
196 Log::trace('Saving current path');
197 $this->savePath();
198
199 // Check BEARSAMPP_PATH, BEARSAMPP_BINS and System Path reg keys
200 Log::trace('Checking PATH registry key');
201 $this->checkPathRegKey();
202
203 Log::trace('Checking BINS registry key');
204 $this->checkBinsRegKey();
205
206 Log::trace('Checking System PATH registry key');
207 $this->checkSystemPathRegKey();
208
209 // Update config
210 Log::trace('Updating configuration');
211 $this->updateConfig();
212
213 // Create SSL certificates
214 Log::trace('Creating SSL certificates');
215 $this->createSslCrts();
216
217 // Install
218 Log::trace('Installing services');
219 $this->installServices();
220
221 // Actions if everything OK
222 if (!$this->restart && empty($this->error)) {
223 Log::trace('Startup completed successfully - refreshing Git repositories');
224 $this->refreshGitRepos();
225 $startupTime = round(Util::getMicrotime() - $this->startTime, 3);
226 $this->writeLog('Started in ' . $startupTime . 's');
227 Log::trace('Application started successfully in ' . $startupTime . ' seconds');
228
229 // Log total startup time in VERBOSE_TRACE mode (mode 3)
230 global $bearsamppConfig;
231 if ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_TRACE) {
232 $minutes = floor($startupTime / 60); // floor() returns int-compatible value
233 $seconds = fmod($startupTime, 60);
234 $formattedTime = sprintf('%d:%05.2f', $minutes, $seconds);
235 Log::trace('=== TOTAL STARTUP TIME: ' . $formattedTime . ' ===');
236 }
237 } else {
238 Log::trace('Startup issues detected - incrementing progress bar');
239 $this->splash->incrProgressBar(2);
240 }
241
242 if ($this->restart) {
243 Log::trace('Restart required - preparing to restart application');
244 $this->writeLog(APP_TITLE . ' has to be restarted');
245 $this->splash->setTextLoading(
246 sprintf(
248 APP_TITLE . ' ' . $bearsamppCore->getAppVersion()
249 )
250 );
251
252 Log::trace('Deleting all services before restart');
253 foreach ($bearsamppBins->getServices() as $sName => $service) {
254 Log::trace('Deleting service: ' . $sName);
255 $service->delete();
256 }
257
258 Log::trace('Setting execution action to RESTART');
260 }
261
262 if (!empty($this->error)) {
263 Log::trace('Errors occurred during startup: ' . $this->error);
264 $this->writeLog('Error: ' . $this->error);
265 $bearsamppWinbinder->messageBoxError($this->error, $bearsamppLang->getValue(Lang::STARTUP_ERROR_TITLE));
266 }
267
268 Log::trace('Starting loading screen');
269 // Moved Util::startLoading() to after splash window destruction to prevent double progress bars
270
271 // Give the loading window time to initialize before we terminate this process
272 Log::trace('Waiting for loading window to initialize');
273 usleep(500000); // 500ms delay to allow loading window to start
274
275 Log::trace('Loading process started');
276
277 // Closing cli to finish startup
278 Log::trace('Finishing startup process');
279
280 // Safely reset WinBinder and destroy the splash window
281 $bearsamppWinbinder->destroyWindow($window);
282 $bearsamppWinbinder->reset();
283
284 // Start loading screen AFTER splash window is destroyed to prevent double progress bars
286
287 // Exit this startup process cleanly - the loading window will continue running
288 Log::trace('Exiting startup process cleanly');
289 exit(0);
290 }
291
296 private function rotationLogs()
297 {
299
300 Log::trace("Starting log rotation process");
301 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_ROTATION_LOGS_TEXT));
302 $this->splash->incrProgressBar();
303
304 $archivesPath = Path::getLogsPath() . '/archives';
305 if (!is_dir($archivesPath)) {
306 Log::trace("Creating archives directory: " . $archivesPath);
307 mkdir($archivesPath, 0777, true);
308
309 return;
310 }
311
312 $date = date('Y-m-d-His', time());
313 $archiveLogsPath = $archivesPath . '/' . $date;
314 $archiveScriptsPath = $archiveLogsPath . '/scripts';
315
316 // Create archive folders
317 Log::trace("Creating archive directories for current rotation");
318 if (!is_dir($archiveLogsPath)) {
319 Log::trace("Creating logs archive directory: " . $archiveLogsPath);
320 mkdir($archiveLogsPath, 0777, true);
321 } else {
322 Log::trace("Logs archive directory already exists: " . $archiveLogsPath);
323 }
324
325 if (!is_dir($archiveScriptsPath)) {
326 Log::trace("Creating scripts archive directory: " . $archiveScriptsPath);
327 mkdir($archiveScriptsPath, 0777, true);
328 } else {
329 Log::trace("Scripts archive directory already exists: " . $archiveScriptsPath);
330 }
331
332 // Count archives
333 Log::trace("Counting existing archives");
334 $archives = array();
335 $handle = @opendir($archivesPath);
336 if (!$handle) {
337 Log::trace("Failed to open archives directory: " . $archivesPath);
338
339 return;
340 }
341
342 while (false !== ($file = readdir($handle))) {
343 if ($file == '.' || $file == '..') {
344 continue;
345 }
346 $archives[] = $archivesPath . '/' . $file;
347 }
348 closedir($handle);
349 sort($archives);
350 Log::trace("Found " . count($archives) . " existing archives");
351
352 // Remove old archives
353 if (count($archives) > $bearsamppConfig->getMaxLogsArchives()) {
354 $total = count($archives) - $bearsamppConfig->getMaxLogsArchives();
355 Log::trace("Removing " . $total . " old archives");
356 for ($i = 0; $i < $total; $i++) {
357 Log::trace("Deleting old archive: " . $archives[$i]);
358 Util::deleteFolder($archives[$i]);
359 }
360 }
361
362 // Helper function to check if a file is locked
363 $isFileLocked = function ($filePath) {
364 if (!file_exists($filePath)) {
365 return false;
366 }
367
368 $handle = @fopen($filePath, 'r+');
369 if ($handle === false) {
370 Log::trace("File appears to be locked: " . $filePath);
371
372 return true; // File is locked
373 }
374
375 fclose($handle);
376
377 return false; // File is not locked
378 };
379
380 // Logs
381 Log::trace("Archiving log files");
382 $srcPath = Path::getLogsPath();
383 $handle = @opendir($srcPath);
384 if (!$handle) {
385 Log::trace("Failed to open logs directory: " . $srcPath);
386
387 return;
388 }
389
390 $logsCopied = 0;
391 $logsSkipped = 0;
392
393 while (false !== ($file = readdir($handle))) {
394 if ($file == '.' || $file == '..' || is_dir($srcPath . '/' . $file)) {
395 continue;
396 }
397
398 $sourceFile = $srcPath . '/' . $file;
399 $destFile = $archiveLogsPath . '/' . $file;
400
401 // Check if file is locked before attempting to copy
402 if ($isFileLocked($sourceFile)) {
403 Log::trace("Skipping locked log file: " . $file);
404 $logsSkipped++;
405 continue;
406 }
407
408 try {
409 if (copy($sourceFile, $destFile)) {
410 $logsCopied++;
411 Log::trace("Archived log file: " . $file);
412 } else {
413 $logsSkipped++;
414 Log::trace("Failed to copy log file: " . $file);
415 }
416 } catch (Exception $e) {
417 $logsSkipped++;
418 Log::trace("Exception copying log file " . $file . ": " . $e->getMessage());
419 }
420 }
421 closedir($handle);
422 Log::trace("Logs archived: " . $logsCopied . " copied, " . $logsSkipped . " skipped");
423
424 // Scripts
425 Log::trace("Archiving script files");
426 $srcPath = Path::getTmpPath();
427 $handle = @opendir($srcPath);
428 if (!$handle) {
429 Log::trace("Failed to open tmp directory: " . $srcPath);
430
431 return;
432 }
433
434 $scriptsCopied = 0;
435 $scriptsSkipped = 0;
436
437 while (false !== ($file = readdir($handle))) {
438 if ($file == '.' || $file == '..' || is_dir($srcPath . '/' . $file)) {
439 continue;
440 }
441
442 $sourceFile = $srcPath . '/' . $file;
443 $destFile = $archiveScriptsPath . '/' . $file;
444
445 // Check if file is locked before attempting to copy
446 if ($isFileLocked($sourceFile)) {
447 Log::trace("Skipping locked script file: " . $file);
448 $scriptsSkipped++;
449 continue;
450 }
451
452 try {
453 if (copy($sourceFile, $destFile)) {
454 $scriptsCopied++;
455 Log::trace("Archived script file: " . $file);
456 } else {
457 $scriptsSkipped++;
458 Log::trace("Failed to copy script file: " . $file);
459 }
460 } catch (Exception $e) {
461 $scriptsSkipped++;
462 Log::trace("Exception copying script file " . $file . ": " . $e->getMessage());
463 }
464 }
465 closedir($handle);
466 Log::trace("Scripts archived: " . $scriptsCopied . " copied, " . $scriptsSkipped . " skipped");
467
468 // Purge logs - only delete files that aren't locked
469 Log::trace("Purging log files");
470 $logsPath = Path::getLogsPath();
471 $handle = @opendir($logsPath);
472 if (!$handle) {
473 Log::trace("Failed to open logs directory for purging: " . $logsPath);
474
475 return;
476 }
477
478 $logsDeleted = 0;
479 $logsPurgeSkipped = 0;
480
481 while (false !== ($file = readdir($handle))) {
482 if ($file == '.' || $file == '..' || $file == 'archives' || $file == '.gitignore' || is_dir($logsPath . '/' . $file)) {
483 continue;
484 }
485
486 $filePath = $logsPath . '/' . $file;
487
488 // Check if file is locked before attempting to delete
489 if ($isFileLocked($filePath)) {
490 Log::trace("Skipping locked log file during purge: " . $file);
491 $logsPurgeSkipped++;
492 continue;
493 }
494
495 try {
496 if (is_link($filePath)) {
497 if (@unlink($filePath)) {
498 $logsDeleted++;
499 Log::trace("Purged log symlink: " . $file);
500 } else {
501 $logsPurgeSkipped++;
502 Log::trace("Failed to purge log symlink: " . $file);
503 }
504 } elseif (file_exists($filePath)) {
505 if (@unlink($filePath)) {
506 $logsDeleted++;
507 Log::trace("Purged log file: " . $file);
508 } else {
509 $logsPurgeSkipped++;
510 Log::trace("Failed to purge log file: " . $file);
511 }
512 } else {
513 $logsPurgeSkipped++;
514 Log::trace("Log file already removed or does not exist: " . $file);
515 }
516 } catch (Exception $e) {
517 $logsPurgeSkipped++;
518 Log::trace("Exception purging log file " . $file . ": " . $e->getMessage());
519 }
520 }
521 closedir($handle);
522 Log::trace("Logs purged: " . $logsDeleted . " deleted, " . $logsPurgeSkipped . " skipped");
523
524 Log::trace("Log rotation completed");
525 }
526
530 private function cleanTmpFolders()
531 {
533
534 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_CLEAN_TMP_TEXT));
535 $this->splash->incrProgressBar();
536
537 $this->writeLog('Clear tmp folders');
538 Util::clearFolder(Path::getTmpPath(), array('cachegrind', 'composer', 'openssl', 'mailpit', 'xlight', 'npm-cache', 'pip', 'opcache', '.gitignore'));
539 Util::clearFolder(Path::getTmpPath(), array('.gitignore'));
540
541 // Ensure opcache directory exists for persistent file cache
542 $opcachePath = Path::getTmpPath() . DIRECTORY_SEPARATOR . 'opcache';
543
544 if (!is_dir($opcachePath)) {
545 $this->writeLog('Creating opcache directory: ' . $opcachePath);
546 if (!@mkdir($opcachePath, 0755, true) && !is_dir($opcachePath)) {
547 $this->writeLog('Failed to create opcache directory: ' . $opcachePath);
548
549 return;
550 }
551 }
552
553 if (!is_writable($opcachePath)) {
554 $this->writeLog('Opcache directory is not writable: ' . $opcachePath);
555 }
556 }
557
561 private function cleanOldBehaviors()
562 {
563 global $bearsamppLang, $bearsamppRegistry;
564
565 $this->writeLog('Clean old behaviors');
566
567 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_CLEAN_OLD_BEHAVIORS_TEXT));
568 $this->splash->incrProgressBar();
569
570 // App >= 1.0.13
571 $bearsamppRegistry->deleteValue(
573 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
575 );
576 }
577
581 private function killOldInstances()
582 {
583 global $bearsamppLang;
584
585 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_KILL_OLD_PROCS_TEXT));
586 $this->splash->incrProgressBar();
587
588 // Stop services
589 /*foreach ($bearsamppBins->getServices() as $sName => $service) {
590 $serviceInfos = $service->infos();
591 if ($serviceInfos === false) {
592 continue;
593 }
594 $service->stop();
595 }*/
596
597 // Stop third party procs
598 $procsKilled = Win32Ps::killBins();
599 if (!empty($procsKilled)) {
600 $this->writeLog('Procs killed:');
601 $procsKilledSort = array();
602 foreach ($procsKilled as $proc) {
604 $procsKilledSort[] = '-> ' . basename($unixExePath) . ' (PID ' . $proc[Win32Ps::PROCESS_ID] . ') in ' . $unixExePath;
605 }
606 sort($procsKilledSort);
607 foreach ($procsKilledSort as $proc) {
608 $this->writeLog($proc);
609 }
610 }
611 }
612
616 private function refreshHostname()
617 {
619
620 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_REFRESH_HOSTNAME_TEXT));
621 $this->splash->incrProgressBar();
622 $this->writeLog('Refresh hostname');
623
624 $bearsamppConfig->replace(Config::CFG_HOSTNAME, gethostname());
625 }
626
630 private function checkLaunchStartup()
631 {
632 global $bearsamppConfig;
633
634 $this->writeLog('Check launch startup');
635
636 if ($bearsamppConfig->isLaunchStartup()) {
638 } else {
640 }
641 }
642
646 private function checkBrowser()
647 {
649
650 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_CHECK_BROWSER_TEXT));
651 $this->splash->incrProgressBar();
652 $this->writeLog('Check browser');
653
654 $currentBrowser = $bearsamppConfig->getBrowser();
655 if (empty($currentBrowser) || !file_exists($currentBrowser)) {
657 }
658 }
659
663 private function sysInfos()
664 {
665 global $bearsamppLang;
666
667 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_SYS_INFOS));
668 $this->splash->incrProgressBar();
669
670 $os = Batch::getOsInfo();
671 $this->writeLog(sprintf('OS: %s', $os));
672 }
673
677 private function refreshAliases()
678 {
680
681 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_REFRESH_ALIAS_TEXT));
682 $this->splash->incrProgressBar();
683 $this->writeLog('Refresh aliases');
684
685 $bearsamppBins->getApache()->refreshAlias($bearsamppConfig->isOnline());
686 }
687
691 private function refreshVhosts()
692 {
694
695 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_REFRESH_VHOSTS_TEXT));
696 $this->splash->incrProgressBar();
697 $this->writeLog('Refresh vhosts');
698
699 $bearsamppBins->getApache()->refreshVhosts($bearsamppConfig->isOnline());
700 }
701
705 private function checkPath()
706 {
708
709 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_CHECK_PATH_TEXT));
710 $this->splash->incrProgressBar();
711
712 $this->writeLog('Last path: ' . $bearsamppCore->getLastPathContent());
713 }
714
720 private function scanFolders()
721 {
723
724 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_SCAN_FOLDERS_TEXT));
725 $this->splash->incrProgressBar();
726
727 $lastPath = $bearsamppCore->getLastPathContent();
728 $currentPath = $this->rootPath;
729
730 // Performance optimization: Skip scan if path hasn't changed
731 // BUT always scan if it's the first time or if we need to ensure placeholders are replaced.
732 // We removed the optimization because it prevents newly added modules from being initialized
733 // with the correct paths if the main project path remains the same.
734 /*
735 if ($lastPath === $currentPath && !empty($lastPath)) {
736 Log::debug('Path unchanged, skipping file scan (performance optimization)');
737 Log::trace('Last path: "' . $lastPath . '" matches current path: "' . $currentPath . '"');
738
739 $this->filesToScan = [];
740 $this->writeLog('Files to scan: 0 (path unchanged - scan skipped)');
741 return;
742 }
743 */
744
745 // Path changed or first run, perform full scan
746 if ($lastPath !== $currentPath || empty($lastPath)) {
747 Log::debug('Path changed or first run, performing full file scan');
748 Log::trace('Last path: "' . $lastPath . '" differs from current path: "' . $currentPath . '"');
749 $this->writeLog('Path changed detected - performing full scan');
750 } else {
751 Log::debug('Ensuring configuration files are checked for placeholders');
752 $this->writeLog('Scanning configuration files for placeholders');
753 }
754
755 $scanStartTime = Util::getMicrotime();
756 $this->filesToScan = Util::getFilesToScan();
757 $scanDuration = round(Util::getMicrotime() - $scanStartTime, 3);
758
759 $this->writeLog('Files to scan: ' . count($this->filesToScan) . ' (scanned in ' . $scanDuration . 's)');
760 }
761
765 private function changePath()
766 {
767 global $bearsamppLang;
768
769 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_CHANGE_PATH_TEXT), $this->rootPath));
770 $this->splash->incrProgressBar();
771
772 $result = Path::changePath($this->filesToScan, $this->rootPath);
773 $this->writeLog('Nb files changed: ' . $result['countChangedFiles']);
774 $this->writeLog('Nb occurences changed: ' . $result['countChangedOcc']);
775 }
776
780 private function savePath()
781 {
782 global $bearsamppCore;
783
784 file_put_contents(Path::getLastPath(), $this->rootPath);
785 $this->writeLog('Save current path: ' . $this->rootPath);
786 }
787
791 private function checkPathRegKey()
792 {
793 global $bearsamppRoot, $bearsamppLang, $bearsamppRegistry;
794
795 $currentAppPathRegKey = $bearsamppRegistry->getAppPathRegKey();
796 $genAppPathRegKey = Path::formatWindowsPath(Path::getRootPath());
797 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_REGISTRY_TEXT), Registry::APP_PATH_REG_ENTRY . ': ' . $genAppPathRegKey));
798 $this->splash->incrProgressBar();
799
800 $this->writeLog('Current app path reg key: ' . $currentAppPathRegKey);
801 $this->writeLog('Gen app path reg key: ' . $genAppPathRegKey);
802 if ($currentAppPathRegKey != $genAppPathRegKey) {
803 if (!$bearsamppRegistry->setAppPathRegKey($genAppPathRegKey)) {
804 if (!empty($this->error)) {
805 $this->error .= PHP_EOL . PHP_EOL;
806 }
808 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
809 } else {
810 $this->writeLog('Need restart: checkPathRegKey');
811 $this->restart = true;
812 }
813 }
814 }
815
823 private function checkBinsRegKey()
824 {
825 global $bearsamppLang, $bearsamppRegistry;
826
827 $currentAppBinsRegKey = $bearsamppRegistry->getAppBinsRegKey();
828 $genAppBinsRegKey = $bearsamppRegistry->getAppBinsRegKey(false);
829 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_REGISTRY_TEXT), Registry::APP_BINS_REG_ENTRY . ': ' . $genAppBinsRegKey));
830 $this->splash->incrProgressBar();
831
832 $this->writeLog('Current app bins reg key: ' . $currentAppBinsRegKey);
833 $this->writeLog('Gen app bins reg key: ' . $genAppBinsRegKey);
834 if ($currentAppBinsRegKey != $genAppBinsRegKey) {
835 if (!$bearsamppRegistry->setAppBinsRegKey($genAppBinsRegKey)) {
836 if (!empty($this->error)) {
837 $this->error .= PHP_EOL . PHP_EOL;
838 }
840 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
841 } else {
842 $this->writeLog('Need restart: checkBinsRegKey');
843 $this->restart = true;
844 }
845 }
846 }
847
855 private function checkSystemPathRegKey()
856 {
857 global $bearsamppLang, $bearsamppRegistry;
858
859 $currentSysPathRegKey = $bearsamppRegistry->getSysPathRegKey();
860 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_REGISTRY_TEXT), Registry::SYSPATH_REG_ENTRY));
861 $this->splash->incrProgressBar();
862
863 $this->writeLog('Current system PATH: ' . $currentSysPathRegKey);
864
865 $newSysPathRegKey = str_replace('%' . Registry::APP_BINS_REG_ENTRY . '%;', '', $currentSysPathRegKey);
866 $newSysPathRegKey = str_replace('%' . Registry::APP_BINS_REG_ENTRY . '%', '', $newSysPathRegKey);
867 $newSysPathRegKey = '%' . Registry::APP_BINS_REG_ENTRY . '%;' . $newSysPathRegKey;
868 $this->writeLog('New system PATH: ' . $newSysPathRegKey);
869
870 if ($currentSysPathRegKey != $newSysPathRegKey) {
871 if (!$bearsamppRegistry->setSysPathRegKey($newSysPathRegKey)) {
872 if (!empty($this->error)) {
873 $this->error .= PHP_EOL . PHP_EOL;
874 }
876 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
877 } else {
878 $this->writeLog('Need restart: checkSystemPathRegKey');
879 $this->restart = true;
880 }
881 } else {
882 $this->writeLog('Refresh system PATH: ' . $currentSysPathRegKey);
883 $bearsamppRegistry->setSysPathRegKey(str_replace('%' . Registry::APP_BINS_REG_ENTRY . '%', '', $currentSysPathRegKey));
884 $bearsamppRegistry->setSysPathRegKey($currentSysPathRegKey);
885 }
886 }
887
892 private function updateConfig()
893 {
894 global $bearsamppLang, $bearsamppBins, $bearsamppTools, $bearsamppApps;
895
896 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_UPDATE_CONFIG_TEXT));
897 $this->splash->incrProgressBar();
898 $this->writeLog('Update config');
899
900 $bearsamppBins->update();
901 $bearsamppTools->update();
902 $bearsamppApps->update();
903 }
904
909 private function createSslCrts()
910 {
911 global $bearsamppLang, $bearsamppOpenSsl, $bearsamppBins;
912
913 Log::info('Checking SSL certificates during startup...');
914 $this->splash->incrProgressBar();
915
916 // Always ensure localhost exists and is valid
917 $localhostExpired = $bearsamppOpenSsl->isExpired('localhost');
918 Log::trace('Localhost SSL expired status: ' . ($localhostExpired ? 'YES' : 'NO'));
919
920 if ($localhostExpired) {
921 Log::info('SSL certificate for "localhost" is missing or expired. Checking if creation is necessary...');
922 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_GEN_SSL_CRT_TEXT), 'localhost'));
923
924 // Only create if NOT present or REALLY expired
925 // (isExpired already returns true if missing)
926 if (!$bearsamppOpenSsl->createCrt('localhost')) {
927 Log::error('FAILED call to createCrt("localhost")');
928 }
929
930 // Re-verify after creation to log success/failure
931 if ($bearsamppOpenSsl->isExpired('localhost')) {
932 Log::error('FAILED to verify localhost SSL certificate after creation attempt.');
933 } else {
934 Log::info('Successfully verified localhost SSL certificate.');
935 }
936 } else {
937 Log::trace('SSL certificate for "localhost" is valid.');
938 }
939
940 // Also check all Apache vhosts
941 $apache = $bearsamppBins->getApache();
942 if ($apache) {
943 $vhosts = $apache->getVhosts();
944 Log::info('Checking SSL certificates for ' . count($vhosts) . ' vhosts');
945 foreach ($vhosts as $vhost) {
946 if ($bearsamppOpenSsl->isExpired($vhost)) {
947 Log::info('SSL certificate for "' . $vhost . '" is missing or expired. Creating new one...');
948 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_GEN_SSL_CRT_TEXT), $vhost));
949 if (!$bearsamppOpenSsl->createCrt($vhost)) {
950 Log::error('FAILED call to createCrt("' . $vhost . '")');
951 }
952 } else {
953 Log::trace('SSL certificate for "' . $vhost . '" is valid.');
954 }
955 }
956 } else {
957 Log::info('Apache module not found or disabled, skipping vhost SSL check');
958 }
959
960 // Offer to trust an existing (previously untrusted) SSL Root CA in Firefox
961 $bearsamppOpenSsl->ensureFirefoxTrustPrompt();
962 }
963
971
972 private function installServices()
973 {
975
976 Log::trace('Starting installServices method');
977
978 if (!$this->restart) {
979 Log::trace('Normal startup mode - processing services');
980
981 // Admin check is now performed at the very beginning of processWindow()
982 // No need to check again here
983
984 // Service Installation
985 $this->installServicesSequential($bearsamppBins, $bearsamppLang);
986 } else {
987 Log::trace('Restart mode - skipping service installation');
988 $this->splash->incrProgressBar(self::GAUGE_SERVICES * count($bearsamppBins->getServices()));
989 }
990
991 Log::trace('Completed installServices method');
992 }
993
1003 {
1004 Log::trace('Starting sequential service installation');
1005 $installStartTime = Util::getMicrotime();
1006
1007 // Pre-fetch all services to speed up preparation phase
1009
1010 // Skip symlink creation during checking phase for performance
1012
1013 try {
1014 // Step 1: Check and prepare all services
1015 $servicesToStart = [];
1016 $serviceErrors = [];
1017
1018 $totalServiceCount = count($bearsamppBins->getServices());
1019 $currentServiceIndex = 0;
1020
1021 foreach ($bearsamppBins->getServices() as $sName => $service) {
1022 $currentServiceIndex++;
1023
1024 Log::trace('Preparing service: ' . $sName);
1025
1026 // prepareService() increments 1 step
1027 $serviceInfo = $this->prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentServiceIndex, $totalServiceCount);
1028
1029 if ($serviceInfo['restart']) {
1030 $this->writeLog('Need restart: installService ' . $serviceInfo['bin']->getName());
1031 Log::trace('Restart required for service: ' . $serviceInfo['bin']->getName());
1032 $this->restart = true;
1033 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
1034 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
1035 continue;
1036 }
1037
1038 if (!empty($serviceInfo['error'])) {
1039 $serviceErrors[$sName] = $serviceInfo;
1040 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
1041 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
1042 continue;
1043 }
1044
1045 if ($serviceInfo['needsStart']) {
1046 $servicesToStart[$sName] = $serviceInfo;
1047 } else {
1048 // Service already running or doesn't need to start
1049 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
1050 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
1051 }
1052 }
1053 } finally {
1054 // Re-enable symlink creation and reload bins with symlinks created
1056 Log::trace('Re-enabling symlink creation after service checking');
1057 $bearsamppBins->reload();
1058 }
1059
1060 // Step 2: Start all services in parallel with progress updates
1061 if (!empty($servicesToStart)) {
1062 Log::trace('Starting ' . count($servicesToStart) . ' services in parallel');
1063
1064 $parallelStartTime = Util::getMicrotime();
1065 $serviceCount = 0;
1066 $totalServices = count($servicesToStart);
1067
1068 // Use parallel startup for optimized performance (40-60% faster)
1070 $servicesToStart,
1071 function ($current, $total, $serviceName) use (&$serviceCount, $bearsamppLang) {
1072 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::LOADING_START_SERVICE), $serviceName) . ' (' . $current . '/' . $total . ')');
1073 // Increment progress bar as each start command is sent
1074 if ($current > $serviceCount) {
1075 $this->splash->incrProgressBar();
1076 $serviceCount = $current;
1077 }
1078 }
1079 );
1080
1081 // Check which services are actually running and handle failures
1082 $parallelDuration = round(Util::getMicrotime() - $parallelStartTime, 3);
1083 Log::trace('Parallel startup phase completed in ' . $parallelDuration . ' seconds');
1084
1085 $verifyCount = 0;
1086 foreach ($servicesToStart as $sName => $serviceInfo) {
1087 $verifyCount++;
1088 $name = $serviceInfo['name'];
1089 $service = $serviceInfo['service'];
1090
1091 // Update splash during verification phase
1092 $this->splash->setTextLoading(sprintf($bearsamppLang->getValue(Lang::STARTUP_VERIFY_SERVICE_TEXT), $name) . ' (' . $verifyCount . '/' . $totalServices . ')');
1093
1094 if ($service->isRunning()) {
1095 $this->writeLog($name . ' service started successfully');
1096 Log::trace('Service ' . $name . ' verified running');
1097 } else {
1098 $error = $service->getError();
1099 if (empty($error)) {
1100 $error = 'Failed to start service after parallel startup';
1101 }
1102
1103 $serviceErrors[$sName] = $serviceInfo;
1104 $serviceErrors[$sName]['error'] = $error;
1105 Log::warning('Service ' . $name . ' failed to start: ' . $error);
1106
1107 // Run syntax check if available
1108 if (!empty($serviceInfo['syntaxCheckCmd'])) {
1109 try {
1110 $cmdSyntaxCheck = $serviceInfo['bin']->getCmdLineOutput($serviceInfo['syntaxCheckCmd']);
1111 if (!$cmdSyntaxCheck['syntaxOk']) {
1112 $serviceErrors[$sName]['error'] .= PHP_EOL . 'Syntax error: ' . $cmdSyntaxCheck['content'];
1113 }
1114 } catch (\Exception $e) {
1115 // Ignore syntax check errors
1116 }
1117 }
1118 }
1119
1120 // Increment progress bar for verification step
1121 // Total for parallel path: 1 (prepare) + 1 (start callback) + 3 (verify) = 5
1122 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 2);
1123 }
1124
1125 Log::info('Parallel service startup completed: ' . count($servicesToStart) . ' services processed');
1126 }
1127
1128 // Step 3: Report any errors
1129 foreach ($serviceErrors as $sName => $serviceInfo) {
1130 if (!empty($serviceInfo['error'])) {
1131 Log::trace('Service error occurred for ' . $sName . ': ' . $serviceInfo['error']);
1132 if (!empty($this->error)) {
1133 $this->error .= PHP_EOL . PHP_EOL;
1134 }
1135 $this->error .= sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_ERROR), $serviceInfo['name']) . PHP_EOL . $serviceInfo['error'];
1136 }
1137 }
1138
1139 $installDuration = round(Util::getMicrotime() - $installStartTime, 3);
1140 $this->writeLog('Service installation completed in ' . $installDuration . 's');
1141 Log::trace('Service installation completed in ' . $installDuration . ' seconds');
1142 }
1143
1156 private function prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentIndex = 0, $totalCount = 0)
1157 {
1158 $serviceInfo = [
1159 'sName' => $sName,
1160 'service' => $service,
1161 'bin' => null,
1162 'name' => '',
1163 'port' => 0,
1164 'syntaxCheckCmd' => null,
1165 'error' => '',
1166 'restart' => false,
1167 'needsStart' => false,
1168 'startTime' => Util::getMicrotime()
1169 ];
1170
1171 // Identify service type and get bin
1172 $syntaxCheckCmd = null;
1173 $bin = null;
1174 $port = 0;
1175
1176 if ($sName == BinMailpit::SERVICE_NAME) {
1177 $bin = $bearsamppBins->getMailpit();
1178 $port = $bearsamppBins->getMailpit()->getSmtpPort();
1179 } elseif ($sName == BinMemcached::SERVICE_NAME) {
1180 $bin = $bearsamppBins->getMemcached();
1181 $port = $bearsamppBins->getMemcached()->getPort();
1182 } elseif ($sName == BinApache::SERVICE_NAME) {
1183 $bin = $bearsamppBins->getApache();
1184 $port = $bearsamppBins->getApache()->getPort();
1185 $syntaxCheckCmd = BinApache::CMD_SYNTAX_CHECK;
1186 } elseif ($sName == BinMysql::SERVICE_NAME) {
1187 $bin = $bearsamppBins->getMysql();
1188 $port = $bearsamppBins->getMysql()->getPort();
1189 $syntaxCheckCmd = BinMysql::CMD_SYNTAX_CHECK;
1190
1191 // Pre-initialize MySQL data if needed
1192 if (!file_exists($bin->getDataDir()) || count(glob($bin->getDataDir() . '/*')) === 0) {
1193 Log::trace('Pre-initializing MySQL data directory');
1194 $bin->initData();
1195 }
1196 } elseif ($sName == BinMariadb::SERVICE_NAME) {
1197 $bin = $bearsamppBins->getMariadb();
1198 $port = $bearsamppBins->getMariadb()->getPort();
1199 $syntaxCheckCmd = BinMariadb::CMD_SYNTAX_CHECK;
1200 } elseif ($sName == BinPostgresql::SERVICE_NAME) {
1201 $bin = $bearsamppBins->getPostgresql();
1202 $port = $bearsamppBins->getPostgresql()->getPort();
1203 } elseif ($sName == BinXlight::SERVICE_NAME) {
1204 $bin = $bearsamppBins->getXlight();
1205 $port = $bearsamppBins->getXlight()->getPort();
1206 }
1207
1208 $name = $bin->getName() . ' ' . $bin->getVersion() . ' (' . $service->getName() . ')';
1209
1210 $serviceInfo['bin'] = $bin;
1211 $serviceInfo['name'] = $name;
1212 $serviceInfo['port'] = $port;
1213 $serviceInfo['syntaxCheckCmd'] = $syntaxCheckCmd;
1214
1215 // Update splash with current service being checked (1 step)
1216 if ($currentIndex > 0 && $totalCount > 0) {
1217 $this->splash->setTextLoading('Checking ' . $bin->getName() . ' service (' . $currentIndex . '/' . $totalCount . ')');
1218 }
1219 $this->splash->incrProgressBar();
1220
1221 // Check if service is already installed
1222 $serviceAlreadyInstalled = false;
1223 $serviceToRemove = false;
1224
1225 // Skip service checks for disabled services
1226 if (!$bin->isEnable()) {
1227 Log::trace('Skipping service check for disabled bin: ' . $bin->getName());
1228 $serviceInfos = false;
1229 } else {
1230 if ($sName == BinApache::SERVICE_NAME) {
1231 $serviceInfos = $this->checkApacheServiceWithTimeout($service);
1232 } else {
1233 if ($sName == BinMysql::SERVICE_NAME) {
1234 $serviceInfos = $this->checkMySQLServiceWithTimeout($service, $bin);
1235 if ($serviceInfos === false && $service->isInstalled()) {
1236 Log::trace('MySQL service appears to be hanging, forcing restart');
1237 Win32Ps::killBins(['mysqld.exe']);
1238 $service->delete();
1239 $serviceToRemove = true;
1240 }
1241 } else {
1242 try {
1243 $serviceInfos = $service->infos();
1244 } catch (\Exception $e) {
1245 Log::trace("Exception during service check: " . $e->getMessage());
1246 $serviceInfos = false;
1247 } catch (\Throwable $e) {
1248 Log::trace("Throwable during service check: " . $e->getMessage());
1249 $serviceInfos = false;
1250 }
1251 }
1252 }
1253 }
1254
1255 if ($serviceInfos !== false) {
1256 $serviceAlreadyInstalled = true;
1257 $this->writeLog($name . ' service already installed');
1258
1259 // Check if service needs to be removed and reinstalled
1260 if ($sName == BinPostgresql::SERVICE_NAME) {
1261 $serviceGenPathName = trim(str_replace('"', '', $service->getBinPath()));
1262 $installedPathParts = explode(' ', $serviceInfos[Win32Service::VBS_PATH_NAME], 2);
1263 $serviceVbsPathName = trim(str_replace('"', '', $installedPathParts[0]));
1264 } else {
1265 $serviceGenPathName = trim(str_replace('"', '', $service->getBinPath() . ($service->getParams() ? ' ' . $service->getParams() : '')));
1266 $serviceVbsPathName = trim(str_replace('"', '', $serviceInfos[Win32Service::VBS_PATH_NAME]));
1267 }
1268
1269 $normalizedGenPath = preg_replace('/\s+/', ' ', $serviceGenPathName);
1270 $normalizedVbsPath = preg_replace('/\s+/', ' ', $serviceVbsPathName);
1271
1272 if ($normalizedGenPath !== $normalizedVbsPath && $serviceGenPathName != $serviceVbsPathName) {
1273 $serviceToRemove = true;
1274 $this->writeLog($name . ' service has to be removed');
1275 }
1276 }
1277
1278 // Remove service if needed (no progress increment - part of check phase)
1279 if ($serviceToRemove) {
1280 if (!$service->delete()) {
1281 $serviceInfo['restart'] = true;
1282
1283 return $serviceInfo;
1284 }
1285 }
1286
1287 // Check port availability (no progress increment - part of check phase)
1288 $isPortInUse = ServiceHelper::isPortInUse($port);
1289 if ($isPortInUse !== false) {
1290 // Port is in use - check if it's our service that's already running
1291 $isRunning = false;
1292 if ($serviceInfos !== false && is_array($serviceInfos)) {
1293 // Extract state from serviceInfos array (works for both NSSM and Win32Service)
1294 $state = $serviceInfos[Win32Service::SERVICE_STATE] ?? $serviceInfos['CurrentState'] ?? null;
1295
1296 // Check if running: state can be numeric '4' or string 'RUNNING'
1297 if ($state !== null) {
1298 $isRunning = ($state == Win32Service::WIN32_SERVICE_RUNNING) ||
1299 (strtoupper((string)$state) === 'RUNNING');
1300 } else {
1301 // Fallback to calling isRunning() if state is not found
1302 $isRunning = $service->isRunning();
1303 }
1304 } else {
1305 if ($serviceInfos === false) {
1306 // Fallback when serviceInfos is false
1307 $isRunning = $service->isRunning();
1308 }
1309 }
1310
1311 if ($isRunning) {
1312 // Service is already running and owns the port - this is OK
1313 $this->writeLog($name . ' service already running on port ' . $port);
1314 Log::trace('Service ' . $name . ' already running - no need to start');
1315 $serviceInfo['needsStart'] = false;
1316
1317 return $serviceInfo;
1318 }
1319
1320 // Fallback to a single status check if serviceInfos didn't give us the answer
1321 if ($serviceInfos === false && $service->isRunning()) {
1322 $this->writeLog($name . ' service already running on port ' . $port);
1323 Log::trace('Service ' . $name . ' already running - no need to start');
1324 $serviceInfo['needsStart'] = false;
1325
1326 return $serviceInfo;
1327 }
1328
1329 // Port is in use by something else - this is an error
1330 $serviceInfo['error'] = sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_PORT_ERROR), $port, $isPortInUse);
1331
1332 return $serviceInfo;
1333 }
1334
1335 // Install service if needed (no progress increment - part of check phase)
1336 if (!$serviceAlreadyInstalled || $serviceToRemove) {
1337 if (!$service->create()) {
1338 $serviceInfo['error'] = sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_CREATE_ERROR), $service->getError());
1339
1340 return $serviceInfo;
1341 }
1342 }
1343
1344 $serviceInfo['needsStart'] = true;
1345
1346 return $serviceInfo;
1347 }
1348
1353 private function refreshGitRepos()
1354 {
1355 global $bearsamppLang, $bearsamppTools;
1356
1357 $this->splash->incrProgressBar();
1358 if ($bearsamppTools->getGit()->isScanStartup()) {
1359 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_REFRESH_GIT_REPOS_TEXT));
1360
1361 $repos = $bearsamppTools->getGit()->findRepos(false);
1362 $this->writeLog('Update GIT repos: ' . count($repos) . ' found');
1363 }
1364 }
1365
1374 private function checkApacheServiceWithTimeout($service)
1375 {
1376 Log::trace('Starting specialized Apache service check with timeout protection');
1377
1378 // Set a timeout for the Apache service check
1379 $serviceCheckStartTime = microtime(true);
1380 $serviceCheckTimeout = 3; // 3 seconds timeout
1381
1382 try {
1383 // Use a non-blocking approach to check service
1384 $serviceInfos = false;
1385
1386 // First try a quick check if the service exists in the list
1387 $serviceList = Win32Service::getServices();
1388 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1389 Log::trace('Apache service found in service list, getting details');
1390
1391 // Service exists, now try to get its details with timeout protection
1392 $startTime = microtime(true);
1393 $serviceInfos = $service->infos();
1394
1395 // Check if we've exceeded our timeout
1396 if (microtime(true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1397 Log::trace("Apache service check timeout exceeded, assuming service needs reinstall");
1398
1399 return false;
1400 }
1401 } else {
1402 Log::trace('Apache service not found in service list');
1403
1404 return false;
1405 }
1406
1407 return $serviceInfos;
1408 } catch (\Exception $e) {
1409 Log::trace("Exception during Apache service check: " . $e->getMessage());
1410
1411 return false;
1412 } catch (\Throwable $e) {
1413 Log::trace("Throwable during Apache service check: " . $e->getMessage());
1414
1415 return false;
1416 }
1417 }
1418
1428 private function checkMySQLServiceWithTimeout($service, $bin)
1429 {
1430 Log::trace('Starting specialized MySQL service check with timeout protection');
1431
1432 // Set a timeout for the MySQL service check
1433 $serviceCheckStartTime = microtime(true);
1434 $serviceCheckTimeout = 3; // 3 seconds timeout
1435
1436 try {
1437 // Use a non-blocking approach to check service
1438 $serviceInfos = false;
1439
1440 // First check if the service exists in the list
1441 $serviceList = Win32Service::getServices();
1442 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1443 Log::trace('MySQL service found in service list, getting details');
1444
1445 // Service exists, now try to get its details with timeout protection
1446 $serviceInfos = $service->infos();
1447
1448 // Check if we've exceeded our timeout
1449 if (microtime(true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1450 Log::trace("MySQL service check timeout exceeded, assuming service needs reinstall");
1451
1452 return false;
1453 }
1454 } else {
1455 Log::trace('MySQL service not found in service list');
1456
1457 return false;
1458 }
1459
1460 return $serviceInfos;
1461 } catch (\Exception $e) {
1462 Log::trace("Exception during MySQL service check: " . $e->getMessage());
1463
1464 return false;
1465 } catch (\Throwable $e) {
1466 Log::trace("Throwable during MySQL service check: " . $e->getMessage());
1467
1468 return false;
1469 }
1470 }
1471
1477 private function writeLog($log)
1478 {
1479 global $bearsamppRoot;
1481 }
1482}
$result
global $bearsamppBins
global $bearsamppLang
global $bearsamppRoot
$port
global $bearsamppCore
$proc
Definition ajax.php:130
prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentIndex=0, $totalCount=0)
installServicesSequential($bearsamppBins, $bearsamppLang)
checkMySQLServiceWithTimeout($service, $bin)
checkApacheServiceWithTimeout($service)
processWindow($window, $id, $ctrl, $param1, $param2)
static getOsInfo()
const CMD_SYNTAX_CHECK
const SERVICE_NAME
const CMD_SYNTAX_CHECK
const CFG_HOSTNAME
const CFG_BROWSER
const VERBOSE_TRACE
const STARTUP_STARTING_TEXT
const STARTUP_SYS_INFOS
const STARTUP_REFRESH_GIT_REPOS_TEXT
const DISABLED
const STARTUP_SERVICE_ERROR
const STARTUP_CHECK_PATH_TEXT
const STARTUP_SCAN_FOLDERS_TEXT
const STARTUP_CHANGE_PATH_TEXT
const STARTUP_PREPARE_RESTART_TEXT
const STARTUP_ROTATION_LOGS_TEXT
const LOADING_START_SERVICE
const STARTUP_SERVICE_CREATE_ERROR
const STARTUP_SERVICE_PORT_ERROR
const STARTUP
const STARTUP_GEN_SSL_CRT_TEXT
const STARTUP_VERIFY_SERVICE_TEXT
const STARTUP_ERROR_TITLE
const STARTUP_CLEAN_OLD_BEHAVIORS_TEXT
const STARTUP_REFRESH_ALIAS_TEXT
const STARTUP_REGISTRY_ERROR_TEXT
const STARTUP_KILL_OLD_PROCS_TEXT
const STARTUP_CHECK_BROWSER_TEXT
const STARTUP_REFRESH_HOSTNAME_TEXT
const STARTUP_REGISTRY_TEXT
const STARTUP_UPDATE_CONFIG_TEXT
const STARTUP_REFRESH_VHOSTS_TEXT
const STARTUP_CLEAN_TMP_TEXT
static info($data, $file=null)
static debug($data, $file=null)
static warning($data, $file=null)
static trace($data, $file=null)
static error($data, $file=null)
static getRootPath($aetrayPath=false)
static getStartupLogFilePath($aetrayPath=false)
static getLogsPath($aetrayPath=false)
static formatWindowsPath($path)
static changePath($filesToScan, $rootPath=null)
static formatUnixPath($path)
static getTmpPath($aetrayPath=false)
static getLastPath($aetrayPath=false)
const SYSPATH_REG_ENTRY
const HKEY_LOCAL_MACHINE
const APP_PATH_REG_ENTRY
const APP_BINS_REG_ENTRY
static startAllServicesParallel($serviceInfos, ?callable $progressCallback=null, $startupTimeout=30)
static isPortInUse($port)
static disableLaunchStartup()
static getMicrotime()
static deleteFolder($path)
static startLoading()
static getFilesToScan($path=null, $useCache=true, $forceRefresh=false)
static clearFolder($path, $exclude=array())
static enableLaunchStartup()
static getDefaultBrowser()
static killBins($refreshProcs=false)
const EXECUTABLE_PATH
const PROCESS_ID
static getServices($forceRefresh=false)
global $bearsamppConfig
Definition homepage.php:46
const APP_TITLE
Definition root.php:19