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