class DBRestore extends DBBackup { /** @var string */ private $sDBPwd; /** @var string */ private $sDBUser; public function __construct(\Config $oConfig = null) { parent::__construct($oConfig); $this->sDBUser = $this->oConfig->Get('db_user'); $this->sDBPwd = $this->oConfig->Get('db_pwd'); } protected function LogInfo($sMsg) { //IssueLog::Info('non juste info: '.$sMsg); } protected function LogError($sMsg) { IssueLog::Error($sMsg); } protected function LoadDatabase($sDataFile) { $this->LogInfo("Loading data onto $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')"); // Just to check the connection to the DB (more accurate than getting the retcode of mysql) $oMysqli = $this->DBConnect(); $sHost = self::EscapeShellArg($this->sDBHost); $sUser = self::EscapeShellArg($this->sDBUser); $sPwd = self::EscapeShellArg($this->sDBPwd); $sDBName = self::EscapeShellArg($this->sDBName); $sMySQLExe = DBBackup::MakeSafeMySQLCommand($this->sMySQLBinDir, 'mysql'); if (is_null($this->iDBPort)) { $sPortOption = ''; } else { $sPortOption = '--port='.$this->iDBPort.' '; } $sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig); $sDataFileEscaped = self::EscapeShellArg($sDataFile); $sCommand = "$sMySQLExe --default-character-set=".DEFAULT_CHARACTER_SET." --host=$sHost $sPortOption --user=$sUser --password=$sPwd $sTlsOptions $sDBName <$sDataFileEscaped 2>&1"; $sCommandDisplay = "$sMySQLExe --default-character-set=".DEFAULT_CHARACTER_SET." --host=$sHost $sPortOption --user=xxxx --password=xxxx $sTlsOptions $sDBName <$sDataFileEscaped 2>&1"; // Now run the command for real $this->LogInfo("Executing command: $sCommandDisplay"); $aOutput = array(); $iRetCode = 0; exec($sCommand, $aOutput, $iRetCode); foreach($aOutput as $sLine) { $this->LogInfo("mysql said: $sLine"); } if ($iRetCode != 0) { $this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode"); foreach($aOutput as $sLine) { $this->LogError("mysql said: $sLine"); } if (count($aOutput) == 1) { $sMoreInfo = trim($aOutput[0]); } else { $sMoreInfo = "Check the log file '".realpath(APPROOT.'/log/error.log')."' for more information."; } throw new BackupException("Failed to execute mysql: ".$sMoreInfo); } } /** * Warning : can't be called with a loaded DataModel as we're compiling after restore * * @param string $sFile A file with the extension .zip or .tar.gz * @param string $sEnvironment Target environment * * @throws \BackupException * * @uses \RunTimeEnvironment::CompileFrom() */ public function RestoreFromCompressedBackup($sFile, $sEnvironment = 'production') { $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'"); $oRestoreMutex->Lock(); try { IssueLog::Info('Backup Restore - LOCK acquired, executing...'); $bReadonlyBefore = SetupUtils::EnterMaintenanceMode(MetaModel::GetConfig()); try { //safe zone for db backup => cron is stopped/ itop in readonly $this->LogInfo("Starting restore of ".basename($sFile)); $sNormalizedFile = strtolower(basename($sFile)); if (substr($sNormalizedFile, -4) == '.zip') { $this->LogInfo('zip file detected'); $oArchive = new ZipArchiveEx(); $oArchive->open($sFile); } elseif (substr($sNormalizedFile, -7) == '.tar.gz') { $this->LogInfo('tar.gz file detected'); $oArchive = new TarGzArchive($sFile); } else { throw new BackupException('Unsupported format for a backup file: '.$sFile); } // Load the database // $sDataDir = utils::GetDataPath().'tmp-backup-'.rand(10000, getrandmax()); SetupUtils::builddir($sDataDir); // Here is the directory $oArchive->extractTo($sDataDir); $sDataFile = $sDataDir.'/itop-dump.sql'; $this->LoadDatabase($sDataFile); // Update the code // $sDeltaFile = utils::GetDataPath().$sEnvironment.'.delta.xml'; if (is_file($sDataDir.'/delta.xml')) { // Extract and rename delta.xml => .delta.xml; rename($sDataDir.'/delta.xml', $sDeltaFile); } else { @unlink($sDeltaFile); } if (is_dir(utils::GetDataPath().'production-modules/')) { try { SetupUtils::rrmdir(utils::GetDataPath().'production-modules/'); } catch (Exception $e) { throw new BackupException("Can't remove production-modules dir", 0, $e); } } if (is_dir($sDataDir.'/production-modules')) { rename($sDataDir.'/production-modules', utils::GetDataPath().'production-modules/'); } $sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php'; @chmod($sConfigFile, 0770); // Allow overwriting the file rename($sDataDir.'/config-itop.php', $sConfigFile); @chmod($sConfigFile, 0440); // Read-only $aExtraFiles = $this->ListExtraFiles($sDataDir); foreach($aExtraFiles as $sSourceFilePath => $sDestinationFilePath) { SetupUtils::builddir(dirname($sDestinationFilePath)); rename($sSourceFilePath, $sDestinationFilePath); } try { SetupUtils::rrmdir($sDataDir); } catch (Exception $e) { throw new BackupException("Can't remove data dir", 0, $e); } $oEnvironment = new RunTimeEnvironment($sEnvironment); $oEnvironment->CompileFrom($sEnvironment); } finally { if (! $bReadonlyBefore) { SetupUtils::ExitMaintenanceMode(); } else { //we are in the scope of main process that needs to handle/keep readonly mode. $this->LogInfo("Keep maintenance mode after restore"); } } } finally { IssueLog::Info('Backup Restore - LOCK released.'); $oRestoreMutex->Unlock(); } } /** * List the 'extra files' found in the decompressed archive * (i.e. files other than config-itop.php, delta.xml, itop-dump.sql or production-modules/* * @param string $sDataDir * @return string[] */ protected function ListExtraFiles(string $sDataDir) { $aExtraFiles = []; $aStandardFiles = ['config-itop.php', 'itop-dump.sql', 'production-modules', 'delta.xml']; $oDirectoryIterator = new RecursiveDirectoryIterator($sDataDir, FilesystemIterator::CURRENT_AS_FILEINFO|FilesystemIterator::SKIP_DOTS); $oIterator = new RecursiveIteratorIterator($oDirectoryIterator); foreach ($oIterator as $oFileInfo) { if (in_array($oFileInfo->getFilename(), $aStandardFiles)) { continue; } // Normalize filenames to cope with Windows backslashes $sPath = str_replace('\\', '/', $oFileInfo->getPathname()); $sRefPath = str_replace('\\', '/', $sDataDir.'/production-modules'); if (strncmp($sPath, $sRefPath, strlen($sRefPath)) == 0) { continue; } $aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir)); } return $aExtraFiles; } }