Merge branch 'devel-backup'
This commit is contained in:
commit
0c0b625a70
@ -1,3 +1,5 @@
|
||||
[run]
|
||||
source = qubesadmin
|
||||
omit = qubesadmin/tests/*
|
||||
# breaks backup tests for unknown reason
|
||||
# concurrency=multiprocessing
|
||||
|
@ -8,6 +8,7 @@ disable=
|
||||
bad-continuation,
|
||||
duplicate-code,
|
||||
fixme,
|
||||
cyclic-import,
|
||||
locally-disabled,
|
||||
locally-enabled
|
||||
|
||||
|
@ -5,3 +5,5 @@ pylint
|
||||
sphinx
|
||||
codecov
|
||||
python-daemon
|
||||
mock
|
||||
lxml
|
||||
|
@ -53,10 +53,6 @@ Options
|
||||
|
||||
Restore VMs that are already present on the host under different names
|
||||
|
||||
.. option:: --force-root
|
||||
|
||||
Force to run, even with root privileges
|
||||
|
||||
.. option:: --replace-template=REPLACE_TEMPLATE
|
||||
|
||||
Restore VMs using another template, syntax:
|
||||
|
@ -344,6 +344,8 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
||||
raise
|
||||
|
||||
for tag in src_vm.tags:
|
||||
if tag.startswith('created-by-'):
|
||||
continue
|
||||
try:
|
||||
dst_vm.tags.add(tag)
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
@ -443,11 +445,12 @@ class QubesLocal(QubesBase):
|
||||
if not os.path.exists(method_path):
|
||||
raise qubesadmin.exc.QubesDaemonCommunicationError(
|
||||
'{} not found'.format(method_path))
|
||||
qrexec_call_env = os.environ.copy()
|
||||
qrexec_call_env['QREXEC_REMOTE_DOMAIN'] = 'dom0'
|
||||
qrexec_call_env['QREXEC_REQUESTED_TARGET'] = dest
|
||||
proc = subprocess.Popen([method_path, arg], stdin=payload_stream,
|
||||
stdout=subprocess.PIPE, env=qrexec_call_env)
|
||||
command = ['env', 'QREXEC_REMOTE_DOMAIN=dom0',
|
||||
'QREXEC_REQUESTED_TARGET=' + dest, method_path, arg]
|
||||
if os.getuid() != 0:
|
||||
command.insert(0, 'sudo')
|
||||
proc = subprocess.Popen(command, stdin=payload_stream,
|
||||
stdout=subprocess.PIPE)
|
||||
payload_stream.close()
|
||||
(return_data, _) = proc.communicate()
|
||||
return self._parse_qubesd_response(return_data)
|
||||
|
1956
qubesadmin/backup/__init__.py
Normal file
1956
qubesadmin/backup/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
387
qubesadmin/backup/core2.py
Normal file
387
qubesadmin/backup/core2.py
Normal file
@ -0,0 +1,387 @@
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
'''Parser for core2 qubes.xml'''
|
||||
|
||||
import ast
|
||||
import xml.parsers
|
||||
import logging
|
||||
import lxml.etree
|
||||
|
||||
from qubesadmin.firewall import Rule, Action, Proto, DstHost, SpecialTarget
|
||||
import qubesadmin.backup
|
||||
|
||||
service_to_feature = {
|
||||
'ntpd': 'service.ntpd',
|
||||
'qubes-update-check': 'check-updates',
|
||||
'meminfo-writer': 'services.meminfo-writer',
|
||||
}
|
||||
|
||||
class Core2VM(qubesadmin.backup.BackupVM):
|
||||
'''VM object'''
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self):
|
||||
super(Core2VM, self).__init__()
|
||||
self.backup_content = False
|
||||
|
||||
@property
|
||||
def included_in_backup(self):
|
||||
return self.backup_content
|
||||
|
||||
@staticmethod
|
||||
def rule_from_xml_v1(node, action):
|
||||
'''Parse single rule in old XML format (pre Qubes 4.0)
|
||||
|
||||
:param node: XML node for the rule
|
||||
:param action: action to apply (in old format it wasn't part of the
|
||||
rule itself)
|
||||
'''
|
||||
netmask = node.get('netmask')
|
||||
if netmask is None:
|
||||
netmask = 32
|
||||
else:
|
||||
netmask = int(netmask)
|
||||
address = node.get('address')
|
||||
if address:
|
||||
dsthost = DstHost(address, netmask)
|
||||
else:
|
||||
dsthost = None
|
||||
|
||||
proto = node.get('proto')
|
||||
|
||||
port = node.get('port')
|
||||
toport = node.get('toport')
|
||||
if port and toport:
|
||||
dstports = port + '-' + toport
|
||||
elif port:
|
||||
dstports = port
|
||||
else:
|
||||
dstports = None
|
||||
|
||||
# backward compatibility: protocol defaults to TCP if port is specified
|
||||
if dstports and not proto:
|
||||
proto = 'tcp'
|
||||
|
||||
if proto == 'any':
|
||||
proto = None
|
||||
|
||||
expire = node.get('expire')
|
||||
|
||||
kwargs = {
|
||||
'action': action,
|
||||
}
|
||||
if dsthost:
|
||||
kwargs['dsthost'] = dsthost
|
||||
if dstports:
|
||||
kwargs['dstports'] = dstports
|
||||
if proto:
|
||||
kwargs['proto'] = proto
|
||||
if expire:
|
||||
kwargs['expire'] = expire
|
||||
|
||||
return Rule(None, **kwargs)
|
||||
|
||||
|
||||
def handle_firewall_xml(self, vm, stream):
|
||||
'''Load old (Qubes < 4.0) firewall XML format'''
|
||||
try:
|
||||
tree = lxml.etree.parse(stream) # pylint: disable=no-member
|
||||
xml_root = tree.getroot()
|
||||
policy_v1 = xml_root.get('policy')
|
||||
assert policy_v1 in ('allow', 'deny')
|
||||
default_policy_is_accept = (policy_v1 == 'allow')
|
||||
rules = []
|
||||
|
||||
def _translate_action(key):
|
||||
'''Translate action name'''
|
||||
if xml_root.get(key, policy_v1) == 'allow':
|
||||
return Action.accept
|
||||
return Action.drop
|
||||
|
||||
rules.append(Rule(None,
|
||||
action=_translate_action('dns'),
|
||||
specialtarget=SpecialTarget('dns')))
|
||||
|
||||
rules.append(Rule(None,
|
||||
action=_translate_action('icmp'),
|
||||
proto=Proto.icmp))
|
||||
|
||||
if default_policy_is_accept:
|
||||
rule_action = Action.drop
|
||||
else:
|
||||
rule_action = Action.accept
|
||||
|
||||
for element in xml_root:
|
||||
rule = self.rule_from_xml_v1(element, rule_action)
|
||||
rules.append(rule)
|
||||
if default_policy_is_accept:
|
||||
rules.append(Rule(None, action='accept'))
|
||||
else:
|
||||
rules.append(Rule(None, action='drop'))
|
||||
|
||||
vm.firewall.rules = rules
|
||||
except: # pylint: disable=bare-except
|
||||
vm.log.exception('Failed to set firewall')
|
||||
|
||||
|
||||
class Core2Qubes(qubesadmin.backup.BackupApp):
|
||||
'''Parsed qubes.xml'''
|
||||
def __init__(self, store=None):
|
||||
if store is None:
|
||||
raise ValueError("store path required")
|
||||
self.qid_map = {}
|
||||
self.log = logging.getLogger('qubesadmin.backup.core2')
|
||||
super(Core2Qubes, self).__init__(store)
|
||||
|
||||
def load_globals(self, element):
|
||||
'''Load global settings
|
||||
|
||||
:param element: XML element containing global settings (root node)
|
||||
'''
|
||||
default_netvm = element.get("default_netvm")
|
||||
if default_netvm is not None:
|
||||
self.globals['default_netvm'] = self.qid_map[int(default_netvm)] \
|
||||
if default_netvm != "None" else None
|
||||
|
||||
# default_fw_netvm = element.get("default_fw_netvm")
|
||||
# if default_fw_netvm is not None:
|
||||
# self.globals['default_fw_netvm'] = \
|
||||
# self.qid_map[int(default_fw_netvm)] \
|
||||
# if default_fw_netvm != "None" else None
|
||||
|
||||
updatevm = element.get("updatevm")
|
||||
if updatevm is not None:
|
||||
self.globals['updatevm'] = self.qid_map[int(updatevm)] \
|
||||
if updatevm != "None" else None
|
||||
|
||||
clockvm = element.get("clockvm")
|
||||
if clockvm is not None:
|
||||
self.globals['clockvm'] = self.qid_map[int(clockvm)] \
|
||||
if clockvm != "None" else None
|
||||
|
||||
default_template = element.get("default_template")
|
||||
self.globals['default_template'] = self.qid_map[int(default_template)] \
|
||||
if default_template.lower() != "none" else None
|
||||
|
||||
|
||||
def set_netvm_dependency(self, element):
|
||||
'''Set dependencies between VMs'''
|
||||
kwargs = {}
|
||||
attr_list = ("name", "uses_default_netvm", "netvm_qid")
|
||||
|
||||
for attribute in attr_list:
|
||||
kwargs[attribute] = element.get(attribute)
|
||||
|
||||
vm = self.domains[kwargs["name"]]
|
||||
|
||||
# netvm property
|
||||
if element.get("uses_default_netvm") is None:
|
||||
uses_default_netvm = True
|
||||
else:
|
||||
uses_default_netvm = (
|
||||
True if element.get("uses_default_netvm") == "True" else False)
|
||||
if not uses_default_netvm:
|
||||
netvm_qid = element.get("netvm_qid")
|
||||
if netvm_qid is None or netvm_qid == "none":
|
||||
vm.properties['netvm'] = None
|
||||
else:
|
||||
vm.properties['netvm'] = self.qid_map[int(netvm_qid)]
|
||||
|
||||
# And DispVM netvm, translated to default_dispvm
|
||||
if element.get("uses_default_dispvm_netvm") is None:
|
||||
uses_default_dispvm_netvm = True
|
||||
else:
|
||||
uses_default_dispvm_netvm = (
|
||||
True if element.get("uses_default_dispvm_netvm") == "True"
|
||||
else False)
|
||||
if not uses_default_dispvm_netvm:
|
||||
dispvm_netvm_qid = element.get("dispvm_netvm_qid")
|
||||
if dispvm_netvm_qid is None or dispvm_netvm_qid == "none":
|
||||
dispvm_netvm = None
|
||||
else:
|
||||
dispvm_netvm = self.qid_map[int(dispvm_netvm_qid)]
|
||||
else:
|
||||
dispvm_netvm = vm.properties.get('netvm', self.globals[
|
||||
'default_netvm'])
|
||||
|
||||
if dispvm_netvm != self.globals['default_netvm']:
|
||||
if dispvm_netvm:
|
||||
dispvm_tpl_name = 'disp-{}'.format(dispvm_netvm)
|
||||
else:
|
||||
dispvm_tpl_name = 'disp-no-netvm'
|
||||
|
||||
vm.properties['default_dispvm'] = dispvm_tpl_name
|
||||
|
||||
if dispvm_tpl_name not in self.domains:
|
||||
vm = Core2VM()
|
||||
vm.name = dispvm_tpl_name
|
||||
vm.label = 'red'
|
||||
vm.properties['netvm'] = dispvm_netvm
|
||||
vm.properties['dispvm_allowed'] = True
|
||||
vm.backup_content = True
|
||||
vm.backup_path = None
|
||||
self.domains[vm.name] = vm
|
||||
# TODO: add support for #2075
|
||||
# TODO: set qrexec policy based on dispvm_netvm value
|
||||
|
||||
def import_core2_vm(self, element):
|
||||
'''Parse a single VM from given XML node
|
||||
|
||||
This method load only VM properties not depending on other VMs
|
||||
(other than template). VM connections are set later.
|
||||
:param element: XML node
|
||||
'''
|
||||
vm_class_name = element.tag
|
||||
vm = Core2VM()
|
||||
vm.name = element.get('name')
|
||||
vm.label = element.get('label', 'red')
|
||||
self.domains[vm.name] = vm
|
||||
kwargs = {}
|
||||
if vm_class_name in ["QubesTemplateVm", "QubesTemplateHVm"]:
|
||||
vm.klass = "TemplateVM"
|
||||
elif element.get('template_qid').lower() == "none":
|
||||
kwargs['dir_path'] = element.get('dir_path')
|
||||
vm.klass = "StandaloneVM"
|
||||
else:
|
||||
kwargs['dir_path'] = element.get('dir_path')
|
||||
vm.template = \
|
||||
self.qid_map[int(element.get('template_qid'))]
|
||||
vm.klass = "AppVM"
|
||||
# simple attributes
|
||||
for attr, default in {
|
||||
#'installed_by_rpm': 'False',
|
||||
'include_in_backups': 'True',
|
||||
'qrexec_timeout': '60',
|
||||
'vcpus': '2',
|
||||
'memory': '400',
|
||||
'maxmem': '4000',
|
||||
'default_user': 'user',
|
||||
'debug': 'False',
|
||||
'mac': None,
|
||||
'autostart': 'False'}.items():
|
||||
value = element.get(attr)
|
||||
if value and value != default:
|
||||
vm.properties[attr] = value
|
||||
# attributes with default value
|
||||
for attr in ["kernel", "kernelopts"]:
|
||||
value = element.get(attr)
|
||||
if value and value.lower() == "none":
|
||||
value = None
|
||||
value_is_default = element.get(
|
||||
"uses_default_{}".format(attr))
|
||||
if value_is_default and value_is_default.lower() != \
|
||||
"true":
|
||||
vm.properties[attr] = value
|
||||
vm.properties['virt_mode'] = 'hvm' if "HVm" in vm_class_name else 'pv'
|
||||
if vm_class_name in ('QubesNetVm', 'QubesProxyVm'):
|
||||
vm.properties['provides_network'] = True
|
||||
if vm_class_name == 'QubesNetVm':
|
||||
vm.properties['netvm'] = None
|
||||
if element.get('internal', False) == 'True':
|
||||
vm.features['internal'] = True
|
||||
|
||||
services = element.get('services')
|
||||
if services:
|
||||
services = ast.literal_eval(services)
|
||||
else:
|
||||
services = {}
|
||||
for service, value in services.items():
|
||||
feature = service
|
||||
for repl_service, repl_feature in \
|
||||
service_to_feature.items():
|
||||
if repl_service == service:
|
||||
feature = repl_feature
|
||||
vm.features[feature] = value
|
||||
|
||||
vm.backup_content = element.get('backup_content', False) == 'True'
|
||||
vm.backup_path = element.get('backup_path', None)
|
||||
vm.size = element.get('backup_size', 0)
|
||||
|
||||
pci_strictreset = element.get('pci_strictreset', True)
|
||||
pcidevs = element.get('pcidevs')
|
||||
if pcidevs:
|
||||
pcidevs = ast.literal_eval(pcidevs)
|
||||
for pcidev in pcidevs:
|
||||
if not pci_strictreset:
|
||||
vm.devices['pci'][('dom0', pcidev.replace(':', '_'))] = {
|
||||
'no-strict-reset': True}
|
||||
else:
|
||||
vm.devices['pci'][('dom0', pcidev.replace(':', '_'))] = {}
|
||||
|
||||
def load(self):
|
||||
with open(self.store) as fh:
|
||||
try:
|
||||
# pylint: disable=no-member
|
||||
tree = lxml.etree.parse(fh)
|
||||
except (EnvironmentError, # pylint: disable=broad-except
|
||||
xml.parsers.expat.ExpatError) as err:
|
||||
self.log.error(err)
|
||||
return False
|
||||
|
||||
self.globals['default_kernel'] = tree.getroot().get("default_kernel")
|
||||
|
||||
vm_classes = ["AdminVM", "TemplateVm", "TemplateHVm",
|
||||
"AppVm", "HVm", "NetVm", "ProxyVm"]
|
||||
|
||||
# First build qid->name map
|
||||
for vm_class_name in vm_classes:
|
||||
vms_of_class = tree.findall("Qubes" + vm_class_name)
|
||||
for element in vms_of_class:
|
||||
qid = element.get('qid', None)
|
||||
name = element.get('name', None)
|
||||
if qid and name:
|
||||
self.qid_map[int(qid)] = name
|
||||
|
||||
# Qubes R2 din't have dom0 in qubes.xml
|
||||
if 0 not in self.qid_map:
|
||||
vm = Core2VM()
|
||||
vm.name = 'dom0'
|
||||
vm.klass = 'AdminVM'
|
||||
vm.label = 'black'
|
||||
self.domains['dom0'] = vm
|
||||
self.qid_map[0] = 'dom0'
|
||||
|
||||
# Then load all VMs - since we have qid_map, no need to preserve
|
||||
# specific load older.
|
||||
for vm_class_name in vm_classes:
|
||||
vms_of_class = tree.findall("Qubes" + vm_class_name)
|
||||
for element in vms_of_class:
|
||||
self.import_core2_vm(element)
|
||||
|
||||
# ... and load other VMs
|
||||
for vm_class_name in ["AppVm", "HVm", "NetVm", "ProxyVm"]:
|
||||
vms_of_class = tree.findall("Qubes" + vm_class_name)
|
||||
# first non-template based, then template based
|
||||
sorted_vms_of_class = sorted(vms_of_class,
|
||||
key=lambda x: str(x.get('template_qid')).lower() != "none")
|
||||
for element in sorted_vms_of_class:
|
||||
self.import_core2_vm(element)
|
||||
|
||||
# and load other defaults (default netvm, updatevm etc)
|
||||
self.load_globals(tree.getroot())
|
||||
|
||||
# After importing all VMs, set netvm references, in the same order
|
||||
for vm_class_name in vm_classes:
|
||||
for element in tree.findall("Qubes" + vm_class_name):
|
||||
try:
|
||||
self.set_netvm_dependency(element)
|
||||
except (ValueError, LookupError) as err:
|
||||
self.log.error("VM %s: failed to set netvm dependency: %s",
|
||||
element.get('name'), err)
|
162
qubesadmin/backup/core3.py
Normal file
162
qubesadmin/backup/core3.py
Normal file
@ -0,0 +1,162 @@
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
'''Parser for core2 qubes.xml'''
|
||||
|
||||
import xml.parsers
|
||||
import logging
|
||||
import lxml.etree
|
||||
|
||||
import qubesadmin.backup
|
||||
import qubesadmin.firewall
|
||||
|
||||
class Core3VM(qubesadmin.backup.BackupVM):
|
||||
'''VM object'''
|
||||
# pylint: disable=too-few-public-methods
|
||||
@property
|
||||
def included_in_backup(self):
|
||||
return self.backup_path is not None
|
||||
|
||||
def handle_firewall_xml(self, vm, stream):
|
||||
'''Load new (Qubes >= 4.0) firewall XML format'''
|
||||
try:
|
||||
tree = lxml.etree.parse(stream) # pylint: disable=no-member
|
||||
xml_root = tree.getroot()
|
||||
rules = []
|
||||
for rule_node in xml_root.findall('./rules/rule'):
|
||||
rule_opts = {}
|
||||
for rule_opt in rule_node.findall('./properties/property'):
|
||||
rule_opts[rule_opt.get('name')] = rule_opt.text
|
||||
rules.append(qubesadmin.firewall.Rule(None, **rule_opts))
|
||||
|
||||
vm.firewall.rules = rules
|
||||
except: # pylint: disable=bare-except
|
||||
vm.log.exception('Failed to set firewall')
|
||||
|
||||
class Core3Qubes(qubesadmin.backup.BackupApp):
|
||||
'''Parsed qubes.xml'''
|
||||
def __init__(self, store=None):
|
||||
if store is None:
|
||||
raise ValueError("store path required")
|
||||
self.log = logging.getLogger('qubesadmin.backup.core3')
|
||||
self.labels = {}
|
||||
super(Core3Qubes, self).__init__(store)
|
||||
|
||||
@staticmethod
|
||||
def get_property(xml_obj, prop):
|
||||
'''Get property of given object (XML node)
|
||||
|
||||
Object can be any PropertyHolder serialized to XML - in practice
|
||||
:py:class:`BaseVM` or :py:class:`Qubes`.
|
||||
'''
|
||||
xml_prop = xml_obj.findall('./property[@name=\'{}\']'.format(prop))
|
||||
if not xml_prop:
|
||||
raise KeyError(prop)
|
||||
return xml_prop[0].text
|
||||
|
||||
def load_labels(self, labels_element):
|
||||
'''Load labels table'''
|
||||
for node in labels_element.findall('label'):
|
||||
ident = node.get('id')
|
||||
assert ident is not None
|
||||
self.labels[ident] = node.text
|
||||
|
||||
|
||||
def load_globals(self, globals_element):
|
||||
'''Load global settings
|
||||
|
||||
:param globals_element: XML element containing global settings
|
||||
'''
|
||||
for node in globals_element.findall('property'):
|
||||
name = node.get('name')
|
||||
assert name is not None
|
||||
self.globals[name] = node.text
|
||||
|
||||
def import_core3_vm(self, element):
|
||||
'''Parse a single VM from given XML node
|
||||
|
||||
This method load only VM properties not depending on other VMs
|
||||
(other than template). VM connections are set later.
|
||||
:param element: XML node
|
||||
'''
|
||||
vm = Core3VM()
|
||||
vm.klass = element.get('class')
|
||||
|
||||
for node in element.findall('./properties/property'):
|
||||
name = node.get('name')
|
||||
assert name is not None
|
||||
vm.properties[name] = node.text
|
||||
|
||||
for node in element.findall('./features/feature'):
|
||||
name = node.get('name')
|
||||
assert name is not None
|
||||
vm.features[name] = False if node.text is None else node.text
|
||||
|
||||
for node in element.findall('./tags/tag'):
|
||||
name = node.get('name')
|
||||
assert name is not None
|
||||
vm.tags.add(name)
|
||||
|
||||
for bus_node in element.findall('./devices'):
|
||||
bus_name = bus_node.get('class')
|
||||
assert bus_name is not None
|
||||
for node in bus_node.findall('./device'):
|
||||
backend_domain = node.get('backend-domain')
|
||||
ident = node.get('id')
|
||||
options = {}
|
||||
for opt_node in node.findall('./option'):
|
||||
opt_name = opt_node.get('name')
|
||||
options[opt_name] = opt_node.text
|
||||
vm.devices[bus_name][(backend_domain, ident)] = options
|
||||
|
||||
# extract base properties
|
||||
if vm.klass == 'AdminVM':
|
||||
vm.name = 'dom0'
|
||||
else:
|
||||
vm.name = vm.properties.pop('name')
|
||||
vm.label = self.labels[vm.properties.pop('label')]
|
||||
vm.template = vm.properties.pop('template', None)
|
||||
# skip UUID and qid, will be generated during restore
|
||||
vm.properties.pop('uuid', None)
|
||||
vm.properties.pop('qid', None)
|
||||
|
||||
if vm.features.pop('backup-content', False):
|
||||
vm.backup_path = vm.features.pop('backup-path', None)
|
||||
vm.size = vm.features.pop('backup-size', 0)
|
||||
|
||||
self.domains[vm.name] = vm
|
||||
|
||||
def load(self):
|
||||
with open(self.store) as fh:
|
||||
try:
|
||||
# pylint: disable=no-member
|
||||
tree = lxml.etree.parse(fh)
|
||||
except (EnvironmentError, # pylint: disable=broad-except
|
||||
xml.parsers.expat.ExpatError) as err:
|
||||
self.log.error(err)
|
||||
return False
|
||||
|
||||
self.load_labels(tree.find('./labels'))
|
||||
|
||||
for element in tree.findall('./domains/domain'):
|
||||
self.import_core3_vm(element)
|
||||
|
||||
# and load other defaults (default netvm, updatevm etc)
|
||||
self.load_globals(tree.find('./properties'))
|
@ -144,7 +144,7 @@ class DeviceCollection(object):
|
||||
|
||||
options = device_assignment.options.copy()
|
||||
if device_assignment.persistent:
|
||||
options['persistent'] = 'yes'
|
||||
options['persistent'] = 'True'
|
||||
options_str = ' '.join('{}={}'.format(opt,
|
||||
val) for opt, val in sorted(options.items()))
|
||||
self._vm.qubesd_call(None,
|
||||
|
@ -42,7 +42,7 @@ class Features(object):
|
||||
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Remove', key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not value:
|
||||
if value is False:
|
||||
# False value needs to be serialized as empty string
|
||||
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key, b'')
|
||||
else:
|
||||
|
@ -88,7 +88,10 @@ class DstHost(RuleOption):
|
||||
# add prefix length to bare IP addresses
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, value)
|
||||
self.prefixlen = prefixlen or 128
|
||||
if prefixlen is not None:
|
||||
self.prefixlen = prefixlen
|
||||
else:
|
||||
self.prefixlen = 128
|
||||
if self.prefixlen < 0 or self.prefixlen > 128:
|
||||
raise ValueError(
|
||||
'netmask for IPv6 must be between 0 and 128')
|
||||
@ -100,7 +103,10 @@ class DstHost(RuleOption):
|
||||
if value.count('.') != 3:
|
||||
raise ValueError(
|
||||
'Invalid number of dots in IPv4 address')
|
||||
self.prefixlen = prefixlen or 32
|
||||
if prefixlen is not None:
|
||||
self.prefixlen = prefixlen
|
||||
else:
|
||||
self.prefixlen = 32
|
||||
if self.prefixlen < 0 or self.prefixlen > 32:
|
||||
raise ValueError(
|
||||
'netmask for IPv4 must be between 0 and 32')
|
||||
@ -137,6 +143,10 @@ class DstHost(RuleOption):
|
||||
@property
|
||||
def rule(self):
|
||||
'''API representation of this rule element'''
|
||||
if self.prefixlen == 0 and self.type != 'dsthost':
|
||||
# 0.0.0.0/0 or ::/0, doesn't limit to any particular host,
|
||||
# so skip it
|
||||
return None
|
||||
return self.type + '=' + str(self)
|
||||
|
||||
|
||||
|
279
qubesadmin/tests/backup/__init__.py
Normal file
279
qubesadmin/tests/backup/__init__.py
Normal file
@ -0,0 +1,279 @@
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
import shutil
|
||||
|
||||
import qubesadmin.backup
|
||||
import qubesadmin.exc
|
||||
import qubesadmin.tests
|
||||
|
||||
SIGNATURE_LEN = 512
|
||||
|
||||
class BackupTestCase(qubesadmin.tests.QubesTestCase):
|
||||
class BackupErrorHandler(logging.Handler):
|
||||
def __init__(self, errors_queue, level=logging.NOTSET):
|
||||
super(BackupTestCase.BackupErrorHandler, self).__init__(level)
|
||||
self.errors_queue = errors_queue
|
||||
|
||||
def emit(self, record):
|
||||
self.errors_queue.put(record.getMessage())
|
||||
|
||||
def make_vm_name(self, name):
|
||||
try:
|
||||
return super(BackupTestCase, self).make_vm_name(name)
|
||||
except AttributeError:
|
||||
return 'test-' + name
|
||||
|
||||
def setUp(self):
|
||||
super(BackupTestCase, self).setUp()
|
||||
self.error_detected = multiprocessing.Queue()
|
||||
self.log = logging.getLogger('qubesadmin.tests.backup')
|
||||
self.log.debug("Creating backupvm")
|
||||
|
||||
self.backupdir = os.path.join(os.environ["HOME"], "test-backup")
|
||||
if os.path.exists(self.backupdir):
|
||||
shutil.rmtree(self.backupdir)
|
||||
os.mkdir(self.backupdir)
|
||||
|
||||
self.error_handler = self.BackupErrorHandler(self.error_detected,
|
||||
level=logging.WARNING)
|
||||
backup_log = logging.getLogger('qubesadmin.backup')
|
||||
backup_log.addHandler(self.error_handler)
|
||||
|
||||
def tearDown(self):
|
||||
super(BackupTestCase, self).tearDown()
|
||||
shutil.rmtree(self.backupdir)
|
||||
|
||||
backup_log = logging.getLogger('qubes.backup')
|
||||
backup_log.removeHandler(self.error_handler)
|
||||
|
||||
def fill_image(self, path, size=None, sparse=False, signature=b''):
|
||||
block_size = 4096
|
||||
|
||||
self.log.debug("Filling %s" % path)
|
||||
f = open(path, 'wb+')
|
||||
if size is None:
|
||||
f.seek(0, 2)
|
||||
size = f.tell()
|
||||
f.seek(0)
|
||||
f.write(signature)
|
||||
f.write(b'\0' * (SIGNATURE_LEN - len(signature)))
|
||||
|
||||
for block_num in range(int(size/block_size)):
|
||||
if sparse:
|
||||
f.seek(block_size, 1)
|
||||
f.write(b'a' * block_size)
|
||||
|
||||
f.close()
|
||||
|
||||
# NOTE: this was create_basic_vms
|
||||
def create_backup_vms(self, pool=None):
|
||||
template = self.app.default_template
|
||||
|
||||
vms = []
|
||||
vmname = self.make_vm_name('test-net')
|
||||
self.log.debug("Creating %s" % vmname)
|
||||
testnet = self.app.add_new_vm('AppVM',
|
||||
name=vmname,
|
||||
label='red')
|
||||
testnet.provides_network = True
|
||||
testnet.create_on_disk(pool=pool)
|
||||
testnet.features['services/ntpd'] = True
|
||||
vms.append(testnet)
|
||||
self.fill_image(testnet.storage.export('private'), 20*1024*1024)
|
||||
|
||||
vmname = self.make_vm_name('test1')
|
||||
self.log.debug("Creating %s" % vmname)
|
||||
testvm1 = self.app.add_new_vm('AppVM',
|
||||
name=vmname, template=template, label='red')
|
||||
testvm1.uses_default_netvm = False
|
||||
testvm1.netvm = testnet
|
||||
testvm1.create_on_disk(pool=pool)
|
||||
vms.append(testvm1)
|
||||
self.fill_image(testvm1.storage.export('private'), 100 * 1024 * 1024)
|
||||
|
||||
vmname = self.make_vm_name('testhvm1')
|
||||
self.log.debug("Creating %s" % vmname)
|
||||
testvm2 = self.app.add_new_vm('StandaloneVM',
|
||||
name=vmname,
|
||||
label='red')
|
||||
testvm2.hvm = True
|
||||
testvm2.create_on_disk(pool=pool)
|
||||
self.fill_image(testvm2.storage.export('root'), 1024 * 1024 * 1024, \
|
||||
True)
|
||||
vms.append(testvm2)
|
||||
|
||||
vmname = self.make_vm_name('template')
|
||||
self.log.debug("Creating %s" % vmname)
|
||||
testvm3 = self.app.add_new_vm('TemplateVM',
|
||||
name=vmname, label='red')
|
||||
testvm3.create_on_disk(pool=pool)
|
||||
self.fill_image(testvm3.storage.export('root'), 100 * 1024 * 1024, True)
|
||||
vms.append(testvm3)
|
||||
|
||||
vmname = self.make_vm_name('custom')
|
||||
self.log.debug("Creating %s" % vmname)
|
||||
testvm4 = self.app.add_new_vm('AppVM',
|
||||
name=vmname, template=testvm3, label='red')
|
||||
testvm4.create_on_disk(pool=pool)
|
||||
vms.append(testvm4)
|
||||
|
||||
self.app.save()
|
||||
|
||||
return vms
|
||||
|
||||
def make_backup(self, vms, target=None, expect_failure=False, **kwargs):
|
||||
if target is None:
|
||||
target = self.backupdir
|
||||
try:
|
||||
backup = qubesadmin.backup.Backup(self.app, vms, **kwargs)
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
if not expect_failure:
|
||||
self.fail("QubesException during backup_prepare: %s" % str(e))
|
||||
else:
|
||||
raise
|
||||
|
||||
if 'passphrase' not in kwargs:
|
||||
backup.passphrase = 'qubes'
|
||||
backup.target_dir = target
|
||||
|
||||
try:
|
||||
backup.backup_do()
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
if not expect_failure:
|
||||
self.fail("QubesException during backup_do: %s" % str(e))
|
||||
else:
|
||||
raise
|
||||
|
||||
def restore_backup(self, source=None, appvm=None, options=None,
|
||||
expect_errors=None, manipulate_restore_info=None,
|
||||
passphrase='qubes'):
|
||||
if source is None:
|
||||
backupfile = os.path.join(self.backupdir,
|
||||
sorted(os.listdir(self.backupdir))[-1])
|
||||
else:
|
||||
backupfile = source
|
||||
|
||||
with self.assertNotRaises(qubesadmin.exc.QubesException):
|
||||
restore_op = qubesadmin.backup.BackupRestore(
|
||||
self.app, backupfile, appvm, passphrase)
|
||||
if options:
|
||||
for key, value in options.items():
|
||||
setattr(restore_op.options, key, value)
|
||||
restore_info = restore_op.get_restore_info()
|
||||
if callable(manipulate_restore_info):
|
||||
restore_info = manipulate_restore_info(restore_info)
|
||||
self.log.debug(restore_op.get_restore_summary(restore_info))
|
||||
|
||||
with self.assertNotRaises(qubesadmin.exc.QubesException):
|
||||
restore_op.restore_do(restore_info)
|
||||
|
||||
errors = []
|
||||
if expect_errors is None:
|
||||
expect_errors = []
|
||||
else:
|
||||
self.assertFalse(self.error_detected.empty(),
|
||||
"Restore errors expected, but none detected")
|
||||
while not self.error_detected.empty():
|
||||
current_error = self.error_detected.get()
|
||||
if any(map(current_error.startswith, expect_errors)):
|
||||
continue
|
||||
errors.append(current_error)
|
||||
self.assertTrue(len(errors) == 0,
|
||||
"Error(s) detected during backup_restore_do: %s" %
|
||||
'\n'.join(errors))
|
||||
if not appvm and not os.path.isdir(backupfile):
|
||||
os.unlink(backupfile)
|
||||
|
||||
def create_sparse(self, path, size, signature=b''):
|
||||
f = open(path, "wb")
|
||||
f.write(signature)
|
||||
f.write(b'\0' * (SIGNATURE_LEN - len(signature)))
|
||||
f.truncate(size)
|
||||
f.close()
|
||||
|
||||
def vm_checksum(self, vms):
|
||||
hashes = {}
|
||||
for vm in vms:
|
||||
assert isinstance(vm, qubesadmin.vm.QubesVM)
|
||||
hashes[vm.name] = {}
|
||||
for name, volume in vm.volumes.items():
|
||||
if not volume.rw or not volume.save_on_stop:
|
||||
continue
|
||||
vol_path = vm.storage.get_pool(volume).export(volume)
|
||||
hasher = hashlib.sha1()
|
||||
with open(vol_path, 'rb') as afile:
|
||||
for buf in iter(lambda: afile.read(4096000), b''):
|
||||
hasher.update(buf)
|
||||
hashes[vm.name][name] = hasher.hexdigest()
|
||||
return hashes
|
||||
|
||||
def assertCorrectlyRestored(self, orig_vms, orig_hashes):
|
||||
''' Verify if restored VMs are identical to those before backup.
|
||||
|
||||
:param orig_vms: collection of original QubesVM objects
|
||||
:param orig_hashes: result of :py:meth:`vm_checksum` on original VMs,
|
||||
before backup
|
||||
:return:
|
||||
'''
|
||||
for vm in orig_vms:
|
||||
self.assertIn(vm.name, self.app.domains)
|
||||
restored_vm = self.app.domains[vm.name]
|
||||
for prop in ('name', 'kernel',
|
||||
'memory', 'maxmem', 'kernelopts',
|
||||
'services', 'vcpus', 'features'
|
||||
'include_in_backups', 'default_user', 'qrexec_timeout',
|
||||
'autostart', 'pci_strictreset', 'debug',
|
||||
'internal'):
|
||||
if not hasattr(vm, prop):
|
||||
continue
|
||||
self.assertEqual(
|
||||
getattr(vm, prop), getattr(restored_vm, prop),
|
||||
"VM {} - property {} not properly restored".format(
|
||||
vm.name, prop))
|
||||
for prop in ('netvm', 'template', 'label'):
|
||||
if not hasattr(vm, prop):
|
||||
continue
|
||||
orig_value = getattr(vm, prop)
|
||||
restored_value = getattr(restored_vm, prop)
|
||||
if orig_value and restored_value:
|
||||
self.assertEqual(orig_value.name, restored_value.name,
|
||||
"VM {} - property {} not properly restored".format(
|
||||
vm.name, prop))
|
||||
else:
|
||||
self.assertEqual(orig_value, restored_value,
|
||||
"VM {} - property {} not properly restored".format(
|
||||
vm.name, prop))
|
||||
for dev_class in vm.devices.keys():
|
||||
for dev in vm.devices[dev_class]:
|
||||
self.assertIn(dev, restored_vm.devices[dev_class],
|
||||
"VM {} - {} device not restored".format(
|
||||
vm.name, dev_class))
|
||||
|
||||
if orig_hashes:
|
||||
hashes = self.vm_checksum([restored_vm])[restored_vm.name]
|
||||
self.assertEqual(orig_hashes[vm.name], hashes,
|
||||
"VM {} - disk images are not properly restored".format(
|
||||
vm.name))
|
1647
qubesadmin/tests/backup/backupcompatibility.py
Normal file
1647
qubesadmin/tests/backup/backupcompatibility.py
Normal file
File diff suppressed because one or more lines are too long
7
qubesadmin/tests/backup/v3-firewall.xml
Normal file
7
qubesadmin/tests/backup/v3-firewall.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<QubesFirewallRules dns="allow" icmp="allow" policy="deny" yumProxy="allow">
|
||||
<rule address="0.0.0.0" proto="tcp" netmask="0" port="22"/>
|
||||
<rule address="0.0.0.0" proto="tcp" netmask="0" port="9418"/>
|
||||
<rule address="192.168.0.1" proto="tcp" port="1234"/>
|
||||
<rule address="fedorahosted.org" proto="tcp" port="443"/>
|
||||
<rule address="xenbits.xen.org" proto="tcp" port="80"/>
|
||||
</QubesFirewallRules>
|
18
qubesadmin/tests/backup/v3-qubes.xml
Normal file
18
qubesadmin/tests/backup/v3-qubes.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<QubesVmCollection updatevm="3" default_kernel="3.7.6-2" default_netvm="3" default_fw_netvm="2" default_template="1" clockvm="2">
|
||||
<QubesTemplateVm installed_by_rpm="True" kernel="3.7.6-2" uses_default_kernelopts="True" qid="1" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="fedora-20-x64.conf" label="black" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{ 'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="fedora-20-x64" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/vm-templates/fedora-20-x64"/>
|
||||
<QubesNetVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="2" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="netvm.conf" label="red" template_qid="1" kernelopts="iommu=soft swiotlb=4096" memory="200" default_user="user" volatile_img="volatile.img" services="{'ntpd': False, 'meminfo-writer': False}" maxmem="1535" pcidevs="['02:00.0', '03:00.0']" name="netvm" netid="1" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/netvm"/>
|
||||
<QubesProxyVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="3" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="firewallvm.conf" label="green" template_qid="1" kernelopts="" memory="200" default_user="user" netvm_qid="2" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="firewallvm" netid="2" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/firewallvm"/>
|
||||
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="4" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="True" conf_file="fedora-20-x64-dvm.conf" label="gray" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{ 'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="fedora-20-x64-dvm" private_img="private.img" vcpus="1" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/fedora-20-x64-dvm"/>
|
||||
<QubesAppVm backup_content="True" backup_path="appvms/test-work" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="5" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-work.conf" label="green" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-work" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-work"/>
|
||||
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="6" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="banking.conf" label="green" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="banking" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/banking"/>
|
||||
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="7" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="personal.conf" label="yellow" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="personal" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/personal"/>
|
||||
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="8" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="untrusted.conf" label="red" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="12" uses_default_netvm="False" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="untrusted" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/untrusted"/>
|
||||
<QubesTemplateVm backup_size="104857600" backup_content="True" backup_path="vm-templates/test-template-clone" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="9" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-template-clone.conf" label="green" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-template-clone" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/vm-templates/test-template-clone"/>
|
||||
<QubesAppVm backup_size="104857600" backup_content="True" backup_path="appvms/test-custom-template-appvm" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="10" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-custom-template-appvm.conf" label="yellow" template_qid="9" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-custom-template-appvm" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-custom-template-appvm"/>
|
||||
<QubesAppVm backup_size="104857600" backup_content="True" backup_path="appvms/test-standalonevm" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="11" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-standalonevm.conf" label="blue" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-standalonevm" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-standalonevm"/>
|
||||
<QubesProxyVm backup_size="104857600" backup_content="True" backup_path="servicevms/test-testproxy" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="12" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-testproxy.conf" label="red" template_qid="1" kernelopts="" memory="200" default_user="user" netvm_qid="3" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-testproxy" netid="3" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/test-testproxy"/>
|
||||
<QubesProxyVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="13" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="testproxy2.conf" label="red" template_qid="9" kernelopts="" memory="200" default_user="user" netvm_qid="2" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="testproxy2" netid="4" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/testproxy2"/>
|
||||
<QubesHVm backup_size="104857600" backup_content="True" backup_path="appvms/test-testhvm" installed_by_rpm="False" netvm_qid="none" qid="14" include_in_backups="True" timezone="localtime" qrexec_timeout="60" conf_file="test-testhvm.conf" label="purple" template_qid="none" internal="False" memory="512" uses_default_netvm="True" services="{'meminfo-writer': False}" default_user="user" pcidevs="[]" name="test-testhvm" qrexec_installed="False" private_img="private.img" drive="None" vcpus="2" root_img="root.img" guiagent_installed="False" debug="False" dir_path="/var/lib/qubes/appvms/test-testhvm"/>
|
||||
<QubesDisposableVm dispid="50" firewall_conf="firewall.xml" label="red" name="disp50" netvm_qid="2" qid="15" template_qid="1"/>
|
||||
<QubesNetVm backup_size="104857600" backup_content="True" backup_path="servicevms/test-net" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="16" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-net.conf" label="red" template_qid="1" kernelopts="iommu=soft swiotlb=4096" memory="200" default_user="user" volatile_img="volatile.img" services="{'ntpd': False, 'meminfo-writer': False}" maxmem="1535" pcidevs="['02:00.0', '03:00.0']" name="test-net" netid="2" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/test-net"/>
|
||||
</QubesVmCollection>
|
43
qubesadmin/tests/backup/v4-firewall.xml
Normal file
43
qubesadmin/tests/backup/v4-firewall.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<firewall version="2">
|
||||
<rules>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">accept</property>
|
||||
<property name="specialtarget">dns</property>
|
||||
</properties>
|
||||
</rule>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">accept</property>
|
||||
<property name="proto">icmp</property>
|
||||
</properties>
|
||||
</rule>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">accept</property>
|
||||
<property name="proto">tcp</property>
|
||||
<property name="dstports">22</property>
|
||||
</properties>
|
||||
</rule>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">accept</property>
|
||||
<property name="dsthost">www.qubes-os.org</property>
|
||||
<property name="proto">tcp</property>
|
||||
<property name="dstports">443</property>
|
||||
</properties>
|
||||
</rule>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">accept</property>
|
||||
<property name="dsthost">192.168.0.0/24</property>
|
||||
<property name="proto">tcp</property>
|
||||
</properties>
|
||||
</rule>
|
||||
<rule>
|
||||
<properties>
|
||||
<property name="action">drop</property>
|
||||
</properties>
|
||||
</rule>
|
||||
</rules>
|
||||
</firewall>
|
526
qubesadmin/tests/backup/v4-qubes.xml
Normal file
526
qubesadmin/tests/backup/v4-qubes.xml
Normal file
@ -0,0 +1,526 @@
|
||||
<qubes>
|
||||
<labels>
|
||||
<label color="0xcc0000" id="label-1">red</label>
|
||||
<label color="0xf57900" id="label-2">orange</label>
|
||||
<label color="0xedd400" id="label-3">yellow</label>
|
||||
<label color="0x73d216" id="label-4">green</label>
|
||||
<label color="0x555753" id="label-5">gray</label>
|
||||
<label color="0x3465a4" id="label-6">blue</label>
|
||||
<label color="0x75507b" id="label-7">purple</label>
|
||||
<label color="0x000000" id="label-8">black</label>
|
||||
</labels>
|
||||
<pools>
|
||||
<pool driver="lvm_thin" name="lvm" thin_pool="pool3" volume_group="core3"/>
|
||||
<pool dir_path="/var/lib/qubes/vm-kernels" driver="linux-kernel" name="linux-kernel"/>
|
||||
<pool dir_path="/var/lib/qubes" driver="file" name="default" revisions_to_keep="1"/>
|
||||
</pools>
|
||||
<properties>
|
||||
<property name="clockvm">sys-net</property>
|
||||
<property name="default_dispvm">fedora-25-dvm</property>
|
||||
<property name="default_kernel">4.9.31-17</property>
|
||||
<property name="default_netvm">sys-firewall</property>
|
||||
<property name="default_template">fedora-25</property>
|
||||
<property name="updatevm">sys-firewall</property>
|
||||
</properties>
|
||||
<domains>
|
||||
<domain id="domain-20" class="AppVM">
|
||||
<properties>
|
||||
<property name="debug">True</property>
|
||||
<property name="kernel"></property>
|
||||
<property name="label">label-5</property>
|
||||
<property name="name">test-d8test</property>
|
||||
<property name="qid">20</property>
|
||||
<property name="uuid">9204481c-7e37-42a6-8a66-b4cc63d65f11</property>
|
||||
<property name="template">debian-8</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-d8test</feature>
|
||||
<feature name="backup-size">20971520</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags>
|
||||
<tag name="created-by-dom0"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid=""/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/debian-8/root" vid="appvms/test-d8test/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-d8test/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-d8test/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-30" class="AppVM">
|
||||
<properties>
|
||||
<property name="label">label-1</property>
|
||||
<property name="name">test-proxy</property>
|
||||
<property name="qid">30</property>
|
||||
<property name="uuid">367c64e6-ab8c-42df-91b6-c9d4c7d015f2</property>
|
||||
<property name="template">debian-8</property>
|
||||
<property name="provides_network">True</property>
|
||||
<property name="netvm">sys-net</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-proxy</feature>
|
||||
<feature name="backup-size">209715200</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags>
|
||||
<tag name="created-by-dom0"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/debian-8/root" vid="appvms/d8test2/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/d8test2/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/d8test2/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-16" class="TemplateVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
<property name="name">debian-8</property>
|
||||
<property name="qid">16</property>
|
||||
<property name="uuid">0e2fa953-016f-4486-9e36-c9b386fc2bac</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="qrexec">True</feature>
|
||||
<feature name="updates-available"></feature>
|
||||
<feature name="gui">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags>
|
||||
<tag name="created-by-dom0"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/debian-8/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/debian-8/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/debian-8/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-0" class="AdminVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
</properties>
|
||||
<features/>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<devices class="usb"/>
|
||||
<tags/>
|
||||
</domain>
|
||||
<domain id="domain-31" class="AppVM">
|
||||
<properties>
|
||||
<property name="debug">True</property>
|
||||
<property name="kernel"></property>
|
||||
<property name="label">label-3</property>
|
||||
<property name="name">test-custom-template-appvm</property>
|
||||
<property name="qid">31</property>
|
||||
<property name="uuid">1bdb7b32-8a4b-4bb7-8da0-6b06494f642c</property>
|
||||
<property name="template">test-fedora-25-clone</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-custom-template-appvm</feature>
|
||||
<feature name="backup-size">20971520</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<devices class="usb"/>
|
||||
<tags>
|
||||
<tag name="created-by-dom0"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid=""/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-f25test2/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-f25test2/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-f25test2/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-7" class="TemplateVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
<property name="maxmem">4000</property>
|
||||
<property name="name">test-fedora-25-clone</property>
|
||||
<property name="qid">7</property>
|
||||
<property name="uuid">e3b8d458-8c4c-4ccb-b00d-b7ae454cb08f</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer">1</feature>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">vm-templates/test-fedora-25-clone</feature>
|
||||
<feature name="backup-size">2097152000</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/test-fedora-25-clone/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/test-fedora-25-clone/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/test-fedora-25-clone/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-12" class="AppVM">
|
||||
<properties>
|
||||
<property name="dispvm_allowed">True</property>
|
||||
<property name="label">label-1</property>
|
||||
<property name="name">fedora-25-clone-dvm</property>
|
||||
<property name="qid">10</property>
|
||||
<property name="uuid">30daa6b5-693b-460a-9da7-99078a2d9d14</property>
|
||||
<property name="template">test-fedora-25-clone</property>
|
||||
<property name="vcpus">1</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer">1</feature>
|
||||
<feature name="internal">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/test-fedora-25-clone/root" vid="appvms/fedora-25-clone-dvm/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0"
|
||||
rw="True" size="10737418240" vid="appvms/fedora-25-clone-dvm/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/fedora-25-clone-dvm/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-10" class="AppVM">
|
||||
<properties>
|
||||
<property name="dispvm_allowed">True</property>
|
||||
<property name="label">label-1</property>
|
||||
<property name="name">fedora-25-dvm</property>
|
||||
<property name="qid">10</property>
|
||||
<property name="uuid">30daa6b5-693b-460a-9da7-99078a2d9d14</property>
|
||||
<property name="template">fedora-25</property>
|
||||
<property name="vcpus">1</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer">1</feature>
|
||||
<feature name="internal">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/fedora-25-dvm/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/fedora-25-dvm/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/fedora-25-dvm/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-14" class="TemplateVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
<property name="maxmem">4000</property>
|
||||
<property name="name">fedora-25-lvm</property>
|
||||
<property name="qid">14</property>
|
||||
<property name="uuid">20785bf4-42fa-4035-ab95-c1bf054c153a</property>
|
||||
</properties>
|
||||
<features/>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="core3/fedora-25-lvm-root"/>
|
||||
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/fedora-25-lvm-volatile"/>
|
||||
<volume name="private" pool="lvm" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="core3/fedora-25-lvm-private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-8" class="TemplateVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
<property name="name">fedora-25</property>
|
||||
<property name="qid">8</property>
|
||||
<property name="uuid">51870d8e-2e71-41d2-9582-30661f611004</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="qrexec">True</feature>
|
||||
<feature name="updates-available"></feature>
|
||||
<feature name="gui">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags>
|
||||
<tag name="created-by-test-work"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/fedora-25/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/fedora-25/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/fedora-25/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-9" class="StandaloneVM">
|
||||
<properties>
|
||||
<property name="hvm">True</property>
|
||||
<property name="label">label-7</property>
|
||||
<property name="maxmem">4000</property>
|
||||
<property name="name">test-hvm</property>
|
||||
<property name="qid">9</property>
|
||||
<property name="uuid">9909066b-0f03-4725-ad9e-fa3561d5566e</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer"></feature>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-hvm</feature>
|
||||
<feature name="backup-size">2097152000</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" vid="appvms/test-hvm/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-hvm/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-hvm/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-11" class="AppVM">
|
||||
<properties>
|
||||
<property name="backup_timestamp">1474318497</property>
|
||||
<property name="label">label-1</property>
|
||||
<property name="name">untrusted</property>
|
||||
<property name="qid">11</property>
|
||||
<property name="uuid">359b8e38-9e50-46a3-a42c-8d3bb15d3890</property>
|
||||
<property name="default_dispvm">fedora-25-clone-dvm</property>
|
||||
<property name="netvm"></property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/untrusted/root"/>
|
||||
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/untrusted-volatile"/>
|
||||
<volume name="private" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="core3/untrusted-private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-22" class="AppVM">
|
||||
<properties>
|
||||
<property name="label">label-3</property>
|
||||
<property name="name">personal</property>
|
||||
<property name="qid">22</property>
|
||||
<property name="uuid">efa8ddc4-6661-4231-9bfd-ef34907da358</property>
|
||||
<property name="netvm">sys-firewall</property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="xxx">1</feature>
|
||||
<feature name="feat1">1</feature>
|
||||
<feature name="featdis"></feature>
|
||||
<feature name="feat2"></feature>
|
||||
<feature name="feat32">1</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/personal/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/personal/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/personal/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-21" class="AppVM">
|
||||
<properties>
|
||||
<property name="autostart">True</property>
|
||||
<property name="label">label-4</property>
|
||||
<property name="memory">500</property>
|
||||
<property name="name">sys-firewall</property>
|
||||
<property name="provides_network">True</property>
|
||||
<property name="qid">21</property>
|
||||
<property name="uuid">40f0775a-c259-44bc-be57-6148c67c42c7</property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features/>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<devices class="usb"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-firewall/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-firewall/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-firewall/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-2" class="AppVM">
|
||||
<properties>
|
||||
<property name="default_user">user</property>
|
||||
<property name="hvm">False</property>
|
||||
<property name="label">label-1</property>
|
||||
<property name="maxmem">300</property>
|
||||
<property name="memory">300</property>
|
||||
<property name="name">sys-net</property>
|
||||
<property name="netvm"></property>
|
||||
<property name="provides_network">True</property>
|
||||
<property name="kernelopts">nopat i8042.nokbd i8042.noaux</property>
|
||||
<property name="qid">2</property>
|
||||
<property name="uuid">eb8b1680-d4fa-449b-8baa-b5146d9b62b7</property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer"></feature>
|
||||
<feature name="service.clocksync">1</feature>
|
||||
</features>
|
||||
<devices class="pci">
|
||||
<device backend-domain="dom0" id="02_00.0"/>
|
||||
</devices>
|
||||
<devices class="block"/>
|
||||
<devices class="usb"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-net/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-net/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-net/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-6" class="AppVM">
|
||||
<properties>
|
||||
<property name="label">label-1</property>
|
||||
<property name="maxmem">300</property>
|
||||
<property name="name">test-net</property>
|
||||
<property name="netvm"></property>
|
||||
<property name="memory">300</property>
|
||||
<property name="provides_network">True</property>
|
||||
<property name="qid">6</property>
|
||||
<property name="uuid">d5e9a792-9247-4f6f-a936-b7c65a1adfac</property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer"></feature>
|
||||
<feature name="service.ntpd"></feature>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-net</feature>
|
||||
<feature name="backup-size">209715200</feature>
|
||||
</features>
|
||||
<devices class="pci">
|
||||
<device backend-domain="dom0" id="03_00.0"/>
|
||||
</devices>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-net/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-net/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-net/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-5" class="AppVM">
|
||||
<properties>
|
||||
<property name="autostart">True</property>
|
||||
<property name="label">label-1</property>
|
||||
<property name="hvm">False</property>
|
||||
<property name="maxmem">400</property>
|
||||
<property name="name">sys-usb</property>
|
||||
<property name="qid">5</property>
|
||||
<property name="uuid">0da5616e-f2db-4c17-9c0d-cdef2e728344</property>
|
||||
<property name="template">fedora-25</property>
|
||||
<property name="provides_network">True</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.network-manager"></feature>
|
||||
<feature name="service.meminfo-writer"></feature>
|
||||
</features>
|
||||
<devices class="pci">
|
||||
<device backend-domain="dom0" id="00_14.0">
|
||||
<option name="no-strict-reset">True</option>
|
||||
</device>
|
||||
<device backend-domain="dom0" id="00_1d.0">
|
||||
<option name="no-strict-reset">True</option>
|
||||
</device>
|
||||
</devices>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-usb/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-usb/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-usb/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-13" class="AppVM">
|
||||
<properties>
|
||||
<property name="label">label-8</property>
|
||||
<property name="name">vault</property>
|
||||
<property name="netvm"></property>
|
||||
<property name="qid">13</property>
|
||||
<property name="uuid">d5284828-988d-46e2-8388-a09c495475e3</property>
|
||||
<property name="template">fedora-25</property>
|
||||
<property name="hvm">False</property>
|
||||
<property name="maxmem">1536</property>
|
||||
</properties>
|
||||
<features/>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/vault/root"/>
|
||||
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/vault-volatile"/>
|
||||
<volume name="private" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="core3/vault-private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-3" class="AppVM">
|
||||
<properties>
|
||||
<property name="ip">192.168.0.1</property>
|
||||
<property name="label">label-4</property>
|
||||
<property name="maxmem">4000</property>
|
||||
<property name="memory">400</property>
|
||||
<property name="name">test-work</property>
|
||||
<property name="qid">3</property>
|
||||
<property name="uuid">07c17d1e-0982-417d-b19e-a81d51bed423</property>
|
||||
<property name="template">fedora-25</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="service.meminfo-writer">1</feature>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-work</feature>
|
||||
<feature name="backup-size">2097152000</feature>
|
||||
</features>
|
||||
<devices class="pci"/>
|
||||
<devices class="block"/>
|
||||
<tags>
|
||||
<tag name="tag1"/>
|
||||
<tag name="tag2"/>
|
||||
</tags>
|
||||
<volume-config>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
|
||||
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-work/root"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-work/volatile"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-work/private"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
<domain id="domain-4" class="StandaloneVM">
|
||||
<properties>
|
||||
<property name="label">label-6</property>
|
||||
<property name="maxmem">4000</property>
|
||||
<property name="name">test-standalonevm</property>
|
||||
<property name="qid">4</property>
|
||||
<property name="uuid">e8034b8a-29b3-4f02-b8cb-05cd74a4bb68</property>
|
||||
</properties>
|
||||
<features>
|
||||
<feature name="backup-content">True</feature>
|
||||
<feature name="backup-path">appvms/test-standalonevm</feature>
|
||||
<feature name="backup-size">2097152000</feature>
|
||||
</features>
|
||||
<tags/>
|
||||
<volume-config>
|
||||
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="appvms/test-standalonevm/root"/>
|
||||
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-standalonevm/private"/>
|
||||
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-standalonevm/volatile"/>
|
||||
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="None"/>
|
||||
</volume-config>
|
||||
</domain>
|
||||
</domains>
|
||||
</qubes>
|
@ -129,7 +129,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase):
|
||||
def test_022_attach_persistent(self):
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.device.test.Attach', 'test-vm2+dev1',
|
||||
b'persistent=yes')] = b'0\0'
|
||||
b'persistent=True')] = b'0\0'
|
||||
assign = qubesadmin.devices.DeviceAssignment(
|
||||
self.app.domains['test-vm2'], 'dev1')
|
||||
assign.persistent = True
|
||||
@ -139,7 +139,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase):
|
||||
def test_023_attach_persistent_options(self):
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.device.test.Attach', 'test-vm2+dev1',
|
||||
b'persistent=yes ro=True')] = b'0\0'
|
||||
b'persistent=True ro=True')] = b'0\0'
|
||||
assign = qubesadmin.devices.DeviceAssignment(
|
||||
self.app.domains['test-vm2'], 'dev1')
|
||||
assign.persistent = True
|
||||
|
@ -87,7 +87,7 @@ class TC_00_qvm_device(qubesadmin.tests.QubesTestCase):
|
||||
None, None)] = b'0\0'
|
||||
self.app.expected_calls[('test-vm3', 'admin.vm.device.test.List',
|
||||
None, None)] = \
|
||||
b'0\0test-vm1+dev1 persistent=yes\n'
|
||||
b'0\0test-vm1+dev1 persistent=True\n'
|
||||
|
||||
with qubesadmin.tests.tools.StdoutBuffer() as buf:
|
||||
qubesadmin.tools.qvm_device.main(
|
||||
@ -144,7 +144,7 @@ class TC_00_qvm_device(qubesadmin.tests.QubesTestCase):
|
||||
def test_011_attach_persistent(self):
|
||||
''' Test attach action '''
|
||||
self.app.expected_calls[('test-vm2', 'admin.vm.device.test.Attach',
|
||||
'test-vm1+dev1', b'persistent=yes')] = b'0\0'
|
||||
'test-vm1+dev1', b'persistent=True')] = b'0\0'
|
||||
qubesadmin.tools.qvm_device.main(
|
||||
['test', 'attach', '-p', 'test-vm2', 'test-vm1:dev1'],
|
||||
app=self.app)
|
||||
|
264
qubesadmin/tools/qvm_backup_restore.py
Normal file
264
qubesadmin/tools/qvm_backup_restore.py
Normal file
@ -0,0 +1,264 @@
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2016 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
'''Console frontend for backup restore code'''
|
||||
|
||||
import getpass
|
||||
import sys
|
||||
|
||||
import qubesadmin.backup
|
||||
import qubesadmin.exc
|
||||
import qubesadmin.tools
|
||||
import qubesadmin.utils
|
||||
|
||||
parser = qubesadmin.tools.QubesArgumentParser()
|
||||
|
||||
parser.add_argument("--verify-only", action="store_true",
|
||||
dest="verify_only", default=False,
|
||||
help="Verify backup integrity without restoring any "
|
||||
"data")
|
||||
|
||||
parser.add_argument("--skip-broken", action="store_true", dest="skip_broken",
|
||||
default=False,
|
||||
help="Do not restore VMs that have missing TemplateVMs "
|
||||
"or NetVMs")
|
||||
|
||||
parser.add_argument("--ignore-missing", action="store_true",
|
||||
dest="ignore_missing", default=False,
|
||||
help="Restore VMs even if their associated TemplateVMs "
|
||||
"and NetVMs are missing")
|
||||
|
||||
parser.add_argument("--skip-conflicting", action="store_true",
|
||||
dest="skip_conflicting", default=False,
|
||||
help="Do not restore VMs that are already present on "
|
||||
"the host")
|
||||
|
||||
parser.add_argument("--rename-conflicting", action="store_true",
|
||||
dest="rename_conflicting", default=False,
|
||||
help="Restore VMs that are already present on the host "
|
||||
"under different names")
|
||||
|
||||
parser.add_argument("--replace-template", action="append",
|
||||
dest="replace_template", default=[],
|
||||
help="Restore VMs using another TemplateVM; syntax: "
|
||||
"old-template-name:new-template-name (may be "
|
||||
"repeated)")
|
||||
|
||||
parser.add_argument("-x", "--exclude", action="append", dest="exclude",
|
||||
default=[],
|
||||
help="Skip restore of specified VM (may be repeated)")
|
||||
|
||||
parser.add_argument("--skip-dom0-home", action="store_false", dest="dom0_home",
|
||||
default=True,
|
||||
help="Do not restore dom0 user home directory")
|
||||
|
||||
parser.add_argument("--ignore-username-mismatch", action="store_true",
|
||||
dest="ignore_username_mismatch", default=False,
|
||||
help="Ignore dom0 username mismatch when restoring home "
|
||||
"directory")
|
||||
|
||||
parser.add_argument("-d", "--dest-vm", action="store", dest="appvm",
|
||||
help="Specify VM containing the backup to be restored")
|
||||
|
||||
parser.add_argument("-p", "--passphrase-file", action="store",
|
||||
dest="pass_file", default=None,
|
||||
help="Read passphrase from file, or use '-' to read from stdin")
|
||||
|
||||
parser.add_argument('backup_location', action='store',
|
||||
help="Backup directory name, or command to pipe from")
|
||||
|
||||
parser.add_argument('vms', nargs='*', action='store', default='[]',
|
||||
help='Restore only those VMs')
|
||||
|
||||
|
||||
def handle_broken(app, args, restore_info):
|
||||
'''Display information about problems with VMs selected for resetore'''
|
||||
there_are_conflicting_vms = False
|
||||
there_are_missing_templates = False
|
||||
there_are_missing_netvms = False
|
||||
dom0_username_mismatch = False
|
||||
|
||||
for vm_info in restore_info.values():
|
||||
assert isinstance(vm_info, qubesadmin.backup.BackupRestore.VMToRestore)
|
||||
if qubesadmin.backup.BackupRestore.VMToRestore.EXCLUDED in \
|
||||
vm_info.problems:
|
||||
continue
|
||||
if qubesadmin.backup.BackupRestore.VMToRestore.MISSING_TEMPLATE in \
|
||||
vm_info.problems:
|
||||
there_are_missing_templates = True
|
||||
if qubesadmin.backup.BackupRestore.VMToRestore.MISSING_NETVM in \
|
||||
vm_info.problems:
|
||||
there_are_missing_netvms = True
|
||||
if qubesadmin.backup.BackupRestore.VMToRestore.ALREADY_EXISTS in \
|
||||
vm_info.problems:
|
||||
there_are_conflicting_vms = True
|
||||
if qubesadmin.backup.BackupRestore.Dom0ToRestore.USERNAME_MISMATCH in \
|
||||
vm_info.problems:
|
||||
dom0_username_mismatch = True
|
||||
|
||||
|
||||
if there_are_conflicting_vms:
|
||||
app.log.error(
|
||||
"*** There are VMs with conflicting names on the host! ***")
|
||||
if args.skip_conflicting:
|
||||
app.log.error(
|
||||
"Those VMs will not be restored. "
|
||||
"The host VMs will NOT be overwritten.")
|
||||
else:
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"Remove VMs with conflicting names from the host "
|
||||
"before proceeding.\n"
|
||||
"Or use --skip-conflicting to restore only those VMs that "
|
||||
"do not exist on the host.\n"
|
||||
"Or use --rename-conflicting to restore those VMs under "
|
||||
"modified names (with numbers at the end).")
|
||||
|
||||
app.log.info("The above VMs will be copied and added to your system.")
|
||||
app.log.info("Exisiting VMs will NOT be removed.")
|
||||
|
||||
if there_are_missing_templates:
|
||||
app.log.warning("*** One or more TemplateVMs are missing on the "
|
||||
"host! ***")
|
||||
if not (args.skip_broken or args.ignore_missing):
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"Install them before proceeding with the restore."
|
||||
"Or pass: --skip-broken or --ignore-missing.")
|
||||
elif args.skip_broken:
|
||||
app.log.warning("Skipping broken entries: VMs that depend on "
|
||||
"missing TemplateVMs will NOT be restored.")
|
||||
elif args.ignore_missing:
|
||||
app.log.warning("Ignoring missing entries: VMs that depend "
|
||||
"on missing TemplateVMs will NOT be restored.")
|
||||
else:
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"INTERNAL ERROR! Please report this to the Qubes OS team!")
|
||||
|
||||
if there_are_missing_netvms:
|
||||
app.log.warning("*** One or more NetVMs are missing on the "
|
||||
"host! ***")
|
||||
if not (args.skip_broken or args.ignore_missing):
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"Install them before proceeding with the restore."
|
||||
"Or pass: --skip-broken or --ignore-missing.")
|
||||
elif args.skip_broken:
|
||||
app.log.warning("Skipping broken entries: VMs that depend on "
|
||||
"missing NetVMs will NOT be restored.")
|
||||
elif args.ignore_missing:
|
||||
app.log.warning("Ignoring missing entries: VMs that depend "
|
||||
"on missing NetVMs will NOT be restored.")
|
||||
else:
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"INTERNAL ERROR! Please report this to the Qubes OS team!")
|
||||
|
||||
if 'dom0' in restore_info.keys() and args.dom0_home:
|
||||
if dom0_username_mismatch:
|
||||
app.log.warning("*** Dom0 username mismatch! This can break "
|
||||
"some settings! ***")
|
||||
if not args.ignore_username_mismatch:
|
||||
raise qubesadmin.exc.QubesException(
|
||||
"Skip restoring the dom0 home directory "
|
||||
"(--skip-dom0-home), or pass "
|
||||
"--ignore-username-mismatch to continue anyway.")
|
||||
else:
|
||||
app.log.warning("Continuing as directed.")
|
||||
app.log.warning("NOTE: Before restoring the dom0 home directory, "
|
||||
"a new directory named "
|
||||
"'home-pre-restore-<current-time>' will be "
|
||||
"created inside the dom0 home directory. If any "
|
||||
"restored files conflict with existing files, "
|
||||
"the existing files will be moved to this new "
|
||||
"directory.")
|
||||
|
||||
def main(args=None):
|
||||
'''Main function of qvm-backup-restore'''
|
||||
# pylint: disable=too-many-return-statements
|
||||
args = parser.parse_args(args)
|
||||
|
||||
appvm = None
|
||||
if args.appvm:
|
||||
try:
|
||||
appvm = args.app.domains[args.appvm]
|
||||
except KeyError:
|
||||
parser.error('no such domain: {!r}'.format(args.appvm))
|
||||
|
||||
if args.pass_file is not None:
|
||||
pass_f = open(args.pass_file) if args.pass_file != "-" else sys.stdin
|
||||
passphrase = pass_f.readline().rstrip()
|
||||
if pass_f is not sys.stdin:
|
||||
pass_f.close()
|
||||
else:
|
||||
passphrase = getpass.getpass("Please enter the passphrase to verify "
|
||||
"and (if encrypted) decrypt the backup: ")
|
||||
|
||||
args.app.log.info("Checking backup content...")
|
||||
|
||||
try:
|
||||
backup = qubesadmin.backup.BackupRestore(args.app, args.backup_location,
|
||||
appvm, passphrase)
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
parser.error_runtime(str(e))
|
||||
# unreachable - error_runtime will raise SystemExit
|
||||
return 1
|
||||
|
||||
if args.ignore_missing:
|
||||
backup.options.use_default_template = True
|
||||
backup.options.use_default_netvm = True
|
||||
if args.replace_template:
|
||||
backup.options.replace_template = args.replace_template
|
||||
if args.rename_conflicting:
|
||||
backup.options.rename_conflicting = True
|
||||
if not args.dom0_home:
|
||||
backup.options.dom0_home = False
|
||||
if args.ignore_username_mismatch:
|
||||
backup.options.ignore_username_mismatch = True
|
||||
if args.exclude:
|
||||
backup.options.exclude = args.exclude
|
||||
if args.verify_only:
|
||||
backup.options.verify_only = True
|
||||
|
||||
restore_info = None
|
||||
try:
|
||||
restore_info = backup.get_restore_info()
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
parser.error_runtime(str(e))
|
||||
|
||||
if args.vms:
|
||||
backup.options.exclude += [vm for vm in restore_info
|
||||
if vm not in args.vms]
|
||||
restore_info = backup.restore_info_verify(restore_info)
|
||||
|
||||
print(backup.get_restore_summary(restore_info))
|
||||
|
||||
try:
|
||||
handle_broken(args.app, args, restore_info)
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
parser.error_runtime(str(e))
|
||||
|
||||
if args.pass_file is None:
|
||||
if input("Do you want to proceed? [y/N] ").upper() != "Y":
|
||||
exit(0)
|
||||
|
||||
try:
|
||||
backup.restore_do(restore_info)
|
||||
except qubesadmin.exc.QubesException as e:
|
||||
parser.error_runtime(str(e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
4
setup.py
4
setup.py
@ -7,6 +7,7 @@ import sys
|
||||
exclude=[]
|
||||
if sys.version_info[0:2] < (3, 4):
|
||||
exclude += ['qubesadmin.tools', 'qubesadmin.tests.tools']
|
||||
exclude += ['qubesadmin.backup', 'qubesadmin.tests.backup']
|
||||
if sys.version_info[0:2] < (3, 5):
|
||||
exclude += ['qubesadmin.events']
|
||||
|
||||
@ -32,6 +33,9 @@ if __name__ == '__main__':
|
||||
license='LGPL2.1+',
|
||||
url='https://www.qubes-os.org/',
|
||||
packages=setuptools.find_packages(exclude=exclude),
|
||||
package_data={
|
||||
'qubesadmin.tests.backup': ['*.xml'],
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': list(get_console_scripts()),
|
||||
'qubesadmin.vm': [
|
||||
|
Loading…
Reference in New Issue
Block a user