packages feed

propellor 2.11.0 → 2.12.0

raw patch · 24 files changed

+704/−259 lines, 24 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor.Property.DiskImage: AIX :: TableType
- Propellor.Property.DiskImage: AMIGA :: TableType
- Propellor.Property.DiskImage: BSD :: TableType
- Propellor.Property.DiskImage: BTRFS :: Fs
- Propellor.Property.DiskImage: BootFlag :: PartFlag
- Propellor.Property.DiskImage: DVH :: TableType
- Propellor.Property.DiskImage: EXT2 :: Fs
- Propellor.Property.DiskImage: EXT3 :: Fs
- Propellor.Property.DiskImage: EXT4 :: Fs
- Propellor.Property.DiskImage: EspFlag :: PartFlag
- Propellor.Property.DiskImage: FAT :: Fs
- Propellor.Property.DiskImage: GPT :: TableType
- Propellor.Property.DiskImage: HiddenFlag :: PartFlag
- Propellor.Property.DiskImage: IrstFlag :: PartFlag
- Propellor.Property.DiskImage: LOOP :: TableType
- Propellor.Property.DiskImage: LbaFlag :: PartFlag
- Propellor.Property.DiskImage: LegacyBootFlag :: PartFlag
- Propellor.Property.DiskImage: LinuxSwap :: Fs
- Propellor.Property.DiskImage: LvmFlag :: PartFlag
- Propellor.Property.DiskImage: MAC :: TableType
- Propellor.Property.DiskImage: MSDOS :: TableType
- Propellor.Property.DiskImage: MegaBytes :: Integer -> PartSize
- Propellor.Property.DiskImage: NTFS :: Fs
- Propellor.Property.DiskImage: PC98 :: TableType
- Propellor.Property.DiskImage: PaloFlag :: PartFlag
- Propellor.Property.DiskImage: REISERFS :: Fs
- Propellor.Property.DiskImage: RaidFlag :: PartFlag
- Propellor.Property.DiskImage: RootFlag :: PartFlag
- Propellor.Property.DiskImage: SUN :: TableType
- Propellor.Property.DiskImage: SwapFlag :: PartFlag
- Propellor.Property.DiskImage: VFAT :: Fs
- Propellor.Property.DiskImage: XFS :: Fs
- Propellor.Property.DiskImage: addFreeSpace :: PartSpec -> PartSize -> PartSpec
- Propellor.Property.DiskImage: adjustp :: PartSpec -> (Partition -> Partition) -> PartSpec
- Propellor.Property.DiskImage: data Fs
- Propellor.Property.DiskImage: data PartFlag
- Propellor.Property.DiskImage: data Partition
- Propellor.Property.DiskImage: data TableType
- Propellor.Property.DiskImage: extended :: PartSpec -> PartSpec
- Propellor.Property.DiskImage: mountedAt :: PartSpec -> FilePath -> PartSpec
- Propellor.Property.DiskImage: newtype PartSize
- Propellor.Property.DiskImage: partition :: Fs -> PartSpec
- Propellor.Property.DiskImage: setFlag :: PartSpec -> PartFlag -> PartSpec
- Propellor.Property.DiskImage: setSize :: PartSpec -> PartSize -> PartSpec
- Propellor.Property.DiskImage: swapPartition :: PartSize -> PartSpec
- Propellor.Property.DiskImage: type MountPoint = Maybe FilePath
- Propellor.Property.DiskImage: type PartSpec = (MountPoint, PartSize -> Partition)
+ Propellor.Property.Chroot: ChrootTarball :: FilePath -> ChrootTarball
+ Propellor.Property.Chroot: data ChrootTarball
+ Propellor.Property.Chroot: inChroot :: Propellor Bool
+ Propellor.Property.Chroot: instance GHC.Show.Show Propellor.Property.Chroot.InChroot
+ Propellor.Property.Chroot: instance Propellor.Property.Chroot.ChrootBootstrapper Propellor.Property.Chroot.ChrootTarball
+ Propellor.Property.DiskImage.PartSpec: AIX :: TableType
+ Propellor.Property.DiskImage.PartSpec: AMIGA :: TableType
+ Propellor.Property.DiskImage.PartSpec: BSD :: TableType
+ Propellor.Property.DiskImage.PartSpec: BTRFS :: Fs
+ Propellor.Property.DiskImage.PartSpec: BootFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: DVH :: TableType
+ Propellor.Property.DiskImage.PartSpec: EXT2 :: Fs
+ Propellor.Property.DiskImage.PartSpec: EXT3 :: Fs
+ Propellor.Property.DiskImage.PartSpec: EXT4 :: Fs
+ Propellor.Property.DiskImage.PartSpec: EspFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: FAT :: Fs
+ Propellor.Property.DiskImage.PartSpec: GPT :: TableType
+ Propellor.Property.DiskImage.PartSpec: HiddenFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: IrstFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: LOOP :: TableType
+ Propellor.Property.DiskImage.PartSpec: LbaFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: LegacyBootFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: LinuxSwap :: Fs
+ Propellor.Property.DiskImage.PartSpec: LvmFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: MAC :: TableType
+ Propellor.Property.DiskImage.PartSpec: MSDOS :: TableType
+ Propellor.Property.DiskImage.PartSpec: MegaBytes :: Integer -> PartSize
+ Propellor.Property.DiskImage.PartSpec: NTFS :: Fs
+ Propellor.Property.DiskImage.PartSpec: PC98 :: TableType
+ Propellor.Property.DiskImage.PartSpec: PaloFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: REISERFS :: Fs
+ Propellor.Property.DiskImage.PartSpec: RaidFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: RootFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: SUN :: TableType
+ Propellor.Property.DiskImage.PartSpec: SwapFlag :: PartFlag
+ Propellor.Property.DiskImage.PartSpec: VFAT :: Fs
+ Propellor.Property.DiskImage.PartSpec: XFS :: Fs
+ Propellor.Property.DiskImage.PartSpec: addFreeSpace :: PartSpec -> PartSize -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: adjustp :: PartSpec -> (Partition -> Partition) -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: data Fs
+ Propellor.Property.DiskImage.PartSpec: data PartFlag
+ Propellor.Property.DiskImage.PartSpec: data Partition
+ Propellor.Property.DiskImage.PartSpec: data TableType
+ Propellor.Property.DiskImage.PartSpec: defSz :: PartSize
+ Propellor.Property.DiskImage.PartSpec: errorReadonly :: MountOpts
+ Propellor.Property.DiskImage.PartSpec: extended :: PartSpec -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: fudge :: PartSize -> PartSize
+ Propellor.Property.DiskImage.PartSpec: mountOpt :: ToMountOpts o => PartSpec -> o -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: mountedAt :: PartSpec -> FilePath -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: newtype PartSize
+ Propellor.Property.DiskImage.PartSpec: partition :: Fs -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: setFlag :: PartSpec -> PartFlag -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: setSize :: PartSpec -> PartSize -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: swapPartition :: PartSize -> PartSpec
+ Propellor.Property.DiskImage.PartSpec: type MountPoint = FilePath
+ Propellor.Property.DiskImage.PartSpec: type PartSpec = (Maybe MountPoint, MountOpts, PartSize -> Partition)
+ Propellor.Property.File: basedOn :: FilePath -> (FilePath, [Line] -> [Line]) -> Property NoInfo
+ Propellor.Property.Grub: installed' :: BIOS -> Property NoInfo
+ Propellor.Property.Grub: mkConfig :: Property NoInfo
+ Propellor.Property.Hostname: extractDomain :: ExtractDomain
+ Propellor.Property.Hostname: sane' :: ExtractDomain -> Property NoInfo
+ Propellor.Property.Hostname: searchDomain' :: ExtractDomain -> Property NoInfo
+ Propellor.Property.Hostname: setTo' :: ExtractDomain -> HostName -> Property NoInfo
+ Propellor.Property.Hostname: type ExtractDomain = HostName -> String
+ Propellor.Property.Mount: MountOpts :: [String] -> MountOpts
+ Propellor.Property.Mount: SwapPartition :: FilePath -> SwapPartition
+ Propellor.Property.Mount: bindMount :: FilePath -> FilePath -> Property NoInfo
+ Propellor.Property.Mount: blkidTag :: String -> Source -> IO (Maybe String)
+ Propellor.Property.Mount: class ToMountOpts a
+ Propellor.Property.Mount: findmntField :: String -> FilePath -> IO (Maybe String)
+ Propellor.Property.Mount: formatMountOpts :: MountOpts -> String
+ Propellor.Property.Mount: fstabbed :: [MountPoint] -> [SwapPartition] -> Property NoInfo
+ Propellor.Property.Mount: genFstab :: [MountPoint] -> [SwapPartition] -> (MountPoint -> MountPoint) -> IO [String]
+ Propellor.Property.Mount: getFsMountOpts :: MountPoint -> IO MountOpts
+ Propellor.Property.Mount: getMountLabel :: MountPoint -> IO (Maybe Label)
+ Propellor.Property.Mount: getMountSource :: MountPoint -> IO (Maybe Source)
+ Propellor.Property.Mount: getMountUUID :: MountPoint -> IO (Maybe UUID)
+ Propellor.Property.Mount: getSourceLabel :: Source -> IO (Maybe UUID)
+ Propellor.Property.Mount: getSourceUUID :: Source -> IO (Maybe UUID)
+ Propellor.Property.Mount: instance GHC.Base.Monoid Propellor.Property.Mount.MountOpts
+ Propellor.Property.Mount: instance Propellor.Property.Mount.ToMountOpts GHC.Base.String
+ Propellor.Property.Mount: instance Propellor.Property.Mount.ToMountOpts Propellor.Property.Mount.MountOpts
+ Propellor.Property.Mount: mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property NoInfo
+ Propellor.Property.Mount: newtype MountOpts
+ Propellor.Property.Mount: newtype SwapPartition
+ Propellor.Property.Mount: noFstab :: IO Bool
+ Propellor.Property.Mount: toMountOpts :: ToMountOpts a => a -> MountOpts
+ Propellor.Property.Mount: type Label = String
+ Propellor.Property.Mount: type MountPoint = FilePath
+ Propellor.Property.Mount: type UUID = String
+ Propellor.Property.Partition: LoopDev :: FilePath -> FilePath -> LoopDev
+ Propellor.Property.Partition: [partitionLoopDev] :: LoopDev -> FilePath
+ Propellor.Property.Partition: [wholeDiskLoopDev] :: LoopDev -> FilePath
+ Propellor.Property.Partition: data LoopDev
+ Propellor.Property.Partition: instance GHC.Classes.Eq Propellor.Property.Partition.Fs
+ Propellor.Property.Partition: instance GHC.Show.Show Propellor.Property.Partition.LoopDev
+ Propellor.Property.Partition: isLoopDev :: LoopDev -> IO Bool
+ Propellor.Property.Partition: isLoopDev' :: FilePath -> IO Bool
+ Propellor.Property.Partition: kpartxParse :: String -> [LoopDev]
+ Propellor.Property.Rsync: Protect :: Pattern -> Filter
+ Propellor.Property.Ssh: SshDsa :: SshKeyType
+ Propellor.Property.Ssh: SshEcdsa :: SshKeyType
+ Propellor.Property.Ssh: SshEd25519 :: SshKeyType
+ Propellor.Property.Ssh: SshRsa :: SshKeyType
+ Propellor.Property.Ssh: data SshKeyType
+ Propellor.Property.User: chpasswd :: User -> String -> [String] -> Propellor Result
+ Propellor.Property.User: hasInsecurePassword :: User -> String -> Property NoInfo
+ Propellor.Types: class LiftPropellor m
+ Propellor.Types: instance Propellor.Types.LiftPropellor GHC.Types.IO
+ Propellor.Types: instance Propellor.Types.LiftPropellor Propellor.Types.Propellor
+ Propellor.Types: liftPropellor :: LiftPropellor m => m a -> Propellor a
- Propellor.Property: check :: IO Bool -> Property i -> Property i
+ Propellor.Property: check :: (LiftPropellor m) => m Bool -> Property i -> Property i
- Propellor.Property.Chroot: Chroot :: FilePath -> System -> b -> Host -> Chroot
+ Propellor.Property.Chroot: Chroot :: FilePath -> b -> Host -> Chroot
- Propellor.Property.Chroot: bootstrapped :: ChrootBootstrapper b => System -> b -> FilePath -> Chroot
+ Propellor.Property.Chroot: bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Chroot
- Propellor.Property.Chroot: buildchroot :: ChrootBootstrapper b => b -> System -> FilePath -> Maybe (Property HasInfo)
+ Propellor.Property.Chroot: buildchroot :: ChrootBootstrapper b => b -> Maybe System -> FilePath -> Either String (Property HasInfo)
- Propellor.Property.Chroot: chrootSystem :: Chroot -> System
+ Propellor.Property.Chroot: chrootSystem :: Chroot -> Maybe System
- Propellor.Property.Chroot: debootstrapped :: System -> DebootstrapConfig -> FilePath -> Chroot
+ Propellor.Property.Chroot: debootstrapped :: DebootstrapConfig -> FilePath -> Chroot
- Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> Finalization -> RevertableProperty
+ Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
- Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> [PartSpec] -> Property NoInfo -> RevertableProperty
+ Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
- Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> Finalization -> RevertableProperty
+ Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
- Propellor.Property.DiskImage: type Finalization = (Property NoInfo, Property NoInfo)
+ Propellor.Property.DiskImage: type Finalization = (Property NoInfo, FilePath -> [LoopDev] -> Property NoInfo)
- Propellor.Property.Mount: getFsType :: FilePath -> IO (Maybe FsType)
+ Propellor.Property.Mount: getFsType :: MountPoint -> IO (Maybe FsType)
- Propellor.Property.Mount: mount :: FsType -> Source -> FilePath -> IO Bool
+ Propellor.Property.Mount: mount :: FsType -> Source -> MountPoint -> MountOpts -> IO Bool
- Propellor.Property.Mount: mountPoints :: IO [FilePath]
+ Propellor.Property.Mount: mountPoints :: IO [MountPoint]
- Propellor.Property.Mount: mountPointsBelow :: FilePath -> IO [FilePath]
+ Propellor.Property.Mount: mountPointsBelow :: FilePath -> IO [MountPoint]
- Propellor.Property.Mount: type FsType = String type of filesystem to mount ("auto" to autodetect)
+ Propellor.Property.Mount: type FsType = String
- Propellor.Property.Partition: kpartx :: FilePath -> ([FilePath] -> Property NoInfo) -> Property NoInfo
+ Propellor.Property.Partition: kpartx :: FilePath -> ([LoopDev] -> Property NoInfo) -> Property NoInfo
- Propellor.Property.Systemd: container :: MachineName -> (FilePath -> Chroot) -> Container
+ Propellor.Property.Systemd: container :: MachineName -> System -> (FilePath -> Chroot) -> Container

Files

CHANGELOG view
@@ -1,3 +1,24 @@+propellor (2.12.0) unstable; urgency=medium++  * The DiskImage module can now make bootable images using grub.+  * Add a ChrootTarball chroot type, for using pre-built tarballs+    as chroots. Thanks, Ben Boeckel.+  * HostName: Improve domain extraction code.+  * Added Mount.fstabbed property to generate /etc/fstab to replicate+    current mounts.+  * HostName: Improve domain extraction code.+  * Add File.basedOn. Thanks, Per Olofsson.+  * Changed how the operating system is provided to Chroot (API change).+    Where before debootstrapped and bootstrapped took a System parameter,+    the os property should now be added to the Chroot.+  * Follow-on change to Systemd.container, which now takes a System parameter.+  * Generalized Property.check so it can be used with Propellor actions as+    well as IO actions.+  * Hostname.sane and Hostname.setTo can now safely be used as a property+    of a chroot, and won't affect the hostname of the host system.++ -- Joey Hess <id@joeyh.name>  Fri, 23 Oct 2015 17:38:32 -0400+ propellor (2.11.0) unstable; urgency=medium    * Rewrote Propellor.Property.ControlHeir one more time, renaming it to
README.md view
@@ -53,6 +53,7 @@    So, edit `~/.propellor/config.hs` to configure the host, add some    properties to it, and re-run step 6.      Repeat until happy and move on to the next host. :)-8. Optionally, set up a [centralized git repository](https://propellor.branchable.com/centralized_git_repository/)-   so that multiple hosts can be updated with a simple `git commit -S; git push`+8. Once you have a lot of hosts, and running `propellor --spin HOST` for+   each host becomes tiresome, you can+   [automate that](http://propellor.branchable.com/automated_spins/). 9. Write some neat new properties and send patches!
config-joey.hs view
@@ -81,14 +81,19 @@ 	& JoeySites.postfixClientRelay (Context "darkstar.kitenet.net") 	& JoeySites.dkimMilter -	& imageBuilt "/tmp/img" c MSDOS-		[ partition EXT2 `mountedAt` "/boot" `setFlag` BootFlag-		, partition EXT4 `mountedAt` "/" `addFreeSpace` MegaBytes 100-		-- , swapPartition (MegaBytes 256)-		] noFinalization -- (grubBooted PC)+	& imageBuilt "/tmp/img" c MSDOS (grubBooted PC)+		[ partition EXT2 `mountedAt` "/boot"+			`setFlag` BootFlag+		, partition EXT4 `mountedAt` "/"+			`mountOpt` errorReadonly+		, swapPartition (MegaBytes 256)+		]   where-	c d = Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty d+	c d = Chroot.debootstrapped mempty d+		& os (System (Debian Unstable) "amd64")+		& Hostname.setTo "demo" 		& Apt.installed ["linux-image-amd64"]+		& User "root" `User.hasInsecurePassword` "root"  gnu :: Host gnu = host "gnu.kitenet.net"@@ -492,14 +497,13 @@  -- This is my standard container setup, Featuring automatic upgrades. standardContainer :: Systemd.MachineName -> DebianSuite -> Architecture -> Systemd.Container-standardContainer name suite arch = Systemd.container name chroot-	& os system-	& Apt.stdSourcesList `onChange` Apt.upgrade-	& Apt.unattendedUpgrades-	& Apt.cacheCleaned+standardContainer name suite arch =+	Systemd.container name system (Chroot.debootstrapped mempty)+		& Apt.stdSourcesList `onChange` Apt.upgrade+		& Apt.unattendedUpgrades+		& Apt.cacheCleaned   where 	system = System (Debian suite) arch-	chroot = Chroot.debootstrapped system mempty  standardStableContainer :: Systemd.MachineName -> Systemd.Container standardStableContainer name = standardContainer name (Stable "jessie") "amd64"
debian/changelog view
@@ -1,3 +1,24 @@+propellor (2.12.0) unstable; urgency=medium++  * The DiskImage module can now make bootable images using grub.+  * Add a ChrootTarball chroot type, for using pre-built tarballs+    as chroots. Thanks, Ben Boeckel.+  * HostName: Improve domain extraction code.+  * Added Mount.fstabbed property to generate /etc/fstab to replicate+    current mounts.+  * HostName: Improve domain extraction code.+  * Add File.basedOn. Thanks, Per Olofsson.+  * Changed how the operating system is provided to Chroot (API change).+    Where before debootstrapped and bootstrapped took a System parameter,+    the os property should now be added to the Chroot.+  * Follow-on change to Systemd.container, which now takes a System parameter.+  * Generalized Property.check so it can be used with Propellor actions as+    well as IO actions.+  * Hostname.sane and Hostname.setTo can now safely be used as a property+    of a chroot, and won't affect the hostname of the host system.++ -- Joey Hess <id@joeyh.name>  Fri, 23 Oct 2015 17:38:32 -0400+ propellor (2.11.0) unstable; urgency=medium    * Rewrote Propellor.Property.ControlHeir one more time, renaming it to
doc/README.mdwn view
@@ -53,6 +53,7 @@    So, edit `~/.propellor/config.hs` to configure the host, add some    properties to it, and re-run step 6.      Repeat until happy and move on to the next host. :)-8. Optionally, set up a [centralized git repository](https://propellor.branchable.com/centralized_git_repository/)-   so that multiple hosts can be updated with a simple `git commit -S; git push`+8. Once you have a lot of hosts, and running `propellor --spin HOST` for+   each host becomes tiresome, you can+   [automate that](http://propellor.branchable.com/automated_spins/). 9. Write some neat new properties and send patches!
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.11.0+Version: 2.12.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -84,6 +84,7 @@     Propellor.Property.DebianMirror     Propellor.Property.Debootstrap     Propellor.Property.DiskImage+    Propellor.Property.DiskImage.PartSpec     Propellor.Property.Dns     Propellor.Property.DnsSec     Propellor.Property.Docker
src/Propellor/Property.hs view
@@ -142,11 +142,12 @@ ensureProperty = catchPropellor . propertySatisfy  -- | Makes a Property only need to do anything when a test succeeds.-check :: IO Bool -> Property i -> Property i-check c p = adjustPropertySatisfy p $ \satisfy -> ifM (liftIO c)-	( satisfy-	, return NoChange-	)+check :: (LiftPropellor m) => m Bool -> Property i -> Property i+check c p = adjustPropertySatisfy p $ \satisfy -> +	ifM (liftPropellor c)+		( satisfy+		, return NoChange+		)  -- | Tries the first property, but if it fails to work, instead uses -- the second.
src/Propellor/Property/Chroot.hs view
@@ -1,12 +1,14 @@-{-# LANGUAGE FlexibleContexts, GADTs #-}+{-# LANGUAGE FlexibleContexts, GADTs, DeriveDataTypeable #-}  module Propellor.Property.Chroot (-	Chroot(..),-	ChrootBootstrapper(..),-	Debootstrapped(..), 	debootstrapped, 	bootstrapped, 	provisioned,+	Chroot(..),+	ChrootBootstrapper(..),+	Debootstrapped(..),+	ChrootTarball(..),+	inChroot, 	-- * Internal use 	provisioned', 	propagateChrootInfo,@@ -22,6 +24,7 @@ import Propellor.Property.Chroot.Util import qualified Propellor.Property.Debootstrap as Debootstrap import qualified Propellor.Property.Systemd.Core as Systemd+import qualified Propellor.Property.File as File import qualified Propellor.Shim as Shim import Propellor.Property.Mount @@ -32,52 +35,78 @@ -- | Specification of a chroot. Normally you'll use `debootstrapped` or -- `bootstrapped` to construct a Chroot value. data Chroot where-	Chroot :: ChrootBootstrapper b => FilePath -> System -> b -> Host -> Chroot+	Chroot :: ChrootBootstrapper b => FilePath -> b -> Host -> Chroot -chrootSystem :: Chroot -> System-chrootSystem (Chroot _ system _ _) = system+chrootSystem :: Chroot -> Maybe System+chrootSystem (Chroot _ _ h) = fromInfoVal (getInfo (hostInfo h))  instance Show Chroot where-	show (Chroot loc system _ _) = "Chroot " ++ loc ++ " " ++ show system+	show c@(Chroot loc _ _) = "Chroot " ++ loc ++ " " ++ show (chrootSystem c)  instance PropAccum Chroot where-	(Chroot l s c h) `addProp` p = Chroot l s c (h & p)-	(Chroot l s c h) `addPropFront` p = Chroot l s c (h `addPropFront` p)-	getProperties (Chroot _ _ _ h) = hostProperties h+	(Chroot l c h) `addProp` p = Chroot l c (h & p)+	(Chroot l c h) `addPropFront` p = Chroot l c (h `addPropFront` p)+	getProperties (Chroot _ _ h) = hostProperties h  -- | Class of things that can do initial bootstrapping of an operating -- System in a chroot. class ChrootBootstrapper b where 	-- | Do initial bootstrapping of an operating system in a chroot.-	-- If the operating System is not supported, return Nothing.-	buildchroot :: b -> System -> FilePath -> Maybe (Property HasInfo)+	-- If the operating System is not supported, return +	-- Left error message.+	buildchroot :: b -> Maybe System -> FilePath -> Either String (Property HasInfo) --- | Use to bootstrap a chroot with debootstrap.+-- | Use this to bootstrap a chroot by extracting a tarball.+--+-- The tarball is expected to contain a root directory (no top-level+-- directory, also known as a "tarbomb").+-- It may be optionally compressed with any format `tar` knows how to+-- detect automatically.+data ChrootTarball = ChrootTarball FilePath++instance ChrootBootstrapper ChrootTarball where+	buildchroot (ChrootTarball tb) _ loc = Right $ extractTarball loc tb++extractTarball :: FilePath -> FilePath -> Property HasInfo+extractTarball target src = toProp .+	check (unpopulated target) $+		cmdProperty "tar" params+			`requires` File.dirExists target+  where+	params =+		[ "-C"+		, target+		, "-xf"+		, src+		]++-- | Use this to bootstrap a chroot with debootstrap. data Debootstrapped = Debootstrapped Debootstrap.DebootstrapConfig  instance ChrootBootstrapper Debootstrapped where 	buildchroot (Debootstrapped cf) system loc = case system of-		(System (Debian _) _) -> Just debootstrap-		(System (Ubuntu _) _) -> Just debootstrap+		(Just s@(System (Debian _) _)) -> Right $ debootstrap s+		(Just s@(System (Ubuntu _) _)) -> Right $ debootstrap s+		Nothing -> Left "Cannot debootstrap; `os` property not specified" 	  where-		debootstrap = Debootstrap.built loc system cf+		debootstrap s = Debootstrap.built loc s cf  -- | Defines a Chroot at the given location, built with debootstrap. ----- Properties can be added to configure the Chroot.+-- Properties can be added to configure the Chroot. At a minimum,+-- add the `os` property to specify the operating system to bootstrap. ----- > debootstrapped (System (Debian Unstable) "amd64") Debootstrap.BuildD "/srv/chroot/ghc-dev"+-- > debootstrapped Debootstrap.BuildD "/srv/chroot/ghc-dev"+-- >	& os (System (Debian Unstable) "amd64") -- > 	& Apt.installed ["ghc", "haskell-platform"] -- > 	& ...-debootstrapped :: System -> Debootstrap.DebootstrapConfig -> FilePath -> Chroot-debootstrapped system conf = bootstrapped system (Debootstrapped conf)+debootstrapped :: Debootstrap.DebootstrapConfig -> FilePath -> Chroot+debootstrapped conf = bootstrapped (Debootstrapped conf)  -- | Defines a Chroot at the given location, bootstrapped with the -- specified ChrootBootstrapper.-bootstrapped :: ChrootBootstrapper b => System -> b -> FilePath -> Chroot-bootstrapped system bootstrapper location =-	Chroot location system bootstrapper h-		& os system+bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Chroot+bootstrapped bootstrapper location = Chroot location bootstrapper h   where 	h = Host location [] mempty @@ -91,7 +120,7 @@ provisioned c = provisioned' (propagateChrootInfo c) c False  provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty-provisioned' propigator c@(Chroot loc system bootstrapper _) systemdonly =+provisioned' propigator c@(Chroot loc bootstrapper _) systemdonly = 	(propigator $ propertyList (chrootDesc c "exists") [setup]) 		<!> 	(propertyList (chrootDesc c "removed") [teardown])@@ -99,18 +128,18 @@ 	setup = propellChroot c (inChrootProcess (not systemdonly) c) systemdonly 		`requires` toProp built 	-	built = case buildchroot bootstrapper system loc of-		Just p -> p-		Nothing -> cantbuild+	built = case buildchroot bootstrapper (chrootSystem c) loc of+		Right p -> p+		Left e -> cantbuild e -	cantbuild = infoProperty (chrootDesc c "built") (error $ "cannot bootstrap " ++ show system ++ " using supplied ChrootBootstrapper") mempty []+	cantbuild e = infoProperty (chrootDesc c "built") (error e) mempty []  	teardown = check (not <$> unpopulated loc) $ 		property ("removed " ++ loc) $ 			makeChange (removeChroot loc)  propagateChrootInfo :: (IsProp (Property i)) => Chroot -> Property i -> Property HasInfo-propagateChrootInfo c@(Chroot location _ _ _) p = propagateContainer location c p'+propagateChrootInfo c@(Chroot location _ _) p = propagateContainer location c p'   where 	p' = infoProperty 		(propertyDesc p)@@ -119,12 +148,12 @@ 		(propertyChildren p)  chrootInfo :: Chroot -> Info-chrootInfo (Chroot loc _ _ h) = mempty `addInfo` +chrootInfo (Chroot loc _ h) = mempty `addInfo`  	mempty { _chroots = M.singleton loc h }  -- | Propellor is run inside the chroot to provision it. propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property NoInfo-propellChroot c@(Chroot loc _ _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do+propellChroot c@(Chroot loc _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do 	let d = localdir </> shimdir c 	let me = localdir </> "propellor" 	shim <- liftIO $ ifM (doesDirectoryExist d)@@ -163,7 +192,7 @@ 		return r  toChain :: HostName -> Chroot -> Bool -> IO CmdLine-toChain parenthost (Chroot loc _ _ _) systemdonly = do+toChain parenthost (Chroot loc _ _) systemdonly = do 	onconsole <- isConsole <$> mkMessageHandle 	return $ ChrootChain parenthost loc systemdonly onconsole @@ -179,7 +208,7 @@ 		changeWorkingDirectory localdir 		when onconsole forceConsole 		onlyProcess (provisioningLock loc) $ do-			r <- runPropellor h $ ensureProperties $+			r <- runPropellor (setInChroot h) $ ensureProperties $ 				if systemdonly 					then [Systemd.installed] 					else map ignoreInfo $@@ -188,14 +217,14 @@ chain _ _ = errorMessage "bad chain command"  inChrootProcess :: Bool -> Chroot -> [String] -> IO (CreateProcess, IO ())-inChrootProcess keepprocmounted (Chroot loc _ _ _) cmd = do+inChrootProcess keepprocmounted (Chroot loc _ _) cmd = do 	mountproc 	return (proc "chroot" (loc:cmd), cleanup)   where 	-- /proc needs to be mounted in the chroot for the linker to use 	-- /proc/self/exe which is necessary for some commands to work 	mountproc = unlessM (elem procloc <$> mountPointsBelow loc) $-		void $ mount "proc" "proc" procloc+		void $ mount "proc" "proc" procloc mempty 	 	procloc = loc </> "proc" @@ -208,10 +237,25 @@ provisioningLock containerloc = "chroot" </> mungeloc containerloc ++ ".lock"  shimdir :: Chroot -> FilePath-shimdir (Chroot loc _ _ _) = "chroot" </> mungeloc loc ++ ".shim"+shimdir (Chroot loc _ _) = "chroot" </> mungeloc loc ++ ".shim"  mungeloc :: FilePath -> String mungeloc = replace "/" "_"  chrootDesc :: Chroot -> String -> String-chrootDesc (Chroot loc _ _ _) desc = "chroot " ++ loc ++ " " ++ desc+chrootDesc (Chroot loc _ _) desc = "chroot " ++ loc ++ " " ++ desc++-- | Check if propellor is currently running within a chroot.+--+-- This allows properties to check and avoid performing actions that+-- should not be done in a chroot.+inChroot :: Propellor Bool+inChroot = extract . fromMaybe (InChroot False) . fromInfoVal <$> askInfo+  where+	extract (InChroot b) = b++setInChroot :: Host -> Host+setInChroot h = h { hostInfo = hostInfo h `addInfo` InfoVal (InChroot True) }++newtype InChroot = InChroot Bool+	deriving (Typeable, Show)
src/Propellor/Property/DiskImage.hs view
@@ -2,33 +2,17 @@ -- -- This module is designed to be imported unqualified. ----- TODO run final---  -- TODO avoid starting services while populating chroot and running final  module Propellor.Property.DiskImage (+	-- * Partition specification+	module Propellor.Property.DiskImage.PartSpec, 	-- * Properties 	DiskImage, 	imageBuilt, 	imageRebuilt, 	imageBuiltFrom, 	imageExists,-	-- * Partitioning-	Partition,-	PartSize(..),-	Fs(..),-	PartSpec,-	MountPoint,-	swapPartition,-	partition,-	mountedAt,-	addFreeSpace,-	setSize,-	PartFlag(..),-	setFlag,-	TableType(..),-	extended,-	adjustp, 	-- * Finalization 	Finalization, 	grubBooted,@@ -37,6 +21,7 @@ ) where  import Propellor.Base+import Propellor.Property.DiskImage.PartSpec import Propellor.Property.Chroot (Chroot) import Propellor.Property.Chroot.Util (removeChroot) import qualified Propellor.Property.Chroot as Chroot@@ -49,7 +34,8 @@ import Propellor.Property.Rsync import Utility.Path -import Data.List (isPrefixOf)+import Data.List (isPrefixOf, isInfixOf, sortBy)+import Data.Function (on) import qualified Data.Map.Strict as M import qualified Data.ByteString.Lazy as L import System.Posix.Files@@ -70,25 +56,31 @@ -- > let chroot d = Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty d -- > 		& Apt.installed ["linux-image-amd64"] -- >		& ...--- > in imageBuilt "/srv/images/foo.img" chroot MSDOS +-- > in imageBuilt "/srv/images/foo.img" chroot+-- >		MSDOS (grubBooted PC) -- >		[ partition EXT2 `mountedAt` "/boot" -- >			`setFlag` BootFlag -- >		, partition EXT4 `mountedAt` "/" -- >			`addFreeSpace` MegaBytes 100+-- >			`mountOpt` errorReadonly -- >		, swapPartition (MegaBytes 256)--- >		] (grubBooted PC)-imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> Finalization -> RevertableProperty+-- >		]+--+-- Note that the disk image file is reused if it already exists,+-- to avoid expensive IO to generate a new one. And, it's updated in-place,+-- so its contents are undefined during the build process.+imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty 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 -> [PartSpec] -> Finalization -> RevertableProperty+imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty imageRebuilt = imageBuilt' True -imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> Finalization -> RevertableProperty-imageBuilt' rebuild img mkchroot tabletype partspec final = -	imageBuiltFrom img chrootdir tabletype partspec (snd final)+imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageBuilt' rebuild img mkchroot tabletype final partspec = +	imageBuiltFrom img chrootdir tabletype final partspec 		`requires` Chroot.provisioned chroot 		`requires` (cleanrebuild <!> doNothing) 		`describe` desc@@ -107,10 +99,8 @@ 		& Apt.cacheCleaned  -- | Builds a disk image from the contents of a chroot.------ The passed property is run inside the mounted disk image.-imageBuiltFrom :: DiskImage -> FilePath -> TableType -> [PartSpec] -> Property NoInfo -> RevertableProperty-imageBuiltFrom img chrootdir tabletype partspec final = mkimg <!> rmimg+imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageBuiltFrom img chrootdir tabletype final partspec = mkimg <!> rmimg   where 	desc = img ++ " built from " ++ chrootdir 	mkimg = property desc $ do@@ -121,25 +111,30 @@ 			<$> liftIO (dirSizes chrootdir) 		let calcsz mnts = maybe defSz fudge . getMountSz szm mnts 		-- tie the knot!-		let (mnts, t) = fitChrootSize tabletype partspec (map (calcsz mnts) mnts)+		let (mnts, mntopts, parttable) = fitChrootSize tabletype partspec $+			map (calcsz mnts) mnts 		ensureProperty $-			imageExists img (partTableSize t)+			imageExists img (partTableSize parttable) 				`before`-			partitioned YesReallyDeleteDiskContents img t+			partitioned YesReallyDeleteDiskContents img parttable 				`before`-			kpartx img (partitionsPopulated chrootdir mnts)+			kpartx img (mkimg' mnts mntopts parttable)+	mkimg' mnts mntopts parttable devs =+		partitionsPopulated chrootdir mnts mntopts devs+			`before`+		imageFinalized final mnts mntopts devs parttable 	rmimg = File.notPresent img -partitionsPopulated :: FilePath -> [MountPoint] -> [FilePath] -> Property NoInfo-partitionsPopulated chrootdir mnts devs = property desc $ mconcat $ zipWith go mnts devs+partitionsPopulated :: FilePath -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> Property NoInfo+partitionsPopulated chrootdir mnts mntopts devs = property desc $ mconcat $ zipWith3 go mnts mntopts devs   where 	desc = "partitions populated from " ++ chrootdir -	go Nothing _ = noChange-	go (Just mnt) dev = withTmpDir "mnt" $ \tmpdir -> bracket-		(liftIO $ mount "auto" dev tmpdir)+	go Nothing _ _ = noChange+	go (Just mnt) mntopt loopdev = withTmpDir "mnt" $ \tmpdir -> bracket+		(liftIO $ mount "auto" (partitionLoopDev loopdev) tmpdir mntopt) 		(const $ liftIO $ umountLazy tmpdir)-		$ \mounted -> if mounted+		$ \ismounted -> if ismounted 			then ensureProperty $ 				syncDirFiltered (filtersfor mnt) (chrootdir ++ mnt) tmpdir 			else return FailedChange@@ -152,25 +147,17 @@ 			-- Include the child mount point, but exclude its contents. 			[ Include (Pattern m) 			, Exclude (filesUnder m)+			-- Preserve any lost+found directory that mkfs made+			, Protect (Pattern "lost+found") 			]) childmnts --- | Ensures that a disk image file of the specified size exists.--- --- If the file doesn't exist, or is too small, creates a new one, full of 0's.------ If the file is too large, truncates it down to the specified size.-imageExists :: FilePath -> ByteSize -> Property NoInfo-imageExists img sz = property ("disk image exists" ++ img) $ liftIO $ do-	ms <- catchMaybeIO $ getFileStatus img-	case ms of-		Just s -			| toInteger (fileSize s) == toInteger sz -> return NoChange-			| toInteger (fileSize s) > toInteger sz -> do-				setFileSize img (fromInteger sz)-				return MadeChange-		_ -> do-			L.writeFile img (L.replicate (fromIntegral sz) 0)-			return MadeChange+-- The constructor for each Partition is passed the size of the files+-- from the chroot that will be put in that partition.+fitChrootSize :: TableType -> [PartSpec] -> [PartSize] -> ([Maybe MountPoint], [MountOpts], PartTable)+fitChrootSize tt l basesizes = (mounts, mountopts, parttable)+  where+	(mounts, mountopts, sizers) = unzip3 l+	parttable = PartTable tt (zipWith id sizers basesizes)  -- | Generates a map of the sizes of the contents of  -- every directory in a filesystem tree. @@ -194,105 +181,134 @@ 			else go (M.insertWith (+) dir sz m) dir is 	subdirof parent i = not (i `equalFilePath` parent) && takeDirectory i `equalFilePath` parent -getMountSz :: (M.Map FilePath PartSize) -> [MountPoint] -> MountPoint -> Maybe PartSize+getMountSz :: (M.Map FilePath PartSize) -> [Maybe MountPoint] -> Maybe MountPoint -> Maybe PartSize getMountSz _ _ Nothing = Nothing getMountSz szm l (Just mntpt) =  	fmap (`reducePartSize` childsz) (M.lookup mntpt szm)   where 	childsz = mconcat $ mapMaybe (getMountSz szm l) (filter (isChild mntpt) l) -isChild :: FilePath -> MountPoint -> Bool-isChild mntpt (Just d)-	| d `equalFilePath` mntpt = False-	| otherwise = mntpt `dirContains` d-isChild _ Nothing = False---- | From a location in a chroot (eg, /tmp/chroot/usr) to--- the corresponding location inside (eg, /usr).-toSysDir :: FilePath -> FilePath -> FilePath-toSysDir chrootdir d = case makeRelative chrootdir d of-		"." -> "/"-		sysdir -> "/" ++ sysdir---- | Where a partition is mounted. Use Nothing for eg, LinuxSwap.-type MountPoint = Maybe FilePath--defSz :: PartSize-defSz = MegaBytes 128---- Add 2% for filesystem overhead. Rationalle for picking 2%:--- A filesystem with 1% overhead might just sneak by as acceptable.--- Double that just in case. Add an additional 3 mb to deal with--- non-scaling overhead, of filesystems (eg, superblocks).-fudge :: PartSize -> PartSize-fudge (MegaBytes n) = MegaBytes (n + n `div` 100 * 2 + 3)---- | Specifies a mount point and a constructor for a Partition.+-- | Ensures that a disk image file of the specified size exists. -- --- The size that is eventually provided is the amount of space needed to --- hold the files that appear in the directory where the partition is to be--- mounted. Plus a fudge factor, since filesystems have some space--- overhead.+-- If the file doesn't exist, or is too small, creates a new one, full of 0's. ----- (Partitions that are not to be mounted (ie, LinuxSwap), or that have--- no corresponding directory in the chroot will have 128 MegaBytes--- provided as a default size.)-type PartSpec = (MountPoint, PartSize -> Partition)---- | Specifies a swap partition of a given size.-swapPartition :: PartSize -> PartSpec-swapPartition sz = (Nothing, const (mkPartition LinuxSwap sz))+-- If the file is too large, truncates it down to the specified size.+imageExists :: FilePath -> ByteSize -> Property NoInfo+imageExists img sz = property ("disk image exists" ++ img) $ liftIO $ do+	ms <- catchMaybeIO $ getFileStatus img+	case ms of+		Just s +			| toInteger (fileSize s) == toInteger sz -> return NoChange+			| toInteger (fileSize s) > toInteger sz -> do+				setFileSize img (fromInteger sz)+				return MadeChange+		_ -> do+			L.writeFile img (L.replicate (fromIntegral sz) 0)+			return MadeChange --- | Specifies a partition with a given filesystem.+-- | A pair of properties. The first property is satisfied within the+-- chroot, and is typically used to download the boot loader. ----- The partition is not mounted anywhere by default; use the combinators--- below to configure it.-partition :: Fs -> PartSpec-partition fs = (Nothing, mkPartition fs)+-- The second property is run after the disk image is created,+-- with its populated partition tree mounted in the provided+-- location from the provided loop devices. This will typically+-- take care of installing the boot loader to the image.+-- +-- It's ok if the second property leaves additional things mounted+-- in the partition tree.+type Finalization = (Property NoInfo, (FilePath -> [LoopDev] -> Property NoInfo)) --- | Specifies where to mount a partition.-mountedAt :: PartSpec -> FilePath -> PartSpec-mountedAt (_, p) mp = (Just mp, p)+imageFinalized :: Finalization -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> PartTable -> Property NoInfo+imageFinalized (_, final) mnts mntopts devs (PartTable _ parts) = +	property "disk image finalized" $ +		withTmpDir "mnt" $ \top -> +			go top `finally` liftIO (unmountall top)+  where+	go top = do+		liftIO $ mountall top+		liftIO $ writefstab top+		ensureProperty $ final top devs+	+	-- Ordered lexographically by mount point, so / comes before /usr+	-- comes before /usr/local+	orderedmntsdevs :: [(Maybe MountPoint, (MountOpts, LoopDev))]+	orderedmntsdevs = sortBy (compare `on` fst) $ zip mnts (zip mntopts devs)+	+	swaps = map (SwapPartition . partitionLoopDev . snd) $+		filter ((== LinuxSwap) . partFs . fst) $+			zip parts devs --- | Adds additional free space to the partition.-addFreeSpace :: PartSpec -> PartSize -> PartSpec-addFreeSpace (mp, p) freesz = (mp, \sz -> p (sz <> freesz))+	mountall top = forM_ orderedmntsdevs $ \(mp, (mopts, loopdev)) -> case mp of+		Nothing -> noop+		Just p -> do+			let mnt = top ++ p+			createDirectoryIfMissing True mnt+			unlessM (mount "auto" (partitionLoopDev loopdev) mnt mopts) $+				error $ "failed mounting " ++ mnt --- | Forced a partition to be a specific size, instead of scaling to the--- size needed for the files in the chroot.-setSize :: PartSpec -> PartSize -> PartSpec-setSize (mp, p) sz = (mp, const (p sz))+	unmountall top = do+		unmountBelow top+		umountLazy top+	+	writefstab top = do+		let fstab = top ++ "/etc/fstab"+		old <- catchDefaultIO [] $ filter (not . unconfigured) . lines+			<$> readFileStrict fstab+		new <- genFstab (map (top ++) (catMaybes mnts))+			swaps (toSysDir top)+		writeFile fstab $ unlines $ new ++ old+	-- Eg "UNCONFIGURED FSTAB FOR BASE SYSTEM"+	unconfigured s = "UNCONFIGURED" `isInfixOf` s --- | Sets a flag on the partition.-setFlag :: PartSpec -> PartFlag -> PartSpec-setFlag s f = adjustp s $ \p -> p { partFlags = (f, True):partFlags p }+noFinalization :: Finalization+noFinalization = (doNothing, \_ _ -> doNothing) --- | Makes a MSDOS partition be Extended, rather than Primary.-extended :: PartSpec -> PartSpec-extended s = adjustp s $ \p -> p { partType = Extended }+-- | Makes grub be the boot loader of the disk image.+grubBooted :: Grub.BIOS -> Finalization+grubBooted bios = (Grub.installed' bios, boots)+  where+	boots mnt loopdevs = combineProperties "disk image boots using grub"+		-- bind mount host /dev so grub can access the loop devices+		[ bindMount "/dev" (inmnt "/dev")+		, mounted "proc" "proc" (inmnt "/proc") mempty+		, mounted "sysfs" "sys" (inmnt "/sys") mempty+		-- update the initramfs so it gets the uuid of the root partition+		, inchroot "update-initramfs" ["-u"]+		-- work around for http://bugs.debian.org/802717+		 , check haveosprober $ inchroot "chmod" ["-x", osprober]+		, inchroot "update-grub" []+		, check haveosprober $ inchroot "chmod" ["+x", osprober]+		, inchroot "grub-install" [wholediskloopdev]+		-- sync all buffered changes out to the disk image+		-- may not be necessary, but seemed needed sometimes+		-- when using the disk image right away.+		, cmdProperty "sync" []+		]+	  where+	  	-- cannot use </> since the filepath is absolute+		inmnt f = mnt ++ f -adjustp :: PartSpec -> (Partition -> Partition) -> PartSpec-adjustp (mp, p) f = (mp, f . p)+		inchroot cmd ps = cmdProperty "chroot" ([mnt, cmd] ++ ps) --- | The constructor for each Partition is passed the size of the files--- from the chroot that will be put in that partition.-fitChrootSize :: TableType -> [PartSpec] -> [PartSize] -> ([MountPoint], PartTable)-fitChrootSize tt l basesizes = (mounts, parttable)-  where-	(mounts, sizers) = unzip l-	parttable = PartTable tt (zipWith id sizers basesizes)+		haveosprober = doesFileExist (inmnt osprober)+		osprober = "/etc/grub.d/30_os-prober" --- | A pair of properties. The first property is satisfied within the--- chroot, and is typically used to download the boot loader.--- The second property is satisfied chrooted into the resulting--- disk image, and will typically take care of installing the boot loader--- to the disk image.-type Finalization = (Property NoInfo, Property NoInfo)+		-- It doesn't matter which loopdev we use; all+		-- come from the same disk image, and it's the loop dev+		-- for the whole disk image we seek.+		wholediskloopdev = case loopdevs of+			(l:_) -> wholeDiskLoopDev l+			[] -> error "No loop devs provided!" --- | Makes grub be the boot loader of the disk image.--- TODO not implemented-grubBooted :: Grub.BIOS -> Finalization-grubBooted bios = (Grub.installed bios, undefined)+isChild :: FilePath -> Maybe MountPoint -> Bool+isChild mntpt (Just d)+	| d `equalFilePath` mntpt = False+	| otherwise = mntpt `dirContains` d+isChild _ Nothing = False -noFinalization :: Finalization-noFinalization = (doNothing, doNothing)+-- | From a location in a chroot (eg, /tmp/chroot/usr) to+-- the corresponding location inside (eg, /usr).+toSysDir :: FilePath -> FilePath -> FilePath+toSysDir chrootdir d = case makeRelative chrootdir d of+		"." -> "/"+		sysdir -> "/" ++ sysdir
+ src/Propellor/Property/DiskImage/PartSpec.hs view
@@ -0,0 +1,81 @@+-- | Disk image partition specification and combinators.++module Propellor.Property.DiskImage.PartSpec (+	module Propellor.Property.DiskImage.PartSpec,+	Partition,+	PartSize(..),+	PartFlag(..),+	TableType(..),+	Fs(..),+	MountPoint,+) where++import Propellor.Base+import Propellor.Property.Parted+import Propellor.Property.Mount++-- | Specifies a mount point, mount options, and a constructor for a Partition.+-- +-- The size that is eventually provided is the amount of space needed to +-- hold the files that appear in the directory where the partition is to be+-- mounted. Plus a fudge factor, since filesystems have some space+-- overhead.+type PartSpec = (Maybe MountPoint, MountOpts, PartSize -> Partition)++-- | Partitions that are not to be mounted (ie, LinuxSwap), or that have+-- no corresponding directory in the chroot will have 128 MegaBytes+-- provided as a default size.+defSz :: PartSize+defSz = MegaBytes 128++-- | Add 2% for filesystem overhead. Rationalle for picking 2%:+-- A filesystem with 1% overhead might just sneak by as acceptable.+-- Double that just in case. Add an additional 3 mb to deal with+-- non-scaling overhead of filesystems (eg, superblocks). +-- Add an additional 200 mb for temp files, journals, etc.+fudge :: PartSize -> PartSize+fudge (MegaBytes n) = MegaBytes (n + n `div` 100 * 2 + 3 + 200)++-- | Specifies a swap partition of a given size.+swapPartition :: PartSize -> PartSpec+swapPartition sz = (Nothing, mempty, const (mkPartition LinuxSwap sz))++-- | Specifies a partition with a given filesystem.+--+-- The partition is not mounted anywhere by default; use the combinators+-- below to configure it.+partition :: Fs -> PartSpec+partition fs = (Nothing, mempty, mkPartition fs)++-- | Specifies where to mount a partition.+mountedAt :: PartSpec -> FilePath -> PartSpec+mountedAt (_, o, p) mp = (Just mp, o, p)++-- | Specifies a mount option, such as "noexec"+mountOpt :: ToMountOpts o => PartSpec -> o -> PartSpec+mountOpt (mp, o, p) o' = (mp, o <> toMountOpts o', p)++-- | Mount option to make a partition be remounted readonly when there's an+-- error accessing it.+errorReadonly :: MountOpts+errorReadonly = toMountOpts "errors=remount-ro"++-- | Adds additional free space to the partition.+addFreeSpace :: PartSpec -> PartSize -> PartSpec+addFreeSpace (mp, o, p) freesz = (mp, o, \sz -> p (sz <> freesz))++-- | Forced a partition to be a specific size, instead of scaling to the+-- size needed for the files in the chroot.+setSize :: PartSpec -> PartSize -> PartSpec+setSize (mp, o, p) sz = (mp, o, const (p sz))++-- | Sets a flag on the partition.+setFlag :: PartSpec -> PartFlag -> PartSpec+setFlag s f = adjustp s $ \p -> p { partFlags = (f, True):partFlags p }++-- | Makes a MSDOS partition be Extended, rather than Primary.+extended :: PartSpec -> PartSpec+extended s = adjustp s $ \p -> p { partType = Extended }++adjustp :: PartSpec -> (Partition -> Partition) -> PartSpec+adjustp (mp, o, p) f = (mp, o, f . p)
src/Propellor/Property/File.hs view
@@ -70,6 +70,13 @@ lacksLines :: FilePath -> [Line] -> Property NoInfo f `lacksLines` ls = fileProperty (f ++ " remove: " ++ show [ls]) (filter (`notElem` ls)) f +-- | Replaces the content of a file with the transformed content of another file+basedOn :: FilePath -> (FilePath, [Line] -> [Line]) -> Property NoInfo+f `basedOn` (f', a) = property desc $ go =<< (liftIO $ readFile f')+  where+	desc = "replace " ++ f+	go tmpl = ensureProperty $ fileProperty desc (\_ -> a $ lines $ tmpl) f+ -- | Removes a file. Does not remove symlinks or non-plain-files. notPresent :: FilePath -> Property NoInfo notPresent f = check (doesFileExist f) $ property (f ++ " not present") $ 
src/Propellor/Property/Grub.hs view
@@ -18,14 +18,19 @@ -- | Installs the grub package. This does not make grub be used as the -- bootloader. ----- This includes running update-grub, so that the grub boot menu is--- created. It will be automatically updated when kernel packages are--- installed.+-- This includes running update-grub. installed :: BIOS -> Property NoInfo-installed bios = -	Apt.installed [pkg] `describe` "grub package installed"-		`before`-	cmdProperty "update-grub" []+installed bios = installed' bios `before` mkConfig++-- Run update-grub, to generate the grub boot menu. It will be+-- automatically updated when kernel packages are+--   -- installed.+mkConfig :: Property NoInfo+mkConfig = cmdProperty "update-grub" []++-- | Installs grub; does not run update-grub.+installed' :: BIOS -> Property NoInfo+installed' bios = Apt.installed [pkg] `describe` "grub package installed"   where 	pkg = case bios of 		PC -> "grub-pc"
src/Propellor/Property/Hostname.hs view
@@ -2,12 +2,17 @@  import Propellor.Base import qualified Propellor.Property.File as File+import Propellor.Property.Chroot (inChroot)  import Data.List+import Data.List.Utils --- | Ensures that the hostname is set using best practices.+-- | Ensures that the hostname is set using best practices, to whatever+-- name the `Host` has. ----- Configures </etc/hostname> and the current hostname.+-- Configures both </etc/hostname> and the current hostname.+-- (However, when used inside a chroot, avoids setting the current hostname+-- as that would impact the system outside the chroot.) -- -- Configures </etc/mailname> with the domain part of the hostname. --@@ -18,13 +23,23 @@ -- other hostnames there is not best practices and can lead to annoying -- messages from eg, apache. sane :: Property NoInfo-sane = property ("sane hostname") (ensureProperty . setTo =<< asks hostName)+sane = sane' extractDomain +sane' :: ExtractDomain -> Property NoInfo+sane' extractdomain = property ("sane hostname") $+	ensureProperty . setTo' extractdomain =<< asks hostName++-- Like `sane`, but you can specify the hostname to use, instead+-- of the default hostname of the `Host`. setTo :: HostName -> Property NoInfo-setTo hn = combineProperties desc go+setTo = setTo' extractDomain++setTo' :: ExtractDomain -> HostName -> Property NoInfo+setTo' extractdomain hn = combineProperties desc go   where 	desc = "hostname " ++ hn-	(basehost, domain) = separate (== '.') hn+	basehost = takeWhile (/= '.') hn+	domain = extractdomain hn  	go = catMaybes 		[ Just $ "/etc/hostname" `File.hasContent` [basehost]@@ -32,7 +47,8 @@ 			then Nothing  			else Just $ trivial $ hostsline "127.0.1.1" [hn, basehost] 		, Just $ trivial $ hostsline "127.0.0.1" ["localhost"]-		, Just $ trivial $ cmdProperty "hostname" [basehost]+		, Just $ trivial $ check (not <$> inChroot) $+			cmdProperty "hostname" [basehost] 		, Just $ "/etc/mailname" `File.hasContent` 			[if null domain then hn else domain] 		]@@ -47,11 +63,14 @@ -- | Makes </etc/resolv.conf> contain search and domain lines for  -- the domain that the hostname is in. searchDomain :: Property NoInfo-searchDomain = property desc (ensureProperty . go =<< asks hostName)+searchDomain = searchDomain' extractDomain++searchDomain' :: ExtractDomain -> Property NoInfo+searchDomain' extractdomain = property desc (ensureProperty . go =<< asks hostName)   where 	desc = "resolv.conf search and domain configured" 	go hn =-		let (_basehost, domain) = separate (== '.') hn+		let domain = extractdomain hn 		in  File.fileProperty desc (use domain) "/etc/resolv.conf" 	use domain ls = filter wanted $ nub (ls ++ cfgs) 	  where@@ -61,3 +80,21 @@ 			| "domain " `isPrefixOf` l = False 			| "search " `isPrefixOf` l = False 			| otherwise = True++-- | Function to extract the domain name from a HostName.+type ExtractDomain = HostName -> String++-- | hostname of foo.example.com has a domain of example.com.+-- But, when the hostname is example.com, the domain is+-- example.com too.+--+-- This doesn't work for eg, foo.co.uk, or when foo.sci.uni.edu+-- is in a sci.uni.edu subdomain. If you are in such a network,+-- provide your own ExtractDomain function to the properties above.+extractDomain :: ExtractDomain+extractDomain hn = +	let bits = split "." hn+	in intercalate "." $+		if length bits > 2+			then drop 1 bits+			else bits
src/Propellor/Property/Mount.hs view
@@ -1,29 +1,188 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances #-}+ module Propellor.Property.Mount where  import Propellor.Base+import qualified Propellor.Property.File as File import Utility.Path -type FsType = String -- ^ type of filesystem to mount ("auto" to autodetect)+import Data.Char+import Data.List+import Utility.Table +-- | type of filesystem to mount ("auto" to autodetect)+type FsType = String++-- | A device or other thing to be mounted. type Source = String +-- | A mount point for a filesystem.+type MountPoint = FilePath++-- | Filesystem mount options. Eg, "errors=remount-ro"+newtype MountOpts = MountOpts [String]+	deriving Monoid++class ToMountOpts a where+	toMountOpts :: a -> MountOpts+	+instance ToMountOpts MountOpts where+	toMountOpts = id++instance ToMountOpts String where+	toMountOpts s = MountOpts [s]++formatMountOpts :: MountOpts -> String+formatMountOpts (MountOpts []) = "defaults"+formatMountOpts (MountOpts l) = intercalate "," l++-- | Mounts a device.+mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property NoInfo+mounted fs src mnt opts = property (mnt ++ " mounted") $ +	toResult <$> liftIO (mount fs src mnt opts)++-- | Bind mounts the first directory so its contents also appear+-- in the second directory.+bindMount :: FilePath -> FilePath -> Property NoInfo+bindMount src dest = cmdProperty "mount" ["--bind", src, dest]+	`describe` ("bind mounted " ++ src ++ " to " ++ dest)++mount :: FsType -> Source -> MountPoint -> MountOpts -> IO Bool+mount fs src mnt opts = boolSystem "mount" $+	[ Param "-t", Param fs+	, Param "-o", Param (formatMountOpts opts)+	, Param src+	, Param mnt+	]++newtype SwapPartition = SwapPartition FilePath++-- | Replaces </etc/fstab> with a file that should cause the currently+-- mounted partitions to be re-mounted the same way on boot.+--+-- For each specified MountPoint, the UUID of each partition+-- (or if there is no UUID, its label), its filesystem type,+-- and its mount options are all automatically probed.+--+-- The SwapPartitions are also included in the generated fstab.+fstabbed :: [MountPoint] -> [SwapPartition] -> Property NoInfo+fstabbed mnts swaps = property "fstabbed" $ do+	fstab <- liftIO $ genFstab mnts swaps id+	ensureProperty $ +		"/etc/fstab" `File.hasContent` fstab++genFstab :: [MountPoint] -> [SwapPartition] -> (MountPoint -> MountPoint) -> IO [String]+genFstab mnts swaps mnttransform = do+	fstab <- liftIO $ mapM getcfg (sort mnts)+	swapfstab <- liftIO $ mapM getswapcfg swaps+	return $ header ++ formatTable (legend : fstab ++ swapfstab)+  where+	header =+		[ "# /etc/fstab: static file system information. See fstab(5)"+		, "# "+		]+	legend = ["# <file system>", "<mount point>", "<type>", "<options>", "<dump>", "<pass>"]+	getcfg mnt = sequence+		[ fromMaybe (error $ "unable to find mount source for " ++ mnt)+			<$> getM (\a -> a mnt)+				[ uuidprefix getMountUUID+				, sourceprefix getMountLabel+				, getMountSource+				]+		, pure (mnttransform mnt)+		, fromMaybe "auto" <$> getFsType mnt+		, formatMountOpts <$> getFsMountOpts mnt+		, pure "0"+		, pure (if mnt == "/" then "1" else "2")+		]+	getswapcfg (SwapPartition swap) = sequence+		[ fromMaybe swap <$> getM (\a -> a swap)+			[ uuidprefix getSourceUUID+			, sourceprefix getSourceLabel+			]+		, pure "none"+		, pure "swap"+		, pure (formatMountOpts mempty)+		, pure "0"+		, pure "0"+		]+	prefix s getter m = fmap (s ++) <$> getter m+	uuidprefix = prefix "UUID="+	sourceprefix = prefix "LABEL="++-- | Checks if </etc/fstab> is not configured. +-- This is the case if it doesn't exist, or+-- consists entirely of blank lines or comments.+--+-- So, if you want to only replace the fstab once, and then never touch it+-- again, allowing local modifications:+--+-- > check noFstab (fstabbed mnts [])+noFstab :: IO Bool+noFstab = ifM (doesFileExist "/etc/fstab")+	( null . filter iscfg . lines <$> readFile "/etc/fstab"+	, return True+	)+  where+	iscfg l+		| null l = False+		| otherwise = not $ "#" `isPrefixOf` dropWhile isSpace l+	 -- | Lists all mount points of the system.-mountPoints :: IO [FilePath]+mountPoints :: IO [MountPoint] mountPoints = lines <$> readProcess "findmnt" ["-rn", "--output", "target"]  -- | Finds all filesystems mounted inside the specified directory.-mountPointsBelow :: FilePath -> IO [FilePath]+mountPointsBelow :: FilePath -> IO [MountPoint] mountPointsBelow target = filter (\p -> simplifyPath p /= simplifyPath target) 	. filter (dirContains target) 	<$> mountPoints  -- | Filesystem type mounted at a given location.-getFsType :: FilePath -> IO (Maybe FsType)-getFsType mnt = catchDefaultIO Nothing $-	headMaybe . lines-		<$> readProcess "findmnt" ["-n", mnt, "--output", "fstype"]+getFsType :: MountPoint -> IO (Maybe FsType)+getFsType = findmntField "fstype" --- | Unmounts a device, lazily so any running processes don't block it.+-- | Mount options for the filesystem mounted at a given location.+getFsMountOpts :: MountPoint -> IO MountOpts+getFsMountOpts p = maybe mempty toMountOpts+	<$> findmntField "fs-options" p++type UUID = String++-- | UUID of filesystem mounted at a given location.+getMountUUID :: MountPoint -> IO (Maybe UUID)+getMountUUID = findmntField "uuid"++-- | UUID of a device+getSourceUUID :: Source -> IO (Maybe UUID)+getSourceUUID = blkidTag "UUID"++type Label = String++-- | Label of filesystem mounted at a given location.+getMountLabel :: MountPoint -> IO (Maybe Label)+getMountLabel = findmntField "label"++-- | Label of a device+getSourceLabel :: Source -> IO (Maybe UUID)+getSourceLabel = blkidTag "LABEL"++-- | Device mounted at a given location.+getMountSource :: MountPoint -> IO (Maybe Source)+getMountSource = findmntField "source"++findmntField :: String -> FilePath -> IO (Maybe String)+findmntField field mnt = catchDefaultIO Nothing $+	headMaybe . filter (not . null) . lines+		<$> readProcess "findmnt" ["-n", mnt, "--output", field]++blkidTag :: String -> Source -> IO (Maybe String)+blkidTag tag dev = catchDefaultIO Nothing $+	headMaybe . filter (not . null) . lines+		<$> readProcess "blkid" [dev, "-s", tag, "-o", "value"]++-- | Unmounts a device or mountpoint,+-- lazily so any running processes don't block it. umountLazy :: FilePath -> IO () umountLazy mnt =   	unlessM (boolSystem "umount" [ Param "-l", Param mnt ]) $@@ -34,7 +193,3 @@ unmountBelow d = do 	submnts <- mountPointsBelow d 	forM_ submnts umountLazy---- | Mounts a device.-mount :: FsType -> Source -> FilePath -> IO Bool-mount fs src mnt = boolSystem "mount" [Param "-t", Param fs, Param src, Param mnt]
src/Propellor/Property/OS.hs view
@@ -123,16 +123,16 @@  		-- Remount /dev, so that block devices etc are 		-- available for other properties to use.-		unlessM (mount devfstype devfstype "/dev") $ do+		unlessM (mount devfstype devfstype "/dev" mempty) $ do 			warningMessage $ "failed mounting /dev using " ++ devfstype ++ "; falling back to MAKEDEV generic" 			void $ boolSystem "sh" [Param "-c", Param "cd /dev && /sbin/MAKEDEV generic"]  		-- Mount /sys too, needed by eg, grub-mkconfig.-		unlessM (mount "sysfs" "sysfs" "/sys") $+		unlessM (mount "sysfs" "sysfs" "/sys" mempty) $ 			warningMessage "failed mounting /sys"  		-- And /dev/pts, used by apt.-		unlessM (mount "devpts" "devpts" "/dev/pts") $+		unlessM (mount "devpts" "devpts" "/dev/pts" mempty) $ 			warningMessage "failed mounting /dev/pts"  		return MadeChange
src/Propellor/Property/Parted.hs view
@@ -160,7 +160,7 @@ 		[ parted eep disk partedparams 		, if isdev 			then formatl (map (\n -> disk ++ show n) [1 :: Int ..])-			else Partition.kpartx disk formatl+			else Partition.kpartx disk (formatl . map Partition.partitionLoopDev) 		]   where 	desc = disk ++ " partitioned"
src/Propellor/Property/Partition.hs view
@@ -4,10 +4,14 @@  import Propellor.Base import qualified Propellor.Property.Apt as Apt+import Utility.Applicative +import System.Posix.Files+import Data.List+ -- | Filesystems etc that can be used for a partition. data Fs = EXT2 | EXT3 | EXT4 | BTRFS | REISERFS | XFS | FAT | VFAT | NTFS | LinuxSwap-	deriving (Show)+	deriving (Show, Eq)  data Eep = YesReallyFormatPartition @@ -41,20 +45,44 @@ 	-- Be quiet. 	q l = "-q":l +data LoopDev = LoopDev+	{ partitionLoopDev :: FilePath -- ^ device for a loop partition+	, wholeDiskLoopDev :: FilePath -- ^ corresponding device for the whole loop disk+	} deriving (Show)++isLoopDev :: LoopDev -> IO Bool+isLoopDev l = isLoopDev' (partitionLoopDev l) <&&> isLoopDev' (wholeDiskLoopDev l)++isLoopDev' :: FilePath -> IO Bool+isLoopDev' f+	| "loop" `isInfixOf` f = catchBoolIO $+		isBlockDevice <$> getFileStatus f+	| otherwise = return False+ -- | Uses the kpartx utility to create device maps for partitions contained--- within a disk image file. The resulting devices are passed to the+-- within a disk image file. The resulting loop devices are passed to the -- property, which can operate on them. Always cleans up after itself, -- by removing the device maps after the property is run.-kpartx :: FilePath -> ([FilePath] -> Property NoInfo) -> Property NoInfo+kpartx :: FilePath -> ([LoopDev] -> Property NoInfo) -> Property NoInfo kpartx diskimage mkprop = go `requires` Apt.installed ["kpartx"]   where 	go = property (propertyDesc (mkprop [])) $ do 		cleanup -- idempotency-		s <- liftIO $ readProcess "kpartx" ["-avs", diskimage]-		r <- ensureProperty (mkprop (devlist s))+		loopdevs <- liftIO $ kpartxParse+			<$> readProcess "kpartx" ["-avs", diskimage]+		bad <- liftIO $ filterM (not <$$> isLoopDev) loopdevs+		unless (null bad) $+			error $ "kpartx output seems to include non-loop-devices (possible parse failure): " ++ show bad+		r <- ensureProperty (mkprop loopdevs) 		cleanup 		return r-	devlist = mapMaybe (finddev . words) . lines-	finddev ("add":"map":s:_) = Just ("/dev/mapper/" ++ s)-	finddev _ = Nothing 	cleanup = void $ liftIO $ boolSystem "kpartx" [Param "-d", File diskimage]++kpartxParse :: String -> [LoopDev]+kpartxParse = mapMaybe (finddev . words) . lines+  where+	finddev ("add":"map":ld:_:_:_:_:wd:_) = Just $ LoopDev +		{ partitionLoopDev = "/dev/mapper/" ++ ld+		, wholeDiskLoopDev = wd+		}+	finddev _ = Nothing
src/Propellor/Property/Rsync.hs view
@@ -22,10 +22,12 @@ data Filter  	= Include Pattern 	| Exclude Pattern+	| Protect Pattern  instance RsyncParam Filter where 	toRsync (Include (Pattern p)) = "--include=" ++ p 	toRsync (Exclude (Pattern p)) = "--exclude=" ++ p+	toRsync (Protect (Pattern p)) = "--filter=P " ++ p  -- | A pattern to match against files that rsync is going to transfer. --
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -99,13 +99,12 @@  autoBuilderContainer :: (System -> Flavor -> Property HasInfo) -> System -> Flavor -> Times -> TimeOut -> Systemd.Container autoBuilderContainer mkprop osver@(System _ arch) flavor crontime timeout =-	Systemd.container name bootstrap+	Systemd.container name osver (Chroot.debootstrapped mempty) 		& mkprop osver flavor 		& buildDepsApt 		& autobuilder arch crontime timeout   where 	name = arch ++ fromMaybe "" flavor ++ "-git-annex-builder"-	bootstrap = Chroot.debootstrapped osver mempty  type Flavor = Maybe String @@ -144,8 +143,7 @@ 	-> Property i 	-> FilePath 	-> Systemd.Container-androidContainer name setupgitannexdir gitannexdir = Systemd.container name bootstrap-	& os osver+androidContainer name setupgitannexdir gitannexdir = Systemd.container name osver bootstrap 	& Apt.stdSourcesList 	& User.accountFor (User builduser) 	& File.dirExists gitbuilderdir@@ -161,4 +159,4 @@ 		[ "cd " ++ gitannexdir ++ " && ./standalone/android/buildchroot-inchroot" 		] 	osver = System (Debian (Stable "jessie")) "i386"-	bootstrap = Chroot.debootstrapped osver mempty+	bootstrap = Chroot.debootstrapped mempty
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -15,6 +15,7 @@ import qualified Propellor.Property.Obnam as Obnam import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.Postfix as Postfix+import qualified Propellor.Property.Systemd as Systemd import Utility.FileMode  import Data.List@@ -346,6 +347,7 @@ 		`onChange` Service.restarted "rsync" 	& "/etc/default/rsync" `File.containsLine` "RSYNC_ENABLE=true" 		`onChange` Service.running "rsync"+	& Systemd.enabled "rsync" 	& endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild" 	& endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/x86_64-apple-yosemite" 	& endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/windows"
src/Propellor/Property/Ssh.hs view
@@ -4,6 +4,7 @@ 	installed, 	restarted, 	PubKeyText,+	SshKeyType(..), 	-- * Daemon configuration 	sshdConfig, 	ConfigKeyword,
src/Propellor/Property/Systemd.hs view
@@ -174,21 +174,22 @@ 					Apt.installed ["systemd-container"] 			_ -> noChange --- | Defines a container with a given machine name.+-- | Defines a container with a given machine name, and operating system,+-- and how to create its chroot if not already present. -- -- Properties can be added to configure the Container. ----- > container "webserver" (Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty)+-- > container "webserver" (System (Debian Unstable) "amd64") (Chroot.debootstrapped mempty) -- >    & Apt.installedRunning "apache2" -- >    & ...-container :: MachineName -> (FilePath -> Chroot.Chroot) -> Container-container name mkchroot = Container name c h+container :: MachineName -> System -> (FilePath -> Chroot.Chroot) -> Container+container name system mkchroot = Container name c h 	& os system 	& resolvConfed 	& linkJournal   where 	c = mkchroot (containerDir name)-	system = Chroot.chrootSystem c+		& os system 	h = Host name [] mempty  -- | Runs a container using systemd-nspawn.@@ -206,7 +207,7 @@ -- Reverting this property stops the container, removes the systemd unit, -- and deletes the chroot and all its contents. nspawned :: Container -> RevertableProperty-nspawned c@(Container name (Chroot.Chroot loc system builder _) h) =+nspawned c@(Container name (Chroot.Chroot loc builder _) h) = 	p `describe` ("nspawned " ++ name)   where 	p = enterScript c@@ -216,7 +217,7 @@  	-- Chroot provisioning is run in systemd-only mode, 	-- which sets up the chroot and ensures systemd and dbus are-	-- installed, but does not handle the other provisions.+	-- installed, but does not handle the other properties. 	chrootprovisioned = Chroot.provisioned' (Chroot.propagateChrootInfo chroot) chroot True  	-- Use nsenter to enter container and and run propellor to@@ -226,7 +227,7 @@ 			<!> 		doNothing -	chroot = Chroot.Chroot loc system builder h+	chroot = Chroot.Chroot loc builder h  -- | Sets up the service file for the container, and then starts -- it running.@@ -382,7 +383,8 @@ -- > 		`requires` Systemd.running Systemd.networkd -- > -- > webserver :: Systemd.container--- > webserver = Systemd.container "webserver" (Chroot.debootstrapped (System (Debian Testing) "amd64") mempty)+-- > webserver = Systemd.container "webserver" (Chroot.debootstrapped mempty)+-- >	& os (System (Debian Testing) "amd64") -- >	& Systemd.privateNetwork -- >	& Systemd.running Systemd.networkd -- >	& Systemd.publish (Port 80 ->- Port 8080)
src/Propellor/Property/User.hs view
@@ -58,14 +58,21 @@ setPassword :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result setPassword getpassword = getpassword $ go   where-	go (Password user, password) = set user (privDataVal password) []-	go (CryptPassword user, hash) = set user (privDataVal hash) ["--encrypted"]+	go (Password user, password) = chpasswd (User user) (privDataVal password) []+	go (CryptPassword user, hash) = chpasswd (User user) (privDataVal hash) ["--encrypted"] 	go (f, _) = error $ "Unexpected type of privdata: " ++ show f -	set user v ps = makeChange $ withHandle StdinHandle createProcessSuccess-		(proc "chpasswd" ps) $ \h -> do-			hPutStrLn h $ user ++ ":" ++ v-			hClose h+-- | Makes a user's password be the passed String. Highly insecure:+-- The password is right there in your config file for anyone to see!+hasInsecurePassword :: User -> String -> Property NoInfo+hasInsecurePassword u@(User n) p = property (n ++ " has insecure password") $+	chpasswd u p []++chpasswd :: User -> String -> [String] -> Propellor Result+chpasswd (User user) v ps = makeChange $ withHandle StdinHandle createProcessSuccess+	(proc "chpasswd" ps) $ \h -> do+		hPutStrLn h $ user ++ ":" ++ v+		hClose h  lockedPassword :: User -> Property NoInfo lockedPassword user@(User u) = check (not <$> isLockedPassword user) $ cmdProperty "passwd"
src/Propellor/Types.hs view
@@ -29,6 +29,7 @@ 	, CombinedType 	, combineWith 	, Propellor(..)+	, LiftPropellor(..) 	, EndAction(..) 	, module Propellor.Types.OS 	, module Propellor.Types.Dns@@ -71,6 +72,15 @@ 		, MonadThrow 		, MonadMask 		)++class LiftPropellor m where+	liftPropellor :: m a -> Propellor a++instance LiftPropellor Propellor where+	liftPropellor = id++instance LiftPropellor IO where+	liftPropellor = liftIO  instance Monoid (Propellor Result) where 	mempty = return NoChange