summaryrefslogtreecommitdiffstats
path: root/modules/backup
diff options
context:
space:
mode:
Diffstat (limited to 'modules/backup')
-rw-r--r--modules/backup/config.json4
-rw-r--r--modules/backup/module.inc.php164
-rw-r--r--modules/backup/templates/_page.html41
-rw-r--r--modules/backup/templates/restore.html62
4 files changed, 271 insertions, 0 deletions
diff --git a/modules/backup/config.json b/modules/backup/config.json
new file mode 100644
index 00000000..4e7fa5fb
--- /dev/null
+++ b/modules/backup/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"content",
+ "enabled":"true"
+}
diff --git a/modules/backup/module.inc.php b/modules/backup/module.inc.php
new file mode 100644
index 00000000..4095f875
--- /dev/null
+++ b/modules/backup/module.inc.php
@@ -0,0 +1,164 @@
+<?php
+
+class Page_Backup extends Page
+{
+
+ private $action = false;
+ private $templateData = array();
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ $this->action = Request::post('action');
+ if ($this->action === 'backup') {
+ $this->backup();
+ } elseif ($this->action === 'restore') {
+ $this->restore();
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ if ($this->action === 'restore') {
+ Render::addTemplate('restore', $this->templateData);
+ } else {
+ Render::addScriptBottom('fileselect');
+ Render::addTemplate('_page');
+ }
+ }
+
+ private function backup()
+ {
+ $task = Taskmanager::submit('BackupRestore', array('mode' => 'backup'));
+ if (!isset($task['id'])) {
+ Message::addError('backup-failed');
+ Util::redirect('?do=Backup');
+ }
+ $task = Taskmanager::waitComplete($task, 30000);
+ if (!Taskmanager::isFinished($task) || !isset($task['data']['backupFile'])) {
+ Taskmanager::addErrorMessage($task);
+ Util::redirect('?do=Backup');
+ }
+ while ((@ob_get_level()) > 0)
+ @ob_end_clean();
+ $fh = @fopen($task['data']['backupFile'], 'rb');
+ if ($fh === false) {
+ Message::addError('error-read', $task['data']['backupFile']);
+ Util::redirect('?do=Backup');
+ }
+ Header('Content-Type: application/octet-stream', true);
+ Header('Content-Disposition: attachment; filename=' . 'satellite-backup_v' . Database::getExpectedSchemaVersion() . '_' . date('Y.m.d-H.i.s') . '.tgz');
+ Header('Content-Length: ' . @filesize($task['data']['backupFile']));
+ while (!feof($fh)) {
+ $data = fread($fh, 16000);
+ if ($data === false) {
+ EventLog::failure('Could not stream system backup to browser - backup corrupted!');
+ die("\r\n\nDOWNLOAD INTERRUPTED!\n");
+ }
+ echo $data;
+ @ob_flush();
+ @flush();
+ }
+ @fclose($fh);
+ @unlink($task['data']['backupFile']);
+ die();
+ }
+
+ private function restore()
+ {
+ if (!isset($_FILES['backupfile'])) {
+ Message::addError('missing-file');
+ Util::redirect('?do=Backup');
+ }
+ if ($_FILES['backupfile']['error'] != UPLOAD_ERR_OK) {
+ Message::addError('upload-failed', Util::uploadErrorString($_FILES['backupfile']['error']));
+ Util::redirect('?do=Backup');
+ }
+ $tempfile = '/tmp/bwlp-' . mt_rand(1, 100000) . '-' . crc32($_SERVER['REMOTE_ADDR']) . '.tgz';
+ if (!move_uploaded_file($_FILES['backupfile']['tmp_name'], $tempfile)) {
+ Message::addError('error-write', $tempfile);
+ Util::redirect('?do=Backup');
+ }
+ // Got uploaded file, now shut down all the daemons etc.
+ $parent = Trigger::stopDaemons(null, $this->templateData);
+ // Unmount store
+ $task = Taskmanager::submit('MountVmStore', array(
+ 'address' => 'null',
+ 'type' => 'images',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['mountid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ EventLog::info('Creating backup, v' . Database::getExpectedSchemaVersion() . ' on ' . Property::getServerIp());
+ // Finally run restore
+ $task = Taskmanager::submit('BackupRestore', array(
+ 'mode' => 'restore',
+ 'backupFile' => $tempfile,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false,
+ 'restoreOpenslx' => Request::post('restore_openslx', 'off') === 'on',
+ 'restoreDozmod' => Request::post('restore_dozmod', 'off') === 'on',
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['restoreid'] = $task['id'];
+ $parent = $task['id'];
+ TaskmanagerCallback::addCallback($task, 'dbRestored');
+ }
+ // Wait a bit
+ $task = Taskmanager::submit('SleepTask', array(
+ 'seconds' => 3,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id']))
+ $parent = $task['id'];
+ // Reboot
+ $task = Taskmanager::submit('Reboot', array(
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ // Leave this comment so the i18n scanner finds it:
+ // Message::addSuccess('restore-done');
+ if (isset($task['id']))
+ $this->templateData['rebootid'] = $task['id'];
+ }
+ private function stopDaemons($parent)
+ {
+ $task = Taskmanager::submit('SyncdaemonLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['syncid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('DozmodLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['dmsdid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('LdadpLauncher', array(
+ 'ids' => array(),
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['ldadpid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ return $parent;
+ }
+}
diff --git a/modules/backup/templates/_page.html b/modules/backup/templates/_page.html
new file mode 100644
index 00000000..47b5a174
--- /dev/null
+++ b/modules/backup/templates/_page.html
@@ -0,0 +1,41 @@
+<h1>{{lang_backupRestore}}</h1>
+
+<form action="?do=Backup" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="backup">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_backup}}</div>
+ <div class="panel-body">
+ <p>{{lang_backupDescription}}</p>
+ <button class="btn btn-primary" type="submit">{{lang_download}}</button>
+ </div>
+ </div>
+</form>
+
+<form action="?do=Backup" method="post" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="restore">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_restore}}</div>
+ <div class="panel-body">
+ <p>{{lang_restoreDescription}}</p>
+ <div class="input-group upload-ex">
+ <input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
+ <span class="input-group-btn">
+ <span class="btn btn-default btn-file">
+ {{lang_browseForFile}}&hellip; <input type="file" name="backupfile">
+ </span>
+ </span>
+ </div>
+ <div>
+ <label><input type="checkbox" name="restore_openslx" checked="checked"> {{lang_restoreSystemConfig}}</label>
+ <p><i>{{lang_systemExplanation}}</i></p>
+ </div>
+ <div>
+ <label><input type="checkbox" name="restore_dozmod" checked="checked"> {{lang_restoreDozmodConfig}}</label>
+ <p><i>{{lang_dozmodExplanation}}</i></p>
+ </div>
+ <button class="btn btn-primary" type="submit">{{lang_restore}}</button>
+ </div>
+ </div>
+</form> \ No newline at end of file
diff --git a/modules/backup/templates/restore.html b/modules/backup/templates/restore.html
new file mode 100644
index 00000000..4494a993
--- /dev/null
+++ b/modules/backup/templates/restore.html
@@ -0,0 +1,62 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_backup}}</div>
+ <div class="panel-body">
+ <div id="zeug">
+ <div data-tm-id="{{syncid}}" data-tm-log="messages">{{lang_stopping}} syncdaemon</div>
+ <div data-tm-id="{{dmsdid}}" data-tm-log="messages">{{lang_stopping}} dmsd</div>
+ <div data-tm-id="{{ldadpid}}" data-tm-log="messages">{{lang_stopping}} ldadp</div>
+ <div data-tm-id="{{mountid}}" data-tm-log="messages">{{lang_stopping}} vmstore</div>
+ <div data-tm-id="{{restoreid}}" data-tm-log="messages" data-tm-callback="restoreCb">{{lang_restoreConfig}}</div>
+ <div data-tm-id="{{rebootid}}" data-tm-log="messages">{{lang_reboot}}</div>
+ </div>
+ <div id="restorefailed" class="alert alert-danger" style="display:none">
+ {{lang_restoreFailed}}
+ </div>
+ <div id="waiting" style="display:none">
+ <span id="dots"></span>
+ </div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ function restoreCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+ if (task.statusCode === 'TASK_ERROR') {
+ $('#restorefailed').show('slow');
+ }
+ if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'TASK_FINISHED') {
+ startRebootPoll();
+ }
+ }
+
+ function startRebootPoll()
+ {
+ $('#waiting').show();
+ $('#waiting').prepend('<span class="glyphicon glyphicon-refresh slx-rotation"></span>');
+ $('#dots').text('{{lang_waitReboot}}');
+ slxDotInterval = setInterval(function() { $('#dots').text($('#dots').text() + '..'); }, 3000);
+ setTimeout('rebootPoll()', 10000);
+ }
+
+ function rebootPoll()
+ {
+ if (slxDotInterval !== false) {
+ clearInterval(slxDotInterval);
+ slxDotInterval = false;
+ }
+ $('#dots').text($('#dots').text() + '..');
+ slxTimeoutId = setTimeout('rebootPoll()', 3500);
+ $.ajax({url: "index.php?do=Main", timeout: 3000}).success(function(data, textStatus, jqXHR) {
+ if (textStatus !== "success" && textStatus !== "notmodified")
+ return;
+ if (data.indexOf('Status: DB running') === -1)
+ return;
+ clearTimeout(slxTimeoutId);
+ setTimeout(function() {
+ window.location.replace("index.php?do=Main&message[]=success%7Crestore-done");
+ }, 3500);
+ });
+ }
+</script>