main.itop-backup.php 7.9 KB

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