 d8af76ed60
			
		
	
	
		d8af76ed60
		
			
		
	
	
	
	
		
			
			This breaks cyclic imports and also allow cleaner separation between backup make and restore code. No functional change.
		
			
				
	
	
		
			266 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # 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
 | |
| 
 | |
| from qubesadmin.backup.restore import BackupRestore
 | |
| 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, BackupRestore.VMToRestore)
 | |
|         if BackupRestore.VMToRestore.EXCLUDED in \
 | |
|                 vm_info.problems:
 | |
|             continue
 | |
|         if BackupRestore.VMToRestore.MISSING_TEMPLATE in \
 | |
|                 vm_info.problems:
 | |
|             there_are_missing_templates = True
 | |
|         if BackupRestore.VMToRestore.MISSING_NETVM in \
 | |
|                 vm_info.problems:
 | |
|             there_are_missing_netvms = True
 | |
|         if BackupRestore.VMToRestore.ALREADY_EXISTS in \
 | |
|                 vm_info.problems:
 | |
|             there_are_conflicting_vms = True
 | |
|         if 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 have default value "
 | |
|                 "assigned.")
 | |
|         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 have default value assigned.")
 | |
|         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, app=None):
 | |
|     '''Main function of qvm-backup-restore'''
 | |
|     # pylint: disable=too-many-return-statements
 | |
|     args = parser.parse_args(args, app=app)
 | |
| 
 | |
|     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 = 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.name for vm in restore_info.values()
 | |
|             if vm.name 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()
 |