main.itop-backup.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. // Copyright (C) 2014-2017 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. require_once(APPROOT.'setup/setuputils.class.inc.php');
  17. require_once(APPROOT.'setup/backup.class.inc.php');
  18. require_once(APPROOT.'core/mutex.class.inc.php');
  19. define('BACKUP_DEFAULT_FORMAT', '__DB__-%Y-%m-%d_%H_%M');
  20. class BackupHandler extends ModuleHandlerAPI
  21. {
  22. public static function OnMetaModelStarted()
  23. {
  24. try
  25. {
  26. $oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
  27. if ($oRestoreMutex->IsLocked())
  28. {
  29. IssueLog::Info(__class__.'::'.__function__.' A user is trying to use iTop while a restore is running. The requested page is in read-only mode.');
  30. MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
  31. MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-restore-running'), 'itop-backup');
  32. }
  33. }
  34. catch(Exception $e)
  35. {
  36. IssueLog::Error(__class__.'::'.__function__.' Failed to check if a backup/restore is running: '.$e->getMessage());
  37. }
  38. }
  39. }
  40. class DBBackupScheduled extends DBBackup
  41. {
  42. protected function LogInfo($sMsg)
  43. {
  44. static $bDebug = null;
  45. if ($bDebug == null)
  46. {
  47. $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
  48. }
  49. if ($bDebug)
  50. {
  51. echo $sMsg."\n";
  52. }
  53. }
  54. protected function LogError($sMsg)
  55. {
  56. static $bDebug = null;
  57. if ($bDebug == null)
  58. {
  59. $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
  60. }
  61. IssueLog::Error($sMsg);
  62. if ($bDebug)
  63. {
  64. echo 'Error: '.$sMsg."\n";
  65. }
  66. }
  67. /**
  68. * List and order by date the backups in the given directory
  69. * Note: the algorithm is currently based on the file modification date... because there is no "creation date" in general
  70. */
  71. public function ListFiles($sBackupDir)
  72. {
  73. $aFiles = array();
  74. $aTimes = array();
  75. // Legacy format -limited to 4 Gb
  76. foreach(glob($sBackupDir.'*.zip') as $sFilePath)
  77. {
  78. $aFiles[] = $sFilePath;
  79. $aTimes[] = filemtime($sFilePath); // unix time
  80. }
  81. // Modern format
  82. foreach(glob($sBackupDir.'*.tar.gz') as $sFilePath)
  83. {
  84. $aFiles[] = $sFilePath;
  85. $aTimes[] = filemtime($sFilePath); // unix time
  86. }
  87. array_multisort($aTimes, $aFiles);
  88. return $aFiles;
  89. }
  90. }
  91. class BackupExec implements iScheduledProcess
  92. {
  93. protected $sBackupDir;
  94. protected $iRetentionCount;
  95. /**
  96. * Constructor
  97. * @param sBackupDir string Target directory, defaults to APPROOT/data/backups/auto
  98. * @param iRetentionCount int Rotation (default to the value given in the configuration file 'retentation_count') set to 0 to disable this feature
  99. */
  100. public function __construct($sBackupDir = null, $iRetentionCount = null)
  101. {
  102. if (is_null($sBackupDir))
  103. {
  104. $this->sBackupDir = APPROOT.'data/backups/auto/';
  105. }
  106. else
  107. {
  108. $this->sBackupDir = $sBackupDir;
  109. }
  110. if (is_null($iRetentionCount))
  111. {
  112. $this->iRetentionCount = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'retention_count', 5);
  113. }
  114. else
  115. {
  116. $this->iRetentionCount = $iRetentionCount;
  117. }
  118. }
  119. public function Process($iUnixTimeLimit)
  120. {
  121. $oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
  122. $oMutex->Lock();
  123. try
  124. {
  125. // Make sure the target directory exists
  126. SetupUtils::builddir($this->sBackupDir);
  127. $oBackup = new DBBackupScheduled();
  128. // Eliminate files exceeding the retention setting
  129. //
  130. if ($this->iRetentionCount > 0)
  131. {
  132. $aFiles = $oBackup->ListFiles($this->sBackupDir);
  133. while (count($aFiles) >= $this->iRetentionCount)
  134. {
  135. $sFileToDelete = array_shift($aFiles);
  136. unlink($sFileToDelete);
  137. if (file_exists($sFileToDelete))
  138. {
  139. // Ok, do not loop indefinitely on this
  140. break;
  141. }
  142. }
  143. }
  144. // Do execute the backup
  145. //
  146. $oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
  147. $sBackupFileFormat = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'file_name_format', '__DB__-%Y-%m-%d_%H_%M');
  148. $sName = $oBackup->MakeName($sBackupFileFormat);
  149. if ($sName == '')
  150. {
  151. $sName = $oBackup->MakeName(BACKUP_DEFAULT_FORMAT);
  152. }
  153. $sBackupFile = $this->sBackupDir.$sName;
  154. $sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
  155. $oBackup->CreateCompressedBackup($sBackupFile, $sSourceConfigFile);
  156. }
  157. catch (Exception $e)
  158. {
  159. $oMutex->Unlock();
  160. throw $e;
  161. }
  162. $oMutex->Unlock();
  163. return "Created the backup: $sBackupFile";
  164. }
  165. /*
  166. Interpret current setting for the week days
  167. @returns array of int (monday = 1)
  168. */
  169. public function InterpretWeekDays()
  170. {
  171. static $aWEEKDAYTON = array('monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6, 'sunday' => 7);
  172. $aDays = array();
  173. $sWeekDays = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'week_days', 'monday, tuesday, wednesday, thursday, friday');
  174. if ($sWeekDays != '')
  175. {
  176. $aWeekDaysRaw = explode(',', $sWeekDays);
  177. foreach ($aWeekDaysRaw as $sWeekDay)
  178. {
  179. $sWeekDay = strtolower(trim($sWeekDay));
  180. if (array_key_exists($sWeekDay, $aWEEKDAYTON))
  181. {
  182. $aDays[] = $aWEEKDAYTON[$sWeekDay];
  183. }
  184. else
  185. {
  186. throw new Exception("'itop-backup: wrong format for setting 'week_days' (found '$sWeekDay')");
  187. }
  188. }
  189. }
  190. if (count($aDays) == 0)
  191. {
  192. throw new Exception("'itop-backup: missing setting 'week_days'");
  193. }
  194. $aDays = array_unique($aDays);
  195. sort($aDays);
  196. return $aDays;
  197. }
  198. /*
  199. Gives the exact time at which the process must be run next time
  200. @returns DateTime
  201. */
  202. public function GetNextOccurrence()
  203. {
  204. $bEnabled = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'enabled', true);
  205. if (!$bEnabled)
  206. {
  207. $oRet = new DateTime('3000-01-01');
  208. }
  209. else
  210. {
  211. // 1st - Interpret the list of days as ordered numbers (monday = 1)
  212. //
  213. $aDays = $this->InterpretWeekDays();
  214. // 2nd - Find the next active week day
  215. //
  216. $sBackupTime = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'time', '23:30');
  217. if (!preg_match('/[0-2][0-9]:[0-5][0-9]/', $sBackupTime))
  218. {
  219. throw new Exception("'itop-backup: wrong format for setting 'time' (found '$sBackupTime')");
  220. }
  221. $oNow = new DateTime();
  222. $iNextPos = false;
  223. for ($iDay = $oNow->format('N') ; $iDay <= 7 ; $iDay++)
  224. {
  225. $iNextPos = array_search($iDay, $aDays);
  226. if ($iNextPos !== false)
  227. {
  228. if (($iDay > $oNow->format('N')) || ($oNow->format('H:i') < $sBackupTime))
  229. {
  230. break;
  231. }
  232. $iNextPos = false; // necessary on sundays
  233. }
  234. }
  235. // 3rd - Compute the result
  236. //
  237. if ($iNextPos === false)
  238. {
  239. // Jump to the first day within the next week
  240. $iFirstDayOfWeek = $aDays[0];
  241. $iDayMove = $oNow->format('N') - $iFirstDayOfWeek;
  242. $oRet = clone $oNow;
  243. $oRet->modify('-'.$iDayMove.' days');
  244. $oRet->modify('+1 weeks');
  245. }
  246. else
  247. {
  248. $iNextDayOfWeek = $aDays[$iNextPos];
  249. $iMove = $iNextDayOfWeek - $oNow->format('N');
  250. $oRet = clone $oNow;
  251. $oRet->modify('+'.$iMove.' days');
  252. }
  253. list($sHours, $sMinutes) = explode(':', $sBackupTime);
  254. $oRet->setTime((int)$sHours, (int) $sMinutes);
  255. }
  256. return $oRet;
  257. }
  258. }
  259. class ItopBackup extends ModuleHandlerAPI
  260. {
  261. public static function OnMenuCreation()
  262. {
  263. if (UserRights::IsAdministrator())
  264. {
  265. $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
  266. new WebPageMenuNode('BackupStatus', utils::GetAbsoluteUrlModulePage('itop-backup', 'status.php'), $oAdminMenu->GetIndex(), 15 /* fRank */);
  267. }
  268. }
  269. }