main.itop-backup.php 8.2 KB

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