diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+propellor (4.3.0) unstable; urgency=medium
+
+  * DiskImage: Removed grubBooted; properties that used to need it as a
+    parameter now look at Info about the bootloader that is installed in
+    the chroot that the disk image is created from.
+    (API change)
+
+ -- Joey Hess <id@joeyh.name>  Wed, 05 Jul 2017 21:04:04 -0400
+
 propellor (4.2.0) unstable; urgency=medium
 
   * DiskImage.grubBooted no longer takes a BIOS parameter,
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+propellor (4.3.0) unstable; urgency=medium
+
+  * DiskImage: Removed grubBooted; properties that used to need it as a
+    parameter now look at Info about the bootloader that is installed in
+    the chroot that the disk image is created from.
+    (API change)
+
+ -- Joey Hess <id@joeyh.name>  Wed, 05 Jul 2017 21:04:04 -0400
+
 propellor (4.2.0) unstable; urgency=medium
 
   * DiskImage.grubBooted no longer takes a BIOS parameter,
diff --git a/joeyconfig.hs b/joeyconfig.hs
--- a/joeyconfig.hs
+++ b/joeyconfig.hs
@@ -75,7 +75,7 @@
 	& Apt.installed ["ssh"]
 	& User.hasPassword (User "root")
   where
-	postinstall :: Property DebianLike
+	postinstall :: Property (HasInfo + DebianLike)
 	postinstall = propertyList "fixing up after clean install" $ props
 		& OS.preserveRootSshAuthorized
 		& OS.preserveResolvConf
@@ -99,7 +99,7 @@
 
 	& imageBuilt "/srv/propellor-disk.img"
 		(Chroot.hostChroot demo (Chroot.Debootstrapped mempty))
-		MSDOS grubBooted
+		MSDOS
 		[ partition EXT2 `mountedAt` "/boot"
 			`setFlag` BootFlag
 		, partition EXT4 `mountedAt` "/"
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 4.2.0
+Version: 4.3.0
 Cabal-Version: >= 1.20
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
@@ -177,6 +177,7 @@
     Propellor.EnsureProperty
     Propellor.Exception
     Propellor.Types
+    Propellor.Types.Bootloader
     Propellor.Types.ConfigurableValue
     Propellor.Types.Core
     Propellor.Types.Chroot
diff --git a/src/Propellor/Property/DiskImage.hs b/src/Propellor/Property/DiskImage.hs
--- a/src/Propellor/Property/DiskImage.hs
+++ b/src/Propellor/Property/DiskImage.hs
@@ -13,11 +13,7 @@
 	imageRebuilt,
 	imageBuiltFrom,
 	imageExists,
-	-- * Finalization
-	Finalization,
-	grubBooted,
 	Grub.BIOS(..),
-	noFinalization,
 ) where
 
 import Propellor.Base
@@ -33,6 +29,8 @@
 import Propellor.Property.Fstab (SwapPartition(..), genFstab)
 import Propellor.Property.Partition
 import Propellor.Property.Rsync
+import Propellor.Types.Info
+import Propellor.Types.Bootloader
 import Propellor.Container
 import Utility.Path
 
@@ -71,7 +69,7 @@
 -- > 
 -- > foo = host "foo.example.com" $ props
 -- > 	& imageBuilt "/srv/diskimages/disk.img" mychroot
--- >		MSDOS grubBooted
+-- >		MSDOS
 -- >		[ partition EXT2 `mountedAt` "/boot"
 -- >			`setFlag` BootFlag
 -- >		, partition EXT4 `mountedAt` "/"
@@ -97,7 +95,7 @@
 -- > foo = host "foo.example.com" $ props
 -- >	& imageBuilt "/srv/diskimages/bar-disk.img"
 -- >		(hostChroot bar (Debootstrapped mempty))
--- >		MSDOS grubBooted
+-- >		MSDOS
 -- >		[ partition EXT2 `mountedAt` "/boot"
 -- >			`setFlag` BootFlag
 -- >		, partition EXT4 `mountedAt` "/"
@@ -111,17 +109,17 @@
 -- >	& Apt.installed ["linux-image-amd64"]
 -- >	& Grub.installed PC
 -- >	& hasPassword (User "root")
-imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
+imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
 imageBuilt = imageBuilt' False
 
 -- | Like 'built', but the chroot is deleted and rebuilt from scratch each
 -- time. This is more expensive, but useful to ensure reproducible results
 -- when the properties of the chroot have been changed.
-imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
+imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
 imageRebuilt = imageBuilt' True
 
-imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
-imageBuilt' rebuild img mkchroot tabletype final partspec =
+imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
+imageBuilt' rebuild img mkchroot tabletype partspec =
 	imageBuiltFrom img chrootdir tabletype final partspec
 		`requires` Chroot.provisioned chroot
 		`requires` (cleanrebuild <!> (doNothing :: Property UnixLike))
@@ -145,6 +143,12 @@
 	-- Only propagate privdata Info from this chroot, nothing else.
 	propprivdataonly (Chroot.Chroot d b ip h) =
 		Chroot.Chroot d b (\c _ -> ip c onlyPrivData) h
+	-- Pick boot loader finalization based on which bootloader is
+	-- installed.
+	final = case fromInfo (containerInfo chroot) of
+		[GrubInstalled] -> grubBooted
+		[] -> unbootable "no bootloader is installed"
+		_ -> unbootable "multiple bootloaders are installed; don't know which to use"
 
 -- | This property is automatically added to the chroot when building a
 -- disk image. It cleans any caches of information that can be omitted;
@@ -323,8 +327,12 @@
 
 	allowservices top = nukeFile (top ++ "/usr/sbin/policy-rc.d")
 
-noFinalization :: Finalization
-noFinalization = \_ _ -> doNothing
+unbootable :: String -> Finalization
+unbootable msg = \_ _ -> property desc $ do
+	warningMessage (desc ++ ": " ++ msg)
+	return FailedChange
+  where
+	desc = "image is not bootable"
 
 -- | Makes grub be the boot loader of the disk image.
 --
diff --git a/src/Propellor/Property/Grub.hs b/src/Propellor/Property/Grub.hs
--- a/src/Propellor/Property/Grub.hs
+++ b/src/Propellor/Property/Grub.hs
@@ -4,6 +4,8 @@
 import qualified Propellor.Property.File as File
 import qualified Propellor.Property.Apt as Apt
 import Propellor.Property.Chroot (inChroot)
+import Propellor.Types.Info
+import Propellor.Types.Bootloader
 
 -- | Eg, \"hd0,0\" or \"xen/xvda1\"
 type GrubDevice = String
@@ -20,7 +22,7 @@
 -- bootloader.
 --
 -- This includes running update-grub, unless it's run in a chroot.
-installed :: BIOS -> Property DebianLike
+installed :: BIOS -> Property (HasInfo + DebianLike)
 installed bios = installed' bios 
 	`onChange` (check (not <$> inChroot) mkConfig)
 
@@ -31,11 +33,11 @@
 	`assume` MadeChange
 
 -- | Installs grub; does not run update-grub.
-installed' :: BIOS -> Property Linux
-installed' bios = (aptinstall `pickOS` unsupportedOS)
+installed' :: BIOS -> Property (HasInfo + DebianLike)
+installed' bios = setInfoProperty aptinstall
+	(toInfo [GrubInstalled])
 	`describe` "grub package installed"
   where
-	aptinstall :: Property DebianLike
 	aptinstall = Apt.installed [debpkg]
 	debpkg = case bios of
 		PC -> "grub-pc"
@@ -66,7 +68,7 @@
 --
 -- The rootdev should be in the form "hd0", while the bootdev is in the form
 -- "xen/xvda".
-chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property DebianLike
+chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property (HasInfo + DebianLike)
 chainPVGrub rootdev bootdev timeout = combineProperties desc $ props
 	& File.dirExists "/boot/grub"
 	& "/boot/grub/menu.lst" `File.hasContent`
diff --git a/src/Propellor/Property/HostingProvider/Linode.hs b/src/Propellor/Property/HostingProvider/Linode.hs
--- a/src/Propellor/Property/HostingProvider/Linode.hs
+++ b/src/Propellor/Property/HostingProvider/Linode.hs
@@ -8,7 +8,7 @@
 -- | Configures grub to use the serial console as set up by Linode.
 -- Useful when running a distribution supplied kernel.
 -- <https://www.linode.com/docs/tools-reference/custom-kernels-distros/run-a-distribution-supplied-kernel-with-kvm>
-serialGrub :: Property DebianLike
+serialGrub :: Property (HasInfo + DebianLike)
 serialGrub = "/etc/default/grub" `File.containsLines`
 	[ "GRUB_CMDLINE_LINUX=\"console=ttyS0,19200n8\""
 	, "GRUB_DISABLE_LINUX_UUID=true"
@@ -22,7 +22,7 @@
 -- | Linode's pv-grub-x86_64 (only used for its older XEN instances)
 -- does not support booting recent Debian kernels compressed
 -- with xz. This sets up pv-grub chaining to enable it.
-chainPVGrub :: Grub.TimeoutSecs -> Property DebianLike
+chainPVGrub :: Grub.TimeoutSecs -> Property (HasInfo + DebianLike)
 chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda"
 
 -- | Linode disables mlocate's cron job's execute permissions,
diff --git a/src/Propellor/Types/Bootloader.hs b/src/Propellor/Types/Bootloader.hs
new file mode 100644
--- /dev/null
+++ b/src/Propellor/Types/Bootloader.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+module Propellor.Types.Bootloader where
+
+import Propellor.Types.Info
+
+-- | Boot loader installed on a host.
+data BootloaderInstalled = GrubInstalled
+	deriving (Typeable, Show)
+
+instance IsInfo [BootloaderInstalled] where
+	propagateInfo _ = PropagateInfo False
