Merge branch 'migration-tools'
This commit is contained in:
commit
e6c831cb79
@ -45,10 +45,7 @@ if not dry_run:
|
|||||||
|
|
||||||
qubes_guid_path = "/usr/bin/qubes_guid"
|
qubes_guid_path = "/usr/bin/qubes_guid"
|
||||||
|
|
||||||
if dry_run:
|
qubes_base_dir = "/var/lib/qubes"
|
||||||
qubes_base_dir = "."
|
|
||||||
else:
|
|
||||||
qubes_base_dir = "/var/lib/qubes"
|
|
||||||
|
|
||||||
qubes_appvms_dir = qubes_base_dir + "/appvms"
|
qubes_appvms_dir = qubes_base_dir + "/appvms"
|
||||||
qubes_templates_dir = qubes_base_dir + "/vm-templates"
|
qubes_templates_dir = qubes_base_dir + "/vm-templates"
|
||||||
@ -643,7 +640,6 @@ class QubesTemplateVm(QubesVm):
|
|||||||
format(src_template_vm.private_img, self.private_img)
|
format(src_template_vm.private_img, self.private_img)
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
retcode = subprocess.call (["cp", src_template_vm.private_img, self.private_img])
|
retcode = subprocess.call (["cp", src_template_vm.private_img, self.private_img])
|
||||||
retcode = 0
|
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
raise IOError ("Error while copying {0} to {1}".\
|
||||||
format(src_template_vm.private_img, self.private_img))
|
format(src_template_vm.private_img, self.private_img))
|
||||||
@ -653,7 +649,6 @@ class QubesTemplateVm(QubesVm):
|
|||||||
format(src_template_vm.root_img, self.root_img)
|
format(src_template_vm.root_img, self.root_img)
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
retcode = subprocess.call (["cp", src_template_vm.root_img, self.root_img])
|
retcode = subprocess.call (["cp", src_template_vm.root_img, self.root_img])
|
||||||
retcode = 0
|
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
raise IOError ("Error while copying {0} to {1}".\
|
||||||
format(src_template_vm.root_img, self.root_img))
|
format(src_template_vm.root_img, self.root_img))
|
||||||
@ -1042,6 +1037,9 @@ class QubesAppVm(QubesVm):
|
|||||||
print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
||||||
os.symlink (self.label.icon_path, self.icon_path)
|
os.symlink (self.label.icon_path, self.icon_path)
|
||||||
|
|
||||||
|
self.create_appmenus (verbose)
|
||||||
|
|
||||||
|
def create_appmenus(self, verbose):
|
||||||
subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name])
|
subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name])
|
||||||
|
|
||||||
def get_disk_utilization_root_img(self):
|
def get_disk_utilization_root_img(self):
|
||||||
@ -1146,10 +1144,11 @@ class QubesVmCollection(dict):
|
|||||||
A collection of Qubes VMs indexed by Qubes id (qid)
|
A collection of Qubes VMs indexed by Qubes id (qid)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, store_filename=qubes_store_filename):
|
||||||
super(QubesVmCollection, self).__init__()
|
super(QubesVmCollection, self).__init__()
|
||||||
self.default_netvm_qid = None
|
self.default_netvm_qid = None
|
||||||
self.default_template_qid = None
|
self.default_template_qid = None
|
||||||
|
self.qubes_store_filename = store_filename
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
for qid in self.keys():
|
for qid in self.keys():
|
||||||
@ -1309,23 +1308,23 @@ class QubesVmCollection(dict):
|
|||||||
|
|
||||||
def check_if_storage_exists(self):
|
def check_if_storage_exists(self):
|
||||||
try:
|
try:
|
||||||
f = open (qubes_store_filename, 'r')
|
f = open (self.qubes_store_filename, 'r')
|
||||||
except IOError:
|
except IOError:
|
||||||
return False
|
return False
|
||||||
f.close()
|
f.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_empty_storage(self):
|
def create_empty_storage(self):
|
||||||
self.qubes_store_file = open (qubes_store_filename, 'w')
|
self.qubes_store_file = open (self.qubes_store_filename, 'w')
|
||||||
self.clear()
|
self.clear()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def lock_db_for_reading(self):
|
def lock_db_for_reading(self):
|
||||||
self.qubes_store_file = open (qubes_store_filename, 'r')
|
self.qubes_store_file = open (self.qubes_store_filename, 'r')
|
||||||
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH)
|
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH)
|
||||||
|
|
||||||
def lock_db_for_writing(self):
|
def lock_db_for_writing(self):
|
||||||
self.qubes_store_file = open (qubes_store_filename, 'r+')
|
self.qubes_store_file = open (self.qubes_store_filename, 'r+')
|
||||||
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
|
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
|
||||||
|
|
||||||
def unlock_db(self):
|
def unlock_db(self):
|
||||||
|
243
dom0/qvm-tools/qvm-backup
Executable file
243
dom0/qvm-tools/qvm-backup
Executable file
@ -0,0 +1,243 @@
|
|||||||
|
#!/usr/bin/python2.6
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
from qubes.qubes import QubesVmCollection
|
||||||
|
from qubes.qubes import QubesException
|
||||||
|
from qubes.qubes import qubes_store_filename
|
||||||
|
from qubes.qubes import qubes_base_dir
|
||||||
|
from optparse import OptionParser
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def size_to_human (size):
|
||||||
|
if size < 1024:
|
||||||
|
return str (size);
|
||||||
|
elif size < 1024*1024:
|
||||||
|
return str(round(size/1024.0,1)) + ' KiB'
|
||||||
|
elif size < 1024*1024*1024:
|
||||||
|
return str(round(size/(1024.0*1024),1)) + ' MiB'
|
||||||
|
else:
|
||||||
|
return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
|
||||||
|
|
||||||
|
def file_to_backup (file_path, sz = None):
|
||||||
|
if sz is None:
|
||||||
|
sz = os.path.getsize (qubes_store_filename)
|
||||||
|
|
||||||
|
abs_file_path = os.path.abspath (file_path)
|
||||||
|
abs_base_dir = os.path.abspath (qubes_base_dir) + '/'
|
||||||
|
abs_file_dir = os.path.dirname (abs_file_path) + '/'
|
||||||
|
(nothing, dir, subdir) = abs_file_dir.partition (abs_base_dir)
|
||||||
|
assert nothing == ""
|
||||||
|
assert dir == abs_base_dir
|
||||||
|
return [ { "path" : file_path, "size": sz, "subdir": subdir} ]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
usage = "usage: %prog [options] <backup-dir-path>"
|
||||||
|
parser = OptionParser (usage)
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args ()
|
||||||
|
|
||||||
|
if (len (args) != 1):
|
||||||
|
print "You must specify the target backup directory (e.g. /mnt/backup)"
|
||||||
|
print "qvm-backup will create a subdirectory there for each individual backup."
|
||||||
|
exit (0)
|
||||||
|
|
||||||
|
base_backup_dir = args[0]
|
||||||
|
|
||||||
|
if not os.path.exists (base_backup_dir):
|
||||||
|
print "The target directory doesn't exist!"
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
qvm_collection = QubesVmCollection()
|
||||||
|
qvm_collection.lock_db_for_reading()
|
||||||
|
qvm_collection.load()
|
||||||
|
|
||||||
|
vms_list = [vm for vm in qvm_collection.values()]
|
||||||
|
no_vms = len (vms_list)
|
||||||
|
|
||||||
|
files_to_backup = file_to_backup (qubes_store_filename)
|
||||||
|
|
||||||
|
appvms_to_backup = [vm for vm in vms_list if vm.is_appvm()]
|
||||||
|
there_are_running_vms = False
|
||||||
|
|
||||||
|
fields_to_display = [
|
||||||
|
{ "name": "VM", "width": 16},
|
||||||
|
{ "name": "type","width": 12 },
|
||||||
|
{ "name": "size", "width": 12}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Display the header
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:>{0}}} |".format(f["width"] + 1)
|
||||||
|
s += fmt.format(f["name"])
|
||||||
|
print s
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
|
||||||
|
if len (appvms_to_backup):
|
||||||
|
for vm in appvms_to_backup:
|
||||||
|
|
||||||
|
vm_sz = vm.get_disk_usage (vm.private_img)
|
||||||
|
files_to_backup += file_to_backup(vm.private_img, vm_sz )
|
||||||
|
|
||||||
|
files_to_backup += file_to_backup(vm.icon_path)
|
||||||
|
files_to_backup += file_to_backup(vm.conf_file)
|
||||||
|
#files_to_backup += file_to_backup(vm.dir_path + "/apps")
|
||||||
|
|
||||||
|
if vm.is_updateable():
|
||||||
|
sz = vm.get_disk_usage(vm.rootcow_img)
|
||||||
|
files_to_backup += file_to_backup(vm.rootcow_img, sz)
|
||||||
|
vm_sz += sz
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
|
||||||
|
s += fmt.format(vm.name)
|
||||||
|
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1)
|
||||||
|
s += fmt.format("AppVM" + (" + COW" if vm.is_updateable() else ""))
|
||||||
|
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[2]["width"] + 1)
|
||||||
|
s += fmt.format(size_to_human(vm_sz))
|
||||||
|
|
||||||
|
if vm.is_running():
|
||||||
|
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
|
||||||
|
there_are_running_vms = True
|
||||||
|
|
||||||
|
print s
|
||||||
|
|
||||||
|
template_vms_worth_backingup = [ vm for vm in vms_list if (vm.is_templete() and not vm.installed_by_rpm)]
|
||||||
|
if len (template_vms_worth_backingup):
|
||||||
|
for vm in template_vms_worth_backingup:
|
||||||
|
vm_sz = vm.get_disk_utilization()
|
||||||
|
files_to_backup += file_to_backup (vm.dir_path, vm_sz)
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
|
||||||
|
s += fmt.format(vm.name)
|
||||||
|
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1)
|
||||||
|
s += fmt.format("Template VM")
|
||||||
|
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[2]["width"] + 1)
|
||||||
|
s += fmt.format(size_to_human(vm_sz))
|
||||||
|
|
||||||
|
if vm.is_running():
|
||||||
|
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
|
||||||
|
there_are_running_vms = True
|
||||||
|
|
||||||
|
print s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
total_backup_sz = 0
|
||||||
|
for file in files_to_backup:
|
||||||
|
total_backup_sz += file["size"]
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
|
||||||
|
s += fmt.format("Total size:")
|
||||||
|
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1 + 2 + fields_to_display[2]["width"] + 1)
|
||||||
|
s += fmt.format(size_to_human(total_backup_sz))
|
||||||
|
print s
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
|
||||||
|
stat = os.statvfs(base_backup_dir)
|
||||||
|
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
|
||||||
|
print
|
||||||
|
if (total_backup_sz > backup_fs_free_sz):
|
||||||
|
print "Not enough space avilable on the backup filesystem!"
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
if (there_are_running_vms):
|
||||||
|
print "Please shutdown all VMs before proceeding."
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
|
||||||
|
backup_dir = base_backup_dir + "/qubes-{0}".format (time.strftime("%Y-%m-%d-%H%M%S"))
|
||||||
|
if os.path.exists (backup_dir):
|
||||||
|
print "ERROR: the path {0} already exists?!".format(backup_dir)
|
||||||
|
print "Aborting..."
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
print "-> Backup dir: {0}".format (backup_dir)
|
||||||
|
print "-> Avilable space: {0}".format(size_to_human(backup_fs_free_sz))
|
||||||
|
|
||||||
|
prompt = raw_input ("Do you want to proceed? [y/N] ")
|
||||||
|
if not (prompt == "y" or prompt == "Y"):
|
||||||
|
exit (0)
|
||||||
|
|
||||||
|
os.mkdir (backup_dir)
|
||||||
|
|
||||||
|
if not os.path.exists (backup_dir):
|
||||||
|
print "ERROR: Strange: couldn't create backup dir: {0}?!".format(backup_dir)
|
||||||
|
print "Aborting..."
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
bytes_backedup = 0
|
||||||
|
for file in files_to_backup:
|
||||||
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
|
progress = bytes_backedup * 100 / total_backup_sz
|
||||||
|
print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
|
||||||
|
dest_dir = backup_dir + '/' + file["subdir"]
|
||||||
|
if file["subdir"] != "":
|
||||||
|
retcode = subprocess.call (["mkdir", "-p", dest_dir])
|
||||||
|
if retcode != 0:
|
||||||
|
print "Cannot create directory: {0}?!".format(dest_dir)
|
||||||
|
print "Aborting..."
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
retcode = subprocess.call (["cp", "-rp", file["path"], dest_dir])
|
||||||
|
if retcode != 0:
|
||||||
|
print "Error while copying file {0} to {1}".format(file["path"], dest_dir)
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
bytes_backedup += file["size"]
|
||||||
|
progress = bytes_backedup * 100 / total_backup_sz
|
||||||
|
print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
|
||||||
|
|
||||||
|
print
|
||||||
|
print "-> Backup completed."
|
||||||
|
|
||||||
|
qvm_collection.unlock_db()
|
||||||
|
main()
|
362
dom0/qvm-tools/qvm-backup-restore
Executable file
362
dom0/qvm-tools/qvm-backup-restore
Executable file
@ -0,0 +1,362 @@
|
|||||||
|
#!/usr/bin/python2.6
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
from qubes.qubes import QubesVmCollection
|
||||||
|
from qubes.qubes import QubesException
|
||||||
|
from qubes.qubes import qubes_store_filename
|
||||||
|
from qubes.qubes import qubes_base_dir
|
||||||
|
from qubes.qubes import qubes_templates_dir
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def size_to_human (size):
|
||||||
|
if size < 1024:
|
||||||
|
return str (size);
|
||||||
|
elif size < 1024*1024:
|
||||||
|
return str(round(size/1024.0,1)) + ' KiB'
|
||||||
|
elif size < 1024*1024*1024:
|
||||||
|
return str(round(size/(1024.0*1024),1)) + ' MiB'
|
||||||
|
else:
|
||||||
|
return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
"qid": {"func": "vm.qid"},
|
||||||
|
|
||||||
|
"name": {"func": "('=>' if backup_collection.get_default_template_vm() is not None\
|
||||||
|
and vm.qid == backup_collection.get_default_template_vm().qid else '')\
|
||||||
|
+ ('[' if vm.is_templete() else '')\
|
||||||
|
+ ('{' if vm.is_netvm() else '')\
|
||||||
|
+ vm.name \
|
||||||
|
+ (']' if vm.is_templete() else '')\
|
||||||
|
+ ('}' if vm.is_netvm() else '')"},
|
||||||
|
|
||||||
|
"type": {"func": "'Tpl' if vm.is_templete() else \
|
||||||
|
(' Net' if vm.is_netvm() else 'App')"},
|
||||||
|
|
||||||
|
"updbl" : {"func": "'Yes' if vm.is_updateable() else ''"},
|
||||||
|
|
||||||
|
"template": {"func": "'n/a' if vm.is_templete() or vm.is_netvm() else\
|
||||||
|
backup_collection[vm.template_vm.qid].name"},
|
||||||
|
|
||||||
|
"netvm": {"func": "'n/a' if vm.is_netvm() else\
|
||||||
|
('*' if vm.uses_default_netvm else '') +\
|
||||||
|
backup_collection[vm.netvm_vm.qid].name\
|
||||||
|
if vm.netvm_vm is not None else '-'"},
|
||||||
|
|
||||||
|
"label" : {"func" : "vm.label.name"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_vm_included_in_backup (backup_dir, vm):
|
||||||
|
if vm.qid == 0:
|
||||||
|
# Dom0 is not included, obviously
|
||||||
|
return False
|
||||||
|
|
||||||
|
backup_vm_dir_path = vm.dir_path.replace (qubes_base_dir, backup_dir)
|
||||||
|
|
||||||
|
if os.path.exists (backup_vm_dir_path):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restore_vm_file (backup_dir, file_path):
|
||||||
|
|
||||||
|
backup_file_path = file_path.replace (qubes_base_dir, backup_dir)
|
||||||
|
#print "cp -rp {0} {1}".format (backup_file_path, file_path)
|
||||||
|
|
||||||
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
|
retcode = subprocess.call (["cp", "-p", backup_file_path, file_path])
|
||||||
|
if retcode != 0:
|
||||||
|
print "*** Error while copying file {0} to {1}".format(backup_file_path, file_path)
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
def restore_vm_dir (backup_dir, src_dir, dst_dir):
|
||||||
|
|
||||||
|
backup_src_dir = src_dir.replace (qubes_base_dir, backup_dir)
|
||||||
|
|
||||||
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
|
retcode = subprocess.call (["cp", "-rp", backup_src_dir, dst_dir])
|
||||||
|
if retcode != 0:
|
||||||
|
print "*** Error while copying file {0} to {1}".format(backup_src_dir, dest_dir)
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
usage = "usage: %prog [options] <backup-dir>"
|
||||||
|
parser = OptionParser (usage)
|
||||||
|
|
||||||
|
parser.add_option ("--skip-broken", action="store_true", dest="skip_broken", default=False,
|
||||||
|
help="Do not restore VMs that have missing templates or netvms")
|
||||||
|
|
||||||
|
parser.add_option ("--ignore-missing", action="store_true", dest="ignore_missing", default=False,
|
||||||
|
help="Ignore missing templates or netvms, restore VMs anyway")
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args ()
|
||||||
|
|
||||||
|
if (len (args) != 1):
|
||||||
|
print "You must specify the backup directory (e.g. /mnt/backup/qubes-2010-12-01-235959)"
|
||||||
|
exit (0)
|
||||||
|
|
||||||
|
backup_dir = args[0]
|
||||||
|
|
||||||
|
if not os.path.exists (backup_dir):
|
||||||
|
print "The backup directory doesn't exist!"
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
backup_collection = QubesVmCollection(store_filename = backup_dir + "/qubes.xml")
|
||||||
|
backup_collection.lock_db_for_reading()
|
||||||
|
backup_collection.load()
|
||||||
|
|
||||||
|
host_collection = QubesVmCollection()
|
||||||
|
host_collection.lock_db_for_writing()
|
||||||
|
host_collection.load()
|
||||||
|
|
||||||
|
backup_vms_list = [vm for vm in backup_collection.values()]
|
||||||
|
host_vms_list = [vm for vm in host_collection.values()]
|
||||||
|
vms_to_restore = []
|
||||||
|
|
||||||
|
fields_to_display = ["name", "type", "template", "updbl", "netvm", "label" ]
|
||||||
|
|
||||||
|
# First calculate the maximum width of each field we want to display
|
||||||
|
total_width = 0;
|
||||||
|
for f in fields_to_display:
|
||||||
|
fields[f]["max_width"] = len(f)
|
||||||
|
for vm in backup_vms_list:
|
||||||
|
l = len(str(eval(fields[f]["func"])))
|
||||||
|
if l > fields[f]["max_width"]:
|
||||||
|
fields[f]["max_width"] = l
|
||||||
|
total_width += fields[f]["max_width"]
|
||||||
|
|
||||||
|
print
|
||||||
|
print "The following VMs are included in the backup:"
|
||||||
|
print
|
||||||
|
|
||||||
|
# Display the header
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
|
||||||
|
s += fmt.format(f)
|
||||||
|
print s
|
||||||
|
s = ""
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
|
||||||
|
s += fmt.format('-')
|
||||||
|
print s
|
||||||
|
|
||||||
|
there_are_conflicting_vms = False
|
||||||
|
there_are_missing_templates = False
|
||||||
|
there_are_missing_netvms = False
|
||||||
|
# ... and the actual data
|
||||||
|
for vm in backup_vms_list:
|
||||||
|
if is_vm_included_in_backup (backup_dir, vm):
|
||||||
|
s = ""
|
||||||
|
good_to_go = True
|
||||||
|
|
||||||
|
for f in fields_to_display:
|
||||||
|
fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
|
||||||
|
s += fmt.format(eval(fields[f]["func"]))
|
||||||
|
|
||||||
|
if host_collection.get_vm_by_name (vm.name) is not None:
|
||||||
|
s += " <-- A VM with the same name already exists on the host!"
|
||||||
|
there_are_conflicting_vms = True
|
||||||
|
good_to_go = False
|
||||||
|
|
||||||
|
if vm.is_appvm():
|
||||||
|
templatevm_name = vm.template_vm.name
|
||||||
|
template_vm_on_host = host_collection.get_vm_by_name (templatevm_name)
|
||||||
|
|
||||||
|
# No template on the host?
|
||||||
|
if not ((template_vm_on_host is not None) and template_vm_on_host.is_templete):
|
||||||
|
|
||||||
|
# Maybe the (custom) template is in the backup?
|
||||||
|
template_vm_on_backup = backup_collection.get_vm_by_name (templatevm_name)
|
||||||
|
if not ((template_vm_on_backup is not None) and template_vm_on_backup.is_templete):
|
||||||
|
s += " <-- No matching template on the host or in the backup found!"
|
||||||
|
there_are_missing_templates = True
|
||||||
|
good_to_go = False if not (options.ignore_missing) else True
|
||||||
|
|
||||||
|
if not vm.is_netvm() and vm.netvm_vm is not None:
|
||||||
|
netvm_name = vm.netvm_vm.name
|
||||||
|
netvm_vm_on_host = host_collection.get_vm_by_name (netvm_name)
|
||||||
|
|
||||||
|
# No netv, on the host?
|
||||||
|
if not ((netvm_vm_on_host is not None) and netvm_vm_on_host.is_netvm):
|
||||||
|
|
||||||
|
# Maybe the (custom) netvm is in the backup?
|
||||||
|
netvm_vm_on_backup = backup_collection.get_vm_by_name (netvm_name)
|
||||||
|
if not ((netvm_vm_on_backup is not None) and netvm_vm_on_backup.is_netvm):
|
||||||
|
s += " <-- No matching netvm on the host found!"
|
||||||
|
there_are_missing_netvms = True
|
||||||
|
good_to_go = False if not (options.ignore_missing) else True
|
||||||
|
|
||||||
|
print s
|
||||||
|
if good_to_go:
|
||||||
|
vms_to_restore.append (vm)
|
||||||
|
|
||||||
|
print
|
||||||
|
|
||||||
|
if there_are_conflicting_vms:
|
||||||
|
print "*** There VMs with conflicting names on the host! ***"
|
||||||
|
print "Remove VMs with conflicting names from the host before proceeding."
|
||||||
|
print "You can use 'qvm-remove <vmname>' command for this."
|
||||||
|
print "Be careful! Think twice before typing qvm-remove!!!"
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
print "The above VMs will be copied and added to your system."
|
||||||
|
print "Exisiting VMs will not be removed."
|
||||||
|
|
||||||
|
if there_are_missing_templates:
|
||||||
|
print "*** One or more template VM is missing on the host! ***"
|
||||||
|
if not (options.skip_broken or options.ignore_missing):
|
||||||
|
print "Install it first, before proceeding with backup restore."
|
||||||
|
print "Or pass: --skip-broken or --ignore-missing switch."
|
||||||
|
exit (1)
|
||||||
|
elif options.skip_broken:
|
||||||
|
print "... VMs that depend on it will not be restored (--skip-broken used)"
|
||||||
|
elif options.ignore_missing:
|
||||||
|
print "... VMs that depend on it be restored anyway (--ignore-missing used)"
|
||||||
|
else:
|
||||||
|
print "INTERNAL ERROR?!"
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
if there_are_missing_netvms:
|
||||||
|
print "*** One or more network VM is missing on the host! ***"
|
||||||
|
if not (options.skip_broken or options.ignore_missing):
|
||||||
|
print "Install it first, before proceeding with backup restore."
|
||||||
|
print "Or pass: --skip_broken or --ignore_missing switch."
|
||||||
|
exit (1)
|
||||||
|
elif options.skip_broken:
|
||||||
|
print "... VMs that depend on it will not be restored (--skip-broken used)"
|
||||||
|
elif options.ignore_missing:
|
||||||
|
print "... VMs that depend on it be restored anyway (--ignore-missing used)"
|
||||||
|
else:
|
||||||
|
print "INTERNAL ERROR?!"
|
||||||
|
exit (1)
|
||||||
|
|
||||||
|
prompt = raw_input ("Do you want to proceed? [y/N] ")
|
||||||
|
if not (prompt == "y" or prompt == "Y"):
|
||||||
|
exit (0)
|
||||||
|
|
||||||
|
for vm in vms_to_restore:
|
||||||
|
print "-> Restoring: {0} ...".format(vm.name)
|
||||||
|
|
||||||
|
retcode = subprocess.call (["mkdir", "-p", vm.dir_path])
|
||||||
|
if retcode != 0:
|
||||||
|
print ("*** Cannot create directory: {0}?!".format(dest_dir))
|
||||||
|
print ("Skiping...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if vm.is_appvm():
|
||||||
|
|
||||||
|
restore_vm_file (backup_dir, vm.private_img)
|
||||||
|
restore_vm_file (backup_dir, vm.icon_path)
|
||||||
|
restore_vm_file (backup_dir, vm.conf_file)
|
||||||
|
|
||||||
|
if vm.is_updateable():
|
||||||
|
restore_vm_file (backup_dir, vm.rootcow_img)
|
||||||
|
|
||||||
|
elif vm.is_templete():
|
||||||
|
restore_vm_dir (backup_dir, vm.dir_path, qubes_templates_dir);
|
||||||
|
else:
|
||||||
|
print "ERROR: VM '{0}', type='{1}': unsupported VM type!".format(vm.name, vm.type)
|
||||||
|
|
||||||
|
# Add templates...
|
||||||
|
for vm in [ vm for vm in vms_to_restore if vm.is_templete()]:
|
||||||
|
print "-> Adding Template VM {0}...".format(vm.name)
|
||||||
|
updateable = vm.updateable
|
||||||
|
vm = host_collection.add_new_templatevm(vm.name,
|
||||||
|
conf_file=vm.conf_file,
|
||||||
|
dir_path=vm.dir_path,
|
||||||
|
installed_by_rpm=False)
|
||||||
|
|
||||||
|
vm.updateable = updateable
|
||||||
|
try:
|
||||||
|
vm.verify_files()
|
||||||
|
except QubesException as err:
|
||||||
|
print "ERROR: {0}".format(err)
|
||||||
|
print "*** Skiping VM: {0}".vm.name
|
||||||
|
host_collection.pop(vm.qid)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
vm.add_to_xen_storage()
|
||||||
|
|
||||||
|
except (IOError, OSError) as err:
|
||||||
|
print "ERROR: {0}".format(err)
|
||||||
|
print "*** Skiping VM: {0}".vm.name
|
||||||
|
host_collection.pop(vm.qid)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ... then appvms...
|
||||||
|
for vm in [ vm for vm in vms_to_restore if vm.is_appvm()]:
|
||||||
|
|
||||||
|
print "-> Adding AppVM {0}...".format(vm.name)
|
||||||
|
template_vm = host_collection.get_vm_by_name(vm.template_vm.name)
|
||||||
|
|
||||||
|
if not vm.uses_default_netvm:
|
||||||
|
uses_default_netvm = False
|
||||||
|
netvm_vm = host_collection.get_vm_by_name (vm.netvm_vm.name) if vm.netvm_vm is not None else None
|
||||||
|
else:
|
||||||
|
uses_default_netvm = True
|
||||||
|
|
||||||
|
updateable = vm.updateable
|
||||||
|
|
||||||
|
vm = host_collection.add_new_appvm(vm.name, template_vm,
|
||||||
|
conf_file=vm.conf_file,
|
||||||
|
dir_path=vm.dir_path,
|
||||||
|
label=vm.label)
|
||||||
|
|
||||||
|
vm.updateable = updateable
|
||||||
|
if not uses_default_netvm:
|
||||||
|
vm.uses_default_netvm = False
|
||||||
|
vm.netvm_vm = netvm_vm
|
||||||
|
|
||||||
|
|
||||||
|
vm.create_appmenus(verbose=True)
|
||||||
|
try:
|
||||||
|
vm.verify_files()
|
||||||
|
except QubesException as err:
|
||||||
|
print "ERROR: {0}".format(err)
|
||||||
|
print "*** Skiping VM: {0}".vm.name
|
||||||
|
host_collection.pop(vm.qid)
|
||||||
|
|
||||||
|
try:
|
||||||
|
vm.add_to_xen_storage()
|
||||||
|
except (IOError, OSError) as err:
|
||||||
|
print "ERROR: {0}".format(err)
|
||||||
|
print "*** Skiping VM: {0}".vm.name
|
||||||
|
host_collection.pop(vm.qid)
|
||||||
|
|
||||||
|
|
||||||
|
backup_collection.unlock_db()
|
||||||
|
host_collection.save()
|
||||||
|
host_collection.unlock_db()
|
||||||
|
print "-> Done."
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user