rebased ostree/atomic patches for rawhide

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I still haven't had a chance to de-uglify these much from comments, but I've managed to rebase on top of the current rawhide versions of anaconda/python-blivet.

This requires the PrivateNetwork= workaround I previously posted for lorax.

Sending out in case anyone is interested in helping me with cleaning them up and landing.

>From ca197ed0482309631165a89c7dbe0421908e16f1 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Wed, 19 Mar 2014 10:56:24 -0400
Subject: [PATCH] Add "targetSysroot" which differs from ROOT_PATH

For OSTree, the location of the OS checkout (and e.g. /etc/fstab) is
really in /ostree/deploy/$osname/deploy/$revision/etc/fstab.

In order to properly support OSTree, Blivet will need to gain an
understanding of the separation between the physical system / and the
target root.

This patch will be used in Anaconda, which will set the targetSysroot
attribute after the root being installed is laid out.

After that, when we call write(), the fstab data will be correctly
written into the target root.

Conflicts:
	blivet/__init__.py
---
 blivet/__init__.py | 50 +++++++++++++++++++++++++++-----------------------
 blivet/util.py     |  4 ++--
 2 files changed, 29 insertions(+), 25 deletions(-)

diff --git a/blivet/__init__.py b/blivet/__init__.py
index b9192d9..4879d4f 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -30,6 +30,7 @@ __version__ = '0.48'
 ##
 isys = None
 ROOT_PATH = '/'
+targetSysroot = ROOT_PATH
 shortProductName = 'blivet'
 productName = 'blivet'
 bootLoaderError = Exception
@@ -99,6 +100,7 @@ def enable_installer_mode():
     """ Configure the module for use by anaconda (OS installer). """
     global isys
     global ROOT_PATH
+    global targetSysroot
     global shortProductName
     global productName
     global get_bootloader
@@ -115,6 +117,8 @@ def enable_installer_mode():
     from pyanaconda.errors import errorHandler
     from pyanaconda.errors import ERROR_RAISE
 
+    targetSysroot = ROOT_PATH
+
     from pyanaconda.anaconda_log import program_log_lock
     util.program_log_lock = program_log_lock
 
@@ -203,7 +207,7 @@ def writeEscrowPackets(storage):
     backupPassphrase = generateBackupPassphrase()
 
     try:
-        escrowDir = ROOT_PATH + "/root"
+        escrowDir = targetSysroot + "/root"
         log.debug("escrow: writing escrow packets to %s", escrowDir)
         util.makedirs(escrowDir)
         for device in escrowDevices:
@@ -1707,22 +1711,22 @@ class Blivet(object):
 
     def write(self):
         """ Write out all storage-related configuration files. """
-        if not os.path.isdir("%s/etc" % ROOT_PATH):
-            os.mkdir("%s/etc" % ROOT_PATH)
+        if not os.path.isdir("%s/etc" % targetSysroot):
+            os.mkdir("%s/etc" % targetSysroot)
 
         self.fsset.write()
         self.makeMtab()
-        self.iscsi.write(ROOT_PATH, self)
-        self.fcoe.write(ROOT_PATH)
-        self.zfcp.write(ROOT_PATH)
-        write_dasd_conf(self.dasd, ROOT_PATH)
+        self.iscsi.write(targetSysroot, self)
+        self.fcoe.write(targetSysroot)
+        self.zfcp.write(targetSysroot)
+        write_dasd_conf(self.dasd, targetSysroot)
 
     def turnOnSwap(self, upgrading=None):
-        self.fsset.turnOnSwap(rootPath=ROOT_PATH,
+        self.fsset.turnOnSwap(rootPath=targetSysroot,
                               upgrading=upgrading)
 
     def mountFilesystems(self, raiseErrors=None, readOnly=None, skipRoot=False):
-        self.fsset.mountFilesystems(rootPath=ROOT_PATH,
+        self.fsset.mountFilesystems(rootPath=targetSysroot,
                                     raiseErrors=raiseErrors,
                                     readOnly=readOnly, skipRoot=skipRoot)
 
@@ -1839,7 +1843,7 @@ class Blivet(object):
     def makeMtab(self):
         path = "/etc/mtab"
         target = "/proc/self/mounts"
-        path = os.path.normpath("%s/%s" % (ROOT_PATH, path))
+        path = os.path.normpath("%s/%s" % (targetSysroot, path))
 
         if os.path.islink(path):
             # return early if the mtab symlink is already how we like it
@@ -2143,7 +2147,7 @@ def mountExistingSystem(fsset, rootDevice,
                         allowDirty=None, dirtyCB=None,
                         readOnly=None):
     """ Mount filesystems specified in rootDevice's /etc/fstab file. """
-    rootPath = ROOT_PATH
+    rootPath = targetSysroot
     if dirtyCB is None:
         dirtyCB = lambda l: False
 
@@ -2185,7 +2189,7 @@ def mountExistingSystem(fsset, rootDevice,
     if dirtyDevs and (not allowDirty or dirtyCB(dirtyDevs)):
         raise DirtyFSError(dirtyDevs)
 
-    fsset.mountFilesystems(rootPath=ROOT_PATH, readOnly=readOnly, skipRoot=True)
+    fsset.mountFilesystems(rootPath=targetSysroot, readOnly=readOnly, skipRoot=True)
 
 
 class BlkidTab(object):
@@ -2545,7 +2549,7 @@ class FSSet(object):
                 loop mounts?
         """
         if not chroot or not os.path.isdir(chroot):
-            chroot = ROOT_PATH
+            chroot = targetSysroot
 
         path = "%s/etc/fstab" % chroot
         if not os.access(path, os.R_OK):
@@ -2742,10 +2746,10 @@ class FSSet(object):
 
     def mkDevRoot(self):
         root = self.rootDevice
-        dev = "%s/%s" % (ROOT_PATH, root.path)
-        if not os.path.exists("%s/dev/root" %(ROOT_PATH,)) and os.path.exists(dev):
+        dev = "%s/%s" % (targetSysroot, root.path)
+        if not os.path.exists("%s/dev/root" %(targetSysroot,)) and os.path.exists(dev):
             rdev = os.stat(dev).st_rdev
-            os.mknod("%s/dev/root" % (ROOT_PATH,), stat.S_IFBLK | 0600, rdev)
+            os.mknod("%s/dev/root" % (targetSysroot,), stat.S_IFBLK | 0600, rdev)
 
     @property
     def swapDevices(self):
@@ -2757,7 +2761,7 @@ class FSSet(object):
 
     @property
     def rootDevice(self):
-        for path in ["/", ROOT_PATH]:
+        for path in ["/", ROOT_PATH, targetSysroot]:
             for device in self.devices:
                 try:
                     mountpoint = device.format.mountpoint
@@ -2770,19 +2774,19 @@ class FSSet(object):
     def write(self):
         """ write out all config files based on the set of filesystems """
         # /etc/fstab
-        fstab_path = os.path.normpath("%s/etc/fstab" % ROOT_PATH)
+        fstab_path = os.path.normpath("%s/etc/fstab" % targetSysroot)
         fstab = self.fstab()
         open(fstab_path, "w").write(fstab)
 
         # /etc/crypttab
-        crypttab_path = os.path.normpath("%s/etc/crypttab" % ROOT_PATH)
+        crypttab_path = os.path.normpath("%s/etc/crypttab" % targetSysroot)
         crypttab = self.crypttab()
         origmask = os.umask(0077)
         open(crypttab_path, "w").write(crypttab)
         os.umask(origmask)
 
         # /etc/mdadm.conf
-        mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % ROOT_PATH)
+        mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % targetSysroot)
         mdadm_conf = self.mdadmConf()
         if mdadm_conf:
             open(mdadm_path, "w").write(mdadm_conf)
@@ -3032,11 +3036,11 @@ def getReleaseString():
     relVer = None
 
     try:
-        relArch = util.capture_output(["arch"], root=ROOT_PATH).strip()
+        relArch = util.capture_output(["arch"], root=targetSysroot).strip()
     except OSError:
         relArch = None
 
-    filename = "%s/etc/redhat-release" % ROOT_PATH
+    filename = "%s/etc/redhat-release" % targetSysroot
     if os.access(filename, os.R_OK):
         (relName, relVer) = releaseFromRedhatRelease(filename)
     else:
@@ -3133,7 +3137,7 @@ class Root(object):
 def parseFSTab(devicetree, chroot=None):
     """ parse /etc/fstab and return a tuple of a mount dict and swap list """
     if not chroot or not os.path.isdir(chroot):
-        chroot = ROOT_PATH
+        chroot = targetSysroot 
 
     mounts = {}
     swaps = []
diff --git a/blivet/util.py b/blivet/util.py
index ae61a1d..003acaa 100644
--- a/blivet/util.py
+++ b/blivet/util.py
@@ -279,13 +279,13 @@ def makedirs(path):
 
 def copy_to_system(source):
     # do the import now because enable_installer_mode() has finally been called.
-    from . import ROOT_PATH
+    from . import targetSysroot
 
     if not os.access(source, os.R_OK):
         log.info("copy_to_system: source '%s' does not exist.", source)
         return False
 
-    target = ROOT_PATH + source
+    target = targetSysroot + source
     target_dir = os.path.dirname(target)
     log.debug("copy_to_system: '%s' -> '%s'.", source, target)
     if not os.path.isdir(target_dir):
-- 
1.8.3.1

>From 3b130c18eb0aaf5f1e31796953f7d813c2870664 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Fri, 4 Apr 2014 18:18:07 -0400
Subject: [PATCH 1/7] gui/spokes/software: Enable iff payload is PackagePayload

This should be the equivalent of the livecd test.
---
 pyanaconda/ui/gui/spokes/software.py | 4 ++--
 pyanaconda/ui/gui/spokes/source.py   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/pyanaconda/ui/gui/spokes/software.py b/pyanaconda/ui/gui/spokes/software.py
index 322f8ca..77a4b4f 100644
--- a/pyanaconda/ui/gui/spokes/software.py
+++ b/pyanaconda/ui/gui/spokes/software.py
@@ -23,7 +23,7 @@ from gi.repository import Gtk, Pango
 
 from pyanaconda.flags import flags
 from pyanaconda.i18n import _, C_, CN_
-from pyanaconda.packaging import MetadataError
+from pyanaconda.packaging import MetadataError, PackagePayload
 from pyanaconda.threads import threadMgr, AnacondaThread
 from pyanaconda import constants
 
@@ -172,7 +172,7 @@ class SoftwareSelectionSpoke(NormalSpoke):
 
     @property
     def showable(self):
-        return not flags.livecdInstall and not self.data.method.method == "liveimg"
+        return isinstance(self.payload, PackagePayload)
 
     @property
     def status(self):
diff --git a/pyanaconda/ui/gui/spokes/source.py b/pyanaconda/ui/gui/spokes/source.py
index cc64ddb..8b56f6b 100644
--- a/pyanaconda/ui/gui/spokes/source.py
+++ b/pyanaconda/ui/gui/spokes/source.py
@@ -41,7 +41,7 @@ from pyanaconda.ui.gui.utils import enlightbox, fire_gtk_action
 from pyanaconda.iutil import ProxyString, ProxyStringError, cmp_obj_attrs
 from pyanaconda.ui.gui.utils import gtk_call_once, really_hide, really_show
 from pyanaconda.threads import threadMgr, AnacondaThread
-from pyanaconda.packaging import PayloadError, MetadataError
+from pyanaconda.packaging import PayloadError, MetadataError, PackagePayload
 from pyanaconda import constants
 
 from blivet.util import get_mount_paths
@@ -797,7 +797,7 @@ class SourceSpoke(NormalSpoke):
 
     @property
     def showable(self):
-        return not flags.livecdInstall and not self.data.method.method == "liveimg"
+        return isinstance(self.payload, PackagePayload)
 
     def _mirror_active(self):
         return self._protocolComboBox.get_active() == PROTOCOL_MIRROR
-- 
1.8.3.1


>From 76b21c14a2c6fba7e6e2b19689d0954c82841237 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Fri, 4 Apr 2014 20:43:30 -0400
Subject: [PATCH 2/7] users: Deduplicate code to fork()+chroot()

This is in preparation for a patch to do it for the password setting,
which blew up in the OSTree patchset.  I'm not sure why this
apparently isn't broken in the traditional path.
---
 pyanaconda/users.py | 101 +++++++++++++++++++++-------------------------------
 1 file changed, 40 insertions(+), 61 deletions(-)

diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index 9113a18..93b84b5 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -26,6 +26,7 @@ import random
 import tempfile
 import os
 import os.path
+from contextlib import contextmanager
 from pyanaconda import iutil
 import pwquality
 from pyanaconda.iutil import strip_accents
@@ -201,19 +202,13 @@ class Users:
     def __init__ (self):
         self.admin = libuser.admin()
 
-    def createGroup (self, group_name, **kwargs):
-        """Create a new user on the system with the given name.  Optional kwargs:
-
-           gid       -- The GID for the new user.  If none is given, the next
-                        available one is used.
-           root      -- The directory of the system to create the new user
-                        in.  homedir will be interpreted relative to this.
-                        Defaults to /mnt/sysimage.
-        """
+    def _prepareChroot(self, root):
+        # Unfortunately libuser doesn't have an API to operate on a
+        # chroot, so we hack it here by forking a child and calling
+        # chroot() in that child's context.
+        import sys
 
         childpid = os.fork()
-        root = kwargs.get("root", ROOT_PATH)
-
         if not childpid:
             if not root in ["","/"]:
                 os.chroot(root)
@@ -221,7 +216,34 @@ class Users:
                 del(os.environ["LIBUSER_CONF"])
 
             self.admin = libuser.admin()
+            
+        return childpid
+
+    def _finishChroot(self, childpid):
+        assert childpid > 0
+        try:
+            status = os.waitpid(childpid, 0)[1]
+        except OSError as e:
+            log.critical("exception from waitpid: %s %s", e.errno, e.strerror)
+            return False
+
+        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
+            return True
+        else:
+            return False
 
+    def createGroup (self, group_name, **kwargs):
+        """Create a new user on the system with the given name.  Optional kwargs:
+
+           gid       -- The GID for the new user.  If none is given, the next
+                        available one is used.
+           root      -- The directory of the system to create the new user
+                        in.  homedir will be interpreted relative to this.
+                        Defaults to /mnt/sysimage.
+        """
+
+        childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH))
+        if childpid == 0:
             if self.admin.lookupGroupByName(group_name):
                 log.error("Group %s already exists, not creating.", group_name)
                 os._exit(1)
@@ -238,17 +260,8 @@ class Users:
                 os._exit(1)
 
             os._exit(0)
-
-        try:
-            status = os.waitpid(childpid, 0)[1]
-        except OSError as e:
-            log.critical("exception from waitpid while creating a group: %s %s", e.errno, e.strerror)
-            return False
-
-        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
-            return True
         else:
-            return False
+            return self._finishChroot(childpid)
 
     def createUser (self, user_name, *args, **kwargs):
         """Create a new user on the system with the given name.  Optional kwargs:
@@ -279,17 +292,8 @@ class Users:
            gid       -- The GID for the new user.  If none is given, the next
                         available one is used.
         """
-        childpid = os.fork()
-        root = kwargs.get("root", ROOT_PATH)
-
-        if not childpid:
-            if not root in ["","/"]:
-                os.chroot(root)
-                os.chdir("/")
-                del(os.environ["LIBUSER_CONF"])
-
-            self.admin = libuser.admin()
-
+        childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH))
+        if childpid == 0:
             if self.admin.lookupUserByName(user_name):
                 log.error("User %s already exists, not creating.", user_name)
                 os._exit(1)
@@ -390,44 +394,19 @@ class Users:
                 os._exit(1)
 
             os._exit(0)
-
-        try:
-            status = os.waitpid(childpid, 0)[1]
-        except OSError as e:
-            log.critical("exception from waitpid while creating a user: %s %s", e.errno, e.strerror)
-            return False
-
-        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
-            return True
         else:
-            return False
+            return self._finishChroot(childpid)
 
     def checkUserExists(self, username, root=ROOT_PATH):
-        childpid = os.fork()
-
-        if not childpid:
-            if not root in ["","/"]:
-                os.chroot(root)
-                os.chdir("/")
-                del(os.environ["LIBUSER_CONF"])
-
-            self.admin = libuser.admin()
+        childpid = self._prepareChroot(root)
 
+        if childpid == 0:
             if self.admin.lookupUserByName(username):
                 os._exit(0)
             else:
                 os._exit(1)
-
-        try:
-            status = os.waitpid(childpid, 0)[1]
-        except OSError as e:
-            log.critical("exception from waitpid while creating a user: %s %s", e.errno, e.strerror)
-            return False
-
-        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
-            return True
         else:
-            return False
+            return self._finishChroot(childpid)
 
     def setUserPassword(self, username, password, isCrypted, lock, algo=None):
         user = self.admin.lookupUserByName(username)
-- 
1.8.3.1


>From 4692bc5e1eaa5cf1be10d41b351b54d9efc462cf Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Fri, 4 Apr 2014 20:50:41 -0400
Subject: [PATCH 3/7] users: Add root= keyword argument to
 set{User,Root}Password

I'm honestly not sure why this isn't broken with the current install
path.  Every other method has the fork()/chroot() dance around it, so
we should do it here too.
---
 pyanaconda/users.py | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index 93b84b5..3c1c8da 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -408,19 +408,24 @@ class Users:
         else:
             return self._finishChroot(childpid)
 
-    def setUserPassword(self, username, password, isCrypted, lock, algo=None):
-        user = self.admin.lookupUserByName(username)
+    def setUserPassword(self, username, password, isCrypted, lock, algo=None, root=ROOT_PATH):
+        childpid = self._prepareChroot(root)
 
-        if isCrypted:
-            self.admin.setpassUser(user, password, True)
-        else:
-            self.admin.setpassUser(user, cryptPassword(password, algo=algo), True)
+        if childpid == 0:
+            user = self.admin.lookupUserByName(username)
 
-        if lock:
-            self.admin.lockUser(user)
+            if isCrypted:
+                self.admin.setpassUser(user, password, True)
+            else:
+                self.admin.setpassUser(user, cryptPassword(password, algo=algo), True)
 
-        user.set(libuser.SHADOWLASTCHANGE, "")
-        self.admin.modifyUser(user)
+            if lock:
+                self.admin.lockUser(user)
+
+            user.set(libuser.SHADOWLASTCHANGE, "")
+            self.admin.modifyUser(user)
+        else:
+            return self._finishChroot(childpid)
 
-    def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None):
-        return self.setUserPassword("root", password, isCrypted, isLocked, algo)
+    def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None, root=ROOT_PATH):
+        return self.setUserPassword("root", password, isCrypted, isLocked, algo, root)
-- 
1.8.3.1


>From 60e2efb1bb8a870d78e42c9235b13b6e949a6be5 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Wed, 19 Mar 2014 06:06:12 -0400
Subject: [PATCH 4/7] WIP Anaconda+OSTree

Conflicts:
	pyanaconda/__init__.py
	pyanaconda/bootloader.py
	pyanaconda/kickstart.py
	pyanaconda/packaging/__init__.py
	pyanaconda/ui/gui/hubs/__init__.py
	pyanaconda/users.py

Conflicts:
	pyanaconda/users.py
---
 pyanaconda/anaconda.py                |   5 +-
 pyanaconda/bootloader.py              |  71 ++++++++-----
 pyanaconda/flags.py                   |   3 +-
 pyanaconda/install.py                 |  41 +++++--
 pyanaconda/iutil.py                   |  31 +++++-
 pyanaconda/kickstart.py               |  30 +++---
 pyanaconda/packaging/__init__.py      |  16 +++
 pyanaconda/packaging/ostreepayload.py | 194 ++++++++++++++++++++++++++++++++++
 pyanaconda/users.py                   |  17 +--
 9 files changed, 347 insertions(+), 61 deletions(-)
 create mode 100644 pyanaconda/packaging/ostreepayload.py

diff --git a/pyanaconda/anaconda.py b/pyanaconda/anaconda.py
index 66706f1..03b2a44 100644
--- a/pyanaconda/anaconda.py
+++ b/pyanaconda/anaconda.py
@@ -117,7 +117,10 @@ class Anaconda(object):
             if not klass:
                 from pyanaconda.flags import flags
 
-                if flags.livecdInstall:
+                if flags.ostree or self.ksdata.ostreesetup.osname is not None:
+                    from pyanaconda.packaging.ostreepayload import OSTreePayload
+                    klass = OSTreePayload
+                elif flags.livecdInstall:
                     from pyanaconda.packaging.livepayload import LiveImagePayload
                     klass = LiveImagePayload
                 elif self.ksdata.method.method == "liveimg":
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index f930752..f0aa7f7 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -945,10 +945,13 @@ class BootLoader(object):
 
         self.add_crash_args()
 
-        config_path = os.path.normpath(ROOT_PATH + self.config_file)
+        config_path = os.path.normpath(iutil.getSysroot() + self.config_file)
         if os.access(config_path, os.R_OK):
             os.rename(config_path, config_path + ".anacbak")
 
+        config_parent = os.path.dirname(config_path)
+        if not os.path.isdir(config_parent):
+            os.makedirs(config_parent)
         config = open(config_path, "w")
         self.write_config_header(config)
         self.write_config_images(config)
@@ -1169,7 +1172,7 @@ class GRUB(BootLoader):
 
         if iutil.isConsoleOnVirtualTerminal(self.console):
             splash = "splash.xpm.gz"
-            splash_path = os.path.normpath("%s/boot/%s/%s" % (ROOT_PATH,
+            splash_path = os.path.normpath("%s/boot/%s/%s" % (iutil.getSysroot(),
                                                         self.splash_dir,
                                                         splash))
             if os.access(splash_path, os.R_OK):
@@ -1222,7 +1225,7 @@ class GRUB(BootLoader):
 
     def write_device_map(self):
         """ Write out a device map containing all supported devices. """
-        map_path = os.path.normpath(ROOT_PATH + self.device_map_file)
+        map_path = os.path.normpath(iutil.getSysroot() + self.device_map_file)
         if os.access(map_path, os.R_OK):
             os.rename(map_path, map_path + ".anacbak")
 
@@ -1238,7 +1241,7 @@ class GRUB(BootLoader):
         super(GRUB, self).write_config_post()
 
         # make symlink for menu.lst (grub's default config file name)
-        menu_lst = "%s%s/menu.lst" % (ROOT_PATH, self.config_dir)
+        menu_lst = "%s%s/menu.lst" % (iutil.getSysroot(), self.config_dir)
         if os.access(menu_lst, os.R_OK):
             try:
                 os.rename(menu_lst, menu_lst + '.anacbak')
@@ -1251,7 +1254,7 @@ class GRUB(BootLoader):
             log.error("failed to create grub menu.lst symlink: %s", e)
 
         # make symlink to grub.conf in /etc since that's where configs belong
-        etc_grub = "%s/etc/%s" % (ROOT_PATH, self._config_file)
+        etc_grub = "%s/etc/%s" % (iutil.getSysroot(), self._config_file)
         if os.access(etc_grub, os.R_OK):
             try:
                 os.unlink(etc_grub)
@@ -1441,7 +1444,7 @@ class GRUB2(GRUB):
 
     def write_device_map(self):
         """ Write out a device map containing all supported devices. """
-        map_path = os.path.normpath(ROOT_PATH + self.device_map_file)
+        map_path = os.path.normpath(iutil.getSysroot() + self.device_map_file)
         if os.access(map_path, os.R_OK):
             os.rename(map_path, map_path + ".anacbak")
 
@@ -1466,7 +1469,7 @@ class GRUB2(GRUB):
         dev_map.close()
 
     def write_defaults(self):
-        defaults_file = "%s%s" % (ROOT_PATH, self.defaults_file)
+        defaults_file = "%s%s" % (iutil.getSysroot(), self.defaults_file)
         defaults = open(defaults_file, "w+")
         defaults.write("GRUB_TIMEOUT=%d\n" % self.timeout)
         defaults.write("GRUB_DISTRIBUTOR=\"$(sed 's, release .*$,,g' /etc/system-release)\"\n")
@@ -1510,7 +1513,7 @@ class GRUB2(GRUB):
         if not self.password and not self.encrypted_password:
             return
 
-        users_file = ROOT_PATH + "/etc/grub.d/01_users"
+        users_file = iutil.getSysroot() + "/etc/grub.d/01_users"
         header = open(users_file, "w")
         header.write("#!/bin/sh -e\n\n")
         header.write("cat << \"EOF\"\n")
@@ -1894,8 +1897,9 @@ class Yaboot(YabootBase):
                 log.error("failed to create /etc/yaboot.conf symlink: %s", e)
 
     def write_config(self):
-        if not os.path.isdir(ROOT_PATH + self.config_dir):
-            os.mkdir(ROOT_PATH + self.config_dir)
+        configdir = iutil.getSysroot() + self.config_dir
+        if not os.path.isdir(configdir):
+            os.mkdir(configdir)
 
         # this writes the config
         super(Yaboot, self).write_config()
@@ -2222,17 +2226,20 @@ class EXTLINUX(BootLoader):
             config.write(stanza)
 
     def write_config_header(self, config):
+        # FIXME re-add this
+        # "ui menu.c32\n\n"
+        # after this is fixed:
+        # https://bugzilla.gnome.org/show_bug.cgi?id=726757
         header = ("# extlinux.conf generated by anaconda\n\n"
-                  "ui menu.c32\n\n"
                   "menu autoboot Welcome to %(productName)s. Automatic boot in # second{,s}. Press a key for options.\n"
                   "menu title %(productName)s Boot Options.\n"
                   "menu hidden\n\n"
                   "timeout %(timeout)d\n"
                   "#totaltimeout 9000\n\n"
-                  "default %(default)s\n\n"
-                  % { "productName": productName, "timeout": self.timeout *10,
-                     "default": self.image_label(self.default)})
+                  % { "productName": productName, "timeout": self.timeout *10 })
         config.write(header)
+        if self.default is not None:
+            config.write("default %(default)s\n\n" % { "default" : self.image_label(self.default) })
         self.write_config_password(config)
 
     def write_config_password(self, config):
@@ -2241,7 +2248,7 @@ class EXTLINUX(BootLoader):
             config.write("menu notabmsg Press [Tab] and enter the password to edit options")
 
     def write_config_post(self):
-        etc_extlinux = os.path.normpath(ROOT_PATH + "/etc/" + self._config_file)
+        etc_extlinux = os.path.normpath(iutil.getSysroot() + "/etc/" + self._config_file)
         if not os.access(etc_extlinux, os.R_OK):
             try:
                 os.symlink("../boot/%s" % self._config_file, etc_extlinux)
@@ -2331,6 +2338,21 @@ def writeSysconfigKernel(storage, version, instClass):
         f.write("HYPERVISOR_ARGS=logging=vga,serial,memory\n")
     f.close()
 
+def writeBootLoaderFinal(storage, payload, instClass, ksdata):
+    """ Do the final write of the bootloader. """
+
+    from pyanaconda.errors import errorHandler, ERROR_RAISE
+
+    # set up dracut/fips boot args
+    # XXX FIXME: do this from elsewhere?
+    storage.bootloader.set_boot_args(storage=storage,
+                                     payload=payload)
+    try:
+        storage.bootloader.write()
+    except BootLoaderError as e:
+        if errorHandler.cb(e) == ERROR_RAISE:
+            raise
+
 def writeBootLoader(storage, payload, instClass, ksdata):
     """ Write bootloader configuration to disk.
 
@@ -2346,6 +2368,13 @@ def writeBootLoader(storage, payload, instClass, ksdata):
         stage2_device = storage.bootloader.stage2_device
         log.info("bootloader stage2 target device is %s", stage2_device.name)
 
+    if payload.handlesBootloaderConfiguration:
+        if storage.bootloader.skip_bootloader:
+            log.info("skipping bootloader install per user request")
+            return
+        writeBootLoaderFinal(storage, payload, instClass, ksdata)
+        return
+
     # get a list of installed kernel packages
     kernel_versions = payload.kernelVersionList + payload.rescueKernelList
     if not kernel_versions:
@@ -2390,14 +2419,4 @@ def writeBootLoader(storage, payload, instClass, ksdata):
                                          label=label, short=short)
         storage.bootloader.add_image(image)
 
-    # set up dracut/fips boot args
-    # XXX FIXME: do this from elsewhere?
-    storage.bootloader.set_boot_args(storage=storage,
-                                     payload=payload)
-
-    try:
-        storage.bootloader.write()
-    except BootLoaderError as e:
-        if errorHandler.cb(e) == ERROR_RAISE:
-            raise
-
+    writeBootLoaderFinal(storage, payload, instClass, ksdata)
diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py
index e6d6e4a..4a7da01 100644
--- a/pyanaconda/flags.py
+++ b/pyanaconda/flags.py
@@ -68,6 +68,7 @@ class Flags(object):
         self.leavebootorder = False
         self.testing = False
         self.dnf = False
+        self.ostree = False
         self.mpathFriendlyNames = True
         # ksprompt is whether or not to prompt for missing ksdata
         self.ksprompt = True
@@ -80,7 +81,7 @@ class Flags(object):
 
     def read_cmdline(self):
         for f in ("selinux", "debug", "leavebootorder", "testing", "extlinux",
-                  "gpt", "dnf"):
+                  "gpt", "dnf", "ostree"):
             self.set_cmdline_bool(f)
 
         if "rpmarch" in self.cmdline:
diff --git a/pyanaconda/install.py b/pyanaconda/install.py
index a7e9d38..ce3eca9 100644
--- a/pyanaconda/install.py
+++ b/pyanaconda/install.py
@@ -25,17 +25,26 @@ from blivet import turnOnFilesystems
 from pyanaconda.bootloader import writeBootLoader
 from pyanaconda.progress import progress_report, progressQ
 from pyanaconda.users import createLuserConf, getPassAlgo, Users
+from pyanaconda import iutil
 from pyanaconda import flags
 from pyanaconda import timezone
 from pyanaconda.i18n import _
 from pyanaconda.threads import threadMgr
 import logging
+import blivet
 log = logging.getLogger("anaconda")
 
 def _writeKS(ksdata):
     import os
 
-    path = ROOT_PATH + "/root/anaconda-ks.cfg"
+    roothome = os.path.join(iutil.getSysroot(), "root")
+    if os.path.islink(roothome) and not os.path.isdir(roothome):
+        # In the OSTree case, /root -> /var/roothome, but
+        # it doesn't exist yet.  If we created it here,
+        # we'd need to ensure it was labeled correctly.
+        # Punt on that for now and just stick it in /var.
+        roothome = os.path.join(iutil.getSysroot(), "var")
+    path = os.path.join(roothome, "anaconda-ks.cfg")
 
     # Clear out certain sensitive information that kickstart doesn't have a
     # way of representing encrypted.
@@ -165,7 +174,8 @@ def doInstall(storage, payload, ksdata, instClass):
     payload.preStorage()
 
     turnOnFilesystems(storage, mountOnly=flags.flags.dirInstall)
-    if not flags.flags.livecdInstall and not flags.flags.dirInstall:
+    write_storage_late = flags.flags.livecdInstall or ksdata.ostreesetup.osname
+    if not write_storage_late and not flags.flags.dirInstall:
         storage.write()
 
     # Do packaging.
@@ -188,15 +198,30 @@ def doInstall(storage, payload, ksdata, instClass):
     payload.preInstall(packages=packages, groups=payload.languageGroups())
     payload.install()
 
-    if flags.flags.livecdInstall:
-        storage.write()
-
-    with progress_report(_("Performing post-installation setup tasks")):
-        payload.postInstall()
-
+    if write_storage_late:
+        if iutil.getSysroot() != ROOT_PATH:
+            blivet.targetSysroot = iutil.getSysroot()
+            storage.write()
+            # Now that we have the FS layout in the target,
+            # umount things that were in the legacy sysroot,
+            # and put them in the target root, except for the
+            # physical /
+            storage.umountFilesystems()
+            rootmnt = storage.mountpoints.get('/')
+            rootmnt.setup()
+            # Explicitly mount the root on the physical sysroot
+            rootmnt.format.setup(rootmnt.format.options, chroot=ROOT_PATH)
+            # But everything else goes in the target root
+            storage.mountFilesystems(skipRoot=True)
+        else:
+            storage.write()
+    
     # Do bootloader.
     if willInstallBootloader:
         with progress_report(_("Installing bootloader")):
             writeBootLoader(storage, payload, instClass, ksdata)
 
+    with progress_report(_("Performing post-installation setup tasks")):
+        payload.postInstall()
+
     progressQ.send_complete()
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 0010ffe..18e8368 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -109,6 +109,23 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
 
     return (proc.returncode, output_string)
 
+_sysroot = ROOT_PATH
+def setSysroot(path):
+    global _sysroot
+    _sysroot = path
+
+def getSysroot():
+    return _sysroot
+
+def execInSysimageRoot(command, argv, **kwargs):
+    """ Run a command from the installed target root.
+        @param command The command to run
+        @param argv The argument list
+    """
+
+    argv = [command] + argv
+    return _run_program(argv, root=_sysroot, **kwargs)[0]
+
 def execWithRedirect(command, argv, stdin=None, stdout=None,
                      root='/', env_prune=None, log_output=True, binary_output=False):
     """ Run an external program and redirect the output to a file.
@@ -127,8 +144,14 @@ def execWithRedirect(command, argv, stdin=None, stdout=None,
                  command, " ".join(argv))
         return 0
 
+    # Transparently redirect callers requesting root=ROOT_PATH to the
+    # configured system root.
+    target_root = root
+    if target_root == ROOT_PATH:
+        target_root = _sysroot
+
     argv = [command] + argv
-    return _run_program(argv, stdin=stdin, stdout=stdout, root=root, env_prune=env_prune,
+    return _run_program(argv, stdin=stdin, stdout=stdout, root=target_root, env_prune=env_prune,
             log_output=log_output, binary_output=binary_output)[0]
 
 def execWithCapture(command, argv, stdin=None, root='/', log_output=True):
@@ -172,6 +195,12 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None):
             queue.put(line.strip())
         out.close()
 
+    # Transparently redirect callers requesting root=ROOT_PATH to the
+    # configured system root.
+    target_root = root
+    if target_root == ROOT_PATH:
+        target_root = _sysroot
+
     def chroot():
         if root and root != '/':
             os.chroot(root)
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index c06dcee..1d89037 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -240,8 +240,8 @@ class Authconfig(commands.authconfig.FC3_Authconfig):
         args = ["--update", "--nostart"] + shlex.split(self.authconfig)
 
         if not flags.automatedInstall and \
-           (os.path.exists(ROOT_PATH + "/lib64/security/pam_fprintd.so") or \
-            os.path.exists(ROOT_PATH + "/lib/security/pam_fprintd.so")):
+           (os.path.exists(iutil.getSysroot() + "/lib64/security/pam_fprintd.so") or \
+            os.path.exists(iutil.getSysroot() + "/lib/security/pam_fprintd.so")):
             args += ["--enablefingerprint"]
 
         try:
@@ -482,7 +482,7 @@ class Realm(commands.realm.F19_Realm):
             # no explicit password arg using implicit --no-password
             pw_args = ["--no-password"]
 
-        argv = ["realm", "join", "--install", ROOT_PATH, "--verbose"] + \
+        argv = ["realm", "join", "--install", iutil.getSysroot(), "--verbose"] + \
                pw_args + self.join_args
         rc = -1
         try:
@@ -502,7 +502,6 @@ class Realm(commands.realm.F19_Realm):
 
         log.info("Joined realm %s", self.join_realm)
 
-
 class ClearPart(commands.clearpart.F17_ClearPart):
     def parse(self, args):
         retval = commands.clearpart.F17_ClearPart.parse(self, args)
@@ -592,7 +591,7 @@ class Firewall(commands.firewall.F20_Firewall):
             args += [ "--service=%s" % (service,) ]
 
         cmd = "/usr/bin/firewall-offline-cmd"
-        if not os.path.exists(ROOT_PATH+cmd):
+        if not os.path.exists(iutil.getSysroot()+cmd):
             if self.enabled:
                 msg = _("%s is missing. Cannot setup firewall.") % (cmd,)
                 raise KickstartError(msg)
@@ -611,7 +610,7 @@ class Firstboot(commands.firstboot.FC3_Firstboot):
         if self.firstboot == FIRSTBOOT_SKIP:
             action = "disable"
         elif self.firstboot == FIRSTBOOT_RECONFIG:
-            f = open(ROOT_PATH + "/etc/reconfigSys", "w+")
+            f = open(iutil.getSysroot() + "/etc/reconfigSys", "w+")
             f.close()
 
         iutil.execWithRedirect("systemctl", [action, "initial-setup-graphical.service",
@@ -622,7 +621,6 @@ class Group(commands.group.F12_Group):
     def execute(self, storage, ksdata, instClass, users):
         for grp in self.groupList:
             kwargs = grp.__dict__
-            kwargs.update({"root": ROOT_PATH})
             users.createGroup(grp.name, **kwargs)
 
 class IgnoreDisk(commands.ignoredisk.RHEL6_IgnoreDisk):
@@ -691,7 +689,7 @@ class IscsiName(commands.iscsiname.FC6_IscsiName):
 
 class Lang(commands.lang.F19_Lang):
     def execute(self, *args, **kwargs):
-        localization.write_language_configuration(self, ROOT_PATH)
+        localization.write_language_configuration(self, iutil.getSysroot())
 
 # no overrides needed here
 Eula = commands.eula.F20_Eula
@@ -924,7 +922,7 @@ class Logging(commands.logging.FC6_Logging):
 
 class Network(commands.network.F21_Network):
     def execute(self, storage, ksdata, instClass):
-        network.write_network_config(storage, ksdata, instClass, ROOT_PATH)
+        network.write_network_config(storage, ksdata, instClass, iutil.getSysroot())
 
 class MultiPath(commands.multipath.FC6_MultiPath):
     def parse(self, args):
@@ -1375,7 +1373,7 @@ class SELinux(commands.selinux.FC3_SELinux):
             return
 
         try:
-            selinux_cfg = SimpleConfigFile(ROOT_PATH+"/etc/selinux/config")
+            selinux_cfg = SimpleConfigFile(iutil.getSysroot()+"/etc/selinux/config")
             selinux_cfg.read()
             selinux_cfg.set(("SELINUX", selinux_states[self.selinux]))
             selinux_cfg.write()
@@ -1445,11 +1443,11 @@ class Timezone(commands.timezone.F18_Timezone):
                         "back to default (America/New_York).", self.timezone)
             self.timezone = "America/New_York"
 
-        timezone.write_timezone_config(self, ROOT_PATH)
+        timezone.write_timezone_config(self, iutil.getSysroot())
 
         # write out NTP configuration (if set)
         if not self.nontp and self.ntpservers:
-            chronyd_conf_path = os.path.normpath(ROOT_PATH + ntp.NTP_CONFIG_FILE)
+            chronyd_conf_path = os.path.normpath(iutil.getSysroot() + ntp.NTP_CONFIG_FILE)
             try:
                 ntp.save_servers_to_config(self.ntpservers,
                                            conf_file_path=chronyd_conf_path)
@@ -1462,7 +1460,7 @@ class User(commands.user.F19_User):
 
         for usr in self.userList:
             kwargs = usr.__dict__
-            kwargs.update({"algo": algo, "root": ROOT_PATH})
+            kwargs.update({"algo": algo})
 
             # If the user password came from a kickstart and it is blank we
             # need to make sure the account is locked, not created with an
@@ -1568,7 +1566,7 @@ class ZFCP(commands.zfcp.F14_ZFCP):
 
 class Keyboard(commands.keyboard.F18_Keyboard):
     def execute(self, *args):
-        keyboard.write_keyboard_config(self, ROOT_PATH)
+        keyboard.write_keyboard_config(self, iutil.getSysroot())
 
 class Upgrade(commands.upgrade.F20_Upgrade):
     # Upgrade is no longer supported. If an upgrade command was included in
@@ -1591,7 +1589,7 @@ class SpokeRegistry(dict):
         return ""
 
     def execute(self, storage, ksdata, instClass, users):
-        path = os.path.join(ROOT_PATH, "var", "lib", "inital-setup")
+        path = os.path.join(iutil.getSysroot(), "var", "lib", "inital-setup")
         try:
             os.makedirs(path, 0755)
         except OSError:
@@ -1806,7 +1804,7 @@ def runPostScripts(scripts):
             del(os.environ[var])
 
     log.info("Running kickstart %%post script(s)")
-    map (lambda s: s.run(ROOT_PATH), postScripts)
+    map (lambda s: s.run(iutil.getSysroot()), postScripts)
     log.info("All kickstart %%post script(s) have been run")
 
 def runPreScripts(scripts):
diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py
index a6262d0..fdedd10 100644
--- a/pyanaconda/packaging/__init__.py
+++ b/pyanaconda/packaging/__init__.py
@@ -147,6 +147,14 @@ class Payload(object):
         """ Reset the instance, not including ksdata. """
         pass
 
+    @property
+    def handlesBootloaderConfiguration(self):
+        """ Set if the payload requires the bootloader be installed
+        but unconfigured before doing an install of the system.  This
+        is used by the OSTreePayload subclass.
+        """
+        return False
+
     ###
     ### METHODS FOR WORKING WITH REPOSITORIES
     ###
@@ -297,6 +305,14 @@ class Payload(object):
     ###
     ### METHODS FOR WORKING WITH PACKAGES
     ###
+    @property
+    def supportsPackages(self):
+        return False
+
+    @property
+    def packages(self):
+        raise NotImplementedError()
+
     def packageSelected(self, pkgid):
         return pkgid in self.data.packages.packageList
 
diff --git a/pyanaconda/packaging/ostreepayload.py b/pyanaconda/packaging/ostreepayload.py
new file mode 100644
index 0000000..f2ed03e
--- /dev/null
+++ b/pyanaconda/packaging/ostreepayload.py
@@ -0,0 +1,194 @@
+# ostreepayload.py
+# Deploy OSTree trees to target
+#
+# Copyright (C) 2012,2014  Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Colin Walters <walters@xxxxxxxxxx>
+#
+
+import shutil
+
+from . import *
+
+
+from pyanaconda.constants import *
+from pyanaconda.flags import flags
+
+from pyanaconda import iutil
+from pyanaconda.i18n import _
+from pyanaconda.progress import progressQ
+from gi.repository import GLib
+from gi.repository import Gio
+
+from blivet.size import Size
+
+import logging
+log = logging.getLogger("anaconda")
+
+from pyanaconda.errors import *
+#from pyanaconda.progress import progress
+
+class OSTreePayload(ArchivePayload):
+    """ A OSTreePayload deploys a tree onto the target system. """
+    def __init__(self, data):
+        super(OSTreePayload, self).__init__(data)
+
+    def setup(self, storage, instClass):
+        super(OSTreePayload, self).setup(storage, instClass)
+
+    @property
+    def handlesBootloaderConfiguration(self):
+        return True
+
+    @property
+    def kernelVersionList(self):
+        # OSTree handles bootloader configuration
+        return []
+
+    @property
+    def spaceRequired(self):
+        # We don't have this data with OSTree at the moment
+        return Size(500*1000*1000)
+
+    def _safeExecWithRedirect(self, cmd, argv, **kwargs):
+        """Like iutil.execWithRedirect, but treat errors as fatal"""
+        rc = iutil.execWithRedirect(cmd, argv, **kwargs)
+        if rc != 0:
+            exn = PayloadInstallError("%s %s exited with code %d" % (cmd, argv, rc))
+            if errorHandler.cb(exn) == ERROR_RAISE:
+                raise exn
+
+    def _pullProgressCb(self, asyncProgress):
+        status = asyncProgress.get_status()
+        outstanding_fetches = asyncProgress.get_uint('outstanding-fetches')
+        if status:
+            progressQ.send_message(status)
+        elif outstanding_fetches > 0:
+            bytes_transferred = asyncProgress.get_uint64('bytes-transferred')
+            fetched = asyncProgress.get_uint('fetched')
+            requested = asyncProgress.get_uint('requested')
+            formatted_bytes = GLib.format_size_full(bytes_transferred, 0)
+
+            if requested == 0:
+                percent = 0.0
+            else:
+                percent = (fetched*1.0 / requested) * 100
+            
+            progressQ.send_message("Receiving objects: %d%% (%d/%d) %s" % (percent, fetched, requested, formatted_bytes))
+        else:
+            progressQ.send_message("Writing objects")
+
+    def install(self):
+        cancellable = None
+        from gi.repository import OSTree
+        ostreesetup = self.data.ostreesetup
+        log.info("executing ostreesetup=%r" % (ostreesetup, ))
+
+        # Initialize the filesystem - this will create the repo as well
+        self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH,
+                                              "init-fs", ROOT_PATH])
+
+        repo_arg = "--repo=" + ROOT_PATH + '/ostree/repo'
+
+        # Set up the chosen remote
+        remote_args = [repo_arg, "remote", "add"]
+        if ostreesetup.noGpg:
+            remote_args.append("--set=gpg-verify=false")
+        remote_args.extend([ostreesetup.remote,
+                            ostreesetup.url])
+        self._safeExecWithRedirect("ostree", remote_args)
+
+        sysroot_path = Gio.File.new_for_path(ROOT_PATH)
+        sysroot = OSTree.Sysroot.new(sysroot_path)
+        sysroot.load(cancellable)
+
+        repo = sysroot.get_repo(None)[1]
+        progressQ.send_message(_("Starting pull of %s from %s") % (ostreesetup.ref, ostreesetup.remote))
+
+        progress = OSTree.AsyncProgress.new()
+        progress.connect('changed', self._pullProgressCb)
+        repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable)
+
+        self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH,
+                                              "os-init", ostreesetup.osname])
+            
+        admin_deploy_args = ["admin", "--sysroot=" + ROOT_PATH,
+                             "deploy", "--os=" + ostreesetup.osname]
+
+        admin_deploy_args.append(ostreesetup.osname + ':' + ostreesetup.ref)
+
+        log.info("ostree admin deploy starting")
+        self._safeExecWithRedirect("ostree", admin_deploy_args)
+        log.info("ostree admin deploy complete")
+
+        ostree_sysroot_path = os.path.join(ROOT_PATH, 'ostree/deploy', ostreesetup.osname, 'current')
+        iutil.setSysroot(ostree_sysroot_path)
+        
+    def postInstall(self):
+        super(OSTreePayload, self).postInstall()
+        
+        bootmnt = self.storage.mountpoints.get('/boot')
+        if bootmnt is not None:
+            # Okay, now /boot goes back to the physical /, since
+            # that's where ostree --sysroot expects to find it.  The
+            # real fix for the madness is for tools like extlinux to
+            # have a --sysroot option too.
+            bootmnt.format.teardown()
+            bootmnt.teardown()
+            bootmnt.format.setup(bootmnt.format.options, chroot=ROOT_PATH)
+
+        # Set up the sysroot bind mount after this so that we have the
+        # tmp -> sysroot/tmp when executing %post scripts and the
+        # like.
+        self._safeExecWithRedirect("mount", ["--bind", ROOT_PATH, os.path.join(iutil.getSysroot(), 'sysroot')])
+
+        # FIXME - Move extlinux.conf to syslinux.cfg, since that's
+        # what OSTree knows about.
+        sysroot_boot_extlinux = os.path.join(ROOT_PATH, 'boot/extlinux')
+        sysroot_boot_syslinux = os.path.join(ROOT_PATH, 'boot/syslinux')
+        sysroot_boot_loader = os.path.join(ROOT_PATH, 'boot/loader')
+        if os.path.isdir(sysroot_boot_extlinux):
+            assert os.path.isdir(sysroot_boot_loader)
+            orig_extlinux_conf = os.path.join(sysroot_boot_extlinux, 'extlinux.conf')
+            target_syslinux_cfg = os.path.join(sysroot_boot_loader, 'syslinux.cfg')
+            log.info("Moving %s -> %s" % (orig_extlinux_conf, target_syslinux_cfg))
+            os.rename(orig_extlinux_conf, target_syslinux_cfg)
+            # A compatibility bit for OSTree
+            os.mkdir(sysroot_boot_syslinux)
+            os.symlink('../loader/syslinux.cfg', os.path.join(sysroot_boot_syslinux, 'syslinux.cfg'))
+            # And *also* tell syslinux that the config is really in /boot/loader
+            os.symlink('loader/syslinux.cfg', os.path.join(ROOT_PATH, 'boot/syslinux.cfg'))
+
+        # FIXME - stub out firewalld
+        firewalloffline = os.path.join(iutil.getSysroot(), 'usr/bin/firewall-offline-cmd')
+        if not os.path.exists(firewalloffline):
+            os.symlink('true', firewalloffline)
+
+        # OSTree owns the bootloader configuration, so here we give it
+        # the argument list we computed from storage, architecture and
+        # such.
+        set_kargs_args = ["admin", "--sysroot=" + ROOT_PATH,
+                          "instutil", "set-kargs"]
+        set_kargs_args.extend(self.storage.bootloader.boot_args)
+        set_kargs_args.append("root=" + self.storage.rootDevice.fstabSpec)
+        self._safeExecWithRedirect("ostree", set_kargs_args)
+        # This command iterates over all files we might have created
+        # and ensures they're labeled. It's like running
+        # chroot(ROOT_PATH) + fixfiles, except with a better name and
+        # semantics.
+        self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH,
+                                              "instutil", "selinux-ensure-labeled", ROOT_PATH, ""])
diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index 3c1c8da..70da4f0 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -209,6 +209,7 @@ class Users:
         import sys
 
         childpid = os.fork()
+
         if not childpid:
             if not root in ["","/"]:
                 os.chroot(root)
@@ -242,7 +243,7 @@ class Users:
                         Defaults to /mnt/sysimage.
         """
 
-        childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH))
+        childpid = self._prepareChroot(kwargs.get("root", iutil.getSysroot()))
         if childpid == 0:
             if self.admin.lookupGroupByName(group_name):
                 log.error("Group %s already exists, not creating.", group_name)
@@ -292,7 +293,7 @@ class Users:
            gid       -- The GID for the new user.  If none is given, the next
                         available one is used.
         """
-        childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH))
+        childpid = self._prepareChroot(kwargs.get("root", iutil.getSysroot()))
         if childpid == 0:
             if self.admin.lookupUserByName(user_name):
                 log.error("User %s already exists, not creating.", user_name)
@@ -397,8 +398,8 @@ class Users:
         else:
             return self._finishChroot(childpid)
 
-    def checkUserExists(self, username, root=ROOT_PATH):
-        childpid = self._prepareChroot(root)
+    def checkUserExists(self, username):
+        childpid = self._prepareChroot(iutil.getSysroot())
 
         if childpid == 0:
             if self.admin.lookupUserByName(username):
@@ -408,8 +409,8 @@ class Users:
         else:
             return self._finishChroot(childpid)
 
-    def setUserPassword(self, username, password, isCrypted, lock, algo=None, root=ROOT_PATH):
-        childpid = self._prepareChroot(root)
+    def setUserPassword(self, username, password, isCrypted, lock, algo=None):
+        childpid = self._prepareChroot(iutil.getSysroot())
 
         if childpid == 0:
             user = self.admin.lookupUserByName(username)
@@ -427,5 +428,5 @@ class Users:
         else:
             return self._finishChroot(childpid)
 
-    def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None, root=ROOT_PATH):
-        return self.setUserPassword("root", password, isCrypted, isLocked, algo, root)
+    def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None):
+        return self.setUserPassword("root", password, isCrypted, isLocked, algo)
-- 
1.8.3.1


>From 5582f1febcdb0cd126b1f0c630f8114c9ca42866 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Sun, 13 Apr 2014 23:07:38 -0700
Subject: [PATCH 5/7] fix gpg variable

---
 pyanaconda/packaging/ostreepayload.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyanaconda/packaging/ostreepayload.py b/pyanaconda/packaging/ostreepayload.py
index f2ed03e..1fa6e91 100644
--- a/pyanaconda/packaging/ostreepayload.py
+++ b/pyanaconda/packaging/ostreepayload.py
@@ -106,7 +106,8 @@ class OSTreePayload(ArchivePayload):
 
         # Set up the chosen remote
         remote_args = [repo_arg, "remote", "add"]
-        if ostreesetup.noGpg:
+        if ((hasattr(ostreesetup, 'noGpg') and ostreesetup.noGpg) or
+            (hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg)):
             remote_args.append("--set=gpg-verify=false")
         remote_args.extend([ostreesetup.remote,
                             ostreesetup.url])
-- 
1.8.3.1


>From 5da62a104b0e5224f7674862d8755e8a24dbec61 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Thu, 17 Apr 2014 15:38:04 -0700
Subject: [PATCH 6/7] kickstart: use iutil.getSysroot()

---
 pyanaconda/kickstart.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index 1d89037..3113dd1 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -230,7 +230,7 @@ def getAvailableDiskSpace(storage):
 class Authconfig(commands.authconfig.FC3_Authconfig):
     def execute(self, *args):
         cmd = "/usr/sbin/authconfig"
-        if not os.path.lexists(ROOT_PATH+cmd):
+        if not os.path.lexists(iutil.getSysroot()+cmd):
             if self.seen:
                 msg = _("%s is missing. Cannot setup authentication.") % cmd
                 raise KickstartError(msg)
-- 
1.8.3.1


>From 3a55b3ad4c10b533ade347f9d264da052b7e347c Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@xxxxxxxxxx>
Date: Thu, 17 Apr 2014 15:42:20 -0700
Subject: [PATCH 7/7] ostreepayload: Add more progressQ

---
 pyanaconda/packaging/ostreepayload.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/pyanaconda/packaging/ostreepayload.py b/pyanaconda/packaging/ostreepayload.py
index 1fa6e91..5dd2b2d 100644
--- a/pyanaconda/packaging/ostreepayload.py
+++ b/pyanaconda/packaging/ostreepayload.py
@@ -124,6 +124,8 @@ class OSTreePayload(ArchivePayload):
         progress.connect('changed', self._pullProgressCb)
         repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable)
 
+        progressQ.send_message(_("Preparing deployment of %s") % (ostreesetup.ref, ))
+
         self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH,
                                               "os-init", ostreesetup.osname])
             
@@ -133,8 +135,10 @@ class OSTreePayload(ArchivePayload):
         admin_deploy_args.append(ostreesetup.osname + ':' + ostreesetup.ref)
 
         log.info("ostree admin deploy starting")
+        progressQ.send_message(_("Deployment starting: %s") % (ostreesetup.ref, ))
         self._safeExecWithRedirect("ostree", admin_deploy_args)
         log.info("ostree admin deploy complete")
+        progressQ.send_message(_("Deployment complete: %s") % (ostreesetup.ref, ))
 
         ostree_sysroot_path = os.path.join(ROOT_PATH, 'ostree/deploy', ostreesetup.osname, 'current')
         iutil.setSysroot(ostree_sysroot_path)
-- 
1.8.3.1

_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/anaconda-devel-list

[Index of Archives]     [Kickstart]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]
  Powered by Linux