Initial backup-and-restore GUI restoration
Initial restoration: basic backup-and-restore works, but - progress bar in backup does not work - selecting only some VMs to restore does not work - various miscellaneous enhancements are not yet in place
This commit is contained in:
parent
05d393035b
commit
7a4e4b35d5
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# pylint: skip-file
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
@ -21,78 +21,68 @@
|
||||
#
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
|
||||
import signal
|
||||
import shutil
|
||||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtGui import *
|
||||
|
||||
from qubes.qubes import QubesVmCollection
|
||||
from qubes.qubes import QubesException
|
||||
from qubes.qubes import QubesDaemonPidfile
|
||||
from qubes.qubes import QubesHost
|
||||
from qubes import backup
|
||||
from qubes import qubesutils
|
||||
from qubesadmin import Qubes, events, exc
|
||||
from qubesadmin import utils as admin_utils
|
||||
from qubes.storage.file import get_disk_usage
|
||||
|
||||
import qubesmanager.resources_rc
|
||||
|
||||
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
|
||||
|
||||
import time
|
||||
from .thread_monitor import *
|
||||
from operator import itemgetter
|
||||
|
||||
from datetime import datetime
|
||||
from string import replace
|
||||
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
|
||||
|
||||
from .ui_backupdlg import *
|
||||
from .multiselectwidget import *
|
||||
|
||||
from .backup_utils import *
|
||||
import main
|
||||
import grp,pwd
|
||||
|
||||
from . import utils
|
||||
import grp
|
||||
import pwd
|
||||
import sys
|
||||
import os
|
||||
from .thread_monitor import *
|
||||
import time
|
||||
|
||||
class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
|
||||
__pyqtSignals__ = ("backup_progress(int)",)
|
||||
|
||||
def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None):
|
||||
def __init__(self, app, qvm_collection, parent=None):
|
||||
super(BackupVMsWindow, self).__init__(parent)
|
||||
|
||||
self.app = app
|
||||
self.qvm_collection = qvm_collection
|
||||
self.blk_manager = blk_manager
|
||||
self.shutdown_vm_func = shutdown_vm_func
|
||||
self.backup_settings = QtCore.QSettings()
|
||||
|
||||
self.func_output = []
|
||||
self.selected_vms = []
|
||||
self.tmpdir_to_remove = None
|
||||
self.canceled = False
|
||||
|
||||
self.vm = self.qvm_collection[0]
|
||||
self.files_to_backup = None
|
||||
|
||||
assert self.vm != None
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self.progress_status.text = self.tr("Backup in progress...")
|
||||
self.show_running_vms_warning(False)
|
||||
self.dir_line_edit.setReadOnly(False)
|
||||
|
||||
self.select_vms_widget = MultiSelectWidget(self)
|
||||
self.verticalLayout.insertWidget(1, self.select_vms_widget)
|
||||
|
||||
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
|
||||
self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running)
|
||||
self.connect(self.select_vms_widget, SIGNAL("items_removed(PyQt_PyObject)"), self.vms_removed)
|
||||
self.connect(self.select_vms_widget, SIGNAL("items_added(PyQt_PyObject)"), self.vms_added)
|
||||
self.refresh_button.clicked.connect(self.check_running)
|
||||
self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected)
|
||||
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
|
||||
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
|
||||
self.connect(self, SIGNAL("currentIdChanged(int)"),
|
||||
self.current_page_changed)
|
||||
self.connect(self.select_vms_widget,
|
||||
SIGNAL("items_removed(PyQt_PyObject)"),
|
||||
self.vms_removed)
|
||||
self.connect(self.select_vms_widget,
|
||||
SIGNAL("items_added(PyQt_PyObject)"),
|
||||
self.vms_added)
|
||||
self.connect(self, SIGNAL("backup_progress(int)"),
|
||||
self.progress_bar.setValue)
|
||||
self.dir_line_edit.connect(self.dir_line_edit,
|
||||
SIGNAL("textChanged(QString)"),
|
||||
self.backup_location_changed)
|
||||
|
||||
self.select_vms_page.isComplete = self.has_selected_vms
|
||||
self.select_dir_page.isComplete = self.has_selected_dir_and_pass
|
||||
@ -112,40 +102,62 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
self.backup_location_changed)
|
||||
|
||||
self.total_size = 0
|
||||
self.__fill_vms_list__()
|
||||
|
||||
fill_appvms_list(self)
|
||||
self.load_settings()
|
||||
# TODO: is the is_running criterion really necessary? It's designed to
|
||||
# avoid backuping a VM into itself or surprising the user with starting
|
||||
# a VM when they didn't plan to.
|
||||
# TODO: inform the user only running VMs are listed?
|
||||
self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice(
|
||||
self.appvm_combobox,
|
||||
self.qvm_collection,
|
||||
None,
|
||||
self.qvm_collection.domains['dom0'],
|
||||
(lambda vm: vm.klass != 'TemplateVM' and vm.is_running()),
|
||||
allow_internal=False,
|
||||
allow_default=False,
|
||||
allow_none=False
|
||||
)
|
||||
|
||||
selected = self.load_settings()
|
||||
self.__fill_vms_list__(selected)
|
||||
|
||||
def load_settings(self):
|
||||
dest_vm_name = main.manager_window.manager_settings.value(
|
||||
'backup/vmname', defaultValue="")
|
||||
dest_vm_idx = self.appvm_combobox.findText(dest_vm_name.toString())
|
||||
try:
|
||||
profile_data = load_backup_profile()
|
||||
except Exception as ex: # TODO: fix just for file not found
|
||||
return
|
||||
if not profile_data:
|
||||
return
|
||||
|
||||
if 'destination_vm' in profile_data:
|
||||
dest_vm_name = profile_data['destination_vm']
|
||||
dest_vm_idx = self.appvm_combobox.findText(dest_vm_name)
|
||||
if dest_vm_idx > -1:
|
||||
self.appvm_combobox.setCurrentIndex(dest_vm_idx)
|
||||
|
||||
if main.manager_window.manager_settings.contains('backup/path'):
|
||||
dest_path = main.manager_window.manager_settings.value(
|
||||
'backup/path', defaultValue=None)
|
||||
self.dir_line_edit.setText(dest_path.toString())
|
||||
if 'destination_path' in profile_data:
|
||||
dest_path = profile_data['destination_path']
|
||||
self.dir_line_edit.setText(dest_path)
|
||||
|
||||
if main.manager_window.manager_settings.contains('backup/encrypt'):
|
||||
encrypt = main.manager_window.manager_settings.value(
|
||||
'backup/encrypt', defaultValue=None)
|
||||
self.encryption_checkbox.setChecked(encrypt.toBool())
|
||||
if 'passphrase_text' in profile_data:
|
||||
self.passphrase_line_edit.setText(profile_data['passphrase_text'])
|
||||
self.passphrase_line_edit_verify.setText(
|
||||
profile_data['passphrase_text'])
|
||||
# TODO: make a checkbox for saving the profile
|
||||
# TODO: warn that unknown data will be overwritten
|
||||
|
||||
if 'include' in profile_data:
|
||||
return profile_data['include']
|
||||
|
||||
return None
|
||||
|
||||
def save_settings(self):
|
||||
main.manager_window.manager_settings.setValue(
|
||||
'backup/vmname', self.appvm_combobox.currentText())
|
||||
main.manager_window.manager_settings.setValue(
|
||||
'backup/path', self.dir_line_edit.text())
|
||||
main.manager_window.manager_settings.setValue(
|
||||
'backup/encrypt', self.encryption_checkbox.isChecked())
|
||||
|
||||
def show_running_vms_warning(self, show):
|
||||
self.running_vms_warning.setVisible(show)
|
||||
self.shutdown_running_vms_button.setVisible(show)
|
||||
self.refresh_button.setVisible(show)
|
||||
settings = {'destination_vm': self.appvm_combobox.currentText(),
|
||||
'destination_path': self.dir_line_edit.text(),
|
||||
'include': [vm.name for vm in self.selected_vms],
|
||||
'passphrase_text': self.passphrase_line_edit.text()}
|
||||
# TODO: add compression when it is added
|
||||
write_backup_profile(settings)
|
||||
|
||||
class VmListItem(QListWidgetItem):
|
||||
def __init__(self, vm):
|
||||
@ -153,114 +165,45 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
if vm.qid == 0:
|
||||
local_user = grp.getgrnam('qubes').gr_mem[0]
|
||||
home_dir = pwd.getpwnam(local_user).pw_dir
|
||||
self.size = qubesutils.get_disk_usage(home_dir)
|
||||
self.size = get_disk_usage(home_dir)
|
||||
else:
|
||||
self.size = self.get_vm_size(vm)
|
||||
super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
|
||||
self.size = vm.get_disk_utilization()
|
||||
super(BackupVMsWindow.VmListItem, self).__init__(
|
||||
vm.name + " (" + admin_utils.size_to_human(self.size) + ")")
|
||||
|
||||
def get_vm_size(self, vm):
|
||||
size = 0
|
||||
if vm.private_img is not None:
|
||||
size += qubesutils.get_disk_usage (vm.private_img)
|
||||
|
||||
if vm.updateable:
|
||||
size += qubesutils.get_disk_usage(vm.root_img)
|
||||
|
||||
return size
|
||||
|
||||
|
||||
def __fill_vms_list__(self):
|
||||
for vm in self.qvm_collection.values():
|
||||
if vm.internal:
|
||||
def __fill_vms_list__(self, selected=None):
|
||||
for vm in self.qvm_collection.domains:
|
||||
if vm.features.get('internal', False):
|
||||
continue
|
||||
|
||||
item = BackupVMsWindow.VmListItem(vm)
|
||||
if vm.include_in_backups == True:
|
||||
if (selected is None and
|
||||
getattr(vm, 'include_in_backups', True)) \
|
||||
or (selected and vm.name in selected):
|
||||
self.select_vms_widget.selected_list.addItem(item)
|
||||
self.total_size += item.size
|
||||
else:
|
||||
self.select_vms_widget.available_list.addItem(item)
|
||||
self.select_vms_widget.available_list.sortItems()
|
||||
self.select_vms_widget.selected_list.sortItems()
|
||||
self.check_running()
|
||||
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
|
||||
|
||||
self.unrecognized_config_label.setVisible(
|
||||
selected is not None and
|
||||
len(selected) != len(self.select_vms_widget.selected_list))
|
||||
self.total_size_label.setText(
|
||||
admin_utils.size_to_human(self.total_size))
|
||||
|
||||
def vms_added(self, items):
|
||||
for i in items:
|
||||
self.total_size += i.size
|
||||
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
|
||||
self.total_size_label.setText(
|
||||
admin_utils.size_to_human(self.total_size))
|
||||
|
||||
def vms_removed(self, items):
|
||||
for i in items:
|
||||
self.total_size -= i.size
|
||||
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
|
||||
|
||||
def check_running(self):
|
||||
some_selected_vms_running = False
|
||||
for i in range(self.select_vms_widget.selected_list.count()):
|
||||
item = self.select_vms_widget.selected_list.item(i)
|
||||
if item.vm.is_running() and item.vm.qid != 0:
|
||||
item.setForeground(QBrush(QColor(255, 0, 0)))
|
||||
some_selected_vms_running = True
|
||||
else:
|
||||
item.setForeground(QBrush(QColor(0, 0, 0)))
|
||||
|
||||
self.show_running_vms_warning(some_selected_vms_running)
|
||||
|
||||
for i in range(self.select_vms_widget.available_list.count()):
|
||||
item = self.select_vms_widget.available_list.item(i)
|
||||
if item.vm.is_running() and item.vm.qid != 0:
|
||||
item.setForeground(QBrush(QColor(255, 0, 0)))
|
||||
else:
|
||||
item.setForeground(QBrush(QColor(0, 0, 0)))
|
||||
|
||||
return some_selected_vms_running
|
||||
|
||||
def shutdown_all_running_selected(self):
|
||||
(names, vms) = self.get_running_vms()
|
||||
if len(vms) == 0:
|
||||
return
|
||||
|
||||
for vm in vms:
|
||||
self.blk_manager.check_if_serves_as_backend(vm)
|
||||
|
||||
reply = QMessageBox.question(None, self.tr("VM Shutdown Confirmation"),
|
||||
self.tr(
|
||||
"Are you sure you want to power down the following VMs: "
|
||||
"<b>{0}</b>?<br/>"
|
||||
"<small>This will shutdown all the running applications "
|
||||
"within them.</small>").format(', '.join(names)),
|
||||
QMessageBox.Yes | QMessageBox.Cancel)
|
||||
|
||||
self.app.processEvents()
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
|
||||
wait_time = 60.0
|
||||
for vm in vms:
|
||||
self.shutdown_vm_func(vm, wait_time*1000)
|
||||
|
||||
progress = QProgressDialog ("Shutting down VMs <b>{0}</b>...".format(', '.join(names)), "", 0, 0)
|
||||
progress.setModal(True)
|
||||
progress.show()
|
||||
|
||||
wait_for = wait_time
|
||||
while self.check_running() and wait_for > 0:
|
||||
self.app.processEvents()
|
||||
time.sleep (0.5)
|
||||
wait_for -= 0.5
|
||||
|
||||
progress.hide()
|
||||
|
||||
def get_running_vms(self):
|
||||
names = []
|
||||
vms = []
|
||||
for i in range(self.select_vms_widget.selected_list.count()):
|
||||
item = self.select_vms_widget.selected_list.item(i)
|
||||
if item.vm.is_running() and item.vm.qid != 0:
|
||||
names.append(item.vm.name)
|
||||
vms.append(item.vm)
|
||||
return (names, vms)
|
||||
self.total_size_label.setText(
|
||||
admin_utils.size_to_human(self.total_size))
|
||||
|
||||
@pyqtSlot(name='on_select_path_button_clicked')
|
||||
def select_path_button_clicked(self):
|
||||
@ -268,44 +211,43 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
|
||||
def validateCurrentPage(self):
|
||||
if self.currentPage() is self.select_vms_page:
|
||||
if self.check_running():
|
||||
QMessageBox.information(None,
|
||||
self.tr("Wait!"),
|
||||
self.tr("Some selected VMs are running. "
|
||||
"Running VMs can not be backuped. "
|
||||
"Please shut them down or remove them from the list."))
|
||||
return False
|
||||
|
||||
self.selected_vms = []
|
||||
for i in range(self.select_vms_widget.selected_list.count()):
|
||||
self.selected_vms.append(self.select_vms_widget.selected_list.item(i).vm)
|
||||
self.selected_vms.append(
|
||||
self.select_vms_widget.selected_list.item(i).vm)
|
||||
|
||||
elif self.currentPage() is self.select_dir_page:
|
||||
backup_location = str(self.dir_line_edit.text())
|
||||
if not backup_location:
|
||||
QMessageBox.information(None, self.tr("Wait!"),
|
||||
QMessageBox.information(
|
||||
None, self.tr("Wait!"),
|
||||
self.tr("Enter backup target location first."))
|
||||
return False
|
||||
if self.appvm_combobox.currentIndex() == 0 and \
|
||||
not os.path.isdir(backup_location):
|
||||
QMessageBox.information(None, self.tr("Wait!"),
|
||||
if self.appvm_combobox.currentIndex() == 0 \
|
||||
and not os.path.isdir(backup_location):
|
||||
QMessageBox.information(
|
||||
None, self.tr("Wait!"),
|
||||
self.tr("Selected directory do not exists or "
|
||||
"not a directory (%s).") % backup_location)
|
||||
return False
|
||||
if not len(self.passphrase_line_edit.text()):
|
||||
QMessageBox.information(None, self.tr("Wait!"),
|
||||
self.tr("Enter passphrase for backup encryption/verification first."))
|
||||
QMessageBox.information(
|
||||
None, self.tr("Wait!"),
|
||||
self.tr("Enter passphrase for backup "
|
||||
"encryption/verification first."))
|
||||
return False
|
||||
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
|
||||
QMessageBox.information(None,
|
||||
self.tr("Wait!"),
|
||||
if self.passphrase_line_edit.text() !=\
|
||||
self.passphrase_line_edit_verify.text():
|
||||
QMessageBox.information(
|
||||
None, self.tr("Wait!"),
|
||||
self.tr("Enter the same passphrase in both fields."))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def gather_output(self, s):
|
||||
self.func_output.append(s)
|
||||
# def gather_output(self, s):
|
||||
# self.func_output.append(s)
|
||||
|
||||
def update_progress_bar(self, value):
|
||||
self.emit(SIGNAL("backup_progress(int)"), value)
|
||||
@ -314,19 +256,23 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
msg = []
|
||||
|
||||
try:
|
||||
backup.backup_do(self.dir_line_edit.text(),
|
||||
self.files_to_backup,
|
||||
self.passphrase_line_edit.text(),
|
||||
progress_callback=self.update_progress_bar,
|
||||
encrypted=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm)
|
||||
#simulate_long_lasting_proces(10, self.update_progress_bar)
|
||||
except backup.BackupCanceledError as ex:
|
||||
msg.append(str(ex))
|
||||
self.canceled = True
|
||||
if ex.tmpdir:
|
||||
self.tmpdir_to_remove = ex.tmpdir
|
||||
except Exception as ex:
|
||||
# TODO: this does nothing, events are not handled
|
||||
events_dispatcher = events.EventsDispatcher(self.app)
|
||||
events_dispatcher.add_handler('backup-progress',
|
||||
self.update_progress_bar)
|
||||
try:
|
||||
vm = self.qvm_collection.domains[
|
||||
self.appvm_combobox.currentText()]
|
||||
if not vm.is_running():
|
||||
vm.start()
|
||||
self.qvm_collection.qubesd_call('dom0',
|
||||
'admin.backup.Execute',
|
||||
'qubes-manager-backup')
|
||||
except exc.QubesException as err:
|
||||
# TODO fixme
|
||||
print('\nBackup error: {}'.format(err), file=sys.stderr)
|
||||
return 1
|
||||
except Exception as ex: # TODO: fixme
|
||||
print("Exception:", ex)
|
||||
msg.append(str(ex))
|
||||
|
||||
@ -340,27 +286,13 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
||||
if self.currentPage() is self.confirm_page:
|
||||
|
||||
self.target_appvm = None
|
||||
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
||||
self.target_appvm = self.qvm_collection.get_vm_by_name(
|
||||
self.appvm_combobox.currentText())
|
||||
|
||||
del self.func_output[:]
|
||||
try:
|
||||
self.files_to_backup = backup.backup_prepare(
|
||||
self.selected_vms,
|
||||
print_callback = self.gather_output,
|
||||
hide_vm_names=self.encryption_checkbox.isChecked())
|
||||
except Exception as ex:
|
||||
print("Exception:", ex)
|
||||
QMessageBox.critical(None,
|
||||
self.tr("Error while preparing backup."),
|
||||
self.tr("ERROR: {0}").format(ex))
|
||||
self.save_settings()
|
||||
backup_summary = self.qvm_collection.qubesd_call(
|
||||
'dom0', 'admin.backup.Info', 'qubes-manager-backup')
|
||||
|
||||
self.textEdit.setReadOnly(True)
|
||||
self.textEdit.setFontFamily("Monospace")
|
||||
self.textEdit.setText("\n".join(self.func_output))
|
||||
self.save_settings()
|
||||
self.textEdit.setText(backup_summary.decode())
|
||||
|
||||
elif self.currentPage() is self.commit_page:
|
||||
self.button(self.FinishButton).setDisabled(True)
|
||||
@ -370,11 +302,11 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
and str(self.dir_line_edit.text())
|
||||
.count("media/") > 0)
|
||||
self.thread_monitor = ThreadMonitor()
|
||||
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
|
||||
thread = threading.Thread(target=self.__do_backup__,
|
||||
args=(self.thread_monitor,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
counter = 0
|
||||
while not self.thread_monitor.is_finished():
|
||||
self.app.processEvents()
|
||||
time.sleep(0.1)
|
||||
@ -383,14 +315,18 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
if self.canceled:
|
||||
self.progress_status.setText(self.tr("Backup aborted."))
|
||||
if self.tmpdir_to_remove:
|
||||
if QMessageBox.warning(None, self.tr("Backup aborted"),
|
||||
self.tr("Do you want to remove temporary files from "
|
||||
"%s?") % self.tmpdir_to_remove,
|
||||
QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
|
||||
if QMessageBox.warning(
|
||||
None, self.tr("Backup aborted"),
|
||||
self.tr(
|
||||
"Do you want to remove temporary files "
|
||||
"from %s?") % self.tmpdir_to_remove,
|
||||
QMessageBox.Yes, QMessageBox.No) == \
|
||||
QMessageBox.Yes:
|
||||
shutil.rmtree(self.tmpdir_to_remove)
|
||||
else:
|
||||
self.progress_status.setText(self.tr("Backup error."))
|
||||
QMessageBox.warning(self, self.tr("Backup error!"),
|
||||
QMessageBox.warning(
|
||||
self, self.tr("Backup error!"),
|
||||
self.tr("ERROR: {}").format(
|
||||
self.thread_monitor.error_msg))
|
||||
else:
|
||||
@ -402,9 +338,9 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
orig_text + self.tr(
|
||||
" Please unmount your backup volume and cancel "
|
||||
"the file selection dialog."))
|
||||
if self.target_appvm:
|
||||
self.target_appvm.run("QUBESRPC %s dom0" % "qubes"
|
||||
".SelectDirectory")
|
||||
if self.target_appvm: # FIXME I'm not sure if this works
|
||||
self.target_appvm.run(
|
||||
"QUBESRPC %s dom0" % "qubes.SelectDirectory")
|
||||
self.button(self.CancelButton).setEnabled(False)
|
||||
self.button(self.FinishButton).setEnabled(True)
|
||||
self.showFileDialog.setEnabled(False)
|
||||
@ -414,8 +350,9 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
# cancell clicked while the backup is in progress.
|
||||
# calling kill on tar.
|
||||
if self.currentPage() is self.commit_page:
|
||||
if backup.backup_cancel():
|
||||
self.button(self.CancelButton).setDisabled(True)
|
||||
pass # TODO: this does nothing
|
||||
# if backup.backup_cancel():
|
||||
# self.button(self.CancelButton).setDisabled(True)
|
||||
else:
|
||||
self.done(0)
|
||||
|
||||
@ -425,7 +362,8 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
def has_selected_dir_and_pass(self):
|
||||
if not len(self.passphrase_line_edit.text()):
|
||||
return False
|
||||
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
|
||||
if self.passphrase_line_edit.text() != \
|
||||
self.passphrase_line_edit_verify.text():
|
||||
return False
|
||||
return len(self.dir_line_edit.text()) > 0
|
||||
|
||||
@ -437,49 +375,37 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
|
||||
|
||||
def handle_exception(exc_type, exc_value, exc_traceback):
|
||||
import sys
|
||||
import os.path
|
||||
import traceback
|
||||
|
||||
filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
|
||||
filename = os.path.basename(filename)
|
||||
error = "%s: %s" % (exc_type.__name__, exc_value)
|
||||
|
||||
QMessageBox.critical(None, "Houston, we have a problem...",
|
||||
QtGui.QMessageBox.critical(
|
||||
None,
|
||||
"Houston, we have a problem...",
|
||||
"Whoops. A critical error has occured. This is most likely a bug "
|
||||
"in Qubes Restore VMs application.<br><br>"
|
||||
"<b><i>%s</i></b>" % error +
|
||||
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
|
||||
"in Qubes Global Settings application.<br><br><b><i>%s</i></b>" %
|
||||
error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
|
||||
% (line, filename))
|
||||
|
||||
|
||||
def app_main():
|
||||
def main():
|
||||
|
||||
global qubes_host
|
||||
qubes_host = QubesHost()
|
||||
|
||||
global app
|
||||
app = QApplication(sys.argv)
|
||||
app.setOrganizationName("The Qubes Project")
|
||||
app.setOrganizationDomain("http://qubes-os.org")
|
||||
app.setApplicationName("Qubes Backup VMs")
|
||||
qtapp = QtGui.QApplication(sys.argv)
|
||||
qtapp.setOrganizationName("The Qubes Project")
|
||||
qtapp.setOrganizationDomain("http://qubes-os.org")
|
||||
qtapp.setApplicationName("Qubes Backup VMs")
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
qvm_collection = QubesVmCollection()
|
||||
qvm_collection.lock_db_for_reading()
|
||||
qvm_collection.load()
|
||||
qvm_collection.unlock_db()
|
||||
app = Qubes()
|
||||
|
||||
global backup_window
|
||||
backup_window = BackupVMsWindow()
|
||||
backup_window = BackupVMsWindow(qtapp, app)
|
||||
|
||||
backup_window.show()
|
||||
|
||||
app.exec_()
|
||||
app.exit()
|
||||
|
||||
qtapp.exec_()
|
||||
qtapp.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app_main()
|
||||
main()
|
||||
|
@ -20,90 +20,97 @@
|
||||
#
|
||||
#
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtGui import *
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from .thread_monitor import *
|
||||
from . import utils
|
||||
import yaml
|
||||
|
||||
path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*")
|
||||
path_max_len = 512
|
||||
|
||||
# TODO: replace it with a more dynamic approach: allowing user to turn on or off
|
||||
# profile saving
|
||||
backup_profile_path = '/etc/qubes/backup/qubes-manager-backup.conf'
|
||||
|
||||
|
||||
def fill_appvms_list(dialog):
|
||||
dialog.appvm_combobox.clear()
|
||||
dialog.appvm_combobox.addItem("dom0")
|
||||
|
||||
dialog.appvm_combobox.setCurrentIndex(0) # current selected is null ""
|
||||
|
||||
for vm in dialog.qvm_collection.values():
|
||||
if vm.is_appvm() and vm.internal:
|
||||
for vm in dialog.qvm_collection.domains:
|
||||
if vm.klass == 'AppVM' and vm.features.get('internal', False):
|
||||
continue
|
||||
if vm.is_template() and vm.installed_by_rpm:
|
||||
if vm.klass == 'TemplateVM' and vm.installed_by_rpm:
|
||||
continue
|
||||
|
||||
if vm.is_running() and vm.qid != 0:
|
||||
# TODO: is the is_running criterion really necessary? It's designed to
|
||||
# avoid backuping a VM into itself or surprising the user with starting
|
||||
# a VM when they didn't plan to.
|
||||
# TODO: remove debug
|
||||
debug = True
|
||||
if (debug or vm.is_running()) and vm.qid != 0:
|
||||
dialog.appvm_combobox.addItem(vm.name)
|
||||
|
||||
|
||||
def enable_dir_line_edit(dialog, boolean):
|
||||
dialog.dir_line_edit.setEnabled(boolean)
|
||||
dialog.select_path_button.setEnabled(boolean)
|
||||
|
||||
def get_path_for_vm(vm, service_name):
|
||||
if not vm:
|
||||
return None
|
||||
proc = vm.run("QUBESRPC %s dom0" % service_name, passio_popen=True)
|
||||
proc.stdin.close()
|
||||
untrusted_path = proc.stdout.readline(path_max_len)
|
||||
if len(untrusted_path) == 0:
|
||||
return None
|
||||
if path_re.match(untrusted_path):
|
||||
assert '../' not in untrusted_path
|
||||
assert '\0' not in untrusted_path
|
||||
return untrusted_path.strip()
|
||||
else:
|
||||
return None
|
||||
|
||||
def select_path_button_clicked(dialog, select_file=False):
|
||||
backup_location = str(dialog.dir_line_edit.text())
|
||||
file_dialog = QFileDialog()
|
||||
file_dialog.setReadOnly(True)
|
||||
|
||||
if select_file:
|
||||
file_dialog_function = file_dialog.getOpenFileName
|
||||
else:
|
||||
file_dialog_function = file_dialog.getExistingDirectory
|
||||
|
||||
new_appvm = None
|
||||
new_path = None
|
||||
if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
||||
# TODO: check if dom0 is available
|
||||
|
||||
new_appvm = str(dialog.appvm_combobox.currentText())
|
||||
vm = dialog.qvm_collection.get_vm_by_name(new_appvm)
|
||||
if vm:
|
||||
new_path = get_path_for_vm(vm, "qubes.SelectFile" if select_file
|
||||
vm = dialog.qvm_collection.domains[new_appvm]
|
||||
try:
|
||||
new_path = utils.get_path_from_vm(
|
||||
vm,
|
||||
"qubes.SelectFile" if select_file
|
||||
else "qubes.SelectDirectory")
|
||||
else:
|
||||
new_path = file_dialog_function(dialog,
|
||||
dialog.tr("Select backup location."),
|
||||
backup_location if backup_location else '/')
|
||||
except subprocess.CalledProcessError as ex:
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
dialog.tr("Nothing selected!"),
|
||||
dialog.tr("No file or directory selected."))
|
||||
|
||||
if new_path != None:
|
||||
if os.path.basename(new_path) == 'qubes.xml':
|
||||
backup_location = os.path.dirname(new_path)
|
||||
else:
|
||||
backup_location = new_path
|
||||
dialog.dir_line_edit.setText(backup_location)
|
||||
# TODO: check if this works for restore
|
||||
if new_path:
|
||||
dialog.dir_line_edit.setText(new_path)
|
||||
|
||||
if (new_path or new_appvm) and len(backup_location) > 0:
|
||||
if new_path and len(backup_location) > 0:
|
||||
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
def simulate_long_lasting_proces(period, progress_callback):
|
||||
for i in range(period):
|
||||
progress_callback((i*100)/period)
|
||||
time.sleep(1)
|
||||
|
||||
progress_callback(100)
|
||||
return 0
|
||||
def load_backup_profile():
|
||||
with open(backup_profile_path) as profile_file:
|
||||
profile_data = yaml.safe_load(profile_file)
|
||||
return profile_data
|
||||
|
||||
|
||||
def write_backup_profile(args):
|
||||
'''Format limited backup profile (for GUI purposes and print it to
|
||||
*output_stream* (a file or stdout)
|
||||
|
||||
:param output_stream: file-like object ro print the profile to
|
||||
:param args: dictionary with arguments
|
||||
:param passphrase: passphrase to use
|
||||
'''
|
||||
|
||||
acceptable_fields = ['include', 'passphrase_text', 'compression',
|
||||
'destination_vm', 'destination_path']
|
||||
|
||||
profile_data = {key: value for key, value in args.items()
|
||||
if key in acceptable_fields}
|
||||
|
||||
# TODO add compression parameter to GUI issue#943
|
||||
with open(backup_profile_path, 'w') as profile_file:
|
||||
yaml.safe_dump(profile_data, profile_file)
|
||||
|
@ -26,23 +26,15 @@ import os
|
||||
import shutil
|
||||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtGui import *
|
||||
from .thread_monitor import *
|
||||
import time
|
||||
import os.path
|
||||
import traceback
|
||||
|
||||
from qubes.qubes import QubesVmCollection
|
||||
from qubes.qubes import QubesException
|
||||
from qubes.qubes import QubesDaemonPidfile
|
||||
from qubes.qubes import QubesHost
|
||||
from qubes.qubes import qubes_base_dir
|
||||
import qubesmanager.resources_rc
|
||||
import signal
|
||||
|
||||
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
|
||||
|
||||
import time
|
||||
from operator import itemgetter
|
||||
from .thread_monitor import *
|
||||
|
||||
from qubes import backup
|
||||
from qubes import qubesutils
|
||||
|
||||
from .ui_restoredlg import *
|
||||
from .multiselectwidget import *
|
||||
@ -50,17 +42,20 @@ from .multiselectwidget import *
|
||||
from .backup_utils import *
|
||||
from multiprocessing import Queue, Event
|
||||
from multiprocessing.queues import Empty
|
||||
from qubesadmin import Qubes, events, exc
|
||||
from qubesadmin import utils as admin_utils
|
||||
from qubesadmin.backup import restore
|
||||
|
||||
|
||||
class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
__pyqtSignals__ = ("restore_progress(int)", "backup_progress(int)")
|
||||
|
||||
def __init__(self, app, qvm_collection, blk_manager, parent=None):
|
||||
def __init__(self, app, qvm_collection, parent=None):
|
||||
super(RestoreVMsWindow, self).__init__(parent)
|
||||
|
||||
self.app = app
|
||||
self.qvm_collection = qvm_collection
|
||||
self.blk_manager = blk_manager
|
||||
|
||||
self.restore_options = None
|
||||
self.vms_to_restore = None
|
||||
@ -72,19 +67,21 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
self.excluded = {}
|
||||
|
||||
self.vm = self.qvm_collection[0]
|
||||
|
||||
assert self.vm != None
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self.select_vms_widget = MultiSelectWidget(self)
|
||||
self.select_vms_layout.insertWidget(1, self.select_vms_widget)
|
||||
|
||||
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
|
||||
self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append)
|
||||
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
|
||||
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
|
||||
self.connect(self,
|
||||
SIGNAL("currentIdChanged(int)"), self.current_page_changed)
|
||||
self.connect(self,
|
||||
SIGNAL("restore_progress(QString)"),
|
||||
self.commit_text_edit.append)
|
||||
self.connect(self,
|
||||
SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
|
||||
self.dir_line_edit.connect(self.dir_line_edit,
|
||||
SIGNAL("textChanged(QString)"),
|
||||
self.backup_location_changed)
|
||||
self.connect(self.verify_only, SIGNAL("stateChanged(int)"),
|
||||
self.on_verify_only_toogled)
|
||||
|
||||
@ -93,10 +90,13 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
self.confirm_page.isComplete = self.all_vms_good
|
||||
# FIXME
|
||||
# this causes to run isComplete() twice, I don't know why
|
||||
self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
|
||||
self.select_vms_page.connect(
|
||||
self.select_vms_widget,
|
||||
SIGNAL("selected_changed()"),
|
||||
SIGNAL("completeChanged()"))
|
||||
|
||||
fill_appvms_list(self)
|
||||
self.__init_restore_options__()
|
||||
# self.__init_restore_options__()
|
||||
|
||||
@pyqtSlot(name='on_select_path_button_clicked')
|
||||
def select_path_button_clicked(self):
|
||||
@ -125,42 +125,41 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
self.select_vms_widget.selected_list.clear()
|
||||
self.select_vms_widget.available_list.clear()
|
||||
|
||||
self.target_appvm = None
|
||||
self.target_appvm = None # TODO: what is the purpose of this
|
||||
if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen
|
||||
self.target_appvm = self.qvm_collection.get_vm_by_name(
|
||||
str(self.appvm_combobox.currentText()))
|
||||
self.target_appvm = self.qvm_collection.domains[
|
||||
str(self.appvm_combobox.currentText())]
|
||||
|
||||
try:
|
||||
self.vms_to_restore = backup.backup_restore_prepare(
|
||||
self.backup_restore = restore.BackupRestore(
|
||||
self.qvm_collection,
|
||||
self.dir_line_edit.text(),
|
||||
self.passphrase_line_edit.text(),
|
||||
options=self.restore_options,
|
||||
host_collection=self.qvm_collection,
|
||||
encrypted=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm)
|
||||
self.target_appvm,
|
||||
self.passphrase_line_edit.text()
|
||||
)
|
||||
|
||||
# TODO: change text of ignore missing to ignore
|
||||
# missing templates and netvms
|
||||
if self.ignore_missing.isChecked():
|
||||
self.backup_restore.options.use_default_template = True
|
||||
self.backup_restore.options.use_default_netvm = True
|
||||
|
||||
if self.ignore_uname_mismatch.isChecked():
|
||||
self.backup_restore.options.ignore_username_mismatch = True
|
||||
|
||||
if self.verify_only.isChecked():
|
||||
self.backup_restore.options.verify_only = True
|
||||
|
||||
self.vms_to_restore = self.backup_restore.get_restore_info()
|
||||
|
||||
for vmname in self.vms_to_restore:
|
||||
if vmname.startswith('$'):
|
||||
# Internal info
|
||||
continue
|
||||
self.select_vms_widget.available_list.addItem(vmname)
|
||||
except QubesException as ex:
|
||||
except exc.QubesException as ex:
|
||||
QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
|
||||
|
||||
def __init_restore_options__(self):
|
||||
if not self.restore_options:
|
||||
self.restore_options = {}
|
||||
backup.backup_restore_set_defaults(self.restore_options)
|
||||
|
||||
if 'use-default-template' in self.restore_options and 'use-default-netvm' in self.restore_options:
|
||||
val = self.restore_options['use-default-template'] and self.restore_options['use-default-netvm']
|
||||
self.ignore_missing.setChecked(val)
|
||||
else:
|
||||
self.ignore_missing.setChecked(False)
|
||||
|
||||
if 'ignore-username-mismatch' in self.restore_options:
|
||||
self.ignore_uname_mismatch.setChecked(self.restore_options['ignore-username-mismatch'])
|
||||
|
||||
def gather_output(self, s):
|
||||
self.func_output.append(s)
|
||||
|
||||
@ -178,25 +177,20 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
def __do_restore__(self, thread_monitor):
|
||||
err_msg = []
|
||||
self.qvm_collection.lock_db_for_writing()
|
||||
try:
|
||||
backup.backup_restore_do(self.vms_to_restore,
|
||||
self.qvm_collection,
|
||||
print_callback=self.restore_output,
|
||||
error_callback=self.restore_error_output,
|
||||
progress_callback=self.update_progress_bar)
|
||||
self.backup_restore.progress_callback = self.update_progress_bar
|
||||
self.backup_restore.restore_do(self.vms_to_restore)
|
||||
|
||||
except backup.BackupCanceledError as ex:
|
||||
self.canceled = True
|
||||
self.tmpdir_to_remove = ex.tmpdir
|
||||
err_msg.append(str(ex))
|
||||
except Exception as ex:
|
||||
print ("Exception:", ex)
|
||||
err_msg.append(str(ex))
|
||||
err_msg.append(
|
||||
self.tr("Partially restored files left in "
|
||||
"/var/tmp/restore_*, investigate them and/or clean them up"))
|
||||
self.tr("Partially restored files left in /var/tmp/restore_*, "
|
||||
"investigate them and/or clean them up"))
|
||||
|
||||
self.qvm_collection.unlock_db()
|
||||
if self.canceled:
|
||||
self.emit(SIGNAL("restore_progress(QString)"),
|
||||
'<b><font color="red">{0}</font></b>'
|
||||
@ -230,13 +224,15 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
del self.vms_to_restore[str(vmname)]
|
||||
|
||||
del self.func_output[:]
|
||||
self.vms_to_restore = backup.restore_info_verify(self.vms_to_restore,
|
||||
self.qvm_collection)
|
||||
backup.backup_restore_print_summary(
|
||||
self.vms_to_restore, print_callback = self.gather_output)
|
||||
# TODO: am I ignoring changes made by user?
|
||||
self.vms_to_restore = self.backup_restore.restore_info_verify(
|
||||
self.backup_restore.get_restore_info())
|
||||
self.func_output = self.backup_restore.get_restore_summary(
|
||||
self.backup_restore.get_restore_info()
|
||||
)
|
||||
self.confirm_text_edit.setReadOnly(True)
|
||||
self.confirm_text_edit.setFontFamily("Monospace")
|
||||
self.confirm_text_edit.setText("\n".join(self.func_output))
|
||||
self.confirm_text_edit.setText(self.func_output)
|
||||
|
||||
self.confirm_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
@ -275,13 +271,13 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
self.tr("Backup error!"), self.tr("ERROR: {0}")
|
||||
.format(self.thread_monitor.error_msg))
|
||||
|
||||
if self.showFileDialog.isChecked():
|
||||
if self.showFileDialog.isChecked(): # TODO: this is not working
|
||||
self.emit(SIGNAL("restore_progress(QString)"),
|
||||
'<b><font color="black">{0}</font></b>'.format(
|
||||
self.tr(
|
||||
"Please unmount your backup volume and cancel"
|
||||
" the file selection dialog.")))
|
||||
if self.target_appvm:
|
||||
if self.target_appvm: # TODO does this work at all?
|
||||
self.target_appvm.run("QUBESRPC %s dom0" %
|
||||
"qubes.SelectDirectory")
|
||||
else:
|
||||
@ -298,16 +294,16 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
signal.signal(signal.SIGCHLD, old_sigchld_handler)
|
||||
|
||||
def all_vms_good(self):
|
||||
for vminfo in self.vms_to_restore.values():
|
||||
if not vminfo.has_key('vm'):
|
||||
for vm_info in self.vms_to_restore.values():
|
||||
if not vm_info.vm:
|
||||
continue
|
||||
if not vminfo['good-to-go']:
|
||||
if not vm_info.good_to_go:
|
||||
return False
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
def reject(self): # TODO: probably not working too
|
||||
if self.currentPage() is self.commit_page:
|
||||
if backup.backup_cancel():
|
||||
if self.backup_restore.canceled:
|
||||
self.emit(SIGNAL("restore_progress(QString)"),
|
||||
'<font color="red">{0}</font>'
|
||||
.format(self.tr("Aborting the operation...")))
|
||||
@ -339,50 +335,37 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
|
||||
|
||||
def handle_exception(exc_type, exc_value, exc_traceback):
|
||||
import sys
|
||||
import os.path
|
||||
import traceback
|
||||
|
||||
filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
|
||||
filename = os.path.basename(filename)
|
||||
error = "%s: %s" % (exc_type.__name__, exc_value)
|
||||
|
||||
QMessageBox.critical(None, "Houston, we have a problem...",
|
||||
"Whoops. A critical error has occured. This is most likely a bug "
|
||||
"Whoops. A critical error has occured. "
|
||||
"This is most likely a bug "
|
||||
"in Qubes Restore VMs application.<br><br>"
|
||||
"<b><i>%s</i></b>" % error +
|
||||
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
|
||||
% (line, filename))
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
global qubes_host
|
||||
qubes_host = QubesHost()
|
||||
|
||||
global app
|
||||
app = QApplication(sys.argv)
|
||||
app.setOrganizationName("The Qubes Project")
|
||||
app.setOrganizationDomain("http://qubes-os.org")
|
||||
app.setApplicationName("Qubes Restore VMs")
|
||||
qtapp = QApplication(sys.argv)
|
||||
qtapp.setOrganizationName("The Qubes Project")
|
||||
qtapp.setOrganizationDomain("http://qubes-os.org")
|
||||
qtapp.setApplicationName("Qubes Restore VMs")
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
qvm_collection = QubesVmCollection()
|
||||
qvm_collection.lock_db_for_reading()
|
||||
qvm_collection.load()
|
||||
qvm_collection.unlock_db()
|
||||
app = Qubes()
|
||||
|
||||
global restore_window
|
||||
restore_window = RestoreVMsWindow()
|
||||
restore_window = RestoreVMsWindow(qtapp, app)
|
||||
|
||||
restore_window.show()
|
||||
|
||||
app.exec_()
|
||||
app.exit()
|
||||
|
||||
qtapp.exec_()
|
||||
qtapp.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -60,6 +60,8 @@ rm -rf $RPM_BUILD_ROOT
|
||||
/usr/bin/qubes-vm-settings
|
||||
/usr/bin/qubes-vm-create
|
||||
/usr/bin/qubes-vm-boot-from-device
|
||||
/usr/bin/qubes-backup
|
||||
/usr/bin/qubes-backup-restore
|
||||
/usr/libexec/qubes-manager/mount_for_backup.sh
|
||||
/usr/libexec/qubes-manager/qvm_about.sh
|
||||
|
||||
|
4
setup.py
4
setup.py
@ -21,6 +21,8 @@ if __name__ == '__main__':
|
||||
'qubes-global-settings = qubesmanager.global_settings:main',
|
||||
'qubes-vm-settings = qubesmanager.settings:main',
|
||||
'qubes-vm-create = qubesmanager.create_new_vm:main',
|
||||
'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main'
|
||||
'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main',
|
||||
'qubes-backup = qubesmanager.backup:main',
|
||||
'qubes-backup-restore = qubesmanager.restore:main'
|
||||
],
|
||||
})
|
||||
|
120
ui/backupdlg.ui
120
ui/backupdlg.ui
@ -23,73 +23,8 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="shutdown_running_vms_button">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shutdown all running selected VMs</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources.qrc">
|
||||
<normaloff>:/shutdownvm.png</normaloff>:/shutdownvm.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="refresh_button">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh running states.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="running_vms_warning">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<italic>true</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color:rgb(255, 0, 0)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Some of the selected VMs are running (red). Running VMs cannot be backed up!</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
<weight>50</weight>
|
||||
<italic>false</italic>
|
||||
<bold>false</bold>
|
||||
<underline>false</underline>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select VMs to backup:</string>
|
||||
</property>
|
||||
@ -145,6 +80,57 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="unrecognized_config_label">
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>139</red>
|
||||
<green>142</green>
|
||||
<blue>142</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<italic>true</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Warning: unrecognized data found in configuration files. </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="select_dir_page">
|
||||
@ -281,8 +267,8 @@
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Loading…
Reference in New Issue
Block a user