qvm-backup-restore 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #!/usr/bin/python2
  2. # -*- encoding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. #
  22. #
  23. from multiprocessing import Event
  24. from qubes.qubes import QubesVmCollection
  25. from qubes.qubes import QubesException
  26. from qubes.backup import backup_restore_header
  27. from qubes.backup import backup_restore_prepare
  28. from qubes.backup import backup_restore_print_summary
  29. from qubes.backup import backup_restore_do
  30. import qubes.backup
  31. import sys
  32. from optparse import OptionParser
  33. from locale import getpreferredencoding
  34. import os
  35. import sys
  36. import getpass
  37. def main():
  38. usage = "usage: %prog [options] <backup-dir> [vms-to-be-restored ...]"
  39. parser = OptionParser (usage)
  40. parser.add_option ("--verify-only", action="store_true",
  41. dest="verify_only", default=False,
  42. help="Do not restore the data, only verify backup "
  43. "integrify.")
  44. parser.add_option ("--skip-broken", action="store_true", dest="skip_broken", default=False,
  45. help="Do not restore VMs that have missing templates or netvms")
  46. parser.add_option ("--ignore-missing", action="store_true", dest="ignore_missing", default=False,
  47. help="Ignore missing templates or netvms, restore VMs anyway")
  48. parser.add_option ("--skip-conflicting", action="store_true", dest="skip_conflicting", default=False,
  49. help="Do not restore VMs that are already present on the host")
  50. parser.add_option ("--rename-conflicting", action="store_true",
  51. dest="rename_conflicting", default=False,
  52. help="Restore VMs that are already present on the host under different name")
  53. parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
  54. help="Force to run, even with root privileges")
  55. parser.add_option ("--replace-template", action="append", dest="replace_template", default=[],
  56. help="Restore VMs using another template, syntax: "
  57. "old-template-name:new-template-name (may be "
  58. "repeated)")
  59. parser.add_option ("-x", "--exclude", action="append", dest="exclude", default=[],
  60. help="Skip restore of specified VM (may be repeated)")
  61. parser.add_option ("--skip-dom0-home", action="store_false", dest="dom0_home", default=True,
  62. help="Do not restore dom0 user home dir")
  63. parser.add_option ("--ignore-username-mismatch", action="store_true", dest="ignore_username_mismatch", default=False,
  64. help="Ignore dom0 username mismatch while restoring homedir")
  65. parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
  66. help="The AppVM to send backups to")
  67. parser.add_option ("-e", "--encrypted", action="store_true", dest="decrypt", default=False,
  68. help="The backup is encrypted")
  69. parser.add_option ("-p", "--passphrase-file", action="store",
  70. dest="pass_file", default=None,
  71. help="File containing the pass phrase to use, or '-' to read it from stdin")
  72. parser.add_option ("-z", "--compressed", action="store_true", dest="compressed", default=False,
  73. help="The backup is compressed")
  74. parser.add_option ("--debug", action="store_true", dest="debug",
  75. default=False, help="Enable (a lot of) debug output")
  76. (options, args) = parser.parse_args ()
  77. if (len (args) < 1):
  78. print >> sys.stderr, "You must specify the backup directory (e.g. /mnt/backup/qubes-2010-12-01-235959)"
  79. exit (0)
  80. backup_dir = args[0]
  81. vmlist = args[1:]
  82. #if not os.path.exists (backup_dir):
  83. # print >> sys.stderr, "The backup directory doesn't exist!"
  84. # exit(1)
  85. host_collection = QubesVmCollection()
  86. host_collection.lock_db_for_writing()
  87. host_collection.load()
  88. restore_options = {}
  89. if options.ignore_missing:
  90. restore_options['use-default-template'] = True
  91. restore_options['use-default-netvm'] = True
  92. if options.replace_template:
  93. restore_options['replace-template'] = options.replace_template
  94. if options.rename_conflicting:
  95. restore_options['rename-conflicting'] = True
  96. if not options.dom0_home:
  97. restore_options['dom0-home'] = False
  98. if options.ignore_username_mismatch:
  99. restore_options['ignore-username-mismatch'] = True
  100. if options.exclude:
  101. restore_options['exclude'] = options.exclude
  102. if options.verify_only:
  103. restore_options['verify-only'] = True
  104. if options.debug:
  105. qubes.backup.BACKUP_DEBUG = True
  106. appvm = None
  107. if options.appvm is not None:
  108. appvm = host_collection.get_vm_by_name(options.appvm)
  109. if appvm is None:
  110. print >>sys.stderr, "ERROR: VM {0} does not exist".format(options.appvm)
  111. exit(1)
  112. if options.pass_file is not None:
  113. f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
  114. passphrase = f.readline().rstrip()
  115. if f is not sys.stdin:
  116. f.close()
  117. else:
  118. passphrase = getpass.getpass("Please enter the pass phrase to decrypt/verify the backup: ")
  119. encoding = sys.stdin.encoding or getpreferredencoding()
  120. passphrase = passphrase.decode(encoding)
  121. print >> sys.stderr, "Checking backup content..."
  122. error_detected = Event()
  123. def error_callback(message):
  124. error_detected.set()
  125. print >> sys.stderr, message
  126. restore_info = None
  127. try:
  128. restore_info = backup_restore_prepare(
  129. backup_dir,
  130. passphrase=passphrase,
  131. options=restore_options,
  132. host_collection=host_collection,
  133. encrypted=options.decrypt,
  134. compressed=options.compressed,
  135. appvm=appvm,
  136. error_callback=error_callback)
  137. except QubesException as e:
  138. print >> sys.stderr, "ERROR: %s" % str(e)
  139. exit(1)
  140. if len(vmlist) > 0:
  141. for vm in restore_info.keys():
  142. if vm.startswith('$'):
  143. continue
  144. if not vm in vmlist:
  145. restore_info.pop(vm)
  146. backup_restore_print_summary(restore_info)
  147. there_are_conflicting_vms = False
  148. there_are_missing_templates = False
  149. there_are_missing_netvms = False
  150. dom0_username_mismatch = False
  151. for vm_info in restore_info.values():
  152. if 'excluded' in vm_info and vm_info['excluded']:
  153. continue
  154. if 'missing-template' in vm_info.keys():
  155. there_are_missing_templates = True
  156. if 'missing-netvm' in vm_info.keys():
  157. there_are_missing_netvms = True
  158. if 'already-exists' in vm_info.keys():
  159. there_are_conflicting_vms = True
  160. if 'username-mismatch' in vm_info.keys():
  161. dom0_username_mismatch = True
  162. print
  163. if hasattr(os, "geteuid") and os.geteuid() == 0:
  164. print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems."
  165. if options.force_root:
  166. print >> sys.stderr, "Continuing as commanded. You have been warned."
  167. else:
  168. print >> sys.stderr, "Retry as unprivileged user."
  169. print >> sys.stderr, "... or use --force-root to continue anyway."
  170. exit(1)
  171. if there_are_conflicting_vms:
  172. print >> sys.stderr, "*** There VMs with conflicting names on the host! ***"
  173. if options.skip_conflicting:
  174. print >> sys.stderr, "Those VMs will not be restored, the host VMs will not be overwritten!"
  175. else:
  176. print >> sys.stderr, "Remove VMs with conflicting names from the host before proceeding."
  177. print >> sys.stderr, "... or use --skip-conflicting to restore only those VMs that do not exist on the host."
  178. print >> sys.stderr, "... or use --rename-conflicting to " \
  179. "restore those VMs under modified " \
  180. "name (with number at the end)"
  181. exit (1)
  182. print "The above VMs will be copied and added to your system."
  183. print "Exisiting VMs will not be removed."
  184. if there_are_missing_templates:
  185. print >> sys.stderr, "*** One or more template VM is missing on the host! ***"
  186. if not (options.skip_broken or options.ignore_missing):
  187. print >> sys.stderr, "Install it first, before proceeding with backup restore."
  188. print >> sys.stderr, "Or pass: --skip-broken or --ignore-missing switch."
  189. exit (1)
  190. elif options.skip_broken:
  191. print >> sys.stderr, "... VMs that depend on it will not be restored (--skip-broken used)"
  192. elif options.ignore_missing:
  193. print >> sys.stderr, "... VMs that depend on it will be restored anyway (--ignore-missing used)"
  194. else:
  195. print >> sys.stderr, "INTERNAL ERROR?!"
  196. exit (1)
  197. if there_are_missing_netvms:
  198. print >> sys.stderr, "*** One or more network VM is missing on the host! ***"
  199. if not (options.skip_broken or options.ignore_missing):
  200. print >> sys.stderr, "Install it first, before proceeding with backup restore."
  201. print >> sys.stderr, "Or pass: --skip_broken or --ignore_missing switch."
  202. exit (1)
  203. elif options.skip_broken:
  204. print >> sys.stderr, "... VMs that depend on it will not be restored (--skip-broken used)"
  205. elif options.ignore_missing:
  206. print >> sys.stderr, "... VMs that depend on it be restored anyway (--ignore-missing used)"
  207. else:
  208. print >> sys.stderr, "INTERNAL ERROR?!"
  209. exit (1)
  210. if 'dom0' in restore_info.keys() and options.dom0_home:
  211. if dom0_username_mismatch:
  212. print >> sys.stderr, "*** Dom0 username mismatch! This can break some settings ***"
  213. if not options.ignore_username_mismatch:
  214. print >> sys.stderr, "Skip dom0 home restore (--skip-dom0-home)"
  215. print >> sys.stderr, "Or pass: --ignore-username-mismatch to continue anyway"
  216. exit(1)
  217. else:
  218. print >> sys.stderr, "Continuing as directed"
  219. print >> sys.stderr, "While restoring user homedir, existing files/dirs will be backed up in 'home-pre-restore-<current-time>' dir"
  220. if options.pass_file is None:
  221. if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
  222. exit(0)
  223. try:
  224. backup_restore_do(restore_info,
  225. host_collection=host_collection,
  226. error_callback=error_callback)
  227. except QubesException as e:
  228. print >> sys.stderr, "ERROR: %s" % str(e)
  229. host_collection.unlock_db()
  230. if error_detected.is_set():
  231. print "-> Completed with errors!"
  232. exit(1)
  233. else:
  234. print "-> Done."
  235. main()