41 $this->splash =
new Splash();
42 $this->restart =
false;
47 $this->filesToScan = array();
49 $gauge = self::GAUGE_SERVICES * count(
$bearsamppBins->getServices() );
50 $gauge += self::GAUGE_OTHERS + 1;
59 $bearsamppWinbinder->setHandler( $this->splash->getWbWindow(), $this,
'processWindow', 1000 );
60 $bearsamppWinbinder->mainLoop();
61 $bearsamppWinbinder->reset();
90 if ($bearsamppRoot->getProcs() !==
false) {
98 foreach ($listProcs as
$proc) {
101 Util::logTrace(
'Found ' . count($listProcs) .
' running processes');
103 Util::logTrace(
'No processes found or unable to retrieve process list');
108 $this->
writeLog(
'List bins modules:');
109 foreach ($bearsamppBins->getAll() as $module) {
110 if (!$module->isEnable()) {
112 Util::logTrace(
'Bin module ' . $module->getName() .
' is disabled');
114 $this->
writeLog(
'-> ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
115 Util::logTrace(
'Bin module ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
120 $this->
writeLog(
'List tools modules:');
121 foreach ($bearsamppTools->getAll() as $module) {
122 if (!$module->isEnable()) {
124 Util::logTrace(
'Tool module ' . $module->getName() .
' is disabled');
126 $this->
writeLog(
'-> ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
127 Util::logTrace(
'Tool module ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
132 $this->
writeLog(
'List apps modules:');
133 foreach ($bearsamppApps->getAll() as $module) {
134 if (!$module->isEnable()) {
136 Util::logTrace(
'App module ' . $module->getName() .
' is disabled');
138 $this->
writeLog(
'-> ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
139 Util::logTrace(
'App module ' . $module->getName() .
': ' . $module->getVersion() .
' (' . $module->getRelease() .
')');
202 if (!$this->restart && empty($this->error)) {
203 Util::logTrace(
'Startup completed successfully - refreshing Git repositories');
206 $this->
writeLog(
'Started in ' . $startupTime .
's');
207 Util::logTrace(
'Application started successfully in ' . $startupTime .
' seconds');
209 Util::logTrace(
'Startup issues detected - incrementing progress bar');
210 $this->splash->incrProgressBar(2);
213 if ($this->restart) {
214 Util::logTrace(
'Restart required - preparing to restart application');
216 $this->splash->setTextLoading(
233 if (!empty($this->error)) {
234 Util::logTrace(
'Errors occurred during startup: ' . $this->error);
235 $this->
writeLog(
'Error: ' . $this->error);
252 $bearsamppWinbinder->destroyWindow($window);
253 $bearsamppWinbinder->reset();
271 $this->splash->incrProgressBar();
274 if (!is_dir($archivesPath)) {
276 mkdir($archivesPath, 0777,
true);
280 $date = date(
'Y-m-d-His', time());
281 $archiveLogsPath = $archivesPath .
'/' . $date;
282 $archiveScriptsPath = $archiveLogsPath .
'/scripts';
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);
290 Util::logTrace(
"Logs archive directory already exists: " . $archiveLogsPath);
293 if (!is_dir($archiveScriptsPath)) {
294 Util::logTrace(
"Creating scripts archive directory: " . $archiveScriptsPath);
295 mkdir($archiveScriptsPath, 0777,
true);
297 Util::logTrace(
"Scripts archive directory already exists: " . $archiveScriptsPath);
303 $handle = @opendir($archivesPath);
305 Util::logTrace(
"Failed to open archives directory: " . $archivesPath);
309 while (
false !== ($file = readdir($handle))) {
310 if ($file ==
'.' || $file ==
'..') {
313 $archives[] = $archivesPath .
'/' . $file;
317 Util::logTrace(
"Found " . count($archives) .
" existing archives");
323 for ($i = 0; $i < $total; $i++) {
330 $isFileLocked =
function($filePath) {
331 if (!file_exists($filePath)) {
335 $handle = @fopen($filePath,
'r+');
336 if ($handle ===
false) {
348 $handle = @opendir($srcPath);
357 while (
false !== ($file = readdir($handle))) {
358 if ($file ==
'.' || $file ==
'..' || is_dir($srcPath .
'/' . $file)) {
362 $sourceFile = $srcPath .
'/' . $file;
363 $destFile = $archiveLogsPath .
'/' . $file;
366 if ($isFileLocked($sourceFile)) {
373 if (copy($sourceFile, $destFile)) {
380 }
catch (Exception $e) {
382 Util::logTrace(
"Exception copying log file " . $file .
": " . $e->getMessage());
386 Util::logTrace(
"Logs archived: " . $logsCopied .
" copied, " . $logsSkipped .
" skipped");
391 $handle = @opendir($srcPath);
400 while (
false !== ($file = readdir($handle))) {
401 if ($file ==
'.' || $file ==
'..' || is_dir($srcPath .
'/' . $file)) {
405 $sourceFile = $srcPath .
'/' . $file;
406 $destFile = $archiveScriptsPath .
'/' . $file;
409 if ($isFileLocked($sourceFile)) {
416 if (copy($sourceFile, $destFile)) {
423 }
catch (Exception $e) {
425 Util::logTrace(
"Exception copying script file " . $file .
": " . $e->getMessage());
429 Util::logTrace(
"Scripts archived: " . $scriptsCopied .
" copied, " . $scriptsSkipped .
" skipped");
434 $handle = @opendir($logsPath);
436 Util::logTrace(
"Failed to open logs directory for purging: " . $logsPath);
441 $logsPurgeSkipped = 0;
443 while (
false !== ($file = readdir($handle))) {
444 if ($file ==
'.' || $file ==
'..' || $file ==
'archives' || $file ==
'.gitignore' || is_dir($logsPath .
'/' . $file)) {
448 $filePath = $logsPath .
'/' . $file;
451 if ($isFileLocked($filePath)) {
452 Util::logTrace(
"Skipping locked log file during purge: " . $file);
458 if (file_exists($filePath) && unlink($filePath)) {
465 }
catch (Exception $e) {
467 Util::logTrace(
"Exception purging log file " . $file .
": " . $e->getMessage());
471 Util::logTrace(
"Logs purged: " . $logsDeleted .
" deleted, " . $logsPurgeSkipped .
" skipped");
484 $this->splash->incrProgressBar();
486 $this->
writeLog(
'Clear tmp folders' );
487 Util::clearFolder( $bearsamppRoot->getTmpPath(), array(
'cachegrind',
'composer',
'openssl',
'mailpit',
'xlight',
'npm-cache',
'pip',
'opcache',
'.gitignore') );
491 $opcachePath =
$bearsamppRoot->getTmpPath() . DIRECTORY_SEPARATOR .
'opcache';
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);
501 if (!is_writable($opcachePath)) {
502 $this->
writeLog(
'Opcache directory is not writable: ' . $opcachePath);
513 $this->
writeLog(
'Clean old behaviors' );
516 $this->splash->incrProgressBar();
519 $bearsamppRegistry->deleteValue(
521 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
534 $this->splash->incrProgressBar();
547 if ( !empty( $procsKilled ) ) {
549 $procsKilledSort = array();
550 foreach ( $procsKilled as
$proc ) {
552 $procsKilledSort[] =
'-> ' . basename( $unixExePath ) .
' (PID ' .
$proc[
Win32Ps::PROCESS_ID] .
') in ' . $unixExePath;
554 sort( $procsKilledSort );
555 foreach ( $procsKilledSort as
$proc ) {
569 $this->splash->incrProgressBar();
570 $this->
writeLog(
'Refresh hostname' );
582 $this->
writeLog(
'Check launch startup' );
584 if ( $bearsamppConfig->isLaunchStartup() ) {
600 $this->splash->incrProgressBar();
604 if ( empty( $currentBrowser ) || !file_exists( $currentBrowser ) ) {
617 $this->splash->incrProgressBar();
620 $this->
writeLog( sprintf(
'OS: %s', $os ) );
631 $this->splash->incrProgressBar();
632 $this->
writeLog(
'Refresh aliases' );
645 $this->splash->incrProgressBar();
646 $this->
writeLog(
'Refresh vhosts' );
659 $this->splash->incrProgressBar();
661 $this->
writeLog(
'Last path: ' . $bearsamppCore->getLastPathContent() );
674 $this->splash->incrProgressBar();
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)');
687 $this->
writeLog(
'Performance: File scan skipped, saving 3-8 seconds');
693 Util::logTrace(
'Last path: "' . $lastPath .
'" differs from current path: "' . $currentPath .
'"');
694 $this->
writeLog(
'Path changed detected - performing full scan');
700 $this->
writeLog(
'Files to scan: ' . count($this->filesToScan) .
' (scanned in ' . $scanDuration .
's)');
711 $this->splash->incrProgressBar();
714 $this->
writeLog(
'Nb files changed: ' .
$result[
'countChangedFiles'] );
715 $this->
writeLog(
'Nb occurences changed: ' .
$result[
'countChangedOcc'] );
725 file_put_contents(
$bearsamppCore->getLastPath(), $this->rootPath );
726 $this->
writeLog(
'Save current path: ' . $this->rootPath );
737 $this->splash->incrProgressBar();
741 $this->
writeLog(
'Current app path reg key: ' . $currentAppPathRegKey );
742 $this->
writeLog(
'Gen app path reg key: ' . $genAppPathRegKey );
743 if ( $currentAppPathRegKey != $genAppPathRegKey ) {
745 if ( !empty( $this->error ) ) {
746 $this->error .= PHP_EOL . PHP_EOL;
749 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
752 $this->
writeLog(
'Need restart: checkPathRegKey' );
753 $this->restart =
true;
770 $this->splash->incrProgressBar();
774 $this->
writeLog(
'Current app bins reg key: ' . $currentAppBinsRegKey );
775 $this->
writeLog(
'Gen app bins reg key: ' . $genAppBinsRegKey );
776 if ( $currentAppBinsRegKey != $genAppBinsRegKey ) {
778 if ( !empty( $this->error ) ) {
779 $this->error .= PHP_EOL . PHP_EOL;
782 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
785 $this->
writeLog(
'Need restart: checkBinsRegKey' );
786 $this->restart =
true;
803 $this->splash->incrProgressBar();
806 $this->
writeLog(
'Current system PATH: ' . $currentSysPathRegKey );
811 $this->
writeLog(
'New system PATH: ' . $newSysPathRegKey );
813 if ( $currentSysPathRegKey != $newSysPathRegKey ) {
815 if ( !empty( $this->error ) ) {
816 $this->error .= PHP_EOL . PHP_EOL;
819 $this->error .= PHP_EOL . $bearsamppRegistry->getLatestError();
822 $this->
writeLog(
'Need restart: checkSystemPathRegKey' );
823 $this->restart =
true;
827 $this->
writeLog(
'Refresh system PATH: ' . $currentSysPathRegKey );
842 $this->splash->incrProgressBar();
845 $bearsamppBins->update();
846 $bearsamppTools->update();
847 $bearsamppApps->update();
858 $this->splash->incrProgressBar();
859 if ( !$bearsamppOpenSsl->existsCrt(
'localhost' ) ) {
861 $bearsamppOpenSsl->createCrt(
'localhost' );
879 if (!$this->restart) {
886 $this->splash->incrProgressBar(self::GAUGE_SERVICES * count(
$bearsamppBins->getServices()));
906 $servicesToStart = [];
910 $currentServiceIndex = 0;
913 $currentServiceIndex++;
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;
925 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
929 if (!empty($serviceInfo[
'error'])) {
930 $serviceErrors[$sName] = $serviceInfo;
932 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
936 if ($serviceInfo[
'needsStart']) {
937 $servicesToStart[$sName] = $serviceInfo;
941 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 1);
946 if (!empty($servicesToStart)) {
947 Util::logTrace(
'Starting ' . count($servicesToStart) .
' services sequentially');
950 $totalServices = count($servicesToStart);
952 foreach ($servicesToStart as $sName => $serviceInfo) {
954 $name = $serviceInfo[
'name'];
955 $service = $serviceInfo[
'service'];
958 $this->splash->setTextLoading(
'Starting ' . $name .
' (' . $serviceCount .
'/' . $totalServices .
')');
959 $this->splash->incrProgressBar();
965 $success = $service->start();
970 $this->
writeLog($name .
' service started in ' . $duration .
's');
971 Util::logTrace(
'Service ' . $name .
' started successfully in ' . $duration .
' seconds');
974 $this->splash->setTextLoading($name .
' started successfully');
976 $error = $service->getError();
978 $error =
'Failed to start service';
981 $serviceErrors[$sName] = $serviceInfo;
982 $serviceErrors[$sName][
'error'] =
$error;
986 if (!empty($serviceInfo[
'syntaxCheckCmd'])) {
988 $cmdSyntaxCheck = $serviceInfo[
'bin']->getCmdLineOutput($serviceInfo[
'syntaxCheckCmd']);
989 if (!$cmdSyntaxCheck[
'syntaxOk']) {
990 $serviceErrors[$sName][
'error'] .= PHP_EOL .
'Syntax error: ' . $cmdSyntaxCheck[
'content'];
992 }
catch (\Exception $e) {
999 $this->splash->incrProgressBar(self::GAUGE_SERVICES - 2);
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;
1015 $this->
writeLog(
'Service installation completed in ' . $installDuration .
's');
1016 Util::logTrace(
'Service installation completed in ' . $installDuration .
' seconds');
1034 'service' => $service,
1038 'syntaxCheckCmd' =>
null,
1041 'needsStart' =>
false,
1046 $syntaxCheckCmd =
null;
1066 if (!file_exists($bin->getDataDir()) || count(glob($bin->getDataDir() .
'/*')) === 0) {
1082 $name = $bin->getName() .
' ' . $bin->getVersion() .
' (' . $service->getName() .
')';
1084 $serviceInfo[
'bin'] = $bin;
1085 $serviceInfo[
'name'] = $name;
1086 $serviceInfo[
'port'] =
$port;
1087 $serviceInfo[
'syntaxCheckCmd'] = $syntaxCheckCmd;
1090 if ($currentIndex > 0 && $totalCount > 0) {
1091 $this->splash->setTextLoading(
'Checking ' . $bin->getName() .
' service (' . $currentIndex .
'/' . $totalCount .
')');
1093 $this->splash->incrProgressBar();
1096 $serviceAlreadyInstalled =
false;
1097 $serviceToRemove =
false;
1103 if ($serviceInfos ===
false && $service->isInstalled()) {
1104 Util::logTrace(
'MySQL service appears to be hanging, forcing restart');
1107 $serviceToRemove =
true;
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;
1121 if ($serviceInfos !==
false) {
1122 $serviceAlreadyInstalled =
true;
1123 $this->
writeLog($name .
' service already installed');
1127 $serviceGenPathName = trim(str_replace(
'"',
'', $service->getBinPath()));
1129 $serviceVbsPathName = trim(str_replace(
'"',
'', $installedPathParts[0]));
1131 $serviceGenPathName = trim(str_replace(
'"',
'', $service->getBinPath() . ($service->getParams() ?
' ' . $service->getParams() :
'')));
1135 $normalizedGenPath = preg_replace(
'/\s+/',
' ', $serviceGenPathName);
1136 $normalizedVbsPath = preg_replace(
'/\s+/',
' ', $serviceVbsPathName);
1138 if ($normalizedGenPath !== $normalizedVbsPath && $serviceGenPathName != $serviceVbsPathName) {
1139 $serviceToRemove =
true;
1140 $this->
writeLog($name .
' service has to be removed');
1145 if ($serviceToRemove) {
1146 if (!$service->delete()) {
1147 $serviceInfo[
'restart'] =
true;
1148 return $serviceInfo;
1154 if ($isPortInUse !==
false) {
1156 if ($service->isRunning()) {
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;
1166 return $serviceInfo;
1170 if (!$serviceAlreadyInstalled || $serviceToRemove) {
1171 if (!$service->create()) {
1173 return $serviceInfo;
1177 $serviceInfo[
'needsStart'] =
true;
1178 return $serviceInfo;
1189 $this->splash->incrProgressBar();
1190 if ( $bearsamppTools->getGit()->isScanStartup() ) {
1193 $repos = $bearsamppTools->getGit()->findRepos(
false );
1194 $this->
writeLog(
'Update GIT repos: ' . count( $repos ) .
' found' );
1207 Util::logTrace(
'Starting specialized Apache service check with timeout protection');
1210 $serviceCheckStartTime = microtime(
true);
1211 $serviceCheckTimeout = 10;
1215 $serviceInfos =
false;
1218 $serviceList = Win32Service::getServices();
1219 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1220 Util::logTrace(
'Apache service found in service list, getting details');
1224 $serviceInfos = $service->infos();
1227 if (microtime(
true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1228 Util::logTrace(
"Apache service check timeout exceeded, assuming service needs reinstall");
1236 return $serviceInfos;
1237 }
catch (\Exception $e) {
1238 Util::logTrace(
"Exception during Apache service check: " . $e->getMessage());
1240 }
catch (\Throwable $e) {
1241 Util::logTrace(
"Throwable during Apache service check: " . $e->getMessage());
1256 Util::logTrace(
'Starting specialized MySQL service check with timeout protection');
1259 $serviceCheckStartTime = microtime(
true);
1260 $serviceCheckTimeout = 8;
1264 $serviceInfos =
false;
1267 $serviceList = Win32Service::getServices();
1268 if (is_array($serviceList) && isset($serviceList[$service->getName()])) {
1269 Util::logTrace(
'MySQL service found in service list, getting details');
1272 $serviceInfos = $service->infos();
1275 if (microtime(
true) - $serviceCheckStartTime > $serviceCheckTimeout) {
1276 Util::logTrace(
"MySQL service check timeout exceeded, assuming service needs reinstall");
1284 return $serviceInfos;
1285 }
catch (\Exception $e) {
1286 Util::logTrace(
"Exception during MySQL service check: " . $e->getMessage());
1288 }
catch (\Throwable $e) {
1289 Util::logTrace(
"Throwable during MySQL service check: " . $e->getMessage());
prepareService($sName, $service, $bearsamppBins, $bearsamppLang, $currentIndex=0, $totalCount=0)
installServicesSequential($bearsamppBins, $bearsamppLang)
checkMySQLServiceWithTimeout($service, $bin)
checkApacheServiceWithTimeout($service)
processWindow($window, $id, $ctrl, $param1, $param2)
const STARTUP_STARTING_TEXT
const STARTUP_REFRESH_GIT_REPOS_TEXT
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_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 disableLaunchStartup()
static deleteFolder($path)
static logTrace($data, $file=null)
static setAppPathRegKey($value)
static getAppBinsRegKey($fromRegistry=true)
static getAppPathRegKey()
static logDebug($data, $file=null)
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()
static killBins($refreshProcs=false)