123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- <?php
- /**
- * Performs deploy of database and config files updates
- */
- class UbillingUpdateManager {
- /**
- * System alter.ini config as key=>value
- *
- * @var array
- */
- protected $altCfg = array();
- /**
- * System billing.ini config as key=>value
- *
- * @var array
- */
- protected $billCfg = array();
- /**
- * System mysql.ini config as key=>value
- *
- * @var array
- */
- protected $mySqlCfg = array();
- /**
- * System message helper object placeholder
- *
- * @var object
- */
- protected $messages = '';
- /**
- * Available mysql dumps for apply as release=>filename
- *
- * @var array
- */
- protected $allDumps = array();
- /**
- * Contains available configs updates as release=>configdata
- *
- * @var array
- */
- protected $allConfigs = array();
- /**
- * Contais configs filenames as shortid=>filename
- *
- * @var array
- */
- protected $configFileNames = array();
- /**
- * system sudo path
- *
- * @var string
- */
- protected $sudoPath = '/usr/local/bin/sudo';
- /**
- * Automatic updater full path
- *
- * @var string
- */
- protected $atoupdaterPath = '/bin/ubautoupgrade.sh';
- const URL_RELEASE_STABLE = 'http://ubilling.net.ua/RELEASE';
- const URL_RELEASE_CURRENT = 'http://snaps.ubilling.net.ua/RELEASE';
- const DUMPS_PATH = 'content/updates/sql/';
- const CONFIGS_PATH = 'content/updates/configs/';
- const URL_ME = '?module=updatemanager';
- const URL_RELNOTES = 'wiki.ubilling.net.ua/doku.php?id=relnotes#section';
- const ROUTE_AUTOSYSUPGRADE = 'autosystemupgrade';
- const PROUTE_UPGRADEAGREE = 'runsystemupgrade';
- const PID_AUTOSYSUPGRADE = 'UPDMGRAUTOUPGRADE';
- /**
- * Creates new update manager instance
- *
- * @return void
- */
- public function __construct() {
- $this->loadSystemConfigs();
- $this->setOptions();
- $this->initMessages();
- $this->setConfigFilenames();
- $this->loadDumps();
- $this->loadConfigs();
- $this->ConnectDB();
- }
- /**
- * Loads all required system config files into protected props for further usage
- *
- * @global object $ubillingConfig
- *
- * @return void
- */
- protected function loadSystemConfigs() {
- global $ubillingConfig;
- $this->altCfg = $ubillingConfig->getAlter();
- $this->billCfg = $ubillingConfig->getBilling();
- $this->mySqlCfg = rcms_parse_ini_file(CONFIG_PATH . 'mysql.ini');
- }
- /**
- * Sets all required options
- *
- * @return void
- */
- protected function setOptions() {
- if (!empty($this->billCfg)) {
- $this->sudoPath = $this->billCfg['SUDO'];
- }
- }
- /**
- * Inits system messages helper object instance
- *
- * @return void
- */
- protected function initMessages() {
- $this->messages = new UbillingMessageHelper();
- }
- /**
- * Loads available mysql dumps filenames into protected prop
- *
- * @return void
- */
- protected function loadDumps() {
- $dumpsTmp = rcms_scandir(self::DUMPS_PATH, '*.sql');
- if (!empty($dumpsTmp)) {
- rsort($dumpsTmp);
- foreach ($dumpsTmp as $io => $each) {
- $release = str_replace('.sql', '', $each);
- $this->allDumps[$release] = $each;
- }
- }
- }
- /**
- * Loads available configs update into protected prop
- *
- * @return void
- */
- protected function loadConfigs() {
- $configsTmp = rcms_scandir(self::CONFIGS_PATH, '*.ini');
- if (!empty($configsTmp)) {
- rsort($configsTmp);
- foreach ($configsTmp as $io => $each) {
- $release = str_replace('.ini', '', $each);
- $fileContent = rcms_parse_ini_file(self::CONFIGS_PATH . $each, true);
- $this->allConfigs[$release] = $fileContent;
- }
- }
- }
- /**
- * Sets shortid=>filename with path configs associations array
- *
- * @return void
- */
- protected function setConfigFilenames() {
- $this->configFileNames = array(
- 'alter' => 'config/alter.ini',
- 'billing' => 'config/billing.ini',
- 'ymaps' => 'config/ymaps.ini',
- 'userstats' => 'userstats/config/userstats.ini',
- 'mysql' => 'config/mysql.ini'
- );
- }
- /**
- * Initialises connection with Ubilling database server and selects needed db
- *
- * @param MySQL Connection Id $connection
- *
- * @return MySQLDB
- */
- protected function ConnectDB() {
- $dbport = (empty($this->mySqlCfg['port']) ? '' : $this->mySqlCfg['port']);
- $this->DBConnection = new DbConnect($this->mySqlCfg['server'], $this->mySqlCfg['username'], $this->mySqlCfg['password'], $this->mySqlCfg['db'], true, false, $dbport);
- }
- /**
- * Returns list of files which was updated in some release
- *
- * @param string $release
- *
- * @return string
- */
- protected function getReleaseConfigFiles($release) {
- $result = '';
- if (isset($this->allConfigs[$release])) {
- if (!empty($this->allConfigs[$release])) {
- foreach ($this->allConfigs[$release] as $shortid => $data) {
- $filename = (isset($this->configFileNames[$shortid])) ? $this->configFileNames[$shortid] : $shortid;
- $result .= $filename . ' ';
- }
- }
- }
- return ($result);
- }
- /**
- * Checks is CLI batch updater available or not?
- *
- * @return bool
- */
- public function isUpdaterAvailable() {
- $result = false;
- if (file_exists($this->atoupdaterPath)) {
- $result = true;
- }
- return ($result);
- }
- /**
- * Apply Mysql Dump and returns results
- *
- * @param string $release
- *
- * @return string
- */
- protected function DoSqlDump($release) {
- $result = '';
- if (!empty($release)) {
- $fileName = self::DUMPS_PATH . $this->allDumps[$release];
- $file = explode(';', file_get_contents($fileName));
- $sql_dumps = array_diff($file, array('')); // Delete empty data Array
- $sql_array = array_map('trim', $sql_dumps);
- // Open DB connection and set character
- $this->DBConnection->open();
- $this->DBConnection->query("set character_set_client='" . $this->mySqlCfg['character'] . "'");
- $this->DBConnection->query("set character_set_results='" . $this->mySqlCfg['character'] . "'");
- $this->DBConnection->query("set collation_connection='" . $this->mySqlCfg['character'] . "_general_ci'");
- foreach ($sql_array as $query) {
- if (!empty($query)) {
- $this->DBConnection->query($query);
- if (!$this->DBConnection->error()) {
- $result .= $this->messages->getStyledMessage(wf_tag('b', false) . __('Done') . ': ' . wf_tag('b', true) . wf_tag('pre', false) . $query . wf_tag('pre', true), 'success') . wf_tag('br');
- } else {
- $result .= $this->messages->getStyledMessage(wf_tag('b', false) . __('Error') . ': ' . wf_tag('b', true) . $this->DBConnection->error() . wf_tag('pre', false) . $query . wf_tag('pre', true), 'error') . wf_tag('br');
- }
- }
- }
- $this->DBConnection->close();
- }
- return ($result);
- }
- /**
- * Renders list of sql dumps available for applying
- *
- * @return string
- */
- public function renderSqlDumpsList() {
- $result = '';
- if (!empty($this->allDumps)) {
- $cells = wf_TableCell(__('Ubilling release'));
- $cells .= wf_TableCell(__('Details'));
- $cells .= wf_TableCell(__('Actions'));
- $rows = wf_TableRow($cells, 'row1');
- foreach ($this->allDumps as $release => $filename) {
- $relnotesUrl = self::URL_RELNOTES . str_replace('.', '', $release);
- $relnotesLink = wf_Link('http://' . $relnotesUrl, __('Release notes') . ' ' . $release, false, '', 'target="_BLANK"');
- $alertText = __('Are you serious') . ' ' . __('Apply') . ' Ubilling ' . $release . '?';
- $actLink = wf_JSAlert(self::URL_ME . '&applysql=' . $release, wf_img('skins/icon_restoredb.png', __('Apply')), $alertText);
- $cells = wf_TableCell($release);
- $cells .= wf_TableCell($relnotesLink);
- $cells .= wf_TableCell($actLink);
- $rows .= wf_TableRow($cells, 'row5');
- }
- $result .= wf_TableBody($rows, '100%', 0, 'sortable');
- } else {
- $result = $this->messages->getStyledMessage(__('Nothing found'), 'info');
- }
- return ($result);
- }
- /**
- * Renders list of available config files updates
- *
- * @return string
- */
- public function renderConfigsList() {
- $result = '';
- if (!empty($this->allConfigs)) {
- $cells = wf_TableCell(__('Ubilling release'));
- $cells .= wf_TableCell(__('Details'));
- $cells .= wf_TableCell(__('Files'));
- $cells .= wf_TableCell(__('Actions'));
- $rows = wf_TableRow($cells, 'row1');
- foreach ($this->allConfigs as $release => $filename) {
- $relnotesUrl = self::URL_RELNOTES . str_replace('.', '', $release);
- $relnotesLink = wf_Link('http://' . $relnotesUrl, __('Release notes') . ' ' . $release, false, '', 'target="_BLANK"');
- $alertText = __('Are you serious') . ' ' . __('Apply') . ' Ubilling ' . $release . '?';
- $actLink = wf_JSAlert(self::URL_ME . '&showconfigs=' . $release, wf_img('skins/icon_addrow.png', __('Apply')), $alertText);
- $cells = wf_TableCell($release);
- $cells .= wf_TableCell($relnotesLink);
- $cells .= wf_TableCell($this->getReleaseConfigFiles($release));
- $cells .= wf_TableCell($actLink);
- $rows .= wf_TableRow($cells, 'row5');
- }
- $result .= wf_TableBody($rows, '100%', 0, 'sortable');
- } else {
- $result = $this->messages->getStyledMessage(__('Nothing found'), 'info');
- }
- return ($result);
- }
- /**
- * Applies mysql dump to current database
- *
- * @param string $release
- *
- * @return string
- */
- public function applyMysqlDump($release) {
- $result = '';
- $release = trim($release);
- $release = vf($release);
- if (isset($this->allDumps[$release])) {
- if (wf_CheckPost(array('applyconfirm', 'applysqldump'))) {
- $result .= $this->messages->getStyledMessage(__('MySQL dump applying result below'), 'info');
- $result .= wf_CleanDiv();
- log_register('UPDMGR APPLY SQL RELEASE `' . $release . '`');
- $result .= $this->DoSqlDump($release);
- $result .= wf_BackLink(self::URL_ME);
- } else {
- if ((!wf_CheckPost(array('applyconfirm'))) and (wf_CheckPost(array('applysqldump')))) {
- $result .= $this->messages->getStyledMessage(__('You are not mentally prepared for this'), 'error');
- $result .= wf_delimiter();
- $result .= wf_BackLink(self::URL_ME . '&applysql=' . $release);
- } else {
- $result .= $this->messages->getStyledMessage(__('Caution: these changes can not be undone.'), 'warning');
- $result .= wf_tag('br');
- $inputs = __('Apply changes for Ubilling release') . ' ' . $release . '?';
- $inputs .= wf_tag('br');
- $inputs .= wf_tag('br');
- $inputs .= wf_HiddenInput('applysqldump', 'true');
- $inputs .= wf_CheckInput('applyconfirm', __('I`m ready'), true, false);
- $inputs .= wf_tag('br');
- $inputs .= wf_Submit(__('Apply'));
- $result .= wf_Form('', 'POST', $inputs, 'glamour');
- $result .= wf_CleanDiv();
- $result .= wf_delimiter();
- $result .= wf_BackLink(self::URL_ME);
- }
- }
- } else {
- $result = $this->messages->getStyledMessage(__('Wrong release'), 'error');
- log_register('UPDMGR FAIL SQL RELEASE `' . $release . '`');
- }
- return ($result);
- }
- /**
- * Changes access rights for some path to be writable
- *
- * @param string $path
- *
- * @return void
- */
- protected function fixAccessRights($path) {
- $command = $this->sudoPath . ' chmod -R 777 ' . $path;
- shell_exec($command);
- }
- /**
- * Renders interface and applies new options to some config files
- *
- * @param string $release
- *
- * @return string
- */
- public function applyConfigOptions($release) {
- $result = '';
- $release = trim($release);
- $release = vf($release);
- $newOptsCount = 0;
- if (isset($this->allConfigs[$release])) {
- $releaseData = $this->allConfigs[$release];
- if (!empty($releaseData)) {
- foreach ($releaseData as $configId => $configOptions) {
- @$configName = $this->configFileNames[$configId];
- $canUpdate = false;
- if (!empty($configName)) {
- if (file_exists($configName)) {
- $currentConfigOptions = rcms_parse_ini_file($configName);
- $result .= $this->messages->getStyledMessage(__('Existing config file') . ': ' . $configName, 'success');
- //some logging
- if (wf_CheckPost(array('applyconfigoptions', 'applyconfirm'))) {
- //Initial line break and update header
- $configUpdateHeader = "\n";
- $configUpdateHeader .= ';release ' . $release . ' update' . "\n";
- if (is_writable($configName)) {
- $canUpdate = true;
- } else {
- $canUpdate = false;
- $result .= $this->messages->getStyledMessage(__('Permission denied') . ': ' . $configName . '.', 'error');
- $result .= $this->messages->getStyledMessage(__('Trying to set write permissions for') . ' ' . $configName . ' ' . __('to fix this issue') . '.', 'warning');
- $this->fixAccessRights($configName);
- if (is_writable($configName)) {
- $canUpdate = true;
- $result .= $this->messages->getStyledMessage(__('Success! Config file') . ' ' . $configName . ' ' . __('now is writable') . '.', 'success');
- } else {
- $canUpdate = false;
- $result .= $this->messages->getStyledMessage(__('Seems like we failed with making this file writable') . '.', 'error');
- }
- }
- //now real put update header
- if ($canUpdate) {
- file_put_contents($configName, $configUpdateHeader, FILE_APPEND);
- log_register('UPDMGR APPLY CONFIG `' . $configId . '` RELEASE `' . $release . '`');
- }
- }
- if (!empty($configOptions)) {
- foreach ($configOptions as $optionName => $optionContent) {
- if (!isset($currentConfigOptions[$optionName])) {
- $newOptsCount++;
- $result .= $this->messages->getStyledMessage(__('New option') . ': ' . $optionName . ' ' . __('will be added with value') . ' ' . $optionContent, 'info');
- if (wf_CheckPost(array('applyconfigoptions', 'applyconfirm'))) {
- $saveOptions = $optionName . '=' . $optionContent . "\n";
- if (is_writable($configName)) {
- file_put_contents($configName, $saveOptions, FILE_APPEND);
- $result .= $this->messages->getStyledMessage(__('Option added') . ': ' . $optionName . '= ' . $optionContent, 'success');
- $newOptsCount--;
- } else {
- $result .= $this->messages->getStyledMessage(__('New option') . ' ' . $optionName . ' ' . __('not created') . '. ' . __('Permission denied') . '.', 'error');
- }
- }
- } else {
- $result .= $this->messages->getStyledMessage(__('Option already exists, will be ignored') . ': ' . $optionName, 'warning');
- }
- }
- }
- } else {
- $result .= $this->messages->getStyledMessage(__('Wrong config path') . ': ' . $configName, 'error');
- }
- } else {
- $result .= $this->messages->getStyledMessage(__('Unknown config') . ': ' . $configId, 'error');
- }
- }
- //confirmation checkbox notice
- if ((wf_CheckPost(array('applyconfigoptions'))) and (!wf_CheckPost(array('applyconfirm')))) {
- $result .= $this->messages->getStyledMessage(__('You are not mentally prepared for this'), 'error');
- }
- //apply form assembly
- if ($newOptsCount > 0) {
- $result .= wf_tag('br');
- $inputs = __('Apply changes for Ubilling release') . ' ' . $release . '?';
- $inputs .= wf_tag('br');
- $inputs .= wf_tag('br');
- $inputs .= wf_HiddenInput('applyconfigoptions', 'true');
- $inputs .= wf_CheckInput('applyconfirm', __('I`m ready'), true, false);
- $inputs .= wf_tag('br');
- $inputs .= wf_Submit(__('Apply'));
- $result .= wf_Form('', 'POST', $inputs, 'glamour');
- $result .= wf_CleanDiv();
- } else {
- $result .= $this->messages->getStyledMessage(__('Everything is fine. All required options for release') . ' ' . $release . ' ' . __('is on their places.'), 'success');
- }
- }
- $result .= wf_CleanDiv();
- $result .= wf_delimiter();
- $result .= wf_BackLink(self::URL_ME);
- } else {
- $result .= $this->messages->getStyledMessage(__('Wrong release'), 'error');
- log_register('UPDMGR FAIL CONF RELEASE `' . $release . '`');
- }
- return ($result);
- }
- /**
- * Renders current release info data and update check controls
- *
- * @return string
- */
- public function renderVersionInfo() {
- $currentRelease = file_get_contents("RELEASE");
- $updatechecker = wf_tag('br') . wf_tag('div', false, '', 'style="margin-left: 3%;"');
- $updatechecker .= wf_AjaxLink('?module=updatemanager&checkupdates=true', wf_img('skins/question.png') . ' ' . __('Check updates'), 'lastrelease', false, 'ubButton');
- $updatechecker .= wf_tag('div', true);
- $updatechecker .= wf_CleanDiv();
- $releaseInfo = wf_tag('style') . '#ubajaxloaderanim { margin-left: 3%; margin-top: 10px; }' . wf_tag('style', true);
- $releaseInfo .= $updatechecker;
- $releaseInfo .= $this->messages->getStyledMessage(__('Current Ubilling version') . ': ' . $currentRelease, 'info');
- $releaseInfo .= wf_AjaxContainer('lastrelease', '', '');
- $releaseInfo .= wf_AjaxLoader();
- return ($releaseInfo);
- }
- /**
- * Performs automatic system upgrade
- *
- * @param string $branch
- *
- * @return void/string on error
- */
- public function performAutoUpgrade($branch) {
- $result = '';
- $updateProcess = new StarDust(self::PID_AUTOSYSUPGRADE);
- if ($updateProcess->notRunning()) {
- $updateProcess->start();
- log_register('UPDMGR AUTOUPGRADE `' . $branch . '` STARTED');
- if ($this->sudoPath and $this->atoupdaterPath) {
- if (file_exists($this->atoupdaterPath)) {
- $command = $this->sudoPath . ' ' . $this->atoupdaterPath . ' ' . $branch;
- $autoUpdaterResult = shell_exec($command);
- if (!ispos($autoUpdaterResult, 'SUCCESS')) {
- $result .= __('Something went wrong') . ': ' . $autoUpdaterResult;
- log_register('UPDMGR AUTOUPGRADE `' . $branch . '` FAILED');
- }
- } else {
- $result .= $this->atoupdaterPath . ' ' . __('not exists');
- }
- }
- log_register('UPDMGR AUTOUPGRADE `' . $branch . '` FINISHED');
- $updateProcess->stop();
- } else {
- $result .= __('System update') . ' ' . __('already running');
- log_register('UPDMGR AUTOUPGRADE `' . $branch . '` SKIPPED');
- }
- return ($result);
- }
- }
- /**
- * Ubilling updates deployment routines
- */
- class UbillingUpdateStuff {
- /**
- * Contains system billing.ini as key=>value
- *
- * @var array
- */
- protected $billingCfg = array();
- /**
- * Wget path
- *
- * @var string
- */
- protected $wgetPath = '/usr/local/bin/wget';
- /**
- * Tar archiver path
- *
- * @var string
- */
- protected $tarPath = '/usr/bin/tar';
- /**
- * system sudo path
- *
- * @var string
- */
- protected $sudoPath = '/usr/local/bin/sudo';
- /**
- * Gzip archiver path
- *
- * @var gzip
- */
- protected $gzipPath = '/usr/bin/gzip';
- public function __construct() {
- $this->loadConfig();
- $this->setOptions();
- }
- /**
- * Loads all required configs
- *
- * @global object $ubillingConfig
- *
- * @return void
- */
- protected function loadConfig() {
- global $ubillingConfig;
- $this->billingCfg = $ubillingConfig->getBilling();
- }
- /**
- * Sets custom paths to required software
- *
- * @return void
- */
- protected function setOptions() {
- if (isset($this->billingCfg['SUDO'])) {
- $this->sudoPath = $this->billingCfg['SUDO'];
- }
- if (isset($this->billingCfg['WGET_PATH'])) {
- $this->wgetPath = $this->billingCfg['WGET_PATH'];
- }
- if (isset($this->billingCfg['TAR_PATH'])) {
- $this->tarPath = $this->billingCfg['TAR_PATH'];
- }
- if (isset($this->billingCfg['GZIP_PATH'])) {
- $this->gzipPath = $this->billingCfg['GZIP_PATH'];
- }
- }
- /**
- * Changes access rights for some directory to be writable
- *
- * @param string $directory
- *
- * @return void
- */
- public function fixAccessRights($directory) {
- $command = $this->sudoPath . ' chmod -R 777 ' . $directory;
- shell_exec($command);
- }
- /**
- * Downloads file from remote host
- *
- * @param string $url
- * @param string $directory
- * @param string $filename
- *
- * @return void
- */
- public function downloadRemoteFile($url, $directory, $filename = '') {
- if ($filename) {
- $wgetOptions = '--output-document=' . $directory . $filename . ' ';
- } else {
- $wgetOptions = '--directory-prefix=' . $directory . basename($url) . ' ';
- }
- $wgetOptions .= '--no-check-certificate ';
- if (file_exists($directory)) {
- if (!is_writable($directory)) {
- throw new Exception('DOWNLOAD_DIRECTORY_NOT_WRITABLE');
- }
- $command = $this->wgetPath . ' ' . $wgetOptions . ' ' . $url;
- shell_exec($command);
- } else {
- throw new Exception('DOWNLOAD_DIRECTORY_NOT_EXISTS');
- }
- }
- /**
- * Extracts tar.gz archive to some path
- *
- * @param string $archivePath
- * @param string $extractPath
- *
- * @return void
- */
- public function extractTgz($archivePath, $extractPath) {
- if (file_exists($archivePath)) {
- if (is_readable($archivePath)) {
- if (file_exists($extractPath)) {
- if (!is_writable($extractPath)) {
- $this->fixAccessRights($extractPath);
- }
- //unpacking archive
- $command = $this->tarPath . ' zxvf ' . $archivePath . ' -C ' . $extractPath;
- shell_exec($command);
- } else {
- throw new Exception('EXTRACT_DIRECTORY_NOT_EXISTS');
- }
- }
- }
- }
- }
|