Bearsampp 2026.5.5
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{
18 private $splash;
19 private $restart;
20 private $startTime;
21 private $error;
22
23 private $rootPath;
24 private $filesToScan;
25
26 const GAUGE_SERVICES = 5;
27 const GAUGE_OTHERS = 19;
28
35 public function __construct($args)
36 {
37 global $bearsamppRoot, $bearsamppCore, $bearsamppLang, $bearsamppBins, $bearsamppWinbinder;
38 $this->writeLog( 'Starting ' . APP_TITLE );
39
40 // Admin check is now performed in root.php before ActionStartup is instantiated
41 // This prevents screen flashes and ensures the check happens before any WinBinder initialization
42 Log::info('Administrator privileges confirmed - proceeding with startup');
43
44 // Init
45 $this->splash = new Splash();
46 $this->restart = false;
47 $this->startTime = Util::getMicrotime();
48 $this->error = '';
49
50 $this->rootPath = $bearsamppRoot->getRootPath();
51 $this->filesToScan = array();
52
53 $gauge = self::GAUGE_SERVICES * count( $bearsamppBins->getServices() );
54 $gauge += self::GAUGE_OTHERS + 1;
55
56 // Start splash screen
57 $this->splash->init(
58 $bearsamppLang->getValue( Lang::STARTUP ),
59 $gauge,
60 sprintf( $bearsamppLang->getValue( Lang::STARTUP_STARTING_TEXT ), APP_TITLE . ' ' . $bearsamppCore->getAppVersion() )
61 );
62
63 $bearsamppWinbinder->setHandler( $this->splash->getWbWindow(), $this, 'processWindow', 1000 );
64 $bearsamppWinbinder->mainLoop();
65 $bearsamppWinbinder->reset();
66 }
67
77 public function processWindow($window, $id, $ctrl, $param1, $param2)
78 {
79 global $bearsamppRoot, $bearsamppCore, $bearsamppLang, $bearsamppBins, $bearsamppTools, $bearsamppApps, $bearsamppWinbinder;
80
81 Log::trace('Starting processWindow method');
82
83 // Admin check is now performed in the constructor before anything else
84 // No need to check again here
85
86 // Rotation logs
87 Log::trace('Performing log rotation');
88 $this->rotationLogs();
89
90 // Clean
91 Log::trace('Starting cleanup operations');
92 $this->cleanTmpFolders();
93 $this->cleanOldBehaviors();
94
95 // List procs
96 Log::trace('Listing running processes');
97 if ($bearsamppRoot->getProcs() !== false) {
98 $this->writeLog('List procs:');
99 $listProcs = array();
100 foreach ($bearsamppRoot->getProcs() as $proc) {
102 $listProcs[] = '-> ' . basename($unixExePath) . ' (PID ' . $proc[Win32Ps::PROCESS_ID] . ') in ' . $unixExePath;
103 }
104 sort($listProcs);
105 foreach ($listProcs as $proc) {
106 $this->writeLog($proc);
107 }
108 Log::trace('Found ' . count($listProcs) . ' running processes');
109 } else {
110 Log::trace('No processes found or unable to retrieve process list');
111 }
112
113 // List modules
114 Log::trace('Listing bins modules');
115 $this->writeLog('List bins modules:');
116 foreach ($bearsamppBins->getAll() as $module) {
117 if (!$module->isEnable()) {
118 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
119 Log::trace('Bin module ' . $module->getName() . ' is disabled');
120 } else {
121 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
122 Log::trace('Bin module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
123 }
124 }
125
126 Log::trace('Listing tools modules');
127 $this->writeLog('List tools modules:');
128 foreach ($bearsamppTools->getAll() as $module) {
129 if (!$module->isEnable()) {
130 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
131 Log::trace('Tool module ' . $module->getName() . ' is disabled');
132 } else {
133 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
134 Log::trace('Tool module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
135 }
136 }
137
138 Log::trace('Listing apps modules');
139 $this->writeLog('List apps modules:');
140 foreach ($bearsamppApps->getAll() as $module) {
141 if (!$module->isEnable()) {
142 $this->writeLog('-> ' . $module->getName() . ': ' . $bearsamppLang->getValue(Lang::DISABLED));
143 Log::trace('App module ' . $module->getName() . ' is disabled');
144 } else {
145 $this->writeLog('-> ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
146 Log::trace('App module ' . $module->getName() . ': ' . $module->getVersion() . ' (' . $module->getRelease() . ')');
147 }
148 }
149
150 // Kill old instances
151 Log::trace('Killing old instances');
152 $this->killOldInstances();
153
154 // Prepare app
155 Log::trace('Preparing application - refreshing hostname');
156 $this->refreshHostname();
157
158 Log::trace('Checking launch startup settings');
159 $this->checkLaunchStartup();
160
161 Log::trace('Checking browser configuration');
162 $this->checkBrowser();
163
164 Log::trace('Gathering system information');
165 $this->sysInfos();
166
167 Log::trace('Refreshing aliases');
168 $this->refreshAliases();
169
170 Log::trace('Refreshing virtual hosts');
171 $this->refreshVhosts();
172
173 // Check app path
174 Log::trace('Checking application path');
175 $this->checkPath();
176
177 Log::trace('Scanning folders');
178 $this->scanFolders();
179
180 Log::trace('Changing paths in files');
181 $this->changePath();
182
183 Log::trace('Saving current path');
184 $this->savePath();
185
186 // Check BEARSAMPP_PATH, BEARSAMPP_BINS and System Path reg keys
187 Log::trace('Checking PATH registry key');
188 $this->checkPathRegKey();
189
190 Log::trace('Checking BINS registry key');
191 $this->checkBinsRegKey();
192
193 Log::trace('Checking System PATH registry key');
194 $this->checkSystemPathRegKey();
195
196 // Update config
197 Log::trace('Updating configuration');
198 $this->updateConfig();
199
200 // Create SSL certificates
201 Log::trace('Creating SSL certificates');
202 $this->createSslCrts();
203
204 // Install
205 Log::trace('Installing services');
206 $this->installServices();
207
208 // Actions if everything OK
209 if (!$this->restart && empty($this->error)) {
210 Log::trace('Startup completed successfully - refreshing Git repositories');
211 $this->refreshGitRepos();
212 $startupTime = round(Util::getMicrotime() - $this->startTime, 3);
213 $this->writeLog('Started in ' . $startupTime . 's');
214 Log::trace('Application started successfully in ' . $startupTime . ' seconds');
215
216 // Log total startup time in VERBOSE_TRACE mode (mode 3)
217 global $bearsamppConfig;
218 if ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_TRACE) {
219 $minutes = floor($startupTime / 60); // floor() returns int-compatible value
220 $seconds = fmod($startupTime, 60);
221 $formattedTime = sprintf('%d:%05.2f', $minutes, $seconds);
222 Log::trace('=== TOTAL STARTUP TIME: ' . $formattedTime . ' ===');
223 }
224 } else {
225 Log::trace('Startup issues detected - incrementing progress bar');
226 $this->splash->incrProgressBar(2);
227 }
228
229 if ($this->restart) {
230 Log::trace('Restart required - preparing to restart application');
231 $this->writeLog(APP_TITLE . ' has to be restarted');
232 $this->splash->setTextLoading(
233 sprintf(
235 APP_TITLE . ' ' . $bearsamppCore->getAppVersion()
236 )
237 );
238
239 Log::trace('Deleting all services before restart');
240 foreach ($bearsamppBins->getServices() as $sName => $service) {
241 Log::trace('Deleting service: ' . $sName);
242 $service->delete();
243 }
244
245 Log::trace('Setting execution action to RESTART');
247 }
248
249 if (!empty($this->error)) {
250 Log::trace('Errors occurred during startup: ' . $this->error);
251 $this->writeLog('Error: ' . $this->error);
252 $bearsamppWinbinder->messageBoxError($this->error, $bearsamppLang->getValue(Lang::STARTUP_ERROR_TITLE));
253 }
254
255 Log::trace('Starting loading screen');
256 // Moved Util::startLoading() to after splash window destruction to prevent double progress bars
257
258 // Give the loading window time to initialize before we terminate this process
259 Log::trace('Waiting for loading window to initialize');
260 usleep(500000); // 500ms delay to allow loading window to start
261
262 Log::trace('Loading process started');
263
264 // Closing cli to finish startup
265 Log::trace('Finishing startup process');
266
267 // Safely reset WinBinder and destroy the splash window
268 $bearsamppWinbinder->destroyWindow($window);
269 $bearsamppWinbinder->reset();
270
271 // Start loading screen AFTER splash window is destroyed to prevent double progress bars
273
274 // Exit this startup process cleanly - the loading window will continue running
275 Log::trace('Exiting startup process cleanly');
276 exit(0);
277
278 }
279
284 private function rotationLogs()
285 {
287
288 Log::trace("Starting log rotation process");
289 $this->splash->setTextLoading($bearsamppLang->getValue(Lang::STARTUP_ROTATION_LOGS_TEXT));
290 $this->splash->incrProgressBar();
291
292 $archivesPath = $bearsamppRoot->getLogsPath() . '/archives';
293 if (!is_dir($archivesPath)) {
294 Log::trace("Creating archives directory: " . $archivesPath);
295 mkdir($archivesPath, 0777, true);
296 return;
297 }
298
299 $date = date('Y-m-d-His', time());
300 $archiveLogsPath = $archivesPath . '/' . $date;
301 $archiveScriptsPath = $archiveLogsPath . '/scripts';
302
303 // Create archive folders
304 Log::trace("Creating archive directories for current rotation");
305 if (!is_dir($archiveLogsPath)) {
306 Log::trace("Creating logs archive directory: " . $archiveLogsPath);
307 mkdir($archiveLogsPath, 0777, true);
308 } else {
309 Log::trace("Logs archive directory already exists: " . $archiveLogsPath);
310 }
311
312 if (!is_dir($archiveScriptsPath)) {
313 Log::trace("Creating scripts archive directory: " . $archiveScriptsPath);
314 mkdir($archiveScriptsPath, 0777, true);
315 } else {
316 Log::trace("Scripts archive directory already exists: " . $archiveScriptsPath);
317 }
318
319 // Count archives
320 Log::trace("Counting existing archives");
321 $archives = array();
322 $handle = @opendir($archivesPath);
323 if (!$handle) {
324 Log::trace("Failed to open archives directory: " . $archivesPath);
325 return;
326 }
327
328 while (false !== ($file = readdir($handle))) {
329 if ($file == '.' || $file == '..') {
330 continue;
331 }
332 $archives[] = $archivesPath . '/' . $file;
333 }
334 closedir($handle);
335 sort($archives);
336 Log::trace("Found " . count($archives) . " existing archives");
337
338 // Remove old archives
339 if (count($archives) > $bearsamppConfig->getMaxLogsArchives()) {
340 $total = count($archives) - $bearsamppConfig->getMaxLogsArchives();
341 Log::trace("Removing " . $total . " old archives");
342 for ($i = 0; $i < $total; $i++) {
343 Log::trace("Deleting old archive: " . $archives[$i]);
344 Util::deleteFolder($archives[$i]);
345 }
346 }
347
348 // Helper function to check if a file is locked
349 $isFileLocked = function($filePath) {
350 if (!file_exists($filePath)) {
351 return false;
352 }
353
354 $handle = @fopen($filePath, 'r+');
355 if ($handle === false) {
356 Log::trace("File appears to be locked: " . $filePath);
357 return true; // File is locked
358 }
359
360 fclose($handle);
361 return false; // File is not locked
362 };
363
364 // Logs
365 Log::trace("Archiving log files");
366 $srcPath = $bearsamppRoot->getLogsPath();
367 $handle = @opendir($srcPath);
368 if (!$handle) {
369 Log::trace("Failed to open logs directory: " . $srcPath);
370 return;
371 }
372
373 $logsCopied = 0;
374 $logsSkipped = 0;
375
376 while (false !== ($file = readdir($handle))) {
377 if ($file == '.' || $file == '..' || is_dir($srcPath . '/' . $file)) {
378 continue;
379 }
380
381 $sourceFile = $srcPath . '/' . $file;
382 $destFile = $archiveLogsPath . '/' . $file;
383
384 // Check if file is locked before attempting to copy
385 if ($isFileLocked($sourceFile)) {
386 Log::trace("Skipping locked log file: " . $file);
387 $logsSkipped++;
388 continue;
389 }
390
391 try {
392 if (copy($sourceFile, $destFile)) {
393 $logsCopied++;
394 Log::trace("Archived log file: " . $file);
395 } else {
396 $logsSkipped++;
397 Log::trace("Failed to copy log file: " . $file);
398 }
399 } catch (Exception $e) {
400 $logsSkipped++;
401 Log::trace("Exception copying log file " . $file . ": " . $e->getMessage());
402 }
403 }
404 closedir($handle);
405 Log::trace("Logs archived: " . $logsCopied . " copied, " . $logsSkipped . " skipped");
406
407 // Scripts
408 Log::trace("Archiving script files");
409 $srcPath = $bearsamppCore->getTmpPath();
410 $handle = @opendir($srcPath);
411 if (!$handle) {
412 Log::trace("Failed to open tmp directory: " . $srcPath);
413 return;
414 }
415
416 $scriptsCopied = 0;
417 $scriptsSkipped = 0;
418
419 while (false !== ($file = readdir($handle))) {
420 if ($file == '.' || $file == '..' || is_dir($srcPath . '/' . $file)) {
421 continue;
422 }
423
424 $sourceFile = $srcPath . '/' . $file;
425 $destFile = $archiveScriptsPath . '/' . $file;
426
427 // Check if file is locked before attempting to copy
428 if ($isFileLocked($sourceFile)) {
429 Log::trace("Skipping locked script file: " . $file);
430 $scriptsSkipped++;
431 continue;
432 }
433
434 try {
435 if (copy($sourceFile, $destFile)) {
436 $scriptsCopied++;
437 Log::trace("Archived script file: " . $file);
438 } else {
439 $scriptsSkipped++;
440 Log::trace("Failed to copy script file: " . $file);
441 }
442 } catch (Exception $e) {
443 $scriptsSkipped++;
444 Log::trace("Exception copying script file " . $file . ": " . $e->getMessage());
445 }
446 }
447 closedir($handle);
448 Log::trace("Scripts archived: " . $scriptsCopied . " copied, " . $scriptsSkipped . " skipped");
449
450 // Purge logs - only delete files that aren't locked
451 Log::trace("Purging log files");
452 $logsPath = $bearsamppRoot->getLogsPath();
453 $handle = @opendir($logsPath);
454 if (!$handle) {
455 Log::trace("Failed to open logs directory for purging: " . $logsPath);
456 return;
457 }
458
459 $logsDeleted = 0;
460 $logsPurgeSkipped = 0;
461
462 while (false !== ($file = readdir($handle))) {
463 if ($file == '.' || $file == '..' || $file == 'archives' || $file == '.gitignore' || is_dir($logsPath . '/' . $file)) {
464 continue;
465 }
466
467 $filePath = $logsPath . '/' . $file;
468
469 // Check if file is locked before attempting to delete
470 if ($isFileLocked($filePath)) {
471 Log::trace("Skipping locked log file during purge: " . $file);
472 $logsPurgeSkipped++;
473 continue;
474 }
475
476 try {
477 if (file_exists($filePath) && unlink($filePath)) {
478 $logsDeleted++;
479 Log::trace("Purged log file: " . $file);
480 } else {
481 $logsPurgeSkipped++;
482 Log::trace("Failed to purge log file: " . $file);
483 }
484 } catch (Exception $e) {
485 $logsPurgeSkipped++;
486 Log::trace("Exception purging log file " . $file . ": " . $e->getMessage());
487 }
488 }
489 closedir($handle);
490 Log::trace("Logs purged: " . $logsDeleted . " deleted, " . $logsPurgeSkipped . " skipped");
491
492 Log::trace("Log rotation completed");
493 }
494
498 private function cleanTmpFolders()
499 {
501
502 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_CLEAN_TMP_TEXT ) );
503 $this->splash->incrProgressBar();
504
505 $this->writeLog( 'Clear tmp folders' );
506 Util::clearFolder( $bearsamppRoot->getTmpPath(), array('cachegrind', 'composer', 'openssl', 'mailpit', 'xlight', 'npm-cache', 'pip', 'opcache', '.gitignore') );
507 Util::clearFolder( $bearsamppCore->getTmpPath(), array('.gitignore') );
508
509 // Ensure opcache directory exists for persistent file cache
510 $opcachePath = $bearsamppRoot->getTmpPath() . DIRECTORY_SEPARATOR . 'opcache';
511
512 if (!is_dir($opcachePath)) {
513 $this->writeLog('Creating opcache directory: ' . $opcachePath);
514 if (!@mkdir($opcachePath, 0755, true) && !is_dir($opcachePath)) {
515 $this->writeLog('Failed to create opcache directory: ' . $opcachePath);
516 return;
517 }
518 }
519
520 if (!is_writable($opcachePath)) {
521 $this->writeLog('Opcache directory is not writable: ' . $opcachePath);
522 }
523 }
524
528 private function cleanOldBehaviors()
529 {
530 global $bearsamppLang, $bearsamppRegistry;
531
532 $this->writeLog( 'Clean old behaviors' );
533
534 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_CLEAN_OLD_BEHAVIORS_TEXT ) );
535 $this->splash->incrProgressBar();
536
537 // App >= 1.0.13
538 $bearsamppRegistry->deleteValue(
540 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
542 );
543 }
544
548 private function killOldInstances()
549 {
550 global $bearsamppLang;
551
552 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_KILL_OLD_PROCS_TEXT ) );
553 $this->splash->incrProgressBar();
554
555 // Stop services
556 /*foreach ($bearsamppBins->getServices() as $sName => $service) {
557 $serviceInfos = $service->infos();
558 if ($serviceInfos === false) {
559 continue;
560 }
561 $service->stop();
562 }*/
563
564 // Stop third party procs
565 $procsKilled = Win32Ps::killBins();
566 if ( !empty( $procsKilled ) ) {
567 $this->writeLog( 'Procs killed:' );
568 $procsKilledSort = array();
569 foreach ( $procsKilled as $proc ) {
571 $procsKilledSort[] = '-> ' . basename( $unixExePath ) . ' (PID ' . $proc[Win32Ps::PROCESS_ID] . ') in ' . $unixExePath;
572 }
573 sort( $procsKilledSort );
574 foreach ( $procsKilledSort as $proc ) {
575 $this->writeLog( $proc );
576 }
577 }
578 }
579
583 private function refreshHostname()
584 {
586
587 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_REFRESH_HOSTNAME_TEXT ) );
588 $this->splash->incrProgressBar();
589 $this->writeLog( 'Refresh hostname' );
590
591 $bearsamppConfig->replace( Config::CFG_HOSTNAME, gethostname() );
592 }
593
597 private function checkLaunchStartup()
598 {
599 global $bearsamppConfig;
600
601 $this->writeLog( 'Check launch startup' );
602
603 if ( $bearsamppConfig->isLaunchStartup() ) {
605 }
606 else {
608 }
609 }
610
614 private function checkBrowser()
615 {
617
618 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_CHECK_BROWSER_TEXT ) );
619 $this->splash->incrProgressBar();
620 $this->writeLog( 'Check browser' );
621
622 $currentBrowser = $bearsamppConfig->getBrowser();
623 if ( empty( $currentBrowser ) || !file_exists( $currentBrowser ) ) {
625 }
626 }
627
631 private function sysInfos()
632 {
633 global $bearsamppLang;
634
635 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_SYS_INFOS ) );
636 $this->splash->incrProgressBar();
637
638 $os = Batch::getOsInfo();
639 $this->writeLog( sprintf( 'OS: %s', $os ) );
640 }
641
645 private function refreshAliases()
646 {
648
649 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_REFRESH_ALIAS_TEXT ) );
650 $this->splash->incrProgressBar();
651 $this->writeLog( 'Refresh aliases' );
652
653 $bearsamppBins->getApache()->refreshAlias( $bearsamppConfig->isOnline() );
654 }
655
659 private function refreshVhosts()
660 {
662
663 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_REFRESH_VHOSTS_TEXT ) );
664 $this->splash->incrProgressBar();
665 $this->writeLog( 'Refresh vhosts' );
666
667 $bearsamppBins->getApache()->refreshVhosts( $bearsamppConfig->isOnline() );
668 }
669
673 private function checkPath()
674 {
676
677 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_CHECK_PATH_TEXT ) );
678 $this->splash->incrProgressBar();
679
680 $this->writeLog( 'Last path: ' . $bearsamppCore->getLastPathContent() );
681 }
682
688 private function scanFolders()
689 {
691
692 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_SCAN_FOLDERS_TEXT ) );
693 $this->splash->incrProgressBar();
694
695 $lastPath = $bearsamppCore->getLastPathContent();
696 $currentPath = $this->rootPath;
697
698 // Performance optimization: Skip scan if path hasn't changed
699 if ($lastPath === $currentPath) {
700 Log::debug('Path unchanged, skipping file scan (performance optimization)');
701 Log::trace('Last path: "' . $lastPath . '" matches current path: "' . $currentPath . '"');
702 $this->filesToScan = [];
703 $this->writeLog('Files to scan: 0 (path unchanged - scan skipped)');
704
705 // Log performance benefit
706 $this->writeLog('Performance: File scan skipped, saving 3-8 seconds');
707 return;
708 }
709
710 // Path changed, perform full scan
711 Log::debug('Path changed, performing full file scan');
712 Log::trace('Last path: "' . $lastPath . '" differs from current path: "' . $currentPath . '"');
713 $this->writeLog('Path changed detected - performing full scan');
714
715 $scanStartTime = Util::getMicrotime();
716 $this->filesToScan = Util::getFilesToScan();
717 $scanDuration = round(Util::getMicrotime() - $scanStartTime, 3);
718
719 $this->writeLog('Files to scan: ' . count($this->filesToScan) . ' (scanned in ' . $scanDuration . 's)');
720 }
721
725 private function changePath()
726 {
727 global $bearsamppLang;
728
729 $this->splash->setTextLoading( sprintf( $bearsamppLang->getValue( Lang::STARTUP_CHANGE_PATH_TEXT ), $this->rootPath ) );
730 $this->splash->incrProgressBar();
731
732 $result = Util::changePath( $this->filesToScan, $this->rootPath );
733 $this->writeLog( 'Nb files changed: ' . $result['countChangedFiles'] );
734 $this->writeLog( 'Nb occurences changed: ' . $result['countChangedOcc'] );
735 }
736
740 private function savePath()
741 {
742 global $bearsamppCore;
743
744 file_put_contents( $bearsamppCore->getLastPath(), $this->rootPath );
745 $this->writeLog( 'Save current path: ' . $this->rootPath );
746 }
747
751 private function checkPathRegKey()
752 {
753 global $bearsamppRoot, $bearsamppLang, $bearsamppRegistry;
754
755 $this->splash->setTextLoading( sprintf( $bearsamppLang->getValue( Lang::STARTUP_REGISTRY_TEXT ), Registry::APP_PATH_REG_ENTRY ) );
756 $this->splash->incrProgressBar();
757
758 $currentAppPathRegKey = Util::getAppPathRegKey();
759 $genAppPathRegKey = UtilPath::formatWindowsPath( $bearsamppRoot->getRootPath() );
760 $this->writeLog( 'Current app path reg key: ' . $currentAppPathRegKey );
761 $this->writeLog( 'Gen app path reg key: ' . $genAppPathRegKey );
762 if ( $currentAppPathRegKey != $genAppPathRegKey ) {
763 if ( !Util::setAppPathRegKey( $genAppPathRegKey ) ) {
764 if ( !empty( $this->error ) ) {
765 $this->error .= PHP_EOL . PHP_EOL;
766 }
768 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
769 }
770 else {
771 $this->writeLog( 'Need restart: checkPathRegKey' );
772 $this->restart = true;
773 }
774 }
775 }
776
784 private function checkBinsRegKey()
785 {
786 global $bearsamppLang, $bearsamppRegistry;
787
788 $this->splash->setTextLoading( sprintf( $bearsamppLang->getValue( Lang::STARTUP_REGISTRY_TEXT ), Registry::APP_BINS_REG_ENTRY ) );
789 $this->splash->incrProgressBar();
790
791 $currentAppBinsRegKey = Util::getAppBinsRegKey();
792 $genAppBinsRegKey = Util::getAppBinsRegKey( false );
793 $this->writeLog( 'Current app bins reg key: ' . $currentAppBinsRegKey );
794 $this->writeLog( 'Gen app bins reg key: ' . $genAppBinsRegKey );
795 if ( $currentAppBinsRegKey != $genAppBinsRegKey ) {
796 if ( !Util::setAppBinsRegKey( $genAppBinsRegKey ) ) {
797 if ( !empty( $this->error ) ) {
798 $this->error .= PHP_EOL . PHP_EOL;
799 }
801 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
802 }
803 else {
804 $this->writeLog( 'Need restart: checkBinsRegKey' );
805 $this->restart = true;
806 }
807 }
808 }
809
817 private function checkSystemPathRegKey()
818 {
819 global $bearsamppLang, $bearsamppRegistry;
820
821 $this->splash->setTextLoading( sprintf( $bearsamppLang->getValue( Lang::STARTUP_REGISTRY_TEXT ), Registry::SYSPATH_REG_ENTRY ) );
822 $this->splash->incrProgressBar();
823
824 $currentSysPathRegKey = Util::getSysPathRegKey();
825 $this->writeLog( 'Current system PATH: ' . $currentSysPathRegKey );
826
827 $newSysPathRegKey = str_replace( '%' . Registry::APP_BINS_REG_ENTRY . '%;', '', $currentSysPathRegKey );
828 $newSysPathRegKey = str_replace( '%' . Registry::APP_BINS_REG_ENTRY . '%', '', $newSysPathRegKey );
829 $newSysPathRegKey = '%' . Registry::APP_BINS_REG_ENTRY . '%;' . $newSysPathRegKey;
830 $this->writeLog( 'New system PATH: ' . $newSysPathRegKey );
831
832 if ( $currentSysPathRegKey != $newSysPathRegKey ) {
833 if ( !Util::setSysPathRegKey( $newSysPathRegKey ) ) {
834 if ( !empty( $this->error ) ) {
835 $this->error .= PHP_EOL . PHP_EOL;
836 }
838 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
839 }
840 else {
841 $this->writeLog( 'Need restart: checkSystemPathRegKey' );
842 $this->restart = true;
843 }
844 }
845 else {
846 $this->writeLog( 'Refresh system PATH: ' . $currentSysPathRegKey );
847 Util::setSysPathRegKey( str_replace( '%' . Registry::APP_BINS_REG_ENTRY . '%', '', $currentSysPathRegKey ) );
848 Util::setSysPathRegKey( $currentSysPathRegKey );
849 }
850 }
851
856 private function updateConfig()
857 {
858 global $bearsamppLang, $bearsamppBins, $bearsamppTools, $bearsamppApps;
859
860 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_UPDATE_CONFIG_TEXT ) );
861 $this->splash->incrProgressBar();
862 $this->writeLog( 'Update config' );
863
864 $bearsamppBins->update();
865 $bearsamppTools->update();
866 $bearsamppApps->update();
867 }
868
873 private function createSslCrts()
874 {
875 global $bearsamppLang, $bearsamppOpenSsl;
876
877 $this->splash->incrProgressBar();
878 if ( !$bearsamppOpenSsl->existsCrt( 'localhost' ) ) {
879 $this->splash->setTextLoading( sprintf( $bearsamppLang->getValue( Lang::STARTUP_GEN_SSL_CRT_TEXT ), 'localhost' ) );
880 $bearsamppOpenSsl->createCrt( 'localhost' );
881 }
882 }
883
891
892 private function installServices()
893 {
895
896 Log::trace('Starting installServices method');
897
898 if (!$this->restart) {
899 Log::trace('Normal startup mode - processing services');
900
901 // Admin check is now performed at the very beginning of processWindow()
902 // No need to check again here
903
904 // Service Installation
905 $this->installServicesSequential($bearsamppBins, $bearsamppLang);
906 } else {
907 Log::trace('Restart mode - skipping service installation');
908 $this->splash->incrProgressBar(self::GAUGE_SERVICES * count($bearsamppBins->getServices()));
909 }
910
911 Log::trace('Completed installServices method');
912 }
913
923 {
924 Log::trace('Starting sequential service installation');
925 $installStartTime = Util::getMicrotime();
926
927 // Step 1: Check and prepare all services
928 $servicesToStart = [];
929 $serviceErrors = [];
930
931 $totalServiceCount = count($bearsamppBins->getServices());
932 $currentServiceIndex = 0;
933
934 foreach ($bearsamppBins->getServices() as $sName => $service) {
935 $currentServiceIndex++;
936
937 Log::trace('Preparing service: ' . $sName);
938
939 // prepareService() increments 1 step
940 $serviceInfo = $this->prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentServiceIndex, $totalServiceCount);
941
942 if ($serviceInfo['restart']) {
943 $this->writeLog('Need restart: installService ' . $serviceInfo['bin']->getName());
944 Log::trace('Restart required for service: ' . $serviceInfo['bin']->getName());
945 $this->restart = true;
946 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
947 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
948 continue;
949 }
950
951 if (!empty($serviceInfo['error'])) {
952 $serviceErrors[$sName] = $serviceInfo;
953 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
954 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
955 continue;
956 }
957
958 if ($serviceInfo['needsStart']) {
959 $servicesToStart[$sName] = $serviceInfo;
960 } else {
961 // Service already running or doesn't need to start
962 // prepareService used 1 step, need 4 more to reach GAUGE_SERVICES (5 total)
963 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
964 }
965 }
966
967 // Step 2: Start all services sequentially with progress updates
968 if (!empty($servicesToStart)) {
969 Log::trace('Starting ' . count($servicesToStart) . ' services sequentially');
970
971 $serviceCount = 0;
972 $totalServices = count($servicesToStart);
973
974 foreach ($servicesToStart as $sName => $serviceInfo) {
975 $serviceCount++;
976 $name = $serviceInfo['name'];
977 $service = $serviceInfo['service'];
978
979 // Update splash before starting (1 step - 2nd of 5)
980 $this->splash->setTextLoading('Starting ' . $name . ' (' . $serviceCount . '/' . $totalServices . ')');
981 $this->splash->incrProgressBar();
982
983 Log::trace('Starting service: ' . $sName);
984 $serviceStartTime = Util::getMicrotime();
985
986 // Start the service
987 $success = $service->start();
988
989 $duration = round(Util::getMicrotime() - $serviceStartTime, 3);
990
991 if ($success) {
992 $this->writeLog($name . ' service started in ' . $duration . 's');
993 Log::trace('Service ' . $name . ' started successfully in ' . $duration . ' seconds');
994
995 // Update splash after successful start
996 $this->splash->setTextLoading($name . ' started successfully');
997 } else {
998 $error = $service->getError();
999 if (empty($error)) {
1000 $error = 'Failed to start service';
1001 }
1002
1003 $serviceErrors[$sName] = $serviceInfo;
1004 $serviceErrors[$sName]['error'] = $error;
1005 Log::trace('Service ' . $name . ' failed to start: ' . $error);
1006
1007 // Run syntax check if available
1008 if (!empty($serviceInfo['syntaxCheckCmd'])) {
1009 try {
1010 $cmdSyntaxCheck = $serviceInfo['bin']->getCmdLineOutput($serviceInfo['syntaxCheckCmd']);
1011 if (!$cmdSyntaxCheck['syntaxOk']) {
1012 $serviceErrors[$sName]['error'] .= PHP_EOL . 'Syntax error: ' . $cmdSyntaxCheck['content'];
1013 }
1014 } catch (\Exception $e) {
1015 // Ignore syntax check errors
1016 }
1017 }
1018 }
1019
1020 // Complete remaining steps: prepareService=1, pre-start=1, now add 3 more = 5 total
1021 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 2);
1022 }
1023 }
1024
1025 // Step 3: Report any errors
1026 foreach ($serviceErrors as $sName => $serviceInfo) {
1027 if (!empty($serviceInfo['error'])) {
1028 Log::trace('Service error occurred for ' . $sName . ': ' . $serviceInfo['error']);
1029 if (!empty($this->error)) {
1030 $this->error .= PHP_EOL . PHP_EOL;
1031 }
1032 $this->error .= sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_ERROR), $serviceInfo['name']) . PHP_EOL . $serviceInfo['error'];
1033 }
1034 }
1035
1036 $installDuration = round(Util::getMicrotime() - $installStartTime, 3);
1037 $this->writeLog('Service installation completed in ' . $installDuration . 's');
1038 Log::trace('Service installation completed in ' . $installDuration . ' seconds');
1039 }
1040
1052 private function prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentIndex = 0, $totalCount = 0)
1053 {
1054 $serviceInfo = [
1055 'sName' => $sName,
1056 'service' => $service,
1057 'bin' => null,
1058 'name' => '',
1059 'port' => 0,
1060 'syntaxCheckCmd' => null,
1061 'error' => '',
1062 'restart' => false,
1063 'needsStart' => false,
1064 'startTime' => Util::getMicrotime()
1065 ];
1066
1067 // Identify service type and get bin
1068 $syntaxCheckCmd = null;
1069 $bin = null;
1070 $port = 0;
1071
1072 if ($sName == BinMailpit::SERVICE_NAME) {
1073 $bin = $bearsamppBins->getMailpit();
1074 $port = $bearsamppBins->getMailpit()->getSmtpPort();
1075 } elseif ($sName == BinMemcached::SERVICE_NAME) {
1076 $bin = $bearsamppBins->getMemcached();
1077 $port = $bearsamppBins->getMemcached()->getPort();
1078 } elseif ($sName == BinApache::SERVICE_NAME) {
1079 $bin = $bearsamppBins->getApache();
1080 $port = $bearsamppBins->getApache()->getPort();
1081 $syntaxCheckCmd = BinApache::CMD_SYNTAX_CHECK;
1082 } elseif ($sName == BinMysql::SERVICE_NAME) {
1083 $bin = $bearsamppBins->getMysql();
1084 $port = $bearsamppBins->getMysql()->getPort();
1085 $syntaxCheckCmd = BinMysql::CMD_SYNTAX_CHECK;
1086
1087 // Pre-initialize MySQL data if needed
1088 if (!file_exists($bin->getDataDir()) || count(glob($bin->getDataDir() . '/*')) === 0) {
1089 Log::trace('Pre-initializing MySQL data directory');
1090 $bin->initData();
1091 }
1092 } elseif ($sName == BinMariadb::SERVICE_NAME) {
1093 $bin = $bearsamppBins->getMariadb();
1094 $port = $bearsamppBins->getMariadb()->getPort();
1095 $syntaxCheckCmd = BinMariadb::CMD_SYNTAX_CHECK;
1096 } elseif ($sName == BinPostgresql::SERVICE_NAME) {
1097 $bin = $bearsamppBins->getPostgresql();
1098 $port = $bearsamppBins->getPostgresql()->getPort();
1099 } elseif ($sName == BinXlight::SERVICE_NAME) {
1100 $bin = $bearsamppBins->getXlight();
1101 $port = $bearsamppBins->getXlight()->getPort();
1102 }
1103
1104 $name = $bin->getName() . ' ' . $bin->getVersion() . ' (' . $service->getName() . ')';
1105
1106 $serviceInfo['bin'] = $bin;
1107 $serviceInfo['name'] = $name;
1108 $serviceInfo['port'] = $port;
1109 $serviceInfo['syntaxCheckCmd'] = $syntaxCheckCmd;
1110
1111 // Update splash with current service being checked (1 step)
1112 if ($currentIndex > 0 && $totalCount > 0) {
1113 $this->splash->setTextLoading('Checking ' . $bin->getName() . ' service (' . $currentIndex . '/' . $totalCount . ')');
1114 }
1115 $this->splash->incrProgressBar();
1116
1117 // Check if service is already installed
1118 $serviceAlreadyInstalled = false;
1119 $serviceToRemove = false;
1120
1121 if ($sName == BinApache::SERVICE_NAME) {
1122 $serviceInfos = $this->checkApacheServiceWithTimeout($service);
1123 } else if ($sName == BinMysql::SERVICE_NAME) {
1124 $serviceInfos = $this->checkMySQLServiceWithTimeout($service, $bin);
1125 if ($serviceInfos === false && $service->isInstalled()) {
1126 Log::trace('MySQL service appears to be hanging, forcing restart');
1127 Win32Ps::killBins(['mysqld.exe']);
1128 $service->delete();
1129 $serviceToRemove = true;
1130 }
1131 } else {
1132 try {
1133 $serviceInfos = $service->infos();
1134 } catch (\Exception $e) {
1135 Log::trace("Exception during service check: " . $e->getMessage());
1136 $serviceInfos = false;
1137 } catch (\Throwable $e) {
1138 Log::trace("Throwable during service check: " . $e->getMessage());
1139 $serviceInfos = false;
1140 }
1141 }
1142
1143 if ($serviceInfos !== false) {
1144 $serviceAlreadyInstalled = true;
1145 $this->writeLog($name . ' service already installed');
1146
1147 // Check if service needs to be removed and reinstalled
1148 if ($sName == BinPostgresql::SERVICE_NAME) {
1149 $serviceGenPathName = trim(str_replace('"', '', $service->getBinPath()));
1150 $installedPathParts = explode(' ', $serviceInfos[Win32Service::VBS_PATH_NAME], 2);
1151 $serviceVbsPathName = trim(str_replace('"', '', $installedPathParts[0]));
1152 } else {
1153 $serviceGenPathName = trim(str_replace('"', '', $service->getBinPath() . ($service->getParams() ? ' ' . $service->getParams() : '')));
1154 $serviceVbsPathName = trim(str_replace('"', '', $serviceInfos[Win32Service::VBS_PATH_NAME]));
1155 }
1156
1157 $normalizedGenPath = preg_replace('/\s+/', ' ', $serviceGenPathName);
1158 $normalizedVbsPath = preg_replace('/\s+/', ' ', $serviceVbsPathName);
1159
1160 if ($normalizedGenPath !== $normalizedVbsPath && $serviceGenPathName != $serviceVbsPathName) {
1161 $serviceToRemove = true;
1162 $this->writeLog($name . ' service has to be removed');
1163 }
1164 }
1165
1166 // Remove service if needed (no progress increment - part of check phase)
1167 if ($serviceToRemove) {
1168 if (!$service->delete()) {
1169 $serviceInfo['restart'] = true;
1170 return $serviceInfo;
1171 }
1172 }
1173
1174 // Check port availability (no progress increment - part of check phase)
1175 $isPortInUse = Util::isPortInUse($port);
1176 if ($isPortInUse !== false) {
1177 // Port is in use - check if it's our service that's already running
1178 if ($service->isRunning()) {
1179 // Service is already running and owns the port - this is OK
1180 $this->writeLog($name . ' service already running on port ' . $port);
1181 Log::trace('Service ' . $name . ' already running - no need to start');
1182 $serviceInfo['needsStart'] = false;
1183 return $serviceInfo;
1184 }
1185
1186 // Port is in use by something else - this is an error
1187 $serviceInfo['error'] = sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_PORT_ERROR), $port, $isPortInUse);
1188 return $serviceInfo;
1189 }
1190
1191 // Install service if needed (no progress increment - part of check phase)
1192 if (!$serviceAlreadyInstalled || $serviceToRemove) {
1193 if (!$service->create()) {
1194 $serviceInfo['error'] = sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_CREATE_ERROR), $service->getError());
1195 return $serviceInfo;
1196 }
1197 }
1198
1199 $serviceInfo['needsStart'] = true;
1200 return $serviceInfo;
1201 }
1202
1207 private function refreshGitRepos()
1208 {
1209 global $bearsamppLang, $bearsamppTools;
1210
1211 $this->splash->incrProgressBar();
1212 if ( $bearsamppTools->getGit()->isScanStartup() ) {
1213 $this->splash->setTextLoading( $bearsamppLang->getValue( Lang::STARTUP_REFRESH_GIT_REPOS_TEXT ) );
1214
1215 $repos = $bearsamppTools->getGit()->findRepos( false );
1216 $this->writeLog( 'Update GIT repos: ' . count( $repos ) . ' found' );
1217 }
1218 }
1219
1227 private function checkApacheServiceWithTimeout($service)
1228 {
1229 Log::trace('Starting specialized Apache service check with timeout protection');
1230
1231 // Set a timeout for the Apache service check
1232 $serviceCheckStartTime = microtime(true);
1233 $serviceCheckTimeout = 10; // 10 seconds timeout
1234
1235 try {
1236 // Use a non-blocking approach to check service
1237 $serviceInfos = false;
1238
1239 // First try a quick check if the service exists in the list
1240 $serviceList = Win32Service::getServices();
1241 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1242 Log::trace('Apache service found in service list, getting details');
1243
1244 // Service exists, now try to get its details with timeout protection
1245 $startTime = microtime(true);
1246 $serviceInfos = $service->infos();
1247
1248 // Check if we've exceeded our timeout
1249 if (microtime(true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1250 Log::trace("Apache service check timeout exceeded, assuming service needs reinstall");
1251 return false;
1252 }
1253 } else {
1254 Log::trace('Apache service not found in service list');
1255 return false;
1256 }
1257
1258 return $serviceInfos;
1259 } catch (\Exception $e) {
1260 Log::trace("Exception during Apache service check: " . $e->getMessage());
1261 return false;
1262 } catch (\Throwable $e) {
1263 Log::trace("Throwable during Apache service check: " . $e->getMessage());
1264 return false;
1265 }
1266 }
1267
1276 private function checkMySQLServiceWithTimeout($service, $bin)
1277 {
1278 Log::trace('Starting specialized MySQL service check with timeout protection');
1279
1280 // Set a timeout for the MySQL service check
1281 $serviceCheckStartTime = microtime(true);
1282 $serviceCheckTimeout = 8; // 8 seconds timeout
1283
1284 try {
1285 // Use a non-blocking approach to check service
1286 $serviceInfos = false;
1287
1288 // First check if the service exists in the list
1289 $serviceList = Win32Service::getServices();
1290 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1291 Log::trace('MySQL service found in service list, getting details');
1292
1293 // Service exists, now try to get its details with timeout protection
1294 $serviceInfos = $service->infos();
1295
1296 // Check if we've exceeded our timeout
1297 if (microtime(true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1298 Log::trace("MySQL service check timeout exceeded, assuming service needs reinstall");
1299 return false;
1300 }
1301 } else {
1302 Log::trace('MySQL service not found in service list');
1303 return false;
1304 }
1305
1306 return $serviceInfos;
1307 } catch (\Exception $e) {
1308 Log::trace("Exception during MySQL service check: " . $e->getMessage());
1309 return false;
1310 } catch (\Throwable $e) {
1311 Log::trace("Throwable during MySQL service check: " . $e->getMessage());
1312 return false;
1313 }
1314 }
1315
1321 private function writeLog($log)
1322 {
1323 global $bearsamppRoot;
1324 Log::debug( $log, $bearsamppRoot->getStartupLogFilePath() );
1325 }
1326}
$result
global $bearsamppBins
global $bearsamppLang
global $bearsamppRoot
$port
global $bearsamppCore
$proc
Definition ajax.php:61
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 STARTUP_SERVICE_CREATE_ERROR
const STARTUP_SERVICE_PORT_ERROR
const STARTUP
const STARTUP_GEN_SSL_CRT_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 trace($data, $file=null)
const SYSPATH_REG_ENTRY
const HKEY_LOCAL_MACHINE
const APP_PATH_REG_ENTRY
const APP_BINS_REG_ENTRY
static disableLaunchStartup()
static getMicrotime()
static deleteFolder($path)
static setAppPathRegKey($value)
static getAppBinsRegKey($fromRegistry=true)
static getAppPathRegKey()
static startLoading()
static changePath($filesToScan, $rootPath=null)
static isPortInUse($port)
static getFilesToScan($path=null, $useCache=true, $forceRefresh=false)
static clearFolder($path, $exclude=array())
static enableLaunchStartup()
static setSysPathRegKey($value)
static getSysPathRegKey()
static setAppBinsRegKey($value)
static formatWindowsPath($path)
static formatUnixPath($path)
static getDefaultBrowser()
static killBins($refreshProcs=false)
const EXECUTABLE_PATH
const PROCESS_ID
global $bearsamppConfig
Definition homepage.php:41
const APP_TITLE
Definition root.php:13