Find file
Fetching contributors…
Cannot retrieve contributors at this time
245 lines (175 sloc) 6.94 KB
#
# Copyright (c) 2010-2013 Liraz Siri <liraz@turnkeylinux.org>
#
# This file is part of TKLBAM (TurnKey GNU/Linux BAckup and Migration).
#
# TKLBAM is open source 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 3 of
# the License, or (at your option) any later version.
#
import sys
import os
from os.path import *
import userdb
import pkgman
from changes import Changes
from pathmap import PathMap
from rollback import Rollback
from utils import AttrDict, fmt_title, apply_overlay
import backup
import conf
import mysql
import pgsql
import simplejson
from temp import TempFile
class Error(Exception):
pass
def system(command):
sys.stdout.flush()
sys.stderr.flush()
return os.system(command)
class Restore:
Error = Error
PACKAGES_BLACKLIST = ['linux-*', 'vmware-tools*']
def __init__(self, backup_extract_path, limits=[], rollback=True, simulate=False):
self.extras = backup.ExtrasPaths(backup_extract_path)
if not isdir(self.extras.path):
raise self.Error("illegal backup_extract_path: can't find '%s'" % self.extras.path)
if simulate:
rollback = False
self.conf = AttrDict(simplejson.loads(file(self.extras.backup_conf).read())) \
if exists(self.extras.backup_conf) else None
self.simulate = simulate
self.rollback = Rollback.create() if rollback else None
self.limits = conf.Limits(limits)
self.backup_extract_path = backup_extract_path
def database(self):
if not exists(self.extras.myfs) and not exists(self.extras.pgfs):
return
if self.rollback:
self.rollback.save_database()
if exists(self.extras.myfs):
print fmt_title("DATABASE - unserializing MySQL databases from " + self.extras.myfs)
try:
mysql.restore(self.extras.myfs, self.extras.etc.mysql,
limits=self.limits.mydb, callback=mysql.cb_print(), simulate=self.simulate)
except mysql.Error, e:
print "SKIPPING MYSQL DATABASE RESTORE: " + str(e)
if exists(self.extras.pgfs):
print "\n" + fmt_title("DATABASE - Unserializing PgSQL databases from " + self.extras.pgfs)
if self.simulate:
print "CAN'T SIMULATE PGSQL RESTORE, SKIPPING"
return
try:
pgsql.restore(self.extras.pgfs, self.limits.pgdb, callback=pgsql.cb_print())
except pgsql.Error, e:
print "SKIPPING PGSQL DATABASE RESTORE: " + str(e)
def packages(self):
newpkgs_file = self.extras.newpkgs
if not exists(newpkgs_file):
return
packages = file(newpkgs_file).read().strip()
packages = [] if not packages else packages.split('\n')
if not packages:
return
print fmt_title("PACKAGES - %d new packages listed in %s" % (len(packages), newpkgs_file), '-')
already_installed = set(pkgman.installed()) & set(packages)
if len(already_installed) == len(packages):
print "ALL NEW PACKAGES ALREADY INSTALLED\n"
return
if already_installed:
print "// New packages not already installed: %d" % (len(packages) - len(already_installed))
# apt-get update, otherwise installer may skip everything
print "// Update list of available packages"
print
print "# apt-get update"
system("apt-get update")
installer = pkgman.Installer(packages, self.PACKAGES_BLACKLIST)
print
print "// Installing new packages"
if installer.skipping:
print "// Skipping uninstallable packages: " + " ".join(installer.skipping)
print
if not installer.command:
print "NO NEW PACKAGES TO INSTALL\n"
return
print "# " + installer.command
if not self.simulate:
exitcode = installer()
if exitcode != 0:
print "# WARNING: non-zero exitcode (%d)" % exitcode
if self.rollback:
self.rollback.save_new_packages(installer.installed)
print
@staticmethod
def _userdb_merge(old_etc, new_etc):
old_passwd = join(old_etc, "passwd")
new_passwd = join(new_etc, "passwd")
old_group = join(old_etc, "group")
new_group = join(new_etc, "group")
def r(path):
return file(path).read()
return userdb.merge(r(old_passwd), r(old_group),
r(new_passwd), r(new_group))
@staticmethod
def _get_fsdelta_olist(fsdelta_olist_path, limits=[]):
pathmap = PathMap(limits)
return [ fpath
for fpath in file(fsdelta_olist_path).read().splitlines()
if fpath in pathmap ]
@staticmethod
def _apply_overlay(src, dst, olist):
tmp = TempFile("fsdelta-olist-")
for fpath in olist:
print >> tmp, fpath.lstrip('/')
tmp.close()
apply_overlay(src, dst, tmp.path)
def files(self):
extras = self.extras
if not exists(extras.fsdelta):
return
overlay = self.backup_extract_path
simulate = self.simulate
rollback = self.rollback
limits = self.limits.fs
print fmt_title("FILES - restoring files, ownership and permissions", '-')
passwd, group, uidmap, gidmap = self._userdb_merge(extras.etc, "/etc")
if uidmap or gidmap:
print "MERGING USERS AND GROUPS:\n"
for olduid in uidmap:
print " UID %d => %d" % (olduid, uidmap[olduid])
for oldgid in gidmap:
print " GID %d => %d" % (oldgid, gidmap[oldgid])
print
changes = Changes.fromfile(extras.fsdelta, limits)
deleted = list(changes.deleted())
if rollback:
rollback.save_files(changes, overlay)
fsdelta_olist = self._get_fsdelta_olist(extras.fsdelta_olist, limits)
if fsdelta_olist:
print "OVERLAY:\n"
for fpath in fsdelta_olist:
print " " + fpath
if not simulate:
self._apply_overlay(overlay, '/', fsdelta_olist)
print
statfixes = list(changes.statfixes(uidmap, gidmap))
if statfixes or deleted:
print "POST-OVERLAY FIXES:\n"
for action in statfixes:
print " " + str(action)
if not simulate:
action()
for action in deleted:
print " " + str(action)
# rollback moves deleted to 'originals'
if not simulate and not rollback:
action()
if statfixes or deleted:
print
def w(path, s):
file(path, "w").write(str(s))
if not simulate:
w("/etc/passwd", passwd)
w("/etc/group", group)